@revealui/harnesses 0.1.0 → 0.1.3

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.
@@ -9,9 +9,8 @@ TERMS AND CONDITIONS
9
9
 
10
10
  "Software" means the RevealUI source code, documentation, and associated
11
11
  files contained in directories and packages designated as commercial,
12
- including but not limited to: packages/ai, packages/mcp, packages/editors,
13
- packages/services, packages/harnesses, and any directory named "ee" within
14
- the repository.
12
+ including but not limited to: packages/ai, packages/harnesses, and any
13
+ directory named "ee" within the repository.
15
14
 
16
15
  "License Key" means a valid RevealUI license key obtained through an active
17
16
  paid subscription at https://revealui.com.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @revealui/harnesses
2
2
 
3
+ > **Commercial package** — requires a [RevealUI Pro license](https://revealui.com/pro). Free to install and evaluate; a license key is required for production use.
4
+
5
+
3
6
  AI harness coordination for RevealUI — enables multiple AI coding tools to work on the same codebase safely and in parallel.
4
7
 
5
8
  ## Features
@@ -0,0 +1,11 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
11
+ //# sourceMappingURL=chunk-DGUM43GV.js.map
@@ -1,6 +1,121 @@
1
+ // src/workboard/file-lock.ts
2
+ import {
3
+ closeSync,
4
+ openSync,
5
+ readFileSync,
6
+ renameSync,
7
+ unlinkSync,
8
+ writeFileSync,
9
+ writeSync
10
+ } from "fs";
11
+ var DEFAULT_TIMEOUT_MS = 2e3;
12
+ var RETRY_MS = 50;
13
+ function isPidAlive(pid) {
14
+ try {
15
+ process.kill(pid, 0);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ function acquireLock(lockPath, timeoutMs = DEFAULT_TIMEOUT_MS) {
22
+ const deadline = Date.now() + timeoutMs;
23
+ while (Date.now() < deadline) {
24
+ try {
25
+ const fd = openSync(lockPath, "wx");
26
+ writeSync(fd, String(process.pid));
27
+ closeSync(fd);
28
+ return true;
29
+ } catch (err) {
30
+ if (err.code !== "EEXIST") return false;
31
+ try {
32
+ const holderPid = Number.parseInt(readFileSync(lockPath, "utf8").trim(), 10);
33
+ if (!(Number.isNaN(holderPid) || isPidAlive(holderPid))) {
34
+ unlinkSync(lockPath);
35
+ continue;
36
+ }
37
+ } catch {
38
+ continue;
39
+ }
40
+ const spinUntil = Date.now() + RETRY_MS;
41
+ while (Date.now() < spinUntil) {
42
+ }
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ function releaseLock(lockPath) {
48
+ try {
49
+ unlinkSync(lockPath);
50
+ } catch {
51
+ }
52
+ }
53
+ function withLock(lockPath, fn, timeoutMs) {
54
+ const acquired = acquireLock(lockPath, timeoutMs);
55
+ if (!acquired) {
56
+ throw new Error(
57
+ `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`
58
+ );
59
+ }
60
+ try {
61
+ return fn();
62
+ } finally {
63
+ releaseLock(lockPath);
64
+ }
65
+ }
66
+ async function withLockAsync(lockPath, fn, timeoutMs) {
67
+ const acquired = acquireLock(lockPath, timeoutMs);
68
+ if (!acquired) {
69
+ throw new Error(
70
+ `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`
71
+ );
72
+ }
73
+ try {
74
+ return await fn();
75
+ } finally {
76
+ releaseLock(lockPath);
77
+ }
78
+ }
79
+ function atomicWriteSync(filePath, content) {
80
+ const tmpPath = `${filePath}.tmp.${process.pid}`;
81
+ writeFileSync(tmpPath, content, "utf8");
82
+ renameSync(tmpPath, filePath);
83
+ }
84
+ function lockPathFor(workboardPath) {
85
+ return workboardPath.replace(/\.md$/, ".lock");
86
+ }
87
+
88
+ // src/workboard/session-identity.ts
89
+ import { readFileSync as readFileSync2 } from "fs";
90
+ function detectSessionType() {
91
+ try {
92
+ let pid = process.ppid;
93
+ for (let depth = 0; depth < 8; depth++) {
94
+ if (!pid || pid <= 1) break;
95
+ const cmdline = readFileSync2(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").toLowerCase();
96
+ if (cmdline.includes("zed")) return "zed";
97
+ if (cmdline.includes("cursor")) return "cursor";
98
+ const status = readFileSync2(`/proc/${pid}/status`, "utf8");
99
+ const m = status.match(/PPid:\s+(\d+)/);
100
+ if (!m) break;
101
+ pid = parseInt(m[1] ?? "0", 10);
102
+ }
103
+ } catch {
104
+ }
105
+ const termProgram = (process.env.TERM_PROGRAM ?? "").toLowerCase();
106
+ if (termProgram.includes("zed")) return "zed";
107
+ if (termProgram.includes("cursor")) return "cursor";
108
+ return "terminal";
109
+ }
110
+ function deriveSessionId(type, existingIds) {
111
+ const matching = existingIds.filter((id) => id.startsWith(`${type}-`)).map((id) => parseInt(id.split("-")[1] ?? "0", 10)).filter((n) => !Number.isNaN(n));
112
+ const maxN = matching.length > 0 ? Math.max(...matching) : 0;
113
+ return `${type}-${maxN + 1}`;
114
+ }
115
+
1
116
  // src/workboard/workboard-manager.ts
2
- import { readFileSync, writeFileSync } from "fs";
3
- import { readFile, writeFile } from "fs/promises";
117
+ import { readFileSync as readFileSync3 } from "fs";
118
+ import { readFile } from "fs/promises";
4
119
  import { resolve } from "path";
5
120
  var STALE_THRESHOLD_MS = 4 * 60 * 60 * 1e3;
6
121
  var WorkboardManager = class {
@@ -10,20 +125,24 @@ var WorkboardManager = class {
10
125
  if (!resolved.endsWith(".md")) {
11
126
  throw new Error(`Invalid workboard path: must be a .md file`);
12
127
  }
128
+ this.lockPath = lockPathFor(resolved);
13
129
  }
130
+ lockPath;
14
131
  /** Read and parse the workboard. */
15
132
  read() {
16
133
  let content;
17
134
  try {
18
- content = readFileSync(this.workboardPath, "utf8");
135
+ content = readFileSync3(this.workboardPath, "utf8");
19
136
  } catch {
20
137
  return { sessions: [], recent: [], plans: "", context: "", planReference: "" };
21
138
  }
22
139
  return parseWorkboard(content);
23
140
  }
24
- /** Write a workboard state back to disk. */
141
+ /** Write a workboard state back to disk (locked + atomic). */
25
142
  write(state) {
26
- writeFileSync(this.workboardPath, serializeWorkboard(state), "utf8");
143
+ withLock(this.lockPath, () => {
144
+ atomicWriteSync(this.workboardPath, serializeWorkboard(state));
145
+ });
27
146
  }
28
147
  /** Read and parse the workboard asynchronously. */
29
148
  async readAsync() {
@@ -34,59 +153,85 @@ var WorkboardManager = class {
34
153
  return { sessions: [], recent: [], plans: "", context: "", planReference: "" };
35
154
  }
36
155
  }
37
- /** Write a workboard state back to disk asynchronously. */
156
+ /** Write a workboard state back to disk asynchronously (locked + atomic). */
38
157
  async writeAsync(state) {
39
- await writeFile(this.workboardPath, serializeWorkboard(state), "utf8");
158
+ await withLockAsync(this.lockPath, async () => {
159
+ atomicWriteSync(this.workboardPath, serializeWorkboard(state));
160
+ });
40
161
  }
41
162
  /** Register a new session, replacing any existing row with the same id. */
42
163
  registerSession(session) {
43
- const state = this.read();
44
- const existing = state.sessions.findIndex((s) => s.id === session.id);
45
- if (existing >= 0) {
46
- state.sessions[existing] = session;
47
- } else {
48
- state.sessions.push(session);
49
- }
50
- this.write(state);
164
+ withLock(this.lockPath, () => {
165
+ const state = this.readUnlocked();
166
+ const existing = state.sessions.findIndex((s) => s.id === session.id);
167
+ if (existing >= 0) {
168
+ state.sessions[existing] = session;
169
+ } else {
170
+ state.sessions.push(session);
171
+ }
172
+ this.writeUnlocked(state);
173
+ });
51
174
  }
52
175
  /** Remove a session row by id. */
53
176
  unregisterSession(id) {
54
- const state = this.read();
55
- state.sessions = state.sessions.filter((s) => s.id !== id);
56
- this.write(state);
177
+ withLock(this.lockPath, () => {
178
+ const state = this.readUnlocked();
179
+ state.sessions = state.sessions.filter((s) => s.id !== id);
180
+ this.writeUnlocked(state);
181
+ });
57
182
  }
58
183
  /** Update specific fields of an existing session row. */
59
184
  updateSession(id, updates) {
60
- const state = this.read();
61
- const idx = state.sessions.findIndex((s) => s.id === id);
62
- if (idx < 0) return;
63
- const current = state.sessions[idx];
64
- if (!current) return;
65
- state.sessions[idx] = { ...current, ...updates };
66
- this.write(state);
185
+ withLock(this.lockPath, () => {
186
+ const state = this.readUnlocked();
187
+ const idx = state.sessions.findIndex((s) => s.id === id);
188
+ if (idx < 0) return;
189
+ const current = state.sessions[idx];
190
+ if (!current) return;
191
+ state.sessions[idx] = { ...current, ...updates };
192
+ this.writeUnlocked(state);
193
+ });
67
194
  }
68
195
  /** Update a session's files list and timestamp. */
69
196
  claimFiles(id, files) {
70
- this.updateSession(id, {
71
- files: files.join(", "),
72
- updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
197
+ withLock(this.lockPath, () => {
198
+ const state = this.readUnlocked();
199
+ const idx = state.sessions.findIndex((s) => s.id === id);
200
+ if (idx < 0) return;
201
+ const current = state.sessions[idx];
202
+ if (!current) return;
203
+ state.sessions[idx] = {
204
+ ...current,
205
+ files: files.join(", "),
206
+ updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
207
+ };
208
+ this.writeUnlocked(state);
73
209
  });
74
210
  }
75
211
  /** Clear a session's file reservations. */
76
212
  releaseFiles(id) {
77
- this.updateSession(id, {
78
- files: "",
79
- updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
213
+ withLock(this.lockPath, () => {
214
+ const state = this.readUnlocked();
215
+ const idx = state.sessions.findIndex((s) => s.id === id);
216
+ if (idx < 0) return;
217
+ const current = state.sessions[idx];
218
+ if (!current) return;
219
+ state.sessions[idx] = {
220
+ ...current,
221
+ files: "",
222
+ updated: `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}Z`
223
+ };
224
+ this.writeUnlocked(state);
80
225
  });
81
226
  }
82
227
  /** Prepend a timestamped entry to the ## Recent section (keeps last 20). */
83
228
  addRecentEntry(entry) {
84
- const state = this.read();
85
- const formatted = `[${entry.timestamp}] ${entry.sessionId}: ${entry.description}`;
86
- state.recent.unshift({ ...entry });
87
- if (state.recent.length > 20) state.recent.splice(20);
88
- void formatted;
89
- this.write(state);
229
+ withLock(this.lockPath, () => {
230
+ const state = this.readUnlocked();
231
+ state.recent.unshift({ ...entry });
232
+ if (state.recent.length > 20) state.recent.splice(20);
233
+ this.writeUnlocked(state);
234
+ });
90
235
  }
91
236
  /** Returns sessions whose `updated` timestamp is older than 4 hours. */
92
237
  detectStale() {
@@ -125,6 +270,19 @@ var WorkboardManager = class {
125
270
  }
126
271
  return { clean: conflicts.length === 0, conflicts };
127
272
  }
273
+ // ---- Internal: unlocked read/write (caller must hold the lock) ----
274
+ readUnlocked() {
275
+ let content;
276
+ try {
277
+ content = readFileSync3(this.workboardPath, "utf8");
278
+ } catch {
279
+ return { sessions: [], recent: [], plans: "", context: "", planReference: "" };
280
+ }
281
+ return parseWorkboard(content);
282
+ }
283
+ writeUnlocked(state) {
284
+ atomicWriteSync(this.workboardPath, serializeWorkboard(state));
285
+ }
128
286
  };
129
287
  function parseWorkboard(content) {
130
288
  const state = {
@@ -215,37 +373,15 @@ function serializeWorkboard(state) {
215
373
  return lines.join("\n");
216
374
  }
217
375
 
218
- // src/workboard/session-identity.ts
219
- import { readFileSync as readFileSync2 } from "fs";
220
- function detectSessionType() {
221
- try {
222
- let pid = process.ppid;
223
- for (let depth = 0; depth < 8; depth++) {
224
- if (!pid || pid <= 1) break;
225
- const cmdline = readFileSync2(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ").toLowerCase();
226
- if (cmdline.includes("zed")) return "zed";
227
- if (cmdline.includes("cursor")) return "cursor";
228
- const status = readFileSync2(`/proc/${pid}/status`, "utf8");
229
- const m = status.match(/PPid:\s+(\d+)/);
230
- if (!m) break;
231
- pid = parseInt(m[1] ?? "0", 10);
232
- }
233
- } catch {
234
- }
235
- const termProgram = (process.env.TERM_PROGRAM ?? "").toLowerCase();
236
- if (termProgram.includes("zed")) return "zed";
237
- if (termProgram.includes("cursor")) return "cursor";
238
- return "terminal";
239
- }
240
- function deriveSessionId(type, existingIds) {
241
- const matching = existingIds.filter((id) => id.startsWith(`${type}-`)).map((id) => parseInt(id.split("-")[1] ?? "0", 10)).filter((n) => !Number.isNaN(n));
242
- const maxN = matching.length > 0 ? Math.max(...matching) : 0;
243
- return `${type}-${maxN + 1}`;
244
- }
245
-
246
376
  export {
377
+ acquireLock,
378
+ releaseLock,
379
+ withLock,
380
+ withLockAsync,
381
+ atomicWriteSync,
382
+ lockPathFor,
247
383
  WorkboardManager,
248
384
  detectSessionType,
249
385
  deriveSessionId
250
386
  };
251
- //# sourceMappingURL=chunk-PG4RAOWS.js.map
387
+ //# sourceMappingURL=chunk-FJGN6DTH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workboard/file-lock.ts","../src/workboard/session-identity.ts","../src/workboard/workboard-manager.ts"],"sourcesContent":["import {\n closeSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n writeSync,\n} from 'node:fs';\n\nconst DEFAULT_TIMEOUT_MS = 2000;\nconst RETRY_MS = 50;\n\n/**\n * Check whether a process is still running.\n * Uses signal 0 which doesn't actually send a signal — just checks existence.\n */\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Acquire an exclusive file lock using O_EXCL (kernel-level atomic create).\n * Writes the current PID to the lock file for dead-holder detection.\n *\n * Spins with a 50ms interval until timeout. If the current lock holder\n * is dead (process no longer running), the lock is stolen.\n *\n * @returns true if the lock was acquired, false on timeout\n */\nexport function acquireLock(lockPath: string, timeoutMs = DEFAULT_TIMEOUT_MS): boolean {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n const fd = openSync(lockPath, 'wx');\n writeSync(fd, String(process.pid));\n closeSync(fd);\n return true;\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') return false;\n // Lock exists — check if holder is alive\n try {\n const holderPid = Number.parseInt(readFileSync(lockPath, 'utf8').trim(), 10);\n if (!(Number.isNaN(holderPid) || isPidAlive(holderPid))) {\n // Holder crashed — steal the lock\n unlinkSync(lockPath);\n continue;\n }\n } catch {\n // Lock file disappeared between checks — retry immediately\n continue;\n }\n // Busy-wait\n const spinUntil = Date.now() + RETRY_MS;\n while (Date.now() < spinUntil) {\n /* spin */\n }\n }\n }\n return false;\n}\n\n/**\n * Release a file lock. Swallows ENOENT (already released).\n */\nexport function releaseLock(lockPath: string): void {\n try {\n unlinkSync(lockPath);\n } catch {\n // Already released or never acquired — non-fatal\n }\n}\n\n/**\n * Execute a synchronous function while holding the file lock.\n * The lock is always released, even if fn throws.\n */\nexport function withLock<T>(lockPath: string, fn: () => T, timeoutMs?: number): T {\n const acquired = acquireLock(lockPath, timeoutMs);\n if (!acquired) {\n throw new Error(\n `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`,\n );\n }\n try {\n return fn();\n } finally {\n releaseLock(lockPath);\n }\n}\n\n/**\n * Execute an async function while holding the file lock.\n * The lock is always released, even if fn throws.\n */\nexport async function withLockAsync<T>(\n lockPath: string,\n fn: () => Promise<T>,\n timeoutMs?: number,\n): Promise<T> {\n const acquired = acquireLock(lockPath, timeoutMs);\n if (!acquired) {\n throw new Error(\n `Failed to acquire lock: ${lockPath} (timeout ${timeoutMs ?? DEFAULT_TIMEOUT_MS}ms)`,\n );\n }\n try {\n return await fn();\n } finally {\n releaseLock(lockPath);\n }\n}\n\n/**\n * Write a file atomically: write to a temporary file, then rename.\n * rename() on the same filesystem is atomic at the kernel level.\n */\nexport function atomicWriteSync(filePath: string, content: string): void {\n const tmpPath = `${filePath}.tmp.${process.pid}`;\n writeFileSync(tmpPath, content, 'utf8');\n renameSync(tmpPath, filePath);\n}\n\n/**\n * Derive a lock path from a workboard path (.md → .lock).\n */\nexport function lockPathFor(workboardPath: string): string {\n return workboardPath.replace(/\\.md$/, '.lock');\n}\n","import { readFileSync } from 'node:fs';\n\n/** Type of session detected from the runtime environment. */\nexport type SessionType = 'zed' | 'cursor' | 'terminal';\n\n/**\n * Detects whether the current process is running inside an AI tool (Zed, Cursor)\n * or a plain terminal session by walking the parent process chain.\n *\n * Uses /proc/<pid>/cmdline on Linux/WSL. Falls back to TERM_PROGRAM env var.\n */\nexport function detectSessionType(): SessionType {\n // Walk parent process chain looking for known AI tool process names.\n try {\n let pid = process.ppid;\n for (let depth = 0; depth < 8; depth++) {\n if (!pid || pid <= 1) break;\n const cmdline = readFileSync(`/proc/${pid}/cmdline`, 'utf8')\n .replace(/\\0/g, ' ')\n .toLowerCase();\n if (cmdline.includes('zed')) return 'zed';\n if (cmdline.includes('cursor')) return 'cursor';\n const status = readFileSync(`/proc/${pid}/status`, 'utf8');\n const m = status.match(/PPid:\\s+(\\d+)/);\n if (!m) break;\n pid = parseInt(m[1] ?? '0', 10);\n }\n } catch {\n // /proc not available (macOS, Windows non-WSL).\n }\n\n // Fallback: TERM_PROGRAM env var (set by some terminal emulators and IDEs).\n const termProgram = (process.env.TERM_PROGRAM ?? '').toLowerCase();\n if (termProgram.includes('zed')) return 'zed';\n if (termProgram.includes('cursor')) return 'cursor';\n\n return 'terminal';\n}\n\n/**\n * Derives a session ID (e.g. \"zed-1\", \"terminal-2\") given a type and a list\n * of existing session IDs already in the workboard.\n *\n * Picks the next available numeric suffix to avoid collisions.\n */\nexport function deriveSessionId(type: SessionType, existingIds: string[]): string {\n const matching = existingIds\n .filter((id) => id.startsWith(`${type}-`))\n .map((id) => parseInt(id.split('-')[1] ?? '0', 10))\n .filter((n) => !Number.isNaN(n));\n\n const maxN = matching.length > 0 ? Math.max(...matching) : 0;\n return `${type}-${maxN + 1}`;\n}\n","import { readFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { atomicWriteSync, lockPathFor, withLock, withLockAsync } from './file-lock.js';\nimport type {\n ConflictResult,\n WorkboardEntry,\n WorkboardSession,\n WorkboardState,\n} from './workboard-protocol.js';\n\nconst STALE_THRESHOLD_MS = 4 * 60 * 60 * 1000; // 4 hours\n\n/**\n * WorkboardManager — reads, parses, and writes .claude/workboard.md.\n *\n * The workboard is a markdown file with a specific structure:\n * ## Sessions — markdown table\n * ## Plans — freeform markdown\n * ## Recent — bullet list\n * ## Context — freeform markdown\n * ## Plan Reference — freeform markdown\n *\n * This class provides programmatic access to the Sessions table and Recent list.\n * Plans, Context, and Plan Reference are treated as opaque strings.\n *\n * All mutating methods use file locking (O_EXCL) to prevent race conditions\n * when multiple harness instances write concurrently. Writes are atomic\n * (tmp file + rename).\n */\nexport class WorkboardManager {\n private readonly lockPath: string;\n\n constructor(private readonly workboardPath: string) {\n const resolved = resolve(workboardPath);\n // Workboard paths must be markdown files. Resolving before checking\n // means path traversal sequences (../../etc) have already been collapsed.\n if (!resolved.endsWith('.md')) {\n throw new Error(`Invalid workboard path: must be a .md file`);\n }\n this.lockPath = lockPathFor(resolved);\n }\n\n /** Read and parse the workboard. */\n read(): WorkboardState {\n let content: string;\n try {\n content = readFileSync(this.workboardPath, 'utf8');\n } catch {\n return { sessions: [], recent: [], plans: '', context: '', planReference: '' };\n }\n return parseWorkboard(content);\n }\n\n /** Write a workboard state back to disk (locked + atomic). */\n write(state: WorkboardState): void {\n withLock(this.lockPath, () => {\n atomicWriteSync(this.workboardPath, serializeWorkboard(state));\n });\n }\n\n /** Read and parse the workboard asynchronously. */\n async readAsync(): Promise<WorkboardState> {\n try {\n const content = await readFile(this.workboardPath, 'utf8');\n return parseWorkboard(content);\n } catch {\n return { sessions: [], recent: [], plans: '', context: '', planReference: '' };\n }\n }\n\n /** Write a workboard state back to disk asynchronously (locked + atomic). */\n async writeAsync(state: WorkboardState): Promise<void> {\n await withLockAsync(this.lockPath, async () => {\n atomicWriteSync(this.workboardPath, serializeWorkboard(state));\n });\n }\n\n /** Register a new session, replacing any existing row with the same id. */\n registerSession(session: WorkboardSession): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const existing = state.sessions.findIndex((s) => s.id === session.id);\n if (existing >= 0) {\n state.sessions[existing] = session;\n } else {\n state.sessions.push(session);\n }\n this.writeUnlocked(state);\n });\n }\n\n /** Remove a session row by id. */\n unregisterSession(id: string): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n state.sessions = state.sessions.filter((s) => s.id !== id);\n this.writeUnlocked(state);\n });\n }\n\n /** Update specific fields of an existing session row. */\n updateSession(id: string, updates: Partial<WorkboardSession>): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const idx = state.sessions.findIndex((s) => s.id === id);\n if (idx < 0) return;\n const current = state.sessions[idx];\n if (!current) return;\n state.sessions[idx] = { ...current, ...updates };\n this.writeUnlocked(state);\n });\n }\n\n /** Update a session's files list and timestamp. */\n claimFiles(id: string, files: string[]): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const idx = state.sessions.findIndex((s) => s.id === id);\n if (idx < 0) return;\n const current = state.sessions[idx];\n if (!current) return;\n state.sessions[idx] = {\n ...current,\n files: files.join(', '),\n updated: `${new Date().toISOString().slice(0, 16)}Z`,\n };\n this.writeUnlocked(state);\n });\n }\n\n /** Clear a session's file reservations. */\n releaseFiles(id: string): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n const idx = state.sessions.findIndex((s) => s.id === id);\n if (idx < 0) return;\n const current = state.sessions[idx];\n if (!current) return;\n state.sessions[idx] = {\n ...current,\n files: '',\n updated: `${new Date().toISOString().slice(0, 16)}Z`,\n };\n this.writeUnlocked(state);\n });\n }\n\n /** Prepend a timestamped entry to the ## Recent section (keeps last 20). */\n addRecentEntry(entry: WorkboardEntry): void {\n withLock(this.lockPath, () => {\n const state = this.readUnlocked();\n state.recent.unshift({ ...entry });\n if (state.recent.length > 20) state.recent.splice(20);\n this.writeUnlocked(state);\n });\n }\n\n /** Returns sessions whose `updated` timestamp is older than 4 hours. */\n detectStale(): WorkboardSession[] {\n const state = this.read();\n const now = Date.now();\n return state.sessions.filter((s) => {\n try {\n return now - new Date(s.updated).getTime() > STALE_THRESHOLD_MS;\n } catch {\n return false;\n }\n });\n }\n\n /**\n * Check whether the given files conflict with any other active session's reservations.\n * Returns a ConflictResult describing any overlaps found.\n */\n checkConflicts(mySessionId: string, files: string[]): ConflictResult {\n const state = this.read();\n const conflicts: ConflictResult['conflicts'] = [];\n\n for (const session of state.sessions) {\n if (session.id === mySessionId) continue;\n const theirFiles = session.files\n .split(',')\n .map((f) => f.trim())\n .filter(Boolean);\n\n const overlapping = files.filter((myFile) =>\n theirFiles.some(\n (theirFile) =>\n myFile === theirFile ||\n myFile.startsWith(theirFile.replace('**', '')) ||\n theirFile.startsWith(myFile.replace('**', '')),\n ),\n );\n\n if (overlapping.length > 0) {\n conflicts.push({\n thisSession: mySessionId,\n otherSession: session.id,\n overlappingFiles: overlapping,\n });\n }\n }\n\n return { clean: conflicts.length === 0, conflicts };\n }\n\n // ---- Internal: unlocked read/write (caller must hold the lock) ----\n\n private readUnlocked(): WorkboardState {\n let content: string;\n try {\n content = readFileSync(this.workboardPath, 'utf8');\n } catch {\n return { sessions: [], recent: [], plans: '', context: '', planReference: '' };\n }\n return parseWorkboard(content);\n }\n\n private writeUnlocked(state: WorkboardState): void {\n atomicWriteSync(this.workboardPath, serializeWorkboard(state));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal parser / serializer\n// ---------------------------------------------------------------------------\n\nfunction parseWorkboard(content: string): WorkboardState {\n const state: WorkboardState = {\n sessions: [],\n recent: [],\n plans: '',\n context: '',\n planReference: '',\n };\n\n // Split into top-level sections at ## headings.\n const sectionRe = /^## (.+)$/gm;\n const sections: Array<{ title: string; start: number }> = [];\n for (const m of content.matchAll(sectionRe)) {\n sections.push({ title: (m[1] ?? '').trim(), start: m.index });\n }\n\n for (let i = 0; i < sections.length; i++) {\n const section = sections[i];\n if (!section) continue;\n const { title, start } = section;\n const nextSection = sections[i + 1];\n const end = nextSection !== undefined ? nextSection.start : content.length;\n const body = content.slice(start, end);\n\n if (title === 'Sessions') {\n state.sessions = parseSessionsTable(body);\n } else if (title === 'Recent') {\n state.recent = parseRecentList(body);\n } else if (title === 'Plans') {\n state.plans = body.replace(/^## Plans\\n/, '');\n } else if (title === 'Context') {\n state.context = body.replace(/^## Context\\n/, '');\n } else if (title === 'Plan Reference') {\n state.planReference = body.replace(/^## Plan Reference\\n/, '');\n }\n }\n\n return state;\n}\n\nfunction parseSessionsTable(section: string): WorkboardSession[] {\n const sessions: WorkboardSession[] = [];\n const lines = section.split('\\n');\n for (const line of lines) {\n if (!line.startsWith('|')) continue;\n const parts = line\n .split('|')\n .slice(1, -1)\n .map((c) => c.trim());\n if (parts.length < 6) continue;\n // Skip header and separator rows\n if (parts[0] === 'id' || /^-+$/.test(parts[0] ?? '')) continue;\n sessions.push({\n id: parts[0] ?? '',\n env: parts[1] ?? '',\n started: parts[2] ?? '',\n task: parts[3] ?? '',\n files: parts[4] ?? '',\n updated: parts[5] ?? '',\n });\n }\n return sessions;\n}\n\nfunction parseRecentList(section: string): WorkboardEntry[] {\n const entries: WorkboardEntry[] = [];\n const lineRe = /^- \\[(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2})\\] ([\\w-]+): (.+)$/;\n for (const line of section.split('\\n')) {\n const m = lineRe.exec(line);\n if (!m) continue;\n entries.push({ timestamp: m[1] ?? '', sessionId: m[2] ?? '', description: m[3] ?? '' });\n }\n return entries;\n}\n\nfunction serializeWorkboard(state: WorkboardState): string {\n const lines: string[] = ['# Workboard', ''];\n\n // Sessions table\n lines.push('## Sessions', '');\n lines.push('| id | env | started | task | files | updated |');\n lines.push('| --- | --- | ------- | ---- | ----- | ------- |');\n for (const s of state.sessions) {\n lines.push(`| ${s.id} | ${s.env} | ${s.started} | ${s.task} | ${s.files} | ${s.updated} |`);\n }\n lines.push('');\n\n // Plans\n lines.push('## Plans');\n lines.push(state.plans.trimEnd());\n lines.push('');\n\n // Recent\n lines.push('## Recent', '');\n for (const e of state.recent) {\n lines.push(`- [${e.timestamp}] ${e.sessionId}: ${e.description}`);\n }\n lines.push('');\n\n // Context\n lines.push('## Context');\n lines.push(state.context.trimEnd());\n lines.push('');\n\n // Plan Reference\n lines.push('## Plan Reference');\n lines.push(state.planReference.trimEnd());\n lines.push('');\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["readFileSync","readFileSync","readFileSync"]}