@braingrid/cli 0.2.41 → 0.2.43
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 +13 -0
- package/dist/cli.js +104 -169
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.43] - 2026-02-17
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **grep -c newline bug in verify-acceptance-criteria.sh** — `grep -c` exits non-zero on 0 matches, causing `|| echo "0"` to produce `"0\n0"` artifacts that break arithmetic expansion; replaced with `|| true` and `${VAR:-0}` default
|
|
15
|
+
|
|
16
|
+
## [0.2.42] - 2026-02-17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **SessionStart and TaskCompleted hook installation** — `braingrid setup claude-code` now installs two additional hook scripts: `check-stale-build-sentinel.sh` (SessionStart) for cleaning up stale build sentinels, and `task-completed-validate.sh` (TaskCompleted) for commit validation before marking tasks complete
|
|
21
|
+
- **Sync script updated** — `sync-claude-code-to-braingrid.sh` now syncs 16 files (up from 14), including the two new hook scripts
|
|
22
|
+
|
|
10
23
|
## [0.2.41] - 2026-02-17
|
|
11
24
|
|
|
12
25
|
### 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.
|
|
225
|
+
var CLI_VERSION = true ? "0.2.43" : "0.0.0-test";
|
|
226
226
|
var PRODUCTION_CONFIG = {
|
|
227
227
|
apiUrl: "https://app.braingrid.ai",
|
|
228
228
|
workosAuthUrl: "https://auth.braingrid.ai",
|
|
@@ -2658,7 +2658,7 @@ async function installStatusLineScript(scriptContent, targetPath = ".claude/stat
|
|
|
2658
2658
|
throw new Error(`Failed to install status line script to ${targetPath}: ${errorMessage}`);
|
|
2659
2659
|
}
|
|
2660
2660
|
}
|
|
2661
|
-
async function installHookScript(scriptContent, targetPath
|
|
2661
|
+
async function installHookScript(scriptContent, targetPath) {
|
|
2662
2662
|
try {
|
|
2663
2663
|
const parentDir = path2.dirname(targetPath);
|
|
2664
2664
|
await fs2.mkdir(parentDir, { recursive: true });
|
|
@@ -2679,7 +2679,64 @@ async function copyBraingridReadme(targetPath = ".braingrid/README.md") {
|
|
|
2679
2679
|
return false;
|
|
2680
2680
|
}
|
|
2681
2681
|
}
|
|
2682
|
-
|
|
2682
|
+
var HOOK_REGISTRY = [
|
|
2683
|
+
{
|
|
2684
|
+
hookType: "PostToolUse",
|
|
2685
|
+
matcher: "TaskUpdate",
|
|
2686
|
+
mergeStrategy: "upsertByMatcher",
|
|
2687
|
+
commands: [
|
|
2688
|
+
{ script: "sync-braingrid-task.sh", timeout: 1e4 },
|
|
2689
|
+
{ script: "post-task-update-prompt.sh" }
|
|
2690
|
+
]
|
|
2691
|
+
},
|
|
2692
|
+
{
|
|
2693
|
+
hookType: "PostToolUse",
|
|
2694
|
+
matcher: "TaskCreate",
|
|
2695
|
+
mergeStrategy: "upsertByMatcher",
|
|
2696
|
+
commands: [{ script: "create-braingrid-task.sh", timeout: 1e4 }]
|
|
2697
|
+
},
|
|
2698
|
+
{
|
|
2699
|
+
hookType: "PreToolUse",
|
|
2700
|
+
matcher: "TaskCreate",
|
|
2701
|
+
mergeStrategy: "upsertByMatcher",
|
|
2702
|
+
commands: [{ script: "pre-task-create-naming.sh" }]
|
|
2703
|
+
},
|
|
2704
|
+
{
|
|
2705
|
+
hookType: "PreToolUse",
|
|
2706
|
+
matcher: "TaskUpdate",
|
|
2707
|
+
mergeStrategy: "upsertByMatcher",
|
|
2708
|
+
commands: [{ script: "pre-task-update-instructions.sh" }]
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
hookType: "Stop",
|
|
2712
|
+
mergeStrategy: "appendDedup",
|
|
2713
|
+
dedupSubstring: "verify-acceptance-criteria",
|
|
2714
|
+
commands: [{ script: "verify-acceptance-criteria.sh" }]
|
|
2715
|
+
},
|
|
2716
|
+
{
|
|
2717
|
+
hookType: "SessionStart",
|
|
2718
|
+
mergeStrategy: "appendDedup",
|
|
2719
|
+
dedupSubstring: "check-stale-build-sentinel",
|
|
2720
|
+
commands: [{ script: "check-stale-build-sentinel.sh" }]
|
|
2721
|
+
},
|
|
2722
|
+
{
|
|
2723
|
+
hookType: "TaskCompleted",
|
|
2724
|
+
mergeStrategy: "appendDedup",
|
|
2725
|
+
dedupSubstring: "task-completed-validate",
|
|
2726
|
+
commands: [{ script: "task-completed-validate.sh" }]
|
|
2727
|
+
}
|
|
2728
|
+
];
|
|
2729
|
+
function buildHookCommand(cmd) {
|
|
2730
|
+
const result = {
|
|
2731
|
+
type: "command",
|
|
2732
|
+
command: `.claude/hooks/${cmd.script}`
|
|
2733
|
+
};
|
|
2734
|
+
if (cmd.timeout) {
|
|
2735
|
+
result.timeout = cmd.timeout;
|
|
2736
|
+
}
|
|
2737
|
+
return result;
|
|
2738
|
+
}
|
|
2739
|
+
async function updateClaudeSettings(settingsPath = ".claude/settings.json", statusLineScriptPath = ".claude/statusline.sh", hookRegistry = HOOK_REGISTRY) {
|
|
2683
2740
|
try {
|
|
2684
2741
|
let settings = {};
|
|
2685
2742
|
try {
|
|
@@ -2688,105 +2745,46 @@ async function updateClaudeSettings(settingsPath = ".claude/settings.json", scri
|
|
|
2688
2745
|
} catch {
|
|
2689
2746
|
}
|
|
2690
2747
|
const existingStatusLine = settings.statusLine;
|
|
2691
|
-
if (existingStatusLine?.command?.includes(
|
|
2748
|
+
if (existingStatusLine?.command?.includes(statusLineScriptPath)) {
|
|
2692
2749
|
} else {
|
|
2693
2750
|
settings.statusLine = {
|
|
2694
2751
|
type: "command",
|
|
2695
|
-
command:
|
|
2752
|
+
command: statusLineScriptPath,
|
|
2696
2753
|
padding: 0
|
|
2697
2754
|
};
|
|
2698
2755
|
}
|
|
2699
|
-
const ourHookEntry = {
|
|
2700
|
-
matcher: "TaskUpdate",
|
|
2701
|
-
hooks: [
|
|
2702
|
-
{
|
|
2703
|
-
type: "command",
|
|
2704
|
-
command: hookScriptPath,
|
|
2705
|
-
timeout: 1e4
|
|
2706
|
-
},
|
|
2707
|
-
{
|
|
2708
|
-
type: "command",
|
|
2709
|
-
command: postTaskUpdatePromptPath
|
|
2710
|
-
}
|
|
2711
|
-
]
|
|
2712
|
-
};
|
|
2713
2756
|
const existingHooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? settings.hooks : {};
|
|
2714
|
-
const
|
|
2715
|
-
const
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
]
|
|
2732
|
-
};
|
|
2733
|
-
const taskCreatePostIdx = mergedPostToolUse.findIndex((e) => e.matcher === "TaskCreate");
|
|
2734
|
-
if (taskCreatePostIdx >= 0) {
|
|
2735
|
-
mergedPostToolUse[taskCreatePostIdx] = ourCreateHookEntry;
|
|
2736
|
-
} else {
|
|
2737
|
-
mergedPostToolUse = [...mergedPostToolUse, ourCreateHookEntry];
|
|
2738
|
-
}
|
|
2739
|
-
const ourPreToolUseEntry = {
|
|
2740
|
-
matcher: "TaskCreate",
|
|
2741
|
-
hooks: [
|
|
2742
|
-
{
|
|
2743
|
-
type: "command",
|
|
2744
|
-
command: preTaskCreatePath
|
|
2757
|
+
const mergedByType = {};
|
|
2758
|
+
for (const entry of hookRegistry) {
|
|
2759
|
+
const hookType = entry.hookType;
|
|
2760
|
+
if (!mergedByType[hookType]) {
|
|
2761
|
+
const existing = Array.isArray(existingHooks[hookType]) ? [...existingHooks[hookType]] : [];
|
|
2762
|
+
mergedByType[hookType] = existing;
|
|
2763
|
+
}
|
|
2764
|
+
const merged = mergedByType[hookType];
|
|
2765
|
+
const hooks = entry.commands.map(buildHookCommand);
|
|
2766
|
+
if (entry.mergeStrategy === "upsertByMatcher") {
|
|
2767
|
+
const matcher = entry.matcher ?? "";
|
|
2768
|
+
const newEntry = { matcher, hooks };
|
|
2769
|
+
const idx = merged.findIndex((e) => e.matcher === matcher);
|
|
2770
|
+
if (idx >= 0) {
|
|
2771
|
+
merged[idx] = newEntry;
|
|
2772
|
+
} else {
|
|
2773
|
+
merged.push(newEntry);
|
|
2745
2774
|
}
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
mergedPreToolUse[taskCreateIdx] = ourPreToolUseEntry;
|
|
2754
|
-
} else {
|
|
2755
|
-
mergedPreToolUse = [...existingPreToolUse, ourPreToolUseEntry];
|
|
2756
|
-
}
|
|
2757
|
-
const ourPreToolUseTaskUpdateEntry = {
|
|
2758
|
-
matcher: "TaskUpdate",
|
|
2759
|
-
hooks: [
|
|
2760
|
-
{
|
|
2761
|
-
type: "command",
|
|
2762
|
-
command: preTaskUpdatePath
|
|
2775
|
+
} else {
|
|
2776
|
+
const substring = entry.dedupSubstring ?? "";
|
|
2777
|
+
const alreadyPresent = merged.some(
|
|
2778
|
+
(e) => e.hooks?.some((h) => h.command?.includes(substring))
|
|
2779
|
+
);
|
|
2780
|
+
if (!alreadyPresent) {
|
|
2781
|
+
merged.push({ hooks });
|
|
2763
2782
|
}
|
|
2764
|
-
|
|
2765
|
-
};
|
|
2766
|
-
const taskUpdatePreIdx = mergedPreToolUse.findIndex((e) => e.matcher === "TaskUpdate");
|
|
2767
|
-
if (taskUpdatePreIdx >= 0) {
|
|
2768
|
-
mergedPreToolUse[taskUpdatePreIdx] = ourPreToolUseTaskUpdateEntry;
|
|
2769
|
-
} else {
|
|
2770
|
-
mergedPreToolUse = [...mergedPreToolUse, ourPreToolUseTaskUpdateEntry];
|
|
2783
|
+
}
|
|
2771
2784
|
}
|
|
2772
|
-
const ourStopHookEntry = {
|
|
2773
|
-
hooks: [
|
|
2774
|
-
{
|
|
2775
|
-
type: "command",
|
|
2776
|
-
command: verifyHookScriptPath
|
|
2777
|
-
}
|
|
2778
|
-
]
|
|
2779
|
-
};
|
|
2780
|
-
const existingStop = Array.isArray(existingHooks.Stop) ? existingHooks.Stop : [];
|
|
2781
|
-
const hasVerifyHook = existingStop.some(
|
|
2782
|
-
(entry) => entry.hooks?.some((h) => h.command?.includes("verify-acceptance-criteria"))
|
|
2783
|
-
);
|
|
2784
|
-
const mergedStop = hasVerifyHook ? existingStop : [...existingStop, ourStopHookEntry];
|
|
2785
2785
|
settings.hooks = {
|
|
2786
2786
|
...existingHooks,
|
|
2787
|
-
|
|
2788
|
-
PostToolUse: mergedPostToolUse,
|
|
2789
|
-
Stop: mergedStop
|
|
2787
|
+
...mergedByType
|
|
2790
2788
|
};
|
|
2791
2789
|
const parentDir = path2.dirname(settingsPath);
|
|
2792
2790
|
await fs2.mkdir(parentDir, { recursive: true });
|
|
@@ -3519,8 +3517,7 @@ function buildSuccessMessage(config2, installedPerDir, extras) {
|
|
|
3519
3517
|
|
|
3520
3518
|
`) + chalk8.dim("Files installed:\n") + dirLines + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
|
|
3521
3519
|
|
|
3522
|
-
`) + chalk8.dim("Next
|
|
3523
|
-
`) + chalk8.dim(" 3. Try the /specify or /breakdown commands\n") + chalk8.dim(" 4. Learn more: ") + chalk8.cyan(config2.docsUrl);
|
|
3520
|
+
`) + chalk8.dim("Next, try:\n") + chalk8.dim(" /build REQ-X \u2192 build a requirement\n") + chalk8.dim(' /specify "add two-factor auth" \u2192 specify a requirement\n') + chalk8.dim(" Learn more: ") + chalk8.cyan(config2.docsUrl);
|
|
3524
3521
|
}
|
|
3525
3522
|
function isSetupResult(result) {
|
|
3526
3523
|
return "data" in result && result.success === true && !("message" in result);
|
|
@@ -3568,85 +3565,23 @@ async function handleSetupClaudeCode(opts) {
|
|
|
3568
3565
|
error instanceof Error ? error.message : String(error)
|
|
3569
3566
|
);
|
|
3570
3567
|
}
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
error
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
const createContent = await fetchFileFromGitHub("claude-code/hooks/create-braingrid-task.sh");
|
|
3585
|
-
await installHookScript(createContent, ".claude/hooks/create-braingrid-task.sh");
|
|
3586
|
-
createHookInstalled = true;
|
|
3587
|
-
} catch (error) {
|
|
3588
|
-
console.error(
|
|
3589
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install create hook:"),
|
|
3590
|
-
error instanceof Error ? error.message : String(error)
|
|
3591
|
-
);
|
|
3592
|
-
}
|
|
3593
|
-
let verifyHookInstalled = false;
|
|
3594
|
-
try {
|
|
3595
|
-
const verifyContent = await fetchFileFromGitHub(
|
|
3596
|
-
"claude-code/hooks/verify-acceptance-criteria.sh"
|
|
3597
|
-
);
|
|
3598
|
-
await installHookScript(verifyContent, ".claude/hooks/verify-acceptance-criteria.sh");
|
|
3599
|
-
verifyHookInstalled = true;
|
|
3600
|
-
} catch (error) {
|
|
3601
|
-
console.error(
|
|
3602
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install verify hook:"),
|
|
3603
|
-
error instanceof Error ? error.message : String(error)
|
|
3604
|
-
);
|
|
3605
|
-
}
|
|
3606
|
-
let preTaskCreateInstalled = false;
|
|
3607
|
-
try {
|
|
3608
|
-
const preTaskCreateContent = await fetchFileFromGitHub(
|
|
3609
|
-
"claude-code/hooks/pre-task-create-naming.sh"
|
|
3610
|
-
);
|
|
3611
|
-
await installHookScript(preTaskCreateContent, ".claude/hooks/pre-task-create-naming.sh");
|
|
3612
|
-
preTaskCreateInstalled = true;
|
|
3613
|
-
} catch (error) {
|
|
3614
|
-
console.error(
|
|
3615
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install pre-task-create hook:"),
|
|
3616
|
-
error instanceof Error ? error.message : String(error)
|
|
3617
|
-
);
|
|
3618
|
-
}
|
|
3619
|
-
let preTaskUpdateInstalled = false;
|
|
3620
|
-
try {
|
|
3621
|
-
const preTaskUpdateContent = await fetchFileFromGitHub(
|
|
3622
|
-
"claude-code/hooks/pre-task-update-instructions.sh"
|
|
3623
|
-
);
|
|
3624
|
-
await installHookScript(
|
|
3625
|
-
preTaskUpdateContent,
|
|
3626
|
-
".claude/hooks/pre-task-update-instructions.sh"
|
|
3627
|
-
);
|
|
3628
|
-
preTaskUpdateInstalled = true;
|
|
3629
|
-
} catch (error) {
|
|
3630
|
-
console.error(
|
|
3631
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install pre-task-update hook:"),
|
|
3632
|
-
error instanceof Error ? error.message : String(error)
|
|
3633
|
-
);
|
|
3634
|
-
}
|
|
3635
|
-
let postTaskUpdateInstalled = false;
|
|
3636
|
-
try {
|
|
3637
|
-
const postTaskUpdateContent = await fetchFileFromGitHub(
|
|
3638
|
-
"claude-code/hooks/post-task-update-prompt.sh"
|
|
3639
|
-
);
|
|
3640
|
-
await installHookScript(postTaskUpdateContent, ".claude/hooks/post-task-update-prompt.sh");
|
|
3641
|
-
postTaskUpdateInstalled = true;
|
|
3642
|
-
} catch (error) {
|
|
3643
|
-
console.error(
|
|
3644
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install post-task-update hook:"),
|
|
3645
|
-
error instanceof Error ? error.message : String(error)
|
|
3646
|
-
);
|
|
3568
|
+
const allScripts = HOOK_REGISTRY.flatMap((e) => e.commands.map((c) => c.script));
|
|
3569
|
+
const installedHooks = [];
|
|
3570
|
+
for (const script of allScripts) {
|
|
3571
|
+
try {
|
|
3572
|
+
const content = await fetchFileFromGitHub(`claude-code/hooks/${script}`);
|
|
3573
|
+
await installHookScript(content, `.claude/hooks/${script}`);
|
|
3574
|
+
installedHooks.push(script);
|
|
3575
|
+
} catch (error) {
|
|
3576
|
+
console.error(
|
|
3577
|
+
chalk8.yellow(`\u26A0\uFE0F Failed to install hook ${script}:`),
|
|
3578
|
+
error instanceof Error ? error.message : String(error)
|
|
3579
|
+
);
|
|
3580
|
+
}
|
|
3647
3581
|
}
|
|
3648
3582
|
const statusLineMessage = statusLineInstalled ? chalk8.dim(" Status line: .claude/statusline.sh\n") : "";
|
|
3649
|
-
const hooksMessage =
|
|
3583
|
+
const hooksMessage = installedHooks.map((s) => chalk8.dim(` Hook script: .claude/hooks/${s}
|
|
3584
|
+
`)).join("");
|
|
3650
3585
|
return {
|
|
3651
3586
|
success: true,
|
|
3652
3587
|
message: buildSuccessMessage(config2, displayPerDir, statusLineMessage + hooksMessage)
|