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
13 changes: 13 additions & 0 deletions catalogue/expected-skills-files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
skills/app-shell-patterns/SKILL.md
skills/app-shell-patterns/references/fundamental/components.md
skills/app-shell-patterns/references/fundamental/design-system.md
skills/app-shell-patterns/references/fundamental/graphql.md
skills/app-shell-patterns/references/patterns/detail-hero-with-actions.md
skills/app-shell-patterns/references/patterns/form-modal.md
skills/app-shell-patterns/references/patterns/form-sectioned.md
skills/app-shell-patterns/references/patterns/form-single-page.md
skills/app-shell-patterns/references/patterns/form-wizard.md
skills/app-shell-patterns/references/patterns/interaction-confirm.md
skills/app-shell-patterns/references/patterns/interaction-multi-select.md
skills/app-shell-patterns/references/patterns/interaction-toast.md
skills/app-shell-patterns/references/patterns/list-dense-scan.md
1 change: 1 addition & 0 deletions catalogue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"type": "module",
"scripts": {
"build": "node scripts/generate-skill.mjs",
"test": "node scripts/check-generated-skills.mjs",
"type-check": "tsc --noEmit"
},
"dependencies": {
Expand Down
86 changes: 86 additions & 0 deletions catalogue/scripts/check-generated-skills.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Verifies that the generated skills tree under packages/core/skills matches
* the expected file manifest from catalogue/expected-skills-files.txt.
*
* Purpose: keep generated skills out of git while still catching drift between
* catalogue inputs and the generated output during test runs.
*/
import { readFile, readdir } from "node:fs/promises";
import { join, relative } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = fileURLToPath(new URL(".", import.meta.url));
const catalogueRoot = join(__dirname, "..");
const repoRoot = join(catalogueRoot, "..");
const expectedManifestPath = join(catalogueRoot, "expected-skills-files.txt");
const corePackageRoot = join(repoRoot, "packages", "core");
const skillsRoot = join(corePackageRoot, "skills");

function parseManifest(text) {
return text
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.sort();
}

function diffLists(expected, actual) {
const expectedSet = new Set(expected);
const actualSet = new Set(actual);

return {
missing: expected.filter((file) => !actualSet.has(file)),
extra: actual.filter((file) => !expectedSet.has(file)),
};
}

async function listFiles(dir) {
const entries = await readdir(dir, { withFileTypes: true });
const files = [];

for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await listFiles(fullPath)));
continue;
}
files.push(fullPath);
}

return files;
}

