@thecolonylab/hivemind 0.1.3 → 0.2.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.
@@ -6,6 +6,7 @@ import { checkCoverageLocally } from './coverageLocal.js';
6
6
  import { saveTestFile, slugify } from '../testFile/write.js';
7
7
  import { loadAllTestsFull } from '../testFile/loadAllTests.js';
8
8
  import { renderCoveragePrComment } from '../report/prComment.js';
9
+ import { loadConfig } from '../config.js';
9
10
  /**
10
11
  * The actual diff-aware check: reads the real code diff (not just filenames), sends it — plus
11
12
  * every local test with its full steps — to the server. The server figures out which known
@@ -23,6 +24,7 @@ export async function coverageCommand(opts) {
23
24
  }
24
25
  const diff = getFullDiff(cwd, base);
25
26
  const localTests = await loadAllTestsFull(cwd);
27
+ const config = await loadConfig(cwd);
26
28
  const serverUrl = process.env.HIVEMIND_SERVER_URL || 'https://app.thecolonylab.com';
27
29
  console.log(`🐝 Scout reading the diff (${changedFiles.length} file${changedFiles.length === 1 ? '' : 's'} changed vs ${base})...`);
28
30
  if (opts.local) {
@@ -31,8 +33,8 @@ export async function coverageCommand(opts) {
31
33
  let result;
32
34
  try {
33
35
  result = opts.local
34
- ? await checkCoverageLocally({ serverUrl, diff, localTests, headed: opts.headed })
35
- : await coverageOnServer(diff, localTests);
36
+ ? await checkCoverageLocally({ serverUrl, diff, localTests, headed: opts.headed, projectId: config.projectId, projectName: config.projectName })
37
+ : await coverageOnServer(diff, localTests, config.projectId, config.projectName);
36
38
  }
37
39
  catch (err) {
38
40
  console.error(err.message);
@@ -21,7 +21,7 @@ export async function checkCoverageLocally(opts) {
21
21
  headers.Authorization = `Bearer ${apiKey}`;
22
22
  const socket = new WebSocket(wsUrl, { headers });
23
23
  socket.on('open', () => {
24
- socket.send(JSON.stringify({ type: 'start', diff: opts.diff, localTests: opts.localTests }));
24
+ socket.send(JSON.stringify({ type: 'start', diff: opts.diff, localTests: opts.localTests, projectId: opts.projectId, projectName: opts.projectName }));
25
25
  });
26
26
  socket.on('message', async (raw) => {
27
27
  let msg;
@@ -10,7 +10,7 @@ export async function createCommand(request, opts) {
10
10
  const accountLabels = Object.keys(config.accounts || {});
11
11
  let proposed;
12
12
  try {
13
- proposed = await createOnServer(request, opts.url, existingTests, accountLabels);
13
+ proposed = await createOnServer(request, opts.url, existingTests, accountLabels, config.projectId);
14
14
  }
15
15
  catch (err) {
16
16
  console.error(err.message);
@@ -2,6 +2,7 @@ import { join } from 'node:path';
2
2
  import { exploreOnServer } from '../serverClient.js';
3
3
  import { saveTestFile, slugify } from '../testFile/write.js';
4
4
  import { listExistingTests } from '../testFile/listExisting.js';
5
+ import { loadConfig } from '../config.js';
5
6
  /**
6
7
  * A real multi-page crawl, not a one-shot snapshot — the server maps pages, follows doors, and
7
8
  * proposes starter tests along the way. Existing tests are sent along (same pattern `create`
@@ -9,10 +10,12 @@ import { listExistingTests } from '../testFile/listExisting.js';
9
10
  */
10
11
  export async function exploreCommand(url, opts) {
11
12
  console.log(`🐝 Scout dispatched to ${url}...`);
12
- const existingTests = await listExistingTests(process.cwd());
13
+ const cwd = process.cwd();
14
+ const existingTests = await listExistingTests(cwd);
15
+ const config = await loadConfig(cwd);
13
16
  let proposed;
14
17
  try {
15
- proposed = await exploreOnServer(url, existingTests);
18
+ proposed = await exploreOnServer(url, existingTests, config.projectId, config.projectName);
16
19
  }
17
20
  catch (err) {
18
21
  console.error(err.message);
@@ -1,8 +1,9 @@
1
1
  import { fileURLToPath } from 'node:url';
2
- import { dirname, join } from 'node:path';
2
+ import { dirname, join, basename } from 'node:path';
3
3
  import { mkdir, readFile, writeFile, copyFile, access } from 'node:fs/promises';
4
4
  import { execFile } from 'node:child_process';
5
5
  import { promisify } from 'node:util';
6
+ import { randomBytes } from 'node:crypto';
6
7
  const execFileAsync = promisify(execFile);
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
9
  // dist/commands/init.js -> package root (skill/ ships alongside dist/, see package.json "files")
@@ -70,16 +71,30 @@ export async function initCommand() {
70
71
  await ensureGitignore(cwd);
71
72
  const configPath = join(cwd, 'hivemind.config.json');
72
73
  if (await exists(configPath)) {
73
- console.log('- hivemind.config.json already exists, left untouched');
74
+ // Patch in a projectId even for a config that predates this field — without it, every
75
+ // request to the server would look like it belongs to no project at all (or worse, get
76
+ // lumped in with other projects on the same account that also lack one).
77
+ const existing = JSON.parse(await readFile(configPath, 'utf-8'));
78
+ if (existing.projectId) {
79
+ console.log('- hivemind.config.json already exists, left untouched');
80
+ }
81
+ else {
82
+ existing.projectId = randomBytes(8).toString('hex');
83
+ existing.projectName ??= basename(cwd);
84
+ await writeFile(configPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
85
+ console.log(`✓ hivemind.config.json — added a projectId (project name: "${existing.projectName}")`);
86
+ }
74
87
  }
75
88
  else {
76
89
  const starter = {
90
+ projectId: randomBytes(8).toString('hex'),
91
+ projectName: basename(cwd),
77
92
  environments: { default: 'http://localhost:3000' },
78
93
  accounts: {},
79
94
  areas: {},
80
95
  };
81
96
  await writeFile(configPath, JSON.stringify(starter, null, 2) + '\n', 'utf-8');
82
- console.log('✓ hivemind.config.json created — edit environments.default to match your app');
97
+ console.log(`✓ hivemind.config.json created (project name: "${starter.projectName}") — edit environments.default to match your app`);
83
98
  }
84
99
  const agentsPath = join(cwd, 'AGENTS.md');
85
100
  const template = await readFile(join(packageRoot, 'skill', 'AGENTS.md.template'), 'utf-8');
@@ -115,6 +115,7 @@ export async function runCommand(target, opts) {
115
115
  memoryContext,
116
116
  credential,
117
117
  headed: opts.headed,
118
+ projectId: config.projectId,
118
119
  });
119
120
  await writeFile(join(cwd, 'hive', 'last-run.json'), JSON.stringify({ reportUrl: reportPath, name: test.name }, null, 2)).catch(() => { });
120
121
  results[index] = { name: test.name, result, reportUrl: reportPath };
@@ -130,6 +131,8 @@ export async function runCommand(target, opts) {
130
131
  steps: resolvedSteps,
131
132
  memoryContext,
132
133
  credential,
134
+ projectId: config.projectId,
135
+ projectName: config.projectName,
133
136
  });
134
137
  const reportUrl = `${serverUrl}${reportPath}`;
135
138
  await writeLocalMemory(cwd, updatedMemory).catch(() => { });
@@ -53,6 +53,7 @@ export async function runTestLocally(opts) {
53
53
  url: opts.url,
54
54
  steps: opts.steps,
55
55
  memoryContext: opts.memoryContext,
56
+ projectId: opts.projectId,
56
57
  credential: opts.credential,
57
58
  }));
58
59
  });
@@ -29,14 +29,14 @@ async function post(path, body) {
29
29
  export async function runOnServer(input) {
30
30
  return post('/api/runs', input);
31
31
  }
32
- export async function createOnServer(request, url, existingTests, accountLabels) {
33
- return post('/api/create', { request, url, existingTests, accountLabels });
32
+ export async function createOnServer(request, url, existingTests, accountLabels, projectId) {
33
+ return post('/api/create', { request, url, existingTests, accountLabels, projectId });
34
34
  }
35
- export async function exploreOnServer(url, existingTests) {
36
- return post('/api/explore', { url, existingTests });
35
+ export async function exploreOnServer(url, existingTests, projectId, projectName) {
36
+ return post('/api/explore', { url, existingTests, projectId, projectName });
37
37
  }
38
- export async function coverageOnServer(diff, localTests) {
39
- return post('/api/coverage', { diff, localTests });
38
+ export async function coverageOnServer(diff, localTests, projectId, projectName) {
39
+ return post('/api/coverage', { diff, localTests, projectId, projectName });
40
40
  }
41
41
  export async function getDraftTest(code) {
42
42
  const apiKey = await readApiKey();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecolonylab/hivemind",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
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
5
  "type": "module",
6
6
  "license": "MIT",