mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 16:46:31 +03:00
Consolidate 18 skills into 1 /impeccable skill with 20 commands
Biggest change in a while. Users previously had 18 standalone skill
entries cluttering their /menu; now they have one entry (/impeccable)
that routes to 20 specialized commands via argument dispatch. The pin
mechanism (/impeccable pin audit) restores standalone shortcuts on
demand for commands users hit all the time.
## Architecture
- Single /impeccable skill with command router section in SKILL.md
- 20 commands served via reference files under source/skills/impeccable/reference/
- /impeccable pin <command> creates a lightweight redirect shim so users
who prefer /audit, /polish, etc. can still have them
- Context gathering (teach) auto-runs on first use
- command-metadata.json is the single source of truth for command
descriptions, argument hints, and relationships
## Site rewrite
- Docs URL: /skills renamed to /docs (with /skills permanent redirects)
- Homepage hero frames Impeccable as "one skill with 20 commands"
- "Get Started" split into 50/50 install + how-to-use with editorial
numbered steps, /impeccable shown as the home command with three modes
- New /docs overview: home command hero card + dense category rows
matching the old cheatsheet density, with leads-to/pairs-with/
combines-with relationship metadata served from a shared source
- Cheatsheet merged into /docs, /cheatsheet redirects
- Magazine spread and mobile cards show /impeccable as a stacked
namespace label above the command name at full display size
- Periodic table updated with craft/teach/extract as first-class cells
- Skill detail pages generate from reference files, with an editorial
wrapper per command for tagline + body
- Tutorials and anti-patterns pages updated to use /impeccable <cmd>
## Build system
- Dead code removed (scripts/lib/transformers/shared.js)
- Build log wording fixed ("1 skill" not "1 skills (1 user-invocable)")
- generateApiData fallback branch removed (throws loudly if metadata
missing instead of silently degrading)
- Commands API includes editorial tagline alongside the long description;
UI surfaces prefer tagline for human display, description for auto-
trigger keyword matching
## Gitignore
- Added .claude/scheduled_tasks.lock, .claude/settings.local.json to
ignore list (local Claude Code state that should not be tracked).
- Harness skill directories (.claude/skills/, .agents/skills/, etc.)
remain tracked by design: npx skills reads them from this repo at
install time and they enable clean submodule use.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
f957fcad20
commit
b0f44f83c6
+9
-7
@@ -2,8 +2,6 @@ import { serve, file } from "bun";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import homepage from "../public/index.html";
|
||||
import cheatsheet from "../public/cheatsheet.html";
|
||||
import gallery from "../public/gallery.html";
|
||||
import privacy from "../public/privacy.html";
|
||||
import {
|
||||
getSkills,
|
||||
@@ -42,15 +40,19 @@ const server = serve({
|
||||
|
||||
routes: {
|
||||
"/": homepage,
|
||||
"/cheatsheet": cheatsheet,
|
||||
"/gallery": gallery,
|
||||
"/privacy": privacy,
|
||||
|
||||
// Legacy URL redirects (kept stable for external links and existing users).
|
||||
"/cheatsheet": Response.redirect("/docs", 301),
|
||||
"/gallery": Response.redirect("/visual-mode#try-it-live", 301),
|
||||
"/skills": Response.redirect("/docs", 301),
|
||||
"/skills/:id": (req) => Response.redirect(`/docs/${req.params.id}`, 301),
|
||||
|
||||
// Generated sub-pages — served directly from the pre-generated files
|
||||
"/skills": () => serveGenerated(path.join(ROOT_DIR, "public/skills/index.html")),
|
||||
"/skills/:id": (req) => {
|
||||
"/docs": () => serveGenerated(path.join(ROOT_DIR, "public/docs/index.html")),
|
||||
"/docs/:id": (req) => {
|
||||
const id = req.params.id.replace(/[^a-z0-9-]/gi, "");
|
||||
return serveGenerated(path.join(ROOT_DIR, `public/skills/${id}.html`));
|
||||
return serveGenerated(path.join(ROOT_DIR, `public/docs/${id}.html`));
|
||||
},
|
||||
"/anti-patterns": () => serveGenerated(path.join(ROOT_DIR, "public/anti-patterns/index.html")),
|
||||
"/visual-mode": () => serveGenerated(path.join(ROOT_DIR, "public/visual-mode/index.html")),
|
||||
|
||||
@@ -47,10 +47,68 @@ export async function getSkills() {
|
||||
return skills;
|
||||
}
|
||||
|
||||
// Read commands (user-invocable skills)
|
||||
// Read a short tagline for a command from its editorial file
|
||||
// (content/site/skills/<id>.md). Returns null if the file or tagline is
|
||||
// missing. Taglines are used by UI surfaces that need a human-friendly
|
||||
// one-liner; `description` stays optimized for auto-trigger matching.
|
||||
async function readCommandTagline(id) {
|
||||
const editorialPath = join(PROJECT_ROOT, "content/site/skills", `${id}.md`);
|
||||
if (!existsSync(editorialPath)) return null;
|
||||
try {
|
||||
const raw = await readFileContent(editorialPath);
|
||||
const match = raw.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (!match) return null;
|
||||
const taglineMatch = match[1].match(/tagline:\s*"([^"]+)"/);
|
||||
return taglineMatch ? taglineMatch[1] : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Read commands. After the v3.0 consolidation, commands are sub-commands of
|
||||
// /impeccable. Read them from command-metadata.json and include the root
|
||||
// impeccable skill itself so UI surfaces (cheatsheet, magazine spread) can
|
||||
// list them.
|
||||
export async function getCommands() {
|
||||
const allSkills = await getSkills();
|
||||
return allSkills.filter(s => s.userInvocable);
|
||||
const metadataPath = join(PROJECT_ROOT, "source/skills/impeccable/scripts/command-metadata.json");
|
||||
|
||||
const commands = [];
|
||||
const impeccable = allSkills.find(s => s.name === "impeccable");
|
||||
if (impeccable) {
|
||||
commands.push({
|
||||
id: "impeccable",
|
||||
name: "impeccable",
|
||||
description: impeccable.description,
|
||||
tagline: await readCommandTagline("impeccable"),
|
||||
userInvocable: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (existsSync(metadataPath)) {
|
||||
try {
|
||||
const raw = await readFileContent(metadataPath);
|
||||
const metadata = JSON.parse(raw);
|
||||
for (const [id, meta] of Object.entries(metadata)) {
|
||||
commands.push({
|
||||
id,
|
||||
name: id,
|
||||
description: meta.description,
|
||||
tagline: await readCommandTagline(id),
|
||||
userInvocable: true,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error reading command metadata:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: return just user-invocable skills if no metadata
|
||||
if (commands.length === 0) {
|
||||
return allSkills.filter(s => s.userInvocable);
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
// Get command/skill source content
|
||||
|
||||
Reference in New Issue
Block a user