brainclaw 1.10.1 → 1.10.2

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.
Binary file
@@ -18,6 +18,13 @@ function gitPath(p) {
18
18
  *
19
19
  * Rules covered: no leading dots/dashes, no trailing dots, no `..`, no
20
20
  * `@{`, no control/space/`~^:?*[\\` characters, no trailing `.lock`.
21
+ *
22
+ * Order matters: the 48-char length cap is applied BEFORE the trailing-dot/dash
23
+ * and `.lock` strips — never after. Dogfood 1.10.1: a multi-file scope ending in
24
+ * `…IntegrationHubPage.astro` sanitized fine, but the final `.slice(0, 48)` cut
25
+ * landed on the dot before `astro`, yielding `…IntegrationHubPage.` — a trailing
26
+ * dot git rejects (`fatal: not a valid branch name`). Truncating first, then
27
+ * stripping, guarantees the cap can never re-introduce an invalid ref.
21
28
  */
22
29
  export function sanitizeBranchComponent(raw, fallback = 'scope') {
23
30
  let slug = raw
@@ -27,12 +34,13 @@ export function sanitizeBranchComponent(raw, fallback = 'scope') {
27
34
  .replace(/[^a-zA-Z0-9._-]/g, '-') // conservative whitelist for the rest
28
35
  .replace(/-+/g, '-') // collapse dashes
29
36
  .replace(/^[.-]+/, '') // no leading dot/dash
30
- .replace(/[.-]+$/, ''); // no trailing dot/dash
37
+ .slice(0, 48) // length cap BEFORE the trailing strips
38
+ .replace(/[.-]+$/, ''); // no trailing dot/dash (cut may have made one)
31
39
  if (/\.lock$/i.test(slug))
32
- slug = slug.slice(0, -'.lock'.length);
40
+ slug = slug.slice(0, -'.lock'.length).replace(/[.-]+$/, '');
33
41
  if (!slug)
34
42
  slug = fallback;
35
- return slug.slice(0, 48);
43
+ return slug;
36
44
  }
37
45
  /**
38
46
  * Stack marker → shared directories mapping.
@@ -181,8 +189,40 @@ export function resolveWorktreePath(mainWorktreePath, branchName) {
181
189
  const slug = branchName.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 64);
182
190
  return path.join(worktreesBaseDir(mainWorktreePath), slug);
183
191
  }
184
- function runGit(args, cwd) {
185
- const result = spawnSync('git', args, { cwd, encoding: 'utf-8', timeout: 15000 });
192
+ /**
193
+ * Default timeout for quick git metadata queries (rev-parse, status, branch…).
194
+ * `git worktree add` is the exception — it materialises the entire working tree
195
+ * and on a large repo / Windows (Defender) easily exceeds this, so it passes an
196
+ * explicit, much larger timeout (see resolveWorktreeAddTimeoutMs).
197
+ *
198
+ * Dogfood 1.10.1: a 662-file site checkout was SIGTERM-killed at ~94% by this
199
+ * flat 15s cap, surfacing as a misleading "git worktree add failed: …Updating
200
+ * files: 94%" — the branch name was fine; the checkout simply ran out of time.
201
+ */
202
+ const GIT_QUERY_TIMEOUT_MS = 15000;
203
+ /**
204
+ * Timeout for `git worktree add` (full working-tree checkout). Defaults to 120s;
205
+ * override with BRAINCLAW_WORKTREE_ADD_TIMEOUT_MS (milliseconds) for very large
206
+ * repos or slow filesystems.
207
+ */
208
+ export function resolveWorktreeAddTimeoutMs() {
209
+ const raw = process.env.BRAINCLAW_WORKTREE_ADD_TIMEOUT_MS;
210
+ const n = raw ? Number.parseInt(raw, 10) : NaN;
211
+ return Number.isFinite(n) && n > 0 ? n : 120_000;
212
+ }
213
+ function runGit(args, cwd, timeoutMs = GIT_QUERY_TIMEOUT_MS) {
214
+ const result = spawnSync('git', args, { cwd, encoding: 'utf-8', timeout: timeoutMs });
215
+ // spawnSync kills on timeout (SIGTERM) and sets error.code=ETIMEDOUT; the raw
216
+ // stderr is then just partial progress ("Updating files: …%"), which reads as
217
+ // a cryptic failure. Surface the real cause — the timeout — instead.
218
+ if (result.error?.code === 'ETIMEDOUT') {
219
+ const hint = args[0] === 'worktree' ? ' (large checkout? raise BRAINCLAW_WORKTREE_ADD_TIMEOUT_MS)' : '';
220
+ return {
221
+ ok: false,
222
+ stdout: result.stdout ?? '',
223
+ stderr: `git ${args[0]} timed out after ${timeoutMs}ms and was killed${hint}.`,
224
+ };
225
+ }
186
226
  return {
187
227
  ok: result.status === 0,
188
228
  stdout: result.stdout ?? '',
@@ -496,7 +536,7 @@ export function createWorktree(mainWorktreePath, branchName, options = {}) {
496
536
  const worktreeArgs = branchExists
497
537
  ? ['worktree', 'add', gitTargetPath, branchName]
498
538
  : ['worktree', 'add', '-b', branchName, gitTargetPath, baseRef];
499
- const result = runGit(worktreeArgs, mainWorktreePath);
539
+ const result = runGit(worktreeArgs, mainWorktreePath, resolveWorktreeAddTimeoutMs());
500
540
  if (!result.ok) {
501
541
  throw new Error(`git worktree add failed: ${result.stderr.trim()}`);
502
542
  }
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.10.1 on 2026-06-21T19:26:16.599Z
2
+ // Source: brainclaw v1.10.2 on 2026-06-21T23:44:46.631Z
3
3
  export const FACTS = {
4
- "version": "1.10.1",
5
- "generated_at": "2026-06-21T19:26:16.599Z",
4
+ "version": "1.10.2",
5
+ "generated_at": "2026-06-21T23:44:46.631Z",
6
6
  "tools": {
7
7
  "count": 66,
8
8
  "published_count": 65,
package/dist/facts.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.10.1",
3
- "generated_at": "2026-06-21T19:26:16.599Z",
2
+ "version": "1.10.2",
3
+ "generated_at": "2026-06-21T23:44:46.631Z",
4
4
  "tools": {
5
5
  "count": 66,
6
6
  "published_count": 65,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainclaw",
3
- "version": "1.10.1",
3
+ "version": "1.10.2",
4
4
  "description": "Shared project memory for humans and coding agents.",
5
5
  "type": "module",
6
6
  "repository": {