brainjar 0.3.702 → 0.3.705

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 +146 -52
  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.702";
110
+ CLI_VERSION = "0.3.705";
111
111
  CACHE = path.join(os.homedir(), ".brainjar", "version-check.json");
112
112
  DAY = 864e5;
113
113
  }
@@ -4587,7 +4587,7 @@ var require_pg_pool = __commonJS({
4587
4587
  function throwOnDoubleRelease() {
4588
4588
  throw new Error("Release called on client which has already been released to the pool.");
4589
4589
  }
4590
- function promisify5(Promise2, callback) {
4590
+ function promisify6(Promise2, callback) {
4591
4591
  if (callback) {
4592
4592
  return { callback, result: void 0 };
4593
4593
  }
@@ -4725,7 +4725,7 @@ var require_pg_pool = __commonJS({
4725
4725
  const err = new Error("Cannot use a pool after calling end on the pool");
4726
4726
  return cb ? cb(err) : this.Promise.reject(err);
4727
4727
  }
4728
- const response = promisify5(this.Promise, cb);
4728
+ const response = promisify6(this.Promise, cb);
4729
4729
  const result = response.result;
4730
4730
  if (this._isFull() || this._idle.length) {
4731
4731
  if (this._idle.length) {
@@ -4910,7 +4910,7 @@ var require_pg_pool = __commonJS({
4910
4910
  }
4911
4911
  query(text, values, cb) {
4912
4912
  if (typeof text === "function") {
4913
- const response2 = promisify5(this.Promise, text);
4913
+ const response2 = promisify6(this.Promise, text);
4914
4914
  setImmediate(function() {
4915
4915
  return response2.callback(new Error("Passing a function as the first parameter to pool.query is not supported"));
4916
4916
  });
@@ -4920,7 +4920,7 @@ var require_pg_pool = __commonJS({
4920
4920
  cb = values;
4921
4921
  values = void 0;
4922
4922
  }
4923
- const response = promisify5(this.Promise, cb);
4923
+ const response = promisify6(this.Promise, cb);
4924
4924
  cb = response.callback;
4925
4925
  this.connect((err, client) => {
4926
4926
  if (err) {
@@ -4965,7 +4965,7 @@ var require_pg_pool = __commonJS({
4965
4965
  return cb ? cb(err) : this.Promise.reject(err);
4966
4966
  }
4967
4967
  this.ending = true;
4968
- const promised = promisify5(this.Promise, cb);
4968
+ const promised = promisify6(this.Promise, cb);
4969
4969
  this._endCallback = promised.callback;
4970
4970
  this._pulseQueue();
4971
4971
  return promised.result;
@@ -12604,6 +12604,33 @@ async function repoRoot(cwd = process.cwd()) {
12604
12604
  function repoName(root) {
12605
12605
  return path5.basename(root) || "project";
12606
12606
  }
12607
+ function parseGitRemote(url) {
12608
+ const s = url.trim();
12609
+ const scp = /^(?:[\w.-]+@)?([\w.-]+):([\w.-]+\/[\w.-]+?)(?:\.git)?\/?$/.exec(
12610
+ s
12611
+ );
12612
+ if (scp) return `${scp[1].toLowerCase()}/${scp[2]}`;
12613
+ try {
12614
+ const u = new URL(s);
12615
+ if (!/^(https?|ssh|git):$/.test(u.protocol) || !u.hostname) return null;
12616
+ const parts = u.pathname.replace(/\.git$/, "").split("/").filter(Boolean);
12617
+ if (parts.length < 2) return null;
12618
+ return `${u.hostname.toLowerCase()}/${parts.slice(-2).join("/")}`;
12619
+ } catch {
12620
+ return null;
12621
+ }
12622
+ }
12623
+ async function repoRemote(root) {
12624
+ try {
12625
+ const { stdout } = await run2("git", ["remote", "get-url", "origin"], {
12626
+ cwd: root,
12627
+ windowsHide: true
12628
+ });
12629
+ return parseGitRemote(stdout);
12630
+ } catch {
12631
+ return null;
12632
+ }
12633
+ }
12607
12634
  async function mainRepoRoot(root) {
12608
12635
  try {
12609
12636
  const { stdout } = await run2(
@@ -15143,13 +15170,20 @@ __export(brief_exports, {
15143
15170
  BRIEF_FILE: () => BRIEF_FILE,
15144
15171
  BRIEF_START: () => BRIEF_START,
15145
15172
  CLAUDE_IMPORT: () => CLAUDE_IMPORT,
15173
+ CURSOR_RULE_FILE: () => CURSOR_RULE_FILE,
15174
+ POINTER: () => POINTER,
15146
15175
  briefMain: () => briefMain,
15147
15176
  claudeMdWithImport: () => claudeMdWithImport,
15148
- refreshBrief: () => refreshBrief,
15149
- upsertBriefBlock: () => upsertBriefBlock
15177
+ cursorRule: () => cursorRule,
15178
+ installPointer: () => installPointer,
15179
+ refreshCursorRules: () => refreshCursorRules,
15180
+ upsertBriefBlock: () => upsertBriefBlock,
15181
+ withExcluded: () => withExcluded
15150
15182
  });
15151
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync3, existsSync as existsSync11 } from "node:fs";
15183
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync3, existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
15152
15184
  import path15 from "node:path";
15185
+ import { execFile as execFile3 } from "node:child_process";
15186
+ import { promisify as promisify3 } from "node:util";
15153
15187
  function claudeMdWithImport(existing) {
15154
15188
  let text = existing;
15155
15189
  const start = text.indexOf(BRIEF_START);
@@ -15180,6 +15214,7 @@ function upsertBriefBlock(existing, brief) {
15180
15214
  const block = `${BRIEF_START}
15181
15215
 
15182
15216
  ${defuseMarkers(brief).trimEnd()}
15217
+
15183
15218
  ${BRIEF_END}
15184
15219
  `;
15185
15220
  const start = existing.indexOf(BRIEF_START);
@@ -15192,18 +15227,11 @@ ${BRIEF_END}
15192
15227
  if (!existing.trim()) return block;
15193
15228
  return existing.trimEnd() + "\n\n" + block;
15194
15229
  }
15195
- async function refreshBrief(cwd) {
15230
+ function installPointer(root) {
15196
15231
  if (loadEnv().BRAINJAR_NO_BRIEF === "1") return "skipped";
15197
- const root = await repoRoot(cwd);
15198
- const link = await captureProject(root);
15199
- if (!link) return "skipped";
15200
- const { text } = await captureApi(
15201
- `/capture/brief?brain=${link.brainId}`
15202
- );
15203
- if (!text) return "skipped";
15204
15232
  const file = path15.join(root, BRIEF_FILE);
15205
15233
  const existing = existsSync11(file) ? readFileSync11(file, "utf8") : "";
15206
- const next = upsertBriefBlock(existing, text);
15234
+ const next = upsertBriefBlock(existing, POINTER);
15207
15235
  if (next !== null) writeFileSync3(file, next);
15208
15236
  const claudeFile = path15.join(root, "CLAUDE.md");
15209
15237
  const claude = existsSync11(claudeFile) ? readFileSync11(claudeFile, "utf8") : "";
@@ -15211,28 +15239,95 @@ async function refreshBrief(cwd) {
15211
15239
  if (claudeNext !== null) writeFileSync3(claudeFile, claudeNext);
15212
15240
  return next !== null || claudeNext !== null ? "updated" : "unchanged";
15213
15241
  }
15242
+ function cursorRule(brief) {
15243
+ return [
15244
+ "---",
15245
+ "description: BrainJar session context (generated by brainjar, kept out of git)",
15246
+ "alwaysApply: true",
15247
+ "---",
15248
+ "",
15249
+ brief.trimEnd(),
15250
+ ""
15251
+ ].join("\n");
15252
+ }
15253
+ function withExcluded(existing, line) {
15254
+ if (existing.split(/\r?\n/).some((l) => l.trim() === line)) return null;
15255
+ const sep = existing.length && !existing.endsWith("\n") ? "\n" : "";
15256
+ return `${existing}${sep}${line}
15257
+ `;
15258
+ }
15259
+ async function refreshCursorRules(root) {
15260
+ if (loadEnv().BRAINJAR_NO_BRIEF === "1") return "skipped";
15261
+ const cursor3 = CAPTURE_TOOLS.find((t) => t.id === "cursor");
15262
+ if (!cursor3?.detect(root)) return "skipped";
15263
+ const link = await captureProject(root);
15264
+ if (!link) return "skipped";
15265
+ const { text } = await captureApi(
15266
+ `/capture/brief?brain=${link.brainId}`
15267
+ );
15268
+ if (!text) return "skipped";
15269
+ const file = path15.join(root, CURSOR_RULE_FILE);
15270
+ const next = cursorRule(text);
15271
+ const changed = !existsSync11(file) || readFileSync11(file, "utf8") !== next;
15272
+ if (changed) {
15273
+ mkdirSync3(path15.dirname(file), { recursive: true });
15274
+ writeFileSync3(file, next);
15275
+ }
15276
+ try {
15277
+ const { stdout } = await run3(
15278
+ "git",
15279
+ ["rev-parse", "--path-format=absolute", "--git-path", "info/exclude"],
15280
+ { cwd: root, windowsHide: true }
15281
+ );
15282
+ const exclude = stdout.trim();
15283
+ const current = existsSync11(exclude) ? readFileSync11(exclude, "utf8") : "";
15284
+ const withLine = withExcluded(current, CURSOR_RULE_FILE);
15285
+ if (withLine !== null) {
15286
+ mkdirSync3(path15.dirname(exclude), { recursive: true });
15287
+ writeFileSync3(exclude, withLine);
15288
+ }
15289
+ } catch {
15290
+ }
15291
+ return changed ? "updated" : "unchanged";
15292
+ }
15214
15293
  async function briefMain() {
15215
15294
  try {
15216
- const result = await refreshBrief(process.cwd());
15295
+ const root = await repoRoot(process.cwd());
15296
+ const link = await captureProject(root);
15297
+ if (!link) {
15298
+ console.error(notConnected(root));
15299
+ process.exitCode = 1;
15300
+ return;
15301
+ }
15302
+ const { text } = await captureApi(
15303
+ `/capture/brief?brain=${link.brainId}`
15304
+ );
15217
15305
  console.log(
15218
- result === "updated" ? "AGENTS.md brief block updated (CLAUDE.md imports it)." : result === "unchanged" ? "AGENTS.md brief block already current." : "skipped (no connected Brain, empty brief, or BRAINJAR_NO_BRIEF=1)"
15306
+ text || "The Brain has no features yet; the brief fills in as captures land."
15219
15307
  );
15220
15308
  } catch (e) {
15221
15309
  console.error("brainjar brief:", e instanceof Error ? e.message : e);
15222
15310
  process.exitCode = 1;
15223
15311
  }
15224
15312
  }
15225
- var BRIEF_START, BRIEF_END, BRIEF_FILE, CLAUDE_IMPORT;
15313
+ var run3, BRIEF_START, BRIEF_END, BRIEF_FILE, CLAUDE_IMPORT, CURSOR_RULE_FILE, POINTER;
15226
15314
  var init_brief = __esm({
15227
15315
  "packages/cli/src/brief.ts"() {
15228
15316
  "use strict";
15229
15317
  init_src3();
15230
15318
  init_src();
15231
15319
  init_api_client();
15320
+ run3 = promisify3(execFile3);
15232
15321
  BRIEF_START = "<!-- brainjar:brief:start -->";
15233
15322
  BRIEF_END = "<!-- brainjar:brief:end -->";
15234
15323
  BRIEF_FILE = "AGENTS.md";
15235
15324
  CLAUDE_IMPORT = "@AGENTS.md";
15325
+ CURSOR_RULE_FILE = ".cursor/rules/brainjar.mdc";
15326
+ POINTER = [
15327
+ "## Memory",
15328
+ "",
15329
+ "This project uses BrainJar as its memory. Before non-trivial work, search it (MCP tool `search_memory`; `get_negative_constraints` before trying an approach that may have failed before). The session context (working discipline, reuse index, project rules, known failures) arrives at session start from the server, never from this file."
15330
+ ].join("\n");
15236
15331
  }
15237
15332
  });
15238
15333
 
@@ -15242,12 +15337,12 @@ __export(debt_exports, {
15242
15337
  debtMain: () => debtMain,
15243
15338
  scanDebtInRepo: () => scanDebtInRepo
15244
15339
  });
15245
- import { execFile as execFile3 } from "node:child_process";
15340
+ import { execFile as execFile4 } from "node:child_process";
15246
15341
  import { readFile as readFile11 } from "node:fs/promises";
15247
- import { promisify as promisify3 } from "node:util";
15342
+ import { promisify as promisify4 } from "node:util";
15248
15343
  import path16 from "node:path";
15249
15344
  async function scanDebtInRepo(root) {
15250
- const { stdout } = await run3("git", ["-C", root, "ls-files"], {
15345
+ const { stdout } = await run4("git", ["-C", root, "ls-files"], {
15251
15346
  maxBuffer: 16 * 1048576
15252
15347
  });
15253
15348
  const files = [];
@@ -15286,13 +15381,13 @@ async function debtMain() {
15286
15381
  `);
15287
15382
  }
15288
15383
  }
15289
- var run3;
15384
+ var run4;
15290
15385
  var init_debt = __esm({
15291
15386
  "packages/cli/src/debt.ts"() {
15292
15387
  "use strict";
15293
15388
  init_src3();
15294
15389
  init_api_client();
15295
- run3 = promisify3(execFile3);
15390
+ run4 = promisify4(execFile4);
15296
15391
  }
15297
15392
  });
15298
15393
 
@@ -16409,7 +16504,7 @@ __export(hook_exports, {
16409
16504
  import {
16410
16505
  appendFileSync,
16411
16506
  existsSync as existsSync12,
16412
- mkdirSync as mkdirSync3,
16507
+ mkdirSync as mkdirSync4,
16413
16508
  readdirSync,
16414
16509
  readFileSync as readFileSync12,
16415
16510
  rmSync,
@@ -16504,7 +16599,7 @@ async function hookMain(argv) {
16504
16599
  if (out.reply) process.stdout.write(out.reply);
16505
16600
  const file = spoolPath(adapter.id, out.sessionId);
16506
16601
  if (out.events.length) {
16507
- mkdirSync3(path17.dirname(file), { recursive: true });
16602
+ mkdirSync4(path17.dirname(file), { recursive: true });
16508
16603
  appendFileSync(
16509
16604
  file,
16510
16605
  out.events.map((e) => JSON.stringify(clampCaptureEvent(e))).join("\n") + "\n"
@@ -16615,10 +16710,9 @@ async function syncRepo(root) {
16615
16710
  }
16616
16711
  } finally {
16617
16712
  try {
16618
- const { refreshBrief: refreshBrief2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
16619
- const r = await refreshBrief2(root);
16620
- if (r === "updated")
16621
- console.error("brainjar: AGENTS.md brief block updated");
16713
+ const { refreshCursorRules: refreshCursorRules2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
16714
+ if (await refreshCursorRules2(root) === "updated")
16715
+ console.error("brainjar: Cursor rule file refreshed");
16622
16716
  } catch {
16623
16717
  }
16624
16718
  }
@@ -16683,12 +16777,14 @@ async function init(argv) {
16683
16777
  step(2, "Connecting the project Brain\u2026");
16684
16778
  const known = await captureProject(root);
16685
16779
  const reuse = known && (!brainFlag || known.brainId === brainFlag) ? known : null;
16780
+ const remote = await repoRemote(root);
16686
16781
  const conn = await captureApi("/capture/connect", {
16687
16782
  method: "POST",
16688
16783
  body: {
16689
16784
  name,
16690
16785
  ...brainFlag ? { brainId: brainFlag } : reuse ? { brainId: reuse.brainId } : {},
16691
- ...reuse ? { sourceId: reuse.sourceId } : {}
16786
+ ...reuse ? { sourceId: reuse.sourceId } : {},
16787
+ ...remote ? { remote } : {}
16692
16788
  }
16693
16789
  });
16694
16790
  ok(
@@ -16783,15 +16879,20 @@ async function init(argv) {
16783
16879
  ok(
16784
16880
  gitHook ? "git post-commit hook installed" : "git post-commit hook already present"
16785
16881
  );
16786
- step(7, "Informed laziness \u2014 the brief block in AGENTS.md\u2026");
16882
+ step(7, "Informed laziness \u2014 the memory pointer in AGENTS.md\u2026");
16787
16883
  try {
16788
- const { refreshBrief: refreshBrief2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
16789
- const r = await refreshBrief2(root);
16884
+ const { installPointer: installPointer2, refreshCursorRules: refreshCursorRules2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
16885
+ const pointer = installPointer2(root);
16790
16886
  ok(
16791
- r === "updated" ? "brief block written (discipline core + reuse index, kept current by sync)" : r === "unchanged" ? "brief block already current" : "skipped (empty Brain or BRAINJAR_NO_BRIEF=1) \u2014 sync adds it once features exist"
16887
+ pointer === "updated" ? "AGENTS.md points to the Brain (static, never changes again); the brief itself arrives at every session start" : pointer === "unchanged" ? "AGENTS.md pointer already in place" : "skipped (BRAINJAR_NO_BRIEF=1)"
16792
16888
  );
16889
+ const cursor3 = await refreshCursorRules2(root);
16890
+ if (cursor3 !== "skipped")
16891
+ ok(
16892
+ `Cursor rule file ${cursor3} (.cursor/rules/brainjar.mdc, kept out of git)`
16893
+ );
16793
16894
  } catch {
16794
- ok("skipped (server unreachable) \u2014 the sync hook will add it");
16895
+ ok("skipped (could not write the pointer)");
16795
16896
  }
16796
16897
  try {
16797
16898
  await captureApi("/capture/connect", {
@@ -16892,13 +16993,6 @@ async function constraintsMain(argv) {
16892
16993
  else if (hook && argv.includes("--cline")) console.log(CLINE_REPLY);
16893
16994
  else if (!hook)
16894
16995
  console.log("No recorded failed approaches or rules for this Brain.");
16895
- if (hook) {
16896
- try {
16897
- const { refreshBrief: refreshBrief2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
16898
- await refreshBrief2(root);
16899
- } catch {
16900
- }
16901
- }
16902
16996
  } catch (e) {
16903
16997
  if (hook && e instanceof TokenError) console.log(envelope(e.message));
16904
16998
  else
@@ -16948,8 +17042,8 @@ __export(doctor_exports, {
16948
17042
  renderDoctorReport: () => renderDoctorReport,
16949
17043
  runDoctor: () => runDoctor
16950
17044
  });
16951
- import { execFile as execFile4 } from "node:child_process";
16952
- import { promisify as promisify4 } from "node:util";
17045
+ import { execFile as execFile5 } from "node:child_process";
17046
+ import { promisify as promisify5 } from "node:util";
16953
17047
  async function runDoctor(root, opts = {}) {
16954
17048
  const checks = [];
16955
17049
  const add = (name, ok2, detail) => checks.push({ name, ok: ok2, detail });
@@ -17019,7 +17113,7 @@ async function runDoctor(root, opts = {}) {
17019
17113
  }
17020
17114
  if (opts.runtime === false) return { checks, ok: checks.every((c) => c.ok) };
17021
17115
  try {
17022
- const { stdout } = await run4("npx", ["--yes", "tsx", "--version"], {
17116
+ const { stdout } = await run5("npx", ["--yes", "tsx", "--version"], {
17023
17117
  cwd: root,
17024
17118
  shell: true,
17025
17119
  // Windows resolves npx.cmd
@@ -17055,14 +17149,14 @@ brainjar doctor. repo: ${root}
17055
17149
  process.exitCode = 1;
17056
17150
  }
17057
17151
  }
17058
- var run4;
17152
+ var run5;
17059
17153
  var init_doctor = __esm({
17060
17154
  "packages/cli/src/doctor.ts"() {
17061
17155
  "use strict";
17062
17156
  init_src3();
17063
17157
  init_api_client();
17064
17158
  init_version_check();
17065
- run4 = promisify4(execFile4);
17159
+ run5 = promisify5(execFile5);
17066
17160
  }
17067
17161
  });
17068
17162
 
@@ -17352,7 +17446,7 @@ async function main() {
17352
17446
  return;
17353
17447
  }
17354
17448
  try {
17355
- await run5(cmd, argv);
17449
+ await run6(cmd, argv);
17356
17450
  } finally {
17357
17451
  if (cmd !== "hook" && !argv.includes("--hook")) {
17358
17452
  const { noticeNewerVersion: noticeNewerVersion2 } = await Promise.resolve().then(() => (init_version_check(), version_check_exports));
@@ -17360,7 +17454,7 @@ async function main() {
17360
17454
  }
17361
17455
  }
17362
17456
  }
17363
- async function run5(cmd, argv) {
17457
+ async function run6(cmd, argv) {
17364
17458
  switch (cmd) {
17365
17459
  case "brief": {
17366
17460
  const { briefMain: briefMain2 } = await Promise.resolve().then(() => (init_brief(), brief_exports));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainjar",
3
- "version": "0.3.702",
3
+ "version": "0.3.705",
4
4
  "description": "BrainJar CLI: turn a repository into a live, self-documenting Brain.",
5
5
  "type": "module",
6
6
  "bin": {