@remixhq/claude-plugin 0.1.11 → 0.1.12

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "remix",
3
3
  "description": "Remix collaboration workflows for Claude Code",
4
- "version": "0.1.11",
4
+ "version": "0.1.12",
5
5
  "author": {
6
6
  "name": "Remix"
7
7
  },
@@ -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
- return import_node_path.default.join(import_node_os.default.tmpdir(), "remix-claude-plugin-hooks");
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.12",
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.8",
386
+ "@remixhq/mcp": "^0.1.8"
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 import_promises3 = __toESM(require("fs/promises"), 1);
333
- var import_node_path2 = __toESM(require("path"), 1);
472
+ var import_promises4 = __toESM(require("fs/promises"), 1);
473
+ var import_node_path3 = __toESM(require("path"), 1);
334
474
 
335
475
  // node_modules/@remixhq/core/dist/chunk-FAZUMWBS.js
336
- var import_promises2 = __toESM(require("fs/promises"), 1);
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 import_promises2.default.readFile(getCollabBindingPath(repoRoot), "utf8");
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;
@@ -462,7 +602,7 @@ function collectPathTargetsFromObject(input, keys) {
462
602
  return keys.flatMap((key) => collectStringPathValue(input[key]));
463
603
  }
464
604
  function resolveCandidatePath(targetPath, baseDir) {
465
- return import_node_path2.default.isAbsolute(targetPath) ? import_node_path2.default.normalize(targetPath) : import_node_path2.default.resolve(baseDir, targetPath);
605
+ return import_node_path3.default.isAbsolute(targetPath) ? import_node_path3.default.normalize(targetPath) : import_node_path3.default.resolve(baseDir, targetPath);
466
606
  }
467
607
  function extractToolPathTargets(payload, toolName) {
468
608
  const name = (toolName ?? extractToolName(payload) ?? "").trim().toLowerCase();
@@ -474,16 +614,16 @@ function extractToolPathTargets(payload, toolName) {
474
614
  }
475
615
  async function findBoundRepo(startPath) {
476
616
  if (!startPath) return null;
477
- let current = import_node_path2.default.resolve(startPath);
478
- let stats = await import_promises3.default.stat(current).catch(() => null);
617
+ let current = import_node_path3.default.resolve(startPath);
618
+ let stats = await import_promises4.default.stat(current).catch(() => null);
479
619
  if (stats?.isFile()) {
480
- current = import_node_path2.default.dirname(current);
620
+ current = import_node_path3.default.dirname(current);
481
621
  }
482
622
  while (true) {
483
- const bindingPath = import_node_path2.default.join(current, ".remix", "config.json");
484
- const bindingStats = await import_promises3.default.stat(bindingPath).catch(() => null);
623
+ const bindingPath = import_node_path3.default.join(current, ".remix", "config.json");
624
+ const bindingStats = await import_promises4.default.stat(bindingPath).catch(() => null);
485
625
  if (bindingStats?.isFile()) return current;
486
- const parent = import_node_path2.default.dirname(current);
626
+ const parent = import_node_path3.default.dirname(current);
487
627
  if (parent === current) return null;
488
628
  current = parent;
489
629
  }
@@ -580,20 +720,69 @@ function isLikelyMutatingShellCommand(command) {
580
720
  }
581
721
  return false;
582
722
  }
583
- async function main() {
584
- const payload = await readJsonStdin();
723
+ async function runHookPostCollab(payload) {
585
724
  const sessionId = typeof payload.session_id === "string" && payload.session_id.trim() ? payload.session_id.trim() : null;
586
725
  const toolName = normalizeHookToolName(extractToolName(payload));
726
+ await appendHookDiagnosticsEvent({
727
+ hook: "PostToolUse",
728
+ sessionId,
729
+ stage: "payload_received",
730
+ result: "start",
731
+ toolName,
732
+ fields: {
733
+ hasSessionId: Boolean(sessionId),
734
+ hasToolName: Boolean(toolName)
735
+ }
736
+ });
587
737
  if (!sessionId || !toolName) {
738
+ await appendHookDiagnosticsEvent({
739
+ hook: "PostToolUse",
740
+ sessionId,
741
+ stage: "payload_validation",
742
+ result: "skip",
743
+ reason: !sessionId ? "missing_session_id" : "missing_tool_name",
744
+ toolName
745
+ });
588
746
  return;
589
747
  }
590
748
  const toolSucceeded = didToolSucceed(payload);
591
749
  const remoteChangeRecordedButSyncFailed = isRemoteChangeRecordingToolName(toolName) && isRemoteChangeRecordedButLocalSyncFailed(payload);
750
+ await appendHookDiagnosticsEvent({
751
+ hook: "PostToolUse",
752
+ sessionId,
753
+ stage: "tool_classified",
754
+ result: "info",
755
+ toolName,
756
+ fields: {
757
+ toolSucceeded,
758
+ remoteChangeRecordedButSyncFailed,
759
+ isMemoryTool: isMemoryToolName(toolName),
760
+ isRepoMutationTool: isRepoMutationToolName(toolName),
761
+ isShellTool: isShellToolName(toolName),
762
+ isStructuredLocalWriteTool: isStructuredLocalWriteToolName(toolName),
763
+ isStructuredLocalReadTool: isStructuredLocalReadToolName(toolName)
764
+ }
765
+ });
592
766
  if (!toolSucceeded && !remoteChangeRecordedButSyncFailed) {
767
+ await appendHookDiagnosticsEvent({
768
+ hook: "PostToolUse",
769
+ sessionId,
770
+ stage: "tool_result_gate",
771
+ result: "skip",
772
+ reason: "tool_failed_or_skipped",
773
+ toolName
774
+ });
593
775
  return;
594
776
  }
595
777
  if (toolSucceeded && isMemoryToolName(toolName)) {
596
778
  await markPendingTurnConsultedMemory(sessionId);
779
+ await appendHookDiagnosticsEvent({
780
+ hook: "PostToolUse",
781
+ sessionId,
782
+ stage: "memory_marked_consulted",
783
+ result: "success",
784
+ toolName
785
+ });
597
786
  }
598
787
  const manualRecordingScope = getManualRecordingScope(toolName);
599
788
  if (isRepoMutationToolName(toolName) || manualRecordingScope) {
@@ -617,6 +806,28 @@ async function main() {
617
806
  remoteChangeRecorded: toolSucceeded ? isRemoteChangeRecordingToolName(toolName) : remoteChangeRecordedButSyncFailed
618
807
  });
619
808
  }
809
+ await appendHookDiagnosticsEvent({
810
+ hook: "PostToolUse",
811
+ sessionId,
812
+ stage: "repo_marked_from_tool_cwd",
813
+ result: "success",
814
+ toolName,
815
+ repoRoot: targetRepo.repoRoot,
816
+ fields: {
817
+ hasObservedWrite: isRepoMutationToolName(toolName),
818
+ manualRecordingScope,
819
+ remoteChangeRecordedButSyncFailed
820
+ }
821
+ });
822
+ } else {
823
+ await appendHookDiagnosticsEvent({
824
+ hook: "PostToolUse",
825
+ sessionId,
826
+ stage: "repo_marked_from_tool_cwd",
827
+ result: "skip",
828
+ reason: "repo_not_bound_from_tool_cwd",
829
+ toolName
830
+ });
620
831
  }
621
832
  }
