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
2 changes: 1 addition & 1 deletion .mex/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A local-first TypeScript CLI and browser Hub that turns repository code and agen
- Ordinary reads never repair, migrate, or initialize state; mutations and maintenance must be explicit.
- Keep the package-root API limited to intentional exports from `src/index.ts`.
- Never weaken containment, freshness, privacy, or bounded-work checks to make a test pass.
- MEX never stages, commits, pushes, or pulls on a user's behalf.
- MEX may create a local setup commit only after the user reviews the exact setup-file diff and explicitly chooses the Hub commit action. Preserve unrelated staged work. MEX never pushes or pulls.

## Commands
- Dev: `npm run dev`
Expand Down
24 changes: 23 additions & 1 deletion .mex/ROUTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ edges:
condition: when starting a task — check the pattern index for a matching pattern file
- target: patterns/release-readme-visuals.md
condition: when refreshing the release README, badges, community links, or architecture illustrations
last_updated: 2026-09-10
last_updated: 2026-09-11
---

# Session Bootstrap
Expand All @@ -28,6 +28,28 @@ Then read this file fully before doing anything else in this session.
## Current Project State

**Working:**
- Incomplete checkouts open a Hub setup wizard from `mex hub` instead of the
full dashboard. Setup begins on a welcome screen, then runs the same ordered
`mex setup` steps through a headless engine, pauses at population with a
copyable prompt when no selected CLI is available. Claude/Codex population
runs as a cancellable background process; failures surface safe diagnostics.
Headless Claude pre-approves only the read-only graph, impact, and event-log
`mex` commands (Bash and PowerShell); other commands are still denied.
Finalization failures show their authored remediation in the Hub.
Both tools stream their visible assistant messages and compact fixed tool
activity labels to a read-only view with bounded scrollback, elapsed time,
and honest quiet periods. Commands, arguments, paths, and tool results are
omitted from the tool stream. History stays in process memory and reconnects
through cursor pages.
Mode and empty tool choices survive refresh. New code projects show a bounded
setup-file diff with numbered, highlighted additions/removals and an explicit
local commit action before in-place Hub promotion; the commit preserves
unrelated staged work and never pushes. The review lists per-file counts and
loads each file's diff on expand from the retained snapshot (128 Ki characters
per file, 1 Mi per review); any truncated diff still forces a manual commit.
Unsupported Git configurations retain the manual checkpoint. Setup never
runs git init, while Agent-memory completes without Graph or Wiki. Existing
committed code projects retain Hub Health recovery for missing local indexes.
- The MEX repository now dogfoods the ordinary `mex setup` path. Resumed setup
reuses persisted AI-tool selection even while population is incomplete, and
existing-codebase prompts merge missing knowledge without replacing authored
Expand Down
8 changes: 4 additions & 4 deletions .mex/context/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ edges:
# Broad overview: keep this empty unless a claim depends on a few specific symbols.
# Entry shape: { node: "function:<tier-1-id>", fingerprint: "mh:64:<hex>" }
grounds_to: []
last_updated: 2026-09-09
last_updated: 2026-09-10
mex:
id: mx_01M1M0CJ5C5XQV0HM5VM787WQS
type: architecture
status: promoted
revision: 6
revision: 10
title: architecture
relations:
- type: related_to
Expand Down Expand Up @@ -83,7 +83,7 @@ revision: 1
- **Code Graph (`src/graph/`)** — deterministic extraction, versioned SQLite storage, immutable read sessions, provenance/freshness checks, retrieval, impact, and explicit refresh/rebuild recovery.
- **Wiki (`src/wiki/`)** — treats repository Markdown as canonical, owns migration/validation/indexing, and exposes bounded query plus repository-adapter services.
- **Team workflows (`src/team/`)** — canonical Members, Activity, Workstreams, Inbox, and Relay records plus signed preview/apply services and isolated checkout-local state.
- **Project Hub (`src/hub/`, `packages/hub-contracts`, `packages/hub-web`)** — `runHubCommand()` composes the loopback server, private API contracts, repository adapters, durable local jobs, and route-lazy React workbench.
- **Project Hub (`src/hub/`, `packages/hub-contracts`, `packages/hub-web`)** — `launchHub()` opens the loopback server. Incomplete checkouts get a setup-only process that shares the CLI setup phases through `runHeadlessSetup()`. Claude Code and Codex run as owned background children. Their structured streams supply activity timing and a separate read-only transcript of assistant messages with compact fixed tool labels; command details and tool results are dropped before transcript retention. Cursor-based SSE replays a bounded process-memory history without copying it into run snapshots. After code setup finishes and the committed scaffold identity passes the existing Team authority check, an explicit action promotes the same listener and session into the full Hub. Existing committed code projects retain Health recovery for missing disposable indexes; Agent memory keeps its separate completion path.
- **Drift and agent workflows (`src/drift/`, `src/sync/`, `src/agent-skills/`)** — check grounded knowledge, prepare bounded repair briefs, and install the governed Inbox/Relay integrations.

