Skip to content

[finding] the INVISIBLE half of the bare-root species is ~33 gates, not 4 — a population literal with no separator never reaches the hint set at all #10840

Description

@os-zhuang

Observation-class, measured while implementing #10705 (the escapable-literal ledger, PR #10839).
No gate is red. Filed unassigned, no pm:queue. ⛔ This is not a request to change
hintCovers' :920 refusal — the +139084 measurement stands.

The distinction #10705 does not draw

#10705 bounds the species where a bare root literal reaches the hint set: a source spelling
carrying a separator somewhere ('scripts/'), which extractWatchHints trims to the bare word
scripts. That population is small and is now enumerated and gated — measured on
a7ea3289eb, 4 literals, 2 escaped and 2 on the shrink-only ledger.

There is a strictly larger species beside it, and the ledger cannot see it. looksPathy
requires a separator:

const looksPathy = s.includes('/') || /^\.(claude|changeset|github|gitattributes)\b/.test(s);

So a gate whose population is spelled with no separator at all builds no hint whatsoever:

extractWatchHints("const POPULATION = 'packages';")   -> []          <- invisible
extractWatchHints("const POPULATION = 'packages/';")  -> ["packages"] <- #10705's species
extractWatchHints("const POPULATION = 'packages/**';")-> ["packages/**"] <- nameable

This is the same shape #10107 recorded and #10114 fixed for check-role-word.mjs' skills
root — but as a one-instance card. Nothing ever enumerated the class.

The measurement

Swept over all 128 discovered families on a7ea3289eb, restricted to quoted literals bound to
a population-declaring constant name (*_ROOT, *_ROOTS, *_DIR, *_DIRS, POPULATION,
*_SCOPE) whose value is a bare top-level tracked directory, with comments and self-test
bodies masked: 34 (family, constant, word) triples, 33 of them undeclared.

The single declared one is check:examples-live-imports' EXAMPLES_ROOT = 'examples', which
took the escape in #10314 — i.e. the sweep finds exactly the shape the two landed fixes
addressed, and 33 more of them. A sample:

family constant word
check:engine-double-contract SCAN_ROOTS packages, examples
check:error-code-casing SCAN_ROOTS packages
check:skill-compatibility PACKAGE_ROOTS, SKILLS_DIR packages, apps, examples, skills
check:skill-frame-sync SCAN_ROOTS skills
check:verify-stand-in SCAN_ROOTS packages, examples
check:driver-conformance DRIVERS_DIR, CASE_SETS_DIR packages
check:resume-authority-declared DEFAULT_SCAN_ROOTS packages, examples
scripts/check-skills-token-ratchet.mjs SKILLS_DIR skills

(Full 33-row listing reproducible with the probe described below.)

Why this was NOT folded into #10705

Two reasons, both measured rather than argued:

  1. The recognizer would be a third idiom. [finding] the escapable-literal species has no ledger — 4 gates found one at a time over 4 cards, and nothing tracks whether the escape was taken #10705's dispatch is explicit that the
    ROOT_DIR_WATCH_HINTS subtree declaration is the idiom to lift, not to reinvent. Detecting
    this species needs a new heuristic over constant NAMES, which is a judgement call baked
    into a regex, on a gate that runs on every PR.
  2. The obvious wider sweep is unenforceable. Dropping the constant-name restriction and
    flagging any bare top-level-dir literal yields 73 (family, word) pairs across 52 of 128
    families
    — overwhelmingly join(ROOT, 'packages', …) path components in gates that never
    read those roots. Demanding a subtree declaration for those is the +139084 fabrication
    re-introduced one level up, and a shrink-only ledger seeded with 73 rows is an allowlist
    nobody re-reads rather than a debt list.

Why it is worth a card anyway

