flagrm 0.4.1 → 1.0.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 +71 -2
- package/README.md +336 -104
- package/dist/cli.js +76 -12
- package/dist/core/change-filter.js +4 -1
- package/dist/core/config-snapshot.js +0 -4
- package/dist/core/config.js +119 -56
- package/dist/core/discover.js +1 -1
- package/dist/core/doctor.js +8 -0
- package/dist/core/install.js +17 -13
- package/dist/core/markdown.js +57 -0
- package/dist/core/report.js +29 -0
- package/dist/core/sources/launchdarkly.js +331 -0
- package/dist/core/sources/types.js +13 -0
- package/dist/core/stale.js +218 -0
- package/dist/core/types.js +2 -0
- package/package.json +2 -2
- package/skills/flagrm-remove/SKILL.md +2 -2
- package/skills/flagrm-remove/batch.md +1 -1
- package/skills/flagrm-review/SKILL.md +49 -0
- package/skills/flagrm-verify/SKILL.md +0 -54
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here. The format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
-
[Semantic Versioning](https://semver.org/)
|
|
5
|
+
[Semantic Versioning](https://semver.org/); the README's "Versioning" section
|
|
6
|
+
says what a minor release won't break. Before 1.0, a minor version could
|
|
6
7
|
contain breaking changes.
|
|
7
8
|
|
|
8
|
-
## [
|
|
9
|
+
## [1.0.0] - 2026-11-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- README "Versioning" section: from 1.0, commands, options, exit codes,
|
|
14
|
+
config keys, `--json` output, the `.flagrm/` files, the skill names and the
|
|
15
|
+
Stop hook command stay compatible within a major version.
|
|
16
|
+
- Releases are staged on npm from a `v*` tag by GitHub Actions, with
|
|
17
|
+
provenance, and go live only after a maintainer approves them with 2FA.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- A usage error found while parsing the command line (unknown option or
|
|
22
|
+
command, missing argument) exits 2, as documented, instead of 1, which
|
|
23
|
+
`verify` uses for a failed removal.
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
- **Breaking:** the `--frontend` and `--backend` options, and the top-level
|
|
28
|
+
`frontend:`, `backend:`, `frontendMethods:`, `backendMethods:` and
|
|
29
|
+
`backendAttributes:` config keys. List each project under `projects:`
|
|
30
|
+
instead, with `methods:` and `attributes:` on the project. A config that
|
|
31
|
+
still has one of these keys is rejected with a message naming its
|
|
32
|
+
replacement:
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
projects:
|
|
36
|
+
- { name: frontend, adapter: angular, path: ./frontend }
|
|
37
|
+
- { name: backend, adapter: dotnet, path: ./backend }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **Breaking:** `flagrm update` no longer replaces the `flagrm-verify` skill
|
|
41
|
+
and prompt file of flagrm 0.4 and earlier, and `doctor` no longer warns
|
|
42
|
+
about them. Coming from 0.4 or earlier, run `flagrm update` with flagrm 0.5
|
|
43
|
+
first, or run `flagrm init` (it adds `flagrm-review`) and delete the
|
|
44
|
+
`flagrm-verify` skill directories and `.github/prompts/flagrm-verify.prompt.md`
|
|
45
|
+
by hand.
|
|
46
|
+
|
|
47
|
+
## [0.5.0] - 2026-10-23
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- `flagrm stale [flags...]` and the `launchdarkly:` config section: reads a
|
|
52
|
+
LaunchDarkly project (read-only, token from `LD_API_TOKEN`) and says which
|
|
53
|
+
flags are ready to remove from code (`remove`), not in this repository
|
|
54
|
+
(`unreferenced`) or should stay (`keep`, with reasons), with `--json` and
|
|
55
|
+
`--md`. Exit 3 when LaunchDarkly can't be reached or refuses the request
|
|
56
|
+
(rejected token, no access to the project, unknown project key).
|
|
57
|
+
The `flagrm-remove` skill uses it instead of asking whether a flag set in
|
|
58
|
+
LaunchDarkly is ON everywhere; `examples/ci/` has scheduled GitHub Actions
|
|
59
|
+
and Azure Pipelines workflows that open a removal PR per ready flag, with
|
|
60
|
+
an advisory `/flagrm-review` by a second, read-only agent in the PR body.
|
|
61
|
+
- `flagrm doctor` warns while an old `flagrm-verify` skill is still installed.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- **Breaking:** the `/flagrm-verify` skill is replaced by `/flagrm-review`.
|
|
66
|
+
Optional, in a new session: it runs `flagrm verify`, stops on a failure, and
|
|
67
|
+
on a pass reviews the diff since the baseline (the ON path kept, deleted
|
|
68
|
+
tests OFF-path only, nothing unrelated changed, no lost side effects). It
|
|
69
|
+
reports findings, edits nothing and writes no commit message; that stays
|
|
70
|
+
with `/flagrm-remove`, which now points to the review. Run `flagrm update`:
|
|
71
|
+
it deletes the `flagrm-verify` skills and Copilot prompt file that flagrm
|
|
72
|
+
installed (edits made to them are lost) and installs `flagrm-review`.
|
|
73
|
+
`flagrm verify` itself is unchanged.
|
|
74
|
+
- README: how to install without a `package.json` (globally or with `npx`),
|
|
75
|
+
and what to do when the UI and the backend are in separate repositories.
|
|
9
76
|
|
|
10
77
|
## [0.4.1] - 2026-10-12
|
|
11
78
|
|
|
@@ -287,6 +354,8 @@ removes the flag through the `flagrm-remove` skill, and flagrm checks the result
|
|
|
287
354
|
`doctor` warns about an incremental `dotnet build`, which hides existing
|
|
288
355
|
warnings from the dead-code comparison.
|
|
289
356
|
|
|
357
|
+
[1.0.0]: https://github.com/dino-cosic/flagrm/releases/tag/v1.0.0
|
|
358
|
+
[0.5.0]: https://github.com/dino-cosic/flagrm/releases/tag/v0.5.0
|
|
290
359
|
[0.4.1]: https://github.com/dino-cosic/flagrm/releases/tag/v0.4.1
|
|
291
360
|
[0.4.0]: https://github.com/dino-cosic/flagrm/releases/tag/v0.4.0
|
|
292
361
|
[0.3.0]: https://github.com/dino-cosic/flagrm/releases/tag/v0.3.0
|
package/README.md
CHANGED
|
@@ -2,36 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/dino-cosic/flagrm/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/flagrm)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](LICENSE)
|
|
5
7
|
|
|
6
|
-
Remove feature flags with your AI coding agent, safely
|
|
8
|
+
**Remove feature flags with your AI coding agent, safely.**
|
|
7
9
|
|
|
8
|
-
flagrm
|
|
9
|
-
guardrails to check the result. It records a baseline before the edit, then
|
|
10
|
-
gates the removal with `flagrm verify`, which fails while the flag is still
|
|
11
|
-
referenced, the build breaks, a test or lint regresses, or the removal leaves
|
|
12
|
-
unused code behind.
|
|
10
|
+
[Website](https://flagrm.com) · [Quick start](#quick-start) · [How it works](#how-it-works) · [Changelog](CHANGELOG.md)
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
Removing a feature flag looks easy: delete the `if`, keep the ON branch. In a
|
|
13
|
+
real codebase the flag hides behind wrappers and aliases, reaches templates and
|
|
14
|
+
config files, has tests for the OFF path, and leaves behind methods nothing
|
|
15
|
+
calls anymore. An AI agent handles most of that well, but it decides on its own
|
|
16
|
+
when it's done, and that's when it misses things.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
flagrm gives Claude Code, GitHub Copilot and Codex a flag-removal skill and a
|
|
19
|
+
deterministic definition of "done":
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
- **The agent does the reasoning and the edits.** It finds the flag, its
|
|
22
|
+
aliases and wrappers, removes the OFF path, and refactors the code the flag
|
|
23
|
+
was passed into. flagrm never edits your source code.
|
|
24
|
+
- **flagrm decides when the removal is finished.** It records a baseline before
|
|
25
|
+
the first edit, then `flagrm verify` fails while the flag is still
|
|
26
|
+
referenced, the build breaks, a test or lint regresses, or the removal
|
|
27
|
+
leaves unused code behind.
|
|
28
|
+
- **The agent doesn't stop early.** In Claude Code, a Stop hook keeps the
|
|
29
|
+
agent from finishing until `verify` passes for the exact files on disk.
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- Node.js 22 or later
|
|
27
|
-
- git: `baseline` and `verify` compare against the commit the removal started from
|
|
28
|
-
- An AI coding agent that reads skills: Claude Code, GitHub Copilot or Codex
|
|
29
|
-
(the Stop hook is Claude Code only)
|
|
31
|
+
Angular and .NET projects work out of the box. Any other stack works through the
|
|
32
|
+
generic adapter.
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
scripts (`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`). Otherwise the
|
|
34
|
-
hook fails and does not guard the removal. `flagrm verify` still works.
|
|
34
|
+
> **Status:** flagrm follows semantic versioning from 1.0. See
|
|
35
|
+
> [Versioning](#versioning) for what a minor release won't break.
|
|
35
36
|
|
|
36
37
|
## Quick start
|
|
37
38
|
|
|
@@ -48,12 +49,8 @@ agent:
|
|
|
48
49
|
/flagrm-remove NewCheckout
|
|
49
50
|
```
|
|
50
51
|
|
|
51
|
-
The agent ends with a short overview
|
|
52
|
-
|
|
53
|
-
flags at once: `/flagrm-remove NewCheckout DarkMode` asks the ON question and
|
|
54
|
-
whether to commit each flag once, then removes them one after another. To
|
|
55
|
-
check a removal later, run `/flagrm-verify NewCheckout`. The overview looks
|
|
56
|
-
like this (abridged):
|
|
52
|
+
The agent ends with a short overview and a proposed commit message; it commits
|
|
53
|
+
only when you ask. The overview looks like this (abridged):
|
|
57
54
|
|
|
58
55
|
```md
|
|
59
56
|
### flagrm verify: NewCheckout ✅ pass
|
|
@@ -74,32 +71,67 @@ like this (abridged):
|
|
|
74
71
|
**Tests renamed (1):** `CheckoutComponent › places the order when NewCheckout is on` → `CheckoutComponent › places the order`
|
|
75
72
|
```
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
- **Several flags:** `/flagrm-remove NewCheckout DarkMode` asks once whether
|
|
75
|
+
they are all ON and whether to commit each, then removes them one after
|
|
76
|
+
another, one commit each.
|
|
77
|
+
- **Independent review:** `/flagrm-review NewCheckout` in a new session runs
|
|
78
|
+
`flagrm verify`, then checks what it can't: that the ON path is the code
|
|
79
|
+
that stayed and that each deleted test covered only the OFF path. It
|
|
80
|
+
reports findings and edits nothing.
|
|
81
|
+
- **Tests that no longer run** are sorted by why: deleted in the diff (the OFF
|
|
82
|
+
path's tests), renamed without the flag in the name, or left out by a test
|
|
83
|
+
command changed since the baseline. Only a test that no longer runs for none
|
|
84
|
+
of these reasons is a warning, for the agent to explain in its notes.
|
|
85
|
+
|
|
86
|
+
### Requirements
|
|
87
|
+
|
|
88
|
+
- Node.js 22 or later
|
|
89
|
+
- git: `baseline` and `verify` compare against the commit the removal started from
|
|
90
|
+
- An AI coding agent that reads skills: Claude Code, GitHub Copilot or Codex
|
|
91
|
+
(the Stop hook is Claude Code only)
|
|
92
|
+
|
|
93
|
+
No `package.json` (a pure .NET or Go repository)? Install globally with
|
|
94
|
+
`npm install -g flagrm`, or run `npx flagrm`. The Stop hook runs
|
|
95
|
+
`npx --no-install flagrm hook stop`, so it needs a local or a global install.
|
|
96
|
+
|
|
97
|
+
On Windows, the Stop hook runs in Git Bash, or in PowerShell when Git Bash is
|
|
98
|
+
not installed. In PowerShell, `npx` needs an execution policy that allows
|
|
99
|
+
scripts (`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`). Otherwise the
|
|
100
|
+
hook fails and does not guard the removal. `flagrm verify` still works.
|
|
81
101
|
|
|
82
102
|
## How it works
|
|
83
103
|
|
|
104
|
+
```mermaid
|
|
105
|
+
flowchart LR
|
|
106
|
+
A["flagrm init<br/>once per repository"] --> B["/flagrm-remove flag"]
|
|
107
|
+
B --> C["flagrm baseline<br/>git commit, build, tests, names"]
|
|
108
|
+
C --> D["agent edits"]
|
|
109
|
+
D --> E{"flagrm verify"}
|
|
110
|
+
E -- fail --> D
|
|
111
|
+
E -- pass --> F["overview +<br/>commit message"]
|
|
112
|
+
H["Stop hook"] -. blocks finishing until verify passes .-> E
|
|
113
|
+
```
|
|
114
|
+
|
|
84
115
|
1. **`flagrm init`** asks which AI coding tools to set up (Claude Code,
|
|
85
116
|
GitHub Copilot, Codex), with the ones the repository already uses
|
|
86
|
-
pre-selected
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`.agents/skills
|
|
117
|
+
pre-selected. It writes `flagrm.config.yaml` for the Angular and .NET
|
|
118
|
+
projects it finds, adds `.flagrm/` to `.gitignore`, and installs each
|
|
119
|
+
tool's files:
|
|
120
|
+
|
|
121
|
+
| Tool | Files |
|
|
122
|
+
|---|---|
|
|
123
|
+
| Claude Code | `flagrm-remove` and `flagrm-review` skills in `.claude/skills/`, Stop hook in `.claude/settings.json` |
|
|
124
|
+
| GitHub Copilot | skills in `.github/skills/`, `/flagrm-remove` and `/flagrm-review` prompt files in `.github/prompts/` |
|
|
125
|
+
| Codex | skills in `.agents/skills/`, a pointer in `AGENTS.md` |
|
|
126
|
+
|
|
127
|
+
`--tool claude-code,codex` answers without asking; without a terminal it
|
|
128
|
+
takes the detected tools (all three when none is). The choice is saved as
|
|
129
|
+
`tools:`. `init` never overwrites or deletes a file.
|
|
95
130
|
2. **`/flagrm-remove <flag>`**: the agent checks that the tree is clean and runs
|
|
96
131
|
`flagrm baseline <flag>`, which records the git commit, the build and test
|
|
97
132
|
results, and the flag's names. It finds every usage with its own search,
|
|
98
133
|
records wrappers and aliases with `flagrm baseline <flag> --name`, removes
|
|
99
|
-
the flag, and repeats `flagrm verify <flag>` until it passes.
|
|
100
|
-
the overview and a commit message. With several flags it asks once whether
|
|
101
|
-
they are all ON and whether to commit each, then removes them in order,
|
|
102
|
-
one commit each.
|
|
134
|
+
the flag, and repeats `flagrm verify <flag>` until it passes.
|
|
103
135
|
3. **The Stop hook** (Claude Code) keeps the agent from finishing while a
|
|
104
136
|
removal is in progress and not verified. In progress means a file git
|
|
105
137
|
tracks changed on top of the baseline commit (untracked files and
|
|
@@ -108,35 +140,57 @@ of these reasons is a warning, for the agent to explain in its notes.
|
|
|
108
140
|
(such as `exclude`), or the last verify used `--skip`. It guards against
|
|
109
141
|
finishing by accident, not an agent working around it: see
|
|
110
142
|
[SECURITY.md](SECURITY.md#what-it-doesnt-guarantee).
|
|
111
|
-
4. **`/flagrm-
|
|
112
|
-
`flagrm verify <flag> --
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
| `init` | Set up the config and `.gitignore` entry, and each chosen AI tool's files (asks, or `--tool claude-code,copilot,codex`; saved as `tools:`). Never overwrites or deletes | 0, 2 usage error |
|
|
119
|
-
| `doctor` | Check the config, the build, test and lint commands (marking adapter defaults; `--run` runs them and fails when tests write no readable `testResults`), the .NET SDK `global.json` asks for, `cargo` and Docker where projects need them, git, `.gitignore`, installed skills and hook, and abandoned baselines | 0 ok, 1 problems |
|
|
120
|
-
| `scope` | For each .NET project, show what the machine lacks and which projects a solution filter would leave out; `--write` creates `flagrm.slnf`, adds it to `.git/info/exclude` and points the project's build and test at it | 0 nothing missing, 1 something missing |
|
|
121
|
-
| `update` | Refresh the installed skills, prompt files, `AGENTS.md` block and hook from this flagrm version, for the config's `tools:` | 0 |
|
|
122
|
-
| `list` | List every feature flag in the project: definitions, configured state per environment, reference counts; flow names include renamed imports of a flag registry (`import { FeatureFlags as AppFeatureFlags }`) and, in Angular, the fields, getters, methods and locals holding a flag and a child component's input bound to one | 0 |
|
|
123
|
-
| `baseline <flag>` | Before any edit, record the git commit, build, test and lint results and the flag's names in `.flagrm/<flag>/baseline.json`. In .NET and Angular code the names include the locals, fields, getters, parameters and methods the flag's value travels through when they name the flag (a local, parameter or field is checked only in its own file, and an Angular component's template); generic or ambiguous ones are listed as `suggestedNames`, with `parameterizedTests` that pass the value as a literal. `--file` (repeatable) limits `--name` entries to those files (and records them as wrappers). `--name X --kind alias\|wrapper` adds names to an existing baseline (a name whose declaration reads only other flags is refused); `--json` prints a summary (test counts, at most 20 failed test names; the file keeps everything); `--force` starts over, but only while the code is unedited since the baseline commit; `--accept config,failures` acknowledges, also mid-removal, a config change or the baseline's failing tests so verify stops warning about them; `--no-checks` skips build, tests and lint; build, test and lint results of a passing verify on the same tree and commands are reused (`--no-reuse` runs them) | 0, 2 usage error |
|
|
124
|
-
| `verify <flag>` | Gate the removal with `leftovers`, `dead-code`, `build`, `tests`, `lint` and `coverage`. Writes `.flagrm/<flag>/verify.json`. `--md` prints the overview, `--json` the full result, `--skip` skips checks, `--strict` fails on warnings; reuses its own or another flag's passing runs on the same tree and commands (`--no-reuse` runs them) | 0 pass, 1 fail, 2 usage error |
|
|
125
|
-
| `hook stop` | The Claude Code Stop hook installed by `init` | always 0 |
|
|
126
|
-
|
|
127
|
-
`list`, `baseline` and `verify` accept `--json`, `--config <path>`, and
|
|
128
|
-
`--include` / `--exclude <glob>`.
|
|
143
|
+
4. **`/flagrm-review <flag>`** (optional), in a new session, reviews the
|
|
144
|
+
removal: it runs `flagrm verify <flag> --json` and stops on a failure; on
|
|
145
|
+
a pass it reads the diff since the baseline commit and checks that the ON
|
|
146
|
+
path was kept, deleted tests covered only the OFF path, and nothing
|
|
147
|
+
unrelated changed or lost a side effect. Its findings never change the
|
|
148
|
+
verify result. A new session matters: an agent reviewing its own removal
|
|
149
|
+
checks its own reasoning.
|
|
129
150
|
|
|
130
151
|
## What `verify` checks
|
|
131
152
|
|
|
132
153
|
| Check | Fails when |
|
|
133
154
|
|---|---|
|
|
134
|
-
| `leftovers` | A recorded name (the flag literal, its constants, wrappers the agent recorded) is still in code or config
|
|
135
|
-
| `dead-code` | The compiler reports new unused code (locals, imports, private members) in changed files
|
|
155
|
+
| `leftovers` | A recorded name (the flag literal, its constants, wrappers the agent recorded) is still in code or config |
|
|
156
|
+
| `dead-code` | The compiler reports new unused code (locals, imports, private members) in changed files |
|
|
136
157
|
| `build` | A project's build command fails |
|
|
137
|
-
| `tests` | A test that passed at the baseline fails
|
|
138
|
-
| `lint` | The project's `lint` command fails where it passed at the baseline
|
|
139
|
-
| `coverage` | A changed file that no
|
|
158
|
+
| `tests` | A test that passed at the baseline fails |
|
|
159
|
+
| `lint` | The project's `lint` command fails where it passed at the baseline |
|
|
160
|
+
| `coverage` | A changed file that no build, test or lint command checks (a warning, so it fails only with `--strict`) |
|
|
161
|
+
|
|
162
|
+
In typed code, deleting the flag's definition turns every missed reference into
|
|
163
|
+
a compile error, so the build does most of the work. `leftovers` covers what the
|
|
164
|
+
compiler cannot see: config files, templates, comments and wrappers.
|
|
165
|
+
|
|
166
|
+
<details>
|
|
167
|
+
<summary>Warnings and edge cases per check</summary>
|
|
168
|
+
|
|
169
|
+
- **`leftovers`**: a mention in a comment is a warning, and so is a test that
|
|
170
|
+
checks text the removal deleted from the code (a test of the OFF path that
|
|
171
|
+
never names the flag), or deleted text whose replacement on the same line no
|
|
172
|
+
test checks (lost ON coverage).
|
|
173
|
+
- **`dead-code`**: a method, property, field or type the removal left named
|
|
174
|
+
only where it is declared is a warning (compilers don't report public
|
|
175
|
+
members).
|
|
176
|
+
- **`tests`**: tests that already failed at the baseline are known failures and
|
|
177
|
+
only warn; without `testResults` they are read from the test log (Karma,
|
|
178
|
+
Jest, Vitest, `dotnet test`), and count as known only when the log names
|
|
179
|
+
every failing test. A test that no longer runs though the diff neither
|
|
180
|
+
deletes nor renames it and the test config is unchanged, and a changed test
|
|
181
|
+
file none of whose tests ran, are warnings for the agent to account for.
|
|
182
|
+
Without `testResults`, deleted and renamed tests come from the changed test
|
|
183
|
+
files, and a test count that dropped by more than the diff deletes is a
|
|
184
|
+
warning (info when the changed test files use data rows such as
|
|
185
|
+
`[InlineData]`, `TestCase`, `DataRow` or `.each`, whose cases count
|
|
186
|
+
separately, or when the test config changed since the baseline).
|
|
187
|
+
- **`lint`**: a lint that already failed at the baseline only warns, unless its
|
|
188
|
+
log has error lines the baseline's didn't (line and column numbers ignored);
|
|
189
|
+
a missing package script or ESLint config is a setup problem and fails.
|
|
190
|
+
- **`coverage`**: a file counts as unchecked when it is outside every project
|
|
191
|
+
with a command, or outside its `covers` globs. Files already dirty at the
|
|
192
|
+
baseline and unchanged since don't count, nor do projects whose commands
|
|
193
|
+
`--skip` left out.
|
|
140
194
|
|
|
141
195
|
The baseline also records the configuration it was taken with: each project's
|
|
142
196
|
resolved build, test and lint commands, the files they name (a `.slnf`, a
|
|
@@ -144,9 +198,76 @@ resolved build, test and lint commands, the files they name (a `.slnf`, a
|
|
|
144
198
|
of it changed since, the check it affects warns and shows the difference: a
|
|
145
199
|
narrower test command makes "no new failures" mean less.
|
|
146
200
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
201
|
+
</details>
|
|
202
|
+
|
|
203
|
+
## Commands
|
|
204
|
+
|
|
205
|
+
| Command | What it does | Exit codes |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `init` | Set up the config, `.gitignore` entry and each chosen AI tool's files | 0, 2 usage error |
|
|
208
|
+
| `doctor` | Check the config, commands, toolchain, git setup, installed skills and hook | 0 ok, 1 problems |
|
|
209
|
+
| `scope` | Show which .NET projects this machine can't build and write a solution filter without them | 0 nothing missing, 1 something missing |
|
|
210
|
+
| `update` | Refresh the installed skills, prompt files, `AGENTS.md` block and hook | 0 |
|
|
211
|
+
| `list` | List every feature flag: definitions, configured state per environment, reference counts | 0 |
|
|
212
|
+
| `baseline <flag>` | Before any edit, record the git commit, build, test and lint results and the flag's names | 0, 2 usage error |
|
|
213
|
+
| `verify <flag>` | Gate the removal with the [checks above](#what-verify-checks) | 0 pass, 1 fail, 2 usage error |
|
|
214
|
+
| `stale [flags...]` | Ask LaunchDarkly which flags are ready to remove from code | 0 evaluated, 2 usage error, 3 LaunchDarkly unreachable or refused the request (token, access, project key) |
|
|
215
|
+
| `hook stop` | The Claude Code Stop hook installed by `init` | always 0 |
|
|
216
|
+
|
|
217
|
+
`list`, `baseline`, `verify` and `stale` accept `--json`, `--config <path>`, and
|
|
218
|
+
`--include` / `--exclude <glob>`.
|
|
219
|
+
|
|
220
|
+
<details>
|
|
221
|
+
<summary>Command details and options</summary>
|
|
222
|
+
|
|
223
|
+
- **`init`**: asks which tools to set up, or `--tool claude-code,copilot,codex`;
|
|
224
|
+
saved as `tools:`. Never overwrites or deletes.
|
|
225
|
+
- **`doctor`**: checks the build, test and lint commands (marking adapter
|
|
226
|
+
defaults; `--run` runs them and fails when tests write no readable
|
|
227
|
+
`testResults`), the .NET SDK `global.json` asks for, `cargo` and Docker
|
|
228
|
+
where projects need them, git, `.gitignore`, installed skills and hook, and
|
|
229
|
+
abandoned baselines.
|
|
230
|
+
- **`scope`**: for each .NET project, shows what the machine lacks and which
|
|
231
|
+
projects a solution filter would leave out; `--write` creates
|
|
232
|
+
`flagrm.slnf`, adds it to `.git/info/exclude` and points the project's build
|
|
233
|
+
and test at it. See [When the machine can't build everything](#when-the-machine-cant-build-everything).
|
|
234
|
+
- **`update`**: refreshes files from this flagrm version, for the config's
|
|
235
|
+
`tools:`.
|
|
236
|
+
- **`list`**: flow names include renamed imports of a flag registry
|
|
237
|
+
(`import { FeatureFlags as AppFeatureFlags }`) and, in Angular, the fields,
|
|
238
|
+
getters, methods and locals holding a flag and a child component's input
|
|
239
|
+
bound to one.
|
|
240
|
+
- **`baseline <flag>`**: writes `.flagrm/<flag>/baseline.json`. In .NET and
|
|
241
|
+
Angular code the names include the locals, fields, getters, parameters and
|
|
242
|
+
methods the flag's value travels through when they name the flag (a local,
|
|
243
|
+
parameter or field is checked only in its own file, and an Angular
|
|
244
|
+
component's template); generic or ambiguous ones are listed as
|
|
245
|
+
`suggestedNames`, with `parameterizedTests` that pass the value as a
|
|
246
|
+
literal.
|
|
247
|
+
- `--name X --kind alias|wrapper` adds names to an existing baseline (a name
|
|
248
|
+
whose declaration reads only other flags is refused); `--file`
|
|
249
|
+
(repeatable) limits `--name` entries to those files (and records them as
|
|
250
|
+
wrappers).
|
|
251
|
+
- `--json` prints a summary (test counts, at most 20 failed test names; the
|
|
252
|
+
file keeps everything).
|
|
253
|
+
- `--force` starts over, but only while the code is unedited since the
|
|
254
|
+
baseline commit.
|
|
255
|
+
- `--accept config,failures` acknowledges, also mid-removal, a config change
|
|
256
|
+
or the baseline's failing tests so verify stops warning about them.
|
|
257
|
+
- `--no-checks` skips build, tests and lint. Build, test and lint results of
|
|
258
|
+
a passing verify on the same tree and commands are reused (`--no-reuse`
|
|
259
|
+
runs them).
|
|
260
|
+
- **`verify <flag>`**: writes `.flagrm/<flag>/verify.json`. `--md` prints the
|
|
261
|
+
overview, `--json` the full result, `--skip` skips checks, `--strict` fails
|
|
262
|
+
on warnings. Reuses its own or another flag's passing runs on the same tree
|
|
263
|
+
and commands (`--no-reuse` runs them).
|
|
264
|
+
- **`stale [flags...]`**: reads the config's `launchdarkly:` section
|
|
265
|
+
(read-only) and sorts every flag, or the LaunchDarkly keys or code names
|
|
266
|
+
given, into `remove`, `unreferenced` or `keep` with reasons. `--md` prints a
|
|
267
|
+
markdown summary, `--json` the full result. See
|
|
268
|
+
[LaunchDarkly and CI](#launchdarkly-and-ci).
|
|
269
|
+
|
|
270
|
+
</details>
|
|
150
271
|
|
|
151
272
|
## Configuration
|
|
152
273
|
|
|
@@ -185,38 +306,70 @@ path. `build` and `test` are resolved one at a time:
|
|
|
185
306
|
| a command | that command |
|
|
186
307
|
| `false` | none, not even the default |
|
|
187
308
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
309
|
+
| Key | What it does |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `testResults` | JUnit XML, TRX or Jest JSON (a path or a glob), so `verify` compares individual tests against the baseline |
|
|
312
|
+
| `lint` | Optional lint or type-check command, run like `build` and `test` |
|
|
313
|
+
| `covers` | Globs (relative to `path`) of the files the project's commands check |
|
|
314
|
+
| `timeout` | Seconds after which a build or test command fails, for example a test runner left in watch mode |
|
|
315
|
+
| `globs` | Globs (relative to `path`) of the files to scan for flags, replacing the adapter's default language globs |
|
|
316
|
+
| `methods` | Method names that evaluate a flag (`isEnabled`, `IsEnabledAsync`, ...), replacing the adapter's defaults |
|
|
317
|
+
| `attributes` | .NET attributes that gate code on a flag, replacing the defaults `FeatureGate` and `Feature` |
|
|
318
|
+
| `tools` | The AI coding tools whose files `init` and `update` manage (`claude-code`, `copilot`, `codex`) |
|
|
319
|
+
| `include` | Extra globs (relative to each project's path) to scan, on top of the language globs, such as `**/*.razor.cs` |
|
|
320
|
+
| `exclude` | Paths that don't count as part of a removal |
|
|
321
|
+
|
|
322
|
+
**`testResults`.** Only files written during the run are read, and every
|
|
323
|
+
matching file is merged, so a sharded run can use `./test-results/*.xml`. The
|
|
324
|
+
default .NET test command writes TRX to `.flagrm/test-results/<project>/`. So
|
|
325
|
+
does the default Angular test command, when the workspace's runner can write
|
|
326
|
+
results without setup: JUnit for Angular's Vitest builder and Vitest, Jest JSON
|
|
327
|
+
for Jest, and JUnit for Karma once karma.conf configures
|
|
328
|
+
`karma-junit-reporter`. A `test` command of your own needs its own
|
|
329
|
+
`testResults`, and `flagrm doctor` says what to add. Without result files,
|
|
330
|
+
`verify` reads failing test names and counts from the test log and deleted
|
|
331
|
+
tests from the diff, which is coarser: passing tests aren't named.
|
|
332
|
+
|
|
333
|
+
**`lint`** has no adapter default. Use it for code the build and tests don't
|
|
334
|
+
reach, such as end-to-end specs. A lint that already fails at the baseline only
|
|
335
|
+
warns, unless the removal adds error lines. flagrm doesn't check formatting:
|
|
336
|
+
put a formatter's check (`npx prettier --check .`) in `lint` if you want it.
|
|
337
|
+
|
|
338
|
+
**`covers`.** The `coverage` check warns about a changed file no project with a
|
|
339
|
+
command covers. Without `covers`, a project covers everything under its path.
|
|
340
|
+
|
|
341
|
+
**`tools`.** Dropping a tool leaves its files in place, and `init` names them so
|
|
342
|
+
you can delete them.
|
|
343
|
+
|
|
344
|
+
**`exclude`.** Paths matching `exclude` (relative to the config file or a
|
|
345
|
+
project's path; a directory excludes everything under it) and flagrm's own
|
|
346
|
+
setup files don't count as part of a removal, so generated output such as
|
|
347
|
+
`graphify-out/` that changes during a session doesn't invalidate a passing
|
|
348
|
+
verify or block the Stop hook. `--exclude` on the command line doesn't reach
|
|
349
|
+
the Stop hook.
|
|
350
|
+
|
|
351
|
+
### LaunchDarkly
|
|
352
|
+
|
|
353
|
+
An optional `launchdarkly:` section lets `flagrm stale` read the project's
|
|
354
|
+
flags from LaunchDarkly (see [LaunchDarkly and CI](#launchdarkly-and-ci)):
|
|
355
|
+
|
|
356
|
+
```yaml
|
|
357
|
+
launchdarkly:
|
|
358
|
+
project: shop # required: the LaunchDarkly project key
|
|
359
|
+
baseUrl: https://app.launchdarkly.com # default; EU and federal instances use their own
|
|
360
|
+
tokenEnv: LD_API_TOKEN # default; the variable holding the API token
|
|
361
|
+
environments: [production, staging] # default: every environment of the project
|
|
362
|
+
minDaysOn: 30 # default: days every environment must have served true
|
|
363
|
+
temporaryOnly: true # default: only flags marked temporary qualify
|
|
364
|
+
tags: { include: [], exclude: [permanent] } # must have every include tag and no exclude tag
|
|
365
|
+
map: { checkout-v2: NewCheckout } # LaunchDarkly key → code name, when the spelling differs
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
The API token is read only from the variable `tokenEnv` names; a `token` or
|
|
369
|
+
`apiKey` in the file is an error. It is sent only to `baseUrl`, which must use
|
|
370
|
+
`https` (plain `http` only to `localhost`). A reader role is enough: flagrm
|
|
371
|
+
never writes to LaunchDarkly. Without the section nothing changes, and
|
|
372
|
+
`flagrm doctor` warns when the token variable isn't set.
|
|
220
373
|
|
|
221
374
|
### When the machine can't build everything
|
|
222
375
|
|
|
@@ -241,6 +394,85 @@ For .NET, keep `--no-incremental` in `build`. An incremental build skips
|
|
|
241
394
|
up-to-date projects and prints none of their warnings, so the dead-code check
|
|
242
395
|
would have no baseline warnings to compare against.
|
|
243
396
|
|
|
397
|
+
## LaunchDarkly and CI
|
|
398
|
+
|
|
399
|
+
With a [`launchdarkly:` section](#launchdarkly) in the config, `flagrm stale`
|
|
400
|
+
reads the project's flags from LaunchDarkly and compares them with the code
|
|
401
|
+
(`flagrm list`'s inventory). Each flag gets one verdict:
|
|
402
|
+
|
|
403
|
+
| Verdict | Meaning |
|
|
404
|
+
|---|---|
|
|
405
|
+
| `remove` | Meets every condition, and one code name in this repository matches it |
|
|
406
|
+
| `unreferenced` | No code name in this repository matches it. Another repository may still read it, so this is not a suggestion to archive it |
|
|
407
|
+
| `keep` | Anything else, with the reasons: still targeted somewhere, serving `false`, on for too few days, permanent, a tag, an ambiguous name, data flagrm couldn't read, or a code name with no LaunchDarkly flag (archived or deleted: the SDK serves the code's fallback, which may be OFF) |
|
|
408
|
+
|
|
409
|
+
A flag meets the conditions when it is boolean, every checked environment
|
|
410
|
+
serves `true` to every context (no rules, individual targets, prerequisites or
|
|
411
|
+
rollouts; or targeting off with `true` as the off variation) and has for at
|
|
412
|
+
least `minDaysOn` days, it is marked temporary (unless `temporaryOnly: false`),
|
|
413
|
+
and its tags pass `tags`. The days count from the latest targeting change in
|
|
414
|
+
the audit log; a name, description or tag edit doesn't reset them. Without one,
|
|
415
|
+
they count from the environment's last change of any kind (marked `*`), which
|
|
416
|
+
can only make a flag wait longer. Set `environments` when the project has
|
|
417
|
+
per-developer or short-lived environments: by default every environment must
|
|
418
|
+
serve `true`. A LaunchDarkly key matches a code name through `map`, then by
|
|
419
|
+
exact name, then ignoring case and `-`, `_`, `.` and spaces (`new-checkout`
|
|
420
|
+
matches `NewCheckout`); two possible matches are reported, not guessed.
|
|
421
|
+
|
|
422
|
+
`stale` exits 0 whenever it could evaluate, whatever the verdicts: read them
|
|
423
|
+
from `--json`. Exit 2 is a usage or config error (no section, no token, an
|
|
424
|
+
unknown environment or flag name). Exit 3 means LaunchDarkly couldn't be
|
|
425
|
+
reached or refused the request: it rejected the token, the token may not read
|
|
426
|
+
the project, or it doesn't know the project key. A wrong project key is
|
|
427
|
+
reported this way because LaunchDarkly gives the same answer when the token
|
|
428
|
+
belongs to another organization or `baseUrl` points at another instance. A
|
|
429
|
+
token with LaunchDarkly's reader role is enough; flagrm only sends GET
|
|
430
|
+
requests.
|
|
431
|
+
|
|
432
|
+
When the config has `launchdarkly:`, the `flagrm-remove` skill runs
|
|
433
|
+
`flagrm stale <flag> --json` instead of asking whether a flag set only in
|
|
434
|
+
LaunchDarkly is ON everywhere, so a removal can run without anyone there.
|
|
435
|
+
[`examples/ci/github-actions.yml`](examples/ci/github-actions.yml) and
|
|
436
|
+
[`examples/ci/azure-pipelines.yml`](examples/ci/azure-pipelines.yml) run it on
|
|
437
|
+
a weekly schedule: they list the `remove` flags, run a headless Claude Code
|
|
438
|
+
agent per flag, run `flagrm verify` themselves and open a pull request only
|
|
439
|
+
when it passes. The agent job needs the project's build toolchain, and the
|
|
440
|
+
pull request is for a person to review.
|
|
441
|
+
|
|
442
|
+
Before the pull request opens, a second agent runs `/flagrm-review` in its own
|
|
443
|
+
process, without the removal's context, as an independent review. It can't
|
|
444
|
+
edit the tree (no Edit or Write tool, and Bash only for `flagrm verify` and
|
|
445
|
+
`git diff`), and it reuses verify's build and test runs, so it costs agent
|
|
446
|
+
tokens but no rebuild. Its findings go into the pull request in a collapsed
|
|
447
|
+
"Independent review" block. The review is advisory: when it fails, the pull
|
|
448
|
+
request still opens and says so. Delete the step if you don't want it.
|
|
449
|
+
|
|
450
|
+
## Separate repositories
|
|
451
|
+
|
|
452
|
+
flagrm works per git repository: the baseline is a commit of one repository,
|
|
453
|
+
and the config, `.flagrm/`, skills and hook live in its root. With the UI and
|
|
454
|
+
the backend in separate repositories, run `flagrm init` in each (the Angular
|
|
455
|
+
adapter in one, .NET in the other) and run `/flagrm-remove <flag>` in each.
|
|
456
|
+
flagrm does not coordinate the two: each repository gets its own verdict, the
|
|
457
|
+
agent has to record the flag's name in each (it may differ between UI and
|
|
458
|
+
API), and the order of the two removals is up to you.
|
|
459
|
+
|
|
460
|
+
## Versioning
|
|
461
|
+
|
|
462
|
+
flagrm follows [semantic versioning](https://semver.org/). Within 1.x, these
|
|
463
|
+
stay compatible:
|
|
464
|
+
|
|
465
|
+
- commands, their options and exit codes;
|
|
466
|
+
- `flagrm.config.yaml` keys and their meaning;
|
|
467
|
+
- `--json` output and the `.flagrm/` files: fields may be added, none removed
|
|
468
|
+
or changed, and a baseline recorded by 1.x is read by every later 1.x;
|
|
469
|
+
- the skill names (`/flagrm-remove`, `/flagrm-review`) and the Stop hook
|
|
470
|
+
command `init` installs.
|
|
471
|
+
|
|
472
|
+
Not covered: the text and markdown output, the wording of the skills, and
|
|
473
|
+
detection itself. A minor release may find more flag usages or unused code,
|
|
474
|
+
so a removal that passed `verify` before can fail it after an upgrade.
|
|
475
|
+
|
|
244
476
|
## Development
|
|
245
477
|
|
|
246
478
|
```sh
|