From 50be338fed99f074ea7857780061e547a655c9c8 Mon Sep 17 00:00:00 2001 From: Alexander Sullivan Date: Fri, 7 Aug 2026 14:35:03 -0400 Subject: [PATCH 1/2] fix --- .claude/rules/prompt-skill-sync.md | 23 ++- .../scripts/check-skill-publishability.mjs | 102 +++++++++- .../audit-docs/.claude-plugin/plugin.json | 5 + .claude/skills/audit-docs/SKILL.md | 28 +-- .../audit-docs/agents/coverage-mapper.md | 6 +- .../audit-docs/agents/surface-auditor.md | 15 +- .../audit-pr/.claude-plugin/plugin.json | 5 + .claude/skills/audit-pr/SKILL.md | 4 +- .../.claude-plugin/plugin.json | 5 + .../SKILL.md | 19 +- .github/copilot-instructions.md | 2 +- .github/prompts/audit-docs.prompt.md | 6 +- .github/prompts/audit-pr.prompt.md | 2 +- .github/prompts/readme.md | 2 +- CLAUDE.md | 2 +- package-lock.json | 181 +++++++++--------- package.json | 8 +- 17 files changed, 283 insertions(+), 132 deletions(-) create mode 100644 .claude/skills/audit-docs/.claude-plugin/plugin.json create mode 100644 .claude/skills/audit-pr/.claude-plugin/plugin.json create mode 100644 .claude/skills/typescript-code-and-test-standards/.claude-plugin/plugin.json diff --git a/.claude/rules/prompt-skill-sync.md b/.claude/rules/prompt-skill-sync.md index 9bc4ae5..9c02ab4 100644 --- a/.claude/rules/prompt-skill-sync.md +++ b/.claude/rules/prompt-skill-sync.md @@ -17,7 +17,7 @@ Three audits ship twice, once as a skill directory and once as a single prompt f | [`audit-pr.prompt.md`](../../.github/prompts/audit-pr.prompt.md) | [`audit-pr/`](../skills/audit-pr/SKILL.md) | | [`audit-quality.prompt.md`](../../.github/prompts/audit-quality.prompt.md) | [`audit-quality/`](../skills/audit-quality/SKILL.md) | -Two audiences drive this. Someone whose employer allows a single file in the repository takes the prompt. Someone who can install a directory takes the skill, and gets the bundled `references/`, `agents/`, and `assets/` with it. +Two audiences drive this. Someone whose employer allows a single file in the repository takes the prompt. Someone who can install a directory takes the skill, and gets the bundled `references/`, `agents/`, and `assets/` with it, subject to the caveat in [the plugin manifest section](#the-plugin-manifest-and-what-it-does-not-change) about which of those the specification actually covers. ## The contract: same objective, not same bytes @@ -52,6 +52,27 @@ An illustrative link, such as `[config.py](../src/config.py)` inside an example `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 plugin manifest, and what it does not change + +A skill directory containing `.claude-plugin/plugin.json` loads as a plugin named `@skills-dir` on the next session, with no marketplace and no install step, and that is what turns the files in `agents/` into agents a run can delegate to. Without it they stay ordinary files, which is what each `SKILL.md` already treats as the default when it tells the run to open one and follow it: `agents/` is a host extension, not part of the Agent Skills specification, which defines `references/`, `assets/`, and `scripts/` and nothing else. + +**The manifest is an optimization, never a dependency.** Every bundled procedure is written to be run by opening its file, and each `SKILL.md` says so before it mentions delegating, because a skill that tells an agent to delegate to something the host never registered has no documented fallback: the call fails and the run improvises. An improvised prompt carries none of the scope bound or evidence bar written inside the procedure, which is the whole reason the file exists. + +Two consequences to know before editing either half: + +- **The delegation identifier is namespaced**, as `@skills-dir:`. A bare agent name never resolves. Write neither form into a published skill: naming the file and letting the run resolve the identifier is what keeps the instruction true on a host that spells it differently. +- **Nothing documents whether `agents/` or `.claude-plugin/` survive `npx skills add` or `gh skill install`**, since neither is in the specification. Test an install rather than assuming, and treat opening the file as the path that has to work. + +[`check-skill-publishability.mjs`](../scripts/check-skill-publishability.mjs) validates a manifest where one exists: that it parses, that its `name` matches the directory, that it carries a `version`, and that any path in an `agents` key resolves. It does not require one. + +## A published skill stays reachable by name + +**A published skill carries neither `user-invocable: false` nor `paths:`.** An adopter installs the directory and has the name on it and nothing else, so a key narrowing who may reach the skill, or when it activates, takes away the only handle they have. `user-invocable: false` hides it from the `/` menu outright. `paths:` is documented as limiting "when this skill is activated", and a skill carrying it did not answer to its own name here, though the documentation does not describe what happens when the command is typed while no matching file is open. + +Where a project wants a skill to load automatically on certain files, the path glob belongs on a rules file, which is where [`code-style.md`](code-style.md) carries one. That mechanism stays inside the project and leaves the skill reachable everywhere. + +An installable or internal skill may use both keys freely, and [`check-skill-publishability.mjs`](../scripts/check-skill-publishability.mjs) enforces this only against the `PUBLISHED` list. Nothing in the specification defines either key, so this is a policy of this repository rather than a rule of the format. + ## The three states Every skill is in exactly one, and [`check-skill-publishability.mjs`](../scripts/check-skill-publishability.mjs) prints which. diff --git a/.claude/scripts/check-skill-publishability.mjs b/.claude/scripts/check-skill-publishability.mjs index b51fbc9..81f4953 100644 --- a/.claude/scripts/check-skill-publishability.mjs +++ b/.claude/scripts/check-skill-publishability.mjs @@ -37,9 +37,24 @@ const MAX_BODY_LINES = 500; /** The spec caps `description` at 1024 characters, because it loads at startup. */ const MAX_DESCRIPTION = 1024; -/** Directories a skill may bundle, per the Agent Skills specification. */ +/** + * Directories a skill may bundle. The specification defines `references/`, `assets/`, and + * `scripts/`; `agents/` is a host extension, read only where a plugin manifest turns the + * directory into a plugin, and inert everywhere else. + */ const BUNDLE_DIRS = ['references', 'agents', 'assets', 'scripts']; +/** + * The manifest that makes a skill directory load as a plugin, so the files in `agents/` register + * as agents a run can delegate to instead of sitting there as unread text. + * + * It is optional, and a skill without one is not at fault: every bundled procedure is written to + * be followed by opening its file, which needs no manifest and no host support. What this path + * is checked for is the failure that hides, namely a manifest whose name disagrees with the + * directory, which registers the plugin under a name nothing refers to. + */ +const PLUGIN_MANIFEST = join('.claude-plugin', 'plugin.json'); + const failures = []; /** Records one failure against a file. */ @@ -180,6 +195,9 @@ function checkSkill(name) { fail(label, 'an installer can offer any skill here, so it needs a LICENSE.txt beside it'); } + checkPluginManifest(name); + checkInvocable(name, parts.frontmatter); + // 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. @@ -196,7 +214,83 @@ function checkSkill(name) { } } -/** Every Markdown file inside a skill, one level of bundle directory deep. */ +/** + * Checks that a published skill can still be invoked by name. + * + * This is a policy of this repository rather than a rule of the specification, which defines + * neither key. A published skill is one an adopter installs holding nothing but the directory + * and the name on it, so a key that narrows when it activates, or who may reach it, takes away + * the only handle that adopter has. An installable or internal skill is free to use both. + * + * @param {string} name Directory name of the skill under `.claude/skills/`. + * @param {string} frontmatter The skill's frontmatter block. + */ +function checkInvocable(name, frontmatter) { + if (!PUBLISHED.includes(name)) { + return; + } + + const label = `.claude/skills/${name}/SKILL.md`; + + if (/^user-invocable:[ \t]*(false|no|off|0)\s*$/im.test(frontmatter)) { + fail(label, 'a published skill may not set `user-invocable: false`, which hides it from the / menu'); + } + + if (/^paths:/m.test(frontmatter)) { + fail( + label, + 'a published skill may not carry `paths:`, which limits when it activates; path-scope a rules file instead', + ); + } +} + +/** + * Checks a skill's plugin manifest, where it has one. A skill without one is skipped silently. + * + * @param {string} name Directory name of the skill under `.claude/skills/`. + */ +function checkPluginManifest(name) { + const manifestPath = join(SKILL_DIR, name, PLUGIN_MANIFEST); + const label = `.claude/skills/${name}/${PLUGIN_MANIFEST}`; + + if (!existsSync(manifestPath)) { + return; + } + + let manifest; + + try { + manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); + } catch (error) { + fail(label, `does not parse as JSON: ${error.message}`); + + return; + } + + if (manifest.name !== name) { + fail(label, `name "${manifest.name}" does not match the directory name "${name}"`); + } + + if (!manifest.version) { + fail(label, 'no version, which a host uses to tell one loaded copy from another'); + } + + // A manifest may point `agents` at somewhere other than the default directory. Either way the + // paths it names travel with the skill, so a broken one breaks in the recipient's copy. + const declaredAgents = manifest.agents ? [manifest.agents].flat() : []; + + for (const target of declaredAgents) { + if (!existsSync(join(SKILL_DIR, name, target))) { + fail(label, `declares agent "${target}", which does not exist in the skill directory`); + } + } +} + +/** + * Every file inside a skill that travels with it and could name a path: Markdown one level of + * bundle directory deep, plus the plugin manifest. The manifest carries a `description`, so it + * can name a prompt file exactly as a body can, and it ships in the copied directory either way. + */ function skillFiles(name) { const root = join(SKILL_DIR, name); const files = readdirSync(root).filter((file) => file.endsWith('.md')); @@ -209,6 +303,10 @@ function skillFiles(name) { files.push(...readdirSync(join(root, dir)).map((file) => `${dir}/${file}`)); } + if (existsSync(join(root, PLUGIN_MANIFEST))) { + files.push(PLUGIN_MANIFEST); + } + return files; } diff --git a/.claude/skills/audit-docs/.claude-plugin/plugin.json b/.claude/skills/audit-docs/.claude-plugin/plugin.json new file mode 100644 index 0000000..8c1cf7a --- /dev/null +++ b/.claude/skills/audit-docs/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "audit-docs", + "version": "1.0.0", + "description": "Audit and update the project's documentation so it matches the current code, grounding every claim in a file opened this run." +} diff --git a/.claude/skills/audit-docs/SKILL.md b/.claude/skills/audit-docs/SKILL.md index 96ad93d..4171af1 100644 --- a/.claude/skills/audit-docs/SKILL.md +++ b/.claude/skills/audit-docs/SKILL.md @@ -40,19 +40,21 @@ Open one of these when the run needs its detail. Nothing here is loaded until yo - [`diagram-and-image-accessibility.md`](references/diagram-and-image-accessibility.md) - worked `accTitle` and `accDescr` examples, and how to choose a diagram type. Read before adding or editing a diagram. - [`audit-report.template.md`](assets/audit-report.template.md) - the report shape for the end of the run. -## Bundled subagents, and when to dispatch one +## Bundled procedures, and when to run one -Five subagents ship with this skill. **The default is to dispatch none of them.** Each one is for work the main run cannot afford to do itself, and the scope rule below bounds what any of them receives: on a pull request they see the changed set, not the tree. A small pull request should reach for nothing here. +Five procedures ship with this skill, one per file under `agents/`. **The default is to run none of them.** Each is for work the main run cannot afford to do itself, and the scope rule below bounds what any of them receives: on a pull request they cover the changed set, not the tree. A small pull request should reach for nothing here. -| Subagent | Dispatch when | Skip when | -| -------------------------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------- | -| [`coverage-mapper`](agents/coverage-mapper.md) | scope is the whole documentation set, or the change adds or removes a component | scope is a single named document | -| [`curation-reviewer`](agents/curation-reviewer.md) | a document was substantially rewritten, or is consumer-facing | only a factual correction was made | -| [`diagram-reviewer`](agents/diagram-reviewer.md) | a document contains a diagram, or describes a flow of five or more steps | neither is true | -| [`surface-auditor`](agents/surface-auditor.md) | the change touches a public surface, or code in scope carries comments this run did not write | the change is documentation-only | -| [`claim-verifier`](agents/claim-verifier.md) | a claim you cannot ground from a file already open | the proof is already in hand | +| Procedure | Run it when | Skip it when | +| -------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------- | +| [`coverage-mapper`](agents/coverage-mapper.md) | scope is the whole documentation set, or the change adds or removes a component | scope is a single named document | +| [`curation-reviewer`](agents/curation-reviewer.md) | a document was substantially rewritten, or is consumer-facing | only a factual correction was made | +| [`diagram-reviewer`](agents/diagram-reviewer.md) | a document contains a diagram, or describes a flow of five or more steps | neither is true | +| [`surface-auditor`](agents/surface-auditor.md) | the change touches a public surface | the change is documentation-only | +| [`claim-verifier`](agents/claim-verifier.md) | a claim you cannot ground from a file already open | the proof is already in hand | -Each returns findings rather than edits, so the decision to act on any of them stays with this run. +**Open the file and follow it yourself.** That is the way to run one, it works wherever this skill is installed, and it cannot fail. Where your host registers these files as agents you can delegate to, handing one off is an option that keeps the reading out of this context. Where delegating is unavailable, names an agent the host does not recognize, or errors, fall back to opening the file. **Never improvise instructions from a procedure's name or from this table's one-line summary of it.** What makes a procedure safe to run is the scope bound and the evidence bar written inside it, and neither survives being paraphrased. + +Each procedure reports findings rather than edits, so every decision stays with this run. **A returned finding is a lead to verify, never a source to publish from.** It names a file and a symbol for you to open, and Rule 2 governs from there: nothing built on a returned summary is grounded until you have opened that file yourself. A summary is not a source, whoever produced it. --- @@ -69,7 +71,7 @@ Each returns findings rather than edits, so the decision to act on any of them s ### Phase 2: general audit - **Inventory before you correct.** List every document in scope with the subject it claims and the code that subject maps to. The three actions below are undecidable without that list: duplication is visible only across documents, a removed feature only where a document's subject is absent from the code, and a missing document only as code with no entry. Report how many documents you opened, and name anything in scope you did not, so that "already accurate" cannot be confused with "not looked at". -- Audit all of `docs/` against the current #codebase. **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. +- Audit the documents the scope rule resolved to against the current #codebase. That is all of `docs/` only where the rule resolved to the whole documentation set, and on a pull request it is the documents describing the changed code. **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. - **Delete** pre-existing content only if it is massively duplicated, describes removed features, or fundamentally cannot be corrected. Default to correcting, not deleting. Your own generated content may be edited or removed freely when wrong. - **Create new files** only when needed: check the existing structure first and reuse a home when one fits; for a genuinely new directory apply the **Diátaxis** framework (Tutorials, How-To Guides, Reference, Explanation); create for new components/systems, external API guides, or missing structures. - **Output:** state whether you made changes or found docs already accurate. @@ -78,13 +80,13 @@ Each returns findings rather than edits, so the decision to act on any of them s **Mandatory.** Execute regardless of Phase 1 and 2 results. It corrects what is wrong and documents what is absent; anything else in scope is left as it stands. -- **Scope:** every `.md` file outside `docs/`, plus documentation comments, inline comments, and file-level headers across the code the scope rule above resolved to. +- **Scope:** the code the scope rule above resolved to, covering its documentation comments, inline comments, and file-level headers, plus every `.md` file inside that scope which sits outside `docs/`. A file the rule did not resolve to stays out whatever it contains, so this phase is never a repository-wide sweep for markdown, for undocumented symbols, or for a comment pattern. - **Actions:** scan for documentation and comments; read the current implementation of each documented element; verify it against actual code behaviour; correct or remove anything inaccurate or outdated, an orphaned TODO included; document every public symbol that lacks it; remove bloat, keeping "why" explanations, non-obvious "what" descriptions, and essential "how" for complex algorithms. Removing bloat means deleting comments that restate the code, never comments that explain a non-obvious internal. - **Always document the public surface.** Every public or exported symbol carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Write for a reader meeting the symbol for the first time, assuming they can infer nothing from its name. Reach for what the declaration cannot express, such as why it exists, a constraint, an invariant, or a caller obligation. Where no such explanation exists, a plain restatement of what the symbol does is correct: being obvious is not a defect on a public surface, being absent is. **Rule 2 still governs, and it comes first.** Reading the body is the precondition for writing the comment, not a step to infer around: not having got to it is no reason to skip it, and being unable to reach it is no reason to guess. Where you have not read the body, leave the symbol as it is and name it in your output. A public symbol left undocumented and reported is a compliant result; a comment written from the symbol's name is a defect, and it is the defect this rule exists to prevent. - **Do not restate what the language's own syntax declares**, such as a type, a visibility modifier, or an override marker. This governs what you write in a **new** documentation comment and never licenses removing an existing one. - **Correct an existing documentation tag; do not strip or delete it.** A parameter, return, throws, or example entry was written deliberately. Read enough surrounding code to judge it, then fix what is factually wrong and leave what is right, including parts a convention would omit in new code. Removing a tag, or a piece of one, because it looks redundant is restyling someone else's work, not auditing it. Delete a whole tag only when it is wrong and uncorrectable, such as one documenting a parameter the signature no longer has. Phase 2's "default to correcting, not deleting" governs in-code documentation too. - **Internal elements** are documented only where the logic is complex or carries a gotcha or edge case, and a comment inside a function body is written only for non-obvious business logic, a workaround, or a complex transformation. Delete an internal comment only when it restates the line beneath it, such as `// Increment counter` above a counter increment (delete the comment, keep the code). -- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. Where a comment naming a symbol already sits above a line that uses that symbol, and it says what the declaration's comment says, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way; leave both in place and report it only where evidence in the opened implementation does not establish whether the usage-site copy adds a distinct constraint. _Bad:_ `isBetaEnabled mirrors the beta-features flag` above every read of `isBetaEnabled`. _Good:_ that sentence once, on the declaration of `isBetaEnabled`, and nothing at the read sites. +- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. **Removing a copy is bounded, and all three preconditions hold before anything is deleted:** the declaration and the usage site both sit inside the scope this run resolved, so the rule never reaches a file the scope rule did not resolve to; the declaration's body has been opened this run, since a copy cannot be judged redundant against a declaration nobody read; and the copy says no more than the declaration's comment says. With all three met, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way. Failing any one of the three, leave both in place and report it: a repetition left alone costs a reader one duplicated sentence, where a wrong deletion destroys the only place a constraint was written down. _Bad:_ `isBetaEnabled mirrors the beta-features flag` above every read of `isBetaEnabled`. _Good:_ that sentence once, on the declaration of `isBetaEnabled`, and nothing at the read sites. - **Comments describe the code as it stands.** Never narrate a change, a fix, or a prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time", "unlike the old"), and never name a file, flag, or tool that no longer exists: version control carries that, and the comment outlives the change that prompted it. Never argue that the code is correct or safe, which documents the edit rather than the code. Delete commented-out code rather than leaving it in place. - **Form:** a documentation comment is a complete sentence, capitalized and punctuated; a short trailing comment may be a fragment. Wrap long comment lines to the width the file already uses, letting an unbreakable URL exceed it. Use the documentation format's own list syntax for enumerations, since indented plain text collapses into one run-on sentence when rendered. Never box a comment in asterisks or other decorative characters. Documentation precedes an annotation or decorator and never sits between it and the declaration. - **Contracts worth stating:** any cleanup the caller owns (a handle to close, a listener to remove, a subscription to cancel), the error values or exception types a caller can branch on, and a deprecation marker naming its replacement. A deprecation without migration directions is incomplete; add one only where it is provable under Rule 2. diff --git a/.claude/skills/audit-docs/agents/coverage-mapper.md b/.claude/skills/audit-docs/agents/coverage-mapper.md index f22494d..167a367 100644 --- a/.claude/skills/audit-docs/agents/coverage-mapper.md +++ b/.claude/skills/audit-docs/agents/coverage-mapper.md @@ -9,7 +9,11 @@ This agent walks the documentation in scope and the code that documentation desc ## Input the agent receives -The caller supplies the scope it already resolved: a set of documents, or a whole documentation tree, plus the code area that documentation describes. Nothing else. The agent does not receive the purpose of the audit, the report being drafted, or a list of suspected problems, and it does not ask for them. It does not widen what it was handed, not to a neighbouring directory and not to a document that a document in scope links to. Where the scope is a pull request, the map covers the documents touching the changed code and stops there, never the tree those documents sit in. +The caller supplies the scope it already resolved, as **two explicit lists of paths**: the documents to walk, and the code those documents describe. Nothing else. + +**A name is not a scope.** A topic, a subsystem, a feature, or a layer describes what the caller wants; turning one into files means running a search, and a search returns what matches the string rather than what the caller selected. Those two sets come apart at every place in the tree that reuses the word, and the difference is code nobody chose, which this pass would then map, judge, and report as though it had been. A caller handed the word `cache` may mean one module and get four. The caller's own scope rule may well begin from an area, and resolving that area into paths is the caller's work, not this pass's. Where what arrives is a name rather than paths, return the empty map with that stated in the counts, and let the caller resolve it. + +The agent does not receive the purpose of the audit, the report being drafted, or a list of suspected problems, and it does not ask for them. It does not widen what it was handed, not to a neighbouring directory and not to a document that a document in scope links to. Where the scope is a pull request, the map covers the documents touching the changed code and stops there, never the tree those documents sit in. ## Walk each document once diff --git a/.claude/skills/audit-docs/agents/surface-auditor.md b/.claude/skills/audit-docs/agents/surface-auditor.md index 9bb930d..4fa681e 100644 --- a/.claude/skills/audit-docs/agents/surface-auditor.md +++ b/.claude/skills/audit-docs/agents/surface-auditor.md @@ -9,7 +9,11 @@ This agent walks the code the caller's scope resolved to one time and returns th ## Input the agent receives -One field arrives: the code area the caller's scope resolved to, as paths or as an area name. Nothing else. The agent does not widen that scope, does not follow an import out of it, and does not read the documentation tree, which belongs to a different pass. It does not ask for the change set, the report in progress, or the reason the scope was drawn where it was. A path inside the scope that cannot be opened is carried into the counts as unread rather than dropped. +One field arrives: **an explicit list of paths**, the code the caller's scope resolved to. Nothing else. + +**A name is not a scope.** A topic, a subsystem, a feature, or a layer describes what the caller wants; turning one into files means running a search, and a search returns what matches the string rather than what the caller selected. Where the two come apart, the difference is code nobody chose, and every undocumented symbol and every comment in it would be reported as though it had been. The caller's own scope rule may well begin from an area, and resolving that area into paths is the caller's work, not this pass's. Where what arrives is a name rather than paths, return the empty lists with that stated in the counts, and let the caller resolve it. + +The agent does not widen the list it was given, does not follow an import out of it, and does not read the documentation tree, which belongs to a different pass. It does not ask for the change set, the report in progress, or the reason the scope was drawn where it was. A path inside the scope that cannot be opened is carried into the counts as unread rather than dropped. ## List one: undocumented public surface @@ -58,6 +62,8 @@ A comment can be accurate and still be in the wrong place. Report every comment Both halves of the test are mechanical, and both are required. The comment names a symbol, and the line beneath it uses that same symbol. A comment above a line that does not reference the symbol it discusses is a different comment and is never reported. +**The declaration bounds the entry, and this list is the one most likely to reach past the scope.** A symbol is used far from where it is declared, so following a usage site to its declaration is exactly how a pass drifts into code nobody asked it to touch. Report a copy under `REPEATED` only where the declaration sits inside the paths handed in **and** its body was opened this run. Where the declaration lies outside those paths, or inside them but unopened, the copy is not an entry: the comparison that would justify removing it was never made. It goes under `UNRESOLVED` with the declaration's path named, which lets the caller widen the scope deliberately rather than inherit a deletion nobody could check. + ```javascript // isBetaEnabled mirrors the beta-features flag. if (isBetaEnabled === undefined) { @@ -113,6 +119,12 @@ COMMENT: ADDS: +UNRESOLVED + :: +COMMENT: +DECLARATION: +BLOCKED BY: + COUNTS Files in scope: Files read: @@ -121,6 +133,7 @@ Undocumented symbols: Contradicted comments: Repeated comments: Differing copies: +Unresolved copies: ``` Every list may be empty. An empty set reported with the counts beside it is a result; the same set reported without them is indistinguishable from a run that opened nothing. diff --git a/.claude/skills/audit-pr/.claude-plugin/plugin.json b/.claude/skills/audit-pr/.claude-plugin/plugin.json new file mode 100644 index 0000000..d355347 --- /dev/null +++ b/.claude/skills/audit-pr/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "audit-pr", + "version": "1.0.0", + "description": "Review a pull request or working-branch diff across eighteen triaged categories and produce findings evidenced by the changed line." +} diff --git a/.claude/skills/audit-pr/SKILL.md b/.claude/skills/audit-pr/SKILL.md index 050847d..daddfad 100644 --- a/.claude/skills/audit-pr/SKILL.md +++ b/.claude/skills/audit-pr/SKILL.md @@ -41,9 +41,11 @@ Open one of these when a category the triage table activated needs its detail. N - [`supply-chain.md`](references/supply-chain.md) - category 15, including install-time execution judged by capability rather than by field name. - [`environment-and-observability.md`](references/environment-and-observability.md) - categories 13 and 14, plus the flakiness causes they share. - [`cost-and-billing.md`](references/cost-and-billing.md) - category 17, unbounded spend first, then the billing dimension each finding moves. -- [`finding-refuter.md`](agents/finding-refuter.md) - a subagent running section 6's refutation pass over one finding. **The default is not to dispatch it:** this run performs section 6 itself, which is faster and holds the context the pass needs. Reach for it only when the finding count makes that impractical, and never as a routine step per finding. +- [`finding-refuter.md`](agents/finding-refuter.md) - section 6's refutation pass over one finding, self-contained so that it can be followed on its own. **The default is not to run it separately:** this run performs section 6 itself, which is faster and holds the context the pass needs. Reach for it only when the finding count makes that impractical, and never as a routine step per finding. - [`review-summary.template.md`](assets/review-summary.template.md) - the finding block and summary shapes for section 7. +`finding-refuter.md` is the one file above that carries work rather than detail, so it has a second question: how to run it. **Open it and follow it yourself**, which works wherever this skill is installed. Where your host registers the file as an agent you can delegate to, handing it off keeps the reading out of this context. Where delegating is unavailable, names an agent the host does not recognize, or errors, open the file rather than improvising the pass from its name, since what the pass is worth is the six questions written inside it. **A returned verdict is a lead to verify, never a source to publish from:** section 6 deletes findings, so re-ground a verdict against the quoted line before dropping or keeping anything on it. + ## 1. Scope and evidence rules **Scope.** This run produces a review. It does not edit files and it does not fix what it finds. diff --git a/.claude/skills/typescript-code-and-test-standards/.claude-plugin/plugin.json b/.claude/skills/typescript-code-and-test-standards/.claude-plugin/plugin.json new file mode 100644 index 0000000..4aed546 --- /dev/null +++ b/.claude/skills/typescript-code-and-test-standards/.claude-plugin/plugin.json @@ -0,0 +1,5 @@ +{ + "name": "typescript-code-and-test-standards", + "version": "1.0.0", + "description": "TypeScript and JavaScript standards that formatters and linters cannot catch, covering comment discipline, JSDoc, the test mandate, and the mocking policy." +} diff --git a/.claude/skills/typescript-code-and-test-standards/SKILL.md b/.claude/skills/typescript-code-and-test-standards/SKILL.md index af91fc0..58a1ad0 100644 --- a/.claude/skills/typescript-code-and-test-standards/SKILL.md +++ b/.claude/skills/typescript-code-and-test-standards/SKILL.md @@ -5,15 +5,6 @@ license: MIT metadata: version: '1.0.0' origin: 'https://github.com/AlexJSully/AlexJSully-Portfolio' -paths: - - '**/*.ts' - - '**/*.tsx' - - '**/*.js' - - '**/*.jsx' - - '**/*.mjs' - - '**/*.cjs' - - '**/*.mts' - - '**/*.cts' --- # TypeScript code and test standards @@ -79,7 +70,7 @@ Writing new code, reviewing a diff, and fixing a failing test are different jobs - Commented-out code, and any deleted tooling directive. - A logic change with no test, or a test weakened, skipped, or deleted. - **Every new mock.** Require the change to name the boundary it crosses in one line. If it cannot, the finding is an unjustified mock. -4. Spawn the bundled subagents when the diff runs past a few files. See **Subagents** below. +4. Run the bundled procedures when the diff runs past a few files. See **Bundled procedures** below. ### Fixing a failing test @@ -175,13 +166,17 @@ When the project's configuration, its rules files, and the surrounding code all A project that consistently applies a different variant of one of those rules has a preference, not a defect. Follow the project. -## Subagents +## Bundled procedures -Two subagent instruction files ship with this skill. Each is self-contained: a spawned subagent inherits none of this context, so the file restates every rule it enforces. Both report findings and edit nothing. +Two procedure files ship with this skill. Each is self-contained, restating every rule it enforces so that it can be followed without any of this context. Both report findings and edit nothing. - [test-quality-reviewer.md](agents/test-quality-reviewer.md), for a test-quality pass weighted to over-mocking. Judging whether a mocked module holds logic means opening that module, its imports, and the subject under test, which is a lot of reading for a short answer. - [comment-and-jsdoc-auditor.md](agents/comment-and-jsdoc-auditor.md), for a comment and documentation pass. Enumerating every export in a file set means reading whole files to produce a short list. +**Open the file and follow it yourself.** That works wherever this skill is installed and cannot fail. Where your host registers these files as agents you can delegate to, handing one off keeps that reading out of the main context, which is why both exist at all. Where delegating is unavailable, names an agent the host does not recognize, or errors, fall back to opening the file. **Never improvise instructions from a file's name or from its one-line summary above:** what each file is worth is the rules it restates, and a paraphrase carries none of them. + +**A returned finding is a lead to verify, never a source to publish from.** It names a file and a symbol to open, and a judgement about a mock or a missing test is made against the code rather than against a summary of it. + There is deliberately no style reviewer: the project's own linter and compiler report that more reliably and at no token cost. ## Adopting this skill in a project diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 222f6b2..cd73330 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -163,7 +163,7 @@ Not adopted: the ban on default exports (this repository uses them for the modul - **Comments describe the code as it stands.** Never narrate a change, fix, or prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time"), and never name a file, flag, or tool that no longer exists; git history carries that. Never argue that the code is correct or safe, which documents the edit rather than the code. Delete commented-out code. A comment contradicting the code is corrected, not deleted - **Every exported symbol carries a `/** */` block, without exception**, as do the members of an exported structure (interface properties, object keys, enum values). Write for a reader meeting it for the first time; where nothing beyond a restatement is true, restate. Being obvious is not a defect on a public surface, being absent is -- A private helper gets a block when its name and signature do not carry it; a binding inside a function body does not, and a comment there that restates the next line is noise. **A fact about a symbol is stated once, on its declaration**, never repeated above the lines that read, call, or branch on it: `isBetaEnabled mirrors the beta-features flag` belongs on the declaration, not above each `if (isBetaEnabled)`. A member of an exported structure is its own declaration; a usage site is not +- A private helper gets a block when its name and signature do not carry it; a binding inside a function body does not, and a comment there that restates the next line is noise. **A fact about a symbol is stated once, on its declaration**, never repeated above the lines that read, call, or branch on it: `isBetaEnabled mirrors the beta-features flag` belongs on the declaration, not above each `if (isBetaEnabled)`. A member of an exported structure is its own declaration; a usage site is not. Removing an existing copy is narrower than writing a new one: delete it only where the declaration is in the same change you are reviewing and you have read it, since a symbol is used far from where it is declared and following a usage site to its declaration is how a comment sweep reaches code nobody touched. Otherwise leave both and say so - **In a block you write, do not put types in JSDoc.** TypeScript ignores `@param {string}`, `@returns {number}`, `@type`, and `@typedef` in `.ts`/`.tsx`, so they drift from the signature. Skip `@implements`, `@enum`, `@private`, and `@override` beside the keyword, and add `@param`/`@returns` where they say more than the name and type do - **Leave existing tags alone unless wrong.** A `@param`/`@returns` already in the tree was added deliberately, annotation and all. Read the surrounding code, fix what is factually wrong, change nothing else: do not strip a `{type}`, reword accurate prose, or delete a tag for looking redundant. Delete only when wrong and uncorrectable, such as documenting a parameter the signature no longer has - `@throws`, `@example`, `@deprecated` (naming its replacement), and `@see` are encouraged: none are expressible in the type system. Open a block with a third-person verb phrase; one tag per line; bodies are Markdown diff --git a/.github/prompts/audit-docs.prompt.md b/.github/prompts/audit-docs.prompt.md index 9527d37..720a5a1 100644 --- a/.github/prompts/audit-docs.prompt.md +++ b/.github/prompts/audit-docs.prompt.md @@ -34,7 +34,7 @@ Act as a **Strictly Factual Technical Writer and Auditor**. Make the project's d ### Phase 2: general audit - **Inventory before you correct.** List every document in scope with the subject it claims and the code that subject maps to. The three actions below are undecidable without that list: duplication is visible only across documents, a removed feature only where a document's subject is absent from the code, and a missing document only as code with no entry. Report how many documents you opened, and name anything in scope you did not, so that "already accurate" cannot be confused with "not looked at". -- Audit all of `docs/` against the codebase as it stands (#codebase). **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. +- Audit the documents the scope rule resolved to against the codebase as it stands (#codebase). That is all of `docs/` only where the rule resolved to the whole documentation set, and on a pull request it is the documents describing the changed code. **Correct** pre-existing content that contradicts the code, preserving accurate content's phrasing and style. - **Delete** pre-existing content only if it is massively duplicated, describes removed features, or fundamentally cannot be corrected. Default to correcting, not deleting. Your own generated content may be edited or removed freely when wrong. - **Create new files** only when needed: check the existing structure first and reuse a home when one fits; for a genuinely new directory apply the **Diátaxis** framework (Tutorials, How-To Guides, Reference, Explanation); create for new components/systems, external API guides, or missing structures. - **Output:** state whether you made changes or found docs already accurate. @@ -43,13 +43,13 @@ Act as a **Strictly Factual Technical Writer and Auditor**. Make the project's d **Mandatory.** Execute regardless of Phase 1 and 2 results. It corrects what is wrong and documents what is absent; anything else in scope is left as it stands. -- **Scope:** every `.md` file outside `docs/`, plus documentation comments, inline comments, and file-level headers across the code the scope rule above resolved to. +- **Scope:** the code the scope rule above resolved to, covering its documentation comments, inline comments, and file-level headers, plus every `.md` file inside that scope which sits outside `docs/`. A file the rule did not resolve to stays out whatever it contains, so this phase is never a repository-wide sweep for markdown, for undocumented symbols, or for a comment pattern. - **Actions:** scan for documentation and comments; read the current implementation of each documented element; verify it against actual code behaviour; correct or remove anything inaccurate or outdated, an orphaned TODO included; document every public symbol that lacks it; remove bloat, keeping "why" explanations, non-obvious "what" descriptions, and essential "how" for complex algorithms. Removing bloat means deleting comments that restate the code, never comments that explain a non-obvious internal. - **Always document the public surface.** Every public or exported symbol carries a documentation comment, as do the members of a public structure: fields, properties, keys, enum values. Write for a reader meeting the symbol for the first time, assuming they can infer nothing from its name. Reach for what the declaration cannot express, such as why it exists, a constraint, an invariant, or a caller obligation. Where no such explanation exists, a plain restatement of what the symbol does is correct: being obvious is not a defect on a public surface, being absent is. **Rule 2 still governs, and it comes first.** Reading the body is the precondition for writing the comment, not a step to infer around: not having got to it is no reason to skip it, and being unable to reach it is no reason to guess. Where you have not read the body, leave the symbol as it is and name it in your output. A public symbol left undocumented and reported is a compliant result; a comment written from the symbol's name is a defect, and it is the defect this rule exists to prevent. - **Do not restate what the language's own syntax declares**, such as a type, a visibility modifier, or an override marker. This governs what you write in a **new** documentation comment and never licenses removing an existing one. - **Correct an existing documentation tag; do not strip or delete it.** A parameter, return, throws, or example entry was written deliberately. Read enough surrounding code to judge it, then fix what is factually wrong and leave what is right, including parts a convention would omit in new code. Removing a tag, or a piece of one, because it looks redundant is restyling someone else's work, not auditing it. Delete a whole tag only when it is wrong and uncorrectable, such as one documenting a parameter the signature no longer has. Phase 2's "default to correcting, not deleting" governs in-code documentation too. - **Internal elements** are documented only where the logic is complex or carries a gotcha or edge case, and a comment inside a function body is written only for non-obvious business logic, a workaround, or a complex transformation. Delete an internal comment only when it restates the line beneath it, such as `// Increment counter` above a counter increment (delete the comment, keep the code). -- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. Where a comment naming a symbol already sits above a line that uses that symbol, and it says what the declaration's comment says, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way; leave both in place and report it only where evidence in the opened implementation does not establish whether the usage-site copy adds a distinct constraint. _Bad:_ `isBetaEnabled mirrors the beta-features flag` above every read of `isBetaEnabled`. _Good:_ that sentence once, on the declaration of `isBetaEnabled`, and nothing at the read sites. +- **A fact is documented once, at the declaration of the thing it is about.** A statement about a symbol belongs on that symbol's own declaration, never above the lines that read it, call it, or branch on it. Where the same sentence would sit above more than one _use_ of a symbol, it belongs on the declaration alone, or in `docs/` where it spans more than one symbol. A declaration is not a use: each member of a public structure still gets its own comment, and a file-level header still summarizes what the file declares. **Removing a copy is bounded, and all three preconditions hold before anything is deleted:** the declaration and the usage site both sit inside the scope this run resolved, so the rule never reaches a file the scope rule did not resolve to; the declaration's body has been opened this run, since a copy cannot be judged redundant against a declaration nobody read; and the copy says no more than the declaration's comment says. With all three met, keep the copy on the declaration, writing it there if it is absent, and delete the one above the use; this is the one case where an accurate comment is removed rather than corrected. Where the copy above the use carries a constraint the declaration does not, fold that into the declaration and then delete the copy, so the fact lands on the declaration either way. Failing any one of the three, leave both in place and report it: a repetition left alone costs a reader one duplicated sentence, where a wrong deletion destroys the only place a constraint was written down. _Bad:_ `isBetaEnabled mirrors the beta-features flag` above every read of `isBetaEnabled`. _Good:_ that sentence once, on the declaration of `isBetaEnabled`, and nothing at the read sites. - **Comments describe the code as it stands.** Never narrate a change, a fix, or a prior state ("now uses", "previously", "no longer", "restored", "replaces", "used to", "formerly", "for the first time", "unlike the old"), and never name a file, flag, or tool that no longer exists: version control carries that, and the comment outlives the change that prompted it. Never argue that the code is correct or safe, which documents the edit rather than the code. Delete commented-out code rather than leaving it in place. - **Form:** a documentation comment is a complete sentence, capitalized and punctuated; a short trailing comment may be a fragment. Wrap long comment lines to the width the file already uses, letting an unbreakable URL exceed it. Use the documentation format's own list syntax for enumerations, since indented plain text collapses into one run-on sentence when rendered. Never box a comment in asterisks or other decorative characters. Documentation precedes an annotation or decorator and never sits between it and the declaration. - **Contracts worth stating:** any cleanup the caller owns (a handle to close, a listener to remove, a subscription to cancel), the error values or exception types a caller can branch on, and a deprecation marker naming its replacement. A deprecation without migration directions is incomplete; add one only where it is provable under Rule 2. diff --git a/.github/prompts/audit-pr.prompt.md b/.github/prompts/audit-pr.prompt.md index 86d0bf8..4e3436f 100644 --- a/.github/prompts/audit-pr.prompt.md +++ b/.github/prompts/audit-pr.prompt.md @@ -221,7 +221,7 @@ Determine which regulations apply from the data the system holds, the people it ## 6. Step 4: Refutation pass -Before writing the summary, take each finding and try to disprove it. This step decides whether the review is accurate. Run it yourself: it needs the diff and the files you already hold, and handing it out costs more than it saves. +Before writing the summary, take each finding and try to disprove it. This step decides whether the review is accurate. Run it yourself: it needs the diff and the files you already hold, and handing it out costs more than it saves. **Whether you run it yourself or hand it out, a verdict is a lead to verify, never a source to publish from:** this step deletes findings, so re-ground each verdict against the quoted line before dropping or keeping anything on it. For each finding, answer: diff --git a/.github/prompts/readme.md b/.github/prompts/readme.md index 6271b3c..b08881f 100644 --- a/.github/prompts/readme.md +++ b/.github/prompts/readme.md @@ -73,7 +73,7 @@ npx skills add AlexJSully/AlexJSully-Portfolio --skill typescript-code-and-test- 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. +`typescript-code-and-test-standards` applies 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 covers `.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`. 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. diff --git a/CLAUDE.md b/CLAUDE.md index b67b318..4b91012 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,7 @@ The conventions live in two layers. The generic set (comment discipline, JSDoc, ## Claude Code extras - 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: `/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` (the codebase-agnostic conventions, which `code-style.md` also loads you into 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 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 39b489f..10c0e33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^9.2.0", "@mui/material": "^9.2.0", - "@sentry/nextjs": "^10.67.0", + "@sentry/nextjs": "^10.68.0", "@vercel/speed-insights": "^2.0.0", "firebase": "^12.16.0", "lodash": "^4.18.1", @@ -24,8 +24,8 @@ "webpack": "^5.109.0" }, "devDependencies": { - "@sentry/core": "^10.67.0", - "@sentry/types": "^10.67.0", + "@sentry/core": "^10.68.0", + "@sentry/types": "^10.68.0", "@svgr/webpack": "^8.1.0", "@testing-library/jest-dom": "^7.0.0", "@testing-library/react": "^16.3.2", @@ -48,7 +48,7 @@ "jest-transform-stub": "^2.0.0", "markdownlint-cli2": "^0.23.1", "prettier": "^3.9.6", - "sass": "^1.101.6", + "sass": "^1.101.7", "start-server-and-test": "^3.0.11", "typescript": "^6.0.3" } @@ -78,9 +78,9 @@ } }, "node_modules/@apm-js-collab/code-transformer-bundler-plugins": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.2.tgz", - "integrity": "sha512-GvpKWDmzBFzbtVElU+tEDMDYyMY8SnMH4NNwwlUQioNHZvE6sSuHEqNkfU3iYhWL+/XO5Ej+MzhVJoqM10NYCQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.7.1.tgz", + "integrity": "sha512-Yidf5GOl60db80UxUtNdKK3pnY7obU/gs0xOfA0SCdnvVLMCvfYIer/egC3TqpPiT0Jg22eg3RlzcO+zKfPMcA==", "license": "MIT", "dependencies": { "@apm-js-collab/code-transformer": "^0.18.0", @@ -6448,30 +6448,30 @@ } }, "node_modules/@sentry/browser": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.67.0.tgz", - "integrity": "sha512-/ZhsAvte4rYhg0A0RtSFFgAgXhyMOfQIeOAfMfptN+X6IVSYOfkA9jtrP+Ej4+6vlaUFWRir1HweF56y63dEEA==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.68.0.tgz", + "integrity": "sha512-8xVgk7oG2lajXnbXF6a7H1xMZ/U6icqSldHGzQu1+bajfrK8Gan9ULG/Xsj1VM1LlNeK6/7znDJ3u1jgvIwznw==", "license": "MIT", "dependencies": { - "@sentry/browser-utils": "10.67.0", + "@sentry/browser-utils": "10.68.0", "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0", - "@sentry/feedback": "10.67.0", - "@sentry/replay": "10.67.0", - "@sentry/replay-canvas": "10.67.0" + "@sentry/core": "10.68.0", + "@sentry/feedback": "10.68.0", + "@sentry/replay": "10.68.0", + "@sentry/replay-canvas": "10.68.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/browser-utils": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.67.0.tgz", - "integrity": "sha512-HUzaf0xAnPAB+OHBkD7N1Py+CTbD5InHulQ/pdhX4JctWtxuwD8odMD1LzdPnW8J6gVHlDVvcVBR8mXMZYSLSw==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.68.0.tgz", + "integrity": "sha512-be8VtdjCngKc77cstJeV+gO15iH+blyXpBDk8yOehmtX4BkFO33mfTMNCWVR2LA0oOxjIHWRAhf77fIUEhzxPg==", "license": "MIT", "dependencies": { "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0" + "@sentry/core": "10.68.0" }, "engines": { "node": ">=18" @@ -6496,14 +6496,14 @@ } }, "node_modules/@sentry/bundler-plugins": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/bundler-plugins/-/bundler-plugins-10.67.0.tgz", - "integrity": "sha512-HKLhbMZJsabZlXTog8CTa1ReeDW/mf1cwN7O8K+DKhe/kGHB3whHRseqsyjxyJwPdzC/0lM+8rgfqgxpc7jR9A==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugins/-/bundler-plugins-10.68.0.tgz", + "integrity": "sha512-XWv7asJuTTUSlacROvqcIFKuNxAf3PYL/mdjMBhBwp3rJ4vMkA73jqNPjqCTm726cHs/Y4yadbbFA/OZLPWzeg==", "license": "MIT", "dependencies": { "@babel/core": "^7.18.5", "@sentry/cli": "^2.58.6", - "@sentry/core": "10.67.0", + "@sentry/core": "10.68.0", "dotenv": "^17.4.2", "find-up": "^5.0.0", "glob": "^13.0.6", @@ -6712,9 +6712,9 @@ } }, "node_modules/@sentry/core": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.67.0.tgz", - "integrity": "sha512-b6U3pJ8AUvN9aouq0vl+VZI8KT8RslBsfGMFuNwRr313zOmdmFJBZqTiUw9VGgJ2jGKxLO9alm9rlxBfX4hf+w==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.68.0.tgz", + "integrity": "sha512-5Amhx8ltVz7vb1bRGyf3c4J69/iHW8R/H+SJxTRILHlsSOBrnVVc/IQEYDC6PTRdRdZ3x2u7RVjxZi2Mhe525g==", "license": "MIT", "dependencies": { "@sentry/conventions": "^0.16.0" @@ -6724,34 +6724,34 @@ } }, "node_modules/@sentry/feedback": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.67.0.tgz", - "integrity": "sha512-I4ML2/SF3enwikb6ZSoRiqolQrx0zSzTSnUgwCmugICF/jpHW0th1pCray9R+t1Zzibw/Dpj4t/DNXaSDRa2MA==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.68.0.tgz", + "integrity": "sha512-XbdcXiBnpC3vgw46eHOPeD/ZQ+XzluP75ubdUcaPDW02hCh2nsdXiwjZ2DBImbpvIpTbJgjHf/sIlHWvcZJ2Mg==", "license": "MIT", "dependencies": { - "@sentry/core": "10.67.0" + "@sentry/core": "10.68.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/nextjs": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-10.67.0.tgz", - "integrity": "sha512-errNYlnhQBTDGzgAH0kkneD3/sK+VqW1qBixV8S/Gg4ygj6+QjigOU9idJA980H7mjyxrM1iT74qtcFt24nFow==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-10.68.0.tgz", + "integrity": "sha512-oMDl9F8jkCgAsd8U9gLTAWr7u/j9/nUw0sBRHoAvK7HXfdrg9fPlsDd3wsHgYDC0U2tIDTXICXAwT2zJ7OHHWg==", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.1", "@rollup/plugin-commonjs": "28.0.1", - "@sentry/browser-utils": "10.67.0", + "@sentry/browser-utils": "10.68.0", "@sentry/bundler-plugin-core": "^5.3.0", "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0", - "@sentry/node": "10.67.0", - "@sentry/opentelemetry": "10.67.0", - "@sentry/react": "10.67.0", - "@sentry/server-utils": "10.67.0", - "@sentry/vercel-edge": "10.67.0", + "@sentry/core": "10.68.0", + "@sentry/node": "10.68.0", + "@sentry/opentelemetry": "10.68.0", + "@sentry/react": "10.68.0", + "@sentry/server-utils": "10.68.0", + "@sentry/vercel-edge": "10.68.0", "@sentry/webpack-plugin": "^5.3.0", "rollup": "^4.60.3", "stacktrace-parser": "^0.1.11" @@ -6764,19 +6764,19 @@ } }, "node_modules/@sentry/node": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.67.0.tgz", - "integrity": "sha512-SFKpZGqOCEFSmP93NdDP6ikZp4NS7A/JR8+2ofK3jF6Y9Vyox7pX0pxdOnPLpFcPtMybMahfWqSAWJvsFs4RmA==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.68.0.tgz", + "integrity": "sha512-bnvRzEehquG/894DD3BWNCBUbDWsLQfYPU+SNCMd6G4Ext75RthVkRs8R0sRaE6b8Tw9HSEgySHL834Tf8lVsA==", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/instrumentation": "^0.220.0", "@opentelemetry/sdk-trace-base": "^2.9.0", "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0", - "@sentry/node-core": "10.67.0", - "@sentry/opentelemetry": "10.67.0", - "@sentry/server-utils": "10.67.0", + "@sentry/core": "10.68.0", + "@sentry/node-core": "10.68.0", + "@sentry/opentelemetry": "10.68.0", + "@sentry/server-utils": "10.68.0", "import-in-the-middle": "^3.0.0" }, "engines": { @@ -6784,14 +6784,14 @@ } }, "node_modules/@sentry/node-core": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.67.0.tgz", - "integrity": "sha512-dBHHRwZyan1pOnFJ+sNBvR8TkXbZAfZU/jpxmALS3JZ2/8AGR7cQKL+b7SleKuJ7iUDZyklN3Nqi0i5JkcA+HA==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.68.0.tgz", + "integrity": "sha512-VreORXnruy8A2SyprZKENAq3ArGwn35KPewLQSQ5dgDXSFtUj2scLuzZoVsZ/Uyt83td0WPvD6Lv0X7MvSYAMQ==", "license": "MIT", "dependencies": { "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0", - "@sentry/opentelemetry": "10.67.0", + "@sentry/core": "10.68.0", + "@sentry/opentelemetry": "10.68.0", "import-in-the-middle": "^3.0.0" }, "engines": { @@ -6823,13 +6823,13 @@ } }, "node_modules/@sentry/opentelemetry": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.67.0.tgz", - "integrity": "sha512-oLTOrAK1rOqmYRktOJZwz37B1seXPx1W2FTMVtzTVNjMFA/LZwGzePeZzhUOgzZgfLHixMd/ceWtGqoxAndcjQ==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.68.0.tgz", + "integrity": "sha512-JDNH9dacX0MSi7FRvabPCJUAXRMTe16bE/Gajc/7Gfcw7Rwvo4DZ0nd162PCSAW9QoEjUT12upDehEHJuFmsXg==", "license": "MIT", "dependencies": { "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0" + "@sentry/core": "10.68.0" }, "engines": { "node": ">=18" @@ -6841,14 +6841,14 @@ } }, "node_modules/@sentry/react": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.67.0.tgz", - "integrity": "sha512-fS0DplcP9eMxBIRurPC/uxa4NrFK+l9ZsnvQo7wZvNutc7DpTAH0hgFt6laVNCe57s1pFo+OZuKsYBA6JDvH4Q==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.68.0.tgz", + "integrity": "sha512-rIq4QR4ScMHHx9JJZv7Jgw31bMdUVJMx+ykHIJb7htjY6mj78sjKs+KpCsMDnvJxDhSmvftGM1KfKD4BggL7OQ==", "license": "MIT", "dependencies": { - "@sentry/browser": "10.67.0", + "@sentry/browser": "10.68.0", "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0" + "@sentry/core": "10.68.0" }, "engines": { "node": ">=18" @@ -6858,67 +6858,68 @@ } }, "node_modules/@sentry/replay": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.67.0.tgz", - "integrity": "sha512-nkEUgPCR82EcyJkCf3XCE9H0R5KisCqyCAaSGxe7NpAoQbvASHx4MUNgXVAn+D0M494gvPZh6lFH7JgzqTcSqQ==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.68.0.tgz", + "integrity": "sha512-ZoG2n16vbkx4GWSCnLIqUUN9xlUmccQFbQ2US2rhruQeHTUnHl/ukr8NHOQXZaEbwKyMkX9bEMwfmZHJm+wSTQ==", "license": "MIT", "dependencies": { - "@sentry/browser-utils": "10.67.0", - "@sentry/core": "10.67.0" + "@sentry/browser-utils": "10.68.0", + "@sentry/core": "10.68.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/replay-canvas": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.67.0.tgz", - "integrity": "sha512-neNA4T6MFtZzMdKYetiR+LZd9BNSd0q2szMn0wk+A15PqHE/IN7a34V6JZc9rCtmzB0wldh0eWGOBb49MSNKjA==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.68.0.tgz", + "integrity": "sha512-HusYcr+He+ohnUDHunYrc5St6vdDnBXpUAndnT5ReyUMVSCiWKfY3paXowU/0787HwYfxdcpZgwC5u79+XbEIg==", "license": "MIT", "dependencies": { - "@sentry/core": "10.67.0", - "@sentry/replay": "10.67.0" + "@sentry/core": "10.68.0", + "@sentry/replay": "10.68.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/server-utils": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.67.0.tgz", - "integrity": "sha512-GQ9t+RSTx5s3b/aZrLFuL4nrwPLMah5NiZk5cjxJmgmOSgm3nMdO8gdqCedDch5B13F3YsxtaQYjSJnzLz8M5A==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.68.0.tgz", + "integrity": "sha512-lp1ZSs1auw7HrCESSYt/n4dOUaKPVUIAKyVYRk6xVr4bMIN3RPub/H5Wm7QPj9CpXVC5bQFvB6+dHZXz809oMg==", "license": "MIT", "dependencies": { - "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.1", + "@apm-js-collab/code-transformer-bundler-plugins": "0.7.1", "@apm-js-collab/tracing-hooks": "^0.13.0", "@sentry/conventions": "^0.16.0", - "@sentry/core": "10.67.0" + "@sentry/core": "10.68.0", + "meriyah": "^6.1.4" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/types": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-10.67.0.tgz", - "integrity": "sha512-gSBqoEdDuUZtdhRDNHwpV5WwYaP8ik2ekttfV/2wwUzRv2eujqz2t9NwzcXxAAf/hAYhl3H6sD90pLOsfkwNfw==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-10.68.0.tgz", + "integrity": "sha512-qgW0G+nYOK+ba7t8YDbzo95ISZHcIrNY8RL1ofewqxYTvqLHdRuwoXf4R3gP82sLfnsgfPEYui6H3rXa3g3uRQ==", "dev": true, "license": "MIT", "dependencies": { - "@sentry/core": "10.67.0" + "@sentry/core": "10.68.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry/vercel-edge": { - "version": "10.67.0", - "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-10.67.0.tgz", - "integrity": "sha512-tez7Kwz24PE4BCjzc2kcRc0gF4DWxYlSn61we9nRKSBh/wwUsQEznJfQ7tYxWbc67kxayQiyS5Vl9zzv5urTGw==", + "version": "10.68.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-10.68.0.tgz", + "integrity": "sha512-6FukkfH1b3T7jMgPVE7todrKcC92zA0gwUA3kyTG645eOdfMTIQ9o5lr5UECixALmzTB0GAgrr2gH9xlR0R9bA==", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.1", - "@sentry/core": "10.67.0" + "@sentry/core": "10.68.0" }, "engines": { "node": ">=18" @@ -10204,9 +10205,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.395", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.395.tgz", - "integrity": "sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==", + "version": "1.5.396", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz", + "integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==", "license": "ISC" }, "node_modules/emittery": { @@ -17015,9 +17016,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.101.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.6.tgz", - "integrity": "sha512-j8qYug9WuX19eU5sxJWQlbR8RYhKgXiOYgGjkJRkcW35c3neWtxPdcUW0saN6Od2L0aqEp0AmH9R/QeAxrffMQ==", + "version": "1.101.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.7.tgz", + "integrity": "sha512-cDeUYU0dhwKVbpYg/ppsjyuoddxYhWlJOkRoI7+/iZsaSp7iowWDfm+tL2HcUafedWBDvbf/+hx0QRgKG4JSHA==", "devOptional": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 0ca8157..8adb0d3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@emotion/styled": "^11.14.1", "@mui/icons-material": "^9.2.0", "@mui/material": "^9.2.0", - "@sentry/nextjs": "^10.67.0", + "@sentry/nextjs": "^10.68.0", "@vercel/speed-insights": "^2.0.0", "firebase": "^12.16.0", "lodash": "^4.18.1", @@ -49,8 +49,8 @@ "webpack": "^5.109.0" }, "devDependencies": { - "@sentry/core": "^10.67.0", - "@sentry/types": "^10.67.0", + "@sentry/core": "^10.68.0", + "@sentry/types": "^10.68.0", "@svgr/webpack": "^8.1.0", "@testing-library/jest-dom": "^7.0.0", "@testing-library/react": "^16.3.2", @@ -73,7 +73,7 @@ "jest-transform-stub": "^2.0.0", "markdownlint-cli2": "^0.23.1", "prettier": "^3.9.6", - "sass": "^1.101.6", + "sass": "^1.101.7", "start-server-and-test": "^3.0.11", "typescript": "^6.0.3" }, From 6b0cc279505aa9593e7b0a8decbdae117db68757 Mon Sep 17 00:00:00 2001 From: Alexander Sullivan Date: Fri, 7 Aug 2026 14:44:58 -0400 Subject: [PATCH 2/2] update docs --- .claude/scripts/check-skill-publishability.mjs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.claude/scripts/check-skill-publishability.mjs b/.claude/scripts/check-skill-publishability.mjs index 81f4953..ae4577d 100644 --- a/.claude/scripts/check-skill-publishability.mjs +++ b/.claude/scripts/check-skill-publishability.mjs @@ -287,9 +287,10 @@ function checkPluginManifest(name) { } /** - * Every file inside a skill that travels with it and could name a path: Markdown one level of - * bundle directory deep, plus the plugin manifest. The manifest carries a `description`, so it - * can name a prompt file exactly as a body can, and it ships in the copied directory either way. + * Every file inside a skill that travels with it and could name a path: top-level Markdown, every + * file one level deep in each bundle directory, plus the plugin manifest. The manifest carries a + * `description`, so it can name a prompt file exactly as a body can, and it ships in the copied + * directory either way. */ function skillFiles(name) { const root = join(SKILL_DIR, name);