@braingrid/cli 0.2.26 → 0.2.28
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 +22 -0
- package/dist/cli.js +422 -8
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.28] - 2025-01-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Tag management CLI commands for requirements**
|
|
15
|
+
- `requirement tag list [id]` - List all tags for a requirement
|
|
16
|
+
- `requirement tag add [id] --name <name> --color <hex>` - Add a tag with name and hex color
|
|
17
|
+
- `requirement tag remove [id] --name <name>` - Remove a tag by name
|
|
18
|
+
- All commands support auto-detection of requirement ID from git branch
|
|
19
|
+
- Multiple output formats supported (table, json, xml, markdown)
|
|
20
|
+
- Hex color validation for #RGB and #RRGGBB formats
|
|
21
|
+
|
|
22
|
+
## [0.2.27] - 2025-01-27
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Requirement tagging support**
|
|
27
|
+
- New `--tags` option for `requirement create` and `specify` commands
|
|
28
|
+
- Accepts comma-separated tags (max 5 per requirement)
|
|
29
|
+
- Tags are validated, trimmed, and empty values filtered
|
|
30
|
+
- Tags displayed in all output formats (table, markdown, XML, JSON)
|
|
31
|
+
|
|
10
32
|
## [0.2.26] - 2025-01-20
|
|
11
33
|
|
|
12
34
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -25,7 +25,7 @@ import axios from "axios";
|
|
|
25
25
|
|
|
26
26
|
// src/build-config.ts
|
|
27
27
|
var BUILD_ENV = true ? "production" : process.env.NODE_ENV === "test" ? "development" : "production";
|
|
28
|
-
var CLI_VERSION = true ? "0.2.
|
|
28
|
+
var CLI_VERSION = true ? "0.2.28" : "0.0.0-test";
|
|
29
29
|
var PRODUCTION_CONFIG = {
|
|
30
30
|
apiUrl: "https://app.braingrid.ai",
|
|
31
31
|
workosAuthUrl: "https://auth.braingrid.ai",
|
|
@@ -2744,6 +2744,11 @@ function formatRequirementOutput(requirement2, options) {
|
|
|
2744
2744
|
}
|
|
2745
2745
|
message += `${chalk5.bold("Status:")} ${requirement2.status}
|
|
2746
2746
|
`;
|
|
2747
|
+
if (requirement2.tags && requirement2.tags.length > 0) {
|
|
2748
|
+
const tagNames = requirement2.tags.map((tag) => tag.name).join(", ");
|
|
2749
|
+
message += `${chalk5.bold("Tags:")} ${tagNames}
|
|
2750
|
+
`;
|
|
2751
|
+
}
|
|
2747
2752
|
if (requirement2.assignee) {
|
|
2748
2753
|
const assigneeName = requirement2.assignee.first_name || requirement2.assignee.last_name ? `${requirement2.assignee.first_name || ""} ${requirement2.assignee.last_name || ""}`.trim() : requirement2.assignee.email;
|
|
2749
2754
|
message += `${chalk5.bold("Assigned to:")} ${assigneeName} (${requirement2.assignee.email})
|
|
@@ -2890,15 +2895,16 @@ function formatRequirementListMarkdown(requirements, pagination) {
|
|
|
2890
2895
|
md += "_No requirements found._\n";
|
|
2891
2896
|
return md;
|
|
2892
2897
|
}
|
|
2893
|
-
md += "| Short ID | Status | Name | Branch | Progress |\n";
|
|
2894
|
-
md += "
|
|
2898
|
+
md += "| Short ID | Status | Name | Branch | Tags | Progress |\n";
|
|
2899
|
+
md += "|----------|--------|------|--------|------|----------|\n";
|
|
2895
2900
|
for (const req of requirements) {
|
|
2896
2901
|
const shortId = req.short_id || req.id.slice(0, 11);
|
|
2897
2902
|
const status = req.status;
|
|
2898
2903
|
const name = req.name;
|
|
2899
2904
|
const branch = req.branch || "N/A";
|
|
2905
|
+
const tags = req.tags && req.tags.length > 0 ? req.tags.map((t) => t.name).join(", ") : "-";
|
|
2900
2906
|
const progress = req.task_progress ? `${req.task_progress.progress_percentage}%` : "N/A";
|
|
2901
|
-
md += `| ${shortId} | ${status} | ${name} | ${branch} | ${progress} |
|
|
2907
|
+
md += `| ${shortId} | ${status} | ${name} | ${branch} | ${tags} | ${progress} |
|
|
2902
2908
|
`;
|
|
2903
2909
|
}
|
|
2904
2910
|
if (pagination) {
|
|
@@ -2971,6 +2977,15 @@ function formatRequirementListXml(requirements, pagination) {
|
|
|
2971
2977
|
`;
|
|
2972
2978
|
xml += " </assignee>\n";
|
|
2973
2979
|
}
|
|
2980
|
+
if (req.tags && req.tags.length > 0) {
|
|
2981
|
+
xml += ` <tags count="${req.tags.length}">
|
|
2982
|
+
`;
|
|
2983
|
+
for (const tag of req.tags) {
|
|
2984
|
+
xml += ` <tag>${escapeXml(tag.name)}</tag>
|
|
2985
|
+
`;
|
|
2986
|
+
}
|
|
2987
|
+
xml += " </tags>\n";
|
|
2988
|
+
}
|
|
2974
2989
|
xml += ` <created_at>${escapeXml(req.created_at)}</created_at>
|
|
2975
2990
|
`;
|
|
2976
2991
|
xml += ` <updated_at>${escapeXml(req.updated_at)}</updated_at>
|
|
@@ -3087,6 +3102,12 @@ function formatRequirementBuildMarkdown(requirement2, options) {
|
|
|
3087
3102
|
md += `**Status:** ${requirement2.status}
|
|
3088
3103
|
|
|
3089
3104
|
`;
|
|
3105
|
+
if (requirement2.tags && requirement2.tags.length > 0) {
|
|
3106
|
+
const tagNames = requirement2.tags.map((tag) => tag.name).join(", ");
|
|
3107
|
+
md += `**Tags:** ${tagNames}
|
|
3108
|
+
|
|
3109
|
+
`;
|
|
3110
|
+
}
|
|
3090
3111
|
if (requirement2.assignee) {
|
|
3091
3112
|
const assigneeName = requirement2.assignee.first_name || requirement2.assignee.last_name ? `${requirement2.assignee.first_name || ""} ${requirement2.assignee.last_name || ""}`.trim() : requirement2.assignee.email;
|
|
3092
3113
|
md += `**Assigned to:** ${assigneeName} (${requirement2.assignee.email})
|
|
@@ -3223,6 +3244,15 @@ function formatRequirementBuildXml(requirement2) {
|
|
|
3223
3244
|
xml += ` <branch>${escapeXml(requirement2.branch)}</branch>
|
|
3224
3245
|
`;
|
|
3225
3246
|
}
|
|
3247
|
+
if (requirement2.tags && requirement2.tags.length > 0) {
|
|
3248
|
+
xml += ` <tags count="${requirement2.tags.length}">
|
|
3249
|
+
`;
|
|
3250
|
+
for (const tag of requirement2.tags) {
|
|
3251
|
+
xml += ` <tag>${escapeXml(tag.name)}</tag>
|
|
3252
|
+
`;
|
|
3253
|
+
}
|
|
3254
|
+
xml += " </tags>\n";
|
|
3255
|
+
}
|
|
3226
3256
|
if (requirement2.assignee) {
|
|
3227
3257
|
xml += " <assignee>\n";
|
|
3228
3258
|
xml += ` <email>${escapeXml(requirement2.assignee.email)}</email>
|
|
@@ -3974,8 +4004,57 @@ var RequirementService = class {
|
|
|
3974
4004
|
});
|
|
3975
4005
|
});
|
|
3976
4006
|
}
|
|
4007
|
+
// Tag management methods
|
|
4008
|
+
async listRequirementTags(projectId, requirementId) {
|
|
4009
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/tags`;
|
|
4010
|
+
const headers = this.getHeaders();
|
|
4011
|
+
const response = await this.axios.get(url, { headers });
|
|
4012
|
+
return response.data;
|
|
4013
|
+
}
|
|
4014
|
+
async addRequirementTag(projectId, requirementId, tag) {
|
|
4015
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/tags`;
|
|
4016
|
+
const headers = this.getHeaders();
|
|
4017
|
+
const response = await this.axios.post(url, tag, { headers });
|
|
4018
|
+
return response.data;
|
|
4019
|
+
}
|
|
4020
|
+
async removeRequirementTag(projectId, requirementId, tagName) {
|
|
4021
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}/requirements/${requirementId}/tags/${encodeURIComponent(tagName)}`;
|
|
4022
|
+
const headers = this.getHeaders();
|
|
4023
|
+
await this.axios.delete(url, { headers });
|
|
4024
|
+
}
|
|
3977
4025
|
};
|
|
3978
4026
|
|
|
4027
|
+
// src/utils/tag-validation.ts
|
|
4028
|
+
var MAX_TAGS = 5;
|
|
4029
|
+
function validateTags(tagsString) {
|
|
4030
|
+
if (!tagsString || tagsString.trim().length === 0) {
|
|
4031
|
+
return { valid: true, tags: [] };
|
|
4032
|
+
}
|
|
4033
|
+
const tags = tagsString.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
|
|
4034
|
+
if (tags.length > MAX_TAGS) {
|
|
4035
|
+
return {
|
|
4036
|
+
valid: false,
|
|
4037
|
+
error: `Maximum ${MAX_TAGS} tags allowed`
|
|
4038
|
+
};
|
|
4039
|
+
}
|
|
4040
|
+
return { valid: true, tags };
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
// src/utils/color-validation.ts
|
|
4044
|
+
function validateHexColor(color) {
|
|
4045
|
+
if (!color || color.trim().length === 0) {
|
|
4046
|
+
return { valid: false, error: "Color is required" };
|
|
4047
|
+
}
|
|
4048
|
+
const hexPattern = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
4049
|
+
if (!hexPattern.test(color)) {
|
|
4050
|
+
return {
|
|
4051
|
+
valid: false,
|
|
4052
|
+
error: "Invalid hex color format. Use #RGB or #RRGGBB (e.g., #FF0000 or #F00)"
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
return { valid: true };
|
|
4056
|
+
}
|
|
4057
|
+
|
|
3979
4058
|
// src/handlers/requirement.handlers.ts
|
|
3980
4059
|
function getServices2() {
|
|
3981
4060
|
const config = getConfig();
|
|
@@ -4203,11 +4282,23 @@ async function handleRequirementCreate(opts) {
|
|
|
4203
4282
|
};
|
|
4204
4283
|
}
|
|
4205
4284
|
}
|
|
4285
|
+
let validatedTags;
|
|
4286
|
+
if (opts.tags) {
|
|
4287
|
+
const tagResult = validateTags(opts.tags);
|
|
4288
|
+
if (!tagResult.valid) {
|
|
4289
|
+
return {
|
|
4290
|
+
success: false,
|
|
4291
|
+
message: chalk7.red(`\u274C ${tagResult.error}`)
|
|
4292
|
+
};
|
|
4293
|
+
}
|
|
4294
|
+
validatedTags = tagResult.tags;
|
|
4295
|
+
}
|
|
4206
4296
|
stopSpinner = showSpinner("Creating requirement", chalk7.gray);
|
|
4207
4297
|
const requirement2 = await requirementService.createProjectRequirement(projectId, {
|
|
4208
4298
|
name: opts.name,
|
|
4209
4299
|
content: opts.content || null,
|
|
4210
|
-
assigned_to: opts.assignedTo || null
|
|
4300
|
+
assigned_to: opts.assignedTo || null,
|
|
4301
|
+
tags: validatedTags
|
|
4211
4302
|
});
|
|
4212
4303
|
stopSpinner();
|
|
4213
4304
|
stopSpinner = null;
|
|
@@ -4280,9 +4371,21 @@ async function handleRequirementSpecify(opts) {
|
|
|
4280
4371
|
message: chalk7.red("\u274C Prompt must be no more than 5000 characters long")
|
|
4281
4372
|
};
|
|
4282
4373
|
}
|
|
4374
|
+
let validatedTags;
|
|
4375
|
+
if (opts.tags) {
|
|
4376
|
+
const tagResult = validateTags(opts.tags);
|
|
4377
|
+
if (!tagResult.valid) {
|
|
4378
|
+
return {
|
|
4379
|
+
success: false,
|
|
4380
|
+
message: chalk7.red(`\u274C ${tagResult.error}`)
|
|
4381
|
+
};
|
|
4382
|
+
}
|
|
4383
|
+
validatedTags = tagResult.tags;
|
|
4384
|
+
}
|
|
4283
4385
|
stopSpinner = showSpinner("Specifying requirement...");
|
|
4284
4386
|
const requirement2 = await requirementService.specifyRequirement(projectId, {
|
|
4285
|
-
prompt: opts.prompt
|
|
4387
|
+
prompt: opts.prompt,
|
|
4388
|
+
tags: validatedTags
|
|
4286
4389
|
});
|
|
4287
4390
|
stopSpinner();
|
|
4288
4391
|
stopSpinner = null;
|
|
@@ -4794,6 +4897,286 @@ async function handleReviewAcceptance(opts) {
|
|
|
4794
4897
|
};
|
|
4795
4898
|
}
|
|
4796
4899
|
}
|
|
4900
|
+
async function handleRequirementTagList(opts) {
|
|
4901
|
+
let stopSpinner = null;
|
|
4902
|
+
try {
|
|
4903
|
+
const { requirementService, auth } = getServices2();
|
|
4904
|
+
const isAuthenticated = await auth.isAuthenticated();
|
|
4905
|
+
if (!isAuthenticated) {
|
|
4906
|
+
return {
|
|
4907
|
+
success: false,
|
|
4908
|
+
message: chalk7.red("\u274C Not authenticated. Please run `braingrid login` first.")
|
|
4909
|
+
};
|
|
4910
|
+
}
|
|
4911
|
+
const format = opts.format || "table";
|
|
4912
|
+
if (!["table", "json", "xml", "markdown"].includes(format)) {
|
|
4913
|
+
return {
|
|
4914
|
+
success: false,
|
|
4915
|
+
message: chalk7.red(
|
|
4916
|
+
`\u274C Invalid format: ${format}. Supported formats: table, json, xml, markdown`
|
|
4917
|
+
)
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4920
|
+
const requirementResult = await workspaceManager.getRequirement(opts.requirementId);
|
|
4921
|
+
if (!requirementResult.success) {
|
|
4922
|
+
return {
|
|
4923
|
+
success: false,
|
|
4924
|
+
message: requirementResult.error
|
|
4925
|
+
};
|
|
4926
|
+
}
|
|
4927
|
+
const requirementId = requirementResult.requirementId;
|
|
4928
|
+
const workspace = await workspaceManager.getProject(opts.project);
|
|
4929
|
+
if (!workspace.success) {
|
|
4930
|
+
return {
|
|
4931
|
+
success: false,
|
|
4932
|
+
message: workspace.error
|
|
4933
|
+
};
|
|
4934
|
+
}
|
|
4935
|
+
const projectId = workspace.projectId;
|
|
4936
|
+
const normalizedId = normalizeRequirementId(requirementId);
|
|
4937
|
+
stopSpinner = showSpinner("Loading tags", chalk7.gray);
|
|
4938
|
+
const tags = await requirementService.listRequirementTags(projectId, normalizedId);
|
|
4939
|
+
stopSpinner();
|
|
4940
|
+
stopSpinner = null;
|
|
4941
|
+
let output;
|
|
4942
|
+
switch (format) {
|
|
4943
|
+
case "json": {
|
|
4944
|
+
output = JSON.stringify(tags, null, 2);
|
|
4945
|
+
break;
|
|
4946
|
+
}
|
|
4947
|
+
case "xml": {
|
|
4948
|
+
output = '<?xml version="1.0" encoding="UTF-8"?>\n<tags>\n';
|
|
4949
|
+
for (const tag of tags) {
|
|
4950
|
+
output += ` <tag>
|
|
4951
|
+
`;
|
|
4952
|
+
output += ` <name>${tag.name}</name>
|
|
4953
|
+
`;
|
|
4954
|
+
output += ` <color>${tag.color || ""}</color>
|
|
4955
|
+
`;
|
|
4956
|
+
output += ` <id>${tag.id}</id>
|
|
4957
|
+
`;
|
|
4958
|
+
output += ` </tag>
|
|
4959
|
+
`;
|
|
4960
|
+
}
|
|
4961
|
+
output += "</tags>";
|
|
4962
|
+
break;
|
|
4963
|
+
}
|
|
4964
|
+
case "markdown": {
|
|
4965
|
+
output = `# Tags for ${normalizedId}
|
|
4966
|
+
|
|
4967
|
+
`;
|
|
4968
|
+
if (tags.length === 0) {
|
|
4969
|
+
output += "_No tags found_\n";
|
|
4970
|
+
} else {
|
|
4971
|
+
output += "| Name | Color |\n";
|
|
4972
|
+
output += "|------|-------|\n";
|
|
4973
|
+
for (const tag of tags) {
|
|
4974
|
+
output += `| ${tag.name} | ${tag.color || "N/A"} |
|
|
4975
|
+
`;
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
break;
|
|
4979
|
+
}
|
|
4980
|
+
case "table":
|
|
4981
|
+
default: {
|
|
4982
|
+
if (tags.length === 0) {
|
|
4983
|
+
return {
|
|
4984
|
+
success: true,
|
|
4985
|
+
message: chalk7.yellow(`No tags found for ${normalizedId}.`),
|
|
4986
|
+
data: tags
|
|
4987
|
+
};
|
|
4988
|
+
}
|
|
4989
|
+
output = chalk7.bold(`\u{1F3F7}\uFE0F Tags for ${normalizedId}
|
|
4990
|
+
|
|
4991
|
+
`);
|
|
4992
|
+
output += "Name Color\n";
|
|
4993
|
+
output += "\u2500".repeat(40) + "\n";
|
|
4994
|
+
for (const tag of tags) {
|
|
4995
|
+
const name = tag.name.padEnd(19);
|
|
4996
|
+
const color = tag.color || "N/A";
|
|
4997
|
+
output += `${name} ${color}
|
|
4998
|
+
`;
|
|
4999
|
+
}
|
|
5000
|
+
break;
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
return {
|
|
5004
|
+
success: true,
|
|
5005
|
+
message: output,
|
|
5006
|
+
data: tags
|
|
5007
|
+
};
|
|
5008
|
+
} catch (error) {
|
|
5009
|
+
if (stopSpinner) {
|
|
5010
|
+
stopSpinner();
|
|
5011
|
+
}
|
|
5012
|
+
return {
|
|
5013
|
+
success: false,
|
|
5014
|
+
message: formatError(
|
|
5015
|
+
error,
|
|
5016
|
+
getResourceContext("requirement", opts.requirementId || "unknown")
|
|
5017
|
+
)
|
|
5018
|
+
};
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
async function handleRequirementTagAdd(opts) {
|
|
5022
|
+
let stopSpinner = null;
|
|
5023
|
+
try {
|
|
5024
|
+
const { requirementService, auth } = getServices2();
|
|
5025
|
+
const isAuthenticated = await auth.isAuthenticated();
|
|
5026
|
+
if (!isAuthenticated) {
|
|
5027
|
+
return {
|
|
5028
|
+
success: false,
|
|
5029
|
+
message: chalk7.red("\u274C Not authenticated. Please run `braingrid login` first.")
|
|
5030
|
+
};
|
|
5031
|
+
}
|
|
5032
|
+
if (!opts.name || opts.name.trim().length === 0) {
|
|
5033
|
+
return {
|
|
5034
|
+
success: false,
|
|
5035
|
+
message: chalk7.red("\u274C Tag name is required")
|
|
5036
|
+
};
|
|
5037
|
+
}
|
|
5038
|
+
const colorResult = validateHexColor(opts.color);
|
|
5039
|
+
if (!colorResult.valid) {
|
|
5040
|
+
return {
|
|
5041
|
+
success: false,
|
|
5042
|
+
message: chalk7.red(`\u274C ${colorResult.error}`)
|
|
5043
|
+
};
|
|
5044
|
+
}
|
|
5045
|
+
const requirementResult = await workspaceManager.getRequirement(opts.requirementId);
|
|
5046
|
+
if (!requirementResult.success) {
|
|
5047
|
+
return {
|
|
5048
|
+
success: false,
|
|
5049
|
+
message: requirementResult.error
|
|
5050
|
+
};
|
|
5051
|
+
}
|
|
5052
|
+
const requirementId = requirementResult.requirementId;
|
|
5053
|
+
const workspace = await workspaceManager.getProject(opts.project);
|
|
5054
|
+
if (!workspace.success) {
|
|
5055
|
+
return {
|
|
5056
|
+
success: false,
|
|
5057
|
+
message: workspace.error
|
|
5058
|
+
};
|
|
5059
|
+
}
|
|
5060
|
+
const projectId = workspace.projectId;
|
|
5061
|
+
const normalizedId = normalizeRequirementId(requirementId);
|
|
5062
|
+
stopSpinner = showSpinner("Adding tag", chalk7.gray);
|
|
5063
|
+
const response = await requirementService.addRequirementTag(projectId, normalizedId, {
|
|
5064
|
+
name: opts.name.trim(),
|
|
5065
|
+
color: opts.color
|
|
5066
|
+
});
|
|
5067
|
+
stopSpinner();
|
|
5068
|
+
stopSpinner = null;
|
|
5069
|
+
return {
|
|
5070
|
+
success: true,
|
|
5071
|
+
message: chalk7.green(
|
|
5072
|
+
`\u2705 Added tag "${response.tag.name}" (${response.tag.color}) to ${normalizedId}`
|
|
5073
|
+
),
|
|
5074
|
+
data: response
|
|
5075
|
+
};
|
|
5076
|
+
} catch (error) {
|
|
5077
|
+
if (stopSpinner) {
|
|
5078
|
+
stopSpinner();
|
|
5079
|
+
}
|
|
5080
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
5081
|
+
const axiosError = error;
|
|
5082
|
+
if (axiosError.response?.status === 400) {
|
|
5083
|
+
const errorData = axiosError.response.data;
|
|
5084
|
+
if (errorData?.code === "MAX_TAGS_EXCEEDED") {
|
|
5085
|
+
return {
|
|
5086
|
+
success: false,
|
|
5087
|
+
message: chalk7.red("\u274C Maximum 5 tags allowed per requirement")
|
|
5088
|
+
};
|
|
5089
|
+
}
|
|
5090
|
+
if (errorData?.code === "TAG_ALREADY_ASSOCIATED") {
|
|
5091
|
+
return {
|
|
5092
|
+
success: false,
|
|
5093
|
+
message: chalk7.red(`\u274C Tag "${opts.name}" is already associated with this requirement`)
|
|
5094
|
+
};
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
return {
|
|
5099
|
+
success: false,
|
|
5100
|
+
message: formatError(
|
|
5101
|
+
error,
|
|
5102
|
+
getResourceContext("requirement", opts.requirementId || "unknown")
|
|
5103
|
+
)
|
|
5104
|
+
};
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
async function handleRequirementTagRemove(opts) {
|
|
5108
|
+
let stopSpinner = null;
|
|
5109
|
+
try {
|
|
5110
|
+
const { requirementService, auth } = getServices2();
|
|
5111
|
+
const isAuthenticated = await auth.isAuthenticated();
|
|
5112
|
+
if (!isAuthenticated) {
|
|
5113
|
+
return {
|
|
5114
|
+
success: false,
|
|
5115
|
+
message: chalk7.red("\u274C Not authenticated. Please run `braingrid login` first.")
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
if (!opts.name || opts.name.trim().length === 0) {
|
|
5119
|
+
return {
|
|
5120
|
+
success: false,
|
|
5121
|
+
message: chalk7.red("\u274C Tag name is required")
|
|
5122
|
+
};
|
|
5123
|
+
}
|
|
5124
|
+
const requirementResult = await workspaceManager.getRequirement(opts.requirementId);
|
|
5125
|
+
if (!requirementResult.success) {
|
|
5126
|
+
return {
|
|
5127
|
+
success: false,
|
|
5128
|
+
message: requirementResult.error
|
|
5129
|
+
};
|
|
5130
|
+
}
|
|
5131
|
+
const requirementId = requirementResult.requirementId;
|
|
5132
|
+
const workspace = await workspaceManager.getProject(opts.project);
|
|
5133
|
+
if (!workspace.success) {
|
|
5134
|
+
return {
|
|
5135
|
+
success: false,
|
|
5136
|
+
message: workspace.error
|
|
5137
|
+
};
|
|
5138
|
+
}
|
|
5139
|
+
const projectId = workspace.projectId;
|
|
5140
|
+
const normalizedId = normalizeRequirementId(requirementId);
|
|
5141
|
+
stopSpinner = showSpinner("Removing tag", chalk7.gray);
|
|
5142
|
+
await requirementService.removeRequirementTag(projectId, normalizedId, opts.name.trim());
|
|
5143
|
+
stopSpinner();
|
|
5144
|
+
stopSpinner = null;
|
|
5145
|
+
return {
|
|
5146
|
+
success: true,
|
|
5147
|
+
message: chalk7.green(`\u2705 Removed tag "${opts.name}" from ${normalizedId}`)
|
|
5148
|
+
};
|
|
5149
|
+
} catch (error) {
|
|
5150
|
+
if (stopSpinner) {
|
|
5151
|
+
stopSpinner();
|
|
5152
|
+
}
|
|
5153
|
+
if (error && typeof error === "object" && "response" in error) {
|
|
5154
|
+
const axiosError = error;
|
|
5155
|
+
if (axiosError.response?.status === 404) {
|
|
5156
|
+
const errorData = axiosError.response.data;
|
|
5157
|
+
if (errorData?.code === "TAG_NOT_FOUND") {
|
|
5158
|
+
return {
|
|
5159
|
+
success: false,
|
|
5160
|
+
message: chalk7.red(`\u274C Tag "${opts.name}" not found`)
|
|
5161
|
+
};
|
|
5162
|
+
}
|
|
5163
|
+
if (errorData?.code === "ASSOCIATION_NOT_FOUND") {
|
|
5164
|
+
return {
|
|
5165
|
+
success: false,
|
|
5166
|
+
message: chalk7.red(`\u274C Tag "${opts.name}" is not associated with this requirement`)
|
|
5167
|
+
};
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
return {
|
|
5172
|
+
success: false,
|
|
5173
|
+
message: formatError(
|
|
5174
|
+
error,
|
|
5175
|
+
getResourceContext("requirement", opts.requirementId || "unknown")
|
|
5176
|
+
)
|
|
5177
|
+
};
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
4797
5180
|
|
|
4798
5181
|
// src/handlers/task.handlers.ts
|
|
4799
5182
|
import chalk8 from "chalk";
|
|
@@ -7610,7 +7993,7 @@ program.command("update").description("Update BrainGrid CLI to the latest versio
|
|
|
7610
7993
|
program.command("specify").description("Create AI-refined requirement from prompt").option(
|
|
7611
7994
|
"-p, --project <id>",
|
|
7612
7995
|
"project ID (auto-detects from .braingrid/project.json if not provided)"
|
|
7613
|
-
).requiredOption("--prompt <prompt>", "requirement description (10-5000 characters)").option("--format <format>", "output format (table, json, xml, markdown)", "table").action(async (opts) => {
|
|
7996
|
+
).requiredOption("--prompt <prompt>", "requirement description (10-5000 characters)").option("-t, --tags <tags>", "comma-separated tags (max 5)").option("--format <format>", "output format (table, json, xml, markdown)", "table").action(async (opts) => {
|
|
7614
7997
|
const result = await handleRequirementSpecify(opts);
|
|
7615
7998
|
console.log(result.message);
|
|
7616
7999
|
if (!result.success) {
|
|
@@ -7695,7 +8078,7 @@ requirement.command("show [id]").description("Show requirement details (auto-det
|
|
|
7695
8078
|
requirement.command("create").description("Create a new requirement").option(
|
|
7696
8079
|
"-p, --project <id>",
|
|
7697
8080
|
"project ID (auto-detects from .braingrid/project.json if not provided)"
|
|
7698
|
-
).requiredOption("-n, --name <name>", "requirement name").option("-c, --content <content>", "requirement content/description").option("-a, --assigned-to <uuid>", "user UUID to assign the requirement to").action(async (opts) => {
|
|
8081
|
+
).requiredOption("-n, --name <name>", "requirement name").option("-c, --content <content>", "requirement content/description").option("-a, --assigned-to <uuid>", "user UUID to assign the requirement to").option("-t, --tags <tags>", "comma-separated tags (max 5)").action(async (opts) => {
|
|
7699
8082
|
const result = await handleRequirementCreate(opts);
|
|
7700
8083
|
console.log(result.message);
|
|
7701
8084
|
if (!result.success) {
|
|
@@ -7767,6 +8150,37 @@ requirement.command("review [id]").description("Run AI code review for a require
|
|
|
7767
8150
|
process.exit(1);
|
|
7768
8151
|
}
|
|
7769
8152
|
});
|
|
8153
|
+
var requirementTag = requirement.command("tag").description("Manage requirement tags");
|
|
8154
|
+
requirementTag.command("list [id]").description("List tags for a requirement (auto-detects ID from git branch if not provided)").option(
|
|
8155
|
+
"-p, --project <id>",
|
|
8156
|
+
"project ID (auto-detects from .braingrid/project.json if not provided)"
|
|
8157
|
+
).option("--format <format>", "output format (table, json, xml, markdown)", "table").action(async (id, opts) => {
|
|
8158
|
+
const result = await handleRequirementTagList({ ...opts, requirementId: id });
|
|
8159
|
+
console.log(result.message);
|
|
8160
|
+
if (!result.success) {
|
|
8161
|
+
process.exit(1);
|
|
8162
|
+
}
|
|
8163
|
+
});
|
|
8164
|
+
requirementTag.command("add [id]").description("Add a tag to a requirement (auto-detects ID from git branch if not provided)").option(
|
|
8165
|
+
"-p, --project <id>",
|
|
8166
|
+
"project ID (auto-detects from .braingrid/project.json if not provided)"
|
|
8167
|
+
).requiredOption("--name <name>", "tag name").requiredOption("--color <hex>", "hex color code (e.g., #FF0000)").action(async (id, opts) => {
|
|
8168
|
+
const result = await handleRequirementTagAdd({ ...opts, requirementId: id });
|
|
8169
|
+
console.log(result.message);
|
|
8170
|
+
if (!result.success) {
|
|
8171
|
+
process.exit(1);
|
|
8172
|
+
}
|
|
8173
|
+
});
|
|
8174
|
+
requirementTag.command("remove [id]").description("Remove a tag from a requirement (auto-detects ID from git branch if not provided)").option(
|
|
8175
|
+
"-p, --project <id>",
|
|
8176
|
+
"project ID (auto-detects from .braingrid/project.json if not provided)"
|
|
8177
|
+
).requiredOption("--name <name>", "tag name to remove").action(async (id, opts) => {
|
|
8178
|
+
const result = await handleRequirementTagRemove({ ...opts, requirementId: id });
|
|
8179
|
+
console.log(result.message);
|
|
8180
|
+
if (!result.success) {
|
|
8181
|
+
process.exit(1);
|
|
8182
|
+
}
|
|
8183
|
+
});
|
|
7770
8184
|
var task = program.command("task").description("Manage tasks");
|
|
7771
8185
|
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) => {
|
|
7772
8186
|
const result = await handleTaskList(opts);
|