@remixhq/claude-plugin 0.1.11 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/agents/remix-collab.md +5 -6
- package/dist/hook-post-collab.cjs +339 -23
- package/dist/hook-post-collab.cjs.map +1 -1
- package/dist/hook-post-collab.d.cts +2 -1
- package/dist/hook-pre-git.cjs +3 -2
- package/dist/hook-pre-git.cjs.map +1 -1
- package/dist/hook-stop-collab.cjs +595 -119
- package/dist/hook-stop-collab.cjs.map +1 -1
- package/dist/hook-stop-collab.d.cts +2 -1
- package/dist/hook-user-prompt.cjs +305 -17
- package/dist/hook-user-prompt.cjs.map +1 -1
- package/dist/hook-user-prompt.d.cts +2 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +12228 -10077
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +3 -3
- package/skills/access-troubleshooting/SKILL.md +35 -0
- package/skills/app-ops-triage/SKILL.md +32 -0
- package/skills/historical-memory-routing/SKILL.md +2 -0
- package/skills/identity-and-scope-routing/SKILL.md +31 -0
- package/skills/init-or-remix/SKILL.md +1 -1
- package/skills/review-merge-request/SKILL.md +1 -1
- package/skills/safe-collab-workflow/SKILL.md +15 -21
- package/skills/submit-change-step/SKILL.md +11 -19
- package/skills/sync-and-reconcile/SKILL.md +3 -1
package/agents/remix-collab.md
CHANGED
|
@@ -22,10 +22,10 @@ Operating rules:
|
|
|
22
22
|
- use `remix_collab_memory_change_step_diff` only after you have identified a specific `changeStepId`.
|
|
23
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
|
-
10.
|
|
26
|
-
11.
|
|
27
|
-
12.
|
|
28
|
-
13.
|
|
25
|
+
10. In a bound repo, Remix MCP tools are the required workflow layer for ordinary collaboration work.
|
|
26
|
+
11. In a bound repo, exactly one final `remix_collab_finalize_turn` is required before the final user-facing response.
|
|
27
|
+
12. The final recording call must use the exact user prompt and your final assistant response.
|
|
28
|
+
13. Do not finish the turn before recording. Do not make additional repo mutations after the final turn-recording call unless you intend to record again.
|
|
29
29
|
14. Do not duplicate core business logic in reasoning. Use the MCP tools to inspect and execute the workflow.
|
|
30
30
|
|
|
31
31
|
When appropriate:
|
|
@@ -35,7 +35,6 @@ When appropriate:
|
|
|
35
35
|
- use `remix_collab_checkout` to continue work on an existing app id without creating a fork
|
|
36
36
|
- use memory summary/search/timeline tools before repo inspection when historical context or reasoning is needed
|
|
37
37
|
- use the explicit change-step diff tool only after you already know which `changeStepId` matters
|
|
38
|
-
- use `
|
|
39
|
-
- use `remix_collab_record_turn` only when a manual no-diff turn recording step is explicitly needed
|
|
38
|
+
- use `remix_collab_finalize_turn` as the final turn recorder
|
|
40
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`
|
|
41
40
|
- use merge request tools for review and approval flows
|
|
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
9
13
|
var __copyProps = (to, from, except, desc) => {
|
|
10
14
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
15
|
for (let key of __getOwnPropNames(from))
|
|
@@ -22,6 +26,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
26
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
27
|
mod
|
|
24
28
|
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/hook-post-collab.ts
|
|
32
|
+
var hook_post_collab_exports = {};
|
|
33
|
+
__export(hook_post_collab_exports, {
|
|
34
|
+
runHookPostCollab: () => runHookPostCollab
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(hook_post_collab_exports);
|
|
37
|
+
|
|
38
|
+
// src/hook-diagnostics.ts
|
|
39
|
+
var import_node_crypto2 = require("crypto");
|
|
40
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
41
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
42
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
25
43
|
|
|
26
44
|
// src/hook-state.ts
|
|
27
45
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
@@ -29,7 +47,8 @@ var import_node_os = __toESM(require("os"), 1);
|
|
|
29
47
|
var import_node_path = __toESM(require("path"), 1);
|
|
30
48
|
var import_node_crypto = require("crypto");
|
|
31
49
|
function stateRoot() {
|
|
32
|
-
|
|
50
|
+
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
|
|
51
|
+
return configured || import_node_path.default.join(import_node_os.default.tmpdir(), "remix-claude-plugin-hooks");
|
|
33
52
|
}
|
|
34
53
|
function statePath(sessionId) {
|
|
35
54
|
return import_node_path.default.join(stateRoot(), `${sessionId}.json`);
|
|
@@ -94,6 +113,7 @@ async function tryRemoveStaleStateLock(sessionId) {
|
|
|
94
113
|
async function acquireStateLock(sessionId) {
|
|
95
114
|
const lockPath = stateLockPath(sessionId);
|
|
96
115
|
const deadline = Date.now() + STATE_LOCK_WAIT_MS;
|
|
116
|
+
await import_promises.default.mkdir(stateRoot(), { recursive: true });
|
|
97
117
|
while (true) {
|
|
98
118
|
try {
|
|
99
119
|
await import_promises.default.mkdir(lockPath);
|
|
@@ -328,19 +348,139 @@ async function markPendingTurnConsultedMemory(sessionId) {
|
|
|
328
348
|
});
|
|
329
349
|
}
|
|
330
350
|
|
|
351
|
+
// package.json
|
|
352
|
+
var package_default = {
|
|
353
|
+
name: "@remixhq/claude-plugin",
|
|
354
|
+
version: "0.1.14",
|
|
355
|
+
description: "Claude Code plugin for Remix collaboration workflows",
|
|
356
|
+
homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
|
|
357
|
+
license: "MIT",
|
|
358
|
+
repository: {
|
|
359
|
+
type: "git",
|
|
360
|
+
url: "https://github.com/RemixDotOne/remix-claude-plugin.git"
|
|
361
|
+
},
|
|
362
|
+
type: "module",
|
|
363
|
+
engines: {
|
|
364
|
+
node: ">=20"
|
|
365
|
+
},
|
|
366
|
+
publishConfig: {
|
|
367
|
+
access: "public"
|
|
368
|
+
},
|
|
369
|
+
files: [
|
|
370
|
+
"dist",
|
|
371
|
+
".claude-plugin/plugin.json",
|
|
372
|
+
".mcp.json",
|
|
373
|
+
"skills",
|
|
374
|
+
"hooks",
|
|
375
|
+
"agents"
|
|
376
|
+
],
|
|
377
|
+
scripts: {
|
|
378
|
+
build: "tsup",
|
|
379
|
+
postbuild: `node -e "const fs=require('node:fs'); for (const p of ['dist/mcp-server.cjs','dist/hook-pre-git.cjs','dist/hook-user-prompt.cjs','dist/hook-post-collab.cjs','dist/hook-stop-collab.cjs']) fs.chmodSync(p, 0o755);"`,
|
|
380
|
+
dev: "tsx src/mcp-server.ts",
|
|
381
|
+
typecheck: "tsc -p tsconfig.json --noEmit",
|
|
382
|
+
prepack: "npm run build"
|
|
383
|
+
},
|
|
384
|
+
dependencies: {
|
|
385
|
+
"@remixhq/core": "^0.1.9",
|
|
386
|
+
"@remixhq/mcp": "^0.1.9"
|
|
387
|
+
},
|
|
388
|
+
devDependencies: {
|
|
389
|
+
"@types/node": "^25.4.0",
|
|
390
|
+
tsup: "^8.5.1",
|
|
391
|
+
tsx: "^4.21.0",
|
|
392
|
+
typescript: "^5.9.3"
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/metadata.ts
|
|
397
|
+
var pluginMetadata = {
|
|
398
|
+
name: package_default.name,
|
|
399
|
+
version: package_default.version,
|
|
400
|
+
description: package_default.description,
|
|
401
|
+
pluginId: "remix",
|
|
402
|
+
agentName: "remix-collab"
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// src/hook-diagnostics.ts
|
|
406
|
+
var MAX_LOG_BYTES = 512 * 1024;
|
|
407
|
+
function resolveClaudeRoot() {
|
|
408
|
+
const configured = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
409
|
+
return configured || import_node_path2.default.join(import_node_os2.default.homedir(), ".claude");
|
|
410
|
+
}
|
|
411
|
+
function resolvePluginDataDirName() {
|
|
412
|
+
return `${pluginMetadata.pluginId}-${pluginMetadata.pluginId}`;
|
|
413
|
+
}
|
|
414
|
+
function getHookDiagnosticsDirPath() {
|
|
415
|
+
const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_DIAGNOSTICS_DIR?.trim();
|
|
416
|
+
return configured || import_node_path2.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
|
|
417
|
+
}
|
|
418
|
+
function getHookDiagnosticsLogPath() {
|
|
419
|
+
return import_node_path2.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
|
|
420
|
+
}
|
|
421
|
+
function toFieldValue(value) {
|
|
422
|
+
if (value === null) return null;
|
|
423
|
+
if (typeof value === "string") return value;
|
|
424
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
425
|
+
if (typeof value === "boolean") return value;
|
|
426
|
+
return void 0;
|
|
427
|
+
}
|
|
428
|
+
function normalizeFields(fields) {
|
|
429
|
+
if (!fields) return {};
|
|
430
|
+
const normalizedEntries = Object.entries(fields).map(([key, value]) => {
|
|
431
|
+
const normalized = toFieldValue(value);
|
|
432
|
+
return normalized === void 0 ? null : [key, normalized];
|
|
433
|
+
}).filter((entry) => entry !== null);
|
|
434
|
+
return Object.fromEntries(normalizedEntries);
|
|
435
|
+
}
|
|
436
|
+
async function rotateLogIfNeeded(logPath) {
|
|
437
|
+
const stat = await import_promises2.default.stat(logPath).catch(() => null);
|
|
438
|
+
if (!stat || stat.size < MAX_LOG_BYTES) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const rotatedPath = `${logPath}.1`;
|
|
442
|
+
await import_promises2.default.rm(rotatedPath, { force: true }).catch(() => void 0);
|
|
443
|
+
await import_promises2.default.rename(logPath, rotatedPath).catch(() => void 0);
|
|
444
|
+
}
|
|
445
|
+
async function appendHookDiagnosticsEvent(params) {
|
|
446
|
+
try {
|
|
447
|
+
const logPath = getHookDiagnosticsLogPath();
|
|
448
|
+
await import_promises2.default.mkdir(import_node_path2.default.dirname(logPath), { recursive: true });
|
|
449
|
+
await rotateLogIfNeeded(logPath);
|
|
450
|
+
const event = {
|
|
451
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
452
|
+
hook: params.hook,
|
|
453
|
+
pluginVersion: pluginMetadata.version,
|
|
454
|
+
pid: process.pid,
|
|
455
|
+
sessionId: params.sessionId?.trim() || null,
|
|
456
|
+
turnId: params.turnId?.trim() || null,
|
|
457
|
+
stage: params.stage.trim(),
|
|
458
|
+
result: params.result,
|
|
459
|
+
reason: params.reason?.trim() || null,
|
|
460
|
+
toolName: params.toolName?.trim() || null,
|
|
461
|
+
repoRoot: params.repoRoot?.trim() || null,
|
|
462
|
+
message: params.message?.trim() || null,
|
|
463
|
+
fields: normalizeFields(params.fields)
|
|
464
|
+
};
|
|
465
|
+
await import_promises2.default.appendFile(logPath, `${JSON.stringify(event)}
|
|
466
|
+
`, "utf8");
|
|
467
|
+
} catch {
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
331
471
|
// src/hook-utils.ts
|
|
332
|
-
var
|
|
333
|
-
var
|
|
472
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
473
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
334
474
|
|
|
335
|
-
// node_modules/@remixhq/core/dist/chunk-
|
|
336
|
-
var
|
|
475
|
+
// node_modules/@remixhq/core/dist/chunk-GEHSFPCD.js
|
|
476
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
337
477
|
var import_path = __toESM(require("path"), 1);
|
|
338
478
|
function getCollabBindingPath(repoRoot) {
|
|
339
479
|
return import_path.default.join(repoRoot, ".remix", "config.json");
|
|
340
480
|
}
|
|
341
481
|
async function readCollabBinding(repoRoot) {
|
|
342
482
|
try {
|
|
343
|
-
const raw = await
|
|
483
|
+
const raw = await import_promises3.default.readFile(getCollabBindingPath(repoRoot), "utf8");
|
|
344
484
|
const parsed = JSON.parse(raw);
|
|
345
485
|
if (parsed?.schemaVersion !== 1) return null;
|
|
346
486
|
if (!parsed.projectId || !parsed.currentAppId || !parsed.upstreamAppId) return null;
|
|
@@ -384,6 +524,11 @@ function extractToolInput(payload) {
|
|
|
384
524
|
function extractToolResponse(payload) {
|
|
385
525
|
return getNestedRecord(payload.tool_response) ?? getNestedRecord(payload.toolResponse);
|
|
386
526
|
}
|
|
527
|
+
function extractToolStructuredData(payload) {
|
|
528
|
+
const toolResponse = extractToolResponse(payload);
|
|
529
|
+
const structuredContent = getNestedRecord(toolResponse?.structuredContent) ?? getNestedRecord(payload.structuredContent);
|
|
530
|
+
return getNestedRecord(toolResponse?.data) ?? getNestedRecord(structuredContent?.data) ?? structuredContent;
|
|
531
|
+
}
|
|
387
532
|
function extractToolName(payload) {
|
|
388
533
|
return extractString(payload, ["tool_name", "toolName"]);
|
|
389
534
|
}
|
|
@@ -397,6 +542,26 @@ function normalizeHookToolName(toolName) {
|
|
|
397
542
|
}
|
|
398
543
|
return trimmed;
|
|
399
544
|
}
|
|
545
|
+
function extractAssistantResponse(payload) {
|
|
546
|
+
const candidateKeys = [
|
|
547
|
+
"last_assistant_message",
|
|
548
|
+
"lastAssistantMessage",
|
|
549
|
+
"assistant_response",
|
|
550
|
+
"assistantResponse",
|
|
551
|
+
"assistant_message",
|
|
552
|
+
"assistantMessage",
|
|
553
|
+
"response",
|
|
554
|
+
"message"
|
|
555
|
+
];
|
|
556
|
+
return extractString(payload, candidateKeys) ?? extractString(extractToolResponse(payload) ?? {}, candidateKeys) ?? extractString(extractToolStructuredData(payload) ?? {}, candidateKeys) ?? extractString(extractToolInput(payload), candidateKeys);
|
|
557
|
+
}
|
|
558
|
+
function extractFinalizeTurnMode(payload) {
|
|
559
|
+
const mode = extractString(extractToolStructuredData(payload) ?? {}, ["mode"]) ?? extractString(extractToolResponse(payload) ?? {}, ["mode"]) ?? extractString(payload, ["mode"]);
|
|
560
|
+
if (mode === "changed_turn" || mode === "no_diff_turn") {
|
|
561
|
+
return mode;
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
400
565
|
function extractString(input, keys) {
|
|
401
566
|
for (const key of keys) {
|
|
402
567
|
const value = input[key];
|
|
@@ -462,7 +627,7 @@ function collectPathTargetsFromObject(input, keys) {
|
|
|
462
627
|
return keys.flatMap((key) => collectStringPathValue(input[key]));
|
|
463
628
|
}
|
|
464
629
|
function resolveCandidatePath(targetPath, baseDir) {
|
|
465
|
-
return
|
|
630
|
+
return import_node_path3.default.isAbsolute(targetPath) ? import_node_path3.default.normalize(targetPath) : import_node_path3.default.resolve(baseDir, targetPath);
|
|
466
631
|
}
|
|
467
632
|
function extractToolPathTargets(payload, toolName) {
|
|
468
633
|
const name = (toolName ?? extractToolName(payload) ?? "").trim().toLowerCase();
|
|
@@ -474,16 +639,16 @@ function extractToolPathTargets(payload, toolName) {
|
|
|
474
639
|
}
|
|
475
640
|
async function findBoundRepo(startPath) {
|
|
476
641
|
if (!startPath) return null;
|
|
477
|
-
let current =
|
|
478
|
-
let stats = await
|
|
642
|
+
let current = import_node_path3.default.resolve(startPath);
|
|
643
|
+
let stats = await import_promises4.default.stat(current).catch(() => null);
|
|
479
644
|
if (stats?.isFile()) {
|
|
480
|
-
current =
|
|
645
|
+
current = import_node_path3.default.dirname(current);
|
|
481
646
|
}
|
|
482
647
|
while (true) {
|
|
483
|
-
const bindingPath =
|
|
484
|
-
const bindingStats = await
|
|
648
|
+
const bindingPath = import_node_path3.default.join(current, ".remix", "config.json");
|
|
649
|
+
const bindingStats = await import_promises4.default.stat(bindingPath).catch(() => null);
|
|
485
650
|
if (bindingStats?.isFile()) return current;
|
|
486
|
-
const parent =
|
|
651
|
+
const parent = import_node_path3.default.dirname(current);
|
|
487
652
|
if (parent === current) return null;
|
|
488
653
|
current = parent;
|
|
489
654
|
}
|
|
@@ -532,15 +697,28 @@ function isShellToolName(toolName) {
|
|
|
532
697
|
function isRemoteChangeRecordingToolName(toolName) {
|
|
533
698
|
return /remix_collab_(add|add_change_step)$/i.test(toolName);
|
|
534
699
|
}
|
|
535
|
-
function
|
|
536
|
-
|
|
537
|
-
|
|
700
|
+
function hasManualFullTurnPayload(payload) {
|
|
701
|
+
const toolInput = extractToolInput(payload);
|
|
702
|
+
return Boolean(extractString(toolInput, ["prompt"]) && extractAssistantResponse(payload));
|
|
703
|
+
}
|
|
704
|
+
function getManualRecordingScope(payload, toolName) {
|
|
705
|
+
if (/remix_collab_finalize_turn$/i.test(toolName)) {
|
|
706
|
+
return "full_turn";
|
|
707
|
+
}
|
|
708
|
+
if (/remix_collab_(add|add_change_step)$/i.test(toolName)) {
|
|
709
|
+
return hasManualFullTurnPayload(payload) ? "full_turn" : "change_step";
|
|
538
710
|
}
|
|
539
|
-
if (/remix_collab_(
|
|
711
|
+
if (/remix_collab_(record_turn|record_no_diff_turn)$/i.test(toolName)) {
|
|
540
712
|
return "full_turn";
|
|
541
713
|
}
|
|
542
714
|
return null;
|
|
543
715
|
}
|
|
716
|
+
function didFinalizeTurnRecordRemoteChange(payload, toolName) {
|
|
717
|
+
if (!/remix_collab_finalize_turn$/i.test(toolName)) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
return extractFinalizeTurnMode(payload) === "changed_turn";
|
|
721
|
+
}
|
|
544
722
|
function isLikelyMutatingShellCommand(command) {
|
|
545
723
|
const normalized = command.trim().toLowerCase();
|
|
546
724
|
if (!normalized) return false;
|
|
@@ -580,22 +758,71 @@ function isLikelyMutatingShellCommand(command) {
|
|
|
580
758
|
}
|
|
581
759
|
return false;
|
|
582
760
|
}
|
|
583
|
-
async function
|
|
584
|
-
const payload = await readJsonStdin();
|
|
761
|
+
async function runHookPostCollab(payload) {
|
|
585
762
|
const sessionId = typeof payload.session_id === "string" && payload.session_id.trim() ? payload.session_id.trim() : null;
|
|
586
763
|
const toolName = normalizeHookToolName(extractToolName(payload));
|
|
764
|
+
await appendHookDiagnosticsEvent({
|
|
765
|
+
hook: "PostToolUse",
|
|
766
|
+
sessionId,
|
|
767
|
+
stage: "payload_received",
|
|
768
|
+
result: "start",
|
|
769
|
+
toolName,
|
|
770
|
+
fields: {
|
|
771
|
+
hasSessionId: Boolean(sessionId),
|
|
772
|
+
hasToolName: Boolean(toolName)
|
|
773
|
+
}
|
|
774
|
+
});
|
|
587
775
|
if (!sessionId || !toolName) {
|
|
776
|
+
await appendHookDiagnosticsEvent({
|
|
777
|
+
hook: "PostToolUse",
|
|
778
|
+
sessionId,
|
|
779
|
+
stage: "payload_validation",
|
|
780
|
+
result: "skip",
|
|
781
|
+
reason: !sessionId ? "missing_session_id" : "missing_tool_name",
|
|
782
|
+
toolName
|
|
783
|
+
});
|
|
588
784
|
return;
|
|
589
785
|
}
|
|
590
786
|
const toolSucceeded = didToolSucceed(payload);
|
|
591
|
-
const remoteChangeRecordedButSyncFailed = isRemoteChangeRecordingToolName(toolName) && isRemoteChangeRecordedButLocalSyncFailed(payload);
|
|
787
|
+
const remoteChangeRecordedButSyncFailed = (isRemoteChangeRecordingToolName(toolName) || /remix_collab_finalize_turn$/i.test(toolName)) && isRemoteChangeRecordedButLocalSyncFailed(payload);
|
|
788
|
+
await appendHookDiagnosticsEvent({
|
|
789
|
+
hook: "PostToolUse",
|
|
790
|
+
sessionId,
|
|
791
|
+
stage: "tool_classified",
|
|
792
|
+
result: "info",
|
|
793
|
+
toolName,
|
|
794
|
+
fields: {
|
|
795
|
+
toolSucceeded,
|
|
796
|
+
remoteChangeRecordedButSyncFailed,
|
|
797
|
+
isMemoryTool: isMemoryToolName(toolName),
|
|
798
|
+
isRepoMutationTool: isRepoMutationToolName(toolName),
|
|
799
|
+
isShellTool: isShellToolName(toolName),
|
|
800
|
+
isStructuredLocalWriteTool: isStructuredLocalWriteToolName(toolName),
|
|
801
|
+
isStructuredLocalReadTool: isStructuredLocalReadToolName(toolName)
|
|
802
|
+
}
|
|
803
|
+
});
|
|
592
804
|
if (!toolSucceeded && !remoteChangeRecordedButSyncFailed) {
|
|
805
|
+
await appendHookDiagnosticsEvent({
|
|
806
|
+
hook: "PostToolUse",
|
|
807
|
+
sessionId,
|
|
808
|
+
stage: "tool_result_gate",
|
|
809
|
+
result: "skip",
|
|
810
|
+
reason: "tool_failed_or_skipped",
|
|
811
|
+
toolName
|
|
812
|
+
});
|
|
593
813
|
return;
|
|
594
814
|
}
|
|
595
815
|
if (toolSucceeded && isMemoryToolName(toolName)) {
|
|
596
816
|
await markPendingTurnConsultedMemory(sessionId);
|
|
817
|
+
await appendHookDiagnosticsEvent({
|
|
818
|
+
hook: "PostToolUse",
|
|
819
|
+
sessionId,
|
|
820
|
+
stage: "memory_marked_consulted",
|
|
821
|
+
result: "success",
|
|
822
|
+
toolName
|
|
823
|
+
});
|
|
597
824
|
}
|
|
598
|
-
const manualRecordingScope = getManualRecordingScope(toolName);
|
|
825
|
+
const manualRecordingScope = getManualRecordingScope(payload, toolName);
|
|
599
826
|
if (isRepoMutationToolName(toolName) || manualRecordingScope) {
|
|
600
827
|
const targetRepo = await resolveBoundRepoFromToolCwd(payload);
|
|
601
828
|
if (targetRepo) {
|
|
@@ -614,9 +841,31 @@ async function main() {
|
|
|
614
841
|
await markTouchedRepoManuallyRecorded(sessionId, targetRepo.repoRoot, {
|
|
615
842
|
toolName,
|
|
616
843
|
scope: manualRecordingScope,
|
|
617
|
-
remoteChangeRecorded: toolSucceeded ? isRemoteChangeRecordingToolName(toolName) : remoteChangeRecordedButSyncFailed
|
|
844
|
+
remoteChangeRecorded: toolSucceeded ? isRemoteChangeRecordingToolName(toolName) || didFinalizeTurnRecordRemoteChange(payload, toolName) : remoteChangeRecordedButSyncFailed
|
|
618
845
|
});
|
|
619
846
|
}
|
|
847
|
+
await appendHookDiagnosticsEvent({
|
|
848
|
+
hook: "PostToolUse",
|
|
849
|
+
sessionId,
|
|
850
|
+
stage: "repo_marked_from_tool_cwd",
|
|
851
|
+
result: "success",
|
|
852
|
+
toolName,
|
|
853
|
+
repoRoot: targetRepo.repoRoot,
|
|
854
|
+
fields: {
|
|
855
|
+
hasObservedWrite: isRepoMutationToolName(toolName),
|
|
856
|
+
manualRecordingScope,
|
|
857
|
+
remoteChangeRecordedButSyncFailed
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
} else {
|
|
861
|
+
await appendHookDiagnosticsEvent({
|
|
862
|
+
hook: "PostToolUse",
|
|
863
|
+
sessionId,
|
|
864
|
+
stage: "repo_marked_from_tool_cwd",
|
|
865
|
+
result: "skip",
|
|
866
|
+
reason: "repo_not_bound_from_tool_cwd",
|
|
867
|
+
toolName
|
|
868
|
+
});
|
|
620
869
|
}
|
|
621
870
|
}
|
|
622
871
|
if (toolSucceeded && isShellToolName(toolName)) {
|
|
@@ -635,10 +884,44 @@ async function main() {
|
|
|
635
884
|
if (hasObservedWrite) {
|
|
636
885
|
await markTouchedRepoObservedWrite(sessionId, targetRepo.repoRoot, { toolName });
|
|
637
886
|
}
|
|
887
|
+
await appendHookDiagnosticsEvent({
|
|
888
|
+
hook: "PostToolUse",
|
|
889
|
+
sessionId,
|
|
890
|
+
stage: "shell_repo_marked",
|
|
891
|
+
result: "success",
|
|
892
|
+
toolName,
|
|
893
|
+
repoRoot: targetRepo.repoRoot,
|
|
894
|
+
fields: {
|
|
895
|
+
hasObservedWrite,
|
|
896
|
+
bashCommandLength: bashCommand?.length ?? 0
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
} else {
|
|
900
|
+
await appendHookDiagnosticsEvent({
|
|
901
|
+
hook: "PostToolUse",
|
|
902
|
+
sessionId,
|
|
903
|
+
stage: "shell_repo_marked",
|
|
904
|
+
result: "skip",
|
|
905
|
+
reason: "repo_not_bound_from_shell_cwd",
|
|
906
|
+
toolName
|
|
907
|
+
});
|
|
638
908
|
}
|
|
639
909
|
}
|
|
640
910
|
if (toolSucceeded && (isStructuredLocalWriteToolName(toolName) || isStructuredLocalReadToolName(toolName))) {
|
|
641
|
-
const
|
|
911
|
+
const touchedPaths = extractToolPathTargets(payload, toolName);
|
|
912
|
+
const touchedRepos = await resolveTouchedBoundReposFromPaths(touchedPaths);
|
|
913
|
+
await appendHookDiagnosticsEvent({
|
|
914
|
+
hook: "PostToolUse",
|
|
915
|
+
sessionId,
|
|
916
|
+
stage: "path_targets_resolved",
|
|
917
|
+
result: touchedRepos.length > 0 ? "info" : "skip",
|
|
918
|
+
reason: touchedRepos.length > 0 ? null : "no_bound_repos_from_paths",
|
|
919
|
+
toolName,
|
|
920
|
+
fields: {
|
|
921
|
+
touchedPathCount: touchedPaths.length,
|
|
922
|
+
boundRepoCount: touchedRepos.length
|
|
923
|
+
}
|
|
924
|
+
});
|
|
642
925
|
for (const repo of touchedRepos) {
|
|
643
926
|
await upsertTouchedRepo(sessionId, {
|
|
644
927
|
repoRoot: repo.repoRoot,
|
|
@@ -651,13 +934,46 @@ async function main() {
|
|
|
651
934
|
if (isStructuredLocalWriteToolName(toolName)) {
|
|
652
935
|
await markTouchedRepoObservedWrite(sessionId, repo.repoRoot, { toolName });
|
|
653
936
|
}
|
|
937
|
+
await appendHookDiagnosticsEvent({
|
|
938
|
+
hook: "PostToolUse",
|
|
939
|
+
sessionId,
|
|
940
|
+
stage: "path_repo_marked",
|
|
941
|
+
result: "success",
|
|
942
|
+
toolName,
|
|
943
|
+
repoRoot: repo.repoRoot,
|
|
944
|
+
fields: {
|
|
945
|
+
hasObservedWrite: isStructuredLocalWriteToolName(toolName)
|
|
946
|
+
}
|
|
947
|
+
});
|
|
654
948
|
}
|
|
655
949
|
}
|
|
950
|
+
await appendHookDiagnosticsEvent({
|
|
951
|
+
hook: "PostToolUse",
|
|
952
|
+
sessionId,
|
|
953
|
+
stage: "completed",
|
|
954
|
+
result: "success",
|
|
955
|
+
toolName
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
async function main() {
|
|
959
|
+
const payload = await readJsonStdin();
|
|
960
|
+
await runHookPostCollab(payload);
|
|
656
961
|
}
|
|
657
962
|
main().catch((error) => {
|
|
658
963
|
const message = error instanceof Error ? error.message : String(error);
|
|
964
|
+
void appendHookDiagnosticsEvent({
|
|
965
|
+
hook: "PostToolUse",
|
|
966
|
+
stage: "unhandled_error",
|
|
967
|
+
result: "error",
|
|
968
|
+
reason: "exception",
|
|
969
|
+
message
|
|
970
|
+
});
|
|
659
971
|
process.stderr.write(`${message}
|
|
660
972
|
`);
|
|
661
973
|
process.exitCode = 0;
|
|
662
974
|
});
|
|
975
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
976
|
+
0 && (module.exports = {
|
|
977
|
+
runHookPostCollab
|
|
978
|
+
});
|
|
663
979
|
//# sourceMappingURL=hook-post-collab.cjs.map
|