async function main() {
const [expectedManifest, actualFiles] = await Promise.all([
readFile(expectedManifestPath, "utf8"),
listFiles(skillsRoot),
]);

const actualSkillsFiles = actualFiles
.map((filePath) => relative(corePackageRoot, filePath))
.sort();
const expectedSkillsFiles = parseManifest(expectedManifest);

const { missing, extra } = diffLists(expectedSkillsFiles, actualSkillsFiles);
if (missing.length === 0 && extra.length === 0) {
console.log(`Skills manifest matches expected (${actualSkillsFiles.length} files).`);
return;
}

console.error("Skills manifest does not match catalogue/expected-skills-files.txt");
if (missing.length > 0) {
console.error("\nMissing:");
for (const file of missing) console.error(` - ${file}`);
}
if (extra.length > 0) {
console.error("\nExtra:");
for (const file of extra) console.error(` - ${file}`);
}

process.exitCode = 1;
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
1 change: 1 addition & 0 deletions packages/core/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
skills
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

exports[`Autocomplete.Parts > snapshots > closed autocomplete with placeholder 1`] = `"<div data-slot="autocomplete-input-group" class="astw:relative astw:flex astw:items-center"><input data-list-empty="" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="none" role="combobox" aria-expanded="false" aria-haspopup="listbox" aria-autocomplete="list" id="base-ui-_r_0_" data-slot="autocomplete-input" data-testid="input" placeholder="Type a fruit..." class="astw:border-input astw:bg-transparent astw:dark:bg-input/30 astw:text-foreground astw:placeholder:text-muted-foreground astw:flex astw:h-9 astw:w-full astw:rounded-md astw:border astw:px-3 astw:py-1 astw:text-sm astw:shadow-xs astw:outline-none astw:transition-colors astw:focus-visible:border-ring astw:focus-visible:ring-ring/50 astw:focus-visible:ring-[3px] astw:disabled:cursor-not-allowed astw:disabled:opacity-50" type="text" value=""><button type="button" data-list-empty="" tabindex="-1" aria-expanded="false" aria-haspopup="listbox" data-slot="autocomplete-trigger" class="astw:absolute astw:inset-y-0 astw:right-0 astw:flex astw:items-center astw:px-2 astw:text-muted-foreground astw:outline-none astw:hover:text-foreground astw:disabled:pointer-events-none astw:disabled:opacity-50"><span aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down astw:size-4" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></span></button></div><input id="base-ui-_r_0_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" aria-hidden="true" value="">"`;

exports[`Autocomplete.Parts > snapshots > open autocomplete 1`] = `"<div data-base-ui-inert=""><div data-slot="autocomplete-input-group" class="astw:relative astw:flex astw:items-center"><span role="button" aria-label="Dismiss" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: absolute;"></span><input data-list-empty="" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="none" role="combobox" aria-expanded="true" aria-haspopup="listbox" aria-autocomplete="list" id="base-ui-_r_4_" data-slot="autocomplete-input" data-testid="input" placeholder="Type a fruit..." class="astw:border-input astw:bg-transparent astw:dark:bg-input/30 astw:text-foreground astw:placeholder:text-muted-foreground astw:flex astw:h-9 astw:w-full astw:rounded-md astw:border astw:px-3 astw:py-1 astw:text-sm astw:shadow-xs astw:outline-none astw:transition-colors astw:focus-visible:border-ring astw:focus-visible:ring-ring/50 astw:focus-visible:ring-[3px] astw:disabled:cursor-not-allowed astw:disabled:opacity-50" type="text" value="a" data-popup-open="" data-pressed="" data-popup-side="bottom" aria-controls="_r_5_"><button type="button" data-visible="" tabindex="-1" data-slot="autocomplete-clear" class="astw:absolute astw:inset-y-0 astw:right-6 astw:flex astw:items-center astw:px-1 astw:text-muted-foreground astw:outline-none astw:hover:text-foreground" data-popup-open="" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x astw:size-3.5" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button><button type="button" data-list-empty="" tabindex="-1" aria-expanded="true" aria-haspopup="listbox" data-slot="autocomplete-trigger" class="astw:absolute astw:inset-y-0 astw:right-0 astw:flex astw:items-center astw:px-2 astw:text-muted-foreground astw:outline-none astw:hover:text-foreground astw:disabled:pointer-events-none astw:disabled:opacity-50" data-popup-open="" data-pressed="" data-popup-side="bottom" aria-controls="_r_5_" aria-hidden="true"><span aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down astw:size-4" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></span></button></div><input id="base-ui-_r_4_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" aria-hidden="true" value="a"></div><div id="_r_8_" data-base-ui-portal="" style="position: relative; z-index: var(--z-popup);"><div data-open="" data-side="bottom" data-align="center" data-empty="" role="presentation" style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px); --available-width: -10px; --available-height: -11px; --anchor-width: 0px; --anchor-height: 0px; --transform-origin: 0px 0px;" data-anchor-hidden=""><div data-open="" data-side="bottom" data-align="center" data-empty="" role="presentation" tabindex="-1" data-slot="autocomplete-content" class="astw:bg-popover astw:text-popover-foreground astw:data-open:animate-in astw:data-ending-style:animate-out astw:data-ending-style:fade-out-0 astw:data-open:fade-in-0 astw:data-ending-style:zoom-out-95 astw:data-open:zoom-in-95 astw:z-(--z-popup) astw:w-[var(--anchor-width)] astw:min-w-32 astw:origin-(--transform-origin) astw:overflow-hidden astw:rounded-md astw:border astw:border-border astw:shadow-md" data-anchor-hidden=""><div data-empty="" tabindex="-1" data-base-ui-focusable="" aria-orientation="vertical" role="listbox" id="_r_5_" data-slot="autocomplete-list" class="astw:max-h-60 astw:overflow-y-auto astw:p-1"><div role="option" id="base-ui-_r_4_-0" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Apple</div><div role="option" id="base-ui-_r_4_-1" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Banana</div><div role="option" id="base-ui-_r_4_-2" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Cherry</div><div role="option" id="base-ui-_r_4_-3" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Date</div><div role="option" id="base-ui-_r_4_-4" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Elderberry</div><div role="status" aria-live="polite" aria-atomic="true" data-slot="autocomplete-empty" class="astw:px-4 astw:pt-2 astw:pb-4 astw:text-center astw:text-sm astw:text-muted-foreground astw:empty:hidden">No suggestions⁠</div></div></div><span role="button" aria-label="Dismiss" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: absolute;"></span></div></div>"`;
exports[`Autocomplete.Parts > snapshots > open autocomplete 1`] = `"<div data-base-ui-inert=""><div data-slot="autocomplete-input-group" class="astw:relative astw:flex astw:items-center"><span role="button" aria-label="Dismiss" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: absolute;"></span><input data-list-empty="" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="none" role="combobox" aria-expanded="true" aria-haspopup="listbox" aria-autocomplete="list" id="base-ui-_r_4_" data-slot="autocomplete-input" data-testid="input" placeholder="Type a fruit..." class="astw:border-input astw:bg-transparent astw:dark:bg-input/30 astw:text-foreground astw:placeholder:text-muted-foreground astw:flex astw:h-9 astw:w-full astw:rounded-md astw:border astw:px-3 astw:py-1 astw:text-sm astw:shadow-xs astw:outline-none astw:transition-colors astw:focus-visible:border-ring astw:focus-visible:ring-ring/50 astw:focus-visible:ring-[3px] astw:disabled:cursor-not-allowed astw:disabled:opacity-50" type="text" value="a" data-popup-open="" data-pressed="" data-popup-side="bottom" aria-controls="_r_5_"><button type="button" data-visible="" tabindex="-1" data-slot="autocomplete-clear" class="astw:absolute astw:inset-y-0 astw:right-6 astw:flex astw:items-center astw:px-1 astw:text-muted-foreground astw:outline-none astw:hover:text-foreground" data-popup-open="" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x astw:size-3.5" aria-hidden="true"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg></button><button type="button" data-list-empty="" tabindex="-1" aria-expanded="true" aria-haspopup="listbox" data-slot="autocomplete-trigger" class="astw:absolute astw:inset-y-0 astw:right-0 astw:flex astw:items-center astw:px-2 astw:text-muted-foreground astw:outline-none astw:hover:text-foreground astw:disabled:pointer-events-none astw:disabled:opacity-50" data-popup-open="" data-pressed="" data-popup-side="bottom" aria-controls="_r_5_" aria-hidden="true"><span aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down astw:size-4" aria-hidden="true"><path d="m6 9 6 6 6-6"></path></svg></span></button></div><input id="base-ui-_r_4_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" aria-hidden="true" value="a"></div><div id="_r_8_" data-base-ui-portal="" style="position: relative; z-index: var(--z-popup);"><div data-open="" data-side="bottom" data-align="center" data-empty="" role="presentation" style="position: absolute; left: 0px; top: 0px; transform: translate(0px, 0px); --available-width: -10px; --available-height: -11px; --anchor-width: 0px; --anchor-height: 0px; --transform-origin: 0px 0px;" data-anchor-hidden=""><div data-open="" data-side="bottom" data-align="center" data-empty="" role="presentation" tabindex="-1" data-slot="autocomplete-content" class="astw:bg-popover astw:text-popover-foreground astw:data-open:animate-in astw:data-ending-style:animate-out astw:data-ending-style:fade-out-0 astw:data-open:fade-in-0 astw:data-ending-style:zoom-out-95 astw:data-open:zoom-in-95 astw:z-(--z-popup) astw:w-[var(--anchor-width)] astw:min-w-32 astw:origin-(--transform-origin) astw:overflow-hidden astw:rounded-md astw:border astw:border-border astw:shadow-md" data-anchor-hidden=""><div data-empty="" tabindex="-1" data-base-ui-focusable="" aria-orientation="vertical" role="listbox" id="_r_5_" data-slot="autocomplete-list" class="astw:max-h-60 astw:overflow-y-auto astw:p-1"><div role="option" id="base-ui-_r_4_-0" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Apple</div><div role="option" id="base-ui-_r_4_-1" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Banana</div><div role="option" id="base-ui-_r_4_-2" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Cherry</div><div role="option" id="base-ui-_r_4_-3" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Date</div><div role="option" id="base-ui-_r_4_-4" data-slot="autocomplete-item" class="astw:relative astw:flex astw:w-full astw:cursor-default astw:items-center astw:gap-2 astw:rounded-sm astw:py-1.5 astw:px-2 astw:text-sm astw:outline-hidden astw:select-none astw:data-highlighted:bg-accent astw:data-highlighted:text-accent-foreground astw:data-disabled:pointer-events-none astw:data-disabled:opacity-50">Elderberry</div><div role="status" aria-live="polite" aria-atomic="true" data-slot="autocomplete-empty" class="astw:px-4 astw:pt-2 astw:pb-4 astw:text-center astw:text-sm astw:text-muted-foreground astw:empty:hidden">No suggestions</div></div></div><span role="button" aria-label="Dismiss" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: absolute;"></span></div></div>"`;

exports[`Autocomplete.Parts > snapshots > with groups 1`] = `"<input data-list-empty="" autocomplete="off" spellcheck="false" autocorrect="off" autocapitalize="none" role="combobox" aria-expanded="false" aria-haspopup="listbox" aria-autocomplete="list" id="base-ui-_r_a_" data-slot="autocomplete-input" placeholder="Search..." class="astw:border-input astw:bg-transparent astw:dark:bg-input/30 astw:text-foreground astw:placeholder:text-muted-foreground astw:flex astw:h-9 astw:w-full astw:rounded-md astw:border astw:px-3 astw:py-1 astw:text-sm astw:shadow-xs astw:outline-none astw:transition-colors astw:focus-visible:border-ring astw:focus-visible:ring-ring/50 astw:focus-visible:ring-[3px] astw:disabled:cursor-not-allowed astw:disabled:opacity-50" type="text" value=""><input id="base-ui-_r_a_-hidden-input" style="clip-path: inset(50%); overflow: hidden; white-space: nowrap; border: 0px; padding: 0px; width: 1px; height: 1px; margin: -1px; position: fixed; top: 0px; left: 0px;" tabindex="-1" aria-hidden="true" value="">"`;
Loading
Loading