diff --git a/README.md b/README.md index 056bf62..ea05c32 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,16 @@ npx -y @simpledoc/simpledoc migrate This will start a step-by-step wizard to migrate existing documentation to SimpleDoc and add instructions to `AGENTS.md` to follow it. +### Agent skill bundle + +SimpleDoc ships a bundled `simpledoc` skill for agent instructions. To install it into a repo-scoped Codex skill store: + +```bash +npx -y @simpledoc/simpledoc --skill export simpledoc | skill-install --agent codex --scope repo +``` + +(`skill-install` is provided by the `skillflag` package.) + ## CI / Enforcement To enforce SimpleDoc conventions in CI, add a step that fails when the repo needs migration: @@ -88,7 +98,7 @@ The ISO 8601 date-prefixed format was inspired by the [Jekyll](https://jekyllrb. ## Examples -For an example in this repo, see [docs/2025-12-22-created-simpledoc.md](docs/2025-12-22-created-simpledoc.md) and [docs/HOW_TO_DOC.md](docs/HOW_TO_DOC.md). +For an example in this repo, see [docs/2025-12-22-created-simpledoc.md](docs/2025-12-22-created-simpledoc.md) and [skills/simpledoc/SKILL.md](skills/simpledoc/SKILL.md). ## License diff --git a/docs/2025-12-22-created-simpledoc.md b/docs/2025-12-22-created-simpledoc.md index 3cec5de..4629fe2 100644 --- a/docs/2025-12-22-created-simpledoc.md +++ b/docs/2025-12-22-created-simpledoc.md @@ -10,4 +10,4 @@ At the beginning, I did not know how to communicate the solution easily while fi So I created this. I hope it helps you create order out of chaos in your `docs/` folder. -This post is both a blog post intended to mark this event, and an example for SimpleDoc itself, together with [HOW_TO_DOC.md](./HOW_TO_DOC.md) in this folder. +This post is both a blog post intended to mark this event, and an example for SimpleDoc itself, together with the bundled skill at [skills/simpledoc/SKILL.md](../skills/simpledoc/SKILL.md). diff --git a/package-lock.json b/package-lock.json index 14ead89..5a8f327 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "MIT", "dependencies": { "@clack/prompts": "^0.11.0", - "commander": "^11.0.0" + "commander": "^11.0.0", + "skillflag": "^0.1.0" }, "bin": { "simpledoc": "dist/bin/simpledoc.js" @@ -1186,6 +1187,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1193,6 +1208,20 @@ "dev": true, "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2549,6 +2578,15 @@ "dev": true, "license": "MIT" }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/execa": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", @@ -2595,6 +2633,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -6433,6 +6477,22 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, + "node_modules/skillflag": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/skillflag/-/skillflag-0.1.0.tgz", + "integrity": "sha512-0z7/dwdxpO1TTmDkA9orTArR1RxZUoihnTAOpdxvgNG4MSMFCgF+p9cZwOIWMZf6CYh7otXLbxZP6M77ty71fw==", + "license": "MIT", + "dependencies": { + "tar-stream": "^3.1.7" + }, + "bin": { + "skill-install": "dist/bin/skill-install.js", + "skillflag": "dist/bin/skillflag.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -6555,6 +6615,17 @@ "node": ">= 0.4" } }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -6720,6 +6791,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", diff --git a/package.json b/package.json index c216067..a9e63a1 100644 --- a/package.json +++ b/package.json @@ -44,11 +44,13 @@ "dist", "README.md", "LICENSE", - "docs" + "docs", + "skills" ], "dependencies": { "@clack/prompts": "^0.11.0", - "commander": "^11.0.0" + "commander": "^11.0.0", + "skillflag": "^0.1.0" }, "devDependencies": { "@eslint/js": "^9.0.0", diff --git a/docs/HOW_TO_DOC.md b/skills/simpledoc/SKILL.md similarity index 84% rename from docs/HOW_TO_DOC.md rename to skills/simpledoc/SKILL.md index cc932e0..49fe001 100644 --- a/docs/HOW_TO_DOC.md +++ b/skills/simpledoc/SKILL.md @@ -1,13 +1,18 @@ -# Documentation Guidelines - --- +name: simpledoc +description: Create or update documentation in this repo following SimpleDoc conventions. +--- + +# SimpleDoc Documentation Skill + +Use this skill whenever you are asked to create or update documentation in this repository. ## MUST DO SETUP -**Attention agent!** This file is a template for creating instructions on how to create documentation in this repository. The placeholder sections should be filled in, before anything else. +**Attention agent!** This section is a template for creating instructions on how to create documentation in this repository. The placeholder sections must be filled in before anything else. 1. Fetch the most recent SimpleDoc specification from https://github.com/osolmaz/SimpleDoc and copy it below, replacing the placeholder content. -2. Ask the user about their preferences in documentation style: Do they prefer a certain style of writing, a certain tone, a certain level of technicality, a certain way to create implementation plans, etc. Do a few rounds of back-and-forth to get the user's preferences, and use the output to populate the placeholder content in the Preferences in Documentation Style section. +2. Ask the user about their preferences in documentation style (tone, technicality, planning style, etc.). Do a few rounds of back-and-forth to get the user's preferences, then replace the placeholder content in the Preferences section. After finishing the setup, DELETE ONLY this section between dividers `---`. The other agent instruction below is to be kept as is. @@ -15,7 +20,7 @@ After finishing the setup, DELETE ONLY this section between dividers `---`. The **Attention agent!** Complete every item below before touching documentation work: -1. **Read this file in full for the current session.** No shortcuts. Open `docs/HOW_TO_DOC.md`, refresh your memory, and only then proceed. +1. **Read this file in full for the current session.** No shortcuts. 2. **Verify that git is initialized and configured.** You will need the name and email of the current user in order to populate the `author` field in the YAML frontmatter. Run the following one-liner to verify: ```bash diff --git a/src/bin/simpledoc.ts b/src/bin/simpledoc.ts index 3e1e802..ab3f704 100644 --- a/src/bin/simpledoc.ts +++ b/src/bin/simpledoc.ts @@ -1,4 +1,15 @@ #!/usr/bin/env node +import process from "node:process"; +import { handleSkillflag } from "skillflag/dist/index.js"; + import { runCli } from "../cli/index.js"; -await runCli(process.argv); +const args = process.argv; +if (args.includes("--skill")) { + const exitCode = await handleSkillflag(args, { + skillsRoot: new URL("../../skills/", import.meta.url), + }); + process.exitCode = exitCode; +} else { + await runCli(args); +} diff --git a/src/cli/migrate.ts b/src/cli/migrate.ts index 910a1a4..63ecc81 100644 --- a/src/cli/migrate.ts +++ b/src/cli/migrate.ts @@ -14,7 +14,7 @@ import { } from "../migrator.js"; import { AGENTS_FILE, - HOW_TO_DOC_FILE, + SIMPLEDOC_SKILL_FILE, applyInstallationActions, buildInstallationActions, formatInstallActions, @@ -149,14 +149,14 @@ function buildDefaultPreviews(opts: { actionCount: addLine.length, }); - const howToDoc = opts.installActions.filter( - (a) => a.type === "write-file" && a.path === HOW_TO_DOC_FILE, + const skillTemplate = opts.installActions.filter( + (a) => a.type === "write-file" && a.path === SIMPLEDOC_SKILL_FILE, ); - if (howToDoc.length > 0) + if (skillTemplate.length > 0) defaultPreviews.push({ - title: `Create \`${HOW_TO_DOC_FILE}\` template`, - actionsText: formatInstallActions(howToDoc), - actionCount: howToDoc.length, + title: `Create \`${SIMPLEDOC_SKILL_FILE}\` template`, + actionsText: formatInstallActions(skillTemplate), + actionCount: skillTemplate.length, }); } @@ -193,7 +193,7 @@ export async function runMigrate(options: MigrateOptions): Promise { createAgentsFile: !installStatus.agentsExists, addAttentionLine: installStatus.agentsExists && !installStatus.agentsHasAttentionLine, - addHowToDoc: !installStatus.howToDocExists, + addSkill: !installStatus.skillExists, }); const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY); @@ -408,8 +408,11 @@ export async function runMigrate(options: MigrateOptions): Promise { summaryLines.push(`- Update \`${AGENTS_FILE}\` (add reminder line)`); continue; } - if (action.type === "write-file" && action.path === HOW_TO_DOC_FILE) { - summaryLines.push(`- Create \`${HOW_TO_DOC_FILE}\``); + if ( + action.type === "write-file" && + action.path === SIMPLEDOC_SKILL_FILE + ) { + summaryLines.push(`- Create \`${SIMPLEDOC_SKILL_FILE}\``); continue; } } diff --git a/src/cli/steps/install.ts b/src/cli/steps/install.ts index f1053e4..f09cc93 100644 --- a/src/cli/steps/install.ts +++ b/src/cli/steps/install.ts @@ -2,18 +2,18 @@ import type { InstallationStatus } from "../../installer.js"; import { AGENTS_ATTENTION_LINE, AGENTS_FILE, - HOW_TO_DOC_FILE, + SIMPLEDOC_SKILL_FILE, } from "../../installer.js"; import { noteWrapped, promptConfirm } from "../ui.js"; export async function runInstallSteps(status: InstallationStatus): Promise<{ createAgentsFile: boolean; addAttentionLine: boolean; - addHowToDoc: boolean; + addSkill: boolean; } | null> { let createAgentsFile = false; let addAttentionLine = false; - let addHowToDoc = false; + let addSkill = false; if (!status.agentsExists) { noteWrapped( @@ -36,18 +36,18 @@ export async function runInstallSteps(status: InstallationStatus): Promise<{ addAttentionLine = include; } - if (!status.howToDocExists) { + if (!status.skillExists) { noteWrapped( - `Will create \`${HOW_TO_DOC_FILE}\` from the bundled SimpleDoc template (won't overwrite if it already exists).`, - "Proposed: Add docs/HOW_TO_DOC.md", + `Will create \`${SIMPLEDOC_SKILL_FILE}\` from the bundled SimpleDoc skill (won't overwrite if it already exists).`, + "Proposed: Add skills/simpledoc/SKILL.md", ); const include = await promptConfirm( - `Create \`${HOW_TO_DOC_FILE}\` template?`, + `Create \`${SIMPLEDOC_SKILL_FILE}\` template?`, true, ); if (include === null) return null; - addHowToDoc = include; + addSkill = include; } - return { createAgentsFile, addAttentionLine, addHowToDoc }; + return { createAgentsFile, addAttentionLine, addSkill }; } diff --git a/src/installer.ts b/src/installer.ts index 5b3e72a..b09cc39 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -3,10 +3,14 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; export const AGENTS_FILE = "AGENTS.md"; -export const HOW_TO_DOC_FILE = path.posix.join("docs", "HOW_TO_DOC.md"); +export const SIMPLEDOC_SKILL_FILE = path.posix.join( + "skills", + "simpledoc", + "SKILL.md", +); export const AGENTS_ATTENTION_LINE = - "**Attention agent!** Before creating ANY documentation, read the docs/HOW_TO_DOC.md file first. It contains guidelines on how to create documentation in this repository."; + "**Attention agent!** Before creating ANY documentation, use the `simpledoc` skill in `skills/simpledoc/SKILL.md`."; export type InstallAction = | { @@ -24,7 +28,7 @@ export type InstallAction = export type InstallationStatus = { agentsExists: boolean; agentsHasAttentionLine: boolean; - howToDocExists: boolean; + skillExists: boolean; }; function normalizeNewlines(input: string): string { @@ -40,8 +44,8 @@ function defaultAgentsFileContent(): string { return ["# Agent Instructions", "", AGENTS_ATTENTION_LINE, ""].join("\n"); } -async function readBundledHowToDocTemplate(): Promise { - const url = new URL("../docs/HOW_TO_DOC.md", import.meta.url); +async function readBundledSimpleDocSkill(): Promise { + const url = new URL("../skills/simpledoc/SKILL.md", import.meta.url); const templatePath = fileURLToPath(url); return await fs.readFile(templatePath, "utf8"); } @@ -59,7 +63,7 @@ export async function getInstallationStatus( repoRootAbs: string, ): Promise { const agentsAbs = path.join(repoRootAbs, AGENTS_FILE); - const howToDocAbs = path.join(repoRootAbs, ...HOW_TO_DOC_FILE.split("/")); + const skillAbs = path.join(repoRootAbs, ...SIMPLEDOC_SKILL_FILE.split("/")); const agentsExists = await fileExists(agentsAbs); const agentsContent = agentsExists @@ -69,15 +73,15 @@ export async function getInstallationStatus( ? hasExactLine(agentsContent, AGENTS_ATTENTION_LINE) : false; - const howToDocExists = await fileExists(howToDocAbs); + const skillExists = await fileExists(skillAbs); - return { agentsExists, agentsHasAttentionLine, howToDocExists }; + return { agentsExists, agentsHasAttentionLine, skillExists }; } export async function buildInstallationActions(opts: { createAgentsFile: boolean; addAttentionLine: boolean; - addHowToDoc: boolean; + addSkill: boolean; }): Promise { const actions: InstallAction[] = []; @@ -98,11 +102,11 @@ export async function buildInstallationActions(opts: { }); } - if (opts.addHowToDoc) { + if (opts.addSkill) { actions.push({ type: "write-file", - path: HOW_TO_DOC_FILE, - content: await readBundledHowToDocTemplate(), + path: SIMPLEDOC_SKILL_FILE, + content: await readBundledSimpleDocSkill(), ifExists: "skip", }); } diff --git a/test/integration/migrator.plan.test.ts b/test/integration/migrator.plan.test.ts index f66158c..9d95329 100644 --- a/test/integration/migrator.plan.test.ts +++ b/test/integration/migrator.plan.test.ts @@ -165,13 +165,13 @@ test("plan: can force removing date prefix for date-prefixed docs when overridde ]); }); -test("plan: does not rename docs/HOW_TO_DOC.md", async (t) => { +test("plan: ignores skills/simpledoc/SKILL.md", async (t) => { const repo = await makeTempRepo(); t.after(repo.cleanup); - await writeFile(repo.dir, "docs/HOW_TO_DOC.md", "# How to doc\n"); + await writeFile(repo.dir, "skills/simpledoc/SKILL.md", "# SimpleDoc skill\n"); commitAll(repo.dir, { - message: "Add HOW_TO_DOC", + message: "Add SimpleDoc skill", author: "Alice ", dateIso: "2024-07-01T12:00:00Z", });