brainclaw 1.12.0 → 1.14.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/README.md +40 -0
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +11 -3
- package/dist/commands/claim-resource.js +1 -0
- package/dist/commands/doctor.js +98 -0
- package/dist/commands/estimation-report.js +1 -1
- package/dist/commands/harvest.js +38 -24
- package/dist/commands/mcp.js +328 -46
- package/dist/commands/release-claim.js +21 -1
- package/dist/commands/session-start.js +16 -1
- package/dist/core/agent-capability.js +15 -4
- package/dist/core/agent-registry.js +7 -1
- package/dist/core/assignment-sweeper.js +92 -11
- package/dist/core/claims.js +160 -1
- package/dist/core/context.js +11 -4
- package/dist/core/dispatch-status.js +113 -7
- package/dist/core/entity-operations.js +51 -3
- package/dist/core/gc-semantic.js +79 -0
- package/dist/core/hint-aging.js +188 -0
- package/dist/core/hygiene-policy.js +77 -0
- package/dist/core/loops/store.js +33 -0
- package/dist/core/reputation.js +18 -0
- package/dist/core/schema.js +29 -0
- package/dist/core/worktree.js +198 -22
- package/dist/facts.js +36 -3
- package/dist/facts.json +35 -2
- package/docs/concepts/dispatch-supervisor.md +393 -0
- package/docs/mcp-schema-changelog.md +7 -2
- package/package.json +6 -4
package/dist/core/worktree.js
CHANGED
|
@@ -229,6 +229,45 @@ function runGit(args, cwd, timeoutMs = GIT_QUERY_TIMEOUT_MS) {
|
|
|
229
229
|
stderr: result.stderr ?? '',
|
|
230
230
|
};
|
|
231
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Resolve the real git worktree root for `cwd` (pln#614). On an IN-TREE project
|
|
234
|
+
* — a project dir that sits inside a larger repo (monorepo), where the git root
|
|
235
|
+
* is an ancestor, not the project dir itself — `git worktree add` MUST run from
|
|
236
|
+
* the true toplevel, and the per-project worktree hash MUST be derived from it.
|
|
237
|
+
*
|
|
238
|
+
* The bug (trp_28025248, cross-machine dogfooding 1.13.0): the assign/review
|
|
239
|
+
* claim path passed the project cwd straight to createWorktree, so `git worktree
|
|
240
|
+
* add` ran from the project dir and — with an empty `.git` left by the embedded
|
|
241
|
+
* init — failed with "not a git repository", while the ideation path (which
|
|
242
|
+
* resolved the toplevel) worked. resolveGitToplevel makes both paths agree.
|
|
243
|
+
*
|
|
244
|
+
* Falls back to the input cwd when `git rev-parse` cannot resolve a toplevel
|
|
245
|
+
* (not a repo, git absent) so non-git callers and tests keep their behaviour.
|
|
246
|
+
*/
|
|
247
|
+
export function resolveGitToplevel(cwd) {
|
|
248
|
+
// Codex review of PR #49 (HIGH): a stale/empty `.git` INSIDE the project dir
|
|
249
|
+
// (left by the embedded init — the exact leazzy case) makes `git rev-parse
|
|
250
|
+
// --show-toplevel` FAIL at that level instead of discovering the parent repo:
|
|
251
|
+
// git stops at the invalid gitdir. A plain fallback-to-cwd would then still
|
|
252
|
+
// run from the project dir and hash the subdir — the bug unfixed. So on
|
|
253
|
+
// failure we walk UP and retry from each ancestor, skipping past the invalid
|
|
254
|
+
// nested gitdir until a real toplevel is found; only a truly non-git tree
|
|
255
|
+
// falls back to the input cwd.
|
|
256
|
+
let dir = path.resolve(cwd);
|
|
257
|
+
for (let depth = 0; depth < 64; depth += 1) {
|
|
258
|
+
const result = runGit(['rev-parse', '--show-toplevel'], dir);
|
|
259
|
+
if (result.ok) {
|
|
260
|
+
const top = result.stdout.trim();
|
|
261
|
+
if (top)
|
|
262
|
+
return path.resolve(top);
|
|
263
|
+
}
|
|
264
|
+
const parent = path.dirname(dir);
|
|
265
|
+
if (parent === dir)
|
|
266
|
+
break; // filesystem root — not inside any repo
|
|
267
|
+
dir = parent;
|
|
268
|
+
}
|
|
269
|
+
return cwd;
|
|
270
|
+
}
|
|
232
271
|
/**
|
|
233
272
|
* Returns true if the given path is a bare git repository.
|
|
234
273
|
* Bare repos have no working tree, so worktree add is not applicable.
|
|
@@ -444,6 +483,12 @@ export function findWorktreePathForBranch(worktrees, branchName) {
|
|
|
444
483
|
* Returns the absolute path to the newly created worktree.
|
|
445
484
|
*/
|
|
446
485
|
export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
486
|
+
// pln#614: resolve the true git toplevel first, so an in-tree project (project
|
|
487
|
+
// dir ≠ git root) creates its worktree from the real repo root — `git worktree
|
|
488
|
+
// add` runs there, and the per-project worktree hash (resolveWorktreePath) is
|
|
489
|
+
// derived from it, matching the ideation path. All git commands + the hash
|
|
490
|
+
// below use this resolved root rather than the raw project cwd.
|
|
491
|
+
mainWorktreePath = resolveGitToplevel(mainWorktreePath);
|
|
447
492
|
const symlinkWarnings = [];
|
|
448
493
|
const trySymlinkSharedPath = (entryName) => {
|
|
449
494
|
const sourcePath = path.join(mainWorktreePath, entryName);
|
|
@@ -582,6 +627,16 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
|
582
627
|
if (fs.existsSync(mainGitignorePath)) {
|
|
583
628
|
fs.copyFileSync(mainGitignorePath, targetGitignorePath);
|
|
584
629
|
}
|
|
630
|
+
// trp#926 — record the RESOLVED base ref SHA at creation time. base_ref is
|
|
631
|
+
// usually "HEAD" or a branch name, both of which drift after creation and
|
|
632
|
+
// are useless for later "how many commits did the worker add?" comparisons.
|
|
633
|
+
// The resolved SHA is the stable anchor: `${base_ref_sha}..HEAD` on the lane
|
|
634
|
+
// deterministically counts the worker's contribution even as master advances.
|
|
635
|
+
// Best-effort — an unresolvable base_ref simply omits the field.
|
|
636
|
+
const baseRefSha = (() => {
|
|
637
|
+
const rev = runGit(['rev-parse', baseRef], mainWorktreePath);
|
|
638
|
+
return rev.ok ? rev.stdout.trim() : undefined;
|
|
639
|
+
})();
|
|
585
640
|
// Write brainclaw metadata sidecar inside the worktree
|
|
586
641
|
const meta = {
|
|
587
642
|
session_id: options.sessionId,
|
|
@@ -590,6 +645,7 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
|
590
645
|
created_at: new Date().toISOString(),
|
|
591
646
|
main_worktree_path: mainWorktreePath,
|
|
592
647
|
base_ref: baseRef,
|
|
648
|
+
...(baseRefSha ? { base_ref_sha: baseRefSha } : {}),
|
|
593
649
|
reset_existing_branch: options.resetExistingBranch === true,
|
|
594
650
|
git_advice: 'git add ONLY specific files, NEVER git add -A.',
|
|
595
651
|
// pln#523: surface any shared-path link failures (e.g. node_modules junction
|
|
@@ -827,25 +883,63 @@ export function safeRemoveWorktreeDir(dirPath) {
|
|
|
827
883
|
catch { /* best effort */ }
|
|
828
884
|
}
|
|
829
885
|
/**
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
886
|
+
* Depth cap for detachWorktreeJunctions' recursive walk. 8 covers realistic
|
|
887
|
+
* monorepo trees (apps/<pkg>/packages/<pkg>/node_modules, pnpm nested links)
|
|
888
|
+
* while keeping the walk bounded on pathological structures. Hitting the cap
|
|
889
|
+
* is a hard failure: continuing to `git worktree remove` after an incomplete
|
|
890
|
+
* scan would re-open the junction-follow wipe class. .git is skipped outright
|
|
891
|
+
* — it never contains user junctions and can be very deep.
|
|
892
|
+
*/
|
|
893
|
+
const JUNCTION_SCAN_MAX_DEPTH = 8;
|
|
894
|
+
/**
|
|
895
|
+
* pln#498 + trp#926 (2026-07-03 incident) — Detach ALL symlinks/junctions from
|
|
896
|
+
* a worktree before any recursive removal. On Windows, `git worktree remove`
|
|
897
|
+
* performs its own recursive rm and historically (git ≤ 2.38) followed NTFS
|
|
898
|
+
* junctions into the main repo, wiping `node_modules`. Unlinking every
|
|
899
|
+
* junction entry first leaves git only regular files/dirs to walk.
|
|
900
|
+
*
|
|
901
|
+
* Historically this only inspected top-level entries — that covered the
|
|
902
|
+
* classic single-stack shared `node_modules` case but MISSED:
|
|
903
|
+
* - monorepo per-package junctions created by pln#523
|
|
904
|
+
* (apps/<pkg>/node_modules, packages/<pkg>/node_modules);
|
|
905
|
+
* - operator- or worker-created manual junctions at nested paths.
|
|
906
|
+
* The 2026-07-03 incident (node_modules racine rasé via the auto-junction)
|
|
907
|
+
* was a recurrence of the pln#498 class, extended one level of nesting.
|
|
835
908
|
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
909
|
+
* The recursion NEVER descends into a symlink (lstat + unlink at the entry
|
|
910
|
+
* itself), so it cannot follow a junction into the main repo. `.git/` is
|
|
911
|
+
* skipped entirely — git manages its own state and it never holds user
|
|
912
|
+
* junctions. Depth is capped defensively at JUNCTION_SCAN_MAX_DEPTH; hitting
|
|
913
|
+
* the cap aborts removal rather than silently leaving deeper links in place.
|
|
838
914
|
*/
|
|
839
915
|
export function detachWorktreeJunctions(worktreePath) {
|
|
916
|
+
if (!fs.existsSync(worktreePath))
|
|
917
|
+
return;
|
|
918
|
+
const failures = [];
|
|
919
|
+
detachJunctionsRecursively(worktreePath, 0, failures);
|
|
920
|
+
if (failures.length > 0) {
|
|
921
|
+
throw new Error(`could not safely detach worktree junctions: ${failures.join('; ')}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function detachJunctionsRecursively(dir, depth, failures) {
|
|
925
|
+
if (depth > JUNCTION_SCAN_MAX_DEPTH) {
|
|
926
|
+
failures.push(`scan depth exceeded at ${dir}`);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
840
929
|
let entries;
|
|
841
930
|
try {
|
|
842
|
-
entries = fs.readdirSync(
|
|
931
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
843
932
|
}
|
|
844
|
-
catch {
|
|
845
|
-
|
|
933
|
+
catch (err) {
|
|
934
|
+
failures.push(`could not read ${dir}: ${err.message}`);
|
|
935
|
+
return;
|
|
846
936
|
}
|
|
847
937
|
for (const entry of entries) {
|
|
848
|
-
|
|
938
|
+
// `.git` is a file (linked worktree pointer) at the worktree root or a
|
|
939
|
+
// real dir in the main repo — either way, do not walk it.
|
|
940
|
+
if (entry.name === '.git')
|
|
941
|
+
continue;
|
|
942
|
+
const child = path.join(dir, entry.name);
|
|
849
943
|
let stat;
|
|
850
944
|
try {
|
|
851
945
|
stat = fs.lstatSync(child);
|
|
@@ -853,16 +947,24 @@ export function detachWorktreeJunctions(worktreePath) {
|
|
|
853
947
|
catch {
|
|
854
948
|
continue;
|
|
855
949
|
}
|
|
856
|
-
if (
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
fs.unlinkSync(child);
|
|
860
|
-
}
|
|
861
|
-
catch {
|
|
950
|
+
if (stat.isSymbolicLink()) {
|
|
951
|
+
// Unlink the junction. Do NOT descend into it (that would follow the
|
|
952
|
+
// link back into the main repo — the exact class we're preventing).
|
|
862
953
|
try {
|
|
863
|
-
fs.
|
|
954
|
+
fs.unlinkSync(child);
|
|
864
955
|
}
|
|
865
|
-
catch {
|
|
956
|
+
catch (unlinkErr) {
|
|
957
|
+
try {
|
|
958
|
+
fs.rmdirSync(child);
|
|
959
|
+
}
|
|
960
|
+
catch (rmdirErr) {
|
|
961
|
+
failures.push(`could not detach link ${child}: unlink=${unlinkErr.message}; rmdir=${rmdirErr.message}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
if (stat.isDirectory()) {
|
|
967
|
+
detachJunctionsRecursively(child, depth + 1, failures);
|
|
866
968
|
}
|
|
867
969
|
}
|
|
868
970
|
}
|
|
@@ -924,6 +1026,57 @@ export function worktreeHasOnlyBirthNoise(statusZStdout) {
|
|
|
924
1026
|
|| isSystemDirtyPath(norm);
|
|
925
1027
|
});
|
|
926
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* trp#926 (squash-aware GC) — True when every commit on `branch` that is not
|
|
1031
|
+
* an ancestor of `baseRef` has a patch-equivalent commit ALREADY on `baseRef`.
|
|
1032
|
+
*
|
|
1033
|
+
* `git branch --merged HEAD` and `merge-base --is-ancestor` are ancestry-only:
|
|
1034
|
+
* a squash-merge on GitHub creates a NEW commit on master whose ancestry does
|
|
1035
|
+
* not include the lane commits, so an ancestry probe returns "not merged" and
|
|
1036
|
+
* the GC keeps the worktree forever. `git cherry <base> <branch>` uses
|
|
1037
|
+
* patch-id equivalence — the same signal GitHub itself uses to say "this PR is
|
|
1038
|
+
* merged". Output is one line per commit in `base..branch`:
|
|
1039
|
+
* `+ <sha>` = patch NOT yet on base (a real un-integrated commit)
|
|
1040
|
+
* `- <sha>` = patch already on base (via squash / cherry-pick / rebase)
|
|
1041
|
+
* The branch is "merged by content" iff there are no `+` lines. An empty
|
|
1042
|
+
* output (no commits ahead of base) is also merged.
|
|
1043
|
+
*
|
|
1044
|
+
* Returns `false` on any git failure — a probe that cannot prove merged must
|
|
1045
|
+
* NEVER lie "yes" and cause a keep-me worktree to be GC'd. Callers combine
|
|
1046
|
+
* this with ancestry ('git branch --merged') so both signals contribute.
|
|
1047
|
+
*/
|
|
1048
|
+
export function isBranchMergedByContent(mainWorktreePath, branchName, baseRef = 'HEAD') {
|
|
1049
|
+
const cherry = runGit(['cherry', baseRef, branchName], mainWorktreePath);
|
|
1050
|
+
if (!cherry.ok)
|
|
1051
|
+
return false;
|
|
1052
|
+
const lines = cherry.stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
1053
|
+
if (lines.length === 0)
|
|
1054
|
+
return true; // no commits ahead of base → fully merged
|
|
1055
|
+
// A `+` line means "patch not on base" — one is enough to disqualify.
|
|
1056
|
+
if (!lines.some((l) => l.startsWith('+ ')))
|
|
1057
|
+
return true;
|
|
1058
|
+
// Multi-commit squash merges often do not produce per-commit patch-id
|
|
1059
|
+
// matches: the squash commit's aggregate patch differs from each individual
|
|
1060
|
+
// branch commit. As a second, still content-only signal, compare the final
|
|
1061
|
+
// content of files changed by the branch. If every branch-touched path now
|
|
1062
|
+
// matches baseRef, removing the worktree cannot drop unique file content.
|
|
1063
|
+
const mergeBase = runGit(['merge-base', baseRef, branchName], mainWorktreePath);
|
|
1064
|
+
if (!mergeBase.ok || !mergeBase.stdout.trim())
|
|
1065
|
+
return false;
|
|
1066
|
+
const changed = runGit(['diff', '--name-only', '-z', mergeBase.stdout.trim(), branchName], mainWorktreePath);
|
|
1067
|
+
if (!changed.ok)
|
|
1068
|
+
return false;
|
|
1069
|
+
const paths = changed.stdout.split('\0').filter(Boolean);
|
|
1070
|
+
if (paths.length === 0)
|
|
1071
|
+
return true;
|
|
1072
|
+
for (let i = 0; i < paths.length; i += 100) {
|
|
1073
|
+
const chunk = paths.slice(i, i + 100);
|
|
1074
|
+
const diff = runGit(['diff', '--quiet', branchName, baseRef, '--', ...chunk], mainWorktreePath);
|
|
1075
|
+
if (!diff.ok)
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
return true;
|
|
1079
|
+
}
|
|
927
1080
|
/**
|
|
928
1081
|
* Removes worktrees whose branch has been fully merged into the current branch
|
|
929
1082
|
* (typically master/main after a merge). Also removes brainclaw-managed
|
|
@@ -931,9 +1084,19 @@ export function worktreeHasOnlyBirthNoise(statusZStdout) {
|
|
|
931
1084
|
* (orphan dirs left behind by force-deleted branches).
|
|
932
1085
|
*
|
|
933
1086
|
* Safe by default: skips worktrees with uncommitted changes unless `force` is set.
|
|
1087
|
+
*
|
|
1088
|
+
* trp#926 — Merged detection is a UNION of two signals:
|
|
1089
|
+
* - ancestry (`git branch --merged HEAD`): catches fast-forward / merge-commit;
|
|
1090
|
+
* - content (`git cherry HEAD <branch>`, patch-id): catches squash merges,
|
|
1091
|
+
* which is GitHub's default merge strategy on this repo and previously left
|
|
1092
|
+
* every squashed lane un-GC-able forever.
|
|
934
1093
|
*/
|
|
935
1094
|
export function cleanMergedWorktrees(mainWorktreePath, options = {}) {
|
|
936
1095
|
const result = { removed: [], skipped: [], pruned: false };
|
|
1096
|
+
// pln#614: resolve the toplevel so an in-tree project scans the same
|
|
1097
|
+
// per-project worktree hash createWorktree wrote under (and runs git from the
|
|
1098
|
+
// real repo root).
|
|
1099
|
+
mainWorktreePath = resolveGitToplevel(mainWorktreePath);
|
|
937
1100
|
// First prune stale git worktree admin entries
|
|
938
1101
|
pruneWorktrees(mainWorktreePath);
|
|
939
1102
|
result.pruned = true;
|
|
@@ -949,7 +1112,12 @@ export function cleanMergedWorktrees(mainWorktreePath, options = {}) {
|
|
|
949
1112
|
for (const wt of worktrees) {
|
|
950
1113
|
if (wt.is_main)
|
|
951
1114
|
continue;
|
|
952
|
-
|
|
1115
|
+
// trp#926 — a lane's branch is "merged" if EITHER git says its commits are
|
|
1116
|
+
// ancestors of HEAD (fast-forward / merge-commit) OR every commit's patch
|
|
1117
|
+
// is already on HEAD (squash-merge, catching GitHub's default strategy).
|
|
1118
|
+
// Without the content probe, squashed lanes accumulate forever.
|
|
1119
|
+
const isMerged = mergedBranches.has(wt.branch)
|
|
1120
|
+
|| isBranchMergedByContent(mainWorktreePath, wt.branch, 'HEAD');
|
|
953
1121
|
if (!isMerged) {
|
|
954
1122
|
continue;
|
|
955
1123
|
}
|
|
@@ -1028,6 +1196,9 @@ export function gcWorktreeIfHarvested(mainWorktreePath, worktreePath, options =
|
|
|
1028
1196
|
});
|
|
1029
1197
|
if (!worktreePath || !fs.existsSync(worktreePath))
|
|
1030
1198
|
return out(false, 'already gone');
|
|
1199
|
+
// pln#614: the merge-base / patch-id probes below run from the main repo — an
|
|
1200
|
+
// in-tree project dir (empty .git) would fail them; resolve the real toplevel.
|
|
1201
|
+
mainWorktreePath = resolveGitToplevel(mainWorktreePath);
|
|
1031
1202
|
if (workerLooksAlive(worktreePath, options.livenessWindowMs ?? WORKTREE_GC_LIVENESS_WINDOW_MS)) {
|
|
1032
1203
|
return out(false, 'worker still active (recent heartbeat)');
|
|
1033
1204
|
}
|
|
@@ -1054,9 +1225,14 @@ export function gcWorktreeIfHarvested(mainWorktreePath, worktreePath, options =
|
|
|
1054
1225
|
}
|
|
1055
1226
|
const ancestor = runGit(['merge-base', '--is-ancestor', laneHead.stdout.trim(), mainHead.stdout.trim()], mainWorktreePath);
|
|
1056
1227
|
// exit 0 = ancestor (safe). Non-zero = not an ancestor OR a git error — both
|
|
1057
|
-
// mean "cannot prove integrated"
|
|
1058
|
-
|
|
1228
|
+
// mean "cannot prove integrated via ancestry". trp#926 — fall back to the
|
|
1229
|
+
// content probe (patch-id): a squash-merged lane is not an ancestor but is
|
|
1230
|
+
// fully integrated, and previously the GC kept it forever. Ancestry OR
|
|
1231
|
+
// content, either signal is enough; a failed content probe still keeps the
|
|
1232
|
+
// worktree (isBranchMergedByContent returns false on git failure).
|
|
1233
|
+
if (!ancestor.ok && branch && !isBranchMergedByContent(mainWorktreePath, branch, mainHead.stdout.trim())) {
|
|
1059
1234
|
return out(false, 'lane branch has un-integrated commits (or unverifiable)', branch);
|
|
1235
|
+
}
|
|
1060
1236
|
}
|
|
1061
1237
|
try {
|
|
1062
1238
|
removeWorktree(mainWorktreePath, worktreePath, { force: true });
|
package/dist/facts.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated by scripts/emit-site-facts.mjs at build time. Do not edit manually.
|
|
2
|
-
// Source: brainclaw v1.
|
|
2
|
+
// Source: brainclaw v1.14.0 on 2026-07-05T13:38:40.276Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.
|
|
5
|
-
"generated_at": "2026-
|
|
4
|
+
"version": "1.14.0",
|
|
5
|
+
"generated_at": "2026-07-05T13:38:40.276Z",
|
|
6
6
|
"tools": {
|
|
7
7
|
"count": 67,
|
|
8
8
|
"published_count": 66,
|
|
@@ -469,6 +469,39 @@ export const FACTS = {
|
|
|
469
469
|
"max_concurrent_tasks": 1
|
|
470
470
|
}
|
|
471
471
|
]
|
|
472
|
+
},
|
|
473
|
+
"bench": {
|
|
474
|
+
"schema": "brainclaw.bench.v1",
|
|
475
|
+
"generated_at": "2026-07-05T13:38:38.216Z",
|
|
476
|
+
"node_version": "v24.18.0",
|
|
477
|
+
"platform": "linux-x64",
|
|
478
|
+
"repeats": 3,
|
|
479
|
+
"scenarios": [
|
|
480
|
+
{
|
|
481
|
+
"name": "cold_onboard",
|
|
482
|
+
"volume": "empty",
|
|
483
|
+
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
484
|
+
"duration_ms_median": 72,
|
|
485
|
+
"payload_chars_median": 1650,
|
|
486
|
+
"payload_tokens_est_median": 413
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"name": "warm_work",
|
|
490
|
+
"volume": "medium",
|
|
491
|
+
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
492
|
+
"duration_ms_median": 126,
|
|
493
|
+
"payload_chars_median": 2626,
|
|
494
|
+
"payload_tokens_est_median": 657
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
"name": "first_edit",
|
|
498
|
+
"volume": "medium",
|
|
499
|
+
"description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
|
|
500
|
+
"duration_ms_median": 8,
|
|
501
|
+
"payload_chars_median": 442,
|
|
502
|
+
"payload_tokens_est_median": 111
|
|
503
|
+
}
|
|
504
|
+
]
|
|
472
505
|
}
|
|
473
506
|
}
|
|
474
507
|
export default FACTS
|
package/dist/facts.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
3
|
-
"generated_at": "2026-
|
|
2
|
+
"version": "1.14.0",
|
|
3
|
+
"generated_at": "2026-07-05T13:38:40.276Z",
|
|
4
4
|
"tools": {
|
|
5
5
|
"count": 67,
|
|
6
6
|
"published_count": 66,
|
|
@@ -467,5 +467,38 @@
|
|
|
467
467
|
"max_concurrent_tasks": 1
|
|
468
468
|
}
|
|
469
469
|
]
|
|
470
|
+
},
|
|
471
|
+
"bench": {
|
|
472
|
+
"schema": "brainclaw.bench.v1",
|
|
473
|
+
"generated_at": "2026-07-05T13:38:38.216Z",
|
|
474
|
+
"node_version": "v24.18.0",
|
|
475
|
+
"platform": "linux-x64",
|
|
476
|
+
"repeats": 3,
|
|
477
|
+
"scenarios": [
|
|
478
|
+
{
|
|
479
|
+
"name": "cold_onboard",
|
|
480
|
+
"volume": "empty",
|
|
481
|
+
"description": "fresh machine → init → first useful context. Baseline for time-to-first-value.",
|
|
482
|
+
"duration_ms_median": 72,
|
|
483
|
+
"payload_chars_median": 1650,
|
|
484
|
+
"payload_tokens_est_median": 413
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"name": "warm_work",
|
|
488
|
+
"volume": "medium",
|
|
489
|
+
"description": "bclaw_work consult over a real-shaped store (~200 plans / 500 handoffs / 450 claims).",
|
|
490
|
+
"duration_ms_median": 126,
|
|
491
|
+
"payload_chars_median": 2626,
|
|
492
|
+
"payload_tokens_est_median": 657
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"name": "first_edit",
|
|
496
|
+
"volume": "medium",
|
|
497
|
+
"description": "code_find + code_brief on the fresh-agent path (missing index, first touch).",
|
|
498
|
+
"duration_ms_median": 8,
|
|
499
|
+
"payload_chars_median": 442,
|
|
500
|
+
"payload_tokens_est_median": 111
|
|
501
|
+
}
|
|
502
|
+
]
|
|
470
503
|
}
|
|
471
504
|
}
|