@remixhq/claude-plugin 0.1.2 → 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.
@@ -5,12 +5,12 @@ description: Specialized Claude agent for Remix repository collaboration, merge
5
5
 
6
6
  You are the Remix collaboration specialist.
7
7
 
8
- Your job is to help Claude use Remix as the primary collaboration workflow layer for bound repositories.
8
+ Your job is to help Claude use Remix as the authoritative collaboration workflow layer for bound repositories.
9
9
 
10
10
  Operating rules:
11
11
 
12
12
  1. Start with `remix_collab_status` for repo-bound collaboration tasks whenever the current state is unclear.
13
- 2. In a Remix-bound repo, treat Remix MCP tools as the default workflow layer for collaboration state and historical reasoning. Raw git may still make sense for separate GitHub-only branch workflows or exact repository facts, but it is not the default way to answer why/history/failed-attempt questions.
13
+ 2. In a Remix-bound repo, Remix MCP tools are the required workflow layer for collaboration state and historical reasoning. Raw git mutation commands must not be used for ordinary bound-repo collaboration work. Raw git reads are only acceptable for exact repository facts such as specific commits, blame, ancestry, or raw patch details.
14
14
  3. Use preview tools before apply tools whenever both exist.
15
15
  4. Treat reconcile as a last-resort, high-risk path.
16
16
  5. Prefer bounded merge request diffs first, then expand only when necessary.
@@ -20,12 +20,13 @@ Operating rules:
20
20
  - use `remix_collab_memory_search` to find the most relevant historical items,
21
21
  - use `remix_collab_memory_timeline` when chronology matters,
22
22
  - use `remix_collab_memory_change_step_diff` only after you have identified a specific `changeStepId`.
23
- 8. Use raw git for historical reads only as a fallback after Remix memory has narrowed the relevant change, or when the user explicitly asks for exact commit, blame, ancestry, or raw patch details.
23
+ 8. Use raw git for historical reads only after Remix memory has narrowed the relevant change, or when the user explicitly asks for exact commit, blame, ancestry, or raw patch details.
24
24
  9. Clearly explain local mutation risk before using tools that can modify the local repo.
25
25
  10. Assume the installed hook is the normal automatic recording path for completed assistant turns in a bound repo: changed turn => `remix_collab_add`, no-diff turn => `remix_collab_record_turn`.
26
- 11. Do not proactively call `remix_collab_add` or `remix_collab_record_turn` during normal work. The hook should do the automatic per-turn recording.
27
- 12. Only use manual `remix_collab_add` or `remix_collab_record_turn` when the user explicitly asks for it, or when doing operational recovery, backfills, or debugging.
28
- 13. Do not duplicate core business logic in reasoning. Use the MCP tools to inspect and execute the workflow.
26
+ 11. Do not proactively call `remix_collab_add` or `remix_collab_record_turn` during normal work. The hook should do the automatic per-turn recording unless recovery is required.
27
+ 12. Only use manual `remix_collab_add` or `remix_collab_record_turn` when the user explicitly asks for it, or when doing operational recovery, backfills, or debugging after automatic recording could not complete safely.
28
+ 13. When you must choose manually, treat `remix_collab_add` or the clearer alias `remix_collab_add_change_step` as the code-diff tool, and treat `remix_collab_record_turn` or `remix_collab_record_no_diff_turn` as no-diff prompt/response history only.
29
+ 14. Do not duplicate core business logic in reasoning. Use the MCP tools to inspect and execute the workflow.
29
30
 
30
31
  When appropriate:
31
32
 
@@ -35,4 +36,5 @@ When appropriate:
35
36
  - use the explicit change-step diff tool only after you already know which `changeStepId` matters
36
37
  - use `remix_collab_add` only when a manual changed-turn recording step is explicitly needed
37
38
  - use `remix_collab_record_turn` only when a manual no-diff turn recording step is explicitly needed
39
+ - use `remix_collab_review_queue` for reviewable merge requests, `remix_collab_my_merge_requests` for authored requests, and `remix_collab_list_app_merge_requests` for app-scoped MR flows with required `queue` set to `app_reviewable`, `app_outgoing`, or `app_related_visible`
38
40
  - use merge request tools for review and approval flows
