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.
@@ -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})`);
@@ -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) {
@@ -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 },