mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
a443ec0d8f0c718e8ebbe8ca165f68fa92f3dd88
56
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
641ff95502 |
CLI: --version reports the npm package version (#731)
* CLI: --version reports the npm package version npx impeccable --version printed 3.6.0 after the 4.0.0 release because the shim handed every argument to the engine, whose baked-in CLI_VERSION still said 3.6.0. The shim now answers --version and -v from its own package.json, as docs/CLI-CONTRACT.md specifies, without locating or downloading a binary; a test covers both flags. The engine's CLI_VERSION moves to 4.0.0 for the next engine release, with the cli-version golden re-recorded and the delta noted. Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY * CLI shim: --version wins whenever it leads, trailing arguments or not Co-Authored-By: Claude Code <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY --------- Co-authored-by: Claude Code <noreply@anthropic.com> |
||
|
|
e2ff625b63 |
The Rust engine: one binary replaces every script and the JS detector, fully open (#714)
* Add oracle harness: verb goldens and function-level vectors
Records stdout/stderr/exit/files for every impeccable verb over a fixed
corpus and replays them against an alternate implementation. Adds a loader
hook that captures per-function call vectors from the pure engine modules.
Prepared with AI assistance (Claude Code).
* Oracle: hook, hook-before-edit, hook-admin cases and goldens
Prepared with AI assistance (Claude Code).
* Add docs/CLI-CONTRACT.md: observable behavior of every impeccable verb
Prepared with AI assistance (Claude Code).
* Oracle: context/doctor/pin/surface-brief/critique/palette/embed/signals/csp/seed/genimg/question cases and goldens
Prepared with AI assistance (Claude Code).
* Oracle: live-mode cases and goldens (roots, inject, wrap, insert, accept, session, manual edits, daemon)
Prepared with AI assistance (Claude Code).
* Oracle: mask the binary path before HOME; export launcher env to the binary
Prepared with AI assistance (Claude Code).
* detect: set process.exitCode instead of exiting after the final write
process.exit() right after a large piped stdout write truncated JSON output
at the pipe buffer boundary; found by the oracle harness. Re-record the six
directory-scan goldens that had captured the truncation.
Prepared with AI assistance (Claude Code).
* Oracle: normalize the hook-admin command in both runtimes' forms and audit chars
Prepared with AI assistance (Claude Code).
* Skill text: invoke the impeccable launcher instead of node scripts
Every `node {{scripts_path}}/<name>.mjs` becomes `{{scripts_path}}/impeccable <verb>`
(context-signals -> signals, hook-admin -> hooks). Setup step 1 drops Node, points
Windows shells without sh at impeccable.cmd, and says the launcher runs a
self-contained binary. allowed-tools follows.
Prepared with AI assistance (Claude Code).
* Scripts dir: replace the Node scripts with the impeccable launcher
skill/scripts keeps command-metadata.json and the page JS; every .mjs entry
point, lib/, and live/ are gone (the binary owns those verbs). Adds the POSIX
launcher, impeccable.cmd, VERSION (copied from the new root ENGINE_VERSION),
scripts/fetch-engine.mjs (bun run fetch:engine) to pull the pinned binary
into skill/scripts/bin/<os>-<arch>/, and gitignores that bin dir.
Prepared with AI assistance (Claude Code).
* Build: ship the launcher instead of bundling the JS engine
readSourceFiles no longer copies cli/engine into the skill; the scripts
payload is the launcher (executable bit preserved through dist, plugin/, and
universal.zip), impeccable.cmd, VERSION (synced from ENGINE_VERSION on every
build), the page JS, and command-metadata.json. Hook manifests call
`<scripts>/impeccable hook` behind an existence guard (Codex adds a
commandWindows sibling calling impeccable.cmd; Cursor runs hook-before-edit;
GitHub keeps the git rev-parse form; Grok mirrors Claude); the Node probe and
systemMessage notice are gone. build:release fetches the pinned engine for
every target (lenient) and stages bin/<os-arch>/ into the dist skill copies
after root harness dirs and plugin/ were synced, so git-delivered trees stay
launcher-only. The detection-rule count check reads the vendored
extension/detector/antipatterns.json and is skipped when absent.
build:browser is a stub; the codex prefix rewrite leaves
`{{scripts_path}}/impeccable` alone.
Prepared with AI assistance (Claude Code).
* CLI: turn the impeccable npm package into a platform-binary shim
cli/engine, cli/lib, and cli/bin/commands are gone; their behavior lives in
the engine binary. cli/bin/cli.js now resolves the binary from IMPECCABLE_BIN,
the @impeccable/cli-<os>-<arch> optional dependency (templates under
cli/platform-packages/, published by the engine release), the
~/.impeccable/bin/<version>/ cache, or a checksum-verified download, and
execs it. package.json drops the engine dependencies and the library
exports; puppeteer moves to devDependencies for the icon scripts.
README.npm.md describes the shim.
Prepared with AI assistance (Claude Code).
* Tests: gate behavior on the oracle and the engine binary
Unit tests of the deleted Node scripts and the JS detector are removed;
their behavior is pinned by tests/oracle goldens (frozen JS behavior plus
reviewed deltas) and the engine's own tests. tests/oracle.test.mjs replays
the corpus against the binary (IMPECCABLE_BIN or skill/scripts/bin/<target>/,
via tests/lib/engine-bin.mjs) and skips cleanly without one; the framework
fixture sweep drives live-inject, live-wrap, and detect-csp through the
binary the same way. record.mjs learns --bin. The function-level vectors
under tests/oracle/vectors/calls are committed as the frozen snapshot they
can no longer be regenerated from. Suites: core trimmed to build and
transformer tests, oracle added to the default run, detector/live reduced to
packaging and reference checks, the live-e2e helper tests move to the opt-in
live-e2e lane pending its retarget, cli-remote-e2e is an empty placeholder.
Prepared with AI assistance (Claude Code).
* Docs: describe the launcher, the engine pin, and the oracle gate
CLAUDE.md gains an Engine binary section (launcher lookup order, ENGINE_VERSION,
untracked binaries, how tests get one, the oracle as behavior gate, what stays
JavaScript) and drops the Node-script and JS-detector descriptions; the CLI
and detection-rule sections point at the shim and the engine repo. README.md
states the skill needs no runtime and lists the launcher-based hook commands;
AGENTS.md follows. CLI-CONTRACT.md's intro notes the scripts it quotes are
the recorded source, not the tree.
Prepared with AI assistance (Claude Code).
* Tests: tighten the hook command guard assertion
Prepared with AI assistance (Claude Code).
* Oracle: re-golden 46 cases for the engine's own command names; record them in DELTAS.md
Prepared with AI assistance (Claude Code).
* Build: ship launcher-only release zips by default
IMPECCABLE_BUNDLE_ENGINE=1 opts in to staging the engine binaries into the
dist skill copies. Bundling every target into every provider copy put
dist/universal.zip near 340 MB, past the 25 MB Cloudflare Pages file cap
that impeccable install downloads through.
Prepared with AI assistance (Claude Code).
* Tests: drive the live-e2e orchestrator through the engine binary
The session, fake-agent loop, steer test, and manual-edit probe spawn
<binary> <verb> (live-server, live, live-inject, live-wrap, live-insert,
live-accept, live-poll, live-complete) resolved by tests/lib/engine-bin.mjs
instead of node skill/scripts/live-*.mjs; the completion typing the agent
imported from the deleted live/completion.mjs is a small local helper. The
live-e2e helper unit tests move back into the default live suite (the steer
loop skips without a binary).
Prepared with AI assistance (Claude Code).
* Tests: run new-work-e2e through the engine's serve-question and generate-image verbs
Prepared with AI assistance (Claude Code).
* Tests: point the skill-behavior harness at the launcher and engine binary
The bash tool exports IMPECCABLE_BIN so the staged skill's launcher runs
without a download; scenarios assert on 'impeccable context' instead of
context.mjs and skip without a binary.
Prepared with AI assistance (Claude Code).
* Tests: note what plugin-e2e validates before and after the generated-output sync
Prepared with AI assistance (Claude Code).
* Oracle: record the engine's 'wasm-unsafe-eval' CSP meta patch as a reviewed delta
Prepared with AI assistance (Claude Code).
* Rebase reconciliation: fold main's post-freeze work into the swapped tree
The rebase onto origin/main brought changes whose JS engine halves left the
tree with the swap. This commit reconciles what survives:
- Suite map: register main's comp-fidelity unit tests (build-phase,
comp-diff, font-match, hero-checks) in the core suite and
live-browser-ignores in the live suite.
- Payload guard: the skill scripts payload now allowlists the comp-fidelity
build pipeline (comp-spec/comp-diff/build-phase/font-match and their libs),
the one Node toolchain that has not moved into the engine.
- Drop skill/scripts/live/project-ignores.mjs, lib/live-path-globs.mjs, and
their test: they import hook-lib/live-inject/impeccable-paths, which the
swap deleted, and their consumer (the JS live server) is the engine now.
- skill text: the comp pipeline's calls to engine verbs (generate-image,
embed-prompt) use the launcher spelling.
- Oracle: re-record 17 detect goldens over the fixture set main changed
(oklch #592, color-mix #578, 1D grid #615, the two comp-fidelity rules)
and record the gap in DELTAS.md; those JS rule changes are not yet ported
to the engine, and the goldens pin its current behavior.
bun run test (oracle included) and bun run build are green on this tree.
AI-assisted change: implemented with Claude Code.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaJv2c4oN8wS7Ttq4XRqyx
* Launcher: engine-probe PATH validation, working .cmd download path; CI: drop stale path, add oracle job
Byte-identical copies of the engine repo's launchers (engine main
af7572c): the retired 3.x npm CLI on PATH or in ~/.impeccable/bin is
rejected by the engine-probe handshake instead of hijacking every verb;
impeccable.cmd's download path is rewritten as straight-line goto flow
(the parenthesized blocks expanded %url%/%cached% at parse time, making
it dead code) with certutil sha256 verification and a windows-arm64 ->
x64 asset fallback; the final error points at the release download
instead of npm i -g (npm still serves the 3.x CLI).
ci.yml: the generated-output check no longer diffs the deleted
cli/engine/detect-antipatterns-browser.js, and a new oracle job fetches
the pinned engine (bun run fetch:engine) and replays tests/oracle/
against it. The job is continue-on-error with a loud warning until the
first engine release exists; flipping it to required is a release-time
toggle, documented in the workflow.
Verified here: sh -n on both launcher copies, bun run build green, full
oracle replay against the rebuilt engine binary green (770 pass, 0
fail), and a launcher behavior test proving a fake 3.x CLI on PATH is
skipped while the download + checksum chain completes against a local
file server.
Prepared with AI assistance (Claude Code).
* Oracle: restore detector goldens to post-fix behavior after the engine ports
The Aug 17-31 detector fixes (oklch parsing, color-mix nested hex, 1D grid
pass, comment stripping, root-relative linked stylesheets, URL userinfo
redaction, inert ignore-value refusal) and the comp-fidelity rules
organic-clip-path / buried-raster are ported to the engine. Re-records the
gap-pinning detect goldens from the fixed binary (glow.html included: its
.photo-opaque-grad column now carries the buried-raster finding it was
written for), replays the frozen checkHtmlPatterns call vectors through the
last JS engine state in history (db1462b9^; args untouched, 14 of 101
results moved), and rewrites the DELTAS gap section into the landed-ports
note. Each re-recorded json fixture golden byte-matches that JS state's
output; oracle: 770 pass, 0 fail.
Prepared with AI assistance (Claude Code).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WaJv2c4oN8wS7Ttq4XRqyx
* Oracle: pin the Aug 17-31 verb fixes ported to the Rust engine
New cases: hook-session-grok-edit-then-stop (Grok Build camelCase envelope,
end_turn/shutdown/stopHookActive Stop handling,
|
||
|
|
a264199177 |
Fix skill subcommand help handling (#708)
Ensure install, link, update, and check render static help before entering any operational path. Covers top-level and legacy routing for both -h and --help. AI-assisted implementation under maintainer direction. |
||
|
|
482368511a |
Fix Codex skill version metadata (#703)
Move Codex and .agents skill versions under metadata while keeping all version readers compatible with legacy top-level frontmatter.\n\nAI assistance: prepared with Codex under maintainer direction. |
||
|
|
9736a9f6e9 |
Fix OpenCode slash command bridge (#483)
Add a first-class OpenCode command bridge across builds, installs, updates, linked installs, and pinned shortcuts. Preserve current provider behavior while backfilling missing or drifted command files.\n\nAI assistance: contributor and maintainer work used AI tools as disclosed in the PR discussion and commits. |
||
|
|
f3df3ffe40 |
Add Veto harness support (#675)
Adds Veto detection, provider transforms, installation paths, documentation, and regression coverage.\n\nAI-assisted maintainer repair, review, and validation by Codex under maintainer direction. |
||
|
|
be87f5eb86 |
Fix: refuse inert exact ignore-value entries (#662)
ignore-value stored exact values for rules that cannot extract one, so the entries never matched. Refuse them and point at "*" --file. AI assistance: implemented with Cursor Grok 4.6. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
af2e8b3ac3 |
Fix: stream bundle downloads to disk instead of buffering
AI assistance: implemented with Cursor Grok 4.6. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5d932f9fbe |
Fix: safe temp staging and downloadFile error handling (#479)
AI assistance: implemented with Cursor Grok 4.6. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
49571365a8 |
Fix: rewrite Grok project hooks to the global skill path (#642)
Grok was skipped by the hook-command rewrite, so a global skill install left .grok/hooks/impeccable.json pointing at a project-relative hook.mjs that does not exist. AI assistance: Cursor Grok 4.6 implemented this change. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
56f44523f7 |
Merge pull request #627 from pbakaus/codex/issue-624-claude-agents
Fix Claude agent installation |
||
|
|
d2a9efb90f |
Preserve inferred agent update scope
Prepared with AI assistance from Codex under explicit maintainer authorization. |
||
|
|
16a218e632 |
Fix home-scoped agent freshness
Prepared with AI assistance from Codex under explicit maintainer authorization. |
||
|
|
7b94585653 |
Fix Claude agent installation
Prepared with AI assistance from Codex under explicit maintainer authorization. |
||
|
|
665c51b903 |
Fix Windows hook migration dedupe
Normalize hook command separators before matching Impeccable-owned entries so updates replace legacy Windows guards instead of duplicating them.\n\nAI assistance: Codex implemented and validated this change under maintainer authorization. |
||
|
|
e36833ce21 |
Merge pull request #521 from digitallamb/pr/hermes-provider
Add Hermes Agent as a supported provider |
||
|
|
def69e157b |
fix(cli): use imported resolve/sep in hermesGlobalHome (#521)
The function called `path.resolve` and `path.sep` but only named- imports `resolve` and `sep` from `node:path`. The ReferenceError was swallowed by the try/catch, so $HERMES_HOME was silently ignored and profile-scoped installs always landed in ~/.hermes instead of the active profile. Greptile (P1) and Cursor Bugbot (High) flagged this on 2026-08-10. Adds 6 regression tests covering default, default- profile, active-profile, cross-home leakage, the override map integration, and the full e2e pipeline. Verified by reverting the fix and observing the relevant tests fail. |
||
|
|
490dcfd678 |
Fix #476: stop using JSON.stringify/double quotes as shell quoting in four exec sites (#533)
* Fix: use argv exec and single-quote escaping for the four #476 shell-injection sites JSON.stringify and raw double-quote interpolation were used as shell quoting, but /bin/sh still expands $(...), backticks, and ${} inside double quotes. - is-generated.mjs / live.mjs runScript: switch execSync string commands to execFileSync argv form, which never invokes a shell. Closes the remote path where a source file named `$(...)` executes during the live-mode walk. - skills.mjs hook command + hook-lib.mjs ignore-value suggestion: values that must stay shell strings now use POSIX single-quote escaping instead of JSON/double quotes. The doctor's hook-token parser learns the single-quoted absolute form so it keeps verifying user-level installs. Adds regression tests for the single-quoted absolute hook form and the single-quoted ignore-value suggestion. Verified end to end in a browser through a real live-mode wrap walk against a hostile-named source file. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> * Test: lock in POSIX single-quoting for a $(...) absolute install path (#476) Follow-up from security review: prove an install path embedding $(...) is single-quoted in the written hook manifest, not double-quoted. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: quote ignore-command args per platform so Windows cmd.exe keeps spaces (#533) Greptile flagged that switching quoteCommandArg to POSIX single quotes fixed $(...) injection on /bin/sh but regressed Windows cmd.exe, where single quotes are literal, so a --file path containing spaces was split and the ignore scope was stored malformed. The suggested command runs on the same machine the hook fired on, so branch on process.platform (the pattern skills.mjs already uses): single-quote on POSIX for the #476 fix, and keep the original double-quote escaping on Windows so that path's behavior is unchanged. Adds a regression test asserting both forms. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> * Test: prove the POSIX hook guard is inert under /bin/sh and Windows keeps double quotes (#533) Greptile's probe could not reach the generated manifest, leaving the hook command contract unverified. Convert that into committed proof: - POSIX: install with a $(touch pwned) absolute path, then actually execute the generated guard under /bin/sh from a clean cwd and assert no marker file appears and the guard exits 0 (single-quoted substitution stays inert). - Windows: drive copyProviderHooks as win32 in-process and assert the command keeps the double-quoted absolute path (usable when the install path has spaces; $(...) is inert on cmd.exe anyway). Test-only; source quoting is unchanged. Prepared with AI assistance (Cursor) under maintainer instruction. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
628aac5a40 |
Fix: point install's next step at the agent chat, not the terminal (#472) (#532)
The install completion message said to run /impeccable init "in your AI harness", and users pasted it into their shell instead (bash: /impeccable: No such file or directory). Say the command is typed in the AI coding agent's chat, and give `npx impeccable init` a pointed redirect instead of the generic unknown-command error. A real path named `init` still routes to detect as before. Prepared with AI assistance (Cursor agent), directed by @abdulwahabone. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
477484aaee |
Fix: install missing explicitly selected providers without --force (#536)
* Fix: install missing explicitly selected providers without --force (#500) An explicit --providers list now treats "already installed" per selected target: providers with an existing install take the update path, providers with none get a fresh install (skills + hooks) in the same run. Previously any existing install (e.g. .claude) made `install --providers=grok` exit 0 without writing .grok, leaving Grok Build on the Claude-variant fallback. Written with AI assistance (Cursor agent), reviewed and tested by maintainer. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix: copy provider agents for freshly installed mixed-install targets Bugbot caught that the mixed explicit-providers path installed skills and hooks for missing targets but skipped copyProviderAgents, which both the update and fresh-install paths run. Written with AI assistance (Cursor agent). Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5ce4a5c6b5 |
Add Hermes Agent as a supported provider
Impeccable now ships a Hermes-compatible bundle under dist/hermes/.hermes/skills/. Hermes reads the Agent Skills spec as-is, so the bundle uses the four spec frontmatter fields (name, description, version, license) plus metadata/compatibility and drops the Claude/Codex-specific extensions Hermes would silently ignore. The .hermes/skills/ tracked root and the regenerated pin.mjs mirrors will be produced by .github/workflows/sync-generated-output.yml after this lands; per AGENTS.md, generated harness churn stays out of feature PRs. What a Hermes user gets: - npx impeccable install --providers=hermes --scope=project writes .hermes/skills/impeccable/ into the cwd. - npx impeccable install --providers=hermes --scope=user honors $HERMES_HOME, so a profile-scoped install (HERMES_HOME=~/.hermes/profiles/forge) lands in the active profile's skills dir, not the default ~/.hermes/. Cross-home HERMES_HOME inheritance is ignored so test isolation holds. - /impeccable registers as a Hermes slash command and routes sub-commands via the Commands table in the skill body, since user-invocable / argument-hint are not honored by Hermes' skill loader. What a Hermes user does NOT get, and why: - No hook surface. Impeccable's PostToolUse/Stop anti-pattern detector on Claude/Codex/Cursor/Grok/GitHub does not translate to Hermes, which has no equivalent tool event lifecycle. The skill body still ships. - No writeOpenAIMetadata, agentFormat, or emitHooks. Hermes has no per-skill tool ACL, no subagent on-disk format, and no hooks.json equivalent. Verified end-to-end with hermes-agent v0.18.2: /impeccable polish and /impeccable critique both load reference/<command>.md and return the documented first step. parse_frontmatter accepts the generated SKILL.md, scan_skill_commands registers /impeccable, and the full default test suite passes (267/267 in the critical files; 0 fail across all suites). |
||
|
|
1a3f588c71 |
Fix: skip POSIX hook guard on Windows installs (#452) (#453)
* Fix: skip POSIX hook guard on Windows installs (#452) PowerShell rejects the `[ ! -f ... ] ||` guard at parse time, so Codex hooks generated by `npx impeccable install` on Windows never ran. Emit the direct `node "PATH"` invocation there; POSIX output is unchanged. AI-assisted (Cursor). Co-authored-by: Cursor <cursoragent@cursor.com> * Keep the missing-file no-op in Windows-generated hook commands Greptile review on #453: project hook manifests are committable, so a bare `node "PATH"` written on Windows loses the silent no-op when a POSIX teammate without the skill consumes it. Replace the bare form with a shell-agnostic `node -e` existence guard that parses in PowerShell, cmd.exe, and sh, and forwards the hook's exit code. AI-assisted (Cursor). Co-authored-by: Cursor <cursoragent@cursor.com> * Use Codex's commandWindows field for the Windows hook guard Per @PatrickSys on #452: Codex runs hooks through COMSPEC (cmd.exe /C), not PowerShell, and 0.146.0+ selects a commandWindows manifest field on Windows. Codex entries now always carry the POSIX guard in command plus an `if exist` cmd.exe guard in commandWindows (his Windows-tested form), so one .codex/hooks.json is correct on every OS regardless of where the install ran. Claude/Cursor keep the node -e wrapper on Windows installs since their manifests have no per-platform field. AI-assisted (Cursor). Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
2d489f898b | Fix Antigravity global skills path to ~/.gemini/config/skills/ | ||
|
|
0a3c12f78e | Add Antigravity install instructions and global auto-detection hints | ||
|
|
bf957452c4 | Add Google Antigravity provider support | ||
|
|
1132e7fff0 |
Fix Pi provider display name
AI-assisted change. |
||
|
|
fa1177ed9c |
Ship native subagent definitions for GitHub Copilot and Cursor
The github and cursor providers previously received only the generated
degraded/ inline fallbacks. Both harnesses support real custom subagents,
so the build now emits them from the same skill/agents/ source:
- GitHub Copilot: .github/agents/impeccable-<role>.agent.md with portable
frontmatter only (name + description; omitting tools grants all tools,
and Copilot has no documented model/effort/max-turns equivalents).
- Cursor: .cursor/agents/impeccable-<role>.md with name, description,
model: inherit, is_background: false, and readonly derived from the
agent's tool list (true only for the finish reviewer, which declares
neither Write nor Edit). effort/max-turns are skipped because Cursor's
effort option requires an explicit model id.
Agent bodies now also resolve {{scripts_path}} and strip rule markers in
the shared agentFormat pipeline, which fixes the previously unresolved
placeholder in the emitted Claude asset-producer agent.
The CLI installer places agents per scope: project installs write
<repo>/.github/agents/ and <repo>/.cursor/agents/; user-level installs
write ~/.copilot/agents/ (Copilot's user dir, not ~/.github/) and
~/.cursor/agents/, overwriting stale impeccable-* copies. Because
Copilot lets user-level agents shadow same-named project ones, a project
install warns when shadowing copies exist; Cursor gives project agents
precedence, so no warning there.
new-work.md and visualize.md extend their harness-naming clauses with
the Cursor and Copilot invocations. The degraded/ fallbacks keep
shipping for surfaces where the model still fails to delegate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
cda1c572d9 |
Guard the OpenCode legacy migration against symlinks and home-rooted repos
Both review bots caught real hazards in the migration: a symlinked ~/.opencode/skills (shared skill storage) would have its target emptied through the link, and in a home-rooted repo that path is a live project-scope install, not a stranded pre-#406 global copy. The migration now requires a real directory (lstat), compares the just-written dir by realpath instead of string, and skips entirely when the home dir is itself a repo. Two regression tests cover the symlink and dotfiles-repo shapes. Prepared with AI assistance (Claude Code), directed by @pbakaus. Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
caef4b8e4c |
Install global OpenCode skills into the config dir OpenCode actually reads
npx impeccable install --providers=opencode --scope=global wrote to ~/.opencode/skills, but OpenCode discovers global skills from its config directory: $OPENCODE_CONFIG_DIR/skills, else $XDG_CONFIG_HOME/opencode/ skills, else ~/.config/opencode/skills. The install succeeded and `opencode debug skill` never listed it (issue #406, diagnosed by @dergachoff). HOME_SKILLS_DIR_OVERRIDES entries become functions of the home dir (the Pi override from #327 was the only entry and is unchanged in behavior), with OpenCode resolving through the env chain above. Detection gains a resolver-based GLOBAL_HARNESS_HINTS entry so a machine with only ~/.config/opencode (no legacy ~/.opencode) still routes global installs to OpenCode. After a global install, the skills just written are removed from the stranded ~/.opencode/skills location; sibling skills and the rest of ~/.opencode stay untouched, and the empty skills dir is pruned. Four new CLI tests (failing-first): default config-dir install, OPENCODE_CONFIG_DIR and XDG_CONFIG_HOME precedence, legacy-copy migration with sibling preservation, and config-dir-only detection. Prepared with AI assistance (Claude Code), directed by @pbakaus. Co-Authored-By: Claude Code <noreply@anthropic.com> |
||
|
|
bcf354cd0c |
Fix Codex hook path so .codex-directory installs run the detector
The committed .codex/hooks.json hardcoded .agents/skills/impeccable/scripts/
hook.mjs. On a .codex-directory install the skill payload lives at .codex/
skills/..., so the guarded command ([ ! -f X ] || node X) found no file and
silently no-opped, leaving the design detector dead for those users.
Derive the hook payload path from the emitting provider's own configDir rather
than hardcoding .agents:
- buildCodexHooksManifest(skillDir) now builds `${skillDir}/skills/impeccable/
scripts/hook.mjs`; hooksJsonFor threads each provider's configDir through. The
Codex provider (configDir .codex) emits .codex/skills; the root sync and the
self-consistent dist/codex bundle both point at their own payload.
- CLI installer: project-scope hook rewriting now derives the provider's own
project-relative path instead of preserving the bundle token. The Codex bundle
ships a .codex/skills command, but the CLI lays the skill at .agents/skills, so
the installed .codex/hooks.json is rewritten to .agents/skills (Claude keeps
its ${CLAUDE_PROJECT_DIR} token; global installs keep the absolute rewrite).
Per-provider hook payload path after the fix:
Emission hook path
dist/codex/.codex/hooks.json .codex/skills/impeccable/scripts/hook.mjs
root .codex/hooks.json (build sync) .codex/skills/impeccable/scripts/hook.mjs
CLI .agents (codex) project install .agents/skills/impeccable/scripts/hook.mjs
CLI .agents (codex) global install <home>/.agents/skills/.../hook.mjs (abs)
.claude / .cursor unchanged
Tests: extended hook-build (codex-dir -> .codex/skills, agents-dir -> .agents/
skills) and skills-cli (bundle ships .codex/skills, install rewrites to .agents/
skills). Regenerated tracked .codex/hooks.json via build:release.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
13c078ae93 |
Fix user-level hook path crash and clarify skills update scope (#399)
Part 1 — user-level hooks got a project-relative command. copyProviderHooks
only rewrote the bundled ${CLAUDE_PROJECT_DIR}-relative hook command to an
absolute skill path when the skill lived elsewhere than the manifest root. A
user-level update (root === ~) kept ${CLAUDE_PROJECT_DIR}, which a global
~/.claude/settings.local.json resolves per-project — crashing node at module
resolution on every PostToolUse/Stop in any project without a local skill copy.
Now the command is rewritten to the resolved absolute path whenever the manifest
is a user/global file (isHomeDir(root)) as well as the pre-existing
skill-elsewhere case, and every hook command is wrapped with a missing-file
guard `[ ! -f "PATH" ] || node "PATH"`. The guard exits 0 when the script is
absent (upholding hook.mjs's "never break a turn" contract even before node can
load it) while preserving node's own exit code when present, so Claude's exit-2
blocking signal still reaches the agent. Project-scope hooks keep the portable
${CLAUDE_PROJECT_DIR} token.
Part 2 — skills update silently targeted CWD. update now resolves and names the
target explicitly (project vs user level, with the absolute path), honors
--user/--project, only counts a provider as installed when the impeccable skill
itself is present (so it never vendors a copy into a repo that merely tracks
other first-party skills), and offers the choice when both a project and a
user-level install exist instead of silently picking. Non-interactive runs
default to the project and print how to target the other.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
06d21dea7d |
Add first-class Grok Build harness support
Emit .grok skills, agents, and PostToolUse/Stop hooks; wire the CLI installer and downloads; fix the plugin install path to #plugin; and document Grok in HARNESSES.md and README. AI assistance: written with Grok Build. |
||
|
|
83c37e453a |
feat: add Mistral Vibe harness support (#373)
* feat: add Mistral Vibe harness support
Mistral Vibe is Mistral AI's open-source CLI coding assistant that ships an
Agent Skills system at .vibe/skills/{name}/SKILL.md with slash-command
invocation, mapping cleanly onto the existing transformer pipeline. Adds
Vibe as a 14th first-class harness:
- PROVIDER_PLACEHOLDERS entry in scripts/lib/utils.js (model, config_file
= AGENTS.md, ask_instruction, command_prefix) mirroring the Qoder shape.
- PROVIDERS entry in scripts/lib/transformers/providers.js with configDir
.vibe and frontmatterFields user-invocable, license, compatibility,
metadata, allowed-tools (Vibe docs do not document argument-hint).
- transformVibe named export in scripts/lib/transformers/index.js for
test-spy parity.
- vibe added to FILE_DOWNLOAD_PROVIDER_CONFIG_DIRS so the download
endpoint accepts /api/download/skill/vibe/* and resolves to
dist/vibe/.vibe/.
- .vibe added to PROVIDER_DIRS, PROVIDER_ALIASES, PROVIDER_DISPLAY,
PROVIDER_INPUT_ORDER, GLOBAL_HARNESS_HINTS, and the normalizeForHash
provider regex in cli/bin/commands/skills.mjs so the CLI detects
existing Vibe installs.
- docs/HARNESSES.md updated: official docs row, frontmatter support
column, directory structure row, and Last verified date bumped.
- docs/DEVELOP.md, README.md (install instructions, providers list,
Supported Tools), .github issue/PR templates, sync-generated-output
workflow, and AGENTS.md extended with Vibe.
The dynamic providers.test.js loop picks up Vibe automatically; all 160
provider tests pass including the 14 new Mistral Vibe cases. The build
regenerates dist/vibe/.vibe/skills/impeccable/ with correct frontmatter
and .vibe-substituted script paths. Generated harness output is left
unstaged per the source-first policy; the sync-generated-output workflow
(now listing .vibe) will commit it back to main after merge.
Co-Authored-By: Mistral Vibe <noreply@mistral.ai>
* Address review on Vibe harness support
Verified the Vibe claims against the docs and the mistralai/mistral-vibe
source, then tightened what the tables say.
- model placeholder: 'Mistral', not 'the model'. Vibe is Mistral's own
first-party CLI, so it belongs with gemini -> Gemini and codex -> GPT
rather than with the provider-agnostic harnesses. SKILL.src.md's one
use of {{model}} now renders 'Mistral is capable of extraordinary
work.' instead of a lowercase 'the model'.
- Docs links point at the skills page, not the product overview, matching
every other row in both tables.
- disable-model-invocation is No, not TBD. The field appears nowhere in
Vibe's source; unknown frontmatter keys are silently ignored.
- Split the directory row into project and global scopes the way the Pi
row already does. Vibe reads .vibe/skills/ and .agents/skills/ at the
project level and ~/.vibe/skills/ and ~/.agents/skills/ globally; the
global .agents dir was missing, and project .agents/skills/ was sitting
in the global column. Sources: vibe/core/paths/_local_config_files.py
and vibe/core/config/harness_files/_paths.py.
- Restored 'Last verified' to 2026-04-28 and dated the Vibe row on its
own. Only that row was checked, and this file warns against trusting
stale claims, so a blanket re-date made ten other rows look fresher
than they are.
AI assistance: written with Claude Code.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Mistral Vibe <noreply@mistral.ai>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
331540ddec |
Scope a single rule to a file with ignore-value "*" --file (#379)
* Scope a single rule to a file with ignore-value "*" --file
`ignore-file <glob>` was the only file-scoped escape the hook offered, and
it is far blunter than most findings justify: it silences every rule for
that path forever, including rules not written yet. A real UI surface with
one noisy rule had no proportionate option.
Add a file scope to `ignore-value`, so one rule can be turned off in
matching files while staying active everywhere else:
hooks ignore-value design-system-font-size "*" --file "src/widget.js"
- Refuse a bare `"*"` with no `--file`. Suppressing a rule project-wide is
`ignore-rule`'s job, and the error says so.
- Reject unknown `--flags` instead of folding them into the value.
`ignore-value overused-font Inter --shard` stored the value
"inter --shard", matched no finding, and reported success.
- Key dedup on the file scope too. The same rule/value legitimately
appears more than once with different scopes; the old rule+value key
silently overwrote the earlier entry.
- Keep normalizer key order (rule, value, files, createdAt, reason) in
step across both copies. Normalizing runs on every write, so emitting a
different order than what is on disk rewrites untouched entries.
- Lead with the narrow form in the hook's directive footer and hooks.md;
`ignore-file` is now documented as the whole-file-out-of-scope case.
Dogfoods it on skill/scripts/live-browser.js, where all 32 findings are
design-system-font-size: the overlay is injected over arbitrary host pages
and builds a self-contained UI, so DESIGN.md's ramp does not describe it.
The other rules stay live for that file.
Assisted-by: Claude Code
* Show the file scope in hooks status, and stop the wildcard error misdirecting
Two findings from Cursor.
status formatted every ignore value as rule=value and dropped files. Now
that the primary hooks path writes file-scoped `"*"` entries, that rendered
`design-system-font-size=*` — which reads as exactly the project-wide
wildcard this command refuses, the opposite of what is on disk. Print the
scope, matching the `rule=value [files]` shape `impeccable ignores list`
already uses. This repo's own config already carries several scoped
wildcards written through the CLI path, so status has been under-reporting
them.
The bare-wildcard refusal always pointed at `ignore-rule <rule>`. For
overused-font that command refuses on its own without --all-values, so the
guidance handed the user a second error. Name the flag for that rule.
Assisted-by: Claude Code
* Refuse an empty --file glob, and store multi-file scopes in canonical order
Two Copilot findings, both the silent-no-op class this PR exists to remove.
An empty glob was dropped by filter(Boolean). So
`ignore-value overused-font Inter --file=` reported "Added
overused-font=inter" and wrote an entry with no files: the user asked to
scope a rule to one file and silently got the project-wide suppression
instead — broader than what they asked for, reported as success. Refuse an
empty or whitespace glob on every form (--file, --file=, --files, --files=)
in both the hook-admin and CLI paths.
Multi-file scopes were deduped but not ordered, and the dedup key compares
the files array, so `--file b.css --file a.css` stored a second entry
distinct from `--file a.css --file b.css`. Sort at parse so storage is
canonical, and sort inside the key so entries already on disk in another
order still compare equal.
Assisted-by: Claude Code
* Sort files in every dedup key, not just two of the four
My previous commit sorted the file scope at parse time and inside
ignoreValueFilesKey, and stopped there. Cursor pointed out ignoreValueKey
(CLI) and ignoreValueEntryKey (hook-admin) still joined `files` in stored
order, so add/remove dedup missed any on-disk scope whose glob order
differed from the sorted argv form: a re-add duplicated the entry and a
remove silently failed.
Four functions hash `files`; I had fixed two. All four sort now. The
remaining `files.join(', ')` call sites are display, not keys.
Verified against a config seeded in non-sorted order, as an older client
would have written it: the re-add updates the existing entry rather than
duplicating it, and remove-value finds it. Test covers that shape.
Assisted-by: Claude Code
* Refuse a following flag as a --file glob
Cursor again, same class as the last two. requireGlob checked non-empty but
not whether the argv it consumed was itself a flag, so
`ignore-value design-system-font-size "*" --file --reason "why"` took
`--reason` as the scope, left "why" to fold into the value, stored
value="* why" files=["--reason"], and reported success. Garbage, announced
as done.
Refuse a glob starting with `--`, in both the hook-admin and CLI paths.
Assisted-by: Claude Code
|
||
|
|
c775e03c1d |
Fix Pi global install path (#338)
* 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> |
||
|
|
467efe4632 |
Fix: preserve external ~/.claude/skills symlink on first install (#295) (#308)
* 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. |
||
|
|
f1e9b3df3a |
Fix: fail loudly on unknown CLI subcommands (#270)
Unknown/mistyped CLI subcommands now print 'Unknown command' and exit non-zero instead of silently routing to the detector. Closes #266. Version bump and changelog entry deferred (batching). Co-Authored-By: abdulwahabone |
||
|
|
41ff946121 |
Add GitHub Copilot hook support (CLI + cloud agent) (#279)
* Add GitHub Copilot hook support (CLI + cloud agent) Wire the Impeccable design detector into GitHub Copilot's hook system so direct file edits get the same post-edit design feedback the Claude Code, Codex, and Cursor harnesses already receive. GitHub Copilot's contract differs from the existing harnesses (verified against Copilot CLI 1.0.63): - Repo-level manifest at `.github/hooks/impeccable.json` (read by both the CLI, once committed to the default branch, and the cloud/app agent). - Flat `postToolUse` entries with `bash`/`timeoutSec` and a full-match `matcher` regex; the file-editing tools are `edit` and `create`. - The stdin event uses camelCase `toolName`/`toolArgs`, where `toolArgs` is a JSON *string* carrying the touched file under `path`. - Context is injected via a top-level `additionalContext` string. Changes: - hooks.js: buildGitHubHooksManifest() + route `github` in hooksJsonFor(). - providers.js: emitHooks/hooksManifestRel for the github provider. - hook-lib.mjs: detect the github harness, normalize the camelCase event (parse the JSON-string toolArgs -> tool_input.file_path), and emit the `additionalContext` payload shape. - hook-admin.mjs / skills.mjs: install + idempotent-repair the `.github/hooks/impeccable.json` manifest (bash-aware marker stripping). - hooks.md: document GitHub Copilot as a supported harness. - Tests for the builder, routing, event normalization, and end-to-end run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Cover Copilot apply_patch edits in the hook (live-verified) The first cut matched only `edit|create`, the tool names `copilot -p` uses. A live trace against Copilot CLI 1.0.63 in an interactive session showed it edits files via `apply_patch`, whose toolArgs is a raw OpenAI-format patch string (`*** Begin Patch` / `*** Add File:`), not JSON. With the narrow matcher the hook command never ran. - hooks.js / hook-admin.mjs: matcher -> `edit|create|apply_patch`. - hook-lib.mjs: normalizeGitHubEvent now routes apply_patch's raw patch string into tool_input.command (reusing the existing parseApplyPatchPaths / resolveTargetFiles plumbing) and only JSON-parses toolArgs for the edit/create/view tools. tool_name is normalized to apply_patch so the patch path is extracted even if a future build relabels the tool. - Tests: apply_patch matcher assertions, event normalization, and an end-to-end runHook covering the interactive/cloud path. Verified live: a trusted interactive `apply_patch` edit fires the hook and returns the expected `additionalContext` design reminder. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address review feedback + add changelog entry - hook-lib.mjs (Bugbot, low): looksLikeApplyPatch no longer misroutes an edit/create event whose edited *content* contains apply_patch markers. A real apply_patch payload is a raw string that does not parse as JSON; an edit payload is a JSON object, so only non-JSON-object strings are treated as apply_patch. Edit events keep extracting `path`. Adds a regression test. - skills.mjs (Bugbot, medium): document why `.github` is intentionally excluded from hookScriptPathForProvider. Its hook manifest is committed and shared (read by the Copilot cloud agent and teammates), so the command must stay portable via `$(git rev-parse ...)`; rewriting it to a machine-local absolute path would break those. GitHub skills are project-scoped, so the project-relative path resolves. - changelog: add an Upcoming (v3.x placeholder) entry for the Copilot hook. Version is not bumped yet (batching with other changes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8b0c895703 |
[codex] Fix CLI skill update detection (#257)
* Fix CLI skill update detection * Preserve linked skills during install refresh * Keep existing installs working offline * Respect provider scope during install refresh |
||
|
|
51d01e3a5f |
[codex] Add design-aware detector rules (#252)
* Add design-aware detector rules * Fix design-aware detector noise * Unify CLI and hook detector ignores * Fix remaining design-system review findings * Add detector ignore CLI * Fix design detector review findings * Fix design color source false positives * Fix core test suite registration * Add design-aware detector docs * Fix font priority design-system parsing * Fix color ignore value matching |
||
|
|
c27a75ad41 |
fix(cli): replace extract-zip with fflate to fix silent install on Node v24.16.0+ (#253)
On Node v24.16.0 / v26.1.0+, `impeccable install` printed "Downloading impeccable skills...", exited 0, and installed nothing. A Node streams regression (nodejs/node#63487) made pause()/resume() no-ops on destroyed streams, stalling extract-zip's yauzl/fd-slicer read stack partway through; its promise never settled and the process exited clean with no error. Swap extract-zip for fflate across both extraction call sites (downloadAndExtractBundle, copyOrExtractLocalBundle) via a new extractZip helper. fflate decompresses from an in-memory buffer and never touches the fs stream path, so it is immune on every Node version. It is pure JS with zero dependencies, so the Windows fix from #198 (no `unzip` binary) holds. Unlike extract-zip, fflate is actively maintained. Because extractZip writes entries itself, it guards against zip-slip (`../` entries escaping the target dir). Tests add a many-file regression guard (fails on partial extraction) and a zip-slip rejection test. Verified end-to-end: the real 1,194-file universal bundle extracts and installs completely. Fixes #250. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6443980117 | Improve CLI install prompts | ||
|
|
32c01595e2 | Prepare CLI 3.0.1 install targeting fix | ||
|
|
8cf2be110d |
feat(cli): interactive hook consent + unified .impeccable/config.json (#245)
* feat(cli): interactive hook consent + unified .impeccable/config.json Make the design-hook install a conscious choice and unify scattered config into one file. Interactive consent - On an interactive `skills install`/`update`, the CLI explains what the hook does and offers to install it (default yes), then records the per-developer decision in the gitignored `.impeccable/config.local.json`, so it never re-asks. A recorded decision or an already-installed hook short-circuits; `-y`/non-TTY keeps the historical install-by-default behavior; `--no-hooks` is a one-off skip that records nothing. The trigger keys on "is the hook installed?" + "is there a recorded decision?", not a brittle version check. Unified config - `.impeccable/config.json` (shared) and `.impeccable/config.local.json` (gitignored) now hold all Impeccable settings: hook settings under a `hook` key, plus top-level `updateCheck`. `/impeccable hooks` writes the `hook` subtree, preserving siblings. The hook runtime reads `hook.quiet` and `hook.auditLog`; context boot reads `updateCheck`. The legacy `IMPECCABLE_HOOK_DISABLED|QUIET|LOG` and `IMPECCABLE_NO_UPDATE_CHECK` env vars still work and override config; docs now lead with config and treat env vars as a legacy note. - No backward compat for the pre-unification `hook.json`/`hook.local.json` (the hook shipped an hour ago; nothing in the wild uses it). This repo's own hook config is migrated to `.impeccable/config.json`. The CLI and skill scripts are separate trees, so a small CLI-side config module (cli/lib/impeccable-config.mjs) duplicates the config-path and .git/info/exclude handling; comments flag the duplication. Tests: new cli config unit test; skills-cli consent tests (declined skips, accepted installs, --no-hooks records nothing); hook.test.mjs back-compat removed and quiet/auditLog-from-config + gitexclude coverage added. Full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(hooks): preserve sibling config fields + resolve audit log from event cwd (Bugbot) Two Bugbot findings: - High: `/impeccable hooks` edits replaced the whole `hook` object with the merge-helper output, dropping fields those helpers don't manage — so an `ignore-value --local` could wipe the recorded install consent and make the CLI re-prompt. writeConfig now merges over the existing hook object, keeping consent/quiet/auditLog. - Medium: config-based audit logging resolved hook.auditLog from process.cwd(), which can differ from the hook event's project root (and Cursor's pre-edit hook passed no cwd). The hook now stamps the resolved project root on the audit entry, and writeAuditLog reads config from entry.cwd when present. Tests: a /impeccable hooks edit preserves consent + quiet; writeAuditLog resolves config auditLog from entry.cwd, not the fallback cwd. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(hooks): resolve a relative auditLog path against the project root (Bugbot) A relative hook.auditLog was read from the project root but written relative to the hook process cwd, so when those differ the log went to the wrong place. writeAuditLog now resolves a relative target (from env or config) against the same project root it reads config from. Absolute and ~/ paths are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Fix hook consent recovery and smoke config * Fix hook consent explainer for Cursor * Fix empty hook target consent --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9c0012d4e1 |
feat(hooks): package design hook in plugin, install to settings.local.json (#243)
* feat(hooks): package design hook in plugin, install to settings.local.json
Three related changes to how the Impeccable design hook is distributed,
plus an unrelated build fix discovered along the way.
Package the hook in the Claude Code plugin
- The marketplace / `/plugin install` path previously shipped the skill and
agents but no hook, so those users never got the design detector. The build
now emits `plugin/hooks/hooks.json` (auto-discovered at the plugin root),
resolving the script via `${CLAUDE_PLUGIN_ROOT}` so it works wherever Claude
Code unpacks the plugin instead of assuming a `.claude/skills/` layout.
CLI installs the hook into settings.local.json, not shared settings.json
- `npx impeccable skills install/update` now writes the Claude hook to the
gitignored `.claude/settings.local.json` (a machine-local install side
effect) rather than the team-shared `settings.json`, which could otherwise
be committed and break for teammates without the skill installed.
- Graceful handling (leave-it-never-duplicate): if our hook already lives in
the shared `settings.json` (a legacy install or a deliberate user move), it
is honored in place and never duplicated into the local override, which
would otherwise run the detector twice per edit.
- The skill's `/impeccable hooks on|off` toggle is unaffected: it only writes
`.impeccable/hook.json`, never the settings files.
Fix universal.zip build failure under archiver v8
- `archiver` was bumped to v8 (now ESM, factory function removed) but
`scripts/lib/zip.js` still used the old `archiver('zip', ...)` API, so every
build silently failed to produce `dist/universal.zip` (the skill-release
artifact). Switched to `new ZipArchive({...})`.
Also folds in a pre-existing local rename of the hook status message
("Scanning design" -> "Checking UI changes") and its regenerated provider
output.
Tests: new coverage for the plugin-packaged hook manifest and the
shared-settings honor-in-place path; existing CLI assertions moved to
settings.local.json. Full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): detect hook by marker, not file existence (Bugbot)
hookInstalledForProvider treated any existing settings.local.json (or
hooks.json) as proof the hook was installed. Those files commonly hold
unrelated local settings, so the already-installed `skills install` path
would skip repairing a genuinely missing hook that `update` would add.
Detect the Impeccable marker in the file instead of mere existence. Adds a
test for the exact case: a settings.local.json with only permissions still
triggers hook repair and preserves the unrelated content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(build): fail loud on a broken release zip + cover the zip writer
Close the gap that let the archiver v8 break ship a 0-byte universal.zip
with a green test suite:
- createProviderZip no longer swallows failures. It throws on a missing
source, an archive with zero entries, or a 0-byte output, and build() now
exits non-zero on any such rejection. A build that can't produce its release
artifact fails instead of deploying an empty bundle.
- New tests/zip.test.mjs exercises the real zip writer and round-trips through
extract-zip (the unpacker the CLI uses): a valid bundle unpacks to the skill
tree, and the empty/missing-source cases throw. Wired into the core suite so
it runs in `bun run test`.
Why this matters: the prior CLI e2e tests stub the bundle as a local
directory, so they never built, downloaded, or unzipped a real archive. The
zip writer had no coverage and failed soft, so Dependabot's archiver 7->8
major bump merged green and the deploy shipped an unusable bundle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): scope hook marker scan to the hooks subtree + prune local dupes (Bugbot)
Two follow-ups from Bugbot:
- fileHasImpeccableHookMarker scanned the whole settings file as raw text, so
an unrelated string (e.g. a permissions allow entry that mentions the hook
path) could falsely read as an installed hook and block install/repair or
the shared-settings skip. Now it parses the JSON and scans only the `hooks`
subtree.
- When the hook is honored in the shared settings.json, copyProviderHooks
skipped the local write but left a stale hook in settings.local.json from an
earlier machine-local install, so Claude Code loaded both and ran the
detector twice per edit. It now prunes the local copy (preserving unrelated
local settings, dropping the file if only our scaffolding remained).
Adds tests for both: a permissions string mentioning the hook path still
triggers repair, and a shared hook prunes the stale local duplicate while
keeping unrelated permissions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
672517f76e |
Add automatic design hook install and exceptions (#170)
* docs: add PRD for design detector hook integration Plans a PostToolUse hook for Claude Code and Codex that runs the existing design detector after every relevant file write and feeds findings back to the agent as advisory system-reminder context. No implementation in this commit; covers UX, technical design, build pipeline changes, distribution, coverage tradeoffs, and rollout. Co-authored-by: Cursor <cursoragent@cursor.com> * docs: revise hook PRD with best-practices review Folds in the P0/P1/P2 findings from an online best-practices critique against the official Claude Code and Codex hook references plus 10+ 2026 community guides and similar prior-art tools (claw-hooks, claude-code-hooks-mastery). Key changes: - Exec form everywhere (Codex snippet was shell form), with Windows rationale. - Default timeout dropped from 10s to 5s. - Re-entrancy guard (CLAUDE_HOOK_DEPTH) and per-file edit counter. - Session-scoped finding dedup promoted from open question to v1. - Per-language inline-ignore syntax map (HTML/JSX/CSS/JS). - Hard-skip rules for sensitive paths and generated/lock files. - Honest framing about Claude Code lacking per-plugin hook disable. - Honest framing about Bash-written files being invisible in v1. - Codex Windows-not-supported call-out, feature flag note, trust ceremony detail. - Optional NDJSON audit log via IMPECCABLE_HOOK_LOG. - Findings cap lowered 8 → 5 with attention-budget rationale. - Versioned envelope ([impeccable@1]) on rendered template. - Expanded test plan, decision log, and stdin payload appendix. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(hooks): ship the design detector hook for Claude Code and Codex Implements docs/hooks-prd.md: a PostToolUse hook that runs the impeccable design detector after every Edit/Write/MultiEdit on a UI file and pushes findings into the agent's next-turn context as a short system reminder. Silent on clean files. Never blocks an edit. Why this matters: today, design slop (side-tab borders, gradient text, purple/cyan palettes, bounce easing, etc.) only gets caught when a human notices or someone explicitly runs /impeccable audit. The hook closes the loop at the moment slop is written. What ships in v1 - skill/scripts/hook.mjs: PostToolUse entry. Reads stdin, runs the detector in-process (no `npx impeccable` cold start), emits hookSpecificOutput.additionalContext when fresh findings exist. - skill/scripts/hook-lib.mjs: extracted helpers (config, cache, filter, render, audit log, runHook orchestrator). 100% unit-testable. - skill/scripts/hook-session-start.mjs: SessionStart greeting, gated by a project-scannable probe + 30-day throttle. - skill/scripts/hook-admin.mjs: backs /impeccable hooks on/off/status/ignore-rule/ignore-file/reset. Hardening built in - Re-entrancy guard (IMPECCABLE_HOOK_DEPTH) so the hook can never recursively spawn itself. - Hard-skip regexes for sensitive paths (.env, .pem, id_rsa, secrets, credentials, .git) and generated/lock/build output. These fire before the file is even read; cannot be turned off via config. - Path-traversal check on the inbound file_path. - Session-scoped dedup keyed by (session, file, rule, line) so the same finding never lands in context twice. Prevents the ~12.5K wasted tokens per chatty session called out in the PRD. - Per-(session, file) edit counter with a one-shot suppression notice on the 7th edit, silent after. - Fail-open contract: every error path returns exit 0 with no stdout. Optional NDJSON audit log via IMPECCABLE_HOOK_LOG. Three kill switches (precedence high to low): 1. IMPECCABLE_HOOK_DISABLED env var (1/true/yes/on, case-insensitive) 2. .impeccable/hook.json `enabled: false` 3. /impeccable hooks off slash command (writes the JSON) Inline ignores are language-aware. `// impeccable: ignore <rule>` for JS/TS, `<!-- impeccable: ignore <rule> -->` for HTML/Vue/Svelte/Astro, `{/* impeccable: ignore <rule> */}` for JSX/TSX, `/* impeccable: ignore <rule> */` for CSS. `*` matches any rule. Directive applies to the next non-blank line. Same shape as ESLint, Stylelint, Biome. Build pipeline - scripts/lib/transformers/hooks.js: per-provider hooks.json builders, plus the slim .codex-plugin/plugin.json manifest. - providers.js: emitHooks: 'claude' for claude-code, emitHooks: 'codex' for codex and agents. Codex also emits emitCodexPlugin. - factory.js: emits hooks/hooks.json next to the skills tree. - build.js: syncs hooks/ into harness roots and into the slim plugin/ subtree; writes .codex-plugin/plugin.json. Build is idempotent (verified: 98 staged files unchanged across two runs). Claude Code wiring uses exec form (command + args) and the ${CLAUDE_PLUGIN_ROOT} placeholder. Matcher: Edit|Write|MultiEdit. `if:` glob filters to UI extensions before spawning Node. PostToolUse timeout 5s, SessionStart timeout 3s. Codex wiring uses ${PLUGIN_ROOT} (Codex's native placeholder), matcher Edit|Write|apply_patch, no `if:` analog (the script does the extension filter). macOS and Linux only; hooks are disabled on Windows in current Codex builds. The trust ceremony and feature flag are documented in README.md. Routing - /impeccable hooks lives outside the 23-command router table on purpose: it is plumbing, not a design skill. The hidden routing slot is added to SKILL.md alongside pin/unpin so the LLM knows to dispatch it. The 23-command count and all stale-count validators remain happy. Tests - tests/hook.test.mjs: 38 unit tests covering env parsing, config load + defaults + malformed, cache round-trip + GC, ignoreRules/minSeverity/inline ignores (all four languages), globbing with **/*/{a,b}, render template with cap + clamp + 0-line prefix drop, audit log NDJSON, payload event-name parameterization, re-entrancy, kill switches, sensitive-path + generated-path + traversal skips, allowlist filter, config ignoreFiles, edit counter cycle including the 7th-edit notice, MultiEdit and apply_patch payload shapes, detector throw swallow, malformed stdin, missing file race. - tests/hook-build.test.mjs: 18 integration tests covering hook manifest shape (matcher, timeouts, exec form, if: glob, placeholders), Codex differences (${PLUGIN_ROOT}, no if:, no SessionStart), Codex plugin manifest (no inline hooks field to avoid the duplicate-file error), routing across the hooksJsonFor table, and presence of all three committed artifacts plus the bundled detector the runtime relative-import path depends on. Full suite: 175 bun tests + 186 node tests, all green. Docs - README.md: new "Design hook" section explaining default behavior, per-project / global / inline disable paths, the JSON schema knobs, the audit log debug flag, and the slop / a11y coverage split. - HARNESSES.md: flips the `hooks` row for Codex from No -> Yes (Claude was already Yes), adds a per-harness hook-surface table with the manifest location and matcher each provider uses. Open questions from the PRD intentionally deferred to v2: Bash-write blind spot, effort-aware suppression, Stop-hook session summary, per-rule severity, async hook mode. None block v1. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Codex hook scanning: apply_patch paths and co-located stylesheets Parse file targets from Codex apply_patch command bodies, co-scan imported and sibling CSS when UI components are edited, drop the git-sweep PostToolUse group, and align Codex SessionStart manifest and trust docs with the official hooks spec. Co-authored-by: Cursor <cursoragent@cursor.com> * Gitignore hook session cache and drop local test HTML Hook dedup/throttle state in .impeccable/hook.cache.json is per-project runtime data like other .impeccable/ sidecars. Remove an untracked bad-nested-flexbox scratch page from site/public/. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix Claude Code hook: drop Edit-only if filter so Write/MultiEdit fire Claude's if permission rule binds to one tool name, so Edit(*.{…}) never spawned the hook on Write or MultiEdit despite the matcher listing them. Extension filtering now lives in hook-lib on both Claude and Codex. Co-authored-by: Cursor <cursoragent@cursor.com> * Surface Cursor design findings via stop-hook followup Replace dropped postToolUse additional_context with afterFileEdit recording and a one-shot stop followup_message so anti-pattern nudges reach the agent. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix design hook packaging and scans * Fix Cursor hook pending bucket fallback * Fix Sass hook scan coverage * Fix Cursor hook review findings * Fix session start dead hook normalization * Fix hook config and relative scan paths * Remove SessionStart design hook * Remove redundant afterFileEdit normalization * Fix Cursor suppression and module style scans * Fix sensitive path hook filter * Fix disabled Cursor stop hook emission * Refresh hook harness artifacts * Fix Cursor hook manifest install * Add hook ignore-value support * Ignore hook runtime files locally * Fix Codex plugin hook packaging * fix: address PR review bot findings Block numeric hook depth counters from re-entering. Avoid following stylesheet imports from traversal-looking hook targets. * fix: gate ignore-value suggestions by supported rules Only render exact ignore-value commands when the same finding can be suppressed by ignoreValues. * Package Codex plugin as hook-only * Remove Codex plugin packaging * Recover hook install probe plumbing * Remove Codex hook packaging follow-up doc * Remove extra hook docs and skill wording changes * Install real design hooks via skills CLI * Add provider hook smoke runner * Fix Cursor hook delivery with preToolUse gate * Simplify Cursor hook install to preToolUse * Clarify confirmed hook exceptions * Persist hook ignores in shared config * Guard font hook exceptions * Fix hook install after main rebase * Fix hook scan target handling * fix: address hook review findings * Address hook review feedback * Stabilize DeepSeek insert live fixture * Fix Cursor hook Python shell write bypass --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
05f9797957 | Remove deprecated cleanup migration (#240) | ||
|
|
82801a4894 |
[codex] Improve CI test coverage (#212)
* Improve CI test coverage * Stabilize live E2E harness * Shard live E2E CI * Cache live E2E CI dependencies * Stabilize live E2E smoke CI * Update generated live browser bundles * Tighten live E2E smoke runtime * Prevent live E2E smoke hangs * Stabilize live E2E CI coverage * Fix stale accept DOM cleanup * Regenerate live browser outputs |
||
|
|
1aedbcf538 | Add Git submodule skill linking | ||
|
|
347a0c06a2 | Fix Windows skill bundle extraction (#198) |