Proactive and schedule-driven; covers the full flag inventory every run, growing
the ledger over time as flags are removed or newly flagged.
Step 0 — Orient and resume
bash
# Read the durable ledger first — known flags, their last-seen rollout state,# what's already been removed, and what's already flagged for human review.cat .singulacomp/memory/flag-cleanup-log.md 2>/dev/null || echo "(no ledger yet — first run)"# Check any open cleanup PRs from a prior run.gh pr list --repo {{target_repo}} --state open \ --search 'in:title "chore(flags)" OR label:feature-flags' \ --json number,title,headRefName,statusCheckRollup,url
If a prior cleanup PR is still on CI or in review, don't duplicate the work —
drive a stalled-but-fixable PR to green, otherwise leave it for review and move
on to flags it didn't cover.
Never classify or remove a flag against a stale checkout.
Step 2 — Inventory every flag
bash
cd /workspace/repo# Flag definitions/registry — adapt the path to this repo's convention# (e.g. flags.ts, feature-flags.json, a flags/ directory).grep -rnE "(defineFlag|registerFlag|FEATURE_FLAGS|flags\.(json|ya?ml))" --include="*.ts" --include="*.tsx" --include="*.json" . 2>/dev/null# Call sites — every place a flag is actually read.grep -rnE "(isEnabled|useFeatureFlag|getFlag|flags\.[a-zA-Z_]+)\(" --include="*.ts" --include="*.tsx" . 2>/dev/null
For every flag found, record: name, where it's declared, every call site, and
(if the rollout percentage or state is tracked in-repo, e.g. a config file) its
current rollout value. Cross-reference git log -1 --format=%ai -- <flag file or last touched call site> for the flag's last-touched date.
Step 3 — Classify each flag
Classification
Criteria
Action
Fully rolled out
Rollout is 100% / hardcoded true with no variant left, or explicitly marked GA
Remove — inline the "on" branch, delete the "off" branch
Long dead
No call site found anywhere in the repo, or unchanged (declaration and all call sites) for more than {{min_flag_age_days}} days with a rollout that was already at 0% or 100%
Remove entirely, including the dead declaration
Partial rollout
Rollout between 0% and 100% exclusive, percentage-based, or user/cohort-targeted
Do not touch. Log in the ledger for human review
Active experiment
Tied to an A/B test, gradual ramp still in progress, or flag age under {{min_flag_age_days}} days with no clear rollout signal
Do not touch. Log in the ledger for human review
When a flag's rollout state can't be determined from the repo alone (e.g. it's
controlled by an external system this agent has no access to), treat it as
ambiguous and log it for human review — never guess.
Step 4 — Isolated cleanup branch
bash
cd /workspace/repoBRANCH="chore/flag-cleanup-$(date +%Y-%m-%d)"git checkout -b "$BRANCH" "origin/$DEFAULT_BRANCH" # or check out an existing branch from this run
Step 5 — Remove each eligible flag
For every flag classified fully rolled out or long dead:
Find every call site (if (flags.x), useFeatureFlag('x'), etc.).
Inline the branch that survives (the "on" branch for fully-rolled-out flags;
nothing survives for long-dead flags with no live call site).
Delete the now-unreachable branch and any helper code that only existed to
support it.
Remove the flag's declaration from its registry/config file.
Remove now-unused imports and any tests that only exercised the dead branch;
update tests that exercised the surviving branch so they no longer gate on
the flag.
Do this one flag at a time so a single bad removal is easy to isolate and
revert.
Step 6 — Full verification suite (the gate)
The removal is not ready until every check is green.
bash
cd /workspace/repopnpm install 2>&1 | tee /tmp/flag-install.logpnpm typecheck 2>&1 | tee /tmp/flag-typecheck.logpnpm lint 2>&1 | tee /tmp/flag-lint.logpnpm build 2>&1 | tee /tmp/flag-build.logpnpm test 2>&1 | tee /tmp/flag-unit.logpnpm test:integration 2>&1 | tee /tmp/flag-integration.log # if < ~10 min
Interpreting failures:
Failure
Action
Typecheck/build error from a removed branch's dependency
Fix the mechanical fallout (unused import, dangling type); if non-mechanical, revert that one flag's removal and log it
Test failure for the removed flag
Expected if the test only covered the dead branch — delete it; if it covered the surviving branch, fix the test
Test failure in an UNRELATED area
File it as a note (likely pre-existing); don't revert unless you can prove causation
Any failure you can't resolve in a few lines
Revert that flag's removal, log it as deferred with the reason, keep the rest of the batch
Step 7 — Commit
bash
cd /workspace/repogit add -Agit commit -m "chore(flags): remove stale feature flags $(date +%Y-%m-%d)Removed: <flag-a> (fully rolled out), <flag-b> (long dead)Verification: typecheck ✓ lint ✓ build ✓ unit ✓ integration ✓"
Step 8 — Open the PR (only when Step 6 is fully green)
A partial green (checks skipped/timed out) is NOT acceptable — rerun or debug.
bash
cd /workspace/repogit push origin "$BRANCH"gh pr create --repo {{target_repo}} --base "$DEFAULT_BRANCH" --head "$BRANCH" \ --title "chore(flags): remove stale feature flags ($(date +%Y-%m-%d))" \ --label feature-flags \ --body "Generated by the flag-cleanup agent. The full suite passed in thesandbox before this PR was opened.**Removed (fully rolled out or long dead):**- <flag-a> — fully rolled out, last touched <date>- <flag-b> — long dead, no live call site found**Left untouched (flagged for human review):**- <flag-c> — partial rollout at <percentage>%- <flag-d> — active experimentA human owns the merge."
Step 9 — Update the ledger
Append a dated entry to .singulacomp/memory/flag-cleanup-log.md (see
<ledger-format>), then open + self-merge a scoped change request for the
ledger update only.