brainjar 0.1.0 → 0.2.0
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/dist/brainjar.js +508 -82
- package/package.json +3 -3
package/dist/brainjar.js
CHANGED
|
@@ -174,8 +174,8 @@ var init_github = __esm({
|
|
|
174
174
|
if (this.opts.token) h.Authorization = `Bearer ${this.opts.token}`;
|
|
175
175
|
return h;
|
|
176
176
|
}
|
|
177
|
-
async *paginate(
|
|
178
|
-
let url = this.buildUrl(
|
|
177
|
+
async *paginate(path7, params) {
|
|
178
|
+
let url = this.buildUrl(path7, params);
|
|
179
179
|
while (url) {
|
|
180
180
|
const res = await this.fetchImpl(url, { headers: this.headers() });
|
|
181
181
|
if (!res.ok) {
|
|
@@ -186,9 +186,9 @@ var init_github = __esm({
|
|
|
186
186
|
url = parseNextLink(res.headers.get("link"));
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
|
-
buildUrl(
|
|
189
|
+
buildUrl(path7, params) {
|
|
190
190
|
const url = new URL(
|
|
191
|
-
`${this.baseUrl}/repos/${this.opts.owner}/${this.opts.repo}${
|
|
191
|
+
`${this.baseUrl}/repos/${this.opts.owner}/${this.opts.repo}${path7}`
|
|
192
192
|
);
|
|
193
193
|
url.searchParams.set("per_page", String(this.perPage));
|
|
194
194
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -667,13 +667,13 @@ var require_textParsers = __commonJS({
|
|
|
667
667
|
return fn(value);
|
|
668
668
|
};
|
|
669
669
|
}
|
|
670
|
-
function
|
|
670
|
+
function parseBool(value) {
|
|
671
671
|
if (value === null) return value;
|
|
672
672
|
return value === "TRUE" || value === "t" || value === "true" || value === "y" || value === "yes" || value === "on" || value === "1";
|
|
673
673
|
}
|
|
674
674
|
function parseBoolArray(value) {
|
|
675
675
|
if (!value) return null;
|
|
676
|
-
return array.parse(value,
|
|
676
|
+
return array.parse(value, parseBool);
|
|
677
677
|
}
|
|
678
678
|
function parseBaseTenInt(string) {
|
|
679
679
|
return parseInt(string, 10);
|
|
@@ -808,7 +808,7 @@ var require_textParsers = __commonJS({
|
|
|
808
808
|
register(26, parseInteger);
|
|
809
809
|
register(700, parseFloat);
|
|
810
810
|
register(701, parseFloat);
|
|
811
|
-
register(16,
|
|
811
|
+
register(16, parseBool);
|
|
812
812
|
register(1082, parseDate);
|
|
813
813
|
register(1114, parseDate);
|
|
814
814
|
register(1184, parseDate);
|
|
@@ -1105,7 +1105,7 @@ var require_binaryParsers = __commonJS({
|
|
|
1105
1105
|
var parseText = function(value) {
|
|
1106
1106
|
return value.toString("utf8");
|
|
1107
1107
|
};
|
|
1108
|
-
var
|
|
1108
|
+
var parseBool = function(value) {
|
|
1109
1109
|
if (value === null) return null;
|
|
1110
1110
|
return parseBits(value, 8) > 0;
|
|
1111
1111
|
};
|
|
@@ -1117,7 +1117,7 @@ var require_binaryParsers = __commonJS({
|
|
|
1117
1117
|
register(1700, parseNumeric);
|
|
1118
1118
|
register(700, parseFloat32);
|
|
1119
1119
|
register(701, parseFloat64);
|
|
1120
|
-
register(16,
|
|
1120
|
+
register(16, parseBool);
|
|
1121
1121
|
register(1114, parseDate.bind(null, false));
|
|
1122
1122
|
register(1184, parseDate.bind(null, true));
|
|
1123
1123
|
register(1e3, parseArray);
|
|
@@ -3671,7 +3671,7 @@ var require_split2 = __commonJS({
|
|
|
3671
3671
|
var require_helper = __commonJS({
|
|
3672
3672
|
"../../node_modules/pgpass/lib/helper.js"(exports, module) {
|
|
3673
3673
|
"use strict";
|
|
3674
|
-
var
|
|
3674
|
+
var path7 = __require("path");
|
|
3675
3675
|
var Stream2 = __require("stream").Stream;
|
|
3676
3676
|
var split = require_split2();
|
|
3677
3677
|
var util = __require("util");
|
|
@@ -3710,7 +3710,7 @@ var require_helper = __commonJS({
|
|
|
3710
3710
|
};
|
|
3711
3711
|
module.exports.getFileName = function(rawEnv) {
|
|
3712
3712
|
var env = rawEnv || process.env;
|
|
3713
|
-
var file = env.PGPASSFILE || (isWin ?
|
|
3713
|
+
var file = env.PGPASSFILE || (isWin ? path7.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path7.join(env.HOME || "./", ".pgpass"));
|
|
3714
3714
|
return file;
|
|
3715
3715
|
};
|
|
3716
3716
|
module.exports.usePgPass = function(stats, fname) {
|
|
@@ -3842,13 +3842,13 @@ var require_helper = __commonJS({
|
|
|
3842
3842
|
var require_lib = __commonJS({
|
|
3843
3843
|
"../../node_modules/pgpass/lib/index.js"(exports, module) {
|
|
3844
3844
|
"use strict";
|
|
3845
|
-
var
|
|
3845
|
+
var path7 = __require("path");
|
|
3846
3846
|
var fs = __require("fs");
|
|
3847
3847
|
var helper = require_helper();
|
|
3848
3848
|
module.exports = function(connInfo, cb) {
|
|
3849
3849
|
var file = helper.getFileName();
|
|
3850
|
-
fs.stat(file, function(err,
|
|
3851
|
-
if (err || !helper.usePgPass(
|
|
3850
|
+
fs.stat(file, function(err, stat3) {
|
|
3851
|
+
if (err || !helper.usePgPass(stat3, file)) {
|
|
3852
3852
|
return cb(void 0);
|
|
3853
3853
|
}
|
|
3854
3854
|
var st = fs.createReadStream(file);
|
|
@@ -5617,10 +5617,6 @@ function loadEnv() {
|
|
|
5617
5617
|
}
|
|
5618
5618
|
return { ...cachedFileVars, ...process.env };
|
|
5619
5619
|
}
|
|
5620
|
-
function parseBool(v) {
|
|
5621
|
-
if (v == null) return false;
|
|
5622
|
-
return /^(1|true|yes|on)$/i.test(v.trim());
|
|
5623
|
-
}
|
|
5624
5620
|
function loadConfig(env = loadEnv()) {
|
|
5625
5621
|
return new Config(env);
|
|
5626
5622
|
}
|
|
@@ -5667,7 +5663,6 @@ var init_config = __esm({
|
|
|
5667
5663
|
};
|
|
5668
5664
|
Config = class {
|
|
5669
5665
|
environment;
|
|
5670
|
-
cloudAiEnabled;
|
|
5671
5666
|
databaseUrl;
|
|
5672
5667
|
/** The tenant all connections are pinned to (RLS). DEFAULT_TENANT_ID in local mode. */
|
|
5673
5668
|
tenantId;
|
|
@@ -5685,16 +5680,14 @@ var init_config = __esm({
|
|
|
5685
5680
|
const tenant = env.BRAINJAR_TENANT_ID?.trim();
|
|
5686
5681
|
if (!tenant) {
|
|
5687
5682
|
throw new Error(
|
|
5688
|
-
"ENVIRONMENT=saas requires BRAINJAR_TENANT_ID
|
|
5683
|
+
"ENVIRONMENT=saas requires BRAINJAR_TENANT_ID: every SaaS connection is tenant-scoped."
|
|
5689
5684
|
);
|
|
5690
5685
|
}
|
|
5691
5686
|
this.databaseUrl = url;
|
|
5692
5687
|
this.tenantId = tenant;
|
|
5693
|
-
this.cloudAiEnabled = true;
|
|
5694
5688
|
} else {
|
|
5695
5689
|
this.databaseUrl = env.DATABASE_URL?.trim() || DEFAULT_DATABASE_URL;
|
|
5696
5690
|
this.tenantId = DEFAULT_TENANT_ID;
|
|
5697
|
-
this.cloudAiEnabled = parseBool(env.BRAINJAR_CLOUD_AI);
|
|
5698
5691
|
}
|
|
5699
5692
|
}
|
|
5700
5693
|
/** Returns a secret, or throws an actionable error naming the env var and its purpose. */
|
|
@@ -5705,17 +5698,11 @@ var init_config = __esm({
|
|
|
5705
5698
|
if (!value2) {
|
|
5706
5699
|
throw new MissingSecretError(
|
|
5707
5700
|
key,
|
|
5708
|
-
`Missing ${CORPORATE_API_KEY_ENV}. In SaaS mode, ${spec.what} is billed to the central corporate account
|
|
5701
|
+
`Missing ${CORPORATE_API_KEY_ENV}. In SaaS mode, ${spec.what} is billed to the central corporate account: set ${CORPORATE_API_KEY_ENV} in the backend environment.`
|
|
5709
5702
|
);
|
|
5710
5703
|
}
|
|
5711
5704
|
return value2;
|
|
5712
5705
|
}
|
|
5713
|
-
if (spec.cloudAi && !this.cloudAiEnabled) {
|
|
5714
|
-
throw new MissingSecretError(
|
|
5715
|
-
key,
|
|
5716
|
-
`Cloud AI is disabled (BrainJar is local-first by default). To use ${spec.what}, set BRAINJAR_CLOUD_AI=true and ${spec.env}.`
|
|
5717
|
-
);
|
|
5718
|
-
}
|
|
5719
5706
|
const value = this.#env[spec.env]?.trim();
|
|
5720
5707
|
if (!value) {
|
|
5721
5708
|
throw new MissingSecretError(
|
|
@@ -5738,7 +5725,6 @@ var init_config = __esm({
|
|
|
5738
5725
|
return {
|
|
5739
5726
|
environment: this.environment,
|
|
5740
5727
|
tenantId: this.tenantId,
|
|
5741
|
-
cloudAiEnabled: this.cloudAiEnabled,
|
|
5742
5728
|
databaseUrl: redactUrlPassword(this.databaseUrl)
|
|
5743
5729
|
};
|
|
5744
5730
|
}
|
|
@@ -5753,12 +5739,14 @@ var init_config = __esm({
|
|
|
5753
5739
|
});
|
|
5754
5740
|
|
|
5755
5741
|
// ../../packages/shared/src/index.ts
|
|
5742
|
+
var PROSE_STYLE;
|
|
5756
5743
|
var init_src = __esm({
|
|
5757
5744
|
"../../packages/shared/src/index.ts"() {
|
|
5758
5745
|
"use strict";
|
|
5759
5746
|
init_memory();
|
|
5760
5747
|
init_confidence();
|
|
5761
5748
|
init_config();
|
|
5749
|
+
PROSE_STYLE = "Never use em dashes (\u2014) or en dashes (\u2013) anywhere; use commas, colons, periods, or parentheses instead.";
|
|
5762
5750
|
}
|
|
5763
5751
|
});
|
|
5764
5752
|
|
|
@@ -5917,6 +5905,34 @@ var init_repository = __esm({
|
|
|
5917
5905
|
[this.brainId]
|
|
5918
5906
|
)).rows;
|
|
5919
5907
|
}
|
|
5908
|
+
/**
|
|
5909
|
+
* Verified facts whose verified_scan evidence points at any of these repo-relative files —
|
|
5910
|
+
* the re-verification trigger: a commit touched their proof, so their status may be stale.
|
|
5911
|
+
*/
|
|
5912
|
+
async verifiedObjectsTouchingFiles(paths) {
|
|
5913
|
+
if (paths.length === 0) return [];
|
|
5914
|
+
return (await this.pool.query(
|
|
5915
|
+
`SELECT DISTINCT ${MO_COLUMNS}, ${EV_AGG} FROM memory_object
|
|
5916
|
+
WHERE brain_id = $1 AND status = 'active'
|
|
5917
|
+
AND EXISTS (
|
|
5918
|
+
SELECT 1 FROM evidence_link ev
|
|
5919
|
+
WHERE ev.memory_object_id = memory_object.id
|
|
5920
|
+
AND ev.artifact_kind = 'verified_scan'
|
|
5921
|
+
AND EXISTS (
|
|
5922
|
+
SELECT 1 FROM unnest($2::text[]) AS p(path)
|
|
5923
|
+
WHERE ev.artifact_uri LIKE '%/' || p.path
|
|
5924
|
+
)
|
|
5925
|
+
)`,
|
|
5926
|
+
[this.brainId, paths]
|
|
5927
|
+
)).rows;
|
|
5928
|
+
}
|
|
5929
|
+
/** Replace a source's config (e.g. the GitHub repo/token a Brain reads from). */
|
|
5930
|
+
async setSourceConfig(id, config) {
|
|
5931
|
+
await this.pool.query(
|
|
5932
|
+
"UPDATE source SET config = $3 WHERE id = $1 AND brain_id = $2",
|
|
5933
|
+
[id, this.brainId, JSON.stringify(config)]
|
|
5934
|
+
);
|
|
5935
|
+
}
|
|
5920
5936
|
async getSource(id) {
|
|
5921
5937
|
return (await this.pool.query(
|
|
5922
5938
|
"SELECT * FROM source WHERE id = $1 AND brain_id = $2",
|
|
@@ -6144,6 +6160,18 @@ var init_repository = __esm({
|
|
|
6144
6160
|
[this.brainId, startIso, endIso, limit]
|
|
6145
6161
|
)).rows;
|
|
6146
6162
|
}
|
|
6163
|
+
/**
|
|
6164
|
+
* The newest substantive objects ("what are the latest ...?"). Entities are timeless
|
|
6165
|
+
* structure, not news, so they are excluded along with summaries.
|
|
6166
|
+
*/
|
|
6167
|
+
async newestObjects(limit = 8) {
|
|
6168
|
+
return (await this.pool.query(
|
|
6169
|
+
`SELECT ${MO_COLUMNS}, ${EV_AGG} FROM memory_object
|
|
6170
|
+
WHERE brain_id = $1 AND type NOT IN ('summary', 'entity')
|
|
6171
|
+
ORDER BY occurred_at DESC LIMIT $2`,
|
|
6172
|
+
[this.brainId, limit]
|
|
6173
|
+
)).rows;
|
|
6174
|
+
}
|
|
6147
6175
|
/** The daily_log (chronology rollup) for a given day, if one exists (one per active day). */
|
|
6148
6176
|
async findDailyLog(date) {
|
|
6149
6177
|
return (await this.pool.query(
|
|
@@ -6602,7 +6630,7 @@ var init_auth = __esm({
|
|
|
6602
6630
|
"../../packages/db/src/auth.ts"() {
|
|
6603
6631
|
"use strict";
|
|
6604
6632
|
init_pool();
|
|
6605
|
-
DEFAULT_TTL_HOURS = 24 *
|
|
6633
|
+
DEFAULT_TTL_HOURS = 24 * 90;
|
|
6606
6634
|
}
|
|
6607
6635
|
});
|
|
6608
6636
|
|
|
@@ -6653,10 +6681,24 @@ async function resolveSetting(key, envValue, pool2 = getPool()) {
|
|
|
6653
6681
|
return void 0;
|
|
6654
6682
|
}
|
|
6655
6683
|
}
|
|
6656
|
-
|
|
6684
|
+
async function resolveAnthropicApiKey(envKey, pool2 = getPool()) {
|
|
6685
|
+
const fromEnv = envKey?.trim();
|
|
6686
|
+
if (fromEnv) return fromEnv;
|
|
6687
|
+
try {
|
|
6688
|
+
const r = await pool2.query(
|
|
6689
|
+
"SELECT value FROM app_setting WHERE key = $1",
|
|
6690
|
+
[ANTHROPIC_KEY_SETTING]
|
|
6691
|
+
);
|
|
6692
|
+
return r.rows[0]?.value?.trim() || null;
|
|
6693
|
+
} catch {
|
|
6694
|
+
return null;
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
var APP_SETTING_KEYS, ANTHROPIC_KEY_SETTING;
|
|
6657
6698
|
var init_settings = __esm({
|
|
6658
6699
|
"../../packages/db/src/settings.ts"() {
|
|
6659
6700
|
"use strict";
|
|
6701
|
+
init_src();
|
|
6660
6702
|
init_pool();
|
|
6661
6703
|
APP_SETTING_KEYS = [
|
|
6662
6704
|
"capture_mode",
|
|
@@ -6670,6 +6712,23 @@ var init_settings = __esm({
|
|
|
6670
6712
|
"public_url"
|
|
6671
6713
|
// this server's address as clients reach it (wizard command, MCP config)
|
|
6672
6714
|
];
|
|
6715
|
+
ANTHROPIC_KEY_SETTING = "anthropic_api_key";
|
|
6716
|
+
}
|
|
6717
|
+
});
|
|
6718
|
+
|
|
6719
|
+
// ../../packages/db/src/notifications.ts
|
|
6720
|
+
var init_notifications = __esm({
|
|
6721
|
+
"../../packages/db/src/notifications.ts"() {
|
|
6722
|
+
"use strict";
|
|
6723
|
+
init_pool();
|
|
6724
|
+
}
|
|
6725
|
+
});
|
|
6726
|
+
|
|
6727
|
+
// ../../packages/db/src/conversations.ts
|
|
6728
|
+
var init_conversations = __esm({
|
|
6729
|
+
"../../packages/db/src/conversations.ts"() {
|
|
6730
|
+
"use strict";
|
|
6731
|
+
init_pool();
|
|
6673
6732
|
}
|
|
6674
6733
|
});
|
|
6675
6734
|
|
|
@@ -6685,6 +6744,8 @@ var init_src2 = __esm({
|
|
|
6685
6744
|
init_team();
|
|
6686
6745
|
init_rbac();
|
|
6687
6746
|
init_settings();
|
|
6747
|
+
init_notifications();
|
|
6748
|
+
init_conversations();
|
|
6688
6749
|
}
|
|
6689
6750
|
});
|
|
6690
6751
|
|
|
@@ -6775,17 +6836,103 @@ var init_raw_store = __esm({
|
|
|
6775
6836
|
async listForExtraction(brainId, limit = 500) {
|
|
6776
6837
|
return (await this.pool.query(
|
|
6777
6838
|
`SELECT * FROM raw_artifact
|
|
6778
|
-
WHERE brain_id = $1 AND extracted_at IS NULL
|
|
6839
|
+
WHERE brain_id = $1 AND extracted_at IS NULL AND kind <> 'source_file'
|
|
6840
|
+
ORDER BY first_seen_at LIMIT $2`,
|
|
6841
|
+
[brainId, limit]
|
|
6842
|
+
)).rows;
|
|
6843
|
+
}
|
|
6844
|
+
/**
|
|
6845
|
+
* Deep-scan queue: source files awaiting synthesis. Covers both fresh uploads (pending) and
|
|
6846
|
+
* files stored while no AI key existed ('raw-source'), so adding a key later and re-running
|
|
6847
|
+
* the scan picks them up.
|
|
6848
|
+
*/
|
|
6849
|
+
/** How many shipped source files still await synthesis (pending or stored without a key). */
|
|
6850
|
+
async sourceScanCount(brainId) {
|
|
6851
|
+
return (await this.pool.query(
|
|
6852
|
+
`SELECT count(*)::int AS n FROM raw_artifact
|
|
6853
|
+
WHERE brain_id = $1 AND kind = 'source_file'
|
|
6854
|
+
AND (extracted_at IS NULL OR extracted_by = 'raw-source')`,
|
|
6855
|
+
[brainId]
|
|
6856
|
+
)).rows[0]?.n ?? 0;
|
|
6857
|
+
}
|
|
6858
|
+
async listSourceFilesForScan(brainId, limit = 500) {
|
|
6859
|
+
return (await this.pool.query(
|
|
6860
|
+
`SELECT * FROM raw_artifact
|
|
6861
|
+
WHERE brain_id = $1 AND kind = 'source_file'
|
|
6862
|
+
AND (extracted_at IS NULL OR extracted_by = 'raw-source')
|
|
6779
6863
|
ORDER BY first_seen_at LIMIT $2`,
|
|
6780
6864
|
[brainId, limit]
|
|
6781
6865
|
)).rows;
|
|
6782
6866
|
}
|
|
6783
|
-
async markExtracted(id) {
|
|
6867
|
+
async markExtracted(id, by) {
|
|
6784
6868
|
await this.pool.query(
|
|
6785
|
-
"UPDATE raw_artifact SET extracted_at = now() WHERE id = $1",
|
|
6786
|
-
[id]
|
|
6869
|
+
"UPDATE raw_artifact SET extracted_at = now(), extracted_by = $2 WHERE id = $1",
|
|
6870
|
+
[id, by]
|
|
6787
6871
|
);
|
|
6788
6872
|
}
|
|
6873
|
+
/**
|
|
6874
|
+
* The two numbers the Re-synthesize UI keys on. `pending` is the NORMAL pipeline (fresh
|
|
6875
|
+
* artifacts not yet processed — never a reason to offer re-synthesis); `heuristic` is the
|
|
6876
|
+
* backlog captured without AI, the only thing the button acts on.
|
|
6877
|
+
*/
|
|
6878
|
+
async extractionState(brainId) {
|
|
6879
|
+
const r = await this.pool.query(
|
|
6880
|
+
`SELECT
|
|
6881
|
+
count(*) FILTER (WHERE extracted_at IS NULL) AS pending,
|
|
6882
|
+
count(*) FILTER (WHERE extracted_by = 'heuristic') AS heuristic
|
|
6883
|
+
FROM raw_artifact WHERE brain_id = $1`,
|
|
6884
|
+
[brainId]
|
|
6885
|
+
);
|
|
6886
|
+
return {
|
|
6887
|
+
pending: Number(r.rows[0]?.pending ?? 0),
|
|
6888
|
+
heuristic: Number(r.rows[0]?.heuristic ?? 0)
|
|
6889
|
+
};
|
|
6890
|
+
}
|
|
6891
|
+
/**
|
|
6892
|
+
* Prepare heuristically-extracted artifacts for AI re-extraction, atomically:
|
|
6893
|
+
* their derived non-entity objects are deleted (evidence/object links cascade), stale
|
|
6894
|
+
* entity evidence pointing at these artifacts is dropped (re-extraction re-adds it —
|
|
6895
|
+
* entities themselves survive, resolveEntity dedupes them on the re-run), and the
|
|
6896
|
+
* artifacts return to the pipeline (extracted_at/by cleared). Returns how many artifacts
|
|
6897
|
+
* were re-queued — 0 means nothing to do, which is also what makes a double click a no-op.
|
|
6898
|
+
*/
|
|
6899
|
+
async clearForResynthesis(brainId) {
|
|
6900
|
+
const client = await this.pool.connect();
|
|
6901
|
+
try {
|
|
6902
|
+
await client.query("BEGIN");
|
|
6903
|
+
const ids = (await client.query(
|
|
6904
|
+
`SELECT id FROM raw_artifact
|
|
6905
|
+
WHERE brain_id = $1 AND extracted_by = 'heuristic' FOR UPDATE`,
|
|
6906
|
+
[brainId]
|
|
6907
|
+
)).rows.map((r) => r.id);
|
|
6908
|
+
if (ids.length === 0) {
|
|
6909
|
+
await client.query("ROLLBACK");
|
|
6910
|
+
return 0;
|
|
6911
|
+
}
|
|
6912
|
+
await client.query(
|
|
6913
|
+
`DELETE FROM memory_object WHERE type <> 'entity' AND id IN (
|
|
6914
|
+
SELECT memory_object_id FROM evidence_link
|
|
6915
|
+
WHERE raw_artifact_id = ANY($1::uuid[]))`,
|
|
6916
|
+
[ids]
|
|
6917
|
+
);
|
|
6918
|
+
await client.query(
|
|
6919
|
+
"DELETE FROM evidence_link WHERE raw_artifact_id = ANY($1::uuid[])",
|
|
6920
|
+
[ids]
|
|
6921
|
+
);
|
|
6922
|
+
await client.query(
|
|
6923
|
+
`UPDATE raw_artifact SET extracted_at = NULL, extracted_by = NULL
|
|
6924
|
+
WHERE id = ANY($1::uuid[])`,
|
|
6925
|
+
[ids]
|
|
6926
|
+
);
|
|
6927
|
+
await client.query("COMMIT");
|
|
6928
|
+
return ids.length;
|
|
6929
|
+
} catch (e) {
|
|
6930
|
+
await client.query("ROLLBACK");
|
|
6931
|
+
throw e;
|
|
6932
|
+
} finally {
|
|
6933
|
+
client.release();
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6789
6936
|
};
|
|
6790
6937
|
}
|
|
6791
6938
|
});
|
|
@@ -8261,12 +8408,12 @@ var init_path = __esm({
|
|
|
8261
8408
|
"../../node_modules/@anthropic-ai/sdk/internal/utils/path.mjs"() {
|
|
8262
8409
|
init_error();
|
|
8263
8410
|
EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
|
|
8264
|
-
createPathTagFunction = (pathEncoder = encodeURIPath) => function
|
|
8411
|
+
createPathTagFunction = (pathEncoder = encodeURIPath) => function path7(statics, ...params) {
|
|
8265
8412
|
if (statics.length === 1)
|
|
8266
8413
|
return statics[0];
|
|
8267
8414
|
let postPath = false;
|
|
8268
8415
|
const invalidSegments = [];
|
|
8269
|
-
const
|
|
8416
|
+
const path8 = statics.reduce((previousValue, currentValue, index) => {
|
|
8270
8417
|
if (/[?#]/.test(currentValue)) {
|
|
8271
8418
|
postPath = true;
|
|
8272
8419
|
}
|
|
@@ -8283,7 +8430,7 @@ var init_path = __esm({
|
|
|
8283
8430
|
}
|
|
8284
8431
|
return previousValue + currentValue + (index === params.length ? "" : encoded);
|
|
8285
8432
|
}, "");
|
|
8286
|
-
const pathOnly =
|
|
8433
|
+
const pathOnly = path8.split(/[?#]/, 1)[0];
|
|
8287
8434
|
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
|
|
8288
8435
|
let match;
|
|
8289
8436
|
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
|
|
@@ -8304,10 +8451,10 @@ var init_path = __esm({
|
|
|
8304
8451
|
}, "");
|
|
8305
8452
|
throw new AnthropicError(`Path parameters result in path with invalid segments:
|
|
8306
8453
|
${invalidSegments.map((e) => e.error).join("\n")}
|
|
8307
|
-
${
|
|
8454
|
+
${path8}
|
|
8308
8455
|
${underline}`);
|
|
8309
8456
|
}
|
|
8310
|
-
return
|
|
8457
|
+
return path8;
|
|
8311
8458
|
};
|
|
8312
8459
|
path2 = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
|
|
8313
8460
|
}
|
|
@@ -11293,9 +11440,9 @@ var init_client = __esm({
|
|
|
11293
11440
|
makeStatusError(status, error, message, headers) {
|
|
11294
11441
|
return APIError.generate(status, error, message, headers);
|
|
11295
11442
|
}
|
|
11296
|
-
buildURL(
|
|
11443
|
+
buildURL(path7, query, defaultBaseURL) {
|
|
11297
11444
|
const baseURL = !__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
|
|
11298
|
-
const url = isAbsoluteURL(
|
|
11445
|
+
const url = isAbsoluteURL(path7) ? new URL(path7) : new URL(baseURL + (baseURL.endsWith("/") && path7.startsWith("/") ? path7.slice(1) : path7));
|
|
11299
11446
|
const defaultQuery = this.defaultQuery();
|
|
11300
11447
|
if (!isEmptyObj(defaultQuery)) {
|
|
11301
11448
|
query = { ...defaultQuery, ...query };
|
|
@@ -11326,24 +11473,24 @@ var init_client = __esm({
|
|
|
11326
11473
|
*/
|
|
11327
11474
|
async prepareRequest(request, { url, options }) {
|
|
11328
11475
|
}
|
|
11329
|
-
get(
|
|
11330
|
-
return this.methodRequest("get",
|
|
11476
|
+
get(path7, opts) {
|
|
11477
|
+
return this.methodRequest("get", path7, opts);
|
|
11331
11478
|
}
|
|
11332
|
-
post(
|
|
11333
|
-
return this.methodRequest("post",
|
|
11479
|
+
post(path7, opts) {
|
|
11480
|
+
return this.methodRequest("post", path7, opts);
|
|
11334
11481
|
}
|
|
11335
|
-
patch(
|
|
11336
|
-
return this.methodRequest("patch",
|
|
11482
|
+
patch(path7, opts) {
|
|
11483
|
+
return this.methodRequest("patch", path7, opts);
|
|
11337
11484
|
}
|
|
11338
|
-
put(
|
|
11339
|
-
return this.methodRequest("put",
|
|
11485
|
+
put(path7, opts) {
|
|
11486
|
+
return this.methodRequest("put", path7, opts);
|
|
11340
11487
|
}
|
|
11341
|
-
delete(
|
|
11342
|
-
return this.methodRequest("delete",
|
|
11488
|
+
delete(path7, opts) {
|
|
11489
|
+
return this.methodRequest("delete", path7, opts);
|
|
11343
11490
|
}
|
|
11344
|
-
methodRequest(method,
|
|
11491
|
+
methodRequest(method, path7, opts) {
|
|
11345
11492
|
return this.request(Promise.resolve(opts).then((opts2) => {
|
|
11346
|
-
return { method, path:
|
|
11493
|
+
return { method, path: path7, ...opts2 };
|
|
11347
11494
|
}));
|
|
11348
11495
|
}
|
|
11349
11496
|
request(options, remainingRetries = null) {
|
|
@@ -11447,8 +11594,8 @@ var init_client = __esm({
|
|
|
11447
11594
|
}));
|
|
11448
11595
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
11449
11596
|
}
|
|
11450
|
-
getAPIList(
|
|
11451
|
-
return this.requestAPIList(Page2, { method: "get", path:
|
|
11597
|
+
getAPIList(path7, Page2, opts) {
|
|
11598
|
+
return this.requestAPIList(Page2, { method: "get", path: path7, ...opts });
|
|
11452
11599
|
}
|
|
11453
11600
|
requestAPIList(Page2, options) {
|
|
11454
11601
|
const request = this.makeRequest(options, null, void 0);
|
|
@@ -11535,8 +11682,8 @@ var init_client = __esm({
|
|
|
11535
11682
|
}
|
|
11536
11683
|
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
|
|
11537
11684
|
const options = { ...inputOptions };
|
|
11538
|
-
const { method, path:
|
|
11539
|
-
const url = this.buildURL(
|
|
11685
|
+
const { method, path: path7, query, defaultBaseURL } = options;
|
|
11686
|
+
const url = this.buildURL(path7, query, defaultBaseURL);
|
|
11540
11687
|
if ("timeout" in options)
|
|
11541
11688
|
validatePositiveInteger("timeout", options.timeout);
|
|
11542
11689
|
options.timeout = options.timeout ?? this.timeout;
|
|
@@ -11652,11 +11799,11 @@ var init_sdk = __esm({
|
|
|
11652
11799
|
|
|
11653
11800
|
// ../../packages/ingest/src/llm.ts
|
|
11654
11801
|
function rationaleModelId(env = process.env) {
|
|
11655
|
-
return env.BRAINJAR_RATIONALE_MODEL?.trim() || "claude-opus-
|
|
11802
|
+
return env.BRAINJAR_RATIONALE_MODEL?.trim() || "claude-opus-5";
|
|
11656
11803
|
}
|
|
11657
|
-
function
|
|
11804
|
+
function anthropicClient(apiKey, model) {
|
|
11658
11805
|
return new AnthropicClient({
|
|
11659
|
-
apiKey
|
|
11806
|
+
apiKey,
|
|
11660
11807
|
model: model?.trim() || rationaleModelId()
|
|
11661
11808
|
});
|
|
11662
11809
|
}
|
|
@@ -11685,6 +11832,24 @@ var init_llm = __esm({
|
|
|
11685
11832
|
});
|
|
11686
11833
|
return res.content.map((b) => b.type === "text" ? b.text : "").join("").trim();
|
|
11687
11834
|
}
|
|
11835
|
+
/** Same completion, but yielded as text deltas for surfaces that render tokens live (Ask chat). */
|
|
11836
|
+
async *completeStream({
|
|
11837
|
+
system,
|
|
11838
|
+
prompt,
|
|
11839
|
+
maxTokens = 1024
|
|
11840
|
+
}) {
|
|
11841
|
+
const stream = this.client.messages.stream({
|
|
11842
|
+
model: this.model,
|
|
11843
|
+
max_tokens: maxTokens,
|
|
11844
|
+
...system ? { system } : {},
|
|
11845
|
+
messages: [{ role: "user", content: prompt }]
|
|
11846
|
+
});
|
|
11847
|
+
for await (const event of stream) {
|
|
11848
|
+
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
11849
|
+
yield event.delta.text;
|
|
11850
|
+
}
|
|
11851
|
+
}
|
|
11852
|
+
}
|
|
11688
11853
|
};
|
|
11689
11854
|
}
|
|
11690
11855
|
});
|
|
@@ -11694,12 +11859,14 @@ var SYSTEM;
|
|
|
11694
11859
|
var init_rationale_extractor = __esm({
|
|
11695
11860
|
"../../packages/ingest/src/rationale-extractor.ts"() {
|
|
11696
11861
|
"use strict";
|
|
11862
|
+
init_src();
|
|
11697
11863
|
init_heuristic_extractor();
|
|
11698
11864
|
SYSTEM = [
|
|
11699
11865
|
"You extract the WHY behind a software artifact for a memory system.",
|
|
11700
|
-
"Given an artifact (commit, PR, review comment, or agent session), explain in 1-3 sentences why it exists",
|
|
11701
|
-
"
|
|
11702
|
-
"Respond with only the rationale sentences. No preamble, no headings, no quotes."
|
|
11866
|
+
"Given an artifact (commit, PR, review comment, or agent session), explain in 1-3 sentences why it exists:",
|
|
11867
|
+
"the decision, intent, or business reason it reflects. Infer from the evidence; do not invent specifics.",
|
|
11868
|
+
"Respond with only the rationale sentences. No preamble, no headings, no quotes.",
|
|
11869
|
+
PROSE_STYLE
|
|
11703
11870
|
].join(" ");
|
|
11704
11871
|
}
|
|
11705
11872
|
});
|
|
@@ -11719,6 +11886,14 @@ var init_impact = __esm({
|
|
|
11719
11886
|
}
|
|
11720
11887
|
});
|
|
11721
11888
|
|
|
11889
|
+
// ../../packages/ingest/src/commit-links.ts
|
|
11890
|
+
var init_commit_links = __esm({
|
|
11891
|
+
"../../packages/ingest/src/commit-links.ts"() {
|
|
11892
|
+
"use strict";
|
|
11893
|
+
init_src2();
|
|
11894
|
+
}
|
|
11895
|
+
});
|
|
11896
|
+
|
|
11722
11897
|
// ../../packages/ingest/src/extraction.ts
|
|
11723
11898
|
var init_extraction = __esm({
|
|
11724
11899
|
"../../packages/ingest/src/extraction.ts"() {
|
|
@@ -11728,6 +11903,9 @@ var init_extraction = __esm({
|
|
|
11728
11903
|
init_raw_store();
|
|
11729
11904
|
init_signal();
|
|
11730
11905
|
init_impact();
|
|
11906
|
+
init_commit_links();
|
|
11907
|
+
init_entities();
|
|
11908
|
+
init_rationale_extractor();
|
|
11731
11909
|
}
|
|
11732
11910
|
});
|
|
11733
11911
|
|
|
@@ -11797,6 +11975,9 @@ var init_structure = __esm({
|
|
|
11797
11975
|
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
11798
11976
|
"node_modules",
|
|
11799
11977
|
".git",
|
|
11978
|
+
// Parallel agent worktrees live inside the repo; scanning them creates duplicate entities
|
|
11979
|
+
// with .claude/worktrees/... paths that pollute retrieval.
|
|
11980
|
+
".claude",
|
|
11800
11981
|
".next",
|
|
11801
11982
|
"dist",
|
|
11802
11983
|
"build",
|
|
@@ -11814,6 +11995,7 @@ var init_product_facts = __esm({
|
|
|
11814
11995
|
"use strict";
|
|
11815
11996
|
init_src2();
|
|
11816
11997
|
init_entities();
|
|
11998
|
+
init_src();
|
|
11817
11999
|
PRODUCT_FACTS_SYSTEM = [
|
|
11818
12000
|
"You read a raw coding-agent session and extract durable PRODUCT facts for a documentation system.",
|
|
11819
12001
|
"Output ONLY a JSON array (no prose, no code fences). Each item:",
|
|
@@ -11822,8 +12004,9 @@ var init_product_facts = __esm({
|
|
|
11822
12004
|
"A 'feature' is user- or developer-facing capability (a button, a page, an endpoint, a command).",
|
|
11823
12005
|
"A 'decision' is an architectural/design choice with a reason.",
|
|
11824
12006
|
"Capture what was BUILT or CHANGED, where it lives (files/symbols), how it behaves, why, and any",
|
|
11825
|
-
"configuration it exposes. Prefer a few high-signal facts over many trivial ones. Invent nothing
|
|
11826
|
-
"if the session only debugged or explored, return []."
|
|
12007
|
+
"configuration it exposes. Prefer a few high-signal facts over many trivial ones. Invent nothing:",
|
|
12008
|
+
"if the session only debugged or explored, return [].",
|
|
12009
|
+
PROSE_STYLE
|
|
11827
12010
|
].join(" ");
|
|
11828
12011
|
}
|
|
11829
12012
|
});
|
|
@@ -11840,24 +12023,18 @@ var CONSOLIDATE_SYSTEM;
|
|
|
11840
12023
|
var init_compress = __esm({
|
|
11841
12024
|
"../../packages/ingest/src/compress.ts"() {
|
|
11842
12025
|
"use strict";
|
|
12026
|
+
init_src();
|
|
11843
12027
|
CONSOLIDATE_SYSTEM = [
|
|
11844
12028
|
"You compress a raw multi-turn coding-agent session for a memory system.",
|
|
11845
12029
|
"Produce a single high-density summary with exactly three sections: Intent, Failed Attempts, Resolution.",
|
|
11846
12030
|
"Preserve 100% of the architectural reasoning and the chronological order of events.",
|
|
11847
12031
|
"Keep every distinct error message (once) and every rejected approach with why it failed.",
|
|
11848
|
-
"Drop all pleasantries, tool chatter, and restated code. Target at most half the input length."
|
|
12032
|
+
"Drop all pleasantries, tool chatter, and restated code. Target at most half the input length.",
|
|
12033
|
+
PROSE_STYLE
|
|
11849
12034
|
].join(" ");
|
|
11850
12035
|
}
|
|
11851
12036
|
});
|
|
11852
12037
|
|
|
11853
|
-
// ../../packages/ingest/src/commit-links.ts
|
|
11854
|
-
var init_commit_links = __esm({
|
|
11855
|
-
"../../packages/ingest/src/commit-links.ts"() {
|
|
11856
|
-
"use strict";
|
|
11857
|
-
init_src2();
|
|
11858
|
-
}
|
|
11859
|
-
});
|
|
11860
|
-
|
|
11861
12038
|
// ../../packages/ingest/src/transcript.ts
|
|
11862
12039
|
var init_transcript = __esm({
|
|
11863
12040
|
"../../packages/ingest/src/transcript.ts"() {
|
|
@@ -12643,6 +12820,7 @@ var init_doku = __esm({
|
|
|
12643
12820
|
"../../packages/ingest/src/doku.ts"() {
|
|
12644
12821
|
"use strict";
|
|
12645
12822
|
init_src2();
|
|
12823
|
+
init_src();
|
|
12646
12824
|
init_doku_model();
|
|
12647
12825
|
DOKU_FILENAME = "BRAINJAR_DOKU.md";
|
|
12648
12826
|
DOKU_DIR = "brainjar-doku";
|
|
@@ -12655,14 +12833,83 @@ var init_doku = __esm({
|
|
|
12655
12833
|
);
|
|
12656
12834
|
isModule = (o) => o.type === "entity" && o.body.kind === "module";
|
|
12657
12835
|
moduleName = (o) => String(o.body.name ?? o.title);
|
|
12658
|
-
isRepoPath = (name) => !/^([a-zA-Z]:[/\\]|[/\\])/.test(name) && !name.includes("node_modules");
|
|
12836
|
+
isRepoPath = (name) => !/^([a-zA-Z]:[/\\]|[/\\])/.test(name) && !name.includes("node_modules") && !name.includes(".claude/");
|
|
12659
12837
|
normPath = (p) => p.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
12660
12838
|
NARRATIVE_SYSTEM = [
|
|
12661
12839
|
"You write the introduction for one feature's documentation page, for a mixed audience",
|
|
12662
12840
|
"(developers, product managers, leadership). You are given the page's structured facts as",
|
|
12663
12841
|
"markdown. Respond with 1-3 short plain-text paragraphs that explain the feature naturally:",
|
|
12664
12842
|
"what it is, why it exists, how it evolved. Keep every fact; invent nothing. No headings,",
|
|
12665
|
-
"no lists, no markdown syntax
|
|
12843
|
+
"no lists, no markdown syntax, prose only.",
|
|
12844
|
+
PROSE_STYLE
|
|
12845
|
+
].join(" ");
|
|
12846
|
+
}
|
|
12847
|
+
});
|
|
12848
|
+
|
|
12849
|
+
// ../../packages/ingest/src/deepscan.ts
|
|
12850
|
+
var SOURCE_FACTS_SYSTEM;
|
|
12851
|
+
var init_deepscan = __esm({
|
|
12852
|
+
"../../packages/ingest/src/deepscan.ts"() {
|
|
12853
|
+
"use strict";
|
|
12854
|
+
init_src2();
|
|
12855
|
+
init_src();
|
|
12856
|
+
init_raw_store();
|
|
12857
|
+
init_product_facts();
|
|
12858
|
+
init_doku();
|
|
12859
|
+
SOURCE_FACTS_SYSTEM = [
|
|
12860
|
+
"You read SOURCE FILES of a software project (each introduced by a '=== path ===' header) and extract durable PRODUCT facts for a documentation system.",
|
|
12861
|
+
"Output ONLY a JSON array (no prose, no code fences). Each item:",
|
|
12862
|
+
'{ "kind": "feature"|"decision", "title": string, "summary": string,',
|
|
12863
|
+
' "where": string[], "behavior"?: string, "rationale"?: string, "config"?: string[] }.',
|
|
12864
|
+
"A 'feature' is user- or developer-facing capability (a page, an endpoint, a command, a job).",
|
|
12865
|
+
"A 'decision' is an architectural/design choice whose reason is visible in the code or comments.",
|
|
12866
|
+
"Describe what the code DOES today, where it lives (use the given paths in 'where'), how it",
|
|
12867
|
+
"behaves, why it is built that way when stated, and any configuration it exposes.",
|
|
12868
|
+
"Prefer a few high-signal facts per batch over many trivial ones. Invent nothing:",
|
|
12869
|
+
"if the files carry no product knowledge (pure plumbing), return [].",
|
|
12870
|
+
PROSE_STYLE
|
|
12871
|
+
].join(" ");
|
|
12872
|
+
}
|
|
12873
|
+
});
|
|
12874
|
+
|
|
12875
|
+
// ../../packages/ingest/src/github-app.ts
|
|
12876
|
+
var init_github_app = __esm({
|
|
12877
|
+
"../../packages/ingest/src/github-app.ts"() {
|
|
12878
|
+
"use strict";
|
|
12879
|
+
}
|
|
12880
|
+
});
|
|
12881
|
+
|
|
12882
|
+
// ../../packages/ingest/src/repo-reader.ts
|
|
12883
|
+
var init_repo_reader = __esm({
|
|
12884
|
+
"../../packages/ingest/src/repo-reader.ts"() {
|
|
12885
|
+
"use strict";
|
|
12886
|
+
init_structure();
|
|
12887
|
+
init_github_app();
|
|
12888
|
+
}
|
|
12889
|
+
});
|
|
12890
|
+
|
|
12891
|
+
// ../../packages/ingest/src/verify-agent.ts
|
|
12892
|
+
var VERIFY_SYSTEM;
|
|
12893
|
+
var init_verify_agent = __esm({
|
|
12894
|
+
"../../packages/ingest/src/verify-agent.ts"() {
|
|
12895
|
+
"use strict";
|
|
12896
|
+
init_sdk();
|
|
12897
|
+
init_src2();
|
|
12898
|
+
init_src();
|
|
12899
|
+
init_product_facts();
|
|
12900
|
+
init_doku();
|
|
12901
|
+
init_llm();
|
|
12902
|
+
VERIFY_SYSTEM = [
|
|
12903
|
+
"You are BrainJar's verification agent. You read a software repository and record durable product knowledge, with one iron rule: prose is never truth, code is.",
|
|
12904
|
+
"Documentation (READMEs, docs) gives you HYPOTHESES. Before recording any fact, read the implementing code with read_file and check it.",
|
|
12905
|
+
"Record via record_fact with the honest verification status:",
|
|
12906
|
+
"- confirmed: you read the implementation and it does this today.",
|
|
12907
|
+
"- contradicted: the docs claim one thing, the code does another. Record what the CODE does as the fact; put the outdated claim in `contradicts`.",
|
|
12908
|
+
"- documented-only: claimed in prose, but you could not find it in the code. Record it as documented intent, phrase the summary accordingly.",
|
|
12909
|
+
"Always fill `where` with the repo paths you actually read for this fact.",
|
|
12910
|
+
"Work highest-signal first: what the product is, its main capabilities, its architecture decisions. A few verified facts beat many shallow ones.",
|
|
12911
|
+
"When you are done (or told the budget is exhausted), reply with a short plain-text summary instead of more tool calls.",
|
|
12912
|
+
PROSE_STYLE
|
|
12666
12913
|
].join(" ");
|
|
12667
12914
|
}
|
|
12668
12915
|
});
|
|
@@ -12711,6 +12958,10 @@ var init_src3 = __esm({
|
|
|
12711
12958
|
init_summaries();
|
|
12712
12959
|
init_doku();
|
|
12713
12960
|
init_doku_model();
|
|
12961
|
+
init_deepscan();
|
|
12962
|
+
init_repo_reader();
|
|
12963
|
+
init_github_app();
|
|
12964
|
+
init_verify_agent();
|
|
12714
12965
|
init_scheduler();
|
|
12715
12966
|
registerConnector(
|
|
12716
12967
|
"github",
|
|
@@ -12883,6 +13134,7 @@ async function init(argv) {
|
|
|
12883
13134
|
`
|
|
12884
13135
|
\u2705 Brain "${name}" is live \u2014 ${final.objects} memory objects.
|
|
12885
13136
|
Open the web app and visit Docs. Approve the hook in Claude Code when prompted.
|
|
13137
|
+
Optional: \`npx brainjar deepscan\` reads the repo's key files so the docs start full.
|
|
12886
13138
|
`
|
|
12887
13139
|
);
|
|
12888
13140
|
return 0;
|
|
@@ -13226,6 +13478,174 @@ var init_doctor = __esm({
|
|
|
13226
13478
|
}
|
|
13227
13479
|
});
|
|
13228
13480
|
|
|
13481
|
+
// ../../packages/cli/src/deepscan.ts
|
|
13482
|
+
var deepscan_exports = {};
|
|
13483
|
+
__export(deepscan_exports, {
|
|
13484
|
+
deepscanMain: () => deepscanMain,
|
|
13485
|
+
selectDeepScanFiles: () => selectDeepScanFiles
|
|
13486
|
+
});
|
|
13487
|
+
import { readdir as readdir3, readFile as readFile6, stat as stat2 } from "node:fs/promises";
|
|
13488
|
+
import path6 from "node:path";
|
|
13489
|
+
function selectDeepScanFiles(candidates, graph, maxFiles = MAX_FILES) {
|
|
13490
|
+
const picked = [];
|
|
13491
|
+
const has = /* @__PURE__ */ new Set();
|
|
13492
|
+
const take = (p) => {
|
|
13493
|
+
if (!has.has(p) && picked.length < maxFiles) {
|
|
13494
|
+
has.add(p);
|
|
13495
|
+
picked.push(p);
|
|
13496
|
+
}
|
|
13497
|
+
};
|
|
13498
|
+
for (const bucket of BUCKET_CAPS) {
|
|
13499
|
+
let n = 0;
|
|
13500
|
+
const ordered = [...candidates].sort().reverse();
|
|
13501
|
+
for (const p of ordered) {
|
|
13502
|
+
if (n >= bucket.cap) break;
|
|
13503
|
+
if (bucket.test(p) && !has.has(p)) {
|
|
13504
|
+
take(p);
|
|
13505
|
+
n++;
|
|
13506
|
+
}
|
|
13507
|
+
}
|
|
13508
|
+
}
|
|
13509
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
13510
|
+
for (const e of graph.edges)
|
|
13511
|
+
inDegree.set(e.to, (inDegree.get(e.to) ?? 0) + 1);
|
|
13512
|
+
const central = [...inDegree.entries()].sort((a, b) => b[1] - a[1]).map(([p]) => p);
|
|
13513
|
+
for (const p of central) {
|
|
13514
|
+
if (picked.length >= maxFiles) break;
|
|
13515
|
+
take(p);
|
|
13516
|
+
}
|
|
13517
|
+
return picked;
|
|
13518
|
+
}
|
|
13519
|
+
async function walkCandidates(root) {
|
|
13520
|
+
const entries = await readdir3(root, { recursive: true, withFileTypes: true });
|
|
13521
|
+
const out = [];
|
|
13522
|
+
for (const e of entries) {
|
|
13523
|
+
if (!e.isFile()) continue;
|
|
13524
|
+
const parent = e.parentPath ?? e.path ?? root;
|
|
13525
|
+
const rel = path6.relative(root, path6.join(parent, e.name)).split(path6.sep).join("/");
|
|
13526
|
+
if (rel.split("/").some((seg) => SKIP_DIRS.has(seg))) continue;
|
|
13527
|
+
out.push(rel);
|
|
13528
|
+
}
|
|
13529
|
+
return out;
|
|
13530
|
+
}
|
|
13531
|
+
async function deepscanMain() {
|
|
13532
|
+
const root = await repoRoot();
|
|
13533
|
+
const project = await captureApi(`/capture/project?root=${encodeURIComponent(root)}`);
|
|
13534
|
+
if (!project.brainId || !project.sourceId) {
|
|
13535
|
+
console.error(
|
|
13536
|
+
`brainjar: no Brain is connected to ${root} \u2014 run \`npx brainjar init\` first`
|
|
13537
|
+
);
|
|
13538
|
+
process.exitCode = 1;
|
|
13539
|
+
return;
|
|
13540
|
+
}
|
|
13541
|
+
console.error("brainjar: selecting key files\u2026");
|
|
13542
|
+
const [candidates, graph] = await Promise.all([
|
|
13543
|
+
walkCandidates(root),
|
|
13544
|
+
scanStructure(root)
|
|
13545
|
+
]);
|
|
13546
|
+
const files = selectDeepScanFiles(candidates, graph);
|
|
13547
|
+
const artifacts = [];
|
|
13548
|
+
for (const rel of files) {
|
|
13549
|
+
try {
|
|
13550
|
+
const abs = path6.join(root, rel);
|
|
13551
|
+
const info = await stat2(abs);
|
|
13552
|
+
if (info.size > RAW_SKIP_BYTES) continue;
|
|
13553
|
+
const content = await readFile6(abs, "utf8");
|
|
13554
|
+
artifacts.push({
|
|
13555
|
+
source: "deepscan",
|
|
13556
|
+
kind: "source_file",
|
|
13557
|
+
externalId: `file:${rel}`,
|
|
13558
|
+
uri: rel,
|
|
13559
|
+
sha: contentHash(content),
|
|
13560
|
+
title: rel,
|
|
13561
|
+
body: content.slice(0, FILE_CAP),
|
|
13562
|
+
author: null,
|
|
13563
|
+
createdAt: info.mtime.toISOString(),
|
|
13564
|
+
updatedAt: info.mtime.toISOString(),
|
|
13565
|
+
payload: { path: rel, bytes: info.size }
|
|
13566
|
+
});
|
|
13567
|
+
} catch {
|
|
13568
|
+
}
|
|
13569
|
+
}
|
|
13570
|
+
let shipped = 0;
|
|
13571
|
+
let queued = 0;
|
|
13572
|
+
let chunk = [];
|
|
13573
|
+
let chunkChars = 0;
|
|
13574
|
+
const flush = async (final) => {
|
|
13575
|
+
if (chunk.length === 0 && !final) return;
|
|
13576
|
+
const r = await captureApi(
|
|
13577
|
+
"/capture/artifacts",
|
|
13578
|
+
{
|
|
13579
|
+
method: "POST",
|
|
13580
|
+
body: {
|
|
13581
|
+
root,
|
|
13582
|
+
sourceId: project.sourceId,
|
|
13583
|
+
artifacts: chunk,
|
|
13584
|
+
// Only the final chunk kicks the server-side synthesis: one scan per run.
|
|
13585
|
+
deepscanFinal: final
|
|
13586
|
+
}
|
|
13587
|
+
}
|
|
13588
|
+
);
|
|
13589
|
+
shipped += chunk.length;
|
|
13590
|
+
queued += r.deepscanQueued ?? 0;
|
|
13591
|
+
chunk = [];
|
|
13592
|
+
chunkChars = 0;
|
|
13593
|
+
};
|
|
13594
|
+
for (const a of artifacts) {
|
|
13595
|
+
if (chunk.length >= CHUNK_FILES || chunkChars + a.body.length > CHUNK_CHARS)
|
|
13596
|
+
await flush(false);
|
|
13597
|
+
chunk.push(a);
|
|
13598
|
+
chunkChars += a.body.length;
|
|
13599
|
+
}
|
|
13600
|
+
await flush(true);
|
|
13601
|
+
console.error(
|
|
13602
|
+
`brainjar: deep scan shipped ${shipped} files (${queued} queued for synthesis).`
|
|
13603
|
+
);
|
|
13604
|
+
console.error(
|
|
13605
|
+
"brainjar: synthesis runs on the server in the background \u2014 the bell in the app reports the outcome. Unchanged files were deduplicated automatically."
|
|
13606
|
+
);
|
|
13607
|
+
}
|
|
13608
|
+
var FILE_CAP, CHUNK_CHARS, CHUNK_FILES, MAX_FILES, RAW_SKIP_BYTES, BUCKET_CAPS;
|
|
13609
|
+
var init_deepscan2 = __esm({
|
|
13610
|
+
"../../packages/cli/src/deepscan.ts"() {
|
|
13611
|
+
"use strict";
|
|
13612
|
+
init_src3();
|
|
13613
|
+
init_api_client();
|
|
13614
|
+
FILE_CAP = 16e3;
|
|
13615
|
+
CHUNK_CHARS = 65e4;
|
|
13616
|
+
CHUNK_FILES = 40;
|
|
13617
|
+
MAX_FILES = 60;
|
|
13618
|
+
RAW_SKIP_BYTES = 2e5;
|
|
13619
|
+
BUCKET_CAPS = [
|
|
13620
|
+
// READMEs first — they outrank everything else the repo says about itself.
|
|
13621
|
+
{ cap: 4, test: (p) => /(^|\/)readme[^/]*$/i.test(p) },
|
|
13622
|
+
// Markdown docs.
|
|
13623
|
+
{ cap: 6, test: (p) => /^docs\//i.test(p) && p.endsWith(".md") },
|
|
13624
|
+
// Manifests: what the project IS and depends on.
|
|
13625
|
+
{
|
|
13626
|
+
cap: 10,
|
|
13627
|
+
test: (p) => /(^|\/)(package\.json|pyproject\.toml|go\.mod|cargo\.toml|composer\.json|gemfile)$/i.test(
|
|
13628
|
+
p
|
|
13629
|
+
) && p.split("/").length <= 3
|
|
13630
|
+
},
|
|
13631
|
+
// Schema + deployment: migrations, compose files, example env.
|
|
13632
|
+
{
|
|
13633
|
+
cap: 12,
|
|
13634
|
+
test: (p) => /(^|\/)migrations\//.test(p) && p.endsWith(".sql") || /(^|\/)(docker-compose[^/]*\.ya?ml|\.env\.example|schema\.[^/]+)$/i.test(
|
|
13635
|
+
p
|
|
13636
|
+
)
|
|
13637
|
+
},
|
|
13638
|
+
// Entry points and API surfaces by name.
|
|
13639
|
+
{
|
|
13640
|
+
cap: 12,
|
|
13641
|
+
test: (p) => /(^|\/)(index|main|app|server|cli|routes?|api)\.(ts|tsx|js|py|go|rb)$/.test(
|
|
13642
|
+
p
|
|
13643
|
+
)
|
|
13644
|
+
}
|
|
13645
|
+
];
|
|
13646
|
+
}
|
|
13647
|
+
});
|
|
13648
|
+
|
|
13229
13649
|
// ../../packages/cli/src/unhook.ts
|
|
13230
13650
|
var unhook_exports = {};
|
|
13231
13651
|
__export(unhook_exports, {
|
|
@@ -13275,11 +13695,11 @@ async function dokuMain(argv = []) {
|
|
|
13275
13695
|
return;
|
|
13276
13696
|
}
|
|
13277
13697
|
let llm;
|
|
13278
|
-
const
|
|
13279
|
-
if (
|
|
13698
|
+
const key = await resolveAnthropicApiKey(loadEnv().ANTHROPIC_API_KEY);
|
|
13699
|
+
if (key) {
|
|
13280
13700
|
try {
|
|
13281
|
-
llm =
|
|
13282
|
-
|
|
13701
|
+
llm = anthropicClient(
|
|
13702
|
+
key,
|
|
13283
13703
|
await resolveSetting(
|
|
13284
13704
|
"rationale_model",
|
|
13285
13705
|
loadEnv().BRAINJAR_RATIONALE_MODEL
|
|
@@ -13329,6 +13749,7 @@ Usage:
|
|
|
13329
13749
|
brainjar constraints [--hook] Known failed approaches (SessionStart hook entrypoint)
|
|
13330
13750
|
brainjar failure --hook Near-live failure capture (PostToolUseFailure entrypoint)
|
|
13331
13751
|
brainjar doctor [--no-sync] Check the capture pipeline end to end
|
|
13752
|
+
brainjar deepscan Read the repo's key files into the Brain (AI synthesis server-side)
|
|
13332
13753
|
brainjar unhook Remove all BrainJar hooks (data stays)
|
|
13333
13754
|
brainjar doku [--export] Rebuild the documentation pages (--export dumps them to files)
|
|
13334
13755
|
brainjar help Show this help
|
|
@@ -13361,6 +13782,11 @@ async function main() {
|
|
|
13361
13782
|
await doctorMain2(argv);
|
|
13362
13783
|
break;
|
|
13363
13784
|
}
|
|
13785
|
+
case "deepscan": {
|
|
13786
|
+
const { deepscanMain: deepscanMain2 } = await Promise.resolve().then(() => (init_deepscan2(), deepscan_exports));
|
|
13787
|
+
await deepscanMain2();
|
|
13788
|
+
break;
|
|
13789
|
+
}
|
|
13364
13790
|
case "unhook": {
|
|
13365
13791
|
const { unhookMain: unhookMain2 } = await Promise.resolve().then(() => (init_unhook(), unhook_exports));
|
|
13366
13792
|
await unhookMain2();
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brainjar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "BrainJar CLI: connect a repository to a BrainJar server (capture, hooks, MCP)",
|
|
5
|
-
"license": "SEE LICENSE ON https://
|
|
6
|
-
"author": "
|
|
5
|
+
"license": "SEE LICENSE ON https://brainjar.dev",
|
|
6
|
+
"author": "Dietrich Gebert",
|
|
7
7
|
"homepage": "https://brainjar.dev",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"bin": {
|