<!-- mex:entity
Expand All @@ -94,7 +94,7 @@ revision: 1
-->
## External Dependencies

- **Git** — repository identity, revisions, sharing, and bounded read-only observations; product code never stages, commits, pushes, or pulls.
- **Git** — repository identity, revisions, sharing, and bounded read-only observations. The setup-only Hub also offers an explicit, revision-bound diff review and local commit of scoped setup files, preserving unrelated staged work. Ordinary reads do not stage or commit; product code never pushes or pulls.
- Claude Code or Codex may be launched for setup population; interactive sync
can use Claude Code, Codex, or OpenCode, and prompt-only fallback works with
any file-reading agent.
Expand Down
1 change: 1 addition & 0 deletions .mex/events/decisions.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"timestamp":"2026-09-10T12:18:18.332Z","kind":"decision","message":"Hub setup now promotes the same process to the Project Hub when Graph and Wiki are ready; the commit/restart screen is no longer the terminal UI.","files":["src/hub/command.ts","packages/hub-web/src/app/App.tsx"],"cwd":"packages/hub-web","source":"agent","status":"decided"}
88 changes: 85 additions & 3 deletions .mex/patterns/secure-local-project-hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ edges:
condition: "when persisting a Hub job or migrating team.db"
- target: "context/architecture.md"
condition: "when wiring a real Graph or Wiki adapter"
last_updated: 2026-09-10
last_updated: 2026-09-11
mex:
id: mx_01M1M0CJQ2BSV71G1C7TXZD9RH
type: pattern
status: promoted
revision: 4
revision: 10
title: secure-local-project-hub
grounds_to:
- node: function:188820af31f0d74c6518f6926f559877
Expand Down Expand Up @@ -86,8 +86,90 @@ preview/apply services.
process. Use a token-bound local lease and recover only a provably dead PID.
- Bundlers can rewrite a static `node:sqlite` import. Load it through
`createRequire(import.meta.url)` and smoke-test the packed CLI.
- Packed setup fixtures that hide agent CLIs must retain directly executable
Git. On Windows, expose native `git.exe` in its installation directory;
a `git.cmd` wrapper cannot satisfy a shell-free Git spawn. Preflight Git and
agent absence with the setup child's exact working directory and environment,
using an absolute lookup tool so a missing probe cannot imply agent absence.
- Terminal SSE events should close the browser connection immediately; do not
let `EventSource` reconnect to a finished job.
let `EventSource` reconnect to a finished job. Setup population pause is a
terminal setup-run status: return the prompt on the run snapshot, then close
the stream. Hub jobs still persist no prompts.
- New code checkouts stay in setup until population and finalization finish and
the existing Team authority check accepts the committed scaffold identity.
Show the Git checkpoint explicitly, with a bounded setup-file diff and a
separate user-requested local commit action. Preview and apply are authenticated
POSTs; bind reviewed files, HEAD, branch, and index to one expiring process-local
revision and revalidate before committing. Scope the candidate to canonical
setup files and selected agent assets; exclude generated databases, local state,
and unrelated project files. Preserve unrelated staged entries. Never push.
Render complete unified diffs as numbered context/addition/deletion rows with
explicit change markers and counts. Hide only recognized Git bookkeeping;
preserve file-mode and missing-newline information. Verify hunk counts before
formatting, mount source rows only for expanded files, and bound formatted
rows per file and across the review. Keep the exact raw diff available when
parsing, truncation or rendering limits prevent a complete formatted view.
Build the exact reviewed tree with an alternate index, create its commit object
with the reviewed parent and configured identity/signing, then publish through
a prepared Git ref transaction with the expected old HEAD. Revalidate the
symbolic branch while Git holds its ref locks; install only the reviewed index
entries alongside the preserved unrelated entries. Review authorization expires
after five minutes; cap it at 200 text files, 256 KiB per file, 32,768 diff
characters per file and 131,072 diff characters in total. Bound review/apply
operations to 60/120 seconds. Active commit/reference hooks, custom content
filters, detached HEAD and incomplete reviews retain the manual Git option.
Return the saved commit receipt even if subsequent Hub promotion fails, so
retries cannot duplicate a successful commit. If index installation cannot be rolled back
safely, retain its recovery file and require manual recovery before retrying
promotion. Promotion replaces
the app on the existing session and port, and its failure must fail the run.
Existing committed projects still open Hub with missing disposable indexes
so Health can offer repair. Agent-memory mode remains separate, even in Git.
Full Hub returns `CAPABILITY_UNAVAILABLE` for `/api/v1/setup`.
- Do not spawn interactive `mex setup` from Hub. Reuse `runHeadlessSetup()` so
detect → scaffold → tools → skills → identity → scan → graph → population →
finalize stay one path. Code-repo without git is a 400; the UI shows `git
init` and never runs it.
- Browser population owns an asynchronous headless CLI process, a private
prompt, a bounded deadline, and cancellation of its process tree. Graph
construction uses an isolated worker. Shutdown waits for setup to settle;
cancellation during promotion closes newly composed jobs before app swap.
Keep command construction shared with the terminal adapter and expose only
fixed, safe failure categories, never raw child diagnostics.
- Claude stream-json and Codex JSONL activity remain a closed vocabulary for
timing and completion. The explicitly requested setup console has a separate
projection of visible assistant prose plus fixed, compact tool labels such
as "Ran a command" and "Read a file". Drop tool arguments, command text, paths,
and results before transcript retention and delivery; keep prose as the main
content. Exclude provider user/system message blocks, reasoning, session
identifiers, usage metadata, and raw diagnostic envelopes. Render prose literally
with terminal controls removed and recognizable credentials masked; masking
is best effort, not a guarantee that arbitrary output contains no secrets.
No transcript content enters telemetry, durable jobs, or canonical knowledge.
- Keep transcript retention process-local and bounded by both UTF-8 text bytes
(1 MiB) and entry count (2,048), with bounded ingress and 4,096-character
entries. Send at most 32 entries per cursor page through an authenticated SSE
endpoint; report lost history on eviction, validate the run identity, honor
Last-Event-ID, and expire even backpressured streams. Subscriber notifications
carry no output backlog. Never put the transcript into repeated run snapshots.
Browser retention is independently bounded, with a limited text/row window,
literal selectable text, stable scrollback, and explicit Follow latest.
- Cap individual provider records, tool correlation, and SSE cadence; discard
malformed/oversized records and require recognized terminal success. Claude
partial/final text, Codex cumulative assistant text, and tool lifecycle
reports require deduplication.
Cancellation drops pending text flushes and prevents late writes to another
run. Activity timestamps advance only from real startup/provider reports;
browser clocks show elapsed/quiet time without API polling or invented percent
completion. Output-format flags do not require another AI session or change
the installed CLI's existing authentication configuration.
- Persist setup mode and explicit empty tool choices. Terminal snapshots
invalidate setup readiness and Hub capabilities; recover stream disconnects
because promotion can finish before the browser connects to SSE. A confirmed
completed setup can retry promotion without repeating AI or Graph work.
- Keep SetupPage and its contract lazy. Release guards track its exact `/setup`
redirect separately from operational routes and enforce a separate setup
asset allowance without relaxing existing Home or workbench budgets.
- A paginated source can hit its corpus safety bound independently of having a
next page. Expose these as separate signals; never turn an incomplete scan
into an exact total or silently mix revision-bound pages.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Then inspect the generated files:
git status --short
```

Review and run the exact scoped `git add` commands printed by setup. After committing that setup checkpoint, open the Hub:
Open `npx mex-agent@0.8.1 hub` to review the setup-file diff and explicitly commit it locally from the setup checkpoint. You can also review and run the scoped `git add` commands printed by setup, then commit through your Git client:

```bash
git commit -m "chore: initialize MEX"
Expand All @@ -112,7 +112,7 @@ npx mex-agent@0.8.1 hub
![Three steps to a ready project: run setup, populate memory, then review and commit the checkpoint before opening Hub.](docs/diagrams/readme/setup.svg)

