Skip to content

docs(skills): correct six false mechanism claims in the published pm-dispatch template - #13891

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-13868-skills-sweep-pm-dispatch
Aug 31, 2026
Merged

docs(skills): correct six false mechanism claims in the published pm-dispatch template#13891
os-zhuang merged 1 commit into
mainfrom
claude/issue-13868-skills-sweep-pm-dispatch

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #13868
Part of #13658

Flight ⑪ of the published-skills factual sweep: skills/objectstack-pm-dispatch/SKILL.md, one file, 1,077 lines at origin/main — the PM's face assumption re-derived from the fetched tree and CONFIRMED exactly.

Governed surface: this PR stays DRAFT for human merge. It is not made ready, not armed, not enqueued.

Boundary this flight was fenced on

The target is the CUSTOMER-PUBLISHED template written for OTHER repositories. {default_branch} / {backlog_repo} / {target_repo} / {n} interpolations are correct there and all survive the diff. The internal agent corpus at .claude/skills/pm-dispatch/ was neither edited nor used as an oracle for any claim below; every correction is settled against an IMPLEMENTATION in this tree (a workflow, a gate script, a shipped shell script), never against another document.

Corrections — 6 distinct false facts, 7 landing sites

F1. Closing keywords were taught in the form GitHub does not honour (2 sites). The dispatch prompt and the operating template both told every dispatched agent to write Fixes {backlog_repo}#{n} in a PR that lands in {target_repo} — a qualified cross-repository closer. Oracle, first sentence of .github/workflows/cross-repo-issue-closer.yml — quoted with its two example issue numbers replaced by NNN, because the live closer workflow parses merged PR bodies and a verbatim quote here would be an instruction to close somebody else's card: "GitHub's closing keywords (Fixes #NNN) only work WITHIN a repository. A PR here that says Fixes objectstack-ai/objectui#NNN reads exactly like a same-repo close to a human, merges, and leaves that issue open forever — with no reference to the PR on the issue's own page either." That workflow exists only because of this, is live (pull_request_target: [closed], if: merged == true), and carries its own gate, scripts/check-cross-repo-closer-outcome.mjs, which measured zero qualified cross-repo closers across 1,176 merged PRs in two independent windows.

This is the falsehood with the largest blast radius in the file, because it fires exactly in the multi-repository routing case the skill is built around: the card never closes, the loop's own terminal state never arrives, and the PM sees no signal that anything went wrong. The document already contradicted itself here — the review step (step 7) verifies the body "references Fixes #{n}", the unqualified, correct form.

  • The dispatch prompt's non-negotiable now says only "Deliver a DRAFT PR in {target_repo}. Never merge anything." The PR-body spelling was a duplicate of the operating template pasted verbatim three lines above it, and it is the copy that had drifted; deleting the drifted duplicate leaves one owner for the rule.
  • The template's Definition of done now carries the correct rule for both cases: Fixes #n in-repo, Part of {backlog_repo}#n cross-repo. Part of is already the skill's own cross-repo backlink spelling in two other sections, so this is the file's existing convention rather than a new one.

F2. The state-model terminal row over-claimed (1 site). "merged PR with Fixes #n → done (GitHub closes the issue)" now reads "done — GitHub closes it, same repo only". Note the boundary: the row as written was TRUE for the unqualified #n it shows, which is why it is a scope correction and not a reversal.

F3. The cloud-mode report marker was the one token this same document measured as destroyed. Step 5 recommended "a machine-findable marker such as" an HTML-comment marker. Sixty lines earlier, step 4 records the measurement: that exact marker "came back as nothing at all", and the file itself says it "cost the most — it was a report-collection marker, so the instruction to sweep for it had been deleted from the very text that carried it." The recommendation is now a machine-findable PLAIN-TEXT marker. A self-refuting instruction: the document could not be right in both places.

F4. The sanitizer's trigger was described more narrowly than the document's own measurement (1 site). The operating template taught that the sanitizer "strips LT followed by a letter as an HTML tag AT REST". The measurement in step 4 includes an HTML-comment span, whose second character is a bang, not a letter — so an author who follows the stated rule and writes an HTML comment believes they are outside the hazard. The template now says the sanitizer deletes tag-shaped spans at rest, LT plus a letter and HTML comments alike. This also matches AGENTS.md's own clause, which warns that fenced code does NOT protect such tokens.

