featureparity 0.0.5 → 0.0.7
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/lib/fp/cli.rb +9 -2
- data/lib/fp/client.rb +66 -10
- data/lib/fp/commands/base.rb +49 -0
- data/lib/fp/commands/check.rb +216 -0
- data/lib/fp/commands/ci_report.rb +115 -0
- data/lib/fp/commands/export.rb +144 -0
- data/lib/fp/commands/help.rb +79 -1
- data/lib/fp/commands/history.rb +188 -0
- data/lib/fp/commands/init.rb +384 -0
- data/lib/fp/commands/list.rb +111 -31
- data/lib/fp/commands/matrix.rb +82 -42
- data/lib/fp/commands/propose.rb +1 -1
- data/lib/fp/commands/propose_change.rb +181 -0
- data/lib/fp/commands/report.rb +10 -6
- data/lib/fp/commands/repos.rb +3 -3
- data/lib/fp/commands/show.rb +5 -2
- data/lib/fp/commands/surfaces.rb +2 -2
- data/lib/fp/commands.rb +6 -0
- data/lib/fp/junit.rb +13 -4
- data/lib/fp/parity.rb +80 -0
- data/lib/fp/project_config.rb +137 -0
- data/lib/fp/version.rb +1 -1
- data/lib/fp.rb +2 -0
- data/skills/fp/SKILL.md +109 -1
- data/skills/fp/references/cli.md +203 -6
- metadata +10 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Fp
|
|
6
|
+
# ProjectConfig — the repo-local `.featureparity.yml`, which marks a checkout as
|
|
7
|
+
# tracked by FeatureParity and says which project it belongs to.
|
|
8
|
+
#
|
|
9
|
+
# This is deliberately a different thing from Fp::Config (`~/.config/fp/config.yml`).
|
|
10
|
+
# That one is per-machine and secret-bearing: API keys, profiles, and where each
|
|
11
|
+
# surface lives on *this* laptop. This one is committed to the repo and answers a
|
|
12
|
+
# question the machine can't: "which FeatureParity project is this code?"
|
|
13
|
+
#
|
|
14
|
+
# It exists because enforcement has to work in CI and in an agent's first minute in a
|
|
15
|
+
# fresh clone, where nobody has run `fp setup`. Without it, every workflow file and
|
|
16
|
+
# every agent prompt has to hardcode `--project <slug>`, and the answer drifts. With
|
|
17
|
+
# it, `fp check` in a fresh clone already knows what to check.
|
|
18
|
+
#
|
|
19
|
+
# # .featureparity.yml
|
|
20
|
+
# project: feature-parity
|
|
21
|
+
# enforcement: regression
|
|
22
|
+
# surface: api
|
|
23
|
+
#
|
|
24
|
+
# Discovery walks up from the working directory to the filesystem root, so the file
|
|
25
|
+
# is found from a subdirectory too (like .git or package.json).
|
|
26
|
+
class ProjectConfig
|
|
27
|
+
FILENAME = '.featureparity.yml'
|
|
28
|
+
|
|
29
|
+
# Also accepted when reading, so a repo that picked the dotless or .yaml spelling
|
|
30
|
+
# isn't silently ignored. `fp init` only ever writes FILENAME.
|
|
31
|
+
ALTERNATE_FILENAMES = ['.featureparity.yaml', 'featureparity.yml'].freeze
|
|
32
|
+
|
|
33
|
+
CANDIDATE_FILENAMES = ([FILENAME] + ALTERNATE_FILENAMES).freeze
|
|
34
|
+
|
|
35
|
+
attr_reader :path, :data
|
|
36
|
+
|
|
37
|
+
def initialize(path: nil, data: nil)
|
|
38
|
+
@path = path
|
|
39
|
+
@data = data || {}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Find and load the nearest .featureparity.yml at or above `dir`.
|
|
43
|
+
# Always returns a ProjectConfig — an absent file is an empty one, so callers can
|
|
44
|
+
# treat "no config" and "config without this key" the same way.
|
|
45
|
+
def self.discover(dir = Dir.pwd)
|
|
46
|
+
found = find_file(dir)
|
|
47
|
+
return new unless found
|
|
48
|
+
|
|
49
|
+
new(path: found, data: parse(found))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Walk up from `dir` looking for any candidate filename. Stops at the filesystem
|
|
53
|
+
# root; File.dirname('/') == '/' is the loop's terminator.
|
|
54
|
+
def self.find_file(dir)
|
|
55
|
+
current = File.expand_path(dir)
|
|
56
|
+
|
|
57
|
+
loop do
|
|
58
|
+
CANDIDATE_FILENAMES.each do |name|
|
|
59
|
+
candidate = File.join(current, name)
|
|
60
|
+
return candidate if File.file?(candidate)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
parent = File.dirname(current)
|
|
64
|
+
return nil if parent == current
|
|
65
|
+
|
|
66
|
+
current = parent
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# A malformed config must not take the CLI down — warn and behave as if absent,
|
|
71
|
+
# so a bad merge in .featureparity.yml doesn't break every fp command in the repo.
|
|
72
|
+
def self.parse(file)
|
|
73
|
+
loaded = YAML.safe_load_file(file, permitted_classes: [Symbol])
|
|
74
|
+
loaded.is_a?(Hash) ? loaded : {}
|
|
75
|
+
rescue StandardError => e
|
|
76
|
+
warn "Warning: Could not load #{file}: #{e.message}"
|
|
77
|
+
{}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Serialize a config file body. Kept here (rather than in `fp init`) so the reader
|
|
81
|
+
# and the writer of this format live next to each other.
|
|
82
|
+
def self.render(project:, enforcement: nil, surface: nil)
|
|
83
|
+
body = +<<~HEADER
|
|
84
|
+
# FeatureParity — this repo is tracked for feature parity.
|
|
85
|
+
# Committed on purpose: it is how CI and agents know which project this code
|
|
86
|
+
# belongs to without anyone passing --project. See https://featureparity.dev
|
|
87
|
+
---
|
|
88
|
+
project: #{project}
|
|
89
|
+
HEADER
|
|
90
|
+
|
|
91
|
+
body << "\n# Enforcement level the CI gate applies (off, regression, covered, strict).\n" \
|
|
92
|
+
"# Omit to use whatever the project is configured with server-side.\n" \
|
|
93
|
+
"enforcement: #{enforcement}\n" if enforcement
|
|
94
|
+
|
|
95
|
+
body << "\n# Default surface for this repo, used when --surface is not given.\n" \
|
|
96
|
+
"surface: #{surface}\n" if surface
|
|
97
|
+
|
|
98
|
+
body
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def exists?
|
|
102
|
+
!path.nil?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Project slug this checkout belongs to.
|
|
106
|
+
def project
|
|
107
|
+
fetch_string('project')
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Enforcement level override committed alongside the code. Nil means "use the
|
|
111
|
+
# project's configured level" — the normal case, since the point of storing the
|
|
112
|
+
# level server-side is that it only has to be raised in one place.
|
|
113
|
+
def enforcement
|
|
114
|
+
fetch_string('enforcement')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Default surface for a single-surface repo. A monorepo leaves this out and passes
|
|
118
|
+
# --surface per job instead.
|
|
119
|
+
def surface
|
|
120
|
+
fetch_string('surface')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def to_h
|
|
124
|
+
{ 'path' => path }.merge(data)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def fetch_string(key)
|
|
130
|
+
value = data[key]
|
|
131
|
+
return nil if value.nil?
|
|
132
|
+
|
|
133
|
+
string = value.to_s.strip
|
|
134
|
+
string.empty? ? nil : string
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/fp/version.rb
CHANGED
data/lib/fp.rb
CHANGED
data/skills/fp/SKILL.md
CHANGED
|
@@ -45,10 +45,11 @@ If not set, ask the user for their API key or have them run `fp setup`.
|
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
fp list --project stowzilla # All active requirements
|
|
48
|
-
fp list --project stowzilla --gaps # Requirements
|
|
48
|
+
fp list --project stowzilla --gaps # Requirements with a required surface lacking evidence
|
|
49
49
|
fp show <slug> --project stowzilla # Requirement details
|
|
50
50
|
fp matrix --project stowzilla # ASCII parity matrix
|
|
51
51
|
fp matrix --project stowzilla --csv # Export as CSV
|
|
52
|
+
fp check --project stowzilla # Enforce parity (exits 1 on violations)
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
### 2. Add the fp:<slug> marker to tests
|
|
@@ -118,16 +119,123 @@ fp propose --project stowzilla \
|
|
|
118
119
|
|
|
119
120
|
**Agents always create requirements as draft.** A human must activate in the web app.
|
|
120
121
|
|
|
122
|
+
### 6. Propose a change to an existing requirement
|
|
123
|
+
|
|
124
|
+
When a requirement no longer says the right thing, don't silently edit it — propose the
|
|
125
|
+
change and say what prompted it. The requirement drops back to **draft** and a human
|
|
126
|
+
reviews the old-vs-new diff before it counts again.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
fp propose-change print_container_qr --project stowzilla \
|
|
130
|
+
--why "Labels must survive a freezer" \
|
|
131
|
+
--acceptance "QR scans after 24h at -20C" \
|
|
132
|
+
--reason "Cold-chain rollout changed the requirement" \
|
|
133
|
+
--source-type fizzy \
|
|
134
|
+
--source-url https://app.fizzy.do/6098707/cards/1377
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
To propose deleting one instead (nothing is destroyed — a human decides):
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
fp propose-change print_container_qr --project stowzilla \
|
|
141
|
+
--removal --reason "Superseded by print_container_label"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Rules:
|
|
145
|
+
|
|
146
|
+
- Always pass `--reason` and, where one exists, `--source-type`/`--source-url`. The whole
|
|
147
|
+
point of a proposal is that the *why* is recorded next to the *what*.
|
|
148
|
+
- Agents cannot change `--slug` (it's the join to every `fp:<slug>` marker) or remove a
|
|
149
|
+
required surface. Both come back as a 403, printed verbatim.
|
|
150
|
+
- Agents cannot approve or reject. Only humans review.
|
|
151
|
+
- One open proposal per requirement — review the current one before proposing another.
|
|
152
|
+
|
|
153
|
+
### 7. See what changed and why
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
fp history print_container_qr --project stowzilla
|
|
157
|
+
fp history print_container_qr --project stowzilla --pending # just what's awaiting review
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Shows the timeline (who changed what, when, and what prompted it), the field-by-field
|
|
161
|
+
diff, and a diff of the **test evidence** — so you can see which tests were written
|
|
162
|
+
against wording that has since moved.
|
|
163
|
+
|
|
164
|
+
### 8. Verify you didn't break the gate
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
fp check # exits non-zero on violations
|
|
168
|
+
fp check --surface api --json # machine-readable violations
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`fp check` is the enforcement gate. It asks the API whether the project violates its
|
|
172
|
+
enforcement level and **exits 1 if it does**, which is what makes it usable as a required
|
|
173
|
+
status check on a pull request. Run it after reporting evidence to confirm your change
|
|
174
|
+
left parity intact.
|
|
175
|
+
|
|
176
|
+
Levels, from most to least forgiving:
|
|
177
|
+
|
|
178
|
+
| Level | Fails the build when |
|
|
179
|
+
|-------|----------------------|
|
|
180
|
+
| `off` | never — report only |
|
|
181
|
+
| `regression` | a test that was passing has gone red |
|
|
182
|
+
| `covered` | any required surface lacks real (non-stub) evidence |
|
|
183
|
+
| `strict` | any required surface isn't green in CI |
|
|
184
|
+
|
|
185
|
+
The level is configured on the project, so don't hardcode one. `--level` overrides it for
|
|
186
|
+
a single run; `--warn-only` prints violations but still exits 0.
|
|
187
|
+
|
|
188
|
+
**A requirement you proposed a change to still counts.** It drops to `draft` until a human
|
|
189
|
+
reviews it, but the gate keeps measuring it — otherwise proposing a change would be a way
|
|
190
|
+
to make a red gate green. What relaxes is only evidence *freshness*: proposing marks the
|
|
191
|
+
existing evidence `suspect`, and `suspect` doesn't fail a build below `strict`. Re-write the
|
|
192
|
+
tests against the new wording and re-report to clear it. `fp check` lists everything
|
|
193
|
+
awaiting review so you can tell that apart from a plain gap.
|
|
194
|
+
|
|
195
|
+
**Gating a branch on its own results.** Plain `fp check` sees only what has been recorded,
|
|
196
|
+
which on a pull request is the default branch's state — it cannot see what your branch
|
|
197
|
+
changed. Pass the run's JUnit to evaluate your results without saving them:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
fp check --surface api --junit junit.xml
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
This also catches the one regression a green test suite can't: a test that was deleted,
|
|
204
|
+
renamed, or had its `fp:` marker stripped. Every remaining test passes, so CI is happy,
|
|
205
|
+
while the requirement quietly lost its only proof.
|
|
206
|
+
|
|
207
|
+
### Repo-local config (`.featureparity.yml`)
|
|
208
|
+
|
|
209
|
+
If a repo has a `.featureparity.yml`, `--project` is optional — `fp` reads the slug from
|
|
210
|
+
it. Check for one before asking the user which project to use:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
cat .featureparity.yml 2>/dev/null
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
To onboard a repo that has no such file (writes the config, a CI gate workflow, and a
|
|
217
|
+
FeatureParity section in `AGENTS.md`):
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
fp init --project <slug> --surface <surface>
|
|
221
|
+
```
|
|
222
|
+
|
|
121
223
|
## Quick reference
|
|
122
224
|
|
|
123
225
|
| Task | Command |
|
|
124
226
|
|------|---------|
|
|
227
|
+
| Onboard a repo | `fp init --project X` |
|
|
125
228
|
| List requirements | `fp list --project X` |
|
|
126
229
|
| Show gaps | `fp list --project X --gaps` |
|
|
127
230
|
| Show details | `fp show <slug> --project X` |
|
|
128
231
|
| Report evidence | `fp report <slug> --project X --surface Y --file Z --repo A/B` |
|
|
129
232
|
| Report from JUnit | `fp report --junit file.xml --project X --surface Y --repo A/B` |
|
|
130
233
|
| Propose requirement | `fp propose --project X --slug Y --name "..."` |
|
|
234
|
+
| Propose a change | `fp propose-change <slug> --project X --why "..." --reason "..."` |
|
|
235
|
+
| Propose a deletion | `fp propose-change <slug> --project X --removal --reason "..."` |
|
|
236
|
+
| View change history | `fp history <slug> --project X` |
|
|
237
|
+
| Enforce parity | `fp check --project X` |
|
|
238
|
+
| Gate on this run | `fp check --project X --surface Y --junit file.xml` |
|
|
131
239
|
| View matrix | `fp matrix --project X` |
|
|
132
240
|
| List surfaces | `fp surfaces --project X` |
|
|
133
241
|
| List projects | `fp projects` |
|
data/skills/fp/references/cli.md
CHANGED
|
@@ -49,17 +49,22 @@ List requirements.
|
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
fp list --project stowzilla # All active requirements
|
|
52
|
-
fp list --project stowzilla --gaps # Requirements
|
|
52
|
+
fp list --project stowzilla --gaps # Requirements with a required surface lacking evidence
|
|
53
53
|
fp list --project stowzilla --status draft
|
|
54
54
|
fp list --project stowzilla --json
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
| Flag | Description |
|
|
58
58
|
|------|-------------|
|
|
59
|
-
| `--project` | Project slug (
|
|
60
|
-
| `--gaps` | Show only requirements
|
|
59
|
+
| `--project` | Project slug (optional if `.featureparity.yml` declares one) |
|
|
60
|
+
| `--gaps` | Show only requirements with an uncovered required surface |
|
|
61
61
|
| `--status` | Filter by status: active, draft, archived |
|
|
62
62
|
| `--category` | Filter by category |
|
|
63
|
+
| `--surface` | Restrict to one surface |
|
|
64
|
+
|
|
65
|
+
`--gaps` reports a requirement when one of its required surfaces has no real evidence —
|
|
66
|
+
missing, stubbed, or failing. The table's UNCOVERED column names those surfaces and their
|
|
67
|
+
state.
|
|
63
68
|
|
|
64
69
|
### fp show
|
|
65
70
|
|
|
@@ -147,16 +152,190 @@ fp propose --project stowzilla \
|
|
|
147
152
|
|
|
148
153
|
**Agents always create requirements as draft.** A human must activate in the web app.
|
|
149
154
|
|
|
155
|
+
### fp propose-change
|
|
156
|
+
|
|
157
|
+
Propose a change to a requirement that already exists — or propose deleting it.
|
|
158
|
+
Recorded with what the requirement *was*, a field-by-field diff, and what prompted the
|
|
159
|
+
change. The requirement drops back to **draft** until a human approves or rejects.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
fp propose-change print_container_qr --project stowzilla \
|
|
163
|
+
--why "Labels must survive a freezer" \
|
|
164
|
+
--acceptance "QR scans after 24h at -20C" \
|
|
165
|
+
--reason "Cold-chain rollout" \
|
|
166
|
+
--source-type fizzy \
|
|
167
|
+
--source-url https://app.fizzy.do/6098707/cards/1377
|
|
168
|
+
|
|
169
|
+
# Propose deletion instead
|
|
170
|
+
fp propose-change print_container_qr --project stowzilla \
|
|
171
|
+
--removal --reason "Superseded by print_container_label"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
| Flag | Required | Description |
|
|
175
|
+
|------|----------|-------------|
|
|
176
|
+
| `<slug>` | Yes | Positional: the requirement to change |
|
|
177
|
+
| `--project` | No | Project slug (optional if `.featureparity.yml` declares one) |
|
|
178
|
+
| `--removal` | No | Propose deleting the requirement instead of modifying it |
|
|
179
|
+
| `--name` | No | Proposed title |
|
|
180
|
+
| `--why` | No | Proposed justification |
|
|
181
|
+
| `--acceptance` | No | Proposed acceptance criteria |
|
|
182
|
+
| `--required` | No | Proposed surfaces (comma-separated; agents cannot remove existing ones) |
|
|
183
|
+
| `--category` | No | Proposed category |
|
|
184
|
+
| `--parent` | No | Proposed parent requirement, by slug |
|
|
185
|
+
| `--slug` | No | Proposed new slug — **humans only**; breaks existing `fp:<slug>` markers |
|
|
186
|
+
| `--reason` | No | Why the change is needed |
|
|
187
|
+
| `--source-type` | No | `fizzy`, `discord`, `github_issue`, `jira`, `notion`, `manual`, `ai_chat` |
|
|
188
|
+
| `--source-url` | No | Link to the card/message/issue that prompted it |
|
|
189
|
+
| `--source-context` | No | Free-text context |
|
|
190
|
+
|
|
191
|
+
At least one proposed value (or `--removal`) is required — a bare invocation would knock a
|
|
192
|
+
live requirement back to draft for nothing, so it errors instead.
|
|
193
|
+
|
|
194
|
+
Refusals, all printed verbatim:
|
|
195
|
+
|
|
196
|
+
| Situation | Status |
|
|
197
|
+
|-----------|--------|
|
|
198
|
+
| Agent tries to change `--slug` | 403 |
|
|
199
|
+
| Agent tries to remove a required surface | 403 |
|
|
200
|
+
| Proposed values match the current text | 422 |
|
|
201
|
+
| Requirement already has a proposal awaiting review | 409 |
|
|
202
|
+
| Requirement is archived | 422 |
|
|
203
|
+
|
|
204
|
+
**Agents cannot approve or reject.** Reviewing is a human action in the web app.
|
|
205
|
+
|
|
206
|
+
### fp history
|
|
207
|
+
|
|
208
|
+
Show a requirement's change history: what changed, who changed it, what prompted it, and
|
|
209
|
+
how the test evidence moved.
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
fp history print_container_qr --project stowzilla
|
|
213
|
+
fp history print_container_qr --project stowzilla --pending # only what awaits review
|
|
214
|
+
fp history print_container_qr --project stowzilla --limit 5 # most recent 5 entries
|
|
215
|
+
fp history print_container_qr --project stowzilla --json
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Entries are printed oldest first. Each shows the event (created, edited, approved,
|
|
219
|
+
change proposed, deletion proposed, change approved/rejected, archived, deleted), the
|
|
220
|
+
actor and whether they were a human or an agent, the field diff, the evidence diff, and
|
|
221
|
+
the review decision where one was made. An open proposal is called out at the end.
|
|
222
|
+
|
|
223
|
+
`--project` is optional when the repo has a `.featureparity.yml`.
|
|
224
|
+
|
|
150
225
|
### fp matrix
|
|
151
226
|
|
|
152
|
-
Display the parity matrix.
|
|
227
|
+
Display the parity matrix. Cells show the state of the evidence filed for each
|
|
228
|
+
(requirement, surface) pair.
|
|
153
229
|
|
|
154
230
|
```bash
|
|
155
231
|
fp matrix --project stowzilla # ASCII table
|
|
156
|
-
fp matrix --project stowzilla --csv # CSV export
|
|
232
|
+
fp matrix --project stowzilla --csv # CSV export (state names, not glyphs)
|
|
157
233
|
fp matrix --project stowzilla --json # JSON
|
|
234
|
+
fp matrix --project stowzilla --surface api
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| Glyph | State | Meaning |
|
|
238
|
+
|-------|-------|---------|
|
|
239
|
+
| `✓` | passing | CI ran the test and it was green |
|
|
240
|
+
| `✗` | failing | CI ran the test and it was red, or the marker vanished |
|
|
241
|
+
| `●` | present | an agent found the `fp:` marker; no CI verdict yet |
|
|
242
|
+
| `○` | stub | reported as a stub / skipped test |
|
|
243
|
+
| `⚠` | suspect | the requirement changed; evidence needs re-verification |
|
|
244
|
+
| `·` | missing | no evidence for this surface |
|
|
245
|
+
| `-` | — | the requirement does not require this surface |
|
|
246
|
+
|
|
247
|
+
A slug suffixed with `✎` has a proposed change awaiting review — it is still measured by
|
|
248
|
+
the gate, and its evidence is `suspect` until someone reviews the change. `--csv` writes
|
|
249
|
+
`<slug> (under review)` instead of the glyph.
|
|
250
|
+
|
|
251
|
+
### fp check
|
|
252
|
+
|
|
253
|
+
The enforcement gate. Exits **1** when the project violates its enforcement level, so it
|
|
254
|
+
can be a required status check on a pull request.
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
fp check # project from .featureparity.yml
|
|
258
|
+
fp check --project stowzilla --surface api
|
|
259
|
+
fp check --level covered # override the project's level for this run
|
|
260
|
+
fp check --warn-only # print violations, still exit 0
|
|
261
|
+
fp check --json
|
|
262
|
+
fp check --surface api --junit junit.xml # gate on this run's results (preview)
|
|
158
263
|
```
|
|
159
264
|
|
|
265
|
+
| Flag | Description |
|
|
266
|
+
|------|-------------|
|
|
267
|
+
| `--project` | Project slug (optional if `.featureparity.yml` declares one) |
|
|
268
|
+
| `--surface` | Gate one surface only |
|
|
269
|
+
| `--level` | Override the project's enforcement level |
|
|
270
|
+
| `--junit` | Evaluate this run's JUnit results without saving them (repeatable) |
|
|
271
|
+
| `--warn-only` | Report violations but exit 0 |
|
|
272
|
+
|
|
273
|
+
**Enforcement levels** (the level is configured on the project; don't hardcode one):
|
|
274
|
+
|
|
275
|
+
| Level | A build fails when |
|
|
276
|
+
|-------|--------------------|
|
|
277
|
+
| `off` | never — report only |
|
|
278
|
+
| `regression` | a test that was passing has gone red |
|
|
279
|
+
| `covered` | any required surface lacks real (non-stub) evidence |
|
|
280
|
+
| `strict` | any required surface isn't green in CI |
|
|
281
|
+
|
|
282
|
+
`suspect` only violates at `strict`: it means the requirement's wording moved, not that
|
|
283
|
+
the test broke.
|
|
284
|
+
|
|
285
|
+
**Requirements awaiting review still count.** `fp propose-change` demotes a requirement to
|
|
286
|
+
`draft`, but the gate keeps measuring it — otherwise proposing a change would be a way to
|
|
287
|
+
turn a red gate green. Coverage is still demanded; only evidence freshness relaxes, because
|
|
288
|
+
proposing marks the evidence `suspect`. `fp check` lists them separately from violations:
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
1 requirement(s) awaiting review of a proposed change (1 modification):
|
|
292
|
+
SLUG PROPOSED TITLE
|
|
293
|
+
print_qr modification Print QR on container label
|
|
294
|
+
These still count toward the gate — proposing a change is not a way out of it.
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`fp matrix` marks the same rows with `✎`, and `fp list` names them even when the default
|
|
298
|
+
`--status active` filter would otherwise hide them.
|
|
299
|
+
|
|
300
|
+
**Preview mode (`--junit`).** Without it, `fp check` sees only what has been recorded —
|
|
301
|
+
on a pull request, that's the default branch's state, not what the branch changed. With
|
|
302
|
+
it, the run's results are applied on top of the recorded grid and then discarded, so a
|
|
303
|
+
branch is judged on its own results without writing evidence that would churn the live
|
|
304
|
+
matrix for everyone.
|
|
305
|
+
|
|
306
|
+
Preview also catches the regression a green suite can't: a test that was deleted,
|
|
307
|
+
renamed, or had its `fp:` marker removed. The remaining tests all pass, so CI is happy,
|
|
308
|
+
while the requirement silently lost its only proof. Pass one `--junit` per surface —
|
|
309
|
+
`--surface` tells the server which cells the run had a chance to cover, and pooling
|
|
310
|
+
surfaces into one call would misattribute vanished tests.
|
|
311
|
+
|
|
312
|
+
### fp init
|
|
313
|
+
|
|
314
|
+
Onboard a repo onto FeatureParity. Writes three files and makes no API calls (so it works
|
|
315
|
+
before a key is configured).
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
fp init --project stowzilla --surface api
|
|
319
|
+
fp init --project stowzilla --no-workflow # skip the CI workflow
|
|
320
|
+
fp init --project stowzilla --no-agents # skip the AGENTS.md section
|
|
321
|
+
fp init --project stowzilla --force # refresh files written previously
|
|
322
|
+
fp init --project stowzilla --dir path/to/repo
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
| File | Purpose |
|
|
326
|
+
|------|---------|
|
|
327
|
+
| `.featureparity.yml` | declares the project slug, so `--project` becomes optional |
|
|
328
|
+
| `.github/workflows/feature-parity.yml` | the CI gate |
|
|
329
|
+
| `AGENTS.md` (appended) | instructions telling agents to mark and report their tests |
|
|
330
|
+
|
|
331
|
+
Existing files are skipped unless `--force`. The `AGENTS.md` section is appended between
|
|
332
|
+
marker comments and recognised on re-run, so it is never duplicated.
|
|
333
|
+
|
|
334
|
+
The `AGENTS.md` section tells agents to bind a test to a requirement with `fp:<slug>`, to
|
|
335
|
+
propose a new requirement when none covers the work, and to use `fp propose-change`
|
|
336
|
+
instead of editing or ignoring a requirement that no longer says the right thing — plus
|
|
337
|
+
the guards they would otherwise learn from a 403.
|
|
338
|
+
|
|
160
339
|
### fp profile
|
|
161
340
|
|
|
162
341
|
Manage named profiles. Each profile stores an API key and (optionally) the
|
|
@@ -222,14 +401,32 @@ fp repos unset customer_android --project stowzilla
|
|
|
222
401
|
|------|---------|
|
|
223
402
|
| 0 | Success |
|
|
224
403
|
| 1 | General error (invalid flags, missing required args, API error) |
|
|
404
|
+
| 1 | `fp check`: the parity gate failed (violations) |
|
|
405
|
+
| 130 | Interrupted (Ctrl-C) |
|
|
406
|
+
|
|
407
|
+
`fp check` deliberately reuses exit 1 for a failed gate rather than a distinct code: CI
|
|
408
|
+
treats any non-zero as a failure, and a special code would only invite scripts to ignore
|
|
409
|
+
it. Use `--json` to distinguish a gate failure (`ok: true`, `passing: false`) from a
|
|
410
|
+
request failure (`ok: false`).
|
|
225
411
|
|
|
226
412
|
## Configuration files
|
|
227
413
|
|
|
228
|
-
- `~/.config/fp/config.yml` — Profiles and global settings
|
|
414
|
+
- `~/.config/fp/config.yml` — Profiles and global settings (per-machine, holds API keys)
|
|
415
|
+
- `.featureparity.yml` — Repo-local, committed. Declares which project a checkout belongs
|
|
416
|
+
to so CI and agents don't need `--project`. Discovered by walking up from the working
|
|
417
|
+
directory. Written by `fp init`.
|
|
229
418
|
- `FP_API_KEY` environment variable overrides all profiles
|
|
230
419
|
- `FP_API_URL` environment variable overrides API URL
|
|
231
420
|
- `FP_PROFILE` environment variable selects a profile
|
|
232
421
|
|
|
422
|
+
### .featureparity.yml
|
|
423
|
+
|
|
424
|
+
```yaml
|
|
425
|
+
project: stowzilla # required — the project slug
|
|
426
|
+
enforcement: regression # optional — usually omit; the level lives on the project
|
|
427
|
+
surface: api # optional — default surface for a single-surface repo
|
|
428
|
+
```
|
|
429
|
+
|
|
233
430
|
## API URL priority
|
|
234
431
|
|
|
235
432
|
1. `--api-url` flag
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: featureparity
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-http
|
|
@@ -95,13 +95,19 @@ files:
|
|
|
95
95
|
- lib/fp/client.rb
|
|
96
96
|
- lib/fp/commands.rb
|
|
97
97
|
- lib/fp/commands/base.rb
|
|
98
|
+
- lib/fp/commands/check.rb
|
|
99
|
+
- lib/fp/commands/ci_report.rb
|
|
98
100
|
- lib/fp/commands/config_cmd.rb
|
|
101
|
+
- lib/fp/commands/export.rb
|
|
99
102
|
- lib/fp/commands/help.rb
|
|
103
|
+
- lib/fp/commands/history.rb
|
|
104
|
+
- lib/fp/commands/init.rb
|
|
100
105
|
- lib/fp/commands/list.rb
|
|
101
106
|
- lib/fp/commands/matrix.rb
|
|
102
107
|
- lib/fp/commands/profile.rb
|
|
103
108
|
- lib/fp/commands/projects.rb
|
|
104
109
|
- lib/fp/commands/propose.rb
|
|
110
|
+
- lib/fp/commands/propose_change.rb
|
|
105
111
|
- lib/fp/commands/report.rb
|
|
106
112
|
- lib/fp/commands/repos.rb
|
|
107
113
|
- lib/fp/commands/setup.rb
|
|
@@ -112,6 +118,8 @@ files:
|
|
|
112
118
|
- lib/fp/config.rb
|
|
113
119
|
- lib/fp/junit.rb
|
|
114
120
|
- lib/fp/output.rb
|
|
121
|
+
- lib/fp/parity.rb
|
|
122
|
+
- lib/fp/project_config.rb
|
|
115
123
|
- lib/fp/version.rb
|
|
116
124
|
- skills/fp/SKILL.md
|
|
117
125
|
- skills/fp/references/cli.md
|