622
833
  if (toolSucceeded && isShellToolName(toolName)) {
@@ -635,10 +846,44 @@ async function main() {
635
846
  if (hasObservedWrite) {
636
847
  await markTouchedRepoObservedWrite(sessionId, targetRepo.repoRoot, { toolName });
637
848
  }
849
+ await appendHookDiagnosticsEvent({
850
+ hook: "PostToolUse",
851
+ sessionId,
852
+ stage: "shell_repo_marked",
853
+ result: "success",
854
+ toolName,
855
+ repoRoot: targetRepo.repoRoot,
856
+ fields: {
857
+ hasObservedWrite,
858
+ bashCommandLength: bashCommand?.length ?? 0
859
+ }
860
+ });
861
+ } else {
862
+ await appendHookDiagnosticsEvent({
863
+ hook: "PostToolUse",
864
+ sessionId,
865
+ stage: "shell_repo_marked",
866
+ result: "skip",
867
+ reason: "repo_not_bound_from_shell_cwd",
868
+ toolName
869
+ });
638
870
  }
639
871
  }
640
872
  if (toolSucceeded && (isStructuredLocalWriteToolName(toolName) || isStructuredLocalReadToolName(toolName))) {
641
- const touchedRepos = await resolveTouchedBoundReposFromPaths(extractToolPathTargets(payload, toolName));
873
+ const touchedPaths = extractToolPathTargets(payload, toolName);
874
+ const touchedRepos = await resolveTouchedBoundReposFromPaths(touchedPaths);
875
+ await appendHookDiagnosticsEvent({
876
+ hook: "PostToolUse",
877
+ sessionId,
878
+ stage: "path_targets_resolved",
879
+ result: touchedRepos.length > 0 ? "info" : "skip",
880
+ reason: touchedRepos.length > 0 ? null : "no_bound_repos_from_paths",
881
+ toolName,
882
+ fields: {
883
+ touchedPathCount: touchedPaths.length,
884
+ boundRepoCount: touchedRepos.length
885
+ }
886
+ });
642
887
  for (const repo of touchedRepos) {
643
888
  await upsertTouchedRepo(sessionId, {
644
889
  repoRoot: repo.repoRoot,
@@ -651,13 +896,46 @@ async function main() {
651
896
  if (isStructuredLocalWriteToolName(toolName)) {
652
897
  await markTouchedRepoObservedWrite(sessionId, repo.repoRoot, { toolName });
653
898
  }
899
+ await appendHookDiagnosticsEvent({
900
+ hook: "PostToolUse",
901
+ sessionId,
902
+ stage: "path_repo_marked",
903
+ result: "success",
904
+ toolName,
905
+ repoRoot: repo.repoRoot,
906
+ fields: {
907
+ hasObservedWrite: isStructuredLocalWriteToolName(toolName)
908
+ }
909
+ });
654
910
  }
655
911
  }
912
+ await appendHookDiagnosticsEvent({
913
+ hook: "PostToolUse",
914
+ sessionId,
915
+ stage: "completed",
916
+ result: "success",
917
+ toolName
918
+ });
919
+ }
920
+ async function main() {
921
+ const payload = await readJsonStdin();
922
+ await runHookPostCollab(payload);
656
923
  }
657
924
  main().catch((error) => {
658
925
  const message = error instanceof Error ? error.message : String(error);
926
+ void appendHookDiagnosticsEvent({
927
+ hook: "PostToolUse",
928
+ stage: "unhandled_error",
929
+ result: "error",
930
+ reason: "exception",
931
+ message
932
+ });
659
933
  process.stderr.write(`${message}
660
934
  `);
661
935
  process.exitCode = 0;
662
936
  });
937
+ // Annotate the CommonJS export names for ESM import in node:
938
+ 0 && (module.exports = {
939
+ runHookPostCollab
940
+ });
663
941
  //# sourceMappingURL=hook-post-collab.cjs.map