> [!NOTE]
> The Hub starts only when the current `.mex/config.json` is committed at `HEAD`. MEX never stages, commits, pushes, or pulls.
> The full Hub opens when the current `.mex/config.json` is committed at `HEAD`. Its setup wizard can commit the reviewed setup files on your explicit request, preserving unrelated staged work. Repositories with unsupported Git hooks or content filters keep the manual checkpoint. MEX never pushes or pulls.

Push the reviewed setup commit through your team's normal Git workflow so teammates receive the same project memory and selected agent instructions. In Hub's Team/Members page, add the people who will participate and choose your local identity. Review and apply those actions explicitly; commit and push new Member records too. Your current-member selection stays local.

Expand Down Expand Up @@ -396,7 +396,7 @@ MEX 0.8 does **not** provide:

- a cloud-hosted Hub or hosted knowledge sync;
- live notifications, presence, or real-time chat;
- automatic Git staging, commits, pushes, or pulls;
- Git staging or commits without explicit review, or any pushes or pulls;
- authentication, repository authorization, or RBAC;
- Jira-style task management;
- a shared Code Graph or Wiki SQLite database;
Expand Down
4 changes: 4 additions & 0 deletions packages/hub-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
"./overview": {
"types": "./dist/overview.d.ts",
"import": "./dist/overview.js"
},
"./setup": {
"types": "./dist/setup.d.ts",
"import": "./dist/setup.js"
}
},
"scripts": {
Expand Down
52 changes: 52 additions & 0 deletions packages/hub-contracts/src/setup-activity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { SetupPopulationActivitySchema, SetupTranscriptBatchSchema } from "./setup.js";

const activity = {
tool: "codex", startedAt: "2026-09-10T12:00:00.000Z", lastActivityAt: "2026-09-10T12:00:10.000Z",
totalEvents: 1,
events: [{ id: 1, at: "2026-09-10T12:00:10.000Z", kind: "writing", state: "completed", target: "architecture" }],
};

describe("setup activity boundary", () => {
it.each(["claude", "codex"])("accepts only bounded %s activity summaries", (tool) => {
expect(SetupPopulationActivitySchema.parse({ ...activity, tool })).toEqual({ ...activity, tool });
});

it("rejects raw provider content and arbitrary labels or paths", () => {
for (const event of [
{ ...activity.events[0], text: "private output" },
{ ...activity.events[0], command: "private command" },
{ ...activity.events[0], kind: "Reading /private/project" },
{ ...activity.events[0], target: "/private/project" },
]) {
expect(SetupPopulationActivitySchema.safeParse({ ...activity, events: [event] }).success).toBe(false);
}
expect(SetupPopulationActivitySchema.safeParse({ ...activity, session_id: "private session" }).success).toBe(false);
});

it("caps retained history and rejects unbounded counters and invalid dates", () => {
expect(SetupPopulationActivitySchema.safeParse({ ...activity, events: Array(40).fill(activity.events[0]) }).success).toBe(true);
expect(SetupPopulationActivitySchema.safeParse({ ...activity, events: Array(41).fill(activity.events[0]) }).success).toBe(false);
expect(SetupPopulationActivitySchema.safeParse({ ...activity, totalEvents: Infinity }).success).toBe(false);
expect(SetupPopulationActivitySchema.safeParse({ ...activity, lastActivityAt: "yesterday" }).success).toBe(false);
expect(SetupPopulationActivitySchema.safeParse({ ...activity, lastActivityAt: null }).success).toBe(true);
});
});

describe("setup transcript boundary", () => {
const entry = { id: 1, at: "2026-09-10T12:00:00.000Z", kind: "assistant", text: "Actual output", truncated: false };
const batch = { runId: "00000000-0000-4000-8000-000000000192", entries: [entry], cursor: 1, firstId: 1, truncated: false, done: false };
it("accepts visible literal output while rejecting internal metadata and unknown channels", () => {
expect(SetupTranscriptBatchSchema.parse(batch)).toEqual(batch);
expect(SetupTranscriptBatchSchema.safeParse({ ...batch, entries: [{ ...entry, text: '<script>alert("plain text")</script>' }] }).success).toBe(true);
for (const changed of [{ ...entry, kind: "reasoning" }, { ...entry, session_id: "private" }, { ...entry, usage: {} }]) {
expect(SetupTranscriptBatchSchema.safeParse({ ...batch, entries: [changed] }).success).toBe(false);
}
});
it("bounds pages and individual output before browser parsing", () => {
expect(SetupTranscriptBatchSchema.safeParse({ ...batch, entries: Array(33).fill(entry) }).success).toBe(false);
expect(SetupTranscriptBatchSchema.safeParse({ ...batch, entries: [{ ...entry, text: "x".repeat(4097) }] }).success).toBe(false);
expect(SetupTranscriptBatchSchema.safeParse({ ...batch, cursor: Infinity }).success).toBe(false);
expect(SetupTranscriptBatchSchema.safeParse({ ...batch, runId: "provider-session-id" }).success).toBe(false);
});
});
Loading
Loading