intercomm-aimfp 0.4.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/README.md +353 -0
- package/dist/claude-tui.d.ts +7 -0
- package/dist/claude-tui.d.ts.map +1 -0
- package/dist/claude-tui.js +55 -0
- package/dist/claude-tui.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +144 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/db.d.ts +9 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +117 -0
- package/dist/db.js.map +1 -0
- package/dist/fs-wrapper.d.ts +3 -0
- package/dist/fs-wrapper.d.ts.map +1 -0
- package/dist/fs-wrapper.js +9 -0
- package/dist/fs-wrapper.js.map +1 -0
- package/dist/git-wrapper.d.ts +11 -0
- package/dist/git-wrapper.d.ts.map +1 -0
- package/dist/git-wrapper.js +44 -0
- package/dist/git-wrapper.js.map +1 -0
- package/dist/mcp-entry.d.ts +3 -0
- package/dist/mcp-entry.d.ts.map +1 -0
- package/dist/mcp-entry.js +8 -0
- package/dist/mcp-entry.js.map +1 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +557 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/orchestrate.d.ts +52 -0
- package/dist/orchestrate.d.ts.map +1 -0
- package/dist/orchestrate.js +226 -0
- package/dist/orchestrate.js.map +1 -0
- package/dist/protocol.d.ts +3 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +33 -0
- package/dist/protocol.js.map +1 -0
- package/dist/store.d.ts +26 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +186 -0
- package/dist/store.js.map +1 -0
- package/dist/task-contract.d.ts +4 -0
- package/dist/task-contract.d.ts.map +1 -0
- package/dist/task-contract.js +111 -0
- package/dist/task-contract.js.map +1 -0
- package/dist/tmux-wrapper.d.ts +17 -0
- package/dist/tmux-wrapper.d.ts.map +1 -0
- package/dist/tmux-wrapper.js +66 -0
- package/dist/tmux-wrapper.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +56 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -0
- package/system-prompt.md +245 -0
package/README.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# InterComm AIMFP
|
|
2
|
+
|
|
3
|
+
Local-only coordination system for multiple Claude Code instances working on the same project. One instance is master, the rest are workers. The master delegates tasks and controls workers via tmux. All state lives in a single SQLite database — no servers, no HTTP, no sockets.
|
|
4
|
+
|
|
5
|
+
**Requires tmux** and the **AIMFP MCP server**. InterComm AIMFP is a hard AIMFP addon — the worker flow runs `aimfp_run` and `git_create_branch`, and the master integrates each branch via `export_state_changeset` / `apply_state_changeset`. Without the AIMFP MCP server connected (listed in the project's `.mcp.json`), instances can still coordinate and isolate files via worktrees, but there is **no AIMFP tracking and no semantic-changeset merge**. (InterComm's own code stays AIMFP-agnostic — it never reads AIMFP's DB.)
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────────────┐
|
|
11
|
+
│ User ↔ Master (Claude Code) │
|
|
12
|
+
│ │ │
|
|
13
|
+
│ ├── tmux send-keys ──→ Worker-1 (tmux session) │
|
|
14
|
+
│ ├── tmux send-keys ──→ Worker-2 (tmux session) │
|
|
15
|
+
│ └── tmux send-keys ──→ Worker-N (tmux session) │
|
|
16
|
+
│ │
|
|
17
|
+
│ All instances share: .intercomm-aimfp/intercomm.db │
|
|
18
|
+
└─────────────────────────────────────────────────────┘
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
1. The user talks to the **master** instance only.
|
|
22
|
+
2. The user either tells the master "I have N tmux sessions available" or the master asks "please spin up N tmux sessions for this task."
|
|
23
|
+
3. The master assigns work via `intercomm_assign` — a **thin-pointer task contract** that points the worker at an AIMFP work entity — and **wakes** the worker (via `intercomm_wake` / tmux send-keys).
|
|
24
|
+
4. Each worker registers, reads its contract, runs `aimfp_run` in its own git worktree, continues the assigned AIMFP entity on its own branch, then reports `done` (with `branch` + `commit`) via InterComm.
|
|
25
|
+
5. Workers do **not** poll. They sit idle until the master pushes a prompt via tmux.
|
|
26
|
+
|
|
27
|
+
InterComm stays **AIMFP-agnostic**: it isolates files (worktrees), carries the pointer contract, and tracks status — it never reads AIMFP's DB and never runs `git merge`. The master uses AIMFP's `export_state_changeset` / `apply_state_changeset` to integrate each worker's DB tracking (see [Integration](#integration)).
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
1. Copy the `intercommAIMFP/` folder somewhere permanent on your machine.
|
|
32
|
+
|
|
33
|
+
2. Install dependencies and build:
|
|
34
|
+
```bash
|
|
35
|
+
cd /path/to/intercommAIMFP
|
|
36
|
+
npm install
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. Add the MCP server to your project's `.mcp.json` (create it in your project root if it doesn't exist):
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"intercomm": {
|
|
45
|
+
"command": "node",
|
|
46
|
+
"args": ["/absolute/path/to/intercommAIMFP/dist/mcp-entry.js"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
Replace `/absolute/path/to/intercommAIMFP` with the actual path on your machine.
|
|
52
|
+
|
|
53
|
+
That's it — there is **no protocol to paste**. The master/worker coordination
|
|
54
|
+
protocol is delivered automatically via the MCP server's `instructions` field the
|
|
55
|
+
moment any instance connects (master or worker), the same way AIMFP injects its
|
|
56
|
+
rules. It travels with the server into every project, so your `CLAUDE.md` stays
|
|
57
|
+
yours (e.g. AIMFP-only) and is never touched. Any instance can re-read the full
|
|
58
|
+
protocol on demand with the `intercomm_get_protocol` tool — useful after a long
|
|
59
|
+
session compacts context. (`system-prompt.md` in this repo is the single in-repo
|
|
60
|
+
source for that injected text; edit it there and rebuild.)
|
|
61
|
+
|
|
62
|
+
## Install as a Claude Code plugin
|
|
63
|
+
|
|
64
|
+
InterComm AIMFP is also packaged as a **Claude Code plugin** (the same model as
|
|
65
|
+
AIMFP). Installing the plugin is the supported one-step distribution path — it
|
|
66
|
+
wires up the MCP server, the slash commands, the setup skill, and the hooks in
|
|
67
|
+
any project, and auto-updates the server. The manual `.mcp.json` install above is
|
|
68
|
+
the **development** path (running your local `dist/`); the plugin is the
|
|
69
|
+
**distribution** path. Use one or the other for a given project, not both, or you
|
|
70
|
+
will get a duplicate `intercomm` server.
|
|
71
|
+
|
|
72
|
+
Once the package is published to npm and this repo is pushed:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# add this repo as a plugin marketplace, then install
|
|
76
|
+
claude plugin marketplace add aryanduntley/intercommAIMFP
|
|
77
|
+
claude plugin install intercomm-aimfp
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The plugin's `intercomm-plugin/.mcp.json` runs the server via
|
|
81
|
+
`npx -y -p intercomm-aimfp@latest intercomm-mcp`, so npm compiles the native
|
|
82
|
+
`better-sqlite3` binding for each machine on first run and `@latest` keeps the
|
|
83
|
+
server current — no bundled binaries. It ships:
|
|
84
|
+
|
|
85
|
+
- **MCP server** — all 19 coordination tools, with the protocol auto-injected via the server's `instructions` field.
|
|
86
|
+
- **Commands** — `/register`, `/spawn`, `/status`, `/teardown`.
|
|
87
|
+
- **Skill** — `intercomm-mode` (a tiny setup bootstrap; the real protocol lives in the injected instructions).
|
|
88
|
+
- **Hooks** — a `SessionStart` note on how to begin coordinating.
|
|
89
|
+
|
|
90
|
+
> **Requires the AIMFP MCP server.** InterComm AIMFP is a hard AIMFP addon. The
|
|
91
|
+
> plugin does not bundle AIMFP — install it separately (its own plugin or
|
|
92
|
+
> `python3 -m aimfp` in your `.mcp.json`). The master's startup preflight warns if
|
|
93
|
+
> the AIMFP tools are absent.
|
|
94
|
+
|
|
95
|
+
**Local development / testing (before publishing):**
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# load the plugin straight from this working tree
|
|
99
|
+
claude --plugin-dir /path/to/intercommAIMFP
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Releasing (versioning + publish)
|
|
103
|
+
|
|
104
|
+
Bump every embedded version in one step, then commit and push — CI publishes:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
node dev/bump-version.mjs <X.Y.Z> # syncs package.json + .claude-plugin/plugin.json
|
|
108
|
+
# + the McpServer version in src/mcp-server.ts,
|
|
109
|
+
# resyncs the lockfile, then runs `npm run build`
|
|
110
|
+
git commit -am "release X.Y.Z" && git push
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`.github/workflows/publish.yml` publishes `intercomm-aimfp@X.Y.Z` to npm on push to
|
|
114
|
+
`main` whenever the version is new (idempotent). It needs the repo secret
|
|
115
|
+
`NPM_TOKEN`; see the one-time setup below.
|
|
116
|
+
|
|
117
|
+
### One-time npm + CI setup
|
|
118
|
+
|
|
119
|
+
Manual, one-time steps to connect GitHub → npm. After these, `bump → commit →
|
|
120
|
+
push` is the whole release.
|
|
121
|
+
|
|
122
|
+
1. **npm account + name.** Have an npm account and `npm login`. The package name
|
|
123
|
+
`intercomm-aimfp` must be free or owned by you (currently unpublished).
|
|
124
|
+
2. **First publish by hand.** CI publishes *updates*, but the package must exist
|
|
125
|
+
first. Once, from the repo root:
|
|
126
|
+
```bash
|
|
127
|
+
npm install && npm run build && npm publish --access public
|
|
128
|
+
```
|
|
129
|
+
(`--access public` is required the first time for an unscoped public package.)
|
|
130
|
+
3. **Create an npm token.** npm → Account → Access Tokens → Generate → **Automation**
|
|
131
|
+
type (works in CI without 2FA). Copy it.
|
|
132
|
+
4. **Add it to GitHub.** Repo → Settings → Secrets and variables → Actions → New
|
|
133
|
+
repository secret → name it **`NPM_TOKEN`**, paste the token.
|
|
134
|
+
|
|
135
|
+
That is the *entire* GitHub↔npm connection. There is **no separate "worker" to
|
|
136
|
+
create** — the workflow file `.github/workflows/publish.yml` *is* the worker;
|
|
137
|
+
GitHub Actions runs it automatically once it is on `main`. (Actions is enabled by
|
|
138
|
+
default. The native `better-sqlite3` build is skipped in CI via `--ignore-scripts`;
|
|
139
|
+
the published artifact is `dist/` + `system-prompt.md`, and the consumer's
|
|
140
|
+
`npx`/`npm install` compiles the native binding.)
|
|
141
|
+
|
|
142
|
+
## Usage
|
|
143
|
+
|
|
144
|
+
The master spawns and controls workers itself through InterComm's MCP tools — no shell scripts and no manual tmux setup required. (The `scripts/*.sh` helpers remain as a dev-only fallback; see [Dev scripts](#dev-scripts).)
|
|
145
|
+
|
|
146
|
+
### 1. Register the master
|
|
147
|
+
|
|
148
|
+
In your main Claude Code instance, register as master (via the `intercomm_register(role: "master")` tool, or just tell it "you are the master"). This must happen **before** spawning workers, so workers can message `master` on startup.
|
|
149
|
+
|
|
150
|
+
### 2. Spawn workers automatically
|
|
151
|
+
|
|
152
|
+
From the master, run:
|
|
153
|
+
|
|
154
|
+
From the master, call the **`intercomm_spawn`** tool:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
intercomm_spawn(count: 3)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
This creates 3 detached tmux sessions (`worker-1`, `worker-2`, `worker-3`), launches Claude Code in each (with `INTERCOMM_DB_ROOT` pinned to the shared bus, so they load `.mcp.json` + `CLAUDE.md`), auto-clears the first-run trust / MCP-approval dialogs, and wakes each to register and read its task. It returns immediately with the session list — workers self-register asynchronously, so call `intercomm_status` to confirm the `session ↔ worker-N` mapping.
|
|
161
|
+
|
|
162
|
+
Useful parameters:
|
|
163
|
+
|
|
164
|
+
| Param | Effect |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `worktrees: true` | Launch each worker in its own isolated git worktree (branch-per-agent) |
|
|
167
|
+
| `worktree_base` | Git ref each worktree checks out detached (default: `main`) |
|
|
168
|
+
| `bootstrap` | Setup command run inside each new worktree (e.g. `npm install`) |
|
|
169
|
+
| `perm_mode` | `claude --permission-mode` (default: `acceptEdits`; `bypassPermissions` for fully hands-off) |
|
|
170
|
+
| `prefix` | tmux session-name prefix (default: `worker`) |
|
|
171
|
+
| `ready_timeout` | Per-session seconds to clear boot dialogs (default: 20) |
|
|
172
|
+
| `wake: false` | Create + launch only; wake them yourself later via `intercomm_wake` |
|
|
173
|
+
|
|
174
|
+
### 3. Approve flagged permission prompts
|
|
175
|
+
|
|
176
|
+
In the default `acceptEdits` mode, workers auto-accept file edits but **pause on Bash and other tools**. A blocked worker is frozen and cannot report over InterComm, so the master polls for them with **`intercomm_scan`**:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
intercomm_scan()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
It reports each worker pane's state (`blocked`, `trust`, `bypass`, `ready`, `running`, `idle`). Clear any pane that needs attention with **`intercomm_approve`**, which selects the right option for whichever dialog it's on:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
intercomm_approve(worker: "worker-1")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
To skip approvals entirely, spawn with `perm_mode: "bypassPermissions"`.
|
|
189
|
+
|
|
190
|
+
### 4. Let it run, then tear down
|
|
191
|
+
|
|
192
|
+
The master delegates via **`intercomm_assign`** (which builds the thin-pointer task contract, records it, and wakes the worker), monitors with `intercomm_scan` / `intercomm_read`, and collects results. When done, **`intercomm_teardown`** kills the sessions, removes the worktrees, and reaps the registry in one call:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
intercomm_assign(
|
|
196
|
+
worker: "worker-1",
|
|
197
|
+
role_instructions: "AIMFP user=alice; stay within src/auth/**; report branch+commit when done",
|
|
198
|
+
target_type: "task", target_id: 42,
|
|
199
|
+
)
|
|
200
|
+
intercomm_teardown() # pass worktrees: true if you spawned with worktrees
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Task Contract (thin pointer)
|
|
204
|
+
|
|
205
|
+
The master does **not** ship prose instructions to a worker. It ships a small JSON **pointer** to an AIMFP work entity; the worker resolves it itself by running `aimfp_run` in its clone and continuing that entity the normal AIMFP way (its own `git_create_branch` on `aimfp-{user}-{number}`, its own tracking + validation). InterComm carries the contract as opaque message content — it never resolves the pointer and never reads `project.db`.
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"kind": "task_contract",
|
|
210
|
+
"v": 2,
|
|
211
|
+
"role": "worker",
|
|
212
|
+
"role_instructions": "Continue the assigned AIMFP entity. Stay within your assigned files.",
|
|
213
|
+
"aimfp_target": { "type": "task", "id": 42, "slug": "task-implement-auth-9f3a1c20" },
|
|
214
|
+
"reportBack": ["branch", "commit"]
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
| Field | Meaning |
|
|
219
|
+
|---|---|
|
|
220
|
+
| `role` / `role_instructions` | Worker role label + free-form guidance (assigned files, a **distinct AIMFP user identity** per worker so `aimfp-{user}-{number}` branches don't collide). |
|
|
221
|
+
| `aimfp_target` | The AIMFP entity to continue: `type` (task / milestone / subtask / sidequest / item) plus an integer `id` and/or a stable `slug` (at least one). |
|
|
222
|
+
| `reportBack` | Fields the worker returns on `done` — at minimum `branch` + `commit`, so the master can export a changeset from the branch. |
|
|
223
|
+
|
|
224
|
+
`intercomm_assign` builds this for you; `intercomm_read` shows the worker a validated summary (or an error if the contract is malformed). A worker that can't parse the contract sends a `question` and waits — it never acts on a bad contract.
|
|
225
|
+
|
|
226
|
+
## Integration
|
|
227
|
+
|
|
228
|
+
When a worker reports `done`, the master integrates its branch — **source and AIMFP DB state are merged by different mechanisms**:
|
|
229
|
+
|
|
230
|
+
1. **Text-merge the worker's source** into `main` (normal code review / conflict resolution). The binary `project.db` is **never** git-merged.
|
|
231
|
+
2. **Export the worker's DB tracking** as a semantic changeset: AIMFP `export_state_changeset(base_main_commit, branch)` — an integer-free diff keyed on stable semantic keys.
|
|
232
|
+
3. **Apply it onto main**: AIMFP `apply_state_changeset(changeset)` — a 3-way merge that auto-applies non-overlapping changes, mints canonical IDs, rewrites references, and reports conflicts for the master to resolve.
|
|
233
|
+
4. **Commit** merged source + updated `project.db` to main; move to the next branch.
|
|
234
|
+
|
|
235
|
+
InterComm only **tracks** the lifecycle (`intercomm_worktree_list` is the master's merge-queue view); the DB merge intelligence lives entirely in AIMFP.
|
|
236
|
+
|
|
237
|
+
> **Parallel git-worktree runs are supported and validated end-to-end** — 4 workers in isolated worktrees through the full `export_state_changeset` → `apply_state_changeset` merge chain, including concurrent shared-type conflict prediction + resolution. Each worker's AIMFP tracking is committed on its own branch (requires the worktree-aware AIMFP build). One residual AIMFP-side caveat: a worker should treat `<its worktree>/src` as the source dir even if `get_source_directory()` still returns the shared-checkout path — the worker pre-flight guard checks this.
|
|
238
|
+
|
|
239
|
+
### Dev scripts
|
|
240
|
+
|
|
241
|
+
`scripts/spawn-workers.sh`, `scan-workers.sh`, and `kill-workers.sh` predate the tools and remain for local development / debugging only — they are **not** a runtime dependency. The MCP tools above are the supported path when InterComm is dropped into any project as an MCP server. (Claude Code's TUI needs a double `Enter` to submit a prompt; the tools and scripts both handle that for you.)
|
|
242
|
+
|
|
243
|
+
## MCP Tools (19 total)
|
|
244
|
+
|
|
245
|
+
**Identity & messaging**
|
|
246
|
+
|
|
247
|
+
| Tool | Description |
|
|
248
|
+
|---|---|
|
|
249
|
+
| `intercomm_register` | Register as master or worker. Initializes DB. Workers auto-assign lowest available `worker-N` name. |
|
|
250
|
+
| `intercomm_send` | Send a direct message to a specific peer. |
|
|
251
|
+
| `intercomm_broadcast` | Broadcast a message to all peers. |
|
|
252
|
+
| `intercomm_read` | Read all new messages since last check. Updates read cursor. For `task` messages, also surfaces the parsed, validated thin-pointer contract. |
|
|
253
|
+
| `intercomm_escalate` | **Worker→master, no polling.** Persist a question/decision to the master AND wake it in its tmux pane (the server does the wake; the worker never touches tmux). Set `needs_user` when the master must ask the human. Returns `{persisted, woke, reason?}`. |
|
|
254
|
+
| `intercomm_assign` | Master-only. Assign work as a thin-pointer task contract (`{role, role_instructions, aimfp_target, reportBack}`): builds it, records the `task` message, and wakes the worker. |
|
|
255
|
+
| `intercomm_status` | Show all instances and their state. |
|
|
256
|
+
| `intercomm_signoff` | Cleanly deactivate this instance before shutting down. |
|
|
257
|
+
| `intercomm_clear` | Delete old messages (master-only). |
|
|
258
|
+
| `intercomm_get_protocol` | Re-read the full master/worker coordination protocol on demand (the same text auto-injected via the server's MCP `instructions` on connect). No registration required. |
|
|
259
|
+
|
|
260
|
+
**Orchestration (master-only) — the tool-driven lifecycle, no shell scripts**
|
|
261
|
+
|
|
262
|
+
| Tool | Description |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `intercomm_spawn` | Spawn N workers in detached tmux sessions; launch claude (optionally one git worktree each), auto-clear boot dialogs, wake to self-register. Non-blocking. |
|
|
265
|
+
| `intercomm_wake` | Push a prompt into a worker's pane (resolves a worker id or raw tmux target). |
|
|
266
|
+
| `intercomm_scan` | Report each worker pane's state (blocked / trust / bypass / ready / running / idle). |
|
|
267
|
+
| `intercomm_approve` | Clear a worker's blocking dialog (trust / MCP-approval / bypass / permission prompt). |
|
|
268
|
+
| `intercomm_teardown` | Kill worker sessions, remove their worktrees, and reap the instance rows in one call. |
|
|
269
|
+
|
|
270
|
+
**Worktree registry (master-only) — branch-per-agent isolation**
|
|
271
|
+
|
|
272
|
+
| Tool | Description |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `intercomm_worktree_add` | Create an isolated git worktree (detached) for a worker and register it. |
|
|
275
|
+
| `intercomm_worktree_list` | List registered worktrees and their lifecycle status (the merge-queue view). |
|
|
276
|
+
| `intercomm_worktree_set_status` | Update a worktree's status; record the branch a worker reported back. |
|
|
277
|
+
| `intercomm_worktree_remove` | Remove a worker's git worktree and mark it removed. |
|
|
278
|
+
|
|
279
|
+
## Message Types
|
|
280
|
+
|
|
281
|
+
| Type | Purpose |
|
|
282
|
+
|---|---|
|
|
283
|
+
| `task` | Master assigns work to a worker |
|
|
284
|
+
| `status` | Instance reports progress |
|
|
285
|
+
| `question` | Instance asks for input |
|
|
286
|
+
| `answer` | Response to a question |
|
|
287
|
+
| `announce` | Broadcast information to all |
|
|
288
|
+
| `done` | Instance signals task completion |
|
|
289
|
+
|
|
290
|
+
## Storage
|
|
291
|
+
|
|
292
|
+
All state is stored in `.intercomm-aimfp/intercomm.db` (SQLite, WAL mode) created in the project root. Four tables: `instances` (incl. `tmux_target` for wake/scan/approve), `messages`, `read_cursors` (monotonic `last_read_seq` cursor), and `worktrees` (the branch-per-agent registry).
|
|
293
|
+
|
|
294
|
+
## Stale Detection
|
|
295
|
+
|
|
296
|
+
Every tool call updates the instance's `last_active` timestamp. An instance is considered stale after **30 seconds** of inactivity. If the master goes stale, a worker can claim master via `intercomm_register(role: "master")`.
|
|
297
|
+
|
|
298
|
+
## Troubleshooting
|
|
299
|
+
|
|
300
|
+
### MCP server fails to start after a Node.js upgrade
|
|
301
|
+
|
|
302
|
+
`better-sqlite3` is a native (C++) addon compiled against a specific Node.js ABI. If you upgrade Node.js (via `nvm`, a system update, etc.), the prebuilt binary no longer loads and the MCP server exits immediately on startup — so every tool call fails. The give-away error (visible by running the entry point directly) looks like:
|
|
303
|
+
|
|
304
|
+
```
|
|
305
|
+
The module '.../better-sqlite3/build/Release/better_sqlite3.node'
|
|
306
|
+
was compiled against a different Node.js version using
|
|
307
|
+
NODE_MODULE_VERSION 131. This version of Node.js requires
|
|
308
|
+
NODE_MODULE_VERSION 137.
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Fix:** rebuild the native module against your current Node.js:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
cd /path/to/intercommAIMFP
|
|
315
|
+
npm rebuild better-sqlite3 # or: npm install
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
To diagnose MCP startup failures in general, run the entry point manually and watch stderr:
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
node dist/mcp-entry.js # prints "InterComm AIMFP MCP server error: ..." on failure
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Known Limitations
|
|
325
|
+
|
|
326
|
+
### Worker Permission Prompts
|
|
327
|
+
|
|
328
|
+
Claude Code requires approval for certain tool calls (e.g., running bash commands). When a worker hits a permission prompt it **blocks** until answered, and while blocked it cannot report over InterComm — so the master must detect blocked workers by polling their panes.
|
|
329
|
+
|
|
330
|
+
This is handled, not eliminated:
|
|
331
|
+
- Workers are spawned in `acceptEdits` mode by default (file edits auto-approved; Bash/other still prompt).
|
|
332
|
+
- `intercomm_scan` polls every worker pane and reports which are blocked.
|
|
333
|
+
- The master clears the dialog with `intercomm_approve` (it reads the pane via `capture-pane` and selects the right option via `send-keys`).
|
|
334
|
+
|
|
335
|
+
**Reduce or remove the prompts further:**
|
|
336
|
+
- Spawn with `--bypass` (`bypassPermissions`) for fully hands-off workers (use with caution — workers run anything unprompted).
|
|
337
|
+
- Pre-approve common operations in `.claude/settings.local.json` (`permissions.allow`), e.g. `Bash(npm run:*)`. The InterComm MCP tools are already allowlisted in this repo.
|
|
338
|
+
|
|
339
|
+
### tmux Prompt Submission
|
|
340
|
+
|
|
341
|
+
Claude Code's input requires an extra `Enter` keystroke to submit prompts sent via `tmux send-keys`. Always use `Enter Enter` (double Enter) when waking workers.
|
|
342
|
+
|
|
343
|
+
## CLI (Debug)
|
|
344
|
+
|
|
345
|
+
The CLI is for debugging only — normal usage is through MCP tools.
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
intercomm status Show all instances
|
|
349
|
+
intercomm send --from <id> <to> <message> [--type t] Send a direct message
|
|
350
|
+
intercomm broadcast --from <id> <message> [--type t] Broadcast to all
|
|
351
|
+
intercomm read --id <id> [--all] Read new messages
|
|
352
|
+
intercomm clear [--keep <n>] Clear old messages (default: keep 100)
|
|
353
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type PaneState = "trust" | "mcp_approval" | "bypass" | "blocked" | "ready" | "running" | "idle";
|
|
2
|
+
export type KeyGroup = readonly string[];
|
|
3
|
+
export declare const detectPaneState: (capture: string) => PaneState;
|
|
4
|
+
export declare const clearKeystrokes: (state: PaneState) => readonly KeyGroup[];
|
|
5
|
+
export declare const isBooted: (state: PaneState) => boolean;
|
|
6
|
+
export declare const isBlocking: (state: PaneState) => boolean;
|
|
7
|
+
//# sourceMappingURL=claude-tui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-tui.d.ts","sourceRoot":"","sources":["../src/claude-tui.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,cAAc,GACd,QAAQ,GACR,SAAS,GACT,OAAO,GACP,SAAS,GACT,MAAM,CAAC;AAGX,MAAM,MAAM,QAAQ,GAAG,SAAS,MAAM,EAAE,CAAC;AAYzC,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,KAAG,SAQjD,CAAC;AAQF,eAAO,MAAM,eAAe,GAAI,OAAO,SAAS,KAAG,SAAS,QAAQ,EAYnE,CAAC;AAIF,eAAO,MAAM,QAAQ,GAAI,OAAO,SAAS,KAAG,OACF,CAAC;AAG3C,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,KAAG,OACX,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Pure logic for reading and clearing Claude Code's terminal (TUI) dialogs.
|
|
2
|
+
//
|
|
3
|
+
// Ported from scripts/spawn-workers.sh (wait_ready) and scripts/scan-workers.sh.
|
|
4
|
+
// NO IO here — these functions take a captured pane string and return a state, or
|
|
5
|
+
// the keystrokes that would clear it. tmux-wrapper.ts carries the keystrokes; the
|
|
6
|
+
// spawn/scan/approve tools compose the two. Keeping detection pure makes the
|
|
7
|
+
// dialog matrix unit-testable without a live tmux or Claude process.
|
|
8
|
+
const TRUST_RE = /trust the files|do you trust/i;
|
|
9
|
+
const MCP_RE = /new MCP servers found|Select any you wish to enable/i;
|
|
10
|
+
const BYPASS_RE = /Yes, I accept/i;
|
|
11
|
+
const BLOCKED_RE = /do you want to proceed/i;
|
|
12
|
+
const READY_RE = /for shortcuts|shift\+tab to cycle|accept edits on|bypass permissions on|plan mode on|auto mode on/i;
|
|
13
|
+
const RUNNING_RE = /esc to interrupt|tokens|Brewed|Thinking|Working|Running/i;
|
|
14
|
+
// Classify a captured pane. Blocking dialogs first (so a stuck pane is never
|
|
15
|
+
// reported "running"), then ready before running, then idle as the fallback.
|
|
16
|
+
export const detectPaneState = (capture) => {
|
|
17
|
+
if (BLOCKED_RE.test(capture))
|
|
18
|
+
return "blocked";
|
|
19
|
+
if (TRUST_RE.test(capture))
|
|
20
|
+
return "trust";
|
|
21
|
+
if (MCP_RE.test(capture))
|
|
22
|
+
return "mcp_approval";
|
|
23
|
+
if (BYPASS_RE.test(capture))
|
|
24
|
+
return "bypass";
|
|
25
|
+
if (READY_RE.test(capture))
|
|
26
|
+
return "ready";
|
|
27
|
+
if (RUNNING_RE.test(capture))
|
|
28
|
+
return "running";
|
|
29
|
+
return "idle";
|
|
30
|
+
};
|
|
31
|
+
// The keystroke groups that clear a blocking dialog, in order. Each group is one
|
|
32
|
+
// send-keys call; callers pause between groups (the bypass warning needs the "2"
|
|
33
|
+
// selection to register before Enter confirms). Non-blocking states return [].
|
|
34
|
+
// trust / blocked -> option 1 (Yes / accept) + Enter
|
|
35
|
+
// mcp_approval -> bare Enter (servers are pre-checked)
|
|
36
|
+
// bypass -> "2" (Yes, I accept), then Enter [default is "1. No, exit"]
|
|
37
|
+
export const clearKeystrokes = (state) => {
|
|
38
|
+
switch (state) {
|
|
39
|
+
case "trust":
|
|
40
|
+
case "blocked":
|
|
41
|
+
return [["1", "Enter"]];
|
|
42
|
+
case "mcp_approval":
|
|
43
|
+
return [["Enter"]];
|
|
44
|
+
case "bypass":
|
|
45
|
+
return [["2"], ["Enter"]];
|
|
46
|
+
default:
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
// True once a pane has booted past first-run dialogs and is usable (ready or
|
|
51
|
+
// actively running). spawn's bounded clear-loop uses this to know it can stop.
|
|
52
|
+
export const isBooted = (state) => state === "ready" || state === "running";
|
|
53
|
+
// True when the pane is stuck on a dialog that a keystroke can clear.
|
|
54
|
+
export const isBlocking = (state) => clearKeystrokes(state).length > 0;
|
|
55
|
+
//# sourceMappingURL=claude-tui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-tui.js","sourceRoot":"","sources":["../src/claude-tui.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,iFAAiF;AACjF,kFAAkF;AAClF,kFAAkF;AAClF,6EAA6E;AAC7E,qEAAqE;AAwBrE,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AACjD,MAAM,MAAM,GAAG,sDAAsD,CAAC;AACtE,MAAM,SAAS,GAAG,gBAAgB,CAAC;AACnC,MAAM,UAAU,GAAG,yBAAyB,CAAC;AAC7C,MAAM,QAAQ,GACZ,oGAAoG,CAAC;AACvG,MAAM,UAAU,GAAG,0DAA0D,CAAC;AAE9E,6EAA6E;AAC7E,6EAA6E;AAC7E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAe,EAAa,EAAE;IAC5D,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/C,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAC3C,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,cAAc,CAAC;IAChD,IAAI,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC7C,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAC3C,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,iFAAiF;AACjF,iFAAiF;AACjF,+EAA+E;AAC/E,uDAAuD;AACvD,4DAA4D;AAC5D,mFAAmF;AACnF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAgB,EAAuB,EAAE;IACvE,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,OAAO,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1B,KAAK,cAAc;YACjB,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5B;YACE,OAAO,EAAE,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,6EAA6E;AAC7E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAgB,EAAW,EAAE,CACpD,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,CAAC;AAE3C,sEAAsE;AACtE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAgB,EAAW,EAAE,CACtD,eAAe,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI entry point — debug-only interface for InterComm AIMFP
|
|
3
|
+
import { initDb, closeDb, resolveRoot } from "./db.js";
|
|
4
|
+
import * as store from "./store.js";
|
|
5
|
+
// --- Pure argument parsing ---
|
|
6
|
+
const parseArgs = (argv) => {
|
|
7
|
+
const args = argv.slice(2);
|
|
8
|
+
const command = args[0] ?? "";
|
|
9
|
+
const positional = [];
|
|
10
|
+
const flags = {};
|
|
11
|
+
for (let i = 1; i < args.length; i++) {
|
|
12
|
+
const arg = args[i];
|
|
13
|
+
if (arg.startsWith("--")) {
|
|
14
|
+
const key = arg.slice(2);
|
|
15
|
+
const next = args[i + 1];
|
|
16
|
+
if (next && !next.startsWith("--")) {
|
|
17
|
+
flags[key] = next;
|
|
18
|
+
i++;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
flags[key] = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
positional.push(arg);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { command, positional, flags };
|
|
29
|
+
};
|
|
30
|
+
const getRoot = () => resolveRoot(process.cwd());
|
|
31
|
+
// --- Command handlers ---
|
|
32
|
+
const cmdInit = () => {
|
|
33
|
+
initDb(getRoot());
|
|
34
|
+
console.log("InterComm AIMFP initialized. DB ready.");
|
|
35
|
+
};
|
|
36
|
+
const cmdStatus = () => {
|
|
37
|
+
initDb(getRoot());
|
|
38
|
+
const instances = store.getAllInstances();
|
|
39
|
+
if (instances.length === 0) {
|
|
40
|
+
console.log("No instances registered.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log("Instances:");
|
|
44
|
+
for (const inst of instances) {
|
|
45
|
+
console.log(store.formatInstanceForDisplay(inst));
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const cmdRegister = (args) => {
|
|
49
|
+
initDb(getRoot());
|
|
50
|
+
const role = args.positional[0] ?? "worker";
|
|
51
|
+
if (role !== "master" && role !== "worker") {
|
|
52
|
+
console.error("Usage: intercomm register [master|worker]");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const sessionId = `cli-${Date.now()}`;
|
|
56
|
+
const instance = store.registerAs(role, sessionId);
|
|
57
|
+
console.log(`Registered as "${instance.id}" (${instance.role})`);
|
|
58
|
+
};
|
|
59
|
+
const cmdSend = (args) => {
|
|
60
|
+
initDb(getRoot());
|
|
61
|
+
const from = args.flags["from"];
|
|
62
|
+
const to = args.positional[0];
|
|
63
|
+
const content = args.positional.slice(1).join(" ");
|
|
64
|
+
const type = args.flags["type"] ?? "status";
|
|
65
|
+
if (!from || !to || !content) {
|
|
66
|
+
console.error("Usage: intercomm send --from <id> <to> <message> [--type <type>]");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
store.insertMessage(from, to, type, content);
|
|
70
|
+
store.touchInstance(from);
|
|
71
|
+
console.log(`Sent (${type}) to ${to}`);
|
|
72
|
+
};
|
|
73
|
+
const cmdBroadcast = (args) => {
|
|
74
|
+
initDb(getRoot());
|
|
75
|
+
const from = args.flags["from"];
|
|
76
|
+
const content = args.positional.join(" ");
|
|
77
|
+
const type = args.flags["type"] ?? "announce";
|
|
78
|
+
if (!from || !content) {
|
|
79
|
+
console.error("Usage: intercomm broadcast --from <id> <message> [--type <type>]");
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
store.insertMessage(from, "all", type, content);
|
|
83
|
+
store.touchInstance(from);
|
|
84
|
+
console.log(`Broadcast (${type}) to all`);
|
|
85
|
+
};
|
|
86
|
+
const cmdRead = (args) => {
|
|
87
|
+
initDb(getRoot());
|
|
88
|
+
const id = args.flags["id"];
|
|
89
|
+
const readAll = args.flags["all"] === true;
|
|
90
|
+
if (!id) {
|
|
91
|
+
console.error("Usage: intercomm read --id <instance-id> [--all]");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
const messages = store.readNewMessages(id, readAll);
|
|
95
|
+
if (messages.length === 0) {
|
|
96
|
+
console.log("No new messages.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log(`--- ${messages.length} message(s) ---`);
|
|
100
|
+
for (const msg of messages) {
|
|
101
|
+
console.log(store.formatMessageForDisplay(msg));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const cmdClear = (args) => {
|
|
105
|
+
initDb(getRoot());
|
|
106
|
+
const keep = parseInt(args.flags["keep"], 10) || 100;
|
|
107
|
+
const deleted = store.clearOldMessages(keep);
|
|
108
|
+
console.log(`Cleared ${deleted} old messages (kept last ${keep}).`);
|
|
109
|
+
};
|
|
110
|
+
const USAGE = `InterComm AIMFP — Debug CLI
|
|
111
|
+
|
|
112
|
+
Commands:
|
|
113
|
+
init Initialize DB
|
|
114
|
+
status Show all instances
|
|
115
|
+
register [master|worker] Register as master or worker (default: worker)
|
|
116
|
+
send --from <id> <to> <message> [--type <type>] Send a direct message
|
|
117
|
+
broadcast --from <id> <message> [--type <type>] Broadcast to all
|
|
118
|
+
read --id <id> [--all] Read new messages
|
|
119
|
+
clear [--keep <n>] Clear old messages (default: keep 100)
|
|
120
|
+
|
|
121
|
+
Message types: task, status, question, answer, announce, done
|
|
122
|
+
`;
|
|
123
|
+
// --- Main dispatch ---
|
|
124
|
+
const commands = {
|
|
125
|
+
init: cmdInit,
|
|
126
|
+
status: cmdStatus,
|
|
127
|
+
register: cmdRegister,
|
|
128
|
+
send: cmdSend,
|
|
129
|
+
broadcast: cmdBroadcast,
|
|
130
|
+
read: cmdRead,
|
|
131
|
+
clear: cmdClear,
|
|
132
|
+
};
|
|
133
|
+
const main = () => {
|
|
134
|
+
const args = parseArgs(process.argv);
|
|
135
|
+
const handler = commands[args.command];
|
|
136
|
+
if (!handler) {
|
|
137
|
+
console.log(USAGE);
|
|
138
|
+
process.exit(args.command ? 1 : 0);
|
|
139
|
+
}
|
|
140
|
+
handler(args);
|
|
141
|
+
closeDb();
|
|
142
|
+
};
|
|
143
|
+
main();
|
|
144
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const STALE_THRESHOLD_MS = 30000;
|
|
2
|
+
export declare const DEFAULT_KEEP = 100;
|
|
3
|
+
export declare const ENV_DB_ROOT = "INTERCOMM_DB_ROOT";
|
|
4
|
+
export declare const basePath: (root: string) => string;
|
|
5
|
+
export declare const dbFile: (root: string) => string;
|
|
6
|
+
export declare const resolveDbRoot: (opts: {
|
|
7
|
+
cwd: string;
|
|
8
|
+
envRoot?: string | undefined;
|
|
9
|
+
gitCommonDir?: string | null | undefined;
|
|
10
|
+
}) => string;
|
|
11
|
+
export declare const worktreesDir: (root: string) => string;
|
|
12
|
+
export declare const worktreePath: (root: string, workerId: string) => string;
|
|
13
|
+
export declare const rootMismatchWarning: (resolvedRoot: string, cwd: string) => string | null;
|
|
14
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,eAAO,MAAM,WAAW,sBAAsB,CAAC;AAE/C,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,MAAmC,CAAC;AAE5E,eAAO,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,MACF,CAAC;AASrC,eAAO,MAAM,aAAa,GAAI,MAAM;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC1C,KAAG,MAKH,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MACH,CAAC;AAE1C,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,UAAU,MAAM,KAAG,MAC1B,CAAC;AASrC,eAAO,MAAM,mBAAmB,GAC9B,cAAc,MAAM,EACpB,KAAK,MAAM,KACV,MAAM,GAAG,IAOX,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Pure functions for paths and constants
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
const BASE_DIR_NAME = ".intercomm-aimfp";
|
|
4
|
+
const DB_FILE_NAME = "intercomm.db";
|
|
5
|
+
const WORKTREES_DIR_NAME = ".intercomm-worktrees";
|
|
6
|
+
export const STALE_THRESHOLD_MS = 30_000;
|
|
7
|
+
export const DEFAULT_KEEP = 100;
|
|
8
|
+
export const ENV_DB_ROOT = "INTERCOMM_DB_ROOT";
|
|
9
|
+
export const basePath = (root) => join(root, BASE_DIR_NAME);
|
|
10
|
+
export const dbFile = (root) => join(basePath(root), DB_FILE_NAME);
|
|
11
|
+
// Resolve the directory that holds the SHARED intercomm.db. With one worktree
|
|
12
|
+
// per worker, the launch cwd differs per worker, so the bus must be pinned to a
|
|
13
|
+
// single path. Precedence (all inputs supplied by the caller's IO layer):
|
|
14
|
+
// 1. an explicit INTERCOMM_DB_ROOT override (exported by spawn-workers.sh),
|
|
15
|
+
// 2. the parent of the repo's common git dir (same for every linked worktree),
|
|
16
|
+
// 3. the launch cwd (non-git / single-tree fallback).
|
|
17
|
+
// Pure: it only transforms the strings it is given.
|
|
18
|
+
export const resolveDbRoot = (opts) => {
|
|
19
|
+
const env = opts.envRoot?.trim();
|
|
20
|
+
if (env)
|
|
21
|
+
return env;
|
|
22
|
+
if (opts.gitCommonDir)
|
|
23
|
+
return dirname(opts.gitCommonDir);
|
|
24
|
+
return opts.cwd;
|
|
25
|
+
};
|
|
26
|
+
// Default on-disk location for a worker's worktree: a sibling of the repo root
|
|
27
|
+
// (outside the main checkout, so git does not recursively scan it).
|
|
28
|
+
export const worktreesDir = (root) => resolve(root, "..", WORKTREES_DIR_NAME);
|
|
29
|
+
export const worktreePath = (root, workerId) => join(worktreesDir(root), workerId);
|
|
30
|
+
// Warn when the resolved bus root differs from the launch cwd — the signature of a
|
|
31
|
+
// project nested inside ANOTHER git repo: with no INTERCOMM_DB_ROOT pin, resolveDbRoot
|
|
32
|
+
// falls back to the parent of the git-common-dir, which for a nested checkout is the
|
|
33
|
+
// OUTER repo's root, not this project's. That mis-root makes intercomm_spawn(worktrees)
|
|
34
|
+
// create worktrees of the wrong repo (the Run-1 live-test bug). Pure: returns null when
|
|
35
|
+
// root == cwd. The caller suppresses it when INTERCOMM_DB_ROOT was explicitly set
|
|
36
|
+
// (root != cwd is then intentional, e.g. a pinned shared bus).
|
|
37
|
+
export const rootMismatchWarning = (resolvedRoot, cwd) => {
|
|
38
|
+
if (resolve(resolvedRoot) === resolve(cwd))
|
|
39
|
+
return null;
|
|
40
|
+
return (`WARNING: InterComm bus root (${resolvedRoot}) differs from the launch directory (${cwd}). ` +
|
|
41
|
+
`If this project is nested inside another git repository, spawned worktrees may be created from the WRONG repo. ` +
|
|
42
|
+
`Pin INTERCOMM_DB_ROOT to the intended project root, or launch from a standalone (non-nested) checkout before spawning.`);
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=config.js.map
|