diff --git a/README.md b/README.md
index 7d42fcb..453cb7c 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,18 @@ The `[1m]` suffix on GLM is load-bearing; dropping it silently shrinks the conte
---
+## Edit-time linting NEW
+
+By default the slop test runs once, at the end. On Claude Code you can move it to the keystroke: a PostToolUse hook lints every `.html`/`.css` Hallmark artifact the moment it is written and feeds any failures back to the model advisorily, so slop gets fixed while the context is small instead of in a big end-of-run pass.
+
+```bash
+node skills/hallmark/scripts/install-hook.mjs
+```
+
+`--global` targets `~/.claude/settings.json` (all projects); `--print` shows the settings block without writing; `--remove` undoes it. The hook is **advisory only**: it never blocks or reverts a write, no-ops silently on non-artifacts, and the Step 7 sweep still runs regardless. It is Claude-Code-only (Cursor/Codex have no hook surface and rely on Step 7).
+
+---
+
## Install
```
diff --git a/skills/hallmark/SKILL.md b/skills/hallmark/SKILL.md
index 4655805..46ba048 100644
--- a/skills/hallmark/SKILL.md
+++ b/skills/hallmark/SKILL.md
@@ -296,6 +296,8 @@ Emit code that satisfies the tone and the structural fingerprint. Match code com
Component scope runs the Core-15 sweep named in `slop-test.md`. Update the preview's Slop test row with the real outcome. If any gate fails, fix it. Do not ship slop.
+**Edit-time linting (optional, Claude Code).** Instead of waiting for Step 7, the user can wire sloplint as a PostToolUse hook so every `.html`/`.css` artifact is linted the moment it is written and FAILs are fed back advisorily: `node /scripts/install-hook.mjs` (project scope; `--global` for all projects; `--remove` to undo). It never blocks a write and no-ops on non-artifacts; Step 7 still runs regardless. Off Claude Code the hook never fires and Step 7 is the only sweep.
+
---
## Fast mode
diff --git a/skills/hallmark/scripts/install-hook.mjs b/skills/hallmark/scripts/install-hook.mjs
new file mode 100644
index 0000000..6632aae
--- /dev/null
+++ b/skills/hallmark/scripts/install-hook.mjs
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+// Install the Hallmark edit-time lint hook into a project's .claude/settings.json.
+//
+// node install-hook.mjs [--global] [--print] [--remove]
+//
+// (default) merge the hook into ./.claude/settings.json (project scope)
+// --global target ~/.claude/settings.json instead
+// --print print the hook block and the target path; write nothing
+// --remove remove the Hallmark hook (matched by its command path)
+//
+// Idempotent: re-running never duplicates the hook. Claude Code only. The hook
+// is advisory (never blocks a write); see lint-hook.mjs.
+
+import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname, join } from "node:path";
+import { homedir } from "node:os";
+
+const HERE = dirname(fileURLToPath(import.meta.url));
+const HOOK_CMD = `node "${join(HERE, "lint-hook.mjs")}"`;
+const MATCHER = "Write|Edit|MultiEdit";
+
+const args = process.argv.slice(2);
+const has = (f) => args.includes(f);
+
+const settingsPath = has("--global")
+ ? join(homedir(), ".claude", "settings.json")
+ : join(process.cwd(), ".claude", "settings.json");
+
+const hookEntry = {
+ matcher: MATCHER,
+ hooks: [{ type: "command", command: HOOK_CMD, timeout: 5000 }],
+};
+
+function readSettings() {
+ if (!existsSync(settingsPath)) return {};
+ try { return JSON.parse(readFileSync(settingsPath, "utf8")); } catch {
+ console.error(`refusing to overwrite unparseable ${settingsPath}; fix or delete it first.`);
+ process.exit(1);
+ }
+}
+
+function isOurs(entry) {
+ return (entry?.hooks ?? []).some((h) => typeof h?.command === "string" && h.command.includes("lint-hook.mjs"));
+}
+
+if (has("--print")) {
+ console.log(`# add to ${settingsPath} :`);
+ console.log(JSON.stringify({ hooks: { PostToolUse: [hookEntry] } }, null, 2));
+ process.exit(0);
+}
+
+const settings = readSettings();
+settings.hooks ??= {};
+settings.hooks.PostToolUse ??= [];
+
+// drop any existing Hallmark hook (keeps everyone else's hooks intact)
+settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter((e) => !isOurs(e));
+
+if (has("--remove")) {
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
+ console.log(`removed the Hallmark lint hook from ${settingsPath}`);
+ process.exit(0);
+}
+
+settings.hooks.PostToolUse.push(hookEntry);
+mkdirSync(dirname(settingsPath), { recursive: true });
+writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
+console.log(`installed the Hallmark edit-time lint hook into ${settingsPath}`);
+console.log(`it lints .html/.css Hallmark artifacts on write and advises (never blocks). Remove with --remove.`);
diff --git a/skills/hallmark/scripts/lint-hook.mjs b/skills/hallmark/scripts/lint-hook.mjs
new file mode 100644
index 0000000..e3df59b
--- /dev/null
+++ b/skills/hallmark/scripts/lint-hook.mjs
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+// Hallmark edit-time lint hook (Claude Code PostToolUse).
+//
+// Wire it in .claude/settings.json (see install-hook.mjs) on Write|Edit|MultiEdit.
+// It lints a Hallmark artifact the instant it is written and feeds any FAILs
+// back ADVISORILY via `additionalContext`, so slop dies at the keystroke instead
+// of at Step 7. It NEVER blocks a write, never exits non-zero, never writes files.
+//
+// Contract:
+// - stdin: the PostToolUse JSON ({ tool_name, tool_input:{file_path,...}, ... }).
+// - stdout: `{}` (silent no-op) OR `{"additionalContext":"..."}` (advisory).
+// - exit: always 0.
+//
+// It no-ops silently unless the written file is a .html/.css that looks like a
+// Hallmark artifact (carries the stamp, or a