testguard-cli 0.5.0 → 0.7.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 +628 -0
- package/README.md +536 -42
- package/package.json +2 -2
- package/spec/GATE-SEMANTICS.md +166 -2
- package/spec/README.md +29 -10
- package/spec/lib/validate.mjs +140 -8
- package/spec/schemas/baseline.schema.json +13 -5
- package/spec/schemas/brief.schema.json +12 -12
- package/spec/schemas/calibration.schema.json +5 -5
- package/spec/schemas/claims.schema.json +122 -25
- package/spec/schemas/common.schema.json +131 -29
- package/spec/schemas/evidence.schema.json +476 -69
- package/spec/schemas/gate.schema.json +12 -12
- package/spec/schemas/ignore.schema.json +4 -4
- package/spec/schemas/replay.schema.json +85 -0
- package/spec/schemas/status.schema.json +258 -56
- package/src/admit/admit.mjs +17 -0
- package/src/baseline/baseline.mjs +31 -2
- package/src/brief/brief.mjs +71 -7
- package/src/claims/removed.mjs +161 -0
- package/src/cli.mjs +68 -10
- package/src/commands/admit.mjs +116 -0
- package/src/commands/baseline.mjs +17 -3
- package/src/commands/brief.mjs +13 -7
- package/src/commands/claims.mjs +55 -6
- package/src/commands/init.mjs +19 -6
- package/src/commands/mcp.mjs +12 -0
- package/src/commands/probe.mjs +35 -7
- package/src/commands/replay.mjs +63 -0
- package/src/commands/status.mjs +3 -1
- package/src/gate/changed.mjs +9 -2
- package/src/git.mjs +5 -0
- package/src/init/init.mjs +182 -29
- package/src/init/templates/SKILL.md +3 -3
- package/src/mcp/server.mjs +104 -0
- package/src/mcp/tools.mjs +114 -0
- package/src/probe/attribution.mjs +123 -0
- package/src/probe/classify.mjs +18 -2
- package/src/probe/contention.mjs +60 -0
- package/src/probe/cost.mjs +145 -0
- package/src/probe/discover.mjs +20 -3
- package/src/probe/independence.mjs +59 -0
- package/src/probe/inject.mjs +41 -9
- package/src/probe/mocks.mjs +122 -0
- package/src/probe/probe.mjs +331 -40
- package/src/probe/progress.mjs +87 -0
- package/src/probe/pyimports.mjs +287 -0
- package/src/probe/rank.mjs +75 -15
- package/src/probe/runners/index.mjs +79 -8
- package/src/probe/runners/jest.mjs +15 -3
- package/src/probe/runners/playwright.mjs +142 -0
- package/src/probe/runners/python/_testguard_pytest_plugin.py +132 -0
- package/src/probe/runners/python/_testguard_report.py +141 -0
- package/src/probe/runners/python/_testguard_unittest_main.py +168 -0
- package/src/probe/runners/python.mjs +316 -0
- package/src/probe/runners/shared.mjs +80 -14
- package/src/probe/runners/vitest.mjs +16 -3
- package/src/render.mjs +2 -2
- package/src/replay/label.mjs +64 -0
- package/src/replay/replay.mjs +310 -0
- package/src/scaffold/producers.mjs +217 -6
- package/src/scaffold/producers.python.mjs +209 -0
- package/src/scaffold/scaffold.mjs +90 -30
- package/src/status/status.mjs +47 -6
- package/src/util/glob.mjs +7 -1
|
@@ -1,27 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:
|
|
4
|
-
"title": "
|
|
3
|
+
"$id": "urn:claimspec:v1:claims",
|
|
4
|
+
"title": "claimspec v1 \u2014 claims file",
|
|
5
5
|
"description": "What must be true, and how to try to falsify it. A claim is a statement the project makes; each fault is one concrete way that statement could be false, expressed as a deterministic source change. This file is code: its `replace` strings execute under the test runner. Treat it with the same review discipline as source.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": [
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"claims"
|
|
10
|
+
],
|
|
8
11
|
"properties": {
|
|
9
|
-
"$schema": {
|
|
10
|
-
|
|
12
|
+
"$schema": {
|
|
13
|
+
"type": "string"
|
|
14
|
+
},
|
|
15
|
+
"schemaVersion": {
|
|
16
|
+
"const": 1
|
|
17
|
+
},
|
|
11
18
|
"claims": {
|
|
12
19
|
"type": "array",
|
|
13
|
-
"items": {
|
|
20
|
+
"items": {
|
|
21
|
+
"$ref": "#/$defs/claim"
|
|
22
|
+
}
|
|
14
23
|
}
|
|
15
24
|
},
|
|
16
25
|
"additionalProperties": false,
|
|
17
26
|
"$defs": {
|
|
18
27
|
"claim": {
|
|
19
28
|
"type": "object",
|
|
20
|
-
"required": [
|
|
29
|
+
"required": [
|
|
30
|
+
"id",
|
|
31
|
+
"statement",
|
|
32
|
+
"source",
|
|
33
|
+
"severity",
|
|
34
|
+
"producedBy",
|
|
35
|
+
"faults"
|
|
36
|
+
],
|
|
21
37
|
"properties": {
|
|
22
38
|
"id": {
|
|
23
39
|
"description": "Stable identifier. Renaming it changes every fingerprint derived from it.",
|
|
24
|
-
"$ref": "urn:
|
|
40
|
+
"$ref": "urn:claimspec:v1:common#/$defs/identifier"
|
|
25
41
|
},
|
|
26
42
|
"statement": {
|
|
27
43
|
"description": "The claim in plain language, as the project states it.",
|
|
@@ -32,46 +48,82 @@
|
|
|
32
48
|
"source": {
|
|
33
49
|
"description": "Where the claim comes from. Spec- and ADR-sourced claims are human-governed; annotation- and comment-sourced claims may have been written by the same agent that wrote the tests, and should be ranked accordingly.",
|
|
34
50
|
"type": "object",
|
|
35
|
-
"required": [
|
|
51
|
+
"required": [
|
|
52
|
+
"kind"
|
|
53
|
+
],
|
|
36
54
|
"properties": {
|
|
37
|
-
"kind": {
|
|
38
|
-
|
|
55
|
+
"kind": {
|
|
56
|
+
"enum": [
|
|
57
|
+
"spec",
|
|
58
|
+
"adr",
|
|
59
|
+
"annotation",
|
|
60
|
+
"comment",
|
|
61
|
+
"manual"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"ref": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"maxLength": 512
|
|
67
|
+
}
|
|
39
68
|
},
|
|
40
69
|
"additionalProperties": false
|
|
41
70
|
},
|
|
42
|
-
"severity": {
|
|
43
|
-
|
|
71
|
+
"severity": {
|
|
72
|
+
"$ref": "urn:claimspec:v1:common#/$defs/severity"
|
|
73
|
+
},
|
|
74
|
+
"producedBy": {
|
|
75
|
+
"$ref": "urn:claimspec:v1:common#/$defs/provenance"
|
|
76
|
+
},
|
|
44
77
|
"defendedBy": {
|
|
45
78
|
"description": "Globs (project-relative) for the test files that supposedly defend this claim. Empty or absent means: discover defenders as the test files that import each fault's target file (relative paths and resolved aliases). If nothing resolves, the verdict is `nocover`.",
|
|
46
79
|
"type": "array",
|
|
47
|
-
"items": {
|
|
80
|
+
"items": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"minLength": 1
|
|
83
|
+
},
|
|
48
84
|
"uniqueItems": true
|
|
49
85
|
},
|
|
50
86
|
"faults": {
|
|
51
87
|
"type": "array",
|
|
52
88
|
"minItems": 1,
|
|
53
|
-
"items": {
|
|
89
|
+
"items": {
|
|
90
|
+
"$ref": "#/$defs/fault"
|
|
91
|
+
}
|
|
54
92
|
},
|
|
55
93
|
"tags": {
|
|
56
94
|
"type": "array",
|
|
57
|
-
"items": {
|
|
95
|
+
"items": {
|
|
96
|
+
"$ref": "urn:claimspec:v1:common#/$defs/identifier"
|
|
97
|
+
},
|
|
58
98
|
"uniqueItems": true
|
|
59
99
|
}
|
|
60
100
|
},
|
|
61
101
|
"additionalProperties": false
|
|
62
102
|
},
|
|
63
103
|
"fault": {
|
|
64
|
-
"description": "One concrete way the claim could be false.
|
|
104
|
+
"description": "One concrete way the claim could be false, and how a tool would try to make it so. `method` says which; it defaults to `fault-injection`, whose shape is an exact-string substitution and is unchanged. Producer-agnostic: a human, an operator, an agent or a derivation may all emit this shape, and every artifact says which.",
|
|
65
105
|
"type": "object",
|
|
66
|
-
"required": [
|
|
106
|
+
"required": [
|
|
107
|
+
"id",
|
|
108
|
+
"description",
|
|
109
|
+
"producedBy"
|
|
110
|
+
],
|
|
67
111
|
"properties": {
|
|
68
112
|
"id": {
|
|
69
113
|
"description": "Unique within the claim.",
|
|
70
|
-
"$ref": "urn:
|
|
114
|
+
"$ref": "urn:claimspec:v1:common#/$defs/identifier"
|
|
115
|
+
},
|
|
116
|
+
"description": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"minLength": 1,
|
|
119
|
+
"maxLength": 2000
|
|
120
|
+
},
|
|
121
|
+
"faultClass": {
|
|
122
|
+
"$ref": "urn:claimspec:v1:common#/$defs/faultClass"
|
|
123
|
+
},
|
|
124
|
+
"file": {
|
|
125
|
+
"$ref": "urn:claimspec:v1:common#/$defs/repoPath"
|
|
71
126
|
},
|
|
72
|
-
"description": { "type": "string", "minLength": 1, "maxLength": 2000 },
|
|
73
|
-
"faultClass": { "$ref": "urn:guard-spec:v1:common#/$defs/faultClass" },
|
|
74
|
-
"file": { "$ref": "urn:guard-spec:v1:common#/$defs/repoPath" },
|
|
75
127
|
"find": {
|
|
76
128
|
"description": "Exact text to locate in `file`. Must occur exactly `expectHits` times or the fault is `unverifiable`.",
|
|
77
129
|
"type": "string",
|
|
@@ -90,14 +142,59 @@
|
|
|
90
142
|
"default": 1
|
|
91
143
|
},
|
|
92
144
|
"expectHits": {
|
|
93
|
-
"description": "How many times `find` must occur in `file`. Any other count means the anchor has rotted or is ambiguous
|
|
145
|
+
"description": "How many times `find` must occur in `file`. Any other count means the anchor has rotted or is ambiguous \u2192 `unverifiable`, never a silent skip.",
|
|
94
146
|
"type": "integer",
|
|
95
147
|
"minimum": 1,
|
|
96
148
|
"default": 1
|
|
97
149
|
},
|
|
98
|
-
"producedBy": {
|
|
150
|
+
"producedBy": {
|
|
151
|
+
"$ref": "urn:claimspec:v1:common#/$defs/provenance"
|
|
152
|
+
},
|
|
153
|
+
"method": {
|
|
154
|
+
"$ref": "urn:claimspec:v1:common#/$defs/method"
|
|
155
|
+
}
|
|
99
156
|
},
|
|
100
|
-
"additionalProperties": false
|
|
157
|
+
"additionalProperties": false,
|
|
158
|
+
"allOf": [
|
|
159
|
+
{
|
|
160
|
+
"if": {
|
|
161
|
+
"anyOf": [
|
|
162
|
+
{
|
|
163
|
+
"properties": {
|
|
164
|
+
"method": {
|
|
165
|
+
"const": "fault-injection"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"required": [
|
|
169
|
+
"method"
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"not": {
|
|
174
|
+
"required": [
|
|
175
|
+
"method"
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"then": {
|
|
182
|
+
"$comment": "The default method. Absent `method` means fault-injection, so every claims file written before the field existed is held to exactly the same requirements as before. The property stubs are what ajv's strict mode needs to accept `required` here; the real definitions stay above.",
|
|
183
|
+
"properties": {
|
|
184
|
+
"faultClass": true,
|
|
185
|
+
"file": true,
|
|
186
|
+
"find": true,
|
|
187
|
+
"replace": true
|
|
188
|
+
},
|
|
189
|
+
"required": [
|
|
190
|
+
"faultClass",
|
|
191
|
+
"file",
|
|
192
|
+
"find",
|
|
193
|
+
"replace"
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
]
|
|
101
198
|
}
|
|
102
199
|
}
|
|
103
200
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "urn:
|
|
4
|
-
"title": "
|
|
5
|
-
"description": "Definitions shared by every Guard-spec format. Referenced as urn:
|
|
3
|
+
"$id": "urn:claimspec:v1:common",
|
|
4
|
+
"title": "claimspec v1 \u2014 shared definitions",
|
|
5
|
+
"description": "Definitions shared by every Guard-spec format. Referenced as urn:claimspec:v1:common#/$defs/<name>.",
|
|
6
6
|
"$defs": {
|
|
7
7
|
"sha256": {
|
|
8
8
|
"type": "string",
|
|
@@ -28,7 +28,12 @@
|
|
|
28
28
|
"pattern": "^(?![/\\\\])(?!\\.\\.(?:[/\\\\]|$))(?!.*[/\\\\]\\.\\.(?:[/\\\\]|$)).*$"
|
|
29
29
|
},
|
|
30
30
|
"severity": {
|
|
31
|
-
"enum": [
|
|
31
|
+
"enum": [
|
|
32
|
+
"critical",
|
|
33
|
+
"high",
|
|
34
|
+
"medium",
|
|
35
|
+
"low"
|
|
36
|
+
]
|
|
32
37
|
},
|
|
33
38
|
"verdict": {
|
|
34
39
|
"description": "Closed set. Only `killed` is a pass. Every other verdict gates by default (see GATE-SEMANTICS.md). `unverifiable` covers a fault that could not be applied (anchor missing/ambiguous) and a claim whose defenders failed to load (`defenders-failed-to-load`).",
|
|
@@ -43,7 +48,7 @@
|
|
|
43
48
|
]
|
|
44
49
|
},
|
|
45
50
|
"faultClass": {
|
|
46
|
-
"description": "Taxonomy of the injected change. Required on every fault so calibration (fault class
|
|
51
|
+
"description": "Taxonomy of the injected change. Required on every fault so calibration (fault class \u2192 real-bug predictiveness) can be computed later. `field-dropped` = a field removed from a persisted/transmitted object, allow-list, schema or merge; `argument-swapped` = a call kept with a parameter-derived argument replaced by a trivial value; `element-removed` = a UI element no longer rendered; `handler-dropped` = an event handler no longer wired.",
|
|
47
52
|
"enum": [
|
|
48
53
|
"guard-removed",
|
|
49
54
|
"condition-forced",
|
|
@@ -53,69 +58,166 @@
|
|
|
53
58
|
"call-removed",
|
|
54
59
|
"return-altered",
|
|
55
60
|
"exception-swallowed",
|
|
61
|
+
"field-dropped",
|
|
62
|
+
"argument-swapped",
|
|
63
|
+
"element-removed",
|
|
64
|
+
"handler-dropped",
|
|
56
65
|
"other"
|
|
57
66
|
]
|
|
58
67
|
},
|
|
59
68
|
"provenance": {
|
|
60
69
|
"description": "Who or what produced an artifact. `human` = authored by a person; `agent` = proposed by an LLM agent; `operator` = emitted by a mechanical mutation operator; `derived` = computed from another artifact (e.g. a bug log).",
|
|
61
70
|
"type": "object",
|
|
62
|
-
"required": [
|
|
71
|
+
"required": [
|
|
72
|
+
"producer"
|
|
73
|
+
],
|
|
63
74
|
"properties": {
|
|
64
|
-
"producer": {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
"producer": {
|
|
76
|
+
"enum": [
|
|
77
|
+
"human",
|
|
78
|
+
"agent",
|
|
79
|
+
"operator",
|
|
80
|
+
"derived"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"by": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"maxLength": 256
|
|
86
|
+
},
|
|
87
|
+
"reviewedBy": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"maxLength": 256
|
|
90
|
+
},
|
|
91
|
+
"at": {
|
|
92
|
+
"$ref": "#/$defs/isoDateTime"
|
|
93
|
+
}
|
|
68
94
|
},
|
|
69
95
|
"additionalProperties": false
|
|
70
96
|
},
|
|
71
97
|
"tool": {
|
|
72
98
|
"type": "object",
|
|
73
|
-
"required": [
|
|
99
|
+
"required": [
|
|
100
|
+
"name",
|
|
101
|
+
"version"
|
|
102
|
+
],
|
|
74
103
|
"properties": {
|
|
75
|
-
"name": {
|
|
76
|
-
|
|
104
|
+
"name": {
|
|
105
|
+
"$ref": "#/$defs/identifier"
|
|
106
|
+
},
|
|
107
|
+
"version": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"minLength": 1,
|
|
110
|
+
"maxLength": 64
|
|
111
|
+
}
|
|
77
112
|
},
|
|
78
113
|
"additionalProperties": false
|
|
79
114
|
},
|
|
80
115
|
"uncoveredFile": {
|
|
81
116
|
"description": "A changed source or test file that no claim covers and no unexpired ignore entry excuses. Shared by the gate and status documents.",
|
|
82
117
|
"type": "object",
|
|
83
|
-
"required": [
|
|
118
|
+
"required": [
|
|
119
|
+
"file",
|
|
120
|
+
"kind",
|
|
121
|
+
"suggestion"
|
|
122
|
+
],
|
|
84
123
|
"properties": {
|
|
85
|
-
"file": {
|
|
86
|
-
|
|
124
|
+
"file": {
|
|
125
|
+
"$ref": "#/$defs/repoPath"
|
|
126
|
+
},
|
|
127
|
+
"kind": {
|
|
128
|
+
"enum": [
|
|
129
|
+
"source",
|
|
130
|
+
"test"
|
|
131
|
+
]
|
|
132
|
+
},
|
|
87
133
|
"nearestClaimId": {
|
|
88
|
-
"description": "The claim whose fault files share the longest directory prefix with this file
|
|
134
|
+
"description": "The claim whose fault files share the longest directory prefix with this file \u2014 where to attach, not a verdict.",
|
|
89
135
|
"$ref": "#/$defs/identifier"
|
|
90
136
|
},
|
|
91
|
-
"suggestion": {
|
|
137
|
+
"suggestion": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"minLength": 1,
|
|
140
|
+
"maxLength": 1024
|
|
141
|
+
}
|
|
92
142
|
},
|
|
93
143
|
"additionalProperties": false
|
|
94
144
|
},
|
|
95
145
|
"ignoreReliance": {
|
|
96
146
|
"description": "An ignore entry the gate relied on (or would have, had it not expired), with the changed files it matched.",
|
|
97
147
|
"type": "object",
|
|
98
|
-
"required": [
|
|
148
|
+
"required": [
|
|
149
|
+
"pattern",
|
|
150
|
+
"reason",
|
|
151
|
+
"files"
|
|
152
|
+
],
|
|
99
153
|
"properties": {
|
|
100
|
-
"pattern": {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
154
|
+
"pattern": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"minLength": 1,
|
|
157
|
+
"maxLength": 512
|
|
158
|
+
},
|
|
159
|
+
"reason": {
|
|
160
|
+
"type": "string",
|
|
161
|
+
"minLength": 8,
|
|
162
|
+
"maxLength": 2000
|
|
163
|
+
},
|
|
164
|
+
"expires": {
|
|
165
|
+
"$ref": "#/$defs/isoDateTime"
|
|
166
|
+
},
|
|
167
|
+
"files": {
|
|
168
|
+
"type": "array",
|
|
169
|
+
"minItems": 1,
|
|
170
|
+
"items": {
|
|
171
|
+
"$ref": "#/$defs/repoPath"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
104
174
|
},
|
|
105
175
|
"additionalProperties": false
|
|
106
176
|
},
|
|
107
177
|
"annotation": {
|
|
108
178
|
"description": "Strictly additive. An annotation never changes, suppresses, or drops a finding; it only attaches context to it.",
|
|
109
179
|
"type": "object",
|
|
110
|
-
"required": [
|
|
180
|
+
"required": [
|
|
181
|
+
"kind",
|
|
182
|
+
"text"
|
|
183
|
+
],
|
|
111
184
|
"properties": {
|
|
112
|
-
"kind": {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
185
|
+
"kind": {
|
|
186
|
+
"enum": [
|
|
187
|
+
"note",
|
|
188
|
+
"accepted-risk",
|
|
189
|
+
"false-positive",
|
|
190
|
+
"tracked",
|
|
191
|
+
"wontfix"
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
"text": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"minLength": 1,
|
|
197
|
+
"maxLength": 4000
|
|
198
|
+
},
|
|
199
|
+
"by": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"maxLength": 256
|
|
202
|
+
},
|
|
203
|
+
"at": {
|
|
204
|
+
"$ref": "#/$defs/isoDateTime"
|
|
205
|
+
},
|
|
206
|
+
"ref": {
|
|
207
|
+
"type": "string",
|
|
208
|
+
"maxLength": 512
|
|
209
|
+
}
|
|
117
210
|
},
|
|
118
211
|
"additionalProperties": false
|
|
212
|
+
},
|
|
213
|
+
"method": {
|
|
214
|
+
"description": "How a tool tries to falsify a claim. `fault-injection` mutates source and runs the claim's defenders; it is the only method fully specified in v1, and the default when the field is absent, so every document written before this field existed stays valid. `assertion` (a claim checked by reading code, as a documentation guard does) and `scan` (a surface checked by a scanner) are RESERVED: their required fields are deliberately not specified here and will be defined by their first real consumer. Inventing them for an absent tool is the mistake that made this discriminator necessary.",
|
|
215
|
+
"enum": [
|
|
216
|
+
"fault-injection",
|
|
217
|
+
"assertion",
|
|
218
|
+
"scan"
|
|
219
|
+
],
|
|
220
|
+
"default": "fault-injection"
|
|
119
221
|
}
|
|
120
222
|
}
|
|
121
223
|
}
|