@remixhq/claude-plugin 0.1.10 → 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.
@@ -29,6 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
29
29
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
30
  mod
31
31
  ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
33
 
33
34
  // node_modules/isexe/windows.js
34
35
  var require_windows = __commonJS({
@@ -36,8 +37,8 @@ var require_windows = __commonJS({
36
37
  "use strict";
37
38
  module2.exports = isexe;
38
39
  isexe.sync = sync;
39
- var fs8 = require("fs");
40
- function checkPathExt(path12, options) {
40
+ var fs9 = require("fs");
41
+ function checkPathExt(path13, options) {
41
42
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
42
43
  if (!pathext) {
43
44
  return true;
@@ -48,25 +49,25 @@ var require_windows = __commonJS({
48
49
  }
49
50
  for (var i2 = 0; i2 < pathext.length; i2++) {
50
51
  var p = pathext[i2].toLowerCase();
51
- if (p && path12.substr(-p.length).toLowerCase() === p) {
52
+ if (p && path13.substr(-p.length).toLowerCase() === p) {
52
53
  return true;
53
54
  }
54
55
  }
55
56
  return false;
56
57
  }
57
- function checkStat(stat, path12, options) {
58
+ function checkStat(stat, path13, options) {
58
59
  if (!stat.isSymbolicLink() && !stat.isFile()) {
59
60
  return false;
60
61
  }
61
- return checkPathExt(path12, options);
62
+ return checkPathExt(path13, options);
62
63
  }
63
- function isexe(path12, options, cb) {
64
- fs8.stat(path12, function(er, stat) {
65
- cb(er, er ? false : checkStat(stat, path12, options));
64
+ function isexe(path13, options, cb) {
65
+ fs9.stat(path13, function(er, stat) {
66
+ cb(er, er ? false : checkStat(stat, path13, options));
66
67
  });
67
68
  }
68
- function sync(path12, options) {
69
- return checkStat(fs8.statSync(path12), path12, options);
69
+ function sync(path13, options) {
70
+ return checkStat(fs9.statSync(path13), path13, options);
70
71
  }
71
72
  }
72
73
  });
@@ -77,14 +78,14 @@ var require_mode = __commonJS({
77
78
  "use strict";
78
79
  module2.exports = isexe;
79
80
  isexe.sync = sync;
80
- var fs8 = require("fs");
81
- function isexe(path12, options, cb) {
82
- fs8.stat(path12, function(er, stat) {
81
+ var fs9 = require("fs");
82
+ function isexe(path13, options, cb) {
83
+ fs9.stat(path13, function(er, stat) {
83
84
  cb(er, er ? false : checkStat(stat, options));
84
85
  });
85
86
  }
86
- function sync(path12, options) {
87
- return checkStat(fs8.statSync(path12), options);
87
+ function sync(path13, options) {
88
+ return checkStat(fs9.statSync(path13), options);
88
89
  }
89
90
  function checkStat(stat, options) {
90
91
  return stat.isFile() && checkMode(stat, options);
@@ -109,7 +110,7 @@ var require_mode = __commonJS({
109
110
  var require_isexe = __commonJS({
110
111
  "node_modules/isexe/index.js"(exports2, module2) {
111
112
  "use strict";
112
- var fs8 = require("fs");
113
+ var fs9 = require("fs");
113
114
  var core;
114
115
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
115
116
  core = require_windows();
@@ -118,7 +119,7 @@ var require_isexe = __commonJS({
118
119
  }
119
120
  module2.exports = isexe;
120
121
  isexe.sync = sync;
121
- function isexe(path12, options, cb) {
122
+ function isexe(path13, options, cb) {
122
123
  if (typeof options === "function") {
123
124
  cb = options;
124
125
  options = {};
@@ -128,7 +129,7 @@ var require_isexe = __commonJS({
128
129
  throw new TypeError("callback not provided");
129
130
  }
130
131
  return new Promise(function(resolve, reject) {
131
- isexe(path12, options || {}, function(er, is) {
132
+ isexe(path13, options || {}, function(er, is) {
132
133
  if (er) {
133
134
  reject(er);
134
135
  } else {
@@ -137,7 +138,7 @@ var require_isexe = __commonJS({
137
138
  });
138
139
  });
139
140
  }
140
- core(path12, options || {}, function(er, is) {
141
+ core(path13, options || {}, function(er, is) {
141
142
  if (er) {
142
143
  if (er.code === "EACCES" || options && options.ignoreErrors) {
143
144
  er = null;
@@ -147,9 +148,9 @@ var require_isexe = __commonJS({
147
148
  cb(er, is);
148
149
  });
149
150
  }
150
- function sync(path12, options) {
151
+ function sync(path13, options) {
151
152
  try {
152
- return core.sync(path12, options || {});
153
+ return core.sync(path13, options || {});
153
154
  } catch (er) {
154
155
  if (options && options.ignoreErrors || er.code === "EACCES") {
155
156
  return false;
@@ -166,7 +167,7 @@ var require_which = __commonJS({
166
167
  "node_modules/which/which.js"(exports2, module2) {
167
168
  "use strict";
168
169
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
169
- var path12 = require("path");
170
+ var path13 = require("path");
170
171
  var COLON = isWindows ? ";" : ":";
171
172
  var isexe = require_isexe();
172
173
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -204,7 +205,7 @@ var require_which = __commonJS({
204
205
  return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
205
206
  const ppRaw = pathEnv[i2];
206
207
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
207
- const pCmd = path12.join(pathPart, cmd);
208
+ const pCmd = path13.join(pathPart, cmd);
208
209
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
209
210
  resolve(subStep(p, i2, 0));
210
211
  });
@@ -231,7 +232,7 @@ var require_which = __commonJS({
231
232
  for (let i2 = 0; i2 < pathEnv.length; i2++) {
232
233
  const ppRaw = pathEnv[i2];
233
234
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
234
- const pCmd = path12.join(pathPart, cmd);
235
+ const pCmd = path13.join(pathPart, cmd);
235
236
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
236
237
  for (let j = 0; j < pathExt.length; j++) {
237
238
  const cur = p + pathExt[j];
@@ -279,7 +280,7 @@ var require_path_key = __commonJS({
279
280
  var require_resolveCommand = __commonJS({
280
281
  "node_modules/cross-spawn/lib/util/resolveCommand.js"(exports2, module2) {
281
282
  "use strict";
282
- var path12 = require("path");
283
+ var path13 = require("path");
283
284
  var which = require_which();
284
285
  var getPathKey = require_path_key();
285
286
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -297,7 +298,7 @@ var require_resolveCommand = __commonJS({
297
298
  try {
298
299
  resolved = which.sync(parsed.command, {
299
300
  path: env[getPathKey({ env })],
300
- pathExt: withoutPathExt ? path12.delimiter : void 0
301
+ pathExt: withoutPathExt ? path13.delimiter : void 0
301
302
  });
302
303
  } catch (e) {
303
304
  } finally {
@@ -306,7 +307,7 @@ var require_resolveCommand = __commonJS({
306
307
  }
307
308
  }
308
309
  if (resolved) {
309
- resolved = path12.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
310
+ resolved = path13.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
310
311
  }
311
312
  return resolved;
312
313
  }
@@ -360,8 +361,8 @@ var require_shebang_command = __commonJS({
360
361
  if (!match) {
361
362
  return null;
362
363
  }
363
- const [path12, argument] = match[0].replace(/#! ?/, "").split(" ");
364
- const binary = path12.split("/").pop();
364
+ const [path13, argument] = match[0].replace(/#! ?/, "").split(" ");
365
+ const binary = path13.split("/").pop();
365
366
  if (binary === "env") {
366
367
  return argument;
367
368
  }
@@ -374,16 +375,16 @@ var require_shebang_command = __commonJS({
374
375
  var require_readShebang = __commonJS({
375
376
  "node_modules/cross-spawn/lib/util/readShebang.js"(exports2, module2) {
376
377
  "use strict";
377
- var fs8 = require("fs");
378
+ var fs9 = require("fs");
378
379
  var shebangCommand = require_shebang_command();
379
380
  function readShebang(command) {
380
381
  const size = 150;
381
382
  const buffer = Buffer.alloc(size);
382
383
  let fd;
383
384
  try {
384
- fd = fs8.openSync(command, "r");
385
- fs8.readSync(fd, buffer, 0, size, 0);
386
- fs8.closeSync(fd);
385
+ fd = fs9.openSync(command, "r");
386
+ fs9.readSync(fd, buffer, 0, size, 0);
387
+ fs9.closeSync(fd);
387
388
  } catch (e) {
388
389
  }
389
390
  return shebangCommand(buffer.toString());
@@ -396,7 +397,7 @@ var require_readShebang = __commonJS({
396
397
  var require_parse = __commonJS({
397
398
  "node_modules/cross-spawn/lib/parse.js"(exports2, module2) {
398
399
  "use strict";
399
- var path12 = require("path");
400
+ var path13 = require("path");
400
401
  var resolveCommand = require_resolveCommand();
401
402
  var escape = require_escape();
402
403
  var readShebang = require_readShebang();
@@ -421,7 +422,7 @@ var require_parse = __commonJS({
421
422
  const needsShell = !isExecutableRegExp.test(commandFile);
422
423
  if (parsed.options.forceShell || needsShell) {
423
424
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
424
- parsed.command = path12.normalize(parsed.command);
425
+ parsed.command = path13.normalize(parsed.command);
425
426
  parsed.command = escape.command(parsed.command);
426
427
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
427
428
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -531,6 +532,13 @@ var require_cross_spawn = __commonJS({
531
532
  }
532
533
  });
533
534
 
535
+ // src/hook-stop-collab.ts
536
+ var hook_stop_collab_exports = {};
537
+ __export(hook_stop_collab_exports, {
538
+ runHookStopCollab: () => runHookStopCollab
539
+ });
540
+ module.exports = __toCommonJS(hook_stop_collab_exports);
541
+
534
542
  // node_modules/@remixhq/core/dist/chunk-YZ34ICNN.js
535
543
  var RemixError = class extends Error {
536
544
  code;
@@ -582,7 +590,7 @@ var REMIX_ERROR_CODES = {
582
590
  PREFERRED_BRANCH_MISMATCH: "PREFERRED_BRANCH_MISMATCH"
583
591
  };
584
592
 
585
- // node_modules/@remixhq/core/dist/chunk-UGKPOCN5.js
593
+ // node_modules/@remixhq/core/dist/chunk-RREREIGW.js
586
594
  var import_promises13 = __toESM(require("fs/promises"), 1);
587
595
  var import_crypto = require("crypto");
588
596
  var import_os = __toESM(require("os"), 1);
@@ -4969,13 +4977,13 @@ var logOutputSync = ({ serializedResult, fdNumber, state, verboseInfo, encoding,
4969
4977
  }
4970
4978
  };
4971
4979
  var writeToFiles = (serializedResult, stdioItems, outputFiles) => {
4972
- for (const { path: path12, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
4973
- const pathString = typeof path12 === "string" ? path12 : path12.toString();
4980
+ for (const { path: path13, append } of stdioItems.filter(({ type }) => FILE_TYPES.has(type))) {
4981
+ const pathString = typeof path13 === "string" ? path13 : path13.toString();
4974
4982
  if (append || outputFiles.has(pathString)) {
4975
- (0, import_node_fs4.appendFileSync)(path12, serializedResult);
4983
+ (0, import_node_fs4.appendFileSync)(path13, serializedResult);
4976
4984
  } else {
4977
4985
  outputFiles.add(pathString);
4978
- (0, import_node_fs4.writeFileSync)(path12, serializedResult);
4986
+ (0, import_node_fs4.writeFileSync)(path13, serializedResult);
4979
4987
  }
4980
4988
  }
4981
4989
  };
@@ -7363,7 +7371,7 @@ var {
7363
7371
  getCancelSignal: getCancelSignal2
7364
7372
  } = getIpcExport();
7365
7373
 
7366
- // node_modules/@remixhq/core/dist/chunk-UGKPOCN5.js
7374
+ // node_modules/@remixhq/core/dist/chunk-RREREIGW.js
7367
7375
  async function runGit(args, cwd) {
7368
7376
  const res = await execa("git", args, { cwd, stderr: "ignore" });
7369
7377
  return String(res.stdout || "").trim();
@@ -8859,7 +8867,7 @@ function createApiClient(config, opts) {
8859
8867
  const apiKey = (opts?.apiKey ?? "").trim();
8860
8868
  const tokenProvider = opts?.tokenProvider;
8861
8869
  const CLIENT_KEY_HEADER = "x-comerge-api-key";
8862
- async function request(path12, init) {
8870
+ async function request(path13, init) {
8863
8871
  if (!tokenProvider) {
8864
8872
  throw new RemixError("API client is missing a token provider.", {
8865
8873
  exitCode: 1,
@@ -8867,7 +8875,7 @@ function createApiClient(config, opts) {
8867
8875
  });
8868
8876
  }
8869
8877
  const auth = await tokenProvider();
8870
- const url = new URL(path12, config.apiUrl).toString();
8878
+ const url = new URL(path13, config.apiUrl).toString();
8871
8879
  const doFetch = async (bearer) => fetch(url, {
8872
8880
  ...init,
8873
8881
  headers: {
@@ -8891,7 +8899,7 @@ function createApiClient(config, opts) {
8891
8899
  const json = await readJsonSafe(res);
8892
8900
  return json ?? null;
8893
8901
  }
8894
- async function requestBinary(path12, init) {
8902
+ async function requestBinary(path13, init) {
8895
8903
  if (!tokenProvider) {
8896
8904
  throw new RemixError("API client is missing a token provider.", {
8897
8905
  exitCode: 1,
@@ -8899,7 +8907,7 @@ function createApiClient(config, opts) {
8899
8907
  });
8900
8908
  }
8901
8909
  const auth = await tokenProvider();
8902
- const url = new URL(path12, config.apiUrl).toString();
8910
+ const url = new URL(path13, config.apiUrl).toString();
8903
8911
  const doFetch = async (bearer) => fetch(url, {
8904
8912
  ...init,
8905
8913
  headers: {
@@ -9619,8 +9627,8 @@ function getErrorMap() {
9619
9627
 
9620
9628
  // node_modules/zod/v3/helpers/parseUtil.js
9621
9629
  var makeIssue = (params) => {
9622
- const { data, path: path12, errorMaps, issueData } = params;
9623
- const fullPath = [...path12, ...issueData.path || []];
9630
+ const { data, path: path13, errorMaps, issueData } = params;
9631
+ const fullPath = [...path13, ...issueData.path || []];
9624
9632
  const fullIssue = {
9625
9633
  ...issueData,
9626
9634
  path: fullPath
@@ -9736,11 +9744,11 @@ var errorUtil;
9736
9744
 
9737
9745
  // node_modules/zod/v3/types.js
9738
9746
  var ParseInputLazyPath = class {
9739
- constructor(parent, value, path12, key) {
9747
+ constructor(parent, value, path13, key) {
9740
9748
  this._cachedPath = [];
9741
9749
  this.parent = parent;
9742
9750
  this.data = value;
9743
- this._path = path12;
9751
+ this._path = path13;
9744
9752
  this._key = key;
9745
9753
  }
9746
9754
  get path() {
@@ -22073,8 +22081,8 @@ var IcebergError = class extends Error {
22073
22081
  return this.status === 419;
22074
22082
  }
22075
22083
  };
22076
- function buildUrl(baseUrl, path12, query) {
22077
- const url = new URL(path12, baseUrl);
22084
+ function buildUrl(baseUrl, path13, query) {
22085
+ const url = new URL(path13, baseUrl);
22078
22086
  if (query) {
22079
22087
  for (const [key, value] of Object.entries(query)) {
22080
22088
  if (value !== void 0) {
@@ -22104,12 +22112,12 @@ function createFetchClient(options) {
22104
22112
  return {
22105
22113
  async request({
22106
22114
  method,
22107
- path: path12,
22115
+ path: path13,
22108
22116
  query,
22109
22117
  body,
22110
22118
  headers
22111
22119
  }) {
22112
- const url = buildUrl(options.baseUrl, path12, query);
22120
+ const url = buildUrl(options.baseUrl, path13, query);
22113
22121
  const authHeaders = await buildAuthHeaders(options.auth);
22114
22122
  const res = await fetchFn(url, {
22115
22123
  method,
@@ -22928,7 +22936,7 @@ var StorageFileApi = class extends BaseApiClient {
22928
22936
  * @param path The relative file path. Should be of the format `folder/subfolder/filename.png`. The bucket must already exist before attempting to upload.
22929
22937
  * @param fileBody The body of the file to be stored in the bucket.
22930
22938
  */
22931
- async uploadOrUpdate(method, path12, fileBody, fileOptions) {
22939
+ async uploadOrUpdate(method, path13, fileBody, fileOptions) {
22932
22940
  var _this = this;
22933
22941
  return _this.handleOperation(async () => {
22934
22942
  let body;
@@ -22952,7 +22960,7 @@ var StorageFileApi = class extends BaseApiClient {
22952
22960
  if ((typeof ReadableStream !== "undefined" && body instanceof ReadableStream || body && typeof body === "object" && "pipe" in body && typeof body.pipe === "function") && !options.duplex) options.duplex = "half";
22953
22961
  }
22954
22962
  if (fileOptions === null || fileOptions === void 0 ? void 0 : fileOptions.headers) headers = _objectSpread22(_objectSpread22({}, headers), fileOptions.headers);
22955
- const cleanPath = _this._removeEmptyFolders(path12);
22963
+ const cleanPath = _this._removeEmptyFolders(path13);
22956
22964
  const _path = _this._getFinalPath(cleanPath);
22957
22965
  const data = await (method == "PUT" ? put : post)(_this.fetch, `${_this.url}/object/${_path}`, body, _objectSpread22({ headers }, (options === null || options === void 0 ? void 0 : options.duplex) ? { duplex: options.duplex } : {}));
22958
22966
  return {
@@ -23013,8 +23021,8 @@ var StorageFileApi = class extends BaseApiClient {
23013
23021
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23014
23022
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below.
23015
23023
  */
23016
- async upload(path12, fileBody, fileOptions) {
23017
- return this.uploadOrUpdate("POST", path12, fileBody, fileOptions);
23024
+ async upload(path13, fileBody, fileOptions) {
23025
+ return this.uploadOrUpdate("POST", path13, fileBody, fileOptions);
23018
23026
  }
23019
23027
  /**
23020
23028
  * Upload a file with a token generated from `createSignedUploadUrl`.
@@ -23053,9 +23061,9 @@ var StorageFileApi = class extends BaseApiClient {
23053
23061
  * - `objects` table permissions: none
23054
23062
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23055
23063
  */
23056
- async uploadToSignedUrl(path12, token, fileBody, fileOptions) {
23064
+ async uploadToSignedUrl(path13, token, fileBody, fileOptions) {
23057
23065
  var _this3 = this;
23058
- const cleanPath = _this3._removeEmptyFolders(path12);
23066
+ const cleanPath = _this3._removeEmptyFolders(path13);
23059
23067
  const _path = _this3._getFinalPath(cleanPath);
23060
23068
  const url = new URL(_this3.url + `/object/upload/sign/${_path}`);
23061
23069
  url.searchParams.set("token", token);
@@ -23117,10 +23125,10 @@ var StorageFileApi = class extends BaseApiClient {
23117
23125
  * - `objects` table permissions: `insert`
23118
23126
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23119
23127
  */
23120
- async createSignedUploadUrl(path12, options) {
23128
+ async createSignedUploadUrl(path13, options) {
23121
23129
  var _this4 = this;
23122
23130
  return _this4.handleOperation(async () => {
23123
- let _path = _this4._getFinalPath(path12);
23131
+ let _path = _this4._getFinalPath(path13);
23124
23132
  const headers = _objectSpread22({}, _this4.headers);
23125
23133
  if (options === null || options === void 0 ? void 0 : options.upsert) headers["x-upsert"] = "true";
23126
23134
  const data = await post(_this4.fetch, `${_this4.url}/object/upload/sign/${_path}`, {}, { headers });
@@ -23129,7 +23137,7 @@ var StorageFileApi = class extends BaseApiClient {
23129
23137
  if (!token) throw new StorageError("No token returned by API");
23130
23138
  return {
23131
23139
  signedUrl: url.toString(),
23132
- path: path12,
23140
+ path: path13,
23133
23141
  token
23134
23142
  };
23135
23143
  });
@@ -23185,8 +23193,8 @@ var StorageFileApi = class extends BaseApiClient {
23185
23193
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23186
23194
  * - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below.
23187
23195
  */
23188
- async update(path12, fileBody, fileOptions) {
23189
- return this.uploadOrUpdate("PUT", path12, fileBody, fileOptions);
23196
+ async update(path13, fileBody, fileOptions) {
23197
+ return this.uploadOrUpdate("PUT", path13, fileBody, fileOptions);
23190
23198
  }
23191
23199
  /**
23192
23200
  * Moves an existing file to a new path in the same bucket.
@@ -23333,10 +23341,10 @@ var StorageFileApi = class extends BaseApiClient {
23333
23341
  * - `objects` table permissions: `select`
23334
23342
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23335
23343
  */
23336
- async createSignedUrl(path12, expiresIn, options) {
23344
+ async createSignedUrl(path13, expiresIn, options) {
23337
23345
  var _this8 = this;
23338
23346
  return _this8.handleOperation(async () => {
23339
- let _path = _this8._getFinalPath(path12);
23347
+ let _path = _this8._getFinalPath(path13);
23340
23348
  const hasTransform = typeof (options === null || options === void 0 ? void 0 : options.transform) === "object" && options.transform !== null && Object.keys(options.transform).length > 0;
23341
23349
  let data = await post(_this8.fetch, `${_this8.url}/object/sign/${_path}`, _objectSpread22({ expiresIn }, hasTransform ? { transform: options.transform } : {}), { headers: _this8.headers });
23342
23350
  const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `&download=${options.download === true ? "" : options.download}` : "";
@@ -23463,11 +23471,11 @@ var StorageFileApi = class extends BaseApiClient {
23463
23471
  * - `objects` table permissions: `select`
23464
23472
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23465
23473
  */
23466
- download(path12, options, parameters) {
23474
+ download(path13, options, parameters) {
23467
23475
  const renderPath = typeof (options === null || options === void 0 ? void 0 : options.transform) !== "undefined" ? "render/image/authenticated" : "object";
23468
23476
  const transformationQuery = this.transformOptsToQueryString((options === null || options === void 0 ? void 0 : options.transform) || {});
23469
23477
  const queryString = transformationQuery ? `?${transformationQuery}` : "";
23470
- const _path = this._getFinalPath(path12);
23478
+ const _path = this._getFinalPath(path13);
23471
23479
  const downloadFn = () => get(this.fetch, `${this.url}/${renderPath}/${_path}${queryString}`, {
23472
23480
  headers: this.headers,
23473
23481
  noResolveJson: true
@@ -23497,9 +23505,9 @@ var StorageFileApi = class extends BaseApiClient {
23497
23505
  * }
23498
23506
  * ```
23499
23507
  */
23500
- async info(path12) {
23508
+ async info(path13) {
23501
23509
  var _this10 = this;
23502
- const _path = _this10._getFinalPath(path12);
23510
+ const _path = _this10._getFinalPath(path13);
23503
23511
  return _this10.handleOperation(async () => {
23504
23512
  return recursiveToCamel(await get(_this10.fetch, `${_this10.url}/object/info/${_path}`, { headers: _this10.headers }));
23505
23513
  });
@@ -23519,9 +23527,9 @@ var StorageFileApi = class extends BaseApiClient {
23519
23527
  * .exists('folder/avatar1.png')
23520
23528
  * ```
23521
23529
  */
23522
- async exists(path12) {
23530
+ async exists(path13) {
23523
23531
  var _this11 = this;
23524
- const _path = _this11._getFinalPath(path12);
23532
+ const _path = _this11._getFinalPath(path13);
23525
23533
  try {
23526
23534
  await head(_this11.fetch, `${_this11.url}/object/${_path}`, { headers: _this11.headers });
23527
23535
  return {
@@ -23598,8 +23606,8 @@ var StorageFileApi = class extends BaseApiClient {
23598
23606
  * - `objects` table permissions: none
23599
23607
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23600
23608
  */
23601
- getPublicUrl(path12, options) {
23602
- const _path = this._getFinalPath(path12);
23609
+ getPublicUrl(path13, options) {
23610
+ const _path = this._getFinalPath(path13);
23603
23611
  const _queryString = [];
23604
23612
  const downloadQueryParam = (options === null || options === void 0 ? void 0 : options.download) ? `download=${options.download === true ? "" : options.download}` : "";
23605
23613
  if (downloadQueryParam !== "") _queryString.push(downloadQueryParam);
@@ -23738,10 +23746,10 @@ var StorageFileApi = class extends BaseApiClient {
23738
23746
  * - `objects` table permissions: `select`
23739
23747
  * - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works
23740
23748
  */
23741
- async list(path12, options, parameters) {
23749
+ async list(path13, options, parameters) {
23742
23750
  var _this13 = this;
23743
23751
  return _this13.handleOperation(async () => {
23744
- const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path12 || "" });
23752
+ const body = _objectSpread22(_objectSpread22(_objectSpread22({}, DEFAULT_SEARCH_OPTIONS), options), {}, { prefix: path13 || "" });
23745
23753
  return await post(_this13.fetch, `${_this13.url}/object/list/${_this13.bucketId}`, body, { headers: _this13.headers }, parameters);
23746
23754
  });
23747
23755
  }
@@ -23805,11 +23813,11 @@ var StorageFileApi = class extends BaseApiClient {
23805
23813
  if (typeof Buffer !== "undefined") return Buffer.from(data).toString("base64");
23806
23814
  return btoa(data);
23807
23815
  }
23808
- _getFinalPath(path12) {
23809
- return `${this.bucketId}/${path12.replace(/^\/+/, "")}`;
23816
+ _getFinalPath(path13) {
23817
+ return `${this.bucketId}/${path13.replace(/^\/+/, "")}`;
23810
23818
  }
23811
- _removeEmptyFolders(path12) {
23812
- return path12.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
23819
+ _removeEmptyFolders(path13) {
23820
+ return path13.replace(/^\/|\/$/g, "").replace(/\/+/g, "/");
23813
23821
  }
23814
23822
  transformOptsToQueryString(transform) {
23815
23823
  const params = [];
@@ -33085,13 +33093,20 @@ async function createHookCollabApiClient() {
33085
33093
  });
33086
33094
  }
33087
33095
 
33096
+ // src/hook-diagnostics.ts
33097
+ var import_node_crypto2 = require("crypto");
33098
+ var import_promises19 = __toESM(require("fs/promises"), 1);
33099
+ var import_node_os5 = __toESM(require("os"), 1);
33100
+ var import_node_path7 = __toESM(require("path"), 1);
33101
+
33088
33102
  // src/hook-state.ts
33089
33103
  var import_promises18 = __toESM(require("fs/promises"), 1);
33090
33104
  var import_node_os4 = __toESM(require("os"), 1);
33091
33105
  var import_node_path6 = __toESM(require("path"), 1);
33092
33106
  var import_node_crypto = require("crypto");
33093
33107
  function stateRoot() {
33094
- return import_node_path6.default.join(import_node_os4.default.tmpdir(), "remix-claude-plugin-hooks");
33108
+ const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_STATE_ROOT?.trim();
33109
+ return configured || import_node_path6.default.join(import_node_os4.default.tmpdir(), "remix-claude-plugin-hooks");
33095
33110
  }
33096
33111
  function statePath(sessionId) {
33097
33112
  return import_node_path6.default.join(stateRoot(), `${sessionId}.json`);
@@ -33156,6 +33171,7 @@ async function tryRemoveStaleStateLock(sessionId) {
33156
33171
  async function acquireStateLock(sessionId) {
33157
33172
  const lockPath = stateLockPath(sessionId);
33158
33173
  const deadline = Date.now() + STATE_LOCK_WAIT_MS;
33174
+ await import_promises18.default.mkdir(stateRoot(), { recursive: true });
33159
33175
  while (true) {
33160
33176
  try {
33161
33177
  await import_promises18.default.mkdir(lockPath);
@@ -33404,7 +33420,7 @@ async function clearPendingTurnState(sessionId) {
33404
33420
  // package.json
33405
33421
  var package_default = {
33406
33422
  name: "@remixhq/claude-plugin",
33407
- version: "0.1.10",
33423
+ version: "0.1.12",
33408
33424
  description: "Claude Code plugin for Remix collaboration workflows",
33409
33425
  homepage: "https://github.com/RemixDotOne/remix-claude-plugin",
33410
33426
  license: "MIT",
@@ -33435,8 +33451,8 @@ var package_default = {
33435
33451
  prepack: "npm run build"
33436
33452
  },
33437
33453
  dependencies: {
33438
- "@remixhq/core": "^0.1.7",
33439
- "@remixhq/mcp": "^0.1.7"
33454
+ "@remixhq/core": "^0.1.8",
33455
+ "@remixhq/mcp": "^0.1.8"
33440
33456
  },
33441
33457
  devDependencies: {
33442
33458
  "@types/node": "^25.4.0",
@@ -33455,9 +33471,90 @@ var pluginMetadata = {
33455
33471
  agentName: "remix-collab"
33456
33472
  };
33457
33473
 
33474
+ // src/hook-diagnostics.ts
33475
+ var MAX_LOG_BYTES = 512 * 1024;
33476
+ function resolveClaudeRoot() {
33477
+ const configured = process.env.CLAUDE_CONFIG_DIR?.trim();
33478
+ return configured || import_node_path7.default.join(import_node_os5.default.homedir(), ".claude");
33479
+ }
33480
+ function resolvePluginDataDirName() {
33481
+ return `${pluginMetadata.pluginId}-${pluginMetadata.pluginId}`;
33482
+ }
33483
+ function getHookDiagnosticsDirPath() {
33484
+ const configured = process.env.REMIX_CLAUDE_PLUGIN_HOOK_DIAGNOSTICS_DIR?.trim();
33485
+ return configured || import_node_path7.default.join(resolveClaudeRoot(), "plugins", "data", resolvePluginDataDirName());
33486
+ }
33487
+ function getHookDiagnosticsLogPath() {
33488
+ return import_node_path7.default.join(getHookDiagnosticsDirPath(), "hooks.ndjson");
33489
+ }
33490
+ function toFieldValue(value) {
33491
+ if (value === null) return null;
33492
+ if (typeof value === "string") return value;
33493
+ if (typeof value === "number" && Number.isFinite(value)) return value;
33494
+ if (typeof value === "boolean") return value;
33495
+ return void 0;
33496
+ }
33497
+ function normalizeFields(fields) {
33498
+ if (!fields) return {};
33499
+ const normalizedEntries = Object.entries(fields).map(([key, value]) => {
33500
+ const normalized = toFieldValue(value);
33501
+ return normalized === void 0 ? null : [key, normalized];
33502
+ }).filter((entry) => entry !== null);
33503
+ return Object.fromEntries(normalizedEntries);
33504
+ }
33505
+ async function rotateLogIfNeeded(logPath) {
33506
+ const stat = await import_promises19.default.stat(logPath).catch(() => null);
33507
+ if (!stat || stat.size < MAX_LOG_BYTES) {
33508
+ return;
33509
+ }
33510
+ const rotatedPath = `${logPath}.1`;
33511
+ await import_promises19.default.rm(rotatedPath, { force: true }).catch(() => void 0);
33512
+ await import_promises19.default.rename(logPath, rotatedPath).catch(() => void 0);
33513
+ }
33514
+ function summarizeText(value) {
33515
+ if (typeof value !== "string" || !value.trim()) {
33516
+ return {
33517
+ present: false,
33518
+ length: 0,
33519
+ sha256Prefix: null
33520
+ };
33521
+ }
33522
+ const trimmed = value.trim();
33523
+ return {
33524
+ present: true,
33525
+ length: trimmed.length,
33526
+ sha256Prefix: (0, import_node_crypto2.createHash)("sha256").update(trimmed).digest("hex").slice(0, 12)
33527
+ };
33528
+ }
33529
+ async function appendHookDiagnosticsEvent(params) {
33530
+ try {
33531
+ const logPath = getHookDiagnosticsLogPath();
33532
+ await import_promises19.default.mkdir(import_node_path7.default.dirname(logPath), { recursive: true });
33533
+ await rotateLogIfNeeded(logPath);
33534
+ const event = {
33535
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
33536
+ hook: params.hook,
33537
+ pluginVersion: pluginMetadata.version,
33538
+ pid: process.pid,
33539
+ sessionId: params.sessionId?.trim() || null,
33540
+ turnId: params.turnId?.trim() || null,
33541
+ stage: params.stage.trim(),
33542
+ result: params.result,
33543
+ reason: params.reason?.trim() || null,
33544
+ toolName: params.toolName?.trim() || null,
33545
+ repoRoot: params.repoRoot?.trim() || null,
33546
+ message: params.message?.trim() || null,
33547
+ fields: normalizeFields(params.fields)
33548
+ };
33549
+ await import_promises19.default.appendFile(logPath, `${JSON.stringify(event)}
33550
+ `, "utf8");
33551
+ } catch {
33552
+ }
33553
+ }
33554
+
33458
33555
  // src/hook-utils.ts
33459
- var import_promises19 = __toESM(require("fs/promises"), 1);
33460
- var import_node_path7 = __toESM(require("path"), 1);
33556
+ var import_promises20 = __toESM(require("fs/promises"), 1);
33557
+ var import_node_path8 = __toESM(require("path"), 1);
33461
33558
  async function readJsonStdin() {
33462
33559
  const chunks = [];
33463
33560
  for await (const chunk of process.stdin) {
@@ -33514,16 +33611,16 @@ function extractBoolean(input, keys) {
33514
33611
  }
33515
33612
  async function findBoundRepo(startPath) {
33516
33613
  if (!startPath) return null;
33517
- let current = import_node_path7.default.resolve(startPath);
33518
- let stats = await import_promises19.default.stat(current).catch(() => null);
33614
+ let current = import_node_path8.default.resolve(startPath);
33615
+ let stats = await import_promises20.default.stat(current).catch(() => null);
33519
33616
  if (stats?.isFile()) {
33520
- current = import_node_path7.default.dirname(current);
33617
+ current = import_node_path8.default.dirname(current);
33521
33618
  }
33522
33619
  while (true) {
33523
- const bindingPath = import_node_path7.default.join(current, ".remix", "config.json");
33524
- const bindingStats = await import_promises19.default.stat(bindingPath).catch(() => null);
33620
+ const bindingPath = import_node_path8.default.join(current, ".remix", "config.json");
33621
+ const bindingStats = await import_promises20.default.stat(bindingPath).catch(() => null);
33525
33622
  if (bindingStats?.isFile()) return current;
33526
- const parent = import_node_path7.default.dirname(current);
33623
+ const parent = import_node_path8.default.dirname(current);
33527
33624
  if (parent === current) return null;
33528
33625
  current = parent;
33529
33626
  }
@@ -33650,8 +33747,21 @@ function createFallbackTouchedRepo(params) {
33650
33747
  };
33651
33748
  }
33652
33749
  async function recordTouchedRepo(params) {
33653
- const { sessionId, turnId, repo, prompt, assistantResponse, api } = params;
33750
+ const { hook, sessionId, turnId, repo, prompt, assistantResponse, api } = params;
33654
33751
  await markTouchedRepoStopAttempted(sessionId, repo.repoRoot);
33752
+ await appendHookDiagnosticsEvent({
33753
+ hook,
33754
+ sessionId,
33755
+ turnId,
33756
+ stage: "recording_started",
33757
+ result: "info",
33758
+ repoRoot: repo.repoRoot,
33759
+ fields: {
33760
+ hasObservedWrite: repo.hasObservedWrite,
33761
+ manuallyRecorded: repo.manuallyRecorded,
33762
+ stopAttempted: true
33763
+ }
33764
+ });
33655
33765
  try {
33656
33766
  const binding = await readCollabBinding(repo.repoRoot).catch(() => null);
33657
33767
  if (!binding) {
@@ -33659,12 +33769,42 @@ async function recordTouchedRepo(params) {
33659
33769
  message: "Automatic Remix turn recording failed because the repository is no longer bound to Remix.",
33660
33770
  hint: `Repo root: ${repo.repoRoot}`
33661
33771
  });
33772
+ await appendHookDiagnosticsEvent({
33773
+ hook,
33774
+ sessionId,
33775
+ turnId,
33776
+ stage: "binding_lookup",
33777
+ result: "error",
33778
+ reason: "repo_not_bound",
33779
+ repoRoot: repo.repoRoot
33780
+ });
33662
33781
  return false;
33663
33782
  }
33664
33783
  const workspaceDiff = await getWorkspaceDiff(repo.repoRoot);
33665
33784
  if (workspaceDiff.diff.trim()) {
33785
+ await appendHookDiagnosticsEvent({
33786
+ hook,
33787
+ sessionId,
33788
+ turnId,
33789
+ stage: "workspace_diff_checked",
33790
+ result: "info",
33791
+ repoRoot: repo.repoRoot,
33792
+ fields: {
33793
+ hasWorkspaceDiff: true,
33794
+ diffLength: workspaceDiff.diff.length
33795
+ }
33796
+ });
33666
33797
  if (repo.manualRemoteChangeRecordedAt && !hasNewObservedWriteSince(repo.manualRemoteChangeRecordedAt, repo)) {
33667
33798
  await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "changed_turn" });
33799
+ await appendHookDiagnosticsEvent({
33800
+ hook,
33801
+ sessionId,
33802
+ turnId,
33803
+ stage: "recording_skipped",
33804
+ result: "success",
33805
+ reason: "manual_recording_already_covers_diff",
33806
+ repoRoot: repo.repoRoot
33807
+ });
33668
33808
  return true;
33669
33809
  }
33670
33810
  const recordingPreflight2 = await collabRecordingPreflight({
@@ -33674,6 +33814,19 @@ async function recordTouchedRepo(params) {
33674
33814
  const blocked2 = getRecordingBlockedMessage(recordingPreflight2, repo.repoRoot);
33675
33815
  if (blocked2) {
33676
33816
  await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, blocked2);
33817
+ await appendHookDiagnosticsEvent({
33818
+ hook,
33819
+ sessionId,
33820
+ turnId,
33821
+ stage: "recording_preflight",
33822
+ result: "error",
33823
+ reason: recordingPreflight2.status,
33824
+ repoRoot: repo.repoRoot,
33825
+ message: blocked2.message,
33826
+ fields: {
33827
+ hint: blocked2.hint
33828
+ }
33829
+ });
33677
33830
  return false;
33678
33831
  }
33679
33832
  await collabAdd({
@@ -33686,8 +33839,29 @@ async function recordTouchedRepo(params) {
33686
33839
  actor: HOOK_ACTOR
33687
33840
  });
33688
33841
  await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "changed_turn" });
33842
+ await appendHookDiagnosticsEvent({
33843
+ hook,
33844
+ sessionId,
33845
+ turnId,
33846
+ stage: "recording_completed",
33847
+ result: "success",
33848
+ reason: "changed_turn_recorded",
33849
+ repoRoot: repo.repoRoot
33850
+ });
33689
33851
  return true;
33690
33852
  }
33853
+ await appendHookDiagnosticsEvent({
33854
+ hook,
33855
+ sessionId,
33856
+ turnId,
33857
+ stage: "workspace_diff_checked",
33858
+ result: "info",
33859
+ repoRoot: repo.repoRoot,
33860
+ fields: {
33861
+ hasWorkspaceDiff: false,
33862
+ diffLength: 0
33863
+ }
33864
+ });
33691
33865
  const recordingPreflight = await collabRecordingPreflight({
33692
33866
  api,
33693
33867
  cwd: repo.repoRoot
@@ -33695,6 +33869,19 @@ async function recordTouchedRepo(params) {
33695
33869
  const blocked = getRecordingBlockedMessage(recordingPreflight, repo.repoRoot);
33696
33870
  if (blocked) {
33697
33871
  await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, blocked);
33872
+ await appendHookDiagnosticsEvent({
33873
+ hook,
33874
+ sessionId,
33875
+ turnId,
33876
+ stage: "recording_preflight",
33877
+ result: "error",
33878
+ reason: recordingPreflight.status,
33879
+ repoRoot: repo.repoRoot,
33880
+ message: blocked.message,
33881
+ fields: {
33882
+ hint: blocked.hint
33883
+ }
33884
+ });
33698
33885
  return false;
33699
33886
  }
33700
33887
  await collabRecordTurn({
@@ -33706,32 +33893,112 @@ async function recordTouchedRepo(params) {
33706
33893
  actor: HOOK_ACTOR
33707
33894
  });
33708
33895
  await markTouchedRepoStopRecorded(sessionId, repo.repoRoot, { mode: "no_diff_turn" });
33896
+ await appendHookDiagnosticsEvent({
33897
+ hook,
33898
+ sessionId,
33899
+ turnId,
33900
+ stage: "recording_completed",
33901
+ result: "success",
33902
+ reason: "no_diff_turn_recorded",
33903
+ repoRoot: repo.repoRoot
33904
+ });
33709
33905
  return true;
33710
33906
  } catch (error) {
33711
33907
  const details = getErrorDetails(error);
33712
33908
  await markTouchedRepoRecordingFailure(sessionId, repo.repoRoot, details);
33909
+ await appendHookDiagnosticsEvent({
33910
+ hook,
33911
+ sessionId,
33912
+ turnId,
33913
+ stage: "recording_failed",
33914
+ result: "error",
33915
+ reason: "exception",
33916
+ repoRoot: repo.repoRoot,
33917
+ message: details.message,
33918
+ fields: {
33919
+ hint: details.hint
33920
+ }
33921
+ });
33713
33922
  return false;
33714
33923
  }
33715
33924
  }
33716
- async function main() {
33717
- const payload = await readJsonStdin();
33925
+ async function runHookStopCollab(payload) {
33926
+ const hook = "Stop";
33718
33927
  if (extractBoolean(payload, ["stop_hook_active"])) {
33928
+ await appendHookDiagnosticsEvent({
33929
+ hook,
33930
+ stage: "reentrancy_guard",
33931
+ result: "skip",
33932
+ reason: "stop_hook_active"
33933
+ });
33719
33934
  return;
33720
33935
  }
33721
33936
  const sessionId = extractString(payload, ["session_id"]);
33937
+ await appendHookDiagnosticsEvent({
33938
+ hook,
33939
+ sessionId,
33940
+ stage: "payload_received",
33941
+ result: "start",
33942
+ fields: {
33943
+ hasSessionId: Boolean(sessionId),
33944
+ hasStopHookActive: Boolean(extractBoolean(payload, ["stop_hook_active"])),
33945
+ hasAssistantResponse: summarizeText(extractAssistantResponse(payload)).present
33946
+ }
33947
+ });
33722
33948
  if (!sessionId) {
33949
+ await appendHookDiagnosticsEvent({
33950
+ hook,
33951
+ stage: "payload_validation",
33952
+ result: "skip",
33953
+ reason: "missing_session_id"
33954
+ });
33723
33955
  return;
33724
33956
  }
33725
33957
  const state = await loadPendingTurnState(sessionId);
33726
33958
  if (!state) {
33959
+ await appendHookDiagnosticsEvent({
33960
+ hook,
33961
+ sessionId,
33962
+ stage: "state_lookup",
33963
+ result: "skip",
33964
+ reason: "pending_state_not_found"
33965
+ });
33727
33966
  return;
33728
33967
  }
33968
+ await appendHookDiagnosticsEvent({
33969
+ hook,
33970
+ sessionId,
33971
+ turnId: state.turnId,
33972
+ stage: "state_loaded",
33973
+ result: "info",
33974
+ fields: {
33975
+ consultedMemory: state.consultedMemory,
33976
+ touchedRepoCount: Object.keys(state.touchedRepos).length,
33977
+ hasTurnFailure: Boolean(state.turnFailureMessage)
33978
+ }
33979
+ });
33729
33980
  try {
33730
33981
  let touchedRepos = await listTouchedRepos(sessionId);
33731
33982
  if (touchedRepos.length === 0) {
33732
33983
  const fallbackRepo = await resolveBoundRepoSummary(state.initialCwd);
33733
33984
  if (!fallbackRepo) {
33734
33985
  await clearPendingTurnState(sessionId);
33986
+ await appendHookDiagnosticsEvent({
33987
+ hook,
33988
+ sessionId,
33989
+ turnId: state.turnId,
33990
+ stage: "fallback_repo_lookup",
33991
+ result: "skip",
33992
+ reason: "no_bound_repo_for_fallback"
33993
+ });
33994
+ await appendHookDiagnosticsEvent({
33995
+ hook,
33996
+ sessionId,
33997
+ turnId: state.turnId,
33998
+ stage: "state_cleanup",
33999
+ result: "success",
34000
+ reason: "cleared_without_bound_repo"
34001
+ });
33735
34002
  return;
33736
34003
  }
33737
34004
  await upsertTouchedRepo(sessionId, {
@@ -33746,22 +34013,80 @@ async function main() {
33746
34013
  if (touchedRepos.length === 0) {
33747
34014
  touchedRepos = [createFallbackTouchedRepo(fallbackRepo)];
33748
34015
  }
34016
+ await appendHookDiagnosticsEvent({
34017
+ hook,
34018
+ sessionId,
34019
+ turnId: state.turnId,
34020
+ stage: "fallback_repo_lookup",
34021
+ result: "info",
34022
+ repoRoot: fallbackRepo.repoRoot,
34023
+ fields: {
34024
+ touchedRepoCount: touchedRepos.length
34025
+ }
34026
+ });
33749
34027
  }
33750
34028
  const prompt = state.prompt.trim();
33751
34029
  const assistantResponse = (extractAssistantResponse(payload) || "").trim();
34030
+ const promptSummary = summarizeText(prompt);
34031
+ const assistantResponseSummary = summarizeText(assistantResponse);
34032
+ await appendHookDiagnosticsEvent({
34033
+ hook,
34034
+ sessionId,
34035
+ turnId: state.turnId,
34036
+ stage: "response_summary",
34037
+ result: "info",
34038
+ fields: {
34039
+ promptLength: promptSummary.length,
34040
+ promptHash: promptSummary.sha256Prefix,
34041
+ assistantResponseLength: assistantResponseSummary.length,
34042
+ assistantResponseHash: assistantResponseSummary.sha256Prefix
34043
+ }
34044
+ });
33752
34045
  if (!prompt || !assistantResponse) {
33753
34046
  await markPendingTurnFailure(sessionId, {
33754
34047
  message: "Automatic Remix turn recording failed because the prompt or assistant response was missing."
33755
34048
  });
34049
+ await appendHookDiagnosticsEvent({
34050
+ hook,
34051
+ sessionId,
34052
+ turnId: state.turnId,
34053
+ stage: "response_validation",
34054
+ result: "error",
34055
+ reason: !prompt ? "missing_prompt" : "missing_assistant_response"
34056
+ });
33756
34057
  return;
33757
34058
  }
33758
34059
  const api = await createHookCollabApiClient();
34060
+ await appendHookDiagnosticsEvent({
34061
+ hook,
34062
+ sessionId,
34063
+ turnId: state.turnId,
34064
+ stage: "api_client_created",
34065
+ result: "success",
34066
+ fields: {
34067
+ touchedRepoCount: touchedRepos.length
34068
+ }
34069
+ });
33759
34070
  let hadFailure = false;
33760
34071
  for (const repo of touchedRepos) {
33761
34072
  if (shouldSkipStopRecording(repo)) {
34073
+ await appendHookDiagnosticsEvent({
34074
+ hook,
34075
+ sessionId,
34076
+ turnId: state.turnId,
34077
+ stage: "recording_skipped",
34078
+ result: "info",
34079
+ reason: "skip_stop_recording",
34080
+ repoRoot: repo.repoRoot,
34081
+ fields: {
34082
+ manuallyRecorded: repo.manuallyRecorded,
34083
+ stopRecorded: repo.stopRecorded
34084
+ }
34085
+ });
33762
34086
  continue;
33763
34087
  }
33764
34088
  const recorded = await recordTouchedRepo({
34089
+ hook,
33765
34090
  sessionId,
33766
34091
  turnId: state.turnId,
33767
34092
  repo,
@@ -33775,16 +34100,60 @@ async function main() {
33775
34100
  }
33776
34101
  if (!hadFailure) {
33777
34102
  await clearPendingTurnState(sessionId);
34103
+ await appendHookDiagnosticsEvent({
34104
+ hook,
34105
+ sessionId,
34106
+ turnId: state.turnId,
34107
+ stage: "state_cleanup",
34108
+ result: "success",
34109
+ reason: "cleared_after_success"
34110
+ });
34111
+ return;
33778
34112
  }
34113
+ await appendHookDiagnosticsEvent({
34114
+ hook,
34115
+ sessionId,
34116
+ turnId: state.turnId,
34117
+ stage: "state_cleanup",
34118
+ result: "info",
34119
+ reason: "retained_after_failure"
34120
+ });
33779
34121
  } catch (error) {
33780
34122
  const details = getErrorDetails(error);
33781
34123
  await markPendingTurnFailure(sessionId, details);
34124
+ await appendHookDiagnosticsEvent({
34125
+ hook,
34126
+ sessionId,
34127
+ turnId: state.turnId,
34128
+ stage: "turn_failed",
34129
+ result: "error",
34130
+ reason: "exception",
34131
+ message: details.message,
34132
+ fields: {
34133
+ hint: details.hint
34134
+ }
34135
+ });
33782
34136
  }
33783
34137
  }
34138
+ async function main() {
34139
+ const payload = await readJsonStdin();
34140
+ await runHookStopCollab(payload);
34141
+ }
33784
34142
  main().catch((error) => {
33785
34143
  const message = error instanceof Error ? error.message : String(error);
34144
+ void appendHookDiagnosticsEvent({
34145
+ hook: "Stop",
34146
+ stage: "unhandled_error",
34147
+ result: "error",
34148
+ reason: "exception",
34149
+ message
34150
+ });
33786
34151
  process.stderr.write(`${message}
33787
34152
  `);
33788
34153
  process.exitCode = 0;
33789
34154
  });
34155
+ // Annotate the CommonJS export names for ESM import in node:
34156
+ 0 && (module.exports = {
34157
+ runHookStopCollab
34158
+ });
33790
34159
  //# sourceMappingURL=hook-stop-collab.cjs.map