specguard-ruby 0.3.10 → 0.3.12

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7d1dfb3eb7af542eee8c1202dae06dd0d9c3981f5415d12875037c9a9067944
4
- data.tar.gz: 9d785e02b70ea9e06b0d0cf55ce31cbb1d67c841ea36fb0b7f3c7217a8f20304
3
+ metadata.gz: a48430615dad6103c9c66ef9a7e78d5f4fabdcf460a2321d4f3b03bb0d096fe5
4
+ data.tar.gz: 68100f41f50db5a7bc0067a6afb79823498aeae6bf6ef0fff0b5d04ff4b3b672
5
5
  SHA512:
6
- metadata.gz: 3d5ae9e309ae02ea33679223f712d04edf7c9e06aab22f011fde61484f2f3c8d2c992b02b792caceeb934b63136734840cdd01d0dbe62619b695dd09c3ceb238
7
- data.tar.gz: 3d3f373483219a5cdbeecdf639065bd99cb90dd648acf4bd75174ae9eba6adefcfc94a6458577480bb526dc636d88b139a4742cda3c12269a320bd5e7f994a3f
6
+ metadata.gz: ffeaa97c7b965463b5efd30a3273101f029dca4a0de18d2af6dbcd6664dacdc42e49bda907ae02d8d33c3a7bd3b5c9716c47c1a0469122c438dd751f146ba6b0
7
+ data.tar.gz: 8745018c339d7073e30e720771a9e4cea8df251a9681e35d536c3015e9e0b5990c55ccba7b9807cb156fd2b69d13834cf62ef9899c9f5740629ee360445df92d
@@ -156,7 +156,11 @@ module SpecGuard
156
156
  # `ok` is derived FROM it rather than recomputed from the findings, so
157
157
  # the two renderers cannot disagree about whether the run passed.
158
158
  code = results.any?(&:failed?) ? EXIT_MALFORMED : EXIT_OK
159
- report_results(results, files: selection.count, json: options[:json], ok: code == EXIT_OK)
159
+ # The selection's file LIST rides along (the `files:` count stays what
160
+ # the document's summary consumes) because the coverage note is about
161
+ # which files were checked, not how many.
162
+ report_results(results, files: selection.count, selected_files: selection.files,
163
+ json: options[:json], ok: code == EXIT_OK)
160
164
 
161
165
  code
162
166
  rescue UsageError, ValidatorError => e
@@ -225,9 +229,18 @@ module SpecGuard
225
229
  #
226
230
  # Under `--json` the count is not dropped, it MOVES: stdout carries one
227
231
  # document and nothing else, and this line's number is that document's
228
- # `summary.files`. The warnings are diagnostics about the linter rather
229
- # than findings, so they stay on stderr exactly as they are a run that
230
- # selected nothing is still loud, in both renderers.
232
+ # `summary.files`. The sentence branches by STREAM, not by content: the
233
+ # human renderer reads it on stdout, the json renderer on stderr, where
234
+ # diagnostics about the linter already live the empty-selection
235
+ # warning is the precedent, SPGD-247 the doctrine. (SPGD-1134: this was
236
+ # an `elsif !json` suppression, which left a json run's selection
237
+ # unverifiable — the machine channel named neither its resolved base nor
238
+ # its untracked leg, so the bridge, which passes `--json`
239
+ # unconditionally, could only verify the diff ∪ untracked union by
240
+ # arithmetic on `summary.files`.) The warnings below are diagnostics
241
+ # about the linter rather than findings, so they stay on stderr exactly
242
+ # as they are — a run that selected nothing is still loud, in both
243
+ # renderers.
231
244
  #
232
245
  # The `--changed` count names its provenance: files can reach the
233
246
  # selection through the untracked leg (`file_selector.rb`), and "changed
@@ -239,15 +252,24 @@ module SpecGuard
239
252
  if selection.empty?
240
253
  @stderr.puts "specguard-lint: warning: selected 0 spec files — #{empty_reason(selection)}"
241
254
  @stderr.puts "specguard-lint: warning: #{selection.note}" if selection.note
242
- elsif !json
243
- untracked = selection.mode == :changed ? selection.stats&.untracked.to_i : 0
244
- @stdout.puts "specguard-lint: checked #{selection.count} spec file#{'s' unless selection.count == 1}" \
245
- "#{" changed since #{selection.base}" if selection.mode == :changed}" \
246
- "#{" under #{Dir.pwd}" if selection.mode == :all}" \
247
- "#{" including #{untracked} untracked" if untracked.positive?}"
255
+ else
256
+ json ? @stderr.puts(selection_line(selection)) : @stdout.puts(selection_line(selection))
248
257
  end
249
258
  end
250
259
 
