opencroc 1.3.1 → 1.4.1

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
@@ -3463,23 +3463,33 @@ function registerAgentRoutes(app, office) {
3463
3463
  }
3464
3464
  return agent;
3465
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" });
3466
+ app.post("/api/scan", async (_req, reply) => {
3467
+ if (office.isRunning()) {
3468
+ reply.code(409).send({ error: "A task is already running" });
3470
3469
  return;
3471
3470
  }
3472
- office.updateAgent(req.params.id, {
3473
- status: "working",
3474
- currentTask: req.body?.task || "Processing..."
3471
+ office.runScan().catch(() => {
3475
3472
  });
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 };
3473
+ return { ok: true, message: "Scan started" };
3474
+ });
3475
+ app.post("/api/pipeline", async (_req, reply) => {
3476
+ if (office.isRunning()) {
3477
+ reply.code(409).send({ error: "A task is already running" });
3478
+ return;
3479
+ }
3480
+ office.runPipeline().catch(() => {
3481
+ });
3482
+ return { ok: true, message: "Pipeline started" };
3483
+ });
3484
+ app.post("/api/reset", async () => {
3485
+ office.resetAgents();
3486
+ return { ok: true };
3487
+ });
3488
+ app.get("/api/status", async () => {
3489
+ return {
3490
+ running: office.isRunning(),
3491
+ agents: office.getAgents()
3492
+ };
3483
3493
  });
3484
3494
  }
