explorbot 0.1.20 → 0.1.22
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 +54 -25
- package/boat/doc-collector/src/ai/documentarian.ts +259 -84
- package/boat/doc-collector/src/ai/tools.ts +544 -0
- package/boat/doc-collector/src/cli.ts +1 -0
- package/boat/doc-collector/src/config.ts +2 -0
- package/boat/doc-collector/src/docbot.ts +64 -5
- package/boat/doc-collector/src/docs-renderer.ts +56 -2
- package/dist/bin/explorbot-cli.js +30 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +220 -71
- package/dist/boat/doc-collector/src/ai/tools.js +415 -0
- package/dist/boat/doc-collector/src/cli.js +1 -0
- package/dist/boat/doc-collector/src/config.js +1 -0
- package/dist/boat/doc-collector/src/docbot.js +57 -5
- package/dist/boat/doc-collector/src/docs-renderer.js +46 -0
- package/dist/package.json +3 -3
- package/dist/src/action-result.js +12 -0
- package/dist/src/ai/navigator.js +179 -43
- package/dist/src/ai/pilot.js +6 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init-command.js +9 -0
- package/dist/src/commands/plans-command.js +83 -0
- package/dist/src/commands/test-command.js +14 -8
- package/dist/src/playwright-recorder.js +19 -5
- package/dist/src/reporter.js +5 -7
- package/package.json +3 -3
- package/src/action-result.ts +11 -0
- package/src/ai/navigator.ts +183 -46
- package/src/ai/pilot.ts +6 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/init-command.ts +9 -0
- package/src/commands/plans-command.ts +99 -0
- package/src/commands/test-command.ts +15 -8
- package/src/config.ts +2 -0
- package/src/playwright-recorder.ts +19 -5
- package/src/reporter.ts +4 -7
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import type { WebPageState } from '../../../src/state-manager.ts';
|
|
3
|
-
import type { PageDocumentation } from './ai/documentarian.ts';
|
|
3
|
+
import type { PageDocumentation, StateTransition } from './ai/documentarian.ts';
|
|
4
4
|
|
|
5
5
|
function renderPageDocumentation(state: WebPageState, documentation: PageDocumentation): string {
|
|
6
6
|
const lines: string[] = [];
|
|
@@ -16,6 +16,28 @@ function renderPageDocumentation(state: WebPageState, documentation: PageDocumen
|
|
|
16
16
|
lines.push('');
|
|
17
17
|
lines.push(ensureSentence(documentation.summary));
|
|
18
18
|
lines.push('');
|
|
19
|
+
|
|
20
|
+
const interactions = documentation.interactions;
|
|
21
|
+
if (interactions && interactions.length > 0) {
|
|
22
|
+
lines.push('## State Transitions');
|
|
23
|
+
lines.push('');
|
|
24
|
+
for (const transition of interactions) {
|
|
25
|
+
lines.push(`### ${transition.action}`);
|
|
26
|
+
lines.push('');
|
|
27
|
+
lines.push(`**Before:** ${transition.before}`);
|
|
28
|
+
lines.push('');
|
|
29
|
+
lines.push(`**After:** ${transition.after}`);
|
|
30
|
+
lines.push('');
|
|
31
|
+
if (transition.newCapabilities && transition.newCapabilities.length > 0) {
|
|
32
|
+
lines.push('**Observed changes:**');
|
|
33
|
+
for (const cap of transition.newCapabilities) {
|
|
34
|
+
lines.push(`- ${cap}`);
|
|
35
|
+
}
|
|
36
|
+
lines.push('');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
lines.push('## User Can');
|
|
20
42
|
lines.push('');
|
|
21
43
|
|
|
@@ -50,6 +72,16 @@ function renderPageDocumentation(state: WebPageState, documentation: PageDocumen
|
|
|
50
72
|
lines.push('');
|
|
51
73
|
}
|
|
52
74
|
|
|
75
|
+
const qualityNotes = documentation.qualityNotes;
|
|
76
|
+
if (qualityNotes && qualityNotes.length > 0) {
|
|
77
|
+
lines.push('## Coverage Notes');
|
|
78
|
+
lines.push('');
|
|
79
|
+
for (const note of qualityNotes) {
|
|
80
|
+
lines.push(`- ${ensureSentence(note)}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push('');
|
|
83
|
+
}
|
|
84
|
+
|
|
53
85
|
return `${lines.join('\n').trimEnd()}\n`;
|
|
54
86
|
}
|
|
55
87
|
|
|
@@ -79,6 +111,9 @@ function renderSpecIndex(outputDir: string, startPath: string, pages: Documented
|
|
|
79
111
|
lines.push(`Purpose: ${ensureSentence(page.summary)}`);
|
|
80
112
|
lines.push(`Proven actions: ${page.canCount}`);
|
|
81
113
|
lines.push(`Possible actions: ${page.mightCount}`);
|
|
114
|
+
if (page.interactionCount > 0) {
|
|
115
|
+
lines.push(`Interactive transitions: ${page.interactionCount}`);
|
|
116
|
+
}
|
|
82
117
|
if (page.title) {
|
|
83
118
|
lines.push(`Title: ${normalizeInlineText(page.title)}`);
|
|
84
119
|
}
|
|
@@ -99,6 +134,22 @@ function renderSpecIndex(outputDir: string, startPath: string, pages: Documented
|
|
|
99
134
|
}
|
|
100
135
|
lines.push('');
|
|
101
136
|
}
|
|
137
|
+
|
|
138
|
+
if (page.interactionActions.length > 0) {
|
|
139
|
+
lines.push('Interactive Findings:');
|
|
140
|
+
for (const action of page.interactionActions.slice(0, 3)) {
|
|
141
|
+
lines.push(`- ${normalizeInlineText(action)}`);
|
|
142
|
+
}
|
|
143
|
+
lines.push('');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (page.qualityNotes.length > 0) {
|
|
147
|
+
lines.push('Coverage Notes:');
|
|
148
|
+
for (const note of page.qualityNotes) {
|
|
149
|
+
lines.push(`- ${ensureSentence(note)}`);
|
|
150
|
+
}
|
|
151
|
+
lines.push('');
|
|
152
|
+
}
|
|
102
153
|
}
|
|
103
154
|
|
|
104
155
|
if (skipped.length > 0) {
|
|
@@ -173,8 +224,11 @@ interface DocumentedPage {
|
|
|
173
224
|
summary: string;
|
|
174
225
|
canCount: number;
|
|
175
226
|
mightCount: number;
|
|
227
|
+
interactionCount: number;
|
|
176
228
|
canActions: string[];
|
|
177
229
|
mightActions: string[];
|
|
230
|
+
interactionActions: string[];
|
|
231
|
+
qualityNotes: string[];
|
|
178
232
|
filePath: string;
|
|
179
233
|
}
|
|
180
234
|
|
|
@@ -184,4 +238,4 @@ interface SkippedPage {
|
|
|
184
238
|
}
|
|
185
239
|
|
|
186
240
|
export { renderPageDocumentation, renderSpecIndex, ensureSentence, normalizeAction };
|
|
187
|
-
export type { DocumentedPage, SkippedPage };
|
|
241
|
+
export type { DocumentedPage, SkippedPage, StateTransition };
|
|
@@ -266,11 +266,17 @@ addCommonOptions(program.command('plan:load <planfile> [index]').description('Lo
|
|
|
266
266
|
process.exit(1);
|
|
267
267
|
}
|
|
268
268
|
});
|
|
269
|
-
addCommonOptions(program.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1,3, 1-5, *, all').option('--grep <pattern>', 'Run tests matching pattern')).action(async (planfile, index, options) => {
|
|
269
|
+
addCommonOptions(program.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1,3, 1-5, *, all').option('--grep <pattern>', 'Run tests matching pattern').option('--from-plan <file>', 'Load plan file when the first argument is a test index')).action(async (planfile, index, options) => {
|
|
270
270
|
try {
|
|
271
271
|
const explorBot = new ExplorBot(buildExplorBotOptions(undefined, options));
|
|
272
272
|
await explorBot.start();
|
|
273
|
-
|
|
273
|
+
let planfileArg = planfile;
|
|
274
|
+
let indexArg = index;
|
|
275
|
+
if (options.fromPlan) {
|
|
276
|
+
planfileArg = options.fromPlan;
|
|
277
|
+
indexArg = planfile;
|
|
278
|
+
}
|
|
279
|
+
const plan = explorBot.loadPlan(planfileArg);
|
|
274
280
|
const pending = plan.getPendingTests();
|
|
275
281
|
log(`Plan loaded: "${plan.title}" (${plan.tests.length} tests, ${pending.length} pending)`);
|
|
276
282
|
const startUrl = plan.url || pending[0]?.startUrl;
|
|
@@ -280,8 +286,8 @@ addCommonOptions(program.command('test <planfile> [index]').description('Execute
|
|
|
280
286
|
log(`Navigating to ${startUrl}`);
|
|
281
287
|
await explorBot.visit(startUrl);
|
|
282
288
|
let args = '';
|
|
283
|
-
if (
|
|
284
|
-
args =
|
|
289
|
+
if (indexArg)
|
|
290
|
+
args = indexArg;
|
|
285
291
|
else if (options.grep)
|
|
286
292
|
args = options.grep;
|
|
287
293
|
const { TestCommand } = await import('../src/commands/test-command.js');
|
|
@@ -315,6 +321,26 @@ program
|
|
|
315
321
|
process.exit(1);
|
|
316
322
|
}
|
|
317
323
|
});
|
|
324
|
+
program
|
|
325
|
+
.command('plans [plan]')
|
|
326
|
+
.description('List saved plans, or show tests for a specific plan')
|
|
327
|
+
.option('-p, --path <path>', 'Working directory path')
|
|
328
|
+
.option('-c, --config <path>', 'Path to configuration file')
|
|
329
|
+
.action(async (plan, options) => {
|
|
330
|
+
try {
|
|
331
|
+
await ConfigParser.getInstance().loadConfig({
|
|
332
|
+
config: options.config,
|
|
333
|
+
path: options.path || process.cwd(),
|
|
334
|
+
});
|
|
335
|
+
const explorBot = new ExplorBot({ path: options.path });
|
|
336
|
+
const { PlansCommand } = await import('../src/commands/plans-command.js');
|
|
337
|
+
await new PlansCommand(explorBot).execute(plan || '');
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
318
344
|
addCommonOptions(program.command('rerun <filename> [index]').description('Re-run generated tests with AI auto-healing')).action(async (filename, index, options) => {
|
|
319
345
|
try {
|
|
320
346
|
const explorBot = new ExplorBot(buildExplorBotOptions(undefined, options));
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { tag } from "../../../../src/utils/logger.js";
|
|
4
|
+
import { collectDocInteractions } from "./tools.js";
|
|
3
5
|
class Documentarian {
|
|
4
6
|
provider;
|
|
5
7
|
config;
|
|
6
|
-
|
|
8
|
+
explorer;
|
|
9
|
+
constructor(provider, config = {}, explorer) {
|
|
7
10
|
this.provider = provider;
|
|
8
11
|
this.config = config;
|
|
12
|
+
this.explorer = explorer;
|
|
9
13
|
}
|
|
10
14
|
async document(state, research) {
|
|
15
|
+
const interactiveEnabled = this.config.docs?.interactive === true && this.explorer;
|
|
16
|
+
if (!interactiveEnabled) {
|
|
17
|
+
tag('info').log('Documentarian: Using static mode (interactive disabled or no explorer)');
|
|
18
|
+
return this.documentStatic(state, research);
|
|
19
|
+
}
|
|
20
|
+
tag('info').log('Documentarian: Using interactive mode with tools');
|
|
21
|
+
return this.documentWithInteraction(state, research);
|
|
22
|
+
}
|
|
23
|
+
async documentStatic(state, research) {
|
|
11
24
|
try {
|
|
12
25
|
return await this.generateDocumentation(state, research);
|
|
13
26
|
}
|
|
@@ -18,39 +31,110 @@ class Documentarian {
|
|
|
18
31
|
return this.generateDocumentation(state, this.sanitizeResearch(research), true);
|
|
19
32
|
}
|
|
20
33
|
}
|
|
34
|
+
async documentWithInteraction(state, research) {
|
|
35
|
+
try {
|
|
36
|
+
tag('info').log('Starting interactive exploration...');
|
|
37
|
+
const deterministicInteractions = await collectDocInteractions(this.explorer, state, research);
|
|
38
|
+
const meaningfulInteractions = this.getMeaningfulInteractions(deterministicInteractions);
|
|
39
|
+
if (meaningfulInteractions.length > 0) {
|
|
40
|
+
tag('success').log(`Collected ${meaningfulInteractions.length} deterministic interactions`);
|
|
41
|
+
return await this.generateDocumentationWithInteractions(state, research, meaningfulInteractions);
|
|
42
|
+
}
|
|
43
|
+
if (deterministicInteractions.length > 0) {
|
|
44
|
+
tag('info').log('Interactive exploration found only low-value navigation changes. Using static documentation.');
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
tag('info').log('Interactive exploration found no reliable deterministic interactions. Using static documentation.');
|
|
48
|
+
}
|
|
49
|
+
return this.documentStatic(state, research);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
tag('warning').log(`Interactive documentation failed: ${message}. Falling back to static.`);
|
|
54
|
+
return this.documentStatic(state, research);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
getMeaningfulInteractions(interactions) {
|
|
58
|
+
return interactions.filter((interaction) => {
|
|
59
|
+
if (interaction.targetUrl) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (interaction.changes?.urlChanged) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if ((interaction.changes?.newElements || 0) > 0) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return (interaction.discoveredUrls || []).length > 0;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async generateDocumentationWithInteractions(state, research, interactions) {
|
|
72
|
+
const messages = [
|
|
73
|
+
{
|
|
74
|
+
role: 'system',
|
|
75
|
+
content: this.getSystemPrompt(),
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
role: 'user',
|
|
79
|
+
content: this.buildPrompt(state, `${research}${this.buildInteractionContext(interactions)}`),
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
|
|
83
|
+
agentName: 'documentarian',
|
|
84
|
+
});
|
|
85
|
+
return this.normalizeDocumentation({
|
|
86
|
+
...response.object,
|
|
87
|
+
interactions,
|
|
88
|
+
}, state, research);
|
|
89
|
+
}
|
|
90
|
+
async generateDocumentation(state, research, simplified = false) {
|
|
91
|
+
const messages = [
|
|
92
|
+
{
|
|
93
|
+
role: 'system',
|
|
94
|
+
content: this.getSystemPrompt(),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
role: 'user',
|
|
98
|
+
content: this.buildPrompt(state, research, simplified),
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
|
|
102
|
+
agentName: 'documentarian',
|
|
103
|
+
});
|
|
104
|
+
return this.normalizeDocumentation(response.object, state, research);
|
|
105
|
+
}
|
|
21
106
|
getSystemPrompt() {
|
|
22
|
-
const customPrompt = this.config.docs?.prompt;
|
|
23
107
|
let promptSuffix = '';
|
|
24
|
-
if (
|
|
25
|
-
promptSuffix =
|
|
108
|
+
if (this.config.docs?.prompt) {
|
|
109
|
+
promptSuffix = this.config.docs.prompt;
|
|
26
110
|
}
|
|
27
111
|
return dedent `
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
112
|
+
<role>
|
|
113
|
+
You are a product analyst preparing functional website documentation from UI research.
|
|
114
|
+
</role>
|
|
31
115
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
116
|
+
<task>
|
|
117
|
+
Convert exploratory UI research into a precise spec of what users can do on the current page.
|
|
118
|
+
Distinguish proven capabilities from assumptions.
|
|
119
|
+
Prefer accuracy over coverage.
|
|
120
|
+
</task>
|
|
37
121
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
122
|
+
<rules>
|
|
123
|
+
Only list capabilities that are grounded in the provided page research.
|
|
124
|
+
Put actions into "can" only when there is direct evidence in the page context.
|
|
125
|
+
Put actions into "might" only when the UI strongly suggests a capability but proof is incomplete.
|
|
126
|
+
Describe each action from the end-user perspective.
|
|
127
|
+
Be explicit about scope:
|
|
128
|
+
- one item
|
|
129
|
+
- list of items
|
|
130
|
+
- bulk operations
|
|
131
|
+
- all items
|
|
132
|
+
- page-level
|
|
133
|
+
Avoid implementation details, selectors, and QA wording.
|
|
134
|
+
Avoid duplicate actions with different phrasing.
|
|
135
|
+
</rules>
|
|
52
136
|
|
|
53
|
-
|
|
137
|
+
${promptSuffix}
|
|
54
138
|
`;
|
|
55
139
|
}
|
|
56
140
|
buildPrompt(state, research, simplified = false) {
|
|
@@ -59,68 +143,89 @@ class Documentarian {
|
|
|
59
143
|
.slice(0, 50)
|
|
60
144
|
.map((link) => `- ${link.title}: ${link.url}`)
|
|
61
145
|
.join('\n');
|
|
62
|
-
|
|
63
|
-
|
|
146
|
+
let simplificationNote = '';
|
|
147
|
+
if (simplified) {
|
|
148
|
+
simplificationNote = dedent `
|
|
64
149
|
<fallback_mode>
|
|
65
150
|
The research text was simplified because the original formatting was noisy.
|
|
66
151
|
Ignore malformed table syntax and rely only on clear, repeated signals.
|
|
67
152
|
Prefer fewer actions over speculative coverage.
|
|
68
153
|
</fallback_mode>
|
|
69
|
-
|
|
70
|
-
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
71
156
|
return dedent `
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
157
|
+
<page>
|
|
158
|
+
URL: ${state.url}
|
|
159
|
+
Title: ${state.title || ''}
|
|
160
|
+
Headings: ${headings}
|
|
161
|
+
</page>
|
|
77
162
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
163
|
+
<navigation_links>
|
|
164
|
+
${links}
|
|
165
|
+
</navigation_links>
|
|
81
166
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
167
|
+
<research>
|
|
168
|
+
${research}
|
|
169
|
+
</research>
|
|
85
170
|
|
|
86
|
-
|
|
171
|
+
${simplificationNote}
|
|
87
172
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
173
|
+
<output_requirements>
|
|
174
|
+
Return structured data.
|
|
175
|
+
summary: short page purpose statement.
|
|
176
|
+
can: actions you are 100% sure are available on page.
|
|
177
|
+
might: actions that look possible but are not fully proven.
|
|
178
|
+
For each action provide:
|
|
179
|
+
- action: concise user-facing capability phrased as "user can ..."
|
|
180
|
+
- scope: one of one item, list of items, bulk operations, all items, page-level
|
|
181
|
+
- evidence: short reason based on visible UI or research
|
|
182
|
+
</output_requirements>
|
|
98
183
|
`;
|
|
99
184
|
}
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
185
|
+
buildInteractionContext(interactions) {
|
|
186
|
+
const lines = interactions
|
|
187
|
+
.map((interaction) => {
|
|
188
|
+
const parts = [`Action: ${interaction.action}`, `Element: ${this.formatInteractionElement(interaction)}`, `Before: ${interaction.before}`, `After: ${interaction.after}`, `Changes: ${this.formatInteractionChanges(interaction)}`];
|
|
189
|
+
if (interaction.targetUrl) {
|
|
190
|
+
parts.push(`Target URL: ${interaction.targetUrl}`);
|
|
191
|
+
}
|
|
192
|
+
if (interaction.discoveredUrls && interaction.discoveredUrls.length > 0) {
|
|
193
|
+
parts.push(`Discovered URLs: ${interaction.discoveredUrls.join(', ')}`);
|
|
194
|
+
}
|
|
195
|
+
return `- ${parts.join('\n ')}`;
|
|
196
|
+
})
|
|
197
|
+
.join('\n');
|
|
198
|
+
return dedent `
|
|
199
|
+
|
|
200
|
+
<interaction_observations>
|
|
201
|
+
These are raw observations collected after interacting with visible controls. They are not semantic conclusions.
|
|
202
|
+
Classify them yourself as proven user capabilities, possible capabilities, navigation, page-state changes, or noise.
|
|
203
|
+
Do not trust the action label as a capability category; use the before/after state, element metadata, and URL changes as evidence.
|
|
204
|
+
${lines}
|
|
205
|
+
</interaction_observations>
|
|
206
|
+
`;
|
|
115
207
|
}
|
|
116
208
|
shouldRetryWithSanitizedResearch(error) {
|
|
117
209
|
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
-
return message.includes('Failed to generate JSON') || message.includes('failed_generation');
|
|
210
|
+
return message.includes('Failed to generate JSON') || message.includes('Failed to validate JSON') || message.includes('failed_generation') || message.includes('No object generated') || message.includes('response did not match schema');
|
|
211
|
+
}
|
|
212
|
+
normalizeDocumentation(documentation, _state, _research) {
|
|
213
|
+
const qualityNotes = this.evaluateDocumentationQuality(documentation);
|
|
214
|
+
return {
|
|
215
|
+
...documentation,
|
|
216
|
+
qualityNotes,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
evaluateDocumentationQuality(documentation) {
|
|
220
|
+
const notes = [];
|
|
221
|
+
if ((documentation.interactions || []).length === 0 && this.config.docs?.interactive) {
|
|
222
|
+
notes.push('Interactive exploration did not produce any reliable page-specific transitions for this page.');
|
|
223
|
+
}
|
|
224
|
+
return notes;
|
|
119
225
|
}
|
|
120
226
|
sanitizeResearch(research) {
|
|
121
|
-
const lines = research.split('\n');
|
|
122
227
|
const sanitized = [];
|
|
123
|
-
for (const line of
|
|
228
|
+
for (const line of research.split('\n')) {
|
|
124
229
|
if (!line.trim()) {
|
|
125
230
|
sanitized.push(line);
|
|
126
231
|
continue;
|
|
@@ -143,15 +248,59 @@ class Documentarian {
|
|
|
143
248
|
}
|
|
144
249
|
return sanitized.join('\n');
|
|
145
250
|
}
|
|
251
|
+
formatInteractionElement(interaction) {
|
|
252
|
+
if (!interaction.element) {
|
|
253
|
+
return 'unknown element';
|
|
254
|
+
}
|
|
255
|
+
const parts = [`role=${interaction.element.role}`, `name=${interaction.element.name}`, `section=${interaction.element.section}`];
|
|
256
|
+
if (interaction.element.container) {
|
|
257
|
+
parts.push(`container=${interaction.element.container}`);
|
|
258
|
+
}
|
|
259
|
+
if (interaction.element.locator) {
|
|
260
|
+
parts.push(`locator=${interaction.element.locator}`);
|
|
261
|
+
}
|
|
262
|
+
return parts.join(', ');
|
|
263
|
+
}
|
|
264
|
+
formatInteractionChanges(interaction) {
|
|
265
|
+
if (!interaction.changes) {
|
|
266
|
+
return 'unknown changes';
|
|
267
|
+
}
|
|
268
|
+
return `urlChanged=${interaction.changes.urlChanged}, newElements=${interaction.changes.newElements}, removedElements=${interaction.changes.removedElements}`;
|
|
269
|
+
}
|
|
146
270
|
}
|
|
147
271
|
const capabilitySchema = z.object({
|
|
148
272
|
action: z.string(),
|
|
149
273
|
scope: z.enum(['one item', 'list of items', 'bulk operations', 'all items', 'page-level']),
|
|
150
274
|
evidence: z.string(),
|
|
151
275
|
});
|
|
276
|
+
const stateTransitionSchema = z.object({
|
|
277
|
+
action: z.string(),
|
|
278
|
+
before: z.string(),
|
|
279
|
+
after: z.string(),
|
|
280
|
+
targetUrl: z.string().optional(),
|
|
281
|
+
discoveredUrls: z.array(z.string()).optional(),
|
|
282
|
+
newCapabilities: z.array(z.string()).optional(),
|
|
283
|
+
element: z
|
|
284
|
+
.object({
|
|
285
|
+
role: z.string(),
|
|
286
|
+
name: z.string(),
|
|
287
|
+
section: z.string(),
|
|
288
|
+
container: z.string().optional(),
|
|
289
|
+
locator: z.string().optional(),
|
|
290
|
+
})
|
|
291
|
+
.optional(),
|
|
292
|
+
changes: z
|
|
293
|
+
.object({
|
|
294
|
+
urlChanged: z.boolean(),
|
|
295
|
+
newElements: z.number(),
|
|
296
|
+
removedElements: z.number(),
|
|
297
|
+
})
|
|
298
|
+
.optional(),
|
|
299
|
+
});
|
|
152
300
|
const pageDocumentationSchema = z.object({
|
|
153
301
|
summary: z.string(),
|
|
154
302
|
can: z.array(capabilitySchema),
|
|
155
303
|
might: z.array(capabilitySchema),
|
|
304
|
+
interactions: z.array(stateTransitionSchema).optional(),
|
|
156
305
|
});
|
|
157
306
|
export { Documentarian };
|