Create a new content-tools plugin at version 1.0.0 whose first skill, artifact-to-post, turns an HTML artifact or a Markdown file into a draft MDX post for a static site generator (Astro first) — preserving interactivity through a per-block fidelity ladder and guarding the output against MDX's JSX parsing rules.
Artifacts and Markdown files die in the session that made them. This plan creates a content-tools plugin whose first skill converts either into a draft MDX post for an Astro site — keeping interactive blocks interactive by scoping their CSS instead of flattening everything to screenshots.
Read and implement all steps in the plan at docs/plans/add-content-tools-plugin.md — Create the content-tools plugin with an artifact-to-post skill. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-content-tools-plugin.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
More ways to run this plan — goal & workflow prompts, file path
Achieve this goal: Create the content-tools plugin with an artifact-to-post skill. The plan at docs/plans/add-content-tools-plugin.md describes one approach — use it as reference, but optimize for the outcome. Fan out across parallel subagents where that serves the outcome. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-content-tools-plugin.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
Run a workflow to implement the plan at docs/plans/add-content-tools-plugin.md — Create the content-tools plugin with an artifact-to-post skill. Brief subagents with the plan file at docs/plans/add-content-tools-plugin.md. Reserve a final verification phase for the lead agent, not a subagent. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-content-tools-plugin.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
add-content-tools-plugin.html
docs/plans/add-content-tools-plugin.html
docs/plans/add-content-tools-plugin.md
Context
The story behind this plan — what prompted the work and why it matters now.
An upstream plan (build-artifact-to-post-pipeline, written for the 513 Astro
site) proposed a standalone pipeline: a convert.mjs HTML-to-Markdown script, ascreenshot.mjs Playwright script, a node-html-parser dependency, and a
site-local skill. That plan flattened every visual block to a screenshot,
because embedding artifact HTML collided with the site's design tokens.
Three decisions reshape it for this repo.
Scoping replaces screenshotting. Token collision is fixed by wrapping a
block in a container and scoping the artifact's CSS to it — which keeps<details>, <dialog>, and range inputs working. Screenshots become rung 4, a
last resort, not the default.
The converter script is dropped. The upstream plan rewrote the parser's
output by hand in the very next step, so the parser earned nothing. Claude reads
the HTML and writes the MDX directly — no convert.mjs, no screenshot.mjs, nonode-html-parser.
This is content management, not social media. The obvious home wassocial-media-tools, but the config proves otherwise: this skill needs a posts
directory, an output extension, frontmatter keys, a draft flag, and a build
command, while SOCIAL.md holds platforms, tone, and hashtags. Nothing overlaps.content-tools is named for the domain — turning work products into publishable
site content — so it can grow without a rename (renaming a plugin later is a
MAJOR bump and a reinstall for every user). social-media-tools:write-guide is
a candidate to migrate here eventually; that move is a separate breaking change
and is explicitly not in this plan.
Two constraints drive the risk. First, MDX parses Markdown as JSX: bare {/}
and <word…> in unfenced prose compile fine as .md and hard-fail an MDX build
— and the hazard is introduced by the prose rewrite, so the safety pass must
run after it. Second, this repo has no Astro install, so the smoke test cannot
validate a real MDX build; the authoritative gate is a manual run against a real
site.
Out of scope for v1: published claude.ai artifact URLs. WebFetch fails on
authenticated/private URLs, so the upstream plan's "WebFetch with session login"
path cannot work from a skill. Sources are local .html/.md paths or pasted
HTML — which is exactly what social-media-tools:save-artifact already produces.
Files that change
Every file this plan touches, and what happens to each one.
kit/plugins/content-tools/.claude-plugin/plugin.jsonnew manifest,nameonly; version lives in marketplace.jsonkit/plugins/content-tools/skills/artifact-to-post/SKILL.mdnew the skill: source branch, ladder, MDX-safety pass, verify, publish gate- kit/plugins/content-tools/references/
mdx-safety.mdnew the fidelity ladder and the MDX/JSX escaping rules, loaded on demandcontent-config.mdnew the CONTENT.md config schema and prerequisite checks
- kit/plugins/content-tools/
README.mdnew plugin overviewCHANGELOG.mdnew v1.0.0 entry
.claude-plugin/marketplace.jsonmodified register content-tools at 1.0.0tests/fixtures/artifact-to-post/sample-artifact.htmlnew fixture carrying every ladder rung and every MDX hazardtests/plugins/test-artifact-to-post.shnew smoke test pinning the skill contract and the escaping rulesCLAUDE.mdmodified add the content-tools row to the plugin table
Steps
The step-by-step work, in order — each step says what to do, why it matters, and how to check it worked.
kit/plugins/content-tools/.claude-plugin/plugin.json containing name: content-tools and a description — no version key, since a relative-path plugin's version lives only in marketplace.json and a plugin.json version silently overrides it. Add README.md stating the plugin's charter (turning work products into publishable site content) and CHANGELOG.md with a v1.0.0 entry. Register the plugin in .claude-plugin/marketplace.json with version: "1.0.0", a relative source path, category: "documentation", and specific tags (mdx, astro, static-site, content-publishing — never generic terms like "tool").
tests/plugins/test-no-orphan-plugin-dirs.sh, and registration is what makes it installable.python3 -m json.tool .claude-plugin/marketplace.json succeeds, bash tests/plugins/test-no-orphan-plugin-dirs.sh exits 0, and plugin.json contains no version key.kit/plugins/content-tools/references/mdx-safety.md — the technical core, kept out of SKILL.md so the skill body stays scannable (the social-media-tools/references/platforms.md precedent). Two sections. (a) The fidelity ladder, applied per content block, highest rung that holds: rung 1 native Markdown (headings, prose, lists, tables, fenced code) — the majority of any document; rung 2 scoped inline HTML for interactivity needing no JS (<details>/<summary>, <dialog>, <input type="range">, native tables), wrapped in a single container with the artifact's CSS prefixed to that container's selector — this is the design-token fix, and it is what preserves the interaction; rung 3 scoped HTML plus the artifact's own inline <script> (charts, calculators); rung 4 screenshot into the configured images directory plus a link to the live artifact — last resort only, for blocks that genuinely cannot be ported. Record that rung 3 is version-sensitive: whether <script> inside MDX is bundled depends on the target site's Astro/MDX version, so the site build is the authority, never an assumption. (b) The MDX-safety rules: in unfenced prose, escape bare { and } and neutralize <word…> sequences — Array<string>, { id }, <T>, and autolinks like <https://…> are the canonical failure cases; fenced blocks and inline code spans are safe and must be left untouched. For HTML emitted at rungs 2–3, which lands in a JSX parser: class → className, for → htmlFor, all void tags self-closed, style as an object rather than a string, and comments converted from <!-- -->.
Array<string>, { id }, and class→className explicitly.kit/plugins/content-tools/references/content-config.md defining a CONTENT.md project config, mirroring the SOCIAL.md convention from social-media-tools:share-init but with its own schema — nothing site-specific may be hardcoded, since the skill ships to arbitrary repos. Fields: posts output directory, output extension (.md or .mdx), frontmatter field names (title, description, date, author), the draft/publish flag name and its unpublished value, images output directory, dev preview URL pattern, build/verify command, and interactivity_ceiling (caps the ladder — a site that forbids inline scripts caps at rung 2). Specify that when no config exists the skill asks once and offers to write one. Document the two prerequisite checks the skill performs against the target repo and never auto-installs: @astrojs/mdx present in package.json, and the content-collection glob including .mdx; on a missing prerequisite the skill reports the exact fix and stops.
src/content/posts/ or publish: key makes the skill work on one site and silently corrupt every other.kit/plugins/content-tools/skills/artifact-to-post/SKILL.md with frontmatter matching repo conventions — name: artifact-to-post, a three-part description ≤200 chars (short label + capability + trigger phrase), and allowed-tools: AskUserQuestion, Read, Write, Edit, Bash, Glob, Grep, Skill, ToolSearch, ExitPlanMode, SendUserFile. Include the Step 0 ExitPlanMode self-bootstrap used by sibling skills across the repo. Phases: 0 locate plugin assets; 1 resolve the source and branch on type — a .md source skips extraction entirely and goes straight to frontmatter synthesis plus the safety pass, while .html or pasted HTML takes the full extraction path, and a claude.ai URL is refused with a one-line pointer to social-media-tools:save-artifact; 2 invoke the social-media-tools:security-scrub skill as a blocking gate before anything is written, degrading to an explicit warning-and-stop if that plugin is not installed rather than silently skipping the scan; 3 read CONTENT.md and run the prerequisite checks; 4 extract to Markdown, classifying each block against the ladder in references/mdx-safety.md and capping at the configured interactivity ceiling; 5 human prose rewrite; 6 the MDX-safety pass — explicitly ordered after the rewrite, with a one-line note saying why, so a later editor does not "tidy" it earlier; 7 capture rung-4 screenshots by reusing the serve-locally-then-Playwright pattern documented in social-media-tools/skills/share-code/SKILL.md (no new script); 8 write the post with synthesized frontmatter and the draft flag set to unpublished, plus a footer link to the source artifact; 9 verification (step 7 below); 10 the publish gate — offer publish, display slug and URL, default to no, preserve the unpublished flag when declined.
python3 tests/plugins/measure_description_budget.py reports ≤200 chars, and the body orders the rewrite before the safety pass.tests/fixtures/artifact-to-post/sample-artifact.html — a small artifact exercising every rung and every hazard: a heading and prose (rung 1), a <details> block with its own scoped-candidate CSS (rung 2), a block with an inline <script> (rung 3), a canvas-style block that cannot be ported (rung 4), and prose containing Array<string>, { id }, <T>, and a bare autolink — plus a fenced code block containing the same constructs, which must survive untouched.
grep finds each hazard construct and all four rung markers.tests/plugins/test-artifact-to-post.sh following existing tests/plugins/test-*.sh conventions. Assert: the plugin manifest exists with no version key and is registered in marketplace.json; the skill file exists with valid frontmatter and a ≤200-char description; the body orders the prose rewrite before the MDX-safety pass; the body reads config values rather than hardcoding src/content/posts, publish:, or astro build; the body refuses claude.ai URLs and points at save-artifact; the security scrub runs before any write and fails loudly when unavailable; the reference documents all four rungs and the class→className rule; and the fixture carries every hazard. Include a runnable guard that scans the fixture's prose regions for unescaped {, }, and <word…> and confirms the fenced region is excluded from that scan — the smallest check that fails if the escaping contract drifts. State plainly in a comment that this repo has no Astro install, so the authoritative MDX build check is step 7's manual validation, not this test.
bash tests/plugins/test-artifact-to-post.sh exits 0, and it fails when the class→className rule is deleted from the reference.claude --plugin-dir ./kit/plugins/content-tools, run the skill on a real artifact, and confirm the draft post builds. The site's own build command is the authoritative gate — an MDX escaping bug is invisible to type-check and fatal to the build. Confirm: the build passes; the post loads at the configured dev preview URL; rung-2 and rung-3 blocks are still interactive in the browser; no style or script leaks outside the scoped container; images resolve; the draft is absent from paginated lists but reachable by direct URL; and declining the publish offer leaves the unpublished flag intact. Then convert a plain Markdown file and confirm it produces the same valid draft without touching the extraction path. Record the observed rung-3 behavior for the target Astro version back into references/mdx-safety.md.
content-tools row to the plugin table in CLAUDE.md describing the plugin and its single skill, and update the plugin count in the surrounding prose.
CLAUDE.md is the map every future session reads first; an unlisted plugin is an invisible one.BASE_REF=main node scripts/check-plugin-versions.mjs passes, and CLAUDE.md lists content-tools with an accurate plugin count.Tests
The tests that prove the change does what it promises.
kit/plugins/, which are the shipped product of this repo{/}/<word…> while the fenced region is left untouched; Run: bash tests/plugins/test-artifact-to-post.shDefinition of done
The plan counts as done when every statement below is true — check each one off as you verify it.
Final check
One last pass to confirm the whole change works end to end.
Run bash tests/plugins/test-artifact-to-post.sh,bash tests/plugins/test-no-orphan-plugin-dirs.sh, andBASE_REF=main node scripts/check-plugin-versions.mjs — all exit 0. Then load
the plugin locally (claude --plugin-dir ./kit/plugins/content-tools) and
convert a real artifact against the 513 Astro site: the site's build command
must exit 0 (this is the authoritative MDX gate — type-check cannot see an
escaping bug), the post must load at the configured preview URL with its<details> and script-backed blocks still responding to input, no artifact CSS
may affect anything outside the scoped container, the draft must be absent from
paginated lists yet reachable by direct URL, and declining the publish offer
must leave the post unpublished. Finally, install the plugin from the
marketplace (/plugin install content-tools@agentics-kit) and confirm the skill
activates on a natural request like "turn this artifact into a blog post".
Wrapping up
Three gates that must all pass before this plan is marked completed.
Completion Report
No items to report — all requirements met.