explorbot 0.1.9 → 0.1.11
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/README.md +27 -1
- package/bin/explorbot-cli.ts +86 -15
- package/boat/api-tester/src/ai/curler-tools.ts +3 -3
- package/boat/api-tester/src/ai/curler.ts +1 -1
- package/boat/api-tester/src/apibot.ts +2 -2
- package/boat/api-tester/src/config.ts +1 -1
- package/dist/bin/explorbot-cli.js +85 -14
- package/dist/boat/api-tester/src/ai/curler-tools.js +2 -2
- package/dist/boat/api-tester/src/apibot.js +2 -2
- package/dist/package.json +2 -2
- package/dist/rules/navigator/output.md +9 -0
- package/dist/rules/navigator/verification-actions.md +2 -0
- package/dist/src/action-result.js +23 -1
- package/dist/src/action.js +46 -38
- package/dist/src/ai/bosun.js +16 -2
- package/dist/src/ai/conversation.js +39 -0
- package/dist/src/ai/experience-compactor.js +235 -50
- package/dist/src/ai/historian/codeceptjs.js +109 -0
- package/dist/src/ai/historian/experience.js +320 -0
- package/dist/src/ai/historian/mixin.js +2 -0
- package/dist/src/ai/historian/playwright.js +145 -0
- package/dist/src/ai/historian/utils.js +18 -0
- package/dist/src/ai/historian.js +19 -398
- package/dist/src/ai/navigator.js +133 -80
- package/dist/src/ai/pilot.js +254 -13
- package/dist/src/ai/planner/subpages.js +1 -30
- package/dist/src/ai/planner.js +33 -13
- package/dist/src/ai/provider.js +55 -18
- package/dist/src/ai/rerunner.js +3 -3
- package/dist/src/ai/researcher/deep-analysis.js +1 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +1 -1
- package/dist/src/ai/researcher/locators.js +1 -1
- package/dist/src/ai/researcher/sections.js +8 -1
- package/dist/src/ai/researcher.js +43 -41
- package/dist/src/ai/rules.js +26 -14
- package/dist/src/ai/tester.js +90 -26
- package/dist/src/ai/tools.js +18 -10
- package/dist/src/api/request-store.js +20 -0
- package/dist/src/api/xhr-capture.js +19 -3
- package/dist/src/browser-server.js +16 -3
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/add-rule-command.js +12 -9
- package/dist/src/commands/base-command.js +20 -0
- package/dist/src/commands/clean-command.js +3 -2
- package/dist/src/commands/compact-command.js +138 -0
- package/dist/src/commands/context-command.js +7 -1
- package/dist/src/commands/drill-command.js +4 -1
- package/dist/src/commands/experience-command.js +104 -0
- package/dist/src/commands/explore-command.js +54 -19
- package/dist/src/commands/freesail-command.js +2 -0
- package/dist/src/commands/index.js +7 -3
- package/dist/src/commands/init-command.js +11 -10
- package/dist/src/commands/learn-command.js +1 -1
- package/dist/src/commands/navigate-command.js +4 -1
- package/dist/src/commands/plan-clear-command.js +4 -1
- package/dist/src/commands/plan-command.js +43 -4
- package/dist/src/commands/plan-edit-command.js +1 -1
- package/dist/src/commands/plan-load-command.js +4 -1
- package/dist/src/commands/plan-reload-command.js +4 -1
- package/dist/src/commands/plan-save-command.js +20 -8
- package/dist/src/commands/rerun-command.js +4 -0
- package/dist/src/commands/research-command.js +5 -2
- package/dist/src/commands/start-command.js +5 -1
- package/dist/src/commands/test-command.js +7 -1
- package/dist/src/components/App.js +15 -5
- package/dist/src/execution-controller.js +13 -2
- package/dist/src/experience-tracker.js +174 -83
- package/dist/src/explorbot.js +31 -22
- package/dist/src/explorer.js +12 -5
- package/dist/src/observability.js +50 -99
- package/dist/src/playwright-recorder.js +309 -0
- package/dist/src/reporter.js +17 -2
- package/dist/src/stats.js +2 -0
- package/dist/src/suite.js +1 -1
- package/dist/src/test-plan.js +12 -0
- package/dist/src/utils/aria.js +37 -1
- package/dist/src/utils/error-page.js +30 -7
- package/dist/src/utils/logger.js +1 -1
- package/dist/src/utils/next-steps.js +37 -0
- package/dist/src/utils/rules-loader.js +1 -1
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/url-matcher.js +50 -0
- package/package.json +2 -2
- package/rules/navigator/output.md +9 -0
- package/rules/navigator/verification-actions.md +2 -0
- package/src/action-result.ts +26 -1
- package/src/action.ts +44 -37
- package/src/ai/bosun.ts +16 -2
- package/src/ai/conversation.ts +37 -0
- package/src/ai/experience-compactor.ts +270 -63
- package/src/ai/historian/codeceptjs.ts +130 -0
- package/src/ai/historian/experience.ts +383 -0
- package/src/ai/historian/mixin.ts +4 -0
- package/src/ai/historian/playwright.ts +169 -0
- package/src/ai/historian/utils.ts +23 -0
- package/src/ai/historian.ts +35 -468
- package/src/ai/navigator.ts +140 -85
- package/src/ai/pilot.ts +259 -14
- package/src/ai/planner/subpages.ts +1 -24
- package/src/ai/planner.ts +34 -14
- package/src/ai/provider.ts +52 -18
- package/src/ai/rerunner.ts +3 -3
- package/src/ai/researcher/deep-analysis.ts +1 -1
- package/src/ai/researcher/fingerprint-worker.ts +1 -1
- package/src/ai/researcher/locators.ts +2 -2
- package/src/ai/researcher/sections.ts +7 -1
- package/src/ai/researcher.ts +47 -42
- package/src/ai/rules.ts +27 -14
- package/src/ai/task-agent.ts +1 -1
- package/src/ai/tester.ts +94 -26
- package/src/ai/tools.ts +53 -29
- package/src/api/request-store.ts +22 -0
- package/src/api/xhr-capture.ts +21 -3
- package/src/browser-server.ts +17 -3
- package/src/command-handler.ts +1 -1
- package/src/commands/add-rule-command.ts +13 -9
- package/src/commands/base-command.ts +26 -1
- package/src/commands/clean-command.ts +4 -3
- package/src/commands/compact-command.ts +156 -0
- package/src/commands/context-command.ts +8 -2
- package/src/commands/drill-command.ts +5 -2
- package/src/commands/experience-command.ts +125 -0
- package/src/commands/explore-command.ts +58 -21
- package/src/commands/freesail-command.ts +2 -0
- package/src/commands/index.ts +7 -3
- package/src/commands/init-command.ts +11 -10
- package/src/commands/learn-command.ts +2 -2
- package/src/commands/navigate-command.ts +5 -2
- package/src/commands/plan-clear-command.ts +5 -2
- package/src/commands/plan-command.ts +47 -5
- package/src/commands/plan-edit-command.ts +2 -2
- package/src/commands/plan-load-command.ts +5 -2
- package/src/commands/plan-reload-command.ts +5 -2
- package/src/commands/plan-save-command.ts +20 -9
- package/src/commands/rerun-command.ts +5 -0
- package/src/commands/research-command.ts +6 -3
- package/src/commands/start-command.ts +6 -2
- package/src/commands/test-command.ts +8 -2
- package/src/components/App.tsx +16 -5
- package/src/config.ts +6 -1
- package/src/execution-controller.ts +14 -3
- package/src/experience-tracker.ts +198 -100
- package/src/explorbot.ts +33 -23
- package/src/explorer.ts +14 -5
- package/src/observability.ts +50 -109
- package/src/playwright-recorder.ts +305 -0
- package/src/reporter.ts +17 -3
- package/src/stats.ts +4 -0
- package/src/suite.ts +1 -1
- package/src/test-plan.ts +12 -0
- package/src/utils/aria.ts +38 -1
- package/src/utils/error-page.ts +32 -7
- package/src/utils/logger.ts +1 -1
- package/src/utils/next-steps.ts +51 -0
- package/src/utils/rules-loader.ts +1 -1
- package/src/utils/test-files.ts +1 -1
- package/src/utils/url-matcher.ts +43 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import figureSet from 'figures';
|
|
2
|
-
import path from 'node:path';
|
|
3
2
|
import { getStyles } from '../ai/planner/styles.js';
|
|
4
|
-
import {
|
|
3
|
+
import { Stats } from '../stats.js';
|
|
5
4
|
import type { Plan } from '../test-plan.js';
|
|
6
|
-
import {
|
|
5
|
+
import { getCliName } from '../utils/cli-name.ts';
|
|
6
|
+
import { ErrorPageError } from '../utils/error-page.ts';
|
|
7
7
|
import { tag } from '../utils/logger.js';
|
|
8
|
-
import {
|
|
8
|
+
import { jsonToTable } from '../utils/markdown-parser.js';
|
|
9
|
+
import { type NextStepSection, printNextSteps, relativeToCwd } from '../utils/next-steps.ts';
|
|
10
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
9
11
|
|
|
10
12
|
export class ExploreCommand extends BaseCommand {
|
|
11
13
|
name = 'explore';
|
|
@@ -14,7 +16,11 @@ export class ExploreCommand extends BaseCommand {
|
|
|
14
16
|
{ flags: '--max-tests <number>', description: 'Maximum number of tests to run' },
|
|
15
17
|
{ flags: '--focus <feature>', description: 'Focus area for exploration' },
|
|
16
18
|
];
|
|
17
|
-
suggestions = [
|
|
19
|
+
suggestions: Suggestion[] = [
|
|
20
|
+
{ command: 'navigate <page>', hint: 'go to another page' },
|
|
21
|
+
{ command: 'research', hint: 'analyze current page' },
|
|
22
|
+
{ command: 'plan <feature>', hint: 'plan testing' },
|
|
23
|
+
];
|
|
18
24
|
|
|
19
25
|
maxTests?: number;
|
|
20
26
|
private testsRun = 0;
|
|
@@ -27,6 +33,8 @@ export class ExploreCommand extends BaseCommand {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
const feature = (opts.focus as string) || remaining.join(' ') || undefined;
|
|
36
|
+
Stats.mode ??= 'explore';
|
|
37
|
+
Stats.focus ??= feature;
|
|
30
38
|
const mainUrl = this.explorBot.getExplorer().getStateManager().getCurrentState()?.url;
|
|
31
39
|
|
|
32
40
|
await this.runAllStyles(mainUrl, feature);
|
|
@@ -34,7 +42,7 @@ export class ExploreCommand extends BaseCommand {
|
|
|
34
42
|
if (!mainPlan) return;
|
|
35
43
|
this.completedPlans.push(mainPlan);
|
|
36
44
|
|
|
37
|
-
if (!this.isLimitReached()) {
|
|
45
|
+
if (!feature && !this.isLimitReached()) {
|
|
38
46
|
const planner = this.explorBot.agentPlanner();
|
|
39
47
|
while (true) {
|
|
40
48
|
if (this.isLimitReached()) break;
|
|
@@ -62,8 +70,8 @@ export class ExploreCommand extends BaseCommand {
|
|
|
62
70
|
this.explorBot.setCurrentPlan(mainPlan);
|
|
63
71
|
if (mainUrl) await this.explorBot.visit(mainUrl);
|
|
64
72
|
const savedPath = this.explorBot.savePlans(this.completedPlans);
|
|
65
|
-
this.printResults(
|
|
66
|
-
this.
|
|
73
|
+
this.printResults();
|
|
74
|
+
this.printNextSteps(savedPath);
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
private async runAllStyles(pageUrl?: string, feature?: string, parentPlan?: Plan, completedPlans?: Plan[]): Promise<void> {
|
|
@@ -74,13 +82,28 @@ export class ExploreCommand extends BaseCommand {
|
|
|
74
82
|
}
|
|
75
83
|
const opts: { fresh: boolean; style: string; extend?: Plan; completedPlans?: Plan[] } = { fresh, style, completedPlans };
|
|
76
84
|
if (fresh && parentPlan) opts.extend = parentPlan;
|
|
77
|
-
await this.
|
|
85
|
+
await this.planWithRetry(feature, opts, pageUrl);
|
|
78
86
|
await this.runPendingTests();
|
|
79
87
|
fresh = false;
|
|
80
88
|
}
|
|
81
89
|
}
|
|
82
90
|
|
|
83
|
-
private
|
|
91
|
+
private async planWithRetry(feature: string | undefined, opts: { fresh: boolean; style: string; extend?: Plan; completedPlans?: Plan[] }, pageUrl?: string): Promise<void> {
|
|
92
|
+
await this.explorBot.plan(feature, opts);
|
|
93
|
+
if (!this.explorBot.lastPlanError) return;
|
|
94
|
+
if (this.explorBot.lastPlanError instanceof ErrorPageError) {
|
|
95
|
+
throw this.explorBot.lastPlanError;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
tag('info').log(`Retrying planning style '${opts.style}'...`);
|
|
99
|
+
if (pageUrl) await this.explorBot.visit(pageUrl);
|
|
100
|
+
await this.explorBot.plan(feature, opts);
|
|
101
|
+
if (this.explorBot.lastPlanError) {
|
|
102
|
+
tag('warning').log(`Planning style '${opts.style}' failed after retry, skipping`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private printResults(): void {
|
|
84
107
|
const allTests = this.completedPlans.flatMap((plan) => plan.tests.filter((t) => t.startTime != null).map((test) => ({ test, planTitle: plan.title })));
|
|
85
108
|
|
|
86
109
|
if (allTests.length === 0) return;
|
|
@@ -109,22 +132,36 @@ export class ExploreCommand extends BaseCommand {
|
|
|
109
132
|
if (hasSubPages) columns.push('Plan');
|
|
110
133
|
tag('multiline').log(jsonToTable(rows, columns));
|
|
111
134
|
tag('info').log(`${figureSet.tick} ${allTests.length} tests completed`);
|
|
135
|
+
}
|
|
112
136
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
private printNextSteps(savedPlanPath?: string | null): void {
|
|
138
|
+
const cli = getCliName();
|
|
139
|
+
const sections: NextStepSection[] = [];
|
|
140
|
+
|
|
141
|
+
if (savedPlanPath) {
|
|
142
|
+
const relPlan = relativeToCwd(savedPlanPath);
|
|
143
|
+
sections.push({
|
|
144
|
+
label: 'Plan',
|
|
145
|
+
path: savedPlanPath,
|
|
146
|
+
commands: [
|
|
147
|
+
{ label: 'Re-run', command: `${cli} test ${relPlan} 1` },
|
|
148
|
+
{ label: 'Run all', command: `${cli} test ${relPlan} *` },
|
|
149
|
+
{ label: 'Run range', command: `${cli} test ${relPlan} 1-3` },
|
|
150
|
+
],
|
|
151
|
+
});
|
|
116
152
|
}
|
|
117
|
-
}
|
|
118
153
|
|
|
119
|
-
private printRerunSuggestions(): void {
|
|
120
154
|
const savedFiles = this.explorBot.agentHistorian().getSavedFiles();
|
|
121
|
-
if (savedFiles.length
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
155
|
+
if (savedFiles.length > 0) {
|
|
156
|
+
const commands = savedFiles.map((f) => ({ label: '', command: `${cli} rerun ${relativeToCwd(f)}` }));
|
|
157
|
+
commands.push({ label: 'List tests', command: `${cli} runs` });
|
|
158
|
+
sections.push({
|
|
159
|
+
label: `Generated tests (${savedFiles.length})`,
|
|
160
|
+
commands,
|
|
161
|
+
});
|
|
125
162
|
}
|
|
126
|
-
|
|
127
|
-
|
|
163
|
+
|
|
164
|
+
printNextSteps(sections);
|
|
128
165
|
}
|
|
129
166
|
|
|
130
167
|
private isLimitReached(): boolean {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Planner } from '../ai/planner.js';
|
|
2
2
|
import { Researcher } from '../ai/researcher.js';
|
|
3
|
+
import { Stats } from '../stats.js';
|
|
3
4
|
import { tag } from '../utils/logger.js';
|
|
4
5
|
import { loop } from '../utils/loop.js';
|
|
5
6
|
import { BaseCommand } from './base-command.js';
|
|
@@ -18,6 +19,7 @@ export class FreesailCommand extends BaseCommand {
|
|
|
18
19
|
];
|
|
19
20
|
|
|
20
21
|
async execute(args: string): Promise<void> {
|
|
22
|
+
Stats.mode = 'freesail';
|
|
21
23
|
const { opts } = this.parseArgs(args);
|
|
22
24
|
let strategy: 'deep' | 'shallow' | undefined;
|
|
23
25
|
if (opts.deep) strategy = 'deep';
|
package/src/commands/index.ts
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import type { ExplorBot } from '../explorbot.js';
|
|
2
|
+
import { AddRuleCommand } from './add-rule-command.js';
|
|
2
3
|
import type { BaseCommand } from './base-command.js';
|
|
3
4
|
import { CleanCommand } from './clean-command.js';
|
|
4
|
-
import {
|
|
5
|
+
import { CompactCommand } from './compact-command.js';
|
|
5
6
|
import { ContextAriaCommand } from './context-aria-command.js';
|
|
6
7
|
import { ContextCommand } from './context-command.js';
|
|
7
8
|
import { ContextDataCommand } from './context-data-command.js';
|
|
8
9
|
import { ContextExperienceCommand } from './context-experience-command.js';
|
|
9
10
|
import { ContextHtmlCommand } from './context-html-command.js';
|
|
10
11
|
import { ContextKnowledgeCommand } from './context-knowledge-command.js';
|
|
12
|
+
import { DebugCommand } from './debug-command.js';
|
|
11
13
|
import { DrillCommand } from './drill-command.js';
|
|
12
14
|
import { ExitCommand } from './exit-command.js';
|
|
15
|
+
import { ExperienceCommand } from './experience-command.js';
|
|
13
16
|
import { ExploreCommand } from './explore-command.js';
|
|
14
17
|
import { FreesailCommand } from './freesail-command.js';
|
|
15
18
|
import { HelpCommand } from './help-command.js';
|
|
16
|
-
import { AddRuleCommand } from './add-rule-command.js';
|
|
17
|
-
import { LearnCommand } from './learn-command.js';
|
|
18
19
|
import { KnowsCommand } from './knows-command.js';
|
|
20
|
+
import { LearnCommand } from './learn-command.js';
|
|
19
21
|
import { NavigateCommand } from './navigate-command.js';
|
|
20
22
|
import { PathCommand } from './path-command.js';
|
|
21
23
|
import { PlanClearCommand } from './plan-clear-command.js';
|
|
@@ -53,6 +55,8 @@ const commandClasses: CommandClass[] = [
|
|
|
53
55
|
PathCommand,
|
|
54
56
|
LearnCommand,
|
|
55
57
|
KnowsCommand,
|
|
58
|
+
ExperienceCommand,
|
|
59
|
+
CompactCommand,
|
|
56
60
|
AddRuleCommand,
|
|
57
61
|
ContextCommand,
|
|
58
62
|
ContextAriaCommand,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, extname, join, resolve } from 'node:path';
|
|
3
|
-
import { log, tag } from '../utils/logger.js';
|
|
4
|
-
import dedent from 'dedent';
|
|
5
3
|
import chalk from 'chalk';
|
|
4
|
+
import dedent from 'dedent';
|
|
6
5
|
import { getCliName } from '../utils/cli-name.ts';
|
|
6
|
+
import { log, tag } from '../utils/logger.js';
|
|
7
|
+
import { relativeToCwd } from '../utils/next-steps.ts';
|
|
7
8
|
|
|
8
9
|
const DEFAULT_CONFIG_TEMPLATE = `import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
9
10
|
// import { '<your provider here>' } from '<your provider package here>';
|
|
@@ -61,10 +62,10 @@ export function runInitCommand(options: InitCommandOptions): void {
|
|
|
61
62
|
const dir = resolve(customPath);
|
|
62
63
|
if (!existsSync(dir)) {
|
|
63
64
|
mkdirSync(dir, { recursive: true });
|
|
64
|
-
log(`Created directory: ${dir}`);
|
|
65
|
+
log(`Created directory: ${relativeToCwd(dir)}`);
|
|
65
66
|
}
|
|
66
67
|
process.chdir(dir);
|
|
67
|
-
log(`Working in directory: ${dir}`);
|
|
68
|
+
log(`Working in directory: ${relativeToCwd(dir)}`);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
try {
|
|
@@ -78,24 +79,24 @@ export function runInitCommand(options: InitCommandOptions): void {
|
|
|
78
79
|
const dir = dirname(outPath);
|
|
79
80
|
if (!existsSync(dir)) {
|
|
80
81
|
mkdirSync(dir, { recursive: true });
|
|
81
|
-
log(`Created directory: ${dir}`);
|
|
82
|
+
log(`Created directory: ${relativeToCwd(dir)}`);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
if (existsSync(outPath) && !force) {
|
|
85
|
-
log(`Config file already exists: ${outPath}`);
|
|
86
|
+
log(`Config file already exists: ${relativeToCwd(outPath)}`);
|
|
86
87
|
log('Use --force to overwrite existing file');
|
|
87
88
|
process.exit(1);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
writeFileSync(outPath, DEFAULT_CONFIG_TEMPLATE, 'utf8');
|
|
91
|
-
log(`Created config file: ${outPath}`);
|
|
92
|
+
log(`Created config file: ${relativeToCwd(outPath)}`);
|
|
92
93
|
|
|
93
94
|
const envPath = resolve(process.cwd(), '.env');
|
|
94
95
|
if (!existsSync(envPath)) {
|
|
95
96
|
writeFileSync(envPath, `${DEFAULT_ENV_TEMPLATE}\n`, 'utf8');
|
|
96
|
-
log(`Created env file: ${envPath}`);
|
|
97
|
+
log(`Created env file: ${relativeToCwd(envPath)}`);
|
|
97
98
|
} else {
|
|
98
|
-
log(`Env file already exists: ${envPath}`);
|
|
99
|
+
log(`Env file already exists: ${relativeToCwd(envPath)}`);
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
log('');
|
|
@@ -112,7 +113,7 @@ export function runInitCommand(options: InitCommandOptions): void {
|
|
|
112
113
|
|
|
113
114
|
if (!existsSync('./output')) {
|
|
114
115
|
mkdirSync('./output', { recursive: true });
|
|
115
|
-
log('Created directory:
|
|
116
|
+
log('Created directory: output');
|
|
116
117
|
}
|
|
117
118
|
} catch (error) {
|
|
118
119
|
log('Failed to create config file:', error);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { render } from 'ink';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { tag } from '../utils/logger.js';
|
|
4
|
-
import { BaseCommand } from './base-command.js';
|
|
4
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
5
5
|
|
|
6
6
|
export class LearnCommand extends BaseCommand {
|
|
7
7
|
name = 'learn';
|
|
8
8
|
description = 'Store knowledge for current page';
|
|
9
|
-
suggestions = ['
|
|
9
|
+
suggestions: Suggestion[] = [{ command: 'knows', hint: 'view all knowledge' }];
|
|
10
10
|
|
|
11
11
|
async execute(args: string): Promise<void> {
|
|
12
12
|
const note = args.trim();
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { tag } from '../utils/logger.js';
|
|
2
|
-
import { BaseCommand } from './base-command.js';
|
|
2
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
3
3
|
|
|
4
4
|
export class NavigateCommand extends BaseCommand {
|
|
5
5
|
name = 'navigate';
|
|
6
6
|
description = 'Navigate to URI or state using AI';
|
|
7
|
-
suggestions = [
|
|
7
|
+
suggestions: Suggestion[] = [
|
|
8
|
+
{ command: 'research', hint: 'analyze current page' },
|
|
9
|
+
{ command: 'plan <feature>', hint: 'plan testing' },
|
|
10
|
+
];
|
|
8
11
|
|
|
9
12
|
async execute(args: string): Promise<void> {
|
|
10
13
|
const destination = args.trim();
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { tag } from '../utils/logger.js';
|
|
2
|
-
import { BaseCommand } from './base-command.js';
|
|
2
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
3
3
|
|
|
4
4
|
export class PlanClearCommand extends BaseCommand {
|
|
5
5
|
name = 'plan:clear';
|
|
6
6
|
description = 'Clear current plan and create a new one';
|
|
7
|
-
suggestions = [
|
|
7
|
+
suggestions: Suggestion[] = [
|
|
8
|
+
{ command: 'test', hint: 'launch first test' },
|
|
9
|
+
{ command: 'test *', hint: 'launch all tests' },
|
|
10
|
+
];
|
|
8
11
|
|
|
9
12
|
async execute(args: string): Promise<void> {
|
|
10
13
|
this.explorBot.clearPlan();
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import figureSet from 'figures';
|
|
4
|
+
import { getCliName } from '../utils/cli-name.ts';
|
|
4
5
|
import { tag } from '../utils/logger.js';
|
|
5
|
-
import {
|
|
6
|
+
import { type NextStepSection, printNextSteps, relativeToCwd } from '../utils/next-steps.ts';
|
|
7
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
6
8
|
|
|
7
9
|
export class PlanCommand extends BaseCommand {
|
|
8
10
|
name = 'plan';
|
|
9
11
|
description = 'Plan testing for a feature';
|
|
10
|
-
suggestions = [
|
|
12
|
+
suggestions: Suggestion[] = [
|
|
13
|
+
{ command: 'test', hint: 'launch first test' },
|
|
14
|
+
{ command: 'test *', hint: 'launch all tests' },
|
|
15
|
+
{ command: 'plan:reload', hint: 'after editing the plan file, reload it' },
|
|
16
|
+
];
|
|
11
17
|
options = [
|
|
12
18
|
{ flags: '--fresh', description: 'Regenerate plan from scratch' },
|
|
13
19
|
{ flags: '--clear', description: 'Clear plan before regenerating' },
|
|
@@ -37,6 +43,39 @@ export class PlanCommand extends BaseCommand {
|
|
|
37
43
|
|
|
38
44
|
this.printPlanSummary();
|
|
39
45
|
this.updateSuggestions();
|
|
46
|
+
this.printNextSteps();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private printNextSteps(): void {
|
|
50
|
+
const savedPath = this.explorBot.lastSavedPlanPath;
|
|
51
|
+
if (!savedPath) return;
|
|
52
|
+
|
|
53
|
+
const cli = getCliName();
|
|
54
|
+
const relPlan = relativeToCwd(savedPath);
|
|
55
|
+
const sections: NextStepSection[] = [
|
|
56
|
+
{
|
|
57
|
+
label: 'Plan',
|
|
58
|
+
path: savedPath,
|
|
59
|
+
commands: [
|
|
60
|
+
{ label: 'Re-run', command: `${cli} test ${relPlan} 1` },
|
|
61
|
+
{ label: 'Run all', command: `${cli} test ${relPlan} *` },
|
|
62
|
+
{ label: 'Run range', command: `${cli} test ${relPlan} 1-3` },
|
|
63
|
+
{ label: 'Reload', command: `/plan:load ${relPlan}` },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const suite = this.explorBot.getSuite();
|
|
69
|
+
const files = suite && suite.automatedTestCount > 0 ? suite.getAutomatedTestFiles() : [];
|
|
70
|
+
if (files.length > 0) {
|
|
71
|
+
const commands = files.map((f) => ({ label: '', command: `${cli} rerun ${relativeToCwd(f)}` }));
|
|
72
|
+
sections.push({
|
|
73
|
+
label: `Automated tests (${files.length})`,
|
|
74
|
+
commands,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
printNextSteps(sections);
|
|
40
79
|
}
|
|
41
80
|
|
|
42
81
|
private printPlanSummary(): void {
|
|
@@ -61,15 +100,18 @@ export class PlanCommand extends BaseCommand {
|
|
|
61
100
|
}
|
|
62
101
|
|
|
63
102
|
private updateSuggestions(): void {
|
|
64
|
-
this.suggestions = [
|
|
103
|
+
this.suggestions = [
|
|
104
|
+
{ command: 'test', hint: 'launch first test' },
|
|
105
|
+
{ command: 'test *', hint: 'launch all tests' },
|
|
106
|
+
];
|
|
65
107
|
|
|
66
108
|
const suite = this.explorBot.getSuite();
|
|
67
109
|
if (suite && suite.automatedTestCount > 0) {
|
|
68
110
|
for (const f of suite.getAutomatedTestFiles()) {
|
|
69
|
-
this.suggestions.push(
|
|
111
|
+
this.suggestions.push({ command: `rerun ${path.relative(process.cwd(), f)}`, hint: 're-run automated tests' });
|
|
70
112
|
}
|
|
71
113
|
}
|
|
72
114
|
|
|
73
|
-
this.suggestions.push(
|
|
115
|
+
this.suggestions.push({ command: 'plan:reload', hint: 'after editing the plan file, reload it' });
|
|
74
116
|
}
|
|
75
117
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { BaseCommand } from './base-command.js';
|
|
1
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
2
2
|
|
|
3
3
|
export class PlanEditCommand extends BaseCommand {
|
|
4
4
|
name = 'plan:edit';
|
|
5
5
|
description = 'Open test plan editor';
|
|
6
|
-
suggestions = ['
|
|
6
|
+
suggestions: Suggestion[] = [{ command: 'plan:edit', hint: 'toggle tests on/off' }];
|
|
7
7
|
|
|
8
8
|
async execute(_args: string): Promise<void> {}
|
|
9
9
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { tag } from '../utils/logger.js';
|
|
2
|
-
import { BaseCommand } from './base-command.js';
|
|
2
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
3
3
|
|
|
4
4
|
export class PlanLoadCommand extends BaseCommand {
|
|
5
5
|
name = 'plan:load';
|
|
6
6
|
description = 'Load plan from file';
|
|
7
|
-
suggestions = [
|
|
7
|
+
suggestions: Suggestion[] = [
|
|
8
|
+
{ command: 'test', hint: 'launch first test' },
|
|
9
|
+
{ command: 'test *', hint: 'launch all tests' },
|
|
10
|
+
];
|
|
8
11
|
|
|
9
12
|
async execute(args: string): Promise<void> {
|
|
10
13
|
const filename = args.trim();
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { tag } from '../utils/logger.js';
|
|
2
|
-
import { BaseCommand } from './base-command.js';
|
|
2
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
3
3
|
|
|
4
4
|
export class PlanReloadCommand extends BaseCommand {
|
|
5
5
|
name = 'plan:reload';
|
|
6
6
|
description = 'Clear current plan and regenerate';
|
|
7
|
-
suggestions = [
|
|
7
|
+
suggestions: Suggestion[] = [
|
|
8
|
+
{ command: 'test', hint: 'launch first test' },
|
|
9
|
+
{ command: 'test *', hint: 'launch all tests' },
|
|
10
|
+
];
|
|
8
11
|
|
|
9
12
|
async execute(args: string): Promise<void> {
|
|
10
13
|
const currentPlan = this.explorBot.getCurrentPlan();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { BaseCommand } from './base-command.js';
|
|
1
|
+
import { getCliName } from '../utils/cli-name.ts';
|
|
2
|
+
import { type NextStepSection, printNextSteps, relativeToCwd } from '../utils/next-steps.ts';
|
|
3
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
4
4
|
|
|
5
5
|
export class PlanSaveCommand extends BaseCommand {
|
|
6
6
|
name = 'plan:save';
|
|
7
7
|
description = 'Save current plan to file';
|
|
8
|
-
suggestions = ['
|
|
8
|
+
suggestions: Suggestion[] = [{ command: 'test', hint: 'launch first test' }];
|
|
9
9
|
|
|
10
10
|
async execute(args: string): Promise<void> {
|
|
11
11
|
const plan = this.explorBot.getCurrentPlan();
|
|
@@ -15,11 +15,22 @@ export class PlanSaveCommand extends BaseCommand {
|
|
|
15
15
|
|
|
16
16
|
const filename = args.trim() || undefined;
|
|
17
17
|
const savedPath = this.explorBot.savePlan(filename);
|
|
18
|
+
if (!savedPath) return;
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
const cli = getCliName();
|
|
21
|
+
const relPlan = relativeToCwd(savedPath);
|
|
22
|
+
const sections: NextStepSection[] = [
|
|
23
|
+
{
|
|
24
|
+
label: 'Plan',
|
|
25
|
+
path: savedPath,
|
|
26
|
+
commands: [
|
|
27
|
+
{ label: 'Re-run', command: `${cli} test ${relPlan} 1` },
|
|
28
|
+
{ label: 'Run all', command: `${cli} test ${relPlan} *` },
|
|
29
|
+
{ label: 'Run range', command: `${cli} test ${relPlan} 1-3` },
|
|
30
|
+
{ label: 'Reload', command: `/plan:load ${relPlan}` },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
printNextSteps(sections);
|
|
24
35
|
}
|
|
25
36
|
}
|
|
@@ -24,6 +24,11 @@ export class RerunCommand extends BaseCommand {
|
|
|
24
24
|
filePath = resolve(ConfigParser.getInstance().getTestsDir(), filename);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
if (filePath.endsWith('.spec.ts') || filePath.endsWith('.spec.js')) {
|
|
28
|
+
tag('error').log(`Rerun does not support Playwright tests. Run them with: npx playwright test ${filePath}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
const testIndices = indexArg ? parseTestIndices(indexArg) : undefined;
|
|
28
33
|
await this.explorBot.agentRerunner().rerun(filePath, { testIndices });
|
|
29
34
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { ConfigParser } from '../config.ts';
|
|
3
3
|
import { tag } from '../utils/logger.ts';
|
|
4
|
-
import { BaseCommand } from './base-command.js';
|
|
4
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
5
5
|
|
|
6
6
|
export class ResearchCommand extends BaseCommand {
|
|
7
7
|
name = 'research';
|
|
8
8
|
description = 'Research current page or navigate to URI and research. Use --deep to explore interactive elements by clicking them. Use --data to include page data.';
|
|
9
|
-
suggestions = [
|
|
9
|
+
suggestions: Suggestion[] = [
|
|
10
|
+
{ command: 'navigate <page>', hint: 'go to another page' },
|
|
11
|
+
{ command: 'plan <feature>', hint: 'plan testing' },
|
|
12
|
+
];
|
|
10
13
|
options = [
|
|
11
14
|
{ flags: '--data', description: 'Include page data' },
|
|
12
15
|
{ flags: '--deep', description: 'Explore interactive elements by clicking them' },
|
|
@@ -45,7 +48,7 @@ export class ResearchCommand extends BaseCommand {
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
if (!enableDeep) {
|
|
48
|
-
this.suggestions = ['
|
|
51
|
+
this.suggestions = [{ command: 'research <page> --deep', hint: 'analyze page for all expandable elements and interactions' }];
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { BaseCommand } from './base-command.js';
|
|
1
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
2
2
|
import { ExploreCommand } from './explore-command.js';
|
|
3
3
|
|
|
4
4
|
export class StartCommand extends BaseCommand {
|
|
5
5
|
name = 'start';
|
|
6
6
|
description = 'Start web exploration';
|
|
7
|
-
suggestions = [
|
|
7
|
+
suggestions: Suggestion[] = [
|
|
8
|
+
{ command: 'navigate <page>', hint: 'go to another page' },
|
|
9
|
+
{ command: 'research', hint: 'analyze current page' },
|
|
10
|
+
{ command: 'plan <feature>', hint: 'plan testing' },
|
|
11
|
+
];
|
|
8
12
|
|
|
9
13
|
async execute(args: string): Promise<void> {
|
|
10
14
|
await new ExploreCommand(this.explorBot).execute(args);
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import { Stats } from '../stats.js';
|
|
1
2
|
import { Test } from '../test-plan.js';
|
|
2
3
|
import { tag } from '../utils/logger.js';
|
|
3
|
-
import { BaseCommand } from './base-command.js';
|
|
4
|
+
import { BaseCommand, type Suggestion } from './base-command.js';
|
|
4
5
|
|
|
5
6
|
export class TestCommand extends BaseCommand {
|
|
6
7
|
name = 'test';
|
|
7
8
|
description = 'Launch tester agent to execute test scenarios';
|
|
8
|
-
suggestions = [
|
|
9
|
+
suggestions: Suggestion[] = [
|
|
10
|
+
{ command: 'test', hint: 'run next test' },
|
|
11
|
+
{ command: 'plan', hint: 'create new plan' },
|
|
12
|
+
];
|
|
9
13
|
|
|
10
14
|
async execute(args: string): Promise<void> {
|
|
11
15
|
const plan = this.explorBot.getCurrentPlan();
|
|
16
|
+
Stats.mode = 'test';
|
|
17
|
+
Stats.focus = plan?.title;
|
|
12
18
|
const toExecute: Test[] = [];
|
|
13
19
|
|
|
14
20
|
const requirePlan = () => {
|
package/src/components/App.tsx
CHANGED
|
@@ -99,7 +99,11 @@ export function App({ explorBot, initialShowInput = false, exitOnEmptyInput = fa
|
|
|
99
99
|
setShowInput(true);
|
|
100
100
|
|
|
101
101
|
return new Promise<string | null>((resolve) => {
|
|
102
|
-
interruptResolveRef.current =
|
|
102
|
+
interruptResolveRef.current = (value) => {
|
|
103
|
+
interruptResolveRef.current = null;
|
|
104
|
+
setInterruptPrompt(null);
|
|
105
|
+
resolve(value);
|
|
106
|
+
};
|
|
103
107
|
});
|
|
104
108
|
});
|
|
105
109
|
|
|
@@ -107,11 +111,19 @@ export function App({ explorBot, initialShowInput = false, exitOnEmptyInput = fa
|
|
|
107
111
|
setShowInput(true);
|
|
108
112
|
};
|
|
109
113
|
|
|
114
|
+
const handleInterrupt = () => {
|
|
115
|
+
if (interruptResolveRef.current) {
|
|
116
|
+
interruptResolveRef.current(null);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
110
120
|
executionController.on('idle', handleIdle);
|
|
121
|
+
executionController.on('interrupt', handleInterrupt);
|
|
111
122
|
setInputCallbackReady(true);
|
|
112
123
|
|
|
113
124
|
return () => {
|
|
114
125
|
executionController.off('idle', handleIdle);
|
|
126
|
+
executionController.off('interrupt', handleInterrupt);
|
|
115
127
|
executionController.reset();
|
|
116
128
|
};
|
|
117
129
|
}, []);
|
|
@@ -284,9 +296,10 @@ export function App({ explorBot, initialShowInput = false, exitOnEmptyInput = fa
|
|
|
284
296
|
}
|
|
285
297
|
|
|
286
298
|
if (isCommand) {
|
|
287
|
-
|
|
299
|
+
if (interruptResolveRef.current) {
|
|
300
|
+
interruptResolveRef.current(null);
|
|
301
|
+
}
|
|
288
302
|
setShowInput(false);
|
|
289
|
-
interruptResolveRef.current = null;
|
|
290
303
|
executionController.startExecution();
|
|
291
304
|
try {
|
|
292
305
|
await commandHandler.executeCommand(trimmed);
|
|
@@ -303,8 +316,6 @@ export function App({ explorBot, initialShowInput = false, exitOnEmptyInput = fa
|
|
|
303
316
|
|
|
304
317
|
if (interruptResolveRef.current) {
|
|
305
318
|
interruptResolveRef.current(input);
|
|
306
|
-
interruptResolveRef.current = null;
|
|
307
|
-
setInterruptPrompt(null);
|
|
308
319
|
setShowInput(false);
|
|
309
320
|
return;
|
|
310
321
|
}
|
package/src/config.ts
CHANGED
|
@@ -107,6 +107,10 @@ interface PlannerAgentConfig extends AgentConfig {
|
|
|
107
107
|
stylesDir?: string;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
interface HistorianAgentConfig extends AgentConfig {
|
|
111
|
+
framework?: 'codeceptjs' | 'playwright';
|
|
112
|
+
}
|
|
113
|
+
|
|
110
114
|
interface AgentsConfig {
|
|
111
115
|
tester?: TesterAgentConfig;
|
|
112
116
|
navigator?: NavigatorAgentConfig;
|
|
@@ -116,7 +120,7 @@ interface AgentsConfig {
|
|
|
116
120
|
'experience-compactor'?: AgentConfig;
|
|
117
121
|
captain?: AgentConfig;
|
|
118
122
|
quartermaster?: AgentConfig;
|
|
119
|
-
historian?:
|
|
123
|
+
historian?: HistorianAgentConfig;
|
|
120
124
|
fisherman?: AgentConfig;
|
|
121
125
|
chief?: AgentConfig;
|
|
122
126
|
curler?: AgentConfig;
|
|
@@ -229,6 +233,7 @@ export type {
|
|
|
229
233
|
ActionConfig,
|
|
230
234
|
AgentConfig,
|
|
231
235
|
AgentsConfig,
|
|
236
|
+
HistorianAgentConfig,
|
|
232
237
|
ResearcherAgentConfig,
|
|
233
238
|
NavigatorAgentConfig,
|
|
234
239
|
PlannerAgentConfig,
|