F5. Cross-reference to the wrong decision axis (1 site). The "Adapting this loop" table sent the reader to "the business-need axis" for the capability-expansion stance. Axis ① is business need; the stance is read by Axis ④ (startup scope discipline), whose own text says so in as many words ("declare the capability-expansion stance in your conventions file … and this axis reads it from there"), as does the template's fourth bullet. Now "axis ④". This correction SHRINKS the file by 14 bytes and is the only one that paid for the others.

F6. An arithmetic claim contradicted its own enumeration (1 site). The completion-baseline passage enumerates four text-only cards and four mixed cards, then summarises "Nine cards, one day, one toolchain". Four plus four is eight. Now "Eight cards".

The --no-track fix — discharging #13729's published half ONLY

The worktree recipe in the operating template (was :741) prescribed git worktree add ../…-issue-n -b claude/issue-n-slug origin/{default_branch} with no --no-track, so a plain -b writes branch.NAME.remote / branch.NAME.merge into the .git/config that every worktree of a repository SHARES. Now hardened, with the {default_branch} interpolation preserved verbatim — that was the constraint that made this a scope-split half rather than a copy of this repo's line.

Per #13729's scoping comment, this discharges its published half only. Its three internal copies (.claude/skills/checklist-author/SKILL.md, .claude/agents/os-dev.md, .claude/skills/dogfood-verification/SKILL.md) stay on that card, untouched here — one of them is inside this flight's hard fence. #13729's table also records that this copy, unlike checklist-author's, was NOT missing the fetch/origin half, and that reading still holds.

Budget — measured BEFORE editing, and the fork it forced

The ceiling was read first, exactly as #13729 required: skills/objectstack-pm-dispatch/SKILL.md = 14,391 tokens against a ceiling of 14,391, headroom ZERO; the ratchet's convention is ceil(utf8 bytes / 4), so the file had one byte of slack.

before after delta
bytes 57,563 57,558 −5
tokens 14,391 14,390 −1
lines 1,077 1,076 −1

Net line budget ≤ 0 and net tokens ≤ 0, both satisfied. No ceiling was touched — the CEILINGS row is unchanged, which is also the posture all ten preceding flights landed with.

The fork #13729 anticipated is real and I hit it. Priced at their most natural spelling, the corrections above came to +70 bytes against 1 byte of slack. Nothing was funded by re-wrapping. What paid for the diff instead: F5's shorter-and-more-precise wording (−14) and, above all, the deletion of F1's drifted duplicate instruction (−52) — a deletion the correction itself justified, since that duplicate was the wrong copy of a rule the template beside it already owns. Two intended corrections were priced out and are NOT in this diff, recorded here rather than dropped:

  1. The reason clause on F1. The template now gives the correct spelling for both cases but not the mechanism behind it ("closing keywords never cross repositories"). Costed ~+45 bytes. A rule without its reason is a rule that gets re-broken; this is the one I would spend a raised ceiling on first.
  2. "close the cross-repo card by hand" on the state-model row. With Part of and no closer, nothing automatic ever closes a cross-repository backlog card, and the file now says nowhere that a human must. Costed ~+44 bytes.

Both are the maintainer's call, not mine: ceilings are theirs, and re-wrap funding is banned.

Verified, and the boundary readings

Roughly 250 behavioural claims inventoried across the file; 6 distinct false facts over 7 landing sites is ~2.4% distinct / ~2.8% by site, inside the program's 1.5–10% working range and at its lower end — consistent with a process skill whose claims are mostly about its own self-defined protocol rather than about a schema that moves underneath it.

