brainblast 0.4.3 → 0.5.1

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/README.md CHANGED
@@ -184,7 +184,7 @@ All types are exported: `Rule`, `CheckResult`, `CostReport`, `AccountFlow`,
184
184
 
185
185
  ```sh
186
186
  npm install
187
- npm test # unit suite (180 tests)
187
+ npm test # unit suite (214 tests)
188
188
  npm run prove # end-to-end: generated tests RED on vulnerable, GREEN on fixed
189
189
  npm run build # produce dist/ (the published artifact)
190
190
  ```
@@ -662,6 +662,44 @@ var taintToSink = (c, p) => {
662
662
  return { result: "pass", detail: "No tracked source value flows to a sink within the analyzed call graph." };
663
663
  };
664
664
 
665
+ // src/checkers/literalMultiplierWrongConstant.ts
666
+ import { SyntaxKind as SyntaxKind8 } from "ts-morph";
667
+ function callName4(call) {
668
+ const exp = call.getExpression();
669
+ if (exp.getKind() === SyntaxKind8.Identifier) return exp.getText();
670
+ if (exp.getKind() === SyntaxKind8.PropertyAccessExpression) {
671
+ return exp.asKind(SyntaxKind8.PropertyAccessExpression).getName();
672
+ }
673
+ return "";
674
+ }
675
+ function containsIdentifier(node, name) {
676
+ if (node.getKind() === SyntaxKind8.Identifier && node.getText() === name) return true;
677
+ return node.getDescendantsOfKind(SyntaxKind8.Identifier).some((id) => id.getText() === name);
678
+ }
679
+ var literalMultiplierWrongConstant = (c, p) => {
680
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind8.CallExpression).filter((x) => callName4(x) === p.call);
681
+ if (calls.length === 0) {
682
+ return { result: "cant_tell", detail: p.absentCallDetail };
683
+ }
684
+ const arg = calls[0].getArguments()[p.argIndex];
685
+ if (!arg) {
686
+ return { result: "cant_tell", detail: p.absentCallDetail };
687
+ }
688
+ const forbidden = Array.isArray(p.forbiddenIdentifiers) ? p.forbiddenIdentifiers : [];
689
+ for (const name of forbidden) {
690
+ if (containsIdentifier(arg, name)) {
691
+ return { result: "fail", detail: String(p.failDetail).replace("{got}", name) };
692
+ }
693
+ }
694
+ const expected = Array.isArray(p.expectedIdentifiers) ? p.expectedIdentifiers : [];
695
+ for (const name of expected) {
696
+ if (containsIdentifier(arg, name)) {
697
+ return { result: "pass", detail: String(p.passDetail) };
698
+ }
699
+ }
700
+ return { result: "cant_tell", detail: p.cantTellDetail };
701
+ };
702
+
665
703
  // src/checkers/index.ts
