@braingrid/cli 0.2.42 → 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 +6 -0
- package/dist/cli.js +104 -223
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ 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
|
+
|
|
10
16
|
## [0.2.42] - 2026-02-17
|
|
11
17
|
|
|
12
18
|
### Added
|
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,133 +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
|
-
const ourSessionStartEntry = {
|
|
2786
|
-
hooks: [
|
|
2787
|
-
{
|
|
2788
|
-
type: "command",
|
|
2789
|
-
command: sessionStartPath
|
|
2790
|
-
}
|
|
2791
|
-
]
|
|
2792
|
-
};
|
|
2793
|
-
const existingSessionStart = Array.isArray(existingHooks.SessionStart) ? existingHooks.SessionStart : [];
|
|
2794
|
-
const hasSessionStartHook = existingSessionStart.some(
|
|
2795
|
-
(entry) => entry.hooks?.some((h) => h.command?.includes("check-stale-build-sentinel"))
|
|
2796
|
-
);
|
|
2797
|
-
const mergedSessionStart = hasSessionStartHook ? existingSessionStart : [...existingSessionStart, ourSessionStartEntry];
|
|
2798
|
-
const ourTaskCompletedEntry = {
|
|
2799
|
-
hooks: [
|
|
2800
|
-
{
|
|
2801
|
-
type: "command",
|
|
2802
|
-
command: taskCompletedPath
|
|
2803
|
-
}
|
|
2804
|
-
]
|
|
2805
|
-
};
|
|
2806
|
-
const existingTaskCompleted = Array.isArray(existingHooks.TaskCompleted) ? existingHooks.TaskCompleted : [];
|
|
2807
|
-
const hasTaskCompletedHook = existingTaskCompleted.some(
|
|
2808
|
-
(entry) => entry.hooks?.some((h) => h.command?.includes("task-completed-validate"))
|
|
2809
|
-
);
|
|
2810
|
-
const mergedTaskCompleted = hasTaskCompletedHook ? existingTaskCompleted : [...existingTaskCompleted, ourTaskCompletedEntry];
|
|
2811
2785
|
settings.hooks = {
|
|
2812
2786
|
...existingHooks,
|
|
2813
|
-
|
|
2814
|
-
PostToolUse: mergedPostToolUse,
|
|
2815
|
-
Stop: mergedStop,
|
|
2816
|
-
SessionStart: mergedSessionStart,
|
|
2817
|
-
TaskCompleted: mergedTaskCompleted
|
|
2787
|
+
...mergedByType
|
|
2818
2788
|
};
|
|
2819
2789
|
const parentDir = path2.dirname(settingsPath);
|
|
2820
2790
|
await fs2.mkdir(parentDir, { recursive: true });
|
|
@@ -3547,8 +3517,7 @@ function buildSuccessMessage(config2, installedPerDir, extras) {
|
|
|
3547
3517
|
|
|
3548
3518
|
`) + chalk8.dim("Files installed:\n") + dirLines + extras + chalk8.dim(` Content injected into: ${config2.injection.targetFile}
|
|
3549
3519
|
|
|
3550
|
-
`) + chalk8.dim("Next
|
|
3551
|
-
`) + 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);
|
|
3552
3521
|
}
|
|
3553
3522
|
function isSetupResult(result) {
|
|
3554
3523
|
return "data" in result && result.success === true && !("message" in result);
|
|
@@ -3596,111 +3565,23 @@ async function handleSetupClaudeCode(opts) {
|
|
|
3596
3565
|
error instanceof Error ? error.message : String(error)
|
|
3597
3566
|
);
|
|
3598
3567
|
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
error
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
const createContent = await fetchFileFromGitHub("claude-code/hooks/create-braingrid-task.sh");
|
|
3613
|
-
await installHookScript(createContent, ".claude/hooks/create-braingrid-task.sh");
|
|
3614
|
-
createHookInstalled = true;
|
|
3615
|
-
} catch (error) {
|
|
3616
|
-
console.error(
|
|
3617
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install create hook:"),
|
|
3618
|
-
error instanceof Error ? error.message : String(error)
|
|
3619
|
-
);
|
|
3620
|
-
}
|
|
3621
|
-
let verifyHookInstalled = false;
|
|
3622
|
-
try {
|
|
3623
|
-
const verifyContent = await fetchFileFromGitHub(
|
|
3624
|
-
"claude-code/hooks/verify-acceptance-criteria.sh"
|
|
3625
|
-
);
|
|
3626
|
-
await installHookScript(verifyContent, ".claude/hooks/verify-acceptance-criteria.sh");
|
|
3627
|
-
verifyHookInstalled = true;
|
|
3628
|
-
} catch (error) {
|
|
3629
|
-
console.error(
|
|
3630
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install verify hook:"),
|
|
3631
|
-
error instanceof Error ? error.message : String(error)
|
|
3632
|
-
);
|
|
3633
|
-
}
|
|
3634
|
-
let preTaskCreateInstalled = false;
|
|
3635
|
-
try {
|
|
3636
|
-
const preTaskCreateContent = await fetchFileFromGitHub(
|
|
3637
|
-
"claude-code/hooks/pre-task-create-naming.sh"
|
|
3638
|
-
);
|
|
3639
|
-
await installHookScript(preTaskCreateContent, ".claude/hooks/pre-task-create-naming.sh");
|
|
3640
|
-
preTaskCreateInstalled = true;
|
|
3641
|
-
} catch (error) {
|
|
3642
|
-
console.error(
|
|
3643
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install pre-task-create hook:"),
|
|
3644
|
-
error instanceof Error ? error.message : String(error)
|
|
3645
|
-
);
|
|
3646
|
-
}
|
|
3647
|
-
let preTaskUpdateInstalled = false;
|
|
3648
|
-
try {
|
|
3649
|
-
const preTaskUpdateContent = await fetchFileFromGitHub(
|
|
3650
|
-
"claude-code/hooks/pre-task-update-instructions.sh"
|
|
3651
|
-
);
|
|
3652
|
-
await installHookScript(
|
|
3653
|
-
preTaskUpdateContent,
|
|
3654
|
-
".claude/hooks/pre-task-update-instructions.sh"
|
|
3655
|
-
);
|
|
3656
|
-
preTaskUpdateInstalled = true;
|
|
3657
|
-
} catch (error) {
|
|
3658
|
-
console.error(
|
|
3659
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install pre-task-update hook:"),
|
|
3660
|
-
error instanceof Error ? error.message : String(error)
|
|
3661
|
-
);
|
|
3662
|
-
}
|
|
3663
|
-
let postTaskUpdateInstalled = false;
|
|
3664
|
-
try {
|
|
3665
|
-
const postTaskUpdateContent = await fetchFileFromGitHub(
|
|
3666
|
-
"claude-code/hooks/post-task-update-prompt.sh"
|
|
3667
|
-
);
|
|
3668
|
-
await installHookScript(postTaskUpdateContent, ".claude/hooks/post-task-update-prompt.sh");
|
|
3669
|
-
postTaskUpdateInstalled = true;
|
|
3670
|
-
} catch (error) {
|
|
3671
|
-
console.error(
|
|
3672
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install post-task-update hook:"),
|
|
3673
|
-
error instanceof Error ? error.message : String(error)
|
|
3674
|
-
);
|
|
3675
|
-
}
|
|
3676
|
-
let sessionStartInstalled = false;
|
|
3677
|
-
try {
|
|
3678
|
-
const sessionStartContent = await fetchFileFromGitHub(
|
|
3679
|
-
"claude-code/hooks/check-stale-build-sentinel.sh"
|
|
3680
|
-
);
|
|
3681
|
-
await installHookScript(sessionStartContent, ".claude/hooks/check-stale-build-sentinel.sh");
|
|
3682
|
-
sessionStartInstalled = true;
|
|
3683
|
-
} catch (error) {
|
|
3684
|
-
console.error(
|
|
3685
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install session-start hook:"),
|
|
3686
|
-
error instanceof Error ? error.message : String(error)
|
|
3687
|
-
);
|
|
3688
|
-
}
|
|
3689
|
-
let taskCompletedInstalled = false;
|
|
3690
|
-
try {
|
|
3691
|
-
const taskCompletedContent = await fetchFileFromGitHub(
|
|
3692
|
-
"claude-code/hooks/task-completed-validate.sh"
|
|
3693
|
-
);
|
|
3694
|
-
await installHookScript(taskCompletedContent, ".claude/hooks/task-completed-validate.sh");
|
|
3695
|
-
taskCompletedInstalled = true;
|
|
3696
|
-
} catch (error) {
|
|
3697
|
-
console.error(
|
|
3698
|
-
chalk8.yellow("\u26A0\uFE0F Failed to install task-completed hook:"),
|
|
3699
|
-
error instanceof Error ? error.message : String(error)
|
|
3700
|
-
);
|
|
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
|
+
}
|
|
3701
3581
|
}
|
|
3702
3582
|
const statusLineMessage = statusLineInstalled ? chalk8.dim(" Status line: .claude/statusline.sh\n") : "";
|
|
3703
|
-
const hooksMessage =
|
|
3583
|
+
const hooksMessage = installedHooks.map((s) => chalk8.dim(` Hook script: .claude/hooks/${s}
|
|
3584
|
+
`)).join("");
|
|
3704
3585
|
return {
|
|
3705
3586
|
success: true,
|
|
3706
3587
|
message: buildSuccessMessage(config2, displayPerDir, statusLineMessage + hooksMessage)
|