Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ evals token-savings and round-trip harness
Unit tests cover handlers, storage, and protocol contracts in isolation. They cannot catch issues that only show up in a globally-installed binary: bin-shim symlink resolution, ESM chunk shebangs, `prepublishOnly` staging, native `better-sqlite3` resolution, dynamic-import bundling. Those failure modes have bitten this repo before — they are now guarded by a dedicated script.

- `bash scripts/e2e-publish.sh` — covers the **changeset publish** path (CI default). Builds, packs (mirroring what `changeset publish` ships), installs into an isolated `.e2e/` prefix with an isolated `$HOME`, drives every Claude Code hook event with a realistic payload, exercises FTS search and the MCP server, then uninstalls. Self-cleans on success. Required to pass in CI before `changeset publish` runs.
- `bash scripts/e2e-pack-release.sh` — covers the **`pnpm publish:release`** path (legacy bespoke flow that uses `apps/cli/scripts/pack-release.mjs` to write `apps/cli/release/`). Run this if you change `pack-release.mjs` or the `dependencies` block of `apps/cli/package.json`.
- The 15 numbered checks in `e2e-publish.sh` must stay green. If you change anything in `apps/cli/`, `packages/installers/`, the hook handler stdout/stderr contract, or the publish surface, re-run it locally before opening a PR.
- Touching the tsup config, the `prepublishOnly` script, or the bin entrypoint guards (`isMainEntry()`) without re-running both scripts is a defect.
- Touching the tsup config, the `prepublishOnly` script, or the bin entrypoint guards (`isMainEntry()`) without re-running `scripts/e2e-publish.sh` is a defect.

## Extension points

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ These mechanisms are intentionally simple. Colony favors observable signals, dec
- MCP transport is stdio-based, so IDE/runtime restarts can close the server process; the next tool call should reconnect through the installed config.
- The viewer is useful for inspection, but the primary workflow is still terminal/agent-driven.

## Demo App

`apps/hivemind-demo` is a private pedagogical artifact. It models a deterministic multi-agent loop in-process so coordination ideas can be tested without launching real IDE agents.

## Roadmap

