feat(skills): top-level plannotator knowledge skill with a CLI freshness guard - #1377
Merged
Conversation
…eshness guard A new model-invocable core skill (apps/skills/core/plannotator) that teaches an agent the whole CLI surface: decision guide, per-command reference with flags and exit codes, env vars, the external-annotations API, and a do-not list. The existing plannotator-* core skills stay lightweight action stubs. A freshness test (apps/hook/server/plannotator-skill-reference.test.ts) parses the skill's documented subcommands and flags and diffs them against cli.ts usage text plus the CLI arg-parsing sources, in both directions, so the reference cannot drift from the real CLI without failing the suite. Installers copy the single-sourced core body into ~/.claude/skills and ~/.agents/skills on all three platforms; uninstall removes it from both scopes. The skill ships model-invocable as a documented exception to the locked-by-default rule, asserted both ways in install.test.ts.
Single-sourced at build time from apps/skills/core/plannotator/SKILL.md per the llmstxt.org spec (H1, blockquote, detail sections, Docs link list), so the CLI freshness guard transitively keeps llms.txt current.
…skill The knowledge skill reached Claude Code and ~/.agents but was missing from three install paths. Six fixes from the install-reach review of #1377. Kiro: the installer's Kiro leg copied only the two action skills, so Kiro users got launchers and no CLI reference. One copy line per installer, and "plannotator" joins uninstall.ts's KIRO_SKILLS. OpenCode npm: @plannotator/opencode's postinstall copied only commands/*.md. The package now ships the skill (copied at build time like the HTML assets, gitignored so the shipped copy cannot drift) and postinstall places it under ${XDG_CONFIG_HOME:-$HOME/.config}/opencode/skills/plannotator/, which is a path OpenCode really scans ({skill,skills}/**/SKILL.md under xdgConfig/ opencode). Uninstall sweeps it, skills only, so a user's own opencode/commands/plannotator.md stays out of scope. Pi npm: vendor.sh copies the skill to apps/pi-extension/skills/plannotator/ and package.json declares it under pi.skills, which Pi resolves relative to the package root. Neither vendored copy carries the // @generated header the .ts files use: a SKILL.md must open with its frontmatter on line 1. llms.txt: the endpoint resolved the skill through process.cwd(), which breaks under any invocation but --cwd apps/marketing. new URL(import.meta.url) does not fix it either, because Vite rewrites import.meta.url to the emitted SSR chunk's location. Inlined with Vite's ?raw, resolved by the bundler relative to the source file. Also drops the summary paragraph the required blockquote already carries; SKILL.md itself is unchanged. Uninstall: KNOWLEDGE_SKILLS is a separate list from CORE_SKILLS precisely so the bare name "plannotator" cannot leak into LEGACY_COMMAND_NAMES or STALE_CODEX_SKILLS and delete a user's own files. Nothing tested that; now a test proves the five installed scopes are removed and commands/plannotator.md (Claude and OpenCode) plus ~/.codex/skills/plannotator survive. Also cleanupStaleSkillLayout now knows KNOWLEDGE_SKILLS. Origins: oh-my-pi (#1373) was missing from SKILL.md's PLANNOTATOR_ORIGIN row. The guard now imports AGENT_CONFIG and asserts the row names every key and invents none, and its header comment is narrowed to what it actually proves: bidirectional for subcommands and origins, one-directional for flags. AI-assisted (Claude) under maintainer direction.
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
Adds a top-level
plannotatorknowledge skill: one reference an agent invokes when a user says "use plannotator to do X". The three existing core skills are action stubs that run the binary; this is the layer that teaches an agent what Plannotator is and how to drive the whole CLI, since none of it is in any model's training data.What it contains
apps/skills/core/plannotator/SKILL.md(14 sections): what Plannotator is in three sentences, an intent-to-command decision guide (plan review, code review, annotate a file / URL / folder / running local app, annotate the last message, archive, guide exports), a per-command flag reference with the gotchas that matter to agents (background launch for long reviews,--staticvs--appon localhost URLs,--gate --jsonmachine-readable outcomes and the exit-code table for strict gates, when an agent-launchedannotate-lastneeds turn exclusion), the env vars an agent may legitimately set, and a short do-not list (never parse UI HTML, never use--hookoutside real hook contexts). The frontmatter description triggers on generic Plannotator intent while deferring to the more specificplannotator-*action skills.Freshness guard
The failure mode for a reference skill is silent drift from the CLI.
apps/hook/server/plannotator-skill-reference.test.tsparses SKILL.md's documented subcommands and flags and asserts each exists in the CLI usage source, and that no CLI subcommand is missing from the skill. Verified both directions: renaming one documented flag in SKILL.md fails exactly the guard test; restoring it goes green. Runs in the normalbun testpass, so CI blocks drift.Install wiring
All three install scripts ship the new skill directory through the existing core-skills sparse checkout; the uninstall sweep knows the new directory;
PLANNOTATOR_SKIP_SKILLS_INSTALLsemantics unchanged (skip means do-not-write, never remove). Install and uninstall tests extended.Verification
Targeted suites (skill guard, install, uninstall, CLI): 191 pass, 0 fail. Typecheck clean. AGENTS.md skills tree updated.
/llms.txt on plannotator.ai
The same skill also becomes https://plannotator.ai/llms.txt (llmstxt.org format): an Astro build-time endpoint reads the skill file, strips the frontmatter, and emits the spec shape (H1, blockquote summary, the full CLI reference as detail sections, and a Docs link list). Because it is generated from the same source file, the CLI freshness guard transitively keeps llms.txt current. Ships with the normal marketing deploy on merge to main.
AI-assisted (Claude) under maintainer direction.
Fix round: install reach
A review of this PR found that the knowledge skill reached Claude Code and
~/.agentsbut was missing from three install paths, plus four smaller issues. All six are fixed here.1. Kiro. The Kiro leg copied only the two action skills, so Kiro users got launchers and no CLI reference. Added one copy line per installer (
install.sh,install.ps1,install.cmd) into~/.kiro/skills/, and addedplannotatortouninstall.ts'sKIRO_SKILLS. A new cross-script parity test asserts the copy in all three dialects; deleting the line from any one script fails it (verified for each).2. OpenCode npm.
@plannotator/opencode's postinstall copied onlycommands/*.md, so npm-plugin users never received the skill. The package now ships it: abuild:skillstep copiesapps/skills/core/plannotatorinto the package at build time (the same way the HTML assets are copied),skillsjoinsfiles, and postinstall creates${XDG_CONFIG_HOME:-$HOME/.config}/opencode/skills/plannotator/and copiesSKILL.mdthere. That is a path OpenCode really scans:Global.Path.configisxdgConfig/opencodeand the skill scan pattern under it is{skill,skills}/**/SKILL.md.uninstall.tssweeps the new path, skills only, so a user's ownopencode/commands/plannotator.mdstays out of scope.3. Pi npm. Pi's manifest supports skills declaratively, and non-glob
pi.skillsentries resolve relative to the package root.vendor.shnow copies the skill toapps/pi-extension/skills/plannotator/,package.jsondeclares"skills": ["skills/plannotator/SKILL.md"]under thepikey, andskills/joinsfiles.Both copies are byte-identical to the source and gitignored, so the shipped copy cannot drift: the only copy that can exist is the one the build makes. They deliberately do not carry the
// @generatedheader the vendored.tsfiles use, because aSKILL.mdmust open with its YAML frontmatter on line 1 and any prepended comment makes it unparseable to every loader that reads it. This matches howplannotator.htmlandcall-flow-runtime/are already handled.4. /llms.txt path.
llms.txt.tsresolved the skill throughprocess.cwd(), which breaks under any invocation that is not--cwd apps/marketing. The review suggestednew URL(..., import.meta.url); that turns out not to work here either, because Vite rewritesimport.meta.urlto the emitted SSR chunk's own location (dist/.prerender/chunks/) rather than the source file, so the relative depth does not survive bundling. It fails loudly with anapps/apps/skills/...ENOENT. Used Vite's?rawinstead, which the bundler resolves relative to the source file. That is the same source-relative reachsrc/lib/shortcutReference.tsalready uses forpackages/*, it inlines at build time with no runtime filesystem access, and it fails the build if the skill ever moves. Verifieddist/llms.txtis emitted, byte-identical, from bothbun run build:marketingat the repo root andbun run --cwd apps/marketing build.Folded in the duplicate-summary strip: the skill's opening paragraph repeats the blockquote summary that llmstxt.org requires, and "This skill is the knowledge layer." orients an agent that loaded a skill, which a reader of llms.txt did not. Both are dropped in
buildLlmsTxtonly.SKILL.mdis unchanged.5. Uninstall "only it".
KNOWLEDGE_SKILLSexists as a list separate fromCORE_SKILLSprecisely so the bare nameplannotatorcannot leak intoLEGACY_COMMAND_NAMESorSTALE_CODEX_SKILLSand start deleting files a user owns. Nothing tested that. Added a test that creates the skill in all five scopes an install writes (Claude,~/.agents, Kiro, OpenCode config, and the pre-0.27skills/core/layout) alongside~/.claude/commands/plannotator.md,~/.config/opencode/commands/plannotator.md, and~/.codex/skills/plannotator, then proves the first five are removed and the last three survive with contents intact. Verified by mutation: reverting each of the three removal fixes fails the matching removal assertion, and foldingKNOWLEDGE_SKILLSinto the command and Codex lists fails the survival assertion. Also took the one-word fix:cleanupStaleSkillLayoutfor thecore/directory now receives[...CORE_SKILLS, ...KNOWLEDGE_SKILLS].6. Origin list. Added
oh-my-pitoSKILL.md'sPLANNOTATOR_ORIGINrow. The same row inAGENTS.mdturned out to be current already, fixed by #1373 in this branch's base, so no change was needed there. Strengthened the freshness guard with both cheap additions: it now importsAGENT_CONFIGand asserts the row names every config key and invents none, and the header comment is narrowed to what the guard actually proves. Subcommands are bidirectional, flags are one-directional (a new CLI flag the skill never mentions still passes, and making that bidirectional needs per-subcommand flag scoping, left as follow-up), origins are bidirectional on values. Re-proved the guard on drift: droppingoh-my-pifails it, adding a fictional origin fails it, and renaming a documented flag still fails the pre-existing leg.Both packages also gained a shipping guard, since three things have to line up in each or the skill silently stops shipping: the build or vendor step must make the copy,
filesmust include it, and the install target must be right (pi.skillsfor Pi, the postinstall destination for OpenCode).Verification
Typecheck clean. Targeted suites green: uninstall 35, install 123, freshness guard 5, OpenCode package boundary 2, Pi startup 6. Full suite 3807 pass / 12 fail, and those 12 are the pre-existing semantic-diff, GitButler, and workspace failures in this environment: the identical 12 fail at the parent commit, and none of them touch anything in this diff.
bun pm pack --dry-runshipsskills/plannotator/SKILL.md(13.68KB) andskills/plannotator/agents/openai.yamlin both@plannotator/opencodeand@plannotator/pi-extension; both copies diff clean against the source.bun run build:marketingemitsdist/llms.txt, byte-identical from either invocation.Every new assertion was proved non-vacuous by reverting the fix it guards: the three installer copy lines individually, the three uninstall removal legs individually, the
KNOWLEDGE_SKILLSisolation, and three separate drifts against the freshness guard.AI-assisted (Claude) under maintainer direction.