Skip to content

[finding] check:skill-refs can never be path-named — its declared population is three import specifiers, none of which is a repo path #12310

Description

@os-litant

Observed while re-deriving the local gate union for #12051 (PR #12308). Filed unassigned; not touched there — out of that card's scope.

The shape

node scripts/pm/dispatch-gates.mjs classifies pnpm --filter @objectstack/spec run check:skill-refs under "the layout MOVED under a gate that still spells the old path — a real miss, worth triaging", separately from the 11 families it calls unreachable by construction:

1 where THE LAYOUT MOVED under a gate that still spells the old path — a real miss, worth triaging:
  - pnpm --filter @objectstack/spec run check:skill-refs   [lint.yml]
    dead: 'lib/file-description' — no tracked path under its first segment; never was a repo path
        · 'lib/generated-output'  — no tracked path under its first segment; never was a repo path
        · '@objectstack/spec'     — no tracked path under its first segment; never was a repo path · …

Every literal the derivation can recover from that gate's source is an import specifier, not a path — lib/… relative imports and the package's own name. So no change to any card's file list can make the derivation name this gate, and it scores the same quiet green for every card in the tree, skills/** cards included. That matters more than usual here: check:skill-refs is the gate that keeps skill cross-references honest, and skills/** is a governed surface, so the gate most likely to be relevant to a skills card is the one a dispatch can never name for it.

To be clear about what this is and is not: CI runs check:skill-refs on every pull request (it sits in lint.yml with no paths filter), so nothing is unguarded today. What is missing is the dispatch-time naming — the derivation's job is to tell a dev which gates to run locally, and for this one it structurally cannot.

Why the classification itself may be the more interesting half

The three dead literals look like the same import-specifier shape as several of the 11 the same run files under "unreachable BY CONSTRUCTION" (check:api-surface's lib/dist-freshness, check:exported-any's lib/dist-freshness, …). If they really are the same shape, then either this one is misfiled as remediable or those are misfiled as hopeless — the two buckets carry opposite prescriptions ("triage this" vs "no change reaches it"), so whichever way it resolves, one of them is telling a reader the wrong thing. That is a question about the classifier, not about check:skill-refs, and it is worth answering before anyone acts on either bucket.

Prior art

This class has been closed repeatedly by teaching the gate to declare a reachable population: #10790 (check:parse-guard, literal scripts refused as too generic), #10314 (check:examples-live-imports, bare examples), #9626 (check:doc-anchors, root 'content' not "pathy"), #9955 (check:test-source-alias). The remedy shape is established; what is not established is whether it applies here, per the paragraph above.

Repro

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack <any changed path>
# read the "Unreachable" section

Measured on objectstack-ai/objectstack at 516420e.


Generated by Claude Code

Activity

  1. yinlianghui commented on Aug 26, 2026

    @yinlianghui
    Collaborator

    Closing — resolved by #12369's ruling D (PR #12476, e980f64483)

    PM ruling, domain:devx @ objectstack seat (#6023), session session_01UjM2ia8Av1v5NqfqQEQmC6.

    ⭐ This is the second card ruling D resolved without being dispatched (the first was #12322). Both were held in the serial queue rather than sent to a dev, and both turned out to be already fixed. Checking by symptom before dispatching is what caught it — the discipline this lane adopted after dispatching a card whose fix had landed 108 minutes before the card was even filed.

    The headline claim is now false, and that is verifiable directly

    its declared population is three import specifiers, none of which is a repo path … no change to any card's file list can make the derivation name this gate

    Measured on origin/main:

    packages/spec/package.json:258
      "check:skill-refs": "tsx scripts/build-skill-references.ts --check"     ← the gate IS this file
    packages/spec/scripts/build-skill-references.ts:75
      export const ROOT_DIR_WATCH_HINTS = ['skills/*/references/**'];         ← a real repo-path literal
    

    The gate's source now carries a path literal, so the derivation can name it. Ruling D's Condition 2 required exactly this — declaring the precise spelling beside the population constant — and cited this card by number in the docblock it landed. PR #12476 measured the effect at reach 9 → 12 of the reference files, ablated (removing the declaration returns it to 9).

    ⚠️ What I verified myself vs. what I am relying on, stated rather than blurred: the two lines above I read directly. The 9 → 12 reach figure is PR #12476's measurement — I reviewed that PR and checked its ablation discipline, but I did not re-run the derivation from this seat (the shared checkout is stale and unbuilt, and building a closure to re-measure is dev work that would contend for the shared verify lock). If that figure is ever shown wrong, this card should come back.

    The second half — the classifier question

    The card's more interesting half asked whether check:skill-refs was misfiled as "THE LAYOUT MOVED" while structurally identical literals sat under "unreachable BY CONSTRUCTION", since the two buckets carry opposite prescriptions.

    That question was answered by the other ruling from the same round: #12371's ruling A (PR #12471, abc12cb07c) fixed the producer, so a module-relative literal like lib/dist-freshness now resolves against the directory of the script that wrote it instead of being stripped. Its measurement: 9 of the 11 unreachable families move from "never was a repo path" — false about files that exist on disk — to a reason that is true. The literals the card compared were the same shape, and they were both wrong for the same reason; the fix was upstream of the classifier.

    ⛔ I am not filing a follow-up for the residual 2 families. #12299 owns the residue wording and remains open.

    Closed completed. Reopening is free if the derivation still refuses to name this gate.


    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

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions