@thecolonylab/hivemind 0.1.0
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/LICENSE +21 -0
- package/bin/hivemind.js +2 -0
- package/dist/browser/session.js +21 -0
- package/dist/cli.js +78 -0
- package/dist/commands/apply.js +33 -0
- package/dist/commands/coverage.js +98 -0
- package/dist/commands/coverageLocal.js +60 -0
- package/dist/commands/create.js +40 -0
- package/dist/commands/dashboard.js +12 -0
- package/dist/commands/explore.js +34 -0
- package/dist/commands/init.js +50 -0
- package/dist/commands/login.js +20 -0
- package/dist/commands/remoteExecutor.js +188 -0
- package/dist/commands/report.js +44 -0
- package/dist/commands/run.js +171 -0
- package/dist/commands/runLocal.js +99 -0
- package/dist/config.js +37 -0
- package/dist/credentials.js +24 -0
- package/dist/report/artifacts.js +48 -0
- package/dist/report/html.js +299 -0
- package/dist/report/prComment.js +50 -0
- package/dist/report/terminal.js +21 -0
- package/dist/serverClient.js +57 -0
- package/dist/testFile/discover.js +19 -0
- package/dist/testFile/listExisting.js +24 -0
- package/dist/testFile/loadAllTests.js +31 -0
- package/dist/testFile/parse.js +91 -0
- package/dist/testFile/resolve.js +65 -0
- package/dist/testFile/select.js +39 -0
- package/dist/testFile/types.js +1 -0
- package/dist/testFile/write.js +37 -0
- package/dist/types.js +4 -0
- package/package.json +35 -0
- package/skill/AGENTS.md.template +17 -0
- package/skill/hivemind-cli/SKILL.md +142 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
export function getChangedFiles(cwd, base) {
|
|
3
|
+
try {
|
|
4
|
+
const out = execSync(`git diff --name-only ${base}...HEAD`, { cwd, encoding: 'utf-8' });
|
|
5
|
+
return out.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
throw new Error(`Could not run \`git diff --name-only ${base}...HEAD\`: ${err?.message || err}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** The real diff — actual added/removed lines, not just filenames. This is what a coverage
|
|
12
|
+
* check needs to reason about: "what did this change do," not just "which files moved." */
|
|
13
|
+
export function getFullDiff(cwd, base) {
|
|
14
|
+
try {
|
|
15
|
+
// maxBuffer bumped up — a real PR diff can be a few MB, default 1MB truncates it silently.
|
|
16
|
+
return execSync(`git diff ${base}...HEAD`, { cwd, encoding: 'utf-8', maxBuffer: 20 * 1024 * 1024 });
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
throw new Error(`Could not run \`git diff ${base}...HEAD\`: ${err?.message || err}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Maps changed file paths to tags via config.areas (path prefix -> tag). */
|
|
23
|
+
export function affectedTags(changedFiles, config) {
|
|
24
|
+
const tags = new Set();
|
|
25
|
+
const areas = config.areas || {};
|
|
26
|
+
for (const file of changedFiles) {
|
|
27
|
+
for (const [prefix, tag] of Object.entries(areas)) {
|
|
28
|
+
if (file.startsWith(prefix))
|
|
29
|
+
tags.add(tag);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return tags;
|
|
33
|
+
}
|
|
34
|
+
/** Filters tests to only those whose Tags: line intersects the affected areas. */
|
|
35
|
+
export function filterByTags(tests, tags) {
|
|
36
|
+
if (tags.size === 0)
|
|
37
|
+
return [];
|
|
38
|
+
return tests.filter(({ test }) => test.tags.some((t) => tags.has(t)));
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
function formatParams(params) {
|
|
4
|
+
return Object.entries(params ?? {})
|
|
5
|
+
.map(([k, v]) => (v.includes(' ') ? `${k}="${v}"` : `${k}=${v}`))
|
|
6
|
+
.join(' ');
|
|
7
|
+
}
|
|
8
|
+
export function renderTestFile(test) {
|
|
9
|
+
const lines = [`# ${test.name}`, ''];
|
|
10
|
+
if (test.url)
|
|
11
|
+
lines.push(`URL: ${test.url}`);
|
|
12
|
+
if (test.login)
|
|
13
|
+
lines.push(`Login: ${test.login}`);
|
|
14
|
+
if (test.tags.length)
|
|
15
|
+
lines.push(`Tags: ${test.tags.join(', ')}`);
|
|
16
|
+
if (test.params && Object.keys(test.params).length)
|
|
17
|
+
lines.push(`Params: ${formatParams(test.params)}`);
|
|
18
|
+
for (const b of test.before || []) {
|
|
19
|
+
const paramStr = formatParams(b.params);
|
|
20
|
+
lines.push(`Before: ${b.name}${paramStr ? ' ' + paramStr : ''}`);
|
|
21
|
+
}
|
|
22
|
+
lines.push('', '## Steps');
|
|
23
|
+
test.steps.forEach((s, i) => lines.push(`${i + 1}. ${s.type}: ${s.text}`));
|
|
24
|
+
lines.push('');
|
|
25
|
+
return lines.join('\n');
|
|
26
|
+
}
|
|
27
|
+
export async function saveTestFile(test, filePath) {
|
|
28
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
29
|
+
await writeFile(filePath, renderTestFile(test), 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
export function slugify(s) {
|
|
32
|
+
return s
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
35
|
+
.replace(/^-+|-+$/g, '')
|
|
36
|
+
.slice(0, 60);
|
|
37
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thecolonylab/hivemind",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Forager — the CLI that runs on your machine and executes for the Hivemind brain. Describe a flow in plain English, an AI agent drives a real browser and reports pass/fail. No LLM key needed on this machine.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hivemind": "./bin/hivemind.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"dev": "tsx src/cli.ts"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"bin",
|
|
17
|
+
"skill",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"commander": "^12.1.0",
|
|
22
|
+
"dotenv": "^17.4.2",
|
|
23
|
+
"playwright": "^1.48.0",
|
|
24
|
+
"ws": "^8.21.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.14.0",
|
|
28
|
+
"@types/ws": "^8.18.1",
|
|
29
|
+
"tsx": "^4.19.0",
|
|
30
|
+
"typescript": "^5.6.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- HIVEMIND:START -->
|
|
2
|
+
## hivemind E2E testing
|
|
3
|
+
|
|
4
|
+
This project uses hivemind for agentic end-to-end testing — a real browser driven by AI, no test
|
|
5
|
+
code or selectors required.
|
|
6
|
+
|
|
7
|
+
- **Skill:** see `.agents/skills/hivemind-cli/SKILL.md` for the full command reference, test file
|
|
8
|
+
format, login/precondition conventions, and how to read results.
|
|
9
|
+
- **Setup check:** `hivemind --help`; server defaults to `http://localhost:4321` unless
|
|
10
|
+
`HIVEMIND_SERVER_URL` is set.
|
|
11
|
+
- **Default workflow:** right after building or changing a UI flow, write or generate a test in
|
|
12
|
+
`hive/tests/`, run it with `hivemind run <file> --url <url> --json`, and fix your own code if it
|
|
13
|
+
fails — before telling the user the feature is done.
|
|
14
|
+
- **Before writing a new test:** check `hive/tests/` for an existing one that already covers the
|
|
15
|
+
precondition you need (e.g. "add to cart" before "remove from cart") and reference it with
|
|
16
|
+
`Before:` instead of re-describing the same steps.
|
|
17
|
+
<!-- HIVEMIND:END -->
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hivemind-cli
|
|
3
|
+
description: Use the hivemind CLI to create, run, and verify end-to-end browser tests against a real app. Prefer this over asking the user to manually test something in a browser, and prefer it as a self-verification step right after building or changing a UI flow. Trigger when defining test coverage, verifying a feature actually works end-to-end, or when the user asks to test/verify a page or flow.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: hivemind
|
|
7
|
+
tags: hivemind, e2e, testing, qa, cli
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# hivemind CLI
|
|
11
|
+
|
|
12
|
+
`hivemind` drives a real browser against a real running app and reports pass/fail, per step, with
|
|
13
|
+
screenshots. You never write test code or CSS selectors — you describe what to test in one plain
|
|
14
|
+
sentence, or write the checklist directly (see format below). No API key is needed on this
|
|
15
|
+
machine — the hivemind server (wherever `HIVEMIND_SERVER_URL` points) holds its own.
|
|
16
|
+
|
|
17
|
+
## When to use this
|
|
18
|
+
|
|
19
|
+
- Right after building or changing a UI flow, to verify it actually works before telling the user
|
|
20
|
+
it's done — this is the primary use case. Don't just say "I built X," run a real test against it.
|
|
21
|
+
- When the user asks to test, verify, or check that some flow works.
|
|
22
|
+
- When defining regression coverage for a feature.
|
|
23
|
+
|
|
24
|
+
## Setup check
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
hivemind --help
|
|
28
|
+
echo $HIVEMIND_SERVER_URL # if unset, defaults to http://localhost:4321
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If the command isn't found, it isn't installed — tell the user rather than trying to work around it.
|
|
32
|
+
|
|
33
|
+
## Core commands
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Turn a plain-English request into a saved test file. Server drafts it; you never write steps by hand
|
|
37
|
+
# unless you already know exactly what's needed (see "Writing files directly" below).
|
|
38
|
+
hivemind create "<what to test, one sentence>" --url <url>
|
|
39
|
+
|
|
40
|
+
# Run a test file or every test file in a folder. Cloud execution by default (needs nothing
|
|
41
|
+
# extra installed here). Add --local when the URL is localhost/private-network — the cloud
|
|
42
|
+
# server can't reach those; --local runs the browser on THIS machine instead, only using the
|
|
43
|
+
# cloud for decisions. Omitting --local against a local URL fails fast with a clear message.
|
|
44
|
+
hivemind run <file-or-folder> --url <url> [--json] [--local]
|
|
45
|
+
|
|
46
|
+
# One-shot: look at a page, propose 3-6 starter tests. Use when starting fresh on a project
|
|
47
|
+
# with no tests yet, not as a substitute for `create` on a specific known flow.
|
|
48
|
+
hivemind explore <url>
|
|
49
|
+
|
|
50
|
+
# Open the most recent run's report, or list past runs.
|
|
51
|
+
hivemind report
|
|
52
|
+
hivemind history [--json]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Always use `--json` on `run` when you (the agent) are consuming the result programmatically — it
|
|
56
|
+
gives you structured `{status, verdict, steps, reportUrl}` per test, not just terminal formatting.
|
|
57
|
+
|
|
58
|
+
## Writing files directly (preferred when you already know the flow)
|
|
59
|
+
|
|
60
|
+
Since you likely just built or read the code for the feature being tested, you often have more
|
|
61
|
+
context than a blind `create` call would — you can see the actual route guards, form fields, and
|
|
62
|
+
component structure. In that case, write the file directly instead of going through `create`:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
hive/tests/<slug>.md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Format:
|
|
69
|
+
```
|
|
70
|
+
# <test name>
|
|
71
|
+
|
|
72
|
+
URL: <default url for this test>
|
|
73
|
+
Login: <account label, optional — see Logins below>
|
|
74
|
+
Tags: <comma-separated area tags>
|
|
75
|
+
Params: <param=default value, optional — see Reusable preconditions below>
|
|
76
|
+
Before: <other-test-file-name> [param="value"] ← optional, can repeat for multiple preconditions
|
|
77
|
+
|
|
78
|
+
## Steps
|
|
79
|
+
1. act: <something the agent does — click, type, navigate>
|
|
80
|
+
2. assert: <something the agent checks — a specific, verifiable outcome>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Write steps as intent, not selectors.** Describe the user-visible action ("Click the 'Get
|
|
84
|
+
started' button in the header"), never a CSS selector or DOM path — the execution agent looks at
|
|
85
|
+
the real page and adapts. Split actions (`act`) and checks (`assert`) into separate steps. One
|
|
86
|
+
test = one user journey, 3-10 steps.
|
|
87
|
+
|
|
88
|
+
| Avoid | Write |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `Click .nav-item:nth-child(3)` | `Click the Pricing link in the header` |
|
|
91
|
+
| One step doing 4 things | Split into 4 focused steps |
|
|
92
|
+
| `Verify the page looks right` | `Verify a "Welcome back" heading and the user's avatar are visible` |
|
|
93
|
+
|
|
94
|
+
## Logins — never write raw credentials
|
|
95
|
+
|
|
96
|
+
If a flow requires signing in, check `hivemind.config.json` at the project root for existing
|
|
97
|
+
`accounts` labels. Reference one by label in the `Login:` field and use a `login`-typed step — do
|
|
98
|
+
not type real usernames/passwords into step text, and never invent credentials. If no suitable
|
|
99
|
+
account exists, tell the user rather than guessing one.
|
|
100
|
+
|
|
101
|
+
## Reusable preconditions — check before writing setup steps inline
|
|
102
|
+
|
|
103
|
+
Before writing a new test, check what already exists:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
ls hive/tests/
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If the flow you're testing needs a precondition that's ALREADY covered by another test file (e.g.
|
|
110
|
+
testing "remove item from cart" needs an item in the cart first, and `add-item-to-cart.md`
|
|
111
|
+
already exists), reference it with `Before:` instead of re-describing the same steps:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Before: add-item-to-cart product="Blue T-Shirt"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This runs that file's steps first, in the same continuous browser session, with `product`
|
|
118
|
+
substituted for that file's own `{{product}}` placeholder. If the precondition doesn't exist yet
|
|
119
|
+
as a file, either write it inline (if it's a one-off) or create it as its own file first (if
|
|
120
|
+
other tests will likely need it too).
|
|
121
|
+
|
|
122
|
+
## Running and reading results
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
hivemind run hive/tests/<file>.md --url <url> --json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Exit code: `0` = all passed, `1` = at least one failed, `2` = error (server unreachable, bad
|
|
129
|
+
config, etc). With `--json`, each result includes `steps[]` — one entry per declared step with
|
|
130
|
+
`ok`/`note` — read this to know exactly which step broke and why, then go fix the actual code, not
|
|
131
|
+
the test (unless the test's assumption was wrong).
|
|
132
|
+
|
|
133
|
+
**If a run fails, do not just report the failure — investigate and fix your own code first**, the
|
|
134
|
+
same way you would for a failing unit test you just introduced. Re-run after fixing to confirm.
|
|
135
|
+
|
|
136
|
+
## What NOT to do
|
|
137
|
+
|
|
138
|
+
- Don't fabricate a passing result — if you can't run hivemind (not installed, server
|
|
139
|
+
unreachable), say so plainly rather than claiming something was tested.
|
|
140
|
+
- Don't put real credentials in step text or commit them anywhere.
|
|
141
|
+
- Don't write a new test for something an existing test in `hive/tests/` already covers — extend
|
|
142
|
+
or reference it instead.
|