opencroc 0.6.1 → 1.3.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/cli/index.js CHANGED
@@ -1086,6 +1086,31 @@ var init_test_code_generator = __esm({
1086
1086
  }
1087
1087
  });
1088
1088
 
1089
+ // src/validators/schema-validator.ts
1090
+ var init_schema_validator = __esm({
1091
+ "src/validators/schema-validator.ts"() {
1092
+ "use strict";
1093
+ init_esm_shims();
1094
+ }
1095
+ });
1096
+
1097
+ // src/validators/semantic-validator.ts
1098
+ var init_semantic_validator = __esm({
1099
+ "src/validators/semantic-validator.ts"() {
1100
+ "use strict";
1101
+ init_esm_shims();
1102
+ }
1103
+ });
1104
+
1105
+ // src/validators/dryrun-validator.ts
1106
+ import { Project as Project4, DiagnosticCategory } from "ts-morph";
1107
+ var init_dryrun_validator = __esm({
1108
+ "src/validators/dryrun-validator.ts"() {
1109
+ "use strict";
1110
+ init_esm_shims();
1111
+ }
1112
+ });
1113
+
1089
1114
  // src/validators/config-validator.ts
1090
1115
  function validateConfig(config) {
1091
1116
  const errors = [];
@@ -1182,6 +1207,49 @@ function validateConfig(config) {
1182
1207
  });
1183
1208
  }
1184
1209
  }
1210
+ if (config.execution && typeof config.execution === "object") {
1211
+ const execution = config.execution;
1212
+ const hookFields = ["setupHook", "authHook", "teardownHook"];
1213
+ for (const hookField of hookFields) {
1214
+ const hook = execution[hookField];
1215
+ if (hook === void 0) continue;
1216
+ if (typeof hook === "string") continue;
1217
+ if (typeof hook !== "object" || hook === null) {
1218
+ errors.push({
1219
+ module: "config",
1220
+ field: `execution.${hookField}`,
1221
+ message: `${hookField} must be a string command or an object { command, args?, cwd? }`,
1222
+ severity: "error"
1223
+ });
1224
+ continue;
1225
+ }
1226
+ const hookObj = hook;
1227
+ if (typeof hookObj.command !== "string" || hookObj.command.trim() === "") {
1228
+ errors.push({
1229
+ module: "config",
1230
+ field: `execution.${hookField}.command`,
1231
+ message: "command is required and must be a non-empty string",
1232
+ severity: "error"
1233
+ });
1234
+ }
1235
+ if (hookObj.args !== void 0 && (!Array.isArray(hookObj.args) || hookObj.args.some((a) => typeof a !== "string"))) {
1236
+ errors.push({
1237
+ module: "config",
1238
+ field: `execution.${hookField}.args`,
1239
+ message: "args must be an array of strings",
1240
+ severity: "error"
1241
+ });
1242
+ }
1243
+ if (hookObj.cwd !== void 0 && typeof hookObj.cwd !== "string") {
1244
+ errors.push({
1245
+ module: "config",
1246
+ field: `execution.${hookField}.cwd`,
1247
+ message: "cwd must be a string path",
1248
+ severity: "error"
1249
+ });
1250
+ }
1251
+ }
1252
+ }
1185
1253
  return errors;
1186
1254
  }
1187
1255
  var REQUIRED_FIELDS, VALID_ADAPTERS, VALID_STEPS, VALID_LLM_PROVIDERS, VALID_REPORT_FORMATS, VALID_HEAL_MODES;
