opencroc 1.8.0 → 1.8.2

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.
Files changed (71) hide show
  1. package/dist/cli/index.js +1107 -49
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/index.d.ts +128 -1
  4. package/dist/index.js +548 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/web/dist/assets/main-Ccg3eDNK.js +1 -0
  7. package/dist/web/dist/assets/office-runtime-B3iNctxE.css +1 -0
  8. package/dist/web/dist/assets/office-runtime-BsCh82Pj.js +183 -0
  9. package/dist/web/dist/assets/pixel-page-3BYGm7dH.js +470 -0
  10. package/dist/web/dist/assets/react-vendor-C8RhVn0h.js +49 -0
  11. package/dist/web/dist/assets/studio-page-BInoyoV2.css +1 -0
  12. package/dist/web/dist/assets/studio-page-o3SCvE_v.js +351 -0
  13. package/dist/web/dist/assets/three-addons-BdrPp04O.js +470 -0
  14. package/dist/web/dist/assets/three-core-CsxM1PCY.js +4057 -0
  15. package/dist/web/dist/index.html +15 -0
  16. package/dist/web/index.html +11 -572
  17. package/dist/web/public/botreview/char_0.png +0 -0
  18. package/dist/web/public/botreview/char_1.png +0 -0
  19. package/dist/web/public/botreview/char_2.png +0 -0
  20. package/dist/web/public/botreview/coffee-machine.gif +0 -0
  21. package/dist/web/public/botreview/server.gif +0 -0
  22. package/dist/web/public/botreview/walls.png +0 -0
  23. package/dist/web/public/star/desk-v3.webp +0 -0
  24. package/dist/web/public/star/office_bg_small.webp +0 -0
  25. package/dist/web/public/star/star-idle-v5.png +0 -0
  26. package/dist/web/public/star/star-working-spritesheet-grid.webp +0 -0
  27. package/dist/web/src/app/AppLayout.tsx +34 -0
  28. package/dist/web/src/app/AppRouter.tsx +46 -0
  29. package/dist/web/src/app/bootstrap.tsx +22 -0
  30. package/dist/web/src/app/routes.tsx +52 -0
  31. package/dist/web/src/features/office/runtime/index.ts +1 -0
  32. package/dist/web/src/features/office/runtime/mount.ts +809 -0
  33. package/dist/web/src/features/pixel/runtime/index.ts +1 -0
  34. package/dist/web/src/features/pixel/runtime/mount.ts +728 -0
  35. package/dist/web/src/features/studio/runtime/index.ts +1 -0
  36. package/dist/web/src/features/studio/runtime/mount.ts +664 -0
  37. package/dist/web/src/features/three/engine/index.ts +1 -0
  38. package/dist/web/src/main.tsx +7 -0
  39. package/dist/web/src/pages/office/index.ts +1 -0
  40. package/dist/web/src/pages/office/page.tsx +283 -0
  41. package/dist/web/src/pages/pixel/index.ts +1 -0
  42. package/dist/web/src/pages/pixel/page.tsx +564 -0
  43. package/dist/web/src/pages/studio/index.ts +1 -0
  44. package/dist/web/src/pages/studio/page.tsx +446 -0
  45. package/dist/web/{js/agents.js → src/runtime/agents.ts} +304 -31
  46. package/dist/web/{js/camera.js → src/runtime/camera.ts} +12 -5
  47. package/dist/web/{js/dataviz.js → src/runtime/dataviz.ts} +38 -14
  48. package/dist/web/{js/effects.js → src/runtime/effects.ts} +139 -2
  49. package/dist/web/{js/engine.js → src/runtime/engine.ts} +45 -6
  50. package/dist/web/{js/office.js → src/runtime/office.ts} +136 -20
  51. package/dist/web/{js/ui.js → src/runtime/ui.ts} +11 -7
  52. package/dist/web/src/shared/assets.ts +4 -0
  53. package/dist/web/src/shared/navigation.ts +47 -0
  54. package/dist/web/src/styles/app-layout.css +19 -0
  55. package/dist/web/src/styles/office.css +268 -0
  56. package/dist/web/tsconfig.json +28 -0
  57. package/dist/web/vite.config.ts +93 -0
  58. package/package.json +11 -2
  59. package/dist/web/index-studio.html +0 -804
  60. package/dist/web/index-v2-pixel.html +0 -1571
  61. /package/dist/web/{assets → dist}/botreview/char_0.png +0 -0
  62. /package/dist/web/{assets → dist}/botreview/char_1.png +0 -0
  63. /package/dist/web/{assets → dist}/botreview/char_2.png +0 -0
  64. /package/dist/web/{assets → dist}/botreview/coffee-machine.gif +0 -0
  65. /package/dist/web/{assets → dist}/botreview/server.gif +0 -0
  66. /package/dist/web/{assets → dist}/botreview/walls.png +0 -0
  67. /package/dist/web/{assets → dist}/star/desk-v3.webp +0 -0
  68. /package/dist/web/{assets → dist}/star/office_bg_small.webp +0 -0
  69. /package/dist/web/{assets → dist}/star/star-idle-v5.png +0 -0
  70. /package/dist/web/{assets → dist}/star/star-working-spritesheet-grid.webp +0 -0
  71. /package/dist/web/{js/state.js → src/runtime/state.ts} +0 -0
package/dist/cli/index.js CHANGED
@@ -6324,8 +6324,640 @@ var init_insight = __esm({
6324
6324
  }
6325
6325
  });
6326
6326
 
