Commit Graph
1162 Commits
Author SHA1 Message Date
Paul BakausandGitHub 63ecc37e54 Merge pull request #415 from pbakaus/css-pseudo-stripe-coverage
Detect pseudo-element stripes in standalone stylesheets and style blocks
2026-07-25 18:39:24 -07:00
github-actions[bot] 43751330a6 Sync generated provider output 2026-07-26 01:39:10 +00:00
Paul BakausandGitHub a4e99eda3a Merge pull request #414 from pbakaus/live-error-clears-checkpoint
Live mode: clear the durable session checkpoint on a terminal SSE error reply
2026-07-25 18:38:39 -07:00
github-actions[bot] 5a39675d3a Sync generated provider output 2026-07-26 01:38:30 +00:00
Paul BakausandGitHub fb1a208a87 Merge pull request #413 from pbakaus/detector-skip-harness-dirs
Skip hidden dirs in the detector walker; filter vendored paths from scan targets
2026-07-25 18:38:01 -07:00
github-actions[bot] 7783f2a622 Sync generated provider output 2026-07-26 01:36:41 +00:00
Paul BakausandGitHub 9a7098c813 Merge pull request #412 from pbakaus/static-named-color-borders
Fix side-tab false negative on named colors in the static-html engine
2026-07-25 18:36:12 -07:00
Paul BakausandClaude Code a4a076005b Carry source lines on pseudo-stripe findings and skip commented-out rules
Review bots caught two real gaps in the pseudo-stripe wiring: findings
had no source line (so line-scoped impeccable-disable directives could
not match them), and the scanner read commented-out CSS as live rules.

