brainjar 0.3.688 → 0.3.694
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/brainjar.js +1227 -242
- 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.
|
|
110
|
+
CLI_VERSION = "0.3.694";
|
|
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(
|
|
242
|
-
let url = this.buildUrl(
|
|
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(
|
|
253
|
+
buildUrl(path19, params) {
|
|
254
254
|
const url = new URL(
|
|
255
|
-
`${this.baseUrl}/repos/${this.opts.owner}/${this.opts.repo}${
|
|
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
|
|
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 ?
|
|
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
|
|
3909
|
+
var path19 = __require("path");
|
|
3910
3910
|
var fs = __require("fs");
|
|
3911
3911
|
var helper = require_helper();
|
|
3912
3912
|
module.exports = function(connInfo, cb) {
|
|
@@ -5665,6 +5665,30 @@ function findEnvFile(startDir = process.cwd()) {
|
|
|
5665
5665
|
dir = parent;
|
|
5666
5666
|
}
|
|
5667
5667
|
}
|
|
5668
|
+
function projectsPath() {
|
|
5669
|
+
return path2.join(os2.homedir(), ".brainjar", "projects.json");
|
|
5670
|
+
}
|
|
5671
|
+
function projectKey(root) {
|
|
5672
|
+
return root.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
5673
|
+
}
|
|
5674
|
+
function readProjects(filePath = projectsPath()) {
|
|
5675
|
+
try {
|
|
5676
|
+
const parsed = JSON.parse(readFileSync2(filePath, "utf8"));
|
|
5677
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
5678
|
+
} catch {
|
|
5679
|
+
return {};
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
function projectLink(root, filePath = projectsPath()) {
|
|
5683
|
+
const link = readProjects(filePath)[projectKey(root)];
|
|
5684
|
+
return link && typeof link.brainId === "string" ? link : null;
|
|
5685
|
+
}
|
|
5686
|
+
function writeProjectLink(root, link, filePath = projectsPath()) {
|
|
5687
|
+
const all = readProjects(filePath);
|
|
5688
|
+
all[projectKey(root)] = link;
|
|
5689
|
+
mkdirSync2(path2.dirname(filePath), { recursive: true });
|
|
5690
|
+
writeFileSync2(filePath, JSON.stringify(all, null, 2) + "\n", "utf8");
|
|
5691
|
+
}
|
|
5668
5692
|
function userConfigPath() {
|
|
5669
5693
|
return path2.join(os2.homedir(), ".brainjar", "config");
|
|
5670
5694
|
}
|
|
@@ -5995,17 +6019,6 @@ var init_vector = __esm({
|
|
|
5995
6019
|
});
|
|
5996
6020
|
|
|
5997
6021
|
// packages/db/src/repository.ts
|
|
5998
|
-
async function brainForProject(root, pool2 = getPool()) {
|
|
5999
|
-
const normalized = root.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
6000
|
-
const r = await pool2.query(
|
|
6001
|
-
`SELECT brain_id FROM source
|
|
6002
|
-
WHERE kind = 'git_local'
|
|
6003
|
-
AND lower(regexp_replace(replace(config->>'cwd', '\\', '/'), '/+$', '')) = $1
|
|
6004
|
-
ORDER BY created_at DESC LIMIT 1`,
|
|
6005
|
-
[normalized]
|
|
6006
|
-
);
|
|
6007
|
-
return r.rows[0]?.brain_id ?? null;
|
|
6008
|
-
}
|
|
6009
6022
|
async function getBrain(id, pool2 = getPool()) {
|
|
6010
6023
|
return (await pool2.query("SELECT * FROM brain WHERE id = $1", [id])).rows[0] ?? null;
|
|
6011
6024
|
}
|
|
@@ -6102,17 +6115,6 @@ var init_repository = __esm({
|
|
|
6102
6115
|
const existing = (await this.listSources()).find((s) => s.kind === kind);
|
|
6103
6116
|
return existing ?? await this.createSource(kind, name);
|
|
6104
6117
|
}
|
|
6105
|
-
/**
|
|
6106
|
-
* The git_local source for THIS root (normalized like brainForProject), or null. One
|
|
6107
|
-
* Brain can hold many checkouts; matching "any git_local" silently swallowed every root
|
|
6108
|
-
* after the first (found 2026-08-21 via the benchmark's silent push).
|
|
6109
|
-
*/
|
|
6110
|
-
async gitLocalSource(root) {
|
|
6111
|
-
const normalized = root.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
6112
|
-
return (await this.listSources()).find(
|
|
6113
|
-
(s) => s.kind === "git_local" && String(s.config.cwd ?? "").replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase() === normalized
|
|
6114
|
-
) ?? null;
|
|
6115
|
-
}
|
|
6116
6118
|
async listSources() {
|
|
6117
6119
|
return (await this.pool.query(
|
|
6118
6120
|
"SELECT * FROM source WHERE brain_id = $1 ORDER BY created_at",
|
|
@@ -8850,12 +8852,12 @@ var init_path = __esm({
|
|
|
8850
8852
|
"node_modules/@anthropic-ai/sdk/internal/utils/path.mjs"() {
|
|
8851
8853
|
init_error();
|
|
8852
8854
|
EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
8853
|
-
createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
8855
|
+
createPathTagFunction = (pathEncoder = encodeURIPath) => function path19(statics, ...params) {
|
|
8854
8856
|
if (statics.length === 1)
|
|
8855
8857
|
return statics[0];
|
|
8856
8858
|
let postPath = false;
|
|
8857
8859
|
const invalidSegments = [];
|
|
8858
|
-
const
|
|
8860
|
+
const path20 = statics.reduce((previousValue, currentValue, index) => {
|
|
8859
8861
|
if (/[?#]/.test(currentValue)) {
|
|
8860
8862
|
postPath = true;
|
|
8861
8863
|
}
|
|
@@ -8872,7 +8874,7 @@ var init_path = __esm({
|
|
|
8872
8874
|
}
|
|
8873
8875
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
8874
8876
|
}, "");
|
|
8875
|
-
const pathOnly =
|
|
8877
|
+
const pathOnly = path20.split(/[?#]/, 1)[0];
|
|
8876
8878
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
8877
8879
|
let match;
|
|
8878
8880
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -8893,10 +8895,10 @@ var init_path = __esm({
|
|
|
8893
8895
|
}, "");
|
|
8894
8896
|
throw new AnthropicError(`Path parameters result in path with invalid segments:
|
|
8895
8897
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
8896
|
-
${
|
|
8898
|
+
${path20}
|
|
8897
8899
|
${underline}`);
|
|
8898
8900
|
}
|
|
8899
|
-
return
|
|
8901
|
+
return path20;
|
|
8900
8902
|
};
|
|
8901
8903
|
path3 = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
8902
8904
|
}
|
|
@@ -11882,9 +11884,9 @@ var init_client = __esm({
|
|
|
11882
11884
|
makeStatusError(status, error, message, headers) {
|
|
11883
11885
|
return APIError.generate(status, error, message, headers);
|
|
11884
11886
|
}
|
|
11885
|
-
buildURL(
|
|
11887
|
+
buildURL(path19, query, defaultBaseURL) {
|
|
11886
11888
|
const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
11887
|
-
const url = isAbsoluteURL(
|
|
11889
|
+
const url = isAbsoluteURL(path19) ? new URL(path19) : new URL(baseURL + (baseURL.endsWith("/") && path19.startsWith("/") ? path19.slice(1) : path19));
|
|
11888
11890
|
const defaultQuery = this.defaultQuery();
|
|
11889
11891
|
if (!isEmptyObj(defaultQuery)) {
|
|
11890
11892
|
query = { ...defaultQuery, ...query };
|
|
@@ -11915,24 +11917,24 @@ var init_client = __esm({
|
|
|
11915
11917
|
*/
|
|
11916
11918
|
async prepareRequest(request, { url, options }) {
|
|
11917
11919
|
}
|
|
11918
|
-
get(
|
|
11919
|
-
return this.methodRequest("get",
|
|
11920
|
+
get(path19, opts) {
|
|
11921
|
+
return this.methodRequest("get", path19, opts);
|
|
11920
11922
|
}
|
|
11921
|
-
post(
|
|
11922
|
-
return this.methodRequest("post",
|
|
11923
|
+
post(path19, opts) {
|
|
11924
|
+
return this.methodRequest("post", path19, opts);
|
|
11923
11925
|
}
|
|
11924
|
-
patch(
|
|
11925
|
-
return this.methodRequest("patch",
|
|
11926
|
+
patch(path19, opts) {
|
|
11927
|
+
return this.methodRequest("patch", path19, opts);
|
|
11926
11928
|
}
|
|
11927
|
-
put(
|
|
11928
|
-
return this.methodRequest("put",
|
|
11929
|
+
put(path19, opts) {
|
|
11930
|
+
return this.methodRequest("put", path19, opts);
|
|
11929
11931
|
}
|
|
11930
|
-
delete(
|
|
11931
|
-
return this.methodRequest("delete",
|
|
11932
|
+
delete(path19, opts) {
|
|
11933
|
+
return this.methodRequest("delete", path19, opts);
|
|
11932
11934
|
}
|
|
11933
|
-
methodRequest(method,
|
|
11935
|
+
methodRequest(method, path19, opts) {
|
|
11934
11936
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
11935
|
-
return { method, path:
|
|
11937
|
+
return { method, path: path19, ...opts2 };
|
|
11936
11938
|
}));
|
|
11937
11939
|
}
|
|
11938
11940
|
request(options, remainingRetries = null) {
|
|
@@ -12036,8 +12038,8 @@ var init_client = __esm({
|
|
|
12036
12038
|
}));
|
|
12037
12039
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
12038
12040
|
}
|
|
12039
|
-
getAPIList(
|
|
12040
|
-
return this.requestAPIList(Page2, { method: "get", path:
|
|
12041
|
+
getAPIList(path19, Page2, opts) {
|
|
12042
|
+
return this.requestAPIList(Page2, { method: "get", path: path19, ...opts });
|
|
12041
12043
|
}
|
|
12042
12044
|
requestAPIList(Page2, options) {
|
|
12043
12045
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -12124,8 +12126,8 @@ var init_client = __esm({
|
|
|
12124
12126
|
}
|
|
12125
12127
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
12126
12128
|
const options = { ...inputOptions };
|
|
12127
|
-
const { method, path:
|
|
12128
|
-
const url = this.buildURL(
|
|
12129
|
+
const { method, path: path19, query, defaultBaseURL } = options;
|
|
12130
|
+
const url = this.buildURL(path19, query, defaultBaseURL);
|
|
12129
12131
|
if ("timeout" in options)
|
|
12130
12132
|
validatePositiveInteger("timeout", options.timeout);
|
|
12131
12133
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -12602,12 +12604,25 @@ async function repoRoot(cwd = process.cwd()) {
|
|
|
12602
12604
|
function repoName(root) {
|
|
12603
12605
|
return path5.basename(root) || "project";
|
|
12604
12606
|
}
|
|
12605
|
-
async function
|
|
12607
|
+
async function mainRepoRoot(root) {
|
|
12606
12608
|
try {
|
|
12607
|
-
|
|
12609
|
+
const { stdout } = await run2(
|
|
12610
|
+
"git",
|
|
12611
|
+
["rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
12612
|
+
{ cwd: root, windowsHide: true }
|
|
12613
|
+
);
|
|
12614
|
+
const common = path5.resolve(root, stdout.trim());
|
|
12615
|
+
if (path5.basename(common) === ".git") return path5.dirname(common);
|
|
12608
12616
|
} catch {
|
|
12609
|
-
return null;
|
|
12610
12617
|
}
|
|
12618
|
+
return root;
|
|
12619
|
+
}
|
|
12620
|
+
async function captureProject(root, filePath) {
|
|
12621
|
+
const top = await repoRoot(root);
|
|
12622
|
+
return projectLink(top, filePath) ?? projectLink(await mainRepoRoot(top), filePath);
|
|
12623
|
+
}
|
|
12624
|
+
async function resolveCaptureBrain(root, filePath) {
|
|
12625
|
+
return (await captureProject(root, filePath))?.brainId ?? null;
|
|
12611
12626
|
}
|
|
12612
12627
|
async function transcriptFiles(root) {
|
|
12613
12628
|
const encoded = root.replace(/[:\\/]/g, "-");
|
|
@@ -12704,22 +12719,22 @@ async function gitHooksDir(root) {
|
|
|
12704
12719
|
return path5.join(root, ".git", "hooks");
|
|
12705
12720
|
}
|
|
12706
12721
|
async function installGitPostCommitHook(root) {
|
|
12707
|
-
const
|
|
12708
|
-
const file = path5.join(
|
|
12722
|
+
const hooksDir2 = await gitHooksDir(root);
|
|
12723
|
+
const file = path5.join(hooksDir2, "post-commit");
|
|
12709
12724
|
const existing = existsSync2(file) ? await readFile2(file, "utf8") : null;
|
|
12710
12725
|
if (existing?.includes(GIT_HOOK_MARKER)) return false;
|
|
12711
12726
|
const content = existing === null ? `#!/bin/sh
|
|
12712
12727
|
${gitHookBlock()}
|
|
12713
12728
|
` : existing.replace(/\n*$/, "\n") + gitHookBlock() + "\n";
|
|
12714
|
-
await mkdir(
|
|
12729
|
+
await mkdir(hooksDir2, { recursive: true });
|
|
12715
12730
|
await writeFile(file, content, { mode: 493 });
|
|
12716
12731
|
await chmod(file, 493).catch(() => {
|
|
12717
12732
|
});
|
|
12718
12733
|
return true;
|
|
12719
12734
|
}
|
|
12720
12735
|
async function removeGitPostCommitHook(root) {
|
|
12721
|
-
const
|
|
12722
|
-
const file = path5.join(
|
|
12736
|
+
const hooksDir2 = await gitHooksDir(root);
|
|
12737
|
+
const file = path5.join(hooksDir2, "post-commit");
|
|
12723
12738
|
if (!existsSync2(file)) return false;
|
|
12724
12739
|
const content = await readFile2(file, "utf8");
|
|
12725
12740
|
if (!content.includes(GIT_HOOK_MARKER)) return false;
|
|
@@ -12790,6 +12805,7 @@ var init_bootstrap = __esm({
|
|
|
12790
12805
|
"packages/ingest/src/bootstrap.ts"() {
|
|
12791
12806
|
"use strict";
|
|
12792
12807
|
init_src2();
|
|
12808
|
+
init_src();
|
|
12793
12809
|
init_sync();
|
|
12794
12810
|
init_extraction();
|
|
12795
12811
|
init_structure();
|
|
@@ -13115,14 +13131,367 @@ var init_copilot_hooks = __esm({
|
|
|
13115
13131
|
}
|
|
13116
13132
|
});
|
|
13117
13133
|
|
|
13134
|
+
// packages/ingest/src/gemini-hooks.ts
|
|
13135
|
+
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5 } from "node:fs/promises";
|
|
13136
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
|
|
13137
|
+
import path9 from "node:path";
|
|
13138
|
+
async function installGeminiHooks(root) {
|
|
13139
|
+
const file = settingsFile(root);
|
|
13140
|
+
let settings = {};
|
|
13141
|
+
if (existsSync6(file)) {
|
|
13142
|
+
try {
|
|
13143
|
+
settings = JSON.parse(await readFile6(file, "utf8"));
|
|
13144
|
+
} catch {
|
|
13145
|
+
settings = {};
|
|
13146
|
+
}
|
|
13147
|
+
}
|
|
13148
|
+
const hooks = settings.hooks ??= {};
|
|
13149
|
+
let changed = false;
|
|
13150
|
+
for (const { event, subcommand, probe, timeout } of EVENTS4) {
|
|
13151
|
+
const groups = hooks[event] ??= [];
|
|
13152
|
+
if (groups.some((g) => g.hooks?.some((h) => h.command?.includes(probe))))
|
|
13153
|
+
continue;
|
|
13154
|
+
groups.push({
|
|
13155
|
+
hooks: [
|
|
13156
|
+
{
|
|
13157
|
+
type: "command",
|
|
13158
|
+
command: captureHookCommand(subcommand),
|
|
13159
|
+
name: "brainjar",
|
|
13160
|
+
timeout
|
|
13161
|
+
}
|
|
13162
|
+
]
|
|
13163
|
+
});
|
|
13164
|
+
changed = true;
|
|
13165
|
+
}
|
|
13166
|
+
if (!changed) return false;
|
|
13167
|
+
await mkdir5(path9.dirname(file), { recursive: true });
|
|
13168
|
+
await writeFile5(file, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
13169
|
+
return true;
|
|
13170
|
+
}
|
|
13171
|
+
function geminiHooksInstalled(root) {
|
|
13172
|
+
const file = settingsFile(root);
|
|
13173
|
+
if (!existsSync6(file)) return false;
|
|
13174
|
+
try {
|
|
13175
|
+
return readFileSync7(file, "utf8").includes(PROBE3);
|
|
13176
|
+
} catch {
|
|
13177
|
+
return false;
|
|
13178
|
+
}
|
|
13179
|
+
}
|
|
13180
|
+
async function removeGeminiHooks(root) {
|
|
13181
|
+
const file = settingsFile(root);
|
|
13182
|
+
if (!existsSync6(file)) return false;
|
|
13183
|
+
let settings;
|
|
13184
|
+
try {
|
|
13185
|
+
settings = JSON.parse(await readFile6(file, "utf8"));
|
|
13186
|
+
} catch {
|
|
13187
|
+
return false;
|
|
13188
|
+
}
|
|
13189
|
+
const hooks = settings.hooks;
|
|
13190
|
+
if (!hooks) return false;
|
|
13191
|
+
let changed = false;
|
|
13192
|
+
for (const event of Object.keys(hooks)) {
|
|
13193
|
+
const groups = hooks[event];
|
|
13194
|
+
if (!Array.isArray(groups)) continue;
|
|
13195
|
+
const kept = groups.filter((g) => !g.hooks?.some(ours2));
|
|
13196
|
+
if (kept.length !== groups.length) {
|
|
13197
|
+
changed = true;
|
|
13198
|
+
if (kept.length === 0) delete hooks[event];
|
|
13199
|
+
else hooks[event] = kept;
|
|
13200
|
+
}
|
|
13201
|
+
}
|
|
13202
|
+
if (!changed) return false;
|
|
13203
|
+
await writeFile5(file, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
13204
|
+
return true;
|
|
13205
|
+
}
|
|
13206
|
+
var PROBE3, EVENTS4, PROBES2, settingsFile, ours2;
|
|
13207
|
+
var init_gemini_hooks = __esm({
|
|
13208
|
+
"packages/ingest/src/gemini-hooks.ts"() {
|
|
13209
|
+
"use strict";
|
|
13210
|
+
init_bootstrap();
|
|
13211
|
+
PROBE3 = "hook gemini";
|
|
13212
|
+
EVENTS4 = [
|
|
13213
|
+
{
|
|
13214
|
+
event: "SessionStart",
|
|
13215
|
+
subcommand: "constraints",
|
|
13216
|
+
probe: "constraints --hook",
|
|
13217
|
+
timeout: 15e3
|
|
13218
|
+
},
|
|
13219
|
+
{
|
|
13220
|
+
event: "BeforeAgent",
|
|
13221
|
+
subcommand: "hook gemini",
|
|
13222
|
+
probe: PROBE3,
|
|
13223
|
+
timeout: 3e4
|
|
13224
|
+
},
|
|
13225
|
+
{
|
|
13226
|
+
event: "AfterTool",
|
|
13227
|
+
subcommand: "hook gemini",
|
|
13228
|
+
probe: PROBE3,
|
|
13229
|
+
timeout: 3e4
|
|
13230
|
+
},
|
|
13231
|
+
{
|
|
13232
|
+
event: "AfterAgent",
|
|
13233
|
+
subcommand: "hook gemini",
|
|
13234
|
+
probe: PROBE3,
|
|
13235
|
+
timeout: 12e4
|
|
13236
|
+
},
|
|
13237
|
+
{
|
|
13238
|
+
event: "SessionEnd",
|
|
13239
|
+
subcommand: "hook gemini",
|
|
13240
|
+
probe: PROBE3,
|
|
13241
|
+
timeout: 1e4
|
|
13242
|
+
}
|
|
13243
|
+
];
|
|
13244
|
+
PROBES2 = EVENTS4.map((e) => e.probe);
|
|
13245
|
+
settingsFile = (root) => path9.join(root, ".gemini", "settings.json");
|
|
13246
|
+
ours2 = (h) => PROBES2.some((p) => h.command?.includes(p) ?? false);
|
|
13247
|
+
}
|
|
13248
|
+
});
|
|
13249
|
+
|
|
13250
|
+
// packages/ingest/src/kiro-hooks.ts
|
|
13251
|
+
import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir6, rm as rm3 } from "node:fs/promises";
|
|
13252
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
|
|
13253
|
+
import path10 from "node:path";
|
|
13254
|
+
function render2() {
|
|
13255
|
+
const hook = (name, trigger, command, timeout) => ({ name, trigger, action: { type: "command", command }, timeout });
|
|
13256
|
+
return JSON.stringify(
|
|
13257
|
+
{
|
|
13258
|
+
version: "v1",
|
|
13259
|
+
hooks: [
|
|
13260
|
+
hook(
|
|
13261
|
+
"brainjar-context",
|
|
13262
|
+
"SessionStart",
|
|
13263
|
+
`${captureHookCommand("constraints")} --plain`,
|
|
13264
|
+
15
|
|
13265
|
+
),
|
|
13266
|
+
hook(
|
|
13267
|
+
"brainjar-prompt",
|
|
13268
|
+
"UserPromptSubmit",
|
|
13269
|
+
captureHookCommand("hook kiro"),
|
|
13270
|
+
30
|
|
13271
|
+
),
|
|
13272
|
+
hook(
|
|
13273
|
+
"brainjar-tool",
|
|
13274
|
+
"PostToolUse",
|
|
13275
|
+
captureHookCommand("hook kiro"),
|
|
13276
|
+
30
|
|
13277
|
+
),
|
|
13278
|
+
hook("brainjar-stop", "Stop", captureHookCommand("hook kiro"), 120)
|
|
13279
|
+
]
|
|
13280
|
+
},
|
|
13281
|
+
null,
|
|
13282
|
+
2
|
|
13283
|
+
) + "\n";
|
|
13284
|
+
}
|
|
13285
|
+
async function installKiroHooks(root) {
|
|
13286
|
+
const file = hooksFile4(root);
|
|
13287
|
+
const next = render2();
|
|
13288
|
+
if (existsSync7(file) && await readFile7(file, "utf8") === next) return false;
|
|
13289
|
+
await mkdir6(path10.dirname(file), { recursive: true });
|
|
13290
|
+
await writeFile6(file, next, "utf8");
|
|
13291
|
+
return true;
|
|
13292
|
+
}
|
|
13293
|
+
function kiroHooksInstalled(root) {
|
|
13294
|
+
const file = hooksFile4(root);
|
|
13295
|
+
if (!existsSync7(file)) return false;
|
|
13296
|
+
try {
|
|
13297
|
+
return readFileSync8(file, "utf8").includes(PROBE4);
|
|
13298
|
+
} catch {
|
|
13299
|
+
return false;
|
|
13300
|
+
}
|
|
13301
|
+
}
|
|
13302
|
+
async function removeKiroHooks(root) {
|
|
13303
|
+
if (!kiroHooksInstalled(root)) return false;
|
|
13304
|
+
await rm3(hooksFile4(root), { force: true });
|
|
13305
|
+
return true;
|
|
13306
|
+
}
|
|
13307
|
+
var PROBE4, hooksFile4;
|
|
13308
|
+
var init_kiro_hooks = __esm({
|
|
13309
|
+
"packages/ingest/src/kiro-hooks.ts"() {
|
|
13310
|
+
"use strict";
|
|
13311
|
+
init_bootstrap();
|
|
13312
|
+
PROBE4 = "hook kiro";
|
|
13313
|
+
hooksFile4 = (root) => path10.join(root, ".kiro", "hooks", "brainjar.json");
|
|
13314
|
+
}
|
|
13315
|
+
});
|
|
13316
|
+
|
|
13317
|
+
// packages/ingest/src/opencode-plugin.ts
|
|
13318
|
+
import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7, rm as rm4 } from "node:fs/promises";
|
|
13319
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
13320
|
+
import path11 from "node:path";
|
|
13321
|
+
function render3() {
|
|
13322
|
+
const cmd = captureHookCommand("hook opencode");
|
|
13323
|
+
return `// BrainJar capture plugin, written by \`brainjar init\` (rewritten on every init; do not edit).
|
|
13324
|
+
// Ships prompts, tool calls, failures, and touched files of every OpenCode session to your Brain.
|
|
13325
|
+
import { spawn } from "node:child_process";
|
|
13326
|
+
|
|
13327
|
+
export const BrainJar = async ({ directory }) => {
|
|
13328
|
+
let session = "";
|
|
13329
|
+
const seen = new Set();
|
|
13330
|
+
const send = (payload) => {
|
|
13331
|
+
try {
|
|
13332
|
+
const child = spawn(${JSON.stringify(cmd)}, {
|
|
13333
|
+
shell: true,
|
|
13334
|
+
cwd: directory,
|
|
13335
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
13336
|
+
});
|
|
13337
|
+
child.on("error", () => {});
|
|
13338
|
+
child.stdin.end(
|
|
13339
|
+
JSON.stringify({ source: "opencode", cwd: directory, session_id: session, ...payload }),
|
|
13340
|
+
);
|
|
13341
|
+
} catch {}
|
|
13342
|
+
};
|
|
13343
|
+
const target = (args) => {
|
|
13344
|
+
if (!args || typeof args !== "object") return "";
|
|
13345
|
+
for (const k of ["command", "filePath", "path"]) if (typeof args[k] === "string") return args[k];
|
|
13346
|
+
return "";
|
|
13347
|
+
};
|
|
13348
|
+
return {
|
|
13349
|
+
event: async ({ event }) => {
|
|
13350
|
+
const p = event.properties ?? {};
|
|
13351
|
+
switch (event.type) {
|
|
13352
|
+
case "session.created":
|
|
13353
|
+
session = p.info?.id ?? session;
|
|
13354
|
+
send({ kind: "session_started" });
|
|
13355
|
+
break;
|
|
13356
|
+
case "session.idle":
|
|
13357
|
+
if (p.sessionID) session = p.sessionID;
|
|
13358
|
+
send({ kind: "turn_ended" });
|
|
13359
|
+
break;
|
|
13360
|
+
case "file.edited":
|
|
13361
|
+
send({ kind: "file_changed", path: p.file });
|
|
13362
|
+
break;
|
|
13363
|
+
case "message.part.updated": {
|
|
13364
|
+
const part = p.part;
|
|
13365
|
+
if (part?.type === "tool" && part.state?.status === "error" && !seen.has(part.callID)) {
|
|
13366
|
+
seen.add(part.callID);
|
|
13367
|
+
session = part.sessionID ?? session;
|
|
13368
|
+
send({ kind: "tool_failed", tool_name: part.tool, target: target(part.state.input), error: String(part.state.error ?? "") });
|
|
13369
|
+
}
|
|
13370
|
+
break;
|
|
13371
|
+
}
|
|
13372
|
+
}
|
|
13373
|
+
},
|
|
13374
|
+
"chat.message": async (input, output) => {
|
|
13375
|
+
session = input.sessionID ?? session;
|
|
13376
|
+
const text = (output.parts ?? []).filter((x) => x.type === "text").map((x) => x.text).join("\\n");
|
|
13377
|
+
if (text.trim()) send({ kind: "prompt", text });
|
|
13378
|
+
},
|
|
13379
|
+
"tool.execute.after": async (input, output) => {
|
|
13380
|
+
session = input.sessionID ?? session;
|
|
13381
|
+
send({ kind: "tool_used", tool_name: input.tool, target: target(input.args), output: String(output.output ?? "").slice(0, 4000) });
|
|
13382
|
+
},
|
|
13383
|
+
};
|
|
13384
|
+
};
|
|
13385
|
+
`;
|
|
13386
|
+
}
|
|
13387
|
+
async function installOpencodePlugin(root) {
|
|
13388
|
+
const file = pluginFile(root);
|
|
13389
|
+
const next = render3();
|
|
13390
|
+
if (existsSync8(file) && await readFile8(file, "utf8") === next) return false;
|
|
13391
|
+
await mkdir7(path11.dirname(file), { recursive: true });
|
|
13392
|
+
await writeFile7(file, next, "utf8");
|
|
13393
|
+
return true;
|
|
13394
|
+
}
|
|
13395
|
+
function opencodePluginInstalled(root) {
|
|
13396
|
+
const file = pluginFile(root);
|
|
13397
|
+
if (!existsSync8(file)) return false;
|
|
13398
|
+
try {
|
|
13399
|
+
return readFileSync9(file, "utf8").includes(PROBE5);
|
|
13400
|
+
} catch {
|
|
13401
|
+
return false;
|
|
13402
|
+
}
|
|
13403
|
+
}
|
|
13404
|
+
async function removeOpencodePlugin(root) {
|
|
13405
|
+
if (!opencodePluginInstalled(root)) return false;
|
|
13406
|
+
await rm4(pluginFile(root), { force: true });
|
|
13407
|
+
return true;
|
|
13408
|
+
}
|
|
13409
|
+
var PROBE5, pluginFile;
|
|
13410
|
+
var init_opencode_plugin = __esm({
|
|
13411
|
+
"packages/ingest/src/opencode-plugin.ts"() {
|
|
13412
|
+
"use strict";
|
|
13413
|
+
init_bootstrap();
|
|
13414
|
+
PROBE5 = "hook opencode";
|
|
13415
|
+
pluginFile = (root) => path11.join(root, ".opencode", "plugins", "brainjar.ts");
|
|
13416
|
+
}
|
|
13417
|
+
});
|
|
13418
|
+
|
|
13419
|
+
// packages/ingest/src/cline-hooks.ts
|
|
13420
|
+
import { readFile as readFile9, writeFile as writeFile8, mkdir as mkdir8, rm as rm5, chmod as chmod2 } from "node:fs/promises";
|
|
13421
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
|
|
13422
|
+
import path12 from "node:path";
|
|
13423
|
+
async function installClineHooks(root) {
|
|
13424
|
+
const dir = hooksDir(root);
|
|
13425
|
+
let changed = false;
|
|
13426
|
+
for (const { name, subcommand, flag } of SCRIPTS) {
|
|
13427
|
+
const file = path12.join(dir, name);
|
|
13428
|
+
const next = script(subcommand, flag);
|
|
13429
|
+
if (existsSync9(file)) {
|
|
13430
|
+
const current = await readFile9(file, "utf8");
|
|
13431
|
+
if (current === next) continue;
|
|
13432
|
+
if (!current.includes(MARK)) continue;
|
|
13433
|
+
}
|
|
13434
|
+
await mkdir8(dir, { recursive: true });
|
|
13435
|
+
await writeFile8(file, next, { mode: 493 });
|
|
13436
|
+
await chmod2(file, 493).catch(() => {
|
|
13437
|
+
});
|
|
13438
|
+
changed = true;
|
|
13439
|
+
}
|
|
13440
|
+
return changed;
|
|
13441
|
+
}
|
|
13442
|
+
function clineHooksInstalled(root) {
|
|
13443
|
+
const file = path12.join(hooksDir(root), "TaskComplete");
|
|
13444
|
+
if (!existsSync9(file)) return false;
|
|
13445
|
+
try {
|
|
13446
|
+
return readFileSync10(file, "utf8").includes("hook cline");
|
|
13447
|
+
} catch {
|
|
13448
|
+
return false;
|
|
13449
|
+
}
|
|
13450
|
+
}
|
|
13451
|
+
async function removeClineHooks(root) {
|
|
13452
|
+
let changed = false;
|
|
13453
|
+
for (const { name } of SCRIPTS) {
|
|
13454
|
+
const file = path12.join(hooksDir(root), name);
|
|
13455
|
+
if (!existsSync9(file)) continue;
|
|
13456
|
+
try {
|
|
13457
|
+
if (!readFileSync10(file, "utf8").includes(MARK)) continue;
|
|
13458
|
+
} catch {
|
|
13459
|
+
continue;
|
|
13460
|
+
}
|
|
13461
|
+
await rm5(file, { force: true });
|
|
13462
|
+
changed = true;
|
|
13463
|
+
}
|
|
13464
|
+
return changed;
|
|
13465
|
+
}
|
|
13466
|
+
var MARK, SCRIPTS, hooksDir, script;
|
|
13467
|
+
var init_cline_hooks = __esm({
|
|
13468
|
+
"packages/ingest/src/cline-hooks.ts"() {
|
|
13469
|
+
"use strict";
|
|
13470
|
+
init_bootstrap();
|
|
13471
|
+
MARK = "# brainjar-capture";
|
|
13472
|
+
SCRIPTS = [
|
|
13473
|
+
{ name: "TaskStart", subcommand: "constraints", flag: " --cline" },
|
|
13474
|
+
{ name: "UserPromptSubmit", subcommand: "hook cline", flag: "" },
|
|
13475
|
+
{ name: "PostToolUse", subcommand: "hook cline", flag: "" },
|
|
13476
|
+
{ name: "TaskComplete", subcommand: "hook cline", flag: "" },
|
|
13477
|
+
{ name: "TaskCancel", subcommand: "hook cline", flag: "" }
|
|
13478
|
+
];
|
|
13479
|
+
hooksDir = (root) => path12.join(root, ".clinerules", "hooks");
|
|
13480
|
+
script = (subcommand, flag) => `#!/bin/sh
|
|
13481
|
+
${MARK}
|
|
13482
|
+
exec ${captureHookCommand(subcommand)}${flag}
|
|
13483
|
+
`;
|
|
13484
|
+
}
|
|
13485
|
+
});
|
|
13486
|
+
|
|
13118
13487
|
// packages/ingest/src/tools.ts
|
|
13119
|
-
import { existsSync as
|
|
13488
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
13120
13489
|
import os4 from "node:os";
|
|
13121
|
-
import
|
|
13490
|
+
import path13 from "node:path";
|
|
13122
13491
|
function detectedTools(root) {
|
|
13123
13492
|
return CAPTURE_TOOLS.filter((t) => t.detect(root));
|
|
13124
13493
|
}
|
|
13125
|
-
var claudeCode, cursor, codex, copilot, CAPTURE_TOOLS;
|
|
13494
|
+
var claudeCode, cursor, codex, copilot, gemini, kiro, opencode, cline, CAPTURE_TOOLS;
|
|
13126
13495
|
var init_tools = __esm({
|
|
13127
13496
|
"packages/ingest/src/tools.ts"() {
|
|
13128
13497
|
"use strict";
|
|
@@ -13130,10 +13499,14 @@ var init_tools = __esm({
|
|
|
13130
13499
|
init_cursor_hooks();
|
|
13131
13500
|
init_codex_hooks();
|
|
13132
13501
|
init_copilot_hooks();
|
|
13502
|
+
init_gemini_hooks();
|
|
13503
|
+
init_kiro_hooks();
|
|
13504
|
+
init_opencode_plugin();
|
|
13505
|
+
init_cline_hooks();
|
|
13133
13506
|
claudeCode = {
|
|
13134
13507
|
id: "claude-code",
|
|
13135
13508
|
name: "Claude Code",
|
|
13136
|
-
detect: (root) =>
|
|
13509
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), ".claude")) || existsSync10(path13.join(root, ".claude")),
|
|
13137
13510
|
install: installCaptureHook,
|
|
13138
13511
|
installed: captureHookInstalled,
|
|
13139
13512
|
remove: removeCaptureHook
|
|
@@ -13141,7 +13514,7 @@ var init_tools = __esm({
|
|
|
13141
13514
|
cursor = {
|
|
13142
13515
|
id: "cursor",
|
|
13143
13516
|
name: "Cursor",
|
|
13144
|
-
detect: (root) =>
|
|
13517
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), ".cursor")) || existsSync10(path13.join(root, ".cursor")),
|
|
13145
13518
|
install: installCursorHooks,
|
|
13146
13519
|
installed: cursorHooksInstalled,
|
|
13147
13520
|
remove: removeCursorHooks
|
|
@@ -13149,7 +13522,7 @@ var init_tools = __esm({
|
|
|
13149
13522
|
codex = {
|
|
13150
13523
|
id: "codex",
|
|
13151
13524
|
name: "Codex",
|
|
13152
|
-
detect: (root) =>
|
|
13525
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), ".codex")) || existsSync10(path13.join(root, ".codex")),
|
|
13153
13526
|
install: installCodexHooks,
|
|
13154
13527
|
installed: codexHooksInstalled,
|
|
13155
13528
|
remove: removeCodexHooks
|
|
@@ -13157,16 +13530,52 @@ var init_tools = __esm({
|
|
|
13157
13530
|
copilot = {
|
|
13158
13531
|
id: "copilot",
|
|
13159
13532
|
name: "GitHub Copilot",
|
|
13160
|
-
detect: (root) =>
|
|
13533
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), ".copilot")) || existsSync10(path13.join(root, ".github", "hooks")),
|
|
13161
13534
|
install: installCopilotHooks,
|
|
13162
13535
|
installed: copilotHooksInstalled,
|
|
13163
13536
|
remove: removeCopilotHooks
|
|
13164
13537
|
};
|
|
13538
|
+
gemini = {
|
|
13539
|
+
id: "gemini",
|
|
13540
|
+
name: "Gemini CLI",
|
|
13541
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), ".gemini")) || existsSync10(path13.join(root, ".gemini")),
|
|
13542
|
+
install: installGeminiHooks,
|
|
13543
|
+
installed: geminiHooksInstalled,
|
|
13544
|
+
remove: removeGeminiHooks
|
|
13545
|
+
};
|
|
13546
|
+
kiro = {
|
|
13547
|
+
id: "kiro",
|
|
13548
|
+
name: "Kiro",
|
|
13549
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), ".kiro")) || existsSync10(path13.join(root, ".kiro")),
|
|
13550
|
+
install: installKiroHooks,
|
|
13551
|
+
installed: kiroHooksInstalled,
|
|
13552
|
+
remove: removeKiroHooks
|
|
13553
|
+
};
|
|
13554
|
+
opencode = {
|
|
13555
|
+
id: "opencode",
|
|
13556
|
+
name: "OpenCode",
|
|
13557
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), ".config", "opencode")) || existsSync10(path13.join(root, ".opencode")),
|
|
13558
|
+
install: installOpencodePlugin,
|
|
13559
|
+
installed: opencodePluginInstalled,
|
|
13560
|
+
remove: removeOpencodePlugin
|
|
13561
|
+
};
|
|
13562
|
+
cline = {
|
|
13563
|
+
id: "cline",
|
|
13564
|
+
name: "Cline",
|
|
13565
|
+
detect: (root) => existsSync10(path13.join(os4.homedir(), "Documents", "Cline")) || existsSync10(path13.join(os4.homedir(), ".cline")) || existsSync10(path13.join(root, ".clinerules")),
|
|
13566
|
+
install: installClineHooks,
|
|
13567
|
+
installed: clineHooksInstalled,
|
|
13568
|
+
remove: removeClineHooks
|
|
13569
|
+
};
|
|
13165
13570
|
CAPTURE_TOOLS = [
|
|
13166
13571
|
claudeCode,
|
|
13167
13572
|
cursor,
|
|
13168
13573
|
codex,
|
|
13169
|
-
copilot
|
|
13574
|
+
copilot,
|
|
13575
|
+
gemini,
|
|
13576
|
+
kiro,
|
|
13577
|
+
opencode,
|
|
13578
|
+
cline
|
|
13170
13579
|
];
|
|
13171
13580
|
}
|
|
13172
13581
|
});
|
|
@@ -13643,8 +14052,8 @@ var init_doku_model = __esm({
|
|
|
13643
14052
|
|
|
13644
14053
|
// packages/ingest/src/doku.ts
|
|
13645
14054
|
import { createHash as createHash2 } from "node:crypto";
|
|
13646
|
-
import { mkdir as
|
|
13647
|
-
import
|
|
14055
|
+
import { mkdir as mkdir9, readFile as readFile10, rm as rm6, stat, writeFile as writeFile9 } from "node:fs/promises";
|
|
14056
|
+
import path14 from "node:path";
|
|
13648
14057
|
function bodyLocations(o) {
|
|
13649
14058
|
return Array.isArray(o.body.location) ? o.body.location.map(String) : [];
|
|
13650
14059
|
}
|
|
@@ -13710,7 +14119,7 @@ async function loadDokuData(brainId, root) {
|
|
|
13710
14119
|
if (root) {
|
|
13711
14120
|
try {
|
|
13712
14121
|
const pkg = JSON.parse(
|
|
13713
|
-
await
|
|
14122
|
+
await readFile10(path14.join(root, "package.json"), "utf8")
|
|
13714
14123
|
);
|
|
13715
14124
|
description = pkg.description ?? null;
|
|
13716
14125
|
scripts = pkg.scripts ?? null;
|
|
@@ -14293,11 +14702,11 @@ function dokuFilePath(kind, slug) {
|
|
|
14293
14702
|
return `${DOKU_DIR}/${slug}.md`;
|
|
14294
14703
|
}
|
|
14295
14704
|
async function writeDokuTree(root, files) {
|
|
14296
|
-
const dir =
|
|
14297
|
-
const manifestPath =
|
|
14705
|
+
const dir = path14.join(root, DOKU_DIR);
|
|
14706
|
+
const manifestPath = path14.join(dir, MANIFEST);
|
|
14298
14707
|
let old = { files: {} };
|
|
14299
14708
|
try {
|
|
14300
|
-
old = JSON.parse(await
|
|
14709
|
+
old = JSON.parse(await readFile10(manifestPath, "utf8"));
|
|
14301
14710
|
if (typeof old.files !== "object" || old.files === null)
|
|
14302
14711
|
old = { files: {} };
|
|
14303
14712
|
} catch {
|
|
@@ -14307,21 +14716,21 @@ async function writeDokuTree(root, files) {
|
|
|
14307
14716
|
for (const file of files) {
|
|
14308
14717
|
const hash = sha1(file.markdown);
|
|
14309
14718
|
next[file.relPath] = { hash };
|
|
14310
|
-
const abs =
|
|
14719
|
+
const abs = path14.join(root, file.relPath);
|
|
14311
14720
|
if (old.files[file.relPath]?.hash === hash && await exists(abs)) continue;
|
|
14312
|
-
await
|
|
14313
|
-
await
|
|
14721
|
+
await mkdir9(path14.dirname(abs), { recursive: true });
|
|
14722
|
+
await writeFile9(abs, file.markdown, "utf8");
|
|
14314
14723
|
written++;
|
|
14315
14724
|
}
|
|
14316
14725
|
let deleted = 0;
|
|
14317
14726
|
for (const rel of Object.keys(old.files)) {
|
|
14318
14727
|
if (next[rel]) continue;
|
|
14319
14728
|
if (rel !== DOKU_FILENAME && !rel.startsWith(`${DOKU_DIR}/`)) continue;
|
|
14320
|
-
await
|
|
14729
|
+
await rm6(path14.join(root, rel), { force: true });
|
|
14321
14730
|
deleted++;
|
|
14322
14731
|
}
|
|
14323
|
-
await
|
|
14324
|
-
await
|
|
14732
|
+
await mkdir9(dir, { recursive: true });
|
|
14733
|
+
await writeFile9(
|
|
14325
14734
|
manifestPath,
|
|
14326
14735
|
JSON.stringify({ files: next }, null, 2),
|
|
14327
14736
|
"utf8"
|
|
@@ -14474,8 +14883,8 @@ var init_ponytail_brief = __esm({
|
|
|
14474
14883
|
});
|
|
14475
14884
|
|
|
14476
14885
|
// packages/ingest/src/ponytail-debt.ts
|
|
14477
|
-
function isDebtScannable(
|
|
14478
|
-
return !/\.(md|mdx|markdown|txt|jsonl|log|lock)$/i.test(
|
|
14886
|
+
function isDebtScannable(path19) {
|
|
14887
|
+
return !/\.(md|mdx|markdown|txt|jsonl|log|lock)$/i.test(path19);
|
|
14479
14888
|
}
|
|
14480
14889
|
function scanPonytailComments(files) {
|
|
14481
14890
|
const out = [];
|
|
@@ -14642,6 +15051,10 @@ var init_src3 = __esm({
|
|
|
14642
15051
|
init_cursor_hooks();
|
|
14643
15052
|
init_codex_hooks();
|
|
14644
15053
|
init_copilot_hooks();
|
|
15054
|
+
init_gemini_hooks();
|
|
15055
|
+
init_kiro_hooks();
|
|
15056
|
+
init_opencode_plugin();
|
|
15057
|
+
init_cline_hooks();
|
|
14645
15058
|
init_commit_links();
|
|
14646
15059
|
init_tool_failure();
|
|
14647
15060
|
init_bootstrap();
|
|
@@ -14685,11 +15098,14 @@ function serverUrl() {
|
|
|
14685
15098
|
""
|
|
14686
15099
|
);
|
|
14687
15100
|
}
|
|
15101
|
+
function notConnected(root) {
|
|
15102
|
+
return `brainjar: no Brain is connected to ${root} on this machine. Run \`npx brainjar init --brain <id>\` there (the id is under Settings > Connect).`;
|
|
15103
|
+
}
|
|
14688
15104
|
async function captureApi(pathAndQuery, init2 = {}) {
|
|
14689
15105
|
const token = loadEnv().BRAINJAR_TOKEN?.trim();
|
|
14690
15106
|
if (!token) {
|
|
14691
|
-
throw new
|
|
14692
|
-
"BRAINJAR_TOKEN is not set
|
|
15107
|
+
throw new TokenError(
|
|
15108
|
+
"BRAINJAR_TOKEN is not set. Create a token in the web app (setup wizard or Settings > Connect) and run `npx brainjar init --brain <id> --token <token>` once on this machine."
|
|
14693
15109
|
);
|
|
14694
15110
|
}
|
|
14695
15111
|
const res = await fetch(serverUrl() + pathAndQuery, {
|
|
@@ -14701,6 +15117,7 @@ async function captureApi(pathAndQuery, init2 = {}) {
|
|
|
14701
15117
|
...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
|
|
14702
15118
|
});
|
|
14703
15119
|
const body = await res.json().catch(() => null);
|
|
15120
|
+
if (res.status === 401) throw new TokenError(TOKEN_HELP);
|
|
14704
15121
|
if (!res.ok) {
|
|
14705
15122
|
throw new Error(
|
|
14706
15123
|
body?.error ?? `BrainJar server responded ${res.status} (${serverUrl()})`
|
|
@@ -14708,10 +15125,14 @@ async function captureApi(pathAndQuery, init2 = {}) {
|
|
|
14708
15125
|
}
|
|
14709
15126
|
return body;
|
|
14710
15127
|
}
|
|
15128
|
+
var TokenError, TOKEN_HELP;
|
|
14711
15129
|
var init_api_client = __esm({
|
|
14712
15130
|
"packages/cli/src/api-client.ts"() {
|
|
14713
15131
|
"use strict";
|
|
14714
15132
|
init_src();
|
|
15133
|
+
TokenError = class extends Error {
|
|
15134
|
+
};
|
|
15135
|
+
TOKEN_HELP = "BrainJar token revoked or invalid. Create a new token under Account in the web app, then run `npx brainjar init --brain <id> --token <token>` once in any project on this machine.";
|
|
14715
15136
|
}
|
|
14716
15137
|
});
|
|
14717
15138
|
|
|
@@ -14727,8 +15148,8 @@ __export(brief_exports, {
|
|
|
14727
15148
|
refreshBrief: () => refreshBrief,
|
|
14728
15149
|
upsertBriefBlock: () => upsertBriefBlock
|
|
14729
15150
|
});
|
|
14730
|
-
import { readFileSync as
|
|
14731
|
-
import
|
|
15151
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync3, existsSync as existsSync11 } from "node:fs";
|
|
15152
|
+
import path15 from "node:path";
|
|
14732
15153
|
function claudeMdWithImport(existing) {
|
|
14733
15154
|
let text = existing;
|
|
14734
15155
|
const start = text.indexOf(BRIEF_START);
|
|
@@ -14774,20 +15195,18 @@ ${BRIEF_END}
|
|
|
14774
15195
|
async function refreshBrief(cwd) {
|
|
14775
15196
|
if (loadEnv().BRAINJAR_NO_BRIEF === "1") return "skipped";
|
|
14776
15197
|
const root = await repoRoot(cwd);
|
|
14777
|
-
const
|
|
14778
|
-
|
|
14779
|
-
);
|
|
14780
|
-
if (!project.brainId) return "skipped";
|
|
15198
|
+
const link = await captureProject(root);
|
|
15199
|
+
if (!link) return "skipped";
|
|
14781
15200
|
const { text } = await captureApi(
|
|
14782
|
-
`/capture/brief?brain=${
|
|
15201
|
+
`/capture/brief?brain=${link.brainId}`
|
|
14783
15202
|
);
|
|
14784
15203
|
if (!text) return "skipped";
|
|
14785
|
-
const file =
|
|
14786
|
-
const existing =
|
|
15204
|
+
const file = path15.join(root, BRIEF_FILE);
|
|
15205
|
+
const existing = existsSync11(file) ? readFileSync11(file, "utf8") : "";
|
|
14787
15206
|
const next = upsertBriefBlock(existing, text);
|
|
14788
15207
|
if (next !== null) writeFileSync3(file, next);
|
|
14789
|
-
const claudeFile =
|
|
14790
|
-
const claude =
|
|
15208
|
+
const claudeFile = path15.join(root, "CLAUDE.md");
|
|
15209
|
+
const claude = existsSync11(claudeFile) ? readFileSync11(claudeFile, "utf8") : "";
|
|
14791
15210
|
const claudeNext = claudeMdWithImport(claude);
|
|
14792
15211
|
if (claudeNext !== null) writeFileSync3(claudeFile, claudeNext);
|
|
14793
15212
|
return next !== null || claudeNext !== null ? "updated" : "unchanged";
|
|
@@ -14824,9 +15243,9 @@ __export(debt_exports, {
|
|
|
14824
15243
|
scanDebtInRepo: () => scanDebtInRepo
|
|
14825
15244
|
});
|
|
14826
15245
|
import { execFile as execFile3 } from "node:child_process";
|
|
14827
|
-
import { readFile as
|
|
15246
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
14828
15247
|
import { promisify as promisify3 } from "node:util";
|
|
14829
|
-
import
|
|
15248
|
+
import path16 from "node:path";
|
|
14830
15249
|
async function scanDebtInRepo(root) {
|
|
14831
15250
|
const { stdout } = await run3("git", ["-C", root, "ls-files"], {
|
|
14832
15251
|
maxBuffer: 16 * 1048576
|
|
@@ -14836,7 +15255,7 @@ async function scanDebtInRepo(root) {
|
|
|
14836
15255
|
const p = rel.trim();
|
|
14837
15256
|
if (!p || !isDebtScannable(p)) continue;
|
|
14838
15257
|
try {
|
|
14839
|
-
const buf = await
|
|
15258
|
+
const buf = await readFile11(path16.join(root, p));
|
|
14840
15259
|
if (buf.length > 512 * 1024 || buf.includes(0)) continue;
|
|
14841
15260
|
files.push({ path: p, content: buf.toString("utf8") });
|
|
14842
15261
|
} catch {
|
|
@@ -14846,14 +15265,13 @@ async function scanDebtInRepo(root) {
|
|
|
14846
15265
|
}
|
|
14847
15266
|
async function debtMain() {
|
|
14848
15267
|
const root = await repoRoot(process.cwd());
|
|
14849
|
-
const
|
|
14850
|
-
if (!
|
|
14851
|
-
console.error(
|
|
14852
|
-
`brainjar: no Brain is connected to ${root} \u2014 run \`npx brainjar init\``
|
|
14853
|
-
);
|
|
15268
|
+
const link = await captureProject(root);
|
|
15269
|
+
if (!link) {
|
|
15270
|
+
console.error(notConnected(root));
|
|
14854
15271
|
process.exitCode = 1;
|
|
14855
15272
|
return;
|
|
14856
15273
|
}
|
|
15274
|
+
const r = await captureApi(`/capture/debt?brain=${link.brainId}`);
|
|
14857
15275
|
if (r.entries.length === 0) {
|
|
14858
15276
|
console.log(
|
|
14859
15277
|
"No ponytail debt on record. Either disciplined or undocumented."
|
|
@@ -15262,11 +15680,11 @@ var init_codex = __esm({
|
|
|
15262
15680
|
...commits.length ? { commits } : {}
|
|
15263
15681
|
});
|
|
15264
15682
|
files.forEach(
|
|
15265
|
-
(
|
|
15683
|
+
(path19, i) => events.push({
|
|
15266
15684
|
...base,
|
|
15267
15685
|
event_id: `file:${id}:${i}`,
|
|
15268
15686
|
kind: "file_changed",
|
|
15269
|
-
path:
|
|
15687
|
+
path: path19
|
|
15270
15688
|
})
|
|
15271
15689
|
);
|
|
15272
15690
|
}
|
|
@@ -15436,76 +15854,630 @@ var init_copilot = __esm({
|
|
|
15436
15854
|
}
|
|
15437
15855
|
});
|
|
15438
15856
|
|
|
15439
|
-
// packages/cli/src/adapters/
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
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
|
-
parseHookArgs: () => parseHookArgs,
|
|
15462
|
-
readSpool: () => readSpool,
|
|
15463
|
-
spoolPath: () => spoolPath
|
|
15464
|
-
});
|
|
15465
|
-
import {
|
|
15466
|
-
appendFileSync,
|
|
15467
|
-
existsSync as existsSync8,
|
|
15468
|
-
mkdirSync as mkdirSync3,
|
|
15469
|
-
readdirSync,
|
|
15470
|
-
readFileSync as readFileSync8,
|
|
15471
|
-
rmSync,
|
|
15472
|
-
statSync as statSync2
|
|
15473
|
-
} from "node:fs";
|
|
15474
|
-
import os5 from "node:os";
|
|
15475
|
-
import path13 from "node:path";
|
|
15476
|
-
function spoolPath(tool, sessionId) {
|
|
15477
|
-
return path13.join(
|
|
15478
|
-
os5.homedir(),
|
|
15479
|
-
".brainjar",
|
|
15480
|
-
"spool",
|
|
15481
|
-
tool,
|
|
15482
|
-
`${sessionId.replace(/[^A-Za-z0-9._-]/g, "_")}.jsonl`
|
|
15483
|
-
);
|
|
15484
|
-
}
|
|
15485
|
-
function readSpool(file) {
|
|
15486
|
-
const out = [];
|
|
15487
|
-
for (const line of readFileSync8(file, "utf8").split("\n")) {
|
|
15488
|
-
if (!line.trim()) continue;
|
|
15489
|
-
try {
|
|
15490
|
-
out.push(clampCaptureEvent(JSON.parse(line)));
|
|
15491
|
-
} catch {
|
|
15492
|
-
}
|
|
15857
|
+
// packages/cli/src/adapters/gemini.ts
|
|
15858
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
15859
|
+
function targetOf4(input) {
|
|
15860
|
+
const i = input ?? {};
|
|
15861
|
+
for (const k of ["command", "file_path"]) {
|
|
15862
|
+
if (typeof i[k] === "string") return i[k];
|
|
15493
15863
|
}
|
|
15494
|
-
return
|
|
15864
|
+
return "";
|
|
15495
15865
|
}
|
|
15496
|
-
function
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
if (Date.now() - statSync2(file).mtimeMs > maxAgeMs)
|
|
15502
|
-
rmSync(file, { force: true });
|
|
15503
|
-
}
|
|
15504
|
-
} catch {
|
|
15866
|
+
function commitShas5(v) {
|
|
15867
|
+
const text = typeof v === "string" ? v : JSON.stringify(v ?? "");
|
|
15868
|
+
const out = /* @__PURE__ */ new Set();
|
|
15869
|
+
for (const m of text.matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
|
|
15870
|
+
out.add(m[1].toLowerCase());
|
|
15505
15871
|
}
|
|
15872
|
+
return [...out];
|
|
15506
15873
|
}
|
|
15507
|
-
|
|
15508
|
-
|
|
15874
|
+
var EDIT_TOOLS3, short6, gemini2;
|
|
15875
|
+
var init_gemini = __esm({
|
|
15876
|
+
"packages/cli/src/adapters/gemini.ts"() {
|
|
15877
|
+
"use strict";
|
|
15878
|
+
init_src();
|
|
15879
|
+
EDIT_TOOLS3 = /* @__PURE__ */ new Set(["write_file", "replace"]);
|
|
15880
|
+
short6 = (s) => createHash7("sha256").update(s).digest("hex").slice(0, 16);
|
|
15881
|
+
gemini2 = {
|
|
15882
|
+
id: "gemini",
|
|
15883
|
+
map(payload, now) {
|
|
15884
|
+
const p = payload;
|
|
15885
|
+
if (!p || typeof p !== "object" || typeof p.session_id !== "string") {
|
|
15886
|
+
return null;
|
|
15887
|
+
}
|
|
15888
|
+
const at = typeof p.timestamp === "string" && !Number.isNaN(Date.parse(p.timestamp)) ? new Date(p.timestamp).toISOString() : now;
|
|
15889
|
+
const base = {
|
|
15890
|
+
v: CAPTURE_EVENT_VERSION,
|
|
15891
|
+
tool: "gemini",
|
|
15892
|
+
session_id: p.session_id,
|
|
15893
|
+
at,
|
|
15894
|
+
cwd: p.cwd ?? null
|
|
15895
|
+
};
|
|
15896
|
+
const events = [];
|
|
15897
|
+
let flush = false;
|
|
15898
|
+
let end = false;
|
|
15899
|
+
switch (p.hook_event_name) {
|
|
15900
|
+
case "SessionStart":
|
|
15901
|
+
events.push({
|
|
15902
|
+
...base,
|
|
15903
|
+
event_id: `start:${p.session_id}`,
|
|
15904
|
+
kind: "session_started"
|
|
15905
|
+
});
|
|
15906
|
+
break;
|
|
15907
|
+
case "BeforeAgent":
|
|
15908
|
+
if (typeof p.prompt === "string" && p.prompt.trim()) {
|
|
15909
|
+
events.push({
|
|
15910
|
+
...base,
|
|
15911
|
+
event_id: `prompt:${short6(at + p.prompt)}`,
|
|
15912
|
+
kind: "prompt",
|
|
15913
|
+
text: p.prompt
|
|
15914
|
+
});
|
|
15915
|
+
}
|
|
15916
|
+
break;
|
|
15917
|
+
case "AfterTool": {
|
|
15918
|
+
const tool = p.tool_name ?? "tool";
|
|
15919
|
+
const target = targetOf4(p.tool_input);
|
|
15920
|
+
const id = short6(at + tool + target);
|
|
15921
|
+
const err = p.tool_response?.error;
|
|
15922
|
+
if (err && typeof err === "object") {
|
|
15923
|
+
const error = String(err.message ?? err.type ?? "tool error");
|
|
15924
|
+
events.push({
|
|
15925
|
+
...base,
|
|
15926
|
+
event_id: `fail:${id}`,
|
|
15927
|
+
kind: "tool_failed",
|
|
15928
|
+
tool_name: tool,
|
|
15929
|
+
...target ? { target } : {},
|
|
15930
|
+
error
|
|
15931
|
+
});
|
|
15932
|
+
break;
|
|
15933
|
+
}
|
|
15934
|
+
const commits = commitShas5(p.tool_response?.llmContent);
|
|
15935
|
+
events.push({
|
|
15936
|
+
...base,
|
|
15937
|
+
event_id: `use:${id}`,
|
|
15938
|
+
kind: "tool_used",
|
|
15939
|
+
tool_name: tool,
|
|
15940
|
+
...target ? { target } : {},
|
|
15941
|
+
...commits.length ? { commits } : {}
|
|
15942
|
+
});
|
|
15943
|
+
if (EDIT_TOOLS3.has(tool) && target) {
|
|
15944
|
+
events.push({
|
|
15945
|
+
...base,
|
|
15946
|
+
event_id: `file:${id}`,
|
|
15947
|
+
kind: "file_changed",
|
|
15948
|
+
path: target
|
|
15949
|
+
});
|
|
15950
|
+
}
|
|
15951
|
+
break;
|
|
15952
|
+
}
|
|
15953
|
+
case "AfterAgent":
|
|
15954
|
+
flush = true;
|
|
15955
|
+
break;
|
|
15956
|
+
case "SessionEnd":
|
|
15957
|
+
events.push({
|
|
15958
|
+
...base,
|
|
15959
|
+
event_id: `end:${p.session_id}`,
|
|
15960
|
+
kind: "session_ended",
|
|
15961
|
+
...p.reason ? { reason: p.reason } : {}
|
|
15962
|
+
});
|
|
15963
|
+
flush = true;
|
|
15964
|
+
end = true;
|
|
15965
|
+
break;
|
|
15966
|
+
default:
|
|
15967
|
+
return null;
|
|
15968
|
+
}
|
|
15969
|
+
return { sessionId: p.session_id, cwd: p.cwd ?? null, events, flush, end };
|
|
15970
|
+
}
|
|
15971
|
+
};
|
|
15972
|
+
}
|
|
15973
|
+
});
|
|
15974
|
+
|
|
15975
|
+
// packages/cli/src/adapters/kiro.ts
|
|
15976
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
15977
|
+
function targetOf5(input) {
|
|
15978
|
+
const i = input ?? {};
|
|
15979
|
+
for (const k of ["command", "cmd", "path", "file_path", "filePath"]) {
|
|
15980
|
+
if (typeof i[k] === "string") return i[k];
|
|
15981
|
+
}
|
|
15982
|
+
return "";
|
|
15983
|
+
}
|
|
15984
|
+
function commitShas6(v) {
|
|
15985
|
+
const text = typeof v === "string" ? v : JSON.stringify(v ?? "");
|
|
15986
|
+
const out = /* @__PURE__ */ new Set();
|
|
15987
|
+
for (const m of text.matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
|
|
15988
|
+
out.add(m[1].toLowerCase());
|
|
15989
|
+
}
|
|
15990
|
+
return [...out];
|
|
15991
|
+
}
|
|
15992
|
+
function failureOf2(response) {
|
|
15993
|
+
if (!response || typeof response !== "object") return null;
|
|
15994
|
+
const r = response;
|
|
15995
|
+
if (r.success !== false) return null;
|
|
15996
|
+
const text = [r.error, r.result, r.message].find(
|
|
15997
|
+
(t) => typeof t === "string" && t.trim()
|
|
15998
|
+
);
|
|
15999
|
+
return (typeof text === "string" ? text : JSON.stringify(r)).trim();
|
|
16000
|
+
}
|
|
16001
|
+
var EDIT_TOOLS4, short7, EVENTS5, kiro2;
|
|
16002
|
+
var init_kiro = __esm({
|
|
16003
|
+
"packages/cli/src/adapters/kiro.ts"() {
|
|
16004
|
+
"use strict";
|
|
16005
|
+
init_src();
|
|
16006
|
+
EDIT_TOOLS4 = /* @__PURE__ */ new Set(["fs_write", "write", "str_replace", "fs_append"]);
|
|
16007
|
+
short7 = (s) => createHash8("sha256").update(s).digest("hex").slice(0, 16);
|
|
16008
|
+
EVENTS5 = {
|
|
16009
|
+
agentSpawn: "start",
|
|
16010
|
+
sessionStart: "start",
|
|
16011
|
+
SessionStart: "start",
|
|
16012
|
+
userPromptSubmit: "prompt",
|
|
16013
|
+
UserPromptSubmit: "prompt",
|
|
16014
|
+
promptSubmit: "prompt",
|
|
16015
|
+
postToolUse: "tool",
|
|
16016
|
+
PostToolUse: "tool",
|
|
16017
|
+
postFileSave: "file",
|
|
16018
|
+
PostFileSave: "file",
|
|
16019
|
+
postFileCreate: "file",
|
|
16020
|
+
PostFileCreate: "file",
|
|
16021
|
+
stop: "stop",
|
|
16022
|
+
Stop: "stop",
|
|
16023
|
+
agentStop: "stop"
|
|
16024
|
+
};
|
|
16025
|
+
kiro2 = {
|
|
16026
|
+
id: "kiro",
|
|
16027
|
+
map(payload, now) {
|
|
16028
|
+
const p = payload;
|
|
16029
|
+
if (!p || typeof p !== "object" || typeof p.session_id !== "string") {
|
|
16030
|
+
return null;
|
|
16031
|
+
}
|
|
16032
|
+
const base = {
|
|
16033
|
+
v: CAPTURE_EVENT_VERSION,
|
|
16034
|
+
tool: "kiro",
|
|
16035
|
+
session_id: p.session_id,
|
|
16036
|
+
at: now,
|
|
16037
|
+
cwd: p.cwd ?? null
|
|
16038
|
+
};
|
|
16039
|
+
const events = [];
|
|
16040
|
+
let flush = false;
|
|
16041
|
+
switch (EVENTS5[p.hook_event_name ?? ""]) {
|
|
16042
|
+
case "start":
|
|
16043
|
+
events.push({
|
|
16044
|
+
...base,
|
|
16045
|
+
event_id: `start:${p.session_id}`,
|
|
16046
|
+
kind: "session_started"
|
|
16047
|
+
});
|
|
16048
|
+
break;
|
|
16049
|
+
case "prompt":
|
|
16050
|
+
if (typeof p.prompt === "string" && p.prompt.trim()) {
|
|
16051
|
+
events.push({
|
|
16052
|
+
...base,
|
|
16053
|
+
event_id: `prompt:${short7(now + p.prompt)}`,
|
|
16054
|
+
kind: "prompt",
|
|
16055
|
+
text: p.prompt
|
|
16056
|
+
});
|
|
16057
|
+
}
|
|
16058
|
+
break;
|
|
16059
|
+
case "tool": {
|
|
16060
|
+
const tool = p.tool_name ?? "tool";
|
|
16061
|
+
const target = targetOf5(p.tool_input);
|
|
16062
|
+
const id = short7(now + tool + target);
|
|
16063
|
+
const error = failureOf2(p.tool_response);
|
|
16064
|
+
if (error) {
|
|
16065
|
+
events.push({
|
|
16066
|
+
...base,
|
|
16067
|
+
event_id: `fail:${id}`,
|
|
16068
|
+
kind: "tool_failed",
|
|
16069
|
+
tool_name: tool,
|
|
16070
|
+
...target ? { target } : {},
|
|
16071
|
+
error
|
|
16072
|
+
});
|
|
16073
|
+
break;
|
|
16074
|
+
}
|
|
16075
|
+
const commits = commitShas6(p.tool_response);
|
|
16076
|
+
events.push({
|
|
16077
|
+
...base,
|
|
16078
|
+
event_id: `use:${id}`,
|
|
16079
|
+
kind: "tool_used",
|
|
16080
|
+
tool_name: tool,
|
|
16081
|
+
...target ? { target } : {},
|
|
16082
|
+
...commits.length ? { commits } : {}
|
|
16083
|
+
});
|
|
16084
|
+
if (EDIT_TOOLS4.has(tool) && target) {
|
|
16085
|
+
events.push({
|
|
16086
|
+
...base,
|
|
16087
|
+
event_id: `file:${id}`,
|
|
16088
|
+
kind: "file_changed",
|
|
16089
|
+
path: target
|
|
16090
|
+
});
|
|
16091
|
+
}
|
|
16092
|
+
break;
|
|
16093
|
+
}
|
|
16094
|
+
case "file":
|
|
16095
|
+
if (typeof p.file_path === "string" && p.file_path) {
|
|
16096
|
+
events.push({
|
|
16097
|
+
...base,
|
|
16098
|
+
event_id: `file:${short7(now + p.file_path)}`,
|
|
16099
|
+
kind: "file_changed",
|
|
16100
|
+
path: p.file_path
|
|
16101
|
+
});
|
|
16102
|
+
}
|
|
16103
|
+
break;
|
|
16104
|
+
case "stop":
|
|
16105
|
+
flush = true;
|
|
16106
|
+
break;
|
|
16107
|
+
default:
|
|
16108
|
+
return null;
|
|
16109
|
+
}
|
|
16110
|
+
return {
|
|
16111
|
+
sessionId: p.session_id,
|
|
16112
|
+
cwd: p.cwd ?? null,
|
|
16113
|
+
events,
|
|
16114
|
+
flush,
|
|
16115
|
+
end: false
|
|
16116
|
+
};
|
|
16117
|
+
}
|
|
16118
|
+
};
|
|
16119
|
+
}
|
|
16120
|
+
});
|
|
16121
|
+
|
|
16122
|
+
// packages/cli/src/adapters/opencode.ts
|
|
16123
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
16124
|
+
function commitShas7(text) {
|
|
16125
|
+
const out = /* @__PURE__ */ new Set();
|
|
16126
|
+
for (const m of (text ?? "").matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
|
|
16127
|
+
out.add(m[1].toLowerCase());
|
|
16128
|
+
}
|
|
16129
|
+
return [...out];
|
|
16130
|
+
}
|
|
16131
|
+
var EDIT_TOOLS5, short8, opencode2;
|
|
16132
|
+
var init_opencode = __esm({
|
|
16133
|
+
"packages/cli/src/adapters/opencode.ts"() {
|
|
16134
|
+
"use strict";
|
|
16135
|
+
init_src();
|
|
16136
|
+
EDIT_TOOLS5 = /* @__PURE__ */ new Set(["edit", "write"]);
|
|
16137
|
+
short8 = (s) => createHash9("sha256").update(s).digest("hex").slice(0, 16);
|
|
16138
|
+
opencode2 = {
|
|
16139
|
+
id: "opencode",
|
|
16140
|
+
map(payload, now) {
|
|
16141
|
+
const p = payload;
|
|
16142
|
+
if (!p || typeof p !== "object" || p.source !== "opencode" || typeof p.session_id !== "string" || !p.session_id) {
|
|
16143
|
+
return null;
|
|
16144
|
+
}
|
|
16145
|
+
const base = {
|
|
16146
|
+
v: CAPTURE_EVENT_VERSION,
|
|
16147
|
+
tool: "opencode",
|
|
16148
|
+
session_id: p.session_id,
|
|
16149
|
+
at: now,
|
|
16150
|
+
cwd: p.cwd ?? null
|
|
16151
|
+
};
|
|
16152
|
+
const events = [];
|
|
16153
|
+
let flush = false;
|
|
16154
|
+
const target = p.target?.trim() ?? "";
|
|
16155
|
+
const tool = p.tool_name ?? "tool";
|
|
16156
|
+
switch (p.kind) {
|
|
16157
|
+
case "session_started":
|
|
16158
|
+
events.push({
|
|
16159
|
+
...base,
|
|
16160
|
+
event_id: `start:${p.session_id}`,
|
|
16161
|
+
kind: "session_started"
|
|
16162
|
+
});
|
|
16163
|
+
break;
|
|
16164
|
+
case "prompt":
|
|
16165
|
+
if (typeof p.text === "string" && p.text.trim()) {
|
|
16166
|
+
events.push({
|
|
16167
|
+
...base,
|
|
16168
|
+
event_id: `prompt:${short8(now + p.text)}`,
|
|
16169
|
+
kind: "prompt",
|
|
16170
|
+
text: p.text
|
|
16171
|
+
});
|
|
16172
|
+
}
|
|
16173
|
+
break;
|
|
16174
|
+
case "tool_used": {
|
|
16175
|
+
const id = short8(now + tool + target);
|
|
16176
|
+
const commits = commitShas7(p.output);
|
|
16177
|
+
events.push({
|
|
16178
|
+
...base,
|
|
16179
|
+
event_id: `use:${id}`,
|
|
16180
|
+
kind: "tool_used",
|
|
16181
|
+
tool_name: tool,
|
|
16182
|
+
...target ? { target } : {},
|
|
16183
|
+
...commits.length ? { commits } : {}
|
|
16184
|
+
});
|
|
16185
|
+
if (EDIT_TOOLS5.has(tool) && target) {
|
|
16186
|
+
events.push({
|
|
16187
|
+
...base,
|
|
16188
|
+
event_id: `file:${id}`,
|
|
16189
|
+
kind: "file_changed",
|
|
16190
|
+
path: target
|
|
16191
|
+
});
|
|
16192
|
+
}
|
|
16193
|
+
break;
|
|
16194
|
+
}
|
|
16195
|
+
case "tool_failed": {
|
|
16196
|
+
const error = (p.error ?? "").trim();
|
|
16197
|
+
if (!error) break;
|
|
16198
|
+
events.push({
|
|
16199
|
+
...base,
|
|
16200
|
+
event_id: `fail:${short8(now + tool + target + error)}`,
|
|
16201
|
+
kind: "tool_failed",
|
|
16202
|
+
tool_name: tool,
|
|
16203
|
+
...target ? { target } : {},
|
|
16204
|
+
error
|
|
16205
|
+
});
|
|
16206
|
+
break;
|
|
16207
|
+
}
|
|
16208
|
+
case "file_changed":
|
|
16209
|
+
if (typeof p.path === "string" && p.path) {
|
|
16210
|
+
events.push({
|
|
16211
|
+
...base,
|
|
16212
|
+
event_id: `file:${short8(now + p.path)}`,
|
|
16213
|
+
kind: "file_changed",
|
|
16214
|
+
path: p.path
|
|
16215
|
+
});
|
|
16216
|
+
}
|
|
16217
|
+
break;
|
|
16218
|
+
case "turn_ended":
|
|
16219
|
+
flush = true;
|
|
16220
|
+
break;
|
|
16221
|
+
default:
|
|
16222
|
+
return null;
|
|
16223
|
+
}
|
|
16224
|
+
return {
|
|
16225
|
+
sessionId: p.session_id,
|
|
16226
|
+
cwd: p.cwd ?? null,
|
|
16227
|
+
events,
|
|
16228
|
+
flush,
|
|
16229
|
+
end: false
|
|
16230
|
+
};
|
|
16231
|
+
}
|
|
16232
|
+
};
|
|
16233
|
+
}
|
|
16234
|
+
});
|
|
16235
|
+
|
|
16236
|
+
// packages/cli/src/adapters/cline.ts
|
|
16237
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
16238
|
+
function targetOf6(params) {
|
|
16239
|
+
const i = params ?? {};
|
|
16240
|
+
for (const k of ["command", "path"]) {
|
|
16241
|
+
if (typeof i[k] === "string") return i[k];
|
|
16242
|
+
}
|
|
16243
|
+
return "";
|
|
16244
|
+
}
|
|
16245
|
+
function commitShas8(text) {
|
|
16246
|
+
const out = /* @__PURE__ */ new Set();
|
|
16247
|
+
for (const m of (text ?? "").matchAll(/\[[^\]\n]* ([0-9a-f]{7,40})\]/g)) {
|
|
16248
|
+
out.add(m[1].toLowerCase());
|
|
16249
|
+
}
|
|
16250
|
+
return [...out];
|
|
16251
|
+
}
|
|
16252
|
+
function stamp2(ts, now) {
|
|
16253
|
+
const n = typeof ts === "string" && /^\d+$/.test(ts) ? Number(ts) : ts;
|
|
16254
|
+
if (typeof n === "number" && Number.isFinite(n))
|
|
16255
|
+
return new Date(n).toISOString();
|
|
16256
|
+
if (typeof ts === "string" && !Number.isNaN(Date.parse(ts)))
|
|
16257
|
+
return new Date(ts).toISOString();
|
|
16258
|
+
return now;
|
|
16259
|
+
}
|
|
16260
|
+
function clineContextEnvelope(text) {
|
|
16261
|
+
return JSON.stringify({ cancel: false, contextModification: text });
|
|
16262
|
+
}
|
|
16263
|
+
var EDIT_TOOLS6, short9, CLINE_REPLY, cline2;
|
|
16264
|
+
var init_cline = __esm({
|
|
16265
|
+
"packages/cli/src/adapters/cline.ts"() {
|
|
16266
|
+
"use strict";
|
|
16267
|
+
init_src();
|
|
16268
|
+
EDIT_TOOLS6 = /* @__PURE__ */ new Set(["write_to_file", "replace_in_file", "apply_patch"]);
|
|
16269
|
+
short9 = (s) => createHash10("sha256").update(s).digest("hex").slice(0, 16);
|
|
16270
|
+
CLINE_REPLY = JSON.stringify({ cancel: false });
|
|
16271
|
+
cline2 = {
|
|
16272
|
+
id: "cline",
|
|
16273
|
+
map(payload, now) {
|
|
16274
|
+
const p = payload;
|
|
16275
|
+
if (!p || typeof p !== "object" || typeof p.taskId !== "string") {
|
|
16276
|
+
return null;
|
|
16277
|
+
}
|
|
16278
|
+
const at = stamp2(p.timestamp, now);
|
|
16279
|
+
const cwd = p.workspaceRoots?.[0] ?? null;
|
|
16280
|
+
const base = {
|
|
16281
|
+
v: CAPTURE_EVENT_VERSION,
|
|
16282
|
+
tool: "cline",
|
|
16283
|
+
session_id: p.taskId,
|
|
16284
|
+
at,
|
|
16285
|
+
cwd
|
|
16286
|
+
};
|
|
16287
|
+
const events = [];
|
|
16288
|
+
let flush = false;
|
|
16289
|
+
let end = false;
|
|
16290
|
+
switch (p.hookName) {
|
|
16291
|
+
case "TaskStart":
|
|
16292
|
+
case "TaskResume":
|
|
16293
|
+
events.push({
|
|
16294
|
+
...base,
|
|
16295
|
+
event_id: `start:${p.taskId}`,
|
|
16296
|
+
kind: "session_started"
|
|
16297
|
+
});
|
|
16298
|
+
break;
|
|
16299
|
+
case "UserPromptSubmit": {
|
|
16300
|
+
const prompt = p.userPromptSubmit?.prompt;
|
|
16301
|
+
if (typeof prompt === "string" && prompt.trim()) {
|
|
16302
|
+
events.push({
|
|
16303
|
+
...base,
|
|
16304
|
+
event_id: `prompt:${short9(at + prompt)}`,
|
|
16305
|
+
kind: "prompt",
|
|
16306
|
+
text: prompt
|
|
16307
|
+
});
|
|
16308
|
+
}
|
|
16309
|
+
break;
|
|
16310
|
+
}
|
|
16311
|
+
case "PostToolUse": {
|
|
16312
|
+
const t = p.postToolUse ?? {};
|
|
16313
|
+
const tool = t.toolName ?? "tool";
|
|
16314
|
+
const target = targetOf6(t.parameters);
|
|
16315
|
+
const id = short9(at + tool + target);
|
|
16316
|
+
if (t.success !== true) {
|
|
16317
|
+
const error = (t.result ?? "").trim() || "tool failed";
|
|
16318
|
+
events.push({
|
|
16319
|
+
...base,
|
|
16320
|
+
event_id: `fail:${id}`,
|
|
16321
|
+
kind: "tool_failed",
|
|
16322
|
+
tool_name: tool,
|
|
16323
|
+
...target ? { target } : {},
|
|
16324
|
+
error
|
|
16325
|
+
});
|
|
16326
|
+
break;
|
|
16327
|
+
}
|
|
16328
|
+
const commits = commitShas8(t.result);
|
|
16329
|
+
events.push({
|
|
16330
|
+
...base,
|
|
16331
|
+
event_id: `use:${id}`,
|
|
16332
|
+
kind: "tool_used",
|
|
16333
|
+
tool_name: tool,
|
|
16334
|
+
...target ? { target } : {},
|
|
16335
|
+
...commits.length ? { commits } : {}
|
|
16336
|
+
});
|
|
16337
|
+
if (EDIT_TOOLS6.has(tool) && target) {
|
|
16338
|
+
events.push({
|
|
16339
|
+
...base,
|
|
16340
|
+
event_id: `file:${id}`,
|
|
16341
|
+
kind: "file_changed",
|
|
16342
|
+
path: target
|
|
16343
|
+
});
|
|
16344
|
+
}
|
|
16345
|
+
break;
|
|
16346
|
+
}
|
|
16347
|
+
case "TaskComplete":
|
|
16348
|
+
case "TaskCancel":
|
|
16349
|
+
events.push({
|
|
16350
|
+
...base,
|
|
16351
|
+
event_id: `end:${p.taskId}`,
|
|
16352
|
+
kind: "session_ended",
|
|
16353
|
+
reason: p.hookName === "TaskCancel" ? "cancelled" : "completed"
|
|
16354
|
+
});
|
|
16355
|
+
flush = true;
|
|
16356
|
+
end = true;
|
|
16357
|
+
break;
|
|
16358
|
+
default:
|
|
16359
|
+
return null;
|
|
16360
|
+
}
|
|
16361
|
+
return {
|
|
16362
|
+
sessionId: p.taskId,
|
|
16363
|
+
cwd,
|
|
16364
|
+
events,
|
|
16365
|
+
flush,
|
|
16366
|
+
end,
|
|
16367
|
+
reply: CLINE_REPLY
|
|
16368
|
+
};
|
|
16369
|
+
}
|
|
16370
|
+
};
|
|
16371
|
+
}
|
|
16372
|
+
});
|
|
16373
|
+
|
|
16374
|
+
// packages/cli/src/adapters/index.ts
|
|
16375
|
+
var ADAPTERS;
|
|
16376
|
+
var init_adapters = __esm({
|
|
16377
|
+
"packages/cli/src/adapters/index.ts"() {
|
|
16378
|
+
"use strict";
|
|
16379
|
+
init_claude_code();
|
|
16380
|
+
init_cursor();
|
|
16381
|
+
init_codex();
|
|
16382
|
+
init_copilot();
|
|
16383
|
+
init_gemini();
|
|
16384
|
+
init_kiro();
|
|
16385
|
+
init_opencode();
|
|
16386
|
+
init_cline();
|
|
16387
|
+
ADAPTERS = {
|
|
16388
|
+
[claudeCode2.id]: claudeCode2,
|
|
16389
|
+
[cursor2.id]: cursor2,
|
|
16390
|
+
[codex2.id]: codex2,
|
|
16391
|
+
[copilot2.id]: copilot2,
|
|
16392
|
+
[gemini2.id]: gemini2,
|
|
16393
|
+
[kiro2.id]: kiro2,
|
|
16394
|
+
[opencode2.id]: opencode2,
|
|
16395
|
+
[cline2.id]: cline2
|
|
16396
|
+
};
|
|
16397
|
+
}
|
|
16398
|
+
});
|
|
16399
|
+
|
|
16400
|
+
// packages/cli/src/hook.ts
|
|
16401
|
+
var hook_exports = {};
|
|
16402
|
+
__export(hook_exports, {
|
|
16403
|
+
hookMain: () => hookMain,
|
|
16404
|
+
parseHookArgs: () => parseHookArgs,
|
|
16405
|
+
readSpool: () => readSpool,
|
|
16406
|
+
spoolPath: () => spoolPath,
|
|
16407
|
+
staleSpools: () => staleSpools
|
|
16408
|
+
});
|
|
16409
|
+
import {
|
|
16410
|
+
appendFileSync,
|
|
16411
|
+
existsSync as existsSync12,
|
|
16412
|
+
mkdirSync as mkdirSync3,
|
|
16413
|
+
readdirSync,
|
|
16414
|
+
readFileSync as readFileSync12,
|
|
16415
|
+
rmSync,
|
|
16416
|
+
statSync as statSync2
|
|
16417
|
+
} from "node:fs";
|
|
16418
|
+
import os5 from "node:os";
|
|
16419
|
+
import path17 from "node:path";
|
|
16420
|
+
function spoolPath(tool, sessionId) {
|
|
16421
|
+
return path17.join(
|
|
16422
|
+
os5.homedir(),
|
|
16423
|
+
".brainjar",
|
|
16424
|
+
"spool",
|
|
16425
|
+
tool,
|
|
16426
|
+
`${sessionId.replace(/[^A-Za-z0-9._-]/g, "_")}.jsonl`
|
|
16427
|
+
);
|
|
16428
|
+
}
|
|
16429
|
+
function readSpool(file) {
|
|
16430
|
+
const out = [];
|
|
16431
|
+
for (const line of readFileSync12(file, "utf8").split("\n")) {
|
|
16432
|
+
if (!line.trim()) continue;
|
|
16433
|
+
try {
|
|
16434
|
+
out.push(clampCaptureEvent(JSON.parse(line)));
|
|
16435
|
+
} catch {
|
|
16436
|
+
}
|
|
16437
|
+
}
|
|
16438
|
+
return out;
|
|
16439
|
+
}
|
|
16440
|
+
function pruneSpool(dir, keep, maxAgeMs = 7 * 864e5) {
|
|
16441
|
+
try {
|
|
16442
|
+
for (const f of readdirSync(dir)) {
|
|
16443
|
+
const file = path17.join(dir, f);
|
|
16444
|
+
if (file === keep) continue;
|
|
16445
|
+
if (Date.now() - statSync2(file).mtimeMs > maxAgeMs)
|
|
16446
|
+
rmSync(file, { force: true });
|
|
16447
|
+
}
|
|
16448
|
+
} catch {
|
|
16449
|
+
}
|
|
16450
|
+
}
|
|
16451
|
+
function staleSpools(dir, keep, minAgeMs = 36e5) {
|
|
16452
|
+
try {
|
|
16453
|
+
return readdirSync(dir).map((f) => path17.join(dir, f)).filter(
|
|
16454
|
+
(file) => file !== keep && Date.now() - statSync2(file).mtimeMs > minAgeMs
|
|
16455
|
+
).sort();
|
|
16456
|
+
} catch {
|
|
16457
|
+
return [];
|
|
16458
|
+
}
|
|
16459
|
+
}
|
|
16460
|
+
async function linkForSpool(file) {
|
|
16461
|
+
const cwd = readSpool(file).find((e) => e.cwd)?.cwd;
|
|
16462
|
+
return cwd ? captureProject(await repoRoot(cwd)) : null;
|
|
16463
|
+
}
|
|
16464
|
+
async function shipSpool(file, link) {
|
|
16465
|
+
const events = readSpool(file);
|
|
16466
|
+
if (!events.length) return;
|
|
16467
|
+
const r = await captureApi("/capture/events", {
|
|
16468
|
+
method: "POST",
|
|
16469
|
+
body: {
|
|
16470
|
+
brain: link.brainId,
|
|
16471
|
+
events,
|
|
16472
|
+
author: loadEnv().BRAINJAR_AUTHOR?.trim() || null
|
|
16473
|
+
}
|
|
16474
|
+
});
|
|
16475
|
+
console.error(
|
|
16476
|
+
`brainjar: session captured \u2014 ${r.prompts ?? 0} intents, ${r.failures ?? 0} failures, ${(r.skippedNoise ?? 0) + (r.skippedFailures ?? 0)} noise skipped` + (r.consolidated ? `, ${r.consolidated} product facts` : "")
|
|
16477
|
+
);
|
|
16478
|
+
}
|
|
16479
|
+
function parseHookArgs(argv) {
|
|
16480
|
+
const eventFlag = argv.indexOf("--event");
|
|
15509
16481
|
const eventIndex = eventFlag >= 0 ? eventFlag + 1 : -1;
|
|
15510
16482
|
const event = eventIndex >= 0 ? argv[eventIndex] : void 0;
|
|
15511
16483
|
const tool = argv.find((a, i) => !a.startsWith("--") && i !== eventIndex);
|
|
@@ -15523,7 +16495,7 @@ async function hookMain(argv) {
|
|
|
15523
16495
|
}
|
|
15524
16496
|
let payload = null;
|
|
15525
16497
|
try {
|
|
15526
|
-
payload = JSON.parse(
|
|
16498
|
+
payload = JSON.parse(readFileSync12(0, "utf8"));
|
|
15527
16499
|
} catch {
|
|
15528
16500
|
return;
|
|
15529
16501
|
}
|
|
@@ -15532,7 +16504,7 @@ async function hookMain(argv) {
|
|
|
15532
16504
|
if (out.reply) process.stdout.write(out.reply);
|
|
15533
16505
|
const file = spoolPath(adapter.id, out.sessionId);
|
|
15534
16506
|
if (out.events.length) {
|
|
15535
|
-
mkdirSync3(
|
|
16507
|
+
mkdirSync3(path17.dirname(file), { recursive: true });
|
|
15536
16508
|
appendFileSync(
|
|
15537
16509
|
file,
|
|
15538
16510
|
out.events.map((e) => JSON.stringify(clampCaptureEvent(e))).join("\n") + "\n"
|
|
@@ -15540,28 +16512,22 @@ async function hookMain(argv) {
|
|
|
15540
16512
|
}
|
|
15541
16513
|
if (!out.flush) return;
|
|
15542
16514
|
const root = await repoRoot(out.cwd ?? process.cwd());
|
|
15543
|
-
const
|
|
15544
|
-
if (
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
root,
|
|
15549
|
-
events,
|
|
15550
|
-
author: loadEnv().BRAINJAR_AUTHOR?.trim() || null
|
|
15551
|
-
}
|
|
15552
|
-
});
|
|
15553
|
-
if (!r.brainId) {
|
|
15554
|
-
console.error(
|
|
15555
|
-
`brainjar: no Brain is connected to ${root} \u2014 run \`npx brainjar init\` (skipping capture)`
|
|
15556
|
-
);
|
|
15557
|
-
} else {
|
|
15558
|
-
console.error(
|
|
15559
|
-
`brainjar: session captured \u2014 ${r.prompts ?? 0} intents, ${r.failures ?? 0} failures, ${(r.skippedNoise ?? 0) + (r.skippedFailures ?? 0)} noise skipped` + (r.consolidated ? `, ${r.consolidated} product facts` : "")
|
|
15560
|
-
);
|
|
15561
|
-
}
|
|
16515
|
+
const link = await captureProject(root);
|
|
16516
|
+
if (!link) {
|
|
16517
|
+
console.error(notConnected(root));
|
|
16518
|
+
if (out.end) rmSync(file, { force: true });
|
|
16519
|
+
return;
|
|
15562
16520
|
}
|
|
16521
|
+
if (existsSync12(file)) await shipSpool(file, link);
|
|
15563
16522
|
if (out.end) rmSync(file, { force: true });
|
|
15564
|
-
|
|
16523
|
+
const dir = path17.dirname(file);
|
|
16524
|
+
for (const other of staleSpools(dir, file)) {
|
|
16525
|
+
const otherLink = await linkForSpool(other);
|
|
16526
|
+
if (!otherLink) continue;
|
|
16527
|
+
await shipSpool(other, otherLink);
|
|
16528
|
+
rmSync(other, { force: true });
|
|
16529
|
+
}
|
|
16530
|
+
pruneSpool(dir, file);
|
|
15565
16531
|
await syncRepo(root);
|
|
15566
16532
|
} catch (e) {
|
|
15567
16533
|
console.error("brainjar hook:", e instanceof Error ? e.message : e);
|
|
@@ -15596,13 +16562,12 @@ function capTranscript(text) {
|
|
|
15596
16562
|
}
|
|
15597
16563
|
async function syncRepo(root) {
|
|
15598
16564
|
try {
|
|
15599
|
-
const
|
|
15600
|
-
if (!
|
|
15601
|
-
console.error(
|
|
15602
|
-
`brainjar: no Brain is connected to ${root} \u2014 run \`npx brainjar init\` (skipping capture)`
|
|
15603
|
-
);
|
|
16565
|
+
const link = await captureProject(root);
|
|
16566
|
+
if (!link) {
|
|
16567
|
+
console.error(notConnected(root));
|
|
15604
16568
|
return;
|
|
15605
16569
|
}
|
|
16570
|
+
const project = await captureApi(`/capture/project?brain=${link.brainId}&source=${link.sourceId}`);
|
|
15606
16571
|
if (project.sourceId) {
|
|
15607
16572
|
const connector = createConnector("git_local", { cwd: root }, {});
|
|
15608
16573
|
const artifacts = await connector.sync(project.cursor ?? void 0);
|
|
@@ -15612,7 +16577,7 @@ async function syncRepo(root) {
|
|
|
15612
16577
|
{
|
|
15613
16578
|
method: "POST",
|
|
15614
16579
|
body: {
|
|
15615
|
-
|
|
16580
|
+
brain: link.brainId,
|
|
15616
16581
|
sourceId: project.sourceId,
|
|
15617
16582
|
artifacts,
|
|
15618
16583
|
cursor: maxUpdatedAt(artifacts) ?? project.cursor
|
|
@@ -15627,7 +16592,7 @@ async function syncRepo(root) {
|
|
|
15627
16592
|
const graph = await scanStructure(root);
|
|
15628
16593
|
await captureApi("/capture/structure", {
|
|
15629
16594
|
method: "POST",
|
|
15630
|
-
body: {
|
|
16595
|
+
body: { brain: link.brainId, graph }
|
|
15631
16596
|
});
|
|
15632
16597
|
}
|
|
15633
16598
|
}
|
|
@@ -15637,7 +16602,10 @@ async function syncRepo(root) {
|
|
|
15637
16602
|
const entries = await scanDebtInRepo2(root);
|
|
15638
16603
|
const r = await captureApi(
|
|
15639
16604
|
"/capture/debt",
|
|
15640
|
-
{
|
|
16605
|
+
{
|
|
16606
|
+
method: "POST",
|
|
16607
|
+
body: { brain: link.brainId, source: link.sourceId, entries }
|
|
16608
|
+
}
|
|
15641
16609
|
);
|
|
15642
16610
|
if (r.added || r.resolved)
|
|
15643
16611
|
console.error(
|
|
@@ -15684,7 +16652,7 @@ __export(init_exports, {
|
|
|
15684
16652
|
init: () => init,
|
|
15685
16653
|
initMain: () => initMain
|
|
15686
16654
|
});
|
|
15687
|
-
import { readFile as
|
|
16655
|
+
import { readFile as readFile12 } from "node:fs/promises";
|
|
15688
16656
|
async function init(argv) {
|
|
15689
16657
|
const skipTranscripts = argv.includes("--no-transcripts");
|
|
15690
16658
|
const flag = (f) => argv.includes(f) ? argv[argv.indexOf(f) + 1] ?? null : null;
|
|
@@ -15713,13 +16681,23 @@ async function init(argv) {
|
|
|
15713
16681
|
return 1;
|
|
15714
16682
|
}
|
|
15715
16683
|
step(2, "Connecting the project Brain\u2026");
|
|
16684
|
+
const known = await captureProject(root);
|
|
16685
|
+
const reuse = known && (!brainFlag || known.brainId === brainFlag) ? known : null;
|
|
15716
16686
|
const conn = await captureApi("/capture/connect", {
|
|
15717
16687
|
method: "POST",
|
|
15718
|
-
body: {
|
|
16688
|
+
body: {
|
|
16689
|
+
name,
|
|
16690
|
+
...brainFlag ? { brainId: brainFlag } : reuse ? { brainId: reuse.brainId } : {},
|
|
16691
|
+
...reuse ? { sourceId: reuse.sourceId } : {}
|
|
16692
|
+
}
|
|
15719
16693
|
});
|
|
15720
16694
|
ok(
|
|
15721
16695
|
conn.createdBrain ? `created Brain "${name}" (${conn.brainId})` : `reusing Brain "${name}" (${conn.brainId})`
|
|
15722
16696
|
);
|
|
16697
|
+
writeProjectLink(root, { brainId: conn.brainId, sourceId: conn.sourceId });
|
|
16698
|
+
ok(
|
|
16699
|
+
`folder linked to the Brain in ${projectsPath()} (the path stays on this machine)`
|
|
16700
|
+
);
|
|
15723
16701
|
if (serverFlag || tokenFlag) {
|
|
15724
16702
|
writeUserConfig({
|
|
15725
16703
|
...serverFlag ? { BRAINJAR_SERVER: serverFlag } : {},
|
|
@@ -15731,7 +16709,7 @@ async function init(argv) {
|
|
|
15731
16709
|
const graph = await scanStructure(root);
|
|
15732
16710
|
const structure = await captureApi(
|
|
15733
16711
|
"/capture/structure",
|
|
15734
|
-
{ method: "POST", body: {
|
|
16712
|
+
{ method: "POST", body: { brain: conn.brainId, graph } }
|
|
15735
16713
|
);
|
|
15736
16714
|
const connector = createConnector("git_local", { cwd: root }, {});
|
|
15737
16715
|
const artifacts = await connector.sync(conn.cursor ?? void 0);
|
|
@@ -15743,7 +16721,7 @@ async function init(argv) {
|
|
|
15743
16721
|
{
|
|
15744
16722
|
method: "POST",
|
|
15745
16723
|
body: {
|
|
15746
|
-
|
|
16724
|
+
brain: conn.brainId,
|
|
15747
16725
|
sourceId: conn.sourceId,
|
|
15748
16726
|
artifacts,
|
|
15749
16727
|
cursor: maxUpdatedAt(artifacts) ?? conn.cursor
|
|
@@ -15769,11 +16747,11 @@ async function init(argv) {
|
|
|
15769
16747
|
let skipped = 0;
|
|
15770
16748
|
for (const f of files) {
|
|
15771
16749
|
try {
|
|
15772
|
-
const { text, truncated } = capTranscript(await
|
|
16750
|
+
const { text, truncated } = capTranscript(await readFile12(f, "utf8"));
|
|
15773
16751
|
if (truncated) truncatedCount++;
|
|
15774
16752
|
const r = await captureApi("/capture/transcript", {
|
|
15775
16753
|
method: "POST",
|
|
15776
|
-
body: {
|
|
16754
|
+
body: { brain: conn.brainId, text, author, backfill: true }
|
|
15777
16755
|
});
|
|
15778
16756
|
prompts += r.prompts;
|
|
15779
16757
|
failures += r.failures;
|
|
@@ -15792,7 +16770,7 @@ async function init(argv) {
|
|
|
15792
16770
|
const tools = detectedTools(root);
|
|
15793
16771
|
if (tools.length === 0) {
|
|
15794
16772
|
ok(
|
|
15795
|
-
"no coding agent detected (looked for Claude Code, Cursor, Codex, GitHub Copilot); rerun init after installing one"
|
|
16773
|
+
"no coding agent detected (looked for Claude Code, Cursor, Codex, GitHub Copilot, Gemini CLI, Kiro, OpenCode, Cline); rerun init after installing one"
|
|
15796
16774
|
);
|
|
15797
16775
|
}
|
|
15798
16776
|
for (const tool of tools) {
|
|
@@ -15816,7 +16794,7 @@ async function init(argv) {
|
|
|
15816
16794
|
ok("skipped (server unreachable) \u2014 the sync hook will add it");
|
|
15817
16795
|
}
|
|
15818
16796
|
const final = await captureApi(
|
|
15819
|
-
`/capture/project?
|
|
16797
|
+
`/capture/project?brain=${conn.brainId}&source=${conn.sourceId}`
|
|
15820
16798
|
);
|
|
15821
16799
|
console.log(
|
|
15822
16800
|
`
|
|
@@ -15859,7 +16837,7 @@ __export(constraints_exports, {
|
|
|
15859
16837
|
constraintsMain: () => constraintsMain,
|
|
15860
16838
|
sessionStartEnvelope: () => sessionStartEnvelope
|
|
15861
16839
|
});
|
|
15862
|
-
import { readFileSync as
|
|
16840
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
15863
16841
|
function sessionStartEnvelope(text) {
|
|
15864
16842
|
return JSON.stringify({
|
|
15865
16843
|
hookSpecificOutput: {
|
|
@@ -15872,12 +16850,13 @@ async function constraintsMain(argv) {
|
|
|
15872
16850
|
const hook = argv.includes("--hook");
|
|
15873
16851
|
const brainFlag = argv[argv.indexOf("--brain") + 1];
|
|
15874
16852
|
const brainOverride = argv.includes("--brain") ? brainFlag ?? null : null;
|
|
16853
|
+
const envelope = argv.includes("--copilot") ? copilotContextEnvelope : argv.includes("--cline") ? clineContextEnvelope : argv.includes("--plain") ? (text) => text : sessionStartEnvelope;
|
|
15875
16854
|
try {
|
|
15876
16855
|
let root;
|
|
15877
16856
|
if (hook) {
|
|
15878
16857
|
let cwd;
|
|
15879
16858
|
try {
|
|
15880
|
-
const payload = JSON.parse(
|
|
16859
|
+
const payload = JSON.parse(readFileSync13(0, "utf8"));
|
|
15881
16860
|
cwd = payload.cwd ?? payload.workspace_roots?.[0] ?? payload.workspaceRoots?.[0];
|
|
15882
16861
|
} catch {
|
|
15883
16862
|
}
|
|
@@ -15885,22 +16864,19 @@ async function constraintsMain(argv) {
|
|
|
15885
16864
|
} else {
|
|
15886
16865
|
root = await repoRoot();
|
|
15887
16866
|
}
|
|
15888
|
-
const
|
|
15889
|
-
if (
|
|
15890
|
-
const r = await captureApi(
|
|
15891
|
-
`/capture/constraints?${q}`
|
|
15892
|
-
);
|
|
15893
|
-
if (!r.brainId) {
|
|
16867
|
+
const brainId = brainOverride ?? (await captureProject(root))?.brainId;
|
|
16868
|
+
if (!brainId) {
|
|
15894
16869
|
if (!hook) {
|
|
15895
|
-
console.error(
|
|
15896
|
-
`brainjar: no Brain is connected to ${root}. Pass --brain <id> or run \`npx brainjar init\` first.`
|
|
15897
|
-
);
|
|
16870
|
+
console.error(notConnected(root));
|
|
15898
16871
|
process.exitCode = 1;
|
|
15899
16872
|
}
|
|
15900
16873
|
return;
|
|
15901
16874
|
}
|
|
15902
|
-
const
|
|
16875
|
+
const r = await captureApi(
|
|
16876
|
+
`/capture/constraints?brain=${encodeURIComponent(brainId)}&limit=5`
|
|
16877
|
+
);
|
|
15903
16878
|
if (r.text) console.log(hook ? envelope(r.text) : r.text);
|
|
16879
|
+
else if (hook && argv.includes("--cline")) console.log(CLINE_REPLY);
|
|
15904
16880
|
else if (!hook)
|
|
15905
16881
|
console.log("No recorded failed approaches or rules for this Brain.");
|
|
15906
16882
|
if (hook) {
|
|
@@ -15911,7 +16887,12 @@ async function constraintsMain(argv) {
|
|
|
15911
16887
|
}
|
|
15912
16888
|
}
|
|
15913
16889
|
} catch (e) {
|
|
15914
|
-
|
|
16890
|
+
if (hook && e instanceof TokenError) console.log(envelope(e.message));
|
|
16891
|
+
else
|
|
16892
|
+
console.error(
|
|
16893
|
+
"brainjar constraints:",
|
|
16894
|
+
e instanceof Error ? e.message : e
|
|
16895
|
+
);
|
|
15915
16896
|
if (!hook) process.exitCode = 1;
|
|
15916
16897
|
}
|
|
15917
16898
|
}
|
|
@@ -15921,6 +16902,7 @@ var init_constraints = __esm({
|
|
|
15921
16902
|
init_src3();
|
|
15922
16903
|
init_api_client();
|
|
15923
16904
|
init_copilot();
|
|
16905
|
+
init_cline();
|
|
15924
16906
|
}
|
|
15925
16907
|
});
|
|
15926
16908
|
|
|
@@ -15972,22 +16954,27 @@ async function runDoctor(root, opts = {}) {
|
|
|
15972
16954
|
`${serverUrl()} unreachable (${String(e).slice(0, 120)})`
|
|
15973
16955
|
);
|
|
15974
16956
|
}
|
|
16957
|
+
const link = await captureProject(root);
|
|
15975
16958
|
if (serverOk) {
|
|
15976
16959
|
try {
|
|
15977
|
-
const project = await captureApi(
|
|
16960
|
+
const project = await captureApi(
|
|
16961
|
+
link ? `/capture/project?brain=${link.brainId}&source=${link.sourceId}` : "/capture/project"
|
|
16962
|
+
);
|
|
15978
16963
|
add("token", true, "accepted");
|
|
15979
16964
|
add(
|
|
15980
16965
|
"brain",
|
|
15981
16966
|
project.brainId !== null,
|
|
15982
|
-
project.brainId !== null ? `${project.brainId} (${project.objects ?? 0} memory objects)` : "
|
|
16967
|
+
project.brainId !== null ? `${project.brainId} (${project.objects ?? 0} memory objects)` : "not connected on this machine. Run npx brainjar init --brain <id>"
|
|
15983
16968
|
);
|
|
15984
16969
|
} catch (e) {
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
false,
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
16970
|
+
const msg = String(e instanceof Error ? e.message : e).slice(0, 200);
|
|
16971
|
+
if (e instanceof TokenError) {
|
|
16972
|
+
add("token", false, msg);
|
|
16973
|
+
add("brain", false, "skipped (token rejected)");
|
|
16974
|
+
} else {
|
|
16975
|
+
add("token", true, "accepted");
|
|
16976
|
+
add("brain", false, msg);
|
|
16977
|
+
}
|
|
15991
16978
|
}
|
|
15992
16979
|
} else {
|
|
15993
16980
|
add("token", false, "skipped (server unreachable)");
|
|
@@ -16072,8 +17059,8 @@ __export(deepscan_exports, {
|
|
|
16072
17059
|
deepscanMain: () => deepscanMain,
|
|
16073
17060
|
selectDeepScanFiles: () => selectDeepScanFiles
|
|
16074
17061
|
});
|
|
16075
|
-
import { readdir as readdir3, readFile as
|
|
16076
|
-
import
|
|
17062
|
+
import { readdir as readdir3, readFile as readFile13, stat as stat2 } from "node:fs/promises";
|
|
17063
|
+
import path18 from "node:path";
|
|
16077
17064
|
function selectDeepScanFiles(candidates, graph, maxFiles = MAX_FILES) {
|
|
16078
17065
|
const picked = [];
|
|
16079
17066
|
const has = /* @__PURE__ */ new Set();
|
|
@@ -16110,7 +17097,7 @@ async function walkCandidates(root) {
|
|
|
16110
17097
|
for (const e of entries) {
|
|
16111
17098
|
if (!e.isFile()) continue;
|
|
16112
17099
|
const parent = e.parentPath ?? e.path ?? root;
|
|
16113
|
-
const rel =
|
|
17100
|
+
const rel = path18.relative(root, path18.join(parent, e.name)).split(path18.sep).join("/");
|
|
16114
17101
|
if (rel.split("/").some((seg) => SKIP_DIRS.has(seg))) continue;
|
|
16115
17102
|
out.push(rel);
|
|
16116
17103
|
}
|
|
@@ -16118,11 +17105,9 @@ async function walkCandidates(root) {
|
|
|
16118
17105
|
}
|
|
16119
17106
|
async function deepscanMain() {
|
|
16120
17107
|
const root = await repoRoot();
|
|
16121
|
-
const project = await
|
|
16122
|
-
if (!project
|
|
16123
|
-
console.error(
|
|
16124
|
-
`brainjar: no Brain is connected to ${root} \u2014 run \`npx brainjar init\` first`
|
|
16125
|
-
);
|
|
17108
|
+
const project = await captureProject(root);
|
|
17109
|
+
if (!project) {
|
|
17110
|
+
console.error(notConnected(root));
|
|
16126
17111
|
process.exitCode = 1;
|
|
16127
17112
|
return;
|
|
16128
17113
|
}
|
|
@@ -16135,10 +17120,10 @@ async function deepscanMain() {
|
|
|
16135
17120
|
const artifacts = [];
|
|
16136
17121
|
for (const rel of files) {
|
|
16137
17122
|
try {
|
|
16138
|
-
const abs =
|
|
17123
|
+
const abs = path18.join(root, rel);
|
|
16139
17124
|
const info = await stat2(abs);
|
|
16140
17125
|
if (info.size > RAW_SKIP_BYTES) continue;
|
|
16141
|
-
const content = await
|
|
17126
|
+
const content = await readFile13(abs, "utf8");
|
|
16142
17127
|
artifacts.push({
|
|
16143
17128
|
source: "deepscan",
|
|
16144
17129
|
kind: "source_file",
|
|
@@ -16166,7 +17151,7 @@ async function deepscanMain() {
|
|
|
16166
17151
|
{
|
|
16167
17152
|
method: "POST",
|
|
16168
17153
|
body: {
|
|
16169
|
-
|
|
17154
|
+
brain: project.brainId,
|
|
16170
17155
|
sourceId: project.sourceId,
|
|
16171
17156
|
artifacts: chunk,
|
|
16172
17157
|
// Only the final chunk kicks the server-side synthesis: one scan per run.
|