Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/rules/prompt-skill-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Comment thread
AlexJSully marked this conversation as resolved.

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 <owner>/<repo> <skill> --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 <owner>/<repo> --skill <name>`, rather than copied in and then maintained.

Expand Down
31 changes: 19 additions & 12 deletions .claude/scripts/check-skill-publishability.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'];

Expand Down Expand Up @@ -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)) {
Expand Down
21 changes: 21 additions & 0 deletions .claude/skills/check-skills/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion .claude/skills/check-skills/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,7 +19,7 @@ Three audits ship twice: `.github/prompts/<name>.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.

Expand Down
21 changes: 21 additions & 0 deletions .claude/skills/write-tests/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions .claude/skills/write-tests/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
---
Expand Down
21 changes: 11 additions & 10 deletions .github/prompts/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <tag-or-commit>
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 <tag-or-commit> # 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`.
Expand All @@ -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.
Comment thread
AlexJSully marked this conversation as resolved.

## Keeping the two halves honest

Expand All @@ -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.

Expand Down
30 changes: 30 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading