@braingrid/cli 0.2.34 → 0.2.36

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/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.36] - 2026-02-14
11
+
12
+ ### Added
13
+
14
+ - **Add `.braingrid/temp/` to `.gitignore` during init**
15
+ - `braingrid init` now automatically appends `.braingrid/temp/` to the project's `.gitignore`
16
+ - Prevents session-specific temp files (e.g., acceptance criteria checklists) from being committed
17
+ - Skips if the entry already exists; creates `.gitignore` if none exists
18
+
19
+ ### Fixed
20
+
21
+ - **Always offer Claude Code setup updates during init**
22
+ - `braingrid init` now prompts to update Claude Code integration even when it's already installed
23
+ - Ensures users get the latest slash commands, skills, and status line configuration
24
+
25
+ ## [0.2.35] - 2026-02-14
26
+
27
+ ### Added
28
+
29
+ - **Acceptance criteria extraction in `/build` skill**
30
+ - `/build` now parses the `## Acceptance Criteria` section from requirement content
31
+ - Extracts all criteria into a flat `[]` checklist at `.braingrid/temp/REQ-{id}-acceptance-criteria.md`
32
+ - Handles both flat and sub-sectioned (`###` headings) criteria formats
33
+ - Strips markdown bold formatting and collapses multi-line criteria
34
+ - Added `Write` to allowed tools in build command
35
+
36
+ ### Fixed
37
+
38
+ - **Add `create-braingrid-task.sh` to Claude Code sync script**
39
+ - The setup handler was fetching this hook from GitHub but the sync script never pushed it
40
+ - Without this fix, `braingrid setup claude-code` would fail to install the TaskCreate hook
41
+
10
42
  ## [0.2.34] - 2026-02-05
11
43
 
12
44
  ### Fixed
