testguard-cli 0.14.2 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -1
- package/README.md +30 -4
- package/package.json +1 -1
- package/spec/GATE-SEMANTICS.md +99 -8
- package/spec/README.md +7 -0
- package/spec/lib/validate.mjs +26 -1
- package/spec/schemas/common.schema.json +10 -0
- package/spec/schemas/evidence.schema.json +14 -0
- package/spec/schemas/gate.schema.json +1 -0
- package/spec/schemas/status.schema.json +2 -1
- package/src/cli.mjs +34 -4
- package/src/command-budget.mjs +52 -0
- package/src/commands/probe.mjs +22 -0
- package/src/commands/replay.mjs +14 -1
- package/src/commands/sweep.mjs +15 -6
- package/src/gate/changed.mjs +40 -3
- package/src/git.mjs +4 -2
- package/src/init/templates/SKILL.md +1 -1
- package/src/probe/attribution.mjs +2 -0
- package/src/probe/discover.mjs +44 -8
- package/src/probe/imports.mjs +785 -0
- package/src/probe/mocks.mjs +13 -4
- package/src/probe/probe.mjs +177 -20
- package/src/probe/rank.mjs +28 -5
- package/src/probe/runners/discovery.mjs +636 -0
- package/src/probe/runners/index.mjs +51 -19
- package/src/probe/runners/jest.mjs +32 -1
- package/src/probe/runners/playwright.mjs +58 -2
- package/src/probe/runners/python.mjs +7 -7
- package/src/probe/runners/shared.mjs +71 -7
- package/src/probe/runners/vitest.mjs +21 -0
- package/src/replay/replay.mjs +108 -13
- package/src/status/status.mjs +6 -0
- package/src/status/surface.mjs +3 -2
- package/src/sweep/sweep.mjs +5 -0
package/src/status/status.mjs
CHANGED
|
@@ -64,6 +64,7 @@ export function computeStatus({ projectDir, toolVersion = '0.0.0', generatedAt =
|
|
|
64
64
|
if (ref) {
|
|
65
65
|
const g = computeChangedGate({ projectDir, ref, includeDirty, toolVersion });
|
|
66
66
|
changes = { ref: g.ref, base: g.base, includeDirty: g.includeDirty, changed: g.changed, evaluated: g.evaluated, excluded: g.excluded.length, uncovered: g.uncovered, reliedOn: g.reliedOn, expired: g.expired };
|
|
67
|
+
if (g.nested?.length) changes.nested = g.nested;
|
|
67
68
|
doc.changes = changes;
|
|
68
69
|
if (existsSync(defaultIgnorePath(projectDir))) doc.paths.ignore = rel(defaultIgnorePath(projectDir));
|
|
69
70
|
}
|
|
@@ -90,6 +91,10 @@ export function computeStatus({ projectDir, toolVersion = '0.0.0', generatedAt =
|
|
|
90
91
|
doc.counts.claims = claims.claims.length;
|
|
91
92
|
doc.counts.faults = claims.claims.reduce((n, c) => n + c.faults.length, 0);
|
|
92
93
|
doc.surface = computeClaimedSurface({ projectDir, claims });
|
|
94
|
+
if (claims.claims.length === 0 && !changes?.uncovered.length) {
|
|
95
|
+
doc.next = { action: 'scaffold', command: 'testguard scaffold <source-file>', why: 'No claims have been declared; no verification was performed. State a claim before probing.' };
|
|
96
|
+
return doc;
|
|
97
|
+
}
|
|
93
98
|
const faultIndex = new Map();
|
|
94
99
|
for (const c of claims.claims) for (const f of c.faults) faultIndex.set(`${c.id}/${f.id}`, { claim: c, fault: f });
|
|
95
100
|
|
|
@@ -281,6 +286,7 @@ export function renderStatus(doc) {
|
|
|
281
286
|
for (const u of c.uncovered) lines.push(`UNCLAIMED ${u.file} (${u.kind}) → ${u.suggestion}`);
|
|
282
287
|
for (const r of c.reliedOn) lines.push(`excused ${r.files.join(', ')} by ignore "${r.pattern}": ${r.reason}`);
|
|
283
288
|
for (const e of c.expired) lines.push(`EXPIRED ignore "${e.pattern}" no longer excuses ${e.files.join(', ')}`);
|
|
289
|
+
for (const n of c.nested ?? []) lines.push(`delegated ${n.files.length} file(s) belong to ${n.project}/; run testguard gate ${n.project} --changed ${c.ref}${c.includeDirty ? ' --include-dirty' : ''}; child coverage has not been evaluated here`);
|
|
284
290
|
}
|
|
285
291
|
for (const c of doc.changedFaults) lines.push(`CHANGED ${c.claimId}/${c.subjectId} edited since it ${c.previousVerdict} (${c.file})`);
|
|
286
292
|
for (const f of doc.invalidFaults ?? []) lines.push(`INVALID ${f.claimId}/${f.subjectId} ${f.reason}: ${f.hits} hits, expected ${f.expected} (${f.file})`);
|
package/src/status/surface.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { extname, join } from 'node:path';
|
|
3
3
|
import { git } from '../git.mjs';
|
|
4
|
-
import { SOURCE_EXT, isDefaultExcluded, isTestFile } from '../gate/changed.mjs';
|
|
4
|
+
import { SOURCE_EXT, isDefaultExcluded, isTestFile, nestedProjectResolver } from '../gate/changed.mjs';
|
|
5
5
|
import { walk } from '../util/glob.mjs';
|
|
6
6
|
|
|
7
7
|
export const HISTORY_COMMITS = 200;
|
|
@@ -24,11 +24,12 @@ function listedFiles(projectDir) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function claimableModules(projectDir) {
|
|
27
|
+
const ownerOf = nestedProjectResolver(projectDir);
|
|
27
28
|
return listedFiles(projectDir).filter((file) =>
|
|
28
29
|
existsSync(join(projectDir, file)) &&
|
|
29
30
|
SOURCE_EXT.has(extname(file)) &&
|
|
30
31
|
!isTestFile(file) &&
|
|
31
|
-
!isDefaultExcluded(file));
|
|
32
|
+
!isDefaultExcluded(file) && !ownerOf(file));
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function recentChanges(projectDir, sourceModules) {
|
package/src/sweep/sweep.mjs
CHANGED
|
@@ -162,6 +162,7 @@ export async function sweep({
|
|
|
162
162
|
cap,
|
|
163
163
|
confirmRuns = 3,
|
|
164
164
|
budgetMs = 120_000,
|
|
165
|
+
commandBudget,
|
|
165
166
|
runnerCommand,
|
|
166
167
|
runnerName,
|
|
167
168
|
nodeModules,
|
|
@@ -172,6 +173,7 @@ export async function sweep({
|
|
|
172
173
|
onStage,
|
|
173
174
|
onWarn = () => {},
|
|
174
175
|
}) {
|
|
176
|
+
commandBudget?.assertOpen();
|
|
175
177
|
const gate = computeChangedGate({ projectDir, ref, includeDirty, exclude, toolVersion, claimsPath, ignorePath });
|
|
176
178
|
|
|
177
179
|
// ── What the sweep is pointed at. ──
|
|
@@ -208,6 +210,7 @@ export async function sweep({
|
|
|
208
210
|
const skipped = [];
|
|
209
211
|
const drafts = [];
|
|
210
212
|
for (const file of targets) {
|
|
213
|
+
commandBudget?.assertOpen();
|
|
211
214
|
let result;
|
|
212
215
|
try {
|
|
213
216
|
result = scaffoldFile({ projectDir, file, existingClaims, toolVersion });
|
|
@@ -256,6 +259,7 @@ export async function sweep({
|
|
|
256
259
|
claims: { schemaVersion: 1, claims },
|
|
257
260
|
confirmRuns,
|
|
258
261
|
budgetMs,
|
|
262
|
+
commandBudget,
|
|
259
263
|
mode: 'worktree',
|
|
260
264
|
includeDirty,
|
|
261
265
|
// A proposed fault has no declared defenders to be missing from, so the
|
|
@@ -288,6 +292,7 @@ export async function sweep({
|
|
|
288
292
|
}
|
|
289
293
|
const ordered = sortFindings(findings);
|
|
290
294
|
const gating = gatingCount(ordered);
|
|
295
|
+
commandBudget?.assertOpen();
|
|
291
296
|
return {
|
|
292
297
|
schemaVersion: 1,
|
|
293
298
|
tool: { name: 'testguard', version: toolVersion },
|