explorbot 0.1.0 → 0.1.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/bin/explorbot-cli.ts +93 -36
- package/dist/bin/explorbot-cli.js +71 -16
- package/dist/rules/rerunner/healing-approach.md +19 -0
- package/dist/src/action.js +8 -10
- package/dist/src/ai/historian.js +34 -3
- package/dist/src/ai/navigator.js +35 -28
- package/dist/src/ai/pilot.js +33 -9
- package/dist/src/ai/planner.js +29 -10
- package/dist/src/ai/rerunner.js +472 -0
- package/dist/src/ai/researcher.js +3 -4
- package/dist/src/ai/rules.js +2 -2
- package/dist/src/ai/tools.js +2 -2
- package/dist/src/commands/add-rule-command.js +1 -2
- package/dist/src/commands/base-command.js +12 -0
- package/dist/src/commands/context-command.js +12 -5
- package/dist/src/commands/drill-command.js +0 -1
- package/dist/src/commands/explore-command.js +20 -5
- package/dist/src/commands/freesail-command.js +8 -22
- package/dist/src/commands/index.js +4 -0
- package/dist/src/commands/init-command.js +3 -3
- package/dist/src/commands/path-command.js +2 -1
- package/dist/src/commands/plan-command.js +37 -15
- package/dist/src/commands/rerun-command.js +42 -0
- package/dist/src/commands/research-command.js +10 -4
- package/dist/src/commands/runs-command.js +22 -0
- package/dist/src/commands/start-command.js +0 -1
- package/dist/src/commands/test-command.js +3 -3
- package/dist/src/components/App.js +8 -0
- package/dist/src/config.js +3 -0
- package/dist/src/explorbot.js +19 -0
- package/dist/src/explorer.js +2 -1
- package/dist/src/suite.js +115 -0
- package/dist/src/utils/html.js +2 -5
- package/dist/src/utils/rules-loader.js +33 -17
- package/dist/src/utils/test-files.js +103 -0
- package/package.json +2 -1
- package/rules/rerunner/healing-approach.md +19 -0
- package/src/action.ts +7 -9
- package/src/ai/historian.ts +37 -3
- package/src/ai/navigator.ts +35 -28
- package/src/ai/pilot.ts +33 -9
- package/src/ai/planner.ts +28 -9
- package/src/ai/rerunner.ts +532 -0
- package/src/ai/researcher.ts +3 -4
- package/src/ai/rules.ts +2 -2
- package/src/ai/tools.ts +2 -2
- package/src/commands/add-rule-command.ts +1 -2
- package/src/commands/base-command.ts +13 -0
- package/src/commands/context-command.ts +12 -5
- package/src/commands/drill-command.ts +0 -1
- package/src/commands/explore-command.ts +21 -5
- package/src/commands/freesail-command.ts +6 -23
- package/src/commands/index.ts +4 -0
- package/src/commands/init-command.ts +3 -3
- package/src/commands/path-command.ts +2 -1
- package/src/commands/plan-command.ts +45 -16
- package/src/commands/rerun-command.ts +46 -0
- package/src/commands/research-command.ts +10 -4
- package/src/commands/runs-command.ts +27 -0
- package/src/commands/start-command.ts +0 -1
- package/src/commands/test-command.ts +3 -3
- package/src/components/App.tsx +8 -0
- package/src/config.ts +23 -0
- package/src/explorbot.ts +21 -0
- package/src/explorer.ts +3 -2
- package/src/suite.ts +135 -0
- package/src/utils/html.ts +1 -5
- package/src/utils/rules-loader.ts +35 -17
- package/src/utils/test-files.ts +122 -0
|
@@ -65,29 +65,47 @@ export class RulesLoader {
|
|
|
65
65
|
return { name, approach: styles[name] };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
static
|
|
69
|
-
const sourceDir = join(BUILT_IN_DIR, agentName
|
|
70
|
-
if (!existsSync(sourceDir)) throw new Error(`No built-in
|
|
68
|
+
static extractRules(agentName: string, targetDir: string): string[] {
|
|
69
|
+
const sourceDir = join(BUILT_IN_DIR, agentName);
|
|
70
|
+
if (!existsSync(sourceDir)) throw new Error(`No built-in rules found for agent: ${agentName}`);
|
|
71
71
|
|
|
72
|
+
const extracted: string[] = [];
|
|
73
|
+
copyMarkdownTree(sourceDir, targetDir, '', extracted);
|
|
74
|
+
return extracted;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function copyMarkdownTree(sourceDir: string, targetDir: string, relative: string, extracted: string[]): void {
|
|
79
|
+
const entries = readdirSync(sourceDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
80
|
+
|
|
81
|
+
let dirCreated = false;
|
|
82
|
+
const ensureTargetDir = () => {
|
|
83
|
+
if (dirCreated) return;
|
|
72
84
|
mkdirSync(targetDir, { recursive: true });
|
|
85
|
+
dirCreated = true;
|
|
86
|
+
};
|
|
73
87
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const sourcePath = join(sourceDir, entry.name);
|
|
90
|
+
const targetPath = join(targetDir, entry.name);
|
|
91
|
+
const relPath = relative ? `${relative}/${entry.name}` : entry.name;
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
tag('info').log(`Skipping ${file} (already exists)`);
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
writeFileSync(target, readFileSync(join(sourceDir, file), 'utf8'));
|
|
86
|
-
extracted.push(file);
|
|
87
|
-
tag('success').log(`Extracted ${file}`);
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
copyMarkdownTree(sourcePath, targetPath, relPath, extracted);
|
|
95
|
+
continue;
|
|
88
96
|
}
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
if (!entry.name.endsWith('.md')) continue;
|
|
99
|
+
|
|
100
|
+
if (existsSync(targetPath)) {
|
|
101
|
+
tag('info').log(`Skipping ${relPath} (already exists)`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
ensureTargetDir();
|
|
106
|
+
writeFileSync(targetPath, readFileSync(sourcePath, 'utf8'));
|
|
107
|
+
extracted.push(relPath);
|
|
108
|
+
tag('success').log(`Extracted ${relPath}`);
|
|
91
109
|
}
|
|
92
110
|
}
|
|
93
111
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { highlight } from 'cli-highlight';
|
|
5
|
+
import * as codeceptjs from 'codeceptjs';
|
|
6
|
+
import store from 'codeceptjs/lib/store';
|
|
7
|
+
import stepsListener from 'codeceptjs/lib/listener/steps';
|
|
8
|
+
import storeListener from 'codeceptjs/lib/listener/store';
|
|
9
|
+
import figureSet from 'figures';
|
|
10
|
+
import { ConfigParser } from '../config.ts';
|
|
11
|
+
|
|
12
|
+
export function loadTestSuites(testsDir: string): any[] {
|
|
13
|
+
if (!existsSync(testsDir)) return [];
|
|
14
|
+
|
|
15
|
+
const jsFiles = readdirSync(testsDir)
|
|
16
|
+
.filter((f) => f.endsWith('.js'))
|
|
17
|
+
.map((f) => path.resolve(testsDir, f));
|
|
18
|
+
|
|
19
|
+
if (jsFiles.length === 0) return [];
|
|
20
|
+
|
|
21
|
+
codeceptjs.container.createMocha();
|
|
22
|
+
const mocha = codeceptjs.container.mocha();
|
|
23
|
+
mocha.files = jsFiles;
|
|
24
|
+
mocha.loadFiles();
|
|
25
|
+
|
|
26
|
+
return mocha.suite.suites || [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function printTestList(suites: any[]): void {
|
|
30
|
+
if (suites.length === 0) {
|
|
31
|
+
console.log(chalk.yellow('No test files found. Run /explore first.'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let totalActive = 0;
|
|
36
|
+
let totalSkipped = 0;
|
|
37
|
+
let index = 0;
|
|
38
|
+
|
|
39
|
+
for (const suite of suites) {
|
|
40
|
+
const file = path.relative(process.cwd(), suite.file || '');
|
|
41
|
+
const active = suite.tests.filter((t: any) => !t.pending).length;
|
|
42
|
+
const skipped = suite.tests.filter((t: any) => t.pending).length;
|
|
43
|
+
totalActive += active;
|
|
44
|
+
totalSkipped += skipped;
|
|
45
|
+
|
|
46
|
+
console.log(`\n${chalk.bold.cyan(suite.title)}`);
|
|
47
|
+
console.log(chalk.gray(file));
|
|
48
|
+
|
|
49
|
+
for (const test of suite.tests) {
|
|
50
|
+
const idx = chalk.dim(`${++index}.`);
|
|
51
|
+
if (test.pending) {
|
|
52
|
+
console.log(chalk.gray(` ${idx} ${figureSet.line} ${test.title} (skipped)`));
|
|
53
|
+
} else {
|
|
54
|
+
console.log(` ${idx} ${chalk.green(figureSet.pointer)} ${test.title}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`\n${chalk.bold(`${totalActive + totalSkipped}`)} scenarios (${chalk.green(`${totalActive} active`)}, ${chalk.gray(`${totalSkipped} skipped`)})`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function dryRunTestFile(filePath: string): Promise<void> {
|
|
63
|
+
const absPath = path.resolve(filePath);
|
|
64
|
+
if (!existsSync(absPath)) {
|
|
65
|
+
console.log(chalk.yellow(`File not found: ${absPath}`));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const config = ConfigParser.getInstance().getConfig();
|
|
70
|
+
const configPath = ConfigParser.getInstance().getConfigPath();
|
|
71
|
+
const projectRoot = configPath ? path.dirname(configPath) : process.cwd();
|
|
72
|
+
|
|
73
|
+
const codeceptConfig = {
|
|
74
|
+
helpers: {
|
|
75
|
+
Playwright: { browser: config.playwright.browser, url: config.playwright.url },
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
(global as any).output_dir = path.join(projectRoot, 'output', 'states');
|
|
80
|
+
(global as any).codecept_dir = projectRoot;
|
|
81
|
+
|
|
82
|
+
codeceptjs.container.create(codeceptConfig, {});
|
|
83
|
+
await codeceptjs.recorder.start();
|
|
84
|
+
await codeceptjs.container.started(null);
|
|
85
|
+
|
|
86
|
+
store.dryRun = true;
|
|
87
|
+
(global as any).container = codeceptjs.container;
|
|
88
|
+
storeListener();
|
|
89
|
+
stepsListener();
|
|
90
|
+
|
|
91
|
+
codeceptjs.container.createMocha();
|
|
92
|
+
const mocha = codeceptjs.container.mocha();
|
|
93
|
+
mocha.reporter(class {});
|
|
94
|
+
mocha.files = [absPath];
|
|
95
|
+
mocha.loadFiles();
|
|
96
|
+
|
|
97
|
+
let currentSuite = '';
|
|
98
|
+
|
|
99
|
+
codeceptjs.event.dispatcher.on('suite.before', (suite: any) => {
|
|
100
|
+
if (suite.title && suite.title !== currentSuite) {
|
|
101
|
+
currentSuite = suite.title;
|
|
102
|
+
console.log(`\n${chalk.bold.cyan(suite.title)}`);
|
|
103
|
+
console.log(chalk.gray(path.relative(process.cwd(), suite.file || absPath)));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
codeceptjs.event.dispatcher.on('test.before', (t: any) => {
|
|
108
|
+
console.log(`\n ${chalk.green(figureSet.pointer)} ${chalk.bold(t.title)}`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
codeceptjs.event.dispatcher.on('step.start', (step: any) => {
|
|
112
|
+
const code = highlight(step.toCode(), { language: 'javascript' });
|
|
113
|
+
console.log(chalk.dim(` ${code}`));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await new Promise<void>((resolve) => {
|
|
117
|
+
const runner = mocha.run(() => resolve());
|
|
118
|
+
runner.on('pending', (t: any) => {
|
|
119
|
+
console.log(chalk.gray(` ${figureSet.line} ${t.title} (skipped)`));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|