@dunnewold-labs/mr-manager 0.4.31 → 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 +698 -164
  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.31",
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;
@@ -1162,6 +1162,19 @@ function taskLikelyDoesNotNeedCodeChanges(task) {
1162
1162
  return NON_CODE_TASK_KEYWORDS.some((keyword) => haystack.includes(keyword)) || NON_CODE_TASK_PATTERNS.some((pattern) => pattern.test(haystack));
1163
1163
  }
1164
1164
 
1165
+ // lib/task-branch.ts
1166
+ function isPrOrMrUrl(input) {
1167
+ try {
1168
+ const url = new URL(input.trim());
1169
+ const path = url.pathname;
1170
+ if (url.hostname.includes("github") && /\/pull\/\d+/.test(path)) return true;
1171
+ if (url.hostname.includes("gitlab") && /\/merge_requests\/\d+/.test(path)) return true;
1172
+ } catch {
1173
+ return false;
1174
+ }
1175
+ return false;
1176
+ }
1177
+
1165
1178
  // cli/browse-runner.ts
1166
1179
  import { execSync as execSync3, spawn as spawn3 } from "child_process";
1167
1180
  import { existsSync as existsSync6 } from "fs";
@@ -1477,11 +1490,21 @@ function ownerPrefix(task) {
1477
1490
  return first || "mr";
1478
1491
  }
