From 83c4fb75ca61b5e038cfbfcb54473f77f2c823f5 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 12:17:39 -0600 Subject: [PATCH 01/15] feat(skills): add cull-feature-flags skill skill half of `wizard cull-feature-flags`. the wizard does the deterministic work (scan, fetch, classify, seed the ledger) so this skill only verifies each proposed row at its call site, asks once, applies what was approved and writes the report. config + description only here, steps follow. one variant per framework the wizard scanner supports (nextjs for now), keyed by the wizard detection id the same way integration/config.yaml does it. --- context/skills/cull-feature-flags/config.yaml | 25 +++++++++ .../skills/cull-feature-flags/description.md | 54 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 context/skills/cull-feature-flags/config.yaml create mode 100644 context/skills/cull-feature-flags/description.md diff --git a/context/skills/cull-feature-flags/config.yaml b/context/skills/cull-feature-flags/config.yaml new file mode 100644 index 00000000..812cf378 --- /dev/null +++ b/context/skills/cull-feature-flags/config.yaml @@ -0,0 +1,25 @@ +# Cull stale feature flags. The wizard seeds the ledger deterministically +# (scan + fetch + classify host-side); this skill verifies, asks once, applies, +# and reports. One variant per framework the wizard's scanner supports; +# `framework` is the detection id the wizard resolves the variant from. +# Reachable via `wizard cull-feature-flags` (wizard-registered flat command) +# and `wizard skill cull-feature-flags-`. +type: docs-only +template: description.md +description: Remove stale PostHog feature flags from a {display_name} codebase and disable them in PostHog, from a ledger the wizard seeds +tags: [feature-flags] +cli: + role: skill +references: + preamble: "**Read ONLY this file.** Do not read any other reference file until this one tells you to." +shared_docs: + - https://posthog.com/docs/feature-flags/best-practices.md +variants: + - id: nextjs + framework: nextjs + default: true + display_name: Next.js + tags: [nextjs, nextjs-feature-flags, react, javascript, javascript_web, javascript_node] + docs_urls: + - https://posthog.com/docs/feature-flags/installation/react.md + - https://posthog.com/docs/libraries/next-js.md diff --git a/context/skills/cull-feature-flags/description.md b/context/skills/cull-feature-flags/description.md new file mode 100644 index 00000000..826376b4 --- /dev/null +++ b/context/skills/cull-feature-flags/description.md @@ -0,0 +1,54 @@ +# Cull stale PostHog feature flags in a {display_name} project + +This skill removes feature flags that have outlived their purpose: rolled out to everyone, never enabled, archived or deleted in PostHog but still checked in code, or defined in PostHog and never evaluated anywhere. The wizard already did the detection before you started: it scanned the source tree for flag calls, fetched the project's flags, classified every flag with plain rules, and wrote one row per flag into `.posthog-audit-checks.json` at the project root. That ledger is the ground truth. You never grep for flags, never re-classify a row, and never promote a healthy flag into a removal. + +Your job is three steps: verify each proposed row at its call site, ask the user once which rows to apply, apply the approved ones (code first, PostHog second), then write the report. + +**Start by reading `references/1-verify-call-sites.md`.** Do not Glob, ls, or find the skill directory. Do not read `2-apply.md` or `3-report.md` until the current step tells you to. + +## State + +- `.posthog-audit-checks.json` (project root): the ledger. `Read` it once at the start of each step. Each row is `{ id, area, label, status, file?, details? }` where `id` is the flag key, `area` is the bucket the wizard assigned, `label` names the proposed action, `file` is the first call site as `path:line`, and `details` carries the rollout summary and every call site. +- Rows change only through `mcp__wizard-tools__audit_resolve_checks`. Never `Edit` or `Write` the ledger file. Never delete it. +- Row statuses this skill uses: `pending` (seeded, not yet verified), `warning` (verified, proposed to the user), `pass` (kept, declined, or applied), `error` (apply failed, reason in `details`). + +The wizard prompt tells you whether the repo uses bulk evaluation (`getAllFlags`) or dynamic flag keys. When it does, every `unreferenced` or `unreferenced-comment-only` row needs a real check at the bulk or dynamic call site before it can be proposed. + +## Status + +Report progress with `[STATUS] ` lines. Each step lists the exact strings to emit. They are cheap; use them at every sub-step. + +## Abort + +Report unrecoverable preconditions with exactly one `[ABORT] ` line and stop. The wizard terminates the run; do not halt yourself. + +- `[ABORT] No flag cull ledger found` when `.posthog-audit-checks.json` does not exist (this skill needs `wizard cull-feature-flags` to seed it). +- `[ABORT] No PostHog feature flag usage found` when the ledger has zero rows. + +## Rules + +1. **Disable, never delete.** The only PostHog mutation this skill makes is disabling a flag. Archiving and deleting are for the user to do in the app. +2. **Code before PostHog.** For a row that needs both a code edit and a disable, the edit lands first and the disable only after the edit succeeded, so a failed edit never leaves a disabled flag behind live code. +3. **One consent call.** Exactly one `wizard_ask` for the whole run, listing every proposed row, decline option first. Nothing is applied without it. +4. **Winning branch only.** Removing a flag check means keeping the branch the flag would resolve to and deleting the other one, plus the now-unused import or hook. Do not restructure beyond that. +5. **Downgrade freely, never upgrade.** During verification a row may be resolved to `pass` (keep) with a reason. A `pass` row seeded as healthy is never touched. + +## Available tools + +{{> mcp-tool-calling}} + +**Verify:** `Read` on each call-site file. + +**Confirm:** `mcp__wizard-tools__wizard_ask`, called once (see `2-apply.md`). + +**Apply:** `Edit` for code; through `exec`, the flag disable tool (discover it with `search feature-flag`, run `info` on it, then `call`). + +**Ledger:** `mcp__wizard-tools__audit_resolve_checks` for every status change. + +## Reference files + +{references} + +## Framework guidelines + +{commandments} From f461b281c275617ae12119e3603e35040ad074ed Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 12:17:39 -0600 Subject: [PATCH 02/15] feat(cull-feature-flags): add verify and apply steps step 1 reads each pending ledger row at its file:line and either confirms the proposed action or downgrades it to keep (never the other way round). step 2 is one wizard_ask over the confirmed rows, decline first, then code edit before the posthog disable so a failed edit never leaves a disabled flag behind live code. --- .../references/1-verify-call-sites.md | 57 ++++++++++++++++++ .../cull-feature-flags/references/2-apply.md | 60 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 context/skills/cull-feature-flags/references/1-verify-call-sites.md create mode 100644 context/skills/cull-feature-flags/references/2-apply.md diff --git a/context/skills/cull-feature-flags/references/1-verify-call-sites.md b/context/skills/cull-feature-flags/references/1-verify-call-sites.md new file mode 100644 index 00000000..39fb0d44 --- /dev/null +++ b/context/skills/cull-feature-flags/references/1-verify-call-sites.md @@ -0,0 +1,57 @@ +--- +next_step: 2-apply.md +--- + +# Step 1: Verify each proposed row at its call site + +This step turns every `pending` ledger row into `warning` (verified, will be proposed) or `pass` (keep, with a reason). It does NOT ask the user anything and does NOT edit code or PostHog; that belongs to step 2. + +## Status + +Emit at the start: + +``` +[STATUS] Reading the flag cull ledger +``` + +## Read the ledger + +`Read` `.posthog-audit-checks.json` at the project root. + +- File missing: emit `[ABORT] No flag cull ledger found` and stop. +- Zero rows: emit `[ABORT] No PostHog feature flag usage found` and stop. + +Rows with `status: pass` were seeded healthy. Leave them alone for the whole run. + +## Verify the pending rows + +Emit: + +``` +[STATUS] Verifying flag call sites +``` + +For each `pending` row, in ledger order, `Read` the file named in `file` (and every other call site listed in `details`) around the given line. Decide one of: + +| bucket (`area`) | confirm as `warning` when | downgrade to `pass` when | +|---|---|---| +| `fully-rolled-out` | the call site is a boolean check whose true branch is the current behaviour | the flag gates something that must stay switchable (kill switch, ops toggle named as such) | +| `never-enabled` | the call site is a boolean check whose false branch is the current behaviour | the feature is clearly mid-build (recent scaffolding, TODOs pointing at it) | +| `archived-still-referenced`, `disabled-but-referenced` | the call site is a boolean check; keep the false branch | never, these are always safe to propose | +| `deleted-still-referenced` | no live flag in PostHog resembles the key | the key looks like a typo of a healthy flag key in the ledger (note the match in `details`) | +| `unreferenced`, `unreferenced-comment-only` | the prompt says no bulk evaluation and no dynamic keys | the prompt reports `getAllFlags` or dynamic keys and the bulk or dynamic call site could reach this key | +| `dead-code-reference` | nothing imports the file (the wizard checked; confirm with one `Grep` for the module's basename) | something imports it after all | +| `multi-callsite-no-wrapper` | always `warning`, it is a suggestion, not a removal | never | + +Resolve every pending row through one `mcp__wizard-tools__audit_resolve_checks` call per batch of decisions: + +- confirmed: `status: "warning"`, `details` = the seeded details plus `; winning branch: true|false|n/a` +- downgraded: `status: "pass"`, `details` = the seeded details plus `; kept: ` + +## Output + +Every row that was `pending` is now `warning` or `pass`. No other file changed. Emit: + +``` +[STATUS] flags proposed, kept +``` diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md new file mode 100644 index 00000000..88ffa739 --- /dev/null +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -0,0 +1,60 @@ +--- +next_step: 3-report.md +--- + +# Step 2: Ask once, then apply the approved rows + +This step asks the user which `warning` rows to apply, then applies them: code edit first, PostHog disable second. It does NOT write the report; that belongs to step 3. Step 1 already decided the winning branch for every row; do not re-verify. + +## Ask + +Emit: + +``` +[STATUS] Waiting for confirmation +``` + +`Read` `.posthog-audit-checks.json`. Collect every row with `status: "warning"` whose `area` is not `multi-callsite-no-wrapper` (that bucket is report-only). + +Zero such rows: skip to the Output section, nothing to apply. + +Call `mcp__wizard-tools__wizard_ask` exactly once, `kind: "multi"`, one option per row plus a decline option listed first: + +- decline option: label `Apply nothing, report only`, value `none` +- per row: label `[] : `, value `` + +If the call errors (non-interactive host, cap reached), treat it as decline. Do not retry more than once. + +Rows the user did not pick: resolve to `status: "pass"` with `details` = seeded details plus `; declined by user`. + +## Apply + +Emit per row: + +``` +[STATUS] Applying +``` + +For each approved row, in ledger order: + +1. **Code edit** (skip for `unreferenced`; for `unreferenced-comment-only` remove the mention only): + - `Read` each call site listed in `details`. + - Keep the winning branch recorded in `details` (`winning branch: true` keeps the code that ran when the flag was on; `false` keeps the code that ran when it was off). Delete the other branch, the flag call, and any import or hook that is now unused. + - `dead-code-reference`: delete the unreachable file instead of editing it. + - `deleted-still-referenced`: code edit only, there is no flag to disable. + - Re-`Read` the edited file once to confirm it still parses by eye (balanced braces, no dangling variable). +2. **Disable the flag in PostHog** (every bucket except `deleted-still-referenced`, and only after step 1 succeeded for this row): + - `exec({ "command": "search feature-flag" })`, pick the tool whose description says it disables a flag. + - `exec({ "command": "info " })`, then `exec({ "command": "call })` with the flag key or id from `details`. + - Never call a delete or archive tool. +3. Resolve the row: + - both parts succeeded: `status: "pass"`, `details` = seeded details plus `; applied` + - anything failed: `status: "error"`, `details` = seeded details plus `; failed: `. If the code edit failed, do not touch PostHog for this row. + +## Output + +Every `warning` row outside `multi-callsite-no-wrapper` is now `pass` or `error`. Emit: + +``` +[STATUS] Applied flags, failed, declined +``` From f8899dfbcfc421658c5573e0692e6a2b14c93258 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 12:17:39 -0600 Subject: [PATCH 03/15] feat(cull-feature-flags): add the report step writes posthog-feature-flag-cull-report.md from the ledger: full findings table first (stale / warning / healthy per flag) so a report-only run still gives something to act on by hand, then applied, manual action, failed and kept. terminal step. --- .../cull-feature-flags/references/3-report.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 context/skills/cull-feature-flags/references/3-report.md diff --git a/context/skills/cull-feature-flags/references/3-report.md b/context/skills/cull-feature-flags/references/3-report.md new file mode 100644 index 00000000..db8f1633 --- /dev/null +++ b/context/skills/cull-feature-flags/references/3-report.md @@ -0,0 +1,96 @@ +--- +next_step: null +--- + +# Step 3: Write the cull report + +The report is rendered from `.posthog-audit-checks.json`. Every row ends up in it; nothing is invented. Do not delete the ledger, the wizard owns it. + +## Status + +Emit: + +``` +[STATUS] Writing feature flag cull report +``` + +## Action + +`Read` the ledger once. `Write` `posthog-feature-flag-cull-report.md` at the project root using the template below. Use `id`, `area`, `label`, `file`, and `details` verbatim where the template calls for them. + +The report serves two readers: someone who declined everything and wants a findings list to act on by hand, and someone who approved changes and wants to see what happened. So it always opens with the full findings table, then groups rows by outcome. + +Findings verdict per row comes from `area`: `healthy` is **healthy**, `multi-callsite-no-wrapper` is **warning**, every other bucket is **stale**. + +Outcome sections, reading the suffix the earlier steps appended to `details`: + +1. **Applied**: `status: pass` with `; applied` +2. **Manual action**: `status: pass` with `; declined by user`, plus every `warning` row still unapplied when `wizard_ask` was unavailable. Each row keeps its proposed action so the reader can do it by hand. +3. **Failed**: `status: error` +4. **Kept**: `status: pass` with `; kept:` plus every row the wizard seeded as healthy, and every `multi-callsite-no-wrapper` row (list those under a "Suggested wrappers" heading with the call-site count) + +If the wizard prompt said the PostHog fetch failed, say so in the summary and note that only code-side buckets could be evaluated. + +## Report template + + +# PostHog Feature Flag Cull Report + +## Summary + +One paragraph: how many flags the wizard looked at, how many were proposed, applied, declined, failed, kept. Whether bulk evaluation or dynamic keys limited what could be proposed. + +| Outcome | Count | +|---|---| +| Applied | n | +| Manual action | n | +| Failed | n | +| Kept | n | + +## Findings + +Every flag the wizard looked at, one row each, ledger order. + +| Flag | Verdict | Bucket | Proposed action | Call sites | +|---|---|---|---|---| +| `` | stale, warning, or healthy | `` | ` + +## Output + +End with exactly this line so the wizard can pick up the path: + +``` +Created cull report: +``` From 90ef9304ec9ec0609a17b6fc355a9995cab87d74 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 12:30:59 -0600 Subject: [PATCH 04/15] feat(cull-feature-flags): spell out consent and undo the consent question now says exactly what happens to a picked flag (disabled in posthog, check removed in code) and that both are reversible. the report gets an undo section: git checkout for the touched files (the wizard refuses to start on a dirty tree, so every change is its own) and a re-enable link per disabled flag. --- context/skills/cull-feature-flags/references/2-apply.md | 6 +++++- context/skills/cull-feature-flags/references/3-report.md | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md index 88ffa739..b8b142fc 100644 --- a/context/skills/cull-feature-flags/references/2-apply.md +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -18,7 +18,11 @@ Emit: Zero such rows: skip to the Output section, nothing to apply. -Call `mcp__wizard-tools__wizard_ask` exactly once, `kind: "multi"`, one option per row plus a decline option listed first: +Call `mcp__wizard-tools__wizard_ask` exactly once, `kind: "multi"`, with this question text verbatim: + +> Nothing has been changed yet. Pick the flags to cull. Each one gets disabled in PostHog (re-enable any time from the flag page) and its check removed from code (revert with git). + +One option per row plus a decline option listed first: - decline option: label `Apply nothing, report only`, value `none` - per row: label `[] : `, value `` diff --git a/context/skills/cull-feature-flags/references/3-report.md b/context/skills/cull-feature-flags/references/3-report.md index db8f1633..d2c21de4 100644 --- a/context/skills/cull-feature-flags/references/3-report.md +++ b/context/skills/cull-feature-flags/references/3-report.md @@ -77,9 +77,16 @@ Same table plus a "Why" column from `details`. For each `multi-callsite-no-wrapper` row: the flag, the number of files evaluating it directly, and one sentence recommending a single hook or helper module. +## Undo + +Only present when something was applied. Everything here is reversible in one step each: + +- Code: `git checkout -- ` (run `git status` first; the tree was clean when the run started, so every change is the wizard's). List the files. +- PostHog: one line per disabled flag, `Re-enable : /project//feature_flags/`. Read the app host and project id from the wizard prompt or the MCP project state. + ## Follow-ups -- Flags disabled here are still in PostHog. Archive or delete them from the app once the deploy is out. +- Flags disabled here are still in PostHog. Archive or delete them from the app once the deploy is out; the wizard never does either. - Any `Failed` row needs a manual pass at the listed call sites. ## About this report From b13f78c8ff86735375f9369f88ee10b5ed49d886 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 12:42:43 -0600 Subject: [PATCH 05/15] fix(cull-feature-flags): drop the fetch-failed report branch the wizard now aborts before the run when it cannot read the project's flags, so the report never sees a half-seeded ledger. --- context/skills/cull-feature-flags/references/3-report.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/context/skills/cull-feature-flags/references/3-report.md b/context/skills/cull-feature-flags/references/3-report.md index d2c21de4..c49a812d 100644 --- a/context/skills/cull-feature-flags/references/3-report.md +++ b/context/skills/cull-feature-flags/references/3-report.md @@ -29,8 +29,6 @@ Outcome sections, reading the suffix the earlier steps appended to `details`: 3. **Failed**: `status: error` 4. **Kept**: `status: pass` with `; kept:` plus every row the wizard seeded as healthy, and every `multi-callsite-no-wrapper` row (list those under a "Suggested wrappers" heading with the call-site count) -If the wizard prompt said the PostHog fetch failed, say so in the summary and note that only code-side buckets could be evaluated. - ## Report template From 4675bdbf3cdaa037d1de4bcd3324f7a345f7aaf6 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 13:01:54 -0600 Subject: [PATCH 06/15] fix(cull-feature-flags): human area names and a status per row ledger areas are now the display names the wizard seeds (Rolled out, Never enabled, Archived in PostHog, ...) instead of bucket ids, defined once in the description. step 1 emits a status line per flag and resolves each row as soon as it is decided so the run screen moves while the agent works. --- .../skills/cull-feature-flags/description.md | 4 ++-- .../references/1-verify-call-sites.md | 20 +++++++++---------- .../cull-feature-flags/references/2-apply.md | 12 +++++------ .../cull-feature-flags/references/3-report.md | 6 +++--- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/context/skills/cull-feature-flags/description.md b/context/skills/cull-feature-flags/description.md index 826376b4..3ade4329 100644 --- a/context/skills/cull-feature-flags/description.md +++ b/context/skills/cull-feature-flags/description.md @@ -8,11 +8,11 @@ Your job is three steps: verify each proposed row at its call site, ask the user ## State -- `.posthog-audit-checks.json` (project root): the ledger. `Read` it once at the start of each step. Each row is `{ id, area, label, status, file?, details? }` where `id` is the flag key, `area` is the bucket the wizard assigned, `label` names the proposed action, `file` is the first call site as `path:line`, and `details` carries the rollout summary and every call site. +- `.posthog-audit-checks.json` (project root): the ledger. `area` is one of: `Rolled out` (100% to everyone, no conditions), `Never enabled` (0% everywhere), `Archived in PostHog`, `Disabled in PostHog`, `Unreferenced` (in PostHog, never evaluated in code), `Comment only` (key appears only in a comment or config string), `Dead code` (the only file evaluating it is unreachable), `Deleted in PostHog` (code evaluates a key PostHog does not have), `Many call sites` (three or more files evaluate it directly, suggestion only), `Healthy`. `Read` it once at the start of each step. Each row is `{ id, area, label, status, file?, details? }` where `id` is the flag key, `area` is the bucket the wizard assigned, `label` names the proposed action, `file` is the first call site as `path:line`, and `details` carries the rollout summary and every call site. - Rows change only through `mcp__wizard-tools__audit_resolve_checks`. Never `Edit` or `Write` the ledger file. Never delete it. - Row statuses this skill uses: `pending` (seeded, not yet verified), `warning` (verified, proposed to the user), `pass` (kept, declined, or applied), `error` (apply failed, reason in `details`). -The wizard prompt tells you whether the repo uses bulk evaluation (`getAllFlags`) or dynamic flag keys. When it does, every `unreferenced` or `unreferenced-comment-only` row needs a real check at the bulk or dynamic call site before it can be proposed. +The wizard prompt tells you whether the repo uses bulk evaluation (`getAllFlags`) or dynamic flag keys. When it does, every `Unreferenced` or `Comment only` row needs a real check at the bulk or dynamic call site before it can be proposed. ## Status diff --git a/context/skills/cull-feature-flags/references/1-verify-call-sites.md b/context/skills/cull-feature-flags/references/1-verify-call-sites.md index 39fb0d44..91a676ce 100644 --- a/context/skills/cull-feature-flags/references/1-verify-call-sites.md +++ b/context/skills/cull-feature-flags/references/1-verify-call-sites.md @@ -31,19 +31,19 @@ Emit: [STATUS] Verifying flag call sites ``` -For each `pending` row, in ledger order, `Read` the file named in `file` (and every other call site listed in `details`) around the given line. Decide one of: +For each `pending` row, in ledger order, emit `[STATUS] Verifying ` and `Read` the file named in `file` (and every other call site listed in `details`) around the given line. Decide one of: | bucket (`area`) | confirm as `warning` when | downgrade to `pass` when | |---|---|---| -| `fully-rolled-out` | the call site is a boolean check whose true branch is the current behaviour | the flag gates something that must stay switchable (kill switch, ops toggle named as such) | -| `never-enabled` | the call site is a boolean check whose false branch is the current behaviour | the feature is clearly mid-build (recent scaffolding, TODOs pointing at it) | -| `archived-still-referenced`, `disabled-but-referenced` | the call site is a boolean check; keep the false branch | never, these are always safe to propose | -| `deleted-still-referenced` | no live flag in PostHog resembles the key | the key looks like a typo of a healthy flag key in the ledger (note the match in `details`) | -| `unreferenced`, `unreferenced-comment-only` | the prompt says no bulk evaluation and no dynamic keys | the prompt reports `getAllFlags` or dynamic keys and the bulk or dynamic call site could reach this key | -| `dead-code-reference` | nothing imports the file (the wizard checked; confirm with one `Grep` for the module's basename) | something imports it after all | -| `multi-callsite-no-wrapper` | always `warning`, it is a suggestion, not a removal | never | - -Resolve every pending row through one `mcp__wizard-tools__audit_resolve_checks` call per batch of decisions: +| `Rolled out` | the call site is a boolean check whose true branch is the current behaviour | the flag gates something that must stay switchable (kill switch, ops toggle named as such) | +| `Never enabled` | the call site is a boolean check whose false branch is the current behaviour | the feature is clearly mid-build (recent scaffolding, TODOs pointing at it) | +| `Archived in PostHog`, `Disabled in PostHog` | the call site is a boolean check; keep the false branch | never, these are always safe to propose | +| `Deleted in PostHog` | no live flag in PostHog resembles the key | the key looks like a typo of a healthy flag key in the ledger (note the match in `details`) | +| `Unreferenced`, `Comment only` | the prompt says no bulk evaluation and no dynamic keys | the prompt reports `getAllFlags` or dynamic keys and the bulk or dynamic call site could reach this key | +| `Dead code` | nothing imports the file (the wizard checked; confirm with one `Grep` for the module's basename) | something imports it after all | +| `Many call sites` | always `warning`, it is a suggestion, not a removal | never | + +Resolve each row through `mcp__wizard-tools__audit_resolve_checks` as soon as it is decided (one call per row, so the run screen moves while you work): - confirmed: `status: "warning"`, `details` = the seeded details plus `; winning branch: true|false|n/a` - downgraded: `status: "pass"`, `details` = the seeded details plus `; kept: ` diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md index b8b142fc..6f9c7d71 100644 --- a/context/skills/cull-feature-flags/references/2-apply.md +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -14,7 +14,7 @@ Emit: [STATUS] Waiting for confirmation ``` -`Read` `.posthog-audit-checks.json`. Collect every row with `status: "warning"` whose `area` is not `multi-callsite-no-wrapper` (that bucket is report-only). +`Read` `.posthog-audit-checks.json`. Collect every row with `status: "warning"` whose `area` is not `Many call sites` (that bucket is report-only). Zero such rows: skip to the Output section, nothing to apply. @@ -41,13 +41,13 @@ Emit per row: For each approved row, in ledger order: -1. **Code edit** (skip for `unreferenced`; for `unreferenced-comment-only` remove the mention only): +1. **Code edit** (skip for `Unreferenced`; for `Comment only` remove the mention only): - `Read` each call site listed in `details`. - Keep the winning branch recorded in `details` (`winning branch: true` keeps the code that ran when the flag was on; `false` keeps the code that ran when it was off). Delete the other branch, the flag call, and any import or hook that is now unused. - - `dead-code-reference`: delete the unreachable file instead of editing it. - - `deleted-still-referenced`: code edit only, there is no flag to disable. + - `Dead code`: delete the unreachable file instead of editing it. + - `Deleted in PostHog`: code edit only, there is no flag to disable. - Re-`Read` the edited file once to confirm it still parses by eye (balanced braces, no dangling variable). -2. **Disable the flag in PostHog** (every bucket except `deleted-still-referenced`, and only after step 1 succeeded for this row): +2. **Disable the flag in PostHog** (every bucket except `Deleted in PostHog`, and only after step 1 succeeded for this row): - `exec({ "command": "search feature-flag" })`, pick the tool whose description says it disables a flag. - `exec({ "command": "info " })`, then `exec({ "command": "call })` with the flag key or id from `details`. - Never call a delete or archive tool. @@ -57,7 +57,7 @@ For each approved row, in ledger order: ## Output -Every `warning` row outside `multi-callsite-no-wrapper` is now `pass` or `error`. Emit: +Every `warning` row outside `Many call sites` is now `pass` or `error`. Emit: ``` [STATUS] Applied flags, failed, declined diff --git a/context/skills/cull-feature-flags/references/3-report.md b/context/skills/cull-feature-flags/references/3-report.md index c49a812d..b4df97a8 100644 --- a/context/skills/cull-feature-flags/references/3-report.md +++ b/context/skills/cull-feature-flags/references/3-report.md @@ -20,14 +20,14 @@ Emit: The report serves two readers: someone who declined everything and wants a findings list to act on by hand, and someone who approved changes and wants to see what happened. So it always opens with the full findings table, then groups rows by outcome. -Findings verdict per row comes from `area`: `healthy` is **healthy**, `multi-callsite-no-wrapper` is **warning**, every other bucket is **stale**. +Findings verdict per row comes from `area`: `Healthy` is **healthy**, `Many call sites` is **warning**, every other bucket is **stale**. Outcome sections, reading the suffix the earlier steps appended to `details`: 1. **Applied**: `status: pass` with `; applied` 2. **Manual action**: `status: pass` with `; declined by user`, plus every `warning` row still unapplied when `wizard_ask` was unavailable. Each row keeps its proposed action so the reader can do it by hand. 3. **Failed**: `status: error` -4. **Kept**: `status: pass` with `; kept:` plus every row the wizard seeded as healthy, and every `multi-callsite-no-wrapper` row (list those under a "Suggested wrappers" heading with the call-site count) +4. **Kept**: `status: pass` with `; kept:` plus every row the wizard seeded as healthy, and every `Many call sites` row (list those under a "Suggested wrappers" heading with the call-site count) ## Report template @@ -73,7 +73,7 @@ Same table plus a "Why" column from `details`. ### Suggested wrappers -For each `multi-callsite-no-wrapper` row: the flag, the number of files evaluating it directly, and one sentence recommending a single hook or helper module. +For each `Many call sites` row: the flag, the number of files evaluating it directly, and one sentence recommending a single hook or helper module. ## Undo From fcea7ebfa013094637f28107788cde52a20b0e78 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 13:05:14 -0600 Subject: [PATCH 07/15] fix(cull-feature-flags): report-only as its own choice, skip disabling flags already off first live run: "apply nothing" was one checkbox among the flags, easy to miss. consent is still one wizard_ask call, now two questions: a single choice (report only first, then cull) and the flag multi-select. report only wins whatever the list holds. also skips the posthog disable for archived and already-disabled rows; the slide already promised nothing changes in posthog for those. --- .../skills/cull-feature-flags/description.md | 2 +- .../cull-feature-flags/references/2-apply.md | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/context/skills/cull-feature-flags/description.md b/context/skills/cull-feature-flags/description.md index 3ade4329..e01cabd3 100644 --- a/context/skills/cull-feature-flags/description.md +++ b/context/skills/cull-feature-flags/description.md @@ -29,7 +29,7 @@ Report unrecoverable preconditions with exactly one `[ABORT] ` line and 1. **Disable, never delete.** The only PostHog mutation this skill makes is disabling a flag. Archiving and deleting are for the user to do in the app. 2. **Code before PostHog.** For a row that needs both a code edit and a disable, the edit lands first and the disable only after the edit succeeded, so a failed edit never leaves a disabled flag behind live code. -3. **One consent call.** Exactly one `wizard_ask` for the whole run, listing every proposed row, decline option first. Nothing is applied without it. +3. **One consent call.** Exactly one `wizard_ask` for the whole run: a report-only choice first, then the flag list. Nothing is applied without it. 4. **Winning branch only.** Removing a flag check means keeping the branch the flag would resolve to and deleting the other one, plus the now-unused import or hook. Do not restructure beyond that. 5. **Downgrade freely, never upgrade.** During verification a row may be resolved to `pass` (keep) with a reason. A `pass` row seeded as healthy is never touched. diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md index 6f9c7d71..d38a790d 100644 --- a/context/skills/cull-feature-flags/references/2-apply.md +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -18,18 +18,27 @@ Emit: Zero such rows: skip to the Output section, nothing to apply. -Call `mcp__wizard-tools__wizard_ask` exactly once, `kind: "multi"`, with this question text verbatim: +Call `mcp__wizard-tools__wizard_ask` exactly once, with two questions in that one call so the report-only choice stands on its own instead of hiding in the flag list: -> Nothing has been changed yet. Pick the flags to cull. Each one gets disabled in PostHog (re-enable any time from the flag page) and its check removed from code (revert with git). +1. `id: "mode"`, `kind: "single"`, prompt verbatim: -One option per row plus a decline option listed first: + > Nothing has been changed yet. Do you want to cull flags now, or just get the report? -- decline option: label `Apply nothing, report only`, value `none` -- per row: label `[] : `, value `` + Options, in this order: + - label `Report only, change nothing`, value `report-only` + - label `Cull the flags I pick below`, value `cull` -If the call errors (non-interactive host, cap reached), treat it as decline. Do not retry more than once. +2. `id: "flags"`, `kind: "multi"`, prompt verbatim: -Rows the user did not pick: resolve to `status: "pass"` with `details` = seeded details plus `; declined by user`. + > Pick the flags to cull. Each one gets disabled in PostHog (re-enable any time from the flag page) and its check removed from code (revert with git). + + One option per row: label `[] : `, value ``. + +If the call errors (non-interactive host, cap reached), treat it as report-only. Do not retry more than once. + +`mode` is `report-only`: nothing is approved, whatever `flags` holds. Otherwise the approved rows are exactly the `flags` answer. + +Rows not approved: resolve to `status: "pass"` with `details` = seeded details plus `; declined by user`. ## Apply @@ -47,7 +56,7 @@ For each approved row, in ledger order: - `Dead code`: delete the unreachable file instead of editing it. - `Deleted in PostHog`: code edit only, there is no flag to disable. - Re-`Read` the edited file once to confirm it still parses by eye (balanced braces, no dangling variable). -2. **Disable the flag in PostHog** (every bucket except `Deleted in PostHog`, and only after step 1 succeeded for this row): +2. **Disable the flag in PostHog** (skip for `Deleted in PostHog`, there is no flag, and for `Archived in PostHog` and `Disabled in PostHog`, the flag is already off; only after step 1 succeeded for this row): - `exec({ "command": "search feature-flag" })`, pick the tool whose description says it disables a flag. - `exec({ "command": "info " })`, then `exec({ "command": "call })` with the flag key or id from `details`. - Never call a delete or archive tool. From f7c2b6ddc319fa5f213d21e7fbbce5fa7f930d5d Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 13:09:35 -0600 Subject: [PATCH 08/15] fix(cull-feature-flags): say culled, not applied "applied" read as process, not outcome. the report sections are now Culled / Left for you / Failed / Kept, the status lines say culling, and the ledger marker the wizard reads is "; culled". --- .../skills/cull-feature-flags/description.md | 6 +++--- .../cull-feature-flags/references/2-apply.md | 10 +++++----- .../cull-feature-flags/references/3-report.md | 20 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/context/skills/cull-feature-flags/description.md b/context/skills/cull-feature-flags/description.md index e01cabd3..31c37f16 100644 --- a/context/skills/cull-feature-flags/description.md +++ b/context/skills/cull-feature-flags/description.md @@ -2,7 +2,7 @@ This skill removes feature flags that have outlived their purpose: rolled out to everyone, never enabled, archived or deleted in PostHog but still checked in code, or defined in PostHog and never evaluated anywhere. The wizard already did the detection before you started: it scanned the source tree for flag calls, fetched the project's flags, classified every flag with plain rules, and wrote one row per flag into `.posthog-audit-checks.json` at the project root. That ledger is the ground truth. You never grep for flags, never re-classify a row, and never promote a healthy flag into a removal. -Your job is three steps: verify each proposed row at its call site, ask the user once which rows to apply, apply the approved ones (code first, PostHog second), then write the report. +Your job is three steps: verify each proposed row at its call site, ask the user once which rows to apply, cull the approved ones (code first, PostHog second), then write the report. **Start by reading `references/1-verify-call-sites.md`.** Do not Glob, ls, or find the skill directory. Do not read `2-apply.md` or `3-report.md` until the current step tells you to. @@ -10,7 +10,7 @@ Your job is three steps: verify each proposed row at its call site, ask the user - `.posthog-audit-checks.json` (project root): the ledger. `area` is one of: `Rolled out` (100% to everyone, no conditions), `Never enabled` (0% everywhere), `Archived in PostHog`, `Disabled in PostHog`, `Unreferenced` (in PostHog, never evaluated in code), `Comment only` (key appears only in a comment or config string), `Dead code` (the only file evaluating it is unreachable), `Deleted in PostHog` (code evaluates a key PostHog does not have), `Many call sites` (three or more files evaluate it directly, suggestion only), `Healthy`. `Read` it once at the start of each step. Each row is `{ id, area, label, status, file?, details? }` where `id` is the flag key, `area` is the bucket the wizard assigned, `label` names the proposed action, `file` is the first call site as `path:line`, and `details` carries the rollout summary and every call site. - Rows change only through `mcp__wizard-tools__audit_resolve_checks`. Never `Edit` or `Write` the ledger file. Never delete it. -- Row statuses this skill uses: `pending` (seeded, not yet verified), `warning` (verified, proposed to the user), `pass` (kept, declined, or applied), `error` (apply failed, reason in `details`). +- Row statuses this skill uses: `pending` (seeded, not yet verified), `warning` (verified, proposed to the user), `pass` (kept, left for you, or culled), `error` (cull failed, reason in `details`). The wizard prompt tells you whether the repo uses bulk evaluation (`getAllFlags`) or dynamic flag keys. When it does, every `Unreferenced` or `Comment only` row needs a real check at the bulk or dynamic call site before it can be proposed. @@ -29,7 +29,7 @@ Report unrecoverable preconditions with exactly one `[ABORT] ` line and 1. **Disable, never delete.** The only PostHog mutation this skill makes is disabling a flag. Archiving and deleting are for the user to do in the app. 2. **Code before PostHog.** For a row that needs both a code edit and a disable, the edit lands first and the disable only after the edit succeeded, so a failed edit never leaves a disabled flag behind live code. -3. **One consent call.** Exactly one `wizard_ask` for the whole run: a report-only choice first, then the flag list. Nothing is applied without it. +3. **One consent call.** Exactly one `wizard_ask` for the whole run: a report-only choice first, then the flag list. Nothing is culled without it. 4. **Winning branch only.** Removing a flag check means keeping the branch the flag would resolve to and deleting the other one, plus the now-unused import or hook. Do not restructure beyond that. 5. **Downgrade freely, never upgrade.** During verification a row may be resolved to `pass` (keep) with a reason. A `pass` row seeded as healthy is never touched. diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md index d38a790d..858e84dd 100644 --- a/context/skills/cull-feature-flags/references/2-apply.md +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -2,7 +2,7 @@ next_step: 3-report.md --- -# Step 2: Ask once, then apply the approved rows +# Step 2: Ask once, then cull the approved rows This step asks the user which `warning` rows to apply, then applies them: code edit first, PostHog disable second. It does NOT write the report; that belongs to step 3. Step 1 already decided the winning branch for every row; do not re-verify. @@ -40,12 +40,12 @@ If the call errors (non-interactive host, cap reached), treat it as report-only. Rows not approved: resolve to `status: "pass"` with `details` = seeded details plus `; declined by user`. -## Apply +## Cull Emit per row: ``` -[STATUS] Applying +[STATUS] Culling ``` For each approved row, in ledger order: @@ -61,7 +61,7 @@ For each approved row, in ledger order: - `exec({ "command": "info " })`, then `exec({ "command": "call })` with the flag key or id from `details`. - Never call a delete or archive tool. 3. Resolve the row: - - both parts succeeded: `status: "pass"`, `details` = seeded details plus `; applied` + - both parts succeeded: `status: "pass"`, `details` = seeded details plus `; culled` - anything failed: `status: "error"`, `details` = seeded details plus `; failed: `. If the code edit failed, do not touch PostHog for this row. ## Output @@ -69,5 +69,5 @@ For each approved row, in ledger order: Every `warning` row outside `Many call sites` is now `pass` or `error`. Emit: ``` -[STATUS] Applied flags, failed, declined +[STATUS] Culled flags, failed, left for you ``` diff --git a/context/skills/cull-feature-flags/references/3-report.md b/context/skills/cull-feature-flags/references/3-report.md index b4df97a8..8eb18beb 100644 --- a/context/skills/cull-feature-flags/references/3-report.md +++ b/context/skills/cull-feature-flags/references/3-report.md @@ -18,14 +18,14 @@ Emit: `Read` the ledger once. `Write` `posthog-feature-flag-cull-report.md` at the project root using the template below. Use `id`, `area`, `label`, `file`, and `details` verbatim where the template calls for them. -The report serves two readers: someone who declined everything and wants a findings list to act on by hand, and someone who approved changes and wants to see what happened. So it always opens with the full findings table, then groups rows by outcome. +The report serves two readers: someone who declined everything and wants a findings list to act on by hand, and someone who culled flags and wants to see what happened. So it always opens with the full findings table, then groups rows by outcome. Findings verdict per row comes from `area`: `Healthy` is **healthy**, `Many call sites` is **warning**, every other bucket is **stale**. Outcome sections, reading the suffix the earlier steps appended to `details`: -1. **Applied**: `status: pass` with `; applied` -2. **Manual action**: `status: pass` with `; declined by user`, plus every `warning` row still unapplied when `wizard_ask` was unavailable. Each row keeps its proposed action so the reader can do it by hand. +1. **Culled**: `status: pass` with `; culled` +2. **Left for you**: `status: pass` with `; declined by user`, plus every `warning` row still open when `wizard_ask` was unavailable. Each row keeps its proposed action so the reader can do it by hand. 3. **Failed**: `status: error` 4. **Kept**: `status: pass` with `; kept:` plus every row the wizard seeded as healthy, and every `Many call sites` row (list those under a "Suggested wrappers" heading with the call-site count) @@ -36,12 +36,12 @@ Outcome sections, reading the suffix the earlier steps appended to `details`: ## Summary -One paragraph: how many flags the wizard looked at, how many were proposed, applied, declined, failed, kept. Whether bulk evaluation or dynamic keys limited what could be proposed. +One paragraph: how many flags the wizard looked at, how many were proposed, culled, left for you, failed, kept. Whether bulk evaluation or dynamic keys limited what could be proposed. | Outcome | Count | |---|---| -| Applied | n | -| Manual action | n | +| Culled | n | +| Left for you | n | | Failed | n | | Kept | n | @@ -53,13 +53,13 @@ Every flag the wizard looked at, one row each, ledger order. |---|---|---|---|---| | `` | stale, warning, or healthy | `` | ` flags, failed, left for you From cb88b3a1062d4dc6614d29e4623c01090b91c9ec Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 14:58:44 -0600 Subject: [PATCH 10/15] docs(cull-feature-flags): say what a truncated scan means for a row The wizard prompt shrinks to facts only, so the verify step now owns the truncation rule: Unreferenced and Comment only rows on a truncated scan are kept with a reason, never proposed and never grepped for. --- .../skills/cull-feature-flags/references/1-verify-call-sites.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/context/skills/cull-feature-flags/references/1-verify-call-sites.md b/context/skills/cull-feature-flags/references/1-verify-call-sites.md index 91a676ce..17cfe1f2 100644 --- a/context/skills/cull-feature-flags/references/1-verify-call-sites.md +++ b/context/skills/cull-feature-flags/references/1-verify-call-sites.md @@ -43,6 +43,8 @@ For each `pending` row, in ledger order, emit `[STATUS] Verifying ` and `Re | `Dead code` | nothing imports the file (the wizard checked; confirm with one `Grep` for the module's basename) | something imports it after all | | `Many call sites` | always `warning`, it is a suggestion, not a removal | never | +**Truncated scan.** When the Scan facts say the scan was truncated, `Unreferenced` and `Comment only` are unproven: the wizard stopped before reading every file. Downgrade those rows to `pass` with `; kept: scan truncated, not proven unreferenced` so they land in the report's Kept table with that reason. Do not Grep for the key; the rule against grepping for flags holds. + Resolve each row through `mcp__wizard-tools__audit_resolve_checks` as soon as it is decided (one call per row, so the run screen moves while you work): - confirmed: `status: "warning"`, `details` = the seeded details plus `; winning branch: true|false|n/a` From adda3079a222568301d6ebb6f52b24f6d03f9d5a Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 16:06:56 -0600 Subject: [PATCH 11/15] fix(cull-feature-flags): typecheck the edits before touching PostHog review asked for real verification instead of "parses by eye", and it was right: a bad edit would have gone straight to a posthog disable. the apply step now edits every approved row, runs the project's own lint and typecheck on just those files (same pattern as the migrate skill), and only then disables flags for rows whose files pass. a row that still fails resolves to error and never reaches posthog. that also broke the cull section into three passes with their own lists (edit, verify, disable and resolve), since one numbered list spanning per-row and once-per-run steps read as "lint after every flag". the 0% bucket is now "off for everyone" to match the wizard: the api has no history, so a rollback and a never-shipped flag look the same. the verify table treats anything that reads like a kill switch as keep. Confidence: high Scope-risk: narrow --- .../skills/cull-feature-flags/description.md | 4 +- .../references/1-verify-call-sites.md | 2 +- .../cull-feature-flags/references/2-apply.md | 52 ++++++++++++------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/context/skills/cull-feature-flags/description.md b/context/skills/cull-feature-flags/description.md index 31c37f16..4edaa283 100644 --- a/context/skills/cull-feature-flags/description.md +++ b/context/skills/cull-feature-flags/description.md @@ -1,6 +1,6 @@ # Cull stale PostHog feature flags in a {display_name} project -This skill removes feature flags that have outlived their purpose: rolled out to everyone, never enabled, archived or deleted in PostHog but still checked in code, or defined in PostHog and never evaluated anywhere. The wizard already did the detection before you started: it scanned the source tree for flag calls, fetched the project's flags, classified every flag with plain rules, and wrote one row per flag into `.posthog-audit-checks.json` at the project root. That ledger is the ground truth. You never grep for flags, never re-classify a row, and never promote a healthy flag into a removal. +This skill removes feature flags that have outlived their purpose: rolled out to everyone, off for everyone, archived or deleted in PostHog but still checked in code, or defined in PostHog and never evaluated anywhere. The wizard already did the detection before you started: it scanned the source tree for flag calls, fetched the project's flags, classified every flag with plain rules, and wrote one row per flag into `.posthog-audit-checks.json` at the project root. That ledger is the ground truth. You never grep for flags, never re-classify a row, and never promote a healthy flag into a removal. Your job is three steps: verify each proposed row at its call site, ask the user once which rows to apply, cull the approved ones (code first, PostHog second), then write the report. @@ -8,7 +8,7 @@ Your job is three steps: verify each proposed row at its call site, ask the user ## State -- `.posthog-audit-checks.json` (project root): the ledger. `area` is one of: `Rolled out` (100% to everyone, no conditions), `Never enabled` (0% everywhere), `Archived in PostHog`, `Disabled in PostHog`, `Unreferenced` (in PostHog, never evaluated in code), `Comment only` (key appears only in a comment or config string), `Dead code` (the only file evaluating it is unreachable), `Deleted in PostHog` (code evaluates a key PostHog does not have), `Many call sites` (three or more files evaluate it directly, suggestion only), `Healthy`. `Read` it once at the start of each step. Each row is `{ id, area, label, status, file?, details? }` where `id` is the flag key, `area` is the bucket the wizard assigned, `label` names the proposed action, `file` is the first call site as `path:line`, and `details` carries the rollout summary and every call site. +- `.posthog-audit-checks.json` (project root): the ledger. `area` is one of: `Rolled out` (100% to everyone, no conditions), `Off for everyone` (0% everywhere, possibly rolled back), `Archived in PostHog`, `Disabled in PostHog`, `Unreferenced` (in PostHog, never evaluated in code), `Comment only` (key appears only in a comment or config string), `Dead code` (the only file evaluating it is unreachable), `Deleted in PostHog` (code evaluates a key PostHog does not have), `Many call sites` (three or more files evaluate it directly, suggestion only), `Healthy`. `Read` it once at the start of each step. Each row is `{ id, area, label, status, file?, details? }` where `id` is the flag key, `area` is the bucket the wizard assigned, `label` names the proposed action, `file` is the first call site as `path:line`, and `details` carries the rollout summary and every call site. - Rows change only through `mcp__wizard-tools__audit_resolve_checks`. Never `Edit` or `Write` the ledger file. Never delete it. - Row statuses this skill uses: `pending` (seeded, not yet verified), `warning` (verified, proposed to the user), `pass` (kept, left for you, or culled), `error` (cull failed, reason in `details`). diff --git a/context/skills/cull-feature-flags/references/1-verify-call-sites.md b/context/skills/cull-feature-flags/references/1-verify-call-sites.md index 17cfe1f2..23a2b764 100644 --- a/context/skills/cull-feature-flags/references/1-verify-call-sites.md +++ b/context/skills/cull-feature-flags/references/1-verify-call-sites.md @@ -36,7 +36,7 @@ For each `pending` row, in ledger order, emit `[STATUS] Verifying ` and `Re | bucket (`area`) | confirm as `warning` when | downgrade to `pass` when | |---|---|---| | `Rolled out` | the call site is a boolean check whose true branch is the current behaviour | the flag gates something that must stay switchable (kill switch, ops toggle named as such) | -| `Never enabled` | the call site is a boolean check whose false branch is the current behaviour | the feature is clearly mid-build (recent scaffolding, TODOs pointing at it) | +| `Off for everyone` | the call site is a boolean check whose false branch is the current behaviour | the feature is clearly mid-build (recent scaffolding, TODOs pointing at it), or the flag or its call site reads like a kill switch or rollback (name, comment, a recent change back to 0%) | | `Archived in PostHog`, `Disabled in PostHog` | the call site is a boolean check; keep the false branch | never, these are always safe to propose | | `Deleted in PostHog` | no live flag in PostHog resembles the key | the key looks like a typo of a healthy flag key in the ledger (note the match in `details`) | | `Unreferenced`, `Comment only` | the prompt says no bulk evaluation and no dynamic keys | the prompt reports `getAllFlags` or dynamic keys and the bulk or dynamic call site could reach this key | diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md index cc426330..1f42276c 100644 --- a/context/skills/cull-feature-flags/references/2-apply.md +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -42,27 +42,43 @@ Rows not approved: resolve to `status: "pass"` with `details` = seeded details p ## Cull -Emit per row: - -``` -[STATUS] Culling -``` +### Edit the code For each approved row, in ledger order: -1. **Code edit** (skip for `Unreferenced`; for `Comment only` remove the mention only): - - `Read` each call site listed in `details`. - - Keep the winning branch recorded in `details` (`winning branch: true` keeps the code that ran when the flag was on; `false` keeps the code that ran when it was off). Delete the other branch, the flag call, and any import or hook that is now unused. - - `Dead code`: delete the unreachable file instead of editing it. - - `Deleted in PostHog`: code edit only, there is no flag to disable. - - Re-`Read` the edited file once to confirm it still parses by eye (balanced braces, no dangling variable). -2. **Disable the flag in PostHog** (skip for `Deleted in PostHog`, there is no flag, and for `Archived in PostHog` and `Disabled in PostHog`, the flag is already off; only after step 1 succeeded for this row): - - `exec({ "command": "search feature-flag" })`, pick the tool whose description says it disables a flag. - - `exec({ "command": "info " })`, then `exec({ "command": "call })` with the flag key or id from `details`. - - Never call a delete or archive tool. -3. Resolve the row: - - both parts succeeded: `status: "pass"`, `details` = seeded details plus `; culled` - - anything failed: `status: "error"`, `details` = seeded details plus `; failed: `. If the code edit failed, do not touch PostHog for this row. +1. Emit: + + ``` + [STATUS] Culling + ``` + +2. `Unreferenced` has no call site: skip the rest of this list, the row goes straight to the verify and disable passes. `Comment only` removes the mention only. +3. `Read` the call site named in `file` and every extra site listed in `details`. +4. Keep the winning branch recorded in `details` (`winning branch: true` keeps the code that ran when the flag was on; `false` keeps the code that ran when it was off). Delete the other branch, the flag call, and any import or hook that is now unused. +5. `Dead code`: delete the unreachable file instead of editing it. +6. `Deleted in PostHog`: code edit only, there is no flag to disable. + +A failed code edit resolves the row to `status: "error"` with `details` = the seeded details plus `; failed: `. That row is finished: it never reaches the disable pass, and PostHog is never touched for it. + +### Verify the edited files + +Once per run, after every approved row has been edited: + +1. Emit `[STATUS] Type checking files`. +2. `Read` `package.json` for the project's lint and typecheck scripts. Run them only on the files edited in this session, never across the whole project. Capture stdout and stderr; truncate long output to the failure region. +3. Re-run after each fix until clean. Fix only cull-induced failures, prioritizing files you edited. + +A row whose file still fails resolves to `error` in the format above and never reaches the disable pass. + +### Disable and resolve + +For each approved row whose code edit and file verification passed, in ledger order: + +1. `Deleted in PostHog` has no flag, and `Archived in PostHog` and `Disabled in PostHog` are already off: these rows need no PostHog call, go straight to step 5. +2. `exec({ "command": "search feature-flag" })`, pick the tool whose description says it disables a flag. +3. `exec({ "command": "info " })`, then run `exec({ "command": "call })` with the flag key or id from `details`. +4. Never call a delete or archive tool. +5. Resolve the row: `status: "pass"`, `details` = seeded details plus `; culled`. Anything that failed in this pass resolves to `error` in the format above instead. ## Output From 2c7b442a4d65918e1f11ee04e7d5ac4020cd3693 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 16:06:56 -0600 Subject: [PATCH 12/15] feat(cull-feature-flags): explain each flag in the consent list the flag picker was a flat list of "[bucket] key: action" labels and a reviewer rightly said it teaches nothing. wizard_ask already renders a dimmed description under each multi-select option, so every flag now carries one sentence: the bucket and why it landed there, then what culling does to the code and the flag. label is the bare key. options stay in ledger order, which the wizard now seeds grouped by bucket. Confidence: high Scope-risk: narrow --- context/skills/cull-feature-flags/references/2-apply.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md index 1f42276c..fc270646 100644 --- a/context/skills/cull-feature-flags/references/2-apply.md +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -32,7 +32,12 @@ Call `mcp__wizard-tools__wizard_ask` exactly once, with two questions in that on > Pick the flags to cull. Each one gets disabled in PostHog (re-enable any time from the flag page) and its check removed from code (revert with git). - One option per row: label `[] : `, value ``. + One option per row, in ledger order: + - `label`: `` + - `value`: `` + - `description`: `, . .` + + For example: `Rolled out, 100% to everyone. Keeps the on path, drops the check, disables the flag.` Or: `Off for everyone, 0% everywhere, may be a rollback. Keeps the off path, drops the check, disables the flag.` If the call errors (non-interactive host, cap reached), treat it as report-only. Do not retry more than once. From 16c36cde538c276e0ce223e2657dec4ba27d9ac6 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 16:06:56 -0600 Subject: [PATCH 13/15] fix(cull-feature-flags): status through the quiet stretches, fewer reads two reviewers hit the same thing: the screen looked frozen before the pick and dead after it. the skill went silent between reads and edits. verify now emits a status per call-site file and "ready to ask" before the prompt; the cull pass emits one per edit, one for the typecheck, and one per posthog disable. cheap speed-ups while in there: archived, disabled and deleted rows read only their first call site (the table already calls them always safe), and the disable tool is discovered once before the loop instead of search plus info per flag. Confidence: high Scope-risk: narrow --- .../references/1-verify-call-sites.md | 8 +++++++- .../cull-feature-flags/references/2-apply.md | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/context/skills/cull-feature-flags/references/1-verify-call-sites.md b/context/skills/cull-feature-flags/references/1-verify-call-sites.md index 23a2b764..2d9d09e1 100644 --- a/context/skills/cull-feature-flags/references/1-verify-call-sites.md +++ b/context/skills/cull-feature-flags/references/1-verify-call-sites.md @@ -31,7 +31,7 @@ Emit: [STATUS] Verifying flag call sites ``` -For each `pending` row, in ledger order, emit `[STATUS] Verifying ` and `Read` the file named in `file` (and every other call site listed in `details`) around the given line. Decide one of: +For each `pending` row, in ledger order, emit `[STATUS] Verifying `. For `Rolled out` and `Off for everyone`, `Read` the call site named in `file` and every extra site listed in `details` as `also `, around the given line. For `Archived in PostHog`, `Disabled in PostHog`, and `Deleted in PostHog`, `Read` only the first call site named in `file`. Emit `[STATUS] Reading ` before each call-site file `Read`. `Dead code` keeps its one `Grep` for the module's basename; do not read call-site files for that row. Decide one of: | bucket (`area`) | confirm as `warning` when | downgrade to `pass` when | |---|---|---| @@ -57,3 +57,9 @@ Every row that was `pending` is now `warning` or `pass`. No other file changed. ``` [STATUS] flags proposed, kept ``` + +Then emit: + +``` +[STATUS] Ready to ask +``` diff --git a/context/skills/cull-feature-flags/references/2-apply.md b/context/skills/cull-feature-flags/references/2-apply.md index fc270646..0696d574 100644 --- a/context/skills/cull-feature-flags/references/2-apply.md +++ b/context/skills/cull-feature-flags/references/2-apply.md @@ -59,9 +59,10 @@ For each approved row, in ledger order: 2. `Unreferenced` has no call site: skip the rest of this list, the row goes straight to the verify and disable passes. `Comment only` removes the mention only. 3. `Read` the call site named in `file` and every extra site listed in `details`. -4. Keep the winning branch recorded in `details` (`winning branch: true` keeps the code that ran when the flag was on; `false` keeps the code that ran when it was off). Delete the other branch, the flag call, and any import or hook that is now unused. -5. `Dead code`: delete the unreachable file instead of editing it. -6. `Deleted in PostHog`: code edit only, there is no flag to disable. +4. Emit `[STATUS] Editing ` before each file edit. +5. Keep the winning branch recorded in `details` (`winning branch: true` keeps the code that ran when the flag was on; `false` keeps the code that ran when it was off). Delete the other branch, the flag call, and any import or hook that is now unused. +6. `Dead code`: delete the unreachable file instead of editing it. +7. `Deleted in PostHog`: code edit only, there is no flag to disable. A failed code edit resolves the row to `status: "error"` with `details` = the seeded details plus `; failed: `. That row is finished: it never reaches the disable pass, and PostHog is never touched for it. @@ -77,13 +78,14 @@ A row whose file still fails resolves to `error` in the format above and never r ### Disable and resolve +Before the loop below, run `exec({ "command": "search feature-flag" })` once, pick the tool whose description says it disables a flag, then run `exec({ "command": "info " })` once. + For each approved row whose code edit and file verification passed, in ledger order: -1. `Deleted in PostHog` has no flag, and `Archived in PostHog` and `Disabled in PostHog` are already off: these rows need no PostHog call, go straight to step 5. -2. `exec({ "command": "search feature-flag" })`, pick the tool whose description says it disables a flag. -3. `exec({ "command": "info " })`, then run `exec({ "command": "call })` with the flag key or id from `details`. -4. Never call a delete or archive tool. -5. Resolve the row: `status: "pass"`, `details` = seeded details plus `; culled`. Anything that failed in this pass resolves to `error` in the format above instead. +1. `Deleted in PostHog` has no flag, and `Archived in PostHog` and `Disabled in PostHog` are already off: these rows need no PostHog call, go straight to step 4. +2. Emit `[STATUS] Disabling in PostHog`, then run only `exec({ "command": "call })` with the flag key or id from `details`. +3. Never call a delete or archive tool. +4. Resolve the row: `status: "pass"`, `details` = seeded details plus `; culled`. Anything that failed in this pass resolves to `error` in the format above instead. ## Output From 7cb43b3940f534080957d01126ce9b1f2986a993 Mon Sep 17 00:00:00 2001 From: johncwaters Date: Fri, 4 Sep 2026 16:27:53 -0600 Subject: [PATCH 14/15] fix(cull-feature-flags): say which culled flags were actually disabled "culled 6" next to "4 disabled" read like two flags fell through. they did not: archived and deleted flags have nothing to disable, the code check is all that goes. the culled table now has a code column and a posthog column, with the posthog cell fixed per bucket, and the summary counts how many culled flags had a live flag to switch off. Confidence: high Scope-risk: narrow --- .../cull-feature-flags/references/3-report.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/context/skills/cull-feature-flags/references/3-report.md b/context/skills/cull-feature-flags/references/3-report.md index 8eb18beb..4d639187 100644 --- a/context/skills/cull-feature-flags/references/3-report.md +++ b/context/skills/cull-feature-flags/references/3-report.md @@ -36,11 +36,12 @@ Outcome sections, reading the suffix the earlier steps appended to `details`: ## Summary -One paragraph: how many flags the wizard looked at, how many were proposed, culled, left for you, failed, kept. Whether bulk evaluation or dynamic keys limited what could be proposed. +One paragraph: how many flags the wizard looked at, how many were proposed, culled, how many of the culled had a live flag to disable, left for you, failed, kept. Whether bulk evaluation or dynamic keys limited what could be proposed. | Outcome | Count | |---|---| | Culled | n | +| of which disabled in PostHog | n | | Left for you | n | | Failed | n | | Kept | n | @@ -55,9 +56,13 @@ Every flag the wizard looked at, one row each, ledger order. ## Culled -| Flag | Bucket | What changed | Call sites | -|---|---|---|---| -| `` | `` | ` flags, failed, left for you ``` + +When nothing was approved, emit this once instead and continue to the report step: + +``` +[STATUS] Report only, nothing changed +```