opencroc 1.4.3 → 1.5.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 +146 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +34 -13
- package/dist/index.js.map +1 -1
- package/dist/web/index.html +74 -3
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1270,6 +1270,10 @@ var init_config_validator = __esm({
|
|
|
1270
1270
|
});
|
|
1271
1271
|
|
|
1272
1272
|
// src/pipeline/index.ts
|
|
1273
|
+
var pipeline_exports = {};
|
|
1274
|
+
__export(pipeline_exports, {
|
|
1275
|
+
createPipeline: () => createPipeline
|
|
1276
|
+
});
|
|
1273
1277
|
import * as fs4 from "fs";
|
|
1274
1278
|
import * as path5 from "path";
|
|
1275
1279
|
function createPipeline(config) {
|
|
@@ -1285,11 +1289,32 @@ function createPipeline(config) {
|
|
|
1285
1289
|
validationErrors: [],
|
|
1286
1290
|
duration: 0
|
|
1287
1291
|
};
|
|
1292
|
+
const backendRoot = path5.resolve(config.backendRoot);
|
|
1293
|
+
const findDir = (name) => {
|
|
1294
|
+
const candidates = [
|
|
1295
|
+
path5.join(backendRoot, name),
|
|
1296
|
+
// ./models
|
|
1297
|
+
path5.join(backendRoot, "src", name),
|
|
1298
|
+
// ./src/models
|
|
1299
|
+
path5.join(backendRoot, "backend", "src", name),
|
|
1300
|
+
// ./backend/src/models
|
|
1301
|
+
path5.join(backendRoot, "backend", name),
|
|
1302
|
+
// ./backend/models
|
|
1303
|
+
path5.join(backendRoot, "server", "src", name),
|
|
1304
|
+
// ./server/src/models
|
|
1305
|
+
path5.join(backendRoot, "app", name)
|
|
1306
|
+
// ./app/models
|
|
1307
|
+
];
|
|
1308
|
+
for (const c of candidates) {
|
|
1309
|
+
if (fs4.existsSync(c)) return c;
|
|
1310
|
+
}
|
|
1311
|
+
return null;
|
|
1312
|
+
};
|
|
1313
|
+
const modelsRoot = findDir("models");
|
|
1314
|
+
const controllersRoot = findDir("controllers");
|
|
1288
1315
|
if (activeSteps.includes("scan")) {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
if (fs4.existsSync(modelsDir)) {
|
|
1292
|
-
const dirs = fs4.readdirSync(modelsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1316
|
+
if (modelsRoot) {
|
|
1317
|
+
const dirs = fs4.readdirSync(modelsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1293
1318
|
const moduleFilter = config.modules;
|
|
1294
1319
|
for (const dir of dirs) {
|
|
1295
1320
|
if (moduleFilter && !moduleFilter.includes(dir)) continue;
|
|
@@ -1298,20 +1323,20 @@ function createPipeline(config) {
|
|
|
1298
1323
|
if (result.modules.length === 0) {
|
|
1299
1324
|
result.modules.push("default");
|
|
1300
1325
|
} else {
|
|
1301
|
-
const rootFiles = fs4.readdirSync(
|
|
1326
|
+
const rootFiles = fs4.readdirSync(modelsRoot).filter((f) => f.endsWith(".ts") && !f.endsWith(".test.ts") && f !== "index.ts");
|
|
1302
1327
|
if (rootFiles.length > 0) {
|
|
1303
1328
|
result.modules.unshift("default");
|
|
1304
1329
|
}
|
|
1305
1330
|
}
|
|
1306
1331
|
}
|
|
1307
1332
|
}
|
|
1308
|
-
const resolveModelDir = (
|
|
1309
|
-
const resolveControllerDir = (
|
|
1333
|
+
const resolveModelDir = (_backendRoot, mod) => mod === "default" ? modelsRoot || path5.join(backendRoot, "models") : path5.join(modelsRoot || path5.join(backendRoot, "models"), mod);
|
|
1334
|
+
const resolveControllerDir = (_backendRoot, mod) => mod === "default" ? controllersRoot || path5.join(backendRoot, "controllers") : path5.join(controllersRoot || path5.join(backendRoot, "controllers"), mod);
|
|
1310
1335
|
if (activeSteps.includes("er-diagram")) {
|
|
1311
1336
|
const erGen = createERDiagramGenerator();
|
|
1312
|
-
const
|
|
1337
|
+
const backendRoot2 = path5.resolve(config.backendRoot);
|
|
1313
1338
|
for (const mod of result.modules) {
|
|
1314
|
-
const modelDir = resolveModelDir(
|
|
1339
|
+
const modelDir = resolveModelDir(backendRoot2, mod);
|
|
1315
1340
|
const tables = fs4.existsSync(modelDir) ? parseModuleModels(modelDir) : [];
|
|
1316
1341
|
const relations = [];
|
|
1317
1342
|
const assocFile = path5.join(modelDir, "associations.ts");
|
|
@@ -1334,9 +1359,9 @@ function createPipeline(config) {
|
|
|
1334
1359
|
}
|
|
1335
1360
|
if (activeSteps.includes("api-chain")) {
|
|
1336
1361
|
const chainAnalyzer = createApiChainAnalyzer();
|
|
1337
|
-
const
|
|
1362
|
+
const backendRoot2 = path5.resolve(config.backendRoot);
|
|
1338
1363
|
for (const mod of result.modules) {
|
|
1339
|
-
const controllerDir = resolveControllerDir(
|
|
1364
|
+
const controllerDir = resolveControllerDir(backendRoot2, mod);
|
|
1340
1365
|
const endpoints = fs4.existsSync(controllerDir) ? parseControllerDirectory(controllerDir) : [];
|
|
1341
1366
|
const analysis = chainAnalyzer.analyze(endpoints);
|
|
1342
1367
|
analysis.moduleName = mod;
|
|
@@ -1353,10 +1378,10 @@ function createPipeline(config) {
|
|
|
1353
1378
|
}
|
|
1354
1379
|
}
|
|
1355
1380
|
if (activeSteps.includes("plan")) {
|
|
1356
|
-
const
|
|
1381
|
+
const backendRoot2 = path5.resolve(config.backendRoot);
|
|
1357
1382
|
const chainAnalyzer = createApiChainAnalyzer();
|
|
1358
1383
|
for (const mod of result.modules) {
|
|
1359
|
-
const controllerDir = resolveControllerDir(
|
|
1384
|
+
const controllerDir = resolveControllerDir(backendRoot2, mod);
|
|
1360
1385
|
const endpoints = fs4.existsSync(controllerDir) ? parseControllerDirectory(controllerDir) : [];
|
|
1361
1386
|
const analysis = chainAnalyzer.analyze(endpoints);
|
|
1362
1387
|
const topoOrder = topologicalSort(analysis.dag);
|
|
@@ -3491,6 +3516,39 @@ function registerAgentRoutes(app, office) {
|
|
|
3491
3516
|
agents: office.getAgents()
|
|
3492
3517
|
};
|
|
3493
3518
|
});
|
|
3519
|
+
app.get("/api/files", async () => {
|
|
3520
|
+
const files = office.getGeneratedFiles();
|
|
3521
|
+
return files.map((f) => ({
|
|
3522
|
+
filePath: f.filePath,
|
|
3523
|
+
module: f.module,
|
|
3524
|
+
chain: f.chain,
|
|
3525
|
+
lines: f.content.split("\n").length,
|
|
3526
|
+
size: f.content.length
|
|
3527
|
+
}));
|
|
3528
|
+
});
|
|
3529
|
+
app.get("/api/files/:index", async (req, reply) => {
|
|
3530
|
+
const files = office.getGeneratedFiles();
|
|
3531
|
+
const idx = parseInt(req.params.index, 10);
|
|
3532
|
+
if (isNaN(idx) || idx < 0 || idx >= files.length) {
|
|
3533
|
+
reply.code(404).send({ error: "File not found" });
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
return files[idx];
|
|
3537
|
+
});
|
|
3538
|
+
app.get("/api/pipeline/result", async () => {
|
|
3539
|
+
const result = office.getLastPipelineResult();
|
|
3540
|
+
if (!result) return { ok: false, message: "No pipeline has been run yet" };
|
|
3541
|
+
return {
|
|
3542
|
+
ok: true,
|
|
3543
|
+
modules: result.modules,
|
|
3544
|
+
erDiagramCount: result.erDiagrams.size,
|
|
3545
|
+
chainCount: [...result.chainPlans.values()].reduce((s, p) => s + p.chains.length, 0),
|
|
3546
|
+
totalSteps: [...result.chainPlans.values()].reduce((s, p) => s + p.totalSteps, 0),
|
|
3547
|
+
filesGenerated: result.generatedFiles.length,
|
|
3548
|
+
validationErrors: result.validationErrors.length,
|
|
3549
|
+
duration: result.duration
|
|
3550
|
+
};
|
|
3551
|
+
});
|
|
3494
3552
|
}
|
|
3495
3553
|
var init_agents = __esm({
|
|
3496
3554
|
"src/server/routes/agents.ts"() {
|
|
@@ -3520,6 +3578,8 @@ var init_croc_office = __esm({
|
|
|
3520
3578
|
agents;
|
|
3521
3579
|
cachedGraph = null;
|
|
3522
3580
|
running = false;
|
|
3581
|
+
lastPipelineResult = null;
|
|
3582
|
+
lastGeneratedFiles = [];
|
|
3523
3583
|
constructor(config, cwd) {
|
|
3524
3584
|
this.config = config;
|
|
3525
3585
|
this.cwd = cwd;
|
|
@@ -3589,41 +3649,92 @@ var init_croc_office = __esm({
|
|
|
3589
3649
|
this.running = false;
|
|
3590
3650
|
}
|
|
3591
3651
|
}
|
|
3592
|
-
/** Run the pipeline: scan → er-diagram → api-chain → plan → codegen */
|
|
3652
|
+
/** Run the real pipeline: scan → er-diagram → api-chain → plan → codegen → report */
|
|
3593
3653
|
async runPipeline() {
|
|
3594
3654
|
if (this.running) return { ok: false, task: "pipeline", duration: 0, error: "Another task is running" };
|
|
3595
3655
|
this.running = true;
|
|
3596
3656
|
const start = Date.now();
|
|
3597
3657
|
try {
|
|
3658
|
+
const { resolve: resolvePath } = await import("path");
|
|
3659
|
+
const { createPipeline: createPipeline2 } = await Promise.resolve().then(() => (init_pipeline(), pipeline_exports));
|
|
3660
|
+
const backendRoot = resolvePath(this.cwd, this.config.backendRoot);
|
|
3661
|
+
const pipelineConfig = { ...this.config, backendRoot };
|
|
3662
|
+
const pipeline = createPipeline2(pipelineConfig);
|
|
3598
3663
|
this.updateAgent("parser-croc", { status: "working", currentTask: "Scanning source code...", progress: 10 });
|
|
3599
|
-
this.log(
|
|
3664
|
+
this.log(`\u{1F40A} \u89E3\u6790\u9CC4 scanning from: ${backendRoot}`);
|
|
3600
3665
|
this.invalidateCache();
|
|
3601
3666
|
await this.buildKnowledgeGraph();
|
|
3602
3667
|
this.updateNodeStatus("module", "testing");
|
|
3603
|
-
this.updateAgent("parser-croc", {
|
|
3668
|
+
this.updateAgent("parser-croc", { currentTask: "Parsing models & ER diagrams...", progress: 40 });
|
|
3669
|
+
const scanResult = await pipeline.run(["scan", "er-diagram"]);
|
|
3670
|
+
const moduleCount = scanResult.modules.length;
|
|
3671
|
+
const erCount = scanResult.erDiagrams.size;
|
|
3672
|
+
this.log(`\u{1F4CA} Found ${moduleCount} modules, ${erCount} ER diagrams`);
|
|
3673
|
+
this.updateAgent("parser-croc", { status: "done", currentTask: `${moduleCount} modules parsed`, progress: 100 });
|
|
3604
3674
|
this.updateAgent("analyzer-croc", { status: "working", currentTask: "Analyzing API chains...", progress: 0 });
|
|
3605
3675
|
this.log("\u{1F40A} \u5206\u6790\u9CC4 is analyzing API dependencies...");
|
|
3606
|
-
await
|
|
3676
|
+
const analyzeResult = await pipeline.run(["api-chain"]);
|
|
3677
|
+
const warnings = analyzeResult.validationErrors.filter((e) => e.severity === "warning");
|
|
3678
|
+
if (warnings.length > 0) {
|
|
3679
|
+
this.log(`\u26A0\uFE0F ${warnings.length} API chain warnings`, "warn");
|
|
3680
|
+
}
|
|
3607
3681
|
this.updateAgent("analyzer-croc", { status: "done", currentTask: "Analysis complete", progress: 100 });
|
|
3608
3682
|
this.updateAgent("planner-croc", { status: "thinking", currentTask: "Planning test chains...", progress: 0 });
|
|
3609
3683
|
this.log("\u{1F40A} \u89C4\u5212\u9CC4 is planning test chains...");
|
|
3610
|
-
await
|
|
3611
|
-
|
|
3684
|
+
const planResult = await pipeline.run(["plan"]);
|
|
3685
|
+
let totalChains = 0, totalSteps = 0;
|
|
3686
|
+
for (const [, plan] of planResult.chainPlans) {
|
|
3687
|
+
totalChains += plan.chains.length;
|
|
3688
|
+
totalSteps += plan.totalSteps;
|
|
3689
|
+
}
|
|
3690
|
+
this.log(`\u{1F4CB} Planned ${totalChains} test chains with ${totalSteps} steps`);
|
|
3691
|
+
this.updateAgent("planner-croc", { status: "done", currentTask: `${totalChains} chains planned`, progress: 100 });
|
|
3612
3692
|
this.updateAgent("tester-croc", { status: "working", currentTask: "Generating test code...", progress: 0 });
|
|
3613
|
-
this.log("\u{1F40A} \u6D4B\u8BD5\u9CC4 is generating test code...");
|
|
3693
|
+
this.log("\u{1F40A} \u6D4B\u8BD5\u9CC4 is generating Playwright test code...");
|
|
3614
3694
|
this.updateNodeStatus("controller", "testing");
|
|
3615
|
-
await
|
|
3695
|
+
const fullResult = await pipeline.run(["scan", "er-diagram", "api-chain", "plan", "codegen"]);
|
|
3696
|
+
this.lastPipelineResult = fullResult;
|
|
3697
|
+
this.lastGeneratedFiles = fullResult.generatedFiles;
|
|
3698
|
+
const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync10 } = await import("fs");
|
|
3699
|
+
const { dirname: dirname6 } = await import("path");
|
|
3700
|
+
let filesWritten = 0;
|
|
3701
|
+
for (const file of fullResult.generatedFiles) {
|
|
3702
|
+
const fullPath = resolvePath(this.cwd, file.filePath);
|
|
3703
|
+
mkdirSync10(dirname6(fullPath), { recursive: true });
|
|
3704
|
+
writeFileSync10(fullPath, file.content, "utf-8");
|
|
3705
|
+
filesWritten++;
|
|
3706
|
+
}
|
|
3616
3707
|
this.updateNodeStatus("controller", "passed");
|
|
3617
|
-
this.
|
|
3618
|
-
this.updateAgent("
|
|
3708
|
+
this.log(`\u2705 Generated ${filesWritten} test files`);
|
|
3709
|
+
this.updateAgent("tester-croc", { status: "done", currentTask: `${filesWritten} files generated`, progress: 100 });
|
|
3710
|
+
this.broadcast("files:generated", fullResult.generatedFiles.map((f) => ({
|
|
3711
|
+
filePath: f.filePath,
|
|
3712
|
+
module: f.module,
|
|
3713
|
+
chain: f.chain,
|
|
3714
|
+
lines: f.content.split("\n").length
|
|
3715
|
+
})));
|
|
3716
|
+
this.updateAgent("reporter-croc", { status: "working", currentTask: "Compiling report...", progress: 0 });
|
|
3619
3717
|
this.log("\u{1F40A} \u6C47\u62A5\u9CC4 is compiling results...");
|
|
3620
|
-
await
|
|
3718
|
+
const validateResult = await pipeline.run(["validate"]);
|
|
3719
|
+
const errors = validateResult.validationErrors.filter((e) => e.severity === "error");
|
|
3720
|
+
if (errors.length > 0) {
|
|
3721
|
+
this.log(`\u26A0\uFE0F ${errors.length} validation errors`, "warn");
|
|
3722
|
+
}
|
|
3621
3723
|
this.updateNodeStatus("module", "passed");
|
|
3622
3724
|
this.updateAgent("reporter-croc", { status: "done", currentTask: "Report ready", progress: 100 });
|
|
3623
3725
|
const duration = Date.now() - start;
|
|
3624
|
-
this.log(`\u2705 Pipeline complete in ${duration}ms`);
|
|
3625
|
-
this.broadcast("pipeline:complete", {
|
|
3626
|
-
|
|
3726
|
+
this.log(`\u2705 Pipeline complete in ${duration}ms \u2014 ${moduleCount} modules, ${totalChains} chains, ${filesWritten} files`);
|
|
3727
|
+
this.broadcast("pipeline:complete", {
|
|
3728
|
+
duration,
|
|
3729
|
+
status: "success",
|
|
3730
|
+
summary: { modules: moduleCount, chains: totalChains, steps: totalSteps, files: filesWritten }
|
|
3731
|
+
});
|
|
3732
|
+
return { ok: true, task: "pipeline", duration, details: {
|
|
3733
|
+
modules: moduleCount,
|
|
3734
|
+
chains: totalChains,
|
|
3735
|
+
steps: totalSteps,
|
|
3736
|
+
files: filesWritten
|
|
3737
|
+
} };
|
|
3627
3738
|
} catch (err) {
|
|
3628
3739
|
this.updateAgent("tester-croc", { status: "error", currentTask: String(err) });
|
|
3629
3740
|
this.log(`\u274C Pipeline failed: ${err}`, "error");
|
|
@@ -3642,6 +3753,14 @@ var init_croc_office = __esm({
|
|
|
3642
3753
|
}
|
|
3643
3754
|
this.broadcast("agent:update", this.agents);
|
|
3644
3755
|
}
|
|
3756
|
+
/** Get last pipeline result */
|
|
3757
|
+
getLastPipelineResult() {
|
|
3758
|
+
return this.lastPipelineResult;
|
|
3759
|
+
}
|
|
3760
|
+
/** Get generated test files from last pipeline run */
|
|
3761
|
+
getGeneratedFiles() {
|
|
3762
|
+
return this.lastGeneratedFiles;
|
|
3763
|
+
}
|
|
3645
3764
|
// ============ Graph Helpers ============
|
|
3646
3765
|
updateNodeStatus(type, status) {
|
|
3647
3766
|
if (!this.cachedGraph) return;
|
|
@@ -3652,9 +3771,6 @@ var init_croc_office = __esm({
|
|
|
3652
3771
|
}
|
|
3653
3772
|
this.broadcast("graph:update", this.cachedGraph);
|
|
3654
3773
|
}
|
|
3655
|
-
delay(ms) {
|
|
3656
|
-
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
3657
|
-
}
|
|
3658
3774
|
/** Build knowledge graph from project source code */
|
|
3659
3775
|
async buildKnowledgeGraph() {
|
|
3660
3776
|
if (this.cachedGraph) return this.cachedGraph;
|