init project

This commit is contained in:
root
2026-06-25 19:06:59 -04:00
parent c5dfee6efb
commit 5d017f533a
349 changed files with 31330 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const manifestPath = path.join(root, 'PHASE_10_PACKAGE_MANIFEST_v1.0.json');
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set(['PHASE_10_PACKAGE_MANIFEST_v1.0.json', 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
const entries = await readdir(directory, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(root, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push({ fullPath, relative });
}
}
return files;
}
const files = (await walk(root)).sort((a, b) => a.relative.localeCompare(b.relative));
const entries = [];
for (const file of files) {
const contents = await readFile(file.fullPath);
const details = await stat(file.fullPath);
entries.push({
path: file.relative,
bytes: details.size,
sha256: createHash('sha256').update(contents).digest('hex'),
});
}
const manifest = {
schema_version: '1.0',
project: 'RentalDriveGo',
phase: 10,
generated_at: '2026-06-25',
excludes: [...excludedDirectories, ...excludedFiles],
file_count: entries.length,
files: entries,
};
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`Generated Phase 10 manifest for ${entries.length} files.`);
+55
View File
@@ -0,0 +1,55 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const manifestPath = path.join(root, 'PHASE_11_PACKAGE_MANIFEST_v1.0.json');
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set(['PHASE_11_PACKAGE_MANIFEST_v1.0.json', 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
const entries = await readdir(directory, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(root, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push({ fullPath, relative });
}
}
return files;
}
const files = (await walk(root)).sort((a, b) => a.relative.localeCompare(b.relative));
const entries = [];
for (const file of files) {
const contents = await readFile(file.fullPath);
const details = await stat(file.fullPath);
entries.push({
path: file.relative,
bytes: details.size,
sha256: createHash('sha256').update(contents).digest('hex'),
});
}
const manifest = {
schema_version: '1.0',
project: 'RentalDriveGo',
phase: 11,
generated_at: '2026-06-25',
excludes: [...excludedDirectories, ...excludedFiles],
file_count: entries.length,
files: entries,
};
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`Generated Phase 11 manifest for ${entries.length} files.`);
+52
View File
@@ -0,0 +1,52 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const manifestPath = path.join(root, 'PHASE_12_PACKAGE_MANIFEST_v1.0.json');
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set(['PHASE_12_PACKAGE_MANIFEST_v1.0.json', 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(root, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push({ fullPath, relative });
}
}
return files;
}
const files = (await walk(root)).sort((a, b) => a.relative.localeCompare(b.relative));
const entries = [];
for (const file of files) {
const contents = await readFile(file.fullPath);
const details = await stat(file.fullPath);
entries.push({
path: file.relative,
bytes: details.size,
sha256: createHash('sha256').update(contents).digest('hex'),
});
}
const manifest = {
schema_version: '1.0',
project: 'RentalDriveGo',
phase: 12,
generated_at: '2026-06-25',
excludes: [...excludedDirectories, ...excludedFiles],
file_count: entries.length,
files: entries,
};
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`Generated Phase 12 manifest for ${entries.length} files.`);
+56
View File
@@ -0,0 +1,56 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const root = path.resolve(import.meta.dirname, '..');
const manifestPath = path.join(root, 'PHASE_13_PACKAGE_MANIFEST_v1.0.json');
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set([
'PHASE_13_PACKAGE_MANIFEST_v1.0.json',
'package-lock.json',
'tsconfig.tsbuildinfo',
]);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(root, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push({ fullPath, relative });
}
}
return files;
}
const files = (await walk(root)).sort((a, b) => a.relative.localeCompare(b.relative));
const entries = [];
for (const file of files) {
const contents = await readFile(file.fullPath);
const details = await stat(file.fullPath);
entries.push({
path: file.relative,
bytes: details.size,
sha256: createHash('sha256').update(contents).digest('hex'),
});
}
const manifest = {
schema_version: '1.0',
project: 'RentalDriveGo',
phase: 13,
generated_at: '2026-06-25',
excludes: [...excludedDirectories, ...excludedFiles],
file_count: entries.length,
files: entries,
};
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(`Generated Phase 13 manifest for ${entries.length} files.`);
+46
View File
@@ -0,0 +1,46 @@
import { createHash } from 'node:crypto';
import { readFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
export const repositoryRoot = path.resolve(import.meta.dirname, '..');
export async function sha256(filePath) {
const contents = await readFile(filePath);
return createHash('sha256').update(contents).digest('hex');
}
export async function walk(directory, predicate = () => true) {
const output = [];
for (const entry of await readdir(directory)) {
const fullPath = path.join(directory, entry);
const details = await stat(fullPath);
if (details.isDirectory()) output.push(...(await walk(fullPath, predicate)));
else if (predicate(fullPath)) output.push(fullPath);
}
return output;
}
export function flatten(value, prefix = '', output = new Map()) {
if (Array.isArray(value)) {
value.forEach((entry, index) => flatten(entry, `${prefix}[${index}]`, output));
if (value.length === 0) output.set(prefix, value);
return output;
}
if (value && typeof value === 'object') {
for (const [key, entry] of Object.entries(value)) {
flatten(entry, prefix ? `${prefix}.${key}` : key, output);
}
return output;
}
output.set(prefix, value);
return output;
}
export function fail(messages) {
for (const message of messages) console.error(`ERROR: ${message}`);
process.exitCode = 1;
}
export function pass(message) {
console.log(`PASS: ${message}`);
}
+24
View File
@@ -0,0 +1,24 @@
import { cp, mkdir, rm } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import path from 'node:path';
const root = process.cwd();
const standalone = path.join(root, '.next', 'standalone');
if (!existsSync(standalone)) {
throw new Error('Missing .next/standalone. Run next build before preparing the server.');
}
const copies = [
[path.join(root, '.next', 'static'), path.join(standalone, '.next', 'static')],
[path.join(root, 'public'), path.join(standalone, 'public')],
];
for (const [source, destination] of copies) {
if (!existsSync(source)) continue;
await rm(destination, { recursive: true, force: true });
await mkdir(path.dirname(destination), { recursive: true });
await cp(source, destination, { recursive: true });
}
console.log('Prepared standalone server assets.');
+19
View File
@@ -0,0 +1,19 @@
import { spawnSync } from 'node:child_process';
const commands = [
['pnpm', ['validate']],
['pnpm', ['format:check']],
['pnpm', ['lint']],
['pnpm', ['typecheck']],
['pnpm', ['test:unit']],
['pnpm', ['build'], { SITE_ORIGIN: 'http://127.0.0.1:3000', PUBLIC_RELEASE_APPROVED: 'false' }],
['pnpm', ['audit']],
];
for (const [command, args, extraEnv = {}] of commands) {
const result = spawnSync(command, args, {
stdio: 'inherit',
shell: process.platform === 'win32',
env: { ...process.env, ...extraEnv },
});
if (result.status !== 0) process.exit(result.status ?? 1);
}
+19
View File
@@ -0,0 +1,19 @@
import { spawnSync } from 'node:child_process';
const scripts = [
'validate-phase9-contract.mjs',
'validate-runtime-dependencies.mjs',
'validate-locales.mjs',
'validate-routes.mjs',
'validate-tokens.mjs',
'validate-phase11-foundation.mjs',
'validate-css-logical.mjs',
'validate-prohibited-content.mjs',
'validate-metadata.mjs',
'validate-phase12-components.mjs',
'validate-phase13-homepage.mjs',
];
for (const script of scripts) {
const result = spawnSync(process.execPath, [`scripts/${script}`], { stdio: 'inherit' });
if (result.status !== 0) process.exit(result.status ?? 1);
}
+26
View File
@@ -0,0 +1,26 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, walk } from './lib.mjs';
const cssFiles = await walk(path.join(repositoryRoot, 'src'), (file) => file.endsWith('.css'));
const errors = [];
const forbidden = [
/(^|[;{]\s*)(left|right)\s*:/gim,
/(^|[;{]\s*)(margin|padding|border)-(left|right)(?:-[a-z]+)?\s*:/gim,
/(^|[;{]\s*)inset-(left|right)\s*:/gim,
/(^|[;{]\s*)text-align\s*:\s*(left|right)/gim,
/(^|[;{]\s*)float\s*:\s*(left|right)/gim,
];
for (const file of cssFiles) {
const text = await readFile(file, 'utf8');
for (const expression of forbidden) {
const matches = text.match(expression);
if (matches)
errors.push(
`${path.relative(repositoryRoot, file)} uses physical CSS: ${matches.join(', ')}`,
);
}
}
if (errors.length > 0) fail(errors);
else pass(`${cssFiles.length} CSS files use direction-safe logical declarations.`);
+47
View File
@@ -0,0 +1,47 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, flatten, pass, repositoryRoot } from './lib.mjs';
const localeRoot = path.join(repositoryRoot, 'src', 'content', 'locales');
const locales = ['en', 'fr', 'ar'];
const files = ['homepage.json', 'shell.json'];
const errors = [];
for (const file of files) {
const catalogs = new Map();
for (const locale of locales) {
const parsed = JSON.parse(await readFile(path.join(localeRoot, locale, file), 'utf8'));
catalogs.set(locale, flatten(parsed));
}
const sourceKeys = [...catalogs.get('en').keys()];
for (const locale of locales) {
const catalog = catalogs.get(locale);
const keys = [...catalog.keys()];
const missing = sourceKeys.filter((key) => !catalog.has(key));
const extra = keys.filter((key) => !catalogs.get('en').has(key));
if (missing.length) errors.push(`${locale}/${file} missing: ${missing.join(', ')}`);
if (extra.length) errors.push(`${locale}/${file} extra: ${extra.join(', ')}`);
for (const [key, value] of catalog) {
if (typeof value === 'string' && value.trim().length === 0) {
errors.push(`${locale}/${file}:${key} is empty.`);
}
}
}
}
const expectedDirections = { en: 'ltr', fr: 'ltr', ar: 'rtl' };
for (const locale of locales) {
const homepage = JSON.parse(
await readFile(path.join(localeRoot, locale, 'homepage.json'), 'utf8'),
);
if (homepage.meta.dir !== expectedDirections[locale]) {
errors.push(`${locale} metadata direction must be ${expectedDirections[locale]}.`);
}
if (JSON.stringify(homepage).includes('homePageCar')) {
errors.push(`${locale} implementation catalog still contains the retired project name.`);
}
}
if (errors.length > 0) fail(errors);
else
pass('English, French, and Arabic catalogs have exact key parity and valid direction metadata.');
+39
View File
@@ -0,0 +1,39 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const errors = [];
const metadata = await readFile(
path.join(repositoryRoot, 'src', 'lib', 'metadata', 'build-metadata.ts'),
'utf8',
);
const homepage = await readFile(
path.join(repositoryRoot, 'src', 'app', '[locale]', 'page.tsx'),
'utf8',
);
const pending = await readFile(
path.join(repositoryRoot, 'src', 'app', '[locale]', '[slug]', 'page.tsx'),
'utf8',
);
for (const required of [
'alternates',
'canonical',
'hreflangMap',
'openGraph',
'alternateLocale',
'twitter',
'robots',
]) {
if (!metadata.includes(required)) errors.push(`Metadata foundation omits ${required}.`);
}
if (!homepage.includes("routeId: 'home'")) errors.push('Homepage metadata is not route-ID driven.');
if (!pending.includes('indexable: false')) {
errors.push('Unapproved route shells must remain noindex.');
}
if (/application\/ld\+json|aggregateRating|priceCurrency/.test(metadata + homepage + pending)) {
errors.push('Unsupported structured data or pricing metadata was introduced.');
}
if (errors.length) fail(errors);
else pass('Localized canonical, hreflang, Open Graph, social, and robots foundations are present.');
+30
View File
@@ -0,0 +1,30 @@
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { repositoryRoot, sha256, fail, pass } from './lib.mjs';
const manifestPath = path.join(repositoryRoot, 'PHASE_10_PACKAGE_MANIFEST_v1.0.json');
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
const failures = [];
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 10) {
failures.push('Phase 10 manifest identity is invalid.');
}
if (manifest.file_count !== manifest.files.length) {
failures.push(
`Manifest file_count ${manifest.file_count} does not match ${manifest.files.length} entries.`,
);
}
for (const entry of manifest.files) {
const filePath = path.join(repositoryRoot, entry.path);
try {
const details = await stat(filePath);
if (details.size !== entry.bytes) failures.push(`${entry.path}: byte size changed.`);
const digest = await sha256(filePath);
if (digest !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
} catch {
failures.push(`${entry.path}: file is missing.`);
}
}
if (failures.length) fail(failures);
else pass(`Phase 10 package manifest verifies ${manifest.files.length} repository files.`);
+89
View File
@@ -0,0 +1,89 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, walk } from './lib.mjs';
const errors = [];
const componentTokenPath = path.join(repositoryRoot, 'src', 'styles', 'component-tokens.css');
const componentTokens = await readFile(componentTokenPath, 'utf8');
const requiredTokens = [
'--surface-page',
'--surface-primary',
'--surface-elevated',
'--surface-muted',
'--text-primary',
'--text-secondary',
'--text-inverse',
'--border-standard',
'--border-strong',
'--interactive-primary',
'--interactive-conversion',
'--interactive-danger',
'--focus-ring',
'--status-success-surface',
'--status-warning-surface',
'--status-error-surface',
'--status-info-surface',
'--overlay-scrim',
'--control-disabled-surface',
'--selection-surface',
'--skeleton-base',
'--shadow-card',
'--container-wide',
'--layer-overlay',
'--type-display-size',
];
for (const token of requiredTokens) {
if (!componentTokens.includes(token)) errors.push(`Missing Phase 11 component token ${token}.`);
}
if (!componentTokens.includes("html[data-theme='dark']")) {
errors.push('Component token layer has no explicit dark-theme mapping.');
}
if (!componentTokens.includes("html[data-theme-preference='system']")) {
errors.push('Component token layer has no CSS-only system-theme fallback.');
}
const cssFiles = await walk(path.join(repositoryRoot, 'src'), (file) => file.endsWith('.css'));
const declarationFiles = new Set([
path.join(repositoryRoot, 'src', 'styles', 'tokens.css'),
componentTokenPath,
]);
const literalColor = /(?:#[0-9a-f]{3,8}\b|\brgba?\(|\bhsla?\()/gi;
const rawPaletteReference = /var\(--color-(?:blue|orange|navy|gray|green|amber|red)-/gi;
for (const file of cssFiles) {
if (declarationFiles.has(file)) continue;
const text = await readFile(file, 'utf8');
if (literalColor.test(text)) {
errors.push(
`${path.relative(repositoryRoot, file)} contains a literal color outside token declarations.`,
);
}
literalColor.lastIndex = 0;
if (rawPaletteReference.test(text)) {
errors.push(`${path.relative(repositoryRoot, file)} consumes a raw palette token.`);
}
rawPaletteReference.lastIndex = 0;
}
const shellFiles = [
'src/components/app-shell/SiteHeader.tsx',
'src/components/app-shell/MobileNavigation.tsx',
'src/components/app-shell/SiteFooter.tsx',
'src/app/[locale]/loading.tsx',
'src/app/[locale]/error.tsx',
'src/app/[locale]/not-found.tsx',
'src/app/[locale]/[slug]/page.tsx',
];
for (const relativePath of shellFiles) {
try {
await readFile(path.join(repositoryRoot, relativePath), 'utf8');
} catch {
errors.push(`Required Phase 11 shell file is missing: ${relativePath}`);
}
}
if (errors.length) fail(errors);
else {
pass('Phase 11 component tokens, theme fallback, and application-shell foundations are present.');
}
+87
View File
@@ -0,0 +1,87 @@
import { readFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
const manifestName = 'PHASE_11_PACKAGE_MANIFEST_v1.0.json';
const manifestPath = path.join(repositoryRoot, manifestName);
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set([manifestName, 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
const entries = await readdir(directory, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
files.push(...(await walk(fullPath)));
continue;
}
const relative = path.relative(repositoryRoot, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push(relative);
}
return files;
}
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
const failures = [];
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 11) {
failures.push('Phase 11 manifest identity is invalid.');
}
if (!Array.isArray(manifest.files)) {
failures.push('Manifest files property must be an array.');
} else if (manifest.file_count !== manifest.files.length) {
failures.push(
`Manifest file_count ${manifest.file_count} does not match ${manifest.files.length} entries.`,
);
}
const listedPaths = new Set();
for (const entry of manifest.files ?? []) {
if (
typeof entry.path !== 'string' ||
entry.path.length === 0 ||
path.isAbsolute(entry.path) ||
entry.path.split('/').includes('..')
) {
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
continue;
}
if (listedPaths.has(entry.path)) {
failures.push(`${entry.path}: duplicate manifest entry.`);
continue;
}
listedPaths.add(entry.path);
const filePath = path.join(repositoryRoot, entry.path);
try {
const details = await stat(filePath);
if (!details.isFile()) failures.push(`${entry.path}: is not a regular file.`);
if (details.size !== entry.bytes) failures.push(`${entry.path}: byte size changed.`);
const digest = await sha256(filePath);
if (digest !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
} catch {
failures.push(`${entry.path}: file is missing.`);
}
}
const actualPaths = new Set(await walk(repositoryRoot));
for (const actual of actualPaths) {
if (!listedPaths.has(actual)) failures.push(`${actual}: unlisted repository file.`);
}
for (const listed of listedPaths) {
if (!actualPaths.has(listed)) failures.push(`${listed}: listed but excluded or absent.`);
}
if (failures.length) fail(failures);
else {
pass(`Phase 11 package manifest verifies ${listedPaths.size} repository files with no extras.`);
}
+97
View File
@@ -0,0 +1,97 @@
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, walk } from './lib.mjs';
const inventoryPath = path.join(repositoryRoot, 'docs/phase12/COMPONENT_INVENTORY_v1.0.json');
const inventory = JSON.parse(await readFile(inventoryPath, 'utf8'));
const errors = [];
const allowedStatus = new Set(['implemented', 'partially-implemented', 'blocked', 'excluded']);
const names = new Set();
if (inventory.phase !== 12 || !Array.isArray(inventory.entries)) {
errors.push('Component inventory must identify Phase 12 and contain an entries array.');
}
for (const entry of inventory.entries ?? []) {
if (names.has(entry.name)) errors.push(`Duplicate inventory component: ${entry.name}`);
names.add(entry.name);
if (!allowedStatus.has(entry.status))
errors.push(`${entry.name}: invalid status ${entry.status}`);
for (const field of ['sourceSpecification', 'variants', 'openIssues', 'homepageConsumers']) {
if (!Array.isArray(entry[field])) errors.push(`${entry.name}: ${field} must be an array.`);
}
if (
!entry.tests ||
!['unit', 'interaction', 'accessibility', 'visual'].every(
(key) => typeof entry.tests[key] === 'boolean',
)
) {
errors.push(`${entry.name}: test coverage flags are incomplete.`);
}
if (entry.status === 'blocked' && entry.openIssues.length === 0) {
errors.push(`${entry.name}: blocked components require an issue reference.`);
}
}
for (const required of [
'Button',
'ActionLink',
'FormField',
'Tabs',
'Accordion',
'Dialog',
'Alert',
'Card',
'Workflow',
'Comparison',
'Metric',
'CTA',
'ProductPreview',
]) {
if (!names.has(required)) errors.push(`Required component missing from inventory: ${required}`);
}
const componentFiles = await walk(path.join(repositoryRoot, 'src/components'), (file) =>
/\.(?:ts|tsx|css)$/.test(file),
);
const forbiddenRawToken = /var\(--color-(?:blue|orange|gray|green|amber|red|navy)-/g;
const forbiddenLiteralColor = /(?:#[0-9a-f]{3,8}\b|rgba?\(|hsla?\()/gi;
for (const file of componentFiles) {
const text = await readFile(file, 'utf8');
const relative = path.relative(repositoryRoot, file);
if (file.endsWith('.css') && forbiddenRawToken.test(text))
errors.push(`${relative}: consumes a raw palette token.`);
if (file.endsWith('.css') && forbiddenLiteralColor.test(text))
errors.push(`${relative}: contains a literal color.`);
if (/href=["']#["']/.test(text)) errors.push(`${relative}: contains a placeholder href="#".`);
}
for (const artifact of [
'COMPONENT_SPECIFICATION_TRACEABILITY_v1.0.csv',
'VARIANT_INVENTORY_v1.0.csv',
'ACCESSIBILITY_BEHAVIOR_MATRIX_v1.0.csv',
'RTL_BEHAVIOR_MATRIX_UPDATE_v1.0.csv',
'RESPONSIVE_VALIDATION_MATRIX_v1.0.csv',
'STORY_AND_FIXTURE_INVENTORY_v1.0.csv',
'TEST_COVERAGE_REPORT_v1.0.csv',
'COMPONENT_CATALOG_v1.0.md',
]) {
try {
await stat(path.join(repositoryRoot, 'docs/phase12', artifact));
} catch {
errors.push(`Required Phase 12 artifact missing: ${artifact}`);
}
}
const fixturePage = await readFile(
path.join(repositoryRoot, 'src/app/[locale]/component-lab/page.tsx'),
'utf8',
);
if (!fixturePage.includes("COMPONENT_FIXTURES_ENABLED !== 'true'")) {
errors.push('Component fixture route is not protected by the production environment guard.');
}
if (errors.length > 0) fail(errors);
else
pass(
`${inventory.entries.length} inventory entries and ${componentFiles.length} component source files satisfy Phase 12 static controls.`,
);
+65
View File
@@ -0,0 +1,65 @@
import { readFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
const manifestName = 'PHASE_12_PACKAGE_MANIFEST_v1.0.json';
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set([manifestName, 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(repositoryRoot, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push(relative);
}
}
return files;
}
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
const failures = [];
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 12)
failures.push('Phase 12 manifest identity is invalid.');
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length)
failures.push('Manifest file count is invalid.');
const listed = new Set();
for (const entry of manifest.files ?? []) {
if (
typeof entry.path !== 'string' ||
path.isAbsolute(entry.path) ||
entry.path.split('/').includes('..')
) {
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
continue;
}
if (listed.has(entry.path)) {
failures.push(`${entry.path}: duplicate manifest entry.`);
continue;
}
listed.add(entry.path);
try {
const filePath = path.join(repositoryRoot, entry.path);
const details = await stat(filePath);
if (!details.isFile() || details.size !== entry.bytes)
failures.push(`${entry.path}: file metadata changed.`);
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
} catch {
failures.push(`${entry.path}: file is missing.`);
}
}
const actual = new Set(await walk(repositoryRoot));
for (const file of actual)
if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
for (const file of listed)
if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
if (failures.length) fail(failures);
else pass(`Phase 12 package manifest verifies ${listed.size} repository files with no extras.`);
+68
View File
@@ -0,0 +1,68 @@
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const errors = [];
const pagePath = path.join(repositoryRoot, 'src/app/[locale]/page.tsx');
const cssPath = path.join(repositoryRoot, 'src/app/[locale]/page.module.css');
const modelPath = path.join(repositoryRoot, 'src/content/homepage-model.ts');
const page = await readFile(pagePath, 'utf8');
const css = await readFile(cssPath, 'utf8');
const model = await readFile(modelPath, 'utf8');
for (const sectionId of ['product', 'workflow', 'modules', 'pricing', 'faq']) {
if (!page.includes(`id="${sectionId}"`))
errors.push(`Homepage section #${sectionId} is missing.`);
}
for (const component of [
'ActionLink',
'Accordion',
'CTA',
'Comparison',
'FeatureCard',
'ProductPreview',
'SectionHeader',
'Workflow',
]) {
if (!page.includes(component))
errors.push(`Homepage does not compose the approved ${component}.`);
}
if (!page.includes('buildHomepageContent'))
errors.push('Homepage does not consume the typed route content model.');
if (!page.includes("localizedPath('home', locale)"))
errors.push('Homepage actions do not use the locale route helper.');
if (page.includes('messages.foundation'))
errors.push('Phase 11 foundation placeholder remains in the homepage route.');
if (/href=["']#["']/.test(page)) errors.push('Homepage contains href="#".');
if (/#[0-9a-f]{3,8}\b|rgba?\(|hsla?\(/i.test(css))
errors.push('Homepage CSS contains literal colors.');
if (/var\(--color-(?:blue|orange|gray|green|amber|red|navy)-/i.test(css)) {
errors.push('Homepage CSS consumes a raw palette token.');
}
if (!model.includes('disabledReason'))
errors.push('Homepage content model does not encode unavailable destinations.');
if (!model.includes('PreviewRow'))
errors.push('Homepage content model does not type product preview rows.');
for (const artifact of [
'tests/unit/homepage-content.test.ts',
'tests/browser/homepage.spec.ts',
'tests/a11y/homepage.a11y.spec.ts',
'tests/visual/homepage.visual.spec.ts',
'docs/phase13/HOMEPAGE_SECTION_MATRIX_v1.0.csv',
'docs/phase13/HOMEPAGE_CONTENT_MODEL_v1.0.md',
]) {
try {
await stat(path.join(repositoryRoot, artifact));
} catch {
errors.push(`Required Phase 13 artifact missing: ${artifact}`);
}
}
if (errors.length > 0) fail(errors);
else
pass(
'Phase 13 homepage composition, content model, destination gates, and test artifacts satisfy static controls.',
);
+69
View File
@@ -0,0 +1,69 @@
import { readFile, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
const manifestName = 'PHASE_13_PACKAGE_MANIFEST_v1.0.json';
const excludedDirectories = new Set([
'.git',
'.next',
'node_modules',
'coverage',
'playwright-report',
'test-results',
]);
const excludedFiles = new Set([manifestName, 'package-lock.json', 'tsconfig.tsbuildinfo']);
async function walk(directory) {
const files = [];
for (const entry of await readdir(directory, { withFileTypes: true })) {
if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue;
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...(await walk(fullPath)));
else {
const relative = path.relative(repositoryRoot, fullPath).split(path.sep).join('/');
if (!excludedFiles.has(relative)) files.push(relative);
}
}
return files;
}
const manifest = JSON.parse(await readFile(path.join(repositoryRoot, manifestName), 'utf8'));
const failures = [];
if (manifest.project !== 'RentalDriveGo' || manifest.phase !== 13) {
failures.push('Phase 13 manifest identity is invalid.');
}
if (!Array.isArray(manifest.files) || manifest.file_count !== manifest.files.length) {
failures.push('Manifest file count is invalid.');
}
const listed = new Set();
for (const entry of manifest.files ?? []) {
if (
typeof entry.path !== 'string' ||
path.isAbsolute(entry.path) ||
entry.path.split('/').includes('..')
) {
failures.push(`Unsafe manifest path: ${String(entry.path)}`);
continue;
}
if (listed.has(entry.path)) {
failures.push(`${entry.path}: duplicate manifest entry.`);
continue;
}
listed.add(entry.path);
try {
const filePath = path.join(repositoryRoot, entry.path);
const details = await stat(filePath);
if (!details.isFile() || details.size !== entry.bytes)
failures.push(`${entry.path}: file metadata changed.`);
if ((await sha256(filePath)) !== entry.sha256) failures.push(`${entry.path}: SHA-256 changed.`);
} catch {
failures.push(`${entry.path}: file is missing.`);
}
}
const actual = new Set(await walk(repositoryRoot));
for (const file of actual)
if (!listed.has(file)) failures.push(`${file}: unlisted repository file.`);
for (const file of listed)
if (!actual.has(file)) failures.push(`${file}: listed but excluded or absent.`);
if (failures.length) fail(failures);
else pass(`Phase 13 package manifest verifies ${listed.size} repository files with no extras.`);
+41
View File
@@ -0,0 +1,41 @@
import { spawnSync } from 'node:child_process';
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, sha256 } from './lib.mjs';
const contractRoot = path.join(repositoryRoot, 'contracts', 'phase9');
const manifestPath = path.join(contractRoot, 'PACKAGE_MANIFEST_v1.0.json');
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
const errors = [];
if (manifest.file_count !== manifest.files.length) {
errors.push(
`Manifest count ${manifest.file_count} differs from ${manifest.files.length} entries.`,
);
}
for (const entry of manifest.files) {
const filePath = path.join(contractRoot, entry.path);
try {
const details = await stat(filePath);
if (details.size !== entry.bytes) errors.push(`${entry.path}: byte count changed.`);
if ((await sha256(filePath)) !== entry.sha256) errors.push(`${entry.path}: checksum changed.`);
} catch {
errors.push(`${entry.path}: missing.`);
}
}
const validator = spawnSync(
process.execPath,
[path.join(contractRoot, 'qa', 'validate-phase9-package_v1.0.mjs'), contractRoot],
{ encoding: 'utf8' },
);
if (validator.status !== 0) {
errors.push(`Phase 9 validator failed: ${validator.stderr || validator.stdout}`);
}
if (errors.length > 0) fail(errors);
else
pass(
`Phase 9 contract package is immutable and ${manifest.files.length} payload checksums match.`,
);
+28
View File
@@ -0,0 +1,28 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, walk } from './lib.mjs';
const sourceFiles = await walk(path.join(repositoryRoot, 'src'), (file) =>
/\.(?:ts|tsx|js|jsx|css)$/.test(file),
);
const errors = [];
const prohibited = [
{ label: 'placeholder domain', pattern: /example\.(?:com|org|net)|placeholder\.(?:com|test)/i },
{ label: 'simulated submission', pattern: /simulate(?:d|Submission)|fakeSubmit/i },
{ label: 'research-only state backdoor', pattern: /[?&](?:state|scenario|fixture)=/i },
{ label: 'prototype research logger', pattern: /researchLog|prototypeLog/i },
];
for (const file of sourceFiles) {
const text = await readFile(file, 'utf8');
for (const rule of prohibited) {
if (rule.pattern.test(text))
errors.push(`${path.relative(repositoryRoot, file)} contains ${rule.label}.`);
}
}
if (errors.length > 0) fail(errors);
else
pass(
'Runtime source contains no placeholder destination, simulated submission, ' +
'or research-only backdoor.',
);
+50
View File
@@ -0,0 +1,50 @@
import { readFile, stat } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const manifest = JSON.parse(
await readFile(
path.join(repositoryRoot, 'src', 'contracts', 'phase9', 'route-manifest.json'),
'utf8',
),
);
const errors = [];
const requiredLocales = ['en', 'fr', 'ar'];
const ids = new Set();
if (JSON.stringify(manifest.locales) !== JSON.stringify(requiredLocales)) {
errors.push('Route locales must remain exactly en, fr, ar.');
}
for (const route of manifest.routes) {
if (ids.has(route.id)) errors.push(`Duplicate route ID: ${route.id}`);
ids.add(route.id);
for (const locale of requiredLocales) {
if (typeof route.slugs[locale] !== 'string') errors.push(`${route.id} has no ${locale} slug.`);
}
}
for (const locale of requiredLocales) {
if (!manifest.hreflang.values.includes(locale)) errors.push(`hreflang omits ${locale}.`);
}
if (manifest.hreflang.xDefaultLocale !== 'en') errors.push('x-default must resolve to English.');
if (manifest.switching.dropUnknownQuery !== true) {
errors.push('Unknown query parameters must be dropped.');
}
for (const routeFile of [
'src/app/[locale]/page.tsx',
'src/app/[locale]/[slug]/page.tsx',
'src/app/[locale]/not-found.tsx',
]) {
try {
await stat(path.join(repositoryRoot, routeFile));
} catch {
errors.push(`Runtime route implementation missing: ${routeFile}`);
}
}
if (errors.length > 0) fail(errors);
else
pass(
`${manifest.routes.length} route IDs have complete locale, runtime, hash, query, ` +
'and hreflang contracts.',
);
+55
View File
@@ -0,0 +1,55 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot } from './lib.mjs';
const expected = {
node: '24.17.0',
pnpm: '11.9.0',
};
const packageJson = JSON.parse(await readFile(path.join(repositoryRoot, 'package.json'), 'utf8'));
const lockfile = await readFile(path.join(repositoryRoot, 'pnpm-lock.yaml'), 'utf8');
const nodeVersion = (await readFile(path.join(repositoryRoot, '.node-version'), 'utf8')).trim();
const nvmVersion = (await readFile(path.join(repositoryRoot, '.nvmrc'), 'utf8')).trim();
const errors = [];
if (packageJson.packageManager !== `pnpm@${expected.pnpm}`) {
errors.push(`packageManager must be pnpm@${expected.pnpm}.`);
}
if (packageJson.engines?.node !== expected.node) {
errors.push(`package.json engines.node must be ${expected.node}.`);
}
if (packageJson.engines?.pnpm !== expected.pnpm) {
errors.push(`package.json engines.pnpm must be ${expected.pnpm}.`);
}
if (nodeVersion !== expected.node || nvmVersion !== expected.node) {
errors.push('.node-version and .nvmrc must match the approved Node.js runtime.');
}
const directDependencies = {
...(packageJson.dependencies ?? {}),
...(packageJson.devDependencies ?? {}),
};
const exactVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
for (const [name, version] of Object.entries(directDependencies)) {
if (typeof version !== 'string' || !exactVersion.test(version)) {
errors.push(`${name} must use an exact direct version, received ${String(version)}.`);
continue;
}
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const escapedVersion = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const lockSpecifier = new RegExp(
`(?:^|\\n)\\s{6}['\"]?${escapedName}['\"]?:\\n\\s{8}specifier: ${escapedVersion}(?:\\n|$)`,
);
if (!lockSpecifier.test(lockfile)) {
errors.push(
`${name}@${version} is not represented by an exact importer specifier in pnpm-lock.yaml.`,
);
}
}
if (errors.length) fail(errors);
else {
pass(
`Pinned Node ${expected.node}, pnpm ${expected.pnpm}, and ${Object.keys(directDependencies).length} exact direct dependency versions match the lockfile.`,
);
}
+50
View File
@@ -0,0 +1,50 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fail, pass, repositoryRoot, walk } from './lib.mjs';
const sourceCss = await readFile(
path.join(repositoryRoot, 'contracts', 'phase9', 'tokens', 'semantic-tokens_v1.0.css'),
'utf8',
);
const implementationCssPath = path.join(repositoryRoot, 'src', 'styles', 'tokens.css');
const implementationCss = await readFile(implementationCssPath, 'utf8');
const designTokens = JSON.parse(
await readFile(
path.join(repositoryRoot, 'src', 'contracts', 'phase9', 'design-tokens.json'),
'utf8',
),
);
const errors = [];
if (sourceCss !== implementationCss)
errors.push('Implementation tokens.css differs from Phase 9 source.');
const rawColors = new Set();
for (const scale of Object.values(designTokens.raw.color)) {
for (const value of Object.values(scale)) rawColors.add(String(value).toLowerCase());
}
const applicationStyles = await walk(
path.join(repositoryRoot, 'src'),
(file) => file.endsWith('.css') && file !== implementationCssPath,
);
for (const file of applicationStyles) {
const text = (await readFile(file, 'utf8')).toLowerCase();
for (const color of rawColors) {
if (text.includes(color))
errors.push(`${path.relative(repositoryRoot, file)} embeds raw brand color ${color}.`);
}
}
for (const required of [
'--color-canvas',
'--color-text',
'--color-action-primary',
'--color-focus',
]) {
if (!implementationCss.includes(required)) errors.push(`Missing semantic token ${required}.`);
}
if (errors.length > 0) fail(errors);
else
pass(
'Phase 9 semantic tokens are unchanged and application styles contain no raw palette colors.',
);