@braingrid/cli 0.2.36 → 0.2.38

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,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.38] - 2026-02-17
11
+
12
+ ### Added
13
+
14
+ - **`--content` option for `requirement update` command**
15
+ - Allows updating a requirement's content directly from the CLI with `--content`
16
+ - **Parallel mode with agent teams for `/build` command**
17
+ - `/build` now supports a parallel execution mode that spawns agent teams for concurrent task implementation
18
+ - **Verify acceptance criteria hook in Claude Code setup**
19
+ - `braingrid setup claude-code` now installs a hook that verifies acceptance criteria during builds
20
+ - **PostToolUse TaskUpdate prompt hook in Claude Code setup**
21
+ - Setup installs a prompt hook that updates task status on PostToolUse events
22
+
23
+ ### Changed
24
+
25
+ - **Synced README and Claude Code guide with CLI implementation**
26
+ - Documentation updated to reflect latest CLI features and usage
27
+
28
+ ## [0.2.37] - 2026-02-14
29
+
30
+ ### Added
31
+
32
+ - **Add `.braingrid/temp/` to `.gitignore` during update**
33
+ - `braingrid update` now ensures `.braingrid/temp/` is in `.gitignore` (previously only `init` did this)
34
+ - Runs in both the "already up-to-date" and "after successful update" code paths
35
+
36
+ ### Changed
37
+
38
+ - **Per-directory install counts in setup success message**
39
+ - Setup success message now shows "Commands: 4" and "Skills: 1" instead of "Commands: 6 files"
40
+ - Each skill directory counts as 1 skill regardless of supporting files inside it
41
+ - Cursor setup shows separate "Commands" and "Rules" counts
42
+
43
+ ### Refactored
44
+
45
+ - **Extract `addBraingridTempToGitignore` to shared utility**
46
+ - Moved from `init.handlers.ts` to `src/utils/gitignore.ts` for reuse across handlers
47
+
10
48
  ## [0.2.36] - 2026-02-14
11
49
 
12
50
  ### Added
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  <div align="center">
2
- <img src="https://www.braingrid.ai/logos/braingrid-symbol-800.png" width="100"/>
2
+ <img src="https://www.braingrid.ai/brand/symbol-lime-on-jungle.svg" width="80" height="80"/>
3
3
  <h1>BrainGrid</h1>
4
4
 
5
- <p>Prompt AI Coding Tools Like a Rockstar Developer</p>
6
- <h3>A CLI to turn messy thoughts into detailed specs and perfectly-prompted tasks to build well-structured, maintainable software with AI.</h3>
5
+ <p>The AI Product Planner</p>
6
+ <h3>BrainGrid is the AI Product Planner that helps you shape ideas, plan features,
7
+ and scope tasks that your AI coding tools can build right the first time.</h3>
7
8
 
