Contract-probe campaign findings (evals repo, notes/fable-oneshot-craft-plan.md):
a single model's resonance ranking is deterministic (30/35 identical
concepts across 16 framings); dice must come from the script, mirroring
the palette-seed result. Derived candidates stay grounded in the
audience's world + subject's cultural home; challengers win only on
identification x clarity; incumbent-with-deliberate-idea overrides the
roll. Validated at contract level on 01-observability + r10-lektor
(teletext ranks #3 for lektor; assigned index 3 produced it).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep short crash-recovery leases without allowing a healthy worker to queue its own generation twice, and surface non-monotonic benchmark journals as errors.\n\nAI-assisted: OpenAI Codex.
Return after a durable starting record, overlap app-server initialization with page startup, dynamically reclaim generation after worker failure, and cap hard-crash leases at 15 seconds.\n\nAI-assisted: OpenAI Codex.
Validate and transactionally publish complete structured agent messages as soon as they arrive while retaining turn-completion serialization for subsequent phases.\n\nAI-assisted: OpenAI Codex.
Journal and stream dedicated worker phases so Live distinguishes first-variant design and validation from remaining-direction work without adding pollable events.\n\nAI-assisted: OpenAI Codex.
Update Live Lab and the Live reference with the default Sol worker, full-task quality gate, Spark control, cold readiness, and production architecture.\n\nAI-assisted: OpenAI Codex.
Route the full-context benchmark through production worker inputs and preserve established shared-control visual roles during variant amplification.\n\nAI-assisted: OpenAI Codex.
Default Codex to a dedicated Sol/medium app-server worker with native skill and image inputs, inherited project context, bounded source neighborhood evidence, and progressive context refresh. Other harnesses retain the portable foreground path.\n\nAI-assisted: OpenAI Codex.
Introduce a Live-owned app-server supervisor with progressive fenced publishing, partitioned control polling, cancellation and recovery safety, and measured integration coverage.
AI-assisted implementation under maintainer direction.
Fix batch from the visitor-mode bias audit. The skill's four modes
(Persuade / Operate / Read / Experience) now reach the places that were
still hard-coded to a SaaS-marketing default:
- palette.mjs: rewrote 45 seed blurbs in material/world terms. The 29
tech-tool-world moods (13 Linear-indigo variants, 6 Figma-era, 5
climate-tech, 3 fintech, 2 Glossier DTC, incl. seed-201's docs-page
CTA red) lose all company names and product-category words; Aesop
trimmed from 17 blurbs to 4 and Klim from 7 to 4, excess rewritten
as unnamed material terms. Also carries the earlier bg-block rewrite
(brand refs out of the composition doc).
- init.md: register explainer now names the four modes and the family
each belongs to (stored value stays brand/product for compatibility);
Conversion & proof interview + PRODUCT.md section gated to Persuade
surfaces only (Experience/Read get no CTA/belief-ladder/proof).
- critique.md: Nielsen heuristics 7 and 10 may score n/a on Persuade
and Experience surfaces, total renormalized to the applicable max,
snapshot records which were n/a; working-memory examples diversified
beyond dashboard/pricing anatomy.
- Register headers in bolder/delight/quieter/colorize/layout/animate/
typeset renamed from Brand:/Product: to Persuade + Experience: /
Operate + Read:; typeset and layout gain one Read-specific sentence
(steady reading measure; navigable linearity).
- animate.md: plan checklist and implementation order lead with
feedback and transitions; the single entrance moment comes after,
scoped to modes that invite it.
- codex.md: mock inventory says "primary-action treatment (when the
surface has one)" instead of assuming a CTA.
- delight.md: loading/empty-state/console-egg examples diversified
beyond SaaS; streaks/badges scoped to Operate surfaces with
recurring tasks.
- distill.md: step-removal and next-action lines neutralized away
from signup/checkout/CTA vocabulary.
- document.md: canonical button label GET STARTED -> SAVE CHANGES;
signature components gain a non-marketing example.
- antipatterns registry: single-font rule renamed to "Single font
without hierarchy" with a description that permits one family when
weight/size contrast carries hierarchy.
Staged provider copies regenerated via build:skills:release for the
touched files only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Answering the obvious question the family-depth framing dodged: with
modes derived per task, files named for the old two-register taxonomy
had no architectural reason to exist. brand.md's surviving depth (lane
test + inverse test, reflex-reject lanes, color discipline, layout
moves, permissions) folds into new-work.md, where all of it belonged:
it is new-identity Persuade/Experience guidance. product.md's content
moves unchanged to operate.md, its true name. Both old files remain as
one-line redirect stubs because register: brand|product in existing
PRODUCT.md files and older links point there. All cross-references
retargeted (SKILL.md modes intro, context.mjs REGISTER hint, live.md,
typeset.md); 85 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Architecture per Paul: impeccable is primarily a daily driver on
existing codebases; the always-loaded core should serve that 90% path,
not carry the full generative arsenal on every invocation. SKILL.md now
holds brief-wins, existing-worlds (the headline path), the four visitor
modes, the full craft floor, and a hard gate: new identity work
(greenfield, or a redesign discarding the current look) MUST read
reference/new-work.md before any design decision. That file carries the
generative playbook (seed, subject grounding, plan/self-check/signature,
hero-thesis, everything-bold, prove-don't-claim, color commitment,
calibration, persuade type/imagery). context.mjs enforces the gate
mechanically: NEW_WORK directive when no PRODUCT.md/DESIGN.md exists,
and the old mandatory register-file read is replaced by a REGISTER
family hint. No surfaces: map anywhere; mode is derived per task.
Gate compliance is measurable via skillEvidence.directSkillFileReads.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Eval evidence showed the per-edit PostToolUse stream fires overwhelmingly
on copy-level rules (em-dash-overuse ~97x/session) and measurably makes
models more conservative, while a full-detector pass at completion is what
actually fixes contrast/padding/glow. Split the hook accordingly:
- Per-edit (PostToolUse) now surfaces only IMMEDIATE_TIER_RULES: broken
output (broken-image, text-overflow, clipped-overflow-container,
body-text-viewport-edge), objective contrast/legibility failures
(low-contrast, gray-on-color, tiny-text), single-property mechanical
slop (gradient-text, dark-glow), and design-system drift (the four
design-system-* rules, which compound if left uncorrected). Everything
else defers. Override with hook.perEditRules: "all" in
.impeccable/config.json. Tiering is off for Cursor/Copilot harnesses,
which have no Stop pass wired, so nothing gets silently dropped there.
- Stop deep pass (runStopHook): runs the FULL rule set over every UI file
touched this session (tracked via the existing hook.cache.json session
state; deferred-only edits now mark the file touched), dedupes against
everything already surfaced per-edit, honors ignore-rule/file/value and
inline disables, reuses the [impeccable@1] envelope, and no-ops fast
when no UI files were touched. Emits hookSpecificOutput
{ hookEventName: "Stop", additionalContext } per the Claude Code SDK
Stop contract (conversation continues so the model can act on it).
Second Stop fire is silent - deep-pass findings are remembered.
- Wiring: Stop entries (timeout 30) in plugin/hooks/hooks.json, the
.claude settings + .codex hooks manifests (transformers + hook-admin
repair path). Claude Code and Codex both dispatch a native Stop event;
Cursor's stop hook is inconsistently dispatched (pre-write gate stays)
and Copilot's agentStop/sessionEnd don't inject model context, so
neither gets a Stop entry - documented in reference/hooks.md.
- Tests: tiering split/override/harness gating, Stop dedupe + silent
no-touched-files + ignore machinery + kill switches; existing per-edit
tests moved to immediate-tier rule ids. 181 tests green; smoke-tested
the built dist skill end to end (glow surfaced per-edit, em-dash only
at Stop, second Stop silent).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add OpenAI plugin submission bundle
Build a Codex-native OpenAI plugin with bundled hooks, public listing metadata, submission guidance, privacy coverage, and regression tests.
AI assistance: OpenAI Codex prepared and validated these changes under maintainer direction.
* Fix provider script command rendering
Replace heuristic rewrites across executable scripts with one explicit provider marker, render pinned shortcuts per target harness, and remove the personal email from the public publisher manifest.
Addresses automated review feedback on PR #363.
AI assistance: OpenAI Codex prepared and validated these changes under maintainer direction.
* Add a platform axis (web / ios / android / adaptive) to the skill
Orthogonal to register: register decides whether design IS or SERVES the
product; platform decides the delivery target and which native conventions
apply. Set `## Platform` in PRODUCT.md; a missing field defaults to `web`,
so legacy projects are unaffected.
- extractPlatform() in skill/scripts/context.mjs (mirrors extractRegister);
the CLI appends a NEXT STEP directive to read the native reference(s).
`adaptive` (Flutter / RN / KMP shipping both iOS and Android) loads both
ios.md and android.md.
- New reference/ios.md (Apple HIG distilled) and reference/android.md
(Material 3 distilled); reference/web.md is a thin pointer. The native
refs frame register's role as narrow: platform conformance is the bar,
brand lives in the expressive layer the platform gives you, never by
breaking the rails.
- Setup step 5 loads the native reference(s) when platform is native. Live
mode and the detect CLI stay web-only, gated off ios/android/adaptive.
- init asks platform right after register; adapt/audit/animate/layout carry
short platform divergence notes; all secondary spots thread `adaptive`.
- a11y stays in audit.md (loading it at design time makes output timid), so
the native refs carry no Accessibility section; audit.md's Platform
section owns native a11y.
- Tests: extractPlatform unit coverage + skill-behavior scenario 10
(PRODUCT.md platform ios -> agent loads ios.md).
Source-first: only skill/, scripts/, tests/, CLAUDE.md, NOTICE.md, the
changelog and version are committed; the sync workflow regenerates the
provider trees and ./plugin on merge.
ios.md / android.md are distilled from the MIT-licensed
ehmo/platform-design-skills; attribution in NOTICE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address review: gate web tools on native platforms, drop version churn
Maintainer-review fixes applied with AI assistance (Claude Code), on top
of the rebased platform-axis commit:
- Design hook (post-edit and Cursor pre-edit) now resolves the project
platform via loadContext + extractPlatform and skips its web rule scan
for ios / android / adaptive projects, so React Native / Flutter code
never draws web-shaped findings (new hook-lib resolveProjectPlatform /
isNativePlatform helpers, covered by unit and subprocess tests).
- context.mjs CLI warns on an unrecognized ## Platform value (e.g. a
toolchain name like `flutter`) instead of silently defaulting to web;
extractRegister / extractPlatform now share extractSectionValue.
- Removed reference/web.md: nothing loaded it; CLAUDE.md carries the
"web has no extra rulebook" explanation.
- init.md: skip live-mode config (Step 6) for native platforms; note the
per-app PRODUCT.md pattern for repos shipping web + native.
- android.md: Material-everywhere apps that also ship on iPhone still
owe iOS OS guarantees (safe areas, Reduce Motion, edge-swipe back).
- ios.md: reworded a design-time line that framed Dynamic Type as an
accessibility check (a11y stays owned by audit.md).
- Renumbered the new skill-behavior scenario to 14 after main's 10-13;
updated CLAUDE.md scenario list; added android + unrecognized-value
CLI test cases.
- No version or changelog changes: versioning happens at release time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Tighten platform reference prose
Editorial pass on the platform-axis text, applied with AI assistance
(Claude Code) under maintainer direction:
- ios.md / android.md rewritten to house style: single-line paragraphs
(no hard wraps), one-sentence scope intro, deduplicated intro/slop-test,
register-compression down to two sentences. In-file attribution
paragraphs removed (NOTICE.md owns attribution); "read on top of the
register reference" cruft removed (SKILL step 5 and the context.mjs
directive already say it). Bans sections dropped: they restated the
rules above them; the two additive items (tab-bar overload,
hover-dependent affordances) folded into rules. ~40% smaller each.
- Sub-command Platform sections (adapt, audit, animate, layout), SKILL
step 5, init.md platform prose, and the context.mjs directive trimmed
the same way.
Build (prose validators, counts) and both test runners green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Treat an empty PRODUCT.md section as absent, not the next heading
Copilot review catch: extractSectionValue read the next `## ...` heading
as the section value when a field was left empty, which made the CLI
warn "value `## Product Purpose` is not recognized". Stop at the next
heading and return null instead. Regression tests for extractPlatform,
extractRegister, and the CLI warning path. Applied with AI assistance
(Claude Code) under maintainer direction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Only read a token list of both native targets as adaptive
Bugbot catch: after the exact platform tokens failed, any Platform line
containing the words ios and android was classified adaptive, so
negated or explanatory prose ("web only, not ios or android") silently
loaded both native refs and skipped the hook, with no warning. The
combo parse now accepts only list separators and the two platform
words; anything else falls through to the CLI's unrecognized-value
WARNING. Regression tests added. Applied with AI assistance (Claude
Code) under maintainer direction.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Paul Bakaus <paul.bakaus@gmail.com>
* 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.
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>
* Fix React hydration mismatch from live pick-cursor class on SSR roots
Entering pick mode toggled a `impeccable-live-pick-cursor` class on
`document.documentElement` (and the insert-axis cursor wrote an inline
`style.cursor` on it). `<html>`/`<body>` are server-rendered by frameworks
like Next.js App Router, so a client-only attribute the server HTML never
emitted makes React 19 log "a tree hydrated but some attributes of the server
rendered HTML didn't match" on the next Fast-Refresh re-render. It surfaced as
a console.error that flaked the nextjs-app-router live-e2e fixture's
expectConsoleClean probe.
This is the same root-cause class as the scroll-anchor lock fixed in #276
(client mutation of a hydrated SSR root), but a separate offender that fix did
not cover. Apply the same shape: drive the pick / insert cursor entirely
through the textContent of one injected `<style>` keyed by PICK_CURSOR_STYLE_ID,
never by a class or inline style on `<html>`. Same computed effect (global
`cursor` rule, reverted inside the overlay chrome), recreated on activation and
removed on teardown.
Regression guard updated to pin the new shape: no
`document.documentElement.classList.*` mutation anywhere in the overlay, the
cursor applied through the injected style, and the style removed by id on exit.
Verified end-to-end: the nextjs-app-router live-e2e fixture now passes the full
click -> Go -> cycle -> accept cycle with a clean console.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Remove now-dead pageInteractionCursorActive flag
The flag's only reader was the old inline-style cleanup branch in
syncPageInteractionCursor, which the stylesheet refactor removed. It is now
write-only, so drop the declaration and both writes (Greptile review). No
behavior change.
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 inline, in-file ignore comments for the detector (issue #283)
Complement config ignores with eslint-disable-style waivers that live where
they apply and travel with the file when it leaves the repo. The motivating
case is a generated/exported standalone document that legitimately uses a
first-party brand typeface (on the overused-font list) and is later scanned
without .impeccable/config.json present.
Marker is comment-syntax-agnostic (works in //, /* */, <!-- -->, #, {/* */}):
impeccable-disable <rule>[, <rule>...] [-- reason | : reason] whole file
impeccable-disable-line <rule>... same line
impeccable-disable-next-line <rule>... next line
Bare directive or * means every rule; reason is optional and discarded at
scan time. Behavior is suppression, for parity with config ignores.
Implementation:
- New pure module cli/engine/shared/inline-ignores.mjs (parser + filter, no
Node deps). Static-HTML findings have no line number, so only whole-file
directives apply there -- exactly the standalone-document case; the
regex/text engine additionally honors the line-scoped forms.
- Wired into detectText and detectHtml, gated by options.inlineIgnores.
- detect CLI applies inline ignores by default; --no-inline-ignores skips
just them, --no-config skips config and inline ignores together.
Docs: config.md (new section), detector.md, README. skill/reference/hooks.md
reversed its prior "inline comments are not supported" guidance and now points
the agent to inline waivers for the travels-with-the-file case. Changelog 3.x.
Tests: tests/inline-ignores.test.mjs (parser units, detectText/detectHtml
integration, CLI end-to-end), registered in the detector suite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Reconcile design hook wording with inline ignores
Two hook-side fixes prompted by review of the new inline-ignore feature:
1. Clean-ack steer line. The old line ("Keep typography hierarchy, spacing
rhythm, and color contrast intentional on the next change.") read as an
odd non-sequitur after "No anti-patterns." Reworded the whole clean ack to
say what it means: a clean scan only clears the deterministic rule set, not
overall design quality, so keep following the design system and skill
guidance. Now: "Design hook scanned X. No deterministic design-quality
issues found. That does not mean the design is good: keep following the
project design system and the impeccable skill guidance."
2. Directive footer. It still told the agent "Do not add source comments such
as `impeccable: ignore`; those pollute the code and do not suppress hook
findings." That is now misleading: the hook runs the same detector engine
as the CLI, which honors inline `impeccable-disable` waivers, so they DO
suppress hook findings (consistent with config ignores, which filterFindings
already honors). Reworded to: don't silence a real finding to skip fixing
it; suppress only after the user confirms intent; prefer a config ignore,
and reach for an inline `impeccable-disable <rule>` comment only when the
waiver must travel with a file that leaves the repo.
Added a hook test asserting an inline `impeccable-disable-line` comment makes
the hook scan the file clean (locks in the cross-cutting behavior), and updated
the clean-ack / footer assertions to the new wording.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address review on inline-ignores parser
- Case-insensitive fast-path bail-out (Cursor): the cheap substring guard was
lowercase-only while DIRECTIVE_RE has the `i` flag, so a mixed-case marker
like `Impeccable-Disable` skipped parsing entirely and never suppressed.
Switched the guard to `/impeccable-disable/i.test(...)`. Added a regression
test.
- Removed the unreachable `-->` branch from TRAILING_CLOSER_RE (Greptile):
`--+>` already matches `-->` and any longer dash run.
- Replaced the always-truthy lazy-match + `if (sep)` reason strip with an
explicit first-separator slice (Greptile): clearer and drops the dead branch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Align inline-ignore line numbering with the detector (CRLF/CR endings)
parseInlineIgnores split lines with /\r\n|\r|\n/, but detectText numbers lines
with split('\n'). On classic `\r`-only endings the two diverged, so a
disable-line / disable-next-line directive could key a different line than the
finding it should waive (Cursor review). Split on '\n' only, matching the
detector exactly; the directive regex already excludes '\r', so a trailing '\r'
on CRLF files is never captured into the rule list. Added a CRLF regression test
through the real detectText.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two Cursor Bugbot Medium findings on the merged monorepo context PR:
- Excluded packages still listed: discoverTargetCandidates added every glob
match but never applied negated workspace patterns, so an excluded package
(e.g. "!packages/internal") showed up as a selectable target even though
resolveWorkspaceProjectRoot sends it back to the repo root. Now filtered
with the same isExcludedByWorkspacePattern check the resolver uses.
- Empty app list blocks root: resolveTargetSelection returned
TARGET_SELECTION_REQUIRED whenever projectRoot === repoRoot, even with zero
discoverable child apps (e.g. `workspaces: ["."]`), leaving an unanswerable
prompt. It now returns null (use the repo root as the project) when there
are no candidates.
Also documents two Greptile P2 clarity notes (the four contextSourceStatus
labels incl. the dual meaning of 'fallback', and the deliberate
isMonorepoRoot-before-hasGitBoundary ordering in findMonorepoRoot).
Adds regression tests for both behaviors.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Context files (PRODUCT.md / DESIGN.md) resolve child-first then fall back to the repo root, and /impeccable live lets the user pick a child app in a monorepo. Single-app behavior is unchanged. Closes#202. Co-Authored-By: abdulwahabone
* 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>
* Fix React hydration mismatch from live scroll-lock on SSR roots
The live overlay's startScrollLock disabled the browser's scroll
anchoring by setting `overflow-anchor: none` as an inline style on
`<html>` and `<body>`. On frameworks that server-render those roots
(notably Next.js App Router), that client-only inline style desyncs from
the server HTML, so React 19 logs "a tree hydrated but some attributes
of the server rendered HTML didn't match" on the next Fast-Refresh
re-render. It surfaced as a flaky failure of the nextjs-app-router
live-e2e fixture's expectConsoleClean probe.
Inject the suppression as a `<style>` rule keyed by a stable id instead
of mutating inline styles on hydrated host elements. Same computed
effect, but React no longer sees a client-only attribute on `<html>` /
`<body>`. The rule is recreated on every startScrollLock and removed on
teardown, so reload survival (driven by the persisted scroll key) is
unchanged.
Adds a regression guard pinning the new shape (no inline overflowAnchor
mutation on html/body; injected <style> created and removed by id).
Verified end-to-end: the nextjs-app-router live-e2e fixture now passes
the expectConsoleClean probe deterministically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Relax regression-guard regex spans to {0,400}
Address Greptile review: the {0,200}/{0,220}/{0,160} character-span
limits between the injected-style constructs were tight enough that an
innocent refactor or added comment inside startScrollLock could silently
break the shape-check. Widen each segment to {0,400}; the guard still
passes on the fix and still fails when the inline html/body overflowAnchor
mutation is reintroduced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Replace npx hints in live scripts with bundled-script paths
The live-mode poll/wrap scripts are invoked by the agent via
`node {{scripts_path}}/live-*.mjs`, never through the `npx impeccable`
CLI. Their help text and runtime error hints still pointed at
`npx impeccable poll|live|wrap`, which is misleading and, for the
error paths, not directly runnable.
- Docstrings/comments (never executed): switch to the
`node <scripts_path>/...` convention already used by live-server.mjs.
- Runtime-printed error/usage strings: resolve the script's own dir via
import.meta.url and print a real, copy-pasteable absolute path instead
of a placeholder.
Verified by triggering the error paths from the synced bundle and by
running the live-mode E2E (vite8-react-modal) through the full cycle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Quote script paths in runtime hints to handle spaces
Paths containing spaces (e.g. /Users/john doe/...) would otherwise
produce a non-runnable command. Addresses Greptile review feedback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>