Repository navigation
v1.3.0
Artifacts gain a dynamic mode for the cases where a prescribed section skeleton makes the document worse, and a written spine covering what an artifact must never do whatever shape it takes. Rule 9 (no-JS robustness) becomes verifiable instead of assumed. Hook automation is one idempotent command. Three contract defects in the shipped examples are fixed, and the summary marker stops naming a job title.
Added
artifact-mode="dynamic". The nine kinds pre-decide a section skeleton before anyone knows what the artifact needs to say, which is the right trade most of the time and the wrong one when the shape is the argument (a findings taxonomy, a four-way exploration, a line-by-line port map). Adding<meta name="artifact-mode" content="dynamic">beside the kind, or scaffolding withnew <kind> "<title>" --mode dynamic, stands down exactly three rules and nothing else:required-section(off),read-map(off), andnav-anchors(BLOCK to WARN). Mode is a separate field from kind on purpose, so the kind still says what the document is for and drives the gallery and the filename, and aplancan go dynamic when the skeleton stops fitting. Every other rule is untouched, because the rule set was always two populations wearing one coat:required-sectionsays a plan looks like this,viewport-metasays a human on a phone can read this, and only the first is about shape. The mode-selection rule is one question, does the reader benefit from this looking like the last one, with the kind as the tie-break, since a kind that fits slightly badly costs a reader less than an invented structure that fits nothing.references/artifact-spine.md. The rules say what an artifact must contain; nothing said what it must never do, and a mode defined only by what it removes drifts straight to a model's defaults. Three layers: the mechanical floor (plus the requirements that cannot be linted, colour never carrying meaning alone, hover always paired with focus and touch, and a scroll container always reachable by keyboard), the spine an artifact never trades away in any mode (no coined framework names, never name or segment a reader, no engineered keystone, hedges stay, every claim earned, credibility from this artifact's own evidence, avoid the first person, never stage common knowledge as a reveal, inherit the tokens), and prose discipline (after every list one sentence on what it implies; name the concrete failure not the category; small precise numbers over adjectives; one canonical noun per thing; no participle analysis tails; vary the block rhythm). Dynamic mode is free in structure and locked in style. The floor table states what each check actually proves next to what it aims at, because every one of them is a marker check.render --no-js. Rule 9 protects readers in JavaScript-disabled previews (iOS Quick Look, Android file previews, email) and was the one rule the harness could not check:js-content-fallbackonly greps for a<noscript>, so it cannot tell a control from content and cannot read whether the fallback matches what the script would build.render --no-jsserves the page as a JS-disabled browser treats it and screenshots the result, so you can look. No Chrome flag does this, which is why it is a rewrite:--disable-javascriptis accepted and silently ignored (its screenshot is byte-identical both to a run with JS on and to a run with a deliberately invented flag), and--blink-settings=scriptEnabled=falsemakes headless Chrome emit no output at all.simulate_no_js()reproduces the only two things disabling JS changes, and says so plainly rather than claiming to be the browser's own setting.- Three dynamic examples that share no structure on purpose (
examples/dynamic-*.html), across three different kinds, so no single shape reads as the template a dynamic artifact should copy. A test fails if any two of them converge on a shared kind or heading set. - Global hook activation.
python3 <skill-dir>/activate_hooks.pymerges the advisory and autoindex hooks into Claude Code, Codex, Cursor, and Windsurf settings without replacing unrelated configuration. - Windsurf support. A thin adapter translates Cascade's write and command hook payloads into the existing agent-neutral hook input.
- Discoverable wiring metadata. The shipped hooks include their event and matcher headers for downstream installers.
- Implementation deviation ledger (
references/workflow-integrations.md). The during-the-build log that had no home:planis written before the work andreviewhappens after it, so everything the code teaches you mid-build evaporates into agent scrollback and the next attempt rediscovers it. Four fixed fields per entry, what the plan said, what the code revealed, the conservative choice taken, what to revisit, closing with a mandatory fold-forward block that turns the surprises into three instructions for the next attempt. Uses the existingstatuskind; no new artifact kind and no validator rule. - Reader-response compiler (
references/workflow-integrations.md). Most artifacts end with the reader having read; this one ends with them having replied, as a block they paste straight back. The no-JS shape is prescribed rather than suggested, because the obvious implementation renders as dead controls and an empty box in exactly the previews Rule 9 exists to protect. - Review order vs execution order (
references/patterns.md). Aplansorted by likelihood-of-tweaking rather than build order, so every judgment call is up front and the mechanical work collapses into<details>. Includes flagging the weakest part of your own plan, and pre-writing the likeliest pushbacks so the reader can send one in a click. - Non-goals (
references/patterns.md). "What we deliberately did not do", written as the omission plus its reason. Pre-empts the whole class of review comment that begins "why didn't you also...". Distinguished from Open questions: a non-goal is settled, an open question needs an owner and a date. - Linked source-to-target correspondence (
references/patterns.md). For ports and migrations: corresponding regions in the source and target excerpts share a token and a number, with the trap in a numbered margin note, plus a preserved / deliberately changed / dropped ledger. The numbered correspondence must carry the meaning with no pointer and no JS. - Pre-merge self-check (
references/patterns.md). A handful of questions attached to a review artifact, answerable only by someone who understood the change. Native<details>, so it works with JavaScript off. Explicitly not a gate and never scored: answering questions about a document is not evidence the change is safe, so the honest claim is the narrow one, that a reviewer who cannot answer these has not read what they are about to approve.
Changed
data-audience="pm"is nowdata-summary="true", and thepm-summaryrule issummary-first. The contract bans labelling reading depth by job title, and the nine canonical read maps were relabelled for that reason. The summary marker was the same segmentation one layer down in the markup, and it was the one rule that blocked unless you wrote it. The pre-rename attribute is still accepted so already-shipped artifacts keep validating, and retired rule IDs still answer to suppression comments, so nothing breaks on upgrade. The.pm-summaryclass is now.lead-summarythroughout, since leaving it behind means the next author copies the job title forward.
Fixed
js-content-fallbacknow detects DOM insertion, not one phrase. The rule matched only.innerHTML =, so content assembled withcreateElement+textContent+appendChildpassed silently, including in this skill's ownprototype-canonical.html, which shipped an empty<ul>filled by JS and no<noscript>, rendering blank in iOS Quick Look. Detection now coversinnerHTML/outerHTMLassignment,insertAdjacentHTML,replaceChildren,appendChild/append/prepend,insertBefore, anddocument.write, and deliberately not a barecreateElement(a detached node puts nothing on the page), so the JS-injected copy-button pattern stays clean. Also fixes a pre-existing false positive:.innerHTML ===(a read) matched as a write.prototype-canonical.htmlnever had a working static floor. It hid its inert slider and copy button through a<noscript>stylesheet rule, but those controls carry an inlinestyle="display:flex", and an inline declaration beats a stylesheet rule. A reader with JavaScript off saw a live-looking slider driving nothing, and it passedjs-content-fallbackthroughout because a<noscript>element was present. Fixed withdisplay: none !important; the trap is documented in SKILL.md and the spine, because anyone hiding an inline-styled control will hit it. Found byrender --no-json its first real use.- Renaming a rule broke every suppression written against its old name.
_addmatches the emitted rule ID exactly, so an installed workspace carrying<!-- human-html-disable: pm-summary -->went from passing to a blockingsummary-firsterror, with nothing in the message explaining why. Retired IDs now keep answering through_RULE_ID_ALIASES, guarded by a test that also asserts the alias does not silence the rule for artifacts that never suppressed it. Any future rename adds its alias there. - Reading guides in all nine canonical examples were role-based, which the contract forbids. SKILL.md requires depth-based guides and explicitly bans job-title labels, yet every example shipped
Exec (2 min)/PM (5 min)/Engineer (15 min). Because SKILL.md tells agents to read the canonical example before writing an artifact of that kind, the example was quietly overriding the rule. Relabelled toQuick read/Standard/Full read, keeping the time budgets. - Mermaid label clipping was fixed in the scaffold but never backfilled to the examples. All nine canonical examples carried live mermaid with neither half of the fix, so every shipped diagram clipped its node labels at the right edge. Both halves are now present in all nine.
- A scroll container with no tab stop cannot be scrolled by keyboard. Two dynamic examples wrapped a wide table in
overflow-x: autowith norole, no label, and notabindex, so a keyboard reader could not reach the clipped columns at all. Fixed in both, and added to the spine as a floor requirement, since the standard wide-table treatment produces this trap by default. - Sixteen tab stops that did nothing.
dynamic-port-correspondence.htmlputtabindex="0"on every<mark>region and on the numbered link inside it, doubling the tab order to 32 stops for 8 pairs with every second stop inert on activation. The highlight listens withfocusin, which bubbles, so focus on the inner link already reaches the region's listener; verified in a browser that all 16 regions still light both halves with the attribute gone. The spine states the rule now, because "make it focusable" reads as "addtabindex" and that is wrong whenever the region already contains a link, a<summary>, or a control. artifact-modefailed silently in two ways. An unrecognised value fell through to the full rule set with no signal, sodyanmicwas indistinguishable from a validator that ignored the field; and the same field written as an element attribute rather than a meta tag was ignored entirely. Both now WARN under anartifact-moderule, naming the value or the element, and stay held to the full rule set.- The spine overstated what
checkestablishes. Its table read as though the checks proved the properties in the left column:summary-firstfinds the summary section anywhere in the page, so one buried above the footer passes;js-content-fallbackis satisfied by any<noscript>, including an empty one about something else;table-responsivecannot tell whether a scroll container is keyboard reachable. The table now carries a third column for what each check actually proves, and the layer is described as marked rather than validated. - The spine cited an unearned population statistic, in the file whose own rule bans them. Replaced with the two mechanisms that actually defeat hue alone plus the WCAG clause. Also narrowed "never address a reader" to "never segment a reader": the ban is on sorting readers into groups, never on the second person.
- Documentation that contradicted the code. SKILL.md claimed four rules always block after
nav-anchorsbecame mode-dependent; the post-initblurb promised a per-kind section skeleton for everynew, which dynamic mode does not produce; Rule 1 read as though the three-bullet block were the only compliant opener whilepatterns.mddocuments BLUF as an alternative (now named as equally compliant in both); and the reading-guide snippet linked to#lead-summary, an id nothing emitted, so the scaffold carries it now. - Three factual defects in the dynamic examples.
dynamic-port-correspondence.htmlclaimed the TypeScript port "clamps downward" an over-capacity bucket; it clamps the headroom, so a downgrade from 600 to 100 with 600 tokens held leaves 600 in place and the tier keeps serving the old allowance until spent.dynamic-design-directions.htmlrecorded a 502 window ending 09:18:00 and then evidenced nine 502 responses after it, and its lead implied direction 3 had the lowest outage cost while its own table gave direction 4 fewer losses.dynamic-blindspot-pass.htmlgave a rebuild duration as both "40 minutes" and "5h40m", and miscounted an archive-to-flip interval by a day. - A
<noscript>mentioned inside an HTML comment corrupted the--no-jsrewrite. The unwrap regex treated the mention as a real opening tag, so the non-greedy match ran to the real closing tag and deleted the page between them. Comments are stripped first, guarded by a test that fails against the pre-fix transform. tests/grew from nothing to 32 tests, covering the DOM-insertion regex, the mode branch in both directions, the suppression alias, the no-JS rewrite, and contract properties across all twelve shipped examples. Each new test fails against the tree it was written for.