8
9
  [![npm version](https://img.shields.io/npm/v/@braingrid/cli.svg?color=blue&logo=npm)](https://www.npmjs.com/package/@braingrid/cli)
9
10
  [![Downloads](https://img.shields.io/npm/dm/@braingrid/cli.svg?color=green)](https://www.npmjs.com/package/@braingrid/cli)
@@ -207,18 +208,24 @@ braingrid specify -p PROJ-123 --prompt "Implement real-time notifications"
207
208
  # Output in different formats:
208
209
  braingrid specify --prompt "Add dark mode support" --format json
209
210
  braingrid specify --prompt "Add export feature" --format markdown
211
+ braingrid specify --prompt "Add search" --tags "search,backend"
210
212
 
211
213
  # Working with the initialized project
212
- braingrid requirement list [--status IDEA|PLANNED|IN_PROGRESS|REVIEW|COMPLETED|CANCELLED] [--format json]
213
- braingrid requirement create --name "Name" [--content "Description"] [--assigned-to <uuid>]
214
+ braingrid requirement list [--status IDEA|PLANNED|IN_PROGRESS|REVIEW|COMPLETED|CANCELLED] [--tree] [--format json]
215
+ braingrid requirement create --name "Name" [--content "Description"] [--assigned-to <uuid>] [--tags "tag1,tag2"]
214
216
  braingrid requirement show [id]
215
- braingrid requirement update [id] [--status IDEA|PLANNED|IN_PROGRESS|REVIEW|COMPLETED|CANCELLED] [--name "New Name"]
217
+ braingrid requirement update [id] [--status IDEA|PLANNED|IN_PROGRESS|REVIEW|COMPLETED|CANCELLED] [--name "New Name"] [--content "markdown"]
216
218
  braingrid requirement delete [id] [--force]
217
- braingrid requirement breakdown [id]
219
+ braingrid requirement breakdown [id] [--format markdown|json|xml]
218
220
  braingrid requirement build [id] [--format markdown|json|xml]
219
221
  braingrid requirement create-branch [id] [--name <branch-name>] [--base <branch>]
220
222
  braingrid requirement review [id] [--pr <number>]
221
223
 
224
+ # Tag management
225
+ braingrid requirement tag list [id]
226
+ braingrid requirement tag add [id] --name "Tag" --color "#FF0000"
227
+ braingrid requirement tag remove [id] --name "Tag"
228
+
222
229
  # Working with a different project:
223
230
  braingrid requirement list -p PROJ-456 [--status PLANNED]
224
231
  braingrid requirement create -p PROJ-456 --name "Description"
@@ -241,9 +248,11 @@ braingrid requirement create -p PROJ-456 --name "Description"
241
248
  ```bash
242
249
  # Working with the initialized project
243
250
  braingrid task list -r REQ-456 [--format table|json|xml|markdown]
244
- braingrid task create -r REQ-456 --title "Task Title" [--content "Description"]
245
- braingrid task show <id>
246
- braingrid task update <id> [--status PLANNED|IN_PROGRESS|COMPLETED|CANCELLED] [--title "New Title"]
251
+ braingrid task create -r REQ-456 --title "Task Title" [--content "Description"] [--external-id <id>]
252
+ braingrid task show [id]
253
+ braingrid task update [id] [--status PLANNED|IN_PROGRESS|COMPLETED|CANCELLED] [--title "New Title"] [--external-id <id>]
254
+ braingrid task summary -r REQ-456
255
+ braingrid task specify -r REQ-456 --prompt "Task description"
247
256
  braingrid task delete <id> [--force]
248
257
 
249
258
  # Working with a different project:
@@ -256,6 +265,14 @@ braingrid task create -p PROJ-123 -r REQ-456 --title "Task Title"
256
265
  > **Note:** The `-r`/`--requirement` parameter is optional and accepts formats like `REQ-456`, `req-456`, or `456`. The CLI will automatically detect the requirement ID from your git branch name (e.g., `feature/REQ-123-description` or `REQ-123-fix-bug`) if it is not provided.
257
266
  >
258
267
  > **Note:** Task status values are: `PLANNED`, `IN_PROGRESS`, `COMPLETED`, `CANCELLED` (tasks do not have `IDEA` or `REVIEW` status).
268
+ >
269
+ > **Note:** `task summary` shows a compact overview table (number, status, title) without full content — useful for quick progress checks.
270
+ >
271
+ > **Note:** `task specify` creates a single AI-generated task from a prompt (10-5000 characters), similar to how `specify` creates requirements.
272
+ >
273
+ > **Note:** The `requirement tag` commands manage tags on requirements. Each requirement can have up to 5 tags. Tags require a name and hex color code (e.g., `#FF0000`).
274
+ >
275
+ > **Note:** `--external-id` links tasks to external systems (e.g., Claude Code task IDs) for status synchronization.
259
276
 
260
277
  ### Informational Commands
261
278
 
@@ -318,7 +335,7 @@ eval "$(braingrid completion zsh)"
318
335
  ### What Gets Completed
319
336
 
320
337
  - **Commands**: `login`, `logout`, `project`, `requirement`, `task`, etc.
321
- - **Subcommands**: `list`, `show`, `create`, `update`, `delete`, `breakdown`, `build`, `create-branch`, `review`
338
+ - **Subcommands**: `list`, `show`, `create`, `update`, `delete`, `breakdown`, `build`, `create-branch`, `review`, `summary`, `specify`, `tag`
322
339
  - **Options**: `--help`, `--format`, `--status`, `--project`, `--requirement`
323
340
  - **Values**: Status values (`IDEA`, `PLANNED`, `IN_PROGRESS`, etc.), format options (`table`, `json`, `xml`, `markdown`)
324
341
 
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.36" : "0.0.0-test";
225
+ var CLI_VERSION = true ? "0.2.38" : "0.0.0-test";
226
226
  var PRODUCTION_CONFIG = {
227
227
  apiUrl: "https://app.braingrid.ai",
228
228
  workosAuthUrl: "https://auth.braingrid.ai",
@@ -2198,8 +2198,7 @@ async function handleCompletion(shellArg, opts) {
2198
2198
  }
2199
2199
 
2200
2200
  // src/handlers/init.handlers.ts
2201
- import { access as access3, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
2202
- import path6 from "path";
2201
+ import { access as access3 } from "fs/promises";
2203
2202
  import { confirm as confirm2, input, select as select3 } from "@inquirer/prompts";
2204
2203
  import chalk10 from "chalk";
2205
2204
 
@@ -2680,7 +2679,18 @@ async function copyBraingridReadme(targetPath = ".braingrid/README.md") {
2680
2679
  return false;
2681
2680
  }
2682
2681
  }
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") {
2682
+ var TASK_UPDATE_CONTINUATION_PROMPT = `After every task status change, follow these rules:
2683
+
2684
+ 1. **On completion**: Verify you committed your changes and updated the task subject with the commit hash (e.g. 'TASK 2 (abc1234): feat: add login'). If you forgot to commit, do it now before moving on.
2685
+
2686
+ 2. **Continue immediately**: After completing a task, check TaskList for the next pending task. Mark it as in_progress and start implementing it right away. Do NOT stop to ask the user for permission.
2687
+
2688
+ 3. **Do not stop until done**: Keep iterating through tasks until ALL tasks are completed. The only valid reason to pause is a genuine blocking question that cannot be answered from the requirement, task descriptions, or codebase.
2689
+
2690
+ 4. **Never ask to continue**: Do NOT say 'Would you like me to continue?', 'Ready for the next task?', 'Shall I proceed?', or any variation. Just continue.
2691
+
2692
+ $ARGUMENTS`;
2693
+ 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", verifyHookScriptPath = ".claude/hooks/verify-acceptance-criteria.sh") {
2684
2694
  try {
2685
2695
  let settings = {};
2686
2696
  try {
@@ -2704,6 +2714,10 @@ async function updateClaudeSettings(settingsPath = ".claude/settings.json", scri
2704
2714
  type: "command",
2705
2715
  command: hookScriptPath,
2706
2716
  timeout: 1e4
2717
+ },
2718
+ {
2719
+ type: "prompt",
2720
+ prompt: TASK_UPDATE_CONTINUATION_PROMPT
2707
2721
  }
2708
2722
  ]
2709
2723
  };
@@ -2766,10 +2780,24 @@ async function updateClaudeSettings(settingsPath = ".claude/settings.json", scri
2766
2780
  } else {
2767
2781
  mergedPreToolUse = [...mergedPreToolUse, ourPreToolUseTaskUpdateEntry];
2768
2782
  }
2783
+ const ourStopHookEntry = {
2784
+ hooks: [
2785
+ {
2786
+ type: "command",
2787
+ command: verifyHookScriptPath
2788
+ }
2789
+ ]
2790
+ };
2791
+ const existingStop = Array.isArray(existingHooks.Stop) ? existingHooks.Stop : [];
2792
+ const hasVerifyHook = existingStop.some(
2793
+ (entry) => entry.hooks?.some((h) => h.command?.includes("verify-acceptance-criteria"))
2794
+ );
2795
+ const mergedStop = hasVerifyHook ? existingStop : [...existingStop, ourStopHookEntry];
2769
2796
  settings.hooks = {
2770
2797
  ...existingHooks,
2771
2798
  PreToolUse: mergedPreToolUse,
2772
- PostToolUse: mergedPostToolUse
2799
+ PostToolUse: mergedPostToolUse,
2800
+ Stop: mergedStop
2773
2801
  };
2774
2802
  const parentDir = path2.dirname(settingsPath);
2775
2803
  await fs2.mkdir(parentDir, { recursive: true });
@@ -3033,9 +3061,33 @@ async function canUseGhAutomation() {
3033
3061
  return isGhAuthenticated();
3034
3062
  }
3035
3063
 
3064
+ // src/utils/gitignore.ts
3065
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
3066
+ import path4 from "path";
3067
+ async function addBraingridTempToGitignore() {
3068
+ const gitRoot = await getGitRoot();
3069
+ if (!gitRoot) return;
3070
+ const gitignorePath = path4.join(gitRoot, ".gitignore");
3071
+ const entry = ".braingrid/temp/";
3072
+ let content = "";
3073
+ try {
3074
+ content = await readFile2(gitignorePath, "utf8");
3075
+ } catch {
3076
+ }
3077
+ const lines = content.split("\n");
3078
+ if (lines.some((line) => line.trim() === entry || line.trim() === ".braingrid/temp")) {
3079
+ return;
3080
+ }
3081
+ const addition = content.endsWith("\n") || content === "" ? "" : "\n";
3082
+ const newContent = `${content}${addition}# BrainGrid temp files
3083
+ ${entry}
3084
+ `;
3085
+ await writeFile2(gitignorePath, newContent, "utf8");
3086
+ }
3087
+
3036
3088
  // src/utils/local-store.ts
3037
3089
  import fs3 from "fs";
3038
- import path4 from "path";
3090
+ import path5 from "path";
3039
3091
 
3040
3092
  // src/types/local-project.ts
3041
3093
  import { z } from "zod";
@@ -3065,11 +3117,11 @@ async function getDefaultCwd() {
3065
3117
  }
3066
3118
  async function getBraingridDir(cwd) {
3067
3119
  const dir = cwd ?? await getDefaultCwd();
3068
- return path4.join(dir, BRAINGRID_DIR);
3120
+ return path5.join(dir, BRAINGRID_DIR);
3069
3121
  }
3070
3122
  async function getProjectConfigPath(cwd) {
3071
3123
  const braingridDir = await getBraingridDir(cwd);
3072
- return path4.join(braingridDir, PROJECT_CONFIG_FILE);
3124
+ return path5.join(braingridDir, PROJECT_CONFIG_FILE);
3073
3125
  }
3074
3126
  async function projectConfigExists(cwd) {
3075
3127
  const configPath = await getProjectConfigPath(cwd);
@@ -3286,7 +3338,7 @@ async function checkAndShowUpdateWarning() {
3286
3338
 
3287
3339
  // src/handlers/setup.handlers.ts
3288
3340
  import * as fs4 from "fs/promises";
3289
- import * as path5 from "path";
3341
+ import * as path6 from "path";
3290
3342
  import { select } from "@inquirer/prompts";
3291
3343
  import chalk8 from "chalk";
3292
3344
  async function fileExists(filePath) {
@@ -3318,22 +3370,23 @@ async function checkPrerequisites() {
3318
3370
  }
3319
3371
  async function getFileList(sourcePaths, targetPaths) {
3320
3372
  const operations = [];
3321
- async function processDirectory(sourceDir, targetDir) {
3373
+ async function processDirectory(sourceDir, targetDir, dirIndex) {
3322
3374
  try {
3323
3375
  const items = await listGitHubDirectory(sourceDir);
3324
3376
  for (const item of items) {
3325
3377
  if (item.type === "file") {
3326
- const itemTargetPath = path5.join(targetDir, item.name);
3378
+ const itemTargetPath = path6.join(targetDir, item.name);
3327
3379
  const exists = await fileExists(itemTargetPath);
3328
3380
  operations.push({
3329
3381
  type: "copy",
3330
3382
  sourcePath: item.path,
3331
3383
  targetPath: itemTargetPath,
3332
- exists
3384
+ exists,
3385
+ dirIndex
3333
3386
  });
3334
3387
  } else if (item.type === "dir") {
3335
- const itemTargetPath = path5.join(targetDir, item.name);
3336
- await processDirectory(item.path, itemTargetPath);
3388
+ const itemTargetPath = path6.join(targetDir, item.name);
3389
+ await processDirectory(item.path, itemTargetPath, dirIndex);
3337
3390
  }
3338
3391
  }
3339
3392
  } catch (error) {
@@ -3344,7 +3397,7 @@ async function getFileList(sourcePaths, targetPaths) {
3344
3397
  }
3345
3398
  }
3346
3399
  for (let i = 0; i < sourcePaths.length; i++) {
3347
- await processDirectory(sourcePaths[i], targetPaths[i]);
3400
+ await processDirectory(sourcePaths[i], targetPaths[i], i);
3348
3401
  }
3349
3402
  return operations;
3350
3403
  }
@@ -3380,15 +3433,15 @@ async function promptForConflict(filePath) {
3380
3433
  });
3381
3434
  return answer;
3382
3435
  }
3383
- async function installFiles(operations, force) {
3384
- let installed = 0;
3436
+ async function installFiles(operations, force, dirCount) {
3437
+ const installedPerDir = new Array(dirCount).fill(0);
3385
3438
  let skipped = 0;
3386
3439
  let overwriteAll = force;
3387
3440
  for (const operation of operations) {
3388
3441
  if (operation.exists && !overwriteAll) {
3389
3442
  const response = await promptForConflict(operation.targetPath);
3390
3443
  if (response === "quit") {
3391
- return { installed, skipped, cancelled: true };
3444
+ return { installedPerDir, skipped, cancelled: true };
3392
3445
  } else if (response === "skip") {
3393
3446
  skipped++;
3394
3447
  continue;
@@ -3398,7 +3451,9 @@ async function installFiles(operations, force) {
3398
3451
  }
3399
3452
  try {
3400
3453
  await copyFileFromGitHub(operation.sourcePath, operation.targetPath);
3401
- installed++;
3454
+ if (operation.dirIndex !== void 0) {
3455
+ installedPerDir[operation.dirIndex]++;
3456
+ }
3402
3457
  } catch (error) {
3403
3458
  console.error(
3404
3459
  chalk8.red(`Failed to copy ${operation.targetPath}:`),
@@ -3407,7 +3462,7 @@ async function installFiles(operations, force) {
3407
3462
  skipped++;
3408
3463
  }
3409
3464
  }
3410
- return { installed, skipped, cancelled: false };
3465
+ return { installedPerDir, skipped, cancelled: false };
3411
3466
  }
3412
3467
  async function _handleSetup(config2, opts) {
3413
3468
  const prerequisiteError = await checkPrerequisites();
@@ -3435,11 +3490,12 @@ async function _handleSetup(config2, opts) {
3435
3490
  };
3436
3491
  }
3437
3492
  const copyOps = operations.filter((op) => op.type === "copy");
3438
- const result = await installFiles(copyOps, opts.force || false);
3493
+ const result = await installFiles(copyOps, opts.force || false, config2.sourceDirs.length);
3439
3494
  if (result.cancelled) {
3495
+ const totalInstalled = result.installedPerDir.reduce((a, b) => a + b, 0);
3440
3496
  return {
3441
3497
  success: false,
3442
- message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${result.installed}, Skipped: ${result.skipped}`),
3498
+ message: chalk8.yellow("\u26A0\uFE0F Installation cancelled.\n\n") + chalk8.dim(`Installed: ${totalInstalled}, Skipped: ${result.skipped}`),
3443
3499
  code: "CANCELLED"
3444
3500
  };
3445
3501
  }
@@ -3456,16 +3512,23 @@ async function _handleSetup(config2, opts) {
3456
3512
  return {
3457
3513
  success: true,
3458
3514
  data: {
3459
- installed: result.installed,
3515
+ installedPerDir: result.installedPerDir,
3460
3516
  skipped: result.skipped
3461
3517
  }
3462
3518
  };
3463
3519
  }
3464
- function buildSuccessMessage(config2, installed, extras) {
3520
+ function buildSuccessMessage(config2, installedPerDir, extras) {
3521
+ let dirLines = "";
3522
+ for (let i = 0; i < config2.dirLabels.length; i++) {
3523
+ const count = installedPerDir[i] ?? 0;
3524
+ if (count === 0) continue;
3525
+ const { label } = config2.dirLabels[i];
3526
+ dirLines += chalk8.dim(` ${label}: ${count}
3527
+ `);
3528
+ }
3465
3529
  return chalk8.green(`\u2705 ${config2.name} integration installed successfully!
3466
3530
 
3467
- `) + chalk8.dim("Files installed:\n") + chalk8.dim(` Commands: ${installed} files
3468
- `) + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3531
+ `) + chalk8.dim("Files installed:\n") + dirLines + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
3469
3532
 
3470
3533
  `) + chalk8.dim("Next steps:\n") + chalk8.dim(" 1. Review the integration files\n") + chalk8.dim(` 2. Open ${config2.name}
3471
3534
  `) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl);
@@ -3478,6 +3541,10 @@ async function handleSetupClaudeCode(opts) {
3478
3541
  name: "Claude Code",
3479
3542
  sourceDirs: ["claude-code/commands", "claude-code/skills/braingrid-cli"],
3480
3543
  targetDirs: [".claude/commands", ".claude/skills/braingrid-cli"],
3544
+ dirLabels: [
3545
+ { label: "Commands", countMode: "files" },
3546
+ { label: "Skills", countMode: "single" }
3547
+ ],
3481
3548
  injection: {
3482
3549
  sourceFile: "claude-code/CLAUDE.md",
3483
3550
  targetFile: "CLAUDE.md"
@@ -3489,7 +3556,10 @@ async function handleSetupClaudeCode(opts) {
3489
3556
  if (!isSetupResult(setupResult)) {
3490
3557
  return setupResult;
3491
3558
  }
3492
- const { installed } = setupResult.data;
3559
+ const { installedPerDir } = setupResult.data;
3560
+ const displayPerDir = installedPerDir.map(
3561
+ (count, i) => config2.dirLabels[i].countMode === "single" ? Math.min(count, 1) : count
3562
+ );
3493
3563
  let statusLineInstalled = false;
3494
3564
  try {
3495
3565
  const scriptContent = await fetchFileFromGitHub("claude-code/statusline.sh");
@@ -3531,11 +3601,24 @@ async function handleSetupClaudeCode(opts) {
3531
3601
  error instanceof Error ? error.message : String(error)
3532
3602
  );
3533
3603
  }
3604
+ let verifyHookInstalled = false;
3605
+ try {
3606
+ const verifyContent = await fetchFileFromGitHub(
3607
+ "claude-code/hooks/verify-acceptance-criteria.sh"
3608
+ );
3609
+ await installHookScript(verifyContent, ".claude/hooks/verify-acceptance-criteria.sh");
3610
+ verifyHookInstalled = true;
3611
+ } catch (error) {
3612
+ console.error(
3613
+ chalk8.yellow("\u26A0\uFE0F Failed to install verify hook:"),
3614
+ error instanceof Error ? error.message : String(error)
3615
+ );
3616
+ }
3534
3617
  const statusLineMessage = statusLineInstalled ? chalk8.dim(" Status line: .claude/statusline.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") : "");
3618
+ 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") : "") + (verifyHookInstalled ? chalk8.dim(" Hook script: .claude/hooks/verify-acceptance-criteria.sh\n") : "");
3536
3619
  return {
3537
3620
  success: true,
3538
- message: buildSuccessMessage(config2, installed, statusLineMessage + hooksMessage)
3621
+ message: buildSuccessMessage(config2, displayPerDir, statusLineMessage + hooksMessage)
3539
3622
  };
3540
3623
  } catch (error) {
3541
3624
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3550,6 +3633,10 @@ async function handleSetupCursor(opts) {
3550
3633
  name: "Cursor",
3551
3634
  sourceDirs: ["cursor/commands", "cursor/rules"],
3552
3635
  targetDirs: [".cursor/commands", ".cursor/rules"],
3636
+ dirLabels: [
3637
+ { label: "Commands", countMode: "files" },
3638
+ { label: "Rules", countMode: "files" }
3639
+ ],
3553
3640
  injection: {
3554
3641
  sourceFile: "cursor/AGENTS.md",
3555
3642
  targetFile: "AGENTS.md"
@@ -3561,10 +3648,10 @@ async function handleSetupCursor(opts) {
3561
3648
  if (!isSetupResult(setupResult)) {
3562
3649
  return setupResult;
3563
3650
  }
3564
- const { installed } = setupResult.data;
3651
+ const { installedPerDir } = setupResult.data;
3565
3652
  return {
3566
3653
  success: true,
3567
- message: buildSuccessMessage(config2, installed, "")
3654
+ message: buildSuccessMessage(config2, installedPerDir, "")
3568
3655
  };
3569
3656
  } catch (error) {
3570
3657
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -3729,6 +3816,7 @@ async function handleUpdate(opts) {
3729
3816
  output += chalk9.green("\u2705 You are already on the latest version!\n");
3730
3817
  console.log(output);
3731
3818
  await copyBraingridReadme();
3819
+ await addBraingridTempToGitignore();
3732
3820
  const setupOutput2 = await promptForIdeUpdates();
3733
3821
  return {
3734
3822
  success: true,
@@ -3768,6 +3856,7 @@ async function handleUpdate(opts) {
3768
3856
  console.log(output);
3769
3857
  executeUpdate(packageManager, PACKAGE_NAME);
3770
3858
  await copyBraingridReadme();
3859
+ await addBraingridTempToGitignore();
3771
3860
  const setupOutput = await promptForIdeUpdates();
3772
3861
  return {
3773
3862
  success: true,
@@ -3798,26 +3887,6 @@ async function fileExists2(filePath) {
3798
3887
  return false;
3799
3888
  }
3800
3889
  }
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
- }
3821
3890
  function getServices() {
3822
3891
  const config2 = getConfig();
3823
3892
  const auth = new BraingridAuth(config2.apiUrl);
@@ -6712,10 +6781,12 @@ async function handleRequirementUpdate(opts) {
6712
6781
  message: chalk14.red("\u274C Not authenticated. Please run `braingrid login` first.")
6713
6782
  };
6714
6783
  }
6715
- if (!opts.status && !opts.name) {
6784
+ if (!opts.status && !opts.name && !opts.content) {
6716
6785
  return {
6717
6786
  success: false,
6718
- message: chalk14.red("\u274C Please provide at least one field to update (--status or --name)")
6787
+ message: chalk14.red(
6788
+ "\u274C Please provide at least one field to update (--status, --name, or --content)"
6789
+ )
6719
6790
  };
6720
6791
  }
6721
6792
  const requirementResult = await workspaceManager.getRequirement(opts.id);
@@ -6738,7 +6809,8 @@ async function handleRequirementUpdate(opts) {
6738
6809
  stopSpinner = showSpinner("Updating requirement", chalk14.gray);
6739
6810
  const requirement2 = await requirementService.updateProjectRequirement(projectId, normalizedId, {
6740
6811
  status: opts.status,
6741
- name: opts.name
6812
+ name: opts.name,
6813
+ content: opts.content
6742
6814
  });
6743
6815
  stopSpinner();
6744
6816
  stopSpinner = null;
@@ -8461,7 +8533,7 @@ requirement.command("create").description("Create a new requirement").option(
8461
8533
  requirement.command("update [id]").description("Update requirement information (auto-detects ID from git branch if not provided)").option("-p, --project <id>", "project ID (auto-detects from workspace if not specified)").option(
8462
8534
  "--status <status>",
8463
8535
  "new status (IDEA, PLANNED, IN_PROGRESS, REVIEW, COMPLETED, CANCELLED)"
8464
- ).option("--name <name>", "new requirement name").action(async (id, opts) => {
8536
+ ).option("--name <name>", "new requirement name").option("-c, --content <content>", "new requirement content (markdown)").action(async (id, opts) => {
8465
8537
  const result = await handleRequirementUpdate({ ...opts, id });
8466
8538
  console.log(result.message);
8467
8539
  if (!result.success) {