Add browser questionnaire foundation

Prepared with AI assistance.
This commit is contained in:
Abdul Wahab
2026-09-01 10:02:05 +05:00
parent 1f20997a6d
commit ba7f3c8891
15 changed files with 1140 additions and 18 deletions
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import { cp, copyFile, mkdir, rm } from 'node:fs/promises';
import path from 'node:path';
import sharp from 'sharp';
import { fileURLToPath } from 'node:url';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const buildDir = path.join(root, 'build-picker');
const outputDir = path.join(root, 'skill/scripts/picker');
const faviconSource = path.join(root, 'site/public/favicon.svg');
const faviconOutput = path.join(outputDir, 'favicon.svg');
const heroSource = path.join(
root,
'site/public/assets/neo-kinpaku/candidates/finalists/m-01-v2-01-light.png',
);
const heroOutput = path.join(outputDir, 'assets/hero-light.jpg');
await rm(buildDir, { recursive: true, force: true });
execFileSync(
'bun',
['x', 'astro', 'build', '--config', 'picker/astro.config.mjs'],
{ cwd: root, stdio: 'inherit' },
);
await rm(outputDir, { recursive: true, force: true });
await cp(buildDir, outputDir, { recursive: true });
await mkdir(path.dirname(heroOutput), { recursive: true });
await copyFile(faviconSource, faviconOutput);
await sharp(heroSource)
.resize({ width: 1536, withoutEnlargement: true })
.jpeg({ quality: 80, mozjpeg: true })
.toFile(heroOutput);
await rm(buildDir, { recursive: true, force: true });
console.log(`Built ${path.relative(root, outputDir)}/`);
+5 -2
View File
@@ -109,9 +109,11 @@ function readSkillScripts(scriptsDir) {
if (PER_PROJECT_SCRIPT_ARTIFACTS.has(entry.name)) continue;
const relPath = path.relative(scriptsDir, entryPath).split(path.sep).join('/');
const isBinary = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.woff', '.woff2']
.includes(path.extname(entry.name).toLowerCase());
scripts.push({
name: relPath,
content: fs.readFileSync(entryPath, 'utf-8'),
content: fs.readFileSync(entryPath, isBinary ? undefined : 'utf-8'),
filePath: entryPath,
});
}
@@ -699,7 +701,8 @@ export function replacePlaceholders(content, provider, commandNames = [], allSki
* their command prefix from lib/provider.mjs, whose declaration is replaced
* here by an exact string match.
*/
export function replaceScriptProviderMarker(content, provider, buildProvider = provider) {
export function replaceScriptProviderMarker(content, provider) {
if (Buffer.isBuffer(content)) return content;
const placeholders = PROVIDER_PLACEHOLDERS[provider] || PROVIDER_PLACEHOLDERS.cursor;
const commandPrefix = placeholders.command_prefix || '/';
const prefixMarker = "export const IMPECCABLE_COMMAND_PREFIX = '/'; // @impeccable-provider-command-prefix";
+32 -10
View File
@@ -8,7 +8,8 @@
// For the skill component, also reruns `bun run build:release` and refuses if the
// regenerated harness directories drift from what is committed.
import { readFileSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { readFileSync, readdirSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';
import { execSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -88,6 +89,24 @@ function runMutating(cmd) {
execSync(cmd, { cwd: repoRoot, stdio: 'inherit' });
}
function directoryHash(directory) {
if (!existsSync(directory)) return null;
const hash = createHash('sha256');
function addDirectory(current) {
for (const entry of readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
const entryPath = path.join(current, entry.name);
const relative = path.relative(directory, entryPath).split(path.sep).join('/');
hash.update(relative);
if (entry.isDirectory()) addDirectory(entryPath);
else if (entry.isFile()) hash.update(readFileSync(entryPath));
}
}
addDirectory(directory);
return hash.digest('hex');
}
step(`Reading version from ${cfg.manifest}`);
const manifest = JSON.parse(readFileSync(path.join(repoRoot, cfg.manifest), 'utf8'));
const version = manifest.version;
@@ -112,16 +131,19 @@ ok('clean');
if (cfg.buildCmd) {
step(`Rebuilding outputs (${cfg.buildCmd})`);
if (dryRun) {
console.log(` [dry-run] ${cfg.buildCmd}`);
} else {
execSync(cfg.buildCmd, { cwd: repoRoot, stdio: 'inherit' });
const postBuild = run('git status --porcelain');
if (postBuild) {
fail(`Build produced uncommitted changes. Run \`${cfg.buildCmd}\`, commit the result, then re-run.\n${postBuild}`);
}
ok('build outputs match source');
const pickerOutput = component === 'skill'
? path.join(repoRoot, 'skill/scripts/picker')
: null;
const pickerHashBefore = pickerOutput ? directoryHash(pickerOutput) : null;
execSync(cfg.buildCmd, { cwd: repoRoot, stdio: 'inherit' });
if (pickerHashBefore && directoryHash(pickerOutput) !== pickerHashBefore) {
fail(`Picker build output was stale. Run \`bun run build:picker\`, then re-run the release check.`);
}
const postBuild = run('git status --porcelain');
if (postBuild) {
fail(`Build produced uncommitted changes. Run \`${cfg.buildCmd}\`, commit the result, then re-run.\n${postBuild}`);
}
ok('build outputs match source');
}
step('Checking HEAD is pushed to origin');
+4 -1
View File
@@ -25,8 +25,10 @@ export const SUITES = {
description: 'Build, provider transforms, CLI helpers, context, and storage unit tests.',
triggers: [
...COMMON_INFRA_PATTERNS,
/^picker\//,
/^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/,
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|comp-diff|comp-spec|build-phase|font-match|data\/font-index|concept-seed|generate-image|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|png|raster|image-metrics|font-fingerprint|font-index|hero-checks|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|pin|surface-brief))/,
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|comp-diff|comp-spec|build-phase|font-match|data\/font-index|concept-seed|generate-image|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|png|raster|image-metrics|font-fingerprint|font-index|hero-checks|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|picker|pin|surface-brief))/,
/^site\/(pages|content|components|layouts)\//,
/^README(\.npm)?\.md$/,
/^cli\/bin\//,
],
@@ -70,6 +72,7 @@ export const SUITES = {
'tests/hook.test.mjs',
'tests/impeccable-paths.test.mjs',
'tests/openai-plugin.test.mjs',
'tests/picker-server.test.mjs',
'tests/pin.test.mjs',
'tests/release.test.mjs',
'tests/doctor.test.mjs',