* Don't force init on scoped commands when PRODUCT.md is missing
Setup step 1 told the agent: "If it reports NO_PRODUCT_MD, stop and
follow reference/init.md before doing anything else." For a project with
no PRODUCT.md, that turned every scoped request (polish, critique, audit,
layout, ...) into a full from-scratch init detour. The user asks to
polish one button and the skill instead starts writing PRODUCT.md from
the beginning. Faced with that gate, agents also frequently abandon the
command and do an ad-hoc pass without loading the command reference.
Make the gate command-aware. A missing PRODUCT.md still routes into init
for the from-scratch build flows where captured product context is the
point (init, craft, shape). For any other command, a scoped request
against existing code, the code is the context: proceed with the
requested command, infer the register from the surface in focus, and
offer /impeccable init once as a suggestion rather than a blocker.
- skill/SKILL.src.md: rewrite the step 1 NO_PRODUCT_MD rule; reconcile
the no-argument routing rule so it leads the menu with init instead of
silently jumping into it; extend the craft init-then-resume footnote to
cover shape, now also a from-scratch flow.
- skill/scripts/context.mjs: soften the NO_PRODUCT_MD message to defer to
the step 1 rule instead of "Stop the current task"; refresh the stale
file-level JSDoc that still described the old empty-stdout signal.
- tests/skill-behavior/scenarios.test.mjs: add scenario 10 (scoped
command + no PRODUCT.md proceeds without forcing init) and scenario 11
(shape + no PRODUCT.md still diverts into init). Scenario 1 (craft
diverts) stays green and pins the build path.
Source-only per repo convention; provider and plugin copies are
regenerated by the maintainer's build:skills sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Fix missing-context routing for build intent
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
* Fix live variant cycling hydration mismatch on SSR frameworks
Drive variant visibility and range/toggle --p-* custom properties through
an injected session stylesheet instead of mutating hidden/style on
server-rendered variant divs. Fixes flaky nextjs-app-router expectConsoleClean
failures (issue #287), same pattern as scroll-anchor (#276) and pick-cursor (#286).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor variant-state stylesheet for readability
Extract named display constants (VARIANT_HIDE_DECL / VARIANT_SHOW_DECL) and
small variantStateSelector / variantParamDecls helpers so the rule-building is
self-documenting. Restore the scroll-lock comment to startScrollLock. No
behavior change; regression guards updated to match.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: keep variant-state stylesheet in sync on first-reveal and paramless cycle
Stop refreshParamsPanel from removing the injected variant-state sheet
during GENERATING first-reveal, and re-sync the sheet when cycling to a
paramless variant so stale --p-* rules do not persist. Harden the
updateVariantStateStylesheet guard to num == null || num < 1.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: apply tuned --p-* inline for client-mounted Svelte component variants
Svelte component sessions mount into [data-impeccable-component-mount]
with no [data-impeccable-variant="N"] wrapper for the state stylesheet to
target. Restore inline --p-* on the client-mounted element for range/toggle
params while keeping the SSR div path on the injected stylesheet.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
A file-scoped wildcard ignore (add-value <rule> "*" --file <glob>) silently no-op'd for rules with no extractable value, such as side-tab. isIgnoredFindingValue bailed on an empty value before the wildcard/file-scope branch could run.
Require a value only on the specific-value path; let the scoped wildcard match on rule + file. Mirrored in skill/scripts/hook-lib.mjs for CLI/hook parity.
* Fix Pi global install path
* Simplify Pi skills-path helpers and consolidate tests
One userProviderSkillsDir helper owns the HOME_SKILLS_DIR_OVERRIDES
lookup, read paths share existingSkillsDirs, and the five Pi install
tests collapse into two that keep the same coverage: global detection
plus the agent-path write, and project scope in a home-rooted repo.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Respect requested scope when resolving Pi skills dirs
An explicit install scope now narrows providerSkillsDirCandidates to
the matching layout, so a project-scope install in a home-rooted repo
no longer matches an existing global Pi install and get swallowed by
the already-installed refresh path. Update/check flows still probe
both layouts since they have no scope. Covers the T-Rex repro in the
home-rooted regression test.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refresh every existing Pi layout on unscoped update
deduplicateProviders keeps one entry per existing layout instead of
only the first, so unscoped check/update refresh both ~/.pi/agent/skills
and ~/.pi/skills when a home-rooted repo holds copies in each. Home-dir
detection now compares realpaths, since findProjectRoot resolves
symlinks while homedir() does not.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Abdul Wahab <abdulwahab@Abduls-MacBook-Pro-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
The PostToolUse hook was writing hook.cache.json after every edit, even
when nothing was scanned or recorded. Gate the persist to earned writes
only, and key the cache to the edited file's project root when the
session starts from an umbrella directory.
Fixes#344, #305
Co-authored-by: Abdul Wahab <abdulwahab@Abduls-MacBook-Pro-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
The Pinstripe display face was single-weight, so every `font-weight` on it
was inert — the documented h1/h2 weight split never actually rendered.
Switch --ks-font-display (and --ks-font-wordmark) to plain Alumni Sans, which
honors weight, and set the display scale intentionally:
- Display / h1 -> weight 100 (thin hairline hero)
- Headline / h2 -> weight 300 via --ks-type-headline-weight (light anchor)
- Wordmark 400, body 400, title 500 unchanged
Centralize h2 weight: the eight section-title sites that hardcoded 600 now
read var(--ks-type-headline-weight), so h2 weight is a single lever.
Google Fonts now loads Alumni Sans wght@100;300;...;700 and no longer pulls
the Pinstripe family. DESIGN.md, design.json, and the token/CSS comments are
updated to match (family, weights, Two-Face and Weight-Inversion rules).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex loads bundled plugin lifecycle hooks from `hooks/hooks.json` using a
strict schema that accepts only the top-level `hooks` field. The
plugin-packaged manifest carried a top-level `description`, so Codex rejected
the whole manifest with `unknown field description, expected hooks` and the
post-edit design detector never registered (issue #330).
Drop `description` from `buildClaudePluginHooksManifest()` and regenerate
`plugin/hooks/hooks.json`. The Claude Code plugin path is unaffected (it only
reads the `hooks` object). Add a regression assertion for the plugin artifact
and bump the skill version to 3.9.1.
Claude-Session: https://claude.ai/code/session_013GTTHY6uHwESUyAgjUEm7x
Co-authored-by: Claude <noreply@anthropic.com>
The generated skill-release tweet pointed at the deprecated
`npx skills add pbakaus/impeccable`; the canonical install/update path
is `npx impeccable install`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add codex-grid-background detector rule
Detects the Codex two-axis grid-line background tell: a single background
value carrying two or more hairline `linear-gradient(... 1px, transparent
1px)` layers (one per axis), usually paired with a repeating
`background-size` cell. Gated behind --gpt like the sibling codex tells,
off by default.
Counts hairline stops within a single background declaration (not across
the page) so unrelated single-axis ruled lines don't add up to a false
flag, and matches the stop directly rather than parsing whole gradient
layers, since colors like oklch(...) carry nested parens.
Extends the gpt-tells fixture with one flag case and two pass cases
(single-axis rule, two-color blend), regenerates the browser detector
bundle, and bumps the rule count 44 -> 45.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Require tiling background-size for codex-grid-background
Address review: two hairline gradients alone draw a fixed crosshair, not a
grid. Scope detection to a single style block (CSS rule body or inline
style attr) and require both >=2 hairline stops AND a tiling
`background-size` px cell in the same block, matching the skill rule's
"plus background-size" wording. Add a crosshair-without-tiling pass case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Scope codex-grid-background hairline count to background values
Address review: count hairline stops only inside background/background-image
declaration values, not the whole style block, so a hairline in an unrelated
property (mask-image, border-image) can't stand in for the grid's second
axis. Add a bg+mask-image hairline pass case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add .gitignore seeding to init for ephemeral .impeccable output
Init now runs ensure-gitignore.mjs to write a marked block to the shared, committed .gitignore so screenshots, live session/preview/cache dirs, hook caches, and per-dev config.local.json never pollute git status across the team. Shared artifacts (config.json, live/config.json, design.json, critique/*.md) stay tracked. Unlike the existing hook/live runtime helpers, which write machine-local .git/info/exclude lazily, this targets .gitignore at init time so every clone is covered up front.
* Fix: unanchored patterns + git-aware tracking for init gitignore
Cursor Bugbot on PR #314 flagged two issues. (1) Patterns were root-anchored (/.impeccable/...) so they missed a nested monorepo .impeccable (apps/web/.impeccable/...); dropped the leading slash to match HOOK_LOCAL_IGNORE_PATTERNS / LIVE_IGNORE_PATTERNS. (2) detectTrackedArtifacts used fs.existsSync, reporting untracked/ignored files as committed; replaced with git ls-files based analyzeTracked that returns gitAvailable, tracked (confirmed shared artifacts), and needsUntrack (committed ephemeral files -> git rm --cached candidates). init Step 7 wording updated to match.
* Pivot to docs-only .gitignore snippet per maintainer feedback
Reverts the automated init Step 7 and the ensure-gitignore.mjs helper/script tests. Adds a copy-paste .gitignore block to the README instead, covering ephemeral .impeccable/ output (screenshots, live session/preview/cache dirs, hook caches, per-dev config.local.json) while keeping shared artifacts (config.json, live/config.json, design.json, critique/*.md) tracked. Patterns are unanchored so they also cover a nested monorepo .impeccable under apps/web/.
* Point DESIGN.md spec links at the open-source GitHub spec.
The Stitch docs site is client-rendered and unreliable for agent fetch; the
google-labs-code/design.md repo tracks the latest machine-readable spec.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Sync plugin and harness copies after DESIGN.md spec link update.
build:release copies skill/reference into plugin/ and all harness dirs, so
refresh those generated outputs here instead of leaving plugin/ stale.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use raw GitHub URL for DESIGN.md spec in agent-facing refs.
The blob URL serves HTML; raw.githubusercontent.com returns plain markdown
that agents can fetch directly.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix: preserve external skills symlink on first install (#295)
* Fix review comments: target-based in-project link detection (#295, #308)
- isInProjectProviderLink now inspects the symlink TARGET lexically instead of comparing shared realpaths, so two providers pointing at the same external dir are no longer misflagged as in-project (cursor High / greptile P1).
- A dangling in-project cross-provider link is now correctly replaced with a real per-provider dir (cursor Medium).
- Adds regression tests for both scenarios.
The skill declared only `Bash(npx impeccable *)` in allowed-tools, but Setup and the no-arg menu shell out to `node {{scripts_path}}/*.mjs`. Under a default-deny Claude Code allowlist those calls are blocked, so Setup fails on context.mjs.
Add a provider-aware `Bash(node {{scripts_path}}/*)` entry and resolve {{scripts_path}} in the frontmatter (the build previously substituted it only in the body). Provider-aware rather than the hardcoded `.claude/...` path the issue suggested, since five providers honor allowed-tools with different script dirs.