@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 +38 -0
- package/README.md +28 -11
- package/dist/cli.js +127 -55
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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/
|
|
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>
|
|
6
|
-
<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
|
[](https://www.npmjs.com/package/@braingrid/cli)
|
|
9
10
|
[](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
|
|
246
|
-
braingrid task update
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
3120
|
+
return path5.join(dir, BRAINGRID_DIR);
|
|
3069
3121
|
}
|
|
3070
3122
|
async function getProjectConfigPath(cwd) {
|
|
3071
3123
|
const braingridDir = await getBraingridDir(cwd);
|
|
3072
|
-
return
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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: ${
|
|
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
|
-
|
|
3515
|
+
installedPerDir: result.installedPerDir,
|
|
3460
3516
|
skipped: result.skipped
|
|
3461
3517
|
}
|
|
3462
3518
|
};
|
|
3463
3519
|
}
|
|
3464
|
-
function buildSuccessMessage(config2,
|
|
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(`
|
|
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 {
|
|
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,
|
|
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 {
|
|
3651
|
+
const { installedPerDir } = setupResult.data;
|
|
3565
3652
|
return {
|
|
3566
3653
|
success: true,
|
|
3567
|
-
message: buildSuccessMessage(config2,
|
|
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(
|
|
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) {
|