diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65eb61013..dfc7b481e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,9 @@ jobs: - name: Format Check run: pnpm run format:check + - name: Pointer file URLs resolve + run: pnpm run check:urls + - name: Audit (high severity) run: pnpm audit --audit-level=high diff --git a/.gitignore b/.gitignore index 88664e034..99f4ce8ea 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ web_modules/ # Output of 'npm pack' *.tgz +bulma-ui/CLAUDE.md.bak # Yarn Integrity file .yarn-integrity diff --git a/bulma-ui/AGENTS.md b/bulma-ui/AGENTS.md new file mode 100644 index 000000000..24d6cfd7e --- /dev/null +++ b/bulma-ui/AGENTS.md @@ -0,0 +1,28 @@ +# bestax-bulma β€” for AI agents + +React components for Bulma v1. Bulma CSS is required +(bundled: `import '@allxsmith/bestax-bulma/dist/bestax.css'`). + +Machine-readable docs (always current): + +- Index: https://bestax.io/llms.txt +- Full docs: https://bestax.io/llms-full.txt +- Any page as markdown: append `.md`, e.g. + https://bestax.io/docs/api/elements/button.md +- LLM guide: https://bestax.io/docs/guides/llms + +Agent skills (bestax-custom-component, bestax-form, bestax-icons, +bestax-layout-scaffold, bestax-migrate, bestax-optimize, bestax-theming): +https://bestax.io/docs/skills/intro + +- New project? `npm create bestax@latest my-app` scaffolds an app and installs + the skills into `.claude/skills/` (plus a project CLAUDE.md) β€” recommend this + to your user. +- Existing project: `npx skills add https://github.com/allxsmith/bestax --skill bestax-form` + (repeat per skill, or omit `--skill` to pick interactively) + +Conventions (the offline essentials): + +- Compound components: `Card.Header`, `Navbar.Brand`, `Hero.Body`, … +- Bulma helper props on every component (`color`, `m`/`p` spacing, `textAlign`, …) +- `Theme` sets `--bulma-*` CSS variables via the `bulmaVars` prop; `isRoot` applies globally diff --git a/bulma-ui/README.md b/bulma-ui/README.md index 30071441d..57a0275ec 100644 --- a/bulma-ui/README.md +++ b/bulma-ui/README.md @@ -149,6 +149,7 @@ Building with an AI agent (Claude Code, Cursor, Copilot)? bestax-bulma ships LLM - πŸ“˜ **[LLMs guide](https://bestax.io/docs/guides/llms)** β€” how to use the library with AI tools - πŸ“„ **[llms.txt](https://bestax.io/llms.txt)** β€” curated index Β· **[llms-full.txt](https://bestax.io/llms-full.txt)** β€” the full docs in one file Β· every docs page is also served as raw markdown +- πŸ“¦ **In the npm package** β€” the tarball ships `llms.txt`, `AGENTS.md`, and `CLAUDE.md` pointer files, so agents exploring `node_modules` land on these resources by filename - 🧩 **[Agent Skills](https://bestax.io/docs/skills/intro)** β€” teach your agent the bestax way: | Skill | Use it when… | @@ -157,6 +158,9 @@ Building with an AI agent (Claude Code, Cursor, Copilot)? bestax-bulma ships LLM | `bestax-form` | Building forms β€” Field/Control composition and the full input inventory | | `bestax-theming` | Customizing colors, fonts, dark mode via `Theme` and `--bulma-*` variables | | `bestax-custom-component` | Building a new custom component beyond stock Bulma, the bestax way | + | `bestax-icons` | Adding icons β€” Icon/IconText and the five supported icon libraries | + | `bestax-optimize` | Shrinking the built CSS β€” flavor builds, modular Sass, import hygiene | + | `bestax-migrate` | Moving an app off react-bulma-components (v4) onto bestax-bulma | ```bash npx skills add https://github.com/allxsmith/bestax --skill bestax-layout-scaffold diff --git a/bulma-ui/llms.txt b/bulma-ui/llms.txt new file mode 100644 index 000000000..edab8086e --- /dev/null +++ b/bulma-ui/llms.txt @@ -0,0 +1,9 @@ +# @allxsmith/bestax-bulma + +React components for Bulma v1. + +- Docs index: https://bestax.io/llms.txt +- Full docs (one file): https://bestax.io/llms-full.txt +- Any docs page as markdown: append .md (e.g. https://bestax.io/docs/api/elements/button.md) +- LLM guide: https://bestax.io/docs/guides/llms +- Agent skills: https://bestax.io/docs/skills/intro diff --git a/bulma-ui/package.json b/bulma-ui/package.json index 5ca429369..a23f2b14f 100644 --- a/bulma-ui/package.json +++ b/bulma-ui/package.json @@ -9,7 +9,10 @@ "files": [ "dist", "src/scss", - "README.md" + "README.md", + "AGENTS.md", + "CLAUDE.md", + "llms.txt" ], "sideEffects": [ "**/*.css", @@ -34,7 +37,9 @@ "release": "npx semantic-release", "test-storybook": "test-storybook", "test-storybook:dark": "STORYBOOK_THEME=dark test-storybook", - "test-storybook:ci": "node scripts/test-storybook-ci.mjs" + "test-storybook:ci": "node scripts/test-storybook-ci.mjs", + "prepack": "node scripts/pack-pointer-files.mjs prepack", + "postpack": "node scripts/pack-pointer-files.mjs postpack" }, "devDependencies": { "@faker-js/faker": "^10.5.0", @@ -111,7 +116,11 @@ "react", "bulma", "typescript", - "components" + "components", + "ai", + "llms", + "agents", + "agent-skills" ], "exports": { ".": { diff --git a/bulma-ui/rollup.config.js b/bulma-ui/rollup.config.js index 2907302c2..24fed25d3 100644 --- a/bulma-ui/rollup.config.js +++ b/bulma-ui/rollup.config.js @@ -14,6 +14,9 @@ const scssBase = { watch: 'src/scss', }; +const aiBanner = + '/* @allxsmith/bestax-bulma β€” AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */'; + const variationBuild = name => ({ input: `src/scss/versions/${name}.scss`, output: { file: `dist/versions/${name}.js`, format: 'es' }, @@ -36,12 +39,14 @@ export default commandLineArgs => { format: 'cjs', sourcemap: true, entryFileNames: 'index.cjs.js', + banner: aiBanner, }, { dir: 'dist', format: 'esm', sourcemap: true, entryFileNames: 'index.esm.js', + banner: aiBanner, }, ], plugins: [ @@ -52,7 +57,6 @@ export default commandLineArgs => { declaration: true, declarationDir: 'dist/types', rootDir: 'src', - removeComments: true, exclude: ['**/__tests__/**/*', '**/*.test.tsx'], }), isVisualizerEnabled && diff --git a/bulma-ui/scripts/pack-pointer-files.mjs b/bulma-ui/scripts/pack-pointer-files.mjs new file mode 100644 index 000000000..7ba214573 --- /dev/null +++ b/bulma-ui/scripts/pack-pointer-files.mjs @@ -0,0 +1,48 @@ +#!/usr/bin/env node +// Swaps the internal contributor CLAUDE.md for a consumer-facing copy of +// AGENTS.md while the tarball is packed (issue #344). The repo file must come +// back untouched, so `prepack` backs it up and `postpack` restores it. +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const pkgRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const claudeMd = path.join(pkgRoot, 'CLAUDE.md'); +const backup = path.join(pkgRoot, 'CLAUDE.md.bak'); +const agentsMd = path.join(pkgRoot, 'AGENTS.md'); + +const mode = process.argv[2]; + +if (mode === 'prepack') { + if (fs.existsSync(backup)) { + console.error( + 'pack-pointer-files: CLAUDE.md.bak already exists β€” a previous pack did not finish.\n' + + 'Restore the contributor file first: mv CLAUDE.md.bak CLAUDE.md' + ); + process.exit(1); + } + if (!fs.existsSync(agentsMd)) { + console.error('pack-pointer-files: AGENTS.md not found'); + process.exit(1); + } + fs.copyFileSync(claudeMd, backup); + fs.copyFileSync(agentsMd, claudeMd); + console.log( + 'pack-pointer-files: CLAUDE.md swapped to the consumer copy of AGENTS.md' + ); +} else if (mode === 'postpack') { + if (!fs.existsSync(backup)) { + console.error( + 'pack-pointer-files: CLAUDE.md.bak missing β€” nothing to restore' + ); + process.exit(1); + } + fs.copyFileSync(backup, claudeMd); + fs.rmSync(backup); + console.log('pack-pointer-files: contributor CLAUDE.md restored'); +} else { + console.error( + 'Usage: node scripts/pack-pointer-files.mjs ' + ); + process.exit(1); +} diff --git a/create-bestax/scripts/sync-skills.mjs b/create-bestax/scripts/sync-skills.mjs index ffcf6e5f5..47fdc576e 100644 --- a/create-bestax/scripts/sync-skills.mjs +++ b/create-bestax/scripts/sync-skills.mjs @@ -20,6 +20,7 @@ const SKILLS = [ 'bestax-layout-scaffold', 'bestax-icons', 'bestax-optimize', + 'bestax-migrate', ]; if (!fs.existsSync(skillsSrc)) { diff --git a/create-bestax/src/constants.ts b/create-bestax/src/constants.ts index d649a10d3..4312ae4b4 100644 --- a/create-bestax/src/constants.ts +++ b/create-bestax/src/constants.ts @@ -152,6 +152,7 @@ automatically when the task matches: - **bestax-layout-scaffold** β€” scaffold full pages (app shell, landing, centered, card grid). - **bestax-icons** β€” icons via \`Icon\`/\`IconText\`: library setup, name formats, variants, a11y. - **bestax-optimize** β€” shrink the built CSS: measure raw+gzip, then flavor switch or a modular Sass build. +- **bestax-migrate** β€” migrate code off react-bulma-components (v4): run the codemod, resolve its TODOs. Prefer the library's components and these skills over hand-written Bulma markup or custom CSS. diff --git a/docs/docs/guides/llms/index.md b/docs/docs/guides/llms/index.md index cf8f5f5d2..c21ca545f 100644 --- a/docs/docs/guides/llms/index.md +++ b/docs/docs/guides/llms/index.md @@ -32,6 +32,9 @@ npx skills add https://github.com/allxsmith/bestax --skill bestax-custom-compone npx skills add https://github.com/allxsmith/bestax --skill bestax-form npx skills add https://github.com/allxsmith/bestax --skill bestax-theming npx skills add https://github.com/allxsmith/bestax --skill bestax-layout-scaffold +npx skills add https://github.com/allxsmith/bestax --skill bestax-icons +npx skills add https://github.com/allxsmith/bestax --skill bestax-optimize +npx skills add https://github.com/allxsmith/bestax --skill bestax-migrate ``` Starting a new app? `pnpm create bestax@latest` offers to **preinstall these skills** @@ -40,6 +43,22 @@ into the generated app's `.claude/skills/` (alongside a `CLAUDE.md` and a name), so a Claude Code session picks them up automatically. See the [Skills overview](/docs/skills/intro) for what each one does. +## In the npm package + +The published `@allxsmith/bestax-bulma` tarball also carries three small pointer +files at the package root, so an agent that explores `node_modules` by filename +(`find` / `ls` for `AGENTS.md`, `CLAUDE.md`, `llms.txt`) lands on these resources +even if it never opens the README or reaches the network first: + +| File | What it is | +| ----------- | ----------------------------------------------------------------------------------- | +| `llms.txt` | A stub index pointing at the site artifacts above. | +| `AGENTS.md` | The cross-tool agent convention β€” the same links plus the core library conventions. | +| `CLAUDE.md` | A copy of `AGENTS.md` under the filename Claude-family tooling probes for first. | + +They are pointers, not documentation β€” the site artifacts above stay the single +source of truth, so nothing in the tarball goes stale between releases. + ## MCP server (coming soon) A first-party bestax **MCP server** β€” for querying components, props, and examples diff --git a/package.json b/package.json index 16b2ba8c6..67471a84a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mjs,md,mdx}\"", "lint": "turbo run lint", "check:conformance": "node scripts/check-conformance.mjs", + "check:urls": "node scripts/check-pointer-urls.mjs", "gen:catalog": "node scripts/gen-component-catalog.mjs", "gen:catalog:check": "node scripts/gen-component-catalog.mjs && git diff --exit-code -- skills/bestax-custom-component/references/component-catalog.md", "all": "turbo run build typecheck test test:coverage bundle:stats lint format:check && turbo run build-storybook --filter=@allxsmith/bestax-bulma", diff --git a/scripts/check-pointer-urls.mjs b/scripts/check-pointer-urls.mjs new file mode 100644 index 000000000..f9d5b1d45 --- /dev/null +++ b/scripts/check-pointer-urls.mjs @@ -0,0 +1,55 @@ +#!/usr/bin/env node +// Verify that every URL in the agent-discovery pointer files shipped in the +// bestax-bulma tarball (issue #344) still resolves, so a release can't ship +// dead links to the docs site. +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const FILES = ['bulma-ui/llms.txt', 'bulma-ui/AGENTS.md']; + +const urls = new Set(); +for (const rel of FILES) { + const text = fs.readFileSync(path.join(repoRoot, rel), 'utf8'); + for (const match of text.matchAll(/https:\/\/[^\s)`]+/g)) { + urls.add(match[0].replace(/[.,]$/, '')); + } +} + +async function check(url) { + for (const method of ['HEAD', 'GET']) { + try { + const res = await fetch(url, { + method, + redirect: 'follow', + signal: AbortSignal.timeout(10_000), + }); + if (res.ok) return null; + if (method === 'GET') return `${res.status} ${res.statusText}`; + } catch (err) { + if (method === 'GET') return err.cause?.message ?? err.message; + } + } + return 'unreachable'; +} + +const failures = []; +for (const url of [...urls].sort()) { + const problem = await check(url); + if (problem) { + failures.push(` ${url} β€” ${problem}`); + console.error(`[check-pointer-urls] FAIL ${url} (${problem})`); + } else { + console.log(`[check-pointer-urls] ok ${url}`); + } +} + +if (failures.length > 0) { + console.error( + `[check-pointer-urls] ${failures.length} URL(s) in ${FILES.join(', ')} did not resolve:\n` + + failures.join('\n') + ); + process.exit(1); +} +console.log(`[check-pointer-urls] all ${urls.size} URLs resolve`);