1479
1492
  function taskBranchName(task) {
1480
- if (task.attachedBranch?.trim()) {
1481
- return task.attachedBranch.trim();
1493
+ const branch = task.attachedBranch?.trim();
1494
+ if (branch && !isPrOrMrUrl(branch)) {
1495
+ return branch;
1482
1496
  }
1483
1497
  return `${ownerPrefix(task)}/${slugify(task.title)}`;
1484
1498
  }
1499
+ function resolveBranchFromPrUrl(prUrl, repoDir, vcs) {
1500
+ const cmd = vcs === "gitlab" ? `glab mr view "${prUrl}" --output json 2>/dev/null | jq -r '.source_branch // empty'` : `gh pr view "${prUrl}" --json headRefName -q .headRefName 2>/dev/null`;
1501
+ return new Promise((resolve9) => {
1502
+ exec(cmd, { cwd: repoDir }, (err, stdout) => {
1503
+ const branch = stdout?.trim();
1504
+ resolve9(branch || null);
1505
+ });
1506
+ });
1507
+ }
1485
1508
  function formatElapsed(ms) {
1486
1509
  const totalMinutes = Math.max(1, Math.floor(ms / 6e4));
1487
1510
  const hours = Math.floor(totalMinutes / 60);
@@ -1992,7 +2015,7 @@ ${task.notes}` : "";
1992
2015
  ],
1993
2016
  ``,
1994
2017
  `3. Implement the task. You may read, write, and run code across any relevant repos under ${repoDir}.`,
1995
- ` - 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>'\``,
1996
2019
  ...pendingSubtasks.length > 0 ? [
1997
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.`
1998
2021
  ] : [],
@@ -2000,7 +2023,7 @@ ${task.notes}` : "";
2000
2023
  `4. Once implementation is complete, for each repo that has changes:`,
2001
2024
  ` a. Commit all changes with a clear, descriptive message.`,
2002
2025
  ` b. Push the branch: \`git push -u origin HEAD\``,
2003
- ...hasFeedback ? [
2026
+ ...hasFeedback || hasAttachedBranch && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink) ? [
2004
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.`
2005
2028
  ] : [
2006
2029
  ` c. Write a structured ${vcs === "gitlab" ? "merge request" : "pull request"} description to \`${prBodyPath}\` using the template below.`,
@@ -2019,7 +2042,7 @@ ${task.notes}` : "";
2019
2042
  ``,
2020
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.`,
2021
2044
  ``,
2022
- ...hasFeedback ? [] : [
2045
+ ...hasFeedback || hasAttachedBranch && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink) ? [] : [
2023
2046
  `## PR Description Template`,
2024
2047
  ``,
2025
2048
  `Write this template to \`${prBodyPath}\`, then replace the placeholders with the actual details from your implementation.`,
@@ -2269,7 +2292,7 @@ function buildPrototypePrompt(proto, repoDir) {
2269
2292
  }
2270
2293
  };
2271
2294
  const config = typeConfig[prototypeType] ?? typeConfig.web_app;
2272
- const typeLabel = {
2295
+ const typeLabel2 = {
2273
2296
  web_app: "Web App",
2274
2297
  mobile_app: "Mobile App",
2275
2298
  desktop_app: "Desktop App",
@@ -2283,7 +2306,7 @@ function buildPrototypePrompt(proto, repoDir) {
2283
2306
  `## Prototype Request`,
2284
2307
  `Title: ${proto.title}`,
2285
2308
  `ID: ${proto.id}`,
2286
- `Type: ${typeLabel[prototypeType] ?? prototypeType}`,
2309
+ `Type: ${typeLabel2[prototypeType] ?? prototypeType}`,
2287
2310
  ``,
2288
2311
  `## Design Prompt`,
2289
2312
  `${proto.prompt}`,
@@ -2351,7 +2374,7 @@ function buildRefinementPrompt(proto, parentFiles, repoDir) {
2351
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.",
2352
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."
2353
2376
  };
2354
- const typeLabel = {
2377
+ const typeLabel2 = {
2355
2378
  web_app: "Web App",
2356
2379
  mobile_app: "Mobile App",
2357
2380
  desktop_app: "Desktop App",
@@ -2365,7 +2388,7 @@ function buildRefinementPrompt(proto, parentFiles, repoDir) {
2365
2388
  `## Prototype Request`,
2366
2389
  `Title: ${proto.title}`,
2367
2390
  `ID: ${proto.id}`,
2368
- `Type: ${typeLabel[prototypeType] ?? prototypeType}`,
2391
+ `Type: ${typeLabel2[prototypeType] ?? prototypeType}`,
2369
2392
  ``,
2370
2393
  `## Original Design Prompt`,
2371
2394
  `${proto.prompt}`,
@@ -2639,7 +2662,7 @@ var watchCommand = new Command8("watch").description(
2639
2662
  logWarn(watchTag(), `Network unavailable \u2014 pausing active tasks until connectivity returns (${reason})`);
2640
2663
  }
2641
2664
  for (const taskId of active.keys()) {
2642
- const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
2665
+ const nonTaskPrefixes = ["proto-", "repo-", "scan-", "review-", "test-"];
2643
2666
  if (nonTaskPrefixes.some((prefix) => taskId.startsWith(prefix))) continue;
2644
2667
  pauseTaskForNetwork(taskId, reason);
2645
2668
  }
@@ -2686,10 +2709,17 @@ var watchCommand = new Command8("watch").description(
2686
2709
  const sid = shortId(task.id);
2687
2710
  const slug = slugify(task.title);
2688
2711
  const owner = ownerPrefix(task);
2689
- const branchName = taskBranchName(task);
2712
+ let branchName = taskBranchName(task);
2690
2713
  const legacyBranchName = `mr/${sid}/${slug}`;
2691
2714
  const prefix = taskTag(sid);
2692
2715
  const vcs = detectVcs(repoDir)?.provider ?? "github";
2716
+ if (!task.attachedBranch?.trim() && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink)) {
2717
+ const resolved = await resolveBranchFromPrUrl(task.attachedBranchLink, repoDir, vcs);
2718
+ if (resolved) {
2719
+ branchName = resolved;
2720
+ logInfo(prefix, `Resolved branch ${paint("cyan", resolved)} from attached ${vcs === "gitlab" ? "MR" : "PR"}`);
2721
+ }
2722
+ }
2693
2723
  logDispatch(prefix, `"${paint("bold", task.title)}" ${paint("gray", repoDir)} ${paint("dim", `[${vcs}]`)}`);
2694
2724
  await postTaskUpdate(task.id, `Agent dispatched \u2014 starting work on "${task.title}"`, "system");
2695
2725
  let subtasks = [];
@@ -2888,29 +2918,6 @@ var watchCommand = new Command8("watch").description(
2888
2918
  try {
2889
2919
  if (code === 0) {
2890
2920
  try {
2891
- const researchPath = resolve2(executionDir, "research.md");
2892
- if (existsSync7(researchPath)) {
2893
- try {
2894
- const researchContent = readFileSync5(researchPath, "utf-8");
2895
- const existingResearch = existingResources.find((r) => r.type === "research");
2896
- if (existingResearch) {
2897
- await api.patch(`/api/tasks/${task.id}/resources/${existingResearch.id}`, {
2898
- content: researchContent
2899
- });
2900
- logSuccess(prefix, `Updated existing research resource`);
2901
- } else {
2902
- await api.post(`/api/tasks/${task.id}/resources`, {
2903
- type: "research",
2904
- title: `Research \u2014 ${task.title}`,
2905
- content: researchContent
2906
- });
2907
- logSuccess(prefix, `Uploaded research.md as task resource`);
2908
- }
2909
- unlinkSync(researchPath);
2910
- } catch (err) {
2911
- logWarn(prefix, `Failed to upload research resource: ${err.message}`);
2912
- }
2913
- }
2914
2921
  const noMrPath = resolve2(executionDir, ".mr-no-mr");
2915
2922
  const noMrRequested = existsSync7(noMrPath);
2916
2923
  let noMrDescription;
@@ -2921,7 +2928,11 @@ var watchCommand = new Command8("watch").description(
2921
2928
  }
2922
2929
  const prLabel = vcs === "gitlab" ? "MR" : "PR";
2923
2930
  let prUrl = null;
2924
- 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) {
2925
2936
  prUrl = await findPrUrlAcrossRepos(branchName, repoDir, vcs);
2926
2937
  if (!prUrl && !task.attachedBranch?.trim()) {
2927
2938
  prUrl = await findPrUrlAcrossRepos(legacyBranchName, repoDir, vcs);
@@ -2968,6 +2979,8 @@ var watchCommand = new Command8("watch").description(
2968
2979
  logWarn(prefix, `No ${prLabel} found for branch ${paint("cyan", branchName)}`);
2969
2980
  await postTaskUpdate(task.id, `Agent finished \u2014 no ${prLabel} found for branch ${branchName}`, "system");
2970
2981
  }
2982
+ } else if (prUrl) {
2983
+ logSuccess(prefix, `${prLabel} ready: ${paint("cyan", prUrl)}`);
2971
2984
  }
2972
2985
  const currentTask = await api.get(`/api/tasks/${task.id}`);
2973
2986
  if (currentTask.status === "completed" || currentTask.status === "review") {
@@ -3412,6 +3425,49 @@ var watchCommand = new Command8("watch").description(
3412
3425
  }
3413
3426
  });
3414
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
+ }
3415
3471
  async function processApprovalQueue() {
3416
3472
  if (approvalRunning || approvalQueue.length === 0) return;
3417
3473
  approvalRunning = true;
@@ -3554,7 +3610,7 @@ ${divider}`);
3554
3610
  }
3555
3611
  }
3556
3612
  for (const [taskId, entry] of active) {
3557
- 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;
3558
3614
  if (!activeTaskIds.has(taskId)) {
3559
3615
  logWarn(watchTag(), `Task ${paint("yellow", taskId.slice(0, 8))} no longer active, terminating\u2026`);
3560
3616
  entry.terminatedForError = true;
@@ -3563,7 +3619,7 @@ ${divider}`);
3563
3619
  queued.delete(taskId);
3564
3620
  }
3565
3621
  }
3566
- const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
3622
+ const nonTaskPrefixes = ["proto-", "repo-", "scan-", "review-", "test-"];
3567
3623
  for (const taskId of failed.keys()) {
3568
3624
  if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
3569
3625
  if (!activeTaskIds.has(taskId)) failed.delete(taskId);
@@ -3869,6 +3925,29 @@ ${divider}`);
3869
3925
  }
3870
3926
  dispatchScan(scan, prefix, key);
3871
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
+ }
3872
3951
  let reviewTasks = [];
3873
3952
  try {
3874
3953
  reviewTasks = await api.get("/api/tasks?status=review");
@@ -4152,9 +4231,9 @@ var prototypeCommand = new Command13("prototype").description("Manage prototypes
4152
4231
  };
4153
4232
  for (const p of prototypes) {
4154
4233
  const date = new Date(p.createdAt).toLocaleDateString();
4155
- const typeLabel = typeLabels[p.prototypeType] ?? p.prototypeType ?? "web";
4234
+ const typeLabel2 = typeLabels[p.prototypeType] ?? p.prototypeType ?? "web";
4156
4235
  console.log(
4157
- ` ${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)}`
4158
4237
  );
4159
4238
  console.log(` ${paint4("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
4160
4239
  console.log();
@@ -4470,7 +4549,7 @@ async function checkApiConnectivity() {
4470
4549
  }
4471
4550
  }
4472
4551
  function printResults(checks) {
4473
- const maxNameLen = Math.max(...checks.map((c11) => c11.name.length));
4552
+ const maxNameLen = Math.max(...checks.map((c13) => c13.name.length));
4474
4553
  let allOk = true;
4475
4554
  for (const check of checks) {
4476
4555
  const isOptional = check.optional ?? false;
@@ -4483,16 +4562,16 @@ function printResults(checks) {
4483
4562
  return allOk;
4484
4563
  }
4485
4564
  async function autoFix(checks, agent) {
4486
- const { spawn: spawn8 } = await import("child_process");
4487
- const ghInstalled = checks.find((c11) => c11.name === "GitHub CLI (gh)").ok;
4488
- const ghAuthed = checks.find((c11) => c11.name === "GitHub CLI auth").ok;
4489
- const mrAuthed = checks.find((c11) => c11.name === "Mr. Manager CLI auth").ok;
4490
- 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)");
4491
4570
  if (claudeCheck && !claudeCheck.ok && agent === "claude") {
4492
4571
  console.log(paint5("cyan", " Installing Claude Code..."));
4493
4572
  console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
4494
4573
  await new Promise((resolve9) => {
4495
- 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" });
4496
4575
  child.on("exit", () => resolve9());
4497
4576
  });
4498
4577
  console.log("");
@@ -4500,7 +4579,7 @@ async function autoFix(checks, agent) {
4500
4579
  if (ghInstalled && !ghAuthed) {
4501
4580
  console.log(paint5("cyan", " Running gh auth login..."));
4502
4581
  await new Promise((resolve9) => {
4503
- const child = spawn8("gh", ["auth", "login"], { stdio: "inherit" });
4582
+ const child = spawn9("gh", ["auth", "login"], { stdio: "inherit" });
4504
4583
  child.on("exit", () => resolve9());
4505
4584
  });
4506
4585
  console.log("");
@@ -4509,7 +4588,7 @@ async function autoFix(checks, agent) {
4509
4588
  console.log(paint5("cyan", " Running mr login..."));
4510
4589
  const entry = process.argv[1];
4511
4590
  await new Promise((resolve9) => {
4512
- const child = spawn8(process.execPath, [entry, "login"], { stdio: "inherit" });
4591
+ const child = spawn9(process.execPath, [entry, "login"], { stdio: "inherit" });
4513
4592
  child.on("exit", () => resolve9());
4514
4593
  });
4515
4594
  console.log("");
@@ -4550,7 +4629,7 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
4550
4629
  console.log("");
4551
4630
  return;
4552
4631
  }
4553
- const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
4632
+ const fixes = checks.filter((c13) => !c13.ok && c13.fix && !c13.optional);
4554
4633
  if (fixes.length > 0) {
4555
4634
  console.log(paint5("yellow", " To fix:"));
4556
4635
  for (const fix of fixes) {
@@ -4566,9 +4645,26 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
4566
4645
 
4567
4646
  // cli/commands/update.ts
4568
4647
  import { Command as Command15 } from "commander";
4569
- 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
+ }
4570
4666
  await api.post(`/api/tasks/${taskId}/updates`, {
4571
- message,
4667
+ message: messageOrTitle,
4572
4668
  source: opts.source
4573
4669
  });
4574
4670
  console.log(`\u2713 Status update posted`);
@@ -5276,11 +5372,329 @@ var noMrCommand = new Command22("no-mr").description("Signal that a task does no
5276
5372
  console.log(` Reason: ${description}`);
5277
5373
  });
5278
5374
 
5279
- // cli/commands/scan.ts
5375
+ // cli/commands/review.ts
5280
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";
5281
5695
 
5282
5696
  // lib/scanner/index.ts
5283
- import { spawn as spawn7 } from "child_process";
5697
+ import { spawn as spawn8 } from "child_process";
5284
5698
 
5285
5699
  // lib/scanner/config.ts
5286
5700
  import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
@@ -5349,7 +5763,7 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
5349
5763
  // lib/scanner/codebase-analysis.ts
5350
5764
  import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
5351
5765
  import { join as join10, relative } from "path";
5352
- import { execSync as execSync5 } from "child_process";
5766
+ import { execSync as execSync6 } from "child_process";
5353
5767
  function resolveDir(projectPath, candidates) {
5354
5768
  for (const candidate of candidates) {
5355
5769
  const dir = join10(projectPath, candidate);
@@ -5475,7 +5889,7 @@ function extractInternalLinks(projectPath) {
5475
5889
  }
5476
5890
  function getRecentCommits(projectPath, count = 20) {
5477
5891
  try {
5478
- const output = execSync5(
5892
+ const output = execSync6(
5479
5893
  `git log --oneline -${count} --no-decorate`,
5480
5894
  { cwd: projectPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5481
5895
  );
@@ -5743,10 +6157,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
5743
6157
  ${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
5744
6158
 
5745
6159
  **Components:**
5746
- ${codebaseAnalysis.components.slice(0, 15).map((c11) => `- ${c11}`).join("\n")}
6160
+ ${codebaseAnalysis.components.slice(0, 15).map((c13) => `- ${c13}`).join("\n")}
5747
6161
 
5748
6162
  **Recent Git Commits:**
5749
- ${codebaseAnalysis.recentCommits.slice(0, 8).map((c11) => `- ${c11}`).join("\n")}
6163
+ ${codebaseAnalysis.recentCommits.slice(0, 8).map((c13) => `- ${c13}`).join("\n")}
5750
6164
 
5751
6165
  **Completed Tasks:**
5752
6166
  ${context.completedTasks.slice(0, 10).map((t) => `- ${t.title}`).join("\n") || "None"}
@@ -5891,7 +6305,7 @@ async function runScanPipeline(opts) {
5891
6305
  crawlResults,
5892
6306
  context.priorFindings
5893
6307
  );
5894
- const synthesisResult = await runClaude(prompt2);
6308
+ const synthesisResult = await runClaude2(prompt2);
5895
6309
  const parsed = parseSynthesisOutput(synthesisResult);
5896
6310
  const scanDurationMs = Date.now() - startTime;
5897
6311
  opts.onLog(`Scan complete in ${Math.round(scanDurationMs / 1e3)}s \u2014 ${parsed.findings.length} findings`);
@@ -5952,9 +6366,9 @@ async function fetchScanContext(opts) {
5952
6366
  priorFindings
5953
6367
  };
5954
6368
  }
5955
- function runClaude(prompt2) {
6369
+ function runClaude2(prompt2) {
5956
6370
  return new Promise((resolve9, reject) => {
5957
- const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
6371
+ const child = spawn8("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
5958
6372
  stdio: ["ignore", "pipe", "pipe"]
5959
6373
  });
5960
6374
  let output = "";
@@ -6010,7 +6424,7 @@ function parseSynthesisOutput(output) {
6010
6424
  }
6011
6425
 
6012
6426
  // cli/commands/scan.ts
6013
- var c8 = {
6427
+ var c9 = {
6014
6428
  reset: "\x1B[0m",
6015
6429
  bold: "\x1B[1m",
6016
6430
  dim: "\x1B[2m",
@@ -6021,53 +6435,53 @@ var c8 = {
6021
6435
  magenta: "\x1B[35m",
6022
6436
  gray: "\x1B[90m"
6023
6437
  };
6024
- function paint8(color, text) {
6025
- return `${c8[color]}${text}${c8.reset}`;
6438
+ function paint9(color, text) {
6439
+ return `${c9[color]}${text}${c9.reset}`;
6026
6440
  }
6027
- function timestamp2() {
6028
- 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 }));
6029
6443
  }
6030
6444
  function scanTag() {
6031
- return paint8("magenta", "[scan]");
6445
+ return paint9("magenta", "[scan]");
6032
6446
  }
6033
- function log(msg) {
6034
- console.log(`${timestamp2()} ${scanTag()} ${msg}`);
6447
+ function log2(msg) {
6448
+ console.log(`${timestamp3()} ${scanTag()} ${msg}`);
6035
6449
  }
6036
- function logOk(msg) {
6037
- console.log(`${timestamp2()} ${scanTag()} ${paint8("green", "\u2713")} ${msg}`);
6450
+ function logOk2(msg) {
6451
+ console.log(`${timestamp3()} ${scanTag()} ${paint9("green", "\u2713")} ${msg}`);
6038
6452
  }
6039
- function logErr(msg) {
6040
- console.error(`${timestamp2()} ${scanTag()} ${paint8("red", "\u2717")} ${msg}`);
6453
+ function logErr2(msg) {
6454
+ console.error(`${timestamp3()} ${scanTag()} ${paint9("red", "\u2717")} ${msg}`);
6041
6455
  }
6042
- 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) => {
6043
6457
  const config = loadConfig();
6044
6458
  if (!config.apiKey) {
6045
- logErr('Not authenticated. Run "mr login" first.');
6459
+ logErr2('Not authenticated. Run "mr login" first.');
6046
6460
  process.exit(1);
6047
6461
  }
6048
6462
  const banner = [
6049
6463
  ``,
6050
- paint8("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
6051
- paint8("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
6052
- paint8("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
6053
- paint8("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
6054
- 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`),
6055
6469
  ``
6056
6470
  ].join("\n");
6057
6471
  console.log(banner);
6058
6472
  const projectId = opts.project || getLinkedProjectId();
6059
6473
  if (!projectId) {
6060
- logErr('No project linked. Run "mr link" or pass --project <id>.');
6474
+ logErr2('No project linked. Run "mr link" or pass --project <id>.');
6061
6475
  process.exit(1);
6062
6476
  }
6063
6477
  let project;
6064
6478
  try {
6065
6479
  project = await api.get(`/api/projects/${projectId}`);
6066
6480
  } catch {
6067
- logErr(`Failed to fetch project ${projectId}`);
6481
+ logErr2(`Failed to fetch project ${projectId}`);
6068
6482
  process.exit(1);
6069
6483
  }
6070
- log(`Scanning project: ${paint8("cyan", project.name)}`);
6484
+ log2(`Scanning project: ${paint9("cyan", project.name)}`);
6071
6485
  let projectPath = project.localPath;
6072
6486
  if (!projectPath) {
6073
6487
  for (const [dir, pid] of Object.entries(config.directories)) {
@@ -6083,12 +6497,12 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6083
6497
  let reportId;
6084
6498
  if (opts.report) {
6085
6499
  reportId = opts.report;
6086
- log(`Using existing scan report ${paint8("yellow", reportId.slice(0, 8))}`);
6500
+ log2(`Using existing scan report ${paint9("yellow", reportId.slice(0, 8))}`);
6087
6501
  } else {
6088
6502
  try {
6089
6503
  const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
6090
6504
  if (scans.length > 0) {
6091
- 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.");
6092
6506
  process.exit(1);
6093
6507
  }
6094
6508
  } catch {
@@ -6099,9 +6513,9 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6099
6513
  status: "pending"
6100
6514
  });
6101
6515
  reportId = report.id;
6102
- log(`Created scan report ${paint8("yellow", reportId.slice(0, 8))}`);
6516
+ log2(`Created scan report ${paint9("yellow", reportId.slice(0, 8))}`);
6103
6517
  } catch (err) {
6104
- logErr(`Failed to create scan report: ${err.message}`);
6518
+ logErr2(`Failed to create scan report: ${err.message}`);
6105
6519
  process.exit(1);
6106
6520
  }
6107
6521
  }
@@ -6112,7 +6526,7 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6112
6526
  try {
6113
6527
  const current = await api.get(`/api/scans/${reportId}`);
6114
6528
  if (current.status === "cancelled") {
6115
- log(paint8("yellow", "Scan was cancelled \u2014 aborting."));
6529
+ log2(paint9("yellow", "Scan was cancelled \u2014 aborting."));
6116
6530
  process.exit(0);
6117
6531
  }
6118
6532
  } catch {
@@ -6126,9 +6540,9 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6126
6540
  apiUrl: config.apiUrl,
6127
6541
  apiKey: config.apiKey,
6128
6542
  runBrowse: runBrowseCommand2,
6129
- onLog: log,
6543
+ onLog: log2,
6130
6544
  onProgress: (phase, detail) => {
6131
- log(`${paint8("dim", `[${phase}]`)} ${detail}`);
6545
+ log2(`${paint9("dim", `[${phase}]`)} ${detail}`);
6132
6546
  }
6133
6547
  });
6134
6548
  let wasCancelled = false;
@@ -6140,7 +6554,7 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6140
6554
  } catch {
6141
6555
  }
6142
6556
  if (wasCancelled) {
6143
- log(paint8("yellow", "Scan was cancelled by user \u2014 discarding results."));
6557
+ log2(paint9("yellow", "Scan was cancelled by user \u2014 discarding results."));
6144
6558
  process.exit(0);
6145
6559
  }
6146
6560
  await api.patch(`/api/scans/${reportId}`, {
@@ -6151,37 +6565,37 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6151
6565
  scanDurationMs: result.scanDurationMs,
6152
6566
  routesCrawled: result.routesCrawled
6153
6567
  });
6154
- logOk(`Scan complete \u2014 ${paint8("cyan", String(result.findings.length))} findings`);
6568
+ logOk2(`Scan complete \u2014 ${paint9("cyan", String(result.findings.length))} findings`);
6155
6569
  console.log("");
6156
- console.log(` ${paint8("bold", "Summary:")} ${result.summary}`);
6570
+ console.log(` ${paint9("bold", "Summary:")} ${result.summary}`);
6157
6571
  console.log("");
6158
6572
  const high = result.findings.filter((f) => f.priority === "high");
6159
6573
  const medium = result.findings.filter((f) => f.priority === "medium");
6160
6574
  const low = result.findings.filter((f) => f.priority === "low");
6161
6575
  if (high.length > 0) {
6162
- console.log(` ${paint8("bold", paint8("red", `High Priority (${high.length})`))}`);
6576
+ console.log(` ${paint9("bold", paint9("red", `High Priority (${high.length})`))}`);
6163
6577
  for (const f of high) {
6164
- console.log(` ${paint8("red", "\u25CF")} [${f.type}] ${f.title}`);
6165
- 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))}`);
6166
6580
  }
6167
6581
  console.log("");
6168
6582
  }
6169
6583
  if (medium.length > 0) {
6170
- console.log(` ${paint8("bold", paint8("yellow", `Medium Priority (${medium.length})`))}`);
6584
+ console.log(` ${paint9("bold", paint9("yellow", `Medium Priority (${medium.length})`))}`);
6171
6585
  for (const f of medium) {
6172
- console.log(` ${paint8("yellow", "\u25CF")} [${f.type}] ${f.title}`);
6586
+ console.log(` ${paint9("yellow", "\u25CF")} [${f.type}] ${f.title}`);
6173
6587
  }
6174
6588
  console.log("");
6175
6589
  }
6176
6590
  if (low.length > 0) {
6177
- console.log(` ${paint8("dim", `Low Priority (${low.length})`)} `);
6591
+ console.log(` ${paint9("dim", `Low Priority (${low.length})`)} `);
6178
6592
  for (const f of low) {
6179
- console.log(` ${paint8("dim", `\u25CB [${f.type}] ${f.title}`)}`);
6593
+ console.log(` ${paint9("dim", `\u25CB [${f.type}] ${f.title}`)}`);
6180
6594
  }
6181
6595
  console.log("");
6182
6596
  }
6183
6597
  } catch (err) {
6184
- logErr(`Scan failed: ${err.message}`);
6598
+ logErr2(`Scan failed: ${err.message}`);
6185
6599
  try {
6186
6600
  await api.patch(`/api/scans/${reportId}`, {
6187
6601
  status: "failed",
@@ -6194,7 +6608,7 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
6194
6608
  });
6195
6609
 
6196
6610
  // cli/commands/doctor.ts
6197
- import { Command as Command24 } from "commander";
6611
+ import { Command as Command25 } from "commander";
6198
6612
  import { existsSync as existsSync15 } from "fs";
6199
6613
  import { homedir as homedir2 } from "os";
6200
6614
  import { join as join11 } from "path";
@@ -6242,7 +6656,7 @@ async function checkProjectLink() {
6242
6656
  optional: true
6243
6657
  };
6244
6658
  }
6245
- 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 () => {
6246
6660
  const banner = [
6247
6661
  ``,
6248
6662
  paint5("cyan", ` MR DOCTOR`),
@@ -6273,7 +6687,7 @@ var doctorCommand = new Command24("doctor").description("Diagnose Mr. Manager CL
6273
6687
  console.log("");
6274
6688
  return;
6275
6689
  }
6276
- const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
6690
+ const fixes = checks.filter((c13) => !c13.ok && c13.fix && !c13.optional);
6277
6691
  if (fixes.length > 0) {
6278
6692
  console.log(paint5("yellow", " To fix:"));
6279
6693
  for (const fix of fixes) {
@@ -6285,14 +6699,14 @@ var doctorCommand = new Command24("doctor").description("Diagnose Mr. Manager CL
6285
6699
  });
6286
6700
 
6287
6701
  // cli/commands/prompt-audit.ts
6288
- import { Command as Command25 } from "commander";
6702
+ import { Command as Command26 } from "commander";
6289
6703
  import { resolve as resolve8 } from "path";
6290
6704
  import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
6291
6705
  function auditLine(label, tokens) {
6292
6706
  const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
6293
6707
  return ` ${label.padEnd(30)} ${formatTokenCount(tokens).padStart(8)} ${bar}`;
6294
6708
  }
6295
- 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) => {
6296
6710
  const results = [];
6297
6711
  if (opts.task) {
6298
6712
  try {
@@ -6497,8 +6911,8 @@ ${r.jobType} [${r.identifier}]`);
6497
6911
  });
6498
6912
 
6499
6913
  // cli/commands/skill.ts
6500
- import { Command as Command26 } from "commander";
6501
- var c9 = {
6914
+ import { Command as Command27 } from "commander";
6915
+ var c10 = {
6502
6916
  reset: "\x1B[0m",
6503
6917
  bold: "\x1B[1m",
6504
6918
  dim: "\x1B[2m",
@@ -6506,7 +6920,7 @@ var c9 = {
6506
6920
  green: "\x1B[32m",
6507
6921
  yellow: "\x1B[33m"
6508
6922
  };
6509
- 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");
6510
6924
  skillCommand.command("list").alias("ls").description("List all skills").option("--category <category>", "Filter by category").action(async (opts) => {
6511
6925
  const params = new URLSearchParams();
6512
6926
  if (opts.category) params.set("category", opts.category);
@@ -6514,17 +6928,17 @@ skillCommand.command("list").alias("ls").description("List all skills").option("
6514
6928
  `/api/skills${params.toString() ? `?${params}` : ""}`
6515
6929
  );
6516
6930
  if (skills.length === 0) {
6517
- console.log(`${c9.dim}No skills found.${c9.reset}`);
6931
+ console.log(`${c10.dim}No skills found.${c10.reset}`);
6518
6932
  return;
6519
6933
  }
6520
6934
  for (const skill of skills) {
6521
- const cat = skill.category ? ` ${c9.dim}[${skill.category}]${c9.reset}` : "";
6522
- const scope = skill.projectId ? ` ${c9.dim}(project)${c9.reset}` : ` ${c9.dim}(global)${c9.reset}`;
6523
- 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}`);
6524
6938
  if (skill.description) {
6525
- console.log(` ${c9.dim}${skill.description}${c9.reset}`);
6939
+ console.log(` ${c10.dim}${skill.description}${c10.reset}`);
6526
6940
  }
6527
- console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
6941
+ console.log(` ${c10.dim}id: ${skill.id}${c10.reset}`);
6528
6942
  }
6529
6943
  });
6530
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) => {
@@ -6560,7 +6974,7 @@ skillCommand.command("create").description("Create a new skill from a markdown f
6560
6974
  projectId
6561
6975
  });
6562
6976
  console.log(
6563
- `${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}`
6564
6978
  );
6565
6979
  });
6566
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) => {
@@ -6574,40 +6988,158 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
6574
6988
  process.exit(1);
6575
6989
  }
6576
6990
  }
6577
- console.log(`${c9.dim}Generating skill...${c9.reset}`);
6991
+ console.log(`${c10.dim}Generating skill...${c10.reset}`);
6578
6992
  try {
6579
6993
  const skill = await api.post("/api/skills/generate", {
6580
6994
  prompt: prompt2,
6581
6995
  projectId
6582
6996
  });
6583
6997
  console.log(
6584
- `${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}`
6585
6999
  );
6586
7000
  if (skill.description) {
6587
- console.log(` ${c9.dim}${skill.description}${c9.reset}`);
7001
+ console.log(` ${c10.dim}${skill.description}${c10.reset}`);
6588
7002
  }
6589
7003
  if (skill.category) {
6590
- console.log(` ${c9.dim}Category: ${skill.category}${c9.reset}`);
7004
+ console.log(` ${c10.dim}Category: ${skill.category}${c10.reset}`);
6591
7005
  }
6592
- console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
7006
+ console.log(` ${c10.dim}id: ${skill.id}${c10.reset}`);
6593
7007
  } catch (err) {
6594
7008
  console.error(`Failed to generate skill: ${err.message}`);
6595
7009
  process.exit(1);
6596
7010
  }
6597
7011
  });
6598
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
+
6599
7131
  // cli/commands/tests.ts
6600
- import { Command as Command27 } from "commander";
6601
- var c10 = {
7132
+ import { Command as Command29 } from "commander";
7133
+ var c12 = {
6602
7134
  reset: "\x1B[0m",
6603
7135
  dim: "\x1B[2m",
6604
7136
  yellow: "\x1B[33m"
6605
7137
  };
6606
- 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 () => {
6607
7139
  const projectId = getLinkedProjectId();
6608
7140
  if (!projectId) {
6609
7141
  console.error(
6610
- `${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.`
6611
7143
  );
6612
7144
  process.exit(1);
6613
7145
  }
@@ -6620,13 +7152,13 @@ var testsCommand = new Command27("tests").description("List MR Test scenarios fo
6620
7152
  process.exit(1);
6621
7153
  }
6622
7154
  if (scenarios.length === 0) {
6623
- 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}`);
6624
7156
  return;
6625
7157
  }
6626
7158
  for (const scenario of scenarios) {
6627
7159
  console.log(`### ${scenario.name}`);
6628
7160
  if (scenario.description) {
6629
- console.log(`${c10.dim}${scenario.description}${c10.reset}`);
7161
+ console.log(`${c12.dim}${scenario.description}${c12.reset}`);
6630
7162
  console.log();
6631
7163
  }
6632
7164
  console.log(scenario.content);
@@ -6641,7 +7173,7 @@ var userArgs = process.argv.slice(2);
6641
7173
  var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
6642
7174
  var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
6643
7175
  if (isFirstRun && !shouldBypass) {
6644
- const c11 = {
7176
+ const c13 = {
6645
7177
  reset: "\x1B[0m",
6646
7178
  bold: "\x1B[1m",
6647
7179
  dim: "\x1B[2m",
@@ -6651,28 +7183,28 @@ if (isFirstRun && !shouldBypass) {
6651
7183
  magenta: "\x1B[35m"
6652
7184
  };
6653
7185
  console.log("");
6654
- 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}`);
6655
- 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}`);
6656
- 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}`);
6657
- 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}`);
6658
7190
  console.log("");
6659
- console.log(`${c11.bold} Welcome to Mr. Manager!${c11.reset}`);
6660
- 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}`);
6661
7193
  console.log("");
6662
- console.log(` ${c11.yellow}Step 1:${c11.reset} Authenticate via Google OAuth`);
6663
- 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}`);
6664
7196
  console.log("");
6665
- console.log(` ${c11.yellow}Step 2:${c11.reset} Verify your environment`);
6666
- 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}`);
6667
7199
  console.log("");
6668
- console.log(` ${c11.yellow}Step 3:${c11.reset} Link a repo and start watching`);
6669
- 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}`);
6670
7202
  console.log("");
6671
- 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}`);
6672
7204
  console.log("");
6673
7205
  process.exit(0);
6674
7206
  }
6675
- var program = new Command28();
7207
+ var program = new Command30();
6676
7208
  program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
6677
7209
  program.addCommand(initCommand);
6678
7210
  program.addCommand(authCommand);
@@ -6699,9 +7231,11 @@ program.addCommand(setPathCommand);
6699
7231
  program.addCommand(testCommand);
6700
7232
  program.addCommand(featuresCommand);
6701
7233
  program.addCommand(noMrCommand);
7234
+ program.addCommand(reviewCommand);
6702
7235
  program.addCommand(scanCommand);
6703
7236
  program.addCommand(doctorCommand);
6704
7237
  program.addCommand(promptAuditCommand);
6705
7238
  program.addCommand(skillCommand);
7239
+ program.addCommand(resourceCommand);
6706
7240
  program.addCommand(testsCommand);
6707
7241
  program.parse();