@dunnewold-labs/mr-manager 0.4.32 → 0.4.35

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 +665 -161
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // cli/index.ts
4
- import { Command as Command28 } from "commander";
4
+ import { Command as Command30 } from "commander";
5
5
  import { existsSync as existsSync17 } from "fs";
6
6
  import { homedir as homedir3 } from "os";
7
7
  import { join as join12 } from "path";
@@ -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.32",
188
+ version: "0.4.35",
189
189
  description: "Mr. Manager - Task and project management CLI",
190
190
  bin: {
191
191
  mr: "./dist/index.mjs"
@@ -880,7 +880,7 @@ async function runTest(options) {
880
880
  recordingEnabled = true,
881
881
  recordingContext = "test-run"
882
882
  } = options;
883
- const log2 = onProgress || (() => {
883
+ const log3 = onProgress || (() => {
884
884
  });
885
885
  const result = {
886
886
  status: "passed",
@@ -897,30 +897,30 @@ async function runTest(options) {
897
897
  let wtPath = null;
898
898
  const worktreeName = `mr-test-${taskId.slice(0, 8)}`;
899
899
  const timeoutHandle = setTimeout(() => {
900
- log2("Test timed out after 5 minutes");
900
+ log3("Test timed out after 5 minutes");
901
901
  if (devProc) devProc.kill("SIGTERM");
902
902
  }, 5 * 60 * 1e3);
903
903
  try {
904
- log2("Extracting branch from MR/PR link...");
904
+ log3("Extracting branch from MR/PR link...");
905
905
  const branch = extractBranchFromLink(taskLink, localPath);
906
906
  if (!branch) {
907
907
  throw new Error(`Could not extract branch from link: ${taskLink}`);
908
908
  }
909
- log2(`Branch: ${branch}`);
910
- log2("Creating git worktree...");
909
+ log3(`Branch: ${branch}`);
910
+ log3("Creating git worktree...");
911
911
  wtPath = createWorktree(localPath, branch, worktreeName).path;
912
- log2(`Worktree created at ${wtPath}`);
913
- log2("Installing dependencies...");
912
+ log3(`Worktree created at ${wtPath}`);
913
+ log3("Installing dependencies...");
914
914
  try {
915
915
  installDependencies(wtPath);
916
916
  } catch (err) {
917
- log2(`Warning: dependency install failed: ${err.message}`);
917
+ log3(`Warning: dependency install failed: ${err.message}`);
918
918
  }
919
- log2("Starting dev server...");
919
+ log3("Starting dev server...");
920
920
  const port = await findAvailablePort(4e3);
921
921
  devProc = await startDevServer(wtPath, port);
922
922
  const baseUrl = `http://127.0.0.1:${port}`;
923
- log2(`Dev server running on ${baseUrl}`);
923
+ log3(`Dev server running on ${baseUrl}`);
924
924
  let recordingStarted = false;
925
925
  if (recordingEnabled) {
926
926
  try {
@@ -929,28 +929,28 @@ async function runTest(options) {
929
929
  throw new Error(recordingStart.stdout || "recording-start failed");
930
930
  }
931
931
  recordingStarted = true;
932
- log2("Proof recording started");
932
+ log3("Proof recording started");
933
933
  } catch (err) {
934
934
  result.proof = {
935
935
  state: "proof_missing_capture_failed",
936
936
  details: `Proof recording could not start: ${err.message}`
937
937
  };
938
- log2(result.proof.details);
938
+ log3(result.proof.details);
939
939
  }
940
940
  } else {
941
941
  result.proof = {
942
942
  state: "proof_missing_disabled",
943
943
  details: "Proof recording disabled for this run."
944
944
  };
945
- log2(result.proof.details);
945
+ log3(result.proof.details);
946
946
  }
947
947
  await browseRunner(["goto", baseUrl]);
948
948
  const plan = customPlan || buildDefaultTestPlan(baseUrl);
949
- log2(`Executing ${plan.length}-step test plan...`);
949
+ log3(`Executing ${plan.length}-step test plan...`);
950
950
  for (let i = 0; i < plan.length; i++) {
951
951
  const step = plan[i];
952
952
  const stepDesc = step.description || `${step.command} ${(step.args || []).join(" ")}`;
953
- log2(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
953
+ log3(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
954
954
  try {
955
955
  if (step.command.startsWith("assert")) {
956
956
  const assertResult = await evaluateAssertion(step, i, browseRunner);
@@ -1000,7 +1000,7 @@ async function runTest(options) {
1000
1000
  }
1001
1001
  } catch (err) {
1002
1002
  result.errors.push(`Step ${i + 1} (${step.command}): ${err.message}`);
1003
- log2(`Step ${i + 1} error: ${err.message}`);
1003
+ log3(`Step ${i + 1} error: ${err.message}`);
1004
1004
  }
1005
1005
  }
1006
1006
  if (recordingStarted) {
@@ -1012,7 +1012,7 @@ async function runTest(options) {
1012
1012
  throw new Error(recordingStop.stdout || "recording-stop did not return a file path");
1013
1013
  }
1014
1014
  result.proof.localPath = savedPath;
1015
- log2(`Proof recording finalized at ${savedPath}`);
1015
+ log3(`Proof recording finalized at ${savedPath}`);
1016
1016
  if (uploadVideo) {
1017
1017
  const videoUrl = await uploadVideo(savedPath);
1018
1018
  if (videoUrl) {
@@ -1034,14 +1034,14 @@ async function runTest(options) {
1034
1034
  }
1035
1035
  ]
1036
1036
  };
1037
- log2(`Proof recording uploaded to ${videoUrl}`);
1037
+ log3(`Proof recording uploaded to ${videoUrl}`);
1038
1038
  } else {
1039
1039
  result.proof = {
1040
1040
  state: "proof_missing_upload_failed",
1041
1041
  details: "Proof recording captured, but upload failed.",
1042
1042
  localPath: savedPath
1043
1043
  };
1044
- log2(result.proof.details);
1044
+ log3(result.proof.details);
1045
1045
  }
1046
1046
  } else {
1047
1047
  result.proof = {
@@ -1049,14 +1049,14 @@ async function runTest(options) {
1049
1049
  details: "Proof recording captured, but no upload handler was configured.",
1050
1050
  localPath: savedPath
1051
1051
  };
1052
- log2(result.proof.details);
1052
+ log3(result.proof.details);
1053
1053
  }
1054
1054
  } catch (err) {
1055
1055
  result.proof = {
1056
1056
  state: "proof_missing_capture_failed",
1057
1057
  details: `Proof recording could not be finalized: ${err.message}`
1058
1058
  };
1059
- log2(result.proof.details);
1059
+ log3(result.proof.details);
1060
1060
  }
1061
1061
  }
1062
1062
  const totalAssertions = result.assertions.length;
@@ -2015,7 +2015,7 @@ ${task.notes}` : "";
2015
2015
  ],
2016
2016
  ``,
2017
2017
  `3. Implement the task. You may read, write, and run code across any relevant repos under ${repoDir}.`,
2018
- ` - If you do significant research or investigation, write your findings to \`research.md\` in ${repoDir} so it gets saved as a project resource.`,
2018
+ ` - If you do significant research or investigation, save your findings as a resource using: \`mr update ${task.id} --resource research "Research \u2014 <short title>" '<markdown content>'\``,
2019
2019
  ...pendingSubtasks.length > 0 ? [
2020
2020
  ` - Work through each subtask in order. After completing each subtask, immediately run \`mr subtask-complete ${task.id} <subtask-id>\` to mark it done before moving on.`
2021
2021
  ] : [],
@@ -2023,7 +2023,7 @@ ${task.notes}` : "";
2023
2023
  `4. Once implementation is complete, for each repo that has changes:`,
2024
2024
  ` a. Commit all changes with a clear, descriptive message.`,
2025
2025
  ` b. Push the branch: \`git push -u origin HEAD\``,
2026
- ...hasFeedback ? [
2026
+ ...hasFeedback || hasAttachedBranch && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink) ? [
2027
2027
  ` c. The existing ${vcs === "gitlab" ? "merge request" : "pull request"} will be updated automatically when you push to the branch. No need to create a new one.`
2028
2028
  ] : [
2029
2029
  ` c. Write a structured ${vcs === "gitlab" ? "merge request" : "pull request"} description to \`${prBodyPath}\` using the template below.`,
@@ -2042,7 +2042,7 @@ ${task.notes}` : "";
2042
2042
  ``,
2043
2043
  `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.`,
2044
2044
  ``,
2045
- ...hasFeedback ? [] : [
2045
+ ...hasFeedback || hasAttachedBranch && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink) ? [] : [
2046
2046
  `## PR Description Template`,
2047
2047
  ``,
2048
2048
  `Write this template to \`${prBodyPath}\`, then replace the placeholders with the actual details from your implementation.`,
@@ -2292,7 +2292,7 @@ function buildPrototypePrompt(proto, repoDir) {
2292
2292
  }
2293
2293
  };
2294
2294
  const config = typeConfig[prototypeType] ?? typeConfig.web_app;
2295
- const typeLabel = {
2295
+ const typeLabel2 = {
2296
2296
  web_app: "Web App",
2297
2297
  mobile_app: "Mobile App",
2298
2298
  desktop_app: "Desktop App",
@@ -2306,7 +2306,7 @@ function buildPrototypePrompt(proto, repoDir) {
2306
2306
  `## Prototype Request`,
2307
2307
  `Title: ${proto.title}`,
2308
2308
  `ID: ${proto.id}`,
2309
- `Type: ${typeLabel[prototypeType] ?? prototypeType}`,
2309
+ `Type: ${typeLabel2[prototypeType] ?? prototypeType}`,
2310
2310
  ``,
2311
2311
  `## Design Prompt`,
2312
2312
  `${proto.prompt}`,
@@ -2374,7 +2374,7 @@ function buildRefinementPrompt(proto, parentFiles, repoDir) {
2374
2374
  desktop_app: "You are a desktop UI designer and frontend engineer. Your job is to REFINE an existing desktop app prototype based on user feedback.",
2375
2375
  logo: "You are a graphic designer specializing in logo and brand identity. Your job is to REFINE an existing logo prototype based on user feedback."
2376
2376
  };
2377
- const typeLabel = {
2377
+ const typeLabel2 = {
2378
2378
  web_app: "Web App",
2379
2379
  mobile_app: "Mobile App",
2380
2380
  desktop_app: "Desktop App",
@@ -2388,7 +2388,7 @@ function buildRefinementPrompt(proto, parentFiles, repoDir) {
2388
2388
  `## Prototype Request`,
2389
2389
  `Title: ${proto.title}`,
2390
2390
  `ID: ${proto.id}`,
2391
- `Type: ${typeLabel[prototypeType] ?? prototypeType}`,
2391
+ `Type: ${typeLabel2[prototypeType] ?? prototypeType}`,
2392
2392
  ``,
2393
2393
  `## Original Design Prompt`,
2394
2394
  `${proto.prompt}`,
@@ -2662,7 +2662,7 @@ var watchCommand = new Command8("watch").description(
2662
2662
  logWarn(watchTag(), `Network unavailable \u2014 pausing active tasks until connectivity returns (${reason})`);
2663
2663
  }
2664
2664
  for (const taskId of active.keys()) {
2665
- const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
2665
+ const nonTaskPrefixes = ["proto-", "repo-", "scan-", "review-", "test-"];
2666
2666
  if (nonTaskPrefixes.some((prefix) => taskId.startsWith(prefix))) continue;
2667
2667
  pauseTaskForNetwork(taskId, reason);
2668
2668
  }
@@ -2918,29 +2918,6 @@ var watchCommand = new Command8("watch").description(
2918
2918
  try {
2919
2919
  if (code === 0) {
2920
2920
  try {
2921
- const researchPath = resolve2(executionDir, "research.md");
2922
- if (existsSync7(researchPath)) {
2923
- try {
2924
- const researchContent = readFileSync5(researchPath, "utf-8");
2925
- const existingResearch = existingResources.find((r) => r.type === "research");
2926
- if (existingResearch) {
2927
- await api.patch(`/api/tasks/${task.id}/resources/${existingResearch.id}`, {
2928
- content: researchContent
2929
- });
2930
- logSuccess(prefix, `Updated existing research resource`);
2931
- } else {
2932
- await api.post(`/api/tasks/${task.id}/resources`, {
2933
- type: "research",
2934
- title: `Research \u2014 ${task.title}`,
2935
- content: researchContent
2936
- });
2937
- logSuccess(prefix, `Uploaded research.md as task resource`);
2938
- }
2939
- unlinkSync(researchPath);
2940
- } catch (err) {
2941
- logWarn(prefix, `Failed to upload research resource: ${err.message}`);
2942
- }
2943
- }
2944
2921
  const noMrPath = resolve2(executionDir, ".mr-no-mr");
2945
2922
  const noMrRequested = existsSync7(noMrPath);
2946
2923
  let noMrDescription;
@@ -2951,7 +2928,11 @@ var watchCommand = new Command8("watch").description(
2951
2928
  }
2952
2929
  const prLabel = vcs === "gitlab" ? "MR" : "PR";
2953
2930
  let prUrl = null;
2954
- if (!noMrRequested) {
2931
+ if (!noMrRequested && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink)) {
2932
+ prUrl = task.attachedBranchLink;
2933
+ logInfo(prefix, `Using attached ${prLabel} link: ${paint("cyan", prUrl)}`);
2934
+ }
2935
+ if (!noMrRequested && !prUrl) {
2955
2936
  prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
2956
2937
  if (!prUrl && !task.attachedBranch?.trim()) {
2957
2938
  prUrl = await findPrUrlAcrossRepos(legacyBranchName, repoDir, vcs);
@@ -2998,6 +2979,8 @@ var watchCommand = new Command8("watch").description(
2998
2979
  logWarn(prefix, `No ${prLabel} found for branch ${paint("cyan", branchName)}`);
2999
2980
  await postTaskUpdate(task.id, `Agent finished \u2014 no ${prLabel} found for branch ${branchName}`, "system");
3000
2981
  }
2982
+ } else if (prUrl) {
2983
+ logSuccess(prefix, `${prLabel} ready: ${paint("cyan", prUrl)}`);
3001
2984
  }
3002
2985
  const currentTask = await api.get(`/api/tasks/${task.id}`);
3003
2986
  if (currentTask.status === "completed" || currentTask.status === "review") {
@@ -3442,6 +3425,49 @@ var watchCommand = new Command8("watch").description(
3442
3425
  }
3443
3426
  });
3444
3427
  }
3428
+ function dispatchCodeReview(review, prefix, key) {
3429
+ logDispatch(prefix, `Running code review on branch ${paint("cyan", review.branch)}`);
3430
+ const reviewProc = spawn4(process.execPath, [process.argv[1], "review", "--project", review.projectId, "--report", review.id, "--branch", review.branch, "--base", review.baseBranch], {
3431
+ stdio: ["ignore", "pipe", "pipe"],
3432
+ cwd: rootDir
3433
+ });
3434
+ reviewProc.on("error", (err) => {
3435
+ logError(prefix, `Failed to spawn review: ${err.message}`);
3436
+ active.delete(key);
3437
+ queued.delete(key);
3438
+ failed.set(key, err.message);
3439
+ });
3440
+ active.set(key, {
3441
+ process: reviewProc,
3442
+ title: `review-${review.id.slice(0, 8)}`,
3443
+ repoDir: rootDir,
3444
+ startedAt: Date.now(),
3445
+ lastActivityAt: Date.now(),
3446
+ outputBytes: 0
3447
+ });
3448
+ reviewProc.stdout?.on("data", (d) => {
3449
+ const lines = d.toString().trim().split("\n");
3450
+ for (const line of lines) {
3451
+ console.log(`${timestamp()} ${watchTag()} ${prefix} ${line}`);
3452
+ }
3453
+ });
3454
+ reviewProc.stderr?.on("data", (d) => {
3455
+ const lines = d.toString().trim().split("\n");
3456
+ for (const line of lines) {
3457
+ console.error(`${timestamp()} ${watchTag()} ${prefix} ${line}`);
3458
+ }
3459
+ });
3460
+ reviewProc.on("exit", (code) => {
3461
+ active.delete(key);
3462
+ queued.delete(key);
3463
+ if (code === 0) {
3464
+ logSuccess(prefix, `Code review completed`);
3465
+ } else {
3466
+ logError(prefix, `Code review exited with code ${code}`);
3467
+ failed.set(key, `exit code ${code}`);
3468
+ }
3469
+ });
3470
+ }
3445
3471
  async function processApprovalQueue() {
3446
3472
  if (approvalRunning || approvalQueue.length === 0) return;
3447
3473
  approvalRunning = true;
@@ -3584,7 +3610,7 @@ ${divider}`);
3584
3610
  }
3585
3611
  }
3586
3612
  for (const [taskId, entry] of active) {
3587
- if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-")) continue;
3613
+ if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-") || taskId.startsWith("review-")) continue;
3588
3614
  if (!activeTaskIds.has(taskId)) {
3589
3615
  logWarn(watchTag(), `Task ${paint("yellow", taskId.slice(0, 8))} no longer active, terminating\u2026`);
3590
3616
  entry.terminatedForError = true;
@@ -3593,7 +3619,7 @@ ${divider}`);
3593
3619
  queued.delete(taskId);
3594
3620
  }
3595
3621
  }
3596
- const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
3622
+ const nonTaskPrefixes = ["proto-", "repo-", "scan-", "review-", "test-"];
3597
3623
  for (const taskId of failed.keys()) {
3598
3624
  if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
3599
3625
  if (!activeTaskIds.has(taskId)) failed.delete(taskId);
@@ -3899,6 +3925,29 @@ ${divider}`);
3899
3925
  }
3900
3926
  dispatchScan(scan, prefix, key);
3901
3927
  }
3928
+ let pendingReviews = [];
3929
+ try {
3930
+ pendingReviews = await api.get("/api/reviews?status=pending&limit=5");
3931
+ } catch (err) {
3932
+ logError(watchTag(), `Failed to fetch pending reviews: ${err.message}`);
3933
+ }
3934
+ for (const review of pendingReviews) {
3935
+ const key = `review-${review.id}`;
3936
+ if (queued.has(key)) continue;
3937
+ if (finishing.has(key)) continue;
3938
+ if (failed.has(key)) continue;
3939
+ const sid = shortId(review.id);
3940
+ const prefix = `${paint("blue", `[review:${sid}]`)}`;
3941
+ queued.add(key);
3942
+ if (dryRun) {
3943
+ logInfo(
3944
+ watchTag(),
3945
+ `${paint("yellow", "[dry-run]")} would run code review ${paint("yellow", sid)} on branch ${paint("cyan", review.branch)}`
3946
+ );
3947
+ continue;
3948
+ }
3949
+ dispatchCodeReview(review, prefix, key);
3950
+ }
3902
3951
  let reviewTasks = [];
3903
3952
  try {
3904
3953
  reviewTasks = await api.get("/api/tasks?status=review");
@@ -4182,9 +4231,9 @@ var prototypeCommand = new Command13("prototype").description("Manage prototypes
4182
4231
  };
4183
4232
  for (const p of prototypes) {
4184
4233
  const date = new Date(p.createdAt).toLocaleDateString();
4185
- const typeLabel = typeLabels[p.prototypeType] ?? p.prototypeType ?? "web";
4234
+ const typeLabel2 = typeLabels[p.prototypeType] ?? p.prototypeType ?? "web";
4186
4235
  console.log(
4187
- ` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("blue", `[${typeLabel}]`)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
4236
+ ` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("blue", `[${typeLabel2}]`)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
4188
4237
  );
4189
4238
  console.log(` ${paint4("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
4190
4239
  console.log();
@@ -4500,7 +4549,7 @@ async function checkApiConnectivity() {
4500
4549
  }
4501
4550
  }
4502
4551
  function printResults(checks) {
4503
- const maxNameLen = Math.max(...checks.map((c11) => c11.name.length));
4552
+ const maxNameLen = Math.max(...checks.map((c13) => c13.name.length));
4504
4553
  let allOk = true;
4505
4554
  for (const check of checks) {
4506
4555
  const isOptional = check.optional ?? false;
@@ -4513,16 +4562,16 @@ function printResults(checks) {
4513
4562
  return allOk;
4514
4563
  }
4515
4564
  async function autoFix(checks, agent) {
4516
- const { spawn: spawn8 } = await import("child_process");
4517
- const ghInstalled = checks.find((c11) => c11.name === "GitHub CLI (gh)").ok;
4518
- const ghAuthed = checks.find((c11) => c11.name === "GitHub CLI auth").ok;
4519
- const mrAuthed = checks.find((c11) => c11.name === "Mr. Manager CLI auth").ok;
4520
- const claudeCheck = checks.find((c11) => c11.name === "Claude Code (claude)");
4565
+ const { spawn: spawn9 } = await import("child_process");
4566
+ const ghInstalled = checks.find((c13) => c13.name === "GitHub CLI (gh)").ok;
4567
+ const ghAuthed = checks.find((c13) => c13.name === "GitHub CLI auth").ok;
4568
+ const mrAuthed = checks.find((c13) => c13.name === "Mr. Manager CLI auth").ok;
4569
+ const claudeCheck = checks.find((c13) => c13.name === "Claude Code (claude)");
4521
4570
  if (claudeCheck && !claudeCheck.ok && agent === "claude") {
4522
4571
  console.log(paint5("cyan", " Installing Claude Code..."));
4523
4572
  console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
4524
4573
  await new Promise((resolve9) => {
4525
- const child = spawn8("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
4574
+ const child = spawn9("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
4526
4575
  child.on("exit", () => resolve9());
4527
4576
  });
4528
4577
  console.log("");
@@ -4530,7 +4579,7 @@ async function autoFix(checks, agent) {
4530
4579
  if (ghInstalled && !ghAuthed) {
4531
4580
  console.log(paint5("cyan", " Running gh auth login..."));
4532
4581
  await new Promise((resolve9) => {
4533
- const child = spawn8("gh", ["auth", "login"], { stdio: "inherit" });
4582
+ const child = spawn9("gh", ["auth", "login"], { stdio: "inherit" });
4534
4583
  child.on("exit", () => resolve9());
4535
4584
  });
4536
4585
  console.log("");
@@ -4539,7 +4588,7 @@ async function autoFix(checks, agent) {
4539
4588
  console.log(paint5("cyan", " Running mr login..."));
4540
4589
  const entry = process.argv[1];
4541
4590
  await new Promise((resolve9) => {
4542
- const child = spawn8(process.execPath, [entry, "login"], { stdio: "inherit" });
4591
+ const child = spawn9(process.execPath, [entry, "login"], { stdio: "inherit" });
4543
4592
  child.on("exit", () => resolve9());
4544
4593
  });
4545
4594
  console.log("");
@@ -4580,7 +4629,7 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
4580
4629
  console.log("");
4581
4630
  return;
4582
4631
  }
4583
- const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
4632
+ const fixes = checks.filter((c13) => !c13.ok && c13.fix && !c13.optional);
4584
4633
  if (fixes.length > 0) {
4585
4634
  console.log(paint5("yellow", " To fix:"));
4586
4635
  for (const fix of fixes) {
@@ -4596,9 +4645,26 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
4596
4645
 
4597
4646
  // cli/commands/update.ts
4598
4647
  import { Command as Command15 } from "commander";
4599
- var updateCommand = new Command15("update").description("Post a status update to a task (used by agents to report progress)").argument("<task-id>", "Task ID").argument("<message>", "Status update message").option("--source <source>", "Update source: agent, system, or user", "agent").action(async (taskId, message, opts) => {
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) => {
4649
+ if (opts.resource) {
4650
+ if (!messageOrTitle || !content) {
4651
+ console.error(`Usage: mr update <task-id> --resource <type> "<title>" '<content>'`);
4652
+ process.exit(1);
4653
+ }
4654
+ await api.post(`/api/tasks/${taskId}/resources`, {
4655
+ type: opts.resource,
4656
+ title: messageOrTitle,
4657
+ content
4658
+ });
4659
+ console.log(`\u2713 Resource created (${opts.resource}): ${messageOrTitle}`);
4660
+ return;
4661
+ }
4662
+ if (!messageOrTitle) {
4663
+ console.error("Message is required for status updates.");
4664
+ process.exit(1);
4665
+ }
4600
4666
  await api.post(`/api/tasks/${taskId}/updates`, {
4601
- message,
4667
+ message: messageOrTitle,
4602
4668
  source: opts.source
4603
4669
  });
4604
4670
  console.log(`\u2713 Status update posted`);
@@ -5306,11 +5372,329 @@ var noMrCommand = new Command22("no-mr").description("Signal that a task does no
5306
5372
  console.log(` Reason: ${description}`);
5307
5373
  });
5308
5374
 
5309
- // cli/commands/scan.ts
5375
+ // cli/commands/review.ts
5310
5376
  import { Command as Command23 } from "commander";
5377
+ import { spawn as spawn7, execSync as execSync5 } from "child_process";
5378
+ var c8 = {
5379
+ reset: "\x1B[0m",
5380
+ bold: "\x1B[1m",
5381
+ dim: "\x1B[2m",
5382
+ cyan: "\x1B[36m",
5383
+ green: "\x1B[32m",
5384
+ yellow: "\x1B[33m",
5385
+ red: "\x1B[31m",
5386
+ magenta: "\x1B[35m",
5387
+ gray: "\x1B[90m",
5388
+ blue: "\x1B[34m"
5389
+ };
5390
+ function paint8(color, text) {
5391
+ return `${c8[color]}${text}${c8.reset}`;
5392
+ }
5393
+ function timestamp2() {
5394
+ return paint8("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
5395
+ }
5396
+ function tag() {
5397
+ return paint8("blue", "[review]");
5398
+ }
5399
+ function log(msg) {
5400
+ console.log(`${timestamp2()} ${tag()} ${msg}`);
5401
+ }
5402
+ function logOk(msg) {
5403
+ console.log(`${timestamp2()} ${tag()} ${paint8("green", "\u2713")} ${msg}`);
5404
+ }
5405
+ function logErr(msg) {
5406
+ console.error(`${timestamp2()} ${tag()} ${paint8("red", "\u2717")} ${msg}`);
5407
+ }
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) => {
5409
+ const config = loadConfig();
5410
+ if (!config.apiKey) {
5411
+ logErr('Not authenticated. Run "mr login" first.');
5412
+ process.exit(1);
5413
+ }
5414
+ const banner = [
5415
+ ``,
5416
+ paint8("blue", ` \u2566\u2550\u2557\u2554\u2550\u2557\u2566 \u2566\u2566\u2554\u2550\u2557\u2566 \u2566`),
5417
+ paint8("blue", ` \u2560\u2566\u255D\u2551\u2563 \u255A\u2557\u2554\u255D\u2551\u2551\u2563 \u2551\u2551\u2551`),
5418
+ paint8("blue", ` \u2569\u255A\u2550\u255A\u2550\u255D \u255A\u255D \u2569\u255A\u2550\u255D\u255A\u2569\u255D`),
5419
+ paint8("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
5420
+ paint8("dim", ` automated code review`),
5421
+ ``
5422
+ ].join("\n");
5423
+ console.log(banner);
5424
+ const projectId = opts.project || getLinkedProjectId();
5425
+ if (!projectId) {
5426
+ logErr('No project linked. Run "mr link" or pass --project <id>.');
5427
+ process.exit(1);
5428
+ }
5429
+ let project;
5430
+ try {
5431
+ project = await api.get(`/api/projects/${projectId}`);
5432
+ } catch {
5433
+ logErr(`Failed to fetch project ${projectId}`);
5434
+ process.exit(1);
5435
+ }
5436
+ let projectPath = project.localPath;
5437
+ if (!projectPath) {
5438
+ for (const [dir, pid] of Object.entries(config.directories)) {
5439
+ if (pid === projectId) {
5440
+ projectPath = dir;
5441
+ break;
5442
+ }
5443
+ }
5444
+ }
5445
+ if (!projectPath) {
5446
+ projectPath = process.cwd();
5447
+ }
5448
+ let branch = opts.branch;
5449
+ if (!branch) {
5450
+ try {
5451
+ branch = execSync5("git rev-parse --abbrev-ref HEAD", {
5452
+ cwd: projectPath,
5453
+ encoding: "utf-8"
5454
+ }).trim();
5455
+ } catch {
5456
+ logErr("Could not determine current branch. Pass --branch <name>.");
5457
+ process.exit(1);
5458
+ }
5459
+ }
5460
+ const baseBranch = opts.base || "main";
5461
+ log(`Reviewing branch: ${paint8("cyan", branch)} against ${paint8("dim", baseBranch)}`);
5462
+ log(`Project: ${paint8("cyan", project.name)}`);
5463
+ let diff;
5464
+ try {
5465
+ diff = execSync5(`git diff ${baseBranch}...${branch} -- . ':!*.lock' ':!package-lock.json' ':!pnpm-lock.yaml'`, {
5466
+ cwd: projectPath,
5467
+ encoding: "utf-8",
5468
+ maxBuffer: 10 * 1024 * 1024
5469
+ }).trim();
5470
+ } catch (err) {
5471
+ try {
5472
+ execSync5(`git fetch origin ${baseBranch}`, { cwd: projectPath, encoding: "utf-8", stdio: "pipe" });
5473
+ diff = execSync5(`git diff origin/${baseBranch}...${branch} -- . ':!*.lock' ':!package-lock.json' ':!pnpm-lock.yaml'`, {
5474
+ cwd: projectPath,
5475
+ encoding: "utf-8",
5476
+ maxBuffer: 10 * 1024 * 1024
5477
+ }).trim();
5478
+ } catch {
5479
+ logErr(`Failed to get diff between ${baseBranch} and ${branch}: ${err.message}`);
5480
+ process.exit(1);
5481
+ }
5482
+ }
5483
+ if (!diff) {
5484
+ logOk("No changes found between branches. Nothing to review.");
5485
+ process.exit(0);
5486
+ }
5487
+ const filesChanged = (() => {
5488
+ try {
5489
+ const stat = execSync5(`git diff --stat ${baseBranch}...${branch} -- . ':!*.lock' ':!package-lock.json' ':!pnpm-lock.yaml'`, {
5490
+ cwd: projectPath,
5491
+ encoding: "utf-8"
5492
+ }).trim();
5493
+ const lines = stat.split("\n");
5494
+ return Math.max(0, lines.length - 1);
5495
+ } catch {
5496
+ const fileHeaders = diff.match(/^diff --git/gm);
5497
+ return fileHeaders?.length ?? 0;
5498
+ }
5499
+ })();
5500
+ log(`Diff size: ${paint8("yellow", `${diff.length.toLocaleString()} chars`)}, ${paint8("yellow", `${filesChanged} files`)}`);
5501
+ let reportId;
5502
+ if (opts.report) {
5503
+ reportId = opts.report;
5504
+ log(`Using existing review report ${paint8("yellow", reportId.slice(0, 8))}`);
5505
+ } else {
5506
+ try {
5507
+ const report = await api.post("/api/reviews", {
5508
+ projectId,
5509
+ branch,
5510
+ baseBranch
5511
+ });
5512
+ reportId = report.id;
5513
+ log(`Created review report ${paint8("yellow", reportId.slice(0, 8))}`);
5514
+ } catch (err) {
5515
+ logErr(`Failed to create review report: ${err.message}`);
5516
+ process.exit(1);
5517
+ }
5518
+ }
5519
+ try {
5520
+ await api.patch(`/api/reviews/${reportId}`, { status: "processing" });
5521
+ } catch {
5522
+ }
5523
+ const startTime = Date.now();
5524
+ const MAX_DIFF_CHARS = 8e4;
5525
+ let truncatedDiff = diff;
5526
+ if (diff.length > MAX_DIFF_CHARS) {
5527
+ truncatedDiff = diff.slice(0, MAX_DIFF_CHARS) + "\n\n... (diff truncated, review covers first " + MAX_DIFF_CHARS.toLocaleString() + " characters)";
5528
+ log(paint8("yellow", `Diff truncated to ${MAX_DIFF_CHARS.toLocaleString()} chars for review`));
5529
+ }
5530
+ try {
5531
+ log("Running code review with Claude...");
5532
+ const prompt2 = buildReviewPrompt(branch, baseBranch, truncatedDiff);
5533
+ const output = await runClaude(prompt2);
5534
+ const result = parseReviewOutput(output);
5535
+ const duration = Date.now() - startTime;
5536
+ let wasCancelled = false;
5537
+ try {
5538
+ const current = await api.get(`/api/reviews/${reportId}`);
5539
+ wasCancelled = current.status === "cancelled";
5540
+ } catch {
5541
+ }
5542
+ if (wasCancelled) {
5543
+ log(paint8("yellow", "Review was cancelled \u2014 discarding results."));
5544
+ process.exit(0);
5545
+ }
5546
+ await api.patch(`/api/reviews/${reportId}`, {
5547
+ status: "completed",
5548
+ summary: result.summary,
5549
+ findings: result.findings,
5550
+ filesReviewed: filesChanged,
5551
+ reviewDurationMs: duration
5552
+ });
5553
+ logOk(`Review completed in ${paint8("cyan", formatDuration(duration))}`);
5554
+ logOk(`Found ${paint8("yellow", String(result.findings.length))} findings`);
5555
+ if (result.findings.length > 0) {
5556
+ console.log("");
5557
+ const critical = result.findings.filter((f) => f.severity === "critical").length;
5558
+ const high = result.findings.filter((f) => f.severity === "high").length;
5559
+ const medium = result.findings.filter((f) => f.severity === "medium").length;
5560
+ const low = result.findings.filter((f) => f.severity === "low").length;
5561
+ if (critical > 0) console.log(` ${paint8("red", "\u25CF")} ${critical} critical`);
5562
+ if (high > 0) console.log(` ${paint8("red", "\u25CF")} ${high} high`);
5563
+ if (medium > 0) console.log(` ${paint8("yellow", "\u25CF")} ${medium} medium`);
5564
+ if (low > 0) console.log(` ${paint8("dim", "\u25CF")} ${low} low`);
5565
+ console.log("");
5566
+ }
5567
+ if (result.summary) {
5568
+ console.log(paint8("dim", " " + result.summary));
5569
+ console.log("");
5570
+ }
5571
+ } catch (err) {
5572
+ const duration = Date.now() - startTime;
5573
+ const errorMessage = err.message || "Unknown error";
5574
+ logErr(`Review failed: ${errorMessage}`);
5575
+ try {
5576
+ await api.patch(`/api/reviews/${reportId}`, {
5577
+ status: "failed",
5578
+ errorMessage,
5579
+ reviewDurationMs: duration
5580
+ });
5581
+ } catch {
5582
+ }
5583
+ process.exit(1);
5584
+ }
5585
+ });
5586
+ function formatDuration(ms) {
5587
+ if (ms < 1e3) return `${ms}ms`;
5588
+ const s = Math.round(ms / 1e3);
5589
+ if (s < 60) return `${s}s`;
5590
+ return `${Math.floor(s / 60)}m ${s % 60}s`;
5591
+ }
5592
+ function buildReviewPrompt(branch, baseBranch, diff) {
5593
+ return `You are a senior code reviewer. Review the following git diff for branch "${branch}" compared to "${baseBranch}".
5594
+
5595
+ Analyze the code changes and produce a JSON response with your review findings.
5596
+
5597
+ Focus on:
5598
+ - Bugs and logical errors
5599
+ - Security vulnerabilities (XSS, injection, auth issues, secrets exposure)
5600
+ - Performance issues (N+1 queries, missing indexes, unnecessary re-renders)
5601
+ - Code style and best practices violations
5602
+ - Suggestions for improvement
5603
+ - Nitpicks (minor style/naming issues)
5604
+
5605
+ For each finding, provide:
5606
+ - A unique ID (e.g. "f1", "f2", etc.)
5607
+ - Type: "bug", "security", "performance", "style", "suggestion", or "nitpick"
5608
+ - Severity: "critical", "high", "medium", or "low"
5609
+ - Title: a brief one-line summary
5610
+ - Description: detailed explanation of the issue
5611
+ - File: the file path where the issue was found
5612
+ - Line: the approximate line number in the new code (optional)
5613
+ - Suggestion: suggested fix or improvement (optional, include actual code when possible)
5614
+
5615
+ Return ONLY a JSON object with this structure (no markdown, no explanation before/after):
5616
+ {
5617
+ "summary": "Brief overall assessment of the code changes (2-3 sentences)",
5618
+ "findings": [
5619
+ {
5620
+ "id": "f1",
5621
+ "type": "bug",
5622
+ "severity": "high",
5623
+ "title": "Brief title",
5624
+ "description": "Detailed description",
5625
+ "file": "path/to/file.ts",
5626
+ "line": 42,
5627
+ "suggestion": "Suggested fix code"
5628
+ }
5629
+ ]
5630
+ }
5631
+
5632
+ If the code looks good with no issues, return an empty findings array with a positive summary.
5633
+
5634
+ Here is the diff to review:
5635
+
5636
+ \`\`\`diff
5637
+ ${diff}
5638
+ \`\`\``;
5639
+ }
5640
+ function runClaude(prompt2) {
5641
+ return new Promise((resolve9, reject) => {
5642
+ const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
5643
+ stdio: ["ignore", "pipe", "pipe"]
5644
+ });
5645
+ let output = "";
5646
+ let errOutput = "";
5647
+ child.stdout?.on("data", (d) => {
5648
+ output += d.toString();
5649
+ });
5650
+ child.stderr?.on("data", (d) => {
5651
+ errOutput += d.toString();
5652
+ });
5653
+ child.on("exit", (code) => {
5654
+ if (code === 0) resolve9(output.trim());
5655
+ else reject(new Error(`claude exited with code ${code}
5656
+ ${errOutput.trim()}`));
5657
+ });
5658
+ });
5659
+ }
5660
+ function parseReviewOutput(output) {
5661
+ const jsonMatch = output.match(/\{[\s\S]*\}/);
5662
+ if (!jsonMatch) {
5663
+ return {
5664
+ summary: "Failed to parse review output",
5665
+ findings: []
5666
+ };
5667
+ }
5668
+ try {
5669
+ const parsed = JSON.parse(jsonMatch[0]);
5670
+ return {
5671
+ summary: parsed.summary || "",
5672
+ findings: (parsed.findings || []).map((f) => ({
5673
+ id: f.id || `f${Math.random().toString(36).slice(2, 8)}`,
5674
+ type: f.type || "suggestion",
5675
+ severity: f.severity || "medium",
5676
+ title: f.title || "Untitled finding",
5677
+ description: f.description || "",
5678
+ file: f.file || "unknown",
5679
+ line: f.line,
5680
+ endLine: f.endLine,
5681
+ suggestion: f.suggestion,
5682
+ status: "new"
5683
+ }))
5684
+ };
5685
+ } catch {
5686
+ return {
5687
+ summary: "Failed to parse review JSON",
5688
+ findings: []
5689
+ };
5690
+ }
5691
+ }
5692
+
5693
+ // cli/commands/scan.ts
5694
+ import { Command as Command24 } from "commander";
5311
5695
 
5312
5696
  // lib/scanner/index.ts
5313
- import { spawn as spawn7 } from "child_process";
5697
+ import { spawn as spawn8 } from "child_process";
5314
5698
 
5315
5699
  // lib/scanner/config.ts
5316
5700
  import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
@@ -5379,7 +5763,7 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
5379
5763
  // lib/scanner/codebase-analysis.ts
5380
5764
  import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
5381
5765
  import { join as join10, relative } from "path";
5382
- import { execSync as execSync5 } from "child_process";
5766
+ import { execSync as execSync6 } from "child_process";
5383
5767
  function resolveDir(projectPath, candidates) {
5384
5768
  for (const candidate of candidates) {
5385
5769
  const dir = join10(projectPath, candidate);
@@ -5505,7 +5889,7 @@ function extractInternalLinks(projectPath) {
5505
5889
  }
5506
5890
  function getRecentCommits(projectPath, count = 20) {
5507
5891
  try {
5508
- const output = execSync5(
5892
+ const output = execSync6(
5509
5893
  `git log --oneline -${count} --no-decorate`,
5510
5894
  { cwd: projectPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5511
5895
  );
@@ -5773,10 +6157,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
5773
6157
  ${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
5774
6158
 
5775
6159
  **Components:**
5776
- ${codebaseAnalysis.components.slice(0, 15).map((c11) => `- ${c11}`).join("\n")}
6160
+ ${codebaseAnalysis.components.slice(0, 15).map((c13) => `- ${c13}`).join("\n")}
5777
6161
 
5778
6162
  **Recent Git Commits:**
5779
- ${codebaseAnalysis.recentCommits.slice(0, 8).map((c11) => `- ${c11}`).join("\n")}
6163
+ ${codebaseAnalysis.recentCommits.slice(0, 8).map((c13) => `- ${c13}`).join("\n")}
5780
6164
 
5781
6165
  **Completed Tasks:**
5782
6166
  ${context.completedTasks.slice(0, 10).map((t) => `- ${t.title}`).join("\n") || "None"}
@@ -5921,7 +6305,7 @@ async function runScanPipeline(opts) {
5921
6305
  crawlResults,
5922
6306
  context.priorFindings
5923
6307
  );
5924
- const synthesisResult = await runClaude(prompt2);
6308
+ const synthesisResult = await runClaude2(prompt2);
5925
6309
  const parsed = parseSynthesisOutput(synthesisResult);
5926
6310
  const scanDurationMs = Date.now() - startTime;
5927
6311
  opts.onLog(`Scan complete in ${Math.round(scanDurationMs / 1e3)}s \u2014 ${parsed.findings.length} findings`);
@@ -5982,9 +6366,9 @@ async function fetchScanContext(opts) {
5982
6366
  priorFindings
5983
6367
  };
5984
6368
  }
5985
- function runClaude(prompt2) {
6369
+ function runClaude2(prompt2) {
5986
6370
  return new Promise((resolve9, reject) => {
5987
- const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
6371
+ const child = spawn8("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
5988
6372
  stdio: ["ignore", "pipe", "pipe"]
5989
6373
  });
5990
6374
  let output = "";
@@ -6040,7 +6424,7 @@ function parseSynthesisOutput(output) {
6040
6424
  }
6041
6425
 
6042
6426
  // cli/commands/scan.ts
6043
- var c8 = {
6427
+ var c9 = {
6044
6428
  reset: "\x1B[0m",
6045
6429
  bold: "\x1B[1m",
6046
6430
  dim: "\x1B[2m",
@@ -6051,53 +6435,53 @@ var c8 = {
6051
6435
  magenta: "\x1B[35m",
6052
6436
  gray: "\x1B[90m"
6053
6437
  };
6054
- function paint8(color, text) {
6055
- return `${c8[color]}${text}${c8.reset}`;
6438
+ function paint9(color, text) {
6439
+ return `${c9[color]}${text}${c9.reset}`;
6056
6440
  }
6057
- function timestamp2() {
6058
- return paint8("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
6441
+ function timestamp3() {
6442
+ return paint9("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
6059
6443
  }
6060
6444
  function scanTag() {
6061
- return paint8("magenta", "[scan]");
6445
+ return paint9("magenta", "[scan]");
6062
6446
  }
6063
- function log(msg) {
6064
- console.log(`${timestamp2()} ${scanTag()} ${msg}`);
6447
+ function log2(msg) {
6448
+ console.log(`${timestamp3()} ${scanTag()} ${msg}`);
6065
6449
  }
6066
- function logOk(msg) {
6067
- console.log(`${timestamp2()} ${scanTag()} ${paint8("green", "\u2713")} ${msg}`);
6450
+ function logOk2(msg) {
6451
+ console.log(`${timestamp3()} ${scanTag()} ${paint9("green", "\u2713")} ${msg}`);
6068
6452
  }
6069
- function logErr(msg) {
6070
- console.error(`${timestamp2()} ${scanTag()} ${paint8("red", "\u2717")} ${msg}`);
6453
+ function logErr2(msg) {
6454
+ console.error(`${timestamp3()} ${scanTag()} ${paint9("red", "\u2717")} ${msg}`);
6071
6455
  }
6072
- var scanCommand = new Command23("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) => {
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) => {
6073
6457
  const config = loadConfig();
6074
6458
  if (!config.apiKey) {
6075
- logErr('Not authenticated. Run "mr login" first.');
6459
+ logErr2('Not authenticated. Run "mr login" first.');
6076
6460
  process.exit(1);
6077
6461
  }
6078
6462
  const banner = [
6079
6463
  ``,
6080
- paint8("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
6081
- paint8("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
6082
- paint8("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
6083
- paint8("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
6084
- paint8("dim", ` autonomous product scanner`),
6464
+ paint9("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
6465
+ paint9("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
6466
+ paint9("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
6467
+ paint9("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
6468
+ paint9("dim", ` autonomous product scanner`),
6085
6469
  ``
6086
6470
  ].join("\n");
6087
6471
  console.log(banner);
6088
6472
  const projectId = opts.project || getLinkedProjectId();
6089
6473
  if (!projectId) {
6090
- logErr('No project linked. Run "mr link" or pass --project <id>.');
6474
+ logErr2('No project linked. Run "mr link" or pass --project <id>.');
6091
6475
  process.exit(1);
6092
6476
  }
6093
6477
  let project;
6094
6478
  try {
6095
6479
  project = await api.get(`/api/projects/${projectId}`);
6096
6480
  } catch {
6097
- logErr(`Failed to fetch project ${projectId}`);
6481
+ logErr2(`Failed to fetch project ${projectId}`);
6098
6482
  process.exit(1);
6099
6483
  }
6100
- log(`Scanning project: ${paint8("cyan", project.name)}`);
6484
+ log2(`Scanning project: ${paint9("cyan", project.name)}`);
6101
6485
  let projectPath = project.localPath;
6102
6486
  if (!projectPath) {
6103
6487
  for (const [dir, pid] of Object.entries(config.directories)) {
@@ -6113,12 +6497,12 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6113
6497
  let reportId;
6114
6498
  if (opts.report) {
6115
6499
  reportId = opts.report;
6116
- log(`Using existing scan report ${paint8("yellow", reportId.slice(0, 8))}`);
6500
+ log2(`Using existing scan report ${paint9("yellow", reportId.slice(0, 8))}`);
6117
6501
  } else {
6118
6502
  try {
6119
6503
  const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
6120
6504
  if (scans.length > 0) {
6121
- logErr("A scan is already in progress for this project. Wait for it to complete.");
6505
+ logErr2("A scan is already in progress for this project. Wait for it to complete.");
6122
6506
  process.exit(1);
6123
6507
  }
6124
6508
  } catch {
@@ -6129,9 +6513,9 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6129
6513
  status: "pending"
6130
6514
  });
6131
6515
  reportId = report.id;
6132
- log(`Created scan report ${paint8("yellow", reportId.slice(0, 8))}`);
6516
+ log2(`Created scan report ${paint9("yellow", reportId.slice(0, 8))}`);
6133
6517
  } catch (err) {
6134
- logErr(`Failed to create scan report: ${err.message}`);
6518
+ logErr2(`Failed to create scan report: ${err.message}`);
6135
6519
  process.exit(1);
6136
6520
  }
6137
6521
  }
@@ -6142,7 +6526,7 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6142
6526
  try {
6143
6527
  const current = await api.get(`/api/scans/${reportId}`);
6144
6528
  if (current.status === "cancelled") {
6145
- log(paint8("yellow", "Scan was cancelled \u2014 aborting."));
6529
+ log2(paint9("yellow", "Scan was cancelled \u2014 aborting."));
6146
6530
  process.exit(0);
6147
6531
  }
6148
6532
  } catch {
@@ -6156,9 +6540,9 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6156
6540
  apiUrl: config.apiUrl,
6157
6541
  apiKey: config.apiKey,
6158
6542
  runBrowse: runBrowseCommand2,
6159
- onLog: log,
6543
+ onLog: log2,
6160
6544
  onProgress: (phase, detail) => {
6161
- log(`${paint8("dim", `[${phase}]`)} ${detail}`);
6545
+ log2(`${paint9("dim", `[${phase}]`)} ${detail}`);
6162
6546
  }
6163
6547
  });
6164
6548
  let wasCancelled = false;
@@ -6170,7 +6554,7 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6170
6554
  } catch {
6171
6555
  }
6172
6556
  if (wasCancelled) {
6173
- log(paint8("yellow", "Scan was cancelled by user \u2014 discarding results."));
6557
+ log2(paint9("yellow", "Scan was cancelled by user \u2014 discarding results."));
6174
6558
  process.exit(0);
6175
6559
  }
6176
6560
  await api.patch(`/api/scans/${reportId}`, {
@@ -6181,37 +6565,37 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6181
6565
  scanDurationMs: result.scanDurationMs,
6182
6566
  routesCrawled: result.routesCrawled
6183
6567
  });
6184
- logOk(`Scan complete \u2014 ${paint8("cyan", String(result.findings.length))} findings`);
6568
+ logOk2(`Scan complete \u2014 ${paint9("cyan", String(result.findings.length))} findings`);
6185
6569
  console.log("");
6186
- console.log(` ${paint8("bold", "Summary:")} ${result.summary}`);
6570
+ console.log(` ${paint9("bold", "Summary:")} ${result.summary}`);
6187
6571
  console.log("");
6188
6572
  const high = result.findings.filter((f) => f.priority === "high");
6189
6573
  const medium = result.findings.filter((f) => f.priority === "medium");
6190
6574
  const low = result.findings.filter((f) => f.priority === "low");
6191
6575
  if (high.length > 0) {
6192
- console.log(` ${paint8("bold", paint8("red", `High Priority (${high.length})`))}`);
6576
+ console.log(` ${paint9("bold", paint9("red", `High Priority (${high.length})`))}`);
6193
6577
  for (const f of high) {
6194
- console.log(` ${paint8("red", "\u25CF")} [${f.type}] ${f.title}`);
6195
- console.log(` ${paint8("dim", f.description.slice(0, 120))}`);
6578
+ console.log(` ${paint9("red", "\u25CF")} [${f.type}] ${f.title}`);
6579
+ console.log(` ${paint9("dim", f.description.slice(0, 120))}`);
6196
6580
  }
6197
6581
  console.log("");
6198
6582
  }
6199
6583
  if (medium.length > 0) {
6200
- console.log(` ${paint8("bold", paint8("yellow", `Medium Priority (${medium.length})`))}`);
6584
+ console.log(` ${paint9("bold", paint9("yellow", `Medium Priority (${medium.length})`))}`);
6201
6585
  for (const f of medium) {
6202
- console.log(` ${paint8("yellow", "\u25CF")} [${f.type}] ${f.title}`);
6586
+ console.log(` ${paint9("yellow", "\u25CF")} [${f.type}] ${f.title}`);
6203
6587
  }
6204
6588
  console.log("");
6205
6589
  }
6206
6590
  if (low.length > 0) {
6207
- console.log(` ${paint8("dim", `Low Priority (${low.length})`)} `);
6591
+ console.log(` ${paint9("dim", `Low Priority (${low.length})`)} `);
6208
6592
  for (const f of low) {
6209
- console.log(` ${paint8("dim", `\u25CB [${f.type}] ${f.title}`)}`);
6593
+ console.log(` ${paint9("dim", `\u25CB [${f.type}] ${f.title}`)}`);
6210
6594
  }
6211
6595
  console.log("");
6212
6596
  }
6213
6597
  } catch (err) {
6214
- logErr(`Scan failed: ${err.message}`);
6598
+ logErr2(`Scan failed: ${err.message}`);
6215
6599
  try {
6216
6600
  await api.patch(`/api/scans/${reportId}`, {
6217
6601
  status: "failed",
@@ -6224,7 +6608,7 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6224
6608
  });
6225
6609
 
6226
6610
  // cli/commands/doctor.ts
6227
- import { Command as Command24 } from "commander";
6611
+ import { Command as Command25 } from "commander";
6228
6612
  import { existsSync as existsSync15 } from "fs";
6229
6613
  import { homedir as homedir2 } from "os";
6230
6614
  import { join as join11 } from "path";
@@ -6272,7 +6656,7 @@ async function checkProjectLink() {
6272
6656
  optional: true
6273
6657
  };
6274
6658
  }
6275
- var doctorCommand = new Command24("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
6659
+ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
6276
6660
  const banner = [
6277
6661
  ``,
6278
6662
  paint5("cyan", ` MR DOCTOR`),
@@ -6303,7 +6687,7 @@ var doctorCommand = new Command24("doctor").description("Diagnose Mr. Manager CL
6303
6687
  console.log("");
6304
6688
  return;
6305
6689
  }
6306
- const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
6690
+ const fixes = checks.filter((c13) => !c13.ok && c13.fix && !c13.optional);
6307
6691
  if (fixes.length > 0) {
6308
6692
  console.log(paint5("yellow", " To fix:"));
6309
6693
  for (const fix of fixes) {
@@ -6315,14 +6699,14 @@ var doctorCommand = new Command24("doctor").description("Diagnose Mr. Manager CL
6315
6699
  });
6316
6700
 
6317
6701
  // cli/commands/prompt-audit.ts
6318
- import { Command as Command25 } from "commander";
6702
+ import { Command as Command26 } from "commander";
6319
6703
  import { resolve as resolve8 } from "path";
6320
6704
  import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
6321
6705
  function auditLine(label, tokens) {
6322
6706
  const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
6323
6707
  return ` ${label.padEnd(30)} ${formatTokenCount(tokens).padStart(8)} ${bar}`;
6324
6708
  }
6325
- var promptAuditCommand = new Command25("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) => {
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) => {
6326
6710
  const results = [];
6327
6711
  if (opts.task) {
6328
6712
  try {
@@ -6527,8 +6911,8 @@ ${r.jobType} [${r.identifier}]`);
6527
6911
  });
6528
6912
 
6529
6913
  // cli/commands/skill.ts
6530
- import { Command as Command26 } from "commander";
6531
- var c9 = {
6914
+ import { Command as Command27 } from "commander";
6915
+ var c10 = {
6532
6916
  reset: "\x1B[0m",
6533
6917
  bold: "\x1B[1m",
6534
6918
  dim: "\x1B[2m",
@@ -6536,7 +6920,7 @@ var c9 = {
6536
6920
  green: "\x1B[32m",
6537
6921
  yellow: "\x1B[33m"
6538
6922
  };
6539
- var skillCommand = new Command26("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
6923
+ var skillCommand = new Command27("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
6540
6924
  skillCommand.command("list").alias("ls").description("List all skills").option("--category <category>", "Filter by category").action(async (opts) => {
6541
6925
  const params = new URLSearchParams();
6542
6926
  if (opts.category) params.set("category", opts.category);
@@ -6544,17 +6928,17 @@ skillCommand.command("list").alias("ls").description("List all skills").option("
6544
6928
  `/api/skills${params.toString() ? `?${params}` : ""}`
6545
6929
  );
6546
6930
  if (skills.length === 0) {
6547
- console.log(`${c9.dim}No skills found.${c9.reset}`);
6931
+ console.log(`${c10.dim}No skills found.${c10.reset}`);
6548
6932
  return;
6549
6933
  }
6550
6934
  for (const skill of skills) {
6551
- const cat = skill.category ? ` ${c9.dim}[${skill.category}]${c9.reset}` : "";
6552
- const scope = skill.projectId ? ` ${c9.dim}(project)${c9.reset}` : ` ${c9.dim}(global)${c9.reset}`;
6553
- console.log(` ${c9.cyan}${skill.name}${c9.reset}${cat}${scope}`);
6935
+ const cat = skill.category ? ` ${c10.dim}[${skill.category}]${c10.reset}` : "";
6936
+ const scope = skill.projectId ? ` ${c10.dim}(project)${c10.reset}` : ` ${c10.dim}(global)${c10.reset}`;
6937
+ console.log(` ${c10.cyan}${skill.name}${c10.reset}${cat}${scope}`);
6554
6938
  if (skill.description) {
6555
- console.log(` ${c9.dim}${skill.description}${c9.reset}`);
6939
+ console.log(` ${c10.dim}${skill.description}${c10.reset}`);
6556
6940
  }
6557
- console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
6941
+ console.log(` ${c10.dim}id: ${skill.id}${c10.reset}`);
6558
6942
  }
6559
6943
  });
6560
6944
  skillCommand.command("create").description("Create a new skill from a markdown file or inline content").argument("<name>", "Skill name").option("-d, --description <desc>", "Short description").option("-c, --category <cat>", "Category (e.g. Deployment, Testing)").option("-f, --file <path>", "Read content from a markdown file").option("--content <text>", "Inline markdown content").option("-p, --project", "Scope to the linked project").action(async (name, opts) => {
@@ -6590,7 +6974,7 @@ skillCommand.command("create").description("Create a new skill from a markdown f
6590
6974
  projectId
6591
6975
  });
6592
6976
  console.log(
6593
- `${c9.green}Created skill:${c9.reset} ${c9.bold}${skill.name}${c9.reset} ${c9.dim}(${skill.id})${c9.reset}`
6977
+ `${c10.green}Created skill:${c10.reset} ${c10.bold}${skill.name}${c10.reset} ${c10.dim}(${skill.id})${c10.reset}`
6594
6978
  );
6595
6979
  });
6596
6980
  skillCommand.command("generate").alias("gen").description("Generate a new skill using AI from a text prompt").argument("<prompt>", "Describe the skill to generate").option("-p, --project", "Scope to the linked project").action(async (prompt2, opts) => {
@@ -6604,40 +6988,158 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
6604
6988
  process.exit(1);
6605
6989
  }
6606
6990
  }
6607
- console.log(`${c9.dim}Generating skill...${c9.reset}`);
6991
+ console.log(`${c10.dim}Generating skill...${c10.reset}`);
6608
6992
  try {
6609
6993
  const skill = await api.post("/api/skills/generate", {
6610
6994
  prompt: prompt2,
6611
6995
  projectId
6612
6996
  });
6613
6997
  console.log(
6614
- `${c9.green}Generated skill:${c9.reset} ${c9.bold}${skill.name}${c9.reset}`
6998
+ `${c10.green}Generated skill:${c10.reset} ${c10.bold}${skill.name}${c10.reset}`
6615
6999
  );
6616
7000
  if (skill.description) {
6617
- console.log(` ${c9.dim}${skill.description}${c9.reset}`);
7001
+ console.log(` ${c10.dim}${skill.description}${c10.reset}`);
6618
7002
  }
6619
7003
  if (skill.category) {
6620
- console.log(` ${c9.dim}Category: ${skill.category}${c9.reset}`);
7004
+ console.log(` ${c10.dim}Category: ${skill.category}${c10.reset}`);
6621
7005
  }
6622
- console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
7006
+ console.log(` ${c10.dim}id: ${skill.id}${c10.reset}`);
6623
7007
  } catch (err) {
6624
7008
  console.error(`Failed to generate skill: ${err.message}`);
6625
7009
  process.exit(1);
6626
7010
  }
6627
7011
  });
6628
7012
 
7013
+ // cli/commands/resource.ts
7014
+ import { Command as Command28 } from "commander";
7015
+ var c11 = {
7016
+ reset: "\x1B[0m",
7017
+ bold: "\x1B[1m",
7018
+ dim: "\x1B[2m",
7019
+ cyan: "\x1B[36m",
7020
+ green: "\x1B[32m",
7021
+ yellow: "\x1B[33m",
7022
+ magenta: "\x1B[35m"
7023
+ };
7024
+ var TYPE_COLORS = {
7025
+ plan: c11.cyan,
7026
+ research: c11.magenta,
7027
+ "test-plan": c11.yellow,
7028
+ note: c11.green
7029
+ };
7030
+ function typeLabel(type) {
7031
+ const color = TYPE_COLORS[type] ?? c11.dim;
7032
+ return `${color}${type}${c11.reset}`;
7033
+ }
7034
+ var resourceCommand = new Command28("resource").description("Manage resources \u2014 documents, plans, research, and notes");
7035
+ 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
+ const params = new URLSearchParams();
7037
+ if (opts.all) {
7038
+ params.set("all", "true");
7039
+ } else {
7040
+ const projectId = getLinkedProjectId();
7041
+ if (projectId) params.set("projectId", projectId);
7042
+ }
7043
+ const resources = await api.get(
7044
+ `/api/resources${params.toString() ? `?${params}` : ""}`
7045
+ );
7046
+ if (resources.length === 0) {
7047
+ console.log(`${c11.dim}No resources found.${c11.reset}`);
7048
+ return;
7049
+ }
7050
+ for (const r of resources) {
7051
+ const project = r.projectName ? ` ${c11.dim}[${r.projectName}]${c11.reset}` : "";
7052
+ console.log(` ${typeLabel(r.type)} ${c11.bold}${r.title}${c11.reset}${project}`);
7053
+ console.log(` ${c11.dim}id: ${r.id}${c11.reset}`);
7054
+ }
7055
+ });
7056
+ resourceCommand.command("create").description("Create a new resource from a file or inline content").argument("<title>", "Resource title").option("-t, --type <type>", "Resource type (note, plan, research, test-plan)", "note").option("-f, --file <path>", "Read content from a file").option("--content <text>", "Inline content").option("--task <taskId>", "Attach to an existing task instead of creating a standalone resource").option("-p, --project", "Scope to the linked project").action(async (title, opts) => {
7057
+ let content = opts.content ?? "";
7058
+ if (opts.file) {
7059
+ const { readFileSync: readFileSync13 } = await import("fs");
7060
+ try {
7061
+ content = readFileSync13(opts.file, "utf-8");
7062
+ } catch (err) {
7063
+ console.error(`Failed to read file: ${err.message}`);
7064
+ process.exit(1);
7065
+ }
7066
+ }
7067
+ if (opts.task) {
7068
+ if (!content.trim()) {
7069
+ console.error("Content is required. Use --file <path> or --content <text>.");
7070
+ process.exit(1);
7071
+ }
7072
+ const resource = await api.post(`/api/tasks/${opts.task}/resources`, {
7073
+ type: opts.type,
7074
+ title,
7075
+ content: content.trim()
7076
+ });
7077
+ console.log(
7078
+ `${c11.green}Created resource:${c11.reset} ${c11.bold}${title}${c11.reset} ${c11.dim}(${resource.id})${c11.reset}`
7079
+ );
7080
+ console.log(` ${c11.dim}Attached to task ${opts.task}${c11.reset}`);
7081
+ return;
7082
+ }
7083
+ let projectId;
7084
+ if (opts.project) {
7085
+ projectId = getLinkedProjectId() ?? void 0;
7086
+ if (!projectId) {
7087
+ console.error(
7088
+ 'No project linked to this directory. Run "mr link <project-id>" first.'
7089
+ );
7090
+ process.exit(1);
7091
+ }
7092
+ }
7093
+ const result = await api.post("/api/resources", {
7094
+ type: opts.type,
7095
+ title,
7096
+ content: content.trim() || " ",
7097
+ projectId
7098
+ });
7099
+ console.log(
7100
+ `${c11.green}Created resource:${c11.reset} ${c11.bold}${title}${c11.reset} ${c11.dim}(${result.resource.id})${c11.reset}`
7101
+ );
7102
+ });
7103
+ resourceCommand.command("generate").alias("gen").description("Generate a resource using AI from a text prompt").argument("<prompt>", "Describe the resource to generate").option("-t, --type <type>", "Resource type (plan or research)", "research").option("-p, --project", "Scope to the linked project").action(async (prompt2, opts) => {
7104
+ const type = opts.type;
7105
+ if (type !== "plan" && type !== "research") {
7106
+ console.error('Type must be "plan" or "research" for AI generation.');
7107
+ process.exit(1);
7108
+ }
7109
+ let projectId;
7110
+ if (opts.project) {
7111
+ projectId = getLinkedProjectId() ?? void 0;
7112
+ if (!projectId) {
7113
+ console.error(
7114
+ 'No project linked to this directory. Run "mr link <project-id>" first.'
7115
+ );
7116
+ process.exit(1);
7117
+ }
7118
+ }
7119
+ console.log(`${c11.dim}Generating ${type}...${c11.reset}`);
7120
+ const result = await api.post("/api/resources", {
7121
+ type,
7122
+ prompt: prompt2,
7123
+ projectId
7124
+ });
7125
+ console.log(
7126
+ `${c11.green}Queued:${c11.reset} ${c11.bold}${result.task.title}${c11.reset}`
7127
+ );
7128
+ console.log(` ${c11.dim}task: ${result.task.id}${c11.reset}`);
7129
+ });
7130
+
6629
7131
  // cli/commands/tests.ts
6630
- import { Command as Command27 } from "commander";
6631
- var c10 = {
7132
+ import { Command as Command29 } from "commander";
7133
+ var c12 = {
6632
7134
  reset: "\x1B[0m",
6633
7135
  dim: "\x1B[2m",
6634
7136
  yellow: "\x1B[33m"
6635
7137
  };
6636
- var testsCommand = new Command27("tests").description("List MR Test scenarios for the linked project").action(async () => {
7138
+ var testsCommand = new Command29("tests").description("List MR Test scenarios for the linked project").action(async () => {
6637
7139
  const projectId = getLinkedProjectId();
6638
7140
  if (!projectId) {
6639
7141
  console.error(
6640
- `${c10.yellow}No project linked to this directory.${c10.reset} Run "mr link <project-id>" first.`
7142
+ `${c12.yellow}No project linked to this directory.${c12.reset} Run "mr link <project-id>" first.`
6641
7143
  );
6642
7144
  process.exit(1);
6643
7145
  }
@@ -6650,13 +7152,13 @@ var testsCommand = new Command27("tests").description("List MR Test scenarios fo
6650
7152
  process.exit(1);
6651
7153
  }
6652
7154
  if (scenarios.length === 0) {
6653
- console.log(`${c10.dim}No test scenarios found for this project.${c10.reset}`);
7155
+ console.log(`${c12.dim}No test scenarios found for this project.${c12.reset}`);
6654
7156
  return;
6655
7157
  }
6656
7158
  for (const scenario of scenarios) {
6657
7159
  console.log(`### ${scenario.name}`);
6658
7160
  if (scenario.description) {
6659
- console.log(`${c10.dim}${scenario.description}${c10.reset}`);
7161
+ console.log(`${c12.dim}${scenario.description}${c12.reset}`);
6660
7162
  console.log();
6661
7163
  }
6662
7164
  console.log(scenario.content);
@@ -6671,7 +7173,7 @@ var userArgs = process.argv.slice(2);
6671
7173
  var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
6672
7174
  var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
6673
7175
  if (isFirstRun && !shouldBypass) {
6674
- const c11 = {
7176
+ const c13 = {
6675
7177
  reset: "\x1B[0m",
6676
7178
  bold: "\x1B[1m",
6677
7179
  dim: "\x1B[2m",
@@ -6681,28 +7183,28 @@ if (isFirstRun && !shouldBypass) {
6681
7183
  magenta: "\x1B[35m"
6682
7184
  };
6683
7185
  console.log("");
6684
- console.log(`${c11.cyan} \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2550\u2557${c11.reset}`);
6685
- console.log(`${c11.magenta} \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563\u2551\u2551\u2551\u2560\u2550\u2563\u2551 \u2566\u2551\u2563 \u2560\u2566\u255D${c11.reset}`);
6686
- console.log(`${c11.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c11.reset}`);
6687
- console.log(`${c11.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c11.reset}`);
7186
+ console.log(`${c13.cyan} \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2550\u2557${c13.reset}`);
7187
+ console.log(`${c13.magenta} \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563\u2551\u2551\u2551\u2560\u2550\u2563\u2551 \u2566\u2551\u2563 \u2560\u2566\u255D${c13.reset}`);
7188
+ console.log(`${c13.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c13.reset}`);
7189
+ console.log(`${c13.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c13.reset}`);
6688
7190
  console.log("");
6689
- console.log(`${c11.bold} Welcome to Mr. Manager!${c11.reset}`);
6690
- console.log(`${c11.dim} Let's get you set up in a few quick steps.${c11.reset}`);
7191
+ console.log(`${c13.bold} Welcome to Mr. Manager!${c13.reset}`);
7192
+ console.log(`${c13.dim} Let's get you set up in a few quick steps.${c13.reset}`);
6691
7193
  console.log("");
6692
- console.log(` ${c11.yellow}Step 1:${c11.reset} Authenticate via Google OAuth`);
6693
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr login${c11.reset}`);
7194
+ console.log(` ${c13.yellow}Step 1:${c13.reset} Authenticate via Google OAuth`);
7195
+ console.log(` ${c13.dim}Run:${c13.reset} ${c13.cyan}mr login${c13.reset}`);
6694
7196
  console.log("");
6695
- console.log(` ${c11.yellow}Step 2:${c11.reset} Verify your environment`);
6696
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr setup${c11.reset}`);
7197
+ console.log(` ${c13.yellow}Step 2:${c13.reset} Verify your environment`);
7198
+ console.log(` ${c13.dim}Run:${c13.reset} ${c13.cyan}mr setup${c13.reset}`);
6697
7199
  console.log("");
6698
- console.log(` ${c11.yellow}Step 3:${c11.reset} Link a repo and start watching`);
6699
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr link${c11.reset} ${c11.dim}&&${c11.reset} ${c11.cyan}mr watch${c11.reset}`);
7200
+ console.log(` ${c13.yellow}Step 3:${c13.reset} Link a repo and start watching`);
7201
+ console.log(` ${c13.dim}Run:${c13.reset} ${c13.cyan}mr link${c13.reset} ${c13.dim}&&${c13.reset} ${c13.cyan}mr watch${c13.reset}`);
6700
7202
  console.log("");
6701
- console.log(`${c11.dim} Or run ${c11.reset}${c11.cyan}mr login${c11.reset}${c11.dim} to get started now.${c11.reset}`);
7203
+ console.log(`${c13.dim} Or run ${c13.reset}${c13.cyan}mr login${c13.reset}${c13.dim} to get started now.${c13.reset}`);
6702
7204
  console.log("");
6703
7205
  process.exit(0);
6704
7206
  }
6705
- var program = new Command28();
7207
+ var program = new Command30();
6706
7208
  program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
6707
7209
  program.addCommand(initCommand);
6708
7210
  program.addCommand(authCommand);
@@ -6729,9 +7231,11 @@ program.addCommand(setPathCommand);
6729
7231
  program.addCommand(testCommand);
6730
7232
  program.addCommand(featuresCommand);
6731
7233
  program.addCommand(noMrCommand);
7234
+ program.addCommand(reviewCommand);
6732
7235
  program.addCommand(scanCommand);
6733
7236
  program.addCommand(doctorCommand);
6734
7237
  program.addCommand(promptAuditCommand);
6735
7238
  program.addCommand(skillCommand);
7239
+ program.addCommand(resourceCommand);
6736
7240
  program.addCommand(testsCommand);
6737
7241
  program.parse();