- Finish release hygiene for the renamed `colony` package.
Expand Down
11 changes: 2 additions & 9 deletions apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,14 @@
"colony": "./dist/index.js"
},
"main": "./dist/index.js",
"files": [
"dist",
"hooks-scripts",
"README.md",
"LICENSE"
],
"files": ["dist", "hooks-scripts", "README.md", "LICENSE"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch --onSuccess \"node dist/index.js\"",
"test": "vitest run",
"typecheck": "tsc --noEmit",
"stage-publish": "node scripts/prepack.mjs",
"prepublishOnly": "node scripts/prepack.mjs",
"pack:release": "pnpm build && node scripts/pack-release.mjs",
"publish:release": "pnpm pack:release && npm publish ./release --access public"
"prepublishOnly": "node scripts/prepack.mjs"
},
"dependencies": {
"commander": "^12.1.0",
Expand Down
58 changes: 0 additions & 58 deletions apps/cli/scripts/pack-release.mjs

This file was deleted.

16 changes: 5 additions & 11 deletions apps/cli/src/commands/backfill.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { join } from 'node:path';
import { loadSettings, resolveDataDir } from '@colony/config';
import { loadSettings } from '@colony/config';
import { inferIdeFromSessionId } from '@colony/core';
import { Storage } from '@colony/storage';
import type { Command } from 'commander';
import { withStorage } from '../util/store.js';

/**
* `colony backfill ide` heals sessions rows whose stored ide is `'unknown'`
Expand All @@ -25,16 +24,11 @@ export function registerBackfillCommand(program: Command): void {
.description('Re-infer the ide column for sessions stored as unknown.')
.action(async () => {
const settings = loadSettings();
const storage = new Storage(join(resolveDataDir(settings.dataDir), 'data.db'));
try {
const { scanned, updated } = storage.backfillUnknownIde((id) =>
inferIdeFromSessionId(id),
);
await withStorage(settings, (storage) => {
const { scanned, updated } = storage.backfillUnknownIde((id) => inferIdeFromSessionId(id));
process.stdout.write(
`backfill ide: scanned=${scanned} updated=${updated} remaining=${scanned - updated}\n`,
);
} finally {
storage.close();
}
});
});
}
16 changes: 6 additions & 10 deletions apps/cli/src/commands/debrief.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { join } from 'node:path';
import { loadSettings, resolveDataDir } from '@colony/config';
import { Storage } from '@colony/storage';
import { loadSettings } from '@colony/config';
import type { Storage } from '@colony/storage';
import type { Command } from 'commander';
import kleur from 'kleur';
import { withStorage } from '../util/store.js';

/**
* Default window: last 24h. The "ran it today" common case. Overridable
Expand Down Expand Up @@ -179,11 +179,9 @@ export function registerDebriefCommand(program: Command): void {
.description('End-of-day collaboration post-mortem: 5 structured sections over DB evidence.')
.option('--hours <n>', 'Window size in hours', String(DEFAULT_HOURS))
.option('--task <id>', 'Narrow the timeline section to a specific task thread')
.action((opts: { hours: string; task?: string }) => {
.action(async (opts: { hours: string; task?: string }) => {
const settings = loadSettings();
const dbPath = join(resolveDataDir(settings.dataDir), 'data.db');
const storage = new Storage(dbPath);
try {
await withStorage(settings, (storage) => {
const ctx: DebriefContext = {
storage,
since: Date.now() - Number(opts.hours) * 3_600_000,
Expand All @@ -206,8 +204,6 @@ export function registerDebriefCommand(program: Command): void {
' • Which failures were missing-tool vs. tool-not-called vs. structural?\n',
);
process.stdout.write(' • What was the most valuable moment the system created?\n');
} finally {
storage.close();
}
});
});
}
9 changes: 3 additions & 6 deletions apps/cli/src/commands/doctor.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { loadSettings, resolveDataDir, settingsPath } from '@colony/config';
import { Storage } from '@colony/storage';
import type { Command } from 'commander';
import kleur from 'kleur';
import { dataDbPath, withStorage } from '../util/store.js';

export function registerDoctorCommand(program: Command): void {
program
Expand All @@ -17,11 +16,9 @@ export function registerDoctorCommand(program: Command): void {
const settings = loadSettings();
const dir = resolveDataDir(settings.dataDir);
process.stdout.write(`dataDir: ${dir}\n`);
const dbPath = join(dir, 'data.db');
const dbPath = dataDbPath(settings);
try {
const s = new Storage(dbPath);
const sessions = s.listSessions(1).length;
s.close();
const sessions = await withStorage(settings, (s) => s.listSessions(1).length);
process.stdout.write(`db: ${dbPath} ${kleur.green('ok')} (${sessions} sessions)\n`);
} catch (err) {
process.stdout.write(`db: ${dbPath} ${kleur.red('fail')} ${String(err)}\n`);
Expand Down
38 changes: 17 additions & 21 deletions apps/cli/src/commands/export.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { loadSettings, resolveDataDir } from '@colony/config';
import { Storage } from '@colony/storage';
import { loadSettings } from '@colony/config';
import type { Command } from 'commander';
import { z } from 'zod';
import { withStorage } from '../util/store.js';

const SessionRecord = z.object({
type: z.literal('session'),
Expand Down Expand Up @@ -34,18 +33,20 @@ export function registerExportCommand(program: Command): void {
.description('Export memory to JSONL')
.action(async (out: string) => {
const settings = loadSettings();
const s = new Storage(join(resolveDataDir(settings.dataDir), 'data.db'), {
readonly: true,
});
const lines: string[] = [];
for (const sess of s.listSessions(10000)) {
lines.push(JSON.stringify({ type: 'session', ...sess }));
for (const o of s.timeline(sess.id, undefined, 10000)) {
lines.push(JSON.stringify({ type: 'observation', ...o }));
}
}
await withStorage(
settings,
(s) => {
for (const sess of s.listSessions(10000)) {
lines.push(JSON.stringify({ type: 'session', ...sess }));
for (const o of s.timeline(sess.id, undefined, 10000)) {
lines.push(JSON.stringify({ type: 'observation', ...o }));
}
}
},
{ readonly: true },
);
writeFileSync(out, lines.join('\n'));
s.close();
process.stdout.write(`wrote ${out} (${lines.length} records)\n`);
});

Expand All @@ -54,20 +55,17 @@ export function registerExportCommand(program: Command): void {
.description('Import memory from JSONL')
.action(async (file: string) => {
const settings = loadSettings();
const s = new Storage(join(resolveDataDir(settings.dataDir), 'data.db'));
const lines = readFileSync(file, 'utf8').split(/\n+/);
let n = 0;
try {
await withStorage(settings, (s) => {
for (let i = 0; i < lines.length; i++) {
const raw = lines[i];
if (!raw) continue;
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch (err) {
throw new Error(
`${file}:${i + 1}: invalid JSON — ${(err as Error).message}`,
);
throw new Error(`${file}:${i + 1}: invalid JSON — ${(err as Error).message}`);
}
const result = ImportRecord.safeParse(parsed);
if (!result.success) {
Expand Down Expand Up @@ -97,9 +95,7 @@ export function registerExportCommand(program: Command): void {
}
n++;
}
} finally {
s.close();
}
});
process.stdout.write(`imported ${n} records\n`);
});
}
30 changes: 9 additions & 21 deletions apps/cli/src/commands/foraging.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { join } from 'node:path';
import { loadSettings, resolveDataDir } from '@colony/config';
import { MemoryStore } from '@colony/core';
import { loadSettings } from '@colony/config';
import type { MemoryStore } from '@colony/core';
import { scanExamples } from '@colony/foraging';
import type { Command } from 'commander';
import kleur from 'kleur';
import { withStore } from '../util/store.js';

const FORAGING_SESSION_ID = 'foraging';

Expand Down Expand Up @@ -37,9 +37,7 @@ export function registerForagingCommand(program: Command): void {
return;
}
const repo_root = opts.cwd ?? process.cwd();
const dbPath = join(resolveDataDir(settings.dataDir), 'data.db');
const store = new MemoryStore({ dbPath, settings });
try {
await withStore(settings, (store) => {
ensureForagingSession(store);
const result = scanExamples({
repo_root,
Expand All @@ -56,9 +54,7 @@ export function registerForagingCommand(program: Command): void {
process.stdout.write(
`${kleur.green('✓')} foraging: ${result.scanned.length} source(s), ${changed} re-indexed, ${result.skipped_unchanged} skipped (unchanged), ${result.indexed_observations} observation(s)\n`,
);
} finally {
store.close();
}
});
});

group
Expand All @@ -68,9 +64,7 @@ export function registerForagingCommand(program: Command): void {
.action(async (opts: { cwd?: string }) => {
const settings = loadSettings();
const repo_root = opts.cwd ?? process.cwd();
const dbPath = join(resolveDataDir(settings.dataDir), 'data.db');
const store = new MemoryStore({ dbPath, settings });
try {
await withStore(settings, (store) => {
const rows = store.storage.listExamples(repo_root);
if (rows.length === 0) {
process.stdout.write(
Expand All @@ -84,9 +78,7 @@ export function registerForagingCommand(program: Command): void {
` ${kleur.cyan(r.example_name.padEnd(28))} ${kleur.dim((r.manifest_kind ?? 'unknown').padEnd(8))} ${r.observation_count} obs ${kleur.dim(when)}\n`,
);
}
} finally {
store.close();
}
});
});

group
Expand All @@ -97,9 +89,7 @@ export function registerForagingCommand(program: Command): void {
.action(async (opts: { cwd?: string; example?: string }) => {
const settings = loadSettings();
const repo_root = opts.cwd ?? process.cwd();
const dbPath = join(resolveDataDir(settings.dataDir), 'data.db');
const store = new MemoryStore({ dbPath, settings });
try {
await withStore(settings, (store) => {
const targets = opts.example
? store.storage.listExamples(repo_root).filter((r) => r.example_name === opts.example)
: store.storage.listExamples(repo_root);
Expand All @@ -115,8 +105,6 @@ export function registerForagingCommand(program: Command): void {
process.stdout.write(
`${kleur.green('✓')} cleared ${targets.length} example(s), dropped ${dropped} observation(s)\n`,
);
} finally {
store.close();
}
});
});
}
Loading