aedee4b8231bf050c3240a00ab6645ede5d87ee9
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
744c01f7fb |
FN-8657: extract move-target literal counter
Make the move-target literal ratchet directly testable without changing its scan behavior. - Export the AST-based legacy destination counter for fixture testing - Cover literal destinations, private moves, and deliberate exemptions Files changed: .../__tests__/check-move-target-literals.test.mjs | 30 ++++++++- scripts/check-move-target-literals.mjs | 77 +++++++++------------- 2 files changed, 60 insertions(+), 47 deletions(-) Fusion-Task-Id: FN-8657 Fusion-Task-Lineage: 5b1f9ac4-6026-4baf-b42c-f41ab0562679 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1660b136d7 |
fix(gate): the move-target ratchet could not see a file until it was committed (#3256)
## What **#3254 fixed this blind spot in the census. It was still present here.** Found by re-running that probe against the other four lifecycle gates. No product change. `git ls-files` lists **tracked** files only, so a brand-new file containing `moveTask(id, "done")` scored **0** locally and flipped the ratchet the moment it was staged. The author sees a green gate, commits, and CI disagrees — the worst possible feedback order. It is also the exact shape that made my own first census probe measure nothing while reading as "no gap", which is how the class was found in the first place. Fixed the way #3254 did — `--cached --others --exclude-standard` — plus a dedupe, because a path can appear under **both** flags in some index states and would otherwise count twice against a baseline expecting one. ## Measured ``` untracked probe: 0 detected before -> 1 after --strict on a clean tree: green before and after (no false positives) lint clean; fnxc-future-dates: none added ``` ## The other gates, measured in the same pass | gate | sees untracked files? | |---|---| | `lifecycle-column-census` | ✅ since #3254 | | `check-sql-column-literals` | ✅ already | | `check-inert-sync-lane-conversions` | ✅ already — walks the filesystem with `readdirSync` | | `check-lane-wiring` | n/a — does not use `ls-files` | | `check-move-target-literals` | ❌ → **fixed here** | This was the last gate with the gap. All five now agree about what a file is. ## Correction to #3250 I wrote there that this script *"has no export seam and runs at import"*, and used that to justify shipping without a unit test. **It does have a seam** — an `isEntryPoint` guard — so a test could import it without triggering the scan. That does not change #3250's conclusion (its revert-proof measurement stands on its own), but the stated reason was wrong, and it was wrong in the direction that excused less testing. Correcting it here rather than leaving it as precedent. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Local source-file checks now include newly created and untracked files. * Duplicate file entries are removed when files appear in multiple Git states. * Existing tracked-file and CI scanning behavior remains unchanged. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b7c7977c09 |
fix(gate): catch cast move-targets, keep ?? fallbacks unflagged, and pin all of it (#3253)
Follow-up to #3250. Two changes, and **one of them is a decision not to widen** — which is the part I would review first. ## Caught now: the cast form ```ts moveTask(id, "done" as ColumnId) // was invisible ``` Columns are typed `ColumnId`, so a cast is the **natural** spelling wherever the parameter is nominally typed. The gate was weakest exactly where this codebase is most likely to write a literal. Cast-wrapping-a-ternary is caught too. ## Deliberately NOT caught: `??` / `||` / `&&` I recommended these arms on #3250. **I was wrong, and the tree proved it.** Adding them flagged: ```ts moveTask(id, (await resolveTaskLifecycleColumns(store, id))?.complete ?? "done", ...) ``` That is the fail-soft idiom this entire programme rests on — resolve, fall back to the legacy id when the workflow is unreadable, exactly as the role helpers degrade. It is the **correct** pattern. A gate that demands a `DELIBERATE-LITERAL` marker on every safe fallback teaches people to add markers by habit, and a habitual marker is how the next real literal walks straight through. So: a legacy id **after** `??` is the safe shape and stays unflagged; a legacy id as the **whole** destination is caught. Backed out, with the reasoning at the site so nobody re-adds it. If you disagree, the counter-argument is that a fallback could mask a lane that should have resolved — but that wants its own report, not this ratchet's exit code. ## Pinned — the gate had no tests at all Fifth spelling missed across three rounds, and every earlier probe was run by hand and thrown away, because the scanner executed on import. Commit 1 makes it importable (behaviour-preserving, `--strict` identical); commit 2 adds **12 tests in both directions**: | catches | does not catch | |---|---| | direct, backtick, ternary, nested ternary, parenthesised, cast, cast-over-ternary | `??` fallback, `\|\|` fallback, resolved destination, substituted template | The negatives are load-bearing, not padding — four of them encode false positives that either shipped or arrived while widening. ## The root cause, recorded in the test header **The destination is a POSITION; every fix so far has enumerated NODE KINDS.** A kind list is something the language extends faster than we guess — I started that pattern myself in #3246 by requiring `arguments[1]` to *be* a literal. The durable defence is that each shape someone finds stays found. ## Measured | check | result | |---|---| | real tree | **0** targets, `--strict` exit 0 (the `??` false positive is gone) | | gate tests | **12 pass / 0 fail** | | anti-vacuity | removing the cast arm **fails** the suite; restoring passes | | eslint / `check-fnxc-future-dates` | clean / 0 | One test corrected itself during writing: I asserted `"drafting"` extracts to `[]`, and it returns `["drafting"]` — legacy filtering is the caller's job. Kept as a test of that split, since folding the vocabulary into the extractor would force every future shape to thread the legacy list. |
||
|
|
0b30eb4146 |
fix(gate): detect ternary move-target literals, which #3246's ratchet could not see (#3250)
## What #3246 landed a gate holding `moveTask` legacy-literal destinations at zero, printing **"POPULATION EMPTY … keep it empty."** I probed that claim the way #3247 probed the census. It held for two spellings and not a third. | form | before | after | |---|---|---| | `moveTask(id, "done")` | ✅ | ✅ | | `` moveTask(id, `todo`) `` | ✅ | ✅ | | `moveTask(id, ok ? "done" : "in-review")` | ❌ **invisible** | ✅ | The check required `arguments[1]` to *be* a literal. A ternary over two lanes is a natural way to write exactly the destination this gate exists to prevent — and per the gate's own header, a wrong target **throws** at runtime rather than no-opping. ## Measured ``` real repo, before and after: 0 targets, --strict passes (no false positives) ternary probe: 0 on HEAD~1 -> 1 after direct + backtick forms: unchanged DELIBERATE-LITERAL marker: still suppresses (canonical placement) lint clean; fnxc-future-dates: none added ``` ## Two scoping decisions, both probed rather than assumed **Not descending into `??` / `||`.** `moveTask(id, lanes.complete ?? "done")` is the documented degraded arm this program writes deliberately — the shape the lifecycle census classifies as `traitFallback` rather than backlog. Counting it would report correct code as debt. Measured at 0 both before and after. **`const t = "archived"; moveTask(id, t)` is still undetected**, and the comment says so at the site. Resolving it needs symbol/dataflow analysis rather than a shape test, which is a different tool than this file is. Flagged so the next person extends deliberately instead of assuming coverage. ## No unit test, and why The script has no export seam and executes at import, so testing it means extracting one — a refactor of a one-commit-old file, which belongs in its own change rather than folded into a behaviour fix. The revert-proof is the measurement above: the ternary probe reads 0 against `HEAD~1` and 1 against this commit. ## Note to #3246's author I raised these gaps on your PR first and offered to send this rather than assume. Two traps that cost me time on the census extension, in case you take it further: - **A count-unless-excluded rule backfires on this vocabulary.** My first census extension counted `switch (x)` unless the receiver looked like a role/status and reported 7 guards — 6 were `switch (eventName)` / `switch (state)` / `switch (event)`, since event enums routinely carry `case "done"`. Requiring a *positive* column signal was the fix. - **Verify the probe file is git-tracked.** My first probe measured nothing because the scanner enumerates tracked files; the scanned-file count stayed flat and I nearly read that as "no gap." |
||
|
|
4e2f52ce8f |
feat(gate): ratchet move-target literals at zero — #3150's population had nothing holding it (#3246)
Closes the gap I flagged when re-measuring #3150: that population is at **0**, and nothing was holding it there. ## Why this surface has no gate today The lifecycle census parses **comparisons**. A move destination is a call **argument**: ```ts await store.moveTask(id, "in-review"); // never counted by anything ``` #3150 measured 31 of these across four files. They are now 0 — I verified that on current main before writing this — but the comparison backlog drifted **787 → 854** during the window its own ratchet was unwired, and this population never had one. ## The failure mode is louder than the guards' A wrong lane **guard** silently answers "no". A wrong move **target** is rejected by `moveTaskInternal` with `TransitionRejectionError: unknown-column` — so on a board that renamed its review lane, every task finishing implementation **threw** instead of reaching review. Loud at runtime, invisible to any test on the default board. ## AST, not grep — and that is measured, not stylistic | scan | result | |---|---| | comment-naive grep of `self-healing.ts` | 1 hit — **JSDoc prose**: `* could call moveTask("in-review")` | | #3150's own SQL survey by grep | 37 hits against **12** real sites (25 comments) | Comments are not AST nodes, so that false-positive class cannot occur here in either direction. ## Controls — all four run, because a gate that only reports 0 proves nothing | probe | expected | got | |---|---|---| | real `moveTask(id, "in-review")` injected | fail | **exit 1**, names the file | | identical call as JSDoc prose | pass | **exit 0** (AST ignores comments) | | legacy target + leading `DELIBERATE-LITERAL` | pass | **exit 0** (marker honored) | | probe removed | pass | **exit 0** | The third is the #1411 `recoveryRehome` safe-landing path, where the legacy id genuinely *is* the target. Marker must be **leading** — the census already learned that an inline marker attaches to the wrong node and is silently ignored. ## Ratchet semantics match the census Fails on a **drop** as well as a rise. A stale allowance is a hole a re-added target can return through while the gate stays green — exactly what let the comparison baseline drift. ## Measured | check | result | |---|---| | this gate | scans **1816** files, reports **0** | | `check:lifecycle-columns` / `check:sql-column-literals` | 0 / 0 | | `check:fnxc-future-dates` / `check:lane-wiring` | 0 / 0 | | eslint / `pnpm test:gate` | clean / exit 0 | Wired into `pr-checks.yml` beside the sibling ratchets, named to match ("Move-target ratchet"). ## Scope Gate only — **no production code touched**, and no conversions in this PR. The population was already empty; this makes "31 → 0" an invariant instead of a snapshot, which is the caveat I attached when recommending #3150 for closure. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Quality Improvements** * Added automated validation for task-movement configuration values. * Pull request checks now detect unexpected changes in tracked values. * Added baseline tracking with strict validation to identify both additions and removals. * Added support for explicitly documenting intentional exceptions. * Improved reporting for file-discovery and source-reading failures. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |