explorbot 0.2.3 → 0.2.4

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.
Files changed (156) hide show
  1. package/README.md +1 -1
  2. package/bin/explorbot-cli.ts +7 -1
  3. package/boat/api-tester/src/config.ts +4 -2
  4. package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
  5. package/boat/doc-collector/src/ai/documentarian.ts +61 -31
  6. package/boat/doc-collector/src/config.ts +4 -2
  7. package/boat/prima/bin/prima-cli.ts +0 -0
  8. package/boat/prima/src/activity-line.ts +33 -0
  9. package/boat/prima/src/cli.ts +115 -86
  10. package/boat/prima/src/envelope.ts +80 -52
  11. package/boat/prima/src/prima.ts +547 -128
  12. package/boat/prima/src/pw-parser.ts +11 -1
  13. package/boat/prima/src/pw-registry.ts +4 -5
  14. package/boat/prima/src/session-log.ts +126 -0
  15. package/dist/bin/explorbot-cli.js +7 -1
  16. package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
  17. package/dist/boat/api-tester/src/config.js +4 -2
  18. package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
  19. package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
  20. package/dist/boat/doc-collector/src/config.js +4 -2
  21. package/dist/boat/prima/src/activity-line.js +30 -0
  22. package/dist/boat/prima/src/cli.js +97 -77
  23. package/dist/boat/prima/src/envelope.js +70 -44
  24. package/dist/boat/prima/src/prima.js +515 -119
  25. package/dist/boat/prima/src/pw-parser.js +13 -1
  26. package/dist/boat/prima/src/pw-registry.js +4 -5
  27. package/dist/boat/prima/src/session-log.js +108 -0
  28. package/dist/package.json +3 -2
  29. package/dist/rules/navigator/verification-actions.md +20 -0
  30. package/dist/src/action-result.d.ts +7 -0
  31. package/dist/src/action-result.js +4 -0
  32. package/dist/src/action.d.ts +2 -0
  33. package/dist/src/action.js +36 -1
  34. package/dist/src/ai/captain/web-mode.js +6 -3
  35. package/dist/src/ai/captain.js +2 -0
  36. package/dist/src/ai/navigator.d.ts +7 -0
  37. package/dist/src/ai/navigator.js +10 -6
  38. package/dist/src/ai/pilot.d.ts +4 -0
  39. package/dist/src/ai/pilot.js +48 -2
  40. package/dist/src/ai/provider.js +2 -2
  41. package/dist/src/ai/rerunner.js +2 -1
  42. package/dist/src/ai/researcher.js +1 -1
  43. package/dist/src/ai/rules.js +17 -10
  44. package/dist/src/ai/task-agent.js +4 -1
  45. package/dist/src/ai/tester.d.ts +1 -1
  46. package/dist/src/ai/tester.js +33 -33
  47. package/dist/src/ai/tools.d.ts +14 -0
  48. package/dist/src/ai/tools.js +113 -36
  49. package/dist/src/config.d.ts +1 -0
  50. package/dist/src/config.js +13 -4
  51. package/dist/src/execution-controller.d.ts +2 -0
  52. package/dist/src/execution-controller.js +6 -0
  53. package/dist/src/explorbot.d.ts +2 -1
  54. package/dist/src/explorbot.js +3 -1
  55. package/dist/src/explorer.js +2 -3
  56. package/dist/src/playwright-recorder.js +30 -0
  57. package/dist/src/remote.d.ts +54 -0
  58. package/dist/src/remote.js +229 -0
  59. package/dist/src/reporter.d.ts +1 -0
  60. package/dist/src/reporter.js +7 -1
  61. package/dist/src/state-manager.d.ts +2 -1
  62. package/dist/src/stats.d.ts +1 -0
  63. package/dist/src/stats.js +1 -0
  64. package/dist/src/test-plan.d.ts +2 -0
  65. package/dist/src/test-plan.js +7 -0
  66. package/dist/src/utils/aria.d.ts +2 -8
  67. package/dist/src/utils/aria.js +69 -40
  68. package/dist/src/utils/html.js +1 -0
  69. package/dist/src/utils/logger.d.ts +6 -0
  70. package/dist/src/utils/logger.js +24 -0
  71. package/dist/src/utils/page-readiness.js +18 -1
  72. package/dist/src/utils/url-matcher.js +3 -0
  73. package/dist/src/utils/web-element.d.ts +2 -0
  74. package/dist/src/utils/web-element.js +8 -0
  75. package/dist/src/utils/web-sandbox.d.ts +1 -1
  76. package/dist/src/utils/web-sandbox.js +2 -3
  77. package/docs/api-testing/basics.md +90 -0
  78. package/docs/api-testing/planning.md +57 -0
  79. package/docs/api-testing/running-tests.md +55 -0
  80. package/docs/assets/cloud-report.png +0 -0
  81. package/docs/assets/html-report.png +0 -0
  82. package/docs/assets/langfuse-trace.png +0 -0
  83. package/docs/assets/successful-explore-run.png +0 -0
  84. package/docs/basics/getting-started.md +140 -0
  85. package/docs/basics/prerequisites.md +63 -0
  86. package/docs/basics/providers.md +362 -0
  87. package/docs/basics/running.md +78 -0
  88. package/docs/contributing/ai-integration-tests.md +57 -0
  89. package/docs/contributing/contributing.md +90 -0
  90. package/docs/contributing/demo-videos.md +36 -0
  91. package/docs/contributing/npm-package.md +138 -0
  92. package/docs/contributing/observability.md +227 -0
  93. package/docs/contributing/regression-tests.md +103 -0
  94. package/docs/contributing/testing.md +95 -0
  95. package/docs/doc-collection/basics.md +128 -0
  96. package/docs/doc-collection/crawling.md +67 -0
  97. package/docs/doc-collection/interactive-mode.md +99 -0
  98. package/docs/index.json +86 -0
  99. package/docs/reference/commands.md +994 -0
  100. package/docs/reference/configuration.md +569 -0
  101. package/docs/reference/scripting.md +303 -0
  102. package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
  103. package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
  104. package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
  105. package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
  106. package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
  107. package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
  108. package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
  109. package/docs/web-testing/agents.md +158 -0
  110. package/docs/web-testing/automated-tests.md +134 -0
  111. package/docs/web-testing/basics.md +91 -0
  112. package/docs/web-testing/customization.md +131 -0
  113. package/docs/web-testing/hooks.md +238 -0
  114. package/docs/web-testing/page-interaction.md +84 -0
  115. package/docs/web-testing/planner.md +122 -0
  116. package/docs/web-testing/rerun.md +164 -0
  117. package/docs/web-testing/researcher.md +380 -0
  118. package/docs/workflow/agentic-usage.md +233 -0
  119. package/docs/workflow/application-spec.md +73 -0
  120. package/docs/workflow/ci.md +202 -0
  121. package/docs/workflow/knowledge.md +310 -0
  122. package/docs/workflow/planning-styles.md +67 -0
  123. package/docs/workflow/reporting.md +133 -0
  124. package/docs/workflow/test-plans.md +90 -0
  125. package/package.json +3 -2
  126. package/rules/navigator/verification-actions.md +20 -0
  127. package/src/action-result.ts +11 -0
  128. package/src/action.ts +38 -2
  129. package/src/ai/captain/web-mode.ts +6 -3
  130. package/src/ai/captain.ts +3 -0
  131. package/src/ai/navigator.ts +14 -8
  132. package/src/ai/pilot.ts +53 -2
  133. package/src/ai/provider.ts +2 -4
  134. package/src/ai/rerunner.ts +2 -1
  135. package/src/ai/researcher.ts +1 -1
  136. package/src/ai/rules.ts +17 -10
  137. package/src/ai/task-agent.ts +3 -1
  138. package/src/ai/tester.ts +32 -33
  139. package/src/ai/tools.ts +132 -36
  140. package/src/config.ts +15 -4
  141. package/src/execution-controller.ts +8 -0
  142. package/src/explorbot.ts +4 -2
  143. package/src/explorer.ts +2 -2
  144. package/src/playwright-recorder.ts +23 -0
  145. package/src/remote.ts +238 -0
  146. package/src/reporter.ts +7 -1
  147. package/src/state-manager.ts +2 -1
  148. package/src/stats.ts +1 -0
  149. package/src/test-plan.ts +9 -0
  150. package/src/utils/aria.ts +65 -45
  151. package/src/utils/html.ts +1 -0
  152. package/src/utils/logger.ts +24 -1
  153. package/src/utils/page-readiness.ts +24 -1
  154. package/src/utils/url-matcher.ts +3 -0
  155. package/src/utils/web-element.ts +9 -0
  156. package/src/utils/web-sandbox.ts +3 -4
