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
+15 -12
View File
@@ -32,7 +32,6 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseTargetOptions } from './lib/target-args.mjs';
import { IMPECCABLE_COMMAND, IMPECCABLE_PROVIDER_ID } from './lib/provider.mjs';
import { renderLlmOnlySlopReview } from './lib/slop-review.mjs';
import { resolveSurfaceBrief } from './lib/surface-briefs.mjs';
const PRODUCT_NAMES = ['PRODUCT.md', 'Product.md', 'product.md'];
@@ -1127,7 +1126,7 @@ async function cli() {
];
appendSurfaceBriefContext(parts, ctx);
parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists }));
appendHookFallback(parts, ctx);
appendDetectorFallback(parts, ctx);
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
@@ -1141,7 +1140,7 @@ async function cli() {
}
appendSurfaceBriefContext(parts, ctx);
parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists }));
appendHookFallback(parts, ctx);
appendDetectorFallback(parts, ctx);
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
@@ -1244,15 +1243,19 @@ function automaticHookMode(ctx) {
return 'none';
}
function appendHookFallback(parts, ctx) {
const hookMode = automaticHookMode(ctx);
if (hookMode === 'stop') return;
const native = ctx.platform === 'ios' || ctx.platform === 'android' || ctx.platform === 'adaptive';
parts.push(renderLlmOnlySlopReview({
automaticDetector: hookMode === 'per-edit',
manualDetector: hookMode === 'none' && !native,
scriptsPath: path.dirname(fileURLToPath(import.meta.url)),
}));
// reference/craft-floor.md carries the detector-blind reflexes on every build,
// so the only gap left here is the mechanical pass. A hook covers it, per-edit
// or Stop; a session without one has to run the detector by hand. The detector
// reads HTML and CSS, so native projects get nothing.
function appendDetectorFallback(parts, ctx) {
if (automaticHookMode(ctx) !== 'none') return;
if (ctx.platform === 'ios' || ctx.platform === 'android' || ctx.platform === 'adaptive') return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'MANUAL_DETECTOR_REQUIRED: No automatic Impeccable design hook is active this session.',
`Once the changed web UI is finished, run the mechanical detector over it: \`node ${scriptsPath}/detect.mjs --json <changed targets>\`.`,
'Run it once, and not earlier during concept selection.',
].join(' '));
}
function buildResolvedContextDirective(ctx, options, { targetExists = null } = {}) {
+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,
},
-30
View File
@@ -1,30 +0,0 @@
export const LLM_ONLY_SLOP_ITEMS = Object.freeze([
'Monospace used merely to signal “technical” or “developer.”',
'Light or dark mode chosen by category habit rather than the actual use scene.',
'Everything wrapped in cards, or identical icon-heading-text cards repeated as the page structure.',
'Hero-metric scaffolds: one oversized number, a small label, supporting stats, and an accent treatment.',
'Decorative glassmorphism, meaningless sparklines, or generic rounded rectangles with drop shadows.',
'A modal chosen by reflex when the task does not require interruption or protected focus.',
]);
export function renderLlmOnlySlopReview({ manualDetector = false, automaticDetector = false, scriptsPath = null } = {}) {
const lines = [
automaticDetector
? 'AI_SLOP_REVIEW_REQUIRED: The automatic detector covers mechanical rules, but this harness has no reliable late review. Before finishing changed UI, inspect the authored result for detector-blind model reflexes:'
: 'AI_SLOP_REVIEW_REQUIRED: The automatic Impeccable design hook is not available for this session. Before finishing changed UI, inspect the authored result for detector-blind model reflexes:',
...LLM_ONLY_SLOP_ITEMS.map((item) => `- ${item}`),
'Fix reflexes, not intentional choices required by the brief or established visual authority.',
];
if (manualDetector && scriptsPath) {
lines.push(`Then run the mechanical detector once over the changed web UI: \`node ${scriptsPath}/detect.mjs --json <changed targets>\`. Do not run it earlier during concept selection.`);
}
return lines.join('\n');
}
export function renderStopSlopReview() {
return [
'[impeccable@1] Detector-blind AI-slop review. The mechanical pass is complete; now inspect the rendered result for model reflexes it cannot reliably detect:',
...LLM_ONLY_SLOP_ITEMS.map((item) => `- ${item}`),
'Fix reflexes, not intentional choices required by the brief or established visual authority. Do not rerun the detector; this is the authored judgment layer.',
].join('\n');
}