60bfebdc98b1d88db02a7df307a75cbec87ea794
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0c7dc8c8ae |
feat(census): report MIXED-VOCABULARY files — the shape behind four half-conversion findings in one day (#2704)
## The pattern Four review findings dispatched to me in a single day were the **same defect**: a guard converted to role resolution while the function it *feeds* still filters on the literal. The resolved guard admits a custom column, the literal collaborator rejects it, and **nothing errors** — the endpoint returns `repaired: 0` and reads as converted. | PR | resolved side | literal collaborator | |---|---|---| | #2700 | review guard | `reconcileInReviewBranchRebind` filters `=== "in-review"` | | #2700 | retry guard | `isInReviewMissingWorktreeSessionStartFailure` likewise | | #2698 | role-aware tabs | reconciliation effects still compare `"done"` / `"in-review"` | | #2688 | role-derived flags | memos and a `useState` capture keyed on the stale value | Since opening this I have been handed **two more** of the identical shape (#2701, #2702). It is not a coincidence; it is what a conversion phase produces by default. ## What this adds A file where **both vocabularies are live** is where that can happen, so the census now names those files. **Measured: 23 of 134 guard-bearing files, holding 311 of 686 guards** — and the top of the list is exactly where the findings landed: ``` MIXED-VOCABULARY files (a role resolver AND legacy literals): 23, holding 311 guards 110 packages/engine/src/self-healing.ts 57 packages/engine/src/executor.ts 26 packages/engine/src/scheduler.ts 20 packages/dashboard/src/routes/register-task-workflow-routes.ts ``` ## Report-only, deliberately A partially converted file is the **expected** state during a conversion phase. Gating this would punish correct in-progress work and would be routed around within a day. What it buys is that a reviewer of a listed file knows to check the collaborators of anything converted — which is what this repo's **Surface Enumeration** rule already requires, and what each of those PRs missed. The rule exists. The fleet work order does not mention it, so reviewers are catching these one site at a time. ## Verification Five tests, both directions: flags a mixed file; does **not** flag a fully literal one (or the entire backlog lights up and the signal carries no information); does **not** flag a fully converted one; does not match a resolver name inside a longer identifier (the `hold`-inside-`threshold` trap from #2677); survives an unreadable file. **Mutation: dropping the resolver condition fails 2 of 38.** The helper lives in the **lib**, not the CLI — importing the CLI executes it and calls `process.exit`, so nothing defined there is reachable from a test. I found that by trying. 38 census tests green · `--strict` and `--compare` exit 0 · lint clean · gate green (487 + 158 + 10 + 71). **No census numbers change.** |
||
|
|
8e211d1870 |
TAKING scripts/: parse instead of grep — an AST classifier for the lifecycle-column bar, cross-checked by a second implementation (#2633)
The program's completion bar is "`column === "triage"` reaches zero".
This measures what that bar actually covers, and checks the measurement
in so it cannot drift.
## The number, measured by the checked-in tool
```
lifecycle-column-census: scanned 1956 source files
COLUMN guards (the backlog): 1031
ROLE comparisons (not guards): 10
DELIBERATE-LITERAL (reviewed): 4
by column id:
313 done
217 in-review
201 in-progress
177 archived
83 todo
40 triage
top files:
151 packages/engine/src/executor.ts
136 packages/engine/src/self-healing.ts
50 packages/dashboard/app/components/TaskCard.tsx
44 packages/core/src/task-store/moves.ts
34 packages/dashboard/app/components/TaskDetailModal.tsx
```
**`triage` is under 4% of the class.** Every one of those 1031 sites is
the same defect: a lifecycle decision made by column NAME, which stops
matching the moment a board renames a column. The bar can be met in full
while 991 identical guards remain — and two files hold a quarter of
them.
## The tracked count is wrong in three directions at once
Each of these cost real work this week, which is why this is a PR and
not a comment.
1. **Vocabulary.** It measures one of six legacy ids.
2. **Receiver.** It is anchored on locals named
`column`/`toColumn`/`fromColumn`, so it never saw the three real guards
in `executor.ts` written against `from` and `originColumn`. One of those
meant completed-but-stranded work was never recovered on a renamed
board, with nothing else owning that state (converted in #2628).
3. **Collision.** `role === "triage"`, `agentType === "triage"`,
`entry.agent === "triage"` compare an **AGENT ROLE**. The planner *lane*
is named `triage` and keeps that name — U11 removed the *column*. Ten
such sites were counted as backlog, and the "obvious" fix (renaming the
role) silently empties the planner's prompt template and mis-binds its
model markers.
A count that is too high and too low simultaneously sends work to the
wrong files while hiding the files that need it. So the census reports
**three separate numbers** and never nets them.
## Proven to fail on the original defect
Not asserted — exercised:
```
$ # reintroduce `task.column === "triage" || task.column === "todo"` into live-agent-count.ts
$ node scripts/lifecycle-column-census.mjs --strict; echo "exit=$?"
packages/core/src/live-agent-count.ts: 10 -> 12
exit=1
$ # restore the file
$ node scripts/lifecycle-column-census.mjs --strict >/dev/null; echo "exit=$?"
exit=0
```
The CLI also exits 1 when its own file list comes back empty — a guard
that reports success without checking anything is worse than no guard.
## 12 regression cases, split by what they defend
Must catch: all six ids; a guard on a local named `from`/`originColumn`
(verbatim the executor.ts shape); single quotes; negation; several
comparisons on one line.
Must **not** catch: role comparisons; comment prose (two tracked
"guards" in `replan-target.ts` were prose about a filter that lives in
another file); a trailing `// … === "triage"` on a code line; sites
carrying a `DELIBERATE-LITERAL` marker.
Plus: **one marker cannot launder a distant guard in the same file** —
that is how allowlists rot.
## Report-only, deliberately
`--strict` compares per-file counts against
`scripts/lib/lifecycle-column-census-baseline.json` and fails when any
file's count **rises**. It is **not** wired into the merge gate: a
thousand-site backlog cannot be a blocking check the day it is first
measured, and a guard nobody can pass is a guard everyone disables.
Owners tightening their own area re-record the baseline in the PR that
lowers it. This is the ratchet shape the `DELIBERATE-LITERAL` markers
scattered through the program already anticipate.
## Stated limitation
Classification is by receiver **name**, so a future field named `agent`
that holds a column would be misclassified as a role comparison.
Recorded at the site, and it is precisely why the two classes are
reported separately instead of netted into one figure.
## Verification
- 12/12 new cases
(`packages/engine/src/__tests__/lifecycle-column-census.test.ts`)
- `pnpm test:gate` **71/71**; `pnpm lint` clean
- `pnpm census:lifecycle-columns`, `--json`, and `--strict` all
exercised end to end
- documented in `docs/testing.md`; no production code touched
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|