@@ -1,6 +1,12 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
+ const EXPECTATION_LABELS = {
5
+ passed: 'PASSED ',
6
+ failed: 'FAILED ',
7
+ unverified: 'not verified',
8
+ };
9
+
4
10
  export interface InstanceInfo {
5
11
  name: string;
6
12
  tabs: number;
@@ -9,59 +15,52 @@ export interface InstanceInfo {
9
15
  others: Array<{ name: string; tabs: number }>;
10
16
  }
11
17
 
12
- export interface HealAttempt {
13
- code: string;
14
- outcome: string;
15
- }
16
-
17
18
  export interface EnvelopeData {
18
19
  ok: boolean;
19
20
  command: string;
20
- healed?: boolean;
21
- healNote?: string;
22
21
  used?: string[];
23
22
  page: { url: string; previousUrl?: string; title: string; state: string; visits: number };
24
23
  changes?: string | null;
24
+ steps?: Array<{ label: string; ok: boolean; proof: string }>;
25
+ expectations?: Array<{ text: string; status: 'passed' | 'failed' | 'unverified' }>;
26
+ stepFiles?: string;
27
+ value?: string;
25
28
  answer?: string;
26
29
  research?: string;
27
- verdict?: { passed: boolean; evidence: string; code: string };
28
- failure?: { error: string; attempts: HealAttempt[]; reasoning?: string; compactAria?: string };
30
+ assertions?: Array<{ code: string; passed: boolean; proof: string[] }>;
31
+ failure?: { error: string; compactAria?: string };
29
32
  instance: InstanceInfo;
30
- artifacts?: { aria: string; html: string; network: string };
33
+ status?: string;
34
+ artifacts?: { aria: string; html: string; network?: string };
31
35
  }
32
36
 
33
37
  export function renderEnvelope(data: EnvelopeData): string {
34
- const sections = [renderResult(data), renderPage(data), renderOutcome(data), ...renderFailure(data), renderInstance(data.instance), renderArtifacts(data)];
38
+ const sections = [renderResult(data), renderPage(data), renderValue(data), renderChanges(data), renderSteps(data), renderExpectations(data), renderOutcome(data), ...renderFailure(data), renderInstance(data), renderArtifacts(data)];
35
39
  return sections.filter((section) => section).join('\n\n');
36
40
  }
37
41
 
38
- export function writeArtifacts(dir: string, snapshot: { aria: string | null; html: string | null; requests: unknown[] }): { aria: string; html: string; network: string } {
42
+ export function writeArtifacts(dir: string, snapshot: { aria: string | null; html: string | null; requests: unknown[] }): { aria: string; html: string; network?: string } {
39
43
  mkdirSync(dir, { recursive: true });
40
- const paths = {
44
+ const paths: { aria: string; html: string; network?: string } = {
41
45
  aria: path.resolve(dir, 'aria.yml'),
42
46
  html: path.resolve(dir, 'page.html'),
43
- network: path.resolve(dir, 'network.jsonl'),
44
47
  };
45
48
  writeFileSync(paths.aria, snapshot.aria ?? '', 'utf-8');
46
49
  writeFileSync(paths.html, snapshot.html ?? '', 'utf-8');
50
+
51
+ if (!snapshot.requests.length) return paths;
52
+
53
+ paths.network = path.resolve(dir, 'network.jsonl');
47
54
  writeFileSync(paths.network, snapshot.requests.map((request) => `${JSON.stringify(request)}\n`).join(''), 'utf-8');
48
55
  return paths;
49
56
  }
50
57
 
51
58
  function renderResult(data: EnvelopeData): string {
52
59
  const lines = [`ok: ${data.ok}`, `command: ${data.command}`];
53
- const healed = renderHealed(data);
54
- if (healed) lines.push(healed);
55
60
  if (data.used?.length) lines.push(`used: ${data.used.join('; ')}`);
56
61
  return section('Result', lines.join('\n'));
57
62
  }
58
63
 
59
- function renderHealed(data: EnvelopeData): string | null {
60
- if (data.healed === undefined) return null;
61
- if (data.healNote) return `healed: ${data.healed} (${data.healNote})`;
62
- return `healed: ${data.healed}`;
63
- }
64
-
65
64
  function renderPage(data: EnvelopeData): string {
66
65
  const { url, previousUrl, title, state, visits } = data.page;
67
66
  const urlLabel = `url: ${url}`;
@@ -73,28 +72,59 @@ function renderPage(data: EnvelopeData): string {
73
72
  return section('Page', lines.join('\n'));
74
73
  }
75
74
 
75
+ function renderValue(data: EnvelopeData): string | null {
76
+ if (data.value === undefined) return null;
77
+ return section('Value', data.value);
78
+ }
79
+
80
+ function renderChanges(data: EnvelopeData): string | null {
81
+ if (data.changes === undefined || data.changes === null) return null;
82
+ return section('Changes', data.changes);
83
+ }
84
+
85
+ function renderSteps(data: EnvelopeData): string | null {
86
+ if (!data.steps?.length) return null;
87
+
88
+ const lines: string[] = [];
89
+ data.steps.forEach((step, index) => {
90
+ lines.push(`${index + 1}. ${step.ok ? 'ok ' : 'FAIL'} ${step.label}`);
91
+ for (const line of (step.proof || '').split('\n').filter(Boolean)) lines.push(` ${line}`);
92
+ });
93
+ if (data.stepFiles) lines.push('', `page after each step: ${data.stepFiles}`);
94
+ return section('Steps', lines.join('\n'));
95
+ }
96
+
97
+ function renderExpectations(data: EnvelopeData): string | null {
98
+ if (!data.expectations?.length) return null;
99
+ const lines = data.expectations.map((expectation, index) => `${index + 1}. ${EXPECTATION_LABELS[expectation.status]} ${expectation.text}`);
100
+ return section('Expected outcomes', lines.join('\n'));
101
+ }
102
+
76
103
  function renderOutcome(data: EnvelopeData): string | null {
77
- if (data.changes) return section('Changes', data.changes);
78
104
  if (data.answer) return section('Answer', data.answer);
79
105
  if (data.research) return section('Research', data.research);
80
- if (!data.verdict) return null;
81
- const lines = [`passed: ${data.verdict.passed}`, `evidence: ${data.verdict.evidence}`, `code: ${data.verdict.code}`];
82
- return section('Verdict', lines.join('\n'));
106
+ if (!data.assertions) return null;
107
+
108
+ if (!data.assertions.length) return section('Assertions', 'none ran — no assertion could express this claim, so nothing was checked against the page');
109
+
110
+ const lines = data.assertions.map((assertion) => {
111
+ const code = assertion.code
112
+ .split('\n')
113
+ .map((line) => line.trim())
114
+ .filter((line) => line && !line.startsWith('//'))
115
+ .join(' ');
116
+ return `${code} => ${assertion.passed ? 'PASSED' : 'FAILED'}`;
117
+ });
118
+
119
+ const proof = data.assertions.flatMap((assertion) => assertion.proof);
120
+ if (proof.length) lines.push('', 'playwright:', ...proof);
121
+
122
+ return section('Assertions', lines.join('\n'));
83
123
  }
84
124
 
85
125
  function renderFailure(data: EnvelopeData): Array<string | null> {
86
126
  if (!data.failure) return [];
87
- const lines = [`error: ${data.failure.error}`];
88
- if (data.failure.reasoning) lines.push(`reasoning: ${data.failure.reasoning}`);
89
- return [section('Failure', lines.join('\n')), renderAttempts(data.failure.attempts), renderCompactAria(data.failure.compactAria)];
90
- }
91
-
92
- function renderAttempts(attempts: HealAttempt[]): string | null {
93
- if (!attempts?.length) return null;
94
- const labels = attempts.map((attempt, index) => `${index + 1}. ${attempt.code}`);
95
- const width = Math.max(...labels.map((label) => label.length)) + 3;
96
- const lines = labels.map((label, index) => align(label, `→ ${attempts[index].outcome}`, width));
97
- return section(`Healing attempts (${attempts.length})`, lines.join('\n'));
127
+ return [section('Failure', `error: ${data.failure.error}`), renderCompactAria(data.failure.compactAria)];
98
128
  }
99
129
 
100
130
  function renderCompactAria(compactAria?: string): string | null {
@@ -102,22 +132,19 @@ function renderCompactAria(compactAria?: string): string | null {
102
132
  return section('Current page (compact ARIA)', compactAria);
103
133
  }
104
134
 
105
- function renderInstance(instance: InstanceInfo): string {
106
- const others = instance.others.map((other) => `${other.name} (${tabsLabel(other.tabs)})`);
107
- const lines = [`instance: ${instance.name} (${tabsLabel(instance.tabs)}) | other instances: ${otherInstances(others)}`, browserLine(instance)];
108
- return section('Instance', lines.join('\n'));
109
- }
110
-
111
- function otherInstances(others: string[]): string {
112
- if (!others.length) return 'none';
113
- return others.join(', ');
135
+ function renderInstance(data: EnvelopeData): string | null {
136
+ const instance = data.instance;
137
+ const parts = [`${instance.name} (${tabsLabel(instance.tabs)})`, browserLine(instance)];
138
+ if (instance.others.length) parts.push(`other instances: ${instance.others.map((other) => `${other.name} (${tabsLabel(other.tabs)})`).join(', ')}`);
139
+ if (data.status) parts.push(`details: prima status ${data.status}`);
140
+ return section('Instance', parts.join(' | '));
114
141
  }
115
142
 
116
143
  function browserLine(instance: InstanceInfo): string {
117
- if (instance.attached) return `browser: attached (${instance.attached})`;
118
- if (instance.startedAgo) return `browser: running, started ${instance.startedAgo} ago`;
119
- if (instance.tabs > 0) return 'browser: running';
120
- return 'browser: not running';
144
+ if (instance.attached) return `attached to ${instance.attached}`;
145
+ if (instance.startedAgo) return `running, started ${instance.startedAgo} ago`;
146
+ if (instance.tabs > 0) return 'running';
147
+ return 'not running';
121
148
  }
122
149
 
123
150
  function tabsLabel(tabs: number): string {
@@ -125,9 +152,10 @@ function tabsLabel(tabs: number): string {
125
152
  return `${tabs} tabs`;
126
153
  }
127
154
 
128
- function renderArtifacts(data: EnvelopeData): string | null {
155
+ export function renderArtifacts(data: EnvelopeData): string | null {
129
156
  if (!data.artifacts) return null;
130
- const lines = [`aria: ${data.artifacts.aria}`, `html: ${data.artifacts.html}`, `network: ${data.artifacts.network}`];
157
+ const lines = [`aria: ${data.artifacts.aria}`, `html: ${data.artifacts.html}`];
158
+ if (data.artifacts.network) lines.push(`network: ${data.artifacts.network}`);
131
159
  return section('Artifacts', lines.join('\n'));
132
160
  }
133
161