@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/CHANGELOG.md +37 -0
- package/dist/chunk-6GC3UJCM.js +230 -0
- package/dist/chunk-6GC3UJCM.js.map +1 -0
- package/dist/cli.js +384 -43
- package/dist/cli.js.map +1 -1
- package/dist/command-execution-7JMH2DK4.js +11 -0
- package/dist/command-execution-7JMH2DK4.js.map +1 -0
- package/package.json +1 -1
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.
|
|
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, "
|
|
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
|
|
1843
|
+
var execAsync2 = promisify(exec);
|
|
1841
1844
|
async function isGitRepository() {
|
|
1842
1845
|
try {
|
|
1843
|
-
const { stdout } = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6285
|
-
import { promisify as
|
|
6591
|
+
import { exec as exec3 } from "child_process";
|
|
6592
|
+
import { promisify as promisify3 } from "util";
|
|
6286
6593
|
import path5 from "path";
|
|
6287
|
-
var execAsync4 =
|
|
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);
|