@qlucent/fishi-core 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { ChildProcess } from 'child_process';
2
+
1
3
  type ProjectType = 'greenfield' | 'brownfield' | 'hybrid';
2
4
  type CostMode = 'performance' | 'balanced' | 'economy';
3
5
  type ModelTier = 'opus' | 'sonnet' | 'haiku';
@@ -620,10 +622,93 @@ declare function runInSandbox(command: string, args: string[], worktreePath: str
620
622
  nodeModulesPath?: string;
621
623
  }): SandboxRunResult;
622
624
 
625
+ interface DevServerConfig {
626
+ command: string;
627
+ args: string[];
628
+ port: number;
629
+ framework: string;
630
+ detected: boolean;
631
+ }
632
+ /**
633
+ * Detect the dev server command from package.json scripts or framework detection.
634
+ */
635
+ declare function detectDevServer(projectDir: string, customCmd?: string): DevServerConfig;
636
+ /**
637
+ * Start the dev server as a background process.
638
+ * Returns the child process handle.
639
+ */
640
+ declare function startDevServer(projectDir: string, config: DevServerConfig): ChildProcess;
641
+ /**
642
+ * Get the vibe mode config for fishi.yaml.
643
+ */
644
+ declare function getVibeModeConfig(enabled: boolean): string;
645
+
646
+ interface DesignTokens {
647
+ colors: Record<string, string>;
648
+ typography: {
649
+ fontFamilies: string[];
650
+ scale: Record<string, string>;
651
+ };
652
+ spacing: Record<string, string>;
653
+ borderRadius: Record<string, string>;
654
+ shadows: Record<string, string>;
655
+ darkMode: boolean;
656
+ }
657
+ interface ComponentEntry {
658
+ name: string;
659
+ path: string;
660
+ type: 'ui' | 'layout' | 'form' | 'data' | 'navigation' | 'other';
661
+ }
662
+ interface ComponentRegistry {
663
+ components: ComponentEntry[];
664
+ library: string | null;
665
+ framework: string | null;
666
+ }
667
+ interface BrandGuardianIssue {
668
+ file: string;
669
+ line: number;
670
+ severity: 'error' | 'warning' | 'info';
671
+ rule: string;
672
+ message: string;
673
+ fix?: string;
674
+ }
675
+ interface BrandGuardianReport {
676
+ issues: BrandGuardianIssue[];
677
+ passed: boolean;
678
+ stats: {
679
+ errors: number;
680
+ warnings: number;
681
+ infos: number;
682
+ filesScanned: number;
683
+ };
684
+ }
685
+ /**
686
+ * Detect design tokens from a project's config files.
687
+ * Checks: tailwind.config, CSS custom properties, theme files.
688
+ */
689
+ declare function detectDesignTokens(projectDir: string): DesignTokens;
690
+ /**
691
+ * Generate default design tokens for a new project.
692
+ */
693
+ declare function generateDefaultTokens(): DesignTokens;
694
+ /**
695
+ * Detect component library and registry from a project.
696
+ */
697
+ declare function detectComponentRegistry(projectDir: string): ComponentRegistry;
698
+ /**
699
+ * Run Brand Guardian validation on project files.
700
+ * Checks for hardcoded colors, inconsistent spacing, missing a11y, etc.
701
+ */
702
+ declare function runBrandGuardian(projectDir: string, tokens: DesignTokens): BrandGuardianReport;
703
+ /**
704
+ * Generate a design system config file for the project.
705
+ */
706
+ declare function generateDesignSystemConfig(tokens: DesignTokens, registry: ComponentRegistry): string;
707
+
623
708
  declare function getSandboxPolicyTemplate(): string;
624
709
 
625
710
  declare function getDockerfileTemplate(): string;
626
711
 
627
712
  declare function getDashboardHtml(): string;
628
713
 