@@ -1189,8 +1257,11 @@ var init_config_validator = __esm({
1189
1257
  "src/validators/config-validator.ts"() {
1190
1258
  "use strict";
1191
1259
  init_esm_shims();
1260
+ init_schema_validator();
1261
+ init_semantic_validator();
1262
+ init_dryrun_validator();
1192
1263
  REQUIRED_FIELDS = ["backendRoot"];
1193
- VALID_ADAPTERS = ["sequelize", "typeorm", "prisma"];
1264
+ VALID_ADAPTERS = ["sequelize", "typeorm", "prisma", "drizzle"];
1194
1265
  VALID_STEPS = ["scan", "er-diagram", "api-chain", "plan", "codegen", "validate"];
1195
1266
  VALID_LLM_PROVIDERS = ["openai", "zhipu", "ollama", "custom"];
1196
1267
  VALID_REPORT_FORMATS = ["html", "json", "markdown"];
@@ -1446,6 +1517,39 @@ import chalk3 from "chalk";
1446
1517
  import { readdirSync as readdirSync5, existsSync as existsSync7 } from "fs";
1447
1518
  import { join as join6, resolve as resolve5 } from "path";
1448
1519
  import { execFileSync } from "child_process";
1520
+ function normalizeHook(hook) {
1521
+ if (!hook) return void 0;
1522
+ if (typeof hook === "string") return hook.trim() ? hook : void 0;
1523
+ return hook.command.trim() ? hook : void 0;
1524
+ }
1525
+ function runShellCommand(command) {
1526
+ if (process.platform === "win32") {
1527
+ execFileSync("cmd.exe", ["/d", "/s", "/c", command], {
1528
+ stdio: "inherit",
1529
+ cwd: process.cwd()
1530
+ });
1531
+ return;
1532
+ }
1533
+ execFileSync("sh", ["-lc", command], {
1534
+ stdio: "inherit",
1535
+ cwd: process.cwd()
1536
+ });
1537
+ }
1538
+ function runHook(name, hook) {
1539
+ const normalized = normalizeHook(hook);
1540
+ if (!normalized) return;
1541
+ console.log(chalk3.cyan(` Running ${name} hook...`));
1542
+ if (typeof normalized === "string") {
1543
+ runShellCommand(normalized);
1544
+ console.log(chalk3.green(` \u2713 ${name} hook passed`));
1545
+ return;
1546
+ }
1547
+ execFileSync(normalized.command, normalized.args ?? [], {
1548
+ stdio: "inherit",
1549
+ cwd: normalized.cwd ? resolve5(normalized.cwd) : process.cwd()
1550
+ });
1551
+ console.log(chalk3.green(` \u2713 ${name} hook passed`));
1552
+ }
1449
1553
  function discoverTestFiles(outDir, moduleFilter) {
1450
1554
  const absDir = resolve5(outDir);
1451
1555
  if (!existsSync7(absDir)) return [];
@@ -1464,6 +1568,25 @@ async function runTests(opts) {
1464
1568
  console.log(chalk3.cyan.bold("\n \u{1F40A} OpenCroc \u2014 Run E2E Tests\n"));
1465
1569
  const { config, filepath } = await loadConfig();
1466
1570
  console.log(chalk3.gray(` Config: ${filepath}`));
1571
+ const execution = {
1572
+ ...config.execution || {},
1573
+ ...opts.setupHook ? { setupHook: opts.setupHook } : {},
1574
+ ...opts.authHook ? { authHook: opts.authHook } : {},
1575
+ ...opts.teardownHook ? { teardownHook: opts.teardownHook } : {}
1576
+ };
1577
+ try {
1578
+ runHook("setup", execution.setupHook);
1579
+ runHook("auth", execution.authHook);
1580
+ } catch {
1581
+ console.log(chalk3.red(" \u2717 setup/auth hook failed. Abort test run.\n"));
1582
+ process.exitCode = 1;
1583
+ try {
1584
+ runHook("teardown", execution.teardownHook);
1585
+ } catch {
1586
+ console.log(chalk3.red(" \u2717 teardown hook also failed.\n"));
1587
+ }
1588
+ return;
1589
+ }
1467
1590
  const outDir = config.outDir || "./opencroc-output";
1468
1591
  const testFiles = discoverTestFiles(outDir, opts.module);
1469
1592
  if (testFiles.length === 0) {
@@ -1492,6 +1615,13 @@ async function runTests(opts) {
1492
1615
  } catch {
1493
1616
  console.log(chalk3.red("\n \u2717 Some tests failed.\n"));
1494
1617
  process.exitCode = 1;
1618
+ } finally {
1619
+ try {
1620
+ runHook("teardown", execution.teardownHook);
1621
+ } catch {
1622
+ console.log(chalk3.red(" \u2717 teardown hook failed.\n"));
1623
+ process.exitCode = 1;
1624
+ }
1495
1625
  }
1496
1626
  }
1497
1627
  var init_test = __esm({
@@ -1585,6 +1715,32 @@ var init_llm = __esm({
1585
1715
  }
1586
1716
  });
1587
1717
 
1718
+ // src/self-healing/dialog-loop-runner.ts
1719
+ var init_dialog_loop_runner = __esm({
1720
+ "src/self-healing/dialog-loop-runner.ts"() {
1721
+ "use strict";
1722
+ init_esm_shims();
1723
+ }
1724
+ });
1725
+
1726
+ // src/self-healing/controlled-fixer.ts
1727
+ import { existsSync as existsSync8, copyFileSync, readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync } from "fs";
1728
+ import { dirname as dirname2 } from "path";
1729
+ var init_controlled_fixer = __esm({
1730
+ "src/self-healing/controlled-fixer.ts"() {
1731
+ "use strict";
1732
+ init_esm_shims();
1733
+ }
1734
+ });
1735
+
1736
+ // src/self-healing/auto-fix-generator.ts
1737
+ var init_auto_fix_generator = __esm({
1738
+ "src/self-healing/auto-fix-generator.ts"() {
1739
+ "use strict";
1740
+ init_esm_shims();
1741
+ }
1742
+ });
1743
+
1588
1744
  // src/self-healing/index.ts
1589
1745
  async function attemptConfigFix(_testResultsDir, _mode, _llm) {
1590
1746
  return {
@@ -1630,6 +1786,9 @@ var init_self_healing = __esm({
1630
1786
  "use strict";
1631
1787
  init_esm_shims();
1632
1788
  init_llm();
1789
+ init_dialog_loop_runner();
1790
+ init_controlled_fixer();
1791
+ init_auto_fix_generator();
1633
1792
  }
1634
1793
  });
1635
1794
 
@@ -1859,6 +2018,30 @@ var init_ci2 = __esm({
1859
2018
  }
1860
2019
  });
1861
2020
 
2021
+ // src/reporters/checklist-reporter.ts
2022
+ var init_checklist_reporter = __esm({
2023
+ "src/reporters/checklist-reporter.ts"() {
2024
+ "use strict";
2025
+ init_esm_shims();
2026
+ }
2027
+ });
2028
+
2029
+ // src/reporters/workorder-reporter.ts
2030
+ var init_workorder_reporter = __esm({
2031
+ "src/reporters/workorder-reporter.ts"() {
2032
+ "use strict";
2033
+ init_esm_shims();
2034
+ }
2035
+ });
2036
+
2037
+ // src/reporters/token-reporter.ts
2038
+ var init_token_reporter = __esm({
2039
+ "src/reporters/token-reporter.ts"() {
2040
+ "use strict";
2041
+ init_esm_shims();
2042
+ }
2043
+ });
2044
+
1862
2045
  // src/reporters/index.ts
1863
2046
  function generateJsonReport(result) {
1864
2047
  const serializable = {
@@ -2075,6 +2258,9 @@ var init_reporters = __esm({
2075
2258
  "src/reporters/index.ts"() {
2076
2259
  "use strict";
2077
2260
  init_esm_shims();
2261
+ init_checklist_reporter();
2262
+ init_workorder_reporter();
2263
+ init_token_reporter();
2078
2264
  REPORTERS = {
2079
2265
  html: generateHtmlReport,
2080
2266
  json: generateJsonReport,
@@ -2485,11 +2671,1215 @@ var init_dashboard2 = __esm({
2485
2671
  }
2486
2672
  });
2487
2673
 
2674
+ // src/runtime/playwright-config-generator.ts
2675
+ function generatePlaywrightConfig(config) {
2676
+ const pw = config.playwright ?? {};
2677
+ const rt = config.runtime ?? {};
2678
+ const outDir = config.outDir || "./opencroc-output";
2679
+ const baseURL = pw.baseURL ? `'${pw.baseURL}'` : "process.env.BASE_URL || 'http://localhost:3000'";
2680
+ const timeout = pw.timeout ?? 3e4;
2681
+ const workers = pw.workers ?? null;
2682
+ const retries = pw.retries ?? null;
2683
+ const actionTimeout = pw.actionTimeout ?? 1e4;
2684
+ const navigationTimeout = pw.navigationTimeout ?? timeout;
2685
+ const storageState = rt.auth?.storageStatePath || "playwright/.auth/user.json";
2686
+ const hasAuth = !!rt.auth?.loginUrl;
2687
+ const lines = [];
2688
+ lines.push(`import { defineConfig, devices } from '@playwright/test';`);
2689
+ lines.push("");
2690
+ lines.push("export default defineConfig({");
2691
+ lines.push(` testDir: '${outDir}',`);
2692
+ lines.push(` testMatch: ['**/*.spec.ts', '**/*.test.ts'],`);
2693
+ lines.push(` fullyParallel: false,`);
2694
+ lines.push(` forbidOnly: !!process.env.CI,`);
2695
+ lines.push(` retries: ${retries !== null ? retries : "process.env.CI ? 1 : 0"},`);
2696
+ lines.push(` workers: ${workers !== null ? workers : "process.env.CI ? 4 : 2"},`);
2697
+ lines.push(` timeout: ${timeout},`);
2698
+ lines.push(` globalSetup: './global-setup.ts',`);
2699
+ lines.push(` globalTeardown: './global-teardown.ts',`);
2700
+ lines.push(` reporter: [['list'], ['html', { open: 'never' }]],`);
2701
+ lines.push(` use: {`);
2702
+ lines.push(` baseURL: ${baseURL},`);
2703
+ lines.push(` trace: 'retain-on-failure',`);
2704
+ lines.push(` screenshot: 'only-on-failure',`);
2705
+ lines.push(` video: 'retain-on-failure',`);
2706
+ lines.push(` actionTimeout: ${actionTimeout},`);
2707
+ lines.push(` navigationTimeout: ${navigationTimeout},`);
2708
+ lines.push(` },`);
2709
+ lines.push(` projects: [`);
2710
+ if (hasAuth) {
2711
+ lines.push(` {`);
2712
+ lines.push(` name: 'setup',`);
2713
+ lines.push(` testMatch: '**/auth.setup.ts',`);
2714
+ lines.push(` use: { ...devices['Desktop Chrome'] },`);
2715
+ lines.push(` },`);
2716
+ lines.push(` {`);
2717
+ lines.push(` name: 'chromium',`);
2718
+ lines.push(` testIgnore: ['**/*.setup.ts'],`);
2719
+ lines.push(` dependencies: ['setup'],`);
2720
+ lines.push(` use: {`);
2721
+ lines.push(` ...devices['Desktop Chrome'],`);
2722
+ lines.push(` storageState: '${storageState}',`);
2723
+ lines.push(` },`);
2724
+ lines.push(` },`);
2725
+ lines.push(` {`);
2726
+ lines.push(` name: 'chromium-no-auth',`);
2727
+ lines.push(` testMatch: '**/login-flow.test.ts',`);
2728
+ lines.push(` testIgnore: ['**/*.setup.ts'],`);
2729
+ lines.push(` use: { ...devices['Desktop Chrome'] },`);
2730
+ lines.push(` },`);
2731
+ } else {
2732
+ lines.push(` {`);
2733
+ lines.push(` name: 'chromium',`);
2734
+ lines.push(` use: { ...devices['Desktop Chrome'] },`);
2735
+ lines.push(` },`);
2736
+ }
2737
+ if (rt.extraProjects) {
2738
+ for (const proj of rt.extraProjects) {
2739
+ lines.push(` {`);
2740
+ lines.push(` name: '${proj.name}',`);
2741
+ lines.push(` testMatch: '${proj.testMatch}',`);
2742
+ if (proj.dependencies?.length) {
2743
+ lines.push(` dependencies: [${proj.dependencies.map((d) => `'${d}'`).join(", ")}],`);
2744
+ }
2745
+ if (proj.useAuth !== false && hasAuth) {
2746
+ lines.push(` use: {`);
2747
+ lines.push(` ...devices['Desktop Chrome'],`);
2748
+ lines.push(` storageState: '${storageState}',`);
2749
+ lines.push(` },`);
2750
+ } else {
2751
+ lines.push(` use: { ...devices['Desktop Chrome'] },`);
2752
+ }
2753
+ lines.push(` },`);
2754
+ }
2755
+ }
2756
+ lines.push(` ],`);
2757
+ lines.push("});");
2758
+ lines.push("");
2759
+ return lines.join("\n");
2760
+ }
2761
+ var init_playwright_config_generator = __esm({
2762
+ "src/runtime/playwright-config-generator.ts"() {
2763
+ "use strict";
2764
+ init_esm_shims();
2765
+ }
2766
+ });
2767
+
2768
+ // src/runtime/global-setup-generator.ts
2769
+ function generateGlobalSetup(config) {
2770
+ const pw = config.playwright ?? {};
2771
+ const rt = config.runtime ?? {};
2772
+ const baseURL = pw.baseURL || "";
2773
+ const hasSeedEndpoint = !!rt.db?.seedEndpoint;
2774
+ const hasLogEndpoint = !!rt.logEndpoint;
2775
+ const hasSeedFile = !!rt.db?.seedFile;
2776
+ const lines = [];
2777
+ lines.push(`/**`);
2778
+ lines.push(` * Global setup \u2014 generated by OpenCroc.`);
2779
+ lines.push(` * Runs once before all test projects.`);
2780
+ lines.push(` */`);
2781
+ lines.push("");
2782
+ lines.push(`const BASE_URL = process.env.BASE_URL || '${baseURL || "http://localhost:3000"}';`);
2783
+ lines.push("");
2784
+ lines.push(`async function fetchWithRetry(`);
2785
+ lines.push(` url: string,`);
2786
+ lines.push(` options: RequestInit,`);
2787
+ lines.push(` retries = 3,`);
2788
+ lines.push(`): Promise<Response | null> {`);
2789
+ lines.push(` for (let i = 0; i < retries; i++) {`);
2790
+ lines.push(` try {`);
2791
+ lines.push(` const resp = await fetch(url, { ...options, signal: AbortSignal.timeout(10_000) });`);
2792
+ lines.push(` if (resp.ok) return resp;`);
2793
+ lines.push(` console.warn(\`[global-setup] \${options.method} \${url} \u2192 \${resp.status} (\${i + 1}/\${retries})\`);`);
2794
+ lines.push(` } catch (err: unknown) {`);
2795
+ lines.push(` const msg = err instanceof Error ? err.message : String(err);`);
2796
+ lines.push(` console.warn(\`[global-setup] \${options.method} \${url} error (\${i + 1}/\${retries}): \${msg}\`);`);
2797
+ lines.push(` }`);
2798
+ lines.push(` if (i < retries - 1) await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, i)));`);
2799
+ lines.push(` }`);
2800
+ lines.push(` return null;`);
2801
+ lines.push(`}`);
2802
+ lines.push("");
2803
+ lines.push(`async function waitForBackend(origin: string, timeoutMs = 30_000): Promise<void> {`);
2804
+ lines.push(` const start = Date.now();`);
2805
+ lines.push(` while (Date.now() - start < timeoutMs) {`);
2806
+ lines.push(` try {`);
2807
+ lines.push(` const res = await fetch(\`\${origin}/health\`, { signal: AbortSignal.timeout(3_000) });`);
2808
+ lines.push(` if (res.ok) return;`);
2809
+ lines.push(` } catch {`);
2810
+ lines.push(` // not ready yet`);
2811
+ lines.push(` }`);
2812
+ lines.push(` await new Promise((r) => setTimeout(r, 1_000));`);
2813
+ lines.push(` }`);
2814
+ lines.push(` throw new Error(\`Backend not ready after \${timeoutMs}ms\`);`);
2815
+ lines.push(`}`);
2816
+ lines.push("");
2817
+ lines.push(`export default async function globalSetup(): Promise<void> {`);
2818
+ lines.push(` console.log('[global-setup] Starting...');`);
2819
+ lines.push("");
2820
+ lines.push(` // Backend readiness probe`);
2821
+ lines.push(` try {`);
2822
+ lines.push(` await waitForBackend(BASE_URL, 30_000);`);
2823
+ lines.push(` console.log('[global-setup] Backend is ready.');`);
2824
+ lines.push(` } catch (e: unknown) {`);
2825
+ lines.push(` console.warn(\`[global-setup] \${e instanceof Error ? e.message : String(e)}\`);`);
2826
+ lines.push(` }`);
2827
+ lines.push("");
2828
+ if (hasSeedEndpoint) {
2829
+ lines.push(` // Seed data via backend endpoint`);
2830
+ lines.push(` try {`);
2831
+ lines.push(` await fetchWithRetry(`);
2832
+ lines.push(` \`\${BASE_URL}${rt.db.seedEndpoint}\`,`);
2833
+ lines.push(` { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' },`);
2834
+ lines.push(` );`);
2835
+ lines.push(` console.log('[global-setup] Seed endpoint called.');`);
2836
+ lines.push(` } catch {`);
2837
+ lines.push(` console.warn('[global-setup] Seed endpoint failed.');`);
2838
+ lines.push(` }`);
2839
+ lines.push("");
2840
+ }
2841
+ if (hasLogEndpoint) {
2842
+ lines.push(` // Clear test logs`);
2843
+ lines.push(` try {`);
2844
+ lines.push(` await fetchWithRetry(\`\${BASE_URL}${rt.logEndpoint}\`, { method: 'DELETE' });`);
2845
+ lines.push(` console.log('[global-setup] Test logs cleared.');`);
2846
+ lines.push(` } catch {`);
2847
+ lines.push(` console.warn('[global-setup] Failed to clear test logs.');`);
2848
+ lines.push(` }`);
2849
+ lines.push("");
2850
+ }
2851
+ if (hasSeedFile) {
2852
+ lines.push(` // SQL seed file execution (placeholder \u2014 wire to your DB helper)`);
2853
+ lines.push(` // import { executeSeedSQL } from './utils/db-helper';`);
2854
+ lines.push(` // await executeSeedSQL('${rt.db.seedFile}');`);
2855
+ lines.push("");
2856
+ }
2857
+ lines.push(` console.log('[global-setup] Done.');`);
2858
+ lines.push(`}`);
2859
+ lines.push("");
2860
+ return lines.join("\n");
2861
+ }
2862
+ var init_global_setup_generator = __esm({
2863
+ "src/runtime/global-setup-generator.ts"() {
2864
+ "use strict";
2865
+ init_esm_shims();
2866
+ }
2867
+ });
2868
+
2869
+ // src/runtime/global-teardown-generator.ts
2870
+ function generateGlobalTeardown(config) {
2871
+ const pw = config.playwright ?? {};
2872
+ const rt = config.runtime ?? {};
2873
+ const baseURL = pw.baseURL || "";
2874
+ const hasCleanupEndpoint = !!rt.db?.cleanupEndpoint;
2875
+ const hasCleanupFile = !!rt.db?.cleanupFile;
2876
+ const lines = [];
2877
+ lines.push(`/**`);
2878
+ lines.push(` * Global teardown \u2014 generated by OpenCroc.`);
2879
+ lines.push(` * Runs once after all test projects complete.`);
2880
+ lines.push(` */`);
2881
+ lines.push("");
2882
+ lines.push(`const BASE_URL = process.env.BASE_URL || '${baseURL || "http://localhost:3000"}';`);
2883
+ lines.push("");
2884
+ lines.push(`export default async function globalTeardown(): Promise<void> {`);
2885
+ lines.push(` console.log('[global-teardown] Starting...');`);
2886
+ lines.push("");
2887
+ if (hasCleanupEndpoint) {
2888
+ lines.push(` // Cleanup via backend endpoint`);
2889
+ lines.push(` try {`);
2890
+ lines.push(` await fetch(\`\${BASE_URL}${rt.db.cleanupEndpoint}\`, { method: 'POST' });`);
2891
+ lines.push(` console.log('[global-teardown] Cleanup endpoint called.');`);
2892
+ lines.push(` } catch {`);
2893
+ lines.push(` console.warn('[global-teardown] Cleanup endpoint failed.');`);
2894
+ lines.push(` }`);
2895
+ lines.push("");
2896
+ }
2897
+ if (hasCleanupFile) {
2898
+ lines.push(` // SQL cleanup file execution (placeholder \u2014 wire to your DB helper)`);
2899
+ lines.push(` // import { executeSeedSQL } from './utils/db-helper';`);
2900
+ lines.push(` // await executeSeedSQL('${rt.db.cleanupFile}');`);
2901
+ lines.push("");
2902
+ }
2903
+ lines.push(` console.log('[global-teardown] Done.');`);
2904
+ lines.push(`}`);
2905
+ lines.push("");
2906
+ return lines.join("\n");
2907
+ }
2908
+ var init_global_teardown_generator = __esm({
2909
+ "src/runtime/global-teardown-generator.ts"() {
2910
+ "use strict";
2911
+ init_esm_shims();
2912
+ }
2913
+ });
2914
+
2915
+ // src/runtime/auth-setup-generator.ts
2916
+ function generateAuthSetup(config) {
2917
+ const rt = config.runtime ?? {};
2918
+ const pw = config.playwright ?? {};
2919
+ const auth = rt.auth ?? {};
2920
+ const baseURL = pw.baseURL || "";
2921
+ const storageStatePath = auth.storageStatePath || "playwright/.auth/user.json";
2922
+ const lines = [];
2923
+ lines.push(`import { test as setup, expect } from '@playwright/test';`);
2924
+ lines.push(`import * as fs from 'fs/promises';`);
2925
+ lines.push(`import * as path from 'path';`);
2926
+ lines.push("");
2927
+ lines.push(`const authFile = '${storageStatePath}';`);
2928
+ lines.push("");
2929
+ if (auth.loginUrl) {
2930
+ lines.push(`setup('authenticate', async ({ request }) => {`);
2931
+ lines.push(` const loginUrl = process.env.AUTH_LOGIN_URL || '${auth.loginUrl}';`);
2932
+ lines.push(` const username = process.env.AUTH_USERNAME || '${auth.username || "admin"}';`);
2933
+ lines.push(` const password = process.env.AUTH_PASSWORD || '${auth.password || ""}';`);
2934
+ lines.push("");
2935
+ lines.push(` // API login`);
2936
+ lines.push(` const response = await request.post(loginUrl, {`);
2937
+ lines.push(` data: { username, password },`);
2938
+ lines.push(` });`);
2939
+ lines.push(` expect(response.ok()).toBeTruthy();`);
2940
+ lines.push("");
2941
+ lines.push(` const body = await response.json();`);
2942
+ lines.push(` const token = body.data?.token || body.token || '';`);
2943
+ lines.push(` console.log('[auth.setup] Login successful, token obtained:', !!token);`);
2944
+ lines.push("");
2945
+ lines.push(` // Save storage state with auth cookie/localStorage`);
2946
+ lines.push(` await fs.mkdir(path.dirname(authFile), { recursive: true });`);
2947
+ lines.push("");
2948
+ lines.push(` // Build storage state JSON`);
2949
+ lines.push(` const baseURL = process.env.BASE_URL || '${baseURL || "http://localhost:3000"}';`);
2950
+ lines.push(` const origin = new URL(baseURL).origin;`);
2951
+ lines.push(` const storageState = {`);
2952
+ lines.push(` cookies: [],`);
2953
+ lines.push(` origins: [`);
2954
+ lines.push(` {`);
2955
+ lines.push(` origin,`);
2956
+ lines.push(` localStorage: [`);
2957
+ lines.push(` { name: 'token', value: token },`);
2958
+ lines.push(` ],`);
2959
+ lines.push(` },`);
2960
+ lines.push(` ],`);
2961
+ lines.push(` };`);
2962
+ lines.push("");
2963
+ lines.push(` await fs.writeFile(authFile, JSON.stringify(storageState, null, 2));`);
2964
+ lines.push(` console.log(\`[auth.setup] Storage state saved \u2192 \${authFile}\`);`);
2965
+ lines.push(`});`);
2966
+ } else {
2967
+ lines.push(`setup('authenticate', async ({ browser }) => {`);
2968
+ lines.push(` const context = await browser.newContext();`);
2969
+ lines.push(` const page = await context.newPage();`);
2970
+ lines.push("");
2971
+ lines.push(` // TODO: Implement your login flow here`);
2972
+ lines.push(` // await page.goto('/login');`);
2973
+ lines.push(` // await page.fill('[name=username]', 'admin');`);
2974
+ lines.push(` // await page.fill('[name=password]', 'password');`);
2975
+ lines.push(` // await page.click('button[type=submit]');`);
2976
+ lines.push(` // await page.waitForURL('/dashboard');`);
2977
+ lines.push("");
2978
+ lines.push(` await fs.mkdir(path.dirname(authFile), { recursive: true });`);
2979
+ lines.push(` await context.storageState({ path: authFile });`);
2980
+ lines.push(` console.log(\`[auth.setup] Storage state saved \u2192 \${authFile}\`);`);
2981
+ lines.push("");
2982
+ lines.push(` await context.close();`);
2983
+ lines.push(`});`);
2984
+ }
2985
+ lines.push("");
2986
+ return lines.join("\n");
2987
+ }
2988
+ var init_auth_setup_generator = __esm({
2989
+ "src/runtime/auth-setup-generator.ts"() {
2990
+ "use strict";
2991
+ init_esm_shims();
2992
+ }
2993
+ });
2994
+
2995
+ // src/cli/commands/init-runtime.ts
2996
+ var init_runtime_exports = {};
2997
+ __export(init_runtime_exports, {
2998
+ initRuntime: () => initRuntime
2999
+ });
3000
+ import chalk9 from "chalk";
3001
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
3002
+ import { join as join10, resolve as resolve7 } from "path";
3003
+ function writeIfNotExists(filePath, content, force) {
3004
+ if (existsSync12(filePath) && !force) {
3005
+ console.log(chalk9.yellow(` \u2298 ${filePath} already exists (use --force to overwrite)`));
3006
+ return false;
3007
+ }
3008
+ const dir = resolve7(filePath, "..");
3009
+ mkdirSync7(dir, { recursive: true });
3010
+ writeFileSync7(filePath, content, "utf-8");
3011
+ console.log(chalk9.green(` \u2713 ${filePath}`));
3012
+ return true;
3013
+ }
3014
+ async function initRuntime(opts) {
3015
+ console.log(chalk9.cyan.bold("\n \u{1F40A} OpenCroc \u2014 Initialize Playwright Runtime\n"));
3016
+ const { config, filepath } = await loadConfig();
3017
+ console.log(chalk9.gray(` Config: ${filepath}`));
3018
+ const outDir = resolve7(opts.output || ".");
3019
+ const force = opts.force ?? false;
3020
+ const hasAuth = !!config.runtime?.auth?.loginUrl;
3021
+ let written = 0;
3022
+ console.log(chalk9.cyan("\n Generating runtime files...\n"));
3023
+ if (writeIfNotExists(join10(outDir, "playwright.config.ts"), generatePlaywrightConfig(config), force)) written++;
3024
+ if (writeIfNotExists(join10(outDir, "global-setup.ts"), generateGlobalSetup(config), force)) written++;
3025
+ if (writeIfNotExists(join10(outDir, "global-teardown.ts"), generateGlobalTeardown(config), force)) written++;
3026
+ if (hasAuth) {
3027
+ if (writeIfNotExists(join10(outDir, "auth.setup.ts"), generateAuthSetup(config), force)) written++;
3028
+ }
3029
+ console.log("");
3030
+ if (written > 0) {
3031
+ console.log(chalk9.green(` \u2713 Generated ${written} runtime file(s) in ${outDir}
3032
+ `));
3033
+ } else {
3034
+ console.log(chalk9.yellow(` No files written. Use --force to overwrite existing files.
3035
+ `));
3036
+ }
3037
+ }
3038
+ var init_init_runtime = __esm({
3039
+ "src/cli/commands/init-runtime.ts"() {
3040
+ "use strict";
3041
+ init_esm_shims();
3042
+ init_load_config();
3043
+ init_playwright_config_generator();
3044
+ init_global_setup_generator();
3045
+ init_global_teardown_generator();
3046
+ init_auth_setup_generator();
3047
+ }
3048
+ });
3049
+
3050
+ // src/orchestrator/index.ts
3051
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, existsSync as existsSync13, readdirSync as readdirSync6 } from "fs";
3052
+ import { dirname as dirname4, join as join11 } from "path";
3053
+ import { execFileSync as execFileSync2 } from "child_process";
3054
+ function discoverTestFiles2(outDir, moduleFilter) {
3055
+ const absDir = join11(process.cwd(), outDir);
3056
+ if (!existsSync13(absDir)) return [];
3057
+ const files = [];
3058
+ const entries = readdirSync6(absDir, { withFileTypes: true, recursive: true });
3059
+ for (const entry of entries) {
3060
+ if (!entry.isFile()) continue;
3061
+ if (!entry.name.endsWith(".spec.ts") && !entry.name.endsWith(".test.ts")) continue;
3062
+ const fullPath = join11(
3063
+ entry.parentPath || entry.path || absDir,
3064
+ entry.name
3065
+ );
3066
+ if (moduleFilter && !fullPath.includes(moduleFilter)) continue;
3067
+ files.push(fullPath);
3068
+ }
3069
+ return files;
3070
+ }
3071
+ function parsePlaywrightOutput(stderr) {
3072
+ const metrics = { passed: 0, failed: 0, skipped: 0, timedOut: 0 };
3073
+ const passedMatch = stderr.match(/(\d+)\s+passed/);
3074
+ const failedMatch = stderr.match(/(\d+)\s+failed/);
3075
+ const skippedMatch = stderr.match(/(\d+)\s+skipped/);
3076
+ const timedOutMatch = stderr.match(/(\d+)\s+timed?\s*out/i);
3077
+ if (passedMatch) metrics.passed = parseInt(passedMatch[1], 10);
3078
+ if (failedMatch) metrics.failed = parseInt(failedMatch[1], 10);
3079
+ if (skippedMatch) metrics.skipped = parseInt(skippedMatch[1], 10);
3080
+ if (timedOutMatch) metrics.timedOut = parseInt(timedOutMatch[1], 10);
3081
+ return metrics;
3082
+ }
3083
+ function createOrchestrator(config, options = {}) {
3084
+ const {
3085
+ phases = ALL_PHASES,
3086
+ selfHeal = false,
3087
+ maxHealIterations = 3,
3088
+ reportFormats = ["html", "json"],
3089
+ headed = false,
3090
+ module: moduleFilter,
3091
+ tokenBudget = 0,
3092
+ abortOnError = false
3093
+ } = options;
3094
+ const outDir = config.outDir || "./opencroc-output";
3095
+ const phaseResults = [];
3096
+ let pipelineResult;
3097
+ let executionMetrics;
3098
+ let healingResult;
3099
+ let reports;
3100
+ let tokensUsed = 0;
3101
+ function isBudgetExceeded() {
3102
+ return tokenBudget > 0 && tokensUsed >= tokenBudget;
3103
+ }
3104
+ function shouldRun(phase) {
3105
+ if (phase === "heal" && !selfHeal) return false;
3106
+ return phases.includes(phase);
3107
+ }
3108
+ async function runPhase(name, fn) {
3109
+ const start = Date.now();
3110
+ try {
3111
+ const output = await fn();
3112
+ const result = {
3113
+ phase: name,
3114
+ status: "success",
3115
+ output,
3116
+ durationMs: Date.now() - start
3117
+ };
3118
+ phaseResults.push(result);
3119
+ return result;
3120
+ } catch (err) {
3121
+ const result = {
3122
+ phase: name,
3123
+ status: "error",
3124
+ error: err instanceof Error ? err.message : String(err),
3125
+ durationMs: Date.now() - start
3126
+ };
3127
+ phaseResults.push(result);
3128
+ return result;
3129
+ }
3130
+ }
3131
+ function skipPhase(name, reason) {
3132
+ const result = {
3133
+ phase: name,
3134
+ status: "skipped",
3135
+ error: reason,
3136
+ durationMs: 0
3137
+ };
3138
+ phaseResults.push(result);
3139
+ return result;
3140
+ }
3141
+ return {
3142
+ async run() {
3143
+ const orchestrationStart = Date.now();
3144
+ if (shouldRun("generate")) {
3145
+ const genResult = await runPhase("generate", async () => {
3146
+ if (moduleFilter) config.modules = [moduleFilter];
3147
+ const pipeline = createPipeline(config);
3148
+ pipelineResult = await pipeline.run();
3149
+ for (const file of pipelineResult.generatedFiles) {
3150
+ const dir = dirname4(file.filePath);
3151
+ if (!existsSync13(dir)) mkdirSync8(dir, { recursive: true });
3152
+ writeFileSync8(file.filePath, file.content, "utf-8");
3153
+ }
3154
+ return pipelineResult;
3155
+ });
3156
+ if (genResult.status === "error" && abortOnError) {
3157
+ return buildSummary(orchestrationStart);
3158
+ }
3159
+ }
3160
+ if (shouldRun("execute")) {
3161
+ const testFiles = discoverTestFiles2(outDir, moduleFilter);
3162
+ if (testFiles.length === 0) {
3163
+ skipPhase("execute", "No test files found");
3164
+ } else {
3165
+ const execResult = await runPhase("execute", async () => {
3166
+ const args = ["test", ...testFiles];
3167
+ if (!headed) args.push("--reporter=list");
3168
+ else args.push("--headed");
3169
+ const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx";
3170
+ try {
3171
+ execFileSync2(npxCmd, ["playwright", ...args], {
3172
+ cwd: process.cwd(),
3173
+ stdio: ["ignore", "pipe", "pipe"],
3174
+ timeout: 3e5
3175
+ });
3176
+ return { passed: testFiles.length, failed: 0, skipped: 0, timedOut: 0 };
3177
+ } catch (err) {
3178
+ const stderr = err?.stderr?.toString() ?? "";
3179
+ const metrics = parsePlaywrightOutput(stderr);
3180
+ if (metrics.passed === 0 && metrics.failed === 0) {
3181
+ metrics.failed = testFiles.length;
3182
+ }
3183
+ executionMetrics = metrics;
3184
+ if (metrics.failed > 0) {
3185
+ throw new Error(`${metrics.failed} test(s) failed, ${metrics.passed} passed`, { cause: err });
3186
+ }
3187
+ return metrics;
3188
+ }
3189
+ });
3190
+ if (!executionMetrics && execResult.output) {
3191
+ executionMetrics = execResult.output;
3192
+ }
3193
+ if (execResult.status === "error" && abortOnError) {
3194
+ return buildSummary(orchestrationStart);
3195
+ }
3196
+ }
3197
+ }
3198
+ if (shouldRun("analyze")) {
3199
+ if (!pipelineResult || !executionMetrics || executionMetrics.failed === 0) {
3200
+ skipPhase("analyze", "No failures to analyze");
3201
+ } else {
3202
+ await runPhase("analyze", async () => {
3203
+ const pipeline = createPipeline(config);
3204
+ const validationResult = await pipeline.run(["validate"]);
3205
+ return {
3206
+ validationErrors: validationResult.validationErrors,
3207
+ failedTestCount: executionMetrics.failed
3208
+ };
3209
+ });
3210
+ }
3211
+ }
3212
+ if (shouldRun("heal")) {
3213
+ if (isBudgetExceeded()) {
3214
+ skipPhase("heal", "Token budget exceeded");
3215
+ } else if (!executionMetrics || executionMetrics.failed === 0) {
3216
+ skipPhase("heal", "No failures to heal");
3217
+ } else {
3218
+ const healResult = await runPhase("heal", async () => {
3219
+ const healConfig = {
3220
+ enabled: true,
3221
+ maxIterations: maxHealIterations,
3222
+ mode: config.selfHealing?.mode || "config-only"
3223
+ };
3224
+ const loop = createSelfHealingLoop(healConfig);
3225
+ const result = await loop.run(outDir);
3226
+ tokensUsed += result.totalTokensUsed;
3227
+ healingResult = result;
3228
+ return result;
3229
+ });
3230
+ if (healResult.status === "success" && healingResult && healingResult.remaining.length > 0) {
3231
+ healResult.status = "warn";
3232
+ phaseResults[phaseResults.length - 1] = healResult;
3233
+ }
3234
+ }
3235
+ }
3236
+ if (shouldRun("report")) {
3237
+ if (!pipelineResult) {
3238
+ skipPhase("report", "No pipeline results to report");
3239
+ } else {
3240
+ await runPhase("report", async () => {
3241
+ reports = generateReports(pipelineResult, reportFormats);
3242
+ if (!existsSync13(outDir)) mkdirSync8(outDir, { recursive: true });
3243
+ for (const r of reports) {
3244
+ writeFileSync8(join11(outDir, r.filename), r.content, "utf-8");
3245
+ }
3246
+ return reports;
3247
+ });
3248
+ }
3249
+ }
3250
+ return buildSummary(orchestrationStart);
3251
+ }
3252
+ };
3253
+ function buildSummary(startTime) {
3254
+ const errorCount = phaseResults.filter((p) => p.status === "error").length;
3255
+ const allSuccess = phaseResults.every((p) => p.status === "success" || p.status === "skipped");
3256
+ let overallStatus;
3257
+ if (allSuccess) overallStatus = "success";
3258
+ else if (errorCount > 1) overallStatus = "fatal-fail";
3259
+ else overallStatus = "partial-fail";
3260
+ let recommendation;
3261
+ if (executionMetrics && executionMetrics.failed > 0 && !selfHeal) {
3262
+ recommendation = "Test failures detected. Consider running with --self-heal to attempt automated fixes.";
3263
+ } else if (healingResult && healingResult.remaining.length > 0) {
3264
+ recommendation = `${healingResult.remaining.length} issue(s) could not be auto-fixed. Manual review needed.`;
3265
+ }
3266
+ return {
3267
+ overallStatus,
3268
+ phases: phaseResults,
3269
+ totalDurationMs: Date.now() - startTime,
3270
+ modules: pipelineResult?.modules ?? [],
3271
+ executionMetrics,
3272
+ reports,
3273
+ healingResult,
3274
+ recommendation
3275
+ };
3276
+ }
3277
+ }
3278
+ var ALL_PHASES;
3279
+ var init_orchestrator = __esm({
3280
+ "src/orchestrator/index.ts"() {
3281
+ "use strict";
3282
+ init_esm_shims();
3283
+ init_pipeline();
3284
+ init_self_healing();
3285
+ init_reporters();
3286
+ ALL_PHASES = ["generate", "execute", "analyze", "heal", "report"];
3287
+ }
3288
+ });
3289
+
3290
+ // src/orchestrator/reporter.ts
3291
+ import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync14 } from "fs";
3292
+ import { join as join12 } from "path";
3293
+ function writeOrchestrationSummary(summary, options) {
3294
+ const { outputDir, module: mod } = options;
3295
+ if (!existsSync14(outputDir)) mkdirSync9(outputDir, { recursive: true });
3296
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3297
+ const moduleName = mod ?? "all";
3298
+ const filename = `orchestration-${moduleName}-${timestamp}.json`;
3299
+ const filePath = join12(outputDir, filename);
3300
+ const serializable = {
3301
+ ...summary,
3302
+ phases: summary.phases.map((p) => ({
3303
+ phase: p.phase,
3304
+ status: p.status,
3305
+ error: p.error,
3306
+ durationMs: p.durationMs
3307
+ })),
3308
+ // Strip report content to avoid massive JSON
3309
+ reports: summary.reports?.map((r) => ({
3310
+ format: r.format,
3311
+ filename: r.filename
3312
+ }))
3313
+ };
3314
+ writeFileSync9(filePath, JSON.stringify(serializable, null, 2), "utf-8");
3315
+ return filePath;
3316
+ }
3317
+ function formatPhase(p) {
3318
+ const icons = {
3319
+ success: "\u2713",
3320
+ warn: "\u26A0",
3321
+ error: "\u2717",
3322
+ skipped: "\u25CB"
3323
+ };
3324
+ const icon = icons[p.status] ?? "?";
3325
+ const dur = p.durationMs > 0 ? ` (${p.durationMs}ms)` : "";
3326
+ const err = p.error ? ` \u2014 ${p.error}` : "";
3327
+ return ` ${icon} ${p.phase}${dur}${err}`;
3328
+ }
3329
+ function printOrchestrationSummary(summary) {
3330
+ const lines = [];
3331
+ lines.push("");
3332
+ lines.push(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
3333
+ lines.push(" Orchestration Summary");
3334
+ lines.push(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
3335
+ lines.push("");
3336
+ for (const p of summary.phases) {
3337
+ lines.push(formatPhase(p));
3338
+ }
3339
+ lines.push("");
3340
+ lines.push(` Overall : ${summary.overallStatus}`);
3341
+ lines.push(` Modules : ${summary.modules.join(", ") || "(none)"}`);
3342
+ lines.push(` Duration : ${summary.totalDurationMs}ms`);
3343
+ if (summary.executionMetrics) {
3344
+ const m = summary.executionMetrics;
3345
+ lines.push(` Tests : ${m.passed} passed, ${m.failed} failed, ${m.skipped} skipped`);
3346
+ }
3347
+ if (summary.healingResult) {
3348
+ const h = summary.healingResult;
3349
+ lines.push(` Healing : ${h.fixed.length} fixed, ${h.remaining.length} remaining (${h.iterations} iterations)`);
3350
+ }
3351
+ if (summary.reports) {
3352
+ lines.push(` Reports : ${summary.reports.map((r) => r.format).join(", ")}`);
3353
+ }
3354
+ if (summary.recommendation) {
3355
+ lines.push("");
3356
+ lines.push(` \u2192 ${summary.recommendation}`);
3357
+ }
3358
+ lines.push("");
3359
+ return lines;
3360
+ }
3361
+ var init_reporter = __esm({
3362
+ "src/orchestrator/reporter.ts"() {
3363
+ "use strict";
3364
+ init_esm_shims();
3365
+ }
3366
+ });
3367
+
3368
+ // src/cli/commands/run.ts
3369
+ var run_exports = {};
3370
+ __export(run_exports, {
3371
+ run: () => run
3372
+ });
3373
+ import chalk10 from "chalk";
3374
+ function parsePhases(raw) {
3375
+ if (!raw) return void 0;
3376
+ const names = raw.split(",").map((s) => s.trim());
3377
+ for (const name of names) {
3378
+ if (!VALID_PHASES.includes(name)) {
3379
+ console.log(chalk10.red(` Unknown phase "${name}". Valid: ${VALID_PHASES.join(", ")}`));
3380
+ process.exitCode = 1;
3381
+ return void 0;
3382
+ }
3383
+ }
3384
+ return names;
3385
+ }
3386
+ async function run(opts) {
3387
+ console.log(chalk10.cyan.bold("\n \u{1F40A} OpenCroc \u2014 Full Orchestration\n"));
3388
+ const { config, filepath } = await loadConfig();
3389
+ console.log(chalk10.gray(` Config: ${filepath}`));
3390
+ const phases = parsePhases(opts.phases);
3391
+ if (phases === void 0 && opts.phases) return;
3392
+ const reportFormats = (opts.report ?? "html,json").split(",").map((s) => s.trim());
3393
+ const orchestrator = createOrchestrator(config, {
3394
+ phases,
3395
+ selfHeal: opts.selfHeal ?? false,
3396
+ headed: opts.headed ?? false,
3397
+ module: opts.module,
3398
+ reportFormats,
3399
+ tokenBudget: opts.tokenBudget ? parseInt(opts.tokenBudget, 10) : 0,
3400
+ abortOnError: opts.abortOnError ?? false
3401
+ });
3402
+ console.log(chalk10.gray(` Phases: ${(phases ?? VALID_PHASES).join(" \u2192 ")}`));
3403
+ if (opts.selfHeal) console.log(chalk10.gray(" Self-heal: enabled"));
3404
+ console.log("");
3405
+ const summary = await orchestrator.run();
3406
+ const lines = printOrchestrationSummary(summary);
3407
+ for (const line of lines) {
3408
+ const color = summary.overallStatus === "success" ? chalk10.green : summary.overallStatus === "partial-fail" ? chalk10.yellow : chalk10.red;
3409
+ console.log(color(line));
3410
+ }
3411
+ const outDir = config.outDir || "./opencroc-output";
3412
+ const summaryPath = writeOrchestrationSummary(summary, { outputDir: outDir, module: opts.module });
3413
+ console.log(chalk10.gray(` Summary: ${summaryPath}
3414
+ `));
3415
+ if (summary.overallStatus !== "success") {
3416
+ process.exitCode = 1;
3417
+ }
3418
+ }
3419
+ var VALID_PHASES;
3420
+ var init_run = __esm({
3421
+ "src/cli/commands/run.ts"() {
3422
+ "use strict";
3423
+ init_esm_shims();
3424
+ init_load_config();
3425
+ init_orchestrator();
3426
+ init_reporter();
3427
+ VALID_PHASES = ["generate", "execute", "analyze", "heal", "report"];
3428
+ }
3429
+ });
3430
+
3431
+ // src/server/routes/project.ts
3432
+ function registerProjectRoutes(app, office) {
3433
+ app.get("/api/project", async () => {
3434
+ return office.getProjectInfo();
3435
+ });
3436
+ app.get("/api/project/graph", async () => {
3437
+ return office.buildKnowledgeGraph();
3438
+ });
3439
+ app.post("/api/project/refresh", async () => {
3440
+ office.invalidateCache();
3441
+ const graph = await office.buildKnowledgeGraph();
3442
+ office.broadcast("graph:update", graph);
3443
+ return { ok: true, nodes: graph.nodes.length, edges: graph.edges.length };
3444
+ });
3445
+ }
3446
+ var init_project = __esm({
3447
+ "src/server/routes/project.ts"() {
3448
+ "use strict";
3449
+ init_esm_shims();
3450
+ }
3451
+ });
3452
+
3453
+ // src/server/routes/agents.ts
3454
+ function registerAgentRoutes(app, office) {
3455
+ app.get("/api/agents", async () => {
3456
+ return office.getAgents();
3457
+ });
3458
+ app.get("/api/agents/:id", async (req, reply) => {
3459
+ const agent = office.getAgent(req.params.id);
3460
+ if (!agent) {
3461
+ reply.code(404).send({ error: "Agent not found" });
3462
+ return;
3463
+ }
3464
+ return agent;
3465
+ });
3466
+ app.post("/api/agents/:id/task", async (req, reply) => {
3467
+ const agent = office.getAgent(req.params.id);
3468
+ if (!agent) {
3469
+ reply.code(404).send({ error: "Agent not found" });
3470
+ return;
3471
+ }
3472
+ office.updateAgent(req.params.id, {
3473
+ status: "working",
3474
+ currentTask: req.body?.task || "Processing..."
3475
+ });
3476
+ setTimeout(() => {
3477
+ office.updateAgent(req.params.id, {
3478
+ status: "done",
3479
+ currentTask: "Task completed"
3480
+ });
3481
+ }, 2e3);
3482
+ return { ok: true, agent: req.params.id, task: req.body?.task };
3483
+ });
3484
+ }
3485
+ var init_agents = __esm({
3486
+ "src/server/routes/agents.ts"() {
3487
+ "use strict";
3488
+ init_esm_shims();
3489
+ }
3490
+ });
3491
+
3492
+ // src/server/croc-office.ts
3493
+ var DEFAULT_AGENTS, CrocOffice;
3494
+ var init_croc_office = __esm({
3495
+ "src/server/croc-office.ts"() {
3496
+ "use strict";
3497
+ init_esm_shims();
3498
+ DEFAULT_AGENTS = [
3499
+ { id: "parser-croc", name: "\u89E3\u6790\u9CC4", role: "parser", sprite: "parser", status: "idle", tokensUsed: 0 },
3500
+ { id: "analyzer-croc", name: "\u5206\u6790\u9CC4", role: "analyzer", sprite: "analyzer", status: "idle", tokensUsed: 0 },
3501
+ { id: "tester-croc", name: "\u6D4B\u8BD5\u9CC4", role: "tester", sprite: "tester", status: "idle", tokensUsed: 0 },
3502
+ { id: "healer-croc", name: "\u4FEE\u590D\u9CC4", role: "healer", sprite: "healer", status: "idle", tokensUsed: 0 },
3503
+ { id: "planner-croc", name: "\u89C4\u5212\u9CC4", role: "planner", sprite: "planner", status: "idle", tokensUsed: 0 },
3504
+ { id: "reporter-croc", name: "\u6C47\u62A5\u9CC4", role: "reporter", sprite: "reporter", status: "idle", tokensUsed: 0 }
3505
+ ];
3506
+ CrocOffice = class {
3507
+ config;
3508
+ cwd;
3509
+ clients = /* @__PURE__ */ new Set();
3510
+ agents;
3511
+ cachedGraph = null;
3512
+ constructor(config, cwd) {
3513
+ this.config = config;
3514
+ this.cwd = cwd;
3515
+ this.agents = DEFAULT_AGENTS.map((a) => ({ ...a }));
3516
+ }
3517
+ addClient(ws) {
3518
+ this.clients.add(ws);
3519
+ }
3520
+ removeClient(ws) {
3521
+ this.clients.delete(ws);
3522
+ }
3523
+ broadcast(type, payload) {
3524
+ const msg = JSON.stringify({ type, payload });
3525
+ for (const client of this.clients) {
3526
+ try {
3527
+ client.send(msg);
3528
+ } catch {
3529
+ this.clients.delete(client);
3530
+ }
3531
+ }
3532
+ }
3533
+ getAgents() {
3534
+ return this.agents;
3535
+ }
3536
+ getAgent(id) {
3537
+ return this.agents.find((a) => a.id === id);
3538
+ }
3539
+ updateAgent(id, update) {
3540
+ const agent = this.agents.find((a) => a.id === id);
3541
+ if (agent) {
3542
+ Object.assign(agent, update);
3543
+ this.broadcast("agent:update", this.agents);
3544
+ }
3545
+ }
3546
+ getConfig() {
3547
+ return this.config;
3548
+ }
3549
+ getCwd() {
3550
+ return this.cwd;
3551
+ }
3552
+ /** Build knowledge graph from project source code */
3553
+ async buildKnowledgeGraph() {
3554
+ if (this.cachedGraph) return this.cachedGraph;
3555
+ this.updateAgent("parser-croc", { status: "working", currentTask: "Scanning project structure..." });
3556
+ try {
3557
+ const { resolve: resolvePath } = await import("path");
3558
+ const { glob } = await import("glob");
3559
+ const backendRoot = resolvePath(this.cwd, this.config.backendRoot);
3560
+ const nodes = [];
3561
+ const edges = [];
3562
+ const moduleSet = /* @__PURE__ */ new Set();
3563
+ const modelFiles = await glob("**/models/**/*.{ts,js}", {
3564
+ cwd: backendRoot,
3565
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/index.*"]
3566
+ });
3567
+ for (const file of modelFiles) {
3568
+ const parts = file.split("/");
3569
+ const moduleName = parts.length >= 3 ? parts[parts.length - 3] : "default";
3570
+ const fileName = parts[parts.length - 1].replace(/\.(ts|js)$/, "");
3571
+ const nodeId = `model:${fileName}`;
3572
+ moduleSet.add(moduleName);
3573
+ nodes.push({
3574
+ id: nodeId,
3575
+ label: fileName,
3576
+ type: "model",
3577
+ status: "idle",
3578
+ module: moduleName
3579
+ });
3580
+ }
3581
+ const controllerFiles = await glob("**/controllers/**/*.{ts,js}", {
3582
+ cwd: backendRoot,
3583
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/index.*"]
3584
+ });
3585
+ for (const file of controllerFiles) {
3586
+ const parts = file.split("/");
3587
+ const moduleName = parts.length >= 3 ? parts[parts.length - 3] : "default";
3588
+ const fileName = parts[parts.length - 1].replace(/\.(ts|js)$/, "").replace(".controller", "");
3589
+ const nodeId = `controller:${fileName}`;
3590
+ moduleSet.add(moduleName);
3591
+ nodes.push({
3592
+ id: nodeId,
3593
+ label: `${fileName} (ctrl)`,
3594
+ type: "controller",
3595
+ status: "idle",
3596
+ module: moduleName
3597
+ });
3598
+ const modelNode = nodes.find((n) => n.type === "model" && n.label.toLowerCase() === fileName.toLowerCase());
3599
+ if (modelNode) {
3600
+ edges.push({ source: nodeId, target: modelNode.id, relation: "uses" });
3601
+ }
3602
+ }
3603
+ for (const mod of moduleSet) {
3604
+ const moduleNodeId = `module:${mod}`;
3605
+ nodes.push({
3606
+ id: moduleNodeId,
3607
+ label: mod,
3608
+ type: "module",
3609
+ status: "idle"
3610
+ });
3611
+ for (const n of nodes) {
3612
+ if (n.module === mod && n.type !== "module") {
3613
+ edges.push({ source: moduleNodeId, target: n.id, relation: "contains" });
3614
+ }
3615
+ }
3616
+ }
3617
+ this.cachedGraph = { nodes, edges };
3618
+ this.updateAgent("parser-croc", { status: "done", currentTask: `Found ${nodes.length} nodes` });
3619
+ return this.cachedGraph;
3620
+ } catch (err) {
3621
+ this.updateAgent("parser-croc", { status: "error", currentTask: String(err) });
3622
+ return { nodes: [], edges: [] };
3623
+ }
3624
+ }
3625
+ invalidateCache() {
3626
+ this.cachedGraph = null;
3627
+ }
3628
+ async getProjectInfo() {
3629
+ const graph = await this.buildKnowledgeGraph();
3630
+ const stats = {
3631
+ modules: graph.nodes.filter((n) => n.type === "module").length,
3632
+ models: graph.nodes.filter((n) => n.type === "model").length,
3633
+ endpoints: graph.nodes.filter((n) => n.type === "api" || n.type === "controller").length,
3634
+ relations: graph.edges.length
3635
+ };
3636
+ return {
3637
+ name: this.config.backendRoot.split("/").pop() || "project",
3638
+ backendRoot: this.config.backendRoot,
3639
+ adapter: typeof this.config.adapter === "string" ? this.config.adapter : "custom",
3640
+ stats,
3641
+ graph,
3642
+ agents: this.agents
3643
+ };
3644
+ }
3645
+ };
3646
+ }
3647
+ });
3648
+
3649
+ // src/server/index.ts
3650
+ var server_exports = {};
3651
+ __export(server_exports, {
3652
+ startServer: () => startServer
3653
+ });
3654
+ import Fastify from "fastify";
3655
+ import fastifyStatic from "@fastify/static";
3656
+ import fastifyWebsocket from "@fastify/websocket";
3657
+ import { fileURLToPath as fileURLToPath2 } from "url";
3658
+ import { dirname as dirname5, join as join13, resolve as resolve8 } from "path";
3659
+ import { existsSync as existsSync15 } from "fs";
3660
+ async function startServer(opts) {
3661
+ const app = Fastify({ logger: false });
3662
+ await app.register(fastifyWebsocket);
3663
+ const webDir = resolve8(__dirname2, "../web");
3664
+ if (existsSync15(webDir)) {
3665
+ await app.register(fastifyStatic, {
3666
+ root: webDir,
3667
+ prefix: "/",
3668
+ decorateReply: false
3669
+ });
3670
+ }
3671
+ const office = new CrocOffice(opts.config, opts.cwd);
3672
+ registerProjectRoutes(app, office);
3673
+ registerAgentRoutes(app, office);
3674
+ app.register(async (fastify) => {
3675
+ fastify.get("/ws", { websocket: true }, (socket) => {
3676
+ office.addClient(socket);
3677
+ socket.on("close", () => office.removeClient(socket));
3678
+ });
3679
+ });
3680
+ app.setNotFoundHandler((req, reply) => {
3681
+ if (req.url.startsWith("/api/")) {
3682
+ reply.code(404).send({ error: "Not found" });
3683
+ return;
3684
+ }
3685
+ const indexPath = join13(webDir, "index.html");
3686
+ if (existsSync15(indexPath)) {
3687
+ reply.sendFile("index.html");
3688
+ } else {
3689
+ reply.code(200).header("content-type", "text/html").send(getEmbeddedHtml());
3690
+ }
3691
+ });
3692
+ try {
3693
+ await app.listen({ port: opts.port, host: opts.host });
3694
+ const url = `http://${opts.host === "0.0.0.0" ? "localhost" : opts.host}:${opts.port}`;
3695
+ console.log(`
3696
+ \u{1F40A} OpenCroc Studio is running at ${url}
3697
+ `);
3698
+ if (opts.open) {
3699
+ const { exec } = await import("child_process");
3700
+ const cmd = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
3701
+ exec(`${cmd} ${url}`);
3702
+ }
3703
+ } catch (err) {
3704
+ console.error("Failed to start server:", err);
3705
+ process.exit(1);
3706
+ }
3707
+ }
3708
+ function getEmbeddedHtml() {
3709
+ return `<!DOCTYPE html>
3710
+ <html lang="en">
3711
+ <head>
3712
+ <meta charset="utf-8">
3713
+ <meta name="viewport" content="width=device-width, initial-scale=1">
3714
+ <title>OpenCroc Studio \u{1F40A}</title>
3715
+ <style>
3716
+ * { margin:0; padding:0; box-sizing:border-box; }
3717
+ body { background:#1a1a2e; color:#e0e0e0; font-family:'Courier New',monospace; display:flex; justify-content:center; align-items:center; min-height:100vh; }
3718
+ .container { text-align:center; }
3719
+ h1 { font-size:3rem; color:#4ecca3; margin-bottom:1rem; }
3720
+ .croc { font-size:6rem; animation: bounce 1s infinite alternate; }
3721
+ @keyframes bounce { from{transform:translateY(0)} to{transform:translateY(-20px)} }
3722
+ p { margin-top:1rem; color:#888; }
3723
+ .status { margin-top:2rem; padding:1rem; background:#16213e; border-radius:8px; }
3724
+ #graph-container { margin-top:2rem; min-height:400px; background:#0f3460; border-radius:8px; position:relative; }
3725
+ .loading { color:#4ecca3; padding:2rem; }
3726
+ </style>
3727
+ </head>
3728
+ <body>
3729
+ <div class="container">
3730
+ <div class="croc">\u{1F40A}</div>
3731
+ <h1>OpenCroc Studio</h1>
3732
+ <p>AI-native E2E testing \u2014 Pixel Croc Office</p>
3733
+ <div class="status" id="status">Connecting...</div>
3734
+ <div id="graph-container"><div class="loading">Loading project graph...</div></div>
3735
+ </div>
3736
+ <script>
3737
+ (async () => {
3738
+ // Fetch project graph data
3739
+ try {
3740
+ const res = await fetch('/api/project');
3741
+ const data = await res.json();
3742
+ document.getElementById('status').innerHTML =
3743
+ '<b>Project:</b> ' + (data.name || 'unknown') +
3744
+ ' | <b>Modules:</b> ' + (data.stats?.modules || 0) +
3745
+ ' | <b>Models:</b> ' + (data.stats?.models || 0) +
3746
+ ' | <b>APIs:</b> ' + (data.stats?.endpoints || 0);
3747
+
3748
+ renderGraph(data.graph);
3749
+ } catch(e) {
3750
+ document.getElementById('status').textContent = 'Error loading project: ' + e.message;
3751
+ }
3752
+
3753
+ // WebSocket for live updates
3754
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
3755
+ const ws = new WebSocket(protocol + '//' + location.host + '/ws');
3756
+ ws.onmessage = (e) => {
3757
+ try {
3758
+ const msg = JSON.parse(e.data);
3759
+ if (msg.type === 'agent:update') {
3760
+ updateAgentStatus(msg.payload);
3761
+ } else if (msg.type === 'graph:update') {
3762
+ renderGraph(msg.payload);
3763
+ }
3764
+ } catch {}
3765
+ };
3766
+ ws.onclose = () => {
3767
+ document.getElementById('status').textContent += ' [disconnected]';
3768
+ };
3769
+ })();
3770
+
3771
+ function renderGraph(graph) {
3772
+ if (!graph || (!graph.nodes?.length)) {
3773
+ document.getElementById('graph-container').innerHTML = '<div class="loading">No modules found. Run opencroc init first.</div>';
3774
+ return;
3775
+ }
3776
+
3777
+ const container = document.getElementById('graph-container');
3778
+ const w = container.clientWidth || 800;
3779
+ const h = 500;
3780
+
3781
+ // Simple force-directed placement
3782
+ const nodes = graph.nodes.map((n, i) => ({
3783
+ ...n,
3784
+ x: w/2 + Math.cos(i * 2 * Math.PI / graph.nodes.length) * Math.min(w,h) * 0.35,
3785
+ y: h/2 + Math.sin(i * 2 * Math.PI / graph.nodes.length) * Math.min(w,h) * 0.35,
3786
+ vx: 0, vy: 0,
3787
+ }));
3788
+
3789
+ const nodeMap = new Map(nodes.map(n => [n.id, n]));
3790
+
3791
+ // Render SVG
3792
+ const colors = { model:'#4ecca3', controller:'#e94560', api:'#f39c12', dto:'#3498db', default:'#888' };
3793
+
3794
+ let svg = '<svg width="'+w+'" height="'+h+'" xmlns="http://www.w3.org/2000/svg">';
3795
+
3796
+ // Edges
3797
+ for (const edge of (graph.edges || [])) {
3798
+ const s = nodeMap.get(edge.source);
3799
+ const t = nodeMap.get(edge.target);
3800
+ if (s && t) {
3801
+ svg += '<line x1="'+s.x+'" y1="'+s.y+'" x2="'+t.x+'" y2="'+t.y+'" stroke="#555" stroke-width="1.5" opacity="0.6"/>';
3802
+ }
3803
+ }
3804
+
3805
+ // Nodes
3806
+ for (const n of nodes) {
3807
+ const color = colors[n.type] || colors.default;
3808
+ const statusColor = n.status === 'passed' ? '#4ecca3' : n.status === 'failed' ? '#e94560' : n.status === 'testing' ? '#f39c12' : '#555';
3809
+ // Pixel-art style square nodes
3810
+ svg += '<rect x="'+(n.x-16)+'" y="'+(n.y-16)+'" width="32" height="32" fill="'+color+'" rx="4" stroke="'+statusColor+'" stroke-width="2"/>';
3811
+ svg += '<text x="'+n.x+'" y="'+(n.y+32)+'" text-anchor="middle" fill="#ccc" font-size="10" font-family="Courier New">'+escapeHtml(n.label || n.id)+'</text>';
3812
+ }
3813
+
3814
+ svg += '</svg>';
3815
+ container.innerHTML = svg;
3816
+ }
3817
+
3818
+ function escapeHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
3819
+
3820
+ function updateAgentStatus(agents) {
3821
+ // Will be enhanced with pixel croc animations in M2
3822
+ console.log('Agent update:', agents);
3823
+ }
3824
+ </script>
3825
+ </body>
3826
+ </html>`;
3827
+ }
3828
+ var __filename2, __dirname2;
3829
+ var init_server = __esm({
3830
+ "src/server/index.ts"() {
3831
+ "use strict";
3832
+ init_esm_shims();
3833
+ init_project();
3834
+ init_agents();
3835
+ init_croc_office();
3836
+ __filename2 = fileURLToPath2(import.meta.url);
3837
+ __dirname2 = dirname5(__filename2);
3838
+ }
3839
+ });
3840
+
3841
+ // src/cli/commands/serve.ts
3842
+ var serve_exports = {};
3843
+ __export(serve_exports, {
3844
+ serve: () => serve
3845
+ });
3846
+ import chalk11 from "chalk";
3847
+ async function serve(opts) {
3848
+ let loaded;
3849
+ try {
3850
+ loaded = await loadConfig();
3851
+ } catch {
3852
+ console.error(chalk11.red("No opencroc config found. Run `opencroc init` first."));
3853
+ process.exitCode = 1;
3854
+ return;
3855
+ }
3856
+ const port = parseInt(opts.port || "8765", 10);
3857
+ const host = opts.host || "localhost";
3858
+ console.log(chalk11.cyan("\u{1F40A} Starting OpenCroc Studio..."));
3859
+ console.log(chalk11.gray(` Config: ${loaded.filepath}`));
3860
+ console.log(chalk11.gray(` Backend: ${loaded.config.backendRoot}`));
3861
+ const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
3862
+ await startServer2({
3863
+ port,
3864
+ host,
3865
+ open: opts.open ?? true,
3866
+ config: loaded.config,
3867
+ cwd: process.cwd()
3868
+ });
3869
+ }
3870
+ var init_serve = __esm({
3871
+ "src/cli/commands/serve.ts"() {
3872
+ "use strict";
3873
+ init_esm_shims();
3874
+ init_load_config();
3875
+ }
3876
+ });
3877
+
2488
3878
  // src/cli/index.ts
2489
3879
  init_esm_shims();
2490
3880
  import { Command } from "commander";
2491
3881
  var program = new Command();
2492
- program.name("opencroc").description("AI-native E2E testing framework").version("0.6.1");
3882
+ program.name("opencroc").description("AI-native E2E testing framework").version("1.0.0");
2493
3883
  program.command("init").description("Initialize OpenCroc in the current project").option("-y, --yes", "Skip prompts and use defaults").action(async (opts) => {
2494
3884
  const { initProject: initProject2 } = await Promise.resolve().then(() => (init_init(), init_exports));
2495
3885
  await initProject2(opts);
@@ -2498,7 +3888,7 @@ program.command("generate").description("Generate E2E test cases from source cod
2498
3888
  const { generate: generate2 } = await Promise.resolve().then(() => (init_generate(), generate_exports));
2499
3889
  await generate2(opts);
2500
3890
  });
2501
- program.command("test").description("Run generated E2E tests").option("-m, --module <name>", "Run tests for a specific module").option("--headed", "Run in headed browser mode").action(async (opts) => {
3891
+ program.command("test").description("Run generated E2E tests").option("-m, --module <name>", "Run tests for a specific module").option("--headed", "Run in headed browser mode").option("--setup-hook <cmd>", "Run setup hook command before test execution").option("--auth-hook <cmd>", "Run auth hook command before test execution").option("--teardown-hook <cmd>", "Run teardown hook command after test execution").action(async (opts) => {
2502
3892
  const { runTests: runTests2 } = await Promise.resolve().then(() => (init_test(), test_exports));
2503
3893
  await runTests2(opts);
2504
3894
  });
@@ -2522,5 +3912,17 @@ program.command("dashboard").description("Generate visual dashboard (opencroc-da
2522
3912
  const { dashboard: dashboard2 } = await Promise.resolve().then(() => (init_dashboard2(), dashboard_exports));
2523
3913
  await dashboard2(opts);
2524
3914
  });
3915
+ program.command("init-runtime").description("Generate Playwright runtime infrastructure (config, setup, teardown, auth)").option("-o, --output <dir>", "Output directory for generated files", ".").option("--force", "Overwrite existing files").action(async (opts) => {
3916
+ const { initRuntime: initRuntime2 } = await Promise.resolve().then(() => (init_init_runtime(), init_runtime_exports));
3917
+ await initRuntime2(opts);
3918
+ });
3919
+ program.command("run").description("Full orchestration: generate \u2192 execute \u2192 analyze \u2192 heal \u2192 report").option("-m, --module <name>", "Run for a specific module").option("--phases <phases>", "Phases to run (comma-separated: generate,execute,analyze,heal,report)").option("--self-heal", "Enable self-healing on test failures").option("--headed", "Run Playwright in headed mode").option("--report <formats>", "Report formats (comma-separated)", "html,json").option("--token-budget <n>", "LLM token budget (0 = unlimited)").option("--abort-on-error", "Abort pipeline on first phase error").action(async (opts) => {
3920
+ const { run: run2 } = await Promise.resolve().then(() => (init_run(), run_exports));
3921
+ await run2(opts);
3922
+ });
3923
+ program.command("serve").description("Start OpenCroc Studio \u2014 pixel croc office + knowledge graph UI").option("-p, --port <port>", "Server port", "8765").option("-H, --host <host>", "Server host", "localhost").option("--no-open", "Do not auto-open browser").action(async (opts) => {
3924
+ const { serve: serve2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
3925
+ await serve2(opts);
3926
+ });
2525
3927
  program.parse();
2526
3928
  //# sourceMappingURL=index.js.map