scanCssTextForPseudoStripe now blanks comment bodies byte-for-byte
(preserving offsets) and returns each rule's selector offset; the three
regex-engine call sites convert that to a real line, including the
whole-file line for component style blocks and CSS-in-JS templates. The
HTML path ignores the new field. Tests now assert every finding's line
against the selector's actual position and cover a commented-out stripe.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 18:24:02 -07:00
Paul BakausandClaude Code aeacf55074 Add .vuepress to the hidden source-dir allowlist
Cursor Bugbot correctly noted classic VuePress keeps theme layouts,
components, and styles under .vuepress/, which the walker scanned before
the hidden-dir rule. Same treatment as .vitepress and .storybook.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 18:15:13 -07:00
Paul BakausandClaude Code b8f1dbf92c Scan pseudo-element stripes in standalone stylesheets and style blocks
The side-tab silhouette drawn as an absolutely-positioned ::before/
::after bar carries no border token, so the regex engine's line matchers
never saw it in .css/.scss files, component style blocks, or CSS-in-JS
templates — while the identical construction on a full HTML page was
flagged via checkHtmlPatterns (issue #394). Wire the existing
scanCssTextForPseudoStripe scanner into all three regex-engine paths.

New fixtures (pseudo-stripe.css, pseudo-stripe.vue) pin four flag shapes
(inset shorthand, longhand pins, bottom edge, height:100%) and six pass
shapes (neutral divider, wide panel, static, hairline, hover-conditional
underline, non-full-height badge), attributed per case via data-case
selectors in the finding snippet.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 18:11:57 -07:00
Paul BakausandClaude Code a1a6441ba1 Exempt hidden dirs that conventionally hold UI source from the skip rule
Greptile's review correctly flagged a regression in the blanket
hidden-dir skip: .vitepress/theme/*.vue and .storybook/ preview files are
real UI source that the walker scanned before this branch. Both the
walker and the scan-target filter now carry a two-entry allowlist
(HIDDEN_SOURCE_DIRS) for those conventional locations; every other
hidden dir keeps being skipped.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 18:04:55 -07:00
Paul BakausandClaude Code 1907335ce5 Give each named-color flag case a unique snippet signature
Review bots (Greptile, Copilot) correctly noted the aggregate count
assertion could pass if one FLAG case stopped emitting while a PASS case
started. Each flag case now carries a distinct width/radius combination
and the test deep-equals the sorted snippet list, so every finding
attributes to exactly one case.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 18:01:14 -07:00
Paul BakausandClaude Code 21d058e744 Clear the durable live-session checkpoint on a terminal SSE error reply
The documented abort flow in reference/live.md (live-poll.mjs --reply <id>
error "...") reset the browser bar to PICKING but left the localStorage
checkpoint written for the GENERATING phase in place. Every reload then
resurrected a dead session the server no longer knew about, and the page
stayed wedged until the user hand-cleared the impeccable-live* keys in
the console (issue #362, diagnosed by @yourcodekitten).

An agent error reply is terminal for the session it names: when the id
matches the current session, run the same markSessionHandled + cleanup
teardown as 'discarded' (cleanup includes clearSession); when it matches
a stored-but-not-current checkpoint (the error raced a reload), drop that
checkpoint too. Errors that name no session keep the existing UI-only
reset, and the accept-cleanup and steer branches are untouched.

Regression guard added to tests/live-browser-regression.test.mjs.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 17:57:29 -07:00
Paul BakausandClaude Code 9f008ebf82 Skip hidden dirs in the detector walker and vendored paths in scan targets
When impeccable (or any agent tool) is installed into a project's
.claude/.cursor/.codex tree, a root scan descended into the vendored skill
code and reported the detector's own example strings as findings, and
context-signals returned installed-skill files as scan candidates whenever
the harness tree appeared in the branch diff (issue #303).

Rather than growing SKIP_DIRS by a denylist of harness names that drifts
as new tools appear, the walker now skips every hidden directory during
recursion — which already covered .git/.next/.nuxt/.svelte-kit/.turbo/
.vercel, and covers all present and future harness installs plus
.impeccable itself. SKIP_DIRS shrinks to the four non-hidden entries.
An explicitly passed hidden target still scans: only child entries are
name-checked, never the root the walker is given.

scanTargets() applies the same rule to git-changed files (directory
segments only, so root dotfiles keep their existing behavior), and falls
through to source-dir targeting when the only dirty files are vendored.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 17:51:12 -07:00
Paul BakausandClaude Code 7622cc8440 Derive static-cascade color extraction from the shared named-color table
The static-html engine never emitted side-tab for `border-left: 4px solid
purple` (or any named color outside a hardcoded 9-name list) in .html
files: extractStaticColor's regex dropped the color token from border
shorthands, the side defaulted to neutral black, and checkBorders skipped
it. The same declaration in a .css file was flagged by the regex engine,
so the two engines disagreed while both exited cleanly (issue #359).

Build the extraction alternation from the same CSS_NAMED_COLORS table
parseAnyColor resolves against (longest-first, whole-token), so the set of
names the extractor recognizes and the set the parser can resolve cannot
drift apart again. STATIC_NAMED_COLORS shrinks to the one keyword
parseAnyColor deliberately refuses (`transparent` as zero-alpha), since
parseAnyColor already covers every real named color in the table.

New two-column fixture (named-color-borders.html) covers the issue
reproducers: purple shorthand + radius, rebeccapurple (substring-safe
matching), crimson top stripe, bare 3px teal, var() resolving to a named
color, and an inline style attribute — with neutral named colors
(dimgray, gainsboro, black), thin, and uniform borders as pass cases.

Prepared with AI assistance (Claude Code), directed by @pbakaus.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-07-25 17:45:55 -07:00
github-actions[bot] af78b1e512 Sync generated provider output 2026-07-25 01:43:39 +00:00
Paul BakausandClaude Fable 5 8634c538fb Verification is two bounded rounds, never a loop
Opus 5 turned the iterate-with-screenshots-until-it-meets-the-bar
instruction into 42 screenshot trips and 150 tool calls per build,
about forty dollars of cache churn a page, before ever reaching the
reviewer. Verification now batches: one desktop-and-mobile round after
the full build, fixes applied together, one confirming round, ceiling
two. Craft-floor's checks share those renders instead of earning
separate trips; per-tweak iteration is live mode's channel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 18:43:04 -07:00
Paul BakausandClaude Fable 5 73819ff573 Stop hook-build test from asserting an unbuilt dist artifact
The "Codex project hooks reference hook.mjs in the .codex skill payload"
test asserted dist/codex/.codex/skills/impeccable/{SKILL.md,hook.mjs}
exist. dist/ is gitignored, and CI's test:core step runs before the
Build step, so the fresh checkout has no dist/ when the assertion runs.
It only passed locally against a stale dist/. This turned every
sync-generated-output push on main red.

The dist/codex bundle's self-consistency is already covered by
build.test.js, which runs an actual build into a temp dir and verifies
the codex payload lands at .codex/skills/. Drop the two dist assertions;
the test keeps verifying the tracked outputs (the .codex/hooks.json path
and the .agents/skills payload) that exist at test:core time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:28:12 -07:00
github-actions[bot] af2a14c12c Sync generated provider output 2026-07-25 00:19:34 +00:00
Paul BakausandClaude Fable 5 6ff9f957ac Add radial-spotlight-glow detector rule
Flags the decorative low-opacity chromatic radial-gradient "spotlight"
washed behind a hero or section and fading to transparent, an AI-slop
reflex the saturated radial-halo gate lets slip (e.g. rgba(80,111,255,
0.26) -> transparent on a mobile hero).

Gates: a non-repeating radial-gradient whose last stop is transparent,
whose visible stops are all low-opacity (alpha < 0.45) with at most two
of them, at least one chromatic (channel spread >= 24 exempts neutral
vignettes), on a decorative-scale surface (width >= 240, height >= 160,
exempting badges/avatars/small lights). The alpha band is disjoint from
radial-halo (>= 0.7), so the two never double-report.

Wired into both element loops (static-html + injected browser) with the
pure checkRadialSpotlight shared by both adapters. TDD fixture with 5
flag / 9 pass shapes. Browser-path sweep over the eval corpus: 29 hits
on 11 pages, 0 false positives. Count 59 -> 60.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:19:04 -07:00
github-actions[bot] e3f732e99c Sync generated provider output 2026-07-25 00:10:01 +00:00
Paul BakausandClaude Fable 5 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>
2026-07-24 17:09:33 -07:00
Paul BakausandClaude Fable 5 bb57be4243 Documenter subagent, reviewer handoff contract, asset gate
From the paired Opus and Codex manual-run analyses. DESIGN.md moves to
the end of the flow and into a shipped documenter subagent that derives
the system from the built artifact: a rulebook written before the build
gets defended against reality, and a half-stable DESIGN.md hands the
design-system detector an unstable target that buries the build in
noise and invites laundering. The finish reviewer gains the handoff
that failed three times live: the parent captures desktop and mobile
screenshots and passes paths, the reviewer never attempts to render
and names missing inputs in one line, the parent verifies the
five-section return and respawns once on empty. Fidelity against the
approved comp joins its checks; the card keeps commitment only. The
comp ingredient inventory becomes a written gate with raster-by-default
materials and no gradient-as-texture, comps persist under
.impeccable/mocks, the degraded seed names the sandboxed-exec cause,
and the finish line is explicit: a clean detector pass is not finished.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 17:09:33 -07:00
github-actions[bot] 94dc732d30 Sync generated provider output 2026-07-24 23:17:52 +00:00
Paul BakausandClaude Fable 5 501528c07f Register orphaned live-tanstack-adapter test in the live suite
tests/live-tanstack-adapter.test.mjs (added in 4cd5ea75) was never listed in
scripts/test-suites.mjs, so the test-suites registry guard failed and the file
never ran in any suite. Add it to the live suite's node command list. Pre-existing
housekeeping, independent of the detector fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:17:24 -07:00
Paul BakausandClaude Fable 5 507725c935 Harden detector against form.id shadowing and gradient/non-rendered false positives
Fixes three detector bugs that surfaced on real-world (Shopify) URL scans:

#407 — DOM named-property shadowing crash. On a <form> with a named control
like <input name="id"> (every Shopify product form), HTMLFormElement's
[LegacyOverrideBuiltIns] behavior makes `form.id` return the input element, not
the id string, so `elId.startsWith(...)` throws and aborts the whole scan. Read
the id via getAttribute whenever `el.id` is not a string, at all three sites:
checkQuality (checks.mjs) and collectBrowserFindings + generateSelector
(browser/injected/index.mjs). Regenerated the browser bundle.

#408 — tiny-text / undersized-ui-text flagged non-rendered elements. On sites
that set html{font-size:62.5%} the root computes to 10px, so <script>/<style>/
<title>/<noscript> and display:none / visibility:hidden blocks — whose JS/CSS/
JSON-LD text clears the hasDirectText gate — produced dozens of phantom "10px
body text" findings. Added isNonRenderedText() (tag list + head descendants +
display/visibility) and gated both text-size floors on it.

#409 — contrast rules misjudged gradients. Case A: background-clip:text paints
its glyphs with the element's own gradient, not a backdrop, so measuring the
never-painted `color` against those stops is a guaranteed false positive; skip
the backdrop-contrast checks when bgClip is 'text' (the gradient-text pattern
flag still fires). Case B: a translucent gradient stop (e.g. a 9%-alpha accent
glow) was treated as an opaque accent; composite alpha stops over the resolved
surface beneath the gradient in resolveGradientStops(), dropping the stop rather
than guessing when that surface is unresolvable.

Fixtures + tests: shadowed-form-id.html (browser, #407), nonrendered-text.html
(#408), and gradient-clipped + alpha-glow cases added to color.html (#409).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:17:24 -07:00
github-actions[bot] 450d5659c7 Sync generated provider output 2026-07-24 22:34:20 +00:00
Paul BakausandClaude Fable 5 253f8e510c Concept machinery: survive truncation, builds, and loud briefs
The release-gate audit traced four ways the roll's output was defeated
downstream of a perfectly healthy seed. Gemini's harness keeps only the
tail of tool output, so the header-only ASSIGNED INDEX never reached
the model in 18 of 18 samples; the seed now restates the assignment
and key at the end of its output. Astro strips frontmatter comments,
so half the anthropic contracts vanished from built artifacts; the
contract now must survive the production build as an HTML comment in
emitted markup. A brief that paints its own picture (the album named
Soft Cathedrals) converged every arm regardless of assigned index; its
literal reading now joins the rut with at most one candidate. And Opus
under 4.0.1 skipped the seed 42% of the time while hand-authoring
plausible contracts; the finish reviewer now verifies FORM carries a
corroborable seed key before any craft point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:33:44 -07:00
github-actions[bot] 08676d5757 Sync generated provider output 2026-07-23 18:11:31 +00:00
Paul BakausandClaude Fable 5 ffe869f4d0 Drop the turn-cap exception from the visualize mandate
Paul's call: the build-exhaustion failure only exists inside eval
workers with hard turn budgets no real harness exposes, and the clause
doubled as a hedge door for skipping the comp round. The eval-side fix
belongs in the worker's max-turns, not in skill prose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:10:59 -07:00
Paul BakausandClaude Fable 5 fc2e694afc Release prep: skill v4.0.2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
skill-v4.0.2
2026-07-23 10:35:22 -07:00
Paul BakausandClaude Fable 5 e76ff27adf Eval-found fixes: workspace-relative cards, build outranks comps at caps
The release-gate campaign confirmed two skill bugs with transcripts.
Sandboxed harnesses reject absolute paths, so following the CHOSEN
CARD directive with the absolute card-base path failed view_image; the
directive and the quality-bar clause now say download into the
workspace and open the relative path. And under the openai worker's
turn cap, models spent the budget on init, cards, and comp generation
and never built the page (a third of small-n supplement slices); the
visualize mandate gains its one exception: at a hard cap the shipped
page outranks optional imagery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:35:22 -07:00
Paul BakausandClaude Fable 5 73dec5d159 Subagent authorization becomes a central harness counter
Paul's call: the reviewer-local authorization patch covered one
command while the harness gate silently disables every shipped
subagent, critique panels and the manual-edit applier included. The
argument now lives beside the autonomy counter in context.mjs, emitted
as tool-result content every run: invoking the skill is the user
request such gates ask for; spawn where a reference directs; the
in-thread substitute is for absent capability only and gets disclosed
in one line. new-work keeps the reviewer mechanics and drops the
now-central argument.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:35:22 -07:00
Paul BakausandClaude Fable 5 4dc2b4d694 Finish reviewer: the skill invocation authorizes its subagents
A live session on a harness whose guidance gates subagent use on user
request resolved the conflict silently against the skill: it never
spawned the reviewer, stretched the no-subagents fallback to cover
permission hesitancy, and self-reviewed with all the context that made
its choices feel correct. Three tightenings: invoking the skill IS the
user request that authorizes its shipped subagents; the fallback is
for harnesses lacking the capability, not for hesitancy; a substituted
review gets disclosed in one line at finish, never silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 10:35:22 -07:00
github-actions[bot] bdaa5a4eb9 Sync generated provider output 2026-07-23 05:50:13 +00:00
Paul BakausandClaude Fable 5 2fa0e7d327 Live: gate mid-generation source injection, monotonic bar, resumable disconnect
Three browser-side fixes for the same 3.5-to-4.0.1 regression.

- Source-preview targets no longer source-inject per variant_progress
  checkpoint. Immediate injection raced framework (React/Vue) ownership and
  triggered removeChild errors, which surfaced as static previews. HMR now
  owns reconciliation while variants stream in; source injection runs only on
  the final done (its 750ms settle + retry ladder stays for non-HMR harnesses
  like Cursor). Progress counts still advance from the variant observer, and
  the svelte-component progressive path is unchanged.
- The agent-phase progress bar advances monotonically. A behind/resumed
  checkpoint re-broadcasts an earlier phase (the server regresses the snapshot
  phase to generating), which moved the visible bar backward; a phase rank
  table now blocks a known-lower phase from overwriting a known-higher one.
- The server-lost toast now frames the drop as resumable (session saved,
  reopen or restart live-poll.mjs) instead of "Session ended", which had led
  agents to rationalize bailing to direct edits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:49:44 -07:00
Paul BakausandClaude Fable 5 dbe0c12b91 Live: stop the preflight writing source, cache the resolution
The polling-rework preflight wrote the variant scaffold into source during the
poll lease, before the agent acted. On source-preview targets (React/Vue/Vite,
everything but the svelte-component path) that write full-reloaded the
framework; a browser caught mid-reload missed the agent's variant write and the
SSE done, and sat stranded at 0/N.

Restore the 3.5 single-atomic-edit semantics: the preflight still resolves the
element location and computes the scaffold, but --defer-source-write leaves
source untouched and hands the agent the wrapper text plus the picked source
range. The agent splices variants into the wrapper and replaces the range in
one write, so the framework reloads exactly once. The svelte-component path is
untouched (it never writes route source). The missed-completion recovery stays
as defense in depth.

Also cache the resolved source file per target signature (locator + route):
the ~7.6s tree search re-ran on every generate for the same element; a hit now
points the helper straight at the file via --file, invalidated when the target
changes or a resolution fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:49:44 -07:00
Paul BakausandClaude Fable 5 4cd5ea7547 Add TanStack Router + Start support to live mode
Live mode had no TanStack coverage: a TanStack Start user hit disconnects
and static previews because there is no static index.html to inject and no
adapter for the SSR root document.

- New tanstack-adapter.mjs, modeled on the SvelteKit/Nuxt adapters: detects
  a TanStack Start project (@tanstack/react-start + src/routes/__root.tsx)
  and patches the __root document to mount a generated dev-only React
  component (src/impeccable/ImpeccableLiveRoot) that appends the live bundle
  on the client after hydration, carrying the ?token= param via
  buildLiveScriptSrc. Patch/unpatch round-trips byte-for-byte and is
  idempotent; refuses to clobber an unmanaged file at the component path.
- Wire detection into live-inject.mjs (insert + remove + gitignore),
  ordered so SvelteKit/Nuxt win and a plain TanStack Router SPA falls
  through to the baseline Vite index.html path.
- tanstack-router-vite fixture (baseline, no adapter) and tanstack-start
  fixture (SSR adapter), both with runtime blocks. Both pass the full
  live-e2e cycle (handshake, steer, pick, Go, cycle, accept, carbonize,
  reloadProbe).
- Unit tests for detection + patch round-trip + apply/remove; tanstack-start
  branches in framework-fixtures.test.mjs; live.md framework table + adapter note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:49:44 -07:00
Paul BakausandClaude Fable 5 d4d02b69f2 Live: overlay preview is the verification channel; disconnects resume
Two prose fixes from the 3.5-to-4.0.1 forensic diff of a real user
regression (15-minute tweaks, repeated disconnects, agent abandoning
the picker). The craft-fold made every generate cycle pay the verify-
the-built-result loop the overlay already provides to the human; live
cycles now verify by construction and run the full check once at
accept. And nothing framed a dropped SSE or closed tab as resumable,
while the client toasts "Session ended", so agents rationalized
bailing to direct edits; the journal is canonical and reopening
continues the session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:49:44 -07:00
github-actions[bot] fc3dc501a6 Sync generated provider output 2026-07-23 04:59:57 +00:00
Paul BakausandClaude Fable 5 3f9fccdfd0 Live: lock down the local server against same-machine token theft (#304)
Two defense-in-depth layers close the P1 in issue #304, where any browser
tab on the machine could fetch /live.js, extract the embedded token, and
drive every token-gated route.

1. Loopback-restricted CORS. The shared handler replaced its wildcard
   `Access-Control-Allow-Origin: *` with reflection gated on a strict
   isLoopbackOrigin() that URL-parses the Origin (so localhost.evil.com and
   127.0.0.1.evil.com fail) and accepts only http/https on localhost,
   127.0.0.1, or [::1]. Reflection always pairs with `Vary: Origin` so a
   cache never hands one origin's authorized response to another. Remote
   origins get no ACAO header; origin-less callers (script tags, curl, the
   agent's own fetches) are unaffected.

2. Token-gated /live.js. The handler now 401s unless `?token=` matches
   state.token, so the bundle (which embeds the token) is no longer served
   to unauthenticated local pages. The injected <script src> carries the
   token: live.mjs passes --token to live-inject.mjs, which threads it
   through every injection path (HTML/JSX tag, Nuxt plugin, SvelteKit root
   component) via a shared buildLiveScriptSrc(). The token stays optional in
   live-inject so static fixture tests keep their bare src.

Tests: new live-server integration cases for the 401 gate, remote-origin
denial, loopback reflection + Vary, and token-guarded routes under a
loopback Origin; e2e session harness now injects with the token.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:59:28 -07:00
Paul BakausandClaude Fable 5 da2982ab95 Fix /source guard escaping the project root via sibling directories
The /source route confined paths with `absPath.startsWith(process.cwd())`,
a string-prefix check with no separator. An absolute request path to a
sibling directory whose name extends the project dir name (projeto ->
projeto-backup) shared the prefix and was served. Switch to the relative-path
check already used by sessionFileMetadataFromPollReply: reject when the
relative path is empty (the root dir itself, never a file this route serves),
starts with `..`, or is absolute.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:59:28 -07:00
github-actions[bot] 762ffd08b2 Sync generated provider output 2026-07-23 04:50:46 +00:00
Paul BakausandClaude Fable 5 55094aaa0d Fix false hook-script-missing in doctor when ${CLAUDE_PROJECT_DIR} is unexpanded
The deep staleness pass extracted a hook-script path with a greedy `\S*`
prefix that swallowed the `${CLAUDE_PROJECT_DIR}/` placeholder, then
existsSync'd the literal string. That string never exists, so every project
installed by `impeccable hooks on` got a `hook-script-missing` finding with
text claiming UI edits were going unscanned — the opposite of the truth.

Split extraction from resolution. hookScriptTokenFrom now pulls the path
token (quoted-first, so it handles the #399 guarded `[ ! -f "PATH" ] || node
"PATH"` form and absolute user-level installs) without absorbing shell
syntax. resolveHookScriptPath then applies a per-placeholder policy:

- ${CLAUDE_PROJECT_DIR} expands to the scanned root (the runtime mapping).
- ${CLAUDE_PLUGIN_ROOT} / ${PLUGIN_ROOT} / ${GROK_PLUGIN_ROOT}, $(...) command
  substitution (GitHub's $(git rev-parse)), and any other $VAR are SKIPPED:
  the doctor cannot know those locations and must never assert a negative it
  cannot verify.

The check stays real: a placeholder that expands to a genuinely absent path
still flags. Adds TDD coverage for every command form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:50:10 -07:00
github-actions[bot] 698a743958 Sync generated provider output 2026-07-23 00:34:48 +00:00
Paul BakausandClaude Fable 5 47aff2e0be Fix Stop-hook loop: honor stop_hook_active per Claude Code contract
The Stop deep pass (runStopHook) never read the stop_hook_active field
from the Claude Code Stop-hook event. When a prior fire kept the turn
alive via hookSpecificOutput.additionalContext and the agent legitimately
declined to act, the hook re-scanned and re-blocked every re-invocation
until Claude Code's consecutive-block cap force-ended the turn (issue #400).

Read stop_hook_active early in runStopHook, right after the event is
parsed and before any scan, and exit 0 with no output when it is true. The
prior fire already surfaced the findings; acting on them is the agent's
call. Only Claude Code sends this field, so the strict === true is a no-op
for other harnesses. runHook (PostToolUse) and hook-before-edit.mjs
(PreToolUse) never receive the field, so they are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:34:20 -07:00
github-actions[bot] 9b7f7ffbba Sync generated provider output 2026-07-22 20:14:42 +00:00
Paul BakausandClaude Fable 5 3e233d22d7 Release prep: CLI v3.3.1
Bump the npm package and regenerate the browser detector bundle with
the advisory tier, entity-aware em-dash counting, and the
undersized-ui-text rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cli-v3.3.1
2026-07-22 13:13:40 -07:00
Paul BakausandClaude Fable 5 087983070b Release script verifies impeccable.style serves the released version
The 4.0.0 release stranded npx-update users on a stale bundle for a
day because the site deploy is a separate step nobody was reminded of.
Skill releases now check /api/version and print the redeploy command
when the served version lags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 12:39:32 -07:00
Paul BakausandClaude Fable 5 eda81f0937 Release prep: skill v4.0.1
Bump plugin + marketplace to 4.0.1 and sync the regenerated provider
output: the guarded hook commands from issue #399 (a missing hook file
exits 0 instead of crashing every turn of a user-level install), the
canon standing exit, the visualize flow, the two shipped subagents, and
the interactive-spine fixes from today's live testing. Detector count
validates at 59 with undersized-ui-text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
skill-v4.0.1
2026-07-22 12:23:07 -07:00