diff --git a/.claude/rules/prompt-skill-sync.md b/.claude/rules/prompt-skill-sync.md index 490a644..9bc4ae5 100644 --- a/.claude/rules/prompt-skill-sync.md +++ b/.claude/rules/prompt-skill-sync.md @@ -50,7 +50,7 @@ Whichever half someone takes is the only thing they get. Four rules follow. An illustrative link, such as `[config.py](../src/config.py)` inside an example teaching the citation format, is not a real link and is allowed. The test is whether the target exists here: if it does, the author linked to something real and it will break. -`make -f .claude/Makefile check-skills` enforces every rule in this section, plus the specification itself: `name` matching the directory, `description` within its character limit, a body under 500 lines, a licence on every published skill, and every bundled path resolving. It is deliberately **not** part of `npm run validate`, because the repository must build, test, and lint with no agent tooling present. +`make -f .claude/Makefile check-skills` enforces every rule in this section, plus the specification itself: `name` matching the directory, `description` within its character limit, a body under 500 lines, a licence on every skill, and every bundled path resolving. It is deliberately **not** part of `npm run validate`, because the repository must build, test, and lint with no agent tooling present. ## The three states @@ -60,9 +60,9 @@ Every skill is in exactly one, and [`check-skill-publishability.mjs`](../scripts - **Installable**: an installer can offer it, but it is not held to the agnosticism bar. - **Internal**: carries `metadata: internal: true`, which hides it from `npx skills` discovery and from installation unless `INSTALL_INTERNAL_SKILLS=1` is set. -**Anything an installer can offer carries a licence**, meaning both a `license` frontmatter key and a `LICENSE.txt` in the directory, because a copied directory is the whole of what the recipient gets. Only an internal skill is exempt, and only because nobody receives it. +**Every skill carries a licence**, meaning both a `license` frontmatter key and a `LICENSE.txt` in the directory, because a copied directory is the whole of what the recipient gets. Nothing is exempt, internal skills included. -There is no `public` marker, because public is the absence of `internal`, and the Agent Skills specification defines no visibility field at all. `metadata` is its designated free-form map, and `internal` is the one key an installer actually reads. Note that `gh skill` documents no visibility concept, so `metadata.internal` may not hide anything from it; repository visibility is the only lever there. +There is no `public` marker, because public is the absence of `internal`, and the Agent Skills specification defines no visibility field at all. `metadata` is its designated free-form map, and `internal` is the one key an installer actually reads, which is why the state does not survive every installer. `gh skill` reads none of it: `gh skill install / --allow-hidden-dirs` installs any of the six here by name, and the same command without the name lists all six. Repository visibility is the only lever there, and the licence on every skill is what keeps that harmless. That flag is also what makes the skills visible at all, since `gh skill` skips `.claude/` as a hidden directory; `npx skills` clones the repository and reads `.claude/skills/` without one. **Nothing is vendored into this repository.** A third-party skill is fetched when wanted with `npx skills add / --skill `, rather than copied in and then maintained. diff --git a/.claude/scripts/check-skill-publishability.mjs b/.claude/scripts/check-skill-publishability.mjs index 75c3929..b51fbc9 100644 --- a/.claude/scripts/check-skill-publishability.mjs +++ b/.claude/scripts/check-skill-publishability.mjs @@ -21,10 +21,13 @@ const SKILL_DIR = join(REPO_ROOT, '.claude', 'skills'); * Skills published for use outside this repository. Only these are held to the agnosticism * bar, because a skill written for this repository alone may name this repository's paths. * - * Every other skill is either `installable`, meaning an installer can offer it and it must - * therefore carry a licence, or `internal`, meaning `metadata.internal: true` hides it from - * discovery. Naming the middle state is the point: a skill that is nothing in particular - * drifts into being offered to strangers with no licence attached. + * Every other skill is either `installable`, meaning an installer offers it without holding it + * to that bar, or `internal`, meaning `metadata.internal: true` keeps it out of `npx skills` + * discovery. Naming the middle state is the point: a skill that is nothing in particular drifts + * into being offered to strangers with nobody having decided that it should be. + * + * The licence rules below apply to all three, because `gh skill` reads no visibility field and + * offers an internal skill as readily as a published one. */ const PUBLISHED = ['audit-docs', 'audit-pr', 'typescript-code-and-test-standards']; @@ -166,18 +169,22 @@ function checkSkill(name) { } } - // An internal skill is hidden from installers, so nobody receives it and the rules below - // about what a recipient gets do not apply. - if (isInternal(parts.frontmatter)) { - return; - } - + // `metadata.internal` buys no exemption here. `gh skill` reads no visibility field, so it + // lists and installs every skill in this directory, and a copied directory is the whole of + // what its recipient gets. if (!frontmatterValue(parts.frontmatter, 'license')) { - fail(label, 'an installer can offer this skill, so it needs a license key or metadata.internal'); + fail(label, 'an installer can offer any skill here, so it needs a license key'); } if (!existsSync(join(SKILL_DIR, name, 'LICENSE.txt'))) { - fail(label, 'an installer can offer this skill, so it needs a LICENSE.txt beside it'); + fail(label, 'an installer can offer any skill here, so it needs a LICENSE.txt beside it'); + } + + // An internal skill names this repository's own prompt files on purpose, so the isolation + // rule below, which exists to keep a recipient from following a path they will not have, + // is the one thing it is exempt from. + if (isInternal(parts.frontmatter)) { + return; } for (const file of skillFiles(name)) { diff --git a/.claude/skills/check-skills/LICENSE.txt b/.claude/skills/check-skills/LICENSE.txt new file mode 100644 index 0000000..7a0e634 --- /dev/null +++ b/.claude/skills/check-skills/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2026 Alexander Joo-Hyun Sullivan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.claude/skills/check-skills/SKILL.md b/.claude/skills/check-skills/SKILL.md index a27d086..9faad8c 100644 --- a/.claude/skills/check-skills/SKILL.md +++ b/.claude/skills/check-skills/SKILL.md @@ -2,6 +2,7 @@ name: check-skills description: Validates every skill against the Agent Skills specification and checks that each published audit's prompt half and skill half still work alone, then delegates any semantic divergence to the prompt-skill-sync subagent. Use after editing any file under .github/prompts/ or .claude/skills/. argument-hint: '[skill name to focus on; omit to check everything]' +license: MIT metadata: internal: true disable-model-invocation: true @@ -18,7 +19,7 @@ Three audits ship twice: `.github/prompts/.prompt.md` for an agent that re make -f .claude/Makefile check-skills ``` -It decides everything a machine can: `name` matching the directory, `description` within its character limit, a body under 500 lines, a licence on every published skill, every bundled path resolving, no skill naming a prompt, and no prompt naming a file that will not travel with it. +It decides everything a machine can: `name` matching the directory, `description` within its character limit, a body under 500 lines, a licence on every skill, every bundled path resolving, no skill naming a prompt, and no prompt naming a file that will not travel with it. Exit 0 means the mechanical rules hold. It does **not** mean the two halves still agree. diff --git a/.claude/skills/write-tests/LICENSE.txt b/.claude/skills/write-tests/LICENSE.txt new file mode 100644 index 0000000..7a0e634 --- /dev/null +++ b/.claude/skills/write-tests/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2026 Alexander Joo-Hyun Sullivan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.claude/skills/write-tests/SKILL.md b/.claude/skills/write-tests/SKILL.md index 1403584..f6cd4ca 100644 --- a/.claude/skills/write-tests/SKILL.md +++ b/.claude/skills/write-tests/SKILL.md @@ -1,6 +1,7 @@ --- name: write-tests description: Author or repair a Jest or Cypress test in this repository's house style. Use when adding a test, when a source change needs coverage, or when a test is failing and needs a root-cause fix rather than a weakened assertion. +license: MIT metadata: internal: true --- diff --git a/.github/prompts/readme.md b/.github/prompts/readme.md index df763b1..ea879d9 100644 --- a/.github/prompts/readme.md +++ b/.github/prompts/readme.md @@ -43,20 +43,21 @@ With [`npx skills`](https://github.com/vercel-labs/skills), from Vercel Labs: npx skills add AlexJSully/AlexJSully-Portfolio # pick from a list npx skills add AlexJSully/AlexJSully-Portfolio --skill audit-docs # or name one npx skills add AlexJSully/AlexJSully-Portfolio --all # or take every one -npx skills check # which have updates -npx skills update # take them +npx skills update # take later changes ``` -`list` and `remove` manage what you already have. +It clones the repository and reads the default branch, so what you get is the current state of `main`. `list` and `remove` manage what you already have. -With [`gh skill`](https://cli.github.com/manual/gh_skill), from the GitHub CLI, version 2.90.0 or later and in public preview: +With [`gh skill`](https://cli.github.com/manual/gh_skill), from the GitHub CLI, in public preview. The skills sit in this repository's `.claude/skills/`, a hidden directory that `gh skill` skips unless told to include it, so every command below carries `--allow-hidden-dirs`. That flag puts the floor at version 2.91.0. Given no version, `gh skill` resolves the newest tagged release rather than the default branch, and a release carries only the skills present at its tag, so `--pin` is what reaches any other commit: ```bash -gh skill install AlexJSully/AlexJSully-Portfolio --skill audit-pr -gh skill install AlexJSully/AlexJSully-Portfolio --skill audit-pr --pin -gh skill update +gh skill install AlexJSully/AlexJSully-Portfolio audit-pr --allow-hidden-dirs # newest release +gh skill install AlexJSully/AlexJSully-Portfolio audit-pr --allow-hidden-dirs --pin # a chosen ref +gh skill install AlexJSully/AlexJSully-Portfolio audit-pr --allow-hidden-dirs --force # overwrite what is installed ``` +Three things differ from `npx skills`. The skill name is positional rather than a `--skill` value. `--pin` takes a git tag or a commit SHA, which is how to ask for a ref the newest release predates. And `gh skill update` accepts no `--allow-hidden-dirs` of its own; where it does not pick up a change, re-running `install --force` does. Expect a warning that skills in a hidden directory may be copies from another publisher: this repository is where these ones are written. + Both target Claude Code, Copilot, Cursor, Codex, and Gemini CLI. `gh skill` installs for Copilot by default and reaches the others through `--agent`. **Resolving the `#` references.** Some hosts resolve `#codebase`, `#changes`, and the rest automatically; the ones that do not need a **context resolution** table, which maps each reference to the command to run instead. `audit-pr` and `audit-quality` carry that table in both halves. `audit-docs` carries it in the skill half only, since every host that reads a prompt file resolves those three itself. Which references appear varies: all three use `#codebase` and `#changes`, `audit-docs` and `audit-pr` add `#activePullRequest`, `audit-quality` adds `#file:path`, and `audit-pr` alone adds `#issue_fetch`. @@ -67,12 +68,12 @@ One more is published from the same place and has no prompt half, because it is ```bash npx skills add AlexJSully/AlexJSully-Portfolio --skill typescript-code-and-test-standards -gh skill install AlexJSully/AlexJSully-Portfolio --skill typescript-code-and-test-standards +gh skill install AlexJSully/AlexJSully-Portfolio typescript-code-and-test-standards --allow-hidden-dirs ``` `typescript-code-and-test-standards` loads while you write rather than after, carrying the TypeScript and JavaScript rules a formatter and a linter cannot check: comment discipline, documentation on every exported symbol, tests shipping alongside logic changes, and a mocking policy whose default is not to mock. It reads the host project's own Prettier, ESLint, and test-runner configuration instead of imposing one, and activates on `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.mts`, and `.cts`. It pairs with `audit-quality` rather than overlapping it: one applies as the code is written, the other audits it once it exists. -Two further skills live in that directory carrying `metadata.internal`, so no installer offers them and `--all` skips them. They drive this repository's own tooling and would do nothing in yours. +Two further skills live in that directory carrying `metadata.internal`. What that hides depends on the installer: `npx skills` reads the key and offers four skills, while `gh skill` reads no visibility field at all and lists all six, so `--all` there takes the other two as well. They drive this repository's own tooling and would do nothing in yours, though they carry the same MIT licence as the rest, so nothing arrives unlicensed. ## Keeping the two halves honest @@ -82,7 +83,7 @@ Only relevant if you keep both. Since they are no longer identical, a diff canno make -f .claude/Makefile check-skills # the mechanical rules, exits 0 when they hold ``` -That decides what a machine can: the frontmatter against the [Agent Skills specification](https://agentskills.io/specification), a licence on every published skill, every bundled path resolving, and each half naming nothing it will not ship with. +That decides what a machine can: the frontmatter against the [Agent Skills specification](https://agentskills.io/specification), a licence on every skill, every bundled path resolving, and each half naming nothing it will not ship with. Whether both halves still aim at the same outcome is a judgement, so it goes to a subagent that reads both, inventories the hard rules in each, and classifies every difference as bundled depth, a host fallback, or a real divergence. The failure worth catching is a rule that exists in the skill and not the prompt, which is a silent downgrade for everyone holding the prompt, and which passes the mechanical check cleanly. diff --git a/CHANGELOG.MD b/CHANGELOG.MD index ccfea23..5063711 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -6,6 +6,36 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p To see tags and releases, please go to [Releases](https://github.com/AlexJSully/AlexJSully-Portfolio/releases) on [GitHub](https://github.com/AlexJSully/AlexJSully-Portfolio). +## [2.0.14] - 2026-08-03 + +Licence: + +- Switched from GPL-3.0 to MIT + +Feature: + +- Added an ultra-wide `xxl` breakpoint that gives the projects grid more columns at 2560px and above + +UI/UX: + +- Updated the Bluesky link to the current handle + +AI tooling: + +- Added `typescript-code-and-test-standards`, a skill carrying the TypeScript and JavaScript rules a formatter and a linter cannot check +- Published `audit-docs`, `audit-pr` and `audit-quality` as installable Agent Skills, each paired with a Copilot prompt file +- Published every skill under MIT so it can be installed into another repository + +Documentation: + +- Fixed the documentation failing to deploy to GitHub Pages +- Rewrote the architecture and usage documentation to match the code + +Bug fix: + +- Fixed a runtime error caused by a deprecated Sentry package +- Fixed the web manifest returning 404 + ## [2.0.13] - 2025-10-19 Feature: diff --git a/CLAUDE.md b/CLAUDE.md index e037b6c..b67b318 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,6 @@ The conventions live in two layers. The generic set (comment discipline, JSDoc, - Each [`.github/prompts/`](.github/prompts/readme.md) file ships twice: as a single prompt file and as a skill directory. The two carry the **same objective, not the same bytes**, because only the skill can bundle `references/`, `agents/`, and `assets/`. After editing either half, run `make -f .claude/Makefile check-skills` and hand both halves to the `prompt-skill-sync` subagent (see [`prompt-skill-sync.md`](.claude/rules/prompt-skill-sync.md)). Each half is downloaded alone: the prompt names nothing beside it, the skill names nothing outside itself, and neither names a sibling audit or this repository. - Skills: `/audit-docs`, `/audit-pr`, and `/audit-quality` (the paired audits); `/write-tests` (repo procedure for authoring a test); `/check-skills` (validate the skills and their prompt halves); and `typescript-code-and-test-standards`, which auto-loads on TypeScript and JavaScript files. Plus the built-in `/code-review` and `/security-review`. -- Skills carry one of **three states**, which `make -f .claude/Makefile check-skills` prints and enforces. **Published** (`audit-docs`, `audit-pr`, `typescript-code-and-test-standards`) are used outside this repository, so they stay codebase-agnostic and, apart from the TypeScript one, language-agnostic. **Installable** (`audit-quality`) can be offered by an installer but is not held to that bar. **Internal** (`check-skills`, `write-tests`) set `metadata: internal: true`, which hides them from `npx skills` discovery. The rule tying it together: anything an installer can offer carries a `license` key and a `LICENSE.txt`, because a copied directory is all the recipient gets. Nothing is vendored here; a third-party skill is fetched on demand with `npx skills add / --skill `. +- Skills carry one of **three states**, which `make -f .claude/Makefile check-skills` prints and enforces. **Published** (`audit-docs`, `audit-pr`, `typescript-code-and-test-standards`) are used outside this repository, so they stay codebase-agnostic and, apart from the TypeScript one, language-agnostic. **Installable** (`audit-quality`) can be offered by an installer but is not held to that bar. **Internal** (`check-skills`, `write-tests`) set `metadata: internal: true`, which hides them from `npx skills` discovery but not from `gh skill`, which reads no visibility field and offers all six. The rule tying it together: every skill carries a `license` key and a `LICENSE.txt`, because a copied directory is all the recipient gets and the state cannot be relied on to stop the copy. Nothing is vendored here; a third-party skill is fetched on demand with `npx skills add / --skill `. - Subagents: `validator` runs the local quality gates in its own context and returns a verdict instead of several thousand lines; `prompt-skill-sync` judges whether a published audit's two halves still aim at the same outcome, repairs a divergence, and returns a verdict instead of two long files. - Hooks ([`.claude/hooks/`](.claude/hooks/validate-gate.mts)): `markdown-audit-reminder` restates the doc-authoring rules whenever you edit a markdown file; `prompt-skill-sync` names the counterpart when you edit either half of a published audit; `validate-gate` tracks which gates have run and blocks the first attempt to finish while any are outstanding. diff --git a/package-lock.json b/package-lock.json index fa41118..fd49498 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "alexjsully-portfolio", - "version": "2.0.13", + "version": "2.0.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "alexjsully-portfolio", - "version": "2.0.13", + "version": "2.0.14", "license": "MIT", "dependencies": { "@emotion/react": "^11.14.0", @@ -11570,9 +11570,9 @@ } }, "node_modules/import-in-the-middle": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.1.tgz", - "integrity": "sha512-0rymlHSFLwZ0ixx8DaQkoIyZojJPY2a0K2nEYslhKJ6jIYO/m0IcCb7iQsFPmS7WmKwISZiIrv5Icstrw/CmqA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.2.tgz", + "integrity": "sha512-jTd2FfOgOWOdgjkHuk/1Ms8VKFXkPs15ymYBETw1sAOrO/dY3XeGVRWir9qBbw7pXr0T2eTFwfCZ+N02HmiNGA==", "license": "Apache-2.0", "dependencies": { "cjs-module-lexer": "^2.2.0", diff --git a/package.json b/package.json index 72d46ea..776e463 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "https://alexjsully.me/" } ], - "version": "2.0.13", + "version": "2.0.14", "private": true, "license": "MIT", "description": "Portfolio and showcase for Alexander Joo-Hyun Sullivan and related projects & experiences.",