mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Refresh the Impeccable product experience
Rework the landing page proof, steering demo, feature grid, slop catalog, detector coverage, theming, Live workflow, and responsive behavior.\n\nAI-assisted implementation by OpenAI Codex.
This commit is contained in:
+139
-42
@@ -41,7 +41,14 @@ const WORKSPACE_DISCOVERY_IGNORED_DIRS = new Set([
|
||||
'.turbo',
|
||||
'.cache',
|
||||
'coverage',
|
||||
'vendor',
|
||||
'vendors',
|
||||
]);
|
||||
const VISUAL_SOURCE_DIRS = ['src', 'app', 'pages', 'components', 'site', 'public', 'styles'];
|
||||
const STYLE_EXTENSIONS = new Set(['.css', '.scss', '.sass', '.less', '.styl']);
|
||||
const UI_EXTENSIONS = new Set(['.html', '.htm', '.jsx', '.tsx', '.vue', '.svelte', '.astro']);
|
||||
const VISUAL_SCAN_FILE_LIMIT = 250;
|
||||
const VISUAL_SCAN_DEPTH_LIMIT = 4;
|
||||
|
||||
// ─── Update check ──────────────────────────────────────────────────────────
|
||||
// Piggyback a lightweight skill-version check on the once-per-session boot.
|
||||
@@ -78,6 +85,7 @@ export function loadContext(cwd = process.cwd(), options = {}) {
|
||||
contextDir: resolved.contextDir,
|
||||
productContextDir: productPath ? path.dirname(productPath) : null,
|
||||
designContextDir: designPath ? path.dirname(designPath) : null,
|
||||
hasVisualImplementation: hasVisualImplementation(resolved.projectRoot),
|
||||
projectRoot: resolved.projectRoot,
|
||||
repoRoot: resolved.repoRoot,
|
||||
isMonorepo: resolved.isMonorepo,
|
||||
@@ -690,15 +698,106 @@ function safeRead(p) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort evidence that the project already has an incumbent visual
|
||||
* implementation. DESIGN.md is documentation, not the only source of design
|
||||
* authority: real tokens, chosen type, and a component system in code must not
|
||||
* be mistaken for a greenfield identity merely because the document is absent.
|
||||
*
|
||||
* The scan is deliberately bounded and conservative. A package.json or one
|
||||
* empty scaffold component is not enough; a tokenized stylesheet, an authored
|
||||
* HTML surface, or several styled UI components is.
|
||||
*/
|
||||
export function hasVisualImplementation(projectRoot) {
|
||||
if (!projectRoot) return false;
|
||||
const root = path.resolve(projectRoot);
|
||||
const queue = [];
|
||||
for (const rel of VISUAL_SOURCE_DIRS) {
|
||||
const dir = path.join(root, rel);
|
||||
if (fs.existsSync(dir)) queue.push({ dir, depth: 0 });
|
||||
}
|
||||
|
||||
let scannedFiles = 0;
|
||||
let styledComponents = 0;
|
||||
|
||||
const inspectFile = (filePath) => {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
if (!STYLE_EXTENSIONS.has(ext) && !UI_EXTENSIONS.has(ext)) return false;
|
||||
const base = path.basename(filePath).toLowerCase();
|
||||
if (/\.min\.[a-z]+$/.test(base)) return false;
|
||||
if (scannedFiles++ >= VISUAL_SCAN_FILE_LIMIT) return false;
|
||||
let body;
|
||||
try {
|
||||
body = fs.readFileSync(filePath, 'utf-8').slice(0, 64 * 1024);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
const evidence = body
|
||||
.replace(/\/\*[\s\S]*?\*\//g, '')
|
||||
.replace(/<!--[\s\S]*?-->/g, '')
|
||||
.replace(/^\s*\/\/.*$/gm, '');
|
||||
if (STYLE_EXTENSIONS.has(ext)) {
|
||||
const customProperties = evidence.match(/--[a-z0-9_-]+\s*:/gi)?.length ?? 0;
|
||||
const visualDeclarations = evidence.match(/\b(?:color|background(?:-color)?|border(?:-color)?|font-family)\s*:/gi)?.length ?? 0;
|
||||
if (/\b(?:tokens?|theme|design-system)\b/.test(base) && evidence.trim().length > 80) return true;
|
||||
if (customProperties >= 3 || visualDeclarations >= 5) return true;
|
||||
}
|
||||
|
||||
if ((ext === '.html' || ext === '.htm') && evidence.length > 600 && /<style\b|<link[^>]+stylesheet/i.test(evidence)) {
|
||||
return true;
|
||||
}
|
||||
if (!['.html', '.htm'].includes(ext) && evidence.length > 300) {
|
||||
const embeddedCustomProperties = evidence.match(/--[a-z0-9_-]+\s*:/gi)?.length ?? 0;
|
||||
const embeddedVisualDeclarations = evidence.match(/\b(?:color|background(?:-color)?|border(?:-color)?|font-family)\s*:/gi)?.length ?? 0;
|
||||
const classTokens = [...evidence.matchAll(/class(?:Name)?\s*=\s*["'`]([^"'`]+)["'`]/gi)]
|
||||
.reduce((count, match) => count + match[1].trim().split(/\s+/).length, 0);
|
||||
if ((embeddedCustomProperties >= 3 && embeddedVisualDeclarations >= 3) || embeddedVisualDeclarations >= 5 || classTokens >= 12) return true;
|
||||
}
|
||||
if (!['.html', '.htm'].includes(ext) && evidence.length > 300 && /class(?:Name)?\s*=|style\s*=|styled\(|css`/i.test(evidence)) {
|
||||
styledComponents += 1;
|
||||
if (styledComponents >= 3) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Root-level authored surfaces and styles are common in small projects.
|
||||
try {
|
||||
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
||||
if (entry.isFile() && inspectFile(path.join(root, entry.name))) return true;
|
||||
}
|
||||
} catch { /* unreadable root: no evidence */ }
|
||||
|
||||
while (queue.length && scannedFiles < VISUAL_SCAN_FILE_LIMIT) {
|
||||
const { dir, depth } = queue.shift();
|
||||
let entries;
|
||||
try {
|
||||
entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
if (depth >= VISUAL_SCAN_DEPTH_LIMIT || entry.name.startsWith('.') || WORKSPACE_DISCOVERY_IGNORED_DIRS.has(entry.name)) continue;
|
||||
queue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
|
||||
} else if (entry.isFile() && inspectFile(path.join(dir, entry.name))) {
|
||||
return true;
|
||||
}
|
||||
if (scannedFiles >= VISUAL_SCAN_FILE_LIMIT) break;
|
||||
}
|
||||
}
|
||||
return styledComponents >= 3;
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the first non-empty line under a bare `## <heading>` section of
|
||||
* PRODUCT.md (e.g. `## Register`, `## Platform`). Returns null when the
|
||||
* PRODUCT.md (for example `## Platform`). Returns null when the
|
||||
* section is absent. The heading match is exact (`\s*$`) so near-miss
|
||||
* headings like `## Register guidelines` don't shadow the real field.
|
||||
* near-miss headings don't shadow the real field.
|
||||
*/
|
||||
export function extractSectionValue(product, heading) {
|
||||
if (!product) return null;
|
||||
@@ -717,16 +816,6 @@ export function extractSectionValue(product, heading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the register (`brand` or `product`) out of PRODUCT.md by looking
|
||||
* for a `## Register` section and reading the first non-empty line that
|
||||
* follows it. Returns null when the file is legacy / register-less.
|
||||
*/
|
||||
export function extractRegister(product) {
|
||||
const word = (extractSectionValue(product, 'Register') || '').toLowerCase();
|
||||
return word === 'brand' || word === 'product' ? word : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the platform (`web`, `ios`, `android`, or `adaptive`) out of PRODUCT.md
|
||||
* by looking for a `## Platform` section and reading the first non-empty line
|
||||
@@ -897,21 +986,35 @@ async function cli() {
|
||||
if (!ctx.hasProduct) {
|
||||
// Direct stdout message instead of relying on empty output as a signal
|
||||
// — cheap models miss the empty case more often than the explicit one.
|
||||
const parts = [
|
||||
'NO_PRODUCT_MD: This project has no PRODUCT.md yet. ' +
|
||||
'For `init`, `teach`, `craft`, `shape`, ' +
|
||||
'or wording that clearly maps to a from-scratch build/shape flow, load ' +
|
||||
'reference/init.md and write PRODUCT.md first, unless no user can ' +
|
||||
'respond (a one-shot or automated run, or the user said not to ask): ' +
|
||||
'then write a one-paragraph understanding of the product, audience, ' +
|
||||
'and the page\'s job from the brief, and continue. For any other ' +
|
||||
'(scoped) command against existing code, proceed using the code as ' +
|
||||
`context and offer \`${IMPECCABLE_COMMAND} init\` as a suggestion (do not block).`,
|
||||
'NEW_WORK: No committed design context was found. If this task produces ' +
|
||||
'new design (a build from scratch, or a redesign that discards the ' +
|
||||
'current look), you MUST read reference/new-work.md before making any ' +
|
||||
'design decision. Scoped fixes to existing code do not need it.',
|
||||
];
|
||||
const parts = ctx.hasVisualImplementation
|
||||
? [
|
||||
'NO_PRODUCT_MD: This project has no PRODUCT.md yet, but it does have an incumbent visual implementation. ' +
|
||||
'For `init`, `teach`, `craft`, or `shape`, load reference/init.md and create PRODUCT.md with the user first. ' +
|
||||
'For extension, init documents the incumbent system; for redesign/rebrand, init replaces it through a new ' +
|
||||
'visual-world choice. Other ' +
|
||||
'narrow refinement commands may read the CSS, tokens, components, and assets and proceed without blocking, then ' +
|
||||
`offer \`${IMPECCABLE_COMMAND} init\` as a follow-up.`,
|
||||
'BUILD_INIT_REQUIRED: Before `craft` or `shape`, init must capture PRODUCT.md with the human or structured ' +
|
||||
'simulated user. A redesign then replaces the visual world; an extension documents it.',
|
||||
'SCOPED_EXISTING_ALLOWED: Narrow refinement commands may use the incumbent implementation as authority without ' +
|
||||
'blocking on context setup; they must preserve it and offer init afterward.',
|
||||
'EXISTING_VISUAL_SYSTEM: For refinement or extension, code and assets are incumbent design authority and missing ' +
|
||||
'DESIGN.md is a documentation gap. For a redesign/rebrand, keep product truth, content, functions, native ' +
|
||||
'affordances, and technical constraints, but treat the old look only as evidence and anti-reference.',
|
||||
]
|
||||
: [
|
||||
'NO_PRODUCT_MD: This project has no PRODUCT.md yet. ' +
|
||||
'For `init`, `teach`, `craft`, `shape`, ' +
|
||||
'or wording that clearly maps to a from-scratch build/shape flow, load ' +
|
||||
'reference/init.md, complete its human or structured simulated-user interview, and write PRODUCT.md plus the ' +
|
||||
'user-chosen seed DESIGN.md before building. If no answer mechanism truly exists, init may infer only from the ' +
|
||||
'explicit brief, label its assumptions, and still write both files. For any other ' +
|
||||
'(scoped) command against existing code, proceed using the code as ' +
|
||||
`context and offer \`${IMPECCABLE_COMMAND} init\` as a suggestion (do not block).`,
|
||||
'IDENTITY_INIT_REQUIRED: No committed product or visual world was found. New builds and redesigns ' +
|
||||
'must finish reference/init.md before reference/new-work.md develops the task-specific surface concept. Scoped ' +
|
||||
'fixes to existing code do not need the new-surface flow.',
|
||||
];
|
||||
parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists }));
|
||||
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
|
||||
parts.push(buildMissingTargetDirective());
|
||||
@@ -928,22 +1031,15 @@ async function cli() {
|
||||
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
|
||||
parts.push(buildMissingTargetDirective());
|
||||
}
|
||||
const register = extractRegister(ctx.product);
|
||||
// The register field survives as a family hint (brand = Persuade/Experience,
|
||||
// product = Operate/Read); SKILL.md's mode section carries the essentials
|
||||
// inline, so no register-file read is mandated here. What IS mandated:
|
||||
// the new-work playbook when no committed design system exists yet.
|
||||
if (!ctx.hasDesign) {
|
||||
parts.push(
|
||||
'NEW_WORK: PRODUCT.md exists but no DESIGN.md was found. If the code ' +
|
||||
'has no committed design system either (check the project files), and ' +
|
||||
'this task produces new design or a redesign that discards the current ' +
|
||||
'look, you MUST read reference/new-work.md before making any design ' +
|
||||
'decision. Scoped fixes inside an existing design system do not need it.',
|
||||
);
|
||||
}
|
||||
if (register) {
|
||||
parts.push(`REGISTER: \`${register}\` (family hint: brand = Persuade/Experience surfaces, product = Operate/Read). Derive the visitor's mode per SKILL.md.`);
|
||||
parts.push(ctx.hasVisualImplementation
|
||||
? 'BUILD_DESIGN_DOCUMENT_REQUIRED: PRODUCT.md exists and DESIGN.md is missing, but code contains incumbent visual decisions. ' +
|
||||
'Before `craft` or `shape`, load reference/init.md Step 5. For extension, document CSS, tokens, components, and ' +
|
||||
'assets as the incumbent world. For redesign/rebrand, replace the visual world with the user and treat the old ' +
|
||||
'look only as evidence and anti-reference. Narrow refinement commands may proceed using the implementation directly.'
|
||||
: 'IDENTITY_INIT_REQUIRED: PRODUCT.md exists but no DESIGN.md or incumbent visual implementation was found. ' +
|
||||
'A new build or redesign must complete reference/init.md Step 5 with the human or structured simulated ' +
|
||||
'user before reference/new-work.md develops the task-specific surface concept. Scoped fixes to existing code do not need it.');
|
||||
}
|
||||
const platform = extractPlatform(ctx.product);
|
||||
const nativeRefs =
|
||||
@@ -991,6 +1087,7 @@ function buildResolvedContextDirective(ctx, options, { targetExists = null } = {
|
||||
repoRoot: ctx.repoRoot,
|
||||
productPath: ctx.productPath,
|
||||
designPath: ctx.designPath,
|
||||
hasVisualImplementation: ctx.hasVisualImplementation,
|
||||
}, null, 2)}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user