@@ -11,12 +11,200 @@ function stateRoot() {
11
11
  function statePath(sessionId) {
12
12
  return path.join(stateRoot(), `${sessionId}.json`);
13
13
  }
14
+ function stateLockPath(sessionId) {
15
+ return path.join(stateRoot(), `${sessionId}.lock`);
16
+ }
17
+ function stateLockMetaPath(sessionId) {
18
+ return path.join(stateLockPath(sessionId), "owner.json");
19
+ }
14
20
  async function writeJsonAtomic(filePath, value) {
15
21
  await fs.mkdir(path.dirname(filePath), { recursive: true });
16
22
  const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
17
23
  await fs.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf8");
18
24
  await fs.rename(tmpPath, filePath);
19
25
  }
26
+ var STATE_LOCK_WAIT_MS = 2e3;
27
+ var STATE_LOCK_POLL_MS = 25;
28
+ var STATE_LOCK_STALE_MS = 3e4;
29
+ var STATE_LOCK_HEARTBEAT_MS = 5e3;
30
+ async function sleep(ms) {
31
+ await new Promise((resolve) => setTimeout(resolve, ms));
32
+ }
33
+ async function readStateLockMetadata(sessionId) {
34
+ const raw = await fs.readFile(stateLockMetaPath(sessionId), "utf8").catch(() => null);
35
+ if (!raw) return null;
36
+ try {
37
+ const parsed = JSON.parse(raw);
38
+ if (typeof parsed.ownerId !== "string" || typeof parsed.pid !== "number" || typeof parsed.createdAt !== "string" || typeof parsed.heartbeatAt !== "string") {
39
+ return null;
40
+ }
41
+ return {
42
+ ownerId: parsed.ownerId,
43
+ pid: parsed.pid,
44
+ createdAt: parsed.createdAt,
45
+ heartbeatAt: parsed.heartbeatAt
46
+ };
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ async function writeStateLockMetadata(sessionId, metadata) {
52
+ await writeJsonAtomic(stateLockMetaPath(sessionId), metadata);
53
+ }
54
+ async function tryRemoveStaleStateLock(sessionId) {
55
+ const lockPath = stateLockPath(sessionId);
56
+ const metadata = await readStateLockMetadata(sessionId);
57
+ const staleByHeartbeat = metadata && Date.now() - new Date(metadata.heartbeatAt).getTime() > STATE_LOCK_STALE_MS;
58
+ if (staleByHeartbeat) {
59
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
60
+ return true;
61
+ }
62
+ if (!metadata) {
63
+ const lockStat = await fs.stat(lockPath).catch(() => null);
64
+ if (lockStat && Date.now() - lockStat.mtimeMs > STATE_LOCK_STALE_MS) {
65
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
66
+ return true;
67
+ }
68
+ }
69
+ return false;
70
+ }
71
+ async function acquireStateLock(sessionId) {
72
+ const lockPath = stateLockPath(sessionId);
73
+ const deadline = Date.now() + STATE_LOCK_WAIT_MS;
74
+ while (true) {
75
+ try {
76
+ await fs.mkdir(lockPath);
77
+ const ownerId = randomUUID();
78
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
79
+ const metadata = {
80
+ ownerId,
81
+ pid: process.pid,
82
+ createdAt,
83
+ heartbeatAt: createdAt
84
+ };
85
+ await writeStateLockMetadata(sessionId, metadata);
86
+ let released = false;
87
+ const heartbeat = setInterval(() => {
88
+ if (released) return;
89
+ void writeStateLockMetadata(sessionId, {
90
+ ...metadata,
91
+ heartbeatAt: (/* @__PURE__ */ new Date()).toISOString()
92
+ }).catch(() => void 0);
93
+ }, STATE_LOCK_HEARTBEAT_MS);
94
+ heartbeat.unref?.();
95
+ return async () => {
96
+ if (released) return;
97
+ released = true;
98
+ clearInterval(heartbeat);
99
+ const currentMetadata = await readStateLockMetadata(sessionId);
100
+ if (currentMetadata?.ownerId === ownerId) {
101
+ await fs.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
102
+ }
103
+ };
104
+ } catch (error) {
105
+ const code = error && typeof error === "object" && "code" in error ? error.code : null;
106
+ if (code !== "EEXIST") {
107
+ throw error;
108
+ }
109
+ if (await tryRemoveStaleStateLock(sessionId)) {
110
+ continue;
111
+ }
112
+ if (Date.now() >= deadline) {
113
+ throw new Error(`Timed out acquiring hook state lock for session ${sessionId}.`);
114
+ }
115
+ await sleep(STATE_LOCK_POLL_MS);
116
+ }
117
+ }
118
+ }
119
+ async function withStateLock(sessionId, fn) {
120
+ const release = await acquireStateLock(sessionId);
121
+ try {
122
+ return await fn();
123
+ } finally {
124
+ await release();
125
+ }
126
+ }
127
+ function normalizeIntent(value) {
128
+ return value === "memory_first" || value === "collab_state" || value === "git_facts" ? value : "neutral";
129
+ }
130
+ function normalizeString(value) {
131
+ return typeof value === "string" && value.trim() ? value.trim() : null;
132
+ }
133
+ function normalizeStringArray(value) {
134
+ if (!Array.isArray(value)) return [];
135
+ return Array.from(
136
+ new Set(
137
+ value.filter((entry) => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim())
138
+ )
139
+ );
140
+ }
141
+ function normalizeTouchedRepo(value, repoRoot) {
142
+ if (!value || typeof value !== "object") return null;
143
+ const parsed = value;
144
+ const normalizedRepoRoot = normalizeString(parsed.repoRoot) ?? repoRoot.trim();
145
+ if (!normalizedRepoRoot) return null;
146
+ return {
147
+ repoRoot: normalizedRepoRoot,
148
+ projectId: normalizeString(parsed.projectId),
149
+ currentAppId: normalizeString(parsed.currentAppId),
150
+ upstreamAppId: normalizeString(parsed.upstreamAppId),
151
+ firstTouchedAt: normalizeString(parsed.firstTouchedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
152
+ lastTouchedAt: normalizeString(parsed.lastTouchedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
153
+ lastObservedWriteAt: normalizeString(parsed.lastObservedWriteAt),
154
+ touchedBy: normalizeStringArray(parsed.touchedBy),
155
+ hasObservedWrite: Boolean(parsed.hasObservedWrite),
156
+ manuallyRecorded: Boolean(parsed.manuallyRecorded),
157
+ manuallyRecordedAt: normalizeString(parsed.manuallyRecordedAt),
158
+ manuallyRecordedByTool: normalizeString(parsed.manuallyRecordedByTool),
159
+ stopAttempted: Boolean(parsed.stopAttempted),
160
+ stopRecorded: Boolean(parsed.stopRecorded),
161
+ stopRecordedAt: normalizeString(parsed.stopRecordedAt),
162
+ stopRecordedMode: parsed.stopRecordedMode === "changed_turn" || parsed.stopRecordedMode === "no_diff_turn" ? parsed.stopRecordedMode : null,
163
+ recordingFailureMessage: normalizeString(parsed.recordingFailureMessage),
164
+ recordingFailureHint: normalizeString(parsed.recordingFailureHint),
165
+ recordingFailedAt: normalizeString(parsed.recordingFailedAt)
166
+ };
167
+ }
168
+ function normalizeTouchedRepos(value) {
169
+ if (!value || typeof value !== "object") return {};
170
+ const entries = Object.entries(value).map(([repoRoot, repo]) => normalizeTouchedRepo(repo, repoRoot)).filter((repo) => repo !== null).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
171
+ return Object.fromEntries(entries.map((repo) => [repo.repoRoot, repo]));
172
+ }
173
+ function createTouchedRepo(params) {
174
+ const now = (/* @__PURE__ */ new Date()).toISOString();
175
+ const touchedBy = params.touchedBy?.trim() ? [params.touchedBy.trim()] : [];
176
+ return {
177
+ repoRoot: params.repoRoot,
178
+ projectId: normalizeString(params.projectId),
179
+ currentAppId: normalizeString(params.currentAppId),
180
+ upstreamAppId: normalizeString(params.upstreamAppId),
181
+ firstTouchedAt: now,
182
+ lastTouchedAt: now,
183
+ lastObservedWriteAt: params.hasObservedWrite ? now : null,
184
+ touchedBy,
185
+ hasObservedWrite: Boolean(params.hasObservedWrite),
186
+ manuallyRecorded: false,
187
+ manuallyRecordedAt: null,
188
+ manuallyRecordedByTool: null,
189
+ stopAttempted: false,
190
+ stopRecorded: false,
191
+ stopRecordedAt: null,
192
+ stopRecordedMode: null,
193
+ recordingFailureMessage: null,
194
+ recordingFailureHint: null,
195
+ recordingFailedAt: null
196
+ };
197
+ }
198
+ async function updatePendingTurnState(sessionId, updater) {
199
+ return withStateLock(sessionId, async () => {
200
+ const existing = await loadPendingTurnState(sessionId);
201
+ if (!existing) return null;
202
+ const result = updater(existing);
203
+ if (result === false) return existing;
204
+ await savePendingTurnState(existing);
205
+ return existing;
206
+ });
207
+ }
20
208
  async function loadPendingTurnState(sessionId) {
21
209
  const raw = await fs.readFile(statePath(sessionId), "utf8").catch(() => null);
22
210
  if (!raw) return null;
@@ -26,16 +214,18 @@ async function loadPendingTurnState(sessionId) {
26
214
  if (typeof parsed.sessionId !== "string" || typeof parsed.turnId !== "string" || typeof parsed.prompt !== "string") {
27
215
  return null;
28
216
  }
29
- const intent = parsed.intent;
30
217
  return {
31
218
  sessionId: parsed.sessionId,
32
219
  turnId: parsed.turnId,
33
220
  prompt: parsed.prompt,
34
- cwd: typeof parsed.cwd === "string" && parsed.cwd.trim() ? parsed.cwd : null,
35
- intent: intent === "memory_first" || intent === "collab_state" || intent === "git_facts" ? intent : "neutral",
221
+ initialCwd: normalizeString(parsed.initialCwd),
222
+ intent: normalizeIntent(parsed.intent),
36
223
  submittedAt: typeof parsed.submittedAt === "string" ? parsed.submittedAt : (/* @__PURE__ */ new Date()).toISOString(),
37
- recordedByTool: Boolean(parsed.recordedByTool),
38
- consultedMemory: Boolean(parsed.consultedMemory)
224
+ consultedMemory: Boolean(parsed.consultedMemory),
225
+ touchedRepos: normalizeTouchedRepos(parsed.touchedRepos),
226
+ turnFailureMessage: normalizeString(parsed.turnFailureMessage),
227
+ turnFailureHint: normalizeString(parsed.turnFailureHint),
228
+ turnFailedAt: normalizeString(parsed.turnFailedAt)
39
229
  };
40
230
  } catch {
41
231
  return null;
@@ -44,22 +234,73 @@ async function loadPendingTurnState(sessionId) {
44
234
  async function savePendingTurnState(state) {
45
235
  await writeJsonAtomic(statePath(state.sessionId), state);
46
236
  }
47
- async function markPendingTurnRecordedByTool(sessionId) {
48
- const existing = await loadPendingTurnState(sessionId);
49
- if (!existing) return;
50
- existing.recordedByTool = true;
51
- await savePendingTurnState(existing);
237
+ async function upsertTouchedRepo(sessionId, params) {
238
+ const normalizedRepoRoot = params.repoRoot.trim();
239
+ if (!normalizedRepoRoot) return null;
240
+ const state = await updatePendingTurnState(sessionId, (existing) => {
241
+ const current = existing.touchedRepos[normalizedRepoRoot] ?? createTouchedRepo({
242
+ repoRoot: normalizedRepoRoot,
243
+ projectId: params.projectId,
244
+ currentAppId: params.currentAppId,
245
+ upstreamAppId: params.upstreamAppId,
246
+ touchedBy: params.touchedBy,
247
+ hasObservedWrite: params.hasObservedWrite
248
+ });
249
+ current.projectId = normalizeString(params.projectId) ?? current.projectId;
250
+ current.currentAppId = normalizeString(params.currentAppId) ?? current.currentAppId;
251
+ current.upstreamAppId = normalizeString(params.upstreamAppId) ?? current.upstreamAppId;
252
+ current.lastTouchedAt = (/* @__PURE__ */ new Date()).toISOString();
253
+ if (params.touchedBy?.trim() && !current.touchedBy.includes(params.touchedBy.trim())) {
254
+ current.touchedBy = [...current.touchedBy, params.touchedBy.trim()].sort((a, b) => a.localeCompare(b));
255
+ }
256
+ if (params.hasObservedWrite) {
257
+ current.hasObservedWrite = true;
258
+ current.lastObservedWriteAt = (/* @__PURE__ */ new Date()).toISOString();
259
+ }
260
+ existing.touchedRepos[normalizedRepoRoot] = current;
261
+ });
262
+ return state?.touchedRepos[normalizedRepoRoot] ?? null;
263
+ }
264
+ async function markTouchedRepoObservedWrite(sessionId, repoRoot, params) {
265
+ await upsertTouchedRepo(sessionId, {
266
+ repoRoot,
267
+ touchedBy: params?.toolName ?? null,
268
+ hasObservedWrite: true
269
+ });
270
+ }
271
+ async function markTouchedRepoManuallyRecorded(sessionId, repoRoot, params) {
272
+ await updatePendingTurnState(sessionId, (existing) => {
273
+ const normalizedRepoRoot = repoRoot.trim();
274
+ if (!normalizedRepoRoot) return false;
275
+ const current = existing.touchedRepos[normalizedRepoRoot] ?? createTouchedRepo({
276
+ repoRoot: normalizedRepoRoot,
277
+ touchedBy: params?.toolName ?? null,
278
+ hasObservedWrite: false
279
+ });
280
+ current.lastTouchedAt = (/* @__PURE__ */ new Date()).toISOString();
281
+ current.manuallyRecorded = true;
282
+ current.manuallyRecordedAt = (/* @__PURE__ */ new Date()).toISOString();
283
+ current.manuallyRecordedByTool = normalizeString(params?.toolName) ?? current.manuallyRecordedByTool;
284
+ current.recordingFailureMessage = null;
285
+ current.recordingFailureHint = null;
286
+ current.recordingFailedAt = null;
287
+ if (params?.toolName?.trim() && !current.touchedBy.includes(params.toolName.trim())) {
288
+ current.touchedBy = [...current.touchedBy, params.toolName.trim()].sort((a, b) => a.localeCompare(b));
289
+ }
290
+ existing.touchedRepos[normalizedRepoRoot] = current;
291
+ });
52
292
  }
53
293
  async function markPendingTurnConsultedMemory(sessionId) {
54
- const existing = await loadPendingTurnState(sessionId);
55
- if (!existing || existing.consultedMemory) return;
56
- existing.consultedMemory = true;
57
- await savePendingTurnState(existing);
294
+ await updatePendingTurnState(sessionId, (existing) => {
295
+ if (existing.consultedMemory) return false;
296
+ existing.consultedMemory = true;
297
+ });
58
298
  }
59
299
 
60
300
  // src/hook-utils.ts
61
301
  import fs2 from "fs/promises";
62
302
  import path2 from "path";
303
+ import { readCollabBinding } from "@remixhq/core/binding";
63
304
  async function readJsonStdin() {
64
305
  const chunks = [];
65
306
  for await (const chunk of process.stdin) {
@@ -74,6 +315,18 @@ async function readJsonStdin() {
74
315
  return {};
75
316
  }
76
317
  }
318
+ function getNestedRecord(value) {
319
+ return value && typeof value === "object" ? value : null;
320
+ }
321
+ function extractToolInput(payload) {
322
+ return getNestedRecord(payload.tool_input) ?? getNestedRecord(payload.toolInput) ?? payload;
323
+ }
324
+ function extractToolResponse(payload) {
325
+ return getNestedRecord(payload.tool_response) ?? getNestedRecord(payload.toolResponse);
326
+ }
327
+ function extractToolName(payload) {
328
+ return extractString(payload, ["tool_name", "toolName"]);
329
+ }
77
330
  function extractString(input, keys) {
78
331
  for (const key of keys) {
79
332
  const value = input[key];
@@ -83,26 +336,148 @@ function extractString(input, keys) {
83
336
  }
84
337
  return null;
85
338
  }
339
+ function extractBoolean(input, keys) {
340
+ for (const key of keys) {
341
+ const value = input[key];
342
+ if (typeof value === "boolean") {
343
+ return value;
344
+ }
345
+ }
346
+ return null;
347
+ }
348
+ function extractToolCwd(payload) {
349
+ const toolInput = extractToolInput(payload);
350
+ return extractString(toolInput, ["cwd"]) ?? extractString(payload, ["cwd"]);
351
+ }
352
+ function didToolSucceed(payload) {
353
+ const toolResponse = extractToolResponse(payload);
354
+ const explicitSuccess = toolResponse ? extractBoolean(toolResponse, ["success", "ok"]) : null;
355
+ if (explicitSuccess !== null) {
356
+ return explicitSuccess;
357
+ }
358
+ const hookEventName = extractString(payload, ["hook_event_name", "hookEventName"]);
359
+ return hookEventName === "PostToolUse";
360
+ }
361
+ function collectStringPathValue(value) {
362
+ if (typeof value === "string" && value.trim()) return [value.trim()];
363
+ if (Array.isArray(value)) {
364
+ return value.flatMap((entry) => collectStringPathValue(entry));
365
+ }
366
+ return [];
367
+ }
368
+ function collectPathTargetsFromObject(input, keys) {
369
+ return keys.flatMap((key) => collectStringPathValue(input[key]));
370
+ }
371
+ function resolveCandidatePath(targetPath, baseDir) {
372
+ return path2.isAbsolute(targetPath) ? path2.normalize(targetPath) : path2.resolve(baseDir, targetPath);
373
+ }
374
+ function extractToolPathTargets(payload, toolName) {
375
+ const name = (toolName ?? extractToolName(payload) ?? "").trim().toLowerCase();
376
+ const toolInput = extractToolInput(payload);
377
+ const baseDir = extractToolCwd(payload) ?? process.cwd();
378
+ const baseKeys = ["path", "paths", "file_path", "filePath", "target_file", "targetFile", "filename"];
379
+ const targets = name === "notebookedit" ? collectPathTargetsFromObject(toolInput, ["target_notebook", "notebook_path", "notebookPath", ...baseKeys]) : collectPathTargetsFromObject(toolInput, baseKeys);
380
+ return Array.from(new Set(targets.map((entry) => resolveCandidatePath(entry, baseDir))));
381
+ }
382
+ async function findBoundRepo(startPath) {
383
+ if (!startPath) return null;
384
+ let current = path2.resolve(startPath);
385
+ let stats = await fs2.stat(current).catch(() => null);
386
+ if (stats?.isFile()) {
387
+ current = path2.dirname(current);
388
+ }
389
+ while (true) {
390
+ const bindingPath = path2.join(current, ".remix", "config.json");
391
+ const bindingStats = await fs2.stat(bindingPath).catch(() => null);
392
+ if (bindingStats?.isFile()) return current;
393
+ const parent = path2.dirname(current);
394
+ if (parent === current) return null;
395
+ current = parent;
396
+ }
397
+ }
398
+ async function resolveBoundRepoSummary(startPath) {
399
+ const repoRoot = await findBoundRepo(startPath);
400
+ if (!repoRoot) return null;
401
+ const binding = await readCollabBinding(repoRoot).catch(() => null);
402
+ if (!binding) return null;
403
+ return {
404
+ repoRoot,
405
+ projectId: binding.projectId,
406
+ currentAppId: binding.currentAppId,
407
+ upstreamAppId: binding.upstreamAppId
408
+ };
409
+ }
410
+ async function resolveTouchedBoundReposFromPaths(paths) {
411
+ const resolved = await Promise.all(paths.map((targetPath) => resolveBoundRepoSummary(targetPath)));
412
+ const unique = /* @__PURE__ */ new Map();
413
+ for (const repo of resolved) {
414
+ if (!repo) continue;
415
+ unique.set(repo.repoRoot, repo);
416
+ }
417
+ return Array.from(unique.values()).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
418
+ }
419
+ async function resolveBoundRepoFromToolCwd(payload) {
420
+ return resolveBoundRepoSummary(extractToolCwd(payload));
421
+ }
86
422
 
87
423
  // src/hook-post-collab.ts
88
424
  function isRecordingToolName(toolName) {
89
- return /remix_collab_(add|record_turn)$/i.test(toolName);
425
+ return /remix_collab_(add|add_change_step|record_turn|record_no_diff_turn)$/i.test(toolName);
426
+ }
427
+ function isRepoMutationToolName(toolName) {
428
+ return /remix_collab_(add|add_change_step|sync_apply|approve_and_sync_target|sync_upstream|reconcile_apply)$/i.test(toolName);
90
429
  }
91
430
  function isMemoryToolName(toolName) {
92
431
  return /remix_collab_memory_(summary|search|timeline|change_step_diff)$/i.test(toolName);
93
432
  }
433
+ function isStructuredLocalWriteToolName(toolName) {
434
+ return /^(Edit|MultiEdit|Write|Delete|NotebookEdit)$/i.test(toolName);
435
+ }
94
436
  async function main() {
95
437
  const payload = await readJsonStdin();
96
- const sessionId = extractString(payload, ["session_id"]);
97
- const toolName = extractString(payload, ["tool_name"]);
438
+ const sessionId = typeof payload.session_id === "string" && payload.session_id.trim() ? payload.session_id.trim() : null;
439
+ const toolName = extractToolName(payload);
98
440
  if (!sessionId || !toolName) {
99
441
  return;
100
442
  }
443
+ if (!didToolSucceed(payload)) {
444
+ return;
445
+ }
101
446
  if (isMemoryToolName(toolName)) {
102
447
  await markPendingTurnConsultedMemory(sessionId);
103
448
  }
104
- if (isRecordingToolName(toolName)) {
105
- await markPendingTurnRecordedByTool(sessionId);
449
+ if (isRepoMutationToolName(toolName) || isRecordingToolName(toolName)) {
450
+ const targetRepo = await resolveBoundRepoFromToolCwd(payload);
451
+ if (targetRepo) {
452
+ await upsertTouchedRepo(sessionId, {
453
+ repoRoot: targetRepo.repoRoot,
454
+ projectId: targetRepo.projectId,
455
+ currentAppId: targetRepo.currentAppId,
456
+ upstreamAppId: targetRepo.upstreamAppId,
457
+ touchedBy: toolName,
458
+ hasObservedWrite: isRepoMutationToolName(toolName)
459
+ });
460
+ if (isRepoMutationToolName(toolName)) {
461
+ await markTouchedRepoObservedWrite(sessionId, targetRepo.repoRoot, { toolName });
462
+ }
463
+ if (isRecordingToolName(toolName)) {
464
+ await markTouchedRepoManuallyRecorded(sessionId, targetRepo.repoRoot, { toolName });
465
+ }
466
+ }
467
+ }
468
+ if (isStructuredLocalWriteToolName(toolName)) {
469
+ const touchedRepos = await resolveTouchedBoundReposFromPaths(extractToolPathTargets(payload, toolName));
470
+ for (const repo of touchedRepos) {
471
+ await upsertTouchedRepo(sessionId, {
472
+ repoRoot: repo.repoRoot,
473
+ projectId: repo.projectId,
474
+ currentAppId: repo.currentAppId,
475
+ upstreamAppId: repo.upstreamAppId,
476
+ touchedBy: toolName,
477
+ hasObservedWrite: true
478
+ });
479
+ await markTouchedRepoObservedWrite(sessionId, repo.repoRoot, { toolName });
480
+ }
106
481
  }
107
482
  }
108
483
  main().catch((error) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hook-state.ts","../src/hook-utils.ts","../src/hook-post-collab.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport type { TurnIntent } from \"./history-routing.js\";\n\nexport type PendingTurnState = {\n sessionId: string;\n turnId: string;\n prompt: string;\n cwd: string | null;\n intent: TurnIntent;\n submittedAt: string;\n recordedByTool: boolean;\n consultedMemory: boolean;\n};\n\nfunction stateRoot(): string {\n return path.join(os.tmpdir(), \"remix-claude-plugin-hooks\");\n}\n\nfunction statePath(sessionId: string): string {\n return path.join(stateRoot(), `${sessionId}.json`);\n}\n\nasync function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n await fs.writeFile(tmpPath, JSON.stringify(value, null, 2) + \"\\n\", \"utf8\");\n await fs.rename(tmpPath, filePath);\n}\n\nexport async function loadPendingTurnState(sessionId: string): Promise<PendingTurnState | null> {\n const raw = await fs.readFile(statePath(sessionId), \"utf8\").catch(() => null);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<PendingTurnState>;\n if (!parsed || typeof parsed !== \"object\") return null;\n if (typeof parsed.sessionId !== \"string\" || typeof parsed.turnId !== \"string\" || typeof parsed.prompt !== \"string\") {\n return null;\n }\n\n const intent = parsed.intent;\n return {\n sessionId: parsed.sessionId,\n turnId: parsed.turnId,\n prompt: parsed.prompt,\n cwd: typeof parsed.cwd === \"string\" && parsed.cwd.trim() ? parsed.cwd : null,\n intent: intent === \"memory_first\" || intent === \"collab_state\" || intent === \"git_facts\" ? intent : \"neutral\",\n submittedAt: typeof parsed.submittedAt === \"string\" ? parsed.submittedAt : new Date().toISOString(),\n recordedByTool: Boolean(parsed.recordedByTool),\n consultedMemory: Boolean(parsed.consultedMemory),\n };\n } catch {\n return null;\n }\n}\n\nexport async function savePendingTurnState(state: PendingTurnState): Promise<void> {\n await writeJsonAtomic(statePath(state.sessionId), state);\n}\n\nexport async function createPendingTurnState(params: {\n sessionId: string;\n prompt: string;\n cwd?: string | null;\n intent: TurnIntent;\n}): Promise<PendingTurnState> {\n const state: PendingTurnState = {\n sessionId: params.sessionId,\n turnId: randomUUID(),\n prompt: params.prompt,\n cwd: params.cwd?.trim() || null,\n intent: params.intent,\n submittedAt: new Date().toISOString(),\n recordedByTool: false,\n consultedMemory: false,\n };\n await savePendingTurnState(state);\n return state;\n}\n\nexport async function markPendingTurnRecordedByTool(sessionId: string): Promise<void> {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing) return;\n existing.recordedByTool = true;\n await savePendingTurnState(existing);\n}\n\nexport async function markPendingTurnConsultedMemory(sessionId: string): Promise<void> {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing || existing.consultedMemory) return;\n existing.consultedMemory = true;\n await savePendingTurnState(existing);\n}\n\nexport async function clearPendingTurnState(sessionId: string): Promise<void> {\n await fs.rm(statePath(sessionId), { force: true }).catch(() => undefined);\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport async function readJsonStdin(): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n const raw = Buffer.concat(chunks).toString(\"utf8\").trim();\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction getNestedRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" ? (value as Record<string, unknown>) : null;\n}\n\nexport function extractToolInput(payload: Record<string, unknown>): Record<string, unknown> {\n return getNestedRecord(payload.tool_input) ?? getNestedRecord(payload.toolInput) ?? payload;\n}\n\nexport function extractString(input: Record<string, unknown>, keys: string[]): string | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"string\" && value.trim()) {\n return value.trim();\n }\n }\n return null;\n}\n\nexport function extractBoolean(input: Record<string, unknown>, keys: string[]): boolean | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"boolean\") {\n return value;\n }\n }\n return null;\n}\n\nexport async function findBoundRepo(startPath: string | null): Promise<string | null> {\n if (!startPath) return null;\n let current = path.resolve(startPath);\n let stats = await fs.stat(current).catch(() => null);\n if (stats?.isFile()) {\n current = path.dirname(current);\n }\n\n while (true) {\n const bindingPath = path.join(current, \".remix\", \"config.json\");\n const bindingStats = await fs.stat(bindingPath).catch(() => null);\n if (bindingStats?.isFile()) return current;\n const parent = path.dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n","import { markPendingTurnConsultedMemory, markPendingTurnRecordedByTool } from \"./hook-state.js\";\nimport { extractString, readJsonStdin } from \"./hook-utils.js\";\n\nfunction isRecordingToolName(toolName: string): boolean {\n return /remix_collab_(add|record_turn)$/i.test(toolName);\n}\n\nfunction isMemoryToolName(toolName: string): boolean {\n return /remix_collab_memory_(summary|search|timeline|change_step_diff)$/i.test(toolName);\n}\n\nasync function main(): Promise<void> {\n const payload = await readJsonStdin();\n const sessionId = extractString(payload, [\"session_id\"]);\n const toolName = extractString(payload, [\"tool_name\"]);\n if (!sessionId || !toolName) {\n return;\n }\n\n if (isMemoryToolName(toolName)) {\n await markPendingTurnConsultedMemory(sessionId);\n }\n\n if (isRecordingToolName(toolName)) {\n await markPendingTurnRecordedByTool(sessionId);\n }\n}\n\nmain().catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`${message}\\n`);\n process.exitCode = 0;\n});\n"],"mappings":"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","names":["fs","path"]}
1
+ {"version":3,"sources":["../src/hook-state.ts","../src/hook-utils.ts","../src/hook-post-collab.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\n\nimport type { TurnIntent } from \"./history-routing.js\";\n\nexport type RepoRecordMode = \"changed_turn\" | \"no_diff_turn\";\n\nexport type TouchedRepoState = {\n repoRoot: string;\n projectId: string | null;\n currentAppId: string | null;\n upstreamAppId: string | null;\n firstTouchedAt: string;\n lastTouchedAt: string;\n lastObservedWriteAt: string | null;\n touchedBy: string[];\n hasObservedWrite: boolean;\n manuallyRecorded: boolean;\n manuallyRecordedAt: string | null;\n manuallyRecordedByTool: string | null;\n stopAttempted: boolean;\n stopRecorded: boolean;\n stopRecordedAt: string | null;\n stopRecordedMode: RepoRecordMode | null;\n recordingFailureMessage: string | null;\n recordingFailureHint: string | null;\n recordingFailedAt: string | null;\n};\n\nexport type PendingTurnState = {\n sessionId: string;\n turnId: string;\n prompt: string;\n initialCwd: string | null;\n intent: TurnIntent;\n submittedAt: string;\n consultedMemory: boolean;\n touchedRepos: Record<string, TouchedRepoState>;\n turnFailureMessage: string | null;\n turnFailureHint: string | null;\n turnFailedAt: string | null;\n};\n\nfunction stateRoot(): string {\n return path.join(os.tmpdir(), \"remix-claude-plugin-hooks\");\n}\n\nfunction statePath(sessionId: string): string {\n return path.join(stateRoot(), `${sessionId}.json`);\n}\n\nfunction stateLockPath(sessionId: string): string {\n return path.join(stateRoot(), `${sessionId}.lock`);\n}\n\nfunction stateLockMetaPath(sessionId: string): string {\n return path.join(stateLockPath(sessionId), \"owner.json\");\n}\n\nasync function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n await fs.writeFile(tmpPath, JSON.stringify(value, null, 2) + \"\\n\", \"utf8\");\n await fs.rename(tmpPath, filePath);\n}\n\nconst STATE_LOCK_WAIT_MS = 2_000;\nconst STATE_LOCK_POLL_MS = 25;\nconst STATE_LOCK_STALE_MS = 30_000;\nconst STATE_LOCK_HEARTBEAT_MS = 5_000;\n\ntype StateLockMetadata = {\n ownerId: string;\n pid: number;\n createdAt: string;\n heartbeatAt: string;\n};\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function readStateLockMetadata(sessionId: string): Promise<StateLockMetadata | null> {\n const raw = await fs.readFile(stateLockMetaPath(sessionId), \"utf8\").catch(() => null);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<StateLockMetadata>;\n if (\n typeof parsed.ownerId !== \"string\" ||\n typeof parsed.pid !== \"number\" ||\n typeof parsed.createdAt !== \"string\" ||\n typeof parsed.heartbeatAt !== \"string\"\n ) {\n return null;\n }\n return {\n ownerId: parsed.ownerId,\n pid: parsed.pid,\n createdAt: parsed.createdAt,\n heartbeatAt: parsed.heartbeatAt,\n };\n } catch {\n return null;\n }\n}\n\nasync function writeStateLockMetadata(sessionId: string, metadata: StateLockMetadata): Promise<void> {\n await writeJsonAtomic(stateLockMetaPath(sessionId), metadata);\n}\n\nasync function tryRemoveStaleStateLock(sessionId: string): Promise<boolean> {\n const lockPath = stateLockPath(sessionId);\n const metadata = await readStateLockMetadata(sessionId);\n const staleByHeartbeat =\n metadata && Date.now() - new Date(metadata.heartbeatAt).getTime() > STATE_LOCK_STALE_MS;\n if (staleByHeartbeat) {\n await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);\n return true;\n }\n\n if (!metadata) {\n const lockStat = await fs.stat(lockPath).catch(() => null);\n if (lockStat && Date.now() - lockStat.mtimeMs > STATE_LOCK_STALE_MS) {\n await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);\n return true;\n }\n }\n\n return false;\n}\n\nasync function acquireStateLock(sessionId: string): Promise<() => Promise<void>> {\n const lockPath = stateLockPath(sessionId);\n const deadline = Date.now() + STATE_LOCK_WAIT_MS;\n\n while (true) {\n try {\n await fs.mkdir(lockPath);\n const ownerId = randomUUID();\n const createdAt = new Date().toISOString();\n const metadata: StateLockMetadata = {\n ownerId,\n pid: process.pid,\n createdAt,\n heartbeatAt: createdAt,\n };\n await writeStateLockMetadata(sessionId, metadata);\n let released = false;\n const heartbeat = setInterval(() => {\n if (released) return;\n void writeStateLockMetadata(sessionId, {\n ...metadata,\n heartbeatAt: new Date().toISOString(),\n }).catch(() => undefined);\n }, STATE_LOCK_HEARTBEAT_MS);\n heartbeat.unref?.();\n\n return async () => {\n if (released) return;\n released = true;\n clearInterval(heartbeat);\n const currentMetadata = await readStateLockMetadata(sessionId);\n if (currentMetadata?.ownerId === ownerId) {\n await fs.rm(lockPath, { recursive: true, force: true }).catch(() => undefined);\n }\n };\n } catch (error) {\n const code = error && typeof error === \"object\" && \"code\" in error ? (error as { code?: unknown }).code : null;\n if (code !== \"EEXIST\") {\n throw error;\n }\n\n if (await tryRemoveStaleStateLock(sessionId)) {\n continue;\n }\n\n if (Date.now() >= deadline) {\n throw new Error(`Timed out acquiring hook state lock for session ${sessionId}.`);\n }\n await sleep(STATE_LOCK_POLL_MS);\n }\n }\n}\n\nasync function withStateLock<T>(sessionId: string, fn: () => Promise<T>): Promise<T> {\n const release = await acquireStateLock(sessionId);\n try {\n return await fn();\n } finally {\n await release();\n }\n}\n\nfunction normalizeIntent(value: unknown): TurnIntent {\n return value === \"memory_first\" || value === \"collab_state\" || value === \"git_facts\" ? value : \"neutral\";\n}\n\nfunction normalizeString(value: unknown): string | null {\n return typeof value === \"string\" && value.trim() ? value.trim() : null;\n}\n\nfunction normalizeStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return Array.from(\n new Set(\n value\n .filter((entry): entry is string => typeof entry === \"string\" && entry.trim().length > 0)\n .map((entry) => entry.trim()),\n ),\n );\n}\n\nfunction normalizeTouchedRepo(value: unknown, repoRoot: string): TouchedRepoState | null {\n if (!value || typeof value !== \"object\") return null;\n const parsed = value as Partial<TouchedRepoState>;\n const normalizedRepoRoot = normalizeString(parsed.repoRoot) ?? repoRoot.trim();\n if (!normalizedRepoRoot) return null;\n\n return {\n repoRoot: normalizedRepoRoot,\n projectId: normalizeString(parsed.projectId),\n currentAppId: normalizeString(parsed.currentAppId),\n upstreamAppId: normalizeString(parsed.upstreamAppId),\n firstTouchedAt: normalizeString(parsed.firstTouchedAt) ?? new Date().toISOString(),\n lastTouchedAt: normalizeString(parsed.lastTouchedAt) ?? new Date().toISOString(),\n lastObservedWriteAt: normalizeString(parsed.lastObservedWriteAt),\n touchedBy: normalizeStringArray(parsed.touchedBy),\n hasObservedWrite: Boolean(parsed.hasObservedWrite),\n manuallyRecorded: Boolean(parsed.manuallyRecorded),\n manuallyRecordedAt: normalizeString(parsed.manuallyRecordedAt),\n manuallyRecordedByTool: normalizeString(parsed.manuallyRecordedByTool),\n stopAttempted: Boolean(parsed.stopAttempted),\n stopRecorded: Boolean(parsed.stopRecorded),\n stopRecordedAt: normalizeString(parsed.stopRecordedAt),\n stopRecordedMode: parsed.stopRecordedMode === \"changed_turn\" || parsed.stopRecordedMode === \"no_diff_turn\" ? parsed.stopRecordedMode : null,\n recordingFailureMessage: normalizeString(parsed.recordingFailureMessage),\n recordingFailureHint: normalizeString(parsed.recordingFailureHint),\n recordingFailedAt: normalizeString(parsed.recordingFailedAt),\n };\n}\n\nfunction normalizeTouchedRepos(value: unknown): Record<string, TouchedRepoState> {\n if (!value || typeof value !== \"object\") return {};\n const entries = Object.entries(value as Record<string, unknown>)\n .map(([repoRoot, repo]) => normalizeTouchedRepo(repo, repoRoot))\n .filter((repo): repo is TouchedRepoState => repo !== null)\n .sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));\n return Object.fromEntries(entries.map((repo) => [repo.repoRoot, repo]));\n}\n\nfunction createTouchedRepo(params: {\n repoRoot: string;\n projectId?: string | null;\n currentAppId?: string | null;\n upstreamAppId?: string | null;\n touchedBy?: string | null;\n hasObservedWrite?: boolean;\n}): TouchedRepoState {\n const now = new Date().toISOString();\n const touchedBy = params.touchedBy?.trim() ? [params.touchedBy.trim()] : [];\n return {\n repoRoot: params.repoRoot,\n projectId: normalizeString(params.projectId),\n currentAppId: normalizeString(params.currentAppId),\n upstreamAppId: normalizeString(params.upstreamAppId),\n firstTouchedAt: now,\n lastTouchedAt: now,\n lastObservedWriteAt: params.hasObservedWrite ? now : null,\n touchedBy,\n hasObservedWrite: Boolean(params.hasObservedWrite),\n manuallyRecorded: false,\n manuallyRecordedAt: null,\n manuallyRecordedByTool: null,\n stopAttempted: false,\n stopRecorded: false,\n stopRecordedAt: null,\n stopRecordedMode: null,\n recordingFailureMessage: null,\n recordingFailureHint: null,\n recordingFailedAt: null,\n };\n}\n\nasync function updatePendingTurnState(\n sessionId: string,\n updater: (state: PendingTurnState) => void | boolean,\n): Promise<PendingTurnState | null> {\n return withStateLock(sessionId, async () => {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing) return null;\n const result = updater(existing);\n if (result === false) return existing;\n await savePendingTurnState(existing);\n return existing;\n });\n}\n\nexport async function loadPendingTurnState(sessionId: string): Promise<PendingTurnState | null> {\n const raw = await fs.readFile(statePath(sessionId), \"utf8\").catch(() => null);\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<PendingTurnState>;\n if (!parsed || typeof parsed !== \"object\") return null;\n if (typeof parsed.sessionId !== \"string\" || typeof parsed.turnId !== \"string\" || typeof parsed.prompt !== \"string\") {\n return null;\n }\n return {\n sessionId: parsed.sessionId,\n turnId: parsed.turnId,\n prompt: parsed.prompt,\n initialCwd: normalizeString(parsed.initialCwd),\n intent: normalizeIntent(parsed.intent),\n submittedAt: typeof parsed.submittedAt === \"string\" ? parsed.submittedAt : new Date().toISOString(),\n consultedMemory: Boolean(parsed.consultedMemory),\n touchedRepos: normalizeTouchedRepos(parsed.touchedRepos),\n turnFailureMessage: normalizeString(parsed.turnFailureMessage),\n turnFailureHint: normalizeString(parsed.turnFailureHint),\n turnFailedAt: normalizeString(parsed.turnFailedAt),\n };\n } catch {\n return null;\n }\n}\n\nexport async function savePendingTurnState(state: PendingTurnState): Promise<void> {\n await writeJsonAtomic(statePath(state.sessionId), state);\n}\n\nexport async function createPendingTurnState(params: {\n sessionId: string;\n prompt: string;\n initialCwd?: string | null;\n intent: TurnIntent;\n}): Promise<PendingTurnState> {\n return withStateLock(params.sessionId, async () => {\n const state: PendingTurnState = {\n sessionId: params.sessionId,\n turnId: randomUUID(),\n prompt: params.prompt,\n initialCwd: params.initialCwd?.trim() || null,\n intent: params.intent,\n submittedAt: new Date().toISOString(),\n consultedMemory: false,\n touchedRepos: {},\n turnFailureMessage: null,\n turnFailureHint: null,\n turnFailedAt: null,\n };\n await savePendingTurnState(state);\n return state;\n });\n}\n\nexport async function upsertTouchedRepo(\n sessionId: string,\n params: {\n repoRoot: string;\n projectId?: string | null;\n currentAppId?: string | null;\n upstreamAppId?: string | null;\n touchedBy?: string | null;\n hasObservedWrite?: boolean;\n },\n): Promise<TouchedRepoState | null> {\n const normalizedRepoRoot = params.repoRoot.trim();\n if (!normalizedRepoRoot) return null;\n const state = await updatePendingTurnState(sessionId, (existing) => {\n const current =\n existing.touchedRepos[normalizedRepoRoot] ??\n createTouchedRepo({\n repoRoot: normalizedRepoRoot,\n projectId: params.projectId,\n currentAppId: params.currentAppId,\n upstreamAppId: params.upstreamAppId,\n touchedBy: params.touchedBy,\n hasObservedWrite: params.hasObservedWrite,\n });\n\n current.projectId = normalizeString(params.projectId) ?? current.projectId;\n current.currentAppId = normalizeString(params.currentAppId) ?? current.currentAppId;\n current.upstreamAppId = normalizeString(params.upstreamAppId) ?? current.upstreamAppId;\n current.lastTouchedAt = new Date().toISOString();\n if (params.touchedBy?.trim() && !current.touchedBy.includes(params.touchedBy.trim())) {\n current.touchedBy = [...current.touchedBy, params.touchedBy.trim()].sort((a, b) => a.localeCompare(b));\n }\n if (params.hasObservedWrite) {\n current.hasObservedWrite = true;\n current.lastObservedWriteAt = new Date().toISOString();\n }\n existing.touchedRepos[normalizedRepoRoot] = current;\n });\n return state?.touchedRepos[normalizedRepoRoot] ?? null;\n}\n\nexport async function markTouchedRepoObservedWrite(\n sessionId: string,\n repoRoot: string,\n params?: { toolName?: string | null },\n): Promise<void> {\n await upsertTouchedRepo(sessionId, {\n repoRoot,\n touchedBy: params?.toolName ?? null,\n hasObservedWrite: true,\n });\n}\n\nexport async function markTouchedRepoManuallyRecorded(\n sessionId: string,\n repoRoot: string,\n params?: { toolName?: string | null },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const normalizedRepoRoot = repoRoot.trim();\n if (!normalizedRepoRoot) return false;\n const current =\n existing.touchedRepos[normalizedRepoRoot] ??\n createTouchedRepo({\n repoRoot: normalizedRepoRoot,\n touchedBy: params?.toolName ?? null,\n hasObservedWrite: false,\n });\n current.lastTouchedAt = new Date().toISOString();\n current.manuallyRecorded = true;\n current.manuallyRecordedAt = new Date().toISOString();\n current.manuallyRecordedByTool = normalizeString(params?.toolName) ?? current.manuallyRecordedByTool;\n current.recordingFailureMessage = null;\n current.recordingFailureHint = null;\n current.recordingFailedAt = null;\n if (params?.toolName?.trim() && !current.touchedBy.includes(params.toolName.trim())) {\n current.touchedBy = [...current.touchedBy, params.toolName.trim()].sort((a, b) => a.localeCompare(b));\n }\n existing.touchedRepos[normalizedRepoRoot] = current;\n });\n}\n\nexport async function markPendingTurnConsultedMemory(sessionId: string): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n if (existing.consultedMemory) return false;\n existing.consultedMemory = true;\n });\n}\n\nexport async function markTouchedRepoStopAttempted(sessionId: string, repoRoot: string): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const current = existing.touchedRepos[repoRoot];\n if (!current) return false;\n current.stopAttempted = true;\n current.lastTouchedAt = new Date().toISOString();\n });\n}\n\nexport async function markTouchedRepoStopRecorded(\n sessionId: string,\n repoRoot: string,\n params: {\n mode: RepoRecordMode;\n },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const current = existing.touchedRepos[repoRoot];\n if (!current) return false;\n current.stopAttempted = true;\n current.stopRecorded = true;\n current.stopRecordedAt = new Date().toISOString();\n current.stopRecordedMode = params.mode;\n current.recordingFailureMessage = null;\n current.recordingFailureHint = null;\n current.recordingFailedAt = null;\n current.lastTouchedAt = new Date().toISOString();\n });\n}\n\nexport async function markTouchedRepoRecordingFailure(\n sessionId: string,\n repoRoot: string,\n params: {\n message: string;\n hint?: string | null;\n },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n const current = existing.touchedRepos[repoRoot];\n if (!current) return false;\n current.stopAttempted = true;\n current.recordingFailureMessage = params.message.trim();\n current.recordingFailureHint = params.hint?.trim() || null;\n current.recordingFailedAt = new Date().toISOString();\n current.lastTouchedAt = new Date().toISOString();\n });\n}\n\nexport async function markPendingTurnFailure(\n sessionId: string,\n params: {\n message: string;\n hint?: string | null;\n },\n): Promise<void> {\n await updatePendingTurnState(sessionId, (existing) => {\n existing.turnFailureMessage = params.message.trim();\n existing.turnFailureHint = params.hint?.trim() || null;\n existing.turnFailedAt = new Date().toISOString();\n });\n}\n\nexport async function listTouchedRepos(sessionId: string): Promise<TouchedRepoState[]> {\n const existing = await loadPendingTurnState(sessionId);\n if (!existing) return [];\n return Object.values(existing.touchedRepos).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));\n}\n\nexport async function clearPendingTurnState(sessionId: string): Promise<void> {\n await withStateLock(sessionId, async () => {\n await fs.rm(statePath(sessionId), { force: true }).catch(() => undefined);\n });\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { readCollabBinding } from \"@remixhq/core/binding\";\n\ntype BindingSummary = {\n repoRoot: string;\n projectId: string | null;\n currentAppId: string | null;\n upstreamAppId: string | null;\n};\n\nexport async function readJsonStdin(): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n const raw = Buffer.concat(chunks).toString(\"utf8\").trim();\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nfunction getNestedRecord(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" ? (value as Record<string, unknown>) : null;\n}\n\nexport function extractToolInput(payload: Record<string, unknown>): Record<string, unknown> {\n return getNestedRecord(payload.tool_input) ?? getNestedRecord(payload.toolInput) ?? payload;\n}\n\nexport function extractToolResponse(payload: Record<string, unknown>): Record<string, unknown> | null {\n return getNestedRecord(payload.tool_response) ?? getNestedRecord(payload.toolResponse);\n}\n\nexport function extractToolName(payload: Record<string, unknown>): string | null {\n return extractString(payload, [\"tool_name\", \"toolName\"]);\n}\n\nexport function extractString(input: Record<string, unknown>, keys: string[]): string | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"string\" && value.trim()) {\n return value.trim();\n }\n }\n return null;\n}\n\nexport function extractBoolean(input: Record<string, unknown>, keys: string[]): boolean | null {\n for (const key of keys) {\n const value = input[key];\n if (typeof value === \"boolean\") {\n return value;\n }\n }\n return null;\n}\n\nexport function extractToolCwd(payload: Record<string, unknown>): string | null {\n const toolInput = extractToolInput(payload);\n return extractString(toolInput, [\"cwd\"]) ?? extractString(payload, [\"cwd\"]);\n}\n\nexport function didToolSucceed(payload: Record<string, unknown>): boolean {\n const toolResponse = extractToolResponse(payload);\n const explicitSuccess = toolResponse ? extractBoolean(toolResponse, [\"success\", \"ok\"]) : null;\n if (explicitSuccess !== null) {\n return explicitSuccess;\n }\n const hookEventName = extractString(payload, [\"hook_event_name\", \"hookEventName\"]);\n return hookEventName === \"PostToolUse\";\n}\n\nfunction collectStringPathValue(value: unknown): string[] {\n if (typeof value === \"string\" && value.trim()) return [value.trim()];\n if (Array.isArray(value)) {\n return value.flatMap((entry) => collectStringPathValue(entry));\n }\n return [];\n}\n\nfunction collectPathTargetsFromObject(input: Record<string, unknown>, keys: string[]): string[] {\n return keys.flatMap((key) => collectStringPathValue(input[key]));\n}\n\nfunction resolveCandidatePath(targetPath: string, baseDir: string): string {\n return path.isAbsolute(targetPath) ? path.normalize(targetPath) : path.resolve(baseDir, targetPath);\n}\n\nexport function extractToolPathTargets(payload: Record<string, unknown>, toolName?: string | null): string[] {\n const name = (toolName ?? extractToolName(payload) ?? \"\").trim().toLowerCase();\n const toolInput = extractToolInput(payload);\n const baseDir = extractToolCwd(payload) ?? process.cwd();\n const baseKeys = [\"path\", \"paths\", \"file_path\", \"filePath\", \"target_file\", \"targetFile\", \"filename\"];\n\n const targets =\n name === \"notebookedit\"\n ? collectPathTargetsFromObject(toolInput, [\"target_notebook\", \"notebook_path\", \"notebookPath\", ...baseKeys])\n : collectPathTargetsFromObject(toolInput, baseKeys);\n\n return Array.from(new Set(targets.map((entry) => resolveCandidatePath(entry, baseDir))));\n}\n\nexport async function findBoundRepo(startPath: string | null): Promise<string | null> {\n if (!startPath) return null;\n let current = path.resolve(startPath);\n let stats = await fs.stat(current).catch(() => null);\n if (stats?.isFile()) {\n current = path.dirname(current);\n }\n\n while (true) {\n const bindingPath = path.join(current, \".remix\", \"config.json\");\n const bindingStats = await fs.stat(bindingPath).catch(() => null);\n if (bindingStats?.isFile()) return current;\n const parent = path.dirname(current);\n if (parent === current) return null;\n current = parent;\n }\n}\n\nexport async function resolveBoundRepoSummary(startPath: string | null): Promise<BindingSummary | null> {\n const repoRoot = await findBoundRepo(startPath);\n if (!repoRoot) return null;\n const binding = await readCollabBinding(repoRoot).catch(() => null);\n if (!binding) return null;\n return {\n repoRoot,\n projectId: binding.projectId,\n currentAppId: binding.currentAppId,\n upstreamAppId: binding.upstreamAppId,\n };\n}\n\nexport async function resolveTouchedBoundReposFromPaths(paths: string[]): Promise<BindingSummary[]> {\n const resolved = await Promise.all(paths.map((targetPath) => resolveBoundRepoSummary(targetPath)));\n const unique = new Map<string, BindingSummary>();\n for (const repo of resolved) {\n if (!repo) continue;\n unique.set(repo.repoRoot, repo);\n }\n return Array.from(unique.values()).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));\n}\n\nexport async function resolveBoundRepoFromToolCwd(payload: Record<string, unknown>): Promise<BindingSummary | null> {\n return resolveBoundRepoSummary(extractToolCwd(payload));\n}\n","import {\n markPendingTurnConsultedMemory,\n markTouchedRepoManuallyRecorded,\n markTouchedRepoObservedWrite,\n upsertTouchedRepo,\n} from \"./hook-state.js\";\nimport {\n didToolSucceed,\n extractToolName,\n extractToolPathTargets,\n readJsonStdin,\n resolveBoundRepoFromToolCwd,\n resolveTouchedBoundReposFromPaths,\n} from \"./hook-utils.js\";\n\nfunction isRecordingToolName(toolName: string): boolean {\n return /remix_collab_(add|add_change_step|record_turn|record_no_diff_turn)$/i.test(toolName);\n}\n\nfunction isRepoMutationToolName(toolName: string): boolean {\n return /remix_collab_(add|add_change_step|sync_apply|approve_and_sync_target|sync_upstream|reconcile_apply)$/i.test(toolName);\n}\n\nfunction isMemoryToolName(toolName: string): boolean {\n return /remix_collab_memory_(summary|search|timeline|change_step_diff)$/i.test(toolName);\n}\n\nfunction isStructuredLocalWriteToolName(toolName: string): boolean {\n return /^(Edit|MultiEdit|Write|Delete|NotebookEdit)$/i.test(toolName);\n}\n\nasync function main(): Promise<void> {\n const payload = await readJsonStdin();\n const sessionId = typeof payload.session_id === \"string\" && payload.session_id.trim() ? payload.session_id.trim() : null;\n const toolName = extractToolName(payload);\n if (!sessionId || !toolName) {\n return;\n }\n\n if (!didToolSucceed(payload)) {\n return;\n }\n\n if (isMemoryToolName(toolName)) {\n await markPendingTurnConsultedMemory(sessionId);\n }\n\n if (isRepoMutationToolName(toolName) || isRecordingToolName(toolName)) {\n const targetRepo = await resolveBoundRepoFromToolCwd(payload);\n if (targetRepo) {\n await upsertTouchedRepo(sessionId, {\n repoRoot: targetRepo.repoRoot,\n projectId: targetRepo.projectId,\n currentAppId: targetRepo.currentAppId,\n upstreamAppId: targetRepo.upstreamAppId,\n touchedBy: toolName,\n hasObservedWrite: isRepoMutationToolName(toolName),\n });\n if (isRepoMutationToolName(toolName)) {\n await markTouchedRepoObservedWrite(sessionId, targetRepo.repoRoot, { toolName });\n }\n if (isRecordingToolName(toolName)) {\n await markTouchedRepoManuallyRecorded(sessionId, targetRepo.repoRoot, { toolName });\n }\n }\n }\n\n if (isStructuredLocalWriteToolName(toolName)) {\n const touchedRepos = await resolveTouchedBoundReposFromPaths(extractToolPathTargets(payload, toolName));\n for (const repo of touchedRepos) {\n await upsertTouchedRepo(sessionId, {\n repoRoot: repo.repoRoot,\n projectId: repo.projectId,\n currentAppId: repo.currentAppId,\n upstreamAppId: repo.upstreamAppId,\n touchedBy: toolName,\n hasObservedWrite: true,\n });\n await markTouchedRepoObservedWrite(sessionId, repo.repoRoot, { toolName });\n }\n }\n}\n\nmain().catch((error) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`${message}\\n`);\n process.exitCode = 0;\n});\n"],"mappings":"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","names":["fs","path"]}