package/dist/cli.js CHANGED
@@ -222,7 +222,7 @@ async function axiosWithRetry(config2, options) {
222
222
 
223
223
  // src/build-config.ts
224
224
  var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
225
- var CLI_VERSION = true ? "0.2.34" : "0.0.0-test";
225
+ var CLI_VERSION = true ? "0.2.36" : "0.0.0-test";
226
226
  var PRODUCTION_CONFIG = {
227
227
  apiUrl: "https://app.braingrid.ai",
228
228
  workosAuthUrl: "https://auth.braingrid.ai",
@@ -2198,7 +2198,8 @@ async function handleCompletion(shellArg, opts) {
2198
2198
  }
2199
2199
 
2200
2200
  // src/handlers/init.handlers.ts
2201
- import { access as access3 } from "fs/promises";
2201
+ import { access as access3, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
2202
+ import path6 from "path";
2202
2203
  import { confirm as confirm2, input, select as select3 } from "@inquirer/prompts";
2203
2204
  import chalk10 from "chalk";
2204
2205
 
@@ -2543,17 +2544,17 @@ function parseGitHubError(error) {
2543
2544
  }
2544
2545
  return errorMessage;
2545
2546
  }
2546
- async function fetchFileFromGitHub(path6) {
2547
+ async function fetchFileFromGitHub(path7) {
2547
2548
  return withRetry(async () => {
2548
2549
  try {
2549
- const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
2550
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path7}`;
2550
2551
  const { stdout } = await execAsync(command);
2551
2552
  const response = JSON.parse(stdout);
2552
2553
  if (response.type !== "file") {
2553
- throw new Error(`Path ${path6} is not a file`);
2554
+ throw new Error(`Path ${path7} is not a file`);
2554
2555
  }
2555
2556
  if (!response.content || !response.encoding) {
2556
- throw new Error(`No content found for file ${path6}`);
2557
+ throw new Error(`No content found for file ${path7}`);
2557
2558
  }
2558
2559
  if (response.encoding !== "base64") {
2559
2560
  throw new Error(`Unexpected encoding: ${response.encoding}`);
@@ -2562,18 +2563,18 @@ async function fetchFileFromGitHub(path6) {
2562
2563
  return content;
2563
2564
  } catch (error) {
2564
2565
  const parsedError = parseGitHubError(error);
2565
- throw new Error(`Failed to fetch file ${path6}: ${parsedError}`);
2566
+ throw new Error(`Failed to fetch file ${path7}: ${parsedError}`);
2566
2567
  }
2567
2568
  });
2568
2569
  }
2569
- async function listGitHubDirectory(path6) {
2570
+ async function listGitHubDirectory(path7) {
2570
2571
  return withRetry(async () => {
2571
2572
  try {
2572
- const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
2573
+ const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path7}`;
2573
2574
  const { stdout } = await execAsync(command);
2574
2575
  const response = JSON.parse(stdout);
2575
2576
  if (!Array.isArray(response)) {
2576
- throw new Error(`Path ${path6} is not a directory`);
2577
+ throw new Error(`Path ${path7} is not a directory`);
2577
2578
  }
2578
2579
  return response.map((item) => ({
2579
2580
  name: item.name,
@@ -2582,7 +2583,7 @@ async function listGitHubDirectory(path6) {
2582
2583
  }));
2583
2584
  } catch (error) {
2584
2585
  const parsedError = parseGitHubError(error);
2585
- throw new Error(`Failed to list directory ${path6}: ${parsedError}`);
2586
+ throw new Error(`Failed to list directory ${path7}: ${parsedError}`);
2586
2587
  }
2587
2588
  });
2588
2589
  }
@@ -2679,7 +2680,7 @@ async function copyBraingridReadme(targetPath = ".braingrid/README.md") {
2679
2680
  return false;
2680
2681
  }
2681
2682
  }
2682
- async function updateClaudeSettings(settingsPath = ".claude/settings.json", scriptPath2 = ".claude/statusline.sh", hookScriptPath = ".claude/hooks/sync-braingrid-task.sh") {
2683
+ async function updateClaudeSettings(settingsPath = ".claude/settings.json", scriptPath2 = ".claude/statusline.sh", hookScriptPath = ".claude/hooks/sync-braingrid-task.sh", createHookScriptPath = ".claude/hooks/create-braingrid-task.sh") {
2683
2684
  try {
2684
2685
  let settings = {};
2685
2686
  try {
@@ -2716,8 +2717,58 @@ async function updateClaudeSettings(settingsPath = ".claude/settings.json", scri
2716
2717
  } else {
2717
2718
  mergedPostToolUse = [...existingPostToolUse, ourHookEntry];
2718
2719
  }
2720
+ const ourCreateHookEntry = {
2721
+ matcher: "TaskCreate",
2722
+ hooks: [
2723
+ {
2724
+ type: "command",
2725
+ command: createHookScriptPath,
2726
+ timeout: 1e4
2727
+ }
2728
+ ]
2729
+ };
2730
+ const taskCreatePostIdx = mergedPostToolUse.findIndex((e) => e.matcher === "TaskCreate");
2731
+ if (taskCreatePostIdx >= 0) {
2732
+ mergedPostToolUse[taskCreatePostIdx] = ourCreateHookEntry;
2733
+ } else {
2734
+ mergedPostToolUse = [...mergedPostToolUse, ourCreateHookEntry];
2735
+ }
2736
+ const ourPreToolUseEntry = {
2737
+ matcher: "TaskCreate",
2738
+ hooks: [
2739
+ {
2740
+ type: "prompt",
2741
+ prompt: "Use sequential task numbering with conventional commit naming for task subjects. The subject MUST follow the format 'TASK N: type: description' where N is the next sequential number (1, 2, 3, ...) based on existing tasks in the current session. Valid types: feat, fix, docs, style, refactor, perf, test, chore. Optional scope in parentheses after type is allowed. Examples: 'TASK 1: feat: add user login', 'TASK 2: fix: resolve null pointer', 'TASK 3: feat(auth): add OAuth support'. Reject if the subject does not follow this convention. $ARGUMENTS"
2742
+ }
2743
+ ]
2744
+ };
2745
+ const existingPreToolUse = Array.isArray(existingHooks.PreToolUse) ? existingHooks.PreToolUse : [];
2746
+ const taskCreateIdx = existingPreToolUse.findIndex((e) => e.matcher === "TaskCreate");
2747
+ let mergedPreToolUse;
2748
+ if (taskCreateIdx >= 0) {
2749
+ mergedPreToolUse = [...existingPreToolUse];
2750
+ mergedPreToolUse[taskCreateIdx] = ourPreToolUseEntry;
2751
+ } else {
2752
+ mergedPreToolUse = [...existingPreToolUse, ourPreToolUseEntry];
2753
+ }
2754
+ const ourPreToolUseTaskUpdateEntry = {
2755
+ matcher: "TaskUpdate",
2756
+ hooks: [
2757
+ {
2758
+ type: "prompt",
2759
+ prompt: "Before completing a task, you MUST validate and commit. If status is being set to 'completed':\n\n1. Run the project's linter, test suite, and type checker (detect from project config \u2014 e.g. package.json, Makefile, pyproject.toml, Cargo.toml, etc.).\n2. If any check fails, DO NOT complete \u2014 fix first.\n3. Stage relevant files with git add (specific paths, not -A).\n4. Commit using the conventional commit part of the subject as the message (e.g. 'feat: add user login').\n5. Get the short hash: git rev-parse --short HEAD\n6. Update the task subject to: 'TASK N (HASH): type: description' (e.g. 'TASK 1 (abc1234): feat: add user login').\n7. Only then mark completed.\n\nFor other status changes, proceed normally.\n\n$ARGUMENTS"
2760
+ }
2761
+ ]
2762
+ };
2763
+ const taskUpdatePreIdx = mergedPreToolUse.findIndex((e) => e.matcher === "TaskUpdate");
2764
+ if (taskUpdatePreIdx >= 0) {
2765
+ mergedPreToolUse[taskUpdatePreIdx] = ourPreToolUseTaskUpdateEntry;
2766
+ } else {
2767
+ mergedPreToolUse = [...mergedPreToolUse, ourPreToolUseTaskUpdateEntry];
2768
+ }
2719
2769
  settings.hooks = {
2720
2770
  ...existingHooks,
2771
+ PreToolUse: mergedPreToolUse,
2721
2772
  PostToolUse: mergedPostToolUse
2722
2773
  };
2723
2774
  const parentDir = path2.dirname(settingsPath);
@@ -3359,88 +3410,132 @@ async function installFiles(operations, force) {
3359
3410
  return { installed, skipped, cancelled: false };
3360
3411
  }
3361
3412
  async function _handleSetup(config2, opts) {
3362
- try {
3363
- const prerequisiteError = await checkPrerequisites();
3364
- if (prerequisiteError) {
3365
- return prerequisiteError;
3366
- }
3367
- console.log(chalk8.bold(`\u{1F680} Setting up ${config2.name} integration...
3413
+ const prerequisiteError = await checkPrerequisites();
3414
+ if (prerequisiteError) {
3415
+ return prerequisiteError;
3416
+ }
3417
+ console.log(chalk8.bold(`\u{1F680} Setting up ${config2.name} integration...
3368
3418
  `));
3369
- const operations = await getFileList(config2.sourceDirs, config2.targetDirs);
3370
- const injectionFileExists = await fileExists(config2.injection.targetFile);
3371
- operations.push({
3372
- type: "inject",
3373
- sourcePath: config2.injection.sourceFile,
3374
- targetPath: config2.injection.targetFile,
3375
- exists: injectionFileExists
3376
- });
3377
- displayInstallationPlan(
3378
- operations.filter((op) => op.type === "copy"),
3379
- config2.injection.targetFile
3419
+ const operations = await getFileList(config2.sourceDirs, config2.targetDirs);
3420
+ const injectionFileExists = await fileExists(config2.injection.targetFile);
3421
+ operations.push({
3422
+ type: "inject",
3423
+ sourcePath: config2.injection.sourceFile,
3424
+ targetPath: config2.injection.targetFile,
3425
+ exists: injectionFileExists
3426
+ });
3427
+ displayInstallationPlan(
3428
+ operations.filter((op) => op.type === "copy"),
3429
+ config2.injection.targetFile
3430
+ );
3431
+ if (opts.dryRun) {
3432
+ return {
3433
+ success: true,
3434
+ message: chalk8.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk8.dim(`Would install ${operations.length} files.`)
3435
+ };
3436
+ }
3437
+ const copyOps = operations.filter((op) => op.type === "copy");
3438
+ const result = await installFiles(copyOps, opts.force || false);
3439
+ if (result.cancelled) {
3440
+ return {
3441
+ success: false,
3442
+ message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
3443
+ code: "CANCELLED"
3444
+ };
3445
+ }
3446
+ try {
3447
+ const content = await fetchFileFromGitHub(config2.injection.sourceFile);
3448
+ await injectContentIntoFile(config2.injection.targetFile, content);
3449
+ } catch (error) {
3450
+ console.error(
3451
+ chalk8.red(`Failed to inject content into ${config2.injection.targetFile}:`),
3452
+ error instanceof Error ? error.message : String(error)
3380
3453
  );
3381
- if (opts.dryRun) {
3382
- return {
3383
- success: true,
3384
- message: chalk8.green("\u2705 Dry-run complete. No files were modified.\n\n") + chalk8.dim(`Would install ${operations.length} files.`)
3385
- };
3454
+ }
3455
+ await copyBraingridReadme();
3456
+ return {
3457
+ success: true,
3458
+ data: {
3459
+ installed: result.installed,
3460
+ skipped: result.skipped
3386
3461
  }
3387
- const copyOps = operations.filter((op) => op.type === "copy");
3388
- const result = await installFiles(copyOps, opts.force || false);
3389
- if (result.cancelled) {
3390
- return {
3391
- success: false,
3392
- message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
3393
- code: "CANCELLED"
3394
- };
3462
+ };
3463
+ }
3464
+ function buildSuccessMessage(config2, installed, extras) {
3465
+ return chalk8.green(`\u2705 ${config2.name} integration installed successfully!
3466
+
3467
+ `) + chalk8.dim("Files installed:\n") + chalk8.dim(` Commands: ${installed} files
3468
+ `) + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3469
+
3470
+ `) + chalk8.dim("Next steps:\n") + chalk8.dim(" 1. Review the integration files\n") + chalk8.dim(` 2. Open ${config2.name}
3471
+ `) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl);
3472
+ }
3473
+ function isSetupResult(result) {
3474
+ return "data" in result && result.success === true && !("message" in result);
3475
+ }
3476
+ async function handleSetupClaudeCode(opts) {
3477
+ const config2 = {
3478
+ name: "Claude Code",
3479
+ sourceDirs: ["claude-code/commands", "claude-code/skills/braingrid-cli"],
3480
+ targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
3481
+ injection: {
3482
+ sourceFile: "claude-code/CLAUDE.md",
3483
+ targetFile: "CLAUDE.md"
3484
+ },
3485
+ docsUrl: "https://docs.braingrid.ai/claude-code"
3486
+ };
3487
+ try {
3488
+ const setupResult = await _handleSetup(config2, opts);
3489
+ if (!isSetupResult(setupResult)) {
3490
+ return setupResult;
3395
3491
  }
3492
+ const { installed } = setupResult.data;
3493
+ let statusLineInstalled = false;
3396
3494
  try {
3397
- const content = await fetchFileFromGitHub(config2.injection.sourceFile);
3398
- await injectContentIntoFile(config2.injection.targetFile, content);
3495
+ const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
3496
+ await installStatusLineScript(scriptContent);
3497
+ statusLineInstalled = true;
3399
3498
  } catch (error) {
3400
3499
  console.error(
3401
- chalk8.red(`Failed to inject content into ${config2.injection.targetFile}:`),
3500
+ chalk8.yellow("\u26A0\uFE0F Failed to install status line script:"),
3402
3501
  error instanceof Error ? error.message : String(error)
3403
3502
  );
3404
3503
  }
3405
- let statusLineInstalled = false;
3406
- if (config2.name === "Claude Code") {
3407
- try {
3408
- const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
3409
- await installStatusLineScript(scriptContent);
3410
- await updateClaudeSettings();
3411
- statusLineInstalled = true;
3412
- } catch (error) {
3413
- console.error(
3414
- chalk8.yellow("\u26A0\uFE0F Failed to install status line script:"),
3415
- error instanceof Error ? error.message : String(error)
3416
- );
3417
- }
3504
+ try {
3505
+ await updateClaudeSettings();
3506
+ } catch (error) {
3507
+ console.error(
3508
+ chalk8.yellow("\u26A0\uFE0F Failed to update Claude settings:"),
3509
+ error instanceof Error ? error.message : String(error)
3510
+ );
3418
3511
  }
3419
- let hookInstalled = false;
3420
- if (config2.name === "Claude Code") {
3421
- try {
3422
- const hookContent = await fetchFileFromGitHub("claude-code/hooks/sync-braingrid-task.sh");
3423
- await installHookScript(hookContent);
3424
- hookInstalled = true;
3425
- } catch (error) {
3426
- console.error(
3427
- chalk8.yellow("\u26A0\uFE0F Failed to install hook script:"),
3428
- error instanceof Error ? error.message : String(error)
3429
- );
3430
- }
3512
+ let syncHookInstalled = false;
3513
+ try {
3514
+ const syncContent = await fetchFileFromGitHub("claude-code/hooks/sync-braingrid-task.sh");
3515
+ await installHookScript(syncContent);
3516
+ syncHookInstalled = true;
3517
+ } catch (error) {
3518
+ console.error(
3519
+ chalk8.yellow("\u26A0\uFE0F Failed to install sync hook:"),
3520
+ error instanceof Error ? error.message : String(error)
3521
+ );
3522
+ }
3523
+ let createHookInstalled = false;
3524
+ try {
3525
+ const createContent = await fetchFileFromGitHub("claude-code/hooks/create-braingrid-task.sh");
3526
+ await installHookScript(createContent, ".claude/hooks/create-braingrid-task.sh");
3527
+ createHookInstalled = true;
3528
+ } catch (error) {
3529
+ console.error(
3530
+ chalk8.yellow("\u26A0\uFE0F Failed to install create hook:"),
3531
+ error instanceof Error ? error.message : String(error)
3532
+ );
3431
3533
  }
3432
- await copyBraingridReadme();
3433
3534
  const statusLineMessage = statusLineInstalled ? chalk8.dim(" Status line: .claude/statusline.sh\n") : "";
3434
- const hookMessage = hookInstalled ? chalk8.dim(" Hook script: .claude/hooks/sync-braingrid-task.sh\n") : "";
3535
+ const hooksMessage = (syncHookInstalled ? chalk8.dim(" Hook script: .claude/hooks/sync-braingrid-task.sh\n") : "") + (createHookInstalled ? chalk8.dim(" Hook script: .claude/hooks/create-braingrid-task.sh\n") : "");
3435
3536
  return {
3436
3537
  success: true,
3437
- message: chalk8.green(`\u2705 ${config2.name} integration installed successfully!
3438
-
3439
- `) + chalk8.dim("Files installed:\n") + chalk8.dim(` Commands: ${result.installed} files
3440
- `) + statusLineMessage + hookMessage + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3441
-
3442
- `) + chalk8.dim("Next steps:\n") + chalk8.dim(" 1. Review the integration files\n") + chalk8.dim(` 2. Open ${config2.name}
3443
- `) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl)
3538
+ message: buildSuccessMessage(config2, installed, statusLineMessage + hooksMessage)
3444
3539
  };
3445
3540
  } catch (error) {
3446
3541
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3450,19 +3545,6 @@ async function _handleSetup(config2, opts) {
3450
3545
  };
3451
3546
  }
3452
3547
  }
3453
- async function handleSetupClaudeCode(opts) {
3454
- const config2 = {
3455
- name: "Claude Code",
3456
- sourceDirs: ["claude-code/commands", "claude-code/skills/braingrid-cli"],
3457
- targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
3458
- injection: {
3459
- sourceFile: "claude-code/CLAUDE.md",
3460
- targetFile: "CLAUDE.md"
3461
- },
3462
- docsUrl: "https://docs.braingrid.ai/claude-code"
3463
- };
3464
- return _handleSetup(config2, opts);
3465
- }
3466
3548
  async function handleSetupCursor(opts) {
3467
3549
  const config2 = {
3468
3550
  name: "Cursor",
@@ -3474,7 +3556,23 @@ async function handleSetupCursor(opts) {
3474
3556
  },
3475
3557
  docsUrl: "https://docs.braingrid.ai/cursor"
3476
3558
  };
3477
- return _handleSetup(config2, opts);
3559
+ try {
3560
+ const setupResult = await _handleSetup(config2, opts);
3561
+ if (!isSetupResult(setupResult)) {
3562
+ return setupResult;
3563
+ }
3564
+ const { installed } = setupResult.data;
3565
+ return {
3566
+ success: true,
3567
+ message: buildSuccessMessage(config2, installed, "")
3568
+ };
3569
+ } catch (error) {
3570
+ const errorMessage = error instanceof Error ? error.message : String(error);
3571
+ return {
3572
+ success: false,
3573
+ message: chalk8.red(`\u274C Setup failed: ${errorMessage}`)
3574
+ };
3575
+ }
3478
3576
  }
3479
3577
 
3480
3578
  // src/handlers/update.handlers.ts
@@ -3700,6 +3798,26 @@ async function fileExists2(filePath) {
3700
3798
  return false;
3701
3799
  }
3702
3800
  }
3801
+ async function addBraingridTempToGitignore() {
3802
+ const gitRoot = await getGitRoot();
3803
+ if (!gitRoot) return;
3804
+ const gitignorePath = path6.join(gitRoot, ".gitignore");
3805
+ const entry = ".braingrid/temp/";
3806
+ let content = "";
3807
+ try {
3808
+ content = await readFile2(gitignorePath, "utf8");
3809
+ } catch {
3810
+ }
3811
+ const lines = content.split("\n");
3812
+ if (lines.some((line) => line.trim() === entry || line.trim() === ".braingrid/temp")) {
3813
+ return;
3814
+ }
3815
+ const addition = content.endsWith("\n") || content === "" ? "" : "\n";
3816
+ const newContent = `${content}${addition}# BrainGrid temp files
3817
+ ${entry}
3818
+ `;
3819
+ await writeFile2(gitignorePath, newContent, "utf8");
3820
+ }
3703
3821
  function getServices() {
3704
3822
  const config2 = getConfig();
3705
3823
  const auth = new BraingridAuth(config2.apiUrl);
@@ -4227,6 +4345,7 @@ async function handleInit(opts) {
4227
4345
  created_at: project2.created_at
4228
4346
  };
4229
4347
  await saveProjectConfig(localConfig);
4348
+ await addBraingridTempToGitignore();
4230
4349
  await copyBraingridReadme();
4231
4350
  console.log(
4232
4351
  chalk10.green("\u2705 Repository initialized successfully!\n\n") + chalk10.dim("Project: ") + chalk10.cyan(project2.name) + chalk10.dim(` (${project2.short_id})`) + "\n" + chalk10.dim("Config: ") + chalk10.gray(".braingrid/project.json") + "\n"
@@ -4234,62 +4353,62 @@ async function handleInit(opts) {
4234
4353
  const installedIDEs = await detectInstalledIDEs();
4235
4354
  if (installedIDEs.claudeCode) {
4236
4355
  const claudeSetupExists = await fileExists2(".claude/commands/specify.md");
4237
- if (!claudeSetupExists) {
4356
+ console.log("");
4357
+ const setupClaude = await confirm2({
4358
+ message: claudeSetupExists ? "Claude Code detected. Update BrainGrid integration?" : "Claude Code detected. Install BrainGrid integration? (slash commands, skills, status line)",
4359
+ default: true
4360
+ });
4361
+ if (setupClaude) {
4238
4362
  console.log("");
4239
- const setupClaude = await confirm2({
4240
- message: "Claude Code detected. Install BrainGrid integration? (slash commands, skills, status line)",
4241
- default: true
4242
- });
4243
- if (setupClaude) {
4244
- console.log("");
4245
- try {
4246
- const result = await handleSetupClaudeCode({ force: false });
4247
- if (result.success) {
4248
- console.log(result.message);
4249
- } else {
4250
- console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup was not completed."));
4251
- console.log(
4252
- chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4253
- );
4254
- }
4255
- } catch {
4256
- console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup encountered an error."));
4363
+ try {
4364
+ const result = await handleSetupClaudeCode({
4365
+ force: claudeSetupExists
4366
+ });
4367
+ if (result.success) {
4368
+ console.log(result.message);
4369
+ } else {
4370
+ console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup was not completed."));
4257
4371
  console.log(
4258
4372
  chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4259
4373
  );
4260
4374
  }
4261
- console.log("");
4375
+ } catch {
4376
+ console.log(chalk10.yellow("\u26A0\uFE0F Claude Code setup encountered an error."));
4377
+ console.log(
4378
+ chalk10.dim("You can run ") + chalk10.cyan("braingrid setup claude-code") + chalk10.dim(" later.")
4379
+ );
4262
4380
  }
4381
+ console.log("");
4263
4382
  }
4264
4383
  }
4265
4384
  if (installedIDEs.cursor) {
4266
4385
  const cursorSetupExists = await fileExists2(".cursor/commands/specify.md");
4267
- if (!cursorSetupExists) {
4386
+ console.log("");
4387
+ const setupCursor = await confirm2({
4388
+ message: cursorSetupExists ? "Cursor detected. Update BrainGrid integration?" : "Cursor detected. Install BrainGrid integration? (slash commands, rules, context)",
4389
+ default: true
4390
+ });
4391
+ if (setupCursor) {
4268
4392
  console.log("");
4269
- const setupCursor = await confirm2({
4270
- message: "Cursor detected. Install BrainGrid integration? (slash commands, rules, context)",
4271
- default: true
4272
- });
4273
- if (setupCursor) {
4274
- console.log("");
4275
- try {
4276
- const result = await handleSetupCursor({ force: false });
4277
- if (result.success) {
4278
- console.log(result.message);
4279
- } else {
4280
- console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup was not completed."));
4281
- console.log(
4282
- chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4283
- );
4284
- }
4285
- } catch {
4286
- console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup encountered an error."));
4393
+ try {
4394
+ const result = await handleSetupCursor({
4395
+ force: cursorSetupExists
4396
+ });
4397
+ if (result.success) {
4398
+ console.log(result.message);
4399
+ } else {
4400
+ console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup was not completed."));
4287
4401
  console.log(
4288
4402
  chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4289
4403
  );
4290
4404
  }
4291
- console.log("");
4405
+ } catch {
4406
+ console.log(chalk10.yellow("\u26A0\uFE0F Cursor setup encountered an error."));
4407
+ console.log(
4408
+ chalk10.dim("You can run ") + chalk10.cyan("braingrid setup cursor") + chalk10.dim(" later.")
4409
+ );
4292
4410
  }
4411
+ console.log("");
4293
4412
  }
4294
4413
  }
4295
4414
  return {