Skip to content

check:skill-frame-sync cannot see any references/ subtree - 32 of 72 hand-written markdown files under its two scan roots, incl. all 12 published skill reference pages, are invisible to the anti-dormancy scan #15056

Description

@os-steve

Measured by the dev seat while implementing #14869 (branch claude/issue-14869-runbook-frame-paste-step), at origin/main d5b330dc plus that branch's one-bullet diff. Unassigned, bare, for triage. The file surface is scripts/check-skill-frame-sync.mjs.

What

check-skill-frame-sync's anti-dormancy half exists to catch "another copy of the decision frame appears somewhere under .claude/ or skills/ and nothing keeps it in sync". Its own header states the job: after the batch-3 item-4 ruling reduced COPIES from four to two, "the anti-dormancy scan below is what keeps a dev-side copy from quietly coming back."

The scan cannot see any references/ subtree. walkMarkdown skips a fixed directory-name set:

const SCAN_ROOTS = ['.claude', 'skills'];
const SCAN_SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'references']);

node_modules, .git and dist are installed or generated trees — nothing hand-written lives there. references is not that kind of directory: under both scan roots it holds hand-written operative prose.

Measured

Walking the two SCAN_ROOTS for .md/.mdx, once with the real skip set and once without references:

population files
what the scan reads today 40
all markdown under the two roots 72
invisible to the scan (references/ only) 32 (44%)

The gate's own pass line agrees with the first row: check-skill-frame-sync: ... 40 markdown files scanned for undeclared copies.

The 32 split into two groups, both hand-authored:

  • 20 internal PM protocol files under .claude/skills/pm-dispatch/references/ — dispatch-runbook.md, platform-readings.md, decision-analysis.md, contract-review.md, state-machine.md, true-green.md, the eight lanes/*.md, and the rest. decision-analysis.md is the file whose subject matter is escalation analysis, so it is the single most likely place for a fourth copy to be pasted.
  • 12 published skill reference pages under skills/objectstack-*/references/ — these ship verbatim to third-party projects via npx skills add, which is the exact distribution path the gate's header gives as the reason the published copy is watched at all.

So a copy of the frame pasted into any of those 32 files today is undetected, and the FINGERPRINTS list that would have caught it (/固定评估轴/, /条评估轴/, /fixed axes/i) never runs against them.

Why it matters

This is the dormancy the scan was built to prevent, one directory level down. The drift incident recorded in the gate's own header — the internal frame widened to three axes while the published mirror stayed at two for two days — is precisely what an unwatched fourth copy reproduces, and the gate would stay green throughout.

It also has a live edge right now. The bullet landing on #14869 quotes the frame's two anchor sentences inside dispatch-runbook.md on purpose (the ruling directs pointing at the frame by its anchors rather than copying it). The first anchor contains 四条固定评估轴, which matches FINGERPRINTS. That bullet is legal today only because references/ is skipped — so whoever narrows the skip set has to decide, in the same stroke, how a deliberate anchor pointer is told apart from a copy.

Not asserted

No claim that a fourth copy exists in the tree today — the sweep run for this card found none of the fingerprints in the 32 files. The defect is the missing coverage, not a present violation.

Suggested shape (for triage, not a decision)

Narrow the skip set to the trees that are genuinely generated or installed (node_modules, .git, dist) and give the anchor-pointer case an explicit, declared exemption — a pointer names the anchors and does not carry the axis entries between them, which is a structural difference the gate already knows how to read. Widening the population without that distinction turns every legitimate pointer red.

Dedup

Complete enumeration of open items, 2026-09-03T19:2xZ: REST GET /repos/objectstack-ai/objectstack/issues?state=open&per_page=100&page=1..6 returned 100+100+100+100+100+30 = 530, reconciled against the repository's own open_issues_count of 530 (506 issues, 24 PRs); local grep over titles and bodies for frame-sync, anti-dormancy, SCAN_SKIP_DIRS, SCAN_ROOTS, walkMarkdown, undeclared copy, decision frame, 评估轴. Nearest neighbour is #14870 (four gate-script comments still say "four"/"two enforced" copies) — a stale-comment card in the same gate family, not this coverage hole. No carrier found.

