Skip to content

feat(skills): top-level plannotator knowledge skill with a CLI freshness guard - #1377

Merged
backnotprop merged 3 commits into
mainfrom
feat/plannotator-knowledge-skill
Aug 22, 2026
Merged

feat(skills): top-level plannotator knowledge skill with a CLI freshness guard#1377
backnotprop merged 3 commits into
mainfrom
feat/plannotator-knowledge-skill

Conversation

@backnotprop

@backnotprop backnotprop commented Aug 22, 2026

Copy link
Copy Markdown
Owner

TLDR

Adds a top-level plannotator knowledge 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, --static vs --app on localhost URLs, --gate --json machine-readable outcomes and the exit-code table for strict gates, when an agent-launched annotate-last needs turn exclusion), the env vars an agent may legitimately set, and a short do-not list (never parse UI HTML, never use --hook outside real hook contexts). The frontmatter description triggers on generic Plannotator intent while deferring to the more specific plannotator-* action skills.

Freshness guard

The failure mode for a reference skill is silent drift from the CLI. apps/hook/server/plannotator-skill-reference.test.ts parses 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 normal bun test pass, 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_INSTALL semantics 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 ~/.agents but 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 added plannotator to uninstall.ts's KIRO_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 only commands/*.md, so npm-plugin users never received the skill. The package now ships it: a build:skill step copies apps/skills/core/plannotator into the package at build time (the same way the HTML assets are copied), skills joins files, and postinstall creates ${XDG_CONFIG_HOME:-$HOME/.config}/opencode/skills/plannotator/ and copies SKILL.md there. That is a path OpenCode really scans: Global.Path.config is xdgConfig/opencode and the skill scan pattern under it is {skill,skills}/**/SKILL.md. uninstall.ts sweeps the new path, skills only, so a user's own opencode/commands/plannotator.md stays out of scope.

3. Pi npm. Pi's manifest supports skills declaratively, and non-glob pi.skills entries resolve relative to the package root. vendor.sh now copies the skill to apps/pi-extension/skills/plannotator/, package.json declares "skills": ["skills/plannotator/SKILL.md"] under the pi key, and skills/ joins files.

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 // @generated header the vendored .ts files use, because a SKILL.md must open with its YAML frontmatter on line 1 and any prepended comment makes it unparseable to every loader that reads it. This matches how plannotator.html and call-flow-runtime/ are already handled.

4. /llms.txt path. llms.txt.ts resolved the skill through process.cwd(), which breaks under any invocation that is not --cwd apps/marketing. The review suggested new URL(..., import.meta.url); that turns out not to work here either, because Vite rewrites import.meta.url to 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 an apps/apps/skills/... ENOENT. Used Vite's ?raw instead, which the bundler resolves relative to the source file. That is the same source-relative reach src/lib/shortcutReference.ts already uses for packages/*, it inlines at build time with no runtime filesystem access, and it fails the build if the skill ever moves. Verified dist/llms.txt is emitted, byte-identical, from both bun run build:marketing at the repo root and bun 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 buildLlmsTxt only. SKILL.md is unchanged.

5. Uninstall "only it". KNOWLEDGE_SKILLS exists as a list separate from CORE_SKILLS precisely so the bare name plannotator cannot leak into LEGACY_COMMAND_NAMES or STALE_CODEX_SKILLS and 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.27 skills/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 folding KNOWLEDGE_SKILLS into the command and Codex lists fails the survival assertion. Also took the one-word fix: cleanupStaleSkillLayout for the core/ directory now receives [...CORE_SKILLS, ...KNOWLEDGE_SKILLS].

6. Origin list. Added oh-my-pi to SKILL.md's PLANNOTATOR_ORIGIN row. The same row in AGENTS.md turned 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 imports AGENT_CONFIG and 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: dropping oh-my-pi fails 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, files must include it, and the install target must be right (pi.skills for 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-run ships skills/plannotator/SKILL.md (13.68KB) and skills/plannotator/agents/openai.yaml in both @plannotator/opencode and @plannotator/pi-extension; both copies diff clean against the source. bun run build:marketing emits dist/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_SKILLS isolation, and three separate drifts against the freshness guard.

AI-assisted (Claude) under maintainer direction.

…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.
@backnotprop
backnotprop merged commit db86d38 into main Aug 22, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant