diff --git a/.agents/skills/data-client-manager/SKILL.md b/.agents/skills/data-client-manager/SKILL.md index af8132651f9b..9d0da3f2c4cb 100644 --- a/.agents/skills/data-client-manager/SKILL.md +++ b/.agents/skills/data-client-manager/SKILL.md @@ -1,6 +1,6 @@ --- name: data-client-manager -description: Write @data-client Managers for app-wide side effects and real-time data such as websockets, SSE and polling: code that reacts to every request or to events from outside the app and updates the store. Use one instead of useEffect for behavior that belongs to the whole app, such as error toasts and reporting, logging and analytics, auth failures, refetching on focus or reconnect, and cross-tab sync. +description: "Write @data-client Managers for app-wide side effects and real-time data such as websockets, SSE and polling: code that reacts to every request or to events from outside the app and updates the store. Use one instead of useEffect for behavior that belongs to the whole app, such as error toasts and reporting, logging and analytics, auth failures, refetching on focus or reconnect, and cross-tab sync." license: Apache 2.0 --- diff --git a/.agents/skills/data-client-react-testing/SKILL.md b/.agents/skills/data-client-react-testing/SKILL.md index 57736a7f5554..852e4c0b73e4 100644 --- a/.agents/skills/data-client-react-testing/SKILL.md +++ b/.agents/skills/data-client-react-testing/SKILL.md @@ -1,6 +1,6 @@ --- name: data-client-react-testing -description: Test React code that uses @data-client/react: components and hooks that fetch or change data, with mocked responses for loading, error, mutation and polling cases. Covers renderDataHook, MockResolver and fixtures. +description: "Test React code that uses @data-client/react: components and hooks that fetch or change data, with mocked responses for loading, error, mutation and polling cases. Covers renderDataHook, MockResolver and fixtures." license: Apache 2.0 --- diff --git a/.agents/skills/data-client-react/SKILL.md b/.agents/skills/data-client-react/SKILL.md index 18f7cfbc6e29..1b195daba530 100644 --- a/.agents/skills/data-client-react/SKILL.md +++ b/.agents/skills/data-client-react/SKILL.md @@ -1,6 +1,6 @@ --- name: data-client-react -description: Read, change and render async and shared state in React with @data-client/react: any data from an API or other async source, and global state shared across components (it has to persist somewhere, which makes it async). Use it for loading and error states, mutations and forms, filtered or derived views, and debugging data that looks wrong or stale on screen. Use it in place of fetch in useEffect, or app-wide useState and context; its main hooks are useSuspense, useQuery and useController. +description: "Read, change and render async and shared state in React with @data-client/react: any data from an API or other async source, and global state shared across components (it has to persist somewhere, which makes it async). Use it for loading and error states, mutations and forms, filtered or derived views, and debugging data that looks wrong or stale on screen. Use it in place of fetch in useEffect, or app-wide useState and context; its main hooks are useSuspense, useQuery and useController." license: Apache 2.0 --- ## Rendering diff --git a/.agents/skills/data-client-vue-testing/SKILL.md b/.agents/skills/data-client-vue-testing/SKILL.md index 5ed995fcd8eb..03f1bfb40fb7 100644 --- a/.agents/skills/data-client-vue-testing/SKILL.md +++ b/.agents/skills/data-client-vue-testing/SKILL.md @@ -1,6 +1,6 @@ --- name: data-client-vue-testing -description: Test Vue code that uses @data-client/vue: components and composables that fetch or change data, with mocked responses for loading, error, mutation and polling cases. Covers renderDataCompose, mountDataClient and fixtures. +description: "Test Vue code that uses @data-client/vue: components and composables that fetch or change data, with mocked responses for loading, error, mutation and polling cases. Covers renderDataCompose, mountDataClient and fixtures." license: Apache 2.0 --- diff --git a/.agents/skills/data-client-vue/SKILL.md b/.agents/skills/data-client-vue/SKILL.md index dbe2e663ae92..72b812716269 100644 --- a/.agents/skills/data-client-vue/SKILL.md +++ b/.agents/skills/data-client-vue/SKILL.md @@ -1,6 +1,6 @@ --- name: data-client-vue -description: Read, change and render async and shared state in Vue with @data-client/vue: any data from an API or other async source, and global state shared across components (it has to persist somewhere, which makes it async). Use it for loading and error states, mutations and forms, filtered or derived views, and debugging data that looks wrong or stale on screen. Use it in place of fetch in onMounted or watch, or app-wide refs and provide/inject; its main composables are useSuspense, useQuery and useController. +description: "Read, change and render async and shared state in Vue with @data-client/vue: any data from an API or other async source, and global state shared across components (it has to persist somewhere, which makes it async). Use it for loading and error states, mutations and forms, filtered or derived views, and debugging data that looks wrong or stale on screen. Use it in place of fetch in onMounted or watch, or app-wide refs and provide/inject; its main composables are useSuspense, useQuery and useController." license: Apache 2.0 --- ## Setup diff --git a/.claude-plugin/README.md b/.claude-plugin/README.md index e66c98c186b8..721bc8322bfb 100644 --- a/.claude-plugin/README.md +++ b/.claude-plugin/README.md @@ -40,6 +40,7 @@ Each listing reads the plugin from `.agents/skills`, whose `README.md` is the li - `.agents/skill-packages.json`: which package ships which skill, so the test can check the plugin's skills also ship in a package. - `website/framework-docs/skillCommands.mjs` and the READMEs that copy its setup commands (root, react, vue, rest, graphql, endpoint), and `website/src/components/AgentPrompt.tsx`'s default prompt: `readme-commands.test.mjs`. +- Every `.agents/skills/*/SKILL.md` frontmatter, which `npx skills add` parses as strict YAML: `skill-frontmatter.test.mjs`. ## CI diff --git a/.claude-plugin/readme-commands.test.mjs b/.claude-plugin/readme-commands.test.mjs index 80cdcf0595a0..1b60c76bcf6a 100644 --- a/.claude-plugin/readme-commands.test.mjs +++ b/.claude-plugin/readme-commands.test.mjs @@ -15,6 +15,7 @@ import { cursorSetupPrompt, setupCommand, setupPrompt, + setupSkillFile, } from '../website/framework-docs/skillCommands.mjs'; const read = file => fs.readFileSync(path.join(root, file), 'utf8'); @@ -60,7 +61,9 @@ for (const pkg of ['rest', 'graphql', 'endpoint']) { test("AgentPrompt's default prompt matches setupPrompt", () => { // its translate() literal can't import setupPrompt (i18n extraction needs a literal) - const message = setupPrompt('{framework}').replace(setupCommand, '{command}'); + const message = setupPrompt('{framework}') + .replace(setupCommand, '{command}') + .replace(setupSkillFile, '{skillFile}'); assert.ok( read('website/src/components/AgentPrompt.tsx').includes(`'${message}'`), message, diff --git a/.claude-plugin/skill-frontmatter.test.mjs b/.claude-plugin/skill-frontmatter.test.mjs new file mode 100644 index 000000000000..035bc187a3a3 --- /dev/null +++ b/.claude-plugin/skill-frontmatter.test.mjs @@ -0,0 +1,33 @@ +// Keeps every skill's top-level frontmatter values valid YAML: `npx skills add` parses it strictly +// and skips a skill it can't parse, with only a warning: +// node --test '.claude-plugin/*.test.mjs' +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { test } from 'node:test'; + +import { root } from './plugin-dir.mjs'; + +const skills = path.join(root, '.agents/skills'); + +test('skill frontmatter values that need quotes have them', () => { + for (const skill of fs.readdirSync(skills)) { + const file = path.join(skills, skill, 'SKILL.md'); + if (!fs.existsSync(file)) continue; + const match = fs + .readFileSync(file, 'utf8') + .match(/^---\r?\n([^]*?)\r?\n---\r?\n/); + assert.ok(match, `${skill}: frontmatter must open the file and close`); + for (const line of match[1].split(/\r?\n/)) { + const [, key, value] = line.match(/^(\w[\w-]*): (.*)$/) ?? []; + // a plain (unquoted) scalar can't start with an indicator like `@`, and + // `: ` (or a trailing `:`) starts a mapping and ` #` a comment + if (value && !/^("(?:[^"\\]|\\.)*"|'(?:[^']|'')*'|[>|]-?)$/.test(value)) + assert.doesNotMatch( + value, + /^[-?:,[\]{}#&*!|>'"%@`]|: | #|:$/, + `${skill} ${key}: quote it`, + ); + } + } +}); diff --git a/README.md b/README.md index a87b9026b75b..ff5a96174ef1 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Then [add the provider](https://dataclient.io/docs/getting-started/installation# Or skip the steps above and paste this into your coding agent's chat. It installs Data Client's setup skill, which adds the packages, wires up the provider and links the skills they ship: ```prompt -Set up `@data-client/react` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then follow it. +Set up `@data-client/react` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then read `.agents/skills/data-client-setup/SKILL.md` and follow it. ```
diff --git a/packages/endpoint/README.md b/packages/endpoint/README.md index 59170686a339..3fa39222967a 100644 --- a/packages/endpoint/README.md +++ b/packages/endpoint/README.md @@ -20,7 +20,7 @@ Declarative, strongly typed, reusable network definitions for networking librari Tell your coding agent: ```prompt -Set up `@data-client/endpoint` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then follow it. +Set up `@data-client/endpoint` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then read `.agents/skills/data-client-setup/SKILL.md` and follow it. ``` It installs the packages, wires up React or Vue, and links the skills they ship. diff --git a/packages/graphql/README.md b/packages/graphql/README.md index 4a944dec2fc1..d4ffe4307c42 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -21,7 +21,7 @@ Tell your coding agent: ```prompt -Set up `@data-client/graphql` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then follow it. +Set up `@data-client/graphql` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then read `.agents/skills/data-client-setup/SKILL.md` and follow it. ``` It installs the packages, wires up React or Vue, and links the skills they ship. diff --git a/packages/react/README.md b/packages/react/README.md index 874200352783..78d93b87b373 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -45,7 +45,7 @@ Then [add the provider](https://dataclient.io/docs/getting-started/installation# Or skip the steps above and paste this into your coding agent's chat. It installs Data Client's setup skill, which adds the packages, wires up the provider and links the skills they ship: ```prompt -Set up `@data-client/react` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then follow it. +Set up `@data-client/react` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then read `.agents/skills/data-client-setup/SKILL.md` and follow it. ```
diff --git a/packages/rest/README.md b/packages/rest/README.md index 86cc73bf6629..795750362e11 100644 --- a/packages/rest/README.md +++ b/packages/rest/README.md @@ -20,7 +20,7 @@ Tell your coding agent: ```prompt -Set up `@data-client/rest` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then follow it. +Set up `@data-client/rest` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then read `.agents/skills/data-client-setup/SKILL.md` and follow it. ``` It installs the packages, wires up React or Vue, and links the skills they ship. diff --git a/packages/vue/README.md b/packages/vue/README.md index 53f1dbdd16e9..8d8a24de174d 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -51,7 +51,7 @@ app.mount('#app'); Or skip the steps above and paste this into your coding agent's chat. It installs Data Client's setup skill, which adds the packages, wires up the provider and links the skills they ship: ```prompt -Set up `@data-client/vue` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then follow it. +Set up `@data-client/vue` and use it for all API data. Install its setup skill first: `npx skills add reactive/data-client --skill data-client-setup -y`, then read `.agents/skills/data-client-setup/SKILL.md` and follow it. ```
diff --git a/website/framework-docs/skillCommands.mjs b/website/framework-docs/skillCommands.mjs index 1cca01d60055..f049af007fdd 100644 --- a/website/framework-docs/skillCommands.mjs +++ b/website/framework-docs/skillCommands.mjs @@ -27,10 +27,14 @@ export const claudePluginCommand = [ const setupTask = framework => `Set up \`@data-client/${framework}\` and use it for all API data`; -/** The prompt any agent takes: installs the setup skill, then follows it. +/** Where setupCommand puts the setup skill for every agent; prompts have the + * agent read it by path, since only users can invoke it by name */ +export const setupSkillFile = `${skillsDir}/data-client-setup/SKILL.md`; + +/** The prompt any agent takes: installs the setup skill, then reads and follows it. * AgentPrompt's translate() default repeats it as a literal for i18n extraction */ export const setupPrompt = pkg => - `${setupTask(pkg)}. Install its setup skill first: \`${setupCommand}\`, then follow it.`; + `${setupTask(pkg)}. Install its setup skill first: \`${setupCommand}\`, then read \`${setupSkillFile}\` and follow it.`; /** Starts the plugin's setup skill by name; through the router skill, Claude * would read it from the plugin cache, outside the project, and ask first */ diff --git a/website/llms-plugin.js b/website/llms-plugin.js index c67a80e57a22..dd384ca02924 100644 --- a/website/llms-plugin.js +++ b/website/llms-plugin.js @@ -64,7 +64,7 @@ module.exports = function llmsPlugin(context) { if (context.i18n.currentLocale !== context.i18n.defaultLocale) return; const { docToMarkdown, agentReferenceUrl, ROOT } = await import('./framework-docs/docsToMarkdown.mjs'); - const { setupCommand, linkCommand } = + const { setupCommand, setupSkillFile, linkCommand } = await import('./framework-docs/skillCommands.mjs'); // docs instance id -> its folder and sidebar sections @@ -139,7 +139,7 @@ module.exports = function llmsPlugin(context) { o => `Using ${o.name}? See ${url}${llmsTxtRoute(o.id)}`, ); // the same steps the site's setup prompt gives (AgentPrompt) - const setupStep = `To add Data Client to an app, run \`${setupCommand}\` from the project root, then read \`.agents/skills/data-client-setup/SKILL.md\` and follow it.`; + const setupStep = `To add Data Client to an app, run \`${setupCommand}\` from the project root, then read \`${setupSkillFile}\` and follow it.`; /** Title, summary and intro of a docs instance's llms.txt */ const header = instance => { const { id, name, framework } = instance; diff --git a/website/src/components/AgentPrompt.tsx b/website/src/components/AgentPrompt.tsx index 8b51ea10e773..47426aeae5ff 100644 --- a/website/src/components/AgentPrompt.tsx +++ b/website/src/components/AgentPrompt.tsx @@ -5,7 +5,10 @@ import React, { useCallback, useId } from 'react'; import styles from './AgentPrompt.module.css'; import useFramework from './useFramework'; import useTapped from './useTapped'; -import { setupCommand } from '../../framework-docs/skillCommands.mjs'; +import { + setupCommand, + setupSkillFile, +} from '../../framework-docs/skillCommands.mjs'; const FEEDBACK_MS = 2000; @@ -27,11 +30,11 @@ export default function AgentPrompt({ prompt, title, framed }: Props) { { id: 'agentPrompt.prompt', message: - 'Set up `@data-client/{framework}` and use it for all API data. Install its setup skill first: `{command}`, then follow it.', + 'Set up `@data-client/{framework}` and use it for all API data. Install its setup skill first: `{command}`, then read `{skillFile}` and follow it.', description: - 'Prompt readers copy into their coding agent; {command} is the shell command the agent runs. Keep the backticks around package names and the command: they keep each on one line.', + 'Prompt readers copy into their coding agent; {command} is the shell command the agent runs and {skillFile} the file it then reads. Keep the backticks around package names and the command: they keep each on one line.', }, - { framework, command: setupCommand }, + { framework, command: setupCommand, skillFile: setupSkillFile }, ); const tipId = useId(); // the check icon and the tooltip (which touch screens, lacking hover, need)