The remedy per gate is known, cheap and already precedented twice (#10114, #10314): declare the
subtree spelling beside the constant and pin the coupling in that gate's own self-test, the way
check-role-word.mjs does. What is missing is the worklist. Left alone, this class keeps
being re-found one gate at a time — which is precisely the discovery cost #10705 was filed to
end, still running in the half its ledger cannot reach.

Note the asymmetry that makes it worth doing deliberately: the remedy for a gate whose real
population is packages/** names that gate for every packages/** card. Whether that is
wanted is a per-gate judgement with fleet-wide blast radius on dispatch prompts, so this wants
triage as a batch, not 33 self-dispatched one-liners.

Reproducing

The probe is ~40 lines against the tool's own exported predicates (discoverFamilies,
extractWatchHints, collapseHint, trackedFiles, maskSelfTests) plus maskComments; it
reads nothing from memory. Both numbers above (33 and 73) came from the same sweep with the
constant-name restriction on and off.

Refs

#10705 / PR #10839 (the visible half, now gated) · #10107 and PR #10114 (skills, first
instance) · #10314 (examples, second) · #10662 / #10687 (two more) · hintCovers' docblock
and extractWatchHints' looksPathy


Generated by Claude Code

Activity

  1. huangyiirene commented on Aug 21, 2026

    @huangyiirene
    Collaborator

    Triage first-touch: promoted to queue, type Task, as the deliberate batch the card asks for — not 33 self-dispatched one-liners. Scope: work the 33-row worklist gate by gate; for each, either declare the subtree spelling beside the constant + pin the coupling in that gate's own self-test (the #10114/#10314 precedent), or record a reasoned won't-declare — the per-gate judgment is exactly the "does naming this gate for every packages/** card help or spam dispatch prompts" question, and the recorded declines are part of the deliverable. ⛔ Constraints from the card, binding: no change to hintCovers' :920 refusal; no new recognizer heuristic in dispatch-gates.mjs (the 73-pair wider sweep stays unbuilt). Sequencing: same territory as #10705 / PR #10839 (the visible-half ledger) — coordinate with that landing, prefer dispatch after #10839 merges so the ledger idiom is settled. Size/model suggestion: M–L, opus.


    Generated by Claude Code

  2. claude commented on Aug 21, 2026

    @claude
    Contributor

    PM claim — dispatching now.

    • session: c970724d-303c-5614-9d20-a3f92205cfad
    • branch: claude/issue-10840-invisible-bare-root-worklist
    • seat: domain:devx

    ⚠️ Every number on this card is stale and the agent is told to re-measure. The card was measured on a7ea3289eb; scripts/pm/dispatch-gates.mjs has moved three times since — PR #10839 (the ledger), PR #10873 (gates declare a scan surface), and PR #10890 (merged 16:23Z), which emptied ESCAPABLE_LITERAL_LEDGER entirely. So the visible half this card contrasts itself against is now zero rows, not two, and the family count is no longer 128.

    That last one matters for how this card reads: PR #10890's ledger comment now says in the file itself that an empty ledger is not "the species is gone", and points here — "#10840 measures the other half at ~33 gates whose population literal carries no separator at all; those are invisible to the derivation AND to this ledger." This card is the named successor, not a loose follow-up.

    The deliverable this card asks for is the WORKLIST and its TRIAGE, not 33 fixes. Its own words: "What is missing is the worklist" and "this wants triage as a batch, not 33 self-dispatched one-liners." ⛔ And it is explicit that the naive wider sweep (73 pairs across 52 families) must not become a ledger — that would re-introduce the +139084 fabrication one level up.

    ⛔ Not a request to loosen hintCovers' bare-literal refusal. The card says so; so does the dispatch.

    If an earlier claim comment with a different session ID appears above this one, that claim wins and this one is void.


    Generated by Claude Code

  3. claude commented on Aug 21, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 10840,
      "status": "done",
      "branch": "claude/issue-10840-invisible-bare-root-worklist",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10906",
      "premise_still_valid": true,
      "summary": "RE-MEASURED on this tree (131 families, not 128): restricted sweep (constant-name ON) = 33 (family, constant, word) triples, 32 undeclared, vs the card's 34/33; unrestricted sweep (constant-name OFF) = 72 (family, word) pairs across 44 families, vs the card's 73 across 52. The counts held; the wide sweep's family spread did not (52 -> 44). One methodological correction accounts for most of the drift and is worth carrying forward: invisibility must be decided by the extractor itself (put each literal back through extractWatchHints and require it to build nothing), NOT by a hand-copied looksPathy -- the dotted roots .claude/.changeset/.github clear looksPathy on its second branch and were never in this species. Probe non-vacuity: it independently recovers all four landed precedents as already-covered (#10114, #10314, #10662, #10687). TRIAGE SPLIT: the card predicted the join()-path-component kind dominates; that is TRUE of the wide sweep and FALSE of the restricted one -- 29 of the 32 undeclared triples name a root the gate really does walk, only 3 are mere path components. That is the empirical justification for the constant-name restriction the card was unsure about. But precision is not what decides the remedy; two structural facts do, and between them they cover 30 of 32 rows: (a) the idiom can only express a SUBTREE (collapseHint strips globs), so a population that is a file-KIND or FILENAME filter has NO honest declaration available -- manifests-only is 73/4903 = 1.5%, one named config per child dir is 3/238 = 1.3%; and (b) a TRUE declaration on an already-saturated root stops discriminating -- packages/** is already declared wholesale by 3 families, scripts/** and skills/** by 4 each, so declaring check:error-code-casing's real 4408/4903 (90%) population adds one more identical line to every card in the repo. The useful conclusion is therefore NOT 'here are 33 gates to fix': the #10114/#10314 escape only works when the population is a subtree, and most of this species' populations are not. LANDED 3 declarations, and 3 and not more because they are the only rows clearing precision >= 85% AND blast radius <= 300 tracked files: check:driver-conformance packages/drivers/** (259/291 = 89%, a 17x narrower subtree than the bare root's 5.3%), check:skill-frame-sync skills/** (48/50 = 96%), check:skill-compatibility skills/** (49/50 = 98%). Each pins the coupling in its OWN self-test derived from the population constant rather than re-spelled, and each pins its REFUSALS by name too (driver-conformance refuses both the bare root and CASE_SETS_DIR; skill-compatibility refuses all three PACKAGE_ROOTS), following #10314's shape. Nearest miss, recorded not taken: check:engine-double-contract examples at 199/238 = 84%. DURABLE HOME: scripts/pm/bare-root-worklist.mjs -- the sweep derived at runtime from the derivation's own exported predicates (no family, root or word listed in the file; --wide runs the unrestricted contrast sweep, never triaged), plus one recorded verdict per row (DECLARED-NARROWER / REFUSE-WIDE / REFUSE-UNSPELLABLE) each carrying its measured reason. Its self-test pins the two halves together in BOTH directions -- a verdict cannot outlive its row (the shrink), a new row cannot land unjudged -- so it cannot rot into the unread allowlist the card refused. NO recognizer was added to dispatch-gates.mjs; nothing in the derivation reads the worklist and no verdict reaches a dispatch prompt. hintCovers' :920 refusal untouched.",
      "tests": "All at final commit 77d6bbbe38 (merged origin/main 409077e93c mid-run after lint.yml moved under me; re-verified after). GATE UNION, re-derived with 'node scripts/pm/dispatch-gates.mjs' (no paths, merge-base changeset) at that sha: 18/18 green, exit codes captured before any pipe. Includes check:driver-conformance, check:skill-compatibility, check:skill-frame-sync, check:pm-dispatch-gates, check:parse-guard, check:entry-guard, check:required-contexts, check:shard-attestation, check:ci-filter-parity, and the brand-new check-step-collectors.mjs (#10887, landed during this run) which parses the very lint.yml block I edited -- 'EXIT=0, 59 assertions, 2 live block(s) driven under a real bash -e'. 'node scripts/pm/dispatch-gates.mjs --self-test' EXIT=0, verdict line: 'dispatch-gates self-test: 506 cases pass.' Real pass green (the 18-gate derivation above ran it end to end). 'node scripts/pm/bare-root-worklist.mjs --self-test' EXIT=0, verdict line: '33 live row(s), 30 unreachable as spelled, 30 recorded verdict(s) -- none stale, none missing.' check:nul-bytes EXIT=0 plus a manual control-byte grep over all 5 touched files: no matches. DECLARED NARROWING: check:type-check-debt skipped locally -- it needs a built workspace closure this container lacks. Proven environment-caused rather than change-caused by reverting lint.yml to the merge base and re-running: still EXIT=1 with the identical '55 workspace dependenc(ies) ... have no built type entry point on disk' message, and this change contains no TypeScript. CI builds the closure before that step. Restore proven byte-identical by blob hash. I initially skipped pnpm install per the dispatch's suggestion; that was WRONG here -- six gates died with ERR_MODULE_NOT_FOUND 'Cannot find package yaml'. Ran 'pnpm install --prefer-offline' through scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 7s, waited 0s), after which all six went green. Also ran the #10853 negative control: 'pnpm --filter @objectstack/definitely-not-a-package run typecheck' EXIT=0 over 'No projects matched the filters' -- none of my gates use --filter. ABLATIONS -- 3 legs, each PREDICTION WRITTEN DOWN BEFORE THE RUN, each mutation proven ON DISK by before/after marker counts of both the removed and the injected text (an editing tool's exit code is not evidence), each restore proven byte-identical. NO REBUILD LEG APPLIES and that is a positive statement, not a skip: these are plain .mjs executed directly by node, with no dist/ anywhere in the resolution path, so there is no stale-artifact direction for a mutation or a restore to hide in. All three were re-run AFTER committing, so 'git diff --quiet' was a real test rather than a comparison against uncommitted work. (A) Retarget check-driver-conformance's declaration from the driver subtree to the bare root. Predicted: self-test RED on 2 cases + the derivation starts naming the gate for packages/core. OBSERVED: markers narrow=1->0, wide=0->1 (MUTATION CONFIRMED ON DISK); self-test EXIT=1 with 4 red cases, not 2 -- DEVIATION FROM PREDICTION, recorded: the bare root also swallows the case-sets path, so both refusal pins are more tightly coupled than I predicted; and 'driver-conformance' count on packages/core went 0 -> 1, the fabricated lead. Restore: markers back to narrow=1/wide=0, self-test EXIT=0, packages/core back to 0 and driver-sql back to 1. (B) Rename one triage key so it names no live row. Predicted: RED under STALE. OBSERVED: EXIT=1, 'STALE: check:where-matcher SCAN_ROOT nosuchroot'. DEVIATION: FRESH fired too, correctly -- renaming a key orphans both a verdict and a row. (C) Delete one triage entry. Predicted: RED under FRESH. OBSERVED: EXIT=1, 'FRESH: check:i18n-coverage EXAMPLES_DIR examples', STALE count 0. BYTE-IDENTITY after all three: 'git diff --quiet' EXIT=0 and a stronger per-file blob-hash comparison (git hash-object vs git rev-parse HEAD:path) -- IDENTICAL for both mutated files. One process note against myself: my first B/C restore proof printed a bogus exit code because $(grep -c ...) substitutions inside the echo clobbered $? before it was read -- the same exit-code-swallowing trap the dispatch warns about, hit in the reporting half. Caught and re-verified cleanly with the exit code captured immediately.",
      "open_questions": [
        {
          "question": "Should the MATCHED column carry gates whose real population is an entire top-level root that other gates already declare wholesale? This is a fleet policy question, not a per-gate one, and it decides ~13 of the 30 remaining rows at once. Measured: packages/** is already declared by 3 families, scripts/** and skills/** by 4 each; check:engine-double-contract and check:error-code-casing each really read ~90% of the 4903 files under packages.",
          "options": [
            "A. Keep refusing (what I landed). The column stays a targeted, discriminating list; these gates stay invisible and are met as red CI, which costs one repair round each. Consistent with the repo's own local-verification doctrine that CI runs the farm regardless.",
            "B. Declare them. Every packages card gains ~10 more true lines; the column becomes a restatement of 'run the farm' and stops discriminating, but recall is complete.",
            "C. Teach the derivation to rank or fold root-wide declarations separately from narrow ones, so recall and precision stop competing for one column. Materially more work, and it touches the derivation the card ring-fenced."
          ],
          "recommendation": "A for now, which is what this PR does, because a wrong 'yes' is priced above a missing lead by hintCovers' own docblock and is fleet-wide. But the decision is genuinely the maintainer's, and C is the only option that makes it stop being a trade -- worth a card if this class keeps recurring."
        },
        {
          "question": "The worklist self-test fails on a FRESH row (a new gate joining the species unjudged), not just on a STALE one. That is a per-PR demand backed by a heuristic over constant NAMES -- close to what #10705 reason (1) refused to put in dispatch-gates.mjs, though the cost here is one line of recorded verdict rather than a fabricated dispatch lead.",
          "options": [
            "A. Keep failing on FRESH (what I landed) -- a debt list that cannot detect new debt is not a debt list.",
            "B. Fail on STALE only, report FRESH without failing -- zero false-positive blocking risk, but a new row is then only visible to someone who runs the tool."
          ],
          "recommendation": "A, because the remedy is explicitly 'record a verdict in one line' and explicitly NOT 'declare a subtree' -- the self-test message says so. Flagged here so the maintainer can downgrade it to B in one line if they read the trade differently; nothing else depends on the choice."
        }
      ],
      "out_of_scope_findings": [
        "filed as #10907: check:i18n-coverage resolves its population CWD-relatively (EXAMPLES_DIR = 'examples', not join(ROOT, ...)), so an off-root run returns an empty population and reports 'OK (0 config(s))' at exit 0 -- the #4690 shape trackedFiles refuses by name. Unassigned, 'finding' label, no pm:queue. No gate is red: CI invokes it from the repo root."
      ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions