brainjar 0.3.685 → 0.3.691

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.
Files changed (2) hide show
  1. package/brainjar.js +1108 -178
  2. package/package.json +1 -1
package/brainjar.js CHANGED
@@ -107,7 +107,7 @@ var CLI_VERSION, CACHE, DAY;
107
107
  var init_version_check = __esm({
108
108
  "packages/cli/src/version-check.ts"() {
109
109
  "use strict";
110
- CLI_VERSION = "0.3.685";
110
+ CLI_VERSION = "0.3.691";
111
111
  CACHE = path.join(os.homedir(), ".brainjar", "version-check.json");
112
112
  DAY = 864e5;
113
113
  }
@@ -238,8 +238,8 @@ var init_github = __esm({
238
238
  if (this.opts.token) h.Authorization = `Bearer ${this.opts.token}`;
239
239
  return h;
240
240
  }
241
- async *paginate(path15, params) {
242
- let url = this.buildUrl(path15, params);
241
+ async *paginate(path19, params) {
242
+ let url = this.buildUrl(path19, params);
243
243
  while (url) {
244
244
  const res = await this.fetchImpl(url, { headers: this.headers() });
245
245
  if (!res.ok) {
@@ -250,9 +250,9 @@ var init_github = __esm({
250
250
  url = parseNextLink(res.headers.get("link"));
251
251
  }
252
252
  }
253
- buildUrl(path15, params) {
253
+ buildUrl(path19, params) {
254
254
  const url = new URL(
255
- `${this.baseUrl}/repos/${this.opts.owner}/${this.opts.repo}${path15}`
255
+ `${this.baseUrl}/repos/${this.opts.owner}/${this.opts.repo}${path19}`
256
256
  );
257
257
  url.searchParams.set("per_page", String(this.perPage));
258
258
  for (const [k, v] of Object.entries(params)) {
@@ -3735,7 +3735,7 @@ var require_split2 = __commonJS({
3735
3735
  var require_helper = __commonJS({
3736
3736
  "node_modules/pgpass/lib/helper.js"(exports, module) {
3737
3737
  "use strict";
3738
- var path15 = __require("path");
3738
+ var path19 = __require("path");
3739
3739
  var Stream2 = __require("stream").Stream;
3740
3740
  var split = require_split2();
3741
3741
  var util = __require("util");
@@ -3774,7 +3774,7 @@ var require_helper = __commonJS({
3774
3774
  };
3775
3775
  module.exports.getFileName = function(rawEnv) {
3776
3776
  var env = rawEnv || process.env;
3777
- var file = env.PGPASSFILE || (isWin ? path15.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path15.join(env.HOME || "./", ".pgpass"));
3777
+ var file = env.PGPASSFILE || (isWin ? path19.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path19.join(env.HOME || "./", ".pgpass"));
3778
3778
  return file;
3779
3779
  };
3780
3780
  module.exports.usePgPass = function(stats, fname) {
@@ -3906,7 +3906,7 @@ var require_helper = __commonJS({
3906
3906
  var require_lib = __commonJS({
3907
3907
  "node_modules/pgpass/lib/index.js"(exports, module) {
3908
3908
  "use strict";
3909
- var path15 = __require("path");
3909
+ var path19 = __require("path");
3910
3910
  var fs = __require("fs");
3911
3911
  var helper = require_helper();
3912
3912
  module.exports = function(connInfo, cb) {
@@ -8850,12 +8850,12 @@ var init_path = __esm({
8850
8850
  "node_modules/@anthropic-ai/sdk/internal/utils/path.mjs"() {
8851
8851
  init_error();
8852
8852
  EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
8853
- createPathTagFunction = (pathEncoder = encodeURIPath) => function path15(statics, ...params) {
8853
+ createPathTagFunction = (pathEncoder = encodeURIPath) => function path19(statics, ...params) {
8854
8854
  if (statics.length === 1)
8855
8855
  return statics[0];
8856
8856
  let postPath = false;
8857
8857
  const invalidSegments = [];
8858
- const path16 = statics.reduce((previousValue, currentValue, index) => {
8858
+ const path20 = statics.reduce((previousValue, currentValue, index) => {
8859
8859
  if (/[?#]/.test(currentValue)) {
8860
8860
  postPath = true;
8861
8861
  }
@@ -8872,7 +8872,7 @@ var init_path = __esm({
8872
8872
  }
8873
8873
  return previousValue + currentValue + (index === params.length ? "" : encoded);
8874
8874
  }, "");
8875
- const pathOnly = path16.split(/[?#]/, 1)[0];
8875
+ const pathOnly = path20.split(/[?#]/, 1)[0];
8876
8876
  const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
8877
8877
  let match;
8878
8878
  while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
@@ -8893,10 +8893,10 @@ var init_path = __esm({
8893
8893
  }, "");
8894
8894
  throw new AnthropicError(`Path parameters result in path with invalid segments:
8895
8895
  ${invalidSegments.map((e) => e.error).join("\n")}
8896
- ${path16}
8896
+ ${path20}
8897
8897
  ${underline}`);
8898
8898
  }
8899
- return path16;
8899
+ return path20;
8900
8900
  };
8901
8901
  path3 = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
8902
8902
  }
@@ -11882,9 +11882,9 @@ var init_client = __esm({
11882
11882
  makeStatusError(status, error, message, headers) {
11883
11883
  return APIError.generate(status, error, message, headers);
11884
11884
  }
11885
- buildURL(path15, query, defaultBaseURL) {
11885
+ buildURL(path19, query, defaultBaseURL) {
11886
11886
  const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
11887
- const url = isAbsoluteURL(path15) ? new URL(path15) : new URL(baseURL + (baseURL.endsWith("/") && path15.startsWith("/") ? path15.slice(1) : path15));
11887
+ const url = isAbsoluteURL(path19) ? new URL(path19) : new URL(baseURL + (baseURL.endsWith("/") && path19.startsWith("/") ? path19.slice(1) : path19));
11888
11888
  const defaultQuery = this.defaultQuery();
11889
11889
  if (!isEmptyObj(defaultQuery)) {
11890
11890
  query = { ...defaultQuery, ...query };
@@ -11915,24 +11915,24 @@ var init_client = __esm({
11915
11915
  */
11916
11916
  async prepareRequest(request, { url, options }) {
11917
11917
  }
11918
- get(path15, opts) {
11919
- return this.methodRequest("get", path15, opts);
11918
+ get(path19, opts) {
11919
+ return this.methodRequest("get", path19, opts);
11920
11920
  }
11921
- post(path15, opts) {
11922
- return this.methodRequest("post", path15, opts);
11921
+ post(path19, opts) {
11922
+ return this.methodRequest("post", path19, opts);
11923
11923
  }
11924
- patch(path15, opts) {
11925
- return this.methodRequest("patch", path15, opts);
11924
+ patch(path19, opts) {
11925
+ return this.methodRequest("patch", path19, opts);
11926
11926
  }
11927
- put(path15, opts) {
11928
- return this.methodRequest("put", path15, opts);
11927
+ put(path19, opts) {
11928
+ return this.methodRequest("put", path19, opts);
11929
11929
  }
11930
- delete(path15, opts) {
11931
- return this.methodRequest("delete", path15, opts);
11930
+ delete(path19, opts) {
11931
+ return this.methodRequest("delete", path19, opts);
11932
11932
  }
11933
- methodRequest(method, path15, opts) {
11933
+ methodRequest(method, path19, opts) {
11934
11934
  return this.request(Promise.resolve(opts).then((opts2) => {
11935
- return { method, path: path15, ...opts2 };
11935
+ return { method, path: path19, ...opts2 };
11936
11936
  }));
11937
11937
  }
11938
11938
  request(options, remainingRetries = null) {
@@ -12036,8 +12036,8 @@ var init_client = __esm({
12036
12036
  }));
12037
12037
  return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
12038
12038
  }
12039
- getAPIList(path15, Page2, opts) {
12040
- return this.requestAPIList(Page2, { method: "get", path: path15, ...opts });
12039
+ getAPIList(path19, Page2, opts) {
12040
+ return this.requestAPIList(Page2, { method: "get", path: path19, ...opts });
12041
12041
  }
12042
12042
  requestAPIList(Page2, options) {
12043
12043
  const request = this.makeRequest(options, null, void 0);
@@ -12124,8 +12124,8 @@ var init_client = __esm({
12124
12124
  }
12125
12125
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
12126
12126
  const options = { ...inputOptions };
12127
- const { method, path: path15, query, defaultBaseURL } = options;
12128
- const url = this.buildURL(path15, query, defaultBaseURL);
12127
+ const { method, path: path19, query, defaultBaseURL } = options;
12128
+ const url = this.buildURL(path19, query, defaultBaseURL);
12129
12129
  if ("timeout" in options)
12130
12130
  validatePositiveInteger("timeout", options.timeout);
12131
12131
  options.timeout = options.timeout ?? this.timeout;
@@ -12704,22 +12704,22 @@ async function gitHooksDir(root) {
12704
12704
  return path5.join(root, ".git", "hooks");
12705
12705
  }
12706
12706
  async function installGitPostCommitHook(root) {
12707
- const hooksDir = await gitHooksDir(root);
12708
- const file = path5.join(hooksDir, "post-commit");
12707
+ const hooksDir2 = await gitHooksDir(root);
12708
+ const file = path5.join(hooksDir2, "post-commit");
12709
12709
  const existing = existsSync2(file) ? await readFile2(file, "utf8") : null;
12710
12710
  if (existing?.includes(GIT_HOOK_MARKER)) return false;
12711
12711
  const content = existing === null ? `#!/bin/sh
12712
12712
  ${gitHookBlock()}
12713
12713
  ` : existing.replace(/\n*$/, "\n") + gitHookBlock() + "\n";
12714
- await mkdir(hooksDir, { recursive: true });
12714
+ await mkdir(hooksDir2, { recursive: true });
12715
12715
  await writeFile(file, content, { mode: 493 });
12716
12716
  await chmod(file, 493).catch(() => {
12717
12717
  });
12718
12718
  return true;
12719
12719
  }
12720
12720
  async function removeGitPostCommitHook(root) {
12721
- const hooksDir = await gitHooksDir(root);
12722
- const file = path5.join(hooksDir, "post-commit");
12721
+ const hooksDir2 = await gitHooksDir(root);
12722
+ const file = path5.join(hooksDir2, "post-commit");
12723
12723
  if (!existsSync2(file)) return false;
12724
12724
  const content = await readFile2(file, "utf8");
12725
12725
  if (!content.includes(GIT_HOOK_MARKER)) return false;
@@ -13115,14 +13115,367 @@ var init_copilot_hooks = __esm({
13115
13115
  }
13116
13116
  });
13117
13117
 
13118
+ // packages/ingest/src/gemini-hooks.ts
13119
+ import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5 } from "node:fs/promises";
13120
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
13121
+ import path9 from "node:path";
13122
+ async function installGeminiHooks(root) {
13123
+ const file = settingsFile(root);
13124
+ let settings = {};
13125
+ if (existsSync6(file)) {
13126
+ try {
13127
+ settings = JSON.parse(await readFile6(file, "utf8"));
13128
+ } catch {
13129
+ settings = {};
13130
+ }
13131
+ }
13132
+ const hooks = settings.hooks ??= {};
13133
+ let changed = false;
13134
+ for (const { event, subcommand, probe, timeout } of EVENTS4) {
13135
+ const groups = hooks[event] ??= [];
13136
+ if (groups.some((g) => g.hooks?.some((h) => h.command?.includes(probe))))
13137
+ continue;
13138
+ groups.push({
13139
+ hooks: [
13140
+ {
13141
+ type: "command",
13142
+ command: captureHookCommand(subcommand),
13143
+ name: "brainjar",
13144
+ timeout
13145
+ }
13146
+ ]
13147
+ });
13148
+ changed = true;
13149
+ }
13150
+ if (!changed) return false;
13151
+ await mkdir5(path9.dirname(file), { recursive: true });
13152
+ await writeFile5(file, JSON.stringify(settings, null, 2) + "\n", "utf8");
13153
+ return true;
13154
+ }
13155
+ function geminiHooksInstalled(root) {
13156
+ const file = settingsFile(root);
13157
+ if (!existsSync6(file)) return false;
13158
+ try {
13159
+ return readFileSync7(file, "utf8").includes(PROBE3);
13160
+ } catch {
13161
+ return false;
13162
+ }
13163
+ }
13164
+ async function removeGeminiHooks(root) {
13165
+ const file = settingsFile(root);
13166
+ if (!existsSync6(file)) return false;
13167
+ let settings;
13168
+ try {
13169
+ settings = JSON.parse(await readFile6(file, "utf8"));
13170
+ } catch {
13171
+ return false;
13172
+ }
13173
+ const hooks = settings.hooks;
13174
+ if (!hooks) return false;
13175
+ let changed = false;
13176
+ for (const event of Object.keys(hooks)) {
13177
+ const groups = hooks[event];
13178
+ if (!Array.isArray(groups)) continue;
13179
+ const kept = groups.filter((g) => !g.hooks?.some(ours2));
13180
+ if (kept.length !== groups.length) {
13181
+ changed = true;
13182
+ if (kept.length === 0) delete hooks[event];
13183
+ else hooks[event] = kept;
13184
+ }
13185
+ }
13186
+ if (!changed) return false;
13187
+ await writeFile5(file, JSON.stringify(settings, null, 2) + "\n", "utf8");
13188
+ return true;
13189
+ }
13190
+ var PROBE3, EVENTS4, PROBES2, settingsFile, ours2;
13191
+ var init_gemini_hooks = __esm({
13192
+ "packages/ingest/src/gemini-hooks.ts"() {
13193
+ "use strict";
13194
+ init_bootstrap();
13195
+ PROBE3 = "hook gemini";
13196
+ EVENTS4 = [
13197
+ {
13198
+ event: "SessionStart",
13199
+ subcommand: "constraints",
13200
+ probe: "constraints --hook",
13201
+ timeout: 15e3
13202
+ },
13203
+ {
13204
+ event: "BeforeAgent",
13205
+ subcommand: "hook gemini",
13206
+ probe: PROBE3,
13207
+ timeout: 3e4
13208
+ },
13209
+ {
13210
+ event: "AfterTool",
13211
+ subcommand: "hook gemini",
13212
+ probe: PROBE3,
13213
+ timeout: 3e4
13214
+ },
13215
+ {
13216
+ event: "AfterAgent",
13217
+ subcommand: "hook gemini",
13218
+ probe: PROBE3,
13219
+ timeout: 12e4
13220
+ },
13221
+ {
13222
+ event: "SessionEnd",
13223
+ subcommand: "hook gemini",
13224
+ probe: PROBE3,
13225
+ timeout: 1e4
13226
+ }
13227
+ ];
13228
+ PROBES2 = EVENTS4.map((e) => e.probe);
13229
+ settingsFile = (root) => path9.join(root, ".gemini", "settings.json");
13230
+ ours2 = (h) => PROBES2.some((p) => h.command?.includes(p) ?? false);
13231
+ }
13232
+ });
13233
+
13234
+ // packages/ingest/src/kiro-hooks.ts
13235
+ import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir6, rm as rm3 } from "node:fs/promises";
13236
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
13237
+ import path10 from "node:path";
13238
+ function render2() {
13239
+ const hook = (name, trigger, command, timeout) => ({ name, trigger, action: { type: "command", command }, timeout });
13240
+ return JSON.stringify(
13241
+ {
13242
+ version: "v1",
13243
+ hooks: [
13244
+ hook(
13245
+ "brainjar-context",
13246
+ "SessionStart",
13247
+ `${captureHookCommand("constraints")} --plain`,
13248
+ 15
13249
+ ),
13250
+ hook(
13251
+ "brainjar-prompt",
13252
+ "UserPromptSubmit",
13253
+ captureHookCommand("hook kiro"),
13254
+ 30
13255
+ ),
13256
+ hook(
13257
+ "brainjar-tool",
13258
+ "PostToolUse",
13259
+ captureHookCommand("hook kiro"),
13260
+ 30
13261
+ ),
13262
+ hook("brainjar-stop", "Stop", captureHookCommand("hook kiro"), 120)
13263
+ ]
13264
+ },
13265
+ null,
13266
+ 2
13267
+ ) + "\n";
13268
+ }
13269
+ async function installKiroHooks(root) {
13270
+ const file = hooksFile4(root);
13271
+ const next = render2();
13272
+ if (existsSync7(file) && await readFile7(file, "utf8") === next) return false;
13273
+ await mkdir6(path10.dirname(file), { recursive: true });
13274
+ await writeFile6(file, next, "utf8");
13275
+ return true;
13276
+ }
13277
+ function kiroHooksInstalled(root) {
13278
+ const file = hooksFile4(root);
13279
+ if (!existsSync7(file)) return false;
13280
+ try {
13281
+ return readFileSync8(file, "utf8").includes(PROBE4);
13282
+ } catch {
13283
+ return false;
13284
+ }
13285
+ }
13286
+ async function removeKiroHooks(root) {
13287
+ if (!kiroHooksInstalled(root)) return false;
13288
+ await rm3(hooksFile4(root), { force: true });
13289
+ return true;
13290
+ }
13291
+ var PROBE4, hooksFile4;
13292
+ var init_kiro_hooks = __esm({
13293
+ "packages/ingest/src/kiro-hooks.ts"() {
13294
+ "use strict";
13295
+ init_bootstrap();
13296
+ PROBE4 = "hook kiro";
13297
+ hooksFile4 = (root) => path10.join(root, ".kiro", "hooks", "brainjar.json");
13298
+ }
13299
+ });
13300
+
13301
+ // packages/ingest/src/opencode-plugin.ts
13302
+ import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7, rm as rm4 } from "node:fs/promises";
13303
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
13304
+ import path11 from "node:path";
13305
+ function render3() {
13306
+ const cmd = captureHookCommand("hook opencode");
13307
+ return `// BrainJar capture plugin, written by \`brainjar init\` (rewritten on every init; do not edit).
13308
+ // Ships prompts, tool calls, failures, and touched files of every OpenCode session to your Brain.
13309
+ import { spawn } from "node:child_process";
13310
+
13311
+ export const BrainJar = async ({ directory }) => {
13312
+ let session = "";
13313
+ const seen = new Set();
13314
+ const send = (payload) => {
13315
+ try {
13316
+ const child = spawn(${JSON.stringify(cmd)}, {
13317
+ shell: true,
13318
+ cwd: directory,
13319
+ stdio: ["pipe", "ignore", "ignore"],
13320
+ });
13321
+ child.on("error", () => {});
13322
+ child.stdin.end(
13323
+ JSON.stringify({ source: "opencode", cwd: directory, session_id: session, ...payload }),
13324
+ );
13325
+ } catch {}
13326
+ };
13327
+ const target = (args) => {
13328
+ if (!args || typeof args !== "object") return "";
13329
+ for (const k of ["command", "filePath", "path"]) if (typeof args[k] === "string") return args[k];
13330
+ return "";
13331
+ };
13332
+ return {
13333
+ event: async ({ event }) => {
13334
+ const p = event.properties ?? {};
13335
+ switch (event.type) {
13336
+ case "session.created":
13337
+ session = p.info?.id ?? session;
13338
+ send({ kind: "session_started" });
13339
+ break;
13340
+ case "session.idle":
13341
+ if (p.sessionID) session = p.sessionID;
13342
+ send({ kind: "turn_ended" });
13343
+ break;
13344
+ case "file.edited":
13345
+ send({ kind: "file_changed", path: p.file });
13346
+ break;
13347
+ case "message.part.updated": {
13348
+ const part = p.part;
13349
+ if (part?.type === "tool" && part.state?.status === "error" && !seen.has(part.callID)) {
13350
+ seen.add(part.callID);
13351
+ session = part.sessionID ?? session;
13352
+ send({ kind: "tool_failed", tool_name: part.tool, target: target(part.state.input), error: String(part.state.error ?? "") });
13353
+ }
13354
+ break;
13355
+ }
13356
+ }
13357
+ },
13358
+ "chat.message": async (input, output) => {
13359
+ session = input.sessionID ?? session;
13360
+ const text = (output.parts ?? []).filter((x) => x.type === "text").map((x) => x.text).join("\\n");
13361
+ if (text.trim()) send({ kind: "prompt", text });
13362
+ },
13363
+ "tool.execute.after": async (input, output) => {
13364
+ session = input.sessionID ?? session;
13365
+ send({ kind: "tool_used", tool_name: input.tool, target: target(input.args), output: String(output.output ?? "").slice(0, 4000) });
13366
+ },
13367
+ };
13368
+ };
13369
+ `;
13370
+ }
13371
+ async function installOpencodePlugin(root) {
13372
+ const file = pluginFile(root);
13373
+ const next = render3();
13374
+ if (existsSync8(file) && await readFile8(file, "utf8") === next) return false;
13375
+ await mkdir7(path11.dirname(file), { recursive: true });
13376
+ await writeFile7(file, next, "utf8");
13377
+ return true;
13378
+ }
13379
+ function opencodePluginInstalled(root) {
13380
+ const file = pluginFile(root);
13381
+ if (!existsSync8(file)) return false;
13382
+ try {
13383
+ return readFileSync9(file, "utf8").includes(PROBE5);
13384
+ } catch {
13385
+ return false;
13386
+ }
13387
+ }
13388
+ async function removeOpencodePlugin(root) {
13389
+ if (!opencodePluginInstalled(root)) return false;
13390
+ await rm4(pluginFile(root), { force: true });
13391
+ return true;
13392
+ }
13393
+ var PROBE5, pluginFile;
13394
+ var init_opencode_plugin = __esm({
13395
+ "packages/ingest/src/opencode-plugin.ts"() {
13396
+ "use strict";
13397
+ init_bootstrap();
13398
+ PROBE5 = "hook opencode";
13399
+ pluginFile = (root) => path11.join(root, ".opencode", "plugins", "brainjar.ts");
13400
+ }
13401
+ });
13402
+
13403
+ // packages/ingest/src/cline-hooks.ts
13404
+ import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir8, rm as rm5, chmod as chmod2 } from "node:fs/promises";
13405
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
13406
+ import path12 from "node:path";
13407
+ async function installClineHooks(root) {
13408
+ const dir = hooksDir(root);
13409
+ let changed = false;
13410
+ for (const { name, subcommand, flag } of SCRIPTS) {
13411
+ const file = path12.join(dir, name);
13412
+ const next = script(subcommand, flag);
13413
+ if (existsSync9(file)) {
13414
+ const current = await readFile9(file, "utf8");
13415
+ if (current === next) continue;
13416
+ if (!current.includes(MARK)) continue;
13417
+ }
13418
+ await mkdir8(dir, { recursive: true });
13419
+ await writeFile8(file, next, { mode: 493 });
13420
+ await chmod2(file, 493).catch(() => {
13421
+ });
13422
+ changed = true;
13423
+ }
13424
+ return changed;
13425
+ }
13426
+ function clineHooksInstalled(root) {
13427
+ const file = path12.join(hooksDir(root), "TaskComplete");
13428
+ if (!existsSync9(file)) return false;
13429
+ try {
13430
+ return readFileSync10(file, "utf8").includes("hook cline");
13431
+ } catch {
13432
+ return false;
13433
+ }
13434
+ }
13435
+ async function removeClineHooks(root) {
13436
+ let changed = false;
13437
+ for (const { name } of SCRIPTS) {
13438
+ const file = path12.join(hooksDir(root), name);
13439
+ if (!existsSync9(file)) continue;
13440
+ try {
13441
+ if (!readFileSync10(file, "utf8").includes(MARK)) continue;
13442
+ } catch {
13443
+ continue;
13444
+ }
13445
+ await rm5(file, { force: true });
13446
+ changed = true;
13447
+ }
13448
+ return changed;
13449
+ }
13450
+ var MARK, SCRIPTS, hooksDir, script;
13451
+ var init_cline_hooks = __esm({
13452
+ "packages/ingest/src/cline-hooks.ts"() {
13453
+ "use strict";
13454
+ init_bootstrap();
13455
+ MARK = "# brainjar-capture";
13456
+ SCRIPTS = [
13457
+ { name: "TaskStart", subcommand: "constraints", flag: " --cline" },
13458
+ { name: "UserPromptSubmit", subcommand: "hook cline", flag: "" },
13459
+ { name: "PostToolUse", subcommand: "hook cline", flag: "" },
13460
+ { name: "TaskComplete", subcommand: "hook cline", flag: "" },
13461
+ { name: "TaskCancel", subcommand: "hook cline", flag: "" }
13462
+ ];
13463
+ hooksDir = (root) => path12.join(root, ".clinerules", "hooks");
13464
+ script = (subcommand, flag) => `#!/bin/sh
13465
+ ${MARK}
13466
+ exec ${captureHookCommand(subcommand)}${flag}
13467
+ `;
13468
+ }
13469
+ });
13470
+
13118
13471
  // packages/ingest/src/tools.ts
13119
- import { existsSync as existsSync6 } from "node:fs";
13472
+ import { existsSync as existsSync10 } from "node:fs";
13120
13473
  import os4 from "node:os";
13121
- import path9 from "node:path";
13474
+ import path13 from "node:path";
13122
13475
  function detectedTools(root) {
13123
13476
  return CAPTURE_TOOLS.filter((t) => t.detect(root));
13124
13477
  }
13125
- var claudeCode, cursor, codex, copilot, CAPTURE_TOOLS;
13478
+ var claudeCode, cursor, codex, copilot, gemini, kiro, opencode, cline, CAPTURE_TOOLS;
13126
13479
  var init_tools = __esm({
13127
13480
  "packages/ingest/src/tools.ts"() {
13128
13481
  "use strict";
@@ -13130,10 +13483,14 @@ var init_tools = __esm({
13130
13483
  init_cursor_hooks();
13131
13484
  init_codex_hooks();
13132
13485
  init_copilot_hooks();
13486
+ init_gemini_hooks();
13487
+ init_kiro_hooks();
13488
+ init_opencode_plugin();
13489
+ init_cline_hooks();
13133
13490
  claudeCode = {
13134
13491
  id: "claude-code",
13135
13492
  name: "Claude Code",
13136
- detect: (root) => existsSync6(path9.join(os4.homedir(), ".claude")) || existsSync6(path9.join(root, ".claude")),
13493
+ detect: (root) => existsSync10(path13.join(os4.homedir(), ".claude")) || existsSync10(path13.join(root, ".claude")),
13137
13494
  install: installCaptureHook,
13138
13495
  installed: captureHookInstalled,
13139
13496
  remove: removeCaptureHook
@@ -13141,7 +13498,7 @@ var init_tools = __esm({
13141
13498
  cursor = {
13142
13499
  id: "cursor",
13143
13500
  name: "Cursor",
13144
- detect: (root) => existsSync6(path9.join(os4.homedir(), ".cursor")) || existsSync6(path9.join(root, ".cursor")),
13501
+ detect: (root) => existsSync10(path13.join(os4.homedir(), ".cursor")) || existsSync10(path13.join(root, ".cursor")),
13145
13502
  install: installCursorHooks,
13146
13503
  installed: cursorHooksInstalled,
13147
13504
  remove: removeCursorHooks
@@ -13149,7 +13506,7 @@ var init_tools = __esm({
13149
13506
  codex = {
13150
13507
  id: "codex",
13151
13508
  name: "Codex",
13152
- detect: (root) => existsSync6(path9.join(os4.homedir(), ".codex")) || existsSync6(path9.join(root, ".codex")),
13509
+ detect: (root) => existsSync10(path13.join(os4.homedir(), ".codex")) || existsSync10(path13.join(root, ".codex")),
13153
13510
  install: installCodexHooks,
13154
13511
  installed: codexHooksInstalled,
13155
13512
  remove: removeCodexHooks
@@ -13157,16 +13514,52 @@ var init_tools = __esm({
13157
13514
  copilot = {
13158
13515
  id: "copilot",
13159
13516
  name: "GitHub Copilot",
13160
- detect: (root) => existsSync6(path9.join(os4.homedir(), ".copilot")) || existsSync6(path9.join(root, ".github", "hooks")),
13517
+ detect: (root) => existsSync10(path13.join(os4.homedir(), ".copilot")) || existsSync10(path13.join(root, ".github", "hooks")),
13161
13518
  install: installCopilotHooks,
13162
13519
  installed: copilotHooksInstalled,
13163
13520
  remove: removeCopilotHooks
13164
13521
  };
13522
+ gemini = {
13523
+ id: "gemini",
13524
+ name: "Gemini CLI",
13525
+ detect: (root) => existsSync10(path13.join(os4.homedir(), ".gemini")) || existsSync10(path13.join(root, ".gemini")),
13526
+ install: installGeminiHooks,
13527
+ installed: geminiHooksInstalled,
13528
+ remove: removeGeminiHooks
13529
+ };
13530
+ kiro = {
13531
+ id: "kiro",
13532
+ name: "Kiro",
13533
+ detect: (root) => existsSync10(path13.join(os4.homedir(), ".kiro")) || existsSync10(path13.join(root, ".kiro")),
13534
+ install: installKiroHooks,
13535
+ installed: kiroHooksInstalled,
13536
+ remove: removeKiroHooks
13537
+ };
13538
+ opencode = {
13539
+ id: "opencode",
13540
+ name: "OpenCode",
13541
+ detect: (root) => existsSync10(path13.join(os4.homedir(), ".config", "opencode")) || existsSync10(path13.join(root, ".opencode")),
13542
+ install: installOpencodePlugin,
13543
+ installed: opencodePluginInstalled,
13544
+ remove: removeOpencodePlugin
13545
+ };
13546
+ cline = {
13547
+ id: "cline",
13548
+ name: "Cline",
13549
+ detect: (root) => existsSync10(path13.join(os4.homedir(), "Documents", "Cline")) || existsSync10(path13.join(os4.homedir(), ".cline")) || existsSync10(path13.join(root, ".clinerules")),
13550
+ install: installClineHooks,
13551
+ installed: clineHooksInstalled,
13552
+ remove: removeClineHooks
13553
+ };
13165
13554
  CAPTURE_TOOLS = [
13166
13555
  claudeCode,
13167
13556
  cursor,
13168
13557
  codex,
13169
- copilot
13558
+ copilot,
13559
+ gemini,
13560
+ kiro,
13561
+ opencode,
13562
+ cline
13170
13563
  ];
13171
13564
  }
13172
13565
  });
@@ -13643,8 +14036,8 @@ var init_doku_model = __esm({
13643
14036
 
13644
14037
  // packages/ingest/src/doku.ts
13645
14038
  import { createHash as createHash2 } from "node:crypto";
13646
- import { mkdir as mkdir5, readFile as readFile6, rm as rm3, stat, writeFile as writeFile5 } from "node:fs/promises";
13647
- import path10 from "node:path";
14039
+ import { mkdir as mkdir9, readFile as readFile10, rm as rm6, stat, writeFile as writeFile9 } from "node:fs/promises";
14040
+ import path14 from "node:path";
13648
14041
  function bodyLocations(o) {
13649
14042
  return Array.isArray(o.body.location) ? o.body.location.map(String) : [];
13650
14043
  }
@@ -13710,7 +14103,7 @@ async function loadDokuData(brainId, root) {
13710
14103
  if (root) {
13711
14104
  try {
13712
14105
  const pkg = JSON.parse(
13713
- await readFile6(path10.join(root, "package.json"), "utf8")
14106
+ await readFile10(path14.join(root, "package.json"), "utf8")
13714
14107
  );
13715
14108
  description = pkg.description ?? null;
13716
14109
  scripts = pkg.scripts ?? null;
@@ -14293,11 +14686,11 @@ function dokuFilePath(kind, slug) {
14293
14686
  return `${DOKU_DIR}/${slug}.md`;
14294
14687
  }
14295
14688
  async function writeDokuTree(root, files) {
14296
- const dir = path10.join(root, DOKU_DIR);
14297
- const manifestPath = path10.join(dir, MANIFEST);
14689
+ const dir = path14.join(root, DOKU_DIR);
14690
+ const manifestPath = path14.join(dir, MANIFEST);
14298
14691
  let old = { files: {} };
14299
14692
  try {
14300
- old = JSON.parse(await readFile6(manifestPath, "utf8"));
14693
+ old = JSON.parse(await readFile10(manifestPath, "utf8"));
14301
14694
  if (typeof old.files !== "object" || old.files === null)
14302
14695
  old = { files: {} };
14303
14696
  } catch {
@@ -14307,21 +14700,21 @@ async function writeDokuTree(root, files) {
14307
14700
  for (const file of files) {
14308
14701
  const hash = sha1(file.markdown);
14309
14702
  next[file.relPath] = { hash };
14310
- const abs = path10.join(root, file.relPath);
14703
+ const abs = path14.join(root, file.relPath);
14311
14704
  if (old.files[file.relPath]?.hash === hash && await exists(abs)) continue;
14312
- await mkdir5(path10.dirname(abs), { recursive: true });
14313
- await writeFile5(abs, file.markdown, "utf8");
14705
+ await mkdir9(path14.dirname(abs), { recursive: true });
14706
+ await writeFile9(abs, file.markdown, "utf8");
14314
14707
  written++;
14315
14708
  }
14316
14709
  let deleted = 0;
14317
14710
  for (const rel of Object.keys(old.files)) {
14318
14711
  if (next[rel]) continue;
14319
14712
  if (rel !== DOKU_FILENAME && !rel.startsWith(`${DOKU_DIR}/`)) continue;
14320
- await rm3(path10.join(root, rel), { force: true });
14713
+ await rm6(path14.join(root, rel), { force: true });
14321
14714
  deleted++;
14322
14715
  }
14323
- await mkdir5(dir, { recursive: true });
14324
- await writeFile5(
14716
+ await mkdir9(dir, { recursive: true });
14717
+ await writeFile9(
14325
14718
  manifestPath,
14326
14719
  JSON.stringify({ files: next }, null, 2),
14327
14720
  "utf8"
@@ -14474,8 +14867,8 @@ var init_ponytail_brief = __esm({
14474
14867
  });
14475
14868
 
14476
14869
  // packages/ingest/src/ponytail-debt.ts
14477
- function isDebtScannable(path15) {
14478
- return !/\.(md|mdx|markdown|txt|jsonl|log|lock)$/i.test(path15);
14870
+ function isDebtScannable(path19) {
14871
+ return !/\.(md|mdx|markdown|txt|jsonl|log|lock)$/i.test(path19);
14479
14872
  }
14480
14873
  function scanPonytailComments(files) {
14481
14874
  const out = [];
@@ -14642,6 +15035,10 @@ var init_src3 = __esm({
14642
15035
  init_cursor_hooks();
14643
15036
  init_codex_hooks();
14644
15037
  init_copilot_hooks();
15038
+ init_gemini_hooks();
15039
+ init_kiro_hooks();
15040
+ init_opencode_plugin();
15041
+ init_cline_hooks();
14645
15042
  init_commit_links();
14646
15043
  init_tool_failure();
14647
15044
  init_bootstrap();
@@ -14727,8 +15124,8 @@ __export(brief_exports, {
14727
15124
  refreshBrief: () => refreshBrief,
14728
15125
  upsertBriefBlock: () => upsertBriefBlock
14729
15126
  });
14730
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync7 } from "node:fs";
14731
- import path11 from "node:path";
15127
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync3, existsSync as existsSync11 } from "node:fs";
15128
+ import path15 from "node:path";
14732
15129
  function claudeMdWithImport(existing) {
14733
15130
  let text = existing;
14734
15131
  const start = text.indexOf(BRIEF_START);
@@ -14782,12 +15179,12 @@ async function refreshBrief(cwd) {
14782
15179
  `/capture/brief?brain=${project.brainId}`
14783
15180
  );
14784
15181
  if (!text) return "skipped";
14785
- const file = path11.join(root, BRIEF_FILE);
14786
- const existing = existsSync7(file) ? readFileSync7(file, "utf8") : "";
15182
+ const file = path15.join(root, BRIEF_FILE);
15183
+ const existing = existsSync11(file) ? readFileSync11(file, "utf8") : "";
14787
15184
  const next = upsertBriefBlock(existing, text);
14788
15185
  if (next !== null) writeFileSync3(file, next);
14789
- const claudeFile = path11.join(root, "CLAUDE.md");
14790
- const claude = existsSync7(claudeFile) ? readFileSync7(claudeFile, "utf8") : "";
15186
+ const claudeFile = path15.join(root, "CLAUDE.md");
15187
+ const claude = existsSync11(claudeFile) ? readFileSync11(claudeFile, "utf8") : "";
14791
15188
  const claudeNext = claudeMdWithImport(claude);
14792
15189
  if (claudeNext !== null) writeFileSync3(claudeFile, claudeNext);
14793
15190
  return next !== null || claudeNext !== null ? "updated" : "unchanged";
@@ -14824,9 +15221,9 @@ __export(debt_exports, {
14824
15221
  scanDebtInRepo: () => scanDebtInRepo
14825
15222
  });
14826
15223
  import { execFile as execFile3 } from "node:child_process";
14827
- import { readFile as readFile7 } from "node:fs/promises";
15224
+ import { readFile as readFile11 } from "node:fs/promises";
14828
15225
  import { promisify as promisify3 } from "node:util";
14829
- import path12 from "node:path";
15226
+ import path16 from "node:path";
14830
15227
  async function scanDebtInRepo(root) {
14831
15228
  const { stdout } = await run3("git", ["-C", root, "ls-files"], {
14832
15229
  maxBuffer: 16 * 1048576
@@ -14836,7 +15233,7 @@ async function scanDebtInRepo(root) {
14836
15233
  const p = rel.trim();
14837
15234
  if (!p || !isDebtScannable(p)) continue;
14838
15235
  try {
14839
- const buf = await readFile7(path12.join(root, p));
15236
+ const buf = await readFile11(path16.join(root, p));
14840
15237
  if (buf.length > 512 * 1024 || buf.includes(0)) continue;
14841
15238
  files.push({ path: p, content: buf.toString("utf8") });
14842
15239
  } catch {
@@ -15262,11 +15659,11 @@ var init_codex = __esm({
15262
15659
  ...commits.length ? { commits } : {}
15263
15660
  });
15264
15661
  files.forEach(
15265
- (path15, i) => events.push({
15662
+ (path19, i) => events.push({
15266
15663
  ...base,
15267
15664
  event_id: `file:${id}:${i}`,
15268
15665
  kind: "file_changed",
15269
- path: path15
15666
+ path: path19
15270
15667
  })
15271
15668
  );
15272
15669
  }
@@ -15436,106 +15833,637 @@ var init_copilot = __esm({
15436
15833
  }
15437
15834
  });
15438
15835
 
15439
- // packages/cli/src/adapters/index.ts
15440
- var ADAPTERS;
15441
- var init_adapters = __esm({
15442
- "packages/cli/src/adapters/index.ts"() {
15443
- "use strict";
15444
- init_claude_code();
15445
- init_cursor();
15446
- init_codex();
15447
- init_copilot();
15448
- ADAPTERS = {
15449
- [claudeCode2.id]: claudeCode2,
15450
- [cursor2.id]: cursor2,
15451
- [codex2.id]: codex2,
15452
- [copilot2.id]: copilot2
15453
- };
15454
- }
15455
- });
15456
-
15457
- // packages/cli/src/hook.ts
15458
- var hook_exports = {};
15459
- __export(hook_exports, {
15460
- hookMain: () => hookMain,
15461
- readSpool: () => readSpool,
15462
- spoolPath: () => spoolPath
15463
- });
15464
- import {
15465
- appendFileSync,
15466
- existsSync as existsSync8,
15467
- mkdirSync as mkdirSync3,
15468
- readdirSync,
15469
- readFileSync as readFileSync8,
15470
- rmSync,
15471
- statSync as statSync2
15472
- } from "node:fs";
15473
- import os5 from "node:os";
15474
- import path13 from "node:path";
15475
- function spoolPath(tool, sessionId) {
15476
- return path13.join(
15477
- os5.homedir(),
15478
- ".brainjar",
15479
- "spool",
15480
- tool,
15481
- `${sessionId.replace(/[^A-Za-z0-9._-]/g, "_")}.jsonl`
15482
- );
15483
- }
15484
- function readSpool(file) {
15485
- const out = [];
15486
- for (const line of readFileSync8(file, "utf8").split("\n")) {
15487
- if (!line.trim()) continue;
15488
- try {
15489
- out.push(clampCaptureEvent(JSON.parse(line)));
15490
- } catch {
15491
- }
15836
+ // packages/cli/src/adapters/gemini.ts
15837
+ import { createHash as createHash7 } from "node:crypto";
15838
+ function targetOf4(input) {
15839
+ const i = input ?? {};
15840
+ for (const k of ["command", "file_path"]) {
15841
+ if (typeof i[k] === "string") return i[k];
15492
15842
  }
15493
- return out;
15843
+ return "";
15494
15844
  }
15495
- function pruneSpool(dir, keep, maxAgeMs = 7 * 864e5) {
15496
- try {
15497
- for (const f of readdirSync(dir)) {
15498
- const file = path13.join(dir, f);
15499
- if (file === keep) continue;
15500
- if (Date.now() - statSync2(file).mtimeMs > maxAgeMs)
15501
- rmSync(file, { force: true });
15502
- }
15503
- } catch {
15845
+ function commitShas5(v) {
15846
+ const text = typeof v === "string" ? v : JSON.stringify(v ?? "");
15847
+ const out = /* @__PURE__ */ new Set();
15848
+ for (const m of text.matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
15849
+ out.add(m[1].toLowerCase());
15504
15850
  }
15851
+ return [...out];
15505
15852
  }
15506
- async function hookMain(argv) {
15507
- const eventFlag = argv.indexOf("--event");
15508
- const event = eventFlag >= 0 ? argv[eventFlag + 1] : void 0;
15509
- const tool = argv.find((a, i) => !a.startsWith("--") && i !== eventFlag + 1);
15510
- const adapter = tool ? ADAPTERS[tool] : void 0;
15511
- try {
15512
- if (!adapter) {
15513
- console.error(
15514
- `brainjar hook: unknown tool "${tool ?? ""}". Known: ${Object.keys(ADAPTERS).join(", ")}`
15515
- );
15516
- return;
15517
- }
15518
- let payload = null;
15519
- try {
15520
- payload = JSON.parse(readFileSync8(0, "utf8"));
15521
- } catch {
15522
- return;
15523
- }
15524
- const out = adapter.map(payload, (/* @__PURE__ */ new Date()).toISOString(), { event });
15525
- if (!out) return;
15526
- if (out.reply) process.stdout.write(out.reply);
15527
- const file = spoolPath(adapter.id, out.sessionId);
15528
- if (out.events.length) {
15529
- mkdirSync3(path13.dirname(file), { recursive: true });
15530
- appendFileSync(
15531
- file,
15532
- out.events.map((e) => JSON.stringify(clampCaptureEvent(e))).join("\n") + "\n"
15533
- );
15534
- }
15535
- if (!out.flush) return;
15536
- const root = await repoRoot(out.cwd ?? process.cwd());
15537
- const events = existsSync8(file) ? readSpool(file) : [];
15538
- if (events.length) {
15853
+ var EDIT_TOOLS3, short6, gemini2;
15854
+ var init_gemini = __esm({
15855
+ "packages/cli/src/adapters/gemini.ts"() {
15856
+ "use strict";
15857
+ init_src();
15858
+ EDIT_TOOLS3 = /* @__PURE__ */ new Set(["write_file", "replace"]);
15859
+ short6 = (s) => createHash7("sha256").update(s).digest("hex").slice(0, 16);
15860
+ gemini2 = {
15861
+ id: "gemini",
15862
+ map(payload, now) {
15863
+ const p = payload;
15864
+ if (!p || typeof p !== "object" || typeof p.session_id !== "string") {
15865
+ return null;
15866
+ }
15867
+ const at = typeof p.timestamp === "string" && !Number.isNaN(Date.parse(p.timestamp)) ? new Date(p.timestamp).toISOString() : now;
15868
+ const base = {
15869
+ v: CAPTURE_EVENT_VERSION,
15870
+ tool: "gemini",
15871
+ session_id: p.session_id,
15872
+ at,
15873
+ cwd: p.cwd ?? null
15874
+ };
15875
+ const events = [];
15876
+ let flush = false;
15877
+ let end = false;
15878
+ switch (p.hook_event_name) {
15879
+ case "SessionStart":
15880
+ events.push({
15881
+ ...base,
15882
+ event_id: `start:${p.session_id}`,
15883
+ kind: "session_started"
15884
+ });
15885
+ break;
15886
+ case "BeforeAgent":
15887
+ if (typeof p.prompt === "string" && p.prompt.trim()) {
15888
+ events.push({
15889
+ ...base,
15890
+ event_id: `prompt:${short6(at + p.prompt)}`,
15891
+ kind: "prompt",
15892
+ text: p.prompt
15893
+ });
15894
+ }
15895
+ break;
15896
+ case "AfterTool": {
15897
+ const tool = p.tool_name ?? "tool";
15898
+ const target = targetOf4(p.tool_input);
15899
+ const id = short6(at + tool + target);
15900
+ const err = p.tool_response?.error;
15901
+ if (err && typeof err === "object") {
15902
+ const error = String(err.message ?? err.type ?? "tool error");
15903
+ events.push({
15904
+ ...base,
15905
+ event_id: `fail:${id}`,
15906
+ kind: "tool_failed",
15907
+ tool_name: tool,
15908
+ ...target ? { target } : {},
15909
+ error
15910
+ });
15911
+ break;
15912
+ }
15913
+ const commits = commitShas5(p.tool_response?.llmContent);
15914
+ events.push({
15915
+ ...base,
15916
+ event_id: `use:${id}`,
15917
+ kind: "tool_used",
15918
+ tool_name: tool,
15919
+ ...target ? { target } : {},
15920
+ ...commits.length ? { commits } : {}
15921
+ });
15922
+ if (EDIT_TOOLS3.has(tool) && target) {
15923
+ events.push({
15924
+ ...base,
15925
+ event_id: `file:${id}`,
15926
+ kind: "file_changed",
15927
+ path: target
15928
+ });
15929
+ }
15930
+ break;
15931
+ }
15932
+ case "AfterAgent":
15933
+ flush = true;
15934
+ break;
15935
+ case "SessionEnd":
15936
+ events.push({
15937
+ ...base,
15938
+ event_id: `end:${p.session_id}`,
15939
+ kind: "session_ended",
15940
+ ...p.reason ? { reason: p.reason } : {}
15941
+ });
15942
+ flush = true;
15943
+ end = true;
15944
+ break;
15945
+ default:
15946
+ return null;
15947
+ }
15948
+ return { sessionId: p.session_id, cwd: p.cwd ?? null, events, flush, end };
15949
+ }
15950
+ };
15951
+ }
15952
+ });
15953
+
15954
+ // packages/cli/src/adapters/kiro.ts
15955
+ import { createHash as createHash8 } from "node:crypto";
15956
+ function targetOf5(input) {
15957
+ const i = input ?? {};
15958
+ for (const k of ["command", "cmd", "path", "file_path", "filePath"]) {
15959
+ if (typeof i[k] === "string") return i[k];
15960
+ }
15961
+ return "";
15962
+ }
15963
+ function commitShas6(v) {
15964
+ const text = typeof v === "string" ? v : JSON.stringify(v ?? "");
15965
+ const out = /* @__PURE__ */ new Set();
15966
+ for (const m of text.matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
15967
+ out.add(m[1].toLowerCase());
15968
+ }
15969
+ return [...out];
15970
+ }
15971
+ function failureOf2(response) {
15972
+ if (!response || typeof response !== "object") return null;
15973
+ const r = response;
15974
+ if (r.success !== false) return null;
15975
+ const text = [r.error, r.result, r.message].find(
15976
+ (t) => typeof t === "string" && t.trim()
15977
+ );
15978
+ return (typeof text === "string" ? text : JSON.stringify(r)).trim();
15979
+ }
15980
+ var EDIT_TOOLS4, short7, EVENTS5, kiro2;
15981
+ var init_kiro = __esm({
15982
+ "packages/cli/src/adapters/kiro.ts"() {
15983
+ "use strict";
15984
+ init_src();
15985
+ EDIT_TOOLS4 = /* @__PURE__ */ new Set(["fs_write", "write", "str_replace", "fs_append"]);
15986
+ short7 = (s) => createHash8("sha256").update(s).digest("hex").slice(0, 16);
15987
+ EVENTS5 = {
15988
+ agentSpawn: "start",
15989
+ sessionStart: "start",
15990
+ SessionStart: "start",
15991
+ userPromptSubmit: "prompt",
15992
+ UserPromptSubmit: "prompt",
15993
+ promptSubmit: "prompt",
15994
+ postToolUse: "tool",
15995
+ PostToolUse: "tool",
15996
+ postFileSave: "file",
15997
+ PostFileSave: "file",
15998
+ postFileCreate: "file",
15999
+ PostFileCreate: "file",
16000
+ stop: "stop",
16001
+ Stop: "stop",
16002
+ agentStop: "stop"
16003
+ };
16004
+ kiro2 = {
16005
+ id: "kiro",
16006
+ map(payload, now) {
16007
+ const p = payload;
16008
+ if (!p || typeof p !== "object" || typeof p.session_id !== "string") {
16009
+ return null;
16010
+ }
16011
+ const base = {
16012
+ v: CAPTURE_EVENT_VERSION,
16013
+ tool: "kiro",
16014
+ session_id: p.session_id,
16015
+ at: now,
16016
+ cwd: p.cwd ?? null
16017
+ };
16018
+ const events = [];
16019
+ let flush = false;
16020
+ switch (EVENTS5[p.hook_event_name ?? ""]) {
16021
+ case "start":
16022
+ events.push({
16023
+ ...base,
16024
+ event_id: `start:${p.session_id}`,
16025
+ kind: "session_started"
16026
+ });
16027
+ break;
16028
+ case "prompt":
16029
+ if (typeof p.prompt === "string" && p.prompt.trim()) {
16030
+ events.push({
16031
+ ...base,
16032
+ event_id: `prompt:${short7(now + p.prompt)}`,
16033
+ kind: "prompt",
16034
+ text: p.prompt
16035
+ });
16036
+ }
16037
+ break;
16038
+ case "tool": {
16039
+ const tool = p.tool_name ?? "tool";
16040
+ const target = targetOf5(p.tool_input);
16041
+ const id = short7(now + tool + target);
16042
+ const error = failureOf2(p.tool_response);
16043
+ if (error) {
16044
+ events.push({
16045
+ ...base,
16046
+ event_id: `fail:${id}`,
16047
+ kind: "tool_failed",
16048
+ tool_name: tool,
16049
+ ...target ? { target } : {},
16050
+ error
16051
+ });
16052
+ break;
16053
+ }
16054
+ const commits = commitShas6(p.tool_response);
16055
+ events.push({
16056
+ ...base,
16057
+ event_id: `use:${id}`,
16058
+ kind: "tool_used",
16059
+ tool_name: tool,
16060
+ ...target ? { target } : {},
16061
+ ...commits.length ? { commits } : {}
16062
+ });
16063
+ if (EDIT_TOOLS4.has(tool) && target) {
16064
+ events.push({
16065
+ ...base,
16066
+ event_id: `file:${id}`,
16067
+ kind: "file_changed",
16068
+ path: target
16069
+ });
16070
+ }
16071
+ break;
16072
+ }
16073
+ case "file":
16074
+ if (typeof p.file_path === "string" && p.file_path) {
16075
+ events.push({
16076
+ ...base,
16077
+ event_id: `file:${short7(now + p.file_path)}`,
16078
+ kind: "file_changed",
16079
+ path: p.file_path
16080
+ });
16081
+ }
16082
+ break;
16083
+ case "stop":
16084
+ flush = true;
16085
+ break;
16086
+ default:
16087
+ return null;
16088
+ }
16089
+ return {
16090
+ sessionId: p.session_id,
16091
+ cwd: p.cwd ?? null,
16092
+ events,
16093
+ flush,
16094
+ end: false
16095
+ };
16096
+ }
16097
+ };
16098
+ }
16099
+ });
16100
+
16101
+ // packages/cli/src/adapters/opencode.ts
16102
+ import { createHash as createHash9 } from "node:crypto";
16103
+ function commitShas7(text) {
16104
+ const out = /* @__PURE__ */ new Set();
16105
+ for (const m of (text ?? "").matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
16106
+ out.add(m[1].toLowerCase());
16107
+ }
16108
+ return [...out];
16109
+ }
16110
+ var EDIT_TOOLS5, short8, opencode2;
16111
+ var init_opencode = __esm({
16112
+ "packages/cli/src/adapters/opencode.ts"() {
16113
+ "use strict";
16114
+ init_src();
16115
+ EDIT_TOOLS5 = /* @__PURE__ */ new Set(["edit", "write"]);
16116
+ short8 = (s) => createHash9("sha256").update(s).digest("hex").slice(0, 16);
16117
+ opencode2 = {
16118
+ id: "opencode",
16119
+ map(payload, now) {
16120
+ const p = payload;
16121
+ if (!p || typeof p !== "object" || p.source !== "opencode" || typeof p.session_id !== "string" || !p.session_id) {
16122
+ return null;
16123
+ }
16124
+ const base = {
16125
+ v: CAPTURE_EVENT_VERSION,
16126
+ tool: "opencode",
16127
+ session_id: p.session_id,
16128
+ at: now,
16129
+ cwd: p.cwd ?? null
16130
+ };
16131
+ const events = [];
16132
+ let flush = false;
16133
+ const target = p.target?.trim() ?? "";
16134
+ const tool = p.tool_name ?? "tool";
16135
+ switch (p.kind) {
16136
+ case "session_started":
16137
+ events.push({
16138
+ ...base,
16139
+ event_id: `start:${p.session_id}`,
16140
+ kind: "session_started"
16141
+ });
16142
+ break;
16143
+ case "prompt":
16144
+ if (typeof p.text === "string" && p.text.trim()) {
16145
+ events.push({
16146
+ ...base,
16147
+ event_id: `prompt:${short8(now + p.text)}`,
16148
+ kind: "prompt",
16149
+ text: p.text
16150
+ });
16151
+ }
16152
+ break;
16153
+ case "tool_used": {
16154
+ const id = short8(now + tool + target);
16155
+ const commits = commitShas7(p.output);
16156
+ events.push({
16157
+ ...base,
16158
+ event_id: `use:${id}`,
16159
+ kind: "tool_used",
16160
+ tool_name: tool,
16161
+ ...target ? { target } : {},
16162
+ ...commits.length ? { commits } : {}
16163
+ });
16164
+ if (EDIT_TOOLS5.has(tool) && target) {
16165
+ events.push({
16166
+ ...base,
16167
+ event_id: `file:${id}`,
16168
+ kind: "file_changed",
16169
+ path: target
16170
+ });
16171
+ }
16172
+ break;
16173
+ }
16174
+ case "tool_failed": {
16175
+ const error = (p.error ?? "").trim();
16176
+ if (!error) break;
16177
+ events.push({
16178
+ ...base,
16179
+ event_id: `fail:${short8(now + tool + target + error)}`,
16180
+ kind: "tool_failed",
16181
+ tool_name: tool,
16182
+ ...target ? { target } : {},
16183
+ error
16184
+ });
16185
+ break;
16186
+ }
16187
+ case "file_changed":
16188
+ if (typeof p.path === "string" && p.path) {
16189
+ events.push({
16190
+ ...base,
16191
+ event_id: `file:${short8(now + p.path)}`,
16192
+ kind: "file_changed",
16193
+ path: p.path
16194
+ });
16195
+ }
16196
+ break;
16197
+ case "turn_ended":
16198
+ flush = true;
16199
+ break;
16200
+ default:
16201
+ return null;
16202
+ }
16203
+ return {
16204
+ sessionId: p.session_id,
16205
+ cwd: p.cwd ?? null,
16206
+ events,
16207
+ flush,
16208
+ end: false
16209
+ };
16210
+ }
16211
+ };
16212
+ }
16213
+ });
16214
+
16215
+ // packages/cli/src/adapters/cline.ts
16216
+ import { createHash as createHash10 } from "node:crypto";
16217
+ function targetOf6(params) {
16218
+ const i = params ?? {};
16219
+ for (const k of ["command", "path"]) {
16220
+ if (typeof i[k] === "string") return i[k];
16221
+ }
16222
+ return "";
16223
+ }
16224
+ function commitShas8(text) {
16225
+ const out = /* @__PURE__ */ new Set();
16226
+ for (const m of (text ?? "").matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
16227
+ out.add(m[1].toLowerCase());
16228
+ }
16229
+ return [...out];
16230
+ }
16231
+ function stamp2(ts, now) {
16232
+ const n = typeof ts === "string" && /^\d+$/.test(ts) ? Number(ts) : ts;
16233
+ if (typeof n === "number" && Number.isFinite(n))
16234
+ return new Date(n).toISOString();
16235
+ if (typeof ts === "string" && !Number.isNaN(Date.parse(ts)))
16236
+ return new Date(ts).toISOString();
16237
+ return now;
16238
+ }
16239
+ function clineContextEnvelope(text) {
16240
+ return JSON.stringify({ cancel: false, contextModification: text });
16241
+ }
16242
+ var EDIT_TOOLS6, short9, CLINE_REPLY, cline2;
16243
+ var init_cline = __esm({
16244
+ "packages/cli/src/adapters/cline.ts"() {
16245
+ "use strict";
16246
+ init_src();
16247
+ EDIT_TOOLS6 = /* @__PURE__ */ new Set(["write_to_file", "replace_in_file", "apply_patch"]);
16248
+ short9 = (s) => createHash10("sha256").update(s).digest("hex").slice(0, 16);
16249
+ CLINE_REPLY = JSON.stringify({ cancel: false });
16250
+ cline2 = {
16251
+ id: "cline",
16252
+ map(payload, now) {
16253
+ const p = payload;
16254
+ if (!p || typeof p !== "object" || typeof p.taskId !== "string") {
16255
+ return null;
16256
+ }
16257
+ const at = stamp2(p.timestamp, now);
16258
+ const cwd = p.workspaceRoots?.[0] ?? null;
16259
+ const base = {
16260
+ v: CAPTURE_EVENT_VERSION,
16261
+ tool: "cline",
16262
+ session_id: p.taskId,
16263
+ at,
16264
+ cwd
16265
+ };
16266
+ const events = [];
16267
+ let flush = false;
16268
+ let end = false;
16269
+ switch (p.hookName) {
16270
+ case "TaskStart":
16271
+ case "TaskResume":
16272
+ events.push({
16273
+ ...base,
16274
+ event_id: `start:${p.taskId}`,
16275
+ kind: "session_started"
16276
+ });
16277
+ break;
16278
+ case "UserPromptSubmit": {
16279
+ const prompt = p.userPromptSubmit?.prompt;
16280
+ if (typeof prompt === "string" && prompt.trim()) {
16281
+ events.push({
16282
+ ...base,
16283
+ event_id: `prompt:${short9(at + prompt)}`,
16284
+ kind: "prompt",
16285
+ text: prompt
16286
+ });
16287
+ }
16288
+ break;
16289
+ }
16290
+ case "PostToolUse": {
16291
+ const t = p.postToolUse ?? {};
16292
+ const tool = t.toolName ?? "tool";
16293
+ const target = targetOf6(t.parameters);
16294
+ const id = short9(at + tool + target);
16295
+ if (t.success !== true) {
16296
+ const error = (t.result ?? "").trim() || "tool failed";
16297
+ events.push({
16298
+ ...base,
16299
+ event_id: `fail:${id}`,
16300
+ kind: "tool_failed",
16301
+ tool_name: tool,
16302
+ ...target ? { target } : {},
16303
+ error
16304
+ });
16305
+ break;
16306
+ }
16307
+ const commits = commitShas8(t.result);
16308
+ events.push({
16309
+ ...base,
16310
+ event_id: `use:${id}`,
16311
+ kind: "tool_used",
16312
+ tool_name: tool,
16313
+ ...target ? { target } : {},
16314
+ ...commits.length ? { commits } : {}
16315
+ });
16316
+ if (EDIT_TOOLS6.has(tool) && target) {
16317
+ events.push({
16318
+ ...base,
16319
+ event_id: `file:${id}`,
16320
+ kind: "file_changed",
16321
+ path: target
16322
+ });
16323
+ }
16324
+ break;
16325
+ }
16326
+ case "TaskComplete":
16327
+ case "TaskCancel":
16328
+ events.push({
16329
+ ...base,
16330
+ event_id: `end:${p.taskId}`,
16331
+ kind: "session_ended",
16332
+ reason: p.hookName === "TaskCancel" ? "cancelled" : "completed"
16333
+ });
16334
+ flush = true;
16335
+ end = true;
16336
+ break;
16337
+ default:
16338
+ return null;
16339
+ }
16340
+ return {
16341
+ sessionId: p.taskId,
16342
+ cwd,
16343
+ events,
16344
+ flush,
16345
+ end,
16346
+ reply: CLINE_REPLY
16347
+ };
16348
+ }
16349
+ };
16350
+ }
16351
+ });
16352
+
16353
+ // packages/cli/src/adapters/index.ts
16354
+ var ADAPTERS;
16355
+ var init_adapters = __esm({
16356
+ "packages/cli/src/adapters/index.ts"() {
16357
+ "use strict";
16358
+ init_claude_code();
16359
+ init_cursor();
16360
+ init_codex();
16361
+ init_copilot();
16362
+ init_gemini();
16363
+ init_kiro();
16364
+ init_opencode();
16365
+ init_cline();
16366
+ ADAPTERS = {
16367
+ [claudeCode2.id]: claudeCode2,
16368
+ [cursor2.id]: cursor2,
16369
+ [codex2.id]: codex2,
16370
+ [copilot2.id]: copilot2,
16371
+ [gemini2.id]: gemini2,
16372
+ [kiro2.id]: kiro2,
16373
+ [opencode2.id]: opencode2,
16374
+ [cline2.id]: cline2
16375
+ };
16376
+ }
16377
+ });
16378
+
16379
+ // packages/cli/src/hook.ts
16380
+ var hook_exports = {};
16381
+ __export(hook_exports, {
16382
+ hookMain: () => hookMain,
16383
+ parseHookArgs: () => parseHookArgs,
16384
+ readSpool: () => readSpool,
16385
+ spoolPath: () => spoolPath
16386
+ });
16387
+ import {
16388
+ appendFileSync,
16389
+ existsSync as existsSync12,
16390
+ mkdirSync as mkdirSync3,
16391
+ readdirSync,
16392
+ readFileSync as readFileSync12,
16393
+ rmSync,
16394
+ statSync as statSync2
16395
+ } from "node:fs";
16396
+ import os5 from "node:os";
16397
+ import path17 from "node:path";
16398
+ function spoolPath(tool, sessionId) {
16399
+ return path17.join(
16400
+ os5.homedir(),
16401
+ ".brainjar",
16402
+ "spool",
16403
+ tool,
16404
+ `${sessionId.replace(/[^A-Za-z0-9._-]/g, "_")}.jsonl`
16405
+ );
16406
+ }
16407
+ function readSpool(file) {
16408
+ const out = [];
16409
+ for (const line of readFileSync12(file, "utf8").split("\n")) {
16410
+ if (!line.trim()) continue;
16411
+ try {
16412
+ out.push(clampCaptureEvent(JSON.parse(line)));
16413
+ } catch {
16414
+ }
16415
+ }
16416
+ return out;
16417
+ }
16418
+ function pruneSpool(dir, keep, maxAgeMs = 7 * 864e5) {
16419
+ try {
16420
+ for (const f of readdirSync(dir)) {
16421
+ const file = path17.join(dir, f);
16422
+ if (file === keep) continue;
16423
+ if (Date.now() - statSync2(file).mtimeMs > maxAgeMs)
16424
+ rmSync(file, { force: true });
16425
+ }
16426
+ } catch {
16427
+ }
16428
+ }
16429
+ function parseHookArgs(argv) {
16430
+ const eventFlag = argv.indexOf("--event");
16431
+ const eventIndex = eventFlag >= 0 ? eventFlag + 1 : -1;
16432
+ const event = eventIndex >= 0 ? argv[eventIndex] : void 0;
16433
+ const tool = argv.find((a, i) => !a.startsWith("--") && i !== eventIndex);
16434
+ return { tool, event };
16435
+ }
16436
+ async function hookMain(argv) {
16437
+ const { tool, event } = parseHookArgs(argv);
16438
+ const adapter = tool ? ADAPTERS[tool] : void 0;
16439
+ try {
16440
+ if (!adapter) {
16441
+ console.error(
16442
+ `brainjar hook: unknown tool "${tool ?? ""}". Known: ${Object.keys(ADAPTERS).join(", ")}`
16443
+ );
16444
+ return;
16445
+ }
16446
+ let payload = null;
16447
+ try {
16448
+ payload = JSON.parse(readFileSync12(0, "utf8"));
16449
+ } catch {
16450
+ return;
16451
+ }
16452
+ const out = adapter.map(payload, (/* @__PURE__ */ new Date()).toISOString(), { event });
16453
+ if (!out) return;
16454
+ if (out.reply) process.stdout.write(out.reply);
16455
+ const file = spoolPath(adapter.id, out.sessionId);
16456
+ if (out.events.length) {
16457
+ mkdirSync3(path17.dirname(file), { recursive: true });
16458
+ appendFileSync(
16459
+ file,
16460
+ out.events.map((e) => JSON.stringify(clampCaptureEvent(e))).join("\n") + "\n"
16461
+ );
16462
+ }
16463
+ if (!out.flush) return;
16464
+ const root = await repoRoot(out.cwd ?? process.cwd());
16465
+ const events = existsSync12(file) ? readSpool(file) : [];
16466
+ if (events.length) {
15539
16467
  const r = await captureApi("/capture/events", {
15540
16468
  method: "POST",
15541
16469
  body: {
@@ -15555,7 +16483,7 @@ async function hookMain(argv) {
15555
16483
  }
15556
16484
  }
15557
16485
  if (out.end) rmSync(file, { force: true });
15558
- pruneSpool(path13.dirname(file), file);
16486
+ pruneSpool(path17.dirname(file), file);
15559
16487
  await syncRepo(root);
15560
16488
  } catch (e) {
15561
16489
  console.error("brainjar hook:", e instanceof Error ? e.message : e);
@@ -15678,7 +16606,7 @@ __export(init_exports, {
15678
16606
  init: () => init,
15679
16607
  initMain: () => initMain
15680
16608
  });
15681
- import { readFile as readFile8 } from "node:fs/promises";
16609
+ import { readFile as readFile12 } from "node:fs/promises";
15682
16610
  async function init(argv) {
15683
16611
  const skipTranscripts = argv.includes("--no-transcripts");
15684
16612
  const flag = (f) => argv.includes(f) ? argv[argv.indexOf(f) + 1] ?? null : null;
@@ -15763,7 +16691,7 @@ async function init(argv) {
15763
16691
  let skipped = 0;
15764
16692
  for (const f of files) {
15765
16693
  try {
15766
- const { text, truncated } = capTranscript(await readFile8(f, "utf8"));
16694
+ const { text, truncated } = capTranscript(await readFile12(f, "utf8"));
15767
16695
  if (truncated) truncatedCount++;
15768
16696
  const r = await captureApi("/capture/transcript", {
15769
16697
  method: "POST",
@@ -15786,7 +16714,7 @@ async function init(argv) {
15786
16714
  const tools = detectedTools(root);
15787
16715
  if (tools.length === 0) {
15788
16716
  ok(
15789
- "no coding agent detected (looked for Claude Code, Cursor, Codex, GitHub Copilot); rerun init after installing one"
16717
+ "no coding agent detected (looked for Claude Code, Cursor, Codex, GitHub Copilot, Gemini CLI, Kiro, OpenCode, Cline); rerun init after installing one"
15790
16718
  );
15791
16719
  }
15792
16720
  for (const tool of tools) {
@@ -15853,7 +16781,7 @@ __export(constraints_exports, {
15853
16781
  constraintsMain: () => constraintsMain,
15854
16782
  sessionStartEnvelope: () => sessionStartEnvelope
15855
16783
  });
15856
- import { readFileSync as readFileSync9 } from "node:fs";
16784
+ import { readFileSync as readFileSync13 } from "node:fs";
15857
16785
  function sessionStartEnvelope(text) {
15858
16786
  return JSON.stringify({
15859
16787
  hookSpecificOutput: {
@@ -15871,8 +16799,8 @@ async function constraintsMain(argv) {
15871
16799
  if (hook) {
15872
16800
  let cwd;
15873
16801
  try {
15874
- const payload = JSON.parse(readFileSync9(0, "utf8"));
15875
- cwd = payload.cwd;
16802
+ const payload = JSON.parse(readFileSync13(0, "utf8"));
16803
+ cwd = payload.cwd ?? payload.workspace_roots?.[0] ?? payload.workspaceRoots?.[0];
15876
16804
  } catch {
15877
16805
  }
15878
16806
  root = await repoRoot(cwd ?? process.cwd());
@@ -15893,8 +16821,9 @@ async function constraintsMain(argv) {
15893
16821
  }
15894
16822
  return;
15895
16823
  }
15896
- const envelope = argv.includes("--copilot") ? copilotContextEnvelope : sessionStartEnvelope;
16824
+ const envelope = argv.includes("--copilot") ? copilotContextEnvelope : argv.includes("--cline") ? clineContextEnvelope : argv.includes("--plain") ? (text) => text : sessionStartEnvelope;
15897
16825
  if (r.text) console.log(hook ? envelope(r.text) : r.text);
16826
+ else if (hook && argv.includes("--cline")) console.log(CLINE_REPLY);
15898
16827
  else if (!hook)
15899
16828
  console.log("No recorded failed approaches or rules for this Brain.");
15900
16829
  if (hook) {
@@ -15915,6 +16844,7 @@ var init_constraints = __esm({
15915
16844
  init_src3();
15916
16845
  init_api_client();
15917
16846
  init_copilot();
16847
+ init_cline();
15918
16848
  }
15919
16849
  });
15920
16850
 
@@ -16066,8 +16996,8 @@ __export(deepscan_exports, {
16066
16996
  deepscanMain: () => deepscanMain,
16067
16997
  selectDeepScanFiles: () => selectDeepScanFiles
16068
16998
  });
16069
- import { readdir as readdir3, readFile as readFile9, stat as stat2 } from "node:fs/promises";
16070
- import path14 from "node:path";
16999
+ import { readdir as readdir3, readFile as readFile13, stat as stat2 } from "node:fs/promises";
17000
+ import path18 from "node:path";
16071
17001
  function selectDeepScanFiles(candidates, graph, maxFiles = MAX_FILES) {
16072
17002
  const picked = [];
16073
17003
  const has = /* @__PURE__ */ new Set();
@@ -16104,7 +17034,7 @@ async function walkCandidates(root) {
16104
17034
  for (const e of entries) {
16105
17035
  if (!e.isFile()) continue;
16106
17036
  const parent = e.parentPath ?? e.path ?? root;
16107
- const rel = path14.relative(root, path14.join(parent, e.name)).split(path14.sep).join("/");
17037
+ const rel = path18.relative(root, path18.join(parent, e.name)).split(path18.sep).join("/");
16108
17038
  if (rel.split("/").some((seg) => SKIP_DIRS.has(seg))) continue;
16109
17039
  out.push(rel);
16110
17040
  }
@@ -16129,10 +17059,10 @@ async function deepscanMain() {
16129
17059
  const artifacts = [];
16130
17060
  for (const rel of files) {
16131
17061
  try {
16132
- const abs = path14.join(root, rel);
17062
+ const abs = path18.join(root, rel);
16133
17063
  const info = await stat2(abs);
16134
17064
  if (info.size > RAW_SKIP_BYTES) continue;
16135
- const content = await readFile9(abs, "utf8");
17065
+ const content = await readFile13(abs, "utf8");
16136
17066
  artifacts.push({
16137
17067
  source: "deepscan",
16138
17068
  kind: "source_file",