6327
+ // src/server/studio-store.ts
6328
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync6, writeFileSync as writeFileSync10 } from "fs";
6329
+ import { dirname as dirname6 } from "path";
6330
+ function isSerializedSnapshotFile(value) {
6331
+ return "snapshots" in value && Array.isArray(value.snapshots);
6332
+ }
6333
+ var EMPTY_STUDIO_STORE, FileStudioSnapshotStore;
6334
+ var init_studio_store = __esm({
6335
+ "src/server/studio-store.ts"() {
6336
+ "use strict";
6337
+ init_esm_shims();
6338
+ EMPTY_STUDIO_STORE = {
6339
+ graph: null,
6340
+ risks: [],
6341
+ scanTime: 0,
6342
+ source: ""
6343
+ };
6344
+ FileStudioSnapshotStore = class {
6345
+ filePath;
6346
+ maxSnapshots;
6347
+ constructor(filePath, maxSnapshots = 12) {
6348
+ this.filePath = filePath;
6349
+ this.maxSnapshots = maxSnapshots;
6350
+ }
6351
+ load() {
6352
+ const data = this.readFile();
6353
+ if (!data || !data.currentSnapshotId) return null;
6354
+ const current = data.snapshots.find((snapshot) => snapshot.id === data.currentSnapshotId);
6355
+ return current ? this.toProjectStore(current) : null;
6356
+ }
6357
+ save(snapshot) {
6358
+ const data = this.readFile() ?? { version: 2, currentSnapshotId: null, snapshots: [] };
6359
+ const record = this.toSnapshotRecord(snapshot);
6360
+ data.currentSnapshotId = record.id;
6361
+ data.snapshots = [record, ...data.snapshots].slice(0, this.maxSnapshots);
6362
+ mkdirSync11(dirname6(this.filePath), { recursive: true });
6363
+ writeFileSync10(this.filePath, JSON.stringify(data, null, 2), "utf-8");
6364
+ }
6365
+ list() {
6366
+ const data = this.readFile();
6367
+ if (!data) return [];
6368
+ return data.snapshots.map((snapshot) => ({
6369
+ id: snapshot.id,
6370
+ name: snapshot.name,
6371
+ source: snapshot.source,
6372
+ scanTime: snapshot.scanTime,
6373
+ nodeCount: snapshot.graph?.nodes.length ?? 0,
6374
+ riskCount: snapshot.risks.length,
6375
+ current: snapshot.id === data.currentSnapshotId,
6376
+ pinned: Boolean(snapshot.pinned),
6377
+ tags: Array.isArray(snapshot.tags) ? snapshot.tags : []
6378
+ })).sort((left, right) => {
6379
+ if (left.pinned !== right.pinned) return left.pinned ? -1 : 1;
6380
+ return right.scanTime - left.scanTime;
6381
+ });
6382
+ }
6383
+ loadById(id) {
6384
+ const data = this.readFile();
6385
+ if (!data) return null;
6386
+ const record = data.snapshots.find((snapshot) => snapshot.id === id);
6387
+ if (!record) return null;
6388
+ data.currentSnapshotId = record.id;
6389
+ mkdirSync11(dirname6(this.filePath), { recursive: true });
6390
+ writeFileSync10(this.filePath, JSON.stringify(data, null, 2), "utf-8");
6391
+ return this.toProjectStore(record);
6392
+ }
6393
+ rename(id, name) {
6394
+ const nextName = name.trim();
6395
+ if (!nextName) return false;
6396
+ const data = this.readFile();
6397
+ if (!data) return false;
6398
+ const record = data.snapshots.find((snapshot) => snapshot.id === id);
6399
+ if (!record) return false;
6400
+ record.name = nextName;
6401
+ mkdirSync11(dirname6(this.filePath), { recursive: true });
6402
+ writeFileSync10(this.filePath, JSON.stringify(data, null, 2), "utf-8");
6403
+ return true;
6404
+ }
6405
+ delete(id) {
6406
+ const data = this.readFile();
6407
+ if (!data) return false;
6408
+ const nextSnapshots = data.snapshots.filter((snapshot) => snapshot.id !== id);
6409
+ if (nextSnapshots.length === data.snapshots.length) return false;
6410
+ data.snapshots = nextSnapshots;
6411
+ if (data.currentSnapshotId === id) {
6412
+ data.currentSnapshotId = nextSnapshots[0]?.id ?? null;
6413
+ }
6414
+ mkdirSync11(dirname6(this.filePath), { recursive: true });
6415
+ writeFileSync10(this.filePath, JSON.stringify(data, null, 2), "utf-8");
6416
+ return true;
6417
+ }
6418
+ pin(id, pinned) {
6419
+ const data = this.readFile();
6420
+ if (!data) return false;
6421
+ const record = data.snapshots.find((snapshot) => snapshot.id === id);
6422
+ if (!record) return false;
6423
+ record.pinned = pinned;
6424
+ mkdirSync11(dirname6(this.filePath), { recursive: true });
6425
+ writeFileSync10(this.filePath, JSON.stringify(data, null, 2), "utf-8");
6426
+ return true;
6427
+ }
6428
+ updateTags(id, tags) {
6429
+ const data = this.readFile();
6430
+ if (!data) return false;
6431
+ const record = data.snapshots.find((snapshot) => snapshot.id === id);
6432
+ if (!record) return false;
6433
+ record.tags = this.normalizeTags(tags);
6434
+ mkdirSync11(dirname6(this.filePath), { recursive: true });
6435
+ writeFileSync10(this.filePath, JSON.stringify(data, null, 2), "utf-8");
6436
+ return true;
6437
+ }
6438
+ readFile() {
6439
+ if (!existsSync18(this.filePath)) return null;
6440
+ try {
6441
+ const raw = readFileSync6(this.filePath, "utf-8");
6442
+ const parsed = JSON.parse(raw);
6443
+ return this.normalize(parsed);
6444
+ } catch {
6445
+ return null;
6446
+ }
6447
+ }
6448
+ normalize(parsed) {
6449
+ if (isSerializedSnapshotFile(parsed)) {
6450
+ return {
6451
+ version: 2,
6452
+ currentSnapshotId: typeof parsed.currentSnapshotId === "string" ? parsed.currentSnapshotId : null,
6453
+ snapshots: parsed.snapshots.map((snapshot) => ({
6454
+ id: snapshot.id,
6455
+ name: snapshot.name,
6456
+ pinned: Boolean(snapshot.pinned),
6457
+ tags: this.normalizeTags(snapshot.tags),
6458
+ graph: snapshot.graph ?? null,
6459
+ risks: Array.isArray(snapshot.risks) ? snapshot.risks : [],
6460
+ scanTime: typeof snapshot.scanTime === "number" ? snapshot.scanTime : 0,
6461
+ source: typeof snapshot.source === "string" ? snapshot.source : ""
6462
+ }))
6463
+ };
6464
+ }
6465
+ const legacyStore = parsed;
6466
+ const legacy = this.toSnapshotRecord({
6467
+ graph: legacyStore.graph ?? null,
6468
+ risks: Array.isArray(legacyStore.risks) ? legacyStore.risks : [],
6469
+ scanTime: typeof legacyStore.scanTime === "number" ? legacyStore.scanTime : 0,
6470
+ source: typeof legacyStore.source === "string" ? legacyStore.source : ""
6471
+ });
6472
+ return {
6473
+ version: 2,
6474
+ currentSnapshotId: legacy.id,
6475
+ snapshots: [legacy]
6476
+ };
6477
+ }
6478
+ toSnapshotRecord(snapshot) {
6479
+ const name = snapshot.graph?.projectInfo?.name || this.deriveName(snapshot.source);
6480
+ return {
6481
+ id: `${snapshot.scanTime || Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
6482
+ name,
6483
+ pinned: false,
6484
+ tags: [],
6485
+ graph: snapshot.graph ?? null,
6486
+ risks: Array.isArray(snapshot.risks) ? snapshot.risks : [],
6487
+ scanTime: typeof snapshot.scanTime === "number" ? snapshot.scanTime : Date.now(),
6488
+ source: typeof snapshot.source === "string" ? snapshot.source : ""
6489
+ };
6490
+ }
6491
+ toProjectStore(snapshot) {
6492
+ return {
6493
+ graph: snapshot.graph ?? null,
6494
+ risks: Array.isArray(snapshot.risks) ? snapshot.risks : [],
6495
+ scanTime: typeof snapshot.scanTime === "number" ? snapshot.scanTime : 0,
6496
+ source: typeof snapshot.source === "string" ? snapshot.source : ""
6497
+ };
6498
+ }
6499
+ deriveName(source) {
6500
+ if (!source) return "unknown-project";
6501
+ const parts = source.split(/[\\/]/).filter(Boolean);
6502
+ return parts[parts.length - 1] || source;
6503
+ }
6504
+ normalizeTags(tags) {
6505
+ if (!Array.isArray(tags)) return [];
6506
+ return [...new Set(
6507
+ tags.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean)
6508
+ )];
6509
+ }
6510
+ };
6511
+ }
6512
+ });
6513
+
6514
+ // src/agents/role-registry.ts
6515
+ var role_registry_exports = {};
6516
+ __export(role_registry_exports, {
6517
+ RoleRegistry: () => RoleRegistry,
6518
+ getRoleRegistry: () => getRoleRegistry
6519
+ });
6520
+ function getRoleRegistry() {
6521
+ if (!_registry) {
6522
+ _registry = new RoleRegistry();
6523
+ }
6524
+ return _registry;
6525
+ }
6526
+ var CORE_ROLES, LANGUAGE_ROLES, FRAMEWORK_ROLES, DOMAIN_ROLES, RoleRegistry, _registry;
6527
+ var init_role_registry = __esm({
6528
+ "src/agents/role-registry.ts"() {
6529
+ "use strict";
6530
+ init_esm_shims();
6531
+ CORE_ROLES = [
6532
+ {
6533
+ id: "parser-croc",
6534
+ name: "\u89E3\u6790\u9CC4",
6535
+ nameEn: "Parser Croc",
6536
+ category: "core",
6537
+ description: "\u89E3\u6790\u9879\u76EE\u7ED3\u6784\u3001\u63D0\u53D6\u5B9E\u4F53\u548C\u5173\u7CFB",
6538
+ sprite: "parser",
6539
+ color: "#34d399",
6540
+ priority: 0,
6541
+ triggers: { custom: () => true },
6542
+ // Always summoned
6543
+ systemPrompt: "You are an expert code parser. Analyze the project structure, extract all entities (classes, functions, APIs, models) and their relationships.",
6544
+ outputType: "analysis",
6545
+ tags: ["core", "parser", "structure"]
6546
+ },
6547
+ {
6548
+ id: "analyzer-croc",
6549
+ name: "\u5206\u6790\u9CC4",
6550
+ nameEn: "Analyzer Croc",
6551
+ category: "core",
6552
+ description: "\u6784\u5EFA\u77E5\u8BC6\u56FE\u8C31\u3001\u5206\u6790\u4F9D\u8D56\u5173\u7CFB",
6553
+ sprite: "analyzer",
6554
+ color: "#60a5fa",
6555
+ priority: 1,
6556
+ triggers: { custom: () => true },
6557
+ systemPrompt: "You are an expert software architect. Build a knowledge graph of the project, analyze dependencies, coupling, and cohesion.",
6558
+ outputType: "diagram",
6559
+ tags: ["core", "graph", "architecture"]
6560
+ },
6561
+ {
6562
+ id: "tester-croc",
6563
+ name: "\u6D4B\u8BD5\u9CC4",
6564
+ nameEn: "Tester Croc",
6565
+ category: "core",
6566
+ description: "\u751F\u6210\u548C\u6267\u884C E2E \u6D4B\u8BD5",
6567
+ sprite: "tester",
6568
+ color: "#a78bfa",
6569
+ priority: 2,
6570
+ triggers: { custom: () => true },
6571
+ systemPrompt: "You are an expert test engineer. Generate comprehensive E2E test cases covering all critical paths, edge cases, and error scenarios.",
6572
+ outputType: "analysis",
6573
+ tags: ["core", "testing", "e2e"]
6574
+ },
6575
+ {
6576
+ id: "healer-croc",
6577
+ name: "\u4FEE\u590D\u9CC4",
6578
+ nameEn: "Healer Croc",
6579
+ category: "core",
6580
+ description: "\u81EA\u52A8\u4FEE\u590D\u6D4B\u8BD5\u5931\u8D25\u548C\u4EE3\u7801\u95EE\u9898",
6581
+ sprite: "healer",
6582
+ color: "#f87171",
6583
+ priority: 3,
6584
+ triggers: { custom: () => true },
6585
+ systemPrompt: "You are an expert debugger and code fixer. Analyze test failures, diagnose root causes, and propose minimal targeted fixes.",
6586
+ outputType: "fix",
6587
+ tags: ["core", "healing", "debug"]
6588
+ },
6589
+ {
6590
+ id: "planner-croc",
6591
+ name: "\u89C4\u5212\u9CC4",
6592
+ nameEn: "Planner Croc",
6593
+ category: "core",
6594
+ description: "\u5236\u5B9A\u6D4B\u8BD5\u7B56\u7565\u548C\u6267\u884C\u8BA1\u5212",
6595
+ sprite: "planner",
6596
+ color: "#fbbf24",
6597
+ priority: 4,
6598
+ triggers: { custom: () => true },
6599
+ systemPrompt: "You are an expert project planner. Create test strategies, prioritize test execution order, and optimize the testing pipeline.",
6600
+ outputType: "analysis",
6601
+ tags: ["core", "planning", "strategy"]
6602
+ },
6603
+ {
6604
+ id: "reporter-croc",
6605
+ name: "\u6C47\u62A5\u9CC4",
6606
+ nameEn: "Reporter Croc",
6607
+ category: "core",
6608
+ description: "\u751F\u6210\u591A\u89C6\u89D2\u5206\u6790\u62A5\u544A",
6609
+ sprite: "reporter",
6610
+ color: "#22d3ee",
6611
+ priority: 5,
6612
+ triggers: { custom: () => true },
6613
+ systemPrompt: "You are an expert technical writer. Generate clear, actionable reports from multiple perspectives (developer, architect, tester, product, executive).",
6614
+ outputType: "report",
6615
+ tags: ["core", "reporting", "documentation"]
6616
+ }
6617
+ ];
6618
+ LANGUAGE_ROLES = [
6619
+ {
6620
+ id: "python-croc",
6621
+ name: "Python\u4E13\u5BB6\u9CC4",
6622
+ nameEn: "Python Expert Croc",
6623
+ category: "language",
6624
+ description: "Python \u751F\u6001\u4E13\u5BB6\uFF1ADjango/Flask/FastAPI/SQLAlchemy",
6625
+ sprite: "language",
6626
+ color: "#3776ab",
6627
+ priority: 10,
6628
+ triggers: { languages: ["python"] },
6629
+ systemPrompt: "You are a Python ecosystem expert. Analyze Python code for best practices, type safety, async patterns, Django/Flask/FastAPI conventions, SQLAlchemy usage, and Python-specific security issues.",
6630
+ outputType: "review",
6631
+ tags: ["language", "python", "django", "flask", "fastapi"]
6632
+ },
6633
+ {
6634
+ id: "go-croc",
6635
+ name: "Go\u4E13\u5BB6\u9CC4",
6636
+ nameEn: "Go Expert Croc",
6637
+ category: "language",
6638
+ description: "Go \u751F\u6001\u4E13\u5BB6\uFF1Agoroutine/channel/\u63A5\u53E3\u8BBE\u8BA1",
6639
+ sprite: "language",
6640
+ color: "#00add8",
6641
+ priority: 10,
6642
+ triggers: { languages: ["go"] },
6643
+ systemPrompt: "You are a Go ecosystem expert. Analyze Go code for goroutine safety, channel patterns, interface design, error handling, and Go-specific performance concerns.",
6644
+ outputType: "review",
6645
+ tags: ["language", "go", "golang", "concurrency"]
6646
+ },
6647
+ {
6648
+ id: "java-croc",
6649
+ name: "Java\u4E13\u5BB6\u9CC4",
6650
+ nameEn: "Java Expert Croc",
6651
+ category: "language",
6652
+ description: "Java/Kotlin \u751F\u6001\u4E13\u5BB6\uFF1ASpring Boot/JPA/\u5FAE\u670D\u52A1",
6653
+ sprite: "language",
6654
+ color: "#ed8b00",
6655
+ priority: 10,
6656
+ triggers: { languages: ["java", "kotlin"] },
6657
+ systemPrompt: "You are a Java/Kotlin ecosystem expert. Analyze Spring Boot applications for bean lifecycle issues, JPA N+1 queries, transaction management, and microservice patterns.",
6658
+ outputType: "review",
6659
+ tags: ["language", "java", "kotlin", "spring"]
6660
+ },
6661
+ {
6662
+ id: "rust-croc",
6663
+ name: "Rust\u4E13\u5BB6\u9CC4",
6664
+ nameEn: "Rust Expert Croc",
6665
+ category: "language",
6666
+ description: "Rust \u751F\u6001\u4E13\u5BB6\uFF1A\u6240\u6709\u6743/\u751F\u547D\u5468\u671F/unsafe",
6667
+ sprite: "language",
6668
+ color: "#dea584",
6669
+ priority: 10,
6670
+ triggers: { languages: ["rust"] },
6671
+ systemPrompt: "You are a Rust ecosystem expert. Analyze Rust code for ownership patterns, lifetime issues, unsafe code safety, and performance optimization.",
6672
+ outputType: "review",
6673
+ tags: ["language", "rust", "ownership", "safety"]
6674
+ }
6675
+ ];
6676
+ FRAMEWORK_ROLES = [
6677
+ {
6678
+ id: "react-croc",
6679
+ name: "React\u4E13\u5BB6\u9CC4",
6680
+ nameEn: "React Expert Croc",
6681
+ category: "framework",
6682
+ description: "React/Next.js \u524D\u7AEF\u6027\u80FD\u548C\u67B6\u6784\u4E13\u5BB6",
6683
+ sprite: "framework",
6684
+ color: "#61dafb",
6685
+ priority: 15,
6686
+ triggers: { frameworks: ["React", "Next.js"] },
6687
+ systemPrompt: "You are a React/Next.js expert. Analyze component architecture, render performance, state management, SSR/SSG patterns, and React-specific anti-patterns.",
6688
+ outputType: "review",
6689
+ tags: ["framework", "react", "nextjs", "frontend"]
6690
+ },
6691
+ {
6692
+ id: "vue-croc",
6693
+ name: "Vue\u4E13\u5BB6\u9CC4",
6694
+ nameEn: "Vue Expert Croc",
6695
+ category: "framework",
6696
+ description: "Vue/Nuxt \u4E13\u5BB6\uFF1A\u7EC4\u5408\u5F0FAPI/\u54CD\u5E94\u5F0F/SSR",
6697
+ sprite: "framework",
6698
+ color: "#42b883",
6699
+ priority: 15,
6700
+ triggers: { frameworks: ["Vue", "Nuxt"] },
6701
+ systemPrompt: "You are a Vue/Nuxt expert. Analyze Composition API usage, reactivity patterns, Pinia stores, SSR hydration, and Vue-specific best practices.",
6702
+ outputType: "review",
6703
+ tags: ["framework", "vue", "nuxt", "frontend"]
6704
+ },
6705
+ {
6706
+ id: "express-croc",
6707
+ name: "Express\u4E13\u5BB6\u9CC4",
6708
+ nameEn: "Express Expert Croc",
6709
+ category: "framework",
6710
+ description: "Express/Koa/Fastify \u8DEF\u7531\u548C\u4E2D\u95F4\u4EF6\u4E13\u5BB6",
6711
+ sprite: "framework",
6712
+ color: "#68a063",
6713
+ priority: 15,
6714
+ triggers: { frameworks: ["Express", "Koa", "Fastify"] },
6715
+ systemPrompt: "You are a Node.js backend expert. Analyze Express/Koa/Fastify middleware chains, route organization, error handling, authentication patterns, and performance.",
6716
+ outputType: "review",
6717
+ tags: ["framework", "express", "koa", "fastify", "nodejs"]
6718
+ },
6719
+ {
6720
+ id: "django-croc",
6721
+ name: "Django\u4E13\u5BB6\u9CC4",
6722
+ nameEn: "Django Expert Croc",
6723
+ category: "framework",
6724
+ description: "Django/DRF \u4E13\u5BB6\uFF1AORM/\u5E8F\u5217\u5316/\u6743\u9650",
6725
+ sprite: "framework",
6726
+ color: "#092e20",
6727
+ priority: 15,
6728
+ triggers: { frameworks: ["Django", "DRF"] },
6729
+ systemPrompt: "You are a Django/DRF expert. Analyze ORM query efficiency, serializer design, view permissions, middleware, and Django-specific security practices.",
6730
+ outputType: "review",
6731
+ tags: ["framework", "django", "drf", "python"]
6732
+ },
6733
+ {
6734
+ id: "spring-croc",
6735
+ name: "SpringBoot\u4E13\u5BB6\u9CC4",
6736
+ nameEn: "Spring Boot Expert Croc",
6737
+ category: "framework",
6738
+ description: "Spring Boot/Cloud \u5FAE\u670D\u52A1\u67B6\u6784\u4E13\u5BB6",
6739
+ sprite: "framework",
6740
+ color: "#6db33f",
6741
+ priority: 15,
6742
+ triggers: { frameworks: ["Spring Boot", "Spring Cloud"] },
6743
+ systemPrompt: "You are a Spring Boot/Cloud expert. Analyze bean configurations, transaction management, service discovery, circuit breakers, and microservice communication patterns.",
6744
+ outputType: "review",
6745
+ tags: ["framework", "spring", "springboot", "microservice"]
6746
+ }
6747
+ ];
6748
+ DOMAIN_ROLES = [
6749
+ {
6750
+ id: "security-croc",
6751
+ name: "\u5B89\u5168\u5BA1\u8BA1\u9CC4",
6752
+ nameEn: "Security Auditor Croc",
6753
+ category: "domain",
6754
+ description: "\u5B89\u5168\u6F0F\u6D1E\u68C0\u6D4B\uFF1A\u6CE8\u5165/XSS/CSRF/\u8BA4\u8BC1/\u6388\u6743",
6755
+ sprite: "security",
6756
+ color: "#ef4444",
6757
+ priority: 8,
6758
+ triggers: {
6759
+ riskCategories: ["security"],
6760
+ custom: (ctx) => ctx.hasAPIs
6761
+ },
6762
+ systemPrompt: "You are a security auditor. Scan for OWASP Top 10 vulnerabilities: SQL injection, XSS, CSRF, broken authentication, sensitive data exposure, insecure deserialization, and missing access controls.",
6763
+ outputType: "report",
6764
+ tags: ["domain", "security", "owasp", "audit"]
6765
+ },
6766
+ {
6767
+ id: "performance-croc",
6768
+ name: "\u6027\u80FD\u5206\u6790\u9CC4",
6769
+ nameEn: "Performance Analyst Croc",
6770
+ category: "domain",
6771
+ description: "\u6027\u80FD\u74F6\u9888\u68C0\u6D4B\uFF1AN+1\u67E5\u8BE2/\u5185\u5B58\u6CC4\u6F0F/\u6162\u63A5\u53E3",
6772
+ sprite: "performance",
6773
+ color: "#f59e0b",
6774
+ priority: 9,
6775
+ triggers: {
6776
+ minEntities: 50,
6777
+ custom: (ctx) => ctx.hasModels && ctx.hasAPIs
6778
+ },
6779
+ systemPrompt: "You are a performance analyst. Detect N+1 queries, memory leaks, slow API endpoints, missing indexes, unnecessary data loading, and recommend caching strategies.",
6780
+ outputType: "report",
6781
+ tags: ["domain", "performance", "optimization", "n+1"]
6782
+ },
6783
+ {
6784
+ id: "architecture-croc",
6785
+ name: "\u67B6\u6784\u8BC4\u5BA1\u9CC4",
6786
+ nameEn: "Architecture Reviewer Croc",
6787
+ category: "domain",
6788
+ description: "\u67B6\u6784\u8D28\u91CF\u8BC4\u5BA1\uFF1A\u8026\u5408\u5EA6/\u5185\u805A\u6027/\u5206\u5C42/DDD",
6789
+ sprite: "architecture",
6790
+ color: "#8b5cf6",
6791
+ priority: 7,
6792
+ triggers: {
6793
+ minEntities: 30
6794
+ },
6795
+ systemPrompt: "You are a software architect. Evaluate coupling, cohesion, layering, dependency injection, SOLID principles, DDD patterns, and recommend architectural improvements.",
6796
+ outputType: "review",
6797
+ tags: ["domain", "architecture", "solid", "ddd"]
6798
+ },
6799
+ {
6800
+ id: "data-modeling-croc",
6801
+ name: "\u6570\u636E\u5EFA\u6A21\u9CC4",
6802
+ nameEn: "Data Modeling Croc",
6803
+ category: "domain",
6804
+ description: "\u6570\u636E\u6A21\u578B\u8BC4\u5BA1\uFF1A\u8303\u5F0F/\u7D22\u5F15/\u5173\u8054/\u8FC1\u79FB",
6805
+ sprite: "database",
6806
+ color: "#06b6d4",
6807
+ priority: 9,
6808
+ triggers: {
6809
+ custom: (ctx) => ctx.hasModels,
6810
+ frameworks: ["Sequelize", "Prisma", "TypeORM", "Drizzle", "SQLAlchemy", "Django"]
6811
+ },
6812
+ systemPrompt: "You are a database expert. Review data models for normalization, index design, relationship integrity, migration safety, cascade delete risks, and query optimization.",
6813
+ outputType: "review",
6814
+ tags: ["domain", "database", "modeling", "orm"]
6815
+ },
6816
+ {
6817
+ id: "devops-croc",
6818
+ name: "\u8FD0\u7EF4\u90E8\u7F72\u9CC4",
6819
+ nameEn: "DevOps Croc",
6820
+ category: "domain",
6821
+ description: "CI/CD\u3001Docker\u3001K8s \u90E8\u7F72\u548C\u8FD0\u7EF4\u4E13\u5BB6",
6822
+ sprite: "devops",
6823
+ color: "#2563eb",
6824
+ priority: 12,
6825
+ triggers: {
6826
+ custom: (ctx) => ctx.hasDocker || ctx.hasCI,
6827
+ filePatterns: ["Dockerfile", "docker-compose*", ".github/workflows/*", ".gitlab-ci*", "Jenkinsfile"]
6828
+ },
6829
+ systemPrompt: "You are a DevOps expert. Analyze Dockerfile efficiency, compose orchestration, CI/CD pipeline design, secret management, health checks, and deployment strategies.",
6830
+ outputType: "review",
6831
+ tags: ["domain", "devops", "docker", "ci/cd", "kubernetes"]
6832
+ },
6833
+ {
6834
+ id: "api-design-croc",
6835
+ name: "API\u8BBE\u8BA1\u9CC4",
6836
+ nameEn: "API Design Croc",
6837
+ category: "domain",
6838
+ description: "RESTful API \u8BBE\u8BA1\u8BC4\u5BA1\uFF1A\u547D\u540D/\u7248\u672C/\u5206\u9875/\u9519\u8BEF\u5904\u7406",
6839
+ sprite: "api",
6840
+ color: "#10b981",
6841
+ priority: 11,
6842
+ triggers: {
6843
+ custom: (ctx) => ctx.hasAPIs,
6844
+ minEntities: 10
6845
+ },
6846
+ systemPrompt: "You are an API design expert. Review REST API naming conventions, versioning strategy, pagination, filtering, error response format, rate limiting, and documentation completeness.",
6847
+ outputType: "review",
6848
+ tags: ["domain", "api", "rest", "design"]
6849
+ },
6850
+ {
6851
+ id: "refactor-croc",
6852
+ name: "\u91CD\u6784\u5EFA\u8BAE\u9CC4",
6853
+ nameEn: "Refactoring Croc",
6854
+ category: "domain",
6855
+ description: "\u4EE3\u7801\u8D28\u91CF\u8BC4\u4F30\uFF1A\u6280\u672F\u503A/\u590D\u6742\u5EA6/\u91CD\u590D\u4EE3\u7801",
6856
+ sprite: "refactor",
6857
+ color: "#ec4899",
6858
+ priority: 13,
6859
+ triggers: {
6860
+ riskCategories: ["maintainability"],
6861
+ minEntities: 40
6862
+ },
6863
+ systemPrompt: "You are a refactoring expert. Identify code smells, duplicated logic, high cyclomatic complexity, god classes, feature envy, and suggest targeted refactoring strategies with minimal risk.",
6864
+ outputType: "review",
6865
+ tags: ["domain", "refactoring", "quality", "tech-debt"]
6866
+ },
6867
+ {
6868
+ id: "microservice-croc",
6869
+ name: "\u670D\u52A1\u6CBB\u7406\u9CC4",
6870
+ nameEn: "Microservice Governance Croc",
6871
+ category: "domain",
6872
+ description: "\u5FAE\u670D\u52A1\u67B6\u6784\u6CBB\u7406\uFF1A\u670D\u52A1\u8FB9\u754C/\u7194\u65AD/\u94FE\u8DEF\u8FFD\u8E2A",
6873
+ sprite: "microservice",
6874
+ color: "#7c3aed",
6875
+ priority: 14,
6876
+ triggers: {
6877
+ projectTypes: ["microservice", "monorepo"],
6878
+ custom: (ctx) => ctx.entityCount > 100
6879
+ },
6880
+ systemPrompt: "You are a microservice governance expert. Analyze service boundaries, inter-service communication, circuit breakers, distributed tracing, service mesh, and API gateway patterns.",
6881
+ outputType: "review",
6882
+ tags: ["domain", "microservice", "governance", "distributed"]
6883
+ }
6884
+ ];
6885
+ RoleRegistry = class {
6886
+ roles = /* @__PURE__ */ new Map();
6887
+ constructor() {
6888
+ for (const role of [...CORE_ROLES, ...LANGUAGE_ROLES, ...FRAMEWORK_ROLES, ...DOMAIN_ROLES]) {
6889
+ this.roles.set(role.id, role);
6890
+ }
6891
+ }
6892
+ /** Register a new role (community or custom) */
6893
+ register(role) {
6894
+ if (this.roles.has(role.id)) {
6895
+ throw new Error(`Role "${role.id}" is already registered`);
6896
+ }
6897
+ this.roles.set(role.id, role);
6898
+ }
6899
+ /** Unregister a role */
6900
+ unregister(id) {
6901
+ return this.roles.delete(id);
6902
+ }
6903
+ /** Get a role by ID */
6904
+ get(id) {
6905
+ return this.roles.get(id);
6906
+ }
6907
+ /** List all registered roles */
6908
+ list() {
6909
+ return Array.from(this.roles.values());
6910
+ }
6911
+ /** List roles by category */
6912
+ listByCategory(category) {
6913
+ return this.list().filter((r) => r.category === category);
6914
+ }
6915
+ /** Search roles by tags */
6916
+ search(query) {
6917
+ const q = query.toLowerCase();
6918
+ return this.list().filter(
6919
+ (r) => r.name.toLowerCase().includes(q) || r.nameEn.toLowerCase().includes(q) || r.description.toLowerCase().includes(q) || r.tags.some((t) => t.includes(q))
6920
+ );
6921
+ }
6922
+ /** Total number of registered roles */
6923
+ get size() {
6924
+ return this.roles.size;
6925
+ }
6926
+ };
6927
+ _registry = null;
6928
+ }
6929
+ });
6930
+
6327
6931
  // src/server/routes/studio.ts
6328
- function registerStudioRoutes(app, office) {
6932
+ function restoreStore(snapshotStore) {
6933
+ return snapshotStore?.load() ?? { ...EMPTY_STUDIO_STORE };
6934
+ }
6935
+ function registerStudioRoutes(app, office, snapshotStore) {
6936
+ const store = restoreStore(snapshotStore);
6937
+ let lastScanResult = null;
6938
+ if (store.graph) {
6939
+ office.log(`\u267B\uFE0F Restored Studio snapshot: ${store.graph.nodes.length} nodes, ${store.risks.length} risks`, "info");
6940
+ }
6941
+ const persistStore = () => {
6942
+ snapshotStore?.save(store);
6943
+ };
6944
+ const broadcastGraph = () => {
6945
+ if (!store.graph) return;
6946
+ office.broadcast("graph:update", {
6947
+ nodes: store.graph.nodes.map((n) => ({
6948
+ id: n.id,
6949
+ label: n.label,
6950
+ type: n.type,
6951
+ module: n.module,
6952
+ status: n.status
6953
+ })),
6954
+ edges: store.graph.edges.map((e) => ({
6955
+ source: e.source,
6956
+ target: e.target,
6957
+ relation: e.relation
6958
+ }))
6959
+ });
6960
+ };
6329
6961
  app.post("/api/studio/scan", async (req, reply) => {
6330
6962
  const { target, branch, useLlm } = req.body || {};
6331
6963
  if (!target || typeof target !== "string") {
@@ -6364,20 +6996,9 @@ function registerStudioRoutes(app, office) {
6364
6996
  store.risks = risks;
6365
6997
  store.scanTime = Date.now();
6366
6998
  store.source = target;
6367
- office.broadcast("graph:update", {
6368
- nodes: graph.nodes.map((n) => ({
6369
- id: n.id,
6370
- label: n.label,
6371
- type: n.type,
6372
- module: n.module,
6373
- status: n.status
6374
- })),
6375
- edges: graph.edges.map((e) => ({
6376
- source: e.source,
6377
- target: e.target,
6378
- relation: e.relation
6379
- }))
6380
- });
6999
+ lastScanResult = scanResult;
7000
+ persistStore();
7001
+ broadcastGraph();
6381
7002
  return {
6382
7003
  ok: true,
6383
7004
  project: graph.projectInfo,
@@ -6511,8 +7132,204 @@ function registerStudioRoutes(app, office) {
6511
7132
  source: store.source
6512
7133
  };
6513
7134
  });
7135
+ app.get("/api/studio/snapshots", async () => {
7136
+ const snapshots = snapshotStore?.list() ?? [];
7137
+ return {
7138
+ total: snapshots.length,
7139
+ snapshots
7140
+ };
7141
+ });
7142
+ app.post("/api/studio/snapshots/:id/load", async (req, reply) => {
7143
+ if (!snapshotStore) {
7144
+ reply.code(501).send({ error: "Snapshot persistence is not configured." });
7145
+ return;
7146
+ }
7147
+ const restored = snapshotStore.loadById(req.params.id);
7148
+ if (!restored) {
7149
+ reply.code(404).send({ error: "Snapshot not found." });
7150
+ return;
7151
+ }
7152
+ store.graph = restored.graph;
7153
+ store.risks = restored.risks;
7154
+ store.scanTime = restored.scanTime;
7155
+ store.source = restored.source;
7156
+ office.log(`\u267B\uFE0F Restored snapshot: ${store.source || "unknown source"}`, "info");
7157
+ broadcastGraph();
7158
+ return {
7159
+ ok: true,
7160
+ source: store.source,
7161
+ scanTime: store.scanTime,
7162
+ graph: store.graph ? {
7163
+ nodeCount: store.graph.nodes.length,
7164
+ edgeCount: store.graph.edges.length
7165
+ } : null,
7166
+ risks: store.risks.length
7167
+ };
7168
+ });
7169
+ app.post("/api/studio/snapshots/:id/rename", async (req, reply) => {
7170
+ if (!snapshotStore) {
7171
+ reply.code(501).send({ error: "Snapshot persistence is not configured." });
7172
+ return;
7173
+ }
7174
+ const name = req.body?.name?.trim();
7175
+ if (!name) {
7176
+ reply.code(400).send({ error: "Snapshot name is required." });
7177
+ return;
7178
+ }
7179
+ const renamed = snapshotStore.rename(req.params.id, name);
7180
+ if (!renamed) {
7181
+ reply.code(404).send({ error: "Snapshot not found." });
7182
+ return;
7183
+ }
7184
+ return {
7185
+ ok: true,
7186
+ snapshots: snapshotStore.list()
7187
+ };
7188
+ });
7189
+ app.post("/api/studio/snapshots/:id/pin", async (req, reply) => {
7190
+ if (!snapshotStore) {
7191
+ reply.code(501).send({ error: "Snapshot persistence is not configured." });
7192
+ return;
7193
+ }
7194
+ const pinned = Boolean(req.body?.pinned);
7195
+ const updated = snapshotStore.pin(req.params.id, pinned);
7196
+ if (!updated) {
7197
+ reply.code(404).send({ error: "Snapshot not found." });
7198
+ return;
7199
+ }
7200
+ return {
7201
+ ok: true,
7202
+ snapshots: snapshotStore.list()
7203
+ };
7204
+ });
7205
+ app.post("/api/studio/snapshots/:id/tags", async (req, reply) => {
7206
+ if (!snapshotStore) {
7207
+ reply.code(501).send({ error: "Snapshot persistence is not configured." });
7208
+ return;
7209
+ }
7210
+ const rawTags = Array.isArray(req.body?.tags) ? req.body.tags : [];
7211
+ const tags = [...new Set(
7212
+ rawTags.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean)
7213
+ )];
7214
+ const updated = snapshotStore.updateTags(req.params.id, tags);
7215
+ if (!updated) {
7216
+ reply.code(404).send({ error: "Snapshot not found." });
7217
+ return;
7218
+ }
7219
+ return {
7220
+ ok: true,
7221
+ snapshots: snapshotStore.list()
7222
+ };
7223
+ });
7224
+ app.post("/api/studio/snapshots/:id/delete", async (req, reply) => {
7225
+ if (!snapshotStore) {
7226
+ reply.code(501).send({ error: "Snapshot persistence is not configured." });
7227
+ return;
7228
+ }
7229
+ const deleted = snapshotStore.delete(req.params.id);
7230
+ if (!deleted) {
7231
+ reply.code(404).send({ error: "Snapshot not found." });
7232
+ return;
7233
+ }
7234
+ const current = snapshotStore.load();
7235
+ store.graph = current?.graph ?? null;
7236
+ store.risks = current?.risks ?? [];
7237
+ store.scanTime = current?.scanTime ?? 0;
7238
+ store.source = current?.source ?? "";
7239
+ if (store.graph) {
7240
+ broadcastGraph();
7241
+ }
7242
+ return {
7243
+ ok: true,
7244
+ hasCurrent: Boolean(current?.graph),
7245
+ snapshots: snapshotStore.list()
7246
+ };
7247
+ });
7248
+ app.get("/api/studio/roles", async (req) => {
7249
+ const { getRoleRegistry: getRoleRegistry2 } = await Promise.resolve().then(() => (init_role_registry(), role_registry_exports));
7250
+ const registry = getRoleRegistry2();
7251
+ const category = req.query.category;
7252
+ const search = req.query.search;
7253
+ let roles = registry.list();
7254
+ if (category) roles = roles.filter((r) => r.category === category);
7255
+ if (search) roles = registry.search(search);
7256
+ return {
7257
+ total: roles.length,
7258
+ categories: {
7259
+ core: roles.filter((r) => r.category === "core").length,
7260
+ language: roles.filter((r) => r.category === "language").length,
7261
+ framework: roles.filter((r) => r.category === "framework").length,
7262
+ domain: roles.filter((r) => r.category === "domain").length,
7263
+ community: roles.filter((r) => r.category === "community").length
7264
+ },
7265
+ roles: roles.map((r) => ({
7266
+ id: r.id,
7267
+ name: r.name,
7268
+ nameEn: r.nameEn,
7269
+ category: r.category,
7270
+ description: r.description,
7271
+ color: r.color,
7272
+ sprite: r.sprite,
7273
+ priority: r.priority,
7274
+ tags: r.tags,
7275
+ outputType: r.outputType
7276
+ }))
7277
+ };
7278
+ });
7279
+ app.post("/api/studio/summon", async (_req, reply) => {
7280
+ if (!lastScanResult) {
7281
+ reply.code(400).send({ error: "No project scanned. Run /api/studio/scan first." });
7282
+ return;
7283
+ }
7284
+ const riskCategories = [...new Set(store.risks.map((r) => r.category))];
7285
+ const plan = await office.summonForProject(lastScanResult, riskCategories);
7286
+ return {
7287
+ ok: true,
7288
+ totalAgents: office.getAgents().length,
7289
+ coreAgents: 6,
7290
+ dynamicAgents: office.getAgents().length - 6,
7291
+ reasoning: plan.reasoning,
7292
+ agents: office.getAgents().map((a) => ({
7293
+ id: a.id,
7294
+ name: a.name,
7295
+ role: a.role,
7296
+ sprite: a.sprite,
7297
+ status: a.status,
7298
+ category: a.category,
7299
+ color: a.color,
7300
+ description: a.description
7301
+ })),
7302
+ context: {
7303
+ languages: plan.context.languages,
7304
+ frameworks: plan.context.frameworks,
7305
+ projectType: plan.context.projectType,
7306
+ entityCount: plan.context.entityCount,
7307
+ hasAPIs: plan.context.hasAPIs,
7308
+ hasModels: plan.context.hasModels,
7309
+ hasFrontend: plan.context.hasFrontend,
7310
+ hasDocker: plan.context.hasDocker,
7311
+ hasCI: plan.context.hasCI
7312
+ }
7313
+ };
7314
+ });
7315
+ app.get("/api/studio/agents", async () => {
7316
+ const info = office.getSummonPlan();
7317
+ return {
7318
+ ...info,
7319
+ agents: info.agents.map((a) => ({
7320
+ id: a.id,
7321
+ name: a.name,
7322
+ role: a.role,
7323
+ sprite: a.sprite,
7324
+ status: a.status,
7325
+ currentTask: a.currentTask,
7326
+ category: a.category,
7327
+ color: a.color,
7328
+ description: a.description
7329
+ }))
7330
+ };
7331
+ });
6514
7332
  }
6515
- var store;
6516
7333
  var init_studio = __esm({
6517
7334
  "src/server/routes/studio.ts"() {
6518
7335
  "use strict";
@@ -6520,12 +7337,156 @@ var init_studio = __esm({
6520
7337
  init_github_cloner();
6521
7338
  init_graph();
6522
7339
  init_insight();
6523
- store = {
6524
- graph: null,
6525
- risks: [],
6526
- scanTime: 0,
6527
- source: ""
6528
- };
7340
+ init_studio_store();
7341
+ }
7342
+ });
7343
+
7344
+ // src/agents/task-router.ts
7345
+ var task_router_exports = {};
7346
+ __export(task_router_exports, {
7347
+ buildMatchContext: () => buildMatchContext,
7348
+ planSummon: () => planSummon
7349
+ });
7350
+ function buildMatchContext(scan2) {
7351
+ const languages = scan2.languages;
7352
+ const frameworks = scan2.frameworks.map((f) => f.name);
7353
+ const frontendFrameworks = ["React", "Vue", "Angular", "Svelte", "Next.js", "Nuxt"];
7354
+ const backendFrameworks = ["Express", "Fastify", "NestJS", "Django", "Flask", "Spring Boot", "Gin", "Actix"];
7355
+ const hasFE = frameworks.some((f) => frontendFrameworks.includes(f));
7356
+ const hasBE = frameworks.some((f) => backendFrameworks.includes(f));
7357
+ const projectType = hasFE && hasBE ? "fullstack" : hasFE ? "frontend" : hasBE ? "backend" : "unknown";
7358
+ const entityTypes = new Set(scan2.entities.map((e) => e.type));
7359
+ const hasModels = entityTypes.has("model") || entityTypes.has("class");
7360
+ const hasAPIs = entityTypes.has("api") || entityTypes.has("route");
7361
+ const hasFrontend = hasFE || (languages["html"] ?? 0) > 10 || (languages["css"] ?? 0) > 5;
7362
+ const allPaths = scan2.files.map((f) => f.path);
7363
+ const hasDocker = allPaths.some(
7364
+ (p) => p.includes("Dockerfile") || p.includes("docker-compose")
7365
+ );
7366
+ const hasCI = allPaths.some(
7367
+ (p) => p.includes(".github/workflows") || p.includes(".gitlab-ci") || p.includes("Jenkinsfile")
7368
+ );
7369
+ const riskCategories = [];
7370
+ return {
7371
+ languages,
7372
+ frameworks,
7373
+ projectType,
7374
+ fileCount: scan2.files.length,
7375
+ entityCount: scan2.entities.length,
7376
+ riskCategories,
7377
+ hasModels,
7378
+ hasAPIs,
7379
+ hasFrontend,
7380
+ hasDocker,
7381
+ hasCI
7382
+ };
7383
+ }
7384
+ function matchesTriggers(trigger, ctx) {
7385
+ const reasons = [];
7386
+ let score = 0;
7387
+ let checks = 0;
7388
+ if (trigger.languages && trigger.languages.length > 0) {
7389
+ checks++;
7390
+ const matched = trigger.languages.filter((l) => (ctx.languages[l] ?? 0) > 0);
7391
+ if (matched.length > 0) {
7392
+ score++;
7393
+ reasons.push(`\u8BED\u8A00\u5339\u914D: ${matched.join(", ")}`);
7394
+ }
7395
+ }
7396
+ if (trigger.frameworks && trigger.frameworks.length > 0) {
7397
+ checks++;
7398
+ const matched = trigger.frameworks.filter(
7399
+ (f) => ctx.frameworks.some((cf) => cf.toLowerCase() === f.toLowerCase())
7400
+ );
7401
+ if (matched.length > 0) {
7402
+ score++;
7403
+ reasons.push(`\u6846\u67B6\u5339\u914D: ${matched.join(", ")}`);
7404
+ }
7405
+ }
7406
+ if (trigger.projectTypes && trigger.projectTypes.length > 0) {
7407
+ checks++;
7408
+ if (trigger.projectTypes.includes(ctx.projectType)) {
7409
+ score++;
7410
+ reasons.push(`\u9879\u76EE\u7C7B\u578B\u5339\u914D: ${ctx.projectType}`);
7411
+ }
7412
+ }
7413
+ if (trigger.minEntities !== void 0) {
7414
+ checks++;
7415
+ if (ctx.entityCount >= trigger.minEntities) {
7416
+ score++;
7417
+ reasons.push(`\u5B9E\u4F53\u6570\u91CF\u6EE1\u8DB3: ${ctx.entityCount} \u2265 ${trigger.minEntities}`);
7418
+ }
7419
+ }
7420
+ if (trigger.riskCategories && trigger.riskCategories.length > 0) {
7421
+ checks++;
7422
+ const matched = trigger.riskCategories.filter((r) => ctx.riskCategories.includes(r));
7423
+ if (matched.length > 0) {
7424
+ score++;
7425
+ reasons.push(`\u98CE\u9669\u7C7B\u522B\u5339\u914D: ${matched.join(", ")}`);
7426
+ }
7427
+ }
7428
+ if (trigger.custom) {
7429
+ checks++;
7430
+ try {
7431
+ if (trigger.custom(ctx)) {
7432
+ score++;
7433
+ reasons.push("\u81EA\u5B9A\u4E49\u6761\u4EF6\u6EE1\u8DB3");
7434
+ }
7435
+ } catch {
7436
+ }
7437
+ }
7438
+ const matches = checks === 0 ? false : score > 0;
7439
+ const confidence = checks > 0 ? score / checks : 0;
7440
+ return {
7441
+ matches,
7442
+ reason: reasons.join("; ") || "\u65E0\u5339\u914D",
7443
+ confidence
7444
+ };
7445
+ }
7446
+ function planSummon(scan2, maxRoles = 8, riskCategories = []) {
7447
+ const registry = getRoleRegistry();
7448
+ const ctx = buildMatchContext(scan2);
7449
+ ctx.riskCategories = riskCategories;
7450
+ const summoned = [];
7451
+ const reasoning = [];
7452
+ const allRoles = registry.list();
7453
+ const coreRoles = allRoles.filter((r) => r.category === "core");
7454
+ for (const role of coreRoles) {
7455
+ summoned.push({ role, reason: "\u6838\u5FC3\u89D2\u8272(\u59CB\u7EC8\u53EC\u5524)", confidence: 1 });
7456
+ reasoning.push(`\u2705 ${role.name} \u2014 \u6838\u5FC3\u89D2\u8272`);
7457
+ }
7458
+ const nonCore = allRoles.filter((r) => r.category !== "core");
7459
+ const candidates = [];
7460
+ for (const role of nonCore) {
7461
+ const result = matchesTriggers(role.triggers, ctx);
7462
+ if (result.matches) {
7463
+ candidates.push({
7464
+ role,
7465
+ reason: result.reason,
7466
+ confidence: result.confidence
7467
+ });
7468
+ }
7469
+ }
7470
+ candidates.sort((a, b) => {
7471
+ if (a.role.priority !== b.role.priority) return a.role.priority - b.role.priority;
7472
+ return b.confidence - a.confidence;
7473
+ });
7474
+ const selected = candidates.slice(0, maxRoles);
7475
+ for (const s of selected) {
7476
+ summoned.push(s);
7477
+ reasoning.push(`\u{1F40A} ${s.role.name} \u2014 ${s.reason} (\u7F6E\u4FE1\u5EA6: ${(s.confidence * 100).toFixed(0)}%)`);
7478
+ }
7479
+ const skipped = candidates.slice(maxRoles);
7480
+ for (const s of skipped) {
7481
+ reasoning.push(`\u23ED\uFE0F ${s.role.name} \u2014 \u5339\u914D\u4F46\u8D85\u51FA\u9650\u5236 (\u7F6E\u4FE1\u5EA6: ${(s.confidence * 100).toFixed(0)}%)`);
7482
+ }
7483
+ return { roles: summoned, reasoning, context: ctx };
7484
+ }
7485
+ var init_task_router = __esm({
7486
+ "src/agents/task-router.ts"() {
7487
+ "use strict";
7488
+ init_esm_shims();
7489
+ init_role_registry();
6529
7490
  }
6530
7491
  });
6531
7492
 
@@ -6728,14 +7689,14 @@ var runtime_bootstrap_exports = {};
6728
7689
  __export(runtime_bootstrap_exports, {
6729
7690
  createRuntimeBootstrap: () => createRuntimeBootstrap
6730
7691
  });
6731
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
6732
- import { dirname as dirname6, join as join16 } from "path";
7692
+ import { existsSync as existsSync19, mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "fs";
7693
+ import { dirname as dirname7, join as join16 } from "path";
6733
7694
  function ensureFile(filePath, content, force) {
6734
- if (existsSync18(filePath) && !force) {
7695
+ if (existsSync19(filePath) && !force) {
6735
7696
  return false;
6736
7697
  }
6737
- mkdirSync11(dirname6(filePath), { recursive: true });
6738
- writeFileSync10(filePath, content, "utf-8");
7698
+ mkdirSync12(dirname7(filePath), { recursive: true });
7699
+ writeFileSync11(filePath, content, "utf-8");
6739
7700
  return true;
6740
7701
  }
6741
7702
  function createRuntimeBootstrap(config) {
@@ -6907,7 +7868,7 @@ var init_croc_office = __esm({
6907
7868
  { id: "planner-croc", name: "\u89C4\u5212\u9CC4", role: "planner", sprite: "planner", status: "idle", tokensUsed: 0 },
6908
7869
  { id: "reporter-croc", name: "\u6C47\u62A5\u9CC4", role: "reporter", sprite: "reporter", status: "idle", tokensUsed: 0 }
6909
7870
  ];
6910
- CrocOffice = class {
7871
+ CrocOffice = class _CrocOffice {
6911
7872
  config;
6912
7873
  cwd;
6913
7874
  clients = /* @__PURE__ */ new Set();
@@ -6919,6 +7880,10 @@ var init_croc_office = __esm({
6919
7880
  lastExecutionMetrics = null;
6920
7881
  lastExecutionQuality = null;
6921
7882
  lastReports = [];
7883
+ static ACTIVE_AGENT_STATUSES = /* @__PURE__ */ new Set([
7884
+ "working",
7885
+ "thinking"
7886
+ ]);
6922
7887
  constructor(config, cwd) {
6923
7888
  this.config = config;
6924
7889
  this.cwd = cwd;
@@ -6953,10 +7918,34 @@ var init_croc_office = __esm({
6953
7918
  updateAgent(id, update) {
6954
7919
  const agent = this.agents.find((a) => a.id === id);
6955
7920
  if (agent) {
7921
+ const wasActive = this.isAgentActive(agent.status);
6956
7922
  Object.assign(agent, update);
7923
+ const isActive = this.isAgentActive(agent.status);
6957
7924
  this.broadcast("agent:update", this.agents);
7925
+ if (!wasActive && isActive) {
7926
+ this.broadcast("agent:assigned", {
7927
+ id: agent.id,
7928
+ name: agent.name,
7929
+ role: agent.role,
7930
+ status: agent.status,
7931
+ currentTask: agent.currentTask ?? null,
7932
+ at: Date.now()
7933
+ });
7934
+ } else if (wasActive && !isActive) {
7935
+ this.broadcast("agent:released", {
7936
+ id: agent.id,
7937
+ name: agent.name,
7938
+ role: agent.role,
7939
+ status: agent.status,
7940
+ currentTask: agent.currentTask ?? null,
7941
+ at: Date.now()
7942
+ });
7943
+ }
6958
7944
  }
6959
7945
  }
7946
+ isAgentActive(status) {
7947
+ return _CrocOffice.ACTIVE_AGENT_STATUSES.has(status);
7948
+ }
6960
7949
  isRunning() {
6961
7950
  return this.running;
6962
7951
  }
@@ -7034,13 +8023,13 @@ var init_croc_office = __esm({
7034
8023
  const fullResult = await pipeline.run(["scan", "er-diagram", "api-chain", "plan", "codegen"]);
7035
8024
  this.lastPipelineResult = fullResult;
7036
8025
  this.lastGeneratedFiles = fullResult.generatedFiles;
7037
- const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync14 } = await import("fs");
7038
- const { dirname: dirname8 } = await import("path");
8026
+ const { writeFileSync: writeFileSync14, mkdirSync: mkdirSync15 } = await import("fs");
8027
+ const { dirname: dirname9 } = await import("path");
7039
8028
  let filesWritten = 0;
7040
8029
  for (const file of fullResult.generatedFiles) {
7041
8030
  const fullPath = resolvePath(this.cwd, file.filePath);
7042
- mkdirSync14(dirname8(fullPath), { recursive: true });
7043
- writeFileSync13(fullPath, file.content, "utf-8");
8031
+ mkdirSync15(dirname9(fullPath), { recursive: true });
8032
+ writeFileSync14(fullPath, file.content, "utf-8");
7044
8033
  filesWritten++;
7045
8034
  }
7046
8035
  this.updateNodeStatus("controller", "passed");
@@ -7086,12 +8075,75 @@ var init_croc_office = __esm({
7086
8075
  /** Reset all agents to idle */
7087
8076
  resetAgents() {
7088
8077
  for (const agent of this.agents) {
8078
+ const wasActive = this.isAgentActive(agent.status);
7089
8079
  agent.status = "idle";
7090
8080
  agent.currentTask = void 0;
7091
8081
  agent.progress = void 0;
8082
+ if (wasActive) {
8083
+ this.broadcast("agent:released", {
8084
+ id: agent.id,
8085
+ name: agent.name,
8086
+ role: agent.role,
8087
+ status: agent.status,
8088
+ currentTask: null,
8089
+ at: Date.now()
8090
+ });
8091
+ }
7092
8092
  }
7093
8093
  this.broadcast("agent:update", this.agents);
7094
8094
  }
8095
+ /**
8096
+ * Dynamically summon crocs based on a project scan result.
8097
+ * Core 6 are always present; additional expert crocs are added based on project characteristics.
8098
+ */
8099
+ async summonForProject(scan2, riskCategories = []) {
8100
+ const { planSummon: planSummon2 } = await Promise.resolve().then(() => (init_task_router(), task_router_exports));
8101
+ const plan = planSummon2(scan2, 8, riskCategories);
8102
+ const coreIds = new Set(DEFAULT_AGENTS.map((a) => a.id));
8103
+ this.agents = DEFAULT_AGENTS.map((a) => ({ ...a }));
8104
+ for (const summoned of plan.roles) {
8105
+ if (coreIds.has(summoned.role.id)) continue;
8106
+ const agent = {
8107
+ id: summoned.role.id,
8108
+ name: summoned.role.name,
8109
+ role: summoned.role.id.replace(/-croc$/, ""),
8110
+ sprite: summoned.role.sprite,
8111
+ status: "idle",
8112
+ tokensUsed: 0,
8113
+ category: summoned.role.category,
8114
+ color: summoned.role.color,
8115
+ description: summoned.role.description
8116
+ };
8117
+ this.agents.push(agent);
8118
+ }
8119
+ this.broadcast("agent:update", this.agents);
8120
+ for (const line of plan.reasoning) {
8121
+ this.log(line);
8122
+ }
8123
+ this.log(`\u{1F40A} \u5171\u53EC\u5524 ${this.agents.length} \u4E2A\u9CC4\u9C7C\u4E13\u5BB6 (${plan.roles.length - DEFAULT_AGENTS.length} \u4E2A\u52A8\u6001\u89D2\u8272)`);
8124
+ const dynamicAgents = this.agents.filter((a) => !coreIds.has(a.id));
8125
+ for (let i = 0; i < dynamicAgents.length; i++) {
8126
+ const agent = dynamicAgents[i];
8127
+ setTimeout(() => {
8128
+ this.updateAgent(agent.id, { status: "working", currentTask: "\u5206\u6790\u9879\u76EE\u4E2D\u2026" });
8129
+ setTimeout(() => {
8130
+ this.updateAgent(agent.id, { status: "idle", currentTask: void 0 });
8131
+ }, 2e3 + Math.random() * 1e3);
8132
+ }, 300 * i);
8133
+ }
8134
+ return plan;
8135
+ }
8136
+ /** Get the current summon plan context */
8137
+ getSummonPlan() {
8138
+ const coreIds = new Set(DEFAULT_AGENTS.map((a) => a.id));
8139
+ const coreCount = this.agents.filter((a) => coreIds.has(a.id)).length;
8140
+ return {
8141
+ agentCount: this.agents.length,
8142
+ coreCount,
8143
+ dynamicCount: this.agents.length - coreCount,
8144
+ agents: this.agents
8145
+ };
8146
+ }
7095
8147
  /** Get last pipeline result */
7096
8148
  getLastPipelineResult() {
7097
8149
  return this.lastPipelineResult;
@@ -7124,14 +8176,14 @@ var init_croc_office = __esm({
7124
8176
  let backendStatus;
7125
8177
  try {
7126
8178
  const { resolve: resolvePath } = await import("path");
7127
- const { existsSync: existsSync21 } = await import("fs");
8179
+ const { existsSync: existsSync22 } = await import("fs");
7128
8180
  const { createExecutionCoordinator: createExecutionCoordinator2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
7129
8181
  const { createBackendManager: createBackendManager2 } = await Promise.resolve().then(() => (init_backend_manager(), backend_manager_exports));
7130
8182
  const { createRuntimeBootstrap: createRuntimeBootstrap2 } = await Promise.resolve().then(() => (init_runtime_bootstrap(), runtime_bootstrap_exports));
7131
8183
  const { createAuthProvisioner: createAuthProvisioner2 } = await Promise.resolve().then(() => (init_auth_provisioner(), auth_provisioner_exports));
7132
8184
  const { buildExecutionQualityGate: buildExecutionQualityGate2 } = await Promise.resolve().then(() => (init_quality_gate(), quality_gate_exports));
7133
8185
  const { categorizeFailure: categorizeFailure2 } = await Promise.resolve().then(() => (init_self_healing(), self_healing_exports));
7134
- const testFiles = this.lastGeneratedFiles.map((f) => resolvePath(this.cwd, f.filePath)).filter((f) => existsSync21(f));
8186
+ const testFiles = this.lastGeneratedFiles.map((f) => resolvePath(this.cwd, f.filePath)).filter((f) => existsSync22(f));
7135
8187
  if (testFiles.length === 0) {
7136
8188
  this.log("\u26A0\uFE0F No test files found on disk", "warn");
7137
8189
  return { ok: false, task: "execute", duration: Date.now() - start, error: "No test files found on disk" };
@@ -7258,12 +8310,12 @@ var init_croc_office = __esm({
7258
8310
  });
7259
8311
  this.lastReports = reports;
7260
8312
  const { resolve: resolvePath } = await import("path");
7261
- const { writeFileSync: writeFileSync13, mkdirSync: mkdirSync14 } = await import("fs");
8313
+ const { writeFileSync: writeFileSync14, mkdirSync: mkdirSync15 } = await import("fs");
7262
8314
  const outDir = resolvePath(this.cwd, this.config.outDir || "./opencroc-output");
7263
- mkdirSync14(outDir, { recursive: true });
8315
+ mkdirSync15(outDir, { recursive: true });
7264
8316
  for (const report2 of reports) {
7265
8317
  const fullPath = resolvePath(outDir, report2.filename);
7266
- writeFileSync13(fullPath, report2.content, "utf-8");
8318
+ writeFileSync14(fullPath, report2.content, "utf-8");
7267
8319
  this.log(`\u{1F4C4} Generated ${report2.format} report: ${report2.filename}`);
7268
8320
  }
7269
8321
  this.updateAgent("reporter-croc", { status: "done", currentTask: `${reports.length} reports generated`, progress: 100 });
@@ -7508,40 +8560,45 @@ import Fastify from "fastify";
7508
8560
  import fastifyStatic from "@fastify/static";
7509
8561
  import fastifyWebsocket from "@fastify/websocket";
7510
8562
  import { fileURLToPath as fileURLToPath2 } from "url";
7511
- import { dirname as dirname7, join as join17, resolve as resolve11 } from "path";
7512
- import { existsSync as existsSync19 } from "fs";
8563
+ import { dirname as dirname8, join as join17, resolve as resolve11 } from "path";
8564
+ import { existsSync as existsSync20 } from "fs";
7513
8565
  async function startServer(opts) {
7514
8566
  const app = Fastify({ logger: false });
7515
8567
  await app.register(fastifyWebsocket);
7516
8568
  const webDir = resolve11(__dirname2, "../web");
7517
- if (existsSync19(webDir)) {
8569
+ if (existsSync20(webDir)) {
7518
8570
  await app.register(fastifyStatic, {
7519
8571
  root: webDir,
7520
8572
  prefix: "/",
7521
- decorateReply: false
8573
+ decorateReply: false,
8574
+ index: false
7522
8575
  });
7523
8576
  }
7524
8577
  const office = new CrocOffice(opts.config, opts.cwd);
8578
+ const snapshotStore = new FileStudioSnapshotStore(resolve11(opts.cwd, ".opencroc/studio-snapshot.json"));
7525
8579
  registerProjectRoutes(app, office);
7526
8580
  registerAgentRoutes(app, office);
7527
- registerStudioRoutes(app, office);
8581
+ registerStudioRoutes(app, office, snapshotStore);
7528
8582
  app.register(async (fastify) => {
7529
8583
  fastify.get("/ws", { websocket: true }, (socket) => {
7530
8584
  office.addClient(socket);
7531
8585
  socket.on("close", () => office.removeClient(socket));
7532
8586
  });
7533
8587
  });
8588
+ app.get("/index-studio.html", (_req, reply) => {
8589
+ reply.redirect("/studio");
8590
+ });
8591
+ app.get("/index-v2-pixel.html", (_req, reply) => {
8592
+ reply.redirect("/pixel");
8593
+ });
7534
8594
  app.setNotFoundHandler((req, reply) => {
7535
8595
  if (req.url.startsWith("/api/")) {
7536
8596
  reply.code(404).send({ error: "Not found" });
7537
8597
  return;
7538
8598
  }
7539
- const studioPath = join17(webDir, "index-studio.html");
7540
- const indexPath = join17(webDir, "index.html");
7541
- if (existsSync19(studioPath)) {
7542
- reply.sendFile("index-studio.html");
7543
- } else if (existsSync19(indexPath)) {
7544
- reply.sendFile("index.html");
8599
+ const builtIndexPath = join17(webDir, "dist", "index.html");
8600
+ if (existsSync20(builtIndexPath)) {
8601
+ reply.sendFile("dist/index.html");
7545
8602
  } else {
7546
8603
  reply.code(200).header("content-type", "text/html").send(getEmbeddedHtml());
7547
8604
  }
@@ -7691,8 +8748,9 @@ var init_server = __esm({
7691
8748
  init_agents();
7692
8749
  init_studio();
7693
8750
  init_croc_office();
8751
+ init_studio_store();
7694
8752
  __filename2 = fileURLToPath2(import.meta.url);
7695
- __dirname2 = dirname7(__filename2);
8753
+ __dirname2 = dirname8(__filename2);
7696
8754
  }
7697
8755
  });
7698
8756