@bigsteele/the-prospect 0.2.0 → 0.3.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/README.md +40 -17
- package/dist/check.js +3 -2
- package/dist/cli.js +93 -27
- package/dist/detect/costs.js +48 -6
- package/dist/detect/database.js +30 -5
- package/dist/detect/deadweight.js +60 -15
- package/dist/detect/handrolled.js +23 -14
- package/dist/detect/stack.d.ts +2 -0
- package/dist/detect/types.d.ts +33 -0
- package/dist/detect/vendors.js +95 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +24 -1
- package/dist/report.js +28 -9
- package/dist/score.js +9 -5
- package/dist/verdicts.d.ts +80 -0
- package/dist/verdicts.js +144 -0
- package/package.json +1 -1
- package/prompt/THE-PROSPECT.md +162 -118
- package/dist/detect/deps 2.d.ts +0 -25
- package/dist/detect/deps 2.js +0 -198
- package/dist/detect/types 2.d.ts +0 -106
- package/dist/detect/types 2.js +0 -11
- package/dist/walk 2.d.ts +0 -44
- package/dist/walk 2.js +0 -123
package/dist/detect/types 2.d.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The fact shapes. Family rule: a field is either read from the repository or
|
|
3
|
-
* absent. Nothing in these types is an opinion; opinions belong to the agent
|
|
4
|
-
* protocol, and every one it forms must cite a field from here.
|
|
5
|
-
*
|
|
6
|
-
* The one register rule encoded structurally: findings carry the honest verb.
|
|
7
|
-
* A dependency is not "unused", it has "no reference found" - the difference
|
|
8
|
-
* is a CLI-only tool that ships to production anyway versus a lie in a report
|
|
9
|
-
* a founder pays attention to.
|
|
10
|
-
*/
|
|
11
|
-
/** One declared dependency and every place it was actually seen. */
|
|
12
|
-
export interface DepFact {
|
|
13
|
-
name: string;
|
|
14
|
-
version: string;
|
|
15
|
-
/** Which manifest declared it, repository-relative. */
|
|
16
|
-
manifest: string;
|
|
17
|
-
dev: boolean;
|
|
18
|
-
/** Files whose import/require statements name this package. */
|
|
19
|
-
imported_by: number;
|
|
20
|
-
/** Config files that name it as a string (tailwind plugins, postcss, eslint). */
|
|
21
|
-
config_mentions: number;
|
|
22
|
-
/**
|
|
23
|
-
* Why it counts as used without an import or config mention, when that is the
|
|
24
|
-
* case: "peer of next", "npm script: email dev", "JSX runtime", "native build
|
|
25
|
-
* (Capacitor)".
|
|
26
|
-
*/
|
|
27
|
-
required_by?: string;
|
|
28
|
-
/** True when no import, config mention, script, or package requiring it was found. */
|
|
29
|
-
no_reference_found: boolean;
|
|
30
|
-
}
|
|
31
|
-
export type VendorCategory = "ai" | "email" | "sms" | "payments" | "database" | "auth" | "storage" | "crm" | "analytics" | "monitoring" | "render" | "search" | "queue" | "maps" | "calendar" | "other";
|
|
32
|
-
/** One external service the code talks to, with the evidence. */
|
|
33
|
-
export interface VendorFact {
|
|
34
|
-
service: string;
|
|
35
|
-
category: VendorCategory;
|
|
36
|
-
/** How it was seen: an SDK import, an outbound URL, an env var NAME. Values never. */
|
|
37
|
-
evidence: Array<{
|
|
38
|
-
kind: "sdk" | "url" | "env";
|
|
39
|
-
what: string;
|
|
40
|
-
file: string;
|
|
41
|
-
}>;
|
|
42
|
-
call_sites: number;
|
|
43
|
-
}
|
|
44
|
-
/** Two or more services doing the same category of work. */
|
|
45
|
-
export interface OverlapFact {
|
|
46
|
-
category: VendorCategory;
|
|
47
|
-
services: string[];
|
|
48
|
-
call_sites: number;
|
|
49
|
-
}
|
|
50
|
-
export type RailCategory = "pdf" | "rate-limiting" | "email-templating" | "auth-session" | "queue-scheduler" | "search" | "payments-logic" | "webhook-plumbing" | "parsing-ocr";
|
|
51
|
-
/** A subsystem built by hand where the market sells a rail. */
|
|
52
|
-
export interface HandrolledFact {
|
|
53
|
-
rail: RailCategory;
|
|
54
|
-
files: string[];
|
|
55
|
-
loc: number;
|
|
56
|
-
/** high: the file's name and its contents agree. low: contents only. */
|
|
57
|
-
confidence: "high" | "low";
|
|
58
|
-
/** The one line of evidence that convinced the detector, with its file. */
|
|
59
|
-
signal: {
|
|
60
|
-
file: string;
|
|
61
|
-
line: string;
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
/** A cluster of near-identical code living in more than one file. */
|
|
65
|
-
export interface DuplicateFact {
|
|
66
|
-
files: string[];
|
|
67
|
-
/** Lines in the repeated block, after normalization. */
|
|
68
|
-
lines: number;
|
|
69
|
-
/** A header comment says the copy is deliberate ("copied from", "never imported"). */
|
|
70
|
-
deliberate: boolean;
|
|
71
|
-
/** First normalized line of the block, so a reader can find it. */
|
|
72
|
-
opens_with: string;
|
|
73
|
-
}
|
|
74
|
-
/** A runtime file no entrypoint reaches. */
|
|
75
|
-
export interface DeadFact {
|
|
76
|
-
file: string;
|
|
77
|
-
loc: number;
|
|
78
|
-
/** Why the detector believes nothing reaches it. */
|
|
79
|
-
note: string;
|
|
80
|
-
}
|
|
81
|
-
/** A call whose cost multiplies: per request, per row, or on a clock. */
|
|
82
|
-
export interface CostFact {
|
|
83
|
-
file: string;
|
|
84
|
-
shape: "per-request" | "per-row" | "per-schedule";
|
|
85
|
-
/** What is being called - a vendor host or an SDK call name. */
|
|
86
|
-
target: string;
|
|
87
|
-
/** The line that shows the shape (the loop, the handler, the interval). */
|
|
88
|
-
line: string;
|
|
89
|
-
}
|
|
90
|
-
/** Vocabulary the repository uses about its own domain, ranked by weight.
|
|
91
|
-
* The CLI never names the industry; it hands the agent the evidence. */
|
|
92
|
-
export interface Fingerprint {
|
|
93
|
-
terms: Array<{
|
|
94
|
-
term: string;
|
|
95
|
-
count: number;
|
|
96
|
-
sources: string[];
|
|
97
|
-
}>;
|
|
98
|
-
/** Where the terms came from: tables, routes, copy, manifest. */
|
|
99
|
-
note: string;
|
|
100
|
-
}
|
|
101
|
-
export interface NorthStar {
|
|
102
|
-
sentence: string | null;
|
|
103
|
-
source: "NORTH-STAR.md" | "planning" | "PRODUCT.md" | "heuristic" | "none";
|
|
104
|
-
confidence: "high" | "low" | "unknown";
|
|
105
|
-
note: string;
|
|
106
|
-
}
|
package/dist/detect/types 2.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The fact shapes. Family rule: a field is either read from the repository or
|
|
3
|
-
* absent. Nothing in these types is an opinion; opinions belong to the agent
|
|
4
|
-
* protocol, and every one it forms must cite a field from here.
|
|
5
|
-
*
|
|
6
|
-
* The one register rule encoded structurally: findings carry the honest verb.
|
|
7
|
-
* A dependency is not "unused", it has "no reference found" - the difference
|
|
8
|
-
* is a CLI-only tool that ships to production anyway versus a lie in a report
|
|
9
|
-
* a founder pays attention to.
|
|
10
|
-
*/
|
|
11
|
-
export {};
|
package/dist/walk 2.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export interface Repo {
|
|
2
|
-
root: string;
|
|
3
|
-
name: string;
|
|
4
|
-
/** Every file considered, repository-relative with forward slashes. */
|
|
5
|
-
files: string[];
|
|
6
|
-
/** Text of a file, or null when it is not text, too large, an env file, or unreadable. */
|
|
7
|
-
read(rel: string): Promise<string | null>;
|
|
8
|
-
/** Files whose path matches. */
|
|
9
|
-
matching(re: RegExp): string[];
|
|
10
|
-
/** Directories skipped because they are a repository of their own or a copy of this one. */
|
|
11
|
-
skipped: string[];
|
|
12
|
-
/**
|
|
13
|
-
* The manifest of an INSTALLED package, looked up the way Node resolves it: from
|
|
14
|
-
* the directory of the manifest that declares it, up to the root. Null when it is
|
|
15
|
-
* not installed. node_modules is never walked; this reads one file on request.
|
|
16
|
-
*/
|
|
17
|
-
installed(fromManifest: string, pkg: string): Promise<InstalledManifest | null>;
|
|
18
|
-
}
|
|
19
|
-
export interface InstalledManifest {
|
|
20
|
-
bin?: string | Record<string, string>;
|
|
21
|
-
peerDependencies?: Record<string, string>;
|
|
22
|
-
peerDependenciesMeta?: Record<string, {
|
|
23
|
-
optional?: boolean;
|
|
24
|
-
}>;
|
|
25
|
-
}
|
|
26
|
-
export declare function openRepo(root: string): Promise<Repo>;
|
|
27
|
-
/** Files whose text matches; each hit carries the count of matches. Reads at most `limit` files. */
|
|
28
|
-
export declare function grep(repo: Repo, files: string[], re: RegExp, limit?: number): Promise<Array<{
|
|
29
|
-
file: string;
|
|
30
|
-
count: number;
|
|
31
|
-
}>>;
|
|
32
|
-
/** Code files only: what the detectors read for behaviour. */
|
|
33
|
-
export declare const CODE: RegExp;
|
|
34
|
-
/** Test files, which describe behaviour but do not run in production. */
|
|
35
|
-
export declare const TEST_FILE: RegExp;
|
|
36
|
-
/**
|
|
37
|
-
* Not the running software: documentation trees, fixtures, golden files, and this
|
|
38
|
-
* auditor's own package when it is audited from inside the repository that holds it.
|
|
39
|
-
* The first run counted the auditor's detector source as an MCP server, a Bedrock
|
|
40
|
-
* integration and a human gate in the host repository.
|
|
41
|
-
*/
|
|
42
|
-
export declare const NOT_RUNTIME: RegExp;
|
|
43
|
-
/** Files the detectors read for behaviour: code, in the runtime tree, not tests. */
|
|
44
|
-
export declare function runtimeCode(files: string[]): string[];
|
package/dist/walk 2.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
// Copied from ai-audit/src/walk.ts, never imported, so the package stays independently
|
|
2
|
-
// deployable (family convention). The only place that touches the filesystem. Walks a repository, skips what is not the
|
|
3
|
-
// project's own code, reads text files under a size cap, and never opens an env file.
|
|
4
|
-
import { readdir, readFile, stat } from "node:fs/promises";
|
|
5
|
-
import { join, relative, sep } from "node:path";
|
|
6
|
-
const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "out", ".next", ".nuxt", ".svelte-kit", ".vercel", ".turbo", "coverage", "vendor", "dist.bak", ".cache", "__pycache__", ".venv", "venv", "target", "test-results", "playwright-report", ".chrome-debug", ".claude-browser"]);
|
|
7
|
-
// Stylesheets are read too: `@import "tw-animate-css"` and Tailwind 4's `@plugin`
|
|
8
|
-
// are how a whole class of packages is used, and a walker that listed .css files
|
|
9
|
-
// but never read them flagged every one of those packages as unreferenced.
|
|
10
|
-
const TEXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|rb|php|java|kt|swift|sql|json|jsonc|ya?ml|toml|md|mdx|txt|prisma|graphql|gql|env\.example|sh|css|scss|sass|less|pcss)$/i;
|
|
11
|
-
const ENV_FILE = /(^|\/)\.env(\.[a-z0-9_-]+)?$/i;
|
|
12
|
-
const MAX_BYTES = 512 * 1024;
|
|
13
|
-
const MAX_FILES = 25_000;
|
|
14
|
-
export async function openRepo(root) {
|
|
15
|
-
const files = [];
|
|
16
|
-
const skipped = [];
|
|
17
|
-
const rootName = root.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? "";
|
|
18
|
-
const walk = async (dir, depth) => {
|
|
19
|
-
let entries;
|
|
20
|
-
try {
|
|
21
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const names = new Set(entries.map((e) => e.name));
|
|
27
|
-
// A directory below the root that is a repository of its own (its own .git), or a
|
|
28
|
-
// copy of this one (named like the root, with its own manifest), is not this
|
|
29
|
-
// software: Brokrr's audit counted a nested Brokrr/ twice and cited both.
|
|
30
|
-
if (depth > 0 && (names.has(".git") || (dir.split(/[\\/]/).pop() === rootName && names.has("package.json")))) {
|
|
31
|
-
skipped.push(relative(root, dir).split(sep).join("/"));
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
35
|
-
if (files.length >= MAX_FILES)
|
|
36
|
-
return;
|
|
37
|
-
const abs = join(dir, e.name);
|
|
38
|
-
if (e.isSymbolicLink())
|
|
39
|
-
continue;
|
|
40
|
-
if (e.isDirectory()) {
|
|
41
|
-
// dist-demo, build_old, out-web: build output under any suffix.
|
|
42
|
-
if (SKIP_DIRS.has(e.name) || /^(dist|build|out)([-_.][a-z0-9-]*)?$/i.test(e.name))
|
|
43
|
-
continue;
|
|
44
|
-
await walk(abs, depth + 1);
|
|
45
|
-
}
|
|
46
|
-
else if (e.isFile()) {
|
|
47
|
-
files.push(relative(root, abs).split(sep).join("/"));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
await walk(root, 0);
|
|
52
|
-
const cache = new Map();
|
|
53
|
-
const name = root.replace(/[\\/]+$/, "").split(/[\\/]/).pop() ?? root;
|
|
54
|
-
return {
|
|
55
|
-
root,
|
|
56
|
-
name,
|
|
57
|
-
files,
|
|
58
|
-
skipped,
|
|
59
|
-
async read(rel) {
|
|
60
|
-
if (cache.has(rel))
|
|
61
|
-
return cache.get(rel);
|
|
62
|
-
let text = null;
|
|
63
|
-
if (TEXT.test(rel) && !ENV_FILE.test(rel)) {
|
|
64
|
-
try {
|
|
65
|
-
const s = await stat(join(root, rel));
|
|
66
|
-
if (s.size <= MAX_BYTES)
|
|
67
|
-
text = await readFile(join(root, rel), "utf8");
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
text = null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
cache.set(rel, text);
|
|
74
|
-
return text;
|
|
75
|
-
},
|
|
76
|
-
matching(re) {
|
|
77
|
-
return files.filter((f) => re.test(f));
|
|
78
|
-
},
|
|
79
|
-
async installed(fromManifest, pkg) {
|
|
80
|
-
const parts = fromManifest.split("/").slice(0, -1);
|
|
81
|
-
for (let i = parts.length; i >= 0; i--) {
|
|
82
|
-
const abs = join(root, ...parts.slice(0, i), "node_modules", ...pkg.split("/"), "package.json");
|
|
83
|
-
try {
|
|
84
|
-
return JSON.parse(await readFile(abs, "utf8"));
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
// not installed at this level
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return null;
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
/** Files whose text matches; each hit carries the count of matches. Reads at most `limit` files. */
|
|
95
|
-
export async function grep(repo, files, re, limit = 4000) {
|
|
96
|
-
const out = [];
|
|
97
|
-
const flags = re.flags.includes("g") ? re.flags : re.flags + "g";
|
|
98
|
-
const global = new RegExp(re.source, flags);
|
|
99
|
-
for (const f of files.slice(0, limit)) {
|
|
100
|
-
const text = await repo.read(f);
|
|
101
|
-
if (!text)
|
|
102
|
-
continue;
|
|
103
|
-
const count = (text.match(global) ?? []).length;
|
|
104
|
-
if (count > 0)
|
|
105
|
-
out.push({ file: f, count });
|
|
106
|
-
}
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
/** Code files only: what the detectors read for behaviour. */
|
|
110
|
-
export const CODE = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|rb|php|java|kt|swift)$/i;
|
|
111
|
-
/** Test files, which describe behaviour but do not run in production. */
|
|
112
|
-
export const TEST_FILE = /(^|\.|_|\/)(test|spec|e2e)s?(\.|\/)|(^|\/)__tests__\//i;
|
|
113
|
-
/**
|
|
114
|
-
* Not the running software: documentation trees, fixtures, golden files, and this
|
|
115
|
-
* auditor's own package when it is audited from inside the repository that holds it.
|
|
116
|
-
* The first run counted the auditor's detector source as an MCP server, a Bedrock
|
|
117
|
-
* integration and a human gate in the host repository.
|
|
118
|
-
*/
|
|
119
|
-
export const NOT_RUNTIME = /(^|\/)(docs?|fixtures?|__fixtures__|__mocks__|golden|examples?|samples?|packages\/the-prospect)\/|(^|\/)\.(?!well-known\/)[^/]+\//i;
|
|
120
|
-
/** Files the detectors read for behaviour: code, in the runtime tree, not tests. */
|
|
121
|
-
export function runtimeCode(files) {
|
|
122
|
-
return files.filter((f) => CODE.test(f) && !TEST_FILE.test(f) && !NOT_RUNTIME.test(f));
|
|
123
|
-
}
|