Activity

  1. claude commented on Sep 3, 2026

    @claude
    Contributor

    Triage — skills seat (session session_019RfFHiRCSs3JXLK4cwcfox, os-steve), 2026-09-03T19:18Z. Graded priority:p3, domain:skills (the gate guards this lane's protocol files), tooling, state pm:queue, type Task. Applied and read back after this comment.

    Scope as queued (XS, scripts/check-skill-frame-sync.mjs only, pure code): drop references from SCAN_SKIP_DIRS so the anti-dormancy scan reaches the two references/ subtrees, measure the scan before and after (expect 2 declared copies still, 0 undeclared, the file count rising from 40 to about 72), and pin the population in the self-test if one exists. Stop condition (evidence, not verdict): if the widened scan finds an undeclared copy, do not edit that file — leave the gate red on it and name the site in the report.


    Generated by Claude Code

  2. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:queueReady for the PM dispatch loop
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Sep 3, 2026
  3. added theissue type on Sep 3, 2026
  4. os-steve commented on Sep 3, 2026

    @os-steve
    CollaboratorAuthor

    Claim: PM loop round 3
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-15056-frame-sync-scan-references
    Worktree: objectstack-issue-15056
    Domain: domain:skills
    File surface: scripts/check-skill-frame-sync.mjs (SCAN_SKIP_DIRS at :345 and the self-test's population pins from :907 at origin/main 82291fba) (stop on breach; explain in the report). ⛔ No markdown file is edited by this flight.
    Container & model: XS code (one set member removed, the self-test's population pin adjusted), mode:subagent, model: opus (tier: node scripts/pm/dispatch-gates.mjs --tier scripts/check-skill-frame-sync.mjs at origin/main 82291fba, 19:17Z: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; PM's call: opus default — the edit changes a gate's population and must reason about why the skip was there)
    Clause-②: no
    Serial constraints cleared: scripts/check-skill-frame-sync.mjs is free on the seat post's hot-file queue; no in-flight claim names it; no open PR touches it (last merge 7b19b9ea 08:58Z today). Not a governed surface (check-governed-merges --test: 0 of 1 hit), no .md ⇒ pure-code landing: in-seat review → ready → auto-merge.

    Decision re-read (19:19Z): no maintainer ruling; the seat's triage (comment 5530864693) fixes the scope. One mechanism assumption for the flight: references sits in the skip set beside node_modules / .git / dist, and the comment block at :304-345 may state a reason (the half of the roots another tool cannot see); the flight reads that reason before removing the entry and reports it — if the skip was deliberate for generated pages, the fix narrows to skipping only the generated subtrees the file names, never a blanket references.


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 3, 2026
  6. claude commented on Sep 3, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15056,
      "status": "done",
      "branch": "claude/issue-15056-frame-sync-scan-references",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15060",
      "premise_still_valid": true,
      "summary": "Removed 'references' from SCAN_SKIP_DIRS in scripts/check-skill-frame-sync.mjs, the one file touched; walkMarkdown at :605 is the only reader of that set, confirmed by grep. The skip's reason is NOT in this file (the docblock at :304-345 is about ROOT_DIR_WATCH_HINTS, a different subject) but is stated in the sibling that shares the idiom, check-corpus-claim-drift.mjs: 'Generated references/ is skipped: the spec source is the fix site there, so a finding in a generated file names the wrong file.' That is about content/docs/references/, which this gate never walks, so the reason does not hold for its two roots and the entry was removed rather than narrowed; no carve-out for the generated pages under skills/** either, matching check-doc-authoring.mjs's recorded refusal of exactly that exemption. Scan population 40 -> 72 files, 2 declared copies unchanged, 0 undeclared copies, so no markdown file was edited and none needed to be. Two card details corrected by measurement, neither changing the outcome: (a) the card's 'live edge' does not exist on this tree - dispatch-runbook.md's only frame mention is '按裁决与评估轴自裁' at :148, which does not match the /条评估轴/ fingerprint, so no anchor-pointer-versus-copy distinction had to be invented; (b) the self-test had no population pin to adjust - the pins at :907 are the dispatch-gates declaration pins over SCAN_ROOTS, untouched by this change - so a new scan-population block was added instead, deriving its numbers from the walk. PM half-state note: none; the claim comment 5530878008 and the assignee were both in place on arrival.",
      "tests": "BEFORE (node scripts/check-skill-frame-sync.mjs, exit captured by redirect before any pipe): EXIT=0, gate's own verdict line '... 5 count mention(s) agree; 40 markdown files scanned for undeclared copies.' | AFTER: EXIT=0, '... 72 markdown files scanned for undeclared copies.', still '2 copies ... structurally isomorphic across 2 files' and zero problems, i.e. 0 undeclared copies over the 32 newly visible files. SELF-TEST after: EXIT=0, 'check-skill-frame-sync self-test: 13 cases pass, plus 5 dispatch-gates declaration cases and 3 scan-population cases (72 markdown files walked under the scan roots, 32 of them inside a references/ directory).' | ABLATION (run from the committed state ffb0f544, script carried trap restore EXIT INT TERM with an absolute REPO_ROOT path). NO REBUILD LEG APPLIES and that is stated rather than skipped: this gate is a plain .mjs invoked directly by package.json ('node scripts/check-skill-frame-sync.mjs'), resolved by path and not through any package's exports/dist, so there is no built artifact between the edit and the run - the ablation-dist-preflight step has nothing to preflight here. MUTATION CONFIRMED ON DISK before any measurement, anchored on the exact text in both directions plus a blob comparison, never on an editor exit code: injected_line_count=1 (the restored 4-entry set), removed_line_count=0 (the 3-entry set), mut_blob=e6bd417339149ab9274423b8a3dcc1ecd5bafd8e vs HEAD_BLOB=53f2d98858f0a7da39c5e7bdd3ef83a02bdcfe16. The first attempt's proof assertion was itself wrong (it grepped a substring that also matches the new INSTALLED_OR_GENERATED constant) and aborted the run before measuring; it was re-anchored on the full SCAN_SKIP_DIRS line and re-run - reported here rather than quietly retried. RESULT on the mutated tree: self-test EXIT=1 with both pins naming themselves ('scan population: the walk reaches the references/ subtrees under SCAN_ROOTS ...' and 'scan population: every SCAN_SKIP_DIRS entry is DECLARED installed-or-generated here ...'), while the GATE ITSELF STAYED GREEN at exit 0 / '40 markdown files scanned' - the direction worth naming, since that green over a 32-file loss is the dormancy this card is about and the reason the pin was needed. RESTORE proved by state, not by exit code: git checkout HEAD -- (absolute path, never the bare form that reads from the index), then git status --porcelain empty, git diff HEAD --name-only empty, git hash-object back to 53f2d98858f0a7da39c5e7bdd3ef83a02bdcfe16 = the HEAD blob. | GATES: re-derived AFTER the commit with 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (no paths - the script took its own change set from merge base 82291fba0, 1 committed path, 0 working-tree, 0 untracked); it printed 22 families, byte-identical to the dispatched list, no additions. Harvested with --commands (22 lines) and all 22 run, each exit code captured by redirect before any pipe. 21 green, 1 NOT MEASURED: check-test-completeness.mjs exits 3 'PREREQUISITE NOT MET - this gate grades a saved turbo run test log, and no log was named', and says of exactly this path 'the local reading for this gate is NOT MEASURED. It is not a red, and there is nothing here to fix.' Verdict lines quoted in the PR body; the load-bearing ones: check:skill-frame-sync as above; check:pm-dispatch-gates 'dispatch-gates self-test: 1288 cases pass'; bare-root-worklist --self-test 'OK self-test: 57 live row(s), 49 unreachable as spelled, 49 recorded verdict(s) - none stale, none missing, none contradicted'; check:watch-hint-literal '48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree'; check:entry-guard '205 scripts/ file(s)'; check:parse-guard '204 scripts/ file(s)'; check:nul-bytes self-test 75 assertions (plus a manual control-byte grep over the edited file: no hits). Gate-script clause: this diff edits a gate script, so its OWN suite was run - there is no vitest pin on it anywhere in the tree (git grep for check-skill-frame-sync across test globs returned nothing), its --self-test IS its suite and is wired (check-self-test-wired green), and the two PM tools that couple to it (check-dispatch-gates.mjs FRAME_MODULE, dispatch-gates.mjs FRAME) assert only file existence, both green. | ESLINT: repo-wide, NOT a narrowing - 'pnpm lint' = 'eslint . --no-inline-config' over the whole tree, exit 0, no findings. | The two heavy runs (check:pm-dispatch-gates, pnpm lint) went through 'bash scripts/pm/os-verify-lock.sh -c ...' with OS_VERIFY_LOCK_SLOT=issue-15056-frame-sync set before the first attempt; both printed 'VERDICT command-exit 0' (held 214s and 63s, waited 0s each - shared-box seconds, as the lock's own line says). | HEAD SHA of the verification run: ffb0f544 (working tree clean against it for every run above; the union was run after the final and only commit). check-governed-merges --test on the final file list: '0 of 1 path(s) hit the register', so an ordinary landing applies. skip-changeset applied additively via REST POST .../issues/15060/labels and read back comparatively: current {size/s, skip-changeset} = union(read, target), nothing stripped.",
      "mcp_calls": "1 - one targeted search_issues for the out-of-scope finding's dedup (control #15056 hit, so the channel was live). Everything else went through the zero-quota card payload for reads and REST for writes; the PR, the label, the finding and this comment are all REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #15061: check:role-word and check:corpus-claim-drift carry the same 'references' entry in SKIP_DIRS while their ROOTS include the published catalog, so 12 of 46 markdown files under skills/ - both hand-authored reference companions plus the 10 generator-owned index pages - are invisible to both scans; unassigned, unlabelled, no present violation asserted. NOT fixed in place: the bounded-in-place exemption fails on the fourth condition (it would add the check:role-word and check:corpus-claim-drift families and widen a baselined claim corpus), and the seat's ruling scoped this flight to exactly one file."
      ]
    }

    Generated by Claude Code

  7. os-steve commented on Sep 3, 2026

    @os-steve
    CollaboratorAuthor

    ACCEPT — skills seat (session session_019RfFHiRCSs3JXLK4cwcfox), reviewer of record, 2026-09-03T19:43Z, verified against GitHub and a compare worktree at the PR head, not against the report.

    PR #15060 @ ffb0f544 — draft, base main, first line Fixes #15056, one file scripts/check-skill-frame-sync.mjs +89/-2, skip-changeset, no other closing keyword, no model name, one footer, no control bytes. check-governed-merges --test: 0 of 1 hit — not governed, no .md ⇒ pure-code landing by the seat.

    Content against the triage scope (comment 5530864693) and the claim's mechanism assumption: references is out of SCAN_SKIP_DIRS; the skip's stated reason lives in a sibling gate and is about content/docs/references/, which this gate never walks — quoted in the docblock, with the two sibling gates that already carry the corrected verdict; no carve-out for the generated pages under skills/**, on the recorded refusal of that exemption. Seat's own runs at the head: the gate answers 2 copies … isomorphic across 2 files and 72 markdown files scanned (40 at the base), 0 undeclared copies over the 32 newly visible files, so no markdown file needed editing; --self-test — 13 cases pass, plus 5 dispatch-gates declaration cases and 3 scan-population cases (72 … 32 of them inside a references/ directory), the population pinned from the walk, not a typed count. The ablation and the first-attempt proof-assertion miss are reported honestly in the PR body — good run.

    Deviations: none. Report mcp_calls: 1 — within contract. Finding #15061 (the same references skip in check:role-word and check:corpus-claim-drift) is real and is triaged by this seat.

    CI at 19:41Z on ffb0f544: 33 check runs, 20 success + 11 skipped, two in_progress (Lint & Repo Gates, Test Core (1/6)); nothing red.

    Landing: the seat flips ready and arms auto-merge once every check is green (next boundary); card closes on merge.


    Generated by Claude Code

  8. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seatpriority:p3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions