specguard-ruby 0.3.29 → 0.3.30
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 +4 -4
- data/README.md +22 -8
- data/bin/specguard-ingest +6 -6
- data/bin/specguard-lint +4 -4
- data/lib/specguard/{rspec → client}/annotation_lookup.rb +4 -4
- data/lib/specguard/{rspec → client}/annotation_scanner.rb +1 -1
- data/lib/specguard/{rspec → client}/cli.rb +2 -2
- data/lib/specguard/{rspec → client}/configuration.rb +2 -37
- data/lib/specguard/{rspec → client}/file_selector.rb +1 -1
- data/lib/specguard/{rspec → client}/finding.rb +2 -2
- data/lib/specguard/{rspec → client}/ingest_cli.rb +4 -4
- data/lib/specguard/{rspec → client}/ingest_reporter.rb +1 -1
- data/lib/specguard/{rspec → client}/json_reporter.rb +1 -1
- data/lib/specguard/{rspec → client}/linter.rb +1 -1
- data/lib/specguard/{rspec → client}/payload_normalizer.rb +1 -1
- data/lib/specguard/{rspec → client}/scanner.rb +22 -3
- data/lib/specguard/{rspec → client}/transport.rb +1 -1
- data/lib/specguard/{rspec → client}/validator_backend.rb +2 -2
- data/lib/specguard/client.rb +121 -0
- data/lib/specguard/minitest/reporter.rb +14 -15
- data/lib/specguard/rspec/formatter.rb +11 -5
- data/lib/specguard/rspec.rb +31 -62
- data/lib/specguard/version.rb +1 -1
- metadata +18 -17
- /data/lib/specguard/{rspec → client}/schemas/open-test-intent.v1.json +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 916a7326516ee737b20d0a0790fd6b6a0f1abe56404083ad67fe61d7d7fb5c46
|
|
4
|
+
data.tar.gz: 0b39ff94f46fb053ea659cb5f69f878bf86a3b04ed1e0e630394769bed0d6589
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d721e5d2d50648b3dfc7cf22ebbd2af16a9edd422feffa80b6d32d1092860c145ef6598919a802e3341a3eaa9fea6341fa26cab71f8abef33e0ff9d74d127b6
|
|
7
|
+
data.tar.gz: 77a24a2798088706388f6ef2ba7c6d4bd648d4fd152b1dcf55eb9e391409a0ece0ec3046d6b0b14bfaa24db03e68dac7e89d4eef340d6c34ba6b9512acdfda7a
|
data/README.md
CHANGED
|
@@ -44,13 +44,26 @@ be assumed, require it explicitly before the run:
|
|
|
44
44
|
bundle exec ruby -rminitest/specguard_plugin -e 'Minitest.extensions << "specguard"; load ARGV[0]' test/your_suite.rb
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
Configure it from Ruby, if the environment is not the right place, through the framework-neutral
|
|
48
|
+
`SpecGuard.configure` — in a `test_helper.rb`, before the run starts:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
SpecGuard.configure do |config|
|
|
52
|
+
config.branch = "release/2.0"
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`SpecGuard::RSpec.configure` is the same call under its RSpec-era spelling: both return the one
|
|
57
|
+
configuration object the reporter and the formatter read, so either works from either framework.
|
|
58
|
+
See [Shipping the run to SpecGuard](#shipping-the-run-to-specguard) for the settings.
|
|
59
|
+
|
|
47
60
|
## The linter — `specguard-lint`
|
|
48
61
|
|
|
49
62
|
Validates `# @intent:` annotations in changed (or all) `*_spec.rb` files against the OpenTestIntent
|
|
50
63
|
JSON Schema. Exits `1` on a malformed annotation — or a well-formed but unreachable one (stacked
|
|
51
64
|
above another comment-form `@intent:` line, so the one-line lookback never claims it; separated
|
|
52
|
-
from its `it` by one blank or ordinary comment line, which puts it out of the lookback's reach;
|
|
53
|
-
directly above an
|
|
65
|
+
from its example (`it`, `specify`, `xit`, `fit`, `example`, …) by one blank or ordinary comment line, which puts it out of the lookback's reach;
|
|
66
|
+
directly above an example that carries its own trailing `@intent:`, which extraction prefers, so the
|
|
54
67
|
comment is shadowed; or trailing on a `describe`/`context` group line, which no example's extraction can ever reach) — and
|
|
55
68
|
**never** fails on a *missing* one (adoption is opt-in and gradual).
|
|
56
69
|
|
|
@@ -337,8 +350,8 @@ Every way the backend can fail — the binary is missing, will not execute, exit
|
|
|
337
350
|
is not a verdict, or emits output that is not a report — is **exit 2**, the linter's "could not do
|
|
338
351
|
my job" code. It never becomes exit 1, which means "an annotation is malformed" — or well-formed
|
|
339
352
|
but unreachable: stacked above another comment-form `@intent:` line, so the one-line lookback
|
|
340
|
-
never claims it, separated from its
|
|
341
|
-
|
|
353
|
+
never claims it, separated from its example by one blank or ordinary comment line, shadowed by its
|
|
354
|
+
example's own trailing `@intent:`, or trailing on a `describe`/`context` group line, which no example's extraction can ever reach — and nothing else.
|
|
342
355
|
|
|
343
356
|
## The formatter — `SpecGuard::RSpecFormatter`
|
|
344
357
|
|
|
@@ -441,8 +454,9 @@ the one on the next line.
|
|
|
441
454
|
# SPECGUARD_SHARD_ID / SPECGUARD_OUTPUT_PATH / SPECGUARD_LOCAL_OUTPUT_PATH
|
|
442
455
|
# override any of it.
|
|
443
456
|
#
|
|
444
|
-
# Assign a value here only when it is one neither source can know
|
|
445
|
-
SpecGuard::RSpec.configure
|
|
457
|
+
# Assign a value here only when it is one neither source can know
|
|
458
|
+
# (`SpecGuard::RSpec.configure` is the same call, under its RSpec-era name):
|
|
459
|
+
SpecGuard.configure do |config|
|
|
446
460
|
config.branch = "release/2.0"
|
|
447
461
|
end
|
|
448
462
|
```
|
|
@@ -462,7 +476,7 @@ export SPECGUARD_TIMEOUT=10 # optional; seconds, applied to connect and
|
|
|
462
476
|
|
|
463
477
|
```ruby
|
|
464
478
|
# ...or in Ruby, if you would rather not use the environment
|
|
465
|
-
SpecGuard
|
|
479
|
+
SpecGuard.configure do |config|
|
|
466
480
|
config.endpoint = "https://specguard.example.com"
|
|
467
481
|
config.api_key = ENV["SPECGUARD_API_KEY"]
|
|
468
482
|
config.timeout = 10
|
|
@@ -474,7 +488,7 @@ is written to `log/test_results.local.jsonl` — the local development record,
|
|
|
474
488
|
kept apart from the replay queue — so local development needs no opt-out, and a
|
|
475
489
|
fork with no secret configured behaves like a laptop rather than like a broken
|
|
476
490
|
build. The local file's name is configurable via `SPECGUARD_LOCAL_OUTPUT_PATH`
|
|
477
|
-
(or `SpecGuard
|
|
491
|
+
(or `SpecGuard.configure { |c| c.local_output_path = ... }`). The write
|
|
478
492
|
itself is silent when it succeeds — the ordinary case on any machine that can
|
|
479
493
|
create `log/` — and it usually does.
|
|
480
494
|
|
data/bin/specguard-ingest
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
# the suite already finished, instead of re-running the suite.
|
|
10
10
|
#
|
|
11
11
|
# Exit codes are the contract, and they are `specguard-lint`'s reasoning
|
|
12
|
-
# transferred (see SpecGuard::
|
|
12
|
+
# transferred (see SpecGuard::Client::IngestCLI):
|
|
13
13
|
#
|
|
14
14
|
# 0 every line was accepted
|
|
15
15
|
# 1 at least one line was refused by the endpoint
|
|
16
16
|
# 2 this tool could not do its job — misuse, or the tool itself broken
|
|
17
17
|
#
|
|
18
|
-
# SpecGuard::
|
|
18
|
+
# SpecGuard::Client::IngestCLI#run is written to *return* one of those three and
|
|
19
19
|
# never to raise. The `require`s below are the one thing outside its reach, so
|
|
20
20
|
# they get the same treatment here: a gem that cannot even load is the tool
|
|
21
21
|
# being broken, which is a 2. Left bare, Ruby would exit 1 and report that the
|
|
@@ -23,18 +23,18 @@
|
|
|
23
23
|
|
|
24
24
|
# Allow running from a source checkout without bundler by putting the local
|
|
25
25
|
# lib/ on the load path. When the gem is installed (or run under
|
|
26
|
-
# `bundle exec`), specguard/
|
|
26
|
+
# `bundle exec`), specguard/client resolves through the normal load path.
|
|
27
27
|
source_lib = File.expand_path("../lib", __dir__)
|
|
28
28
|
$LOAD_PATH.unshift(source_lib) unless $LOAD_PATH.include?(source_lib)
|
|
29
29
|
|
|
30
30
|
begin
|
|
31
|
-
require "specguard/
|
|
31
|
+
require "specguard/client"
|
|
32
32
|
# By its own path, deliberately. `net/http`, `uri` and `zlib` are not on the
|
|
33
33
|
# linter's load chain and this command is the reason to pay for them.
|
|
34
|
-
require "specguard/
|
|
34
|
+
require "specguard/client/ingest_cli"
|
|
35
35
|
rescue ScriptError, StandardError => e
|
|
36
36
|
warn "specguard-ingest: error: could not load specguard-ruby: #{e.class}: #{e.message}"
|
|
37
37
|
exit 2
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
exit SpecGuard::
|
|
40
|
+
exit SpecGuard::Client::IngestCLI.new.run(ARGV)
|
data/bin/specguard-lint
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# 1 one or more annotations are malformed
|
|
12
12
|
# 2 the linter could not do its job — misuse, or the tool itself broken
|
|
13
13
|
#
|
|
14
|
-
# SpecGuard::
|
|
14
|
+
# SpecGuard::Client::CLI#run is written to *return* one of those three and never
|
|
15
15
|
# to raise; see its class comment for why that takes deliberate effort in Ruby.
|
|
16
16
|
# The `require` below is the one thing outside its reach, so it gets the same
|
|
17
17
|
# treatment here: a gem that cannot even load is the linter being broken, which
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
|
|
21
21
|
# Allow running from a source checkout without bundler by putting the local
|
|
22
22
|
# lib/ on the load path. When the gem is installed (or run under
|
|
23
|
-
# `bundle exec`), specguard/
|
|
23
|
+
# `bundle exec`), specguard/client resolves through the normal load path.
|
|
24
24
|
source_lib = File.expand_path("../lib", __dir__)
|
|
25
25
|
$LOAD_PATH.unshift(source_lib) unless $LOAD_PATH.include?(source_lib)
|
|
26
26
|
|
|
27
27
|
begin
|
|
28
|
-
require "specguard/
|
|
28
|
+
require "specguard/client"
|
|
29
29
|
rescue ScriptError, StandardError => e
|
|
30
30
|
warn "specguard-lint: error: could not load specguard-ruby: #{e.class}: #{e.message}"
|
|
31
31
|
exit 2
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
exit SpecGuard::
|
|
34
|
+
exit SpecGuard::Client::CLI.new.run(ARGV)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# with what?*
|
|
5
5
|
#
|
|
6
6
|
# It is the formatter's half of the annotation story. The linter's half already
|
|
7
|
-
# exists and is not duplicated here — {SpecGuard::
|
|
7
|
+
# exists and is not duplicated here — {SpecGuard::Client::Scanner} finds every
|
|
8
8
|
# `@intent:` in a file, captures its payload string-aware and parses it, and
|
|
9
9
|
# the `validate-intent` binary decides whether the result is valid. This class
|
|
10
10
|
# consumes both. Writing a second extractor would guarantee that the tool
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
# platform eventually disagree about what an annotation *is*.
|
|
13
13
|
#
|
|
14
14
|
# Requiring the linter's chain from here is safe in the direction that matters:
|
|
15
|
-
# `lib/specguard/
|
|
15
|
+
# `lib/specguard/client.rb` does not require `rspec/core`, so pulling it in from
|
|
16
16
|
# the formatter's side keeps `bin/specguard-lint` loadable on a machine with no
|
|
17
17
|
# RSpec installed. spec/specguard/rspec/formatter_loading_spec.rb pins that in
|
|
18
18
|
# both directions.
|
|
19
|
-
require_relative "../
|
|
19
|
+
require_relative "../client"
|
|
20
20
|
|
|
21
21
|
module SpecGuard
|
|
22
|
-
module
|
|
22
|
+
module Client
|
|
23
23
|
# == The lookback rule (SPGD-12 §2)
|
|
24
24
|
#
|
|
25
25
|
# `example.metadata[:line_number]` is the line the `it` is on. An annotation
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
|
|
5
5
|
module SpecGuard
|
|
6
|
-
module
|
|
6
|
+
module Client
|
|
7
7
|
# `specguard-lint`'s command line, and the whole of the exit contract.
|
|
8
8
|
#
|
|
9
9
|
# == The contract, and the reason it needs defending
|
|
@@ -358,7 +358,7 @@ module SpecGuard
|
|
|
358
358
|
end
|
|
359
359
|
|
|
360
360
|
# The fence arm of the `:all` empty reason: every matching file the walk
|
|
361
|
-
# found was inside a {SpecGuard::
|
|
361
|
+
# found was inside a {SpecGuard::Client::FileSelector::SKIPPED_DIRECTORIES}
|
|
362
362
|
# directory, so the silence is the fence's, not the tree's.
|
|
363
363
|
def all_fenced_reason(selection)
|
|
364
364
|
n = selection.skipped
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SpecGuard
|
|
4
|
-
module
|
|
4
|
+
module Client
|
|
5
5
|
# Where the checkout is sitting — which commit, and which branch — asked of
|
|
6
6
|
# git directly.
|
|
7
7
|
#
|
|
@@ -120,7 +120,7 @@ module SpecGuard
|
|
|
120
120
|
# The escape hatch stays open on top of all of it, for a value neither
|
|
121
121
|
# source can know:
|
|
122
122
|
#
|
|
123
|
-
# SpecGuard
|
|
123
|
+
# SpecGuard.configure do |config|
|
|
124
124
|
# config.branch = "release/2.0"
|
|
125
125
|
# end
|
|
126
126
|
#
|
|
@@ -433,40 +433,5 @@ module SpecGuard
|
|
|
433
433
|
parsed
|
|
434
434
|
end
|
|
435
435
|
end
|
|
436
|
-
|
|
437
|
-
class << self
|
|
438
|
-
# The process-wide formatter configuration.
|
|
439
|
-
#
|
|
440
|
-
# Built on first use rather than at load time so that a `configure` block
|
|
441
|
-
# in a `spec_helper.rb` and a variable exported by a CI job describe the
|
|
442
|
-
# same object regardless of which the interpreter reached first.
|
|
443
|
-
#
|
|
444
|
-
# @return [Configuration]
|
|
445
|
-
def configuration
|
|
446
|
-
@configuration ||= Configuration.new
|
|
447
|
-
end
|
|
448
|
-
|
|
449
|
-
# Configure the formatter.
|
|
450
|
-
#
|
|
451
|
-
# SpecGuard::RSpec.configure do |config|
|
|
452
|
-
# config.branch = "release/2.0"
|
|
453
|
-
# end
|
|
454
|
-
#
|
|
455
|
-
# @yieldparam configuration [Configuration]
|
|
456
|
-
# @return [Configuration] the configuration, block or no block
|
|
457
|
-
def configure
|
|
458
|
-
yield configuration if block_given?
|
|
459
|
-
configuration
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
# Drop the memoized configuration so the next read re-seeds from ENV.
|
|
463
|
-
# Exists for tests, and for the rare caller that changes the environment
|
|
464
|
-
# after this file was loaded.
|
|
465
|
-
#
|
|
466
|
-
# @return [void]
|
|
467
|
-
def reset_configuration!
|
|
468
|
-
@configuration = nil
|
|
469
|
-
end
|
|
470
|
-
end
|
|
471
436
|
end
|
|
472
437
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SpecGuard
|
|
4
|
-
module
|
|
4
|
+
module Client
|
|
5
5
|
# One `@intent:` annotation, located and (where possible) parsed.
|
|
6
6
|
#
|
|
7
7
|
# Exactly one of `intent` and `problem` is non-nil:
|
|
@@ -25,7 +25,7 @@ module SpecGuard
|
|
|
25
25
|
#
|
|
26
26
|
# NOTE: this is a subclass of an anonymous `Data.define` rather than a
|
|
27
27
|
# `Data.define do ... end` block, because a constant assigned inside that
|
|
28
|
-
# block binds to the *lexically* enclosing module (SpecGuard::
|
|
28
|
+
# block binds to the *lexically* enclosing module (SpecGuard::Client) rather
|
|
29
29
|
# than to the value class — so KIND_EXTRACTION below would silently not be
|
|
30
30
|
# `Finding::KIND_EXTRACTION`.
|
|
31
31
|
class Finding < Data.define(:file, :line, :intent, :problem, :kind)
|
|
@@ -3,21 +3,21 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "optparse"
|
|
5
5
|
|
|
6
|
-
require_relative "../
|
|
6
|
+
require_relative "../client"
|
|
7
7
|
require_relative "ingest_reporter"
|
|
8
8
|
require_relative "transport"
|
|
9
9
|
|
|
10
10
|
# `specguard-ingest`'s command line — the other end of `log/test_results.jsonl`.
|
|
11
11
|
#
|
|
12
|
-
# == Why this file is not on `require "specguard/
|
|
12
|
+
# == Why this file is not on `require "specguard/client"`'s chain
|
|
13
13
|
#
|
|
14
14
|
# `specguard-lint` runs on machines that never make a network call, and putting
|
|
15
15
|
# this file on the umbrella's chain would put `net/http`, `uri` and `zlib` on
|
|
16
16
|
# the linter's load path to serve a command it never invokes. So it is loaded by
|
|
17
17
|
# its own path, exactly as the formatter is and for the same reason —
|
|
18
|
-
# `require "specguard/
|
|
18
|
+
# `require "specguard/client/ingest_cli"`, which `bin/specguard-ingest` does.
|
|
19
19
|
module SpecGuard
|
|
20
|
-
module
|
|
20
|
+
module Client
|
|
21
21
|
# Replays a saved run: reads a `log/test_results.jsonl` back and re-delivers
|
|
22
22
|
# each line through the {Transport} this gem already ships.
|
|
23
23
|
#
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
5
|
module SpecGuard
|
|
6
|
-
module
|
|
6
|
+
module Client
|
|
7
7
|
# Runs the discovery pipeline over source files and returns {Finding}s.
|
|
8
8
|
#
|
|
9
9
|
# Pipeline, per `@intent:` token:
|
|
@@ -354,6 +354,18 @@ module SpecGuard
|
|
|
354
354
|
# example on the NEXT line may claim ({AnnotationLookup::COMMENT_LINE}).
|
|
355
355
|
COMMENT_INTENT_LINE = /\A\s*#.*@intent:/
|
|
356
356
|
|
|
357
|
+
# The one source of the example-call vocabulary, shared by
|
|
358
|
+
# {EXAMPLE_LINE} and {EXAMPLE_ON_LINE}. `it`/`specify` are matched on
|
|
359
|
+
# `\b` alone; the other eight keywords also carry a `(?![:.])`
|
|
360
|
+
# lookahead, which is load-bearing: `example.run` (the around-hook
|
|
361
|
+
# idiom), `example.metadata[:x]` and `focus: true` are not example
|
|
362
|
+
# definitions and must never anchor. (`\b` already excludes `fitness`
|
|
363
|
+
# and `example_group`, a GROUP_LINE keyword.) `skip` and `pending` are
|
|
364
|
+
# deliberately EXCLUDED: at line start they are the runtime call inside
|
|
365
|
+
# an example body, not an example definition.
|
|
366
|
+
EXAMPLE_CALL_SOURCE =
|
|
367
|
+
"(?:(?:it|specify)\\b|(?:example|focus|fexample|fit|fspecify|xexample|xit|xspecify)\\b(?![:.]))"
|
|
368
|
+
|
|
357
369
|
# The line an annotation run may be claimed from: the first example
|
|
358
370
|
# keyword. The lookback rule is `example.metadata[:line_number] - 1`, so
|
|
359
371
|
# only the comment form on the line immediately above an example is
|
|
@@ -367,7 +379,14 @@ module SpecGuard
|
|
|
367
379
|
# example: stacked lines above it (SPGD-897), the trailing form on
|
|
368
380
|
# a group line directly above it (SPGD-1510), and a comment-form run
|
|
369
381
|
# separated from it by one blank/comment line (SPGD-1554).
|
|
370
|
-
|
|
382
|
+
#
|
|
383
|
+
# The vocabulary is every example method RSpec core defines
|
|
384
|
+
# (`example focus fexample fit fspecify xexample xit xspecify`, besides
|
|
385
|
+
# `it`/`specify`): lookup is keyed by `example.metadata[:line_number]`, so
|
|
386
|
+
# an annotation stacked/separated/shadowed above `xit` is exactly as dead
|
|
387
|
+
# as one above `it`. Both this anchor and {EXAMPLE_ON_LINE} are built
|
|
388
|
+
# from {EXAMPLE_CALL_SOURCE}, so anchor and exemption cannot drift.
|
|
389
|
+
EXAMPLE_LINE = /\A\s*#{EXAMPLE_CALL_SOURCE}/
|
|
371
390
|
|
|
372
391
|
# A blank (whitespace-only) line — one of the two interleaves the
|
|
373
392
|
# separated pass reads. `\n` is whitespace, so a `text.lines` element
|
|
@@ -574,7 +593,7 @@ module SpecGuard
|
|
|
574
593
|
# only ever a missed flag: prose can turn the exemption ON, never a
|
|
575
594
|
# real example OFF. The one-line example's own payload is never
|
|
576
595
|
# consulted, so a payload can never exempt anything.
|
|
577
|
-
EXAMPLE_ON_LINE = /(?:\bdo\b|\{|;)\s
|
|
596
|
+
EXAMPLE_ON_LINE = /(?:\bdo\b|\{|;)\s*#{EXAMPLE_CALL_SOURCE}/
|
|
578
597
|
|
|
579
598
|
# @param text [String] source of one file
|
|
580
599
|
# @param file [String] path to record on each Finding
|
|
@@ -7,7 +7,7 @@ require "net/http"
|
|
|
7
7
|
require "open3"
|
|
8
8
|
|
|
9
9
|
module SpecGuard
|
|
10
|
-
module
|
|
10
|
+
module Client
|
|
11
11
|
# The Go validator backend: `validate-intent --source --json`, and since
|
|
12
12
|
# SPGD-867 the ONLY validator `specguard-lint` has.
|
|
13
13
|
#
|
|
@@ -36,7 +36,7 @@ module SpecGuard
|
|
|
36
36
|
# consumers are `Formatter` and `Transport`, and `CLI` does not reference
|
|
37
37
|
# it at all — so threading the lint backend through it would make the
|
|
38
38
|
# linter depend on the telemetry object solely to read one env var. It also
|
|
39
|
-
# memoizes process-wide (`SpecGuard
|
|
39
|
+
# memoizes process-wide (`SpecGuard.configuration`), with
|
|
40
40
|
# `reset_configuration!` existing only for tests, which is a poor fit for
|
|
41
41
|
# something a spec wants to vary per example. {CLI} takes an `env:` instead
|
|
42
42
|
# and asks this module, so the seam is one hash away in a test and one
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "version"
|
|
4
|
+
|
|
5
|
+
module SpecGuard
|
|
6
|
+
module Client
|
|
7
|
+
# The Ruby client for SpecGuard.
|
|
8
|
+
#
|
|
9
|
+
# The linter is complete end to end: it finds `@intent:` annotations in
|
|
10
|
+
# spec files, captures each payload string-aware, normalizes PROTOCOL.md
|
|
11
|
+
# §1's permissive syntax into strict JSON, validates the result against the
|
|
12
|
+
# vendored OpenTestIntent schema, and reports violations in the reference
|
|
13
|
+
# tool's own grammar under the 0/1/2 exit contract (see {CLI}).
|
|
14
|
+
#
|
|
15
|
+
# Everything under `SpecGuard::Client` is framework-free: the RSpec
|
|
16
|
+
# formatter and the Minitest reporter are the framework halves of the
|
|
17
|
+
# client and both build on it. Neither is required from here — `rspec`
|
|
18
|
+
# is a development dependency of this gem, not a runtime one, and
|
|
19
|
+
# `bin/specguard-lint` loads this file on machines that may have no RSpec
|
|
20
|
+
# at all. Requiring `rspec/core` from this chain would turn a missing test
|
|
21
|
+
# framework into a broken linter. Load the formatter by its own path when
|
|
22
|
+
# you want it — `require "specguard/rspec/formatter"` — and see that file
|
|
23
|
+
# for the opt-in wiring.
|
|
24
|
+
class Error < StandardError; end
|
|
25
|
+
|
|
26
|
+
# A source line could not be scanned (unterminated string or object
|
|
27
|
+
# literal). Internal to the scanner: it is caught and turned into a
|
|
28
|
+
# {Finding}'s `problem` rather than reaching a caller.
|
|
29
|
+
class ScanError < Error; end
|
|
30
|
+
|
|
31
|
+
# The tool was invoked in a way that cannot work — `--changed` outside a git
|
|
32
|
+
# repository, say. Distinct from "the annotations are bad": the exit-code
|
|
33
|
+
# contract makes this a 2, and it is typed here so the CLI can map it
|
|
34
|
+
# without re-deriving the distinction from an error message.
|
|
35
|
+
class UsageError < Error; end
|
|
36
|
+
|
|
37
|
+
# The Go validator backend could not produce a verdict — the binary named
|
|
38
|
+
# by `SPECGUARD_VALIDATE_INTENT`, or the default binary the backend
|
|
39
|
+
# resolves when that is blank, is missing, will not execute, exited with a
|
|
40
|
+
# code that is not a verdict, or emitted something this cannot read as a
|
|
41
|
+
# report.
|
|
42
|
+
#
|
|
43
|
+
# Typed separately from {UsageError} because the two are different
|
|
44
|
+
# accusations ("you invoked me wrongly" vs "the tool I was told to use is
|
|
45
|
+
# broken"), and rescued *beside* it in {CLI#run} because the answer to both
|
|
46
|
+
# is the same and non-negotiable: exit 2, never 1. See {ValidatorBackend}.
|
|
47
|
+
class ValidatorError < Error; end
|
|
48
|
+
|
|
49
|
+
# The vendored canonical OpenTestIntent schema, copied byte-for-byte from
|
|
50
|
+
# open-test-intent so this gem has **no cross-repo runtime dependency**.
|
|
51
|
+
# It ships packaged (it lives under `lib/`, which the gemspec includes);
|
|
52
|
+
# the spec fixtures deliberately do not, so nothing here may be
|
|
53
|
+
# load-bearing at runtime.
|
|
54
|
+
#
|
|
55
|
+
# Since the SPGD-867 cutover nothing VALIDATES against this copy — every
|
|
56
|
+
# verdict comes from the `validate-intent` binary's own compiled-in
|
|
57
|
+
# schema. It survives because {ValidatorBackend}'s schema-contract check
|
|
58
|
+
# digests it at runtime and refuses a binary that would enforce different
|
|
59
|
+
# bytes: the two halves of the seam must keep meeting somewhere, and this
|
|
60
|
+
# file is where the gem's half lives.
|
|
61
|
+
SCHEMA_PATH = File.expand_path("client/schemas/open-test-intent.v1.json", __dir__).freeze
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
module SpecGuard
|
|
66
|
+
class << self
|
|
67
|
+
# The process-wide client configuration — one object shared by the RSpec
|
|
68
|
+
# formatter, the Minitest reporter and the CLIs, whatever framework a
|
|
69
|
+
# consumer runs.
|
|
70
|
+
#
|
|
71
|
+
# Built on first use rather than at load time so that a `configure` block
|
|
72
|
+
# in a `spec_helper.rb` / `test_helper.rb` and a variable exported by a CI
|
|
73
|
+
# job describe the same object regardless of which the interpreter reached
|
|
74
|
+
# first.
|
|
75
|
+
#
|
|
76
|
+
# {SpecGuard::RSpec.configure} (the documented RSpec entry point) delegates
|
|
77
|
+
# here and returns this same object — there is exactly one configuration,
|
|
78
|
+
# not one per entry point.
|
|
79
|
+
#
|
|
80
|
+
# @return [Client::Configuration]
|
|
81
|
+
def configuration
|
|
82
|
+
# Loaded here, not at the top of this file: the linter's chain
|
|
83
|
+
# (`require "specguard/client"`) must stay free of the formatter's
|
|
84
|
+
# configuration/transport half — formatter_loading_spec pins it.
|
|
85
|
+
require_relative "client/configuration"
|
|
86
|
+
@configuration ||= Client::Configuration.new
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Configure the client.
|
|
90
|
+
#
|
|
91
|
+
# SpecGuard.configure do |config|
|
|
92
|
+
# config.branch = "release/2.0"
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# @yieldparam configuration [Client::Configuration]
|
|
96
|
+
# @return [Client::Configuration] the configuration, block or no block
|
|
97
|
+
def configure
|
|
98
|
+
yield configuration if block_given?
|
|
99
|
+
configuration
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Drop the memoized configuration so the next read re-seeds from ENV.
|
|
103
|
+
# Exists for tests, and for the rare caller that changes the environment
|
|
104
|
+
# after this file was loaded.
|
|
105
|
+
#
|
|
106
|
+
# @return [void]
|
|
107
|
+
def reset_configuration!
|
|
108
|
+
@configuration = nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
require_relative "client/finding"
|
|
114
|
+
require_relative "client/annotation_scanner"
|
|
115
|
+
require_relative "client/payload_normalizer"
|
|
116
|
+
require_relative "client/scanner"
|
|
117
|
+
require_relative "client/file_selector"
|
|
118
|
+
require_relative "client/linter"
|
|
119
|
+
require_relative "client/json_reporter"
|
|
120
|
+
require_relative "client/validator_backend"
|
|
121
|
+
require_relative "client/cli"
|
|
@@ -4,11 +4,11 @@ require "json"
|
|
|
4
4
|
require "fileutils"
|
|
5
5
|
|
|
6
6
|
# Configuration and Transport are the framework-free halves of the client
|
|
7
|
-
# (env resolution, wire delivery, the never-raise contract)
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
require_relative "../
|
|
7
|
+
# (env resolution, wire delivery, the never-raise contract), under
|
|
8
|
+
# `SpecGuard::Client`. Required here rather than assumed because nothing else
|
|
9
|
+
# loads them on a minitest-only consumer's machine.
|
|
10
|
+
require_relative "../client"
|
|
11
|
+
require_relative "../client/configuration"
|
|
12
12
|
|
|
13
13
|
# The annotation lookup is the third framework-free half: what counts as a
|
|
14
14
|
# test's annotation is a property of the source line, not of the framework
|
|
@@ -17,20 +17,19 @@ require_relative "../rspec/configuration"
|
|
|
17
17
|
# already warns that a second extractor guarantees scanner and reporter
|
|
18
18
|
# eventually disagree about what an annotation is). Requiring it pulls the
|
|
19
19
|
# linter's chain — the same direction-safe require the formatter itself makes;
|
|
20
|
-
# `specguard/
|
|
20
|
+
# `specguard/client` does not require `rspec/core`, so a minitest-only machine
|
|
21
21
|
# stays loadable.
|
|
22
|
-
require_relative "../
|
|
22
|
+
require_relative "../client/annotation_lookup"
|
|
23
23
|
|
|
24
24
|
# The Minitest half of `specguard-ruby`. Everything the RSpec formatter knows
|
|
25
25
|
# about the platform — the envelope's field names, the transport's
|
|
26
26
|
# never-raise contract, the switch that a missing key is — is framework-free
|
|
27
|
-
# knowledge that already lives in `SpecGuard::
|
|
28
|
-
# `SpecGuard::
|
|
29
|
-
# ship first, neither containing any RSpec). This adapter contributes only the
|
|
27
|
+
# knowledge that already lives in `SpecGuard::Client::Configuration` and
|
|
28
|
+
# `SpecGuard::Client::Transport`. This adapter contributes only the
|
|
30
29
|
# part that is genuinely Minitest's: turning `Minitest::Result` objects into
|
|
31
30
|
# the same row shape the RSpec formatter emits, at the moment Minitest hands
|
|
32
31
|
# them to a reporter — and attaching each test's annotation the same way the
|
|
33
|
-
# formatter does, through the shared {SpecGuard::
|
|
32
|
+
# formatter does, through the shared {SpecGuard::Client::AnnotationLookup} and
|
|
34
33
|
# its one-line lookback (SPGD-12 §2), so a Minitest row carries the same
|
|
35
34
|
# `status` / `intent` pair a RSpec row does.
|
|
36
35
|
#
|
|
@@ -78,7 +77,7 @@ module SpecGuard
|
|
|
78
77
|
end
|
|
79
78
|
end
|
|
80
79
|
|
|
81
|
-
def initialize(configuration: SpecGuard
|
|
80
|
+
def initialize(configuration: SpecGuard.configuration,
|
|
82
81
|
transport: nil, output: $stderr,
|
|
83
82
|
annotations: nil,
|
|
84
83
|
clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
|
|
@@ -108,7 +107,7 @@ module SpecGuard
|
|
|
108
107
|
# binds one root, hands it to the default lookup, and pins rspec-core's
|
|
109
108
|
# lazily memoized relativization regex to the same moment.)
|
|
110
109
|
root = self.class.repo_root
|
|
111
|
-
@annotations = annotations || SpecGuard::
|
|
110
|
+
@annotations = annotations || SpecGuard::Client::AnnotationLookup.new(root: root)
|
|
112
111
|
@clock = clock
|
|
113
112
|
@rows = []
|
|
114
113
|
@started_at = nil
|
|
@@ -244,8 +243,8 @@ module SpecGuard
|
|
|
244
243
|
end
|
|
245
244
|
|
|
246
245
|
def transport_for
|
|
247
|
-
require_relative "../
|
|
248
|
-
SpecGuard::
|
|
246
|
+
require_relative "../client/transport"
|
|
247
|
+
SpecGuard::Client::Transport.new(
|
|
249
248
|
endpoint: @configuration.endpoint,
|
|
250
249
|
api_key: @configuration.api_key,
|
|
251
250
|
timeout: @configuration.timeout
|
|
@@ -40,13 +40,19 @@ require "rspec/core/formatters/base_formatter"
|
|
|
40
40
|
require "json"
|
|
41
41
|
require "fileutils"
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
# `specguard/rspec` is what defines `SpecGuard::RSpec` — the namespace whose
|
|
44
|
+
# existence is the shadowing hazard documented below, and the home of the
|
|
45
|
+
# documented `SpecGuard::RSpec.configure` entry point. It is required here so
|
|
46
|
+
# that both stay true however this file is reached, now that the client chain
|
|
47
|
+
# no longer pulls it in.
|
|
48
|
+
require_relative "../rspec"
|
|
49
|
+
require_relative "../client/configuration"
|
|
50
|
+
require_relative "../client/transport"
|
|
45
51
|
# Brings the linter's discovery chain with it (Scanner, Finding, Schema). That
|
|
46
52
|
# direction is safe — `specguard/rspec` does not require `rspec/core`, so the
|
|
47
53
|
# linter stays loadable without RSpec; it is only the reverse that would break
|
|
48
54
|
# packaging.
|
|
49
|
-
require_relative "annotation_lookup"
|
|
55
|
+
require_relative "../client/annotation_lookup"
|
|
50
56
|
|
|
51
57
|
module SpecGuard
|
|
52
58
|
# == A NOTE ON CONSTANT RESOLUTION — read this before editing
|
|
@@ -338,7 +344,7 @@ module SpecGuard
|
|
|
338
344
|
root = self.class.repo_root
|
|
339
345
|
::RSpec::Core::Metadata.relative_path_regex
|
|
340
346
|
@error_stream = error_stream
|
|
341
|
-
@annotations = annotations || SpecGuard::
|
|
347
|
+
@annotations = annotations || SpecGuard::Client::AnnotationLookup.new(root: root)
|
|
342
348
|
@specs = []
|
|
343
349
|
@warned = false
|
|
344
350
|
# Stamped here rather than from a `start` hook on purpose. `:start` is not
|
|
@@ -1031,7 +1037,7 @@ module SpecGuard
|
|
|
1031
1037
|
end
|
|
1032
1038
|
|
|
1033
1039
|
def transport_for(configuration)
|
|
1034
|
-
SpecGuard::
|
|
1040
|
+
SpecGuard::Client::Transport.new(
|
|
1035
1041
|
endpoint: configuration.endpoint,
|
|
1036
1042
|
api_key: configuration.api_key,
|
|
1037
1043
|
timeout: configuration.timeout
|
data/lib/specguard/rspec.rb
CHANGED
|
@@ -1,72 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "rspec/version"
|
|
4
|
+
require_relative "client"
|
|
4
5
|
|
|
5
6
|
module SpecGuard
|
|
7
|
+
# The RSpec-facing entry point of the gem.
|
|
8
|
+
#
|
|
9
|
+
# The framework-free client — scanner, linter, CLIs, configuration,
|
|
10
|
+
# transport — lives under {SpecGuard::Client}; the one component that is
|
|
11
|
+
# genuinely RSpec's is {SpecGuard::RSpecFormatter}
|
|
12
|
+
# (`require "specguard/rspec/formatter"`). What remains here is the
|
|
13
|
+
# documented `SpecGuard::RSpec.configure` entry point, kept working for
|
|
14
|
+
# anybody who configures the client from a `spec_helper.rb`.
|
|
15
|
+
#
|
|
16
|
+
# It is a thin delegate, not a second configuration: all three methods
|
|
17
|
+
# forward to {SpecGuard.configure} / {SpecGuard.configuration} /
|
|
18
|
+
# {SpecGuard.reset_configuration!}, so `SpecGuard::RSpec.configure` and
|
|
19
|
+
# `SpecGuard.configure` return the very same {Client::Configuration} object.
|
|
20
|
+
# A Minitest consumer has no reason to say `RSpec` and should configure
|
|
21
|
+
# through `SpecGuard.configure`.
|
|
6
22
|
module RSpec
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# tool's own grammar under the 0/1/2 exit contract (see {CLI}).
|
|
14
|
-
#
|
|
15
|
-
# The RSpec formatter (`SpecGuard::RSpecFormatter`) is the other half of
|
|
16
|
-
# the client, and it is deliberately **not** required from here: `rspec` is
|
|
17
|
-
# a development dependency of this gem, not a runtime one, and
|
|
18
|
-
# `bin/specguard-lint` loads this file on machines that may have no RSpec
|
|
19
|
-
# at all. Requiring `rspec/core` from this chain would turn a missing test
|
|
20
|
-
# framework into a broken linter. Load it by its own path when you want it
|
|
21
|
-
# — `require "specguard/rspec/formatter"` — and see that file for the
|
|
22
|
-
# opt-in wiring.
|
|
23
|
-
class Error < StandardError; end
|
|
23
|
+
class << self
|
|
24
|
+
# @yieldparam configuration [Client::Configuration]
|
|
25
|
+
# @return [Client::Configuration] the same object {SpecGuard.configure} returns
|
|
26
|
+
def configure(&)
|
|
27
|
+
SpecGuard.configure(&)
|
|
28
|
+
end
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
# @return [Client::Configuration]
|
|
31
|
+
def configuration
|
|
32
|
+
SpecGuard.configuration
|
|
33
|
+
end
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# The Go validator backend could not produce a verdict — the binary named
|
|
37
|
-
# by `SPECGUARD_VALIDATE_INTENT`, or the default binary the backend
|
|
38
|
-
# resolves when that is blank, is missing, will not execute, exited with a
|
|
39
|
-
# code that is not a verdict, or emitted something this cannot read as a
|
|
40
|
-
# report.
|
|
41
|
-
#
|
|
42
|
-
# Typed separately from {UsageError} because the two are different
|
|
43
|
-
# accusations ("you invoked me wrongly" vs "the tool I was told to use is
|
|
44
|
-
# broken"), and rescued *beside* it in {CLI#run} because the answer to both
|
|
45
|
-
# is the same and non-negotiable: exit 2, never 1. See {ValidatorBackend}.
|
|
46
|
-
class ValidatorError < Error; end
|
|
47
|
-
|
|
48
|
-
# The vendored canonical OpenTestIntent schema, copied byte-for-byte from
|
|
49
|
-
# open-test-intent so this gem has **no cross-repo runtime dependency**.
|
|
50
|
-
# It ships packaged (it lives under `lib/`, which the gemspec includes);
|
|
51
|
-
# the spec fixtures deliberately do not, so nothing here may be
|
|
52
|
-
# load-bearing at runtime.
|
|
53
|
-
#
|
|
54
|
-
# Since the SPGD-867 cutover nothing VALIDATES against this copy — every
|
|
55
|
-
# verdict comes from the `validate-intent` binary's own compiled-in
|
|
56
|
-
# schema. It survives because {ValidatorBackend}'s schema-contract check
|
|
57
|
-
# digests it at runtime and refuses a binary that would enforce different
|
|
58
|
-
# bytes: the two halves of the seam must keep meeting somewhere, and this
|
|
59
|
-
# file is where the gem's half lives.
|
|
60
|
-
SCHEMA_PATH = File.expand_path("rspec/schemas/open-test-intent.v1.json", __dir__).freeze
|
|
35
|
+
# @return [void]
|
|
36
|
+
def reset_configuration!
|
|
37
|
+
SpecGuard.reset_configuration!
|
|
38
|
+
end
|
|
39
|
+
end
|
|
61
40
|
end
|
|
62
41
|
end
|
|
63
|
-
|
|
64
|
-
require_relative "rspec/finding"
|
|
65
|
-
require_relative "rspec/annotation_scanner"
|
|
66
|
-
require_relative "rspec/payload_normalizer"
|
|
67
|
-
require_relative "rspec/scanner"
|
|
68
|
-
require_relative "rspec/file_selector"
|
|
69
|
-
require_relative "rspec/linter"
|
|
70
|
-
require_relative "rspec/json_reporter"
|
|
71
|
-
require_relative "rspec/validator_backend"
|
|
72
|
-
require_relative "rspec/cli"
|
data/lib/specguard/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.3.30
|
|
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-10-
|
|
11
|
+
date: 2026-10-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
@@ -47,24 +47,25 @@ files:
|
|
|
47
47
|
- bin/specguard-ingest
|
|
48
48
|
- bin/specguard-lint
|
|
49
49
|
- lib/minitest/specguard_plugin.rb
|
|
50
|
+
- lib/specguard/client.rb
|
|
51
|
+
- lib/specguard/client/annotation_lookup.rb
|
|
52
|
+
- lib/specguard/client/annotation_scanner.rb
|
|
53
|
+
- lib/specguard/client/cli.rb
|
|
54
|
+
- lib/specguard/client/configuration.rb
|
|
55
|
+
- lib/specguard/client/file_selector.rb
|
|
56
|
+
- lib/specguard/client/finding.rb
|
|
57
|
+
- lib/specguard/client/ingest_cli.rb
|
|
58
|
+
- lib/specguard/client/ingest_reporter.rb
|
|
59
|
+
- lib/specguard/client/json_reporter.rb
|
|
60
|
+
- lib/specguard/client/linter.rb
|
|
61
|
+
- lib/specguard/client/payload_normalizer.rb
|
|
62
|
+
- lib/specguard/client/scanner.rb
|
|
63
|
+
- lib/specguard/client/schemas/open-test-intent.v1.json
|
|
64
|
+
- lib/specguard/client/transport.rb
|
|
65
|
+
- lib/specguard/client/validator_backend.rb
|
|
50
66
|
- lib/specguard/minitest/reporter.rb
|
|
51
67
|
- lib/specguard/rspec.rb
|
|
52
|
-
- lib/specguard/rspec/annotation_lookup.rb
|
|
53
|
-
- lib/specguard/rspec/annotation_scanner.rb
|
|
54
|
-
- lib/specguard/rspec/cli.rb
|
|
55
|
-
- lib/specguard/rspec/configuration.rb
|
|
56
|
-
- lib/specguard/rspec/file_selector.rb
|
|
57
|
-
- lib/specguard/rspec/finding.rb
|
|
58
68
|
- lib/specguard/rspec/formatter.rb
|
|
59
|
-
- lib/specguard/rspec/ingest_cli.rb
|
|
60
|
-
- lib/specguard/rspec/ingest_reporter.rb
|
|
61
|
-
- lib/specguard/rspec/json_reporter.rb
|
|
62
|
-
- lib/specguard/rspec/linter.rb
|
|
63
|
-
- lib/specguard/rspec/payload_normalizer.rb
|
|
64
|
-
- lib/specguard/rspec/scanner.rb
|
|
65
|
-
- lib/specguard/rspec/schemas/open-test-intent.v1.json
|
|
66
|
-
- lib/specguard/rspec/transport.rb
|
|
67
|
-
- lib/specguard/rspec/validator_backend.rb
|
|
68
69
|
- lib/specguard/rspec/version.rb
|
|
69
70
|
- lib/specguard/version.rb
|
|
70
71
|
- script/bump-version.sh
|
|
File without changes
|