Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/src/pages/cli.astro
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,17 @@ dex complete abc123 --result "Planning complete, no code changes" --no-commit`}
<li><code>-d, --description</code> — Updated description</li>
<li><code>--add-blocker &lt;id&gt;</code> — Add blocking dependency</li>
<li><code>--remove-blocker &lt;id&gt;</code> — Remove blocking dependency</li>
<li><code>--parent &lt;id&gt;</code> — Move task under a new parent</li>
<li><code>--remove-parent</code> — Promote a subtask to a top-level task</li>
</ul>
<p><strong>Note:</strong> <code>--parent</code> and <code>--remove-parent</code> cannot be used together.</p>
<Terminal title="Terminal">
<Code
code={`dex edit abc123 -n "Updated name"
dex edit abc123 --add-blocker xyz789
dex edit abc123 --remove-blocker xyz789`}
dex edit abc123 --remove-blocker xyz789
dex edit abc123 --parent xyz789
dex edit abc123 --remove-parent`}
lang="bash"
theme="vitesse-black"
/>
Expand Down
4 changes: 4 additions & 0 deletions plugins/dex/skills/dex/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ This captures commit SHA, message, and branch automatically. The linked GitHub/S
dex edit <id> -n "Updated name" --description "Updated description"
dex edit <id> --add-blocker xyz123 # Add blocking dependency
dex edit <id> --remove-blocker xyz123 # Remove blocking dependency
dex edit <id> --parent xyz123 # Move task under a new parent
dex edit <id> --remove-parent # Promote a subtask to a top-level task
```

Note: `--parent` and `--remove-parent` cannot be used together.

## Delete a Task

```bash
Expand Down
23 changes: 23 additions & 0 deletions src/cli/completion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ describe("completion command", () => {
expect(out).toContain("complete");
expect(out).toContain("completion");
});

it("includes --remove-parent flag for edit command", async () => {
await runCli(["completion", "bash"], { storage });

const out = getStdout();
expect(out).toContain("--remove-parent");
});
});

describe("zsh completion", () => {
Expand All @@ -73,6 +80,15 @@ describe("completion command", () => {
expect(out).toContain("list:List tasks");
expect(out).toContain("show:View task details");
});

it("includes --remove-parent flag for edit command", async () => {
await runCli(["completion", "zsh"], { storage });

const out = getStdout();
expect(out).toContain(
"--remove-parent[Promote subtask to top-level task]",
);
});
});

describe("fish completion", () => {
Expand All @@ -93,6 +109,13 @@ describe("completion command", () => {
expect(out).toContain('-a "list" -d "List tasks"');
expect(out).toContain('-a "show" -d "View task details"');
});

it("includes --remove-parent flag for edit command", async () => {
await runCli(["completion", "fish"], { storage });

const out = getStdout();
expect(out).toContain("-l remove-parent");
});
});

