mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-19 01:26:29 +03:00
Add concept world catalog and review workflow
AI-assisted: prepared by Codex at Paul's request.
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
import crypto from 'node:crypto';
|
||||
import { readFileSync } from 'node:fs';
|
||||
|
||||
export const CONCEPT_STATUSES = new Set(['approved', 'rejected']);
|
||||
|
||||
export function normalizeConceptForm(value) {
|
||||
return String(value || '')
|
||||
.normalize('NFKD')
|
||||
.toLowerCase()
|
||||
.replace(/[’‘]/g, "'")
|
||||
.replace(/[^a-z0-9]+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function readConceptCatalog(catalogPath, reviewsPath) {
|
||||
const catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
|
||||
const reviewData = JSON.parse(readFileSync(reviewsPath, 'utf8'));
|
||||
const reviews = reviewData.reviews || {};
|
||||
const concepts = [];
|
||||
|
||||
for (const family of catalog.families || []) {
|
||||
for (const concept of family.concepts || []) {
|
||||
concepts.push({
|
||||
...concept,
|
||||
familyId: family.id,
|
||||
familyLabel: family.label,
|
||||
status: reviews[concept.id]?.status || 'pending',
|
||||
review: reviews[concept.id] || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { catalog, reviewData, reviews, concepts };
|
||||
}
|
||||
|
||||
export function validateConceptCatalog(catalog, reviewData, { expectedTotal, minimumTotal } = {}) {
|
||||
const errors = [];
|
||||
const warnings = [];
|
||||
const familyIds = new Set();
|
||||
const conceptIds = new Set();
|
||||
const normalizedForms = new Map();
|
||||
const concepts = [];
|
||||
|
||||
if (!Number.isInteger(catalog?.schemaVersion) || catalog.schemaVersion < 1) {
|
||||
errors.push('catalog.schemaVersion must be a positive integer');
|
||||
}
|
||||
if (typeof catalog?.catalogVersion !== 'string' || !catalog.catalogVersion.trim()) {
|
||||
errors.push('catalog.catalogVersion must be a non-empty string');
|
||||
}
|
||||
if (!Array.isArray(catalog?.families) || catalog.families.length < 3) {
|
||||
errors.push('catalog.families must contain at least three families');
|
||||
}
|
||||
|
||||
for (const family of catalog?.families || []) {
|
||||
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(family.id || '')) {
|
||||
errors.push(`invalid family id: ${String(family.id)}`);
|
||||
} else if (familyIds.has(family.id)) {
|
||||
errors.push(`duplicate family id: ${family.id}`);
|
||||
}
|
||||
familyIds.add(family.id);
|
||||
if (typeof family.label !== 'string' || !family.label.trim()) {
|
||||
errors.push(`family ${family.id || '(unknown)'} needs a label`);
|
||||
}
|
||||
if (!Array.isArray(family.concepts) || family.concepts.length === 0) {
|
||||
errors.push(`family ${family.id || '(unknown)'} has no concepts`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const concept of family.concepts) {
|
||||
concepts.push(concept);
|
||||
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(concept.id || '')) {
|
||||
errors.push(`invalid concept id: ${String(concept.id)}`);
|
||||
} else if (conceptIds.has(concept.id)) {
|
||||
errors.push(`duplicate concept id: ${concept.id}`);
|
||||
}
|
||||
conceptIds.add(concept.id);
|
||||
|
||||
const normalized = normalizeConceptForm(concept.form);
|
||||
if (!normalized) {
|
||||
errors.push(`concept ${concept.id || '(unknown)'} needs a form`);
|
||||
} else if (normalizedForms.has(normalized)) {
|
||||
errors.push(`duplicate concept form: ${concept.id} and ${normalizedForms.get(normalized)}`);
|
||||
}
|
||||
normalizedForms.set(normalized, concept.id);
|
||||
|
||||
if (typeof concept.form !== 'string' || !concept.form.includes(',')) {
|
||||
errors.push(`concept ${concept.id || '(unknown)'} must name a form and inherited structure after a comma`);
|
||||
}
|
||||
if (typeof concept.lineage !== 'string' || !concept.lineage.trim()) {
|
||||
errors.push(`concept ${concept.id || '(unknown)'} needs lineage metadata`);
|
||||
}
|
||||
if (!Array.isArray(concept.tags) || concept.tags.length !== 3 || concept.tags.some(tag => typeof tag !== 'string' || !tag.trim())) {
|
||||
errors.push(`concept ${concept.id || '(unknown)'} must have exactly three structural tags`);
|
||||
}
|
||||
if (/\b(?:in the style of|styled like|copy of)\b/i.test(concept.form)) {
|
||||
errors.push(`concept ${concept.id || '(unknown)'} contains imitation language`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedTotal !== undefined && concepts.length !== expectedTotal) {
|
||||
errors.push(`expected ${expectedTotal} concepts, found ${concepts.length}`);
|
||||
}
|
||||
if (minimumTotal !== undefined && concepts.length < minimumTotal) {
|
||||
errors.push(`expected at least ${minimumTotal} concepts, found ${concepts.length}`);
|
||||
}
|
||||
|
||||
if (!Number.isInteger(reviewData?.schemaVersion) || reviewData.schemaVersion < 1) {
|
||||
errors.push('reviews.schemaVersion must be a positive integer');
|
||||
}
|
||||
for (const [id, review] of Object.entries(reviewData?.reviews || {})) {
|
||||
if (!conceptIds.has(id)) errors.push(`review references missing concept: ${id}`);
|
||||
if (!CONCEPT_STATUSES.has(review?.status)) errors.push(`invalid review status for ${id}: ${String(review?.status)}`);
|
||||
if (typeof review?.reviewedBy !== 'string' || !review.reviewedBy.trim()) {
|
||||
errors.push(`review ${id} needs reviewedBy`);
|
||||
}
|
||||
if (typeof review?.reviewedAt !== 'string' || Number.isNaN(Date.parse(review.reviewedAt))) {
|
||||
errors.push(`review ${id} needs an ISO reviewedAt timestamp`);
|
||||
}
|
||||
}
|
||||
|
||||
const approved = concepts.filter(concept => reviewData?.reviews?.[concept.id]?.status === 'approved');
|
||||
const approvedFamilies = new Set(
|
||||
(catalog?.families || [])
|
||||
.filter(family => family.concepts?.some(concept => reviewData?.reviews?.[concept.id]?.status === 'approved'))
|
||||
.map(family => family.id)
|
||||
);
|
||||
if (approved.length < 3) errors.push('at least three concepts must be approved');
|
||||
if (approvedFamilies.size < 3) errors.push('approved concepts must span at least three families');
|
||||
|
||||
return {
|
||||
errors,
|
||||
warnings,
|
||||
stats: {
|
||||
families: familyIds.size,
|
||||
concepts: concepts.length,
|
||||
approved: approved.length,
|
||||
pending: concepts.length - Object.keys(reviewData?.reviews || {}).length,
|
||||
rejected: Object.values(reviewData?.reviews || {}).filter(review => review?.status === 'rejected').length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function approvedPoolRevision(concepts) {
|
||||
const payload = concepts
|
||||
.filter(concept => concept.status === 'approved')
|
||||
.map(concept => `${concept.familyId}:${concept.id}:${concept.form}`)
|
||||
.sort()
|
||||
.join('\n');
|
||||
return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12);
|
||||
}
|
||||
|
||||
export function deterministicRank(items, input, idFor = item => item.id) {
|
||||
return [...items].sort((a, b) => {
|
||||
const scoreA = crypto.createHash('sha256').update(`${input}:${idFor(a)}`).digest('hex');
|
||||
const scoreB = crypto.createHash('sha256').update(`${input}:${idFor(b)}`).digest('hex');
|
||||
return scoreB.localeCompare(scoreA) || idFor(a).localeCompare(idFor(b));
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// Source scripts default to slash commands. The provider build replaces only
|
||||
// this exact declaration, avoiding heuristic rewrites across executable code.
|
||||
export const IMPECCABLE_COMMAND_PREFIX = '/'; // @impeccable-provider-command-prefix
|
||||
export const IMPECCABLE_PROVIDER_ID = 'source'; // @impeccable-provider-id
|
||||
export const IMPECCABLE_COMMAND = `${IMPECCABLE_COMMAND_PREFIX}impeccable`;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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