@dunnewold-labs/mr-manager 0.4.35 → 0.4.37

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.
Files changed (2) hide show
  1. package/dist/index.mjs +134 -77
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // cli/index.ts
4
- import { Command as Command30 } from "commander";
5
- import { existsSync as existsSync17 } from "fs";
4
+ import { Command as Command31 } from "commander";
5
+ import { existsSync as existsSync18 } from "fs";
6
6
  import { homedir as homedir3 } from "os";
7
7
  import { join as join12 } from "path";
8
8
 
@@ -185,7 +185,7 @@ import { fileURLToPath } from "url";
185
185
  // cli/package.json
186
186
  var package_default = {
187
187
  name: "@dunnewold-labs/mr-manager",
188
- version: "0.4.35",
188
+ version: "0.4.37",
189
189
  description: "Mr. Manager - Task and project management CLI",
190
190
  bin: {
191
191
  mr: "./dist/index.mjs"
@@ -354,8 +354,28 @@ var tasksCommand = new Command5("tasks").description("List tasks for the linked
354
354
  console.log(JSON.stringify(tasks, null, 2));
355
355
  });
356
356
 
357
- // cli/commands/link.ts
357
+ // cli/commands/task-group.ts
358
358
  import { Command as Command6 } from "commander";
359
+ var taskGroupCommand = new Command6("task-group").description("Group existing tasks under a new parent task with linked subtasks").requiredOption("--title <title>", "Parent task title").argument("<taskIds...>", "IDs of tasks to link as subtasks").action(async (taskIds, opts) => {
360
+ if (!Array.isArray(taskIds) || taskIds.length === 0) {
361
+ console.error("At least one task ID is required");
362
+ process.exit(1);
363
+ }
364
+ try {
365
+ const parent = await api.post("/api/tasks/group", {
366
+ title: opts.title,
367
+ taskIds
368
+ });
369
+ console.log(parent.id);
370
+ } catch (err) {
371
+ const message = err instanceof Error ? err.message : String(err);
372
+ console.error(`Failed to create parent task: ${message}`);
373
+ process.exit(1);
374
+ }
375
+ });
376
+
377
+ // cli/commands/link.ts
378
+ import { Command as Command7 } from "commander";
359
379
  import { createInterface } from "readline";
360
380
 
361
381
  // cli/vcs.ts
@@ -402,7 +422,7 @@ function prompt(question) {
402
422
  });
403
423
  });
404
424
  }