629
- export { type AgentDefinition, type AgentRole, type AgentTemplate, type BackupManifest, type BrownfieldAnalysisData, type ClaudeMdOptions, type CommandTemplate, type ConflictCategory, type ConflictMap, type ConflictResolution, type CostMode, type DetectionCheck, type DetectionResult, type DynamicAgent, type DynamicAgentConfig, type ExecutionConfig, type FileConflict, type FileResolutionMap, type FishiConfig, type FishiYamlOptions, type GateConfig, type GateStatus, type GitConfig, type HookTemplate, type InitOptions, type McpConfig, type McpServerConfig, type ModelRoutingConfig, type ModelTier, type MonitorEvent, type MonitorState, type MonitorSummary, type ProjectConfig, type ProjectType, type ProjectYamlOptions, type SandboxConfig, type SandboxMode, type SandboxPolicy, type SandboxRunResult, type ScaffoldOptions, type ScaffoldResult, type SkillTemplate, type StateConfig, type TaskStatus, type TaskboardConfig, type TemplateContext, architectAgentTemplate, backendAgentTemplate, buildSandboxEnv, createBackup, detectConflicts, detectDocker, devLeadTemplate, devopsAgentTemplate, docsAgentTemplate, emitEvent, frontendAgentTemplate, fullstackAgentTemplate, generateScaffold, getAdaptiveTaskGraphSkill, getAgentCompleteHook, getAgentFactoryTemplate, getAgentRegistryTemplate, getAgentSummary, getApiDesignSkill, getAutoCheckpointHook, getBoardCommand, getBrainstormingSkill, getBrownfieldAnalysisSkill, getBrownfieldDiscoverySkill, getClaudeMdTemplate, getCodeGenSkill, getCoordinatorFactoryTemplate, getDashboardHtml, getDebuggingSkill, getDeploymentSkill, getDocCheckerScript, getDockerfileTemplate, getDocumentationSkill, getFishiYamlTemplate, getGateCommand, getGateManagerScript, getGitignoreAdditions, getInitCommand, getLearningsManagerScript, getMasterOrchestratorTemplate, getMcpJsonTemplate, getMemoryManagerScript, getModelRoutingReference, getMonitorEmitterScript, getPhaseRunnerScript, getPostEditHook, getPrdCommand, getPrdSkill, getProjectYamlTemplate, getResetCommand, getResumeCommand, getSafetyCheckHook, getSandboxPolicyTemplate, getSessionStartHook, getSettingsJsonTemplate, getSprintCommand, getStatusCommand, getTaskboardOpsSkill, getTaskboardUpdateHook, getTestingSkill, getTodoManagerScript, getValidateScaffoldScript, getWorktreeManagerScript, getWorktreeSetupHook, marketingAgentTemplate, mergeClaudeMd, mergeClaudeMdTop, mergeGitignore, mergeMcpJson, mergeSettingsJson, opsLeadTemplate, planningAgentTemplate, planningLeadTemplate, qualityLeadTemplate, readMonitorState, readSandboxConfig, readSandboxPolicy, researchAgentTemplate, runInDockerSandbox, runInProcessSandbox, runInSandbox, securityAgentTemplate, testingAgentTemplate, uiuxAgentTemplate, writingAgentTemplate };
714
+ export { type AgentDefinition, type AgentRole, type AgentTemplate, type BackupManifest, type BrandGuardianIssue, type BrandGuardianReport, type BrownfieldAnalysisData, type ClaudeMdOptions, type CommandTemplate, type ComponentEntry, type ComponentRegistry, type ConflictCategory, type ConflictMap, type ConflictResolution, type CostMode, type DesignTokens, type DetectionCheck, type DetectionResult, type DevServerConfig, type DynamicAgent, type DynamicAgentConfig, type ExecutionConfig, type FileConflict, type FileResolutionMap, type FishiConfig, type FishiYamlOptions, type GateConfig, type GateStatus, type GitConfig, type HookTemplate, type InitOptions, type McpConfig, type McpServerConfig, type ModelRoutingConfig, type ModelTier, type MonitorEvent, type MonitorState, type MonitorSummary, type ProjectConfig, type ProjectType, type ProjectYamlOptions, type SandboxConfig, type SandboxMode, type SandboxPolicy, type SandboxRunResult, type ScaffoldOptions, type ScaffoldResult, type SkillTemplate, type StateConfig, type TaskStatus, type TaskboardConfig, type TemplateContext, architectAgentTemplate, backendAgentTemplate, buildSandboxEnv, createBackup, detectComponentRegistry, detectConflicts, detectDesignTokens, detectDevServer, detectDocker, devLeadTemplate, devopsAgentTemplate, docsAgentTemplate, emitEvent, frontendAgentTemplate, fullstackAgentTemplate, generateDefaultTokens, generateDesignSystemConfig, generateScaffold, getAdaptiveTaskGraphSkill, getAgentCompleteHook, getAgentFactoryTemplate, getAgentRegistryTemplate, getAgentSummary, getApiDesignSkill, getAutoCheckpointHook, getBoardCommand, getBrainstormingSkill, getBrownfieldAnalysisSkill, getBrownfieldDiscoverySkill, getClaudeMdTemplate, getCodeGenSkill, getCoordinatorFactoryTemplate, getDashboardHtml, getDebuggingSkill, getDeploymentSkill, getDocCheckerScript, getDockerfileTemplate, getDocumentationSkill, getFishiYamlTemplate, getGateCommand, getGateManagerScript, getGitignoreAdditions, getInitCommand, getLearningsManagerScript, getMasterOrchestratorTemplate, getMcpJsonTemplate, getMemoryManagerScript, getModelRoutingReference, getMonitorEmitterScript, getPhaseRunnerScript, getPostEditHook, getPrdCommand, getPrdSkill, getProjectYamlTemplate, getResetCommand, getResumeCommand, getSafetyCheckHook, getSandboxPolicyTemplate, getSessionStartHook, getSettingsJsonTemplate, getSprintCommand, getStatusCommand, getTaskboardOpsSkill, getTaskboardUpdateHook, getTestingSkill, getTodoManagerScript, getValidateScaffoldScript, getVibeModeConfig, getWorktreeManagerScript, getWorktreeSetupHook, marketingAgentTemplate, mergeClaudeMd, mergeClaudeMdTop, mergeGitignore, mergeMcpJson, mergeSettingsJson, opsLeadTemplate, planningAgentTemplate, planningLeadTemplate, qualityLeadTemplate, readMonitorState, readSandboxConfig, readSandboxPolicy, researchAgentTemplate, runBrandGuardian, runInDockerSandbox, runInProcessSandbox, runInSandbox, securityAgentTemplate, startDevServer, testingAgentTemplate, uiuxAgentTemplate, writingAgentTemplate };
package/dist/index.js CHANGED
@@ -11111,7 +11111,7 @@ async function createBackup(targetDir, conflictingFiles) {
11111
11111
  manifestFiles.push({ path: relPath, size: stat.size });
11112
11112
  }
