brainclaw 1.7.2 → 1.7.4
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 +132 -102
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +13 -1
- package/dist/commands/harvest.js +124 -1
- package/dist/commands/mcp.js +23 -10
- package/dist/core/agent-capability.js +28 -0
- package/dist/core/agent-inventory.js +54 -7
- package/dist/core/agentrun-reconciler.js +72 -6
- package/dist/core/dirty-scope.js +11 -5
- package/dist/core/dispatch-status.js +67 -4
- package/dist/core/dispatcher.js +51 -3
- package/dist/core/entity-operations.js +36 -0
- package/dist/core/entity-registry.js +1 -1
- package/dist/core/instruction-templates.js +1 -1
- package/dist/core/runtime-signals.js +72 -0
- package/dist/core/schema.js +18 -0
- package/dist/core/worktree.js +227 -7
- package/dist/facts.js +3 -3
- package/dist/facts.json +2 -2
- package/package.json +1 -1
package/dist/core/schema.js
CHANGED
|
@@ -855,7 +855,25 @@ export const RuntimeEventTypeSchema = z.enum([
|
|
|
855
855
|
'run_interrupted',
|
|
856
856
|
'plan_cascade_to_done',
|
|
857
857
|
'candidate_harvested',
|
|
858
|
+
'lane_result_harvested',
|
|
858
859
|
]);
|
|
860
|
+
/**
|
|
861
|
+
* pln#526 — LANE-RESULT convention. A dispatched worker writes a single
|
|
862
|
+
* `LANE-RESULT.json` at its worktree root as its final step (a fallback that
|
|
863
|
+
* works even when bclaw_assignment_update / MCP is unavailable, e.g. sandboxed
|
|
864
|
+
* agents). The coordinator ingests it with `brainclaw harvest <assignment_id>`.
|
|
865
|
+
*/
|
|
866
|
+
export const LaneResultSchema = z.object({
|
|
867
|
+
assignment_id: z.string(),
|
|
868
|
+
status: z.enum(['completed', 'blocked', 'failed']),
|
|
869
|
+
summary: z.string(),
|
|
870
|
+
/** Paths or refs the worker produced (commits, files, docs). */
|
|
871
|
+
artifacts: z.array(z.string()).optional(),
|
|
872
|
+
/** Files the worker changed in the worktree. */
|
|
873
|
+
files_changed: z.array(z.string()).optional(),
|
|
874
|
+
/** Free-form notes (blockers, follow-ups). */
|
|
875
|
+
notes: z.string().optional(),
|
|
876
|
+
});
|
|
859
877
|
export const RuntimeEventSchema = z.object({
|
|
860
878
|
id: z.string(),
|
|
861
879
|
agent: z.string(),
|
package/dist/core/worktree.js
CHANGED
|
@@ -3,6 +3,9 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import yaml from 'yaml';
|
|
7
|
+
import { logger } from './logger.js';
|
|
8
|
+
import { parsePorcelainZ, isSystemDirtyPath } from './dirty-scope.js';
|
|
6
9
|
/** Normalizes a path for use in git CLI arguments (forward slashes on Windows). */
|
|
7
10
|
function gitPath(p) {
|
|
8
11
|
return p.replace(/\\/g, '/');
|
|
@@ -37,6 +40,85 @@ export function detectStackSharedPaths(projectRoot) {
|
|
|
37
40
|
}
|
|
38
41
|
return [...result];
|
|
39
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* pln#523 — read declared monorepo workspace globs from npm/yarn/bun
|
|
45
|
+
* `workspaces` (package.json) and pnpm-workspace.yaml. Returns the raw
|
|
46
|
+
* patterns (e.g. "packages/*", "apps/api"); empty when the project is not a
|
|
47
|
+
* workspace root or the manifests are absent/invalid.
|
|
48
|
+
*/
|
|
49
|
+
export function readWorkspacePatterns(projectRoot) {
|
|
50
|
+
const patterns = [];
|
|
51
|
+
// npm / yarn / bun: package.json "workspaces" (array, or { packages: [...] })
|
|
52
|
+
try {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf-8'));
|
|
54
|
+
const ws = pkg.workspaces;
|
|
55
|
+
if (Array.isArray(ws))
|
|
56
|
+
patterns.push(...ws);
|
|
57
|
+
else if (ws && Array.isArray(ws.packages))
|
|
58
|
+
patterns.push(...ws.packages);
|
|
59
|
+
}
|
|
60
|
+
catch { /* no / invalid package.json — not a node workspace root */ }
|
|
61
|
+
// pnpm: pnpm-workspace.yaml "packages"
|
|
62
|
+
try {
|
|
63
|
+
const parsed = yaml.parse(fs.readFileSync(path.join(projectRoot, 'pnpm-workspace.yaml'), 'utf-8'));
|
|
64
|
+
if (parsed && Array.isArray(parsed.packages))
|
|
65
|
+
patterns.push(...parsed.packages);
|
|
66
|
+
}
|
|
67
|
+
catch { /* no pnpm workspace file */ }
|
|
68
|
+
return [...new Set(patterns)];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* pln#523 — resolve monorepo workspace globs to the per-package `node_modules`
|
|
72
|
+
* directories that actually exist on disk. Hoisted monorepos (all deps at the
|
|
73
|
+
* root) need only the root link from detectStackSharedPaths; this additionally
|
|
74
|
+
* covers packages that keep a LOCAL node_modules (pnpm, nohoist, partial
|
|
75
|
+
* hoisting) so a dispatched worker can build/typecheck a sub-package, not just
|
|
76
|
+
* the root — the exact gap behind a worker stalling on `tsc` in a worktree.
|
|
77
|
+
*
|
|
78
|
+
* Pattern shapes supported without a glob dependency (zero-runtime-dep policy):
|
|
79
|
+
* - exact dir: "apps/api"
|
|
80
|
+
* - single wildcard: "packages/*" → immediate child directories
|
|
81
|
+
* - deep wildcard: "packages/**" → treated as one level ("packages/*")
|
|
82
|
+
* Negations ("!pkg/excluded") are skipped — they only narrow coverage and a
|
|
83
|
+
* missing link degrades gracefully to central validation.
|
|
84
|
+
*
|
|
85
|
+
* Returns relative paths with forward slashes (e.g. "apps/api/node_modules").
|
|
86
|
+
*/
|
|
87
|
+
export function detectWorkspaceNodeModules(projectRoot) {
|
|
88
|
+
const patterns = readWorkspacePatterns(projectRoot);
|
|
89
|
+
if (patterns.length === 0)
|
|
90
|
+
return [];
|
|
91
|
+
const result = new Set();
|
|
92
|
+
const addIfHasNodeModules = (relPkgDir) => {
|
|
93
|
+
const rel = `${relPkgDir.replace(/\\/g, '/').replace(/\/+$/, '')}/node_modules`;
|
|
94
|
+
if (fs.existsSync(path.join(projectRoot, rel)))
|
|
95
|
+
result.add(rel);
|
|
96
|
+
};
|
|
97
|
+
for (const raw of patterns) {
|
|
98
|
+
const pattern = raw.trim();
|
|
99
|
+
if (!pattern || pattern.startsWith('!'))
|
|
100
|
+
continue;
|
|
101
|
+
const wildcardIdx = pattern.indexOf('*');
|
|
102
|
+
if (wildcardIdx === -1) {
|
|
103
|
+
addIfHasNodeModules(pattern);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Base dir = the path segment before the first wildcard.
|
|
107
|
+
const base = pattern.slice(0, wildcardIdx).replace(/\/+$/, '');
|
|
108
|
+
let children = [];
|
|
109
|
+
try {
|
|
110
|
+
children = fs
|
|
111
|
+
.readdirSync(path.join(projectRoot, base), { withFileTypes: true })
|
|
112
|
+
.filter((d) => d.isDirectory())
|
|
113
|
+
.map((d) => d.name);
|
|
114
|
+
}
|
|
115
|
+
catch { /* base dir absent — skip this pattern */ }
|
|
116
|
+
for (const child of children) {
|
|
117
|
+
addIfHasNodeModules(base ? `${base}/${child}` : child);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return [...result];
|
|
121
|
+
}
|
|
40
122
|
function canonicalizeScopePath(target) {
|
|
41
123
|
let resolved;
|
|
42
124
|
try {
|
|
@@ -191,6 +273,7 @@ export function findWorktreePathForBranch(worktrees, branchName) {
|
|
|
191
273
|
* Returns the absolute path to the newly created worktree.
|
|
192
274
|
*/
|
|
193
275
|
export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
276
|
+
const symlinkWarnings = [];
|
|
194
277
|
const trySymlinkSharedPath = (entryName) => {
|
|
195
278
|
const sourcePath = path.join(mainWorktreePath, entryName);
|
|
196
279
|
const linkPath = path.join(targetPath, entryName);
|
|
@@ -205,8 +288,20 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
|
205
288
|
}
|
|
206
289
|
fs.symlinkSync(sourcePath, linkPath, 'junction');
|
|
207
290
|
}
|
|
208
|
-
catch {
|
|
209
|
-
//
|
|
291
|
+
catch (err) {
|
|
292
|
+
// pln#523: do NOT swallow silently. A missing node_modules junction is
|
|
293
|
+
// exactly what leaves a dispatched worker unable to build/typecheck in its
|
|
294
|
+
// worktree (it then stalls on `tsc` or npm scripts). Record a structured
|
|
295
|
+
// warning — surfaced in the worktree sidecar + logger — instead of an
|
|
296
|
+
// invisible degradation. Linking remains best-effort (non-fatal).
|
|
297
|
+
const sameVolume = path.parse(sourcePath).root.toLowerCase() === path.parse(targetPath).root.toLowerCase();
|
|
298
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
299
|
+
const hint = sameVolume
|
|
300
|
+
? ''
|
|
301
|
+
: ' (source and worktree are on different volumes — directory junctions require the same volume; deps cannot be linked here, validate builds centrally)';
|
|
302
|
+
const msg = `Failed to link '${entryName}' into worktree: ${reason}${hint}`;
|
|
303
|
+
symlinkWarnings.push(msg);
|
|
304
|
+
logger.warn(`[worktree] ${msg}`);
|
|
210
305
|
}
|
|
211
306
|
};
|
|
212
307
|
// Guard: bare repos have no working tree
|
|
@@ -256,7 +351,15 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
|
256
351
|
// pln#480: auto-detect shared paths from stack markers + config overrides.
|
|
257
352
|
// `dist` intentionally excluded — build outputs must be per-worktree
|
|
258
353
|
// (EBUSY during clean:dist when MCP/extension holds a handle on junction target).
|
|
259
|
-
|
|
354
|
+
// pln#523: also link per-package node_modules for JS/TS monorepos so workers
|
|
355
|
+
// can build/typecheck sub-packages, not just the root. Set
|
|
356
|
+
// BRAINCLAW_NO_LINK_DEPS=1 to disable auto dependency linking (e.g. when the
|
|
357
|
+
// worktree lives on a different volume and central validation is preferred);
|
|
358
|
+
// explicit options.sharedPaths are still honored.
|
|
359
|
+
const linkDepsDisabled = process.env.BRAINCLAW_NO_LINK_DEPS === '1';
|
|
360
|
+
const detected = linkDepsDisabled
|
|
361
|
+
? []
|
|
362
|
+
: [...detectStackSharedPaths(mainWorktreePath), ...detectWorkspaceNodeModules(mainWorktreePath)];
|
|
260
363
|
const extra = options.sharedPaths ?? [];
|
|
261
364
|
const excluded = new Set(options.excludeShared ?? []);
|
|
262
365
|
const sharedPaths = [...new Set([...detected, ...extra])].filter((p) => !excluded.has(p));
|
|
@@ -267,6 +370,14 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
|
267
370
|
// Symlinking .brainclaw/ causes hooks and session_start to trigger on the
|
|
268
371
|
// shared store, creating session conflicts and potentially blocking agents
|
|
269
372
|
// (especially Claude CLI which auto-detects .brainclaw/ presence).
|
|
373
|
+
// pln#479: opt-in per-worktree typecheck gate. Off by default — on large
|
|
374
|
+
// monorepos `tsc` is slow and a per-commit gate would be punishing — enable
|
|
375
|
+
// with BRAINCLAW_WORKTREE_TYPECHECK_GATE=1. Isolated to this worktree, so the
|
|
376
|
+
// main repo's commits are never affected.
|
|
377
|
+
let typecheckGate;
|
|
378
|
+
if (process.env.BRAINCLAW_WORKTREE_TYPECHECK_GATE === '1') {
|
|
379
|
+
typecheckGate = installWorktreeTypecheckGate(mainWorktreePath, targetPath);
|
|
380
|
+
}
|
|
270
381
|
const mainGitignorePath = path.join(mainWorktreePath, '.gitignore');
|
|
271
382
|
const targetGitignorePath = path.join(targetPath, '.gitignore');
|
|
272
383
|
if (fs.existsSync(mainGitignorePath)) {
|
|
@@ -282,10 +393,87 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
|
|
|
282
393
|
base_ref: baseRef,
|
|
283
394
|
reset_existing_branch: options.resetExistingBranch === true,
|
|
284
395
|
git_advice: 'git add ONLY specific files, NEVER git add -A.',
|
|
396
|
+
// pln#523: surface any shared-path link failures (e.g. node_modules junction
|
|
397
|
+
// that could not be created) so the worker / supervisor can see why a build
|
|
398
|
+
// might fail, instead of an invisible degradation.
|
|
399
|
+
...(symlinkWarnings.length > 0 ? { symlink_warnings: symlinkWarnings } : {}),
|
|
400
|
+
// pln#479: record whether the per-worktree typecheck gate is active.
|
|
401
|
+
...(typecheckGate?.installed ? { typecheck_gate: true } : {}),
|
|
285
402
|
};
|
|
286
403
|
fs.writeFileSync(path.join(targetPath, '.brainclaw-worktree.json'), JSON.stringify(meta, null, 2));
|
|
287
404
|
return targetPath;
|
|
288
405
|
}
|
|
406
|
+
/** Directory (relative to a worktree root) holding the per-worktree git hooks. */
|
|
407
|
+
export const WORKTREE_HOOKS_DIRNAME = '.brainclaw-hooks';
|
|
408
|
+
/**
|
|
409
|
+
* The pre-commit gate body (pln#479). Runs via `node -e` — same SIGPIPE-avoiding
|
|
410
|
+
* pattern as install-hooks.ts. Git runs hooks with cwd = worktree root, so the
|
|
411
|
+
* relative paths resolve there. `node` and the tsc entry point are invoked with
|
|
412
|
+
* forward-slash relative paths to stay cross-platform (no quoting/backslash
|
|
413
|
+
* pitfalls). If typescript is absent the gate degrades to a warning rather than
|
|
414
|
+
* blocking — a tooling gap must not trap a worker.
|
|
415
|
+
*/
|
|
416
|
+
export function buildTypecheckPreCommitScript() {
|
|
417
|
+
return `#!/bin/sh
|
|
418
|
+
# brainclaw worktree typecheck gate (pln#479) — do not edit manually.
|
|
419
|
+
# Blocks the commit when 'tsc --noEmit' fails. Bypass: git commit --no-verify.
|
|
420
|
+
exec node -e "
|
|
421
|
+
const fs = require('fs');
|
|
422
|
+
const { execSync } = require('child_process');
|
|
423
|
+
if (!fs.existsSync('tsconfig.json')) process.exit(0);
|
|
424
|
+
if (!fs.existsSync('node_modules/typescript/bin/tsc')) {
|
|
425
|
+
process.stderr.write('\\\\n[brainclaw] typecheck gate: typescript not found in worktree node_modules — skipping (commit allowed).\\\\n');
|
|
426
|
+
process.exit(0);
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
execSync('node node_modules/typescript/bin/tsc --noEmit', { stdio: 'inherit' });
|
|
430
|
+
} catch (e) {
|
|
431
|
+
process.stderr.write('\\\\n[brainclaw] commit blocked: tsc --noEmit reported type errors (above). Fix them, or bypass with: git commit --no-verify\\\\n\\\\n');
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
" 2>&1 || exit $?
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* pln#479 — install an ISOLATED pre-commit gate in a dispatched worktree that
|
|
439
|
+
* blocks a commit when `tsc --noEmit` fails, so a worker cannot land code that
|
|
440
|
+
* breaks the type-check (observed: workers committing strict-mode-broken TS that
|
|
441
|
+
* only blew up at merge/build time, pln#466).
|
|
442
|
+
*
|
|
443
|
+
* Isolation is the crux: git hooks are shared across all worktrees of a repo by
|
|
444
|
+
* default, so we must NOT write into the common hooks dir — that would impose
|
|
445
|
+
* tsc on the human's main-repo commits too. Instead we point THIS worktree's
|
|
446
|
+
* `core.hooksPath` at a worktree-local dir via the `--worktree` config scope
|
|
447
|
+
* (enabling `extensions.worktreeConfig`), which leaves the main repo's hook
|
|
448
|
+
* setup completely untouched and is torn down with the worktree.
|
|
449
|
+
*
|
|
450
|
+
* No-ops when the worktree has no `tsconfig.json`. Depends on pln#523 having
|
|
451
|
+
* linked `node_modules` so `tsc` resolves.
|
|
452
|
+
*/
|
|
453
|
+
export function installWorktreeTypecheckGate(mainWorktreePath, worktreePath) {
|
|
454
|
+
if (!fs.existsSync(path.join(worktreePath, 'tsconfig.json'))) {
|
|
455
|
+
return { installed: false, reason: 'no tsconfig.json — not a TypeScript worktree' };
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
const hooksDir = path.join(worktreePath, WORKTREE_HOOKS_DIRNAME);
|
|
459
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
460
|
+
fs.writeFileSync(path.join(hooksDir, 'pre-commit'), buildTypecheckPreCommitScript(), {
|
|
461
|
+
encoding: 'utf-8',
|
|
462
|
+
mode: 0o755,
|
|
463
|
+
});
|
|
464
|
+
// Enable per-worktree config on the repo (idempotent, additive) so the
|
|
465
|
+
// hooksPath override stays scoped to THIS worktree only.
|
|
466
|
+
runGit(['config', 'extensions.worktreeConfig', 'true'], mainWorktreePath);
|
|
467
|
+
const set = runGit(['config', '--worktree', 'core.hooksPath', gitPath(hooksDir)], worktreePath);
|
|
468
|
+
if (!set.ok) {
|
|
469
|
+
return { installed: false, reason: `git config --worktree core.hooksPath failed: ${set.stderr.trim()}` };
|
|
470
|
+
}
|
|
471
|
+
return { installed: true };
|
|
472
|
+
}
|
|
473
|
+
catch (err) {
|
|
474
|
+
return { installed: false, reason: err instanceof Error ? err.message : String(err) };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
289
477
|
/**
|
|
290
478
|
* Lists all git worktrees for the given repo and enriches them with
|
|
291
479
|
* brainclaw metadata if available.
|
|
@@ -510,6 +698,30 @@ export function removeWorktree(mainWorktreePath, worktreePath, options = {}) {
|
|
|
510
698
|
export function pruneWorktrees(mainWorktreePath) {
|
|
511
699
|
runGit(['worktree', 'prune'], mainWorktreePath);
|
|
512
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Files brainclaw itself writes into a worktree AT BIRTH — they are never user
|
|
703
|
+
* work and must not count as "uncommitted changes" when deciding whether a
|
|
704
|
+
* merged worktree can be GC'd:
|
|
705
|
+
* - `.gitignore` — copied from the main repo by createWorktree; on
|
|
706
|
+
* Windows autocrlf flags it as ` M .gitignore`,
|
|
707
|
+
* which previously made EVERY brainclaw worktree
|
|
708
|
+
* look dirty and skipped the clean forever.
|
|
709
|
+
* - `.brainclaw-worktree.json` — the sidecar metadata createWorktree writes.
|
|
710
|
+
* Combined with isSystemDirtyPath (.brainclaw/, .git/, agent config dirs).
|
|
711
|
+
*/
|
|
712
|
+
const WORKTREE_BIRTH_NOISE = new Set(['.gitignore', '.brainclaw-worktree.json']);
|
|
713
|
+
/**
|
|
714
|
+
* True when a worktree's `git status --porcelain=v1 -z` output contains ONLY
|
|
715
|
+
* brainclaw birth artifacts / coordination-store noise — i.e. no real user work
|
|
716
|
+
* would be lost by removing it. Empty output (fully clean) also returns true.
|
|
717
|
+
*/
|
|
718
|
+
export function worktreeHasOnlyBirthNoise(statusZStdout) {
|
|
719
|
+
const paths = parsePorcelainZ(statusZStdout);
|
|
720
|
+
return paths.every((p) => {
|
|
721
|
+
const norm = p.replace(/\\/g, '/');
|
|
722
|
+
return WORKTREE_BIRTH_NOISE.has(norm) || isSystemDirtyPath(norm);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
513
725
|
/**
|
|
514
726
|
* Removes worktrees whose branch has been fully merged into the current branch
|
|
515
727
|
* (typically master/main after a merge). Also removes brainclaw-managed
|
|
@@ -539,10 +751,13 @@ export function cleanMergedWorktrees(mainWorktreePath, options = {}) {
|
|
|
539
751
|
if (!isMerged) {
|
|
540
752
|
continue;
|
|
541
753
|
}
|
|
542
|
-
// Check for uncommitted changes
|
|
754
|
+
// Check for uncommitted changes — but ignore brainclaw birth-noise
|
|
755
|
+
// (.gitignore autocrlf, the sidecar, coordination store). Without this,
|
|
756
|
+
// every merged brainclaw worktree looked dirty and was skipped forever,
|
|
757
|
+
// so `worktree clean` removed nothing and worktrees accumulated (pln#525).
|
|
543
758
|
if (!options.force) {
|
|
544
|
-
const status = runGit(['status', '--porcelain'], wt.path);
|
|
545
|
-
if (status.ok && status.stdout
|
|
759
|
+
const status = runGit(['status', '--porcelain=v1', '-z', '--untracked-files=normal'], wt.path);
|
|
760
|
+
if (status.ok && !worktreeHasOnlyBirthNoise(status.stdout)) {
|
|
546
761
|
result.skipped.push({ path: wt.path, reason: 'uncommitted changes' });
|
|
547
762
|
continue;
|
|
548
763
|
}
|
|
@@ -552,7 +767,12 @@ export function cleanMergedWorktrees(mainWorktreePath, options = {}) {
|
|
|
552
767
|
continue;
|
|
553
768
|
}
|
|
554
769
|
try {
|
|
555
|
-
|
|
770
|
+
// Reaching here means EITHER options.force OR the birth-noise gate above
|
|
771
|
+
// passed (no real user work). In both cases git's own `worktree remove`
|
|
772
|
+
// must be forced: otherwise it refuses on the untracked sidecar /
|
|
773
|
+
// autocrlf .gitignore that we already classified as discardable noise
|
|
774
|
+
// (pln#525 — the refusal that left every merged worktree un-GC-able).
|
|
775
|
+
removeWorktree(mainWorktreePath, wt.path, { force: true });
|
|
556
776
|
result.removed.push(wt.path);
|
|
557
777
|
}
|
|
558
778
|
catch {
|
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.7.
|
|
2
|
+
// Source: brainclaw v1.7.4 on 2026-06-08T21:59:54.110Z
|
|
3
3
|
export const FACTS = {
|
|
4
|
-
"version": "1.7.
|
|
5
|
-
"generated_at": "2026-06-
|
|
4
|
+
"version": "1.7.4",
|
|
5
|
+
"generated_at": "2026-06-08T21:59:54.110Z",
|
|
6
6
|
"tools": {
|
|
7
7
|
"count": 62,
|
|
8
8
|
"published_count": 61,
|
package/dist/facts.json
CHANGED