260
+ # The selection sentence, built at exactly one site and written by both
261
+ # renderers — only the stream differs. The human line is the contract
262
+ # of record; the json renderer printing the SAME bytes is what makes
263
+ # "the machine reads the provenance the human does" a property of the
264
+ # code rather than a promise two copies could drift apart on.
265
+ def selection_line(selection)
266
+ untracked = selection.mode == :changed ? selection.stats&.untracked.to_i : 0
267
+ "specguard-lint: checked #{selection.count} spec file#{'s' unless selection.count == 1}" \
268
+ "#{" changed since #{selection.base}" if selection.mode == :changed}" \
269
+ "#{" under #{Dir.pwd}" if selection.mode == :all}" \
270
+ "#{" including #{untracked} untracked" if untracked.positive?}"
271
+ end
272
+
251
273
  # `:explicit` is absent by construction — an explicit Selection is only
252
274
  # built from a non-empty file list, so it can never be empty.
253
275
  def empty_reason(selection)
@@ -337,9 +359,11 @@ module SpecGuard
337
359
  # statements about the same numbers, and a linter whose two renderers can
338
360
  # disagree about how much it checked is worse than one that only prints
339
361
  # prose: the disagreement is unfalsifiable from outside the process.
340
- def report_results(results, files:, json:, ok:)
362
+ def report_results(results, files:, selected_files:, json:, ok:)
341
363
  annotations, unread = results.partition(&:line_scoped?)
342
364
 
365
+ report_zero_annotation_files(selected_files, annotations, unread)
366
+
343
367
  if json
344
368
  @stdout.puts JSONReporter.render(results, files: files, annotations: annotations.length, ok: ok)
345
369
  return
@@ -350,6 +374,43 @@ module SpecGuard
350
374
  @stdout.puts summary_line(annotations, unread)
351
375
  end
352
376
 
377
+ # The coverage note: which of the checked files were read and yielded no
378
+ # `@intent` annotation at all. Zero findings on stdout (or an empty
379
+ # `findings` list in the document) is otherwise ambiguous between
380
+ # "every checked file was annotated and valid" and "half the checked
381
+ # files carry nothing" — and the missing-annotation question is the most
382
+ # actionable one this tool touches, because the bridge agent reading
383
+ # `linter_stderr` has no other view into the repository's spec files.
384
+ #
385
+ # The boundary is the set-difference the partition above already
386
+ # supports: a file counts as covered when it yielded any line-scoped
387
+ # result (an annotation, valid or not — the SPGD-900 unreachable findings
388
+ # are line-scoped too and require an annotation to exist, so they cover
389
+ # their file) or when it could not be read at all (KIND_READ → `unread`).
390
+ # An unread file is NOT a zero-annotation file: it already gets its own
391
+ # summary clause and FAIL line, and calling it annotation-free would be
392
+ # the same overstatement `summary_line` refuses to make. Unreachable
393
+ # findings need a stacked annotation — impossible in a file with none —
394
+ # so `annotations ∪ unread` already covers every non-bare file and no
395
+ # third subtraction is needed.
396
+ #
397
+ # It is a NOTE on stderr, never a warning and never an exit code: "lint,
398
+ # don't require" (SPGD-12 §1) keeps a missing annotation a non-error.
399
+ # It is emitted before the json early-return so both renderers get it —
400
+ # stderr is renderer-indifferent (SPGD-247; the SPGD-1134 selection
401
+ # sentence is the landed precedent, and the bridge forwards stderr
402
+ # verbatim as `linter_stderr`). The prefix is deliberately not
403
+ # `specguard-lint: checked`, which the selection-sentence pin counts.
404
+ def report_zero_annotation_files(selected_files, annotations, unread)
405
+ bare = selected_files - annotations.map(&:file) - unread.map(&:file)
406
+ return if bare.empty?
407
+
408
+ verb = bare.length == 1 ? "carries" : "carry"
409
+ @stderr.puts "specguard-lint: note: #{bare.length} of #{selected_files.length} checked " \
410
+ "spec file#{'s' unless selected_files.length == 1} #{verb} no @intent " \
411
+ "annotations: #{bare.join(', ')}"
412
+ end
413
+
353
414
  # The summary line exists for one reason — so "checked nothing" can never
354
415
  # read as "all clean" — which makes overstating what was inspected its
355
416
  # own kind of lie. A file that could not be opened contributed no
@@ -7,5 +7,5 @@
7
7
  # framework-scoped version constant a lie, so the number moved here and the
8
8
  # rspec module now points at it for back-compat.
9
9
  module SpecGuard
10
- VERSION = "0.3.10"
10
+ VERSION = "0.3.12"
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: specguard-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.10
4
+ version: 0.3.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - specguard Agent
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-14 00:00:00.000000000 Z
11
+ date: 2026-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json