11113
11113
  }
11114
- const fishiVersion = "0.7.0";
11114
+ const fishiVersion = "0.9.0";
11115
11115
  const manifest = {
11116
11116
  timestamp: now.toISOString(),
11117
11117
  fishi_version: fishiVersion,
@@ -11368,6 +11368,418 @@ function runInSandbox(command, args, worktreePath, projectDir, options = {}) {
11368
11368
  return runInProcessSandbox(command, args, worktreePath, policy);
11369
11369
  }
11370
11370
 
11371
+ // src/generators/preview-server.ts
11372
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
11373
+ import { join as join6 } from "path";
11374
+ import { spawn as spawn2 } from "child_process";
11375
+ function detectDevServer(projectDir, customCmd) {
11376
+ if (customCmd) {
11377
+ const parts = customCmd.split(/\s+/);
11378
+ return {
11379
+ command: parts[0],
11380
+ args: parts.slice(1),
11381
+ port: 3e3,
11382
+ framework: "custom",
11383
+ detected: true
11384
+ };
11385
+ }
11386
+ const pkgPath = join6(projectDir, "package.json");
11387
+ if (!existsSync6(pkgPath)) {
11388
+ if (existsSync6(join6(projectDir, "manage.py"))) {
11389
+ return { command: "python", args: ["manage.py", "runserver"], port: 8e3, framework: "django", detected: true };
11390
+ }
11391
+ if (existsSync6(join6(projectDir, "requirements.txt"))) {
11392
+ const reqs = readFileSync3(join6(projectDir, "requirements.txt"), "utf-8");
11393
+ if (reqs.includes("flask")) return { command: "python", args: ["-m", "flask", "run"], port: 5e3, framework: "flask", detected: true };
11394
+ if (reqs.includes("fastapi")) return { command: "uvicorn", args: ["main:app", "--reload"], port: 8e3, framework: "fastapi", detected: true };
11395
+ }
11396
+ return { command: "", args: [], port: 3e3, framework: "unknown", detected: false };
11397
+ }
11398
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
11399
+ const scripts = pkg.scripts || {};
11400
+ if (scripts.dev) {
11401
+ const devCmd = scripts.dev;
11402
+ const port = extractPort(devCmd) || 3e3;
11403
+ const framework = detectFrameworkFromCmd(devCmd, pkg);
11404
+ return { command: "npm", args: ["run", "dev"], port, framework, detected: true };
11405
+ }
11406
+ if (scripts.start) {
11407
+ const port = extractPort(scripts.start) || 3e3;
11408
+ const framework = detectFrameworkFromCmd(scripts.start, pkg);
11409
+ return { command: "npm", args: ["run", "start"], port, framework, detected: true };
11410
+ }
11411
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
11412
+ if (deps["next"]) return { command: "npx", args: ["next", "dev"], port: 3e3, framework: "nextjs", detected: true };
11413
+ if (deps["vite"]) return { command: "npx", args: ["vite"], port: 5173, framework: "vite", detected: true };
11414
+ if (deps["astro"]) return { command: "npx", args: ["astro", "dev"], port: 4321, framework: "astro", detected: true };
11415
+ if (deps["nuxt"]) return { command: "npx", args: ["nuxt", "dev"], port: 3e3, framework: "nuxt", detected: true };
11416
+ if (deps["svelte-kit"] || deps["@sveltejs/kit"]) return { command: "npx", args: ["vite", "dev"], port: 5173, framework: "sveltekit", detected: true };
11417
+ if (deps["gatsby"]) return { command: "npx", args: ["gatsby", "develop"], port: 8e3, framework: "gatsby", detected: true };
11418
+ if (deps["remix"] || deps["@remix-run/dev"]) return { command: "npx", args: ["remix", "vite:dev"], port: 5173, framework: "remix", detected: true };
11419
+ if (deps["express"]) return { command: "node", args: ["index.js"], port: 3e3, framework: "express", detected: true };
11420
+ if (existsSync6(join6(projectDir, "manage.py"))) {
11421
+ return { command: "python", args: ["manage.py", "runserver"], port: 8e3, framework: "django", detected: true };
11422
+ }
11423
+ if (existsSync6(join6(projectDir, "requirements.txt"))) {
11424
+ const reqs = readFileSync3(join6(projectDir, "requirements.txt"), "utf-8");
11425
+ if (reqs.includes("flask")) return { command: "python", args: ["-m", "flask", "run"], port: 5e3, framework: "flask", detected: true };
11426
+ if (reqs.includes("fastapi")) return { command: "uvicorn", args: ["main:app", "--reload"], port: 8e3, framework: "fastapi", detected: true };
11427
+ }
11428
+ return { command: "", args: [], port: 3e3, framework: "unknown", detected: false };
11429
+ }
11430
+ function extractPort(cmd) {
11431
+ const portMatch = cmd.match(/(?:--port|-p)\s+(\d+)/) || cmd.match(/PORT=(\d+)/) || cmd.match(/:(\d{4,5})/);
11432
+ return portMatch ? parseInt(portMatch[1], 10) : null;
11433
+ }
11434
+ function detectFrameworkFromCmd(cmd, _pkg) {
11435
+ if (cmd.includes("next")) return "nextjs";
11436
+ if (cmd.includes("vite")) return "vite";
11437
+ if (cmd.includes("astro")) return "astro";
11438
+ if (cmd.includes("nuxt")) return "nuxt";
11439
+ if (cmd.includes("gatsby")) return "gatsby";
11440
+ if (cmd.includes("remix")) return "remix";
11441
+ if (cmd.includes("svelte")) return "sveltekit";
11442
+ if (cmd.includes("express") || cmd.includes("node server")) return "express";
11443
+ if (cmd.includes("flask")) return "flask";
11444
+ if (cmd.includes("django") || cmd.includes("manage.py")) return "django";
11445
+ if (cmd.includes("uvicorn") || cmd.includes("fastapi")) return "fastapi";
11446
+ return "unknown";
11447
+ }
11448
+ function startDevServer(projectDir, config) {
11449
+ const child = spawn2(config.command, config.args, {
11450
+ cwd: projectDir,
11451
+ stdio: "pipe",
11452
+ detached: false,
11453
+ env: { ...process.env, PORT: String(config.port) },
11454
+ shell: true
11455
+ });
11456
+ return child;
11457
+ }
11458
+ function getVibeModeConfig(enabled) {
11459
+ return `
11460
+ vibe_mode:
11461
+ enabled: ${enabled}
11462
+ auto_approve_gates: ${enabled}
11463
+ auto_generate_prd: ${enabled}
11464
+ background_testing: ${enabled}
11465
+ dev_server_autostart: ${enabled}
11466
+ `;
11467
+ }
11468
+
11469
+ // src/generators/design-system.ts
11470
+ import { existsSync as existsSync7, readFileSync as readFileSync4, readdirSync } from "fs";
11471
+ import { join as join7 } from "path";
11472
+ function detectDesignTokens(projectDir) {
11473
+ const tokens = {
11474
+ colors: {},
11475
+ typography: { fontFamilies: [], scale: {} },
11476
+ spacing: {},
11477
+ borderRadius: {},
11478
+ shadows: {},
11479
+ darkMode: false
11480
+ };
11481
+ const tailwindFiles = ["tailwind.config.js", "tailwind.config.ts", "tailwind.config.mjs", "tailwind.config.cjs"];
11482
+ for (const file of tailwindFiles) {
11483
+ const p = join7(projectDir, file);
11484
+ if (existsSync7(p)) {
11485
+ const content = readFileSync4(p, "utf-8");
11486
+ const colorMatches = content.matchAll(/['"]?([\w-]+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,8})['"]?/g);
11487
+ for (const m of colorMatches) {
11488
+ tokens.colors[m[1]] = m[2];
11489
+ }
11490
+ if (content.includes("darkMode")) tokens.darkMode = true;
11491
+ break;
11492
+ }
11493
+ }
11494
+ const cssFiles = findFiles(projectDir, ["src", "styles", "app"], [".css"]);
11495
+ for (const file of cssFiles.slice(0, 10)) {
11496
+ try {
11497
+ const content = readFileSync4(file, "utf-8");
11498
+ const varMatches = content.matchAll(/--(\w[\w-]*)\s*:\s*([^;]+)/g);
11499
+ for (const m of varMatches) {
11500
+ const name = m[1];
11501
+ const value = m[2].trim();
11502
+ if (name.includes("color") || name.includes("bg") || value.startsWith("#") || value.startsWith("rgb") || value.startsWith("hsl")) {
11503
+ tokens.colors[name] = value;
11504
+ } else if (name.includes("font")) {
11505
+ if (name.includes("size")) tokens.typography.scale[name] = value;
11506
+ else if (name.includes("family")) tokens.typography.fontFamilies.push(value);
11507
+ } else if (name.includes("spacing") || name.includes("gap") || name.includes("padding") || name.includes("margin")) {
11508
+ tokens.spacing[name] = value;
11509
+ } else if (name.includes("radius")) {
11510
+ tokens.borderRadius[name] = value;
11511
+ } else if (name.includes("shadow")) {
11512
+ tokens.shadows[name] = value;
11513
+ }
11514
+ }
11515
+ if (content.includes("prefers-color-scheme: dark") || content.includes(".dark")) {
11516
+ tokens.darkMode = true;
11517
+ }
11518
+ } catch {
11519
+ }
11520
+ }
11521
+ const themeFiles = ["theme.ts", "theme.js", "theme.json", "src/theme.ts", "src/theme.js", "src/styles/theme.ts"];
11522
+ for (const file of themeFiles) {
11523
+ if (existsSync7(join7(projectDir, file))) {
11524
+ try {
11525
+ const content = readFileSync4(join7(projectDir, file), "utf-8");
11526
+ const colorMatches = content.matchAll(/['"]?([\w-]+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,8})['"]?/g);
11527
+ for (const m of colorMatches) {
11528
+ tokens.colors[m[1]] = m[2];
11529
+ }
11530
+ } catch {
11531
+ }
11532
+ break;
11533
+ }
11534
+ }
11535
+ return tokens;
11536
+ }
11537
+ function generateDefaultTokens() {
11538
+ return {
11539
+ colors: {
11540
+ "brand-50": "#f0f7ff",
11541
+ "brand-100": "#e0efff",
11542
+ "brand-200": "#b8d9ff",
11543
+ "brand-500": "#0066cc",
11544
+ "brand-600": "#0052a3",
11545
+ "brand-700": "#003d7a",
11546
+ "brand-900": "#001f3f",
11547
+ "gray-50": "#f9fafb",
11548
+ "gray-100": "#f3f4f6",
11549
+ "gray-200": "#e5e7eb",
11550
+ "gray-500": "#6b7280",
11551
+ "gray-700": "#374151",
11552
+ "gray-900": "#111827",
11553
+ "success": "#22c55e",
11554
+ "warning": "#f59e0b",
11555
+ "error": "#ef4444"
11556
+ },
11557
+ typography: {
11558
+ fontFamilies: ['-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'],
11559
+ scale: {
11560
+ "xs": "0.75rem",
11561
+ "sm": "0.875rem",
11562
+ "base": "1rem",
11563
+ "lg": "1.125rem",
11564
+ "xl": "1.25rem",
11565
+ "2xl": "1.5rem",
11566
+ "3xl": "1.875rem",
11567
+ "4xl": "2.25rem"
11568
+ }
11569
+ },
11570
+ spacing: {
11571
+ "xs": "0.25rem",
11572
+ "sm": "0.5rem",
11573
+ "md": "1rem",
11574
+ "lg": "1.5rem",
11575
+ "xl": "2rem",
11576
+ "2xl": "3rem",
11577
+ "3xl": "4rem"
11578
+ },
11579
+ borderRadius: {
11580
+ "sm": "0.25rem",
11581
+ "md": "0.375rem",
11582
+ "lg": "0.5rem",
11583
+ "xl": "0.75rem",
11584
+ "full": "9999px"
11585
+ },
11586
+ shadows: {
11587
+ "sm": "0 1px 2px rgba(0,0,0,0.05)",
11588
+ "md": "0 4px 6px rgba(0,0,0,0.1)",
11589
+ "lg": "0 10px 15px rgba(0,0,0,0.1)"
11590
+ },
11591
+ darkMode: true
11592
+ };
11593
+ }
11594
+ function detectComponentRegistry(projectDir) {
11595
+ const registry = {
11596
+ components: [],
11597
+ library: null,
11598
+ framework: null
11599
+ };
11600
+ const pkgPath = join7(projectDir, "package.json");
11601
+ if (existsSync7(pkgPath)) {
11602
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
11603
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
11604
+ if (deps["@shadcn/ui"] || existsSync7(join7(projectDir, "components.json"))) registry.library = "shadcn";
11605
+ else if (deps["@radix-ui/react-dialog"] || deps["@radix-ui/themes"]) registry.library = "radix";
11606
+ else if (deps["@mui/material"]) registry.library = "mui";
11607
+ else if (deps["antd"]) registry.library = "antd";
11608
+ else if (deps["@chakra-ui/react"]) registry.library = "chakra";
11609
+ else if (deps["@headlessui/react"]) registry.library = "headlessui";
11610
+ if (deps["react"] || deps["react-dom"]) registry.framework = "react";
11611
+ else if (deps["vue"]) registry.framework = "vue";
11612
+ else if (deps["svelte"]) registry.framework = "svelte";
11613
+ else if (deps["@angular/core"]) registry.framework = "angular";
11614
+ }
11615
+ const componentDirs = ["src/components", "components", "src/ui", "app/components", "src/components/ui"];
11616
+ for (const dir of componentDirs) {
11617
+ const fullDir = join7(projectDir, dir);
11618
+ if (existsSync7(fullDir)) {
11619
+ try {
11620
+ scanComponents(fullDir, dir, registry.components);
11621
+ } catch {
11622
+ }
11623
+ }
11624
+ }
11625
+ return registry;
11626
+ }
11627
+ function scanComponents(dir, relBase, components) {
11628
+ try {
11629
+ const entries = readdirSync(dir, { withFileTypes: true });
11630
+ for (const entry of entries) {
11631
+ if (entry.isDirectory()) {
11632
+ scanComponents(join7(dir, entry.name), `${relBase}/${entry.name}`, components);
11633
+ } else if (entry.name.match(/\.(tsx|jsx|vue|svelte)$/)) {
11634
+ const name = entry.name.replace(/\.(tsx|jsx|vue|svelte)$/, "");
11635
+ if (name === "index") continue;
11636
+ const type = classifyComponent(name);
11637
+ components.push({ name, path: `${relBase}/${entry.name}`, type });
11638
+ }
11639
+ }
11640
+ } catch {
11641
+ }
11642
+ }
11643
+ function classifyComponent(name) {
11644
+ const lower = name.toLowerCase();
11645
+ if (/button|badge|avatar|icon|tag|chip|tooltip/i.test(lower)) return "ui";
11646
+ if (/layout|header|footer|sidebar|menu/i.test(lower)) return "layout";
11647
+ if (/input|form|select|checkbox|radio|textarea|switch|slider/i.test(lower)) return "form";
11648
+ if (/table|list|card|grid|chart|graph|stat/i.test(lower)) return "data";
11649
+ if (/link|breadcrumb|tab|pagination|stepper|nav|navbar/i.test(lower)) return "navigation";
11650
+ return "other";
11651
+ }
11652
+ function runBrandGuardian(projectDir, tokens) {
11653
+ const issues = [];
11654
+ let filesScanned = 0;
11655
+ const files = findFiles(projectDir, ["src", "app", "pages", "components"], [".tsx", ".jsx", ".vue", ".svelte", ".css"]);
11656
+ for (const file of files.slice(0, 100)) {
11657
+ try {
11658
+ const content = readFileSync4(file, "utf-8");
11659
+ const lines = content.split("\n");
11660
+ const relPath = file.replace(projectDir, "").replace(/\\/g, "/").replace(/^\//, "");
11661
+ filesScanned++;
11662
+ for (let i = 0; i < lines.length; i++) {
11663
+ const line = lines[i];
11664
+ const lineNum = i + 1;
11665
+ if (file.match(/\.(tsx|jsx|vue|svelte)$/)) {
11666
+ const hexMatches = line.matchAll(/#[0-9a-fA-F]{3,8}\b/g);
11667
+ for (const m of hexMatches) {
11668
+ if (line.trimStart().startsWith("//") || line.trimStart().startsWith("*")) continue;
11669
+ if (line.includes("--")) continue;
11670
+ issues.push({
11671
+ file: relPath,
11672
+ line: lineNum,
11673
+ severity: "warning",
11674
+ rule: "no-hardcoded-colors",
11675
+ message: `Hardcoded color ${m[0]} \u2014 use design tokens instead`,
11676
+ fix: "Replace with a CSS variable or Tailwind class from your design system"
11677
+ });
11678
+ }
11679
+ }
11680
+ if (line.includes("style=") && line.match(/\d+px/)) {
11681
+ issues.push({
11682
+ file: relPath,
11683
+ line: lineNum,
11684
+ severity: "warning",
11685
+ rule: "no-inline-px",
11686
+ message: "Inline px values \u2014 use spacing tokens or Tailwind classes",
11687
+ fix: "Replace px values with spacing scale (xs, sm, md, lg, xl)"
11688
+ });
11689
+ }
11690
+ if (line.match(/<img\b/) && !line.includes("alt=") && !line.includes("alt =")) {
11691
+ issues.push({
11692
+ file: relPath,
11693
+ line: lineNum,
11694
+ severity: "error",
11695
+ rule: "img-alt-text",
11696
+ message: "Image missing alt attribute \u2014 accessibility violation",
11697
+ fix: 'Add alt="descriptive text" or alt="" for decorative images'
11698
+ });
11699
+ }
11700
+ if (line.includes("onClick") && !line.includes("onKeyDown") && !line.includes("onKeyUp") && !line.includes("onKeyPress")) {
11701
+ if (line.includes("<div") || line.includes("<span")) {
11702
+ issues.push({
11703
+ file: relPath,
11704
+ line: lineNum,
11705
+ severity: "warning",
11706
+ rule: "keyboard-accessible",
11707
+ message: "Click handler on non-interactive element without keyboard support",
11708
+ fix: 'Add onKeyDown handler and role="button" tabIndex={0}, or use <button>'
11709
+ });
11710
+ }
11711
+ }
11712
+ if (line.match(/font-size:\s*\d+px/) && !line.includes("--")) {
11713
+ issues.push({
11714
+ file: relPath,
11715
+ line: lineNum,
11716
+ severity: "info",
11717
+ rule: "use-typography-scale",
11718
+ message: "Hardcoded font-size \u2014 use typography scale tokens",
11719
+ fix: "Replace with typography scale class (text-xs, text-sm, text-base, etc.)"
11720
+ });
11721
+ }
11722
+ if (line.match(/<html\b/) && !line.includes("lang=")) {
11723
+ issues.push({
11724
+ file: relPath,
11725
+ line: lineNum,
11726
+ severity: "error",
11727
+ rule: "html-lang",
11728
+ message: "HTML element missing lang attribute \u2014 accessibility violation",
11729
+ fix: 'Add lang="en" (or appropriate language code) to <html>'
11730
+ });
11731
+ }
11732
+ }
11733
+ } catch {
11734
+ }
11735
+ }
11736
+ const errors = issues.filter((i) => i.severity === "error").length;
11737
+ const warnings = issues.filter((i) => i.severity === "warning").length;
11738
+ const infos = issues.filter((i) => i.severity === "info").length;
11739
+ return {
11740
+ issues,
11741
+ passed: errors === 0,
11742
+ stats: { errors, warnings, infos, filesScanned }
11743
+ };
11744
+ }
11745
+ function generateDesignSystemConfig(tokens, registry) {
11746
+ return JSON.stringify({
11747
+ version: "1.0",
11748
+ tokens,
11749
+ components: {
11750
+ library: registry.library,
11751
+ framework: registry.framework,
11752
+ count: registry.components.length,
11753
+ entries: registry.components
11754
+ }
11755
+ }, null, 2) + "\n";
11756
+ }
11757
+ function findFiles(base, dirs, extensions) {
11758
+ const files = [];
11759
+ for (const dir of dirs) {
11760
+ const fullDir = join7(base, dir);
11761
+ if (existsSync7(fullDir)) {
11762
+ walkDir(fullDir, extensions, files);
11763
+ }
11764
+ }
11765
+ return files;
11766
+ }
11767
+ function walkDir(dir, extensions, result) {
11768
+ try {
11769
+ const entries = readdirSync(dir, { withFileTypes: true });
11770
+ for (const entry of entries) {
11771
+ const full = join7(dir, entry.name);
11772
+ if (entry.isDirectory()) {
11773
+ if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "dist" || entry.name === ".git") continue;
11774
+ walkDir(full, extensions, result);
11775
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
11776
+ result.push(full);
11777
+ }
11778
+ }
11779
+ } catch {
11780
+ }
11781
+ }
11782
+
11371
11783
  // src/templates/configs/sandbox-policy.ts
11372
11784
  function getSandboxPolicyTemplate() {
11373
11785
  return `# FISHI Sandbox Policy
@@ -11764,7 +12176,10 @@ export {
11764
12176
  backendAgentTemplate,
11765
12177
  buildSandboxEnv,
11766
12178
  createBackup,
12179
+ detectComponentRegistry,
11767
12180
  detectConflicts,
12181
+ detectDesignTokens,
12182
+ detectDevServer,
11768
12183
  detectDocker,
11769
12184
  devLeadTemplate,
11770
12185
  devopsAgentTemplate,
@@ -11772,6 +12187,8 @@ export {
11772
12187
  emitEvent,
11773
12188
  frontendAgentTemplate,
11774
12189
  fullstackAgentTemplate,
12190
+ generateDefaultTokens,
12191
+ generateDesignSystemConfig,
11775
12192
  generateScaffold,
11776
12193
  getAdaptiveTaskGraphSkill,
11777
12194
  getAgentCompleteHook,
@@ -11822,6 +12239,7 @@ export {
11822
12239
  getTestingSkill,
11823
12240
  getTodoManagerScript,
11824
12241
  getValidateScaffoldScript,
12242
+ getVibeModeConfig,
11825
12243
  getWorktreeManagerScript,
11826
12244
  getWorktreeSetupHook,
11827
12245
  marketingAgentTemplate,
@@ -11838,10 +12256,12 @@ export {
11838
12256
  readSandboxConfig,
11839
12257
  readSandboxPolicy,
11840
12258
  researchAgentTemplate,
12259
+ runBrandGuardian,
11841
12260
  runInDockerSandbox,
11842
12261
  runInProcessSandbox,
11843
12262
  runInSandbox,
11844
12263
  securityAgentTemplate,
12264
+ startDevServer,
11845
12265
  testingAgentTemplate,
11846
12266
  uiuxAgentTemplate,
11847
12267
  writingAgentTemplate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlucent/fishi-core",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Shared templates, types, and generators for the FISHI framework",
5
5
  "license": "MIT",
6
6
  "type": "module",