Move the slop defects back into the craft floor

The detector-blind slop review existed because the AI-tell rules had been
stripped out of SKILL.md and nothing carried them. The floor is a better
home: it loads after concept ideation and immediately before editing UI,
which is the placement that made stripping them necessary in the first
place. Models tread lightly when a ban list is present during ideation;
by the time the floor loads, the direction is already committed.

- Rename build-floor.md to craft-floor.md and restore the absolute bans
  (side-stripes, gradient text, glassmorphism, hero-metric, identical card
  grids, eyebrow-on-every-section, numbered markers, text overflow), the
  codex and gemini defect lists, and the reflexes no scanner catches.
  Rule ids match the ones the ablation catalog already knows.
- Delete lib/slop-review.mjs and both injections. The Stop hook is now
  purely a mechanical pass and stays silent with nothing to report.
- context.mjs replaces AI_SLOP_REVIEW_REQUIRED with the narrower
  MANUAL_DETECTOR_REQUIRED, emitted only when a session has no hook at
  all. A per-edit hook already covers the mechanical gap, and the floor
  covers the judgment one either way.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-21 02:04:36 -07:00
co-authored by Claude
parent d7d10277d1
commit 153b416f2e
9 changed files with 87 additions and 118 deletions
+2 -26
View File
@@ -46,7 +46,6 @@ import path from 'node:path';
import { pathToFileURL, fileURLToPath } from 'node:url';
import { extractPlatform, loadContext } from './context.mjs';
import { IMPECCABLE_COMMAND } from './lib/provider.mjs';
import { renderStopSlopReview } from './lib/slop-review.mjs';
// `detector.extensions` (issue #316) is shared with Live's source search, which
// needs the same answer for `.heex` / `.blade.php` when it hunts for session
// markers. lib/template-extensions.mjs owns the shape; re-exported here because
@@ -2054,22 +2053,9 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
return result({ skipped: 'native-platform', platform, durationMs: Date.now() - started });
}
const session = ensureSession(cache, sessionId);
const needsSlopReview = session.llmSlopReviewed !== true;
const det = detector || await loadDetector();
if (!det || typeof det.detectText !== 'function') {
if (!needsSlopReview) return result({ skipped: 'detector-missing', durationMs: Date.now() - started });
session.llmSlopReviewed = true;
session.updatedAt = Date.now();
persistCache(projectCwd, cache);
const text = renderStopSlopReview();
return {
exitCode: 0,
stdout: payload(text, 'Stop', harness),
emission: { kind: 'stop-llm-slop-review', llmSlopReview: true },
audit: { ...audit, emitted: true, detectorMissing: true, llmSlopReview: true, chars: text.length, durationMs: Date.now() - started },
};
return result({ skipped: 'detector-missing', durationMs: Date.now() - started });
}
const scanOptions = designSystemOptions(config, det, projectCwd);
@@ -2130,15 +2116,10 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
}
audit.scannedFiles = scanned;
if (freshGroups.length === 0 && contractEntries.length === 0 && !needsSlopReview) {
if (freshGroups.length === 0 && contractEntries.length === 0) {
return result({ emitted: false, skipped: 'stop-clean', durationMs: Date.now() - started });
}
if (needsSlopReview) {
session.llmSlopReviewed = true;
session.updatedAt = Date.now();
}
// Fresh findings and first-time contract audits earn the cache write;
// both mark this batch as surfaced so the next Stop fire is silent
// unless new issues appear.
@@ -2153,9 +2134,6 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
if (contractEntries.length > 0) {
parts.push(renderContractAudit(contractEntries, { cwd: projectCwd }));
}
if (needsSlopReview) {
parts.push(renderStopSlopReview());
}
const text = appendDesignSystemNote(parts.join('\n\n'), scanOptions);
return {
exitCode: 0,
@@ -2166,7 +2144,6 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
...(contractEntries.length > 0
? { contractFiles: contractEntries.map((entry) => entry.filePath) }
: {}),
...(needsSlopReview ? { llmSlopReview: true } : {}),
},
audit: {
...audit,
@@ -2174,7 +2151,6 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
freshFiles: freshGroups.length,
freshFindings: freshGroups.reduce((sum, group) => sum + group.findings.length, 0),
...(contractEntries.length > 0 ? { contractAudits: contractEntries.length } : {}),
...(needsSlopReview ? { llmSlopReview: true } : {}),
chars: text.length,
durationMs: Date.now() - started,
},