Repository navigation
Description
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 eightlanes/*.md, and the rest.decision-analysis.mdis 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 vianpx 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
claude commentedon Sep 3, 2026
Triage — skills seat (session
session_019RfFHiRCSs3JXLK4cwcfox, os-steve), 2026-09-03T19:18Z. Gradedpriority:p3,domain:skills(the gate guards this lane's protocol files),tooling, statepm:queue, type Task. Applied and read back after this comment.Scope as queued (XS,
scripts/check-skill-frame-sync.mjsonly, pure code): dropreferencesfromSCAN_SKIP_DIRSso the anti-dormancy scan reaches the tworeferences/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
os-steve commentedon Sep 3, 2026
Claim: PM loop round 3
Session:
session_019RfFHiRCSs3JXLK4cwcfoxBranch:
claude/issue-15056-frame-sync-scan-referencesWorktree:
objectstack-issue-15056Domain:
domain:skillsFile surface:
scripts/check-skill-frame-sync.mjs(SCAN_SKIP_DIRSat:345and the self-test's population pins from:907atorigin/main82291fba) (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.mjsatorigin/main82291fba, 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.mjsis free on the seat post's hot-file queue; no in-flight claim names it; no open PR touches it (last merge7b19b9ea08: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:
referencessits in the skip set besidenode_modules/.git/dist, and the comment block at:304-345may 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 blanketreferences.Generated by Claude Code
claude commentedon Sep 3, 2026
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
os-steve commentedon Sep 3, 2026
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, basemain, first lineFixes #15056, one filescripts/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:
referencesis out ofSCAN_SKIP_DIRS; the skip's stated reason lives in a sibling gate and is aboutcontent/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 underskills/**, on the recorded refusal of that exemption. Seat's own runs at the head: the gate answers2 copies … isomorphic across 2 filesand72 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 samereferencesskip incheck:role-wordandcheck:corpus-claim-drift) is real and is triaged by this seat.CI at 19:41Z on
ffb0f544: 33 check runs, 20success+ 11skipped, twoin_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