@extenshi/mcp 0.20.0 → 0.22.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.
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Where the build map lives and how every client touches it (plan
3
+ * 2026-11-03-extension-build-map §6.1).
4
+ *
5
+ * ONE MAP PER PROJECT, whoever asks. Agents often work on one extension in
6
+ * parallel, each in its own git worktree, and `.extenshi/` is usually ignored by
7
+ * git, so a per-checkout file would give every agent its own map and the author
8
+ * one browser tab per agent. The map therefore lives in the MAIN checkout: from
9
+ * a linked worktree, `resolveMapHome` maps the directory to the same path in the
10
+ * main working tree. Every agent, from any worktree, reads and writes the same
11
+ * `.extenshi/build-map.json` and finds the same server.
12
+ *
13
+ * Files under `<home>/.extenshi/`:
14
+ * build-map.json the state (BuildMapState); written under build-map.lock
15
+ * build-map.lock/ held for one read-modify-write; stale after a crash is taken over
16
+ * build-map.server.json the running server: pid, port, instance id, shutdown token (0600);
17
+ * a stopped server leaves it with pid 0 so the next start reuses the port
18
+ * build-map.server.lock/ held while a server starts, so two starts cannot both listen
19
+ * build-map.log output of the background server
20
+ *
21
+ * Writes go to a temporary file in the same directory and are renamed over the
22
+ * target, so a reader never sees half a file.
23
+ *
24
+ * The stdio MCP server (`@extenshi/mcp`) is the map's other client and ships a
25
+ * byte-identical copy at tools/extenshi-mcp/src/build-map-store.ts, so both find
26
+ * the same home and the same server; build-map-store-sync.test.ts there fails on
27
+ * any drift. Edit this file, then copy it over.
28
+ */
29
+ import { execFile } from 'node:child_process';
30
+ import { randomUUID } from 'node:crypto';
31
+ import { mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
32
+ import os from 'node:os';
33
+ import path from 'node:path';
34
+ import { emptyBuildMapState, normalizeBuildMapState } from '@extenshi/contracts';
35
+ export const MAP_DIR = '.extenshi';
36
+ export const MAP_STATE_FILE = 'build-map.json';
37
+ export const MAP_SERVER_FILE = 'build-map.server.json';
38
+ export const MAP_LOG_FILE = 'build-map.log';
39
+ const STATE_LOCK = 'build-map.lock';
40
+ const SERVER_LOCK = 'build-map.server.lock';
41
+ /** Bumped when the page/server protocol changes: a newer CLI replaces an older server instead of reusing it. */
42
+ export const MAP_PROTOCOL = 1;
43
+ export const mapPath = (home, file) => path.join(home, MAP_DIR, file);
44
+ function git(cwd, args) {
45
+ return new Promise((resolve) => {
46
+ execFile('git', args, { cwd, timeout: 5_000, windowsHide: true }, (error, stdout) => resolve(error ? undefined : stdout));
47
+ });
48
+ }
49
+ async function isDirectory(location) {
50
+ try {
51
+ return (await stat(location)).isDirectory();
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ /**
58
+ * The map home for `dir`: `dir` itself, unless it sits in a linked git worktree
59
+ * whose main working tree has the same directory — then that one. Outside git,
60
+ * in a bare-repository worktree or a submodule, the directory stays its own home.
61
+ */
62
+ export async function resolveMapHome(dir) {
63
+ const real = await realpath(path.resolve(dir));
64
+ const own = { home: real, dir: real, worktree: false };
65
+ const out = await git(real, ['rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir']);
66
+ const [top, common] = (out ?? '').trim().split('\n');
67
+ if (!top || !common || path.basename(common) !== '.git')
68
+ return own;
69
+ let mainRoot;
70
+ let topReal;
71
+ try {
72
+ mainRoot = await realpath(path.dirname(common));
73
+ topReal = await realpath(top);
74
+ }
75
+ catch {
76
+ return own;
77
+ }
78
+ if (mainRoot === topReal)
79
+ return own;
80
+ const candidate = path.join(mainRoot, path.relative(topReal, real));
81
+ if (!(await isDirectory(candidate)))
82
+ return own;
83
+ return { home: candidate, dir: real, worktree: true };
84
+ }
85
+ /** Every checkout of the repository `home` belongs to (main first), mapped to the project directory in each. */
86
+ export async function projectCheckouts(home) {
87
+ const out = await git(home, ['worktree', 'list', '--porcelain']);
88
+ const top = (await git(home, ['rev-parse', '--show-toplevel']))?.trim();
89
+ if (!out || !top)
90
+ return [home];
91
+ const relative = path.relative(top, home);
92
+ const roots = out
93
+ .split('\n')
94
+ .filter((line) => line.startsWith('worktree '))
95
+ .map((line) => line.slice('worktree '.length));
96
+ const dirs = [home];
97
+ for (const root of roots) {
98
+ const candidate = path.join(root, relative);
99
+ if (!dirs.includes(candidate) && (await isDirectory(candidate)))
100
+ dirs.push(candidate);
101
+ }
102
+ return dirs;
103
+ }
104
+ export function pidAlive(pid) {
105
+ if (!Number.isInteger(pid) || pid <= 0)
106
+ return false;
107
+ try {
108
+ process.kill(pid, 0);
109
+ return true;
110
+ }
111
+ catch (error) {
112
+ return error.code === 'EPERM';
113
+ }
114
+ }
115
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
116
+ async function readOwner(lockDir) {
117
+ try {
118
+ return JSON.parse(await readFile(path.join(lockDir, 'owner.json'), 'utf8'));
119
+ }
120
+ catch {
121
+ return undefined; // created a moment ago and not written yet, or a crash in between
122
+ }
123
+ }
124
+ /**
125
+ * Whether the lock as it is now may be taken over: its owner died on this
126
+ * machine, or it is older than `staleMs` (the critical sections take
127
+ * milliseconds). `undefined` when the lock is gone.
128
+ */
129
+ async function lockIsStale(lockDir, staleMs) {
130
+ const owner = await readOwner(lockDir);
131
+ let age;
132
+ try {
133
+ age = Date.now() - (owner?.at ?? (await stat(lockDir)).mtimeMs);
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ const dead = owner !== undefined && owner.host === os.hostname() && !pidAlive(owner.pid);
139
+ return dead || age > staleMs;
140
+ }
141
+ /**
142
+ * Hold `<lockDir>.breaker` while removing `lockDir`. Every removal — a release
143
+ * and a takeover alike — happens under it and re-checks the lock it is about to
144
+ * remove, so a waiter that judged a dead owner's lock stale a moment ago cannot
145
+ * delete the fresh lock another waiter has taken since. The breaker is held for
146
+ * a few file operations; one older than 10 s belongs to a process that crashed
147
+ * inside them.
148
+ */
149
+ async function withBreaker(lockDir, deadline, remove) {
150
+ const breaker = `${lockDir}.breaker`;
151
+ for (;;) {
152
+ try {
153
+ await mkdir(breaker);
154
+ break;
155
+ }
156
+ catch (error) {
157
+ if (error.code !== 'EEXIST')
158
+ throw error;
159
+ }
160
+ try {
161
+ if (Date.now() - (await stat(breaker)).mtimeMs > 10_000)
162
+ await rm(breaker, { recursive: true, force: true });
163
+ }
164
+ catch {
165
+ // released while we looked
166
+ }
167
+ if (Date.now() > deadline)
168
+ throw new Error(`${path.basename(breaker)} is held; remove it if no extenshi command runs.`);
169
+ await sleep(2 + Math.floor(Math.random() * 8));
170
+ }
171
+ try {
172
+ await remove();
173
+ }
174
+ finally {
175
+ await rm(breaker, { recursive: true, force: true });
176
+ }
177
+ }
178
+ /** Move the lock directory aside, then delete it: after the rename, `mkdir(lockDir)` is free again. */
179
+ async function removeLockDir(lockDir) {
180
+ const aside = `${lockDir}.gone-${randomUUID()}`;
181
+ try {
182
+ await rename(lockDir, aside);
183
+ }
184
+ catch {
185
+ return; // already gone
186
+ }
187
+ await rm(aside, { recursive: true, force: true });
188
+ }
189
+ /**
190
+ * Run `work` holding a directory lock. `mkdir` is atomic, so exactly one
191
+ * process creates the directory; its `owner.json` carries a random token. A
192
+ * lock whose owner died on this machine, or that is older than `staleMs`, is
193
+ * taken over — but the verdict is checked again under the breaker
194
+ * (`withBreaker`) before anything is removed, and a release removes the lock
195
+ * only while it still carries this holder's token.
196
+ */
197
+ export async function withDirLock(lockDir, work, options = {}) {
198
+ const timeoutMs = options.timeoutMs ?? 10_000;
199
+ const staleMs = options.staleMs ?? 30_000;
200
+ const deadline = Date.now() + timeoutMs;
201
+ await mkdir(path.dirname(lockDir), { recursive: true });
202
+ for (;;) {
203
+ try {
204
+ await mkdir(lockDir);
205
+ break;
206
+ }
207
+ catch (error) {
208
+ if (error.code !== 'EEXIST')
209
+ throw error;
210
+ }
211
+ const stale = await lockIsStale(lockDir, staleMs);
212
+ if (stale === undefined)
213
+ continue; // released while we looked
214
+ if (stale) {
215
+ await withBreaker(lockDir, deadline, async () => {
216
+ if (await lockIsStale(lockDir, staleMs))
217
+ await removeLockDir(lockDir);
218
+ });
219
+ continue;
220
+ }
221
+ if (Date.now() > deadline)
222
+ throw new Error(`${options.what ?? path.basename(lockDir)} is held by process ${(await readOwner(lockDir))?.pid ?? 'unknown'}. ` +
223
+ 'If no extenshi command is running, remove the lock directory.');
224
+ await sleep(20 + Math.floor(Math.random() * 40));
225
+ }
226
+ const token = randomUUID();
227
+ try {
228
+ const owner = { pid: process.pid, host: os.hostname(), at: Date.now(), token };
229
+ await writeFile(path.join(lockDir, 'owner.json'), JSON.stringify(owner));
230
+ return await work();
231
+ }
232
+ finally {
233
+ await withBreaker(lockDir, Date.now() + 15_000, async () => {
234
+ if ((await readOwner(lockDir))?.token === token)
235
+ await removeLockDir(lockDir);
236
+ });
237
+ }
238
+ }
239
+ /** Write JSON so a concurrent reader sees the old file or the new one, never a mix. */
240
+ export async function writeJsonAtomic(file, value, mode = 0o644) {
241
+ await mkdir(path.dirname(file), { recursive: true });
242
+ const temporary = `${file}.${randomUUID()}.tmp`;
243
+ try {
244
+ const handle = await open(temporary, 'wx', mode);
245
+ try {
246
+ await handle.writeFile(`${JSON.stringify(value, null, '\t')}\n`);
247
+ await handle.sync();
248
+ }
249
+ finally {
250
+ await handle.close();
251
+ }
252
+ await rename(temporary, file);
253
+ }
254
+ finally {
255
+ await rm(temporary, { force: true });
256
+ }
257
+ }
258
+ /** Read the state for display: a missing or broken file reads as empty, with the problem named. */
259
+ export async function readMapState(home) {
260
+ let text;
261
+ try {
262
+ text = await readFile(mapPath(home, MAP_STATE_FILE), 'utf8');
263
+ }
264
+ catch (error) {
265
+ if (error.code === 'ENOENT')
266
+ return { state: emptyBuildMapState(), exists: false };
267
+ return { state: emptyBuildMapState(), exists: true, problem: error.message };
268
+ }
269
+ try {
270
+ return { state: normalizeBuildMapState(JSON.parse(text)), exists: true };
271
+ }
272
+ catch (error) {
273
+ return {
274
+ state: emptyBuildMapState(),
275
+ exists: true,
276
+ problem: `${MAP_STATE_FILE}: ${error.message}`,
277
+ };
278
+ }
279
+ }
280
+ /**
281
+ * Read, change and write the state as one step, under the state lock, so two
282
+ * agents reporting at the same moment both land. Refuses to write over a file
283
+ * it cannot read rather than replace someone's edit with an empty map.
284
+ */
285
+ export async function updateMapState(home, change) {
286
+ return withDirLock(mapPath(home, STATE_LOCK), async () => {
287
+ const read = await readMapState(home);
288
+ if (read.problem)
289
+ throw new Error(`Cannot update the build map: ${read.problem}`);
290
+ const next = change(read.state) ?? read.state;
291
+ await writeJsonAtomic(mapPath(home, MAP_STATE_FILE), next);
292
+ return next;
293
+ }, { what: `${MAP_DIR}/${STATE_LOCK}` });
294
+ }
295
+ export async function readServerRecord(home) {
296
+ try {
297
+ const record = JSON.parse(await readFile(mapPath(home, MAP_SERVER_FILE), 'utf8'));
298
+ if (typeof record?.port !== 'number' || typeof record?.instance !== 'string')
299
+ return undefined;
300
+ return record;
301
+ }
302
+ catch {
303
+ return undefined;
304
+ }
305
+ }
306
+ export async function fetchHealth(port, timeoutMs = 1_500) {
307
+ try {
308
+ const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
309
+ signal: AbortSignal.timeout(timeoutMs),
310
+ });
311
+ if (!response.ok)
312
+ return undefined;
313
+ return (await response.json());
314
+ }
315
+ catch {
316
+ return undefined;
317
+ }
318
+ }
319
+ /**
320
+ * The server recorded for `home`, if it is really up: same machine, process
321
+ * alive, and its health answer carries the recorded instance id and home. A
322
+ * record left by a crash, by another machine (a synced folder) or a reused pid
323
+ * reads as no server.
324
+ */
325
+ export async function probeServer(home) {
326
+ const record = await readServerRecord(home);
327
+ if (!record || record.host !== os.hostname() || !pidAlive(record.pid))
328
+ return undefined;
329
+ const health = await fetchHealth(record.port);
330
+ if (!health || health.instance !== record.instance || health.home !== home)
331
+ return undefined;
332
+ return { ...record, clients: health.clients };
333
+ }
334
+ export const serverLockPath = (home) => mapPath(home, SERVER_LOCK);
335
+ /**
336
+ * Mark the server record stopped, but only if it still names `instance` —
337
+ * never a successor's. The port stays in it, so the next start binds the same
338
+ * address and a tab left open reconnects by itself; pid 0 and no token make it
339
+ * read as no server.
340
+ */
341
+ export async function retireServerRecord(home, instance) {
342
+ const record = await readServerRecord(home);
343
+ if (record?.instance !== instance)
344
+ return;
345
+ await writeJsonAtomic(mapPath(home, MAP_SERVER_FILE), { ...record, pid: 0, token: '', stoppedAt: new Date().toISOString() }, 0o600);
346
+ }
347
+ //# sourceMappingURL=build-map-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-map-store.js","sourceRoot":"","sources":["../src/build-map-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC/F,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAsB,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEpG,MAAM,CAAC,MAAM,OAAO,GAAG,WAAW,CAAA;AAClC,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAA;AAC9C,MAAM,CAAC,MAAM,eAAe,GAAG,uBAAuB,CAAA;AACtD,MAAM,CAAC,MAAM,YAAY,GAAG,eAAe,CAAA;AAC3C,MAAM,UAAU,GAAG,gBAAgB,CAAA;AACnC,MAAM,WAAW,GAAG,uBAAuB,CAAA;AAE3C,gHAAgH;AAChH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAA;AAW7B,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;AAErF,SAAS,GAAG,CAAC,GAAW,EAAE,IAAc;IACvC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CACnF,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CACnC,CAAA;IACF,CAAC,CAAC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,QAAgB;IAC1C,IAAI,CAAC;QACJ,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAA;IACb,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC/C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAY,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAC/D,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC,CAAA;IAC3G,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACpD,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,MAAM;QAAE,OAAO,GAAG,CAAA;IACnE,IAAI,QAAgB,CAAA;IACpB,IAAI,OAAe,CAAA;IACnB,IAAI,CAAC;QACJ,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;QAC/C,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,GAAG,CAAA;IACX,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,GAAG,CAAA;IACpC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAA;IACnE,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;QAAE,OAAO,GAAG,CAAA;IAC/C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AACtD,CAAC;AAED,gHAAgH;AAChH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY;IAClD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAA;IAChE,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAA;IACvE,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;QAAE,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACzC,MAAM,KAAK,GAAG,GAAG;SACf,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;SAC9C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAA;IAC/C,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,CAAA;IACnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAC3C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACtF,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAW;IACnC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IACpD,IAAI,CAAC;QACJ,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QACpB,OAAO,IAAI,CAAA;IACZ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAA;IACzD,CAAC;AACF,CAAC;AAED,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAI/E,KAAK,UAAU,SAAS,CAAC,OAAe;IACvC,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;IAC5E,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAA,CAAC,kEAAkE;IACpF,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,OAAe;IAC1D,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAA;IACtC,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;IAChE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAA;IACjB,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACxF,OAAO,IAAI,IAAI,GAAG,GAAG,OAAO,CAAA;AAC7B,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,QAAgB,EAAE,MAA2B;IACxF,MAAM,OAAO,GAAG,GAAG,OAAO,UAAU,CAAA;IACpC,SAAS,CAAC;QACT,IAAI,CAAC;YACJ,MAAM,KAAK,CAAC,OAAO,CAAC,CAAA;YACpB,MAAK;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAA;QACpE,CAAC;QACD,IAAI,CAAC;YACJ,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,MAAM;gBACtD,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACrD,CAAC;QAAC,MAAM,CAAC;YACR,2BAA2B;QAC5B,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ;YACxB,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,kDAAkD,CAAC,CAAA;QAC7F,MAAM,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IAC/C,CAAC;IACD,IAAI,CAAC;QACJ,MAAM,MAAM,EAAE,CAAA;IACf,CAAC;YAAS,CAAC;QACV,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACpD,CAAC;AACF,CAAC;AAED,uGAAuG;AACvG,KAAK,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,KAAK,GAAG,GAAG,OAAO,SAAS,UAAU,EAAE,EAAE,CAAA;IAC/C,IAAI,CAAC;QACJ,MAAM,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAC7B,CAAC;IAAC,MAAM,CAAC;QACR,OAAM,CAAC,eAAe;IACvB,CAAC;IACD,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,OAAe,EACf,IAAsB,EACtB,UAAmE,EAAE;IAErE,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAA;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,CAAA;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAA;IACvC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACvD,SAAS,CAAC;QACT,IAAI,CAAC;YACJ,MAAM,KAAK,CAAC,OAAO,CAAC,CAAA;YACpB,MAAK;QACN,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,KAAK,CAAA;QACpE,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACjD,IAAI,KAAK,KAAK,SAAS;YAAE,SAAQ,CAAC,2BAA2B;QAC7D,IAAI,KAAK,EAAE,CAAC;YACX,MAAM,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,IAAI,EAAE;gBAC/C,IAAI,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC;oBAAE,MAAM,aAAa,CAAC,OAAO,CAAC,CAAA;YACtE,CAAC,CAAC,CAAA;YACF,SAAQ;QACT,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ;YACxB,MAAM,IAAI,KAAK,CACd,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,uBAAuB,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,IAAI,SAAS,IAAI;gBAC/G,+DAA+D,CAChE,CAAA;QACF,MAAM,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAA;IACjD,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAA;IAC1B,IAAI,CAAC;QACJ,MAAM,KAAK,GAAc,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAA;QACzF,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QACxE,OAAO,MAAM,IAAI,EAAE,CAAA;IACpB,CAAC;YAAS,CAAC;QACV,MAAM,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE,KAAK,IAAI,EAAE;YAC1D,IAAI,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,KAAK,KAAK;gBAAE,MAAM,aAAa,CAAC,OAAO,CAAC,CAAA;QAC9E,CAAC,CAAC,CAAA;IACH,CAAC;AACF,CAAC;AAED,uFAAuF;AACvF,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,KAAc,EAAE,IAAI,GAAG,KAAK;IAC/E,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACpD,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,UAAU,EAAE,MAAM,CAAA;IAC/C,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QAChD,IAAI,CAAC;YACJ,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;YAChE,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;QACpB,CAAC;gBAAS,CAAC;YACV,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;QACrB,CAAC;QACD,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC9B,CAAC;YAAS,CAAC;QACV,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACrC,CAAC;AACF,CAAC;AAID,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAY;IAC9C,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACJ,IAAI,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAA;IAC7D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YACrD,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;QACtD,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAG,KAAe,CAAC,OAAO,EAAE,CAAA;IACxF,CAAC;IACD,IAAI,CAAC;QACJ,OAAO,EAAE,KAAK,EAAE,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACzE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO;YACN,KAAK,EAAE,kBAAkB,EAAE;YAC3B,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,GAAG,cAAc,KAAM,KAAe,CAAC,OAAO,EAAE;SACzD,CAAA;IACF,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,IAAY,EACZ,MAA2D;IAE3D,OAAO,WAAW,CACjB,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,EACzB,KAAK,IAAI,EAAE;QACV,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAA;QACrC,IAAI,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAA;QACjF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAA;QAC7C,MAAM,eAAe,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,IAAI,CAAC,CAAA;QAC1D,OAAO,IAAI,CAAA;IACZ,CAAC,EACD,EAAE,IAAI,EAAE,GAAG,OAAO,IAAI,UAAU,EAAE,EAAE,CACpC,CAAA;AACF,CAAC;AAmBD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY;IAClD,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;QACjF,IAAI,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,EAAE,QAAQ,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QAC9F,OAAO,MAAyB,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAA;IACjB,CAAC;AACF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,SAAS,GAAG,KAAK;IAChE,IAAI,CAAC;QACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,oBAAoB,IAAI,aAAa,EAAE;YACnE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;SACtC,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,SAAS,CAAA;QAClC,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAc,CAAA;IAC5C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAA;IACjB,CAAC;AACF,CAAC;AAID;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC7C,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAC3C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IACvF,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7C,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IAC5F,OAAO,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;AAC9C,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;AAE1E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAY,EAAE,QAAgB;IACtE,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAC3C,IAAI,MAAM,EAAE,QAAQ,KAAK,QAAQ;QAAE,OAAM;IACzC,MAAM,eAAe,CACpB,OAAO,CAAC,IAAI,EAAE,eAAe,CAAC,EAC9B,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,EACrE,KAAK,CACL,CAAA;AACF,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `buildMap` in the local server's create_project and get_project_state answers
3
+ * (plan 2026-11-03-extension-build-map §6.7): whether the author's build map is
4
+ * open, its address, and how offering it went. Read from the same home and the
5
+ * same server record the CLI uses (build-map-store.ts, a copy of the CLI's), so
6
+ * every agent, from any git worktree of the project, sees the one map there is.
7
+ *
8
+ * Local only. The hosted connector has no access to the author's files and
9
+ * registers the tools without `readBuildMap`, so the field is absent there.
10
+ * Reading never starts a server and never writes the state file.
11
+ */
12
+ export type BuildMapSummary = {
13
+ /** A map server for this project answers on this machine. */
14
+ running: boolean;
15
+ /** The page while it runs; `#node=<step>` opens one step. */
16
+ url?: string;
17
+ /** The author has been asked about the map at least once. */
18
+ offered: boolean;
19
+ /** The author's latest answer was no, and no map runs. */
20
+ declined: boolean;
21
+ /** How many times the author answered the offer, yes or no. */
22
+ offers: number;
23
+ /** The project directory whose `.extenshi/` holds the map: the main checkout when the given one is a linked git worktree. */
24
+ home: string;
25
+ /** Why the state file could not be read, when it exists but could not. */
26
+ problem?: string;
27
+ };
28
+ export declare function readBuildMapSummary(dir: string): Promise<BuildMapSummary>;
29
+ //# sourceMappingURL=build-map-summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-map-summary.d.ts","sourceRoot":"","sources":["../src/build-map-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,eAAe,GAAG;IAC7B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAA;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAA;IACd,6HAA6H;IAC7H,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAgB/E"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `buildMap` in the local server's create_project and get_project_state answers
3
+ * (plan 2026-11-03-extension-build-map §6.7): whether the author's build map is
4
+ * open, its address, and how offering it went. Read from the same home and the
5
+ * same server record the CLI uses (build-map-store.ts, a copy of the CLI's), so
6
+ * every agent, from any git worktree of the project, sees the one map there is.
7
+ *
8
+ * Local only. The hosted connector has no access to the author's files and
9
+ * registers the tools without `readBuildMap`, so the field is absent there.
10
+ * Reading never starts a server and never writes the state file.
11
+ */
12
+ import { probeServer, readMapState, resolveMapHome } from './build-map-store.js';
13
+ export async function readBuildMapSummary(dir) {
14
+ const where = await resolveMapHome(dir);
15
+ const [read, server] = await Promise.all([readMapState(where.home), probeServer(where.home)]);
16
+ const { offer } = read.state;
17
+ const declinedAt = Date.parse(offer.declinedAt ?? '');
18
+ const acceptedAt = Date.parse(offer.acceptedAt ?? '');
19
+ return {
20
+ running: Boolean(server),
21
+ ...(server ? { url: server.url } : {}),
22
+ offered: offer.count > 0 || Boolean(offer.offeredAt ?? offer.acceptedAt ?? offer.declinedAt),
23
+ // A map the author opened after saying no (acceptedAt later, or a server up now) is not declined.
24
+ declined: !server && Number.isFinite(declinedAt) && !(acceptedAt > declinedAt),
25
+ offers: offer.count,
26
+ home: where.home,
27
+ ...(read.problem ? { problem: read.problem } : {}),
28
+ };
29
+ }
30
+ //# sourceMappingURL=build-map-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-map-summary.js","sourceRoot":"","sources":["../src/build-map-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAmBhF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,GAAW;IACpD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7F,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;IAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;IACrD,OAAO;QACN,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC;QACxB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC;QAC5F,kGAAkG;QAClG,QAAQ,EAAE,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,UAAU,CAAC;QAC9E,MAAM,EAAE,KAAK,CAAC,KAAK;QACnB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClD,CAAA;AACF,CAAC"}