describe("help", () => {
Expand Down
2 changes: 1 addition & 1 deletion src/cli/completion/bash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ _dex_completion() {
flags="--expand -e --full -f --json --help -h"
;;
edit|update)
flags="--name -n --description -d --priority -p --parent --add-blocker --remove-blocker --help -h"
flags="--name -n --description -d --priority -p --parent --remove-parent --add-blocker --remove-blocker --help -h"
;;
complete|done)
flags="--result -r --commit -c --help -h"
Expand Down
1 change: 1 addition & 0 deletions src/cli/completion/fish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ complete -c dex -n "contains -- edit (commandline -opc); or contains -- update (
complete -c dex -n "contains -- edit (commandline -opc); or contains -- update (commandline -opc)" -s d -l description -d "New description" -r
complete -c dex -n "contains -- edit (commandline -opc); or contains -- update (commandline -opc)" -s p -l priority -d "New priority" -r
complete -c dex -n "contains -- edit (commandline -opc); or contains -- update (commandline -opc)" -l parent -d "New parent task ID" -r -a "(__dex_task_ids)"
complete -c dex -n "contains -- edit (commandline -opc); or contains -- update (commandline -opc)" -l remove-parent -d "Promote subtask to top-level task"
complete -c dex -n "contains -- edit (commandline -opc); or contains -- update (commandline -opc)" -s s -l status -d "New status" -r -a "pending completed"
complete -c dex -n "contains -- edit (commandline -opc); or contains -- update (commandline -opc)" -s h -l help -d "Show help"

Expand Down
1 change: 1 addition & 0 deletions src/cli/completion/zsh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ _dex() {
'(-d --description)'{-d,--description}'[New description]:description:' \\
'(-p --priority)'{-p,--priority}'[New priority]:priority:' \\
'--parent[New parent task ID]:parent:_dex_task_ids' \\
'--remove-parent[Promote subtask to top-level task]' \\
'--add-blocker[Add blocker task IDs]:blockers:_dex_task_ids' \\
'--remove-blocker[Remove blocker task IDs]:blockers:_dex_task_ids' \\
'(-h --help)'{-h,--help}'[Show help]'
Expand Down
79 changes: 79 additions & 0 deletions src/cli/edit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,92 @@ describe("edit command", () => {
expect(out).toContain("--description");
expect(out).toContain("--add-blocker");
expect(out).toContain("--remove-blocker");
expect(out).toContain("--remove-parent");
});

it("requires task ID", async () => {
await expect(runCli(["edit"], { storage })).rejects.toThrow("process.exit");
expect(output.stderr.join("\n")).toContain("Task ID is required");
});

it("fails if both --remove-parent and --parent is provided", async () => {
await expect(
runCli(
[
"edit",
"abc123", // taskId
"--remove-parent",
"--parent",
"def456", // parentId
],
{ storage },
),
).rejects.toThrow("process.exit");

const out = output.stderr.join("\n");
expect(out).toContain(
"You cannot both remove a parent and set a new parent.",
);
expect(out).toContain(
"Use --parent <id> to overwrite the existing parent ID with another parent task.",
);
});

it("removes parent_id from task if --remove-parent is provided", async () => {
// Create parent task
await runCli(["create", "-n", "Test task", "--description", "ctx"], {
storage,
});
const parentTaskId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
output.stdout.length = 0;

// Create subtask
await runCli(
[
"create",
"-n",
"Test subtask",
"--description",
"ctx",
"--parent",
parentTaskId!,
],
{
storage,
},
);
const taskId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
output.stdout.length = 0;

// Test the paren taks lists the subtask as a child
await runCli(["show", parentTaskId!, "--json"], { storage });
let showOut = output.stdout.join("\n");
expect(JSON.parse(showOut).children.includes(taskId!)).toBe(true);
output.stdout.length = 0;

// Test the task is a child of the parent task
await runCli(["show", taskId!], { storage });
showOut = output.stdout.join("\n");
expect(showOut).toContain(`View parent task: dex show ${parentTaskId!}`);
output.stdout.length = 0;

// Remove the parent
await runCli(["edit", taskId!, "--remove-parent"], { storage });
output.stdout.length = 0;

// Test the task no longer shows it's parent task
await runCli(["show", taskId!], { storage });
showOut = output.stdout.join("\n");
expect(showOut).not.toContain("View parent task");
output.stdout.length = 0;

// Test the parent task no longer lists the subtask as a child
await runCli(["show", parentTaskId!, "--json"], { storage });
showOut = output.stdout.join("\n");
expect(JSON.parse(showOut).children.includes(taskId!)).toBe(false);
output.stdout.length = 0;
});

it("links commit to task with --commit flag", async () => {
await runCli(["create", "-n", "Test task", "--description", "ctx"], {
storage,
Expand Down
19 changes: 18 additions & 1 deletion src/cli/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function editCommand(
parent: { hasValue: true },
"add-blocker": { hasValue: true },
"remove-blocker": { hasValue: true },
"remove-parent": { hasValue: false },
commit: { short: "c", hasValue: true },
help: { short: "h", hasValue: false },
},
Expand All @@ -45,6 +46,7 @@ ${colors.bold}OPTIONS:${colors.reset}
--parent <id> New parent task ID
--add-blocker <ids> Comma-separated task IDs to add as blockers
--remove-blocker <ids> Comma-separated task IDs to remove as blockers
--remove-parent Change the task from a subtask to a task
-c, --commit <sha> Link a git commit to the task
-h, --help Show this help message

Expand All @@ -54,6 +56,7 @@ ${colors.bold}EXAMPLE:${colors.reset}
dex edit abc123 --description "More details about the task"
dex edit abc123 --add-blocker def456
dex edit abc123 --remove-blocker def456
dex edit abc123 --remove-parent
dex edit abc123 --commit a1b2c3d
`);
return;
Expand Down Expand Up @@ -97,6 +100,20 @@ ${colors.bold}EXAMPLE:${colors.reset}
process.exit(1);
}

// Determine what parentId to assign to the task
const removeParent = getBooleanFlag(flags, "remove-parent");
const setParentId = getStringFlag(flags, "parent");
if (removeParent && setParentId) {
console.error(
`${colors.red}Error:${colors.reset} You cannot both remove a parent and set a new parent.`,
);
console.error(
` Use --parent <id> to overwrite the existing parent ID with another parent task.`,
);
process.exit(1);
}
const newParentId = removeParent ? null : setParentId;

const service = createService(options);
try {
// Fetch existing task to merge metadata
Expand All @@ -122,7 +139,7 @@ ${colors.bold}EXAMPLE:${colors.reset}
id,
name: getStringFlag(flags, "name"),
description: getStringFlag(flags, "description"),
parent_id: getStringFlag(flags, "parent"),
parent_id: newParentId,
priority: parseIntFlag(flags, "priority"),
add_blocked_by: addBlockedBy,
remove_blocked_by: removeBlockedBy,
Expand Down