405
- var linkCommand = new Command6("link").description("Associate current directory with a project").argument("[project-id]", "Project ID to link").action(async (projectId) => {
425
+ var linkCommand = new Command7("link").description("Associate current directory with a project").argument("[project-id]", "Project ID to link").action(async (projectId) => {
406
426
  if (!projectId) {
407
427
  const current = getLinkedProjectId();
408
428
  if (current) {
@@ -441,7 +461,7 @@ Created project: ${project.name} (${project.id})`);
441
461
  );
442
462
  }
443
463
  });
444
- var unlinkCommand = new Command6("unlink").description("Remove current directory's project association").action(async () => {
464
+ var unlinkCommand = new Command7("unlink").description("Remove current directory's project association").action(async () => {
445
465
  const config = loadConfig();
446
466
  const cwd = process.cwd();
447
467
  if (!config.directories[cwd]) {
@@ -454,7 +474,7 @@ var unlinkCommand = new Command6("unlink").description("Remove current directory
454
474
  });
455
475
 
456
476
  // cli/commands/context.ts
457
- import { Command as Command7 } from "commander";
477
+ import { Command as Command8 } from "commander";
458
478
  import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
459
479
  import { join as join3, sep } from "path";
460
480
  var FEATURES_FILE = ".mr-features.md";
@@ -472,7 +492,7 @@ function readFeatures() {
472
492
  if (!existsSync3(path)) return null;
473
493
  return readFileSync3(path, "utf-8");
474
494
  }
475
- var contextCommand = new Command7("context").description("Output project context JSON for Claude Code").option("--install", "Install Claude Code command to .claude/commands/").action(async (opts) => {
495
+ var contextCommand = new Command8("context").description("Output project context JSON for Claude Code").option("--install", "Install Claude Code command to .claude/commands/").action(async (opts) => {
476
496
  if (opts.install) {
477
497
  const dir = join3(process.cwd(), ".claude", "commands");
478
498
  if (!existsSync3(dir)) {
@@ -527,7 +547,7 @@ var contextCommand = new Command7("context").description("Output project context
527
547
  });
528
548
 
529
549
  // cli/commands/watch.ts
530
- import { Command as Command8 } from "commander";
550
+ import { Command as Command9 } from "commander";
531
551
  import { spawn as spawn4, exec } from "child_process";
532
552
  import { randomUUID } from "crypto";
533
553
  import { resolve as resolve2 } from "path";
@@ -1950,11 +1970,18 @@ function buildExecutionPrompt(task, repoDir, subtasks, vcs = "github", protoRefs
1950
1970
  const workingDir = executionDir ?? repoDir;
1951
1971
  const prBodyPath = "/tmp/mr-pr-body.md";
1952
1972
  const hasAttachedBranch = !!task.attachedBranch?.trim();
1953
- const notes = task.prdContent ? `
1954
-
1955
- ## PRD (Product Requirements Document)
1956
-
1957
- ${task.prdContent}` : task.notes ? `
1973
+ const hasPrd = !!task.prdContent?.trim();
1974
+ const notes = hasPrd ? [
1975
+ ``,
1976
+ ``,
1977
+ `## Approved PRD (Product Requirements Document)`,
1978
+ ``,
1979
+ `A PRD for this task has been generated and approved by the user. Your job now is to IMPLEMENT the PRD below in code. The planning/proposal phase is already complete \u2014 do not produce another plan or proposal.`,
1980
+ ``,
1981
+ `Even if the task title includes words like "proposal", "plan", "design", "spec", or "PRD", treat the PRD below as the finalized spec and deliver working code that fulfills it. Only signal "no MR/PR needed" if the PRD itself explicitly calls out that no code changes are required.`,
1982
+ ``,
1983
+ task.prdContent
1984
+ ].join("\n") : task.notes ? `
1958
1985
 
1959
1986
  Task notes:
1960
1987
  ${task.notes}` : "";
@@ -2041,6 +2068,10 @@ ${task.notes}` : "";
2041
2068
  `\`mr no-mr ${task.id} "Brief description of what was done instead"\``,
2042
2069
  ``,
2043
2070
  `This tells the watch system to skip looking for a ${vcs === "gitlab" ? "MR" : "PR"} and records what action was taken. You should still clean up any worktrees and exit normally.`,
2071
+ ...hasPrd ? [
2072
+ ``,
2073
+ `**Important:** This task has an approved PRD, which means the user has already committed to implementation. Do NOT use \`mr no-mr\` just because the task title mentions "proposal", "plan", or because a PRD already exists \u2014 the PRD is the starting point for your implementation, not the deliverable. Only use \`mr no-mr\` if the PRD itself explicitly states that no code changes are required.`
2074
+ ] : [],
2044
2075
  ``,
2045
2076
  ...hasFeedback || hasAttachedBranch && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink) ? [] : [
2046
2077
  `## PR Description Template`,
@@ -2560,7 +2591,7 @@ function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name
2560
2591
  });
2561
2592
  return child;
2562
2593
  }
2563
- var watchCommand = new Command8("watch").description(
2594
+ var watchCommand = new Command9("watch").description(
2564
2595
  "Watch for in-progress tasks and autonomously dispatch an AI coding agent to work on them"
2565
2596
  ).option("--interval <seconds>", "Polling interval in seconds", "15").option("--dry-run", "Show what would be dispatched without spawning the agent", false).option("--plan-approval", "Show the agent's plan and ask for approval before executing", false).option("--root <dir>", "Root directory filter for linked repos (default: cwd)").option("--agent <agent>", "AI agent to use: claude, codex, or gemini", "claude").option("--scan-at <HH:MM>", "Run a product scan daily at this time (e.g., 02:00)").action(async (opts) => {
2566
2597
  const intervalMs = parseInt(opts.interval, 10) * 1e3;
@@ -4066,7 +4097,7 @@ ${timestamp()} ${watchTag()} Shutting down\u2026`);
4066
4097
  });
4067
4098
 
4068
4099
  // cli/commands/start.ts
4069
- import { Command as Command9 } from "commander";
4100
+ import { Command as Command10 } from "commander";
4070
4101
  var c2 = {
4071
4102
  reset: "\x1B[0m",
4072
4103
  bold: "\x1B[1m",
@@ -4092,19 +4123,19 @@ function printTaskBanner(action, title, id) {
4092
4123
  ``
4093
4124
  ].join("\n"));
4094
4125
  }
4095
- var startCommand = new Command9("start").description("Mark a task as in-progress (you are working on it)").argument("<task-id>", "Task ID to start").action(async (taskId) => {
4126
+ var startCommand = new Command10("start").description("Mark a task as in-progress (you are working on it)").argument("<task-id>", "Task ID to start").action(async (taskId) => {
4096
4127
  const task = await api.patch(`/api/tasks/${taskId}`, {
4097
4128
  status: "in_progress"
4098
4129
  });
4099
4130
  printTaskBanner(paint2("green", "start"), task.title, task.id);
4100
4131
  });
4101
- var delegateCommand = new Command9("delegate").description("Queue a task for the watch agent to pick up").argument("<task-id>", "Task ID to delegate").action(async (taskId) => {
4132
+ var delegateCommand = new Command10("delegate").description("Queue a task for the watch agent to pick up").argument("<task-id>", "Task ID to delegate").action(async (taskId) => {
4102
4133
  const task = await api.patch(`/api/tasks/${taskId}`, {
4103
4134
  status: "queued"
4104
4135
  });
4105
4136
  printTaskBanner(paint2("cyan", "delegate"), task.title, task.id);
4106
4137
  });
4107
- var undelegateCommand = new Command9("undelegate").description("Remove delegation from a task and return to todo").argument("<task-id>", "Task ID to undelegate").action(async (taskId) => {
4138
+ var undelegateCommand = new Command10("undelegate").description("Remove delegation from a task and return to todo").argument("<task-id>", "Task ID to undelegate").action(async (taskId) => {
4108
4139
  const task = await api.patch(`/api/tasks/${taskId}`, {
4109
4140
  status: "todo",
4110
4141
  mode: "development"
@@ -4113,8 +4144,8 @@ var undelegateCommand = new Command9("undelegate").description("Remove delegatio
4113
4144
  });
4114
4145
 
4115
4146
  // cli/commands/create.ts
4116
- import { Command as Command10 } from "commander";
4117
- var createCommand = new Command10("create").description("Create a new task in the linked project").argument("<title>", "Task title").option("--notes <notes>", "Task notes").option("--in-progress", "Mark the task as in-progress immediately").action(async (title, opts) => {
4147
+ import { Command as Command11 } from "commander";
4148
+ var createCommand = new Command11("create").description("Create a new task in the linked project").argument("<title>", "Task title").option("--notes <notes>", "Task notes").option("--in-progress", "Mark the task as in-progress immediately").action(async (title, opts) => {
4118
4149
  const projectId = getLinkedProjectId();
4119
4150
  if (!projectId) {
4120
4151
  console.error(
@@ -4133,7 +4164,7 @@ var createCommand = new Command10("create").description("Create a new task in th
4133
4164
  });
4134
4165
 
4135
4166
  // cli/commands/complete.ts
4136
- import { Command as Command11 } from "commander";
4167
+ import { Command as Command12 } from "commander";
4137
4168
  var c3 = {
4138
4169
  reset: "\x1B[0m",
4139
4170
  bold: "\x1B[1m",
@@ -4159,7 +4190,7 @@ function printTaskBanner2(action, title, id) {
4159
4190
  ``
4160
4191
  ].join("\n"));
4161
4192
  }
4162
- var completeCommand = new Command11("complete").description("Mark a task as completed").argument("<task-id>", "Task ID to complete").action(async (taskId) => {
4193
+ var completeCommand = new Command12("complete").description("Mark a task as completed").argument("<task-id>", "Task ID to complete").action(async (taskId) => {
4163
4194
  const task = await api.patch(`/api/tasks/${taskId}`, {
4164
4195
  status: "completed"
4165
4196
  });
@@ -4167,8 +4198,8 @@ var completeCommand = new Command11("complete").description("Mark a task as comp
4167
4198
  });
4168
4199
 
4169
4200
  // cli/commands/subtask-complete.ts
4170
- import { Command as Command12 } from "commander";
4171
- var subtaskCompleteCommand = new Command12("subtask-complete").description("Mark a subtask as completed").argument("<task-id>", "Parent task ID").argument("<subtask-id>", "Subtask ID to complete").action(async (taskId, subtaskId) => {
4201
+ import { Command as Command13 } from "commander";
4202
+ var subtaskCompleteCommand = new Command13("subtask-complete").description("Mark a subtask as completed").argument("<task-id>", "Parent task ID").argument("<subtask-id>", "Subtask ID to complete").action(async (taskId, subtaskId) => {
4172
4203
  const subtask = await api.patch(
4173
4204
  `/api/tasks/${taskId}/subtasks/${subtaskId}`,
4174
4205
  { completed: true }
@@ -4177,7 +4208,7 @@ var subtaskCompleteCommand = new Command12("subtask-complete").description("Mark
4177
4208
  });
4178
4209
 
4179
4210
  // cli/commands/prototype.ts
4180
- import { Command as Command13 } from "commander";
4211
+ import { Command as Command14 } from "commander";
4181
4212
  var c4 = {
4182
4213
  reset: "\x1B[0m",
4183
4214
  bold: "\x1B[1m",
@@ -4206,8 +4237,8 @@ function statusBadge(status) {
4206
4237
  return paint4("gray", status);
4207
4238
  }
4208
4239
  }
4209
- var prototypeCommand = new Command13("prototype").description("Manage prototypes").addCommand(
4210
- new Command13("list").description("List prototypes for the linked project").option("--all", "Show prototypes for all projects").action(async (opts) => {
4240
+ var prototypeCommand = new Command14("prototype").description("Manage prototypes").addCommand(
4241
+ new Command14("list").description("List prototypes for the linked project").option("--all", "Show prototypes for all projects").action(async (opts) => {
4211
4242
  const params = new URLSearchParams();
4212
4243
  if (!opts.all) {
4213
4244
  const projectId = getLinkedProjectId();
@@ -4240,7 +4271,7 @@ var prototypeCommand = new Command13("prototype").description("Manage prototypes
4240
4271
  }
4241
4272
  })
4242
4273
  ).addCommand(
4243
- new Command13("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project, when available)").option("--variants <count>", "Number of variants to generate (1-50)", "5").option("--type <type>", "Prototype type: web_app, mobile_app, desktop_app, logo (default: web_app)", "web_app").action(async (title, opts) => {
4274
+ new Command14("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project, when available)").option("--variants <count>", "Number of variants to generate (1-50)", "5").option("--type <type>", "Prototype type: web_app, mobile_app, desktop_app, logo (default: web_app)", "web_app").action(async (title, opts) => {
4244
4275
  const projectId = opts.project ?? getLinkedProjectId();
4245
4276
  const variantCount = Math.max(1, Math.min(50, parseInt(opts.variants, 10) || 5));
4246
4277
  const validTypes = ["web_app", "mobile_app", "desktop_app", "logo"];
@@ -4269,7 +4300,7 @@ var prototypeCommand = new Command13("prototype").description("Manage prototypes
4269
4300
  console.log();
4270
4301
  })
4271
4302
  ).addCommand(
4272
- new Command13("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
4303
+ new Command14("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
4273
4304
  const prototype = await api.patch(`/api/prototypes/${id}`, {
4274
4305
  status: "in_progress"
4275
4306
  });
@@ -4279,7 +4310,7 @@ var prototypeCommand = new Command13("prototype").description("Manage prototypes
4279
4310
  console.log();
4280
4311
  })
4281
4312
  ).addCommand(
4282
- new Command13("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
4313
+ new Command14("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
4283
4314
  const prototype = await api.patch(`/api/prototypes/${id}`, {
4284
4315
  status: "in_progress",
4285
4316
  files: null
@@ -4291,7 +4322,7 @@ var prototypeCommand = new Command13("prototype").description("Manage prototypes
4291
4322
  );
4292
4323
 
4293
4324
  // cli/commands/setup.ts
4294
- import { Command as Command14 } from "commander";
4325
+ import { Command as Command15 } from "commander";
4295
4326
  import { exec as exec2 } from "child_process";
4296
4327
  var c5 = {
4297
4328
  reset: "\x1B[0m",
@@ -4594,7 +4625,7 @@ async function autoFix(checks, agent) {
4594
4625
  console.log("");
4595
4626
  }
4596
4627
  }
4597
- var setupCommand = new Command14("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
4628
+ var setupCommand = new Command15("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
4598
4629
  const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
4599
4630
  const banner = [
4600
4631
  ``,
@@ -4644,8 +4675,8 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
4644
4675
  });
4645
4676
 
4646
4677
  // cli/commands/update.ts
4647
- import { Command as Command15 } from "commander";
4648
- var updateCommand = new Command15("update").description("Post a status update to a task, or attach a resource").argument("<task-id>", "Task ID").argument("[message-or-title]", "Status update message, or resource title when using --resource").argument("[content]", "Resource content (only used with --resource)").option("--source <source>", "Update source: agent, system, or user", "agent").option("--resource <type>", "Create a task resource (e.g. test-plan, note, plan, research)").action(async (taskId, messageOrTitle, content, opts) => {
4678
+ import { Command as Command16 } from "commander";
4679
+ var updateCommand = new Command16("update").description("Post a status update to a task, or attach a resource").argument("<task-id>", "Task ID").argument("[message-or-title]", "Status update message, or resource title when using --resource").argument("[content]", "Resource content (only used with --resource)").option("--source <source>", "Update source: agent, system, or user", "agent").option("--resource <type>", "Create a task resource (e.g. test-plan, note, plan, research)").action(async (taskId, messageOrTitle, content, opts) => {
4649
4680
  if (opts.resource) {
4650
4681
  if (!messageOrTitle || !content) {
4651
4682
  console.error(`Usage: mr update <task-id> --resource <type> "<title>" '<content>'`);
@@ -4671,11 +4702,11 @@ var updateCommand = new Command15("update").description("Post a status update to
4671
4702
  });
4672
4703
 
4673
4704
  // cli/commands/screenshot.ts
4674
- import { Command as Command16 } from "commander";
4705
+ import { Command as Command17 } from "commander";
4675
4706
  import { readFileSync as readFileSync6, existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
4676
4707
  import { join as join7 } from "path";
4677
4708
  import { tmpdir } from "os";
4678
- var screenshotCommand = new Command16("screenshot").description(
4709
+ var screenshotCommand = new Command17("screenshot").description(
4679
4710
  "Take or attach a screenshot to a task update (agents use this to show their work)"
4680
4711
  ).argument("<task-id>", "Task ID").argument("[file]", "Path to an image file (if omitted, uses headless browser to screenshot the app)").option("-m, --message <message>", "Optional message to include with the screenshot").option("-u, --url <url>", "Custom URL to screenshot (defaults to the task's project page)").action(async (taskId, file, opts) => {
4681
4712
  let filePath = file;
@@ -4766,7 +4797,7 @@ var screenshotCommand = new Command16("screenshot").description(
4766
4797
  });
4767
4798
 
4768
4799
  // cli/commands/resume.ts
4769
- import { Command as Command17 } from "commander";
4800
+ import { Command as Command18 } from "commander";
4770
4801
  import { spawn as spawn5 } from "child_process";
4771
4802
  import { resolve as resolve3 } from "path";
4772
4803
  var c6 = {
@@ -4783,7 +4814,7 @@ var c6 = {
4783
4814
  function paint6(color, text) {
4784
4815
  return `${c6[color]}${text}${c6.reset}`;
4785
4816
  }
4786
- var resumeCommand = new Command17("resume").description("Resume an interactive Claude session for a task (non-headless)").argument("<task-id>", "Task ID whose Claude session to resume").option("--dir <directory>", "Override the working directory for the session").action(async (taskId, opts) => {
4817
+ var resumeCommand = new Command18("resume").description("Resume an interactive Claude session for a task (non-headless)").argument("<task-id>", "Task ID whose Claude session to resume").option("--dir <directory>", "Override the working directory for the session").action(async (taskId, opts) => {
4787
4818
  const task = await api.get(`/api/tasks/${taskId}`);
4788
4819
  if (!task.claudeSessionId) {
4789
4820
  console.error(
@@ -4852,7 +4883,7 @@ var resumeCommand = new Command17("resume").description("Resume an interactive C
4852
4883
  });
4853
4884
 
4854
4885
  // cli/commands/browse.ts
4855
- import { Command as Command18 } from "commander";
4886
+ import { Command as Command19 } from "commander";
4856
4887
  import { execSync as execSync4, spawn as spawn6 } from "child_process";
4857
4888
  import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
4858
4889
  import { createHash } from "crypto";
@@ -4967,7 +4998,7 @@ async function ensureDevServer(options = {}) {
4967
4998
  }
4968
4999
  throw new Error(`Dev server failed to start within 60s. Command: ${devCmd} in ${projectCwd}`);
4969
5000
  }
4970
- var browseCommand = new Command18("browse").description("Control a headless browser for QA and testing").argument("[command]", "Browse command (goto, click, fill, screenshot, etc.)").argument("[args...]", "Command arguments").option(
5001
+ var browseCommand = new Command19("browse").description("Control a headless browser for QA and testing").argument("[command]", "Browse command (goto, click, fill, screenshot, etc.)").argument("[args...]", "Command arguments").option(
4971
5002
  "--task-id <id>",
4972
5003
  "Attach output to a task update (for screenshot and recording-stop commands)"
4973
5004
  ).option("--dev", "Auto-start local dev server before browsing").option("--dev-cwd <path>", "Working directory for the dev server (defaults to mr-manager root)").option("--dev-cmd <command>", "Dev server command to run (auto-detected from package.json if omitted)").option("--dev-port-flag <flag>", "CLI flag name used to set port (e.g. --port). Omit to use PORT env var.").allowUnknownOption(true).action(
@@ -5110,10 +5141,10 @@ var browseCommand = new Command18("browse").description("Control a headless brow
5110
5141
  );
5111
5142
 
5112
5143
  // cli/commands/set-path.ts
5113
- import { Command as Command19 } from "commander";
5144
+ import { Command as Command20 } from "commander";
5114
5145
  import { resolve as resolve5 } from "path";
5115
5146
  import { existsSync as existsSync10 } from "fs";
5116
- var setPathCommand = new Command19("set-path").description("Set or update the local repo path for a project").argument("<project-id>", "Project ID").argument("<path>", "Absolute or relative path to the local repo").action(async (projectId, pathArg) => {
5147
+ var setPathCommand = new Command20("set-path").description("Set or update the local repo path for a project").argument("<project-id>", "Project ID").argument("<path>", "Absolute or relative path to the local repo").action(async (projectId, pathArg) => {
5117
5148
  const absolutePath = resolve5(pathArg);
5118
5149
  if (!existsSync10(absolutePath)) {
5119
5150
  console.error(`Error: Path does not exist: ${absolutePath}`);
@@ -5131,9 +5162,9 @@ var setPathCommand = new Command19("set-path").description("Set or update the lo
5131
5162
  });
5132
5163
 
5133
5164
  // cli/commands/test.ts
5134
- import { Command as Command20 } from "commander";
5165
+ import { Command as Command21 } from "commander";
5135
5166
  import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
5136
- var testCommand = new Command20("test").description("Run automated browser test for a task's MR/PR").argument("<task-id>", "Task ID to test").option("--plan <file>", "Path to a custom test plan JSON file").option("--no-recording", "Disable proof recording for this run").action(async (taskId, opts) => {
5167
+ var testCommand = new Command21("test").description("Run automated browser test for a task's MR/PR").argument("<task-id>", "Task ID to test").option("--plan <file>", "Path to a custom test plan JSON file").option("--no-recording", "Disable proof recording for this run").action(async (taskId, opts) => {
5137
5168
  const config = loadConfig();
5138
5169
  console.log("[test] Fetching task...");
5139
5170
  let task;
@@ -5295,7 +5326,7 @@ var testCommand = new Command20("test").description("Run automated browser test
5295
5326
  });
5296
5327
 
5297
5328
  // cli/commands/features.ts
5298
- import { Command as Command21 } from "commander";
5329
+ import { Command as Command22 } from "commander";
5299
5330
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
5300
5331
  import { resolve as resolve6, sep as sep2 } from "path";
5301
5332
  var FEATURES_FILE3 = ".mr-features.md";
@@ -5329,7 +5360,7 @@ function readFeatures2() {
5329
5360
  if (!existsSync12(path)) return null;
5330
5361
  return readFileSync9(path, "utf-8");
5331
5362
  }
5332
- var featuresCommand = new Command21("features").description("View or update the project features & goals document (.mr-features.md)").option("--update <content>", "Replace the features document with the given content").option("--file <path>", "Read content from a file and use it to update the features document").option("--path", "Print the path to the features file").action(async (opts) => {
5363
+ var featuresCommand = new Command22("features").description("View or update the project features & goals document (.mr-features.md)").option("--update <content>", "Replace the features document with the given content").option("--file <path>", "Read content from a file and use it to update the features document").option("--path", "Print the path to the features file").action(async (opts) => {
5333
5364
  if (opts.path) {
5334
5365
  console.log(getFeaturesPath());
5335
5366
  return;
@@ -5357,11 +5388,11 @@ var featuresCommand = new Command21("features").description("View or update the
5357
5388
  });
5358
5389
 
5359
5390
  // cli/commands/no-mr.ts
5360
- import { Command as Command22 } from "commander";
5391
+ import { Command as Command23 } from "commander";
5361
5392
  import { writeFileSync as writeFileSync6 } from "fs";
5362
5393
  import { resolve as resolve7 } from "path";
5363
5394
  var NO_MR_FILE = ".mr-no-mr";
5364
- var noMrCommand = new Command22("no-mr").description("Signal that a task does not require a merge/pull request and describe what was done instead").argument("<task-id>", "Task ID").argument("<description>", "Description of what was done instead of creating an MR/PR").action(async (taskId, description) => {
5395
+ var noMrCommand = new Command23("no-mr").description("Signal that a task does not require a merge/pull request and describe what was done instead").argument("<task-id>", "Task ID").argument("<description>", "Description of what was done instead of creating an MR/PR").action(async (taskId, description) => {
5365
5396
  const filePath = resolve7(process.cwd(), NO_MR_FILE);
5366
5397
  writeFileSync6(filePath, description, "utf-8");
5367
5398
  await api.post(`/api/tasks/${taskId}/updates`, {
@@ -5373,8 +5404,9 @@ var noMrCommand = new Command22("no-mr").description("Signal that a task does no
5373
5404
  });
5374
5405
 
5375
5406
  // cli/commands/review.ts
5376
- import { Command as Command23 } from "commander";
5407
+ import { Command as Command24 } from "commander";
5377
5408
  import { spawn as spawn7, execSync as execSync5 } from "child_process";
5409
+ import { existsSync as existsSync13, statSync as statSync2 } from "fs";
5378
5410
  var c8 = {
5379
5411
  reset: "\x1B[0m",
5380
5412
  bold: "\x1B[1m",
@@ -5405,7 +5437,7 @@ function logOk(msg) {
5405
5437
  function logErr(msg) {
5406
5438
  console.error(`${timestamp2()} ${tag()} ${paint8("red", "\u2717")} ${msg}`);
5407
5439
  }
5408
- var reviewCommand = new Command23("review").description("Run an automated code review on a branch").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing review report ID (created by UI trigger)").option("--branch <name>", "Branch to review (defaults to current branch)").option("--base <name>", "Base branch to diff against (defaults to main)").action(async (opts) => {
5440
+ var reviewCommand = new Command24("review").description("Run an automated code review on a branch").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing review report ID (created by UI trigger)").option("--branch <name>", "Branch to review (defaults to current branch)").option("--base <name>", "Base branch to diff against (defaults to main)").action(async (opts) => {
5409
5441
  const config = loadConfig();
5410
5442
  if (!config.apiKey) {
5411
5443
  logErr('Not authenticated. Run "mr login" first.');
@@ -5445,6 +5477,26 @@ var reviewCommand = new Command23("review").description("Run an automated code r
5445
5477
  if (!projectPath) {
5446
5478
  projectPath = process.cwd();
5447
5479
  }
5480
+ if (!existsSync13(projectPath)) {
5481
+ logErr(`Project path does not exist: ${projectPath}`);
5482
+ logErr(`Update the project's localPath or run "mr link" from the correct directory.`);
5483
+ process.exit(1);
5484
+ }
5485
+ try {
5486
+ if (!statSync2(projectPath).isDirectory()) {
5487
+ logErr(`Project path is not a directory: ${projectPath}`);
5488
+ process.exit(1);
5489
+ }
5490
+ } catch (err) {
5491
+ logErr(`Cannot stat project path ${projectPath}: ${err.message}`);
5492
+ process.exit(1);
5493
+ }
5494
+ if (!existsSync13(`${projectPath}/.git`)) {
5495
+ logErr(`Project path is not a git repository: ${projectPath}`);
5496
+ logErr(`Update the project's localPath to point to the local checkout.`);
5497
+ process.exit(1);
5498
+ }
5499
+ log(`Using project path: ${paint8("dim", projectPath)}`);
5448
5500
  let branch = opts.branch;
5449
5501
  if (!branch) {
5450
5502
  try {
@@ -5691,13 +5743,13 @@ function parseReviewOutput(output) {
5691
5743
  }
5692
5744
 
5693
5745
  // cli/commands/scan.ts
5694
- import { Command as Command24 } from "commander";
5746
+ import { Command as Command25 } from "commander";
5695
5747
 
5696
5748
  // lib/scanner/index.ts
5697
5749
  import { spawn as spawn8 } from "child_process";
5698
5750
 
5699
5751
  // lib/scanner/config.ts
5700
- import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
5752
+ import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
5701
5753
  import { join as join9 } from "path";
5702
5754
  var ALL_FINDING_TYPES = [
5703
5755
  "idea",
@@ -5715,7 +5767,7 @@ var DEFAULTS = {
5715
5767
  };
5716
5768
  function loadScanConfig(projectPath) {
5717
5769
  const configPath2 = join9(projectPath, ".mr-scan.json");
5718
- if (!existsSync13(configPath2)) {
5770
+ if (!existsSync14(configPath2)) {
5719
5771
  return { ...DEFAULTS };
5720
5772
  }
5721
5773
  try {
@@ -5761,13 +5813,13 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
5761
5813
  }
5762
5814
 
5763
5815
  // lib/scanner/codebase-analysis.ts
5764
- import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
5816
+ import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync15 } from "fs";
5765
5817
  import { join as join10, relative } from "path";
5766
5818
  import { execSync as execSync6 } from "child_process";
5767
5819
  function resolveDir(projectPath, candidates) {
5768
5820
  for (const candidate of candidates) {
5769
5821
  const dir = join10(projectPath, candidate);
5770
- if (existsSync14(dir)) return dir;
5822
+ if (existsSync15(dir)) return dir;
5771
5823
  }
5772
5824
  return null;
5773
5825
  }
@@ -5801,7 +5853,7 @@ function discoverRoutes(projectPath) {
5801
5853
  }
5802
5854
  function extractModels(projectPath) {
5803
5855
  const schemaPath = join10(projectPath, "prisma", "schema.prisma");
5804
- if (existsSync14(schemaPath)) {
5856
+ if (existsSync15(schemaPath)) {
5805
5857
  const content = readFileSync11(schemaPath, "utf-8");
5806
5858
  const models2 = [];
5807
5859
  const modelRegex = /^model\s+(\w+)\s*\{/gm;
@@ -5815,7 +5867,7 @@ function extractModels(projectPath) {
5815
5867
  const drizzleDirs = ["src/db", "src/schema", "db", "drizzle"];
5816
5868
  for (const dir of drizzleDirs) {
5817
5869
  const fullDir = join10(projectPath, dir);
5818
- if (!existsSync14(fullDir)) continue;
5870
+ if (!existsSync15(fullDir)) continue;
5819
5871
  try {
5820
5872
  const entries = readdirSync2(fullDir, { withFileTypes: true });
5821
5873
  for (const entry of entries) {
@@ -5856,7 +5908,7 @@ function discoverComponents(projectPath) {
5856
5908
  function extractInternalLinks(projectPath) {
5857
5909
  const links = /* @__PURE__ */ new Set();
5858
5910
  function searchDir(dir) {
5859
- if (!existsSync14(dir)) return;
5911
+ if (!existsSync15(dir)) return;
5860
5912
  const entries = readdirSync2(dir, { withFileTypes: true });
5861
5913
  for (const entry of entries) {
5862
5914
  if (entry.isDirectory()) {
@@ -6453,7 +6505,7 @@ function logOk2(msg) {
6453
6505
  function logErr2(msg) {
6454
6506
  console.error(`${timestamp3()} ${scanTag()} ${paint9("red", "\u2717")} ${msg}`);
6455
6507
  }
6456
- var scanCommand = new Command24("scan").description("Run a product scan on the current project \u2014 analyzes codebase, crawls the app, and surfaces findings").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing scan report ID (created by UI trigger)").option("--no-crawl", "Skip live crawl (codebase analysis only)").action(async (opts) => {
6508
+ var scanCommand = new Command25("scan").description("Run a product scan on the current project \u2014 analyzes codebase, crawls the app, and surfaces findings").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing scan report ID (created by UI trigger)").option("--no-crawl", "Skip live crawl (codebase analysis only)").action(async (opts) => {
6457
6509
  const config = loadConfig();
6458
6510
  if (!config.apiKey) {
6459
6511
  logErr2('Not authenticated. Run "mr login" first.');
@@ -6608,13 +6660,13 @@ var scanCommand = new Command24("scan").description("Run a product scan on the c
6608
6660
  });
6609
6661
 
6610
6662
  // cli/commands/doctor.ts
6611
- import { Command as Command25 } from "commander";
6612
- import { existsSync as existsSync15 } from "fs";
6663
+ import { Command as Command26 } from "commander";
6664
+ import { existsSync as existsSync16 } from "fs";
6613
6665
  import { homedir as homedir2 } from "os";
6614
6666
  import { join as join11 } from "path";
6615
6667
  async function checkConfigExists() {
6616
6668
  const configPath2 = join11(homedir2(), ".mr-manager", "config.json");
6617
- const exists = existsSync15(configPath2);
6669
+ const exists = existsSync16(configPath2);
6618
6670
  if (!exists) {
6619
6671
  return {
6620
6672
  name: "Config file",
@@ -6656,7 +6708,7 @@ async function checkProjectLink() {
6656
6708
  optional: true
6657
6709
  };
6658
6710
  }
6659
- var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
6711
+ var doctorCommand = new Command26("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
6660
6712
  const banner = [
6661
6713
  ``,
6662
6714
  paint5("cyan", ` MR DOCTOR`),
@@ -6699,14 +6751,14 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
6699
6751
  });
6700
6752
 
6701
6753
  // cli/commands/prompt-audit.ts
6702
- import { Command as Command26 } from "commander";
6754
+ import { Command as Command27 } from "commander";
6703
6755
  import { resolve as resolve8 } from "path";
6704
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
6756
+ import { existsSync as existsSync17, readFileSync as readFileSync12 } from "fs";
6705
6757
  function auditLine(label, tokens) {
6706
6758
  const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
6707
6759
  return ` ${label.padEnd(30)} ${formatTokenCount(tokens).padStart(8)} ${bar}`;
6708
6760
  }
6709
- var promptAuditCommand = new Command26("prompt-audit").description("Dry-run prompt construction and report estimated token counts by job type").option("--task <id>", "Audit prompts for a specific task ID").option("--all", "Audit all supported job types with representative data", false).option("--json", "Output as JSON instead of plain text", false).action(async (opts) => {
6761
+ var promptAuditCommand = new Command27("prompt-audit").description("Dry-run prompt construction and report estimated token counts by job type").option("--task <id>", "Audit prompts for a specific task ID").option("--all", "Audit all supported job types with representative data", false).option("--json", "Output as JSON instead of plain text", false).action(async (opts) => {
6710
6762
  const results = [];
6711
6763
  if (opts.task) {
6712
6764
  try {
@@ -6722,7 +6774,11 @@ var promptAuditCommand = new Command26("prompt-audit").description("Dry-run prom
6722
6774
  const sections = [];
6723
6775
  const notesContent = task.prdContent ? `
6724
6776
 
6725
- ## PRD (Product Requirements Document)
6777
+ ## Approved PRD (Product Requirements Document)
6778
+
6779
+ A PRD for this task has been generated and approved by the user. Your job now is to IMPLEMENT the PRD below in code. The planning/proposal phase is already complete \u2014 do not produce another plan or proposal.
6780
+
6781
+ Even if the task title includes words like "proposal", "plan", "design", "spec", or "PRD", treat the PRD below as the finalized spec and deliver working code that fulfills it. Only signal "no MR/PR needed" if the PRD itself explicitly calls out that no code changes are required.
6726
6782
 
6727
6783
  ${task.prdContent}` : task.notes ? `
6728
6784
 
@@ -6756,7 +6812,7 @@ ${task.notes}` : "";
6756
6812
  const repoDir = Object.entries(config.directories).find(([, pid]) => pid === task.projectId)?.[0];
6757
6813
  if (repoDir) {
6758
6814
  const featuresPath = resolve8(repoDir, ".mr-features.md");
6759
- if (existsSync16(featuresPath)) {
6815
+ if (existsSync17(featuresPath)) {
6760
6816
  const featuresContent = readFileSync12(featuresPath, "utf-8");
6761
6817
  sections.push({ name: "features-doc", tokens: estimateTokens(featuresContent) });
6762
6818
  }
@@ -6911,7 +6967,7 @@ ${r.jobType} [${r.identifier}]`);
6911
6967
  });
6912
6968
 
6913
6969
  // cli/commands/skill.ts
6914
- import { Command as Command27 } from "commander";
6970
+ import { Command as Command28 } from "commander";
6915
6971
  var c10 = {
6916
6972
  reset: "\x1B[0m",
6917
6973
  bold: "\x1B[1m",
@@ -6920,7 +6976,7 @@ var c10 = {
6920
6976
  green: "\x1B[32m",
6921
6977
  yellow: "\x1B[33m"
6922
6978
  };
6923
- var skillCommand = new Command27("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
6979
+ var skillCommand = new Command28("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
6924
6980
  skillCommand.command("list").alias("ls").description("List all skills").option("--category <category>", "Filter by category").action(async (opts) => {
6925
6981
  const params = new URLSearchParams();
6926
6982
  if (opts.category) params.set("category", opts.category);
@@ -7011,7 +7067,7 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
7011
7067
  });
7012
7068
 
7013
7069
  // cli/commands/resource.ts
7014
- import { Command as Command28 } from "commander";
7070
+ import { Command as Command29 } from "commander";
7015
7071
  var c11 = {
7016
7072
  reset: "\x1B[0m",
7017
7073
  bold: "\x1B[1m",
@@ -7031,7 +7087,7 @@ function typeLabel(type) {
7031
7087
  const color = TYPE_COLORS[type] ?? c11.dim;
7032
7088
  return `${color}${type}${c11.reset}`;
7033
7089
  }
7034
- var resourceCommand = new Command28("resource").description("Manage resources \u2014 documents, plans, research, and notes");
7090
+ var resourceCommand = new Command29("resource").description("Manage resources \u2014 documents, plans, research, and notes");
7035
7091
  resourceCommand.command("list").alias("ls").description("List resources for the linked project (or all)").option("--all", "List all resources across projects").action(async (opts) => {
7036
7092
  const params = new URLSearchParams();
7037
7093
  if (opts.all) {
@@ -7129,13 +7185,13 @@ resourceCommand.command("generate").alias("gen").description("Generate a resourc
7129
7185
  });
7130
7186
 
7131
7187
  // cli/commands/tests.ts
7132
- import { Command as Command29 } from "commander";
7188
+ import { Command as Command30 } from "commander";
7133
7189
  var c12 = {
7134
7190
  reset: "\x1B[0m",
7135
7191
  dim: "\x1B[2m",
7136
7192
  yellow: "\x1B[33m"
7137
7193
  };
7138
- var testsCommand = new Command29("tests").description("List MR Test scenarios for the linked project").action(async () => {
7194
+ var testsCommand = new Command30("tests").description("List MR Test scenarios for the linked project").action(async () => {
7139
7195
  const projectId = getLinkedProjectId();
7140
7196
  if (!projectId) {
7141
7197
  console.error(
@@ -7168,7 +7224,7 @@ var testsCommand = new Command29("tests").description("List MR Test scenarios fo
7168
7224
 
7169
7225
  // cli/index.ts
7170
7226
  var configPath = join12(homedir3(), ".mr-manager", "config.json");
7171
- var isFirstRun = !existsSync17(configPath);
7227
+ var isFirstRun = !existsSync18(configPath);
7172
7228
  var userArgs = process.argv.slice(2);
7173
7229
  var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
7174
7230
  var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
@@ -7204,13 +7260,14 @@ if (isFirstRun && !shouldBypass) {
7204
7260
  console.log("");
7205
7261
  process.exit(0);
7206
7262
  }
7207
- var program = new Command30();
7263
+ var program = new Command31();
7208
7264
  program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
7209
7265
  program.addCommand(initCommand);
7210
7266
  program.addCommand(authCommand);
7211
7267
  program.addCommand(loginCommand);
7212
7268
  program.addCommand(projectsCommand);
7213
7269
  program.addCommand(tasksCommand);
7270
+ program.addCommand(taskGroupCommand);
7214
7271
  program.addCommand(linkCommand);
7215
7272
  program.addCommand(unlinkCommand);
7216
7273
  program.addCommand(contextCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunnewold-labs/mr-manager",
3
- "version": "0.4.35",
3
+ "version": "0.4.37",
4
4
  "description": "Mr. Manager - Task and project management CLI",
5
5
  "bin": {
6
6
  "mr": "./dist/index.mjs"