@braingrid/cli 0.2.23 → 0.2.25

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/dist/cli.js CHANGED
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ execAsync
4
+ } from "./chunk-6GC3UJCM.js";
2
5
  import {
3
6
  checkInstalledCliTools,
4
7
  detectLinuxPackageManager,
@@ -422,7 +425,7 @@ import axios3, { AxiosError as AxiosError2 } from "axios";
422
425
 
423
426
  // src/build-config.ts
424
427
  var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
425
- var CLI_VERSION = true ? "0.2.23" : "0.0.0-test";
428
+ var CLI_VERSION = true ? "0.2.25" : "0.0.0-test";
426
429
  var PRODUCTION_CONFIG = {
427
430
  apiUrl: "https://app.braingrid.ai",
428
431
  workosAuthUrl: "https://auth.braingrid.ai",
@@ -632,7 +635,7 @@ var OAuth2Handler = class {
632
635
  `);
633
636
  }
634
637
  });
635
- this.server.listen(port, "127.0.0.1", () => {
638
+ this.server.listen(port, "0.0.0.0", () => {
636
639
  logger2.debug(`Callback server listening on http://127.0.0.1:${port}`);
637
640
  resolve();
638
641
  });
@@ -1837,10 +1840,10 @@ var LocalProjectConfigSchema = z.object({
1837
1840
  // src/utils/git.ts
1838
1841
  import { exec } from "child_process";
1839
1842
  import { promisify } from "util";
1840
- var execAsync = promisify(exec);
1843
+ var execAsync2 = promisify(exec);
1841
1844
  async function isGitRepository() {
1842
1845
  try {
1843
- const { stdout } = await execAsync("git rev-parse --is-inside-work-tree");
1846
+ const { stdout } = await execAsync2("git rev-parse --is-inside-work-tree");
1844
1847
  return stdout.trim() === "true";
1845
1848
  } catch {
1846
1849
  return false;
@@ -1848,7 +1851,7 @@ async function isGitRepository() {
1848
1851
  }
1849
1852
  async function getRemoteUrl() {
1850
1853
  try {
1851
- const { stdout } = await execAsync("git config --get remote.origin.url");
1854
+ const { stdout } = await execAsync2("git config --get remote.origin.url");
1852
1855
  return stdout.trim() || null;
1853
1856
  } catch {
1854
1857
  return null;
@@ -1874,7 +1877,7 @@ function parseGitHubRepo(url) {
1874
1877
  }
1875
1878
  async function getCurrentBranch() {
1876
1879
  try {
1877
- const { stdout } = await execAsync("git rev-parse --abbrev-ref HEAD");
1880
+ const { stdout } = await execAsync2("git rev-parse --abbrev-ref HEAD");
1878
1881
  return stdout.trim() || null;
1879
1882
  } catch {
1880
1883
  return null;
@@ -1890,7 +1893,7 @@ function parseRequirementFromBranch(branchName) {
1890
1893
  }
1891
1894
  async function getGitRoot() {
1892
1895
  try {
1893
- const { stdout } = await execAsync("git rev-parse --show-toplevel");
1896
+ const { stdout } = await execAsync2("git rev-parse --show-toplevel");
1894
1897
  return stdout.trim() || null;
1895
1898
  } catch {
1896
1899
  return null;
@@ -1900,12 +1903,12 @@ async function getGitUser() {
1900
1903
  let name = null;
1901
1904
  let email = null;
1902
1905
  try {
1903
- const { stdout: nameStdout } = await execAsync("git config --get user.name");
1906
+ const { stdout: nameStdout } = await execAsync2("git config --get user.name");
1904
1907
  name = nameStdout.trim() || null;
1905
1908
  } catch {
1906
1909
  }
1907
1910
  try {
1908
- const { stdout: emailStdout } = await execAsync("git config --get user.email");
1911
+ const { stdout: emailStdout } = await execAsync2("git config --get user.email");
1909
1912
  email = emailStdout.trim() || null;
1910
1913
  } catch {
1911
1914
  }
@@ -3828,6 +3831,31 @@ var RequirementService = class {
3828
3831
  const response = await this.axios.post(url, data, { headers });
3829
3832
  return response.data;
3830
3833
  }
3834
+ async createGitBranch(projectId, requirementId, data) {
3835
+ const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/create-git-branch`;
3836
+ const headers = this.getHeaders();
3837
+ const response = await this.axios.post(url, data, { headers });
3838
+ return response.data;
3839
+ }
3840
+ async reviewAcceptance(projectId, requirementId, data, onChunk) {
3841
+ const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/review/acceptance`;
3842
+ const headers = this.getHeaders();
3843
+ const response = await this.axios.post(url, data, {
3844
+ headers,
3845
+ responseType: "stream"
3846
+ });
3847
+ return new Promise((resolve, reject) => {
3848
+ response.data.on("data", (chunk) => {
3849
+ onChunk(chunk.toString());
3850
+ });
3851
+ response.data.on("end", () => {
3852
+ resolve();
3853
+ });
3854
+ response.data.on("error", (error) => {
3855
+ reject(error);
3856
+ });
3857
+ });
3858
+ }
3831
3859
  };
3832
3860
 
3833
3861
  // src/handlers/requirement.handlers.ts
@@ -4464,6 +4492,190 @@ async function handleRequirementBuild(opts) {
4464
4492
  };
4465
4493
  }
4466
4494
  }
4495
+ function slugify(text) {
4496
+ return text.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 40);
4497
+ }
4498
+ async function handleCreateGitBranch(opts) {
4499
+ let stopSpinner = null;
4500
+ try {
4501
+ const { requirementService, auth } = getServices2();
4502
+ const isAuthenticated = await auth.isAuthenticated();
4503
+ if (!isAuthenticated) {
4504
+ return {
4505
+ success: false,
4506
+ message: chalk7.red("\u274C Not authenticated. Please run `braingrid login` first.")
4507
+ };
4508
+ }
4509
+ const format = opts.format || "table";
4510
+ if (!["table", "json", "markdown"].includes(format)) {
4511
+ return {
4512
+ success: false,
4513
+ message: chalk7.red(
4514
+ `\u274C Invalid format: ${format}. Supported formats: table, json, markdown`
4515
+ )
4516
+ };
4517
+ }
4518
+ const requirementResult = await workspaceManager.getRequirement(opts.id);
4519
+ if (!requirementResult.success) {
4520
+ return {
4521
+ success: false,
4522
+ message: requirementResult.error
4523
+ };
4524
+ }
4525
+ const requirementId = requirementResult.requirementId;
4526
+ const workspace = await workspaceManager.getProject(opts.project);
4527
+ if (!workspace.success) {
4528
+ return {
4529
+ success: false,
4530
+ message: workspace.error
4531
+ };
4532
+ }
4533
+ const projectId = workspace.projectId;
4534
+ const normalizedId = normalizeRequirementId(requirementId);
4535
+ let branchName = opts.name;
4536
+ if (!branchName) {
4537
+ const user = await auth.getCurrentUser();
4538
+ if (!user) {
4539
+ return {
4540
+ success: false,
4541
+ message: chalk7.red("\u274C Could not get current user for branch name generation")
4542
+ };
4543
+ }
4544
+ const requirement2 = await requirementService.getProjectRequirement(projectId, normalizedId);
4545
+ const username = slugify(user.email.split("@")[0]);
4546
+ const reqShortId = requirement2.short_id || normalizedId;
4547
+ const sluggedName = slugify(requirement2.name);
4548
+ branchName = `${username}/${reqShortId}-${sluggedName}`;
4549
+ }
4550
+ stopSpinner = showSpinner("Creating GitHub branch...", chalk7.gray);
4551
+ const response = await requirementService.createGitBranch(projectId, normalizedId, {
4552
+ branchName,
4553
+ baseBranch: opts.base
4554
+ });
4555
+ stopSpinner();
4556
+ stopSpinner = null;
4557
+ let output;
4558
+ switch (format) {
4559
+ case "json": {
4560
+ output = JSON.stringify(response, null, 2);
4561
+ break;
4562
+ }
4563
+ case "markdown": {
4564
+ output = `# Branch Created
4565
+
4566
+ `;
4567
+ output += `**Branch:** \`${response.branch.name}\`
4568
+ `;
4569
+ output += `**SHA:** \`${response.branch.sha.substring(0, 7)}\`
4570
+
4571
+ `;
4572
+ output += `## Checkout Command
4573
+
4574
+ `;
4575
+ output += `\`\`\`bash
4576
+ git fetch origin && git checkout ${response.branch.name}
4577
+ \`\`\`
4578
+ `;
4579
+ break;
4580
+ }
4581
+ case "table":
4582
+ default: {
4583
+ output = chalk7.green(`\u2705 Created branch: ${response.branch.name}
4584
+
4585
+ `);
4586
+ output += `${chalk7.bold("SHA:")} ${response.branch.sha.substring(0, 7)}
4587
+ `;
4588
+ output += `
4589
+ ${chalk7.dim("To checkout:")} git fetch origin && git checkout ${response.branch.name}`;
4590
+ break;
4591
+ }
4592
+ }
4593
+ return {
4594
+ success: true,
4595
+ message: output,
4596
+ data: response
4597
+ };
4598
+ } catch (error) {
4599
+ if (stopSpinner) {
4600
+ stopSpinner();
4601
+ }
4602
+ return {
4603
+ success: false,
4604
+ message: formatError(error, getResourceContext("requirement", opts.id || "unknown"))
4605
+ };
4606
+ }
4607
+ }
4608
+ async function handleReviewAcceptance(opts) {
4609
+ try {
4610
+ const { requirementService, auth } = getServices2();
4611
+ const isAuthenticated = await auth.isAuthenticated();
4612
+ if (!isAuthenticated) {
4613
+ return {
4614
+ success: false,
4615
+ message: chalk7.red("\u274C Not authenticated. Please run `braingrid login` first.")
4616
+ };
4617
+ }
4618
+ const requirementResult = await workspaceManager.getRequirement(opts.id);
4619
+ if (!requirementResult.success) {
4620
+ return {
4621
+ success: false,
4622
+ message: requirementResult.error
4623
+ };
4624
+ }
4625
+ const requirementId = requirementResult.requirementId;
4626
+ const workspace = await workspaceManager.getProject(opts.project);
4627
+ if (!workspace.success) {
4628
+ return {
4629
+ success: false,
4630
+ message: workspace.error
4631
+ };
4632
+ }
4633
+ const projectId = workspace.projectId;
4634
+ const normalizedId = normalizeRequirementId(requirementId);
4635
+ let prNumber = opts.pr;
4636
+ if (!prNumber) {
4637
+ const { execAsync: execAsync5 } = await import("./command-execution-7JMH2DK4.js");
4638
+ try {
4639
+ const { stdout } = await execAsync5("gh pr view --json number -q .number");
4640
+ const detectedPrNumber = stdout.trim();
4641
+ if (!detectedPrNumber) {
4642
+ throw new Error("No PR number returned from gh CLI");
4643
+ }
4644
+ prNumber = detectedPrNumber;
4645
+ } catch {
4646
+ return {
4647
+ success: false,
4648
+ message: chalk7.red(
4649
+ "\u274C No PR specified and could not detect PR for current branch.\n Use --pr <number> or ensure you have an open PR for this branch."
4650
+ )
4651
+ };
4652
+ }
4653
+ }
4654
+ console.log(chalk7.bold("\n\u{1F50D} AI Code Review\n"));
4655
+ let fullOutput = "";
4656
+ await requirementService.reviewAcceptance(
4657
+ projectId,
4658
+ normalizedId,
4659
+ { pullRequest: prNumber },
4660
+ (chunk) => {
4661
+ process.stdout.write(chunk);
4662
+ fullOutput += chunk;
4663
+ }
4664
+ );
4665
+ console.log("\n");
4666
+ return {
4667
+ success: true,
4668
+ message: "",
4669
+ // Already printed via streaming
4670
+ data: { review: fullOutput }
4671
+ };
4672
+ } catch (error) {
4673
+ return {
4674
+ success: false,
4675
+ message: formatError(error, getResourceContext("requirement", opts.id || "unknown"))
4676
+ };
4677
+ }
4678
+ }
4467
4679
 
4468
4680
  // src/handlers/task.handlers.ts
4469
4681
  import chalk8 from "chalk";
@@ -4512,6 +4724,12 @@ var TaskService = class {
4512
4724
  const headers = this.getHeaders();
4513
4725
  await this.axios.delete(url, { headers });
4514
4726
  }
4727
+ async specifyTask(projectId, requirementId, data) {
4728
+ const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/tasks/specify`;
4729
+ const headers = this.getHeaders();
4730
+ const response = await this.axios.post(url, data, { headers });
4731
+ return response.data;
4732
+ }
4515
4733
  };
4516
4734
 
4517
4735
  // src/handlers/task.handlers.ts
@@ -4977,6 +5195,117 @@ async function handleTaskDelete(id, opts) {
4977
5195
  };
4978
5196
  }
4979
5197
  }
5198
+ async function handleTaskSpecify(opts) {
5199
+ let stopSpinner = null;
5200
+ try {
5201
+ const { taskService, auth } = getServices3();
5202
+ const isAuthenticated = await auth.isAuthenticated();
5203
+ if (!isAuthenticated) {
5204
+ return {
5205
+ success: false,
5206
+ message: chalk8.red("\u274C Not authenticated. Please run `braingrid login` first.")
5207
+ };
5208
+ }
5209
+ const format = opts.format || "table";
5210
+ if (!["table", "json", "markdown"].includes(format)) {
5211
+ return {
5212
+ success: false,
5213
+ message: chalk8.red(
5214
+ `\u274C Invalid format: ${format}. Supported formats: table, json, markdown`
5215
+ )
5216
+ };
5217
+ }
5218
+ const workspace = await workspaceManager.getProject(opts.project);
5219
+ if (!workspace.success) {
5220
+ return {
5221
+ success: false,
5222
+ message: workspace.error
5223
+ };
5224
+ }
5225
+ const projectId = workspace.projectId;
5226
+ const requirementResult = await workspaceManager.getRequirement(opts.requirement);
5227
+ if (!requirementResult.success) {
5228
+ return {
5229
+ success: false,
5230
+ message: requirementResult.error
5231
+ };
5232
+ }
5233
+ const requirementId = requirementResult.requirementId;
5234
+ if (opts.prompt.length < 10) {
5235
+ return {
5236
+ success: false,
5237
+ message: chalk8.red("\u274C Prompt must be at least 10 characters long")
5238
+ };
5239
+ }
5240
+ if (opts.prompt.length > 5e3) {
5241
+ return {
5242
+ success: false,
5243
+ message: chalk8.red("\u274C Prompt must be no more than 5000 characters long")
5244
+ };
5245
+ }
5246
+ stopSpinner = showSpinner("Creating task from prompt...", chalk8.gray);
5247
+ const response = await taskService.specifyTask(projectId, requirementId, {
5248
+ prompt: opts.prompt
5249
+ });
5250
+ stopSpinner();
5251
+ stopSpinner = null;
5252
+ const config = getConfig();
5253
+ let output;
5254
+ switch (format) {
5255
+ case "json": {
5256
+ output = JSON.stringify(response, null, 2);
5257
+ break;
5258
+ }
5259
+ case "markdown": {
5260
+ output = formatTaskSpecifyMarkdown(response, config.apiUrl);
5261
+ break;
5262
+ }
5263
+ case "table":
5264
+ default: {
5265
+ output = formatTaskSpecifyTable(response);
5266
+ break;
5267
+ }
5268
+ }
5269
+ return {
5270
+ success: true,
5271
+ message: output,
5272
+ data: response
5273
+ };
5274
+ } catch (error) {
5275
+ if (stopSpinner) {
5276
+ stopSpinner();
5277
+ }
5278
+ return {
5279
+ success: false,
5280
+ message: formatError(error, "specifying task")
5281
+ };
5282
+ }
5283
+ }
5284
+ function formatTaskSpecifyTable(response) {
5285
+ const task2 = response.task;
5286
+ const lines = [];
5287
+ lines.push(chalk8.green(`\u2705 Created task ${response.requirement_short_id}/TASK-${task2.number}`));
5288
+ lines.push("");
5289
+ lines.push(`${chalk8.bold("Title:")} ${task2.title}`);
5290
+ lines.push(`${chalk8.bold("Status:")} ${task2.status}`);
5291
+ return lines.join("\n");
5292
+ }
5293
+ function formatTaskSpecifyMarkdown(response, _apiUrl) {
5294
+ const task2 = response.task;
5295
+ const lines = [];
5296
+ lines.push(`# Task ${response.requirement_short_id}/TASK-${task2.number}: ${task2.title}`);
5297
+ lines.push("");
5298
+ lines.push(`**Status:** ${task2.status}`);
5299
+ lines.push(`**Requirement:** ${response.requirement_short_id}`);
5300
+ lines.push(`**Project:** ${response.project_short_id}`);
5301
+ lines.push("");
5302
+ if (task2.content) {
5303
+ lines.push("## Content");
5304
+ lines.push("");
5305
+ lines.push(task2.content);
5306
+ }
5307
+ return lines.join("\n");
5308
+ }
4980
5309
 
4981
5310
  // src/handlers/auth.handlers.ts
4982
5311
  import chalk9 from "chalk";
@@ -5332,7 +5661,7 @@ var GitHubService = class {
5332
5661
  import { exec as exec2 } from "child_process";
5333
5662
  import { promisify as promisify2 } from "util";
5334
5663
  import chalk11 from "chalk";
5335
- var execAsync2 = promisify2(exec2);
5664
+ var execAsync3 = promisify2(exec2);
5336
5665
  async function isGitInstalled() {
5337
5666
  return isCliInstalled("git");
5338
5667
  }
@@ -5345,7 +5674,7 @@ async function getGitVersion() {
5345
5674
  async function installViaHomebrew() {
5346
5675
  console.log(chalk11.blue("\u{1F4E6} Installing Git via Homebrew..."));
5347
5676
  try {
5348
- await execAsync2("brew install git", {
5677
+ await execAsync3("brew install git", {
5349
5678
  timeout: 3e5
5350
5679
  // 5 minutes
5351
5680
  });
@@ -5373,7 +5702,7 @@ async function installViaXcodeSelect() {
5373
5702
  console.log(chalk11.blue("\u{1F4E6} Installing Git via Xcode Command Line Tools..."));
5374
5703
  console.log(chalk11.dim('A system dialog will appear - click "Install" to continue.\n'));
5375
5704
  try {
5376
- await execAsync2("xcode-select --install", {
5705
+ await execAsync3("xcode-select --install", {
5377
5706
  timeout: 6e5
5378
5707
  // 10 minutes (user interaction required)
5379
5708
  });
@@ -5414,7 +5743,7 @@ async function installGitWindows() {
5414
5743
  }
5415
5744
  console.log(chalk11.blue("\u{1F4E6} Installing Git via winget..."));
5416
5745
  try {
5417
- await execAsync2("winget install --id Git.Git -e --source winget --silent", {
5746
+ await execAsync3("winget install --id Git.Git -e --source winget --silent", {
5418
5747
  timeout: 3e5
5419
5748
  // 5 minutes
5420
5749
  });
@@ -5469,7 +5798,7 @@ async function installGitLinux() {
5469
5798
  message: chalk11.red(`\u274C Unsupported package manager: ${packageManager.name}`)
5470
5799
  };
5471
5800
  }
5472
- await execAsync2(installCommand, {
5801
+ await execAsync3(installCommand, {
5473
5802
  timeout: 3e5
5474
5803
  // 5 minutes
5475
5804
  });
@@ -5671,28 +6000,6 @@ import { select as select2 } from "@inquirer/prompts";
5671
6000
  import * as path4 from "path";
5672
6001
  import * as fs4 from "fs/promises";
5673
6002
 
5674
- // src/utils/command-execution.ts
5675
- import { exec as exec3, spawn } from "child_process";
5676
- import { promisify as promisify3 } from "util";
5677
- var execAsyncReal = promisify3(exec3);
5678
- async function execAsync3(command, options, isTestMode = false, mockExecHandler) {
5679
- if (isTestMode && mockExecHandler) {
5680
- return mockExecHandler(command);
5681
- }
5682
- const defaultOptions = {
5683
- maxBuffer: 1024 * 1024 * 10,
5684
- // 10MB default
5685
- timeout: 3e5,
5686
- // 5 minutes
5687
- ...options
5688
- };
5689
- if (command.includes("claude")) {
5690
- defaultOptions.maxBuffer = 1024 * 1024 * 50;
5691
- defaultOptions.timeout = 6e5;
5692
- }
5693
- return execAsyncReal(command, defaultOptions);
5694
- }
5695
-
5696
6003
  // src/services/setup-service.ts
5697
6004
  import * as fs3 from "fs/promises";
5698
6005
  import * as path3 from "path";
@@ -5743,7 +6050,7 @@ async function fetchFileFromGitHub(path6) {
5743
6050
  return withRetry(async () => {
5744
6051
  try {
5745
6052
  const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
5746
- const { stdout } = await execAsync3(command);
6053
+ const { stdout } = await execAsync(command);
5747
6054
  const response = JSON.parse(stdout);
5748
6055
  if (response.type !== "file") {
5749
6056
  throw new Error(`Path ${path6} is not a file`);
@@ -5766,7 +6073,7 @@ async function listGitHubDirectory(path6) {
5766
6073
  return withRetry(async () => {
5767
6074
  try {
5768
6075
  const command = `gh api repos/${GITHUB_OWNER}/${GITHUB_REPO}/contents/${path6}`;
5769
- const { stdout } = await execAsync3(command);
6076
+ const { stdout } = await execAsync(command);
5770
6077
  const response = JSON.parse(stdout);
5771
6078
  if (!Array.isArray(response)) {
5772
6079
  throw new Error(`Path ${path6} is not a directory`);
@@ -5899,7 +6206,7 @@ async function fileExists(filePath) {
5899
6206
  }
5900
6207
  async function checkPrerequisites() {
5901
6208
  try {
5902
- await execAsync3("gh --version");
6209
+ await execAsync("gh --version");
5903
6210
  } catch {
5904
6211
  return {
5905
6212
  success: false,
@@ -5907,7 +6214,7 @@ async function checkPrerequisites() {
5907
6214
  };
5908
6215
  }
5909
6216
  try {
5910
- await execAsync3("gh auth status");
6217
+ await execAsync("gh auth status");
5911
6218
  } catch {
5912
6219
  return {
5913
6220
  success: false,
@@ -6281,10 +6588,10 @@ async function checkAndShowUpdateWarning() {
6281
6588
  import { access as access3 } from "fs/promises";
6282
6589
 
6283
6590
  // src/utils/github-repo.ts
6284
- import { exec as exec4 } from "child_process";
6285
- import { promisify as promisify4 } from "util";
6591
+ import { exec as exec3 } from "child_process";
6592
+ import { promisify as promisify3 } from "util";
6286
6593
  import path5 from "path";
6287
- var execAsync4 = promisify4(exec4);
6594
+ var execAsync4 = promisify3(exec3);
6288
6595
  async function initGitRepo() {
6289
6596
  try {
6290
6597
  await execAsync4("git init");
@@ -7293,6 +7600,30 @@ requirement.command("build [id]").description(
7293
7600
  process.exit(1);
7294
7601
  }
7295
7602
  });
7603
+ requirement.command("create-branch [id]").description(
7604
+ "Create a GitHub branch for a requirement (auto-detects ID from git branch if not provided)"
7605
+ ).option(
7606
+ "-p, --project <id>",
7607
+ "project ID (auto-detects from .braingrid/project.json if not provided)"
7608
+ ).option("--name <name>", "branch name (auto-generates {username}/REQ-123-slug if not provided)").option("--base <branch>", "base branch to create from (defaults to repository default branch)").option("--format <format>", "output format (table, json, markdown)", "table").action(async (id, opts) => {
7609
+ const result = await handleCreateGitBranch({ ...opts, id });
7610
+ console.log(result.message);
7611
+ if (!result.success) {
7612
+ process.exit(1);
7613
+ }
7614
+ });
7615
+ requirement.command("review [id]").description("Run AI code review for a requirement PR (auto-detects ID and PR from git branch)").option(
7616
+ "-p, --project <id>",
7617
+ "project ID (auto-detects from .braingrid/project.json if not provided)"
7618
+ ).option("--pr <number>", "PR number or URL (auto-detects from current branch if not provided)").action(async (id, opts) => {
7619
+ const result = await handleReviewAcceptance({ ...opts, id });
7620
+ if (result.message) {
7621
+ console.log(result.message);
7622
+ }
7623
+ if (!result.success) {
7624
+ process.exit(1);
7625
+ }
7626
+ });
7296
7627
  var task = program.command("task").description("Manage tasks");
7297
7628
  task.command("list").description("List tasks for a requirement").option("-r, --requirement <id>", "requirement ID (REQ-456, auto-detects project if initialized)").option("-p, --project <id>", "project ID (PROJ-123, optional if project is initialized)").option("--format <format>", "output format (table, json, xml, markdown)", "markdown").option("--page <page>", "page number for pagination", "1").option("--limit <limit>", "number of tasks per page", "20").action(async (opts) => {
7298
7629
  const result = await handleTaskList(opts);
@@ -7336,6 +7667,16 @@ task.command("delete <id>").description("Delete a task").option("-r, --requireme
7336
7667
  process.exit(1);
7337
7668
  }
7338
7669
  });
7670
+ task.command("specify").description("Create a single task from a prompt using AI").option(
7671
+ "-r, --requirement <id>",
7672
+ "requirement ID (REQ-456, auto-detects from git branch if not provided)"
7673
+ ).option("-p, --project <id>", "project ID (PROJ-123, optional if project is initialized)").requiredOption("--prompt <prompt>", "task description (10-5000 characters)").option("--format <format>", "output format (table, json, markdown)", "table").action(async (opts) => {
7674
+ const result = await handleTaskSpecify(opts);
7675
+ console.log(result.message);
7676
+ if (!result.success) {
7677
+ process.exit(1);
7678
+ }
7679
+ });
7339
7680
  program.command("completion [shell]").description("Generate shell completion script (bash, zsh)").option("--setup", "automatically install completion for current shell").option("-s, --shell <shell>", "shell type (bash, zsh)").action(async (shell, opts) => {
7340
7681
  const result = await handleCompletion(shell, opts);
7341
7682
  console.log(result.message);