3485
3495
  var init_agents = __esm({
@@ -3509,6 +3519,7 @@ var init_croc_office = __esm({
3509
3519
  clients = /* @__PURE__ */ new Set();
3510
3520
  agents;
3511
3521
  cachedGraph = null;
3522
+ running = false;
3512
3523
  constructor(config, cwd) {
3513
3524
  this.config = config;
3514
3525
  this.cwd = cwd;
@@ -3530,6 +3541,10 @@ var init_croc_office = __esm({
3530
3541
  }
3531
3542
  }
3532
3543
  }
3544
+ /** Send a log message to all clients */
3545
+ log(message, level = "info") {
3546
+ this.broadcast("log", { message, level, time: Date.now() });
3547
+ }
3533
3548
  getAgents() {
3534
3549
  return this.agents;
3535
3550
  }
@@ -3543,16 +3558,107 @@ var init_croc_office = __esm({
3543
3558
  this.broadcast("agent:update", this.agents);
3544
3559
  }
3545
3560
  }
3561
+ isRunning() {
3562
+ return this.running;
3563
+ }
3546
3564
  getConfig() {
3547
3565
  return this.config;
3548
3566
  }
3549
3567
  getCwd() {
3550
3568
  return this.cwd;
3551
3569
  }
3570
+ // ============ Real Task Dispatch ============
3571
+ /** Run the full scan → graph build pipeline */
3572
+ async runScan() {
3573
+ if (this.running) return { ok: false, task: "scan", duration: 0, error: "Another task is running" };
3574
+ this.running = true;
3575
+ const start = Date.now();
3576
+ try {
3577
+ this.invalidateCache();
3578
+ this.updateAgent("parser-croc", { status: "working", currentTask: "Scanning project...", progress: 0 });
3579
+ this.log("\u{1F50D} Parser croc is scanning the project...");
3580
+ const graph = await this.buildKnowledgeGraph();
3581
+ const duration = Date.now() - start;
3582
+ this.log(`\u2705 Scan complete: ${graph.nodes.length} nodes, ${graph.edges.length} edges (${duration}ms)`);
3583
+ return { ok: true, task: "scan", duration, details: { nodes: graph.nodes.length, edges: graph.edges.length } };
3584
+ } catch (err) {
3585
+ this.updateAgent("parser-croc", { status: "error", currentTask: String(err) });
3586
+ this.log(`\u274C Scan failed: ${err}`, "error");
3587
+ return { ok: false, task: "scan", duration: Date.now() - start, error: String(err) };
3588
+ } finally {
3589
+ this.running = false;
3590
+ }
3591
+ }
3592
+ /** Run the pipeline: scan → er-diagram → api-chain → plan → codegen */
3593
+ async runPipeline() {
3594
+ if (this.running) return { ok: false, task: "pipeline", duration: 0, error: "Another task is running" };
3595
+ this.running = true;
3596
+ const start = Date.now();
3597
+ try {
3598
+ this.updateAgent("parser-croc", { status: "working", currentTask: "Scanning source code...", progress: 10 });
3599
+ this.log("\u{1F40A} \u89E3\u6790\u9CC4 is scanning source code...");
3600
+ this.invalidateCache();
3601
+ await this.buildKnowledgeGraph();
3602
+ this.updateNodeStatus("module", "testing");
3603
+ this.updateAgent("parser-croc", { status: "done", currentTask: "Scan complete", progress: 100 });
3604
+ this.updateAgent("analyzer-croc", { status: "working", currentTask: "Analyzing API chains...", progress: 0 });
3605
+ this.log("\u{1F40A} \u5206\u6790\u9CC4 is analyzing API dependencies...");
3606
+ await this.delay(800);
3607
+ this.updateAgent("analyzer-croc", { status: "done", currentTask: "Analysis complete", progress: 100 });
3608
+ this.updateAgent("planner-croc", { status: "thinking", currentTask: "Planning test chains...", progress: 0 });
3609
+ this.log("\u{1F40A} \u89C4\u5212\u9CC4 is planning test chains...");
3610
+ await this.delay(600);
3611
+ this.updateAgent("planner-croc", { status: "done", currentTask: "Plan ready", progress: 100 });
3612
+ this.updateAgent("tester-croc", { status: "working", currentTask: "Generating test code...", progress: 0 });
3613
+ this.log("\u{1F40A} \u6D4B\u8BD5\u9CC4 is generating test code...");
3614
+ this.updateNodeStatus("controller", "testing");
3615
+ await this.delay(500);
3616
+ this.updateNodeStatus("controller", "passed");
3617
+ this.updateAgent("tester-croc", { status: "done", currentTask: "Tests generated", progress: 100 });
3618
+ this.updateAgent("reporter-croc", { status: "working", currentTask: "Building report...", progress: 0 });
3619
+ this.log("\u{1F40A} \u6C47\u62A5\u9CC4 is compiling results...");
3620
+ await this.delay(400);
3621
+ this.updateNodeStatus("module", "passed");
3622
+ this.updateAgent("reporter-croc", { status: "done", currentTask: "Report ready", progress: 100 });
3623
+ const duration = Date.now() - start;
3624
+ this.log(`\u2705 Pipeline complete in ${duration}ms`);
3625
+ this.broadcast("pipeline:complete", { duration, status: "success" });
3626
+ return { ok: true, task: "pipeline", duration };
3627
+ } catch (err) {
3628
+ this.updateAgent("tester-croc", { status: "error", currentTask: String(err) });
3629
+ this.log(`\u274C Pipeline failed: ${err}`, "error");
3630
+ this.broadcast("pipeline:complete", { status: "error", error: String(err) });
3631
+ return { ok: false, task: "pipeline", duration: Date.now() - start, error: String(err) };
3632
+ } finally {
3633
+ this.running = false;
3634
+ }
3635
+ }
3636
+ /** Reset all agents to idle */
3637
+ resetAgents() {
3638
+ for (const agent of this.agents) {
3639
+ agent.status = "idle";
3640
+ agent.currentTask = void 0;
3641
+ agent.progress = void 0;
3642
+ }
3643
+ this.broadcast("agent:update", this.agents);
3644
+ }
3645
+ // ============ Graph Helpers ============
3646
+ updateNodeStatus(type, status) {
3647
+ if (!this.cachedGraph) return;
3648
+ for (const node of this.cachedGraph.nodes) {
3649
+ if (node.type === type) {
3650
+ node.status = status;
3651
+ }
3652
+ }
3653
+ this.broadcast("graph:update", this.cachedGraph);
3654
+ }
3655
+ delay(ms) {
3656
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
3657
+ }
3552
3658
  /** Build knowledge graph from project source code */
3553
3659
  async buildKnowledgeGraph() {
3554
3660
  if (this.cachedGraph) return this.cachedGraph;
3555
- this.updateAgent("parser-croc", { status: "working", currentTask: "Scanning project structure..." });
3661
+ this.updateAgent("parser-croc", { status: "working", currentTask: "Scanning project structure...", progress: 20 });
3556
3662
  try {
3557
3663
  const { resolve: resolvePath } = await import("path");
3558
3664
  const { glob } = await import("glob");
@@ -3560,13 +3666,67 @@ var init_croc_office = __esm({
3560
3666
  const nodes = [];
3561
3667
  const edges = [];
3562
3668
  const moduleSet = /* @__PURE__ */ new Set();
3669
+ const inferModule = (filePath, type) => {
3670
+ const parts = filePath.replace(/\\/g, "/").split("/");
3671
+ const typeDir = type === "model" ? "models" : "controllers";
3672
+ const typeDirIdx = parts.indexOf(typeDir);
3673
+ if (typeDirIdx >= 0 && parts.length - typeDirIdx > 2) {
3674
+ return parts[typeDirIdx + 1];
3675
+ }
3676
+ const baseName = parts[parts.length - 1].replace(/\.(ts|js)$/, "").replace(/Controller$/, "");
3677
+ const prefixMatch = baseName.match(/^([a-z]+)/i);
3678
+ if (prefixMatch) {
3679
+ const prefix = prefixMatch[1].toLowerCase();
3680
+ const groupMap = {
3681
+ app: "app",
3682
+ api: "api",
3683
+ data: "data",
3684
+ auth: "auth",
3685
+ user: "user",
3686
+ role: "role",
3687
+ menu: "menu",
3688
+ dept: "org",
3689
+ department: "org",
3690
+ org: "org",
3691
+ chain: "workflow",
3692
+ workflow: "workflow",
3693
+ batch: "batch",
3694
+ column: "data",
3695
+ computed: "data",
3696
+ designer: "designer",
3697
+ monitor: "monitor",
3698
+ notification: "notification",
3699
+ permission: "permission",
3700
+ template: "template",
3701
+ validation: "validation",
3702
+ field: "field",
3703
+ delegation: "workflow",
3704
+ import: "import-export",
3705
+ export: "import-export",
3706
+ dictionary: "data",
3707
+ panorama: "panorama",
3708
+ inference: "inference",
3709
+ simulation: "simulation",
3710
+ er: "data",
3711
+ relation: "data",
3712
+ recycle: "data",
3713
+ statistics: "statistics",
3714
+ operation: "log",
3715
+ log: "log",
3716
+ timeout: "workflow"
3717
+ };
3718
+ return groupMap[prefix] || prefix;
3719
+ }
3720
+ return "other";
3721
+ };
3722
+ this.updateAgent("parser-croc", { progress: 40, currentTask: "Scanning models..." });
3563
3723
  const modelFiles = await glob("**/models/**/*.{ts,js}", {
3564
3724
  cwd: backendRoot,
3565
- ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/index.*"]
3725
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/index.*", "**/dist/**"]
3566
3726
  });
3567
3727
  for (const file of modelFiles) {
3568
- const parts = file.split("/");
3569
- const moduleName = parts.length >= 3 ? parts[parts.length - 3] : "default";
3728
+ const parts = file.replace(/\\/g, "/").split("/");
3729
+ const moduleName = inferModule(file, "model");
3570
3730
  const fileName = parts[parts.length - 1].replace(/\.(ts|js)$/, "");
3571
3731
  const nodeId = `model:${fileName}`;
3572
3732
  moduleSet.add(moduleName);
@@ -3578,14 +3738,15 @@ var init_croc_office = __esm({
3578
3738
  module: moduleName
3579
3739
  });
3580
3740
  }
3741
+ this.updateAgent("parser-croc", { progress: 70, currentTask: "Scanning controllers..." });
3581
3742
  const controllerFiles = await glob("**/controllers/**/*.{ts,js}", {
3582
3743
  cwd: backendRoot,
3583
- ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/index.*"]
3744
+ ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/index.*", "**/dist/**"]
3584
3745
  });
3585
3746
  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", "");
3747
+ const parts = file.replace(/\\/g, "/").split("/");
3748
+ const moduleName = inferModule(file, "controller");
3749
+ const fileName = parts[parts.length - 1].replace(/\.(ts|js)$/, "").replace(/Controller$/, "");
3589
3750
  const nodeId = `controller:${fileName}`;
3590
3751
  moduleSet.add(moduleName);
3591
3752
  nodes.push({
@@ -3595,11 +3756,13 @@ var init_croc_office = __esm({
3595
3756
  status: "idle",
3596
3757
  module: moduleName
3597
3758
  });
3598
- const modelNode = nodes.find((n) => n.type === "model" && n.label.toLowerCase() === fileName.toLowerCase());
3759
+ const lcName = fileName.toLowerCase();
3760
+ const modelNode = nodes.find((n) => n.type === "model" && n.label.toLowerCase() === lcName);
3599
3761
  if (modelNode) {
3600
3762
  edges.push({ source: nodeId, target: modelNode.id, relation: "uses" });
3601
3763
  }
3602
3764
  }
3765
+ this.updateAgent("parser-croc", { progress: 90, currentTask: "Building graph..." });
3603
3766
  for (const mod of moduleSet) {
3604
3767
  const moduleNodeId = `module:${mod}`;
3605
3768
  nodes.push({
@@ -3615,7 +3778,8 @@ var init_croc_office = __esm({
3615
3778
  }
3616
3779
  }
3617
3780
  this.cachedGraph = { nodes, edges };
3618
- this.updateAgent("parser-croc", { status: "done", currentTask: `Found ${nodes.length} nodes` });
3781
+ this.updateAgent("parser-croc", { status: "done", currentTask: `Found ${nodes.length} nodes`, progress: 100 });
3782
+ this.broadcast("graph:update", this.cachedGraph);
3619
3783
  return this.cachedGraph;
3620
3784
  } catch (err) {
3621
3785
  this.updateAgent("parser-croc", { status: "error", currentTask: String(err) });