Checked and CLEAN, each against an implementation:

  • Install commands. npx skills add objectstack-ai/objectstack/skills --skill objectstack-pm-dispatch and the --all note both hold: skills/README.md ships the --all spelling, and packages/create-objectstack/CHANGELOG.md records that "the skills CLI's --all implies --skill '*'" — so --skill is a real flag and the /skills subpath is the published-catalog boundary the file correctly keeps.
  • Label vocabulary and colours, against the shipped scripts/pm/ensure-pm-labels.sh. pm:queue / pm:dispatched / needs-user-decision all real; every -d in the file is under GitHub's 100-character description cap; gh label create's -R / -c / -d short flags are the ones that script's own reconcile shim parses. The routing-label example's colour fbca04 is not merely plausible — that script names the published skill as the source it converged on.
  • objectstack-ai/objectstack as the upstream for @objectstack/*, against packages/spec/package.json's repository field. The 17.2.0 pin example matches the workspace's live version.
  • Every backticked identifier in the file (82 unique spans) swept for phantoms: zero. pnpm list, gh pr list --search/--state/--limit, git log --oneline, vitest --maxWorkers, kill -0, pkill -f, expect(...).toThrow() / rejects.toThrow() all real surfaces.
  • Frontmatter untouched, deliberately: check:skill-compatibility's exemption for this file is self-invalidating and re-matches the live text against /No\s+@objectstack\/spec\s+dependency/i. Editing that sentence would have killed the exemption and turned the gate red.
  • Defaults cross-checked against their own tables — batch 3, mode subagent, label pm:queue, routingLabelPrefix repo:, conventionsFile first-existing-of-three, rounds unbounded — all internally consistent across the Quickstart, config table, args table and zero-config paragraph.

Boundary readings recorded rather than "corrected", per the card's instruction that a template may legitimately describe a GENERIC shape:

  • origin/main in the stale-premise git log snippet, while the worktree recipe uses origin/{default_branch}. An inconsistency, but that snippet is plainly a fill-in-the-blanks block (its path argument is a prose placeholder), and main is not a misdescribed mechanism. Costed at +12 bytes; NOT taken, and NOT counted as a falsehood.
  • "An independent session cannot return a message to the PM" (cloud mode). NOT MEASURABLE from this repository: whether a spawned session has a return channel is a property of the reader's harness, and nothing in this tree is an oracle for it.
  • "a lossless channel" / "nothing can be lost between agent and reviewer" for subagent mode. In tension with the same section's "an agent that dies … counts as blocked" and "never treat the absence of a report as success", but the two claims are about different things (the channel vs the agent), and I found no in-repo implementation that settles it. Left alone rather than corrected on judgement.
  • The || true in the label-setup block. ensure-pm-labels.sh documents, from live measurement, that || true also swallows the HTTP 422 an over-long description earns — so the label is silently never created and no rerun repairs it. The published block's "(idempotent)" claim is TRUE as written, and the missing cap warning is an omission, not a falsehood; adding it costs bytes this budget does not have. Recorded for the roster, no card filed.
  • The state-model table says "read and write state only through these signals", and Blocked-by: body lines are not one of its rows — though the file reads them at selection time. An enumeration gap rather than a false row; the fix is additive and the budget forbids it.

Model tier — a half-state the PM should settle

The claim comment on #13868 declares model: opus, on the reasoning that "the fable mandate covers the INTERNAL pm-dispatch SKILL.md, which is fenced out of this card entirely". The machine derivation disagrees, and names THIS file:

Model tier — MANDATORY: claude-fable-5 (derived from the file surface, not recalled).

  • skills/objectstack-pm-dispatch/SKILL.md ⇢ clause ① (2026-08-20 narrowing): the published PM skill carries two enforced copies of the decision frame (check:skill-frame-sync COPIES) and ships verbatim to third-party projects

This work was constructed at opus, i.e. below the derived floor. I am reporting the contradiction rather than picking a side. The compensating control that does hold: check:skill-frame-sync is green and neither copy of the decision frame was edited.

Gates — 14 families derived from the REAL diff, all at HEAD 9e28a1a5f

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack with no path argument (the script takes its own change set from the merge base), exit 0; its header confirms the tree it answered for: objectstack-ai/objectstack at 9e28a1a5f. Harvested with --commands so neither invocation spelling is dropped. Every exit code captured by redirect BEFORE any pipe.

13 of 14 exit 0. The 14th, node scripts/check-test-completeness.mjs, exits 3 and is NOT MEASURED by its own printed verdict — "the local reading for this gate is NOT MEASURED. ⛔ It is not a red" — because it grades a saved turbo run test log only CI produces. Recorded as not-measured, never as green or red.

Verdict lines quoted from the gates themselves:

  • check-skills-token-ratchet: "skills/objectstack-pm-dispatch/SKILL.md is 14390 tokens (ceiling 14391; headroom 1)" and "38 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted."
  • check-skill-frame-sync: "4 copies of the decision frame are structurally isomorphic across 3 files."
  • check-skill-compatibility-version: "11 SKILL.md file(s) reconciled against 78 workspace packages."
  • check-doc-authoring: "48 published skill files clean — no internal issue-id references."
  • check-nul-bytes: "OK (scanned 7608 text file(s) … no raw ASCII control bytes)", plus a manual scan of the edited file over the 0x00–0x1f/0x7f class returning no matches.
  • check-doc-formula-expressions: "22 record-scoped formula example(s) across 427 files / 1451 TS blocks judged clean."

Four gates first exited non-zero on PREREQUISITE NOT MET (yaml / typescript uninstalled, @objectstack/formula and @objectstack/lint unbuilt). Those are refusals, not findings; cleared by pnpm install and two targeted turbo builds, then re-run — all four green. Every heavy run went through bash scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-13868 (waits of 4m39s and 2m51s, spent on unlocked work).

Ablation — the ratchet can go red, proven rather than assumed. From the committed state, 43 bytes of ordinary prose appended to the file; the mutation was confirmed ON DISK by blob hash (3883c5ad…d89f3e1a…, not by an editor's exit code) before anything was measured. Mutated run: exit 1, "is 14401 tokens; the ratchet ceiling is 14391 (over by 10)". Restore leg given the same treatment: git checkout HEAD -- ABSOLUTE_PATH under an EXIT INT TERM trap, then proven by blob hash equality with the HEAD blob AND an empty git diff HEAD; restored run exit 0, headroom 1. So the headroom-1 reading above is a live measurement on a gate that demonstrably fails on growth.

ESLint narrowing, declared with all three legs. (1) The population is read from eslint's OWN config resolution, not from my guess: npx eslint --no-inline-config --format json on the changed path returns "File ignored because no matching configuration was supplied." (2) The count is read from that --format json output: 1 result, 0 errors, 0 lint warnings (1 ignore notice), exit 0. (3) Invariance: eslint.config.mjs supplies no matcher for .md at all — every files: entry names TS/JS extensions — and the config carries no parserOptions.project and no typed rules, so a Markdown-only diff cannot move any judgment on any file eslint does lint. The repo-wide pnpm lint run stays CI's.

skip-changeset: pure skills/** diff, publishing nothing from any package. Verified against the three preceding sweep PRs (#13777, #13808, #13867), each of which carries the same label; the Check Changeset job has no path filter, so the label is the author's step and is attached in the same stroke as this PR.

needs:contract-review is attached to this PR AND to card #13868 in the same stroke, per the clause ② CONTENT limb (批 #12). This seat does not self-clear it.

Generated by Claude Code


Generated by Claude Code

…-dispatch template

Flight 11 of the published-skills factual sweep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@huangyiirene huangyiirene added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review labels Aug 31, 2026 — with Claude

Copy link
Copy Markdown
Collaborator Author

Sanitizer probe for correction F4 — an executed measurement, not a claim about this PR.

F4 rewrites the operating template's rule for GitHub's at-rest body sanitizer. The skill's own recorded measurement and its published RULE disagree about the trigger (the rule says "LT followed by a letter"; the measurement includes a span whose second character is a bang), so the correction needed the platform itself as the oracle rather than either document. Four spans written through the API below, each between unique sentinels; the stored body is read back and the result recorded in the flight report.

P1-START P1-END

P2-START <n> P2-END

P3-START < n> P3-END

P4-START &lt;n&gt; P4-END

P1 is the HTML-comment form the template's rule does not cover and the skill's step-5 text recommended. P2 is the LT-plus-letter form the rule does cover. P3 and P4 are the two escapes the skill prescribes. Whatever is missing between a START/END pair was deleted at rest.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Probe result — read back from the stored comment above. All four cells measured, and F4's correction is settled by the platform rather than by either document.

span written read back at rest verdict
P1 HTML-comment marker P1-START P1-END — content gone, two spaces left DELETED
P2 LT-plus-letter inside a code span P2-START P2-END`` — backticks survive, contents gone DELETED
P3 space after the LT, in a code span present SURVIVES
P4 the two HTML entities, in a code span present SURVIVES

Four things this establishes, each of which the corrections in this PR depend on:

  1. The HTML-comment form is deleted at rest. So the template's published rule — "strips LT followed by a letter" — really is narrower than the mechanism, and an author who trusts it and writes an HTML comment is inside the hazard while believing they are outside it. F4 stands.
  2. The step-5 recommendation was recommending the one token that cannot survive. F3 stands: the cloud-mode report marker had to become plain text.
  3. Backticks are no protection, exactly as the skill's step-4 paragraph already says — P2's code span came back with its delimiters intact and nothing between them. The correction keeps that warning.
  4. Both escapes the skill prescribes work. P3 and P4 survived, so the remediation half of the template's paragraph needed no change and got none.

P3 and P4 come back through the API read path with their angle brackets as numeric entities. That is the known read-side artifact of that path, not corruption — the measurement here is presence, and both are present.

Method note, since it cuts the other way too: this comment and its probe are the reason the flight's PR body and report carry no literal tag-shaped fragments anywhere. Writing the poison deliberately, once, inside sentinels is a measurement; writing it in the load-bearing text is how the skill's own report-collection marker was deleted from the very instruction that carried it.

Generated by Claude Code


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Housekeeping note, found during teardown and reported rather than quietly left: the single commit's message says "correct four false mechanism claims". The final count is six — the message was written mid-sweep and two more corrections landed after it. The PR title, the PR body and the flight report all say six and are correct.

Not repaired, deliberately, and the reasoning is the same discipline the rest of this flight ran on. Amending means a force-push, which is banned outright. A fresh commit instead would move HEAD, and every measurement in the PR body and the report is anchored to 9e28a1a5f by name — the gate union, the ratchet reading, the ablation's blob hashes. Re-pointing all of that to buy one numeral trades a real, checkable anchor for a cosmetic fix, and a report whose sha no longer matches the tree it describes is the exact failure the "run the union at the final commit" rule exists to prevent.

Squash-merging takes its subject from the PR title, so what lands on the default branch reads "six"; the stale numeral survives only in the squashed body. Happy to push a corrected commit and re-run the union at the new head if you would rather have it clean — that is a cheap round, just not one worth spending unasked.

Generated by Claude Code


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review — PASS at head 9e28a1a5f, with a dispatch-tier breach DISCLOSED

Disclosure first, because it is this seat's error, not the dev's. node scripts/pm/dispatch-gates.mjs --tier skills/objectstack-pm-dispatch/SKILL.md, run by this seat after the dev's report flagged it, answers MANDATORY: claude-fable-5 — clause ① (2026-08-20 narrowing): the published PM skill carries two enforced copies of the decision frame and ships verbatim to third-party projects. The dispatch claim declared opus on the reasoning that the fable mandate covered only the internal corpus — wrong on its own terms, and wrong because it cited a MORNING tier reading taken over a different face instead of running --tier on this one, which the claim discipline requires verbatim. Construction therefore ran below the clause-① floor. Compensating controls actually in force: this review runs at CONTRACT_REVIEW_TIER (fuse read this sitting: last_served_model = claude-fable-5) — the same compensation clause ① itself prescribes for opus construction on governance faces; check:skill-frame-sync is green and NEITHER enforced frame copy was edited; every correction is backed by a named in-tree implementation or a probe executed on GitHub itself; and the PR is draft for the maintainer's own merge. On that basis the work is ACCEPTED rather than reconstructed — the diff is nine lines, each independently re-checkable — and the breach goes to the round report, not under it.

Review proper: anchors re-measured this hour — .github/workflows/cross-repo-issue-closer.yml and scripts/check-cross-repo-closer-outcome.mjs both live at origin/main, the latter's header carrying the 1,176-merged-PR zero-qualified-closer measurement, so the headline correction (closing keywords never cross repositories; the qualified Fixes owner/repo#n teaching was false) stands on the platform's own measured behavior. The two sanitizer corrections are proven by probes EXECUTED against GitHub on this very PR (comments 5481019877/5481028991) — the strongest oracle available. The #13729 published half is discharged with the {default_branch} interpolation preserved. The fenced internal corpus was neither edited nor cited as an oracle — fence held. Ablation discipline (blob-hash-proven mutation and restore) exemplary.

Budget fork (dev Q1): Option A accepted — landed at −5 bytes / −1 token, no ceiling touched; Option B (a ~90-byte maintainer-authorized ceiling raise so the rule ships with its reason and the hand-close instruction) is put to the maintainer in the round report as a batch item, not decided here.

needs:contract-review cleared on BOTH carriers. Governed .md: PR remains DRAFT for the maintainer's merge (os-zhuang / hotlong requested).


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills-sweep ⑪: objectstack-pm-dispatch (1,077 lines, 1 file) — behavioral-claim verification with the governance-face fence

2 participants