Check the rule against the exact source shape.
Call arguments are exact:
--set 'useFlag("new-ui")=false'
matches useFlag("new-ui") but not useFlag("new_ui"), useFlag("other"), or useFlag(flagName).
Static access can match:
features.newUi
features["newUi"]
features?.newUi
Dynamic access is intentionally ignored:
features[key]
A configured program-level or imported binding does not match a local parameter or variable with the same name.
import { FLAG } from "./flags"
function render(FLAG: boolean) {
// This local FLAG is not replaced by ./flags#FLAG=false.
}
A call result is propagated only through a stable binding.
let enabled = useFlag("new-ui")
enabled = readOverride()
Because enabled is reassigned, it is not safe to replace all later reads with one constant.
These are different selectors:
./flags#FLAG
../flags#FLAG
@acme/flags#FLAG
Use the exact string from the source import declaration.
Quote rules containing parentheses, spaces, #, or shell-sensitive punctuation:
npx flag-prune --set 'useFlag("new-ui")=false' src
For a string value with spaces:
npx flag-prune --set 'getVariant("checkout")="new treatment"' src
Configured call arguments must be literal strings, numbers, booleans, negative numbers, or null.
Valid:
--set 'resolveFlag("checkout", 2, true, null)=false'
Invalid:
--set 'resolveFlag(flagName)=false'
The call site may still contain additional dynamic arguments after the configured prefix.
The CLI accepts direct --set rules and does not load a config file:
npx flag-prune --set 'FLAG=false' src
There is no --config option.
Add one or more targets:
npx flag-prune --set 'FLAG=false' src packages/app
The target contained no supported source files, or only declaration files.
Supported extensions:
.js .jsx .mjs .cjs .ts .tsx .mts .cts
Files such as .d.ts and Markdown are skipped. An empty target set is a successful no-op with exit code 0.
The following directory names are skipped by default:
.git node_modules dist coverage
Any name passed with --ignore is also skipped.
Nested symlinks are skipped with a warning to avoid ambiguous or repeated traversal. Pass a symlink as a direct file target when you intentionally want to transform its resolved target.
The CLI processes files independently. If one file cannot be parsed, it reports a warning, skips that file, and continues with the rest.
Run with --strict when any skipped file should fail the command:
npx flag-prune --set 'FLAG=false' --strict src
Check that the file extension reflects its syntax. In particular, .ts, .mts, and .cts are parsed without JSX, while JSX-capable extensions enable JSX parsing.
flag-prune preserves the original formatting of untouched code, but a statement
it moves or rewrites is reprinted in a normalized style. A moved expression
statement gains a trailing semicolon, for example, even if the source relied on
automatic semicolon insertion. This keeps the output valid and is expected.
Run your formatter (Prettier, Biome, or ESLint with the semi and quotes
rules) as part of the recommended workflow; it normalizes
semicolon and quote style across the file so the final diff stays consistent.
Ordinary comments located only in removed code are removed and reported by default.
Preserve them:
npx flag-prune \
--set 'FLAG=false' \
--comment-policy preserve \
src
Protected license, preserve, TODO, FIXME, and copyright comments survive under every policy. See Safety guarantees.
This is expected:
import "./flags"
It preserves module initialization after the final configured binding is removed.
Remove it only when the module is proven side-effect-free:
npx flag-prune \
--set './flags#FLAG=false' \
--remove-side-effect-imports \
src
By default, constant conditions are collapsed while required evaluation is kept:
if (load() || true) run()
becomes the equivalent of:
load()
run()
Keep the original condition instead:
npx flag-prune \
--set 'FLAG=false' \
--skip-effectful-conditions \
src
Exit code 1 is used only when --check is present and files would change. Preview or write the migration, then rerun the check.
Exit code 2 indicates one of:
--strict.Read stderr for the specific error.
The default pass limit is 20. Increase it temporarily:
npx flag-prune \
--set 'FLAG=false' \
--max-passes 40 \
src
A representative migration should converge and become idempotent. If a small reproducible input repeatedly fails to converge, treat it as a bug rather than permanently relying on a very high limit.