testguard-cli 0.19.0 → 0.20.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/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.20.0] - 2026-10-11
11
+
12
+ Automated weekly release — everything merged since `v0.19.0`.
13
+
14
+ ### Changed
15
+
16
+ - feat(spec): structured requirement references in source.ref (#214)
17
+ - docs(jules): note the Inactive-schedule quirk and the shallow VM clone (#213)
18
+ - chore(homebrew): sha256 for v0.19.0 (#212)
19
+
20
+
21
+ ### Added
22
+
23
+ - Structured requirement references: a claim's `source.ref` may name the requirement it defends as `req:<spec-id>#<requirement-id>` (kind `spec`). A malformed `req:` reference, or one under another kind, now fails validation; every other reference stays opaque. The claim source is now defined once in `common.schema.json` (`$defs/claimSource`), and `requirementRef()` in `spec/lib/origins.mjs` parses the same pattern. Older readers accept the form as an opaque string. This is the format DocGuard's requirement anchors read to report the tests that cite a changed requirement as re-verified.
24
+
10
25
  ## [0.19.0] - 2026-10-11
11
26
 
12
27
  Automated weekly release — everything merged since `v0.18.3`.
package/README.md CHANGED
@@ -129,9 +129,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
129
129
  | npm | `npm i -D testguard-cli` then `npx testguard probe` |
130
130
  | pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
131
131
  | Homebrew | `brew tap raccioly/tap && brew install testguard` |
132
- | GitHub Action | `uses: raccioly/testguard@v0.19.0` |
132
+ | GitHub Action | `uses: raccioly/testguard@v0.20.0` |
133
133
  | pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-gate`, `testguard-probe` |
134
- | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.19.0/packaging/gitlab/testguard.gitlab-ci.yml` |
134
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.20.0/packaging/gitlab/testguard.gitlab-ci.yml` |
135
135
 
136
136
  Requirements, offline installs, `ENOVERSIONS` and every CI option are in the
137
137
  [installation guide](https://github.com/raccioly/testguard/blob/main/docs/installation.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,6 +16,24 @@ These are declarations, not authentication of independence, chronology,
16
16
  production occurrence or correctness. The optional reference remains opaque
17
17
  metadata (at most 512 characters); verification does not fetch it. Producer
18
18
  provenance and measured test independence are separate signals.
19
+
20
+ One prefix is reserved. A `ref` that starts with `req:` is a structured
21
+ requirement reference, `req:<spec-id>#<requirement-id>`, with `<spec-id>`
22
+ matching `[A-Za-z0-9][A-Za-z0-9._-]{0,127}` and `<requirement-id>` matching
23
+ `[A-Za-z][A-Za-z0-9._-]{0,63}`. It must be declared with kind `spec`.
24
+ Validation rejects a malformed one at `/source/ref` and one under another kind
25
+ at `/source/kind`; it is never degraded to opaque, because a typo would
26
+ otherwise read as "no evidence for this requirement". Every other `ref` stays
27
+ opaque. A structured reference is still a declaration. TestGuard does not
28
+ resolve it, fetch the spec or check that the requirement exists. A consumer
29
+ that keeps a requirement index, such as DocGuard, compares
30
+ `<spec-id>#<requirement-id>` exactly against its own keys. `requirementRef()`
31
+ in `spec/lib/origins.mjs` parses the same pattern the shared
32
+ `$defs/claimSource` declares. Older readers accept the form as an opaque string,
33
+ so adopting it needs no upgrade; only readers with this rule catch a malformed
34
+ one. The reference is claim metadata: editing it makes saved evidence for that
35
+ claim stale, as any source edit does. Fingerprints, verdicts and gates do not
36
+ change.
19
37
  Adding kinds does not change verdicts, baseline/fingerprint identity or default
20
38
  gates. Existing ranking weights stay unchanged; new kinds use the existing 0.9
21
39
  fallback, not an invented strength hierarchy. Existing documents remain valid
@@ -5,6 +5,25 @@ import { isDeepStrictEqual } from 'node:util';
5
5
  const common = JSON.parse(readFileSync(new URL('../schemas/common.schema.json', import.meta.url), 'utf8'));
6
6
  export const CLAIM_SOURCE_KINDS = Object.freeze([...common.$defs.claimSourceKind.enum]);
7
7
  const kindSet = new Set(CLAIM_SOURCE_KINDS);
8
+
9
+ /**
10
+ * A structured requirement reference, `req:<spec-id>#<requirement-id>`, read
11
+ * from the one pattern the shared schema declares (`$defs/claimSource`). It is
12
+ * a declaration: nothing here resolves the spec or checks the requirement
13
+ * exists. Consumers compare `specId#requirementId` exactly.
14
+ */
15
+ const sourceRef = common.$defs.claimSource.properties.ref.pattern;
16
+ // The schema's second alternative is the structured form; the first keeps every other ref opaque.
17
+ export const REQUIREMENT_REF_PATTERN = new RegExp(`^${sourceRef.slice(sourceRef.indexOf('|^') + 2)}`, 'u');
18
+ const REQUIREMENT_REF_PARTS = /^req:([^#]+)#(.+)$/u;
19
+
20
+ /** `{ specId, requirementId }` for a valid structured `spec` reference; `null` for anything else. */
21
+ export function requirementRef(source) {
22
+ if (!source || typeof source !== 'object' || source.kind !== 'spec' || typeof source.ref !== 'string') return null;
23
+ if (!REQUIREMENT_REF_PATTERN.test(source.ref)) return null;
24
+ const [, specId, requirementId] = REQUIREMENT_REF_PARTS.exec(source.ref);
25
+ return { specId, requirementId };
26
+ }
8
27
  const counts = () => Object.fromEntries(CLAIM_SOURCE_KINDS.map((kind) => [kind, 0]));
9
28
 
10
29
  // Consumers validate their full document first. These narrow guards prevent
@@ -46,21 +46,7 @@
46
46
  "maxLength": 2000
47
47
  },
48
48
  "source": {
49
- "description": "Declared origin of claim intent. A source label or opaque reference does not authenticate independence, chronology or correctness. Producer provenance and measured test independence are separate.",
50
- "type": "object",
51
- "required": [
52
- "kind"
53
- ],
54
- "properties": {
55
- "kind": {
56
- "$ref": "urn:claimspec:v1:common#/$defs/claimSourceKind"
57
- },
58
- "ref": {
59
- "type": "string",
60
- "maxLength": 512
61
- }
62
- },
63
- "additionalProperties": false
49
+ "$ref": "urn:claimspec:v1:common#/$defs/claimSource"
64
50
  },
65
51
  "severity": {
66
52
  "$ref": "urn:claimspec:v1:common#/$defs/severity"
@@ -77,6 +77,22 @@
77
77
  "description": "Declared origin of claim intent, not authenticated independence or an evidence-strength order. References remain opaque; producer provenance and measured test independence are separate.",
78
78
  "enum": ["spec", "adr", "annotation", "comment", "manual", "doc", "bug", "incident", "review", "inferred"]
79
79
  },
80
+ "claimSource": {
81
+ "description": "Declared origin of claim intent. A source label or reference does not authenticate independence, chronology or correctness. Producer provenance and measured test independence are separate. `ref` is opaque, except that the `req:` prefix is reserved for a structured requirement reference, `req:<spec-id>#<requirement-id>`, which must be declared with kind `spec`. Neither form is ever resolved or fetched.",
82
+ "type": "object",
83
+ "required": ["kind"],
84
+ "properties": {
85
+ "kind": { "$ref": "#/$defs/claimSourceKind" },
86
+ "ref": {
87
+ "type": "string",
88
+ "maxLength": 512,
89
+ "pattern": "^(?!req:)[\\s\\S]*$|^req:[A-Za-z0-9][A-Za-z0-9._-]{0,127}#[A-Za-z][A-Za-z0-9._-]{0,63}$"
90
+ }
91
+ },
92
+ "additionalProperties": false,
93
+ "if": { "type": "object", "required": ["ref"], "properties": { "ref": { "type": "string", "pattern": "^req:" } } },
94
+ "then": { "properties": { "kind": { "const": "spec" } } }
95
+ },
80
96
  "nestedProject": {
81
97
  "description": "Changed files delegated to a separately validated descendant TestGuard project. This is not coverage or evidence; run that project's gate separately.",
82
98
  "type": "object",
@@ -339,20 +339,7 @@
339
339
  "$ref": "urn:claimspec:v1:common#/$defs/severity"
340
340
  },
341
341
  "source": {
342
- "type": "object",
343
- "required": [
344
- "kind"
345
- ],
346
- "properties": {
347
- "kind": {
348
- "$ref": "urn:claimspec:v1:common#/$defs/claimSourceKind"
349
- },
350
- "ref": {
351
- "type": "string",
352
- "maxLength": 512
353
- }
354
- },
355
- "additionalProperties": false
342
+ "$ref": "urn:claimspec:v1:common#/$defs/claimSource"
356
343
  },
357
344
  "producedBy": {
358
345
  "$ref": "urn:claimspec:v1:common#/$defs/provenance"