archcheck 0.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/LICENSE +21 -0
- package/README.md +536 -0
- package/dist/archcheck.d.ts +99 -0
- package/dist/archcheck.d.ts.map +1 -0
- package/dist/archcheck.js +339 -0
- package/dist/archcheck.js.map +1 -0
- package/dist/baseline/index.d.ts +29 -0
- package/dist/baseline/index.d.ts.map +1 -0
- package/dist/baseline/index.js +116 -0
- package/dist/baseline/index.js.map +1 -0
- package/dist/cli/args.d.ts +36 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +233 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +219 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +29 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/contracts/banned-syntax.d.ts +48 -0
- package/dist/contracts/banned-syntax.d.ts.map +1 -0
- package/dist/contracts/banned-syntax.js +201 -0
- package/dist/contracts/banned-syntax.js.map +1 -0
- package/dist/contracts/declared-field.d.ts +39 -0
- package/dist/contracts/declared-field.d.ts.map +1 -0
- package/dist/contracts/declared-field.js +142 -0
- package/dist/contracts/declared-field.js.map +1 -0
- package/dist/contracts/encapsulation.d.ts +28 -0
- package/dist/contracts/encapsulation.d.ts.map +1 -0
- package/dist/contracts/encapsulation.js +130 -0
- package/dist/contracts/encapsulation.js.map +1 -0
- package/dist/contracts/index.d.ts +10 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +9 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/judgement.d.ts +21 -0
- package/dist/contracts/judgement.d.ts.map +1 -0
- package/dist/contracts/judgement.js +109 -0
- package/dist/contracts/judgement.js.map +1 -0
- package/dist/contracts/layers.d.ts +75 -0
- package/dist/contracts/layers.d.ts.map +1 -0
- package/dist/contracts/layers.js +244 -0
- package/dist/contracts/layers.js.map +1 -0
- package/dist/contracts/naming.d.ts +21 -0
- package/dist/contracts/naming.d.ts.map +1 -0
- package/dist/contracts/naming.js +120 -0
- package/dist/contracts/naming.js.map +1 -0
- package/dist/contracts/registry.d.ts +23 -0
- package/dist/contracts/registry.d.ts.map +1 -0
- package/dist/contracts/registry.js +48 -0
- package/dist/contracts/registry.js.map +1 -0
- package/dist/contracts/required-file.d.ts +29 -0
- package/dist/contracts/required-file.d.ts.map +1 -0
- package/dist/contracts/required-file.js +131 -0
- package/dist/contracts/required-file.js.map +1 -0
- package/dist/contracts/types.d.ts +61 -0
- package/dist/contracts/types.d.ts.map +1 -0
- package/dist/contracts/types.js +2 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/coverage/index.d.ts +55 -0
- package/dist/coverage/index.d.ts.map +1 -0
- package/dist/coverage/index.js +68 -0
- package/dist/coverage/index.js.map +1 -0
- package/dist/emit/collect.d.ts +20 -0
- package/dist/emit/collect.d.ts.map +1 -0
- package/dist/emit/collect.js +17 -0
- package/dist/emit/collect.js.map +1 -0
- package/dist/emit/header.d.ts +21 -0
- package/dist/emit/header.d.ts.map +1 -0
- package/dist/emit/header.js +20 -0
- package/dist/emit/header.js.map +1 -0
- package/dist/emit/index.d.ts +57 -0
- package/dist/emit/index.d.ts.map +1 -0
- package/dist/emit/index.js +128 -0
- package/dist/emit/index.js.map +1 -0
- package/dist/emit/merge.d.ts +23 -0
- package/dist/emit/merge.d.ts.map +1 -0
- package/dist/emit/merge.js +71 -0
- package/dist/emit/merge.js.map +1 -0
- package/dist/emit/write.d.ts +20 -0
- package/dist/emit/write.d.ts.map +1 -0
- package/dist/emit/write.js +61 -0
- package/dist/emit/write.js.map +1 -0
- package/dist/exceptions/index.d.ts +46 -0
- package/dist/exceptions/index.d.ts.map +1 -0
- package/dist/exceptions/index.js +117 -0
- package/dist/exceptions/index.js.map +1 -0
- package/dist/fs/index.d.ts +12 -0
- package/dist/fs/index.d.ts.map +1 -0
- package/dist/fs/index.js +78 -0
- package/dist/fs/index.js.map +1 -0
- package/dist/graph/cache.d.ts +12 -0
- package/dist/graph/cache.d.ts.map +1 -0
- package/dist/graph/cache.js +76 -0
- package/dist/graph/cache.js.map +1 -0
- package/dist/graph/edges.d.ts +16 -0
- package/dist/graph/edges.d.ts.map +1 -0
- package/dist/graph/edges.js +161 -0
- package/dist/graph/edges.js.map +1 -0
- package/dist/graph/index.d.ts +15 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +90 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/match.d.ts +12 -0
- package/dist/graph/match.d.ts.map +1 -0
- package/dist/graph/match.js +35 -0
- package/dist/graph/match.js.map +1 -0
- package/dist/graph/program.d.ts +12 -0
- package/dist/graph/program.d.ts.map +1 -0
- package/dist/graph/program.js +54 -0
- package/dist/graph/program.js.map +1 -0
- package/dist/graph/types.d.ts +55 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +2 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/init/index.d.ts +23 -0
- package/dist/init/index.d.ts.map +1 -0
- package/dist/init/index.js +218 -0
- package/dist/init/index.js.map +1 -0
- package/dist/normalize/identity.d.ts +4 -0
- package/dist/normalize/identity.d.ts.map +1 -0
- package/dist/normalize/identity.js +33 -0
- package/dist/normalize/identity.js.map +1 -0
- package/dist/normalize/index.d.ts +3 -0
- package/dist/normalize/index.d.ts.map +1 -0
- package/dist/normalize/index.js +3 -0
- package/dist/normalize/index.js.map +1 -0
- package/dist/normalize/normalize.d.ts +34 -0
- package/dist/normalize/normalize.d.ts.map +1 -0
- package/dist/normalize/normalize.js +91 -0
- package/dist/normalize/normalize.js.map +1 -0
- package/dist/probes/graph.d.ts +19 -0
- package/dist/probes/graph.d.ts.map +1 -0
- package/dist/probes/graph.js +181 -0
- package/dist/probes/graph.js.map +1 -0
- package/dist/probes/index.d.ts +5 -0
- package/dist/probes/index.d.ts.map +1 -0
- package/dist/probes/index.js +5 -0
- package/dist/probes/index.js.map +1 -0
- package/dist/probes/lint.d.ts +17 -0
- package/dist/probes/lint.d.ts.map +1 -0
- package/dist/probes/lint.js +121 -0
- package/dist/probes/lint.js.map +1 -0
- package/dist/probes/model/anthropic.d.ts +47 -0
- package/dist/probes/model/anthropic.d.ts.map +1 -0
- package/dist/probes/model/anthropic.js +100 -0
- package/dist/probes/model/anthropic.js.map +1 -0
- package/dist/probes/model/budget.d.ts +22 -0
- package/dist/probes/model/budget.d.ts.map +1 -0
- package/dist/probes/model/budget.js +27 -0
- package/dist/probes/model/budget.js.map +1 -0
- package/dist/probes/model/cache.d.ts +13 -0
- package/dist/probes/model/cache.d.ts.map +1 -0
- package/dist/probes/model/cache.js +74 -0
- package/dist/probes/model/cache.js.map +1 -0
- package/dist/probes/model/index.d.ts +20 -0
- package/dist/probes/model/index.d.ts.map +1 -0
- package/dist/probes/model/index.js +160 -0
- package/dist/probes/model/index.js.map +1 -0
- package/dist/probes/model/prompt.d.ts +16 -0
- package/dist/probes/model/prompt.d.ts.map +1 -0
- package/dist/probes/model/prompt.js +57 -0
- package/dist/probes/model/prompt.js.map +1 -0
- package/dist/probes/model/provider.d.ts +88 -0
- package/dist/probes/model/provider.d.ts.map +1 -0
- package/dist/probes/model/provider.js +48 -0
- package/dist/probes/model/provider.js.map +1 -0
- package/dist/probes/structure.d.ts +3 -0
- package/dist/probes/structure.d.ts.map +1 -0
- package/dist/probes/structure.js +95 -0
- package/dist/probes/structure.js.map +1 -0
- package/dist/probes/types.d.ts +46 -0
- package/dist/probes/types.d.ts.map +1 -0
- package/dist/probes/types.js +15 -0
- package/dist/probes/types.js.map +1 -0
- package/dist/registry/define.d.ts +40 -0
- package/dist/registry/define.d.ts.map +1 -0
- package/dist/registry/define.js +4 -0
- package/dist/registry/define.js.map +1 -0
- package/dist/registry/ids.d.ts +19 -0
- package/dist/registry/ids.d.ts.map +1 -0
- package/dist/registry/ids.js +86 -0
- package/dist/registry/ids.js.map +1 -0
- package/dist/registry/index.d.ts +45 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +71 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/load.d.ts +14 -0
- package/dist/registry/load.d.ts.map +1 -0
- package/dist/registry/load.js +108 -0
- package/dist/registry/load.js.map +1 -0
- package/dist/registry/validate.d.ts +24 -0
- package/dist/registry/validate.d.ts.map +1 -0
- package/dist/registry/validate.js +160 -0
- package/dist/registry/validate.js.map +1 -0
- package/dist/report/index.d.ts +10 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +30 -0
- package/dist/report/index.js.map +1 -0
- package/dist/report/json.d.ts +20 -0
- package/dist/report/json.d.ts.map +1 -0
- package/dist/report/json.js +34 -0
- package/dist/report/json.js.map +1 -0
- package/dist/report/sarif.d.ts +21 -0
- package/dist/report/sarif.d.ts.map +1 -0
- package/dist/report/sarif.js +138 -0
- package/dist/report/sarif.js.map +1 -0
- package/dist/report/terminal.d.ts +3 -0
- package/dist/report/terminal.d.ts.map +1 -0
- package/dist/report/terminal.js +117 -0
- package/dist/report/terminal.js.map +1 -0
- package/dist/report/types.d.ts +28 -0
- package/dist/report/types.d.ts.map +1 -0
- package/dist/report/types.js +11 -0
- package/dist/report/types.js.map +1 -0
- package/dist/report/verdict.d.ts +21 -0
- package/dist/report/verdict.d.ts.map +1 -0
- package/dist/report/verdict.js +31 -0
- package/dist/report/verdict.js.map +1 -0
- package/dist/result.d.ts +22 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +19 -0
- package/dist/result.js.map +1 -0
- package/dist/types.d.ts +178 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/util/compilerApi.d.ts +2 -0
- package/dist/util/compilerApi.d.ts.map +1 -0
- package/dist/util/compilerApi.js +28 -0
- package/dist/util/compilerApi.js.map +1 -0
- package/dist/util/glob.d.ts +43 -0
- package/dist/util/glob.d.ts.map +1 -0
- package/dist/util/glob.js +195 -0
- package/dist/util/glob.js.map +1 -0
- package/dist/util/paths.d.ts +6 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +22 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +12 -0
- package/dist/version.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph probe. Runs layer rules against the module graph and returns raw findings.
|
|
3
|
+
*
|
|
4
|
+
* What rule this enforces: findings carry EVIDENCE, not prose. The probe never writes a sentence —
|
|
5
|
+
* the contract type's `explain()` does. That keeps one vocabulary for "what was observed" and one
|
|
6
|
+
* for "what a person should read", and it is what lets two probes observe the same violation and
|
|
7
|
+
* deduplicate cleanly (plan 06, plan 07).
|
|
8
|
+
*
|
|
9
|
+
* Diff scoping is edge-level: given a changed set, evaluate edges whose `from` is in it. An edge
|
|
10
|
+
* INTO a changed file is not a new violation — the importer did not change.
|
|
11
|
+
*/
|
|
12
|
+
import { asEncapsulationParams, resolveEncapsulation } from '../contracts/encapsulation.js';
|
|
13
|
+
import { buildLayerTable, resolveEdge } from '../contracts/layers.js';
|
|
14
|
+
import { fileMatches } from '../graph/match.js';
|
|
15
|
+
import { repoRelative } from '../util/paths.js';
|
|
16
|
+
export const GRAPH_REPORT_PATH = '.archcheck/graph.json';
|
|
17
|
+
export function toGraphReport(graph, root) {
|
|
18
|
+
const facts = {};
|
|
19
|
+
for (const file of graph.files) {
|
|
20
|
+
const fileFacts = graph.factsFor(file);
|
|
21
|
+
facts[repoRelative(root, file)] = {
|
|
22
|
+
implementsPorts: fileFacts?.implementsPorts ?? [],
|
|
23
|
+
exports: fileFacts?.exports ?? [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
root,
|
|
29
|
+
files: graph.files.map((file) => repoRelative(root, file)).sort(),
|
|
30
|
+
edges: graph.edges
|
|
31
|
+
.map(({ node: _node, ...edge }) => ({
|
|
32
|
+
...edge,
|
|
33
|
+
from: repoRelative(root, edge.from),
|
|
34
|
+
to: edge.external ? edge.to : repoRelative(root, edge.to),
|
|
35
|
+
}))
|
|
36
|
+
.sort((a, b) => `${a.from}${a.line}${a.specifier}`.localeCompare(`${b.from}${b.line}${b.specifier}`)),
|
|
37
|
+
facts,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** A ModuleGraph restored from a report. Absolute paths again, so evaluation is identical. */
|
|
41
|
+
export function fromGraphReport(report, root) {
|
|
42
|
+
const absolute = (path) => `${root}/${path}`;
|
|
43
|
+
const facts = new Map();
|
|
44
|
+
for (const [relative, entry] of Object.entries(report.facts)) {
|
|
45
|
+
facts.set(absolute(relative), {
|
|
46
|
+
file: absolute(relative),
|
|
47
|
+
edges: [],
|
|
48
|
+
implementsPorts: entry.implementsPorts,
|
|
49
|
+
exports: entry.exports,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const edges = report.edges.map((edge) => ({
|
|
53
|
+
...edge,
|
|
54
|
+
from: absolute(edge.from),
|
|
55
|
+
to: edge.external ? edge.to : absolute(edge.to),
|
|
56
|
+
}));
|
|
57
|
+
for (const edge of edges) {
|
|
58
|
+
const entry = facts.get(edge.from);
|
|
59
|
+
if (entry)
|
|
60
|
+
entry.edges.push(edge);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
files: report.files.map(absolute),
|
|
64
|
+
edges,
|
|
65
|
+
factsFor: (file) => facts.get(file),
|
|
66
|
+
// A graph restored from a report has no parsed sources; nothing that needs an AST may use it.
|
|
67
|
+
sourceFileFor: () => undefined,
|
|
68
|
+
walkedCount: 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function layerOf(layers, file, root, graph) {
|
|
72
|
+
// First match wins, so manifest order is how a reader resolves an overlap — a narrow layer
|
|
73
|
+
// declared above a broad one reads the way it behaves.
|
|
74
|
+
return layers.find((layer) => fileMatches(layer.match, file, root, graph.factsFor(file)));
|
|
75
|
+
}
|
|
76
|
+
function evaluate(graph, ctx) {
|
|
77
|
+
const table = buildLayerTable(ctx.rules);
|
|
78
|
+
const barrelRules = ctx.rules.filter((rule) => rule.kind === 'encapsulation' && rule.severity !== 'off');
|
|
79
|
+
if (table.layers.length === 0 && barrelRules.length === 0)
|
|
80
|
+
return [];
|
|
81
|
+
const changed = ctx.changed === undefined ? undefined : new Set(ctx.changed);
|
|
82
|
+
const inScope = (file) => changed === undefined || changed.has(repoRelative(ctx.root, file));
|
|
83
|
+
const findings = [];
|
|
84
|
+
const layerCache = new Map();
|
|
85
|
+
const layerFor = (file) => {
|
|
86
|
+
if (!layerCache.has(file))
|
|
87
|
+
layerCache.set(file, layerOf(table.layers, file, ctx.root, graph));
|
|
88
|
+
return layerCache.get(file);
|
|
89
|
+
};
|
|
90
|
+
for (const edge of graph.edges) {
|
|
91
|
+
if (!inScope(edge.from))
|
|
92
|
+
continue;
|
|
93
|
+
// The barrel ban is edge-level too, and independent of layers: a deep import is a deep
|
|
94
|
+
// import whether or not the two files happen to sit in the same layer.
|
|
95
|
+
if (!edge.external) {
|
|
96
|
+
const fromRelative = repoRelative(ctx.root, edge.from);
|
|
97
|
+
const toRelative = repoRelative(ctx.root, edge.to);
|
|
98
|
+
for (const rule of barrelRules) {
|
|
99
|
+
const verdict = resolveEncapsulation(asEncapsulationParams(rule), fromRelative, toRelative);
|
|
100
|
+
if (verdict.allowed)
|
|
101
|
+
continue;
|
|
102
|
+
findings.push({
|
|
103
|
+
ruleId: rule.id,
|
|
104
|
+
file: fromRelative,
|
|
105
|
+
line: edge.line,
|
|
106
|
+
col: edge.col,
|
|
107
|
+
evidence: {
|
|
108
|
+
from: fromRelative,
|
|
109
|
+
to: toRelative,
|
|
110
|
+
module: verdict.module,
|
|
111
|
+
deepPath: verdict.deepPath,
|
|
112
|
+
specifier: edge.specifier,
|
|
113
|
+
},
|
|
114
|
+
identity: '',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const from = layerFor(edge.from);
|
|
119
|
+
const to = edge.external ? undefined : layerFor(edge.to);
|
|
120
|
+
const verdict = resolveEdge(table, from, to?.name, edge);
|
|
121
|
+
if (verdict.allowed)
|
|
122
|
+
continue;
|
|
123
|
+
findings.push({
|
|
124
|
+
ruleId: verdict.ruleId,
|
|
125
|
+
file: repoRelative(ctx.root, edge.from),
|
|
126
|
+
line: edge.line,
|
|
127
|
+
col: edge.col,
|
|
128
|
+
evidence: {
|
|
129
|
+
from: repoRelative(ctx.root, edge.from),
|
|
130
|
+
to: repoRelative(ctx.root, edge.to),
|
|
131
|
+
fromLayer: verdict.fromLayer,
|
|
132
|
+
toLayer: verdict.toLayer,
|
|
133
|
+
specifier: edge.specifier,
|
|
134
|
+
},
|
|
135
|
+
identity: '',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Case 5 of the resolution order: a file nobody declared. Reported once per file, against the
|
|
139
|
+
// one layers rule that owns the unlayered report.
|
|
140
|
+
const owner = table.unlayeredOwner;
|
|
141
|
+
if (owner !== undefined) {
|
|
142
|
+
for (const file of graph.files) {
|
|
143
|
+
if (!inScope(file))
|
|
144
|
+
continue;
|
|
145
|
+
if (layerFor(file) !== undefined)
|
|
146
|
+
continue;
|
|
147
|
+
const relative = repoRelative(ctx.root, file);
|
|
148
|
+
findings.push({
|
|
149
|
+
ruleId: owner,
|
|
150
|
+
file: relative,
|
|
151
|
+
evidence: { unlayered: true, file: relative },
|
|
152
|
+
identity: '',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return findings;
|
|
157
|
+
}
|
|
158
|
+
export function createGraphProbe() {
|
|
159
|
+
return {
|
|
160
|
+
name: 'graph',
|
|
161
|
+
// `forbidden` is NOT here, although the graph is where it would live. This probe evaluates
|
|
162
|
+
// `layers` and `encapsulation` and nothing else, and a probe that claims a kind it ignores
|
|
163
|
+
// turns a declared rule into a silent pass: the run reports no skip, says "nothing to fix",
|
|
164
|
+
// and counts the rule as checked. `handles` is a promise, so it has to be true.
|
|
165
|
+
handles: ['layers', 'encapsulation'],
|
|
166
|
+
async run(ctx) {
|
|
167
|
+
const graph = ctx.graph();
|
|
168
|
+
if (!graph.ok)
|
|
169
|
+
return { skipped: true, reason: graph.error.join('; ') };
|
|
170
|
+
return evaluate(graph.value, ctx);
|
|
171
|
+
},
|
|
172
|
+
ingest(report, ctx) {
|
|
173
|
+
const parsed = report;
|
|
174
|
+
if (parsed?.schemaVersion !== 1 || !Array.isArray(parsed.edges)) {
|
|
175
|
+
throw new Error(`${GRAPH_REPORT_PATH} is not a graph report of schemaVersion 1`);
|
|
176
|
+
}
|
|
177
|
+
return evaluate(fromGraphReport(parsed, ctx.root), ctx);
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/probes/graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createGraphProbe, fromGraphReport, GRAPH_REPORT_PATH, type GraphReport, toGraphReport, } from './graph.js';
|
|
2
|
+
export { createLintProbe, type ExternalLintReport, LINT_REPORT_PATH, } from './lint.js';
|
|
3
|
+
export { createStructureProbe } from './structure.js';
|
|
4
|
+
export { isSkip, type Probe, type RunCtx, type Skip } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/probes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,KAAK,WAAW,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,EAAE,KAAK,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createGraphProbe, fromGraphReport, GRAPH_REPORT_PATH, toGraphReport, } from './graph.js';
|
|
2
|
+
export { createLintProbe, LINT_REPORT_PATH, } from './lint.js';
|
|
3
|
+
export { createStructureProbe } from './structure.js';
|
|
4
|
+
export { isSkip } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/probes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EAEjB,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EAEf,gBAAgB,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAsC,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Probe } from './types.js';
|
|
2
|
+
export declare const LINT_REPORT_PATH = ".archcheck/lint.json";
|
|
3
|
+
/** The shape an external linter's report is adapted into before it reaches us. */
|
|
4
|
+
export type ExternalLintReport = {
|
|
5
|
+
schemaVersion: 1;
|
|
6
|
+
findings: {
|
|
7
|
+
ruleId: string;
|
|
8
|
+
/** Repo-relative POSIX. */
|
|
9
|
+
file: string;
|
|
10
|
+
line?: number;
|
|
11
|
+
col?: number;
|
|
12
|
+
/** Whatever the linter saw. Passed through into evidence. */
|
|
13
|
+
evidence?: Record<string, unknown>;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
16
|
+
export declare function createLintProbe(): Probe;
|
|
17
|
+
//# sourceMappingURL=lint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/probes/lint.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,YAAY,CAAC;AAEtD,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,kFAAkF;AAClF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,QAAQ,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,2BAA2B;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,6DAA6D;QAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,EAAE,CAAC;CACL,CAAC;AAoFF,wBAAgB,eAAe,IAAI,KAAK,CA8BvC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lint probe. Evaluates `naming` and `banned-syntax` over our own program.
|
|
3
|
+
*
|
|
4
|
+
* Why evaluate in process at all, given we also emit lint config (plan 13)? Two reasons. The editor
|
|
5
|
+
* path uses the emitted config, but CI must not need the consumer's linter installed to run
|
|
6
|
+
* archcheck; and our findings carry evidence — the symbol, the module, the snippet — that a linter's
|
|
7
|
+
* report cannot express, and that the identity hash needs.
|
|
8
|
+
*
|
|
9
|
+
* What a caller must know: `run()` needs a real program, because `banned-syntax` reads the AST.
|
|
10
|
+
* `ingest()` is the other half — an external linter's report, mapped back by rule id, for the CI
|
|
11
|
+
* lane that already ran one.
|
|
12
|
+
*/
|
|
13
|
+
import { asBannedSyntaxParams, bannedSyntaxApplies, findBannedOccurrences, } from '../contracts/banned-syntax.js';
|
|
14
|
+
import { asNamingParams, nameIsWellFormed, namingApplies } from '../contracts/naming.js';
|
|
15
|
+
import { repoRelative } from '../util/paths.js';
|
|
16
|
+
export const LINT_REPORT_PATH = '.archcheck/lint.json';
|
|
17
|
+
function baseNameOf(relativePath) {
|
|
18
|
+
return relativePath.slice(relativePath.lastIndexOf('/') + 1);
|
|
19
|
+
}
|
|
20
|
+
function evaluate(ctx, graph) {
|
|
21
|
+
const namingRules = ctx.rules.filter((rule) => rule.kind === 'naming' && rule.severity !== 'off');
|
|
22
|
+
const syntaxRules = ctx.rules.filter((rule) => rule.kind === 'banned-syntax' && rule.severity !== 'off');
|
|
23
|
+
if (namingRules.length === 0 && syntaxRules.length === 0)
|
|
24
|
+
return [];
|
|
25
|
+
const changed = ctx.changed === undefined ? undefined : new Set(ctx.changed);
|
|
26
|
+
const findings = [];
|
|
27
|
+
const computedByFile = new Map();
|
|
28
|
+
for (const edge of graph.edges) {
|
|
29
|
+
if (edge.dynamic !== 'computed')
|
|
30
|
+
continue;
|
|
31
|
+
const list = computedByFile.get(edge.from);
|
|
32
|
+
if (list)
|
|
33
|
+
list.push(edge);
|
|
34
|
+
else
|
|
35
|
+
computedByFile.set(edge.from, [edge]);
|
|
36
|
+
}
|
|
37
|
+
for (const file of graph.files) {
|
|
38
|
+
const relative = repoRelative(ctx.root, file);
|
|
39
|
+
if (changed !== undefined && !changed.has(relative))
|
|
40
|
+
continue;
|
|
41
|
+
for (const rule of namingRules) {
|
|
42
|
+
const params = asNamingParams(rule);
|
|
43
|
+
if (!namingApplies(params, relative))
|
|
44
|
+
continue;
|
|
45
|
+
const target = params.target ?? 'exports';
|
|
46
|
+
if (target === 'files' || target === 'both') {
|
|
47
|
+
const name = baseNameOf(relative);
|
|
48
|
+
if (!nameIsWellFormed(params, name)) {
|
|
49
|
+
findings.push({
|
|
50
|
+
ruleId: rule.id,
|
|
51
|
+
file: relative,
|
|
52
|
+
evidence: { file: relative, name, namedThing: 'file' },
|
|
53
|
+
identity: '',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (target === 'exports' || target === 'both') {
|
|
58
|
+
for (const name of graph.factsFor(file)?.exports ?? []) {
|
|
59
|
+
if (nameIsWellFormed(params, name))
|
|
60
|
+
continue;
|
|
61
|
+
findings.push({
|
|
62
|
+
ruleId: rule.id,
|
|
63
|
+
file: relative,
|
|
64
|
+
evidence: { file: relative, name, namedThing: 'export' },
|
|
65
|
+
identity: '',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const rule of syntaxRules) {
|
|
71
|
+
const params = asBannedSyntaxParams(rule);
|
|
72
|
+
if (!bannedSyntaxApplies(params, relative))
|
|
73
|
+
continue;
|
|
74
|
+
const occurrences = findBannedOccurrences(params, graph.sourceFileFor(file), computedByFile.get(file) ?? []);
|
|
75
|
+
for (const occurrence of occurrences) {
|
|
76
|
+
findings.push({
|
|
77
|
+
ruleId: rule.id,
|
|
78
|
+
file: relative,
|
|
79
|
+
line: occurrence.line,
|
|
80
|
+
col: occurrence.col,
|
|
81
|
+
// The snippet, not the line number, is what makes two occurrences distinguishable —
|
|
82
|
+
// and it is stable when the file moves down the page.
|
|
83
|
+
evidence: { file: relative, construct: params.selector, snippet: occurrence.snippet },
|
|
84
|
+
identity: '',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return findings;
|
|
90
|
+
}
|
|
91
|
+
export function createLintProbe() {
|
|
92
|
+
return {
|
|
93
|
+
name: 'lint',
|
|
94
|
+
handles: ['naming', 'banned-syntax'],
|
|
95
|
+
async run(ctx) {
|
|
96
|
+
const graph = ctx.graph();
|
|
97
|
+
if (!graph.ok)
|
|
98
|
+
return { skipped: true, reason: graph.error.join('; ') };
|
|
99
|
+
return evaluate(ctx, graph.value);
|
|
100
|
+
},
|
|
101
|
+
/** Map an external linter's report back by rule id. Anything we do not know is an error. */
|
|
102
|
+
ingest(report, ctx) {
|
|
103
|
+
const parsed = report;
|
|
104
|
+
if (parsed?.schemaVersion !== 1 || !Array.isArray(parsed.findings)) {
|
|
105
|
+
throw new Error(`${LINT_REPORT_PATH} is not a lint report of schemaVersion 1`);
|
|
106
|
+
}
|
|
107
|
+
const known = new Set(ctx.rules.map((rule) => rule.id));
|
|
108
|
+
return parsed.findings
|
|
109
|
+
.filter((finding) => known.has(finding.ruleId))
|
|
110
|
+
.map((finding) => ({
|
|
111
|
+
ruleId: finding.ruleId,
|
|
112
|
+
file: finding.file,
|
|
113
|
+
...(finding.line === undefined ? {} : { line: finding.line }),
|
|
114
|
+
...(finding.col === undefined ? {} : { col: finding.col }),
|
|
115
|
+
evidence: { file: finding.file, ...(finding.evidence ?? {}) },
|
|
116
|
+
identity: '',
|
|
117
|
+
}));
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../../src/probes/lint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one shipped LlmProvider, on the Anthropic SDK.
|
|
3
|
+
*
|
|
4
|
+
* The SDK is an OPTIONAL peer dependency: a consumer with no judgement rules installs nothing, and
|
|
5
|
+
* one who has them but has not installed it gets a clean skip with the install command in the
|
|
6
|
+
* reason. That is why the import is dynamic and why this file never appears in a code path that a
|
|
7
|
+
* key-less run reaches.
|
|
8
|
+
*
|
|
9
|
+
* Prompt layout, and the reason for the order (LLD §9.3):
|
|
10
|
+
*
|
|
11
|
+
* [ system: judge instructions ] stable
|
|
12
|
+
* [ all judgement rules + examples ] stable ← cache breakpoint
|
|
13
|
+
* [ the one file under review ] volatile
|
|
14
|
+
*
|
|
15
|
+
* Every judgement rule's statement and examples are identical across every file, so they cache
|
|
16
|
+
* once and are re-read at a fraction of the price. Batching by file makes it 40 calls for 40
|
|
17
|
+
* changed files rather than 600.
|
|
18
|
+
*/
|
|
19
|
+
import { type Result } from '../../result.js';
|
|
20
|
+
import { type JudgeResponse, type LlmProvider } from './provider.js';
|
|
21
|
+
/** As much of the SDK's reply as this file reads. */
|
|
22
|
+
type JudgeMessage = {
|
|
23
|
+
content: {
|
|
24
|
+
type: string;
|
|
25
|
+
text?: string;
|
|
26
|
+
}[];
|
|
27
|
+
usage?: Record<string, number>;
|
|
28
|
+
stop_reason?: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Did the judge ANSWER at all? A refusal, or a reply this file cannot read, is a NON-ANSWER, and a
|
|
32
|
+
* non-answer is an error — never `ok({ verdicts: [] })`, which the caller cannot tell from
|
|
33
|
+
* "answered: no violations". The probe's `!response.ok` branch in index.ts is what makes a
|
|
34
|
+
* non-answer count as unjudged and keeps it out of the verdict cache, so one transient refusal
|
|
35
|
+
* cannot become a permanent "clean" for that file. Both halves are pinned by model.test.ts:
|
|
36
|
+
* 'a non-answer is not a clean bill of health' and 'a non-answer is not cached'.
|
|
37
|
+
*
|
|
38
|
+
* A non-answer reports no cost, because there is no verdict to attribute it to. What bounds a
|
|
39
|
+
* refusing or failing endpoint is the call cap: index.ts charges every ISSUED call to the budget.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readJudgeAnswer(message: JudgeMessage): Result<JudgeResponse>;
|
|
42
|
+
export declare function createAnthropicProvider(options: {
|
|
43
|
+
apiKey: string;
|
|
44
|
+
model: string;
|
|
45
|
+
}): LlmProvider;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/probes/model/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,WAAW,EAGjB,MAAM,eAAe,CAAC;AAEvB,qDAAqD;AACrD,KAAK,YAAY,GAAG;IAClB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAQF;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAAC,aAAa,CAAC,CA2B5E;AAgBD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CA+B/F"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one shipped LlmProvider, on the Anthropic SDK.
|
|
3
|
+
*
|
|
4
|
+
* The SDK is an OPTIONAL peer dependency: a consumer with no judgement rules installs nothing, and
|
|
5
|
+
* one who has them but has not installed it gets a clean skip with the install command in the
|
|
6
|
+
* reason. That is why the import is dynamic and why this file never appears in a code path that a
|
|
7
|
+
* key-less run reaches.
|
|
8
|
+
*
|
|
9
|
+
* Prompt layout, and the reason for the order (LLD §9.3):
|
|
10
|
+
*
|
|
11
|
+
* [ system: judge instructions ] stable
|
|
12
|
+
* [ all judgement rules + examples ] stable ← cache breakpoint
|
|
13
|
+
* [ the one file under review ] volatile
|
|
14
|
+
*
|
|
15
|
+
* Every judgement rule's statement and examples are identical across every file, so they cache
|
|
16
|
+
* once and are re-read at a fraction of the price. Batching by file makes it 40 calls for 40
|
|
17
|
+
* changed files rather than 600.
|
|
18
|
+
*/
|
|
19
|
+
import { err, ok } from '../../result.js';
|
|
20
|
+
import { estimateCostUsd, parseVerdicts, VERDICT_SCHEMA, } from './provider.js';
|
|
21
|
+
/**
|
|
22
|
+
* Did the judge ANSWER at all? A refusal, or a reply this file cannot read, is a NON-ANSWER, and a
|
|
23
|
+
* non-answer is an error — never `ok({ verdicts: [] })`, which the caller cannot tell from
|
|
24
|
+
* "answered: no violations". The probe's `!response.ok` branch in index.ts is what makes a
|
|
25
|
+
* non-answer count as unjudged and keeps it out of the verdict cache, so one transient refusal
|
|
26
|
+
* cannot become a permanent "clean" for that file. Both halves are pinned by model.test.ts:
|
|
27
|
+
* 'a non-answer is not a clean bill of health' and 'a non-answer is not cached'.
|
|
28
|
+
*
|
|
29
|
+
* A non-answer reports no cost, because there is no verdict to attribute it to. What bounds a
|
|
30
|
+
* refusing or failing endpoint is the call cap: index.ts charges every ISSUED call to the budget.
|
|
31
|
+
*/
|
|
32
|
+
export function readJudgeAnswer(message) {
|
|
33
|
+
if (message.stop_reason === 'refusal') {
|
|
34
|
+
return err(['the judge refused to answer — the file was not judged']);
|
|
35
|
+
}
|
|
36
|
+
const text = message.content.find((block) => block.type === 'text')?.text ?? '';
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return err(["the judge's answer was not JSON — the file was not judged"]);
|
|
43
|
+
}
|
|
44
|
+
const answered = parsed?.verdicts;
|
|
45
|
+
if (!Array.isArray(answered)) {
|
|
46
|
+
return err(["the judge's answer did not match the verdict schema — the file was not judged"]);
|
|
47
|
+
}
|
|
48
|
+
// Schema-invalid output is DISCARDED, never salvaged by guessing: a judge that half-parses is a
|
|
49
|
+
// judge that invents findings. But an answer where nothing at all survived is a non-answer, not
|
|
50
|
+
// a clean file.
|
|
51
|
+
const verdicts = parseVerdicts(parsed);
|
|
52
|
+
if (answered.length > 0 && verdicts.length === 0) {
|
|
53
|
+
return err(["no verdict in the judge's answer was usable — the file was not judged"]);
|
|
54
|
+
}
|
|
55
|
+
return ok({ verdicts, costUsd: estimateCostUsd(message.usage ?? {}) });
|
|
56
|
+
}
|
|
57
|
+
async function loadClient(apiKey) {
|
|
58
|
+
try {
|
|
59
|
+
const module = (await import('@anthropic-ai/sdk'));
|
|
60
|
+
return ok(new module.default({ apiKey }));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return err([
|
|
64
|
+
'@anthropic-ai/sdk is not installed — it is an optional peer dependency',
|
|
65
|
+
' → install it to turn the judgement tier on: npm install -D @anthropic-ai/sdk',
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function createAnthropicProvider(options) {
|
|
70
|
+
return {
|
|
71
|
+
name: 'anthropic',
|
|
72
|
+
async judge(request) {
|
|
73
|
+
const client = await loadClient(options.apiKey);
|
|
74
|
+
if (!client.ok)
|
|
75
|
+
return client;
|
|
76
|
+
try {
|
|
77
|
+
const response = await client.value.messages.create({
|
|
78
|
+
model: request.model,
|
|
79
|
+
max_tokens: 4096,
|
|
80
|
+
thinking: { type: 'adaptive' },
|
|
81
|
+
output_config: {
|
|
82
|
+
effort: 'high',
|
|
83
|
+
format: { type: 'json_schema', schema: VERDICT_SCHEMA },
|
|
84
|
+
},
|
|
85
|
+
system: [
|
|
86
|
+
{ type: 'text', text: request.instructions },
|
|
87
|
+
// The breakpoint: everything above here is identical for every file in the run.
|
|
88
|
+
{ type: 'text', text: request.rulesBlock, cache_control: { type: 'ephemeral' } },
|
|
89
|
+
],
|
|
90
|
+
messages: [{ role: 'user', content: request.fileBlock }],
|
|
91
|
+
});
|
|
92
|
+
return readJudgeAnswer(response);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return err([`the judge could not be reached: ${error.message}`]);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/probes/model/anthropic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-run spend ceiling, checked BEFORE each request.
|
|
3
|
+
*
|
|
4
|
+
* What rule this enforces: the probe refuses rather than overspending, and it reports how many
|
|
5
|
+
* rules went unjudged. A budget checked after the fact is not a budget (LLD §1.7).
|
|
6
|
+
*/
|
|
7
|
+
export type Budget = {
|
|
8
|
+
maxUsd: number;
|
|
9
|
+
maxCalls: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const DEFAULT_BUDGET: Budget;
|
|
12
|
+
export type BudgetState = {
|
|
13
|
+
/** May another call be made? */
|
|
14
|
+
canSpend(): boolean;
|
|
15
|
+
/** Why not, for the skip reason. */
|
|
16
|
+
exhaustedReason(): string | undefined;
|
|
17
|
+
record(costUsd: number): void;
|
|
18
|
+
readonly spentUsd: number;
|
|
19
|
+
readonly calls: number;
|
|
20
|
+
};
|
|
21
|
+
export declare function createBudget(budget?: Budget): BudgetState;
|
|
22
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../../src/probes/model/budget.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,eAAO,MAAM,cAAc,EAAE,MAAsC,CAAC;AAEpE,MAAM,MAAM,WAAW,GAAG;IACxB,gCAAgC;IAChC,QAAQ,IAAI,OAAO,CAAC;IACpB,oCAAoC;IACpC,eAAe,IAAI,MAAM,GAAG,SAAS,CAAC;IACtC,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,YAAY,CAAC,MAAM,GAAE,MAAuB,GAAG,WAAW,CAuBzE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const DEFAULT_BUDGET = { maxUsd: 0.5, maxCalls: 60 };
|
|
2
|
+
export function createBudget(budget = DEFAULT_BUDGET) {
|
|
3
|
+
let spentUsd = 0;
|
|
4
|
+
let calls = 0;
|
|
5
|
+
return {
|
|
6
|
+
canSpend: () => calls < budget.maxCalls && spentUsd < budget.maxUsd,
|
|
7
|
+
exhaustedReason: () => {
|
|
8
|
+
if (calls >= budget.maxCalls)
|
|
9
|
+
return `call budget spent (${calls}/${budget.maxCalls} calls)`;
|
|
10
|
+
if (spentUsd >= budget.maxUsd) {
|
|
11
|
+
return `spend budget spent ($${spentUsd.toFixed(4)}/$${budget.maxUsd.toFixed(2)})`;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
},
|
|
15
|
+
record(costUsd) {
|
|
16
|
+
spentUsd += costUsd;
|
|
17
|
+
calls += 1;
|
|
18
|
+
},
|
|
19
|
+
get spentUsd() {
|
|
20
|
+
return spentUsd;
|
|
21
|
+
},
|
|
22
|
+
get calls() {
|
|
23
|
+
return calls;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../../src/probes/model/budget.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,cAAc,GAAW,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAYpE,MAAM,UAAU,YAAY,CAAC,SAAiB,cAAc;IAC1D,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,GAAG,MAAM,CAAC,QAAQ,IAAI,QAAQ,GAAG,MAAM,CAAC,MAAM;QACnE,eAAe,EAAE,GAAG,EAAE;YACpB,IAAI,KAAK,IAAI,MAAM,CAAC,QAAQ;gBAAE,OAAO,sBAAsB,KAAK,IAAI,MAAM,CAAC,QAAQ,SAAS,CAAC;YAC7F,IAAI,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC9B,OAAO,wBAAwB,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;YACrF,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,CAAC,OAAe;YACpB,QAAQ,IAAI,OAAO,CAAC;YACpB,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;QACD,IAAI,QAAQ;YACV,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { JudgeVerdict } from './provider.js';
|
|
2
|
+
export declare const MODEL_CACHE_PATH = ".archcheck/cache/judgement.json";
|
|
3
|
+
export type VerdictCache = {
|
|
4
|
+
get(key: string): JudgeVerdict[] | undefined;
|
|
5
|
+
set(key: string, verdicts: JudgeVerdict[]): void;
|
|
6
|
+
flush(): void;
|
|
7
|
+
readonly hits: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function cacheKey(ruleSetHash: string, path: string, fileContent: string): string;
|
|
10
|
+
export declare function hashRuleSet(parts: readonly string[]): string;
|
|
11
|
+
export declare function createNullVerdictCache(): VerdictCache;
|
|
12
|
+
export declare function createVerdictCache(root: string): VerdictCache;
|
|
13
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/probes/model/cache.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,eAAO,MAAM,gBAAgB,oCAAoC,CAAC;AAIlE,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,SAAS,CAAC;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IACjD,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAKvF;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAK5D;AAED,wBAAgB,sBAAsB,IAAI,YAAY,CAErD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAuC7D"}
|