666
704
  var registry = {
667
705
  "positional-arg-identity": positionalArgIdentity,
@@ -671,7 +709,8 @@ var registry = {
671
709
  "object-arg-property-literal-equals": objectArgPropertyLiteralEquals,
672
710
  "anchor-init-if-needed-guarded": anchorInitIfNeededGuarded,
673
711
  "env-secrets-committed": envSecretsCommitted,
674
- "taint-to-sink": taintToSink
712
+ "taint-to-sink": taintToSink,
713
+ "literal-multiplier-wrong-constant": literalMultiplierWrongConstant
675
714
  };
676
715
  function runChecker(kind, c, params) {
677
716
  const fn = registry[kind];
@@ -887,7 +926,7 @@ function findRustCandidates(targetDir, rule) {
887
926
  }
888
927
 
889
928
  // src/fixers/positionalArgIdentity.ts
890
- import { SyntaxKind as SyntaxKind8 } from "ts-morph";
929
+ import { SyntaxKind as SyntaxKind9 } from "ts-morph";
891
930
 
892
931
  // src/fixers/diffUtil.ts
893
932
  function buildDiff(node, replacement) {
@@ -912,9 +951,9 @@ function buildDiff(node, replacement) {
912
951
  // src/fixers/positionalArgIdentity.ts
913
952
  var fixPositionalArgIdentity = (c, p, outcome) => {
914
953
  if (outcome.result !== "fail") return void 0;
915
- const calls = c.fn.getDescendantsOfKind(SyntaxKind8.CallExpression).filter((call) => {
954
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind9.CallExpression).filter((call) => {
916
955
  const exp = call.getExpression();
917
- return exp.getKind() === SyntaxKind8.PropertyAccessExpression && exp.asKind(SyntaxKind8.PropertyAccessExpression).getName() === p.call;
956
+ return exp.getKind() === SyntaxKind9.PropertyAccessExpression && exp.asKind(SyntaxKind9.PropertyAccessExpression).getName() === p.call;
918
957
  });
919
958
  if (calls.length === 0) {
920
959
  const wantParam2 = c.params[p.paramIndex] ?? "<rawBodyParam>";
@@ -929,7 +968,7 @@ Do not call JSON.parse() on the body before this verification step.`
929
968
  }
930
969
  const arg = calls[0].getArguments()[p.argIndex];
931
970
  const wantParam = c.params[p.paramIndex];
932
- if (arg && wantParam && arg.getKind() === SyntaxKind8.CallExpression) {
971
+ if (arg && wantParam && arg.getKind() === SyntaxKind9.CallExpression) {
933
972
  return {
934
973
  summary: `Pass the raw body parameter '${wantParam}' to ${p.call} instead of a parsed value`,
935
974
  diff: buildDiff(arg, wantParam)
@@ -939,12 +978,12 @@ Do not call JSON.parse() on the body before this verification step.`
939
978
  };
940
979
 
941
980
  // src/fixers/requiredCallWithOptions.ts
942
- import { SyntaxKind as SyntaxKind9 } from "ts-morph";
943
- function callName4(call) {
981
+ import { SyntaxKind as SyntaxKind10 } from "ts-morph";
982
+ function callName5(call) {
944
983
  const exp = call.getExpression();
945
- if (exp.getKind() === SyntaxKind9.Identifier) return exp.getText();
946
- if (exp.getKind() === SyntaxKind9.PropertyAccessExpression) {
947
- return exp.asKind(SyntaxKind9.PropertyAccessExpression).getName();
984
+ if (exp.getKind() === SyntaxKind10.Identifier) return exp.getText();
985
+ if (exp.getKind() === SyntaxKind10.PropertyAccessExpression) {
986
+ return exp.asKind(SyntaxKind10.PropertyAccessExpression).getName();
948
987
  }
949
988
  return "";
950
989
  }
@@ -962,15 +1001,15 @@ function placeholderFor(propName) {
962
1001
  }
963
1002
  var fixRequiredCallWithOptions = (c, p, outcome) => {
964
1003
  if (outcome.result !== "fail") return void 0;
965
- const calls = c.fn.getDescendantsOfKind(SyntaxKind9.CallExpression);
966
- const verify = calls.filter((x) => p.verifyCalls.includes(callName4(x)));
1004
+ const calls = c.fn.getDescendantsOfKind(SyntaxKind10.CallExpression);
1005
+ const verify = calls.filter((x) => p.verifyCalls.includes(callName5(x)));
967
1006
  if (verify.length > 0) {
968
1007
  const call = verify[0];
969
1008
  const args = call.getArguments();
970
1009
  const lastArg = args[args.length - 1];
971
- const obj = lastArg?.asKind(SyntaxKind9.ObjectLiteralExpression);
1010
+ const obj = lastArg?.asKind(SyntaxKind10.ObjectLiteralExpression);
972
1011
  const presentNames = obj ? obj.getProperties().map((pr) => {
973
- const pa = pr.asKind(SyntaxKind9.PropertyAssignment) ?? pr.asKind(SyntaxKind9.ShorthandPropertyAssignment);
1012
+ const pa = pr.asKind(SyntaxKind10.PropertyAssignment) ?? pr.asKind(SyntaxKind10.ShorthandPropertyAssignment);
974
1013
  return pa?.getName() ?? "";
975
1014
  }) : [];
976
1015
  const missingGroups = p.requiredProps.filter(
@@ -978,7 +1017,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
978
1017
  );
979
1018
  if (missingGroups.length === 0) return void 0;
980
1019
  const newProps = missingGroups.map((g) => placeholderFor(g[0])).join(", ");
981
- const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${callName4(call)} call`;
1020
+ const summary = `Add ${missingGroups.map((g) => g[0]).join(" and ")} to the ${callName5(call)} call`;
982
1021
  if (obj) {
983
1022
  const inner = obj.getText().slice(1, -1).trim();
984
1023
  const newText = inner.length > 0 ? `{ ${inner}, ${newProps} }` : `{ ${newProps} }`;
@@ -990,7 +1029,7 @@ var fixRequiredCallWithOptions = (c, p, outcome) => {
990
1029
  }
991
1030
  return {
992
1031
  summary,
993
- suggestion: `Add an options object ({ ${newProps} }) as an argument to ${callName4(call)}.`
1032
+ suggestion: `Add an options object ({ ${newProps} }) as an argument to ${callName5(call)}.`
994
1033
  };
995
1034
  }
996
1035
  return {
@@ -1486,63 +1525,113 @@ function loadRules(dir) {
1486
1525
  return rules2;
1487
1526
  }
1488
1527
 
1528
+ // src/packs.ts
1529
+ import { existsSync, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
1530
+ import { join as join5 } from "path";
1531
+ import { parse as parse2 } from "yaml";
1532
+ var PACK_MANIFEST_FILE = "brainblast-pack.yaml";
1533
+ function validatePackManifest(m, file) {
1534
+ const errs = [];
1535
+ if (!m || typeof m !== "object") {
1536
+ throw new Error(`invalid pack manifest in ${file}: not a mapping`);
1537
+ }
1538
+ if (!m.id || typeof m.id !== "string") errs.push("missing id");
1539
+ if (!m.name || typeof m.name !== "string") errs.push("missing name");
1540
+ if (!m.version || typeof m.version !== "string") errs.push("missing version");
1541
+ if (!m.author || typeof m.author !== "string") errs.push("missing author");
1542
+ if (errs.length) throw new Error(`invalid pack manifest in ${file}: ${errs.join("; ")}`);
1543
+ }
1544
+ function loadPack(dir) {
1545
+ const manifestPath = join5(dir, PACK_MANIFEST_FILE);
1546
+ const raw = parse2(readFileSync5(manifestPath, "utf8"));
1547
+ validatePackManifest(raw, manifestPath);
1548
+ const manifest = raw;
1549
+ const rulesDir = join5(dir, "rules");
1550
+ const rules2 = existsSync(rulesDir) ? loadRules(rulesDir).map((r) => ({
1551
+ ...r,
1552
+ pack: { id: manifest.id, version: manifest.version, author: manifest.author }
1553
+ })) : [];
1554
+ return { manifest, rules: rules2 };
1555
+ }
1556
+ function loadPacksFromDir(packsDir) {
1557
+ if (!existsSync(packsDir)) return [];
1558
+ const out = [];
1559
+ for (const entry of readdirSync4(packsDir).sort()) {
1560
+ const dir = join5(packsDir, entry);
1561
+ if (!statSync3(dir).isDirectory()) continue;
1562
+ if (!existsSync(join5(dir, PACK_MANIFEST_FILE))) continue;
1563
+ out.push(loadPack(dir));
1564
+ }
1565
+ return out;
1566
+ }
1567
+
1489
1568
  // rules/index.ts
1490
- import { existsSync } from "fs";
1491
- import { dirname, join as join5 } from "path";
1569
+ import { existsSync as existsSync2 } from "fs";
1570
+ import { dirname, join as join6 } from "path";
1492
1571
  import { fileURLToPath } from "url";
1493
1572
  function bundledRulesDir() {
1494
1573
  const here = dirname(fileURLToPath(import.meta.url));
1495
- if (existsSync(join5(here, "stripe-webhook-raw-body.yaml"))) return here;
1496
- const sub = join5(here, "rules");
1497
- if (existsSync(join5(sub, "stripe-webhook-raw-body.yaml"))) return sub;
1574
+ if (existsSync2(join6(here, "stripe-webhook-raw-body.yaml"))) return here;
1575
+ const sub = join6(here, "rules");
1576
+ if (existsSync2(join6(sub, "stripe-webhook-raw-body.yaml"))) return sub;
1498
1577
  return here;
1499
1578
  }
1500
1579
  var rules = loadRules(bundledRulesDir());
1501
1580
 
1502
1581
  // src/resolveRules.ts
1503
- import { existsSync as existsSync2 } from "fs";
1504
- import { join as join6 } from "path";
1505
- function resolveRules(targetDir) {
1582
+ import { existsSync as existsSync3 } from "fs";
1583
+ import { join as join7 } from "path";
1584
+ function resolveRules(targetDir, extraPackDirs = []) {
1506
1585
  const all = [...rules];
1507
- const projDir = join6(targetDir, ".agent-research", "rules");
1508
- if (existsSync2(projDir)) {
1509
- const seen = new Set(all.map((r) => r.id));
1510
- for (const r of loadRules(projDir)) {
1586
+ const seen = new Set(all.map((r) => r.id));
1587
+ const addRules = (rules2, sourceLabel) => {
1588
+ for (const r of rules2) {
1511
1589
  if (seen.has(r.id)) {
1512
- console.warn(`brainblast: project rule '${r.id}' shadows a bundled rule; keeping bundled.`);
1590
+ console.warn(`brainblast: rule '${r.id}' from ${sourceLabel} shadows an existing rule; keeping the first one loaded.`);
1513
1591
  continue;
1514
1592
  }
1515
1593
  all.push(r);
1516
1594
  seen.add(r.id);
1517
1595
  }
1596
+ };
1597
+ const projDir = join7(targetDir, ".agent-research", "rules");
1598
+ if (existsSync3(projDir)) {
1599
+ addRules(loadRules(projDir), "project rules");
1600
+ }
1601
+ for (const { manifest, rules: rules2 } of loadPacksFromDir(join7(targetDir, ".agent-research", "packs"))) {
1602
+ addRules(rules2, `pack '${manifest.id}'`);
1603
+ }
1604
+ for (const dir of extraPackDirs) {
1605
+ const { manifest, rules: rules2 } = loadPack(dir);
1606
+ addRules(rules2, `pack '${manifest.id}' (${dir})`);
1518
1607
  }
1519
1608
  return all;
1520
1609
  }
1521
1610
 
1522
1611
  // src/trustGraph/directory.ts
1523
- import { readFileSync as readFileSync5, existsSync as existsSync3 } from "fs";
1612
+ import { readFileSync as readFileSync6, existsSync as existsSync4 } from "fs";
1524
1613
  import { fileURLToPath as fileURLToPath2 } from "url";
1525
- import { join as join7 } from "path";
1526
- import { parse as parse2 } from "yaml";
1614
+ import { join as join8 } from "path";
1615
+ import { parse as parse3 } from "yaml";
1527
1616
  var cache = null;
1528
1617
  function bundledPath() {
1529
1618
  const here = fileURLToPath2(new URL(".", import.meta.url));
1530
1619
  const candidates = [
1531
- join7(here, "programs", "directory.yaml"),
1620
+ join8(here, "programs", "directory.yaml"),
1532
1621
  // dist/programs/directory.yaml
1533
- join7(here, "..", "..", "programs", "directory.yaml"),
1622
+ join8(here, "..", "..", "programs", "directory.yaml"),
1534
1623
  // src/../../programs/
1535
- join7(here, "..", "programs", "directory.yaml")
1624
+ join8(here, "..", "programs", "directory.yaml")
1536
1625
  // fallback
1537
1626
  ];
1538
1627
  for (const c of candidates) {
1539
- if (existsSync3(c)) return c;
1628
+ if (existsSync4(c)) return c;
1540
1629
  }
1541
1630
  return candidates[0];
1542
1631
  }
1543
1632
  function loadDirectory(path = bundledPath()) {
1544
1633
  if (cache && path === bundledPath()) return cache;
1545
- const raw = parse2(readFileSync5(path, "utf8"));
1634
+ const raw = parse3(readFileSync6(path, "utf8"));
1546
1635
  if (!raw || !Array.isArray(raw.programs)) {
1547
1636
  throw new Error(`invalid program directory at ${path}: missing 'programs' array`);
1548
1637
  }
@@ -1613,23 +1702,23 @@ function isValidSolanaAddress(s) {
1613
1702
  }
1614
1703
 
1615
1704
  // src/trustGraph/programCache.ts
1616
- import { readFileSync as readFileSync6, writeFileSync, mkdirSync, existsSync as existsSync4 } from "fs";
1617
- import { join as join8, dirname as dirname2 } from "path";
1705
+ import { readFileSync as readFileSync7, writeFileSync, mkdirSync, existsSync as existsSync5 } from "fs";
1706
+ import { join as join9, dirname as dirname2 } from "path";
1618
1707
  import { homedir } from "os";
1619
1708
  var DEFAULT_TTL_HOURS = 168;
1620
1709
  var SCHEMA_VERSION = "1.0";
1621
1710
  function defaultCachePath() {
1622
1711
  const envOverride = process.env["BRAINBLAST_CACHE_PATH"];
1623
- return envOverride ?? join8(homedir(), ".brainblast", "program-cache.json");
1712
+ return envOverride ?? join9(homedir(), ".brainblast", "program-cache.json");
1624
1713
  }
1625
1714
  function emptyCache() {
1626
1715
  return { schemaVersion: SCHEMA_VERSION, entries: {} };
1627
1716
  }
1628
1717
  function loadProgramCache(cachePath) {
1629
1718
  const path = cachePath ?? defaultCachePath();
1630
- if (!existsSync4(path)) return emptyCache();
1719
+ if (!existsSync5(path)) return emptyCache();
1631
1720
  try {
1632
- const raw = JSON.parse(readFileSync6(path, "utf8"));
1721
+ const raw = JSON.parse(readFileSync7(path, "utf8"));
1633
1722
  if (raw?.schemaVersion !== SCHEMA_VERSION) {
1634
1723
  return emptyCache();
1635
1724
  }
@@ -1906,7 +1995,7 @@ function renderTrustGraphMd(g) {
1906
1995
  }
1907
1996
 
1908
1997
  // src/costAnalysis.ts
1909
- import { Project as Project2, SyntaxKind as SyntaxKind10 } from "ts-morph";
1998
+ import { Project as Project2, SyntaxKind as SyntaxKind11 } from "ts-morph";
1910
1999
  var LAMPORTS_PER_BYTE_YEAR = 3480;
1911
2000
  var EXEMPTION_THRESHOLD = 2;
1912
2001
  var OVERHEAD_BYTES = 128;
@@ -1987,11 +2076,11 @@ var KNOWN_FLOWS = [
1987
2076
  }
1988
2077
  ];
1989
2078
  var LOOP_NODE_KINDS = /* @__PURE__ */ new Set([
1990
- SyntaxKind10.ForStatement,
1991
- SyntaxKind10.ForOfStatement,
1992
- SyntaxKind10.ForInStatement,
1993
- SyntaxKind10.WhileStatement,
1994
- SyntaxKind10.DoStatement
2079
+ SyntaxKind11.ForStatement,
2080
+ SyntaxKind11.ForOfStatement,
2081
+ SyntaxKind11.ForInStatement,
2082
+ SyntaxKind11.WhileStatement,
2083
+ SyntaxKind11.DoStatement
1995
2084
  ]);
1996
2085
  var ARRAY_METHOD_LOOPS = /* @__PURE__ */ new Set(["map", "forEach", "flatMap", "reduce", "filter"]);
1997
2086
  function isInsideLoop(node) {
@@ -1999,12 +2088,12 @@ function isInsideLoop(node) {
1999
2088
  while (cur) {
2000
2089
  const k = cur.getKind?.();
2001
2090
  if (k !== void 0 && LOOP_NODE_KINDS.has(k)) {
2002
- return { scalable: true, note: `call is inside a ${SyntaxKind10[k]} \u2014 cost scales with loop iterations` };
2091
+ return { scalable: true, note: `call is inside a ${SyntaxKind11[k]} \u2014 cost scales with loop iterations` };
2003
2092
  }
2004
- if (k === SyntaxKind10.CallExpression) {
2093
+ if (k === SyntaxKind11.CallExpression) {
2005
2094
  const expr = cur.getExpression?.();
2006
- if (expr?.getKind?.() === SyntaxKind10.PropertyAccessExpression) {
2007
- const name = expr.asKind?.(SyntaxKind10.PropertyAccessExpression)?.getName?.();
2095
+ if (expr?.getKind?.() === SyntaxKind11.PropertyAccessExpression) {
2096
+ const name = expr.asKind?.(SyntaxKind11.PropertyAccessExpression)?.getName?.();
2008
2097
  if (name && ARRAY_METHOD_LOOPS.has(name)) {
2009
2098
  return { scalable: true, note: `call is inside .${name}() \u2014 cost scales with array length` };
2010
2099
  }
@@ -2018,7 +2107,7 @@ function detectPriorityFee(targetDir) {
2018
2107
  const project = new Project2({ skipAddingFilesFromTsConfig: true });
2019
2108
  for (const file of walk(targetDir)) {
2020
2109
  const sf = project.addSourceFileAtPath(file);
2021
- const calls = sf.getDescendantsOfKind(SyntaxKind10.CallExpression);
2110
+ const calls = sf.getDescendantsOfKind(SyntaxKind11.CallExpression);
2022
2111
  for (const ce of calls) {
2023
2112
  const expr = ce.getExpression();
2024
2113
  const text = expr.getText();
@@ -2046,22 +2135,22 @@ function detectAccountFlows(targetDir) {
2046
2135
  const importedModules = new Set(
2047
2136
  sf.getImportDeclarations().map((d) => d.getModuleSpecifierValue())
2048
2137
  );
2049
- for (const ce of sf.getDescendantsOfKind(SyntaxKind10.CallExpression)) {
2138
+ for (const ce of sf.getDescendantsOfKind(SyntaxKind11.CallExpression)) {
2050
2139
  const expr = ce.getExpression();
2051
- let callName5 = null;
2052
- if (expr.getKind() === SyntaxKind10.Identifier) {
2053
- callName5 = expr.getText();
2054
- } else if (expr.getKind() === SyntaxKind10.PropertyAccessExpression) {
2055
- callName5 = expr.asKind(SyntaxKind10.PropertyAccessExpression).getName();
2140
+ let callName6 = null;
2141
+ if (expr.getKind() === SyntaxKind11.Identifier) {
2142
+ callName6 = expr.getText();
2143
+ } else if (expr.getKind() === SyntaxKind11.PropertyAccessExpression) {
2144
+ callName6 = expr.asKind(SyntaxKind11.PropertyAccessExpression).getName();
2056
2145
  }
2057
- if (!callName5) continue;
2058
- const known = callIndex.get(callName5);
2146
+ if (!callName6) continue;
2147
+ const known = callIndex.get(callName6);
2059
2148
  if (!known) continue;
2060
2149
  if (!importedModules.has(known.module)) continue;
2061
2150
  const lamports = rentExemptMinimum(known.dataLen);
2062
2151
  const { scalable, note } = isInsideLoop(ce);
2063
2152
  flows.push({
2064
- call: callName5,
2153
+ call: callName6,
2065
2154
  module: known.module,
2066
2155
  accountType: known.accountType,
2067
2156
  file,
@@ -2206,7 +2295,7 @@ function startWatch(targetDir, opts = {}) {
2206
2295
  }
2207
2296
 
2208
2297
  // src/fixers/applyDiff.ts
2209
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
2298
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
2210
2299
  function parseDiff(diff) {
2211
2300
  const lines = diff.split("\n");
2212
2301
  const fileLine = lines.find((l) => l.startsWith("+++ b"));
@@ -2223,7 +2312,7 @@ function parseDiff(diff) {
2223
2312
  }
2224
2313
  function applyDiffToFile(diff) {
2225
2314
  const { filePath, oldStart, oldCount, newLines } = parseDiff(diff);
2226
- const content = readFileSync7(filePath, "utf8");
2315
+ const content = readFileSync8(filePath, "utf8");
2227
2316
  const fileLines = content.split("\n");
2228
2317
  const removedLines = diff.split("\n").filter((l) => l.startsWith("-") && !l.startsWith("---")).map((l) => l.slice(1));
2229
2318
  const actual = fileLines.slice(oldStart - 1, oldStart - 1 + oldCount);
@@ -2233,6 +2322,156 @@ function applyDiffToFile(diff) {
2233
2322
  return true;
2234
2323
  }
2235
2324
 
2325
+ // src/pack.ts
2326
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
2327
+ import { join as join10 } from "path";
2328
+ function initPack(dir, opts) {
2329
+ if (existsSync6(join10(dir, PACK_MANIFEST_FILE))) {
2330
+ throw new Error(`${dir} already contains a ${PACK_MANIFEST_FILE}`);
2331
+ }
2332
+ const manifest = {
2333
+ id: opts.id,
2334
+ name: opts.name ?? opts.id,
2335
+ version: opts.version ?? "0.1.0",
2336
+ author: opts.author ?? "unknown",
2337
+ ...opts.description ? { description: opts.description } : {}
2338
+ };
2339
+ mkdirSync2(dir, { recursive: true });
2340
+ mkdirSync2(join10(dir, "rules"), { recursive: true });
2341
+ mkdirSync2(join10(dir, "fixtures"), { recursive: true });
2342
+ const manifestYaml = [
2343
+ `id: ${manifest.id}`,
2344
+ `name: ${manifest.name}`,
2345
+ `version: ${manifest.version}`,
2346
+ `author: ${manifest.author}`,
2347
+ ...manifest.description ? [`description: ${manifest.description}`] : [],
2348
+ ""
2349
+ ].join("\n");
2350
+ const manifestFile = join10(dir, PACK_MANIFEST_FILE);
2351
+ writeFileSync3(manifestFile, manifestYaml, "utf8");
2352
+ return manifestFile;
2353
+ }
2354
+ function validatePack(dir) {
2355
+ const { manifest, rules: rules2 } = loadPack(dir);
2356
+ const fixturesRoot = join10(dir, "fixtures");
2357
+ const ruleResults = rules2.map((rule) => {
2358
+ const ruleFixturesDir = join10(fixturesRoot, rule.id);
2359
+ const vulnerableDir = join10(ruleFixturesDir, "vulnerable");
2360
+ const fixedDir = join10(ruleFixturesDir, "fixed");
2361
+ if (!existsSync6(vulnerableDir) || !existsSync6(fixedDir)) {
2362
+ return {
2363
+ ruleId: rule.id,
2364
+ status: "missing-fixtures",
2365
+ detail: `no fixtures/${rule.id}/{vulnerable,fixed}/ directory \u2014 prove gate skipped`
2366
+ };
2367
+ }
2368
+ const redChecks = auditWithRule(vulnerableDir, rule);
2369
+ const redFails = redChecks.filter((c) => c.result === "fail");
2370
+ if (redFails.length === 0) {
2371
+ return {
2372
+ ruleId: rule.id,
2373
+ status: "red-failed",
2374
+ detail: `expected at least one FAIL against fixtures/${rule.id}/vulnerable/, got none`
2375
+ };
2376
+ }
2377
+ const greenChecks = auditWithRule(fixedDir, rule);
2378
+ const greenFails = greenChecks.filter((c) => c.result === "fail");
2379
+ if (greenFails.length > 0) {
2380
+ return {
2381
+ ruleId: rule.id,
2382
+ status: "green-failed",
2383
+ detail: `expected no FAIL against fixtures/${rule.id}/fixed/, got ${greenFails.length}`
2384
+ };
2385
+ }
2386
+ return { ruleId: rule.id, status: "ok", detail: "RED -> GREEN proven" };
2387
+ });
2388
+ const ok = ruleResults.every((r) => r.status === "ok" || r.status === "missing-fixtures");
2389
+ return { manifest, rules: rules2, ruleResults, ok };
2390
+ }
2391
+
2392
+ // src/telemetry.ts
2393
+ import { createHash, randomUUID } from "crypto";
2394
+ import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
2395
+ import { execFileSync as execFileSync3 } from "child_process";
2396
+ import { homedir as homedir2 } from "os";
2397
+ import { dirname as dirname3, join as join11, resolve } from "path";
2398
+ function sha256Hex(s) {
2399
+ return createHash("sha256").update(s).digest("hex");
2400
+ }
2401
+ function isTelemetryEnabled(targetDir) {
2402
+ const env = process.env.BRAINBLAST_TELEMETRY;
2403
+ if (env === "1" || env === "true") return true;
2404
+ if (env === "0" || env === "false") return false;
2405
+ const configPath = join11(targetDir, ".agent-research", "config.json");
2406
+ if (!existsSync7(configPath)) return false;
2407
+ try {
2408
+ const cfg = JSON.parse(readFileSync9(configPath, "utf8"));
2409
+ return cfg?.telemetry === true;
2410
+ } catch {
2411
+ return false;
2412
+ }
2413
+ }
2414
+ function getUserHash() {
2415
+ const idPath = join11(homedir2(), ".brainblast", "telemetry-id");
2416
+ let id;
2417
+ if (existsSync7(idPath)) {
2418
+ id = readFileSync9(idPath, "utf8").trim();
2419
+ } else {
2420
+ id = randomUUID();
2421
+ mkdirSync3(dirname3(idPath), { recursive: true });
2422
+ writeFileSync4(idPath, id, "utf8");
2423
+ }
2424
+ return sha256Hex(id).slice(0, 16);
2425
+ }
2426
+ function getRepoHash(targetDir) {
2427
+ let key = "";
2428
+ try {
2429
+ key = execFileSync3("git", ["config", "--get", "remote.origin.url"], {
2430
+ cwd: targetDir,
2431
+ encoding: "utf8",
2432
+ stdio: ["ignore", "pipe", "ignore"]
2433
+ }).trim();
2434
+ } catch {
2435
+ }
2436
+ if (!key) key = resolve(targetDir);
2437
+ return sha256Hex(key).slice(0, 16);
2438
+ }
2439
+ function telemetryFilePath(targetDir) {
2440
+ return join11(targetDir, ".agent-research", "telemetry.ndjson");
2441
+ }
2442
+ var DEFAULT_REGISTRY_URL = "https://registry.brainblast.tech";
2443
+ async function submitTelemetry(targetDir, registryUrl = process.env.BRAINBLAST_REGISTRY_URL || DEFAULT_REGISTRY_URL) {
2444
+ const file = telemetryFilePath(targetDir);
2445
+ if (!existsSync7(file)) {
2446
+ return { submitted: 0, accepted: 0, rejected: 0, graduations: [] };
2447
+ }
2448
+ const events = readFileSync9(file, "utf8").trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
2449
+ if (events.length === 0) {
2450
+ return { submitted: 0, accepted: 0, rejected: 0, graduations: [] };
2451
+ }
2452
+ const res = await fetch(`${registryUrl.replace(/\/$/, "")}/api/telemetry`, {
2453
+ method: "POST",
2454
+ headers: { "content-type": "application/json" },
2455
+ body: JSON.stringify({ events })
2456
+ });
2457
+ if (!res.ok) {
2458
+ const body = await res.text().catch(() => "");
2459
+ throw new Error(`telemetry submit failed: ${res.status} ${res.statusText} ${body}`.trim());
2460
+ }
2461
+ const json = await res.json();
2462
+ return { submitted: events.length, ...json };
2463
+ }
2464
+ function recordGraduationEvents(targetDir, events) {
2465
+ if (events.length === 0) return;
2466
+ const file = telemetryFilePath(targetDir);
2467
+ mkdirSync3(dirname3(file), { recursive: true });
2468
+ const repo_hash = getRepoHash(targetDir);
2469
+ const user_hash = getUserHash();
2470
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2471
+ const lines = events.map((e) => JSON.stringify({ ...e, repo_hash, user_hash, timestamp })).join("\n");
2472
+ appendFileSync(file, lines + "\n", "utf8");
2473
+ }
2474
+
2236
2475
  export {
2237
2476
  findCandidates,
2238
2477
  findConfigCandidates,
@@ -2247,6 +2486,10 @@ export {
2247
2486
  renderTest,
2248
2487
  testKinds,
2249
2488
  loadRules,
2489
+ PACK_MANIFEST_FILE,
2490
+ validatePackManifest,
2491
+ loadPack,
2492
+ loadPacksFromDir,
2250
2493
  rules,
2251
2494
  resolveRules,
2252
2495
  loadDirectory,
@@ -2271,5 +2514,14 @@ export {
2271
2514
  runIncrementalScan,
2272
2515
  startWatch,
2273
2516
  parseDiff,
2274
- applyDiffToFile
2517
+ applyDiffToFile,
2518
+ initPack,
2519
+ validatePack,
2520
+ isTelemetryEnabled,
2521
+ getUserHash,
2522
+ getRepoHash,
2523
+ telemetryFilePath,
2524
+ DEFAULT_REGISTRY_URL,
2525
+ submitTelemetry,
2526
+ recordGraduationEvents
2275
2527
  };
package/dist/cli.js CHANGED
@@ -7,14 +7,20 @@ import {
7
7
  cacheSize,
8
8
  defaultCachePath,
9
9
  getChangedRanges,
10
+ initPack,
11
+ isTelemetryEnabled,
10
12
  isValidSolanaAddress,
11
13
  loadProgramCache,
12
14
  parseDiff,
15
+ recordGraduationEvents,
13
16
  renderCostReportMd,
14
17
  renderTrustGraphMd,
15
18
  resolveRules,
16
- startWatch
17
- } from "./chunk-XQUQOBXZ.js";
19
+ startWatch,
20
+ submitTelemetry,
21
+ telemetryFilePath,
22
+ validatePack
23
+ } from "./chunk-LOPYKIXE.js";
18
24
 
19
25
  // src/cli.ts
20
26
  import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
@@ -94,10 +100,25 @@ function updateMemory(memory, checks2, now = /* @__PURE__ */ new Date()) {
94
100
  // src/cli.ts
95
101
  import { execFileSync } from "child_process";
96
102
  var args = process.argv.slice(2);
103
+ function parsePackDirs(argv) {
104
+ const idx = argv.indexOf("--packs");
105
+ if (idx < 0) return [];
106
+ const value = argv[idx + 1];
107
+ if (!value || value.startsWith("--")) return [];
108
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
109
+ }
97
110
  if (args[0] === "trust-graph") {
98
111
  await runTrustGraph(args.slice(1));
99
112
  process.exit(0);
100
113
  }
114
+ if (args[0] === "pack") {
115
+ runPack(args.slice(1));
116
+ process.exit(0);
117
+ }
118
+ if (args[0] === "telemetry") {
119
+ await runTelemetry(args.slice(1));
120
+ process.exit(0);
121
+ }
101
122
  if (args[0] === "watch") {
102
123
  const watchDir = args.find((a, i) => i > 0 && !a.startsWith("--")) ?? process.cwd();
103
124
  startWatch(watchDir);
@@ -119,7 +140,7 @@ if (sinceIdx >= 0 && !since) {
119
140
  console.error("error: --since requires a <ref> argument, e.g. --since origin/main");
120
141
  process.exit(2);
121
142
  }
122
- var rules = resolveRules(targetDir);
143
+ var rules = resolveRules(targetDir, parsePackDirs(args));
123
144
  var changedRanges;
124
145
  if (since) {
125
146
  try {
@@ -211,6 +232,75 @@ if (ci) {
211
232
  const gateFail = fails > 0 || strict && cantTell > 0;
212
233
  process.exit(gateFail ? 1 : 0);
213
234
  }
235
+ function runPack(argv) {
236
+ const sub = argv[0];
237
+ if (sub === "init") {
238
+ const dir = argv.find((a, i) => i > 0 && !a.startsWith("--") && argv[i - 1] !== "--id" && argv[i - 1] !== "--name" && argv[i - 1] !== "--author" && argv[i - 1] !== "--version" && argv[i - 1] !== "--description");
239
+ const flag = (name) => {
240
+ const idx = argv.indexOf(`--${name}`);
241
+ return idx >= 0 ? argv[idx + 1] : void 0;
242
+ };
243
+ const id = flag("id");
244
+ if (!dir || !id) {
245
+ console.error("usage: brainblast pack init <dir> --id <pack-id> [--name <name>] [--author <author>] [--version <semver>] [--description <text>]");
246
+ process.exit(2);
247
+ }
248
+ const manifestFile = initPack(dir, {
249
+ id,
250
+ name: flag("name"),
251
+ author: flag("author"),
252
+ version: flag("version"),
253
+ description: flag("description")
254
+ });
255
+ console.log(`brainblast pack init: wrote ${manifestFile}`);
256
+ console.log(` rules: ${join2(dir, "rules")}/`);
257
+ console.log(` fixtures: ${join2(dir, "fixtures")}/`);
258
+ return;
259
+ }
260
+ if (sub === "validate") {
261
+ const dir = argv.find((a, i) => i > 0 && !a.startsWith("--"));
262
+ if (!dir) {
263
+ console.error("usage: brainblast pack validate <dir>");
264
+ process.exit(2);
265
+ }
266
+ const result = validatePack(dir);
267
+ console.log(`pack: ${result.manifest.id} v${result.manifest.version} (${result.manifest.author})`);
268
+ console.log(` ${result.rules.length} rule(s)`);
269
+ for (const r of result.ruleResults) {
270
+ const marker = r.status === "ok" ? "OK" : r.status === "missing-fixtures" ? "WARN" : "FAIL";
271
+ console.log(` [${marker}] ${r.ruleId}: ${r.detail}`);
272
+ }
273
+ process.exit(result.ok ? 0 : 1);
274
+ }
275
+ console.error("usage: brainblast pack <init|validate> ...");
276
+ process.exit(2);
277
+ }
278
+ async function runTelemetry(argv) {
279
+ const sub = argv[0];
280
+ if (sub !== "submit") {
281
+ console.error("usage: brainblast telemetry submit [targetDir]");
282
+ process.exit(2);
283
+ }
284
+ const targetDir2 = argv.find((a, i) => i > 0 && !a.startsWith("--")) ?? process.cwd();
285
+ try {
286
+ const result = await submitTelemetry(targetDir2);
287
+ if (result.submitted === 0) {
288
+ console.log(`brainblast telemetry submit: no events to submit (${telemetryFilePath(targetDir2)} is empty or missing)`);
289
+ return;
290
+ }
291
+ console.log(`brainblast telemetry submit: sent ${result.submitted} event(s) \u2014 ${result.accepted} accepted, ${result.rejected} rate-limited`);
292
+ for (const g of result.graduations) {
293
+ if (g.graduated) {
294
+ console.log(` [GRADUATED] ${g.pack_id}/${g.rule_id} (${g.distinct_pairs} distinct repo/user pairs)`);
295
+ } else {
296
+ console.log(` [PROGRESS] ${g.pack_id}/${g.rule_id} ${g.distinct_pairs}/5 distinct repo/user pairs`);
297
+ }
298
+ }
299
+ } catch (e) {
300
+ console.error(`brainblast telemetry submit: ${e.message ?? String(e)}`);
301
+ process.exit(1);
302
+ }
303
+ }
214
304
  async function runTrustGraph(argv) {
215
305
  const rpcIdx = argv.indexOf("--rpc");
216
306
  const rpcUrl = rpcIdx >= 0 ? argv[rpcIdx + 1] : void 0;
@@ -248,7 +338,7 @@ async function runFix(argv) {
248
338
  const apply = argv.includes("--apply");
249
339
  const branch = argv.includes("--branch");
250
340
  const targetDir2 = argv.find((a) => !a.startsWith("--")) ?? process.cwd();
251
- const rules2 = resolveRules(targetDir2);
341
+ const rules2 = resolveRules(targetDir2, parsePackDirs(argv));
252
342
  const { checks: before } = audit(targetDir2, rules2);
253
343
  const fixable = before.filter((c) => c.result === "fail" && c.fix?.diff);
254
344
  if (fixable.length === 0) {
@@ -300,6 +390,15 @@ Warning: ${stillFailing.length} fix(es) applied but the rule still fails:`);
300
390
  } else if (applied > 0) {
301
391
  console.log("All applied fixes now pass (or cant_tell) on re-audit. \u2713");
302
392
  }
393
+ if (isTelemetryEnabled(targetDir2)) {
394
+ const graduated = fixable.filter((c) => !stillFailing.includes(c));
395
+ const events = graduated.map((c) => rules2.find((r) => r.id === c.ruleId)).filter((r) => !!r?.pack).map((r) => ({ pack_id: r.pack.id, rule_id: r.id }));
396
+ if (events.length > 0) {
397
+ recordGraduationEvents(targetDir2, events);
398
+ console.log(`
399
+ Telemetry: recorded ${events.length} graduation event(s) to ${telemetryFilePath(targetDir2)}`);
400
+ }
401
+ }
303
402
  if (branch && applied > 0) {
304
403
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
305
404
  const branchName = `brainblast/auto-fix-${ts}`;
package/dist/index.d.ts CHANGED
@@ -156,6 +156,23 @@ interface Rule {
156
156
  kind: string;
157
157
  params?: Record<string, any>;
158
158
  };
159
+ /**
160
+ * Provenance for rules loaded from a third-party rule pack (see
161
+ * src/packs.ts). Absent for bundled rules. Stamped by the pack loader from
162
+ * the pack's brainblast-pack.yaml manifest, not set by the rule author.
163
+ */
164
+ pack?: {
165
+ id: string;
166
+ version: string;
167
+ author?: string;
168
+ };
169
+ }
170
+ interface PackManifest {
171
+ id: string;
172
+ name: string;
173
+ version: string;
174
+ author: string;
175
+ description?: string;
159
176
  }
160
177
  type Checker = (candidate: Candidate, params: any) => CheckOutcome;
161
178
  type RustChecker = (candidate: RustCandidate, params: any) => CheckOutcome;
@@ -224,7 +241,7 @@ declare function audit(targetDir: string, rules: Rule[], changedRanges?: Changed
224
241
  };
225
242
  };
226
243
 
227
- declare function resolveRules(targetDir: string): Rule[];
244
+ declare function resolveRules(targetDir: string, extraPackDirs?: string[]): Rule[];
228
245
 
229
246
  declare function loadRules(dir: string): Rule[];
230
247
 
@@ -285,6 +302,70 @@ interface ParsedDiff {
285
302
  declare function parseDiff(diff: string): ParsedDiff;
286
303
  declare function applyDiffToFile(diff: string): boolean;
287
304
 
305
+ declare const PACK_MANIFEST_FILE = "brainblast-pack.yaml";
306
+ declare function validatePackManifest(m: any, file: string): void;
307
+ declare function loadPack(dir: string): {
308
+ manifest: PackManifest;
309
+ rules: Rule[];
310
+ };
311
+ declare function loadPacksFromDir(packsDir: string): {
312
+ manifest: PackManifest;
313
+ rules: Rule[];
314
+ }[];
315
+
316
+ interface PackInitOptions {
317
+ id: string;
318
+ name?: string;
319
+ author?: string;
320
+ version?: string;
321
+ description?: string;
322
+ }
323
+ declare function initPack(dir: string, opts: PackInitOptions): string;
324
+ interface PackValidateResult {
325
+ manifest: PackManifest;
326
+ rules: Rule[];
327
+ /** Per-rule prove-gate results. */
328
+ ruleResults: PackRuleValidation[];
329
+ /** True if the manifest is valid, every rule loaded cleanly, and every rule with fixtures passed RED->GREEN. */
330
+ ok: boolean;
331
+ }
332
+ interface PackRuleValidation {
333
+ ruleId: string;
334
+ /** "ok" | "missing-fixtures" | "red-failed" | "green-failed" */
335
+ status: "ok" | "missing-fixtures" | "red-failed" | "green-failed";
336
+ detail: string;
337
+ }
338
+ declare function validatePack(dir: string): PackValidateResult;
339
+
340
+ interface GraduationEvent {
341
+ pack_id: string;
342
+ rule_id: string;
343
+ repo_hash: string;
344
+ user_hash: string;
345
+ timestamp: string;
346
+ }
347
+ declare function isTelemetryEnabled(targetDir: string): boolean;
348
+ declare function getUserHash(): string;
349
+ declare function getRepoHash(targetDir: string): string;
350
+ declare function telemetryFilePath(targetDir: string): string;
351
+ declare const DEFAULT_REGISTRY_URL = "https://registry.brainblast.tech";
352
+ interface TelemetrySubmitResult {
353
+ submitted: number;
354
+ accepted: number;
355
+ rejected: number;
356
+ graduations: {
357
+ pack_id: string;
358
+ rule_id: string;
359
+ distinct_pairs: number;
360
+ graduated: boolean;
361
+ }[];
362
+ }
363
+ declare function submitTelemetry(targetDir: string, registryUrl?: string): Promise<TelemetrySubmitResult>;
364
+ declare function recordGraduationEvents(targetDir: string, events: {
365
+ pack_id: string;
366
+ rule_id: string;
367
+ }[]): void;
368
+
288
369
  type UpgradeAuthorityKind = "renounced" | "single-key" | "multisig" | "dao" | "unknown";
289
370
  type UpgradeAuthoritySource = "directory" | "rpc" | "research";
290
371
  interface UpgradeAuthority {
@@ -414,4 +495,4 @@ declare function isEntryExpired(entry: ProgramCacheEntry, ttlHoursOverride?: num
414
495
  */
415
496
  declare function cacheSize(cache: ProgramCache, ttlHoursOverride?: number): number;
416
497
 
417
- export { type AccountFlow, type AuditRef, type BuildOpts, type Candidate, type ChangedRanges, type CheckOutcome, type CheckResult, type CheckResultKind, type Checker, type ConfigCandidate, type ConfigChecker, type CostReport, DEFAULT_TTL_HOURS, type OnChainProgram, type ParityNote, type ParsedDiff, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type Rule, type RustAccountField, type RustCandidate, type RustChecker, type Severity, type TrustGraph, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, type WatchEvent, type WatchOptions, analyzeCosts, applyDiffToFile, audit, auditWithRule, base58Decode, base58Encode, buildTrustGraph, rules as bundledRules, cacheSize, checkerKinds, defaultCachePath, fileChanged, findCandidates, findConfigCandidates, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getWorkingTreeChanges, isEntryExpired, isValidSolanaAddress, lamportsToSol, loadDirectory, loadProgramCache, loadRules, parseDiff, putCacheEntry, rangeChanged, renderCostReportMd, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, runChecker, runIncrementalScan, saveProgramCache, startWatch, testKinds };
498
+ export { type AccountFlow, type AuditRef, type BuildOpts, type Candidate, type ChangedRanges, type CheckOutcome, type CheckResult, type CheckResultKind, type Checker, type ConfigCandidate, type ConfigChecker, type CostReport, DEFAULT_REGISTRY_URL, DEFAULT_TTL_HOURS, type GraduationEvent, type OnChainProgram, PACK_MANIFEST_FILE, type PackInitOptions, type PackManifest, type PackRuleValidation, type PackValidateResult, type ParityNote, type ParsedDiff, type PriorityFeePosture, type ProgramCache, type ProgramCacheEntry, type Recoverability, type Rule, type RustAccountField, type RustCandidate, type RustChecker, type Severity, type TelemetrySubmitResult, type TrustGraph, type UpgradeAuthority, type UpgradeAuthorityKind, type UpgradeAuthoritySource, type VerifiedBuildState, type WatchEvent, type WatchOptions, analyzeCosts, applyDiffToFile, audit, auditWithRule, base58Decode, base58Encode, buildTrustGraph, rules as bundledRules, cacheSize, checkerKinds, defaultCachePath, fileChanged, findCandidates, findConfigCandidates, generateTestForResult, getCacheEntry, getCacheEntryMeta, getChangedRanges, getRepoHash, getUserHash, getWorkingTreeChanges, initPack, isEntryExpired, isTelemetryEnabled, isValidSolanaAddress, lamportsToSol, loadDirectory, loadPack, loadPacksFromDir, loadProgramCache, loadRules, parseDiff, putCacheEntry, rangeChanged, recordGraduationEvents, renderCostReportMd, renderTest, renderTrustGraphMd, rentExemptMinimum, resolveRules, runChecker, runIncrementalScan, saveProgramCache, startWatch, submitTelemetry, telemetryFilePath, testKinds, validatePack, validatePackManifest };
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import {
2
+ DEFAULT_REGISTRY_URL,
2
3
  DEFAULT_TTL_HOURS,
4
+ PACK_MANIFEST_FILE,
3
5
  analyzeCosts,
4
6
  applyDiffToFile,
5
7
  audit,
@@ -16,16 +18,23 @@ import {
16
18
  getCacheEntry,
17
19
  getCacheEntryMeta,
18
20
  getChangedRanges,
21
+ getRepoHash,
22
+ getUserHash,
19
23
  getWorkingTreeChanges,
24
+ initPack,
20
25
  isEntryExpired,
26
+ isTelemetryEnabled,
21
27
  isValidSolanaAddress,
22
28
  lamportsToSol,
23
29
  loadDirectory,
30
+ loadPack,
31
+ loadPacksFromDir,
24
32
  loadProgramCache,
25
33
  loadRules,
26
34
  parseDiff,
27
35
  putCacheEntry,
28
36
  rangeChanged,
37
+ recordGraduationEvents,
29
38
  renderCostReportMd,
30
39
  renderTest,
31
40
  renderTrustGraphMd,
@@ -36,8 +45,12 @@ import {
36
45
  runIncrementalScan,
37
46
  saveProgramCache,
38
47
  startWatch,
39
- testKinds
40
- } from "./chunk-XQUQOBXZ.js";
48
+ submitTelemetry,
49
+ telemetryFilePath,
50
+ testKinds,
51
+ validatePack,
52
+ validatePackManifest
53
+ } from "./chunk-LOPYKIXE.js";
41
54
 
42
55
  // src/generate.ts
43
56
  import { writeFileSync, mkdirSync } from "fs";
@@ -53,7 +66,9 @@ function generateTestForResult(result, rule, outPath) {
53
66
  return outPath;
54
67
  }
55
68
  export {
69
+ DEFAULT_REGISTRY_URL,
56
70
  DEFAULT_TTL_HOURS,
71
+ PACK_MANIFEST_FILE,
57
72
  analyzeCosts,
58
73
  applyDiffToFile,
59
74
  audit,
@@ -72,16 +87,23 @@ export {
72
87
  getCacheEntry,
73
88
  getCacheEntryMeta,
74
89
  getChangedRanges,
90
+ getRepoHash,
91
+ getUserHash,
75
92
  getWorkingTreeChanges,
93
+ initPack,
76
94
  isEntryExpired,
95
+ isTelemetryEnabled,
77
96
  isValidSolanaAddress,
78
97
  lamportsToSol,
79
98
  loadDirectory,
99
+ loadPack,
100
+ loadPacksFromDir,
80
101
  loadProgramCache,
81
102
  loadRules,
82
103
  parseDiff,
83
104
  putCacheEntry,
84
105
  rangeChanged,
106
+ recordGraduationEvents,
85
107
  renderCostReportMd,
86
108
  renderTest,
87
109
  renderTrustGraphMd,
@@ -91,5 +113,9 @@ export {
91
113
  runIncrementalScan,
92
114
  saveProgramCache,
93
115
  startWatch,
94
- testKinds
116
+ submitTelemetry,
117
+ telemetryFilePath,
118
+ testKinds,
119
+ validatePack,
120
+ validatePackManifest
95
121
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainblast",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "description": "Deterministic auditor for catastrophic AI-integration bugs: scan a repo, find the silent money/auth traps, and generate the behavioral test that proves they're fixed.",
6
6
  "keywords": [