mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-14 07:06:45 +03:00
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:
+15
-12
@@ -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 } = {}) {
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
Reference in New Issue
Block a user