@dunnewold-labs/mr-manager 0.4.0 → 0.4.3

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 +660 -581
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // cli/index.ts
4
- import { Command as Command28 } from "commander";
5
- import { existsSync as existsSync15 } from "fs";
6
- import { homedir as homedir4 } from "os";
7
- import { join as join13 } from "path";
4
+ import { Command as Command27 } from "commander";
5
+ import { existsSync as existsSync16 } from "fs";
6
+ import { homedir as homedir3 } from "os";
7
+ import { join as join12 } from "path";
8
8
 
9
9
  // cli/commands/init.ts
10
10
  import { Command } from "commander";
@@ -177,19 +177,70 @@ var loginCommand = new Command3("login").description("Authenticate the CLI via b
177
177
  // cli/commands/projects.ts
178
178
  import { Command as Command4 } from "commander";
179
179
 
180
- // cli/api.ts
181
- import { readFileSync as readFileSync2 } from "fs";
180
+ // cli/package.ts
181
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
182
+ import { dirname, join as join2, resolve } from "path";
182
183
  import { fileURLToPath } from "url";
183
- import { dirname, join as join2 } from "path";
184
- var cliVersion = "unknown";
185
- try {
186
- const __filename = fileURLToPath(import.meta.url);
187
- const __dirname = dirname(__filename);
188
- const pkg = JSON.parse(readFileSync2(join2(__dirname, "..", "package.json"), "utf-8"));
189
- cliVersion = pkg.version;
190
- } catch {
191
- cliVersion = "0.2.0";
184
+
185
+ // cli/package.json
186
+ var package_default = {
187
+ name: "@dunnewold-labs/mr-manager",
188
+ version: "0.4.3",
189
+ description: "Mr. Manager - Task and project management CLI",
190
+ bin: {
191
+ mr: "./dist/index.mjs"
192
+ },
193
+ files: [
194
+ "dist"
195
+ ],
196
+ type: "module",
197
+ engines: {
198
+ node: ">=20"
199
+ },
200
+ publishConfig: {
201
+ access: "public"
202
+ },
203
+ scripts: {
204
+ postinstall: `echo '\\n Run "mr login" to authenticate with Mr. Manager.\\n'`
205
+ },
206
+ dependencies: {
207
+ commander: "^13.1.0"
208
+ }
209
+ };
210
+
211
+ // cli/package.ts
212
+ var FALLBACK_CLI_VERSION = "0.2.0";
213
+ var CLI_PACKAGE_NAME = "@dunnewold-labs/mr-manager";
214
+ var __filename = fileURLToPath(import.meta.url);
215
+ var __dirname = dirname(__filename);
216
+ function isCliPackageDir(dir) {
217
+ const pkgPath = join2(dir, "package.json");
218
+ if (!existsSync2(pkgPath)) {
219
+ return false;
220
+ }
221
+ try {
222
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
223
+ return pkg.name === CLI_PACKAGE_NAME;
224
+ } catch {
225
+ return false;
226
+ }
227
+ }
228
+ function resolveCliDir() {
229
+ const candidates = [resolve(__dirname), resolve(__dirname, "..")];
230
+ for (const candidate of candidates) {
231
+ if (isCliPackageDir(candidate)) {
232
+ return candidate;
233
+ }
234
+ }
235
+ return resolve(__dirname, "..");
192
236
  }
237
+ var CLI_DIR = resolveCliDir();
238
+ var REPO_ROOT = resolve(CLI_DIR, "..");
239
+ var CLI_PACKAGE_PATH = join2(CLI_DIR, "package.json");
240
+ var CLI_VERSION = package_default.version ?? FALLBACK_CLI_VERSION;
241
+
242
+ // cli/api.ts
243
+ var cliVersion = CLI_VERSION;
193
244
  var ApiError = class extends Error {
194
245
  constructor(status, statusText, body) {
195
246
  super(`API error ${status} ${statusText}: ${body}`);
@@ -365,7 +416,7 @@ var unlinkCommand = new Command6("unlink").description("Remove current directory
365
416
 
366
417
  // cli/commands/context.ts
367
418
  import { Command as Command7 } from "commander";
368
- import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
419
+ import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
369
420
  import { join as join3, sep } from "path";
370
421
  var FEATURES_FILE = ".mr-features.md";
371
422
  function resolveProjectRoot() {
@@ -379,13 +430,13 @@ function resolveProjectRoot() {
379
430
  }
380
431
  function readFeatures() {
381
432
  const path = join3(resolveProjectRoot(), FEATURES_FILE);
382
- if (!existsSync2(path)) return null;
433
+ if (!existsSync3(path)) return null;
383
434
  return readFileSync3(path, "utf-8");
384
435
  }
385
436
  var contextCommand = new Command7("context").description("Output project context JSON for Claude Code").option("--install", "Install Claude Code command to .claude/commands/").action(async (opts) => {
386
437
  if (opts.install) {
387
438
  const dir = join3(process.cwd(), ".claude", "commands");
388
- if (!existsSync2(dir)) {
439
+ if (!existsSync3(dir)) {
389
440
  mkdirSync2(dir, { recursive: true });
390
441
  }
391
442
  const filePath = join3(dir, "mr-tasks.md");
@@ -408,11 +459,13 @@ var contextCommand = new Command7("context").description("Output project context
408
459
  `/api/projects/${projectId}/tasks`
409
460
  );
410
461
  const inProgress = allTasks.filter((t) => t.status === "in_progress" || t.status === "queued" || t.status === "delegated" || t.status === "review");
462
+ const needsAttention = allTasks.filter((t) => t.status === "error");
411
463
  const todo = allTasks.filter((t) => t.status === "todo");
412
464
  const recentlyCompleted = allTasks.filter((t) => t.status === "completed").slice(0, 10);
413
465
  const summaryLines = [
414
466
  `Project: ${project.name} (${project.status})`,
415
467
  ...inProgress.map((t) => `In Progress: ${t.title}`),
468
+ ...needsAttention.map((t) => `Needs Attention: ${t.title}`),
416
469
  `Todo: ${todo.length} tasks`,
417
470
  `Recently Completed: ${recentlyCompleted.length} tasks`
418
471
  ];
@@ -438,51 +491,55 @@ var contextCommand = new Command7("context").description("Output project context
438
491
  import { Command as Command8 } from "commander";
439
492
  import { spawn as spawn4, exec } from "child_process";
440
493
  import { randomUUID } from "crypto";
441
- import { resolve, join as join7 } from "path";
442
- import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as existsSync6, statSync } from "fs";
443
- import { homedir as homedir2 } from "os";
494
+ import { resolve as resolve2 } from "path";
495
+ import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as existsSync7, statSync } from "fs";
444
496
  import * as readline from "readline";
445
497
 
446
498
  // lib/test-runner.ts
447
499
  import { spawn as spawn2 } from "child_process";
448
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
500
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
449
501
  import { join as join5 } from "path";
450
502
 
451
503
  // lib/git-worktree.ts
452
504
  import { execSync as execSync2 } from "child_process";
453
- import { copyFileSync, existsSync as existsSync3 } from "fs";
505
+ import { copyFileSync, existsSync as existsSync4 } from "fs";
454
506
  import { join as join4 } from "path";
507
+ function tryExec(command, cwd) {
508
+ try {
509
+ execSync2(command, { cwd, stdio: "pipe" });
510
+ return true;
511
+ } catch {
512
+ return false;
513
+ }
514
+ }
455
515
  function createWorktree(repoDir, branch, worktreeName) {
456
516
  const wtPath = join4(repoDir, ".mr-worktrees", worktreeName);
457
- if (existsSync3(wtPath)) {
517
+ if (existsSync4(wtPath)) {
458
518
  execSync2(`git checkout ${branch}`, { cwd: wtPath, stdio: "pipe" });
459
519
  return wtPath;
460
520
  }
461
- try {
462
- execSync2(`git fetch origin ${branch}`, { cwd: repoDir, stdio: "pipe" });
463
- } catch {
464
- }
465
- try {
466
- execSync2(`git worktree add "${wtPath}" "origin/${branch}"`, {
521
+ tryExec(`git fetch origin ${branch}`, repoDir);
522
+ const hasRemoteBranch = tryExec(`git rev-parse --verify "origin/${branch}"`, repoDir);
523
+ const hasLocalBranch = tryExec(`git rev-parse --verify "${branch}"`, repoDir);
524
+ if (hasRemoteBranch && !hasLocalBranch) {
525
+ execSync2(`git worktree add -b "${branch}" "${wtPath}" "origin/${branch}"`, {
526
+ cwd: repoDir,
527
+ stdio: "pipe"
528
+ });
529
+ } else if (hasLocalBranch) {
530
+ execSync2(`git worktree add "${wtPath}" "${branch}"`, {
531
+ cwd: repoDir,
532
+ stdio: "pipe"
533
+ });
534
+ } else {
535
+ execSync2(`git worktree add -b "${branch}" "${wtPath}"`, {
467
536
  cwd: repoDir,
468
537
  stdio: "pipe"
469
538
  });
470
- } catch {
471
- try {
472
- execSync2(`git worktree add "${wtPath}" "${branch}"`, {
473
- cwd: repoDir,
474
- stdio: "pipe"
475
- });
476
- } catch {
477
- execSync2(`git worktree add -b "${branch}" "${wtPath}" "origin/${branch}"`, {
478
- cwd: repoDir,
479
- stdio: "pipe"
480
- });
481
- }
482
539
  }
483
540
  for (const envFile of [".env", ".env.local"]) {
484
541
  const src = join4(repoDir, envFile);
485
- if (existsSync3(src)) {
542
+ if (existsSync4(src)) {
486
543
  copyFileSync(src, join4(wtPath, envFile));
487
544
  }
488
545
  }
@@ -525,13 +582,13 @@ function extractBranchFromLink(link, cwd) {
525
582
  return null;
526
583
  }
527
584
  function installDependencies(wtPath) {
528
- if (existsSync3(join4(wtPath, "bun.lock")) || existsSync3(join4(wtPath, "bun.lockb"))) {
585
+ if (existsSync4(join4(wtPath, "bun.lock")) || existsSync4(join4(wtPath, "bun.lockb"))) {
529
586
  execSync2("bun install", { cwd: wtPath, stdio: "pipe" });
530
- } else if (existsSync3(join4(wtPath, "pnpm-lock.yaml"))) {
587
+ } else if (existsSync4(join4(wtPath, "pnpm-lock.yaml"))) {
531
588
  execSync2("pnpm install --frozen-lockfile", { cwd: wtPath, stdio: "pipe" });
532
- } else if (existsSync3(join4(wtPath, "package-lock.json"))) {
589
+ } else if (existsSync4(join4(wtPath, "package-lock.json"))) {
533
590
  execSync2("npm ci", { cwd: wtPath, stdio: "pipe" });
534
- } else if (existsSync3(join4(wtPath, "yarn.lock"))) {
591
+ } else if (existsSync4(join4(wtPath, "yarn.lock"))) {
535
592
  execSync2("yarn install --frozen-lockfile", { cwd: wtPath, stdio: "pipe" });
536
593
  }
537
594
  }
@@ -539,7 +596,7 @@ function installDependencies(wtPath) {
539
596
  // lib/test-runner.ts
540
597
  function detectDevCommand(wtPath) {
541
598
  const configPath2 = join5(wtPath, ".mr-test.json");
542
- if (existsSync4(configPath2)) {
599
+ if (existsSync5(configPath2)) {
543
600
  try {
544
601
  const config = JSON.parse(readFileSync4(configPath2, "utf-8"));
545
602
  if (config.devCommand) {
@@ -549,16 +606,16 @@ function detectDevCommand(wtPath) {
549
606
  } catch {
550
607
  }
551
608
  }
552
- if (existsSync4(join5(wtPath, "bun.lock")) || existsSync4(join5(wtPath, "bun.lockb"))) {
609
+ if (existsSync5(join5(wtPath, "bun.lock")) || existsSync5(join5(wtPath, "bun.lockb"))) {
553
610
  return { cmd: "bun", args: ["dev"] };
554
611
  }
555
- if (existsSync4(join5(wtPath, "pnpm-lock.yaml"))) {
612
+ if (existsSync5(join5(wtPath, "pnpm-lock.yaml"))) {
556
613
  return { cmd: "pnpm", args: ["dev"] };
557
614
  }
558
- if (existsSync4(join5(wtPath, "package-lock.json"))) {
615
+ if (existsSync5(join5(wtPath, "package-lock.json"))) {
559
616
  return { cmd: "npm", args: ["run", "dev"] };
560
617
  }
561
- if (existsSync4(join5(wtPath, "yarn.lock"))) {
618
+ if (existsSync5(join5(wtPath, "yarn.lock"))) {
562
619
  return { cmd: "yarn", args: ["dev"] };
563
620
  }
564
621
  return { cmd: "npm", args: ["run", "dev"] };
@@ -696,7 +753,7 @@ async function runTest(options) {
696
753
  postUpdate,
697
754
  onProgress
698
755
  } = options;
699
- const log3 = onProgress || (() => {
756
+ const log2 = onProgress || (() => {
700
757
  });
701
758
  const result = {
702
759
  status: "passed",
@@ -709,37 +766,37 @@ async function runTest(options) {
709
766
  let wtPath = null;
710
767
  const worktreeName = `mr-test-${taskId.slice(0, 8)}`;
711
768
  const timeoutHandle = setTimeout(() => {
712
- log3("Test timed out after 5 minutes");
769
+ log2("Test timed out after 5 minutes");
713
770
  if (devProc) devProc.kill("SIGTERM");
714
771
  }, 5 * 60 * 1e3);
715
772
  try {
716
- log3("Extracting branch from MR/PR link...");
773
+ log2("Extracting branch from MR/PR link...");
717
774
  const branch = extractBranchFromLink(taskLink, localPath);
718
775
  if (!branch) {
719
776
  throw new Error(`Could not extract branch from link: ${taskLink}`);
720
777
  }
721
- log3(`Branch: ${branch}`);
722
- log3("Creating git worktree...");
778
+ log2(`Branch: ${branch}`);
779
+ log2("Creating git worktree...");
723
780
  wtPath = createWorktree(localPath, branch, worktreeName);
724
- log3(`Worktree created at ${wtPath}`);
725
- log3("Installing dependencies...");
781
+ log2(`Worktree created at ${wtPath}`);
782
+ log2("Installing dependencies...");
726
783
  try {
727
784
  installDependencies(wtPath);
728
785
  } catch (err) {
729
- log3(`Warning: dependency install failed: ${err.message}`);
786
+ log2(`Warning: dependency install failed: ${err.message}`);
730
787
  }
731
- log3("Starting dev server...");
788
+ log2("Starting dev server...");
732
789
  const port = await findAvailablePort(4e3);
733
790
  devProc = await startDevServer(wtPath, port);
734
791
  const baseUrl = `http://127.0.0.1:${port}`;
735
- log3(`Dev server running on ${baseUrl}`);
792
+ log2(`Dev server running on ${baseUrl}`);
736
793
  await browseRunner(["goto", baseUrl]);
737
794
  const plan = customPlan || buildDefaultTestPlan(baseUrl);
738
- log3(`Executing ${plan.length}-step test plan...`);
795
+ log2(`Executing ${plan.length}-step test plan...`);
739
796
  for (let i = 0; i < plan.length; i++) {
740
797
  const step = plan[i];
741
798
  const stepDesc = step.description || `${step.command} ${(step.args || []).join(" ")}`;
742
- log3(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
799
+ log2(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
743
800
  try {
744
801
  if (step.command.startsWith("assert")) {
745
802
  const assertResult = await evaluateAssertion(step, i, browseRunner);
@@ -789,7 +846,7 @@ async function runTest(options) {
789
846
  }
790
847
  } catch (err) {
791
848
  result.errors.push(`Step ${i + 1} (${step.command}): ${err.message}`);
792
- log3(`Step ${i + 1} error: ${err.message}`);
849
+ log2(`Step ${i + 1} error: ${err.message}`);
793
850
  }
794
851
  }
795
852
  const totalAssertions = result.assertions.length;
@@ -835,18 +892,18 @@ async function runTest(options) {
835
892
 
836
893
  // cli/browse-runner.ts
837
894
  import { execSync as execSync4, spawn as spawn3 } from "child_process";
838
- import { existsSync as existsSync5 } from "fs";
895
+ import { existsSync as existsSync6 } from "fs";
839
896
  import { join as join6 } from "path";
840
897
  var BROWSE_DIR = join6(import.meta.dirname, "..", "..", "browse");
841
898
  var BROWSE_BINARY = join6(BROWSE_DIR, "dist", "browse");
842
899
  var BROWSE_DEV_CMD = join6(BROWSE_DIR, "src", "cli.ts");
843
900
  function getBrowseRunner() {
844
- if (existsSync5(BROWSE_BINARY)) {
901
+ if (existsSync6(BROWSE_BINARY)) {
845
902
  return { cmd: BROWSE_BINARY, args: [] };
846
903
  }
847
904
  try {
848
905
  execSync4("which bun", { stdio: "pipe" });
849
- if (existsSync5(BROWSE_DEV_CMD)) {
906
+ if (existsSync6(BROWSE_DEV_CMD)) {
850
907
  return { cmd: "bun", args: ["run", BROWSE_DEV_CMD] };
851
908
  }
852
909
  } catch {
@@ -886,95 +943,11 @@ async function runBrowseCommand2(browseArgs) {
886
943
 
887
944
  // cli/commands/watch.ts
888
945
  var FEATURES_FILE2 = ".mr-features.md";
889
- var CLAUDE_PROJECTS_DIR = join7(homedir2(), ".claude", "projects");
890
946
  function readFeaturesDoc(repoDir) {
891
- const path = resolve(repoDir, FEATURES_FILE2);
892
- if (!existsSync6(path)) return null;
947
+ const path = resolve2(repoDir, FEATURES_FILE2);
948
+ if (!existsSync7(path)) return null;
893
949
  return readFileSync5(path, "utf-8");
894
950
  }
895
- function toClaudePath(dir) {
896
- return dir.replace(/\//g, "-");
897
- }
898
- function findSessionFile(sessionId, repoDir) {
899
- const mainPath = join7(CLAUDE_PROJECTS_DIR, toClaudePath(repoDir));
900
- const mainFile = join7(mainPath, `${sessionId}.jsonl`);
901
- if (existsSync6(mainFile)) return mainFile;
902
- const worktreePrefix = toClaudePath(repoDir) + "-.mr-worktrees-";
903
- try {
904
- const entries = readdirSync(CLAUDE_PROJECTS_DIR);
905
- for (const entry of entries) {
906
- if (entry.startsWith(worktreePrefix)) {
907
- const worktreeFile = join7(CLAUDE_PROJECTS_DIR, entry, `${sessionId}.jsonl`);
908
- if (existsSync6(worktreeFile)) return worktreeFile;
909
- }
910
- }
911
- } catch {
912
- }
913
- return null;
914
- }
915
- function parseSessionJsonl(raw) {
916
- const lines = raw.split("\n").filter((l) => l.trim());
917
- const entries = [];
918
- for (const line of lines) {
919
- try {
920
- const parsed = JSON.parse(line);
921
- if (parsed.type !== "user" && parsed.type !== "assistant") continue;
922
- if (!parsed.message) continue;
923
- const content = [];
924
- const msgContent = parsed.message.content;
925
- if (typeof msgContent === "string") {
926
- content.push({ type: "text", text: msgContent });
927
- } else if (Array.isArray(msgContent)) {
928
- for (const block of msgContent) {
929
- if (block.type === "thinking" && block.thinking) {
930
- content.push({ type: "thinking", thinking: block.thinking });
931
- } else if (block.type === "text" && block.text) {
932
- content.push({ type: "text", text: block.text });
933
- } else if (block.type === "tool_use") {
934
- content.push({
935
- type: "tool_use",
936
- id: block.id,
937
- name: block.name,
938
- input: block.input ?? {}
939
- });
940
- } else if (block.type === "tool_result") {
941
- let resultContent = block.content;
942
- if (typeof resultContent === "string") {
943
- } else if (Array.isArray(resultContent)) {
944
- resultContent = resultContent.filter((r) => r.type === "text").map((r) => r.text).join("\n");
945
- } else {
946
- resultContent = "";
947
- }
948
- content.push({
949
- type: "tool_result",
950
- tool_use_id: block.tool_use_id,
951
- content: resultContent
952
- });
953
- }
954
- }
955
- }
956
- if (content.length === 0) continue;
957
- entries.push({
958
- type: parsed.type,
959
- timestamp: parsed.timestamp,
960
- uuid: parsed.uuid,
961
- content
962
- });
963
- } catch {
964
- }
965
- }
966
- return entries;
967
- }
968
- function readAndParseSessionLog(sessionId, repoDir) {
969
- const filePath = findSessionFile(sessionId, repoDir);
970
- if (!filePath) return null;
971
- try {
972
- const raw = readFileSync5(filePath, "utf-8");
973
- return parseSessionJsonl(raw);
974
- } catch {
975
- return null;
976
- }
977
- }
978
951
  var c = {
979
952
  reset: "\x1B[0m",
980
953
  bold: "\x1B[1m",
@@ -1028,9 +1001,20 @@ async function postTaskUpdate(taskId, message, source = "system") {
1028
1001
  logError(taskTag(shortId(taskId)), `Failed to post update: ${err.message}`);
1029
1002
  }
1030
1003
  }
1004
+ var DEFAULT_TASK_STALL_TIMEOUT_MS = 45 * 60 * 1e3;
1005
+ function getTaskStallTimeoutMs() {
1006
+ const raw = process.env.MR_TASK_STALL_TIMEOUT_MS;
1007
+ if (!raw) return DEFAULT_TASK_STALL_TIMEOUT_MS;
1008
+ const parsed = Number(raw);
1009
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_TASK_STALL_TIMEOUT_MS;
1010
+ }
1011
+ function formatTimeoutMinutes(ms) {
1012
+ const minutes = Math.round(ms / 6e4);
1013
+ return `${minutes} minute${minutes === 1 ? "" : "s"}`;
1014
+ }
1031
1015
  function findDirectoryForProject(config, projectId, rootDir) {
1032
1016
  for (const [dir, pid] of Object.entries(config.directories)) {
1033
- if (pid === projectId && resolve(dir).startsWith(resolve(rootDir))) {
1017
+ if (pid === projectId && resolve2(dir).startsWith(resolve2(rootDir))) {
1034
1018
  return dir;
1035
1019
  }
1036
1020
  }
@@ -1042,9 +1026,107 @@ function slugify(title) {
1042
1026
  function shortId(id) {
1043
1027
  return id.slice(0, 8);
1044
1028
  }
1029
+ function formatElapsed(ms) {
1030
+ const totalMinutes = Math.max(1, Math.floor(ms / 6e4));
1031
+ const hours = Math.floor(totalMinutes / 60);
1032
+ const minutes = totalMinutes % 60;
1033
+ if (hours > 0) {
1034
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
1035
+ }
1036
+ return `${totalMinutes}m`;
1037
+ }
1045
1038
  function worktreePath(sid) {
1046
1039
  return `.mr-worktrees/mr-${sid}`;
1047
1040
  }
1041
+ function worktreeNameFromPath(wtPath) {
1042
+ return wtPath.replace(/^\.mr-worktrees\//, "");
1043
+ }
1044
+ function normalizeWhitespace(value) {
1045
+ return value.replace(/\s+/g, " ").trim();
1046
+ }
1047
+ function extractFirstMeaningfulParagraph(markdown) {
1048
+ const lines = markdown.split(/\r?\n/);
1049
+ const quoteLines = [];
1050
+ let inCodeFence = false;
1051
+ for (const rawLine of lines) {
1052
+ const line = rawLine.trim();
1053
+ if (line.startsWith("```")) {
1054
+ inCodeFence = !inCodeFence;
1055
+ continue;
1056
+ }
1057
+ if (inCodeFence) continue;
1058
+ if (!line) {
1059
+ if (quoteLines.length > 0) break;
1060
+ continue;
1061
+ }
1062
+ if (line.startsWith(">")) {
1063
+ quoteLines.push(line.replace(/^>\s?/, ""));
1064
+ continue;
1065
+ }
1066
+ if (quoteLines.length > 0) break;
1067
+ }
1068
+ if (quoteLines.length > 0) {
1069
+ const text2 = normalizeWhitespace(quoteLines.join(" "));
1070
+ return text2 || null;
1071
+ }
1072
+ const paragraphLines = [];
1073
+ inCodeFence = false;
1074
+ for (const rawLine of lines) {
1075
+ const line = rawLine.trim();
1076
+ if (line.startsWith("```")) {
1077
+ inCodeFence = !inCodeFence;
1078
+ continue;
1079
+ }
1080
+ if (inCodeFence) continue;
1081
+ if (!line) {
1082
+ if (paragraphLines.length > 0) break;
1083
+ continue;
1084
+ }
1085
+ if (/^(#{1,6}\s|[-*]\s|\d+\.\s|\|)/.test(line)) {
1086
+ if (paragraphLines.length > 0) break;
1087
+ continue;
1088
+ }
1089
+ paragraphLines.push(line);
1090
+ }
1091
+ if (paragraphLines.length === 0) return null;
1092
+ const text = normalizeWhitespace(paragraphLines.join(" "));
1093
+ return text || null;
1094
+ }
1095
+ function truncateText(value, maxLength) {
1096
+ if (value.length <= maxLength) return value;
1097
+ return `${value.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
1098
+ }
1099
+ function buildPrBodyTemplate(task, subtasks, protoRefs = [], feedbackUpdates = [], existingResources = [], skillRefs = []) {
1100
+ const contextSource = task.prdContent ?? task.notes ?? "";
1101
+ const taskContext = extractFirstMeaningfulParagraph(contextSource);
1102
+ const contextBullets = [
1103
+ `- Resolves MR Manager task ${task.id}.`,
1104
+ `- Task: ${task.title}.`,
1105
+ ...taskContext ? [`- Goal/context: ${truncateText(taskContext, 220)}`] : [],
1106
+ ...subtasks.map((subtask) => `- Completed subtask: ${subtask.title}.`),
1107
+ ...protoRefs.map((ref) => `- Related prototype: ${ref.prototype.title}${ref.selectedVariants?.length ? ` (variants ${ref.selectedVariants.join(", ")})` : ""}.`),
1108
+ ...feedbackUpdates.slice(0, 3).map((update) => `- Addresses feedback from ${new Date(update.createdAt).toLocaleDateString("en-US")}.`),
1109
+ ...existingResources.slice(0, 3).map((resource) => `- Referenced resource: ${resource.name}.${resource.kind === "link" && resource.url ? ` (${resource.url})` : ""}`),
1110
+ ...skillRefs.slice(0, 3).map((ref) => `- Applied skill guidance: ${ref.skill.name}.`)
1111
+ ];
1112
+ const testingHint = task.mode === "development" ? "- [ ] Manual verification completed <describe the user flow or CLI behavior checked>." : "- [ ] Verification completed <describe what you validated>.";
1113
+ return [
1114
+ "## Summary",
1115
+ "- <Replace with 2-4 concise bullets describing the actual behavior or code changes in this branch.>",
1116
+ "- <Mention the main files, systems, or workflows that changed.>",
1117
+ "- <Call out any follow-up, rollout note, or migration detail if relevant. Remove this bullet if not needed.>",
1118
+ "",
1119
+ "## Context",
1120
+ ...contextBullets,
1121
+ "",
1122
+ "## Testing",
1123
+ "- [ ] Automated tests: <list the exact command(s) you ran, or replace with `Not run` plus a reason>.",
1124
+ testingHint,
1125
+ "- [ ] Additional verification: <note screenshots, logs, or why no UI/browser flow applies. Remove if not needed.>",
1126
+ "",
1127
+ "_Replace every placeholder above before creating the PR. Remove bullets or sections that do not apply._"
1128
+ ].join("\n");
1129
+ }
1048
1130
  function pullLatestMain(repoDir, prefix) {
1049
1131
  return new Promise((resolve7) => {
1050
1132
  exec(
@@ -1102,14 +1184,14 @@ function findPrUrl(branchName, repoDir, vcs = "github") {
1102
1184
  }
1103
1185
  function isGitRepo(dir) {
1104
1186
  try {
1105
- return existsSync6(resolve(dir, ".git"));
1187
+ return existsSync7(resolve2(dir, ".git"));
1106
1188
  } catch {
1107
1189
  return false;
1108
1190
  }
1109
1191
  }
1110
1192
  function findChildGitRepos(parentDir) {
1111
1193
  try {
1112
- return readdirSync(parentDir).filter((name) => !name.startsWith(".")).map((name) => resolve(parentDir, name)).filter((path) => {
1194
+ return readdirSync(parentDir).filter((name) => !name.startsWith(".")).map((name) => resolve2(parentDir, name)).filter((path) => {
1113
1195
  try {
1114
1196
  return statSync(path).isDirectory() && isGitRepo(path);
1115
1197
  } catch {
@@ -1272,11 +1354,13 @@ function buildFeaturesSection(repoDir) {
1272
1354
  ``
1273
1355
  ].join("\n");
1274
1356
  }
1275
- function buildExecutionPrompt(task, repoDir, subtasks, vcs = "github", protoRefs = [], feedbackUpdates = [], existingResources = [], skillRefs = []) {
1357
+ function buildExecutionPrompt(task, repoDir, subtasks, vcs = "github", protoRefs = [], feedbackUpdates = [], existingResources = [], skillRefs = [], executionDir) {
1276
1358
  const sid = shortId(task.id);
1277
1359
  const slug = slugify(task.title);
1278
1360
  const branchName = `mr/${sid}/${slug}`;
1279
1361
  const wtPath = worktreePath(sid);
1362
+ const workingDir = executionDir ?? repoDir;
1363
+ const prBodyPath = "/tmp/mr-pr-body.md";
1280
1364
  const notes = task.prdContent ? `
1281
1365
 
1282
1366
  ## PRD (Product Requirements Document)
@@ -1298,10 +1382,11 @@ ${task.notes}` : "";
1298
1382
  ].join("\n") : "";
1299
1383
  const hasFeedback = feedbackUpdates.length > 0;
1300
1384
  const feedbackWtPath = hasFeedback ? worktreePath(`${sid}-fb`) : wtPath;
1301
- const prCreateCmd = vcs === "gitlab" ? `glab mr create --title "${task.title}" --description "Resolves MR Manager task ${task.id}" --yes` : `gh pr create --title "${task.title}" --body "Resolves MR Manager task ${task.id}"`;
1385
+ const prBodyTemplate = buildPrBodyTemplate(task, pendingSubtasks, protoRefs, feedbackUpdates, existingResources, skillRefs);
1386
+ const prCreateCmd = vcs === "gitlab" ? `glab mr create --title "${task.title}" --description-file ${prBodyPath} --yes` : `gh pr create --title "${task.title}" --body-file ${prBodyPath}`;
1302
1387
  return [
1303
1388
  `You are an autonomous agent working on a task from MR Manager.`,
1304
- `Working directory: ${repoDir}`,
1389
+ `Working directory: ${workingDir}`,
1305
1390
  ``,
1306
1391
  `## Task`,
1307
1392
  `Title: ${task.title}`,
@@ -1315,7 +1400,12 @@ ${task.notes}` : "";
1315
1400
  ``,
1316
1401
  `1. **Gather project context first.** Run \`mr context\` to get the project description, current task list, and the last 10 completed tasks. Use this to understand what's already been built, recent decisions, and the overall project direction before you start coding.`,
1317
1402
  ``,
1318
- ...hasFeedback ? [
1403
+ ...executionDir && executionDir !== repoDir ? [
1404
+ `2. Your isolated git worktree is already prepared and this session starts inside it.`,
1405
+ ` - Current working directory: ${executionDir}`,
1406
+ ` - Branch checked out in this worktree: ${branchName}`,
1407
+ ` - If the task spans additional repos, create matching worktrees there as needed.`
1408
+ ] : hasFeedback ? [
1319
1409
  `2. Set up your working environment:`,
1320
1410
  ` - Fetch the latest from origin: \`git fetch origin\``,
1321
1411
  ` - Check out the existing branch in a worktree: \`git worktree add ${feedbackWtPath} origin/${branchName}\``,
@@ -1340,7 +1430,9 @@ ${task.notes}` : "";
1340
1430
  ...hasFeedback ? [
1341
1431
  ` 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.`
1342
1432
  ] : [
1343
- ` c. Open a ${vcs === "gitlab" ? "merge request" : "pull request"}: \`${prCreateCmd}\``
1433
+ ` c. Write a structured ${vcs === "gitlab" ? "merge request" : "pull request"} description to \`${prBodyPath}\` using the template below.`,
1434
+ ` Replace every placeholder with concrete implementation details before you create the ${vcs === "gitlab" ? "MR" : "PR"}.`,
1435
+ ` d. Open a ${vcs === "gitlab" ? "merge request" : "pull request"}: \`${prCreateCmd}\``
1344
1436
  ],
1345
1437
  ``,
1346
1438
  `5. Clean up any worktrees you created: \`git worktree remove --force <path>\``,
@@ -1369,6 +1461,17 @@ ${task.notes}` : "";
1369
1461
  ``,
1370
1462
  `Keep messages short (1 sentence). Post 3-5 updates total.`,
1371
1463
  ``,
1464
+ ...hasFeedback ? [] : [
1465
+ `## PR Description Template`,
1466
+ ``,
1467
+ `Write this template to \`${prBodyPath}\`, then replace the placeholders with the actual details from your implementation.`,
1468
+ `Delete any bullet or section that does not apply, but keep the final description specific and reviewer-friendly.`,
1469
+ ``,
1470
+ "```md",
1471
+ prBodyTemplate,
1472
+ "```",
1473
+ ``
1474
+ ],
1372
1475
  `## Screenshots`,
1373
1476
  ``,
1374
1477
  `Before you finish, take a screenshot of your work to attach to the task. This helps the reviewer see what changed visually.`,
@@ -1806,9 +1909,13 @@ function buildIdeaPrompt(idea, repoDir) {
1806
1909
  }
1807
1910
  function buildAgentArgs(agent, prompt2, mode, sessionId, name) {
1808
1911
  if (agent === "codex") {
1809
- const args = ["exec"];
1912
+ const args = [];
1810
1913
  if (mode === "execute") {
1811
- args.push("--full-auto");
1914
+ args.push("-a", "never");
1915
+ }
1916
+ args.push("exec");
1917
+ if (mode === "execute") {
1918
+ args.push("-s", "danger-full-access");
1812
1919
  }
1813
1920
  args.push(prompt2);
1814
1921
  return { bin: "codex", args };
@@ -1847,7 +1954,7 @@ function runPlanningPhase(task, repoDir, agent) {
1847
1954
  ${output.trim()}`));
1848
1955
  return;
1849
1956
  }
1850
- const planPath = resolve(repoDir, "plan.md");
1957
+ const planPath = resolve2(repoDir, "plan.md");
1851
1958
  try {
1852
1959
  const planContent = readFileSync5(planPath, "utf-8");
1853
1960
  try {
@@ -1873,7 +1980,7 @@ function askYesNo(question) {
1873
1980
  });
1874
1981
  });
1875
1982
  }
1876
- function spawnAgent(agent, repoDir, prompt2, prefix, sessionId, name) {
1983
+ function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name) {
1877
1984
  const { bin, args } = buildAgentArgs(agent, prompt2, "execute", sessionId, name);
1878
1985
  const child = spawn4(bin, args, { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] });
1879
1986
  child.on("error", (err) => {
@@ -1882,12 +1989,19 @@ function spawnAgent(agent, repoDir, prompt2, prefix, sessionId, name) {
1882
1989
  logError(prefix, `Check that "${bin}" is on PATH and "${repoDir}" exists`);
1883
1990
  }
1884
1991
  });
1992
+ if (agent === "codex") {
1993
+ child.stdout?.on("data", () => onActivity?.());
1994
+ child.stderr?.on("data", () => onActivity?.());
1995
+ return child;
1996
+ }
1885
1997
  child.stdout?.on("data", (data) => {
1998
+ onActivity?.();
1886
1999
  for (const line of data.toString().split("\n")) {
1887
2000
  if (line) console.log(`${timestamp()} ${prefix} ${paint("dim", line)}`);
1888
2001
  }
1889
2002
  });
1890
2003
  child.stderr?.on("data", (data) => {
2004
+ onActivity?.();
1891
2005
  for (const line of data.toString().split("\n")) {
1892
2006
  if (line) logError(prefix, paint("dim", line));
1893
2007
  }
@@ -1900,9 +2014,10 @@ var watchCommand = new Command8("watch").description(
1900
2014
  const intervalMs = parseInt(opts.interval, 10) * 1e3;
1901
2015
  const dryRun = opts.dryRun;
1902
2016
  const planApproval = opts.planApproval;
1903
- const rootDir = opts.root ? resolve(opts.root) : process.cwd();
2017
+ const rootDir = opts.root ? resolve2(opts.root) : process.cwd();
1904
2018
  const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
1905
2019
  const scanAt = opts.scanAt;
2020
+ const taskStallTimeoutMs = getTaskStallTimeoutMs();
1906
2021
  const active = /* @__PURE__ */ new Map();
1907
2022
  const failed = /* @__PURE__ */ new Map();
1908
2023
  const queued = /* @__PURE__ */ new Set();
@@ -1914,9 +2029,11 @@ var watchCommand = new Command8("watch").description(
1914
2029
  `interval=${paint("cyan", opts.interval + "s")}`,
1915
2030
  `root=${paint("cyan", rootDir)}`,
1916
2031
  `agent=${paint("cyan", agent)}`,
2032
+ `stall-timeout=${paint("cyan", formatTimeoutMinutes(taskStallTimeoutMs))}`,
1917
2033
  ...planApproval ? [paint("yellow", "plan-approval")] : [],
1918
2034
  ...dryRun ? [paint("yellow", "dry-run")] : [],
1919
- ...scanAt ? [`scan-at=${paint("cyan", scanAt)}`] : []
2035
+ ...scanAt ? [`scan-at=${paint("cyan", scanAt)}`] : [],
2036
+ `hung-timeout=${paint("cyan", `${hungTaskTimeoutMinutes}m`)}`
1920
2037
  ].join(" ");
1921
2038
  const banner = [
1922
2039
  ``,
@@ -1930,6 +2047,17 @@ var watchCommand = new Command8("watch").description(
1930
2047
  console.log(banner);
1931
2048
  console.log(` ${flags}
1932
2049
  `);
2050
+ async function moveTaskToError(task, prefix, reason) {
2051
+ try {
2052
+ await api.patch(`/api/tasks/${task.id}`, { status: "error" });
2053
+ } catch (err) {
2054
+ logError(prefix, `Failed to mark task as error: ${err.message}`);
2055
+ }
2056
+ try {
2057
+ await postTaskUpdate(task.id, reason, "system");
2058
+ } catch {
2059
+ }
2060
+ }
1933
2061
  async function dispatchTask(task, repoDir) {
1934
2062
  const sid = shortId(task.id);
1935
2063
  const slug = slugify(task.title);
@@ -1981,23 +2109,49 @@ var watchCommand = new Command8("watch").description(
1981
2109
  }
1982
2110
  } catch {
1983
2111
  }
1984
- const prompt2 = buildExecutionPrompt(task, repoDir, subtasks, vcs, protoRefs, feedbackUpdates, existingResources, skillRefs);
2112
+ const hasFeedback = feedbackUpdates.length > 0;
2113
+ const desiredWorktreePath = hasFeedback ? worktreePath(`${sid}-fb`) : worktreePath(sid);
2114
+ let executionDir = repoDir;
2115
+ let cleanupWorktreePath;
2116
+ if (isGitRepo(repoDir)) {
2117
+ executionDir = createWorktree(
2118
+ repoDir,
2119
+ branchName,
2120
+ worktreeNameFromPath(desiredWorktreePath)
2121
+ );
2122
+ cleanupWorktreePath = executionDir;
2123
+ logInfo(prefix, `Prepared worktree ${paint("cyan", executionDir)}`);
2124
+ }
2125
+ const prompt2 = buildExecutionPrompt(task, repoDir, subtasks, vcs, protoRefs, feedbackUpdates, existingResources, skillRefs, executionDir);
1985
2126
  const sessionId = agent === "claude" ? randomUUID() : void 0;
1986
- const child = spawnAgent(agent, repoDir, prompt2, prefix, sessionId, task.title);
2127
+ const activeEntry = {
2128
+ process: void 0,
2129
+ title: task.title,
2130
+ repoDir: executionDir,
2131
+ cleanupRepoDir: cleanupWorktreePath ? repoDir : void 0,
2132
+ cleanupWorktreePath,
2133
+ startedAt: Date.now(),
2134
+ lastActivityAt: Date.now()
2135
+ };
2136
+ const touchActivity = () => {
2137
+ activeEntry.lastActivityAt = Date.now();
2138
+ };
2139
+ const child = spawnAgent(agent, executionDir, prompt2, prefix, touchActivity, sessionId, task.title);
2140
+ activeEntry.process = child;
1987
2141
  if (sessionId) {
1988
2142
  api.patch(`/api/tasks/${task.id}`, { claudeSessionId: sessionId }).catch(() => {
1989
2143
  });
1990
2144
  logInfo(prefix, `Claude session: ${paint("dim", sessionId)}`);
1991
2145
  }
1992
- active.set(task.id, { process: child, title: task.title, repoDir });
2146
+ active.set(task.id, activeEntry);
1993
2147
  child.on("exit", async (code) => {
1994
2148
  active.delete(task.id);
1995
2149
  finishing.add(task.id);
1996
2150
  try {
1997
2151
  if (code === 0) {
1998
2152
  try {
1999
- const researchPath = resolve(repoDir, "research.md");
2000
- if (existsSync6(researchPath)) {
2153
+ const researchPath = resolve2(executionDir, "research.md");
2154
+ if (existsSync7(researchPath)) {
2001
2155
  try {
2002
2156
  const researchContent = readFileSync5(researchPath, "utf-8");
2003
2157
  const existingResearch = existingResources.find((r) => r.type === "research");
@@ -2019,32 +2173,8 @@ var watchCommand = new Command8("watch").description(
2019
2173
  logWarn(prefix, `Failed to upload research resource: ${err.message}`);
2020
2174
  }
2021
2175
  }
2022
- if (sessionId) {
2023
- try {
2024
- const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
2025
- if (sessionEntries && sessionEntries.length > 0) {
2026
- const sessionContent = JSON.stringify(sessionEntries);
2027
- const existingSessionLog = existingResources.find((r) => r.type === "session-log");
2028
- if (existingSessionLog) {
2029
- await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
2030
- content: sessionContent
2031
- });
2032
- logSuccess(prefix, `Updated session log resource (${sessionEntries.length} messages)`);
2033
- } else {
2034
- await api.post(`/api/tasks/${task.id}/resources`, {
2035
- type: "session-log",
2036
- title: `Session Log \u2014 ${task.title}`,
2037
- content: sessionContent
2038
- });
2039
- logSuccess(prefix, `Uploaded session log (${sessionEntries.length} messages)`);
2040
- }
2041
- }
2042
- } catch (err) {
2043
- logWarn(prefix, `Failed to upload session log: ${err.message}`);
2044
- }
2045
- }
2046
- const noMrPath = resolve(repoDir, ".mr-no-mr");
2047
- const noMrRequested = existsSync6(noMrPath);
2176
+ const noMrPath = resolve2(executionDir, ".mr-no-mr");
2177
+ const noMrRequested = existsSync7(noMrPath);
2048
2178
  let noMrDescription;
2049
2179
  if (noMrRequested) {
2050
2180
  noMrDescription = readFileSync5(noMrPath, "utf-8").trim();
@@ -2077,33 +2207,15 @@ var watchCommand = new Command8("watch").description(
2077
2207
  } catch (err) {
2078
2208
  logError(prefix, `Failed to update task: ${err.message}`);
2079
2209
  }
2080
- } else {
2081
- logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), leaving status unchanged`);
2082
- await postTaskUpdate(task.id, `Agent failed with exit code ${code}`, "system");
2083
- if (sessionId) {
2084
- try {
2085
- const sessionEntries = readAndParseSessionLog(sessionId, repoDir);
2086
- if (sessionEntries && sessionEntries.length > 0) {
2087
- const sessionContent = JSON.stringify(sessionEntries);
2088
- const existingSessionLog = existingResources.find((r) => r.type === "session-log");
2089
- if (existingSessionLog) {
2090
- await api.patch(`/api/tasks/${task.id}/resources/${existingSessionLog.id}`, {
2091
- content: sessionContent
2092
- });
2093
- } else {
2094
- await api.post(`/api/tasks/${task.id}/resources`, {
2095
- type: "session-log",
2096
- title: `Session Log \u2014 ${task.title}`,
2097
- content: sessionContent
2098
- });
2099
- }
2100
- logInfo(prefix, `Uploaded session log for failed task (${sessionEntries.length} messages)`);
2101
- }
2102
- } catch {
2103
- }
2104
- }
2210
+ } else if (!activeEntry.terminatedForError) {
2211
+ const reason = `Agent failed with exit code ${code} \u2014 task moved to error`;
2212
+ logError(prefix, `"${paint("bold", task.title)}" failed (exit ${code}), moving task to error`);
2213
+ await moveTaskToError(task, prefix, reason);
2105
2214
  }
2106
2215
  } finally {
2216
+ if (activeEntry.cleanupRepoDir && activeEntry.cleanupWorktreePath) {
2217
+ removeWorktree(activeEntry.cleanupRepoDir, activeEntry.cleanupWorktreePath);
2218
+ }
2107
2219
  queued.delete(task.id);
2108
2220
  finishing.delete(task.id);
2109
2221
  }
@@ -2138,15 +2250,15 @@ var watchCommand = new Command8("watch").description(
2138
2250
  "system"
2139
2251
  );
2140
2252
  const prompt2 = buildPrdPrompt(task, repoDir, existingPlanResource?.content, feedbackUpdates);
2141
- const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, task.title);
2142
- active.set(task.id, { process: child, title: task.title, repoDir });
2253
+ const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, void 0, task.title);
2254
+ active.set(task.id, { process: child, title: task.title, repoDir, startedAt: Date.now() });
2143
2255
  child.on("exit", async (code) => {
2144
2256
  active.delete(task.id);
2145
2257
  finishing.add(task.id);
2146
2258
  try {
2147
2259
  if (code === 0) {
2148
2260
  try {
2149
- const prdPath = resolve(repoDir, "prd.md");
2261
+ const prdPath = resolve2(repoDir, "prd.md");
2150
2262
  let prdContent;
2151
2263
  try {
2152
2264
  prdContent = readFileSync5(prdPath, "utf-8");
@@ -2184,8 +2296,13 @@ var watchCommand = new Command8("watch").description(
2184
2296
  logError(prefix, `Failed to update task: ${err.message}`);
2185
2297
  }
2186
2298
  } else {
2187
- logError(prefix, `"${paint("bold", task.title)}" PRD generation failed (exit ${code}), leaving status unchanged`);
2188
- await postTaskUpdate(task.id, `PRD generation failed with exit code ${code}`, "system");
2299
+ logError(prefix, `"${paint("bold", task.title)}" PRD generation failed (exit ${code}), marked as error`);
2300
+ try {
2301
+ await api.patch(`/api/tasks/${task.id}`, { status: "error" });
2302
+ } catch (err) {
2303
+ logError(prefix, `Failed to mark task as error: ${err.message}`);
2304
+ }
2305
+ await postTaskUpdate(task.id, `PRD generation failed with exit code ${code} \u2014 task moved to error`, "system");
2189
2306
  }
2190
2307
  } finally {
2191
2308
  queued.delete(task.id);
@@ -2200,7 +2317,7 @@ var watchCommand = new Command8("watch").description(
2200
2317
  const stalePattern = /^prototype-\d+\.html$/;
2201
2318
  for (const f of readdirSync(repoDir).filter((f2) => stalePattern.test(f2))) {
2202
2319
  try {
2203
- unlinkSync(resolve(repoDir, f));
2320
+ unlinkSync(resolve2(repoDir, f));
2204
2321
  } catch {
2205
2322
  }
2206
2323
  }
@@ -2218,7 +2335,7 @@ var watchCommand = new Command8("watch").description(
2218
2335
  prompt2 = buildPrototypePrompt(proto, repoDir);
2219
2336
  }
2220
2337
  const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, proto.title);
2221
- active.set(`proto-${proto.id}`, { process: child, title: proto.title, repoDir });
2338
+ active.set(`proto-${proto.id}`, { process: child, title: proto.title, repoDir, startedAt: Date.now() });
2222
2339
  child.on("exit", async (code) => {
2223
2340
  const key = `proto-${proto.id}`;
2224
2341
  active.delete(key);
@@ -2230,7 +2347,7 @@ var watchCommand = new Command8("watch").description(
2230
2347
  const found = readdirSync(repoDir).filter((f) => protoPattern.test(f)).sort();
2231
2348
  const files = found.map((f) => ({
2232
2349
  name: f,
2233
- content: readFileSync5(resolve(repoDir, f), "utf-8")
2350
+ content: readFileSync5(resolve2(repoDir, f), "utf-8")
2234
2351
  }));
2235
2352
  if (files.length === 0) {
2236
2353
  logError(prefix, `No prototype HTML files found in ${repoDir}`);
@@ -2241,7 +2358,7 @@ var watchCommand = new Command8("watch").description(
2241
2358
  logSuccess(prefix, `"${paint("bold", proto.title)}" uploaded ${files.length} file(s)`);
2242
2359
  for (const file of files) {
2243
2360
  try {
2244
- unlinkSync(resolve(repoDir, file.name));
2361
+ unlinkSync(resolve2(repoDir, file.name));
2245
2362
  } catch {
2246
2363
  }
2247
2364
  }
@@ -2275,7 +2392,7 @@ var watchCommand = new Command8("watch").description(
2275
2392
  }
2276
2393
  const prompt2 = buildRepoCreationPrompt(project, workDir);
2277
2394
  const child = spawnAgent(agent, workDir, prompt2, prefix, void 0, project.name);
2278
- active.set(`repo-${project.id}`, { process: child, title: project.name, repoDir: workDir });
2395
+ active.set(`repo-${project.id}`, { process: child, title: project.name, repoDir: workDir, startedAt: Date.now() });
2279
2396
  child.on("exit", async (code) => {
2280
2397
  const key = `repo-${project.id}`;
2281
2398
  active.delete(key);
@@ -2302,13 +2419,13 @@ var watchCommand = new Command8("watch").description(
2302
2419
  logDispatch(prefix, `"${paint("bold", idea.title)}"${idea.feedback ? paint("cyan", " (iteration)") : ""} ${paint("gray", repoDir)}`);
2303
2420
  for (const f of ["idea-plan.md", "idea-tasks.json", "idea-prototype.html"]) {
2304
2421
  try {
2305
- unlinkSync(resolve(repoDir, f));
2422
+ unlinkSync(resolve2(repoDir, f));
2306
2423
  } catch {
2307
2424
  }
2308
2425
  }
2309
2426
  const prompt2 = buildIdeaPrompt(idea, repoDir);
2310
2427
  const child = spawnAgent(agent, repoDir, prompt2, prefix, void 0, idea.title);
2311
- active.set(`idea-${idea.id}`, { process: child, title: idea.title, repoDir });
2428
+ active.set(`idea-${idea.id}`, { process: child, title: idea.title, repoDir, startedAt: Date.now() });
2312
2429
  child.on("exit", async (code) => {
2313
2430
  const key = `idea-${idea.id}`;
2314
2431
  active.delete(key);
@@ -2319,9 +2436,9 @@ var watchCommand = new Command8("watch").description(
2319
2436
  let plan;
2320
2437
  let protoHtml;
2321
2438
  let followUpTasks;
2322
- const planPath = resolve(repoDir, "idea-plan.md");
2323
- const tasksPath = resolve(repoDir, "idea-tasks.json");
2324
- const protoPath = resolve(repoDir, "idea-prototype.html");
2439
+ const planPath = resolve2(repoDir, "idea-plan.md");
2440
+ const tasksPath = resolve2(repoDir, "idea-tasks.json");
2441
+ const protoPath = resolve2(repoDir, "idea-prototype.html");
2325
2442
  try {
2326
2443
  plan = readFileSync5(planPath, "utf-8");
2327
2444
  } catch {
@@ -2410,7 +2527,7 @@ var watchCommand = new Command8("watch").description(
2410
2527
  queued.delete(key);
2411
2528
  failed.set(key, err.message);
2412
2529
  });
2413
- active.set(key, { process: scanProc, title: `scan-${scan.id.slice(0, 8)}`, repoDir: rootDir });
2530
+ active.set(key, { process: scanProc, title: `scan-${scan.id.slice(0, 8)}`, repoDir: rootDir, startedAt: Date.now() });
2414
2531
  scanProc.stdout?.on("data", (d) => {
2415
2532
  const lines = d.toString().trim().split("\n");
2416
2533
  for (const line of lines) {
@@ -2487,9 +2604,60 @@ ${divider}`);
2487
2604
  }
2488
2605
  const nonTestQueued = queuedTasks.filter((t) => t.mode !== "testing");
2489
2606
  const nonTestDelegated = delegatedTasks.filter((t) => t.mode !== "testing");
2490
- const tasks = [...nonTestQueued, ...nonTestDelegated];
2607
+ const staleDelegatedTasks = nonTestDelegated.filter((task) => {
2608
+ if (!task.inProgressSince) return false;
2609
+ return Date.now() - new Date(task.inProgressSince).getTime() >= hungTaskTimeoutMs;
2610
+ });
2611
+ for (const task of staleDelegatedTasks) {
2612
+ const prefix = taskTag(shortId(task.id));
2613
+ const running = active.get(task.id);
2614
+ if (running) {
2615
+ logWarn(prefix, `Task exceeded hang timeout after ${formatElapsed(Date.now() - running.startedAt)}, terminating agent\u2026`);
2616
+ running.process.kill("SIGTERM");
2617
+ active.delete(task.id);
2618
+ }
2619
+ queued.delete(task.id);
2620
+ finishing.delete(task.id);
2621
+ const elapsed = task.inProgressSince ? formatElapsed(Date.now() - new Date(task.inProgressSince).getTime()) : `${hungTaskTimeoutMinutes}m`;
2622
+ await moveTaskToError(
2623
+ task,
2624
+ prefix,
2625
+ `Agent appears hung after ${elapsed} without finishing \u2014 task moved to error`
2626
+ );
2627
+ }
2628
+ const staleTaskIds = new Set(staleDelegatedTasks.map((task) => task.id));
2629
+ const tasks = [...nonTestQueued, ...nonTestDelegated.filter((task) => !staleTaskIds.has(task.id))];
2491
2630
  const config = loadConfig();
2492
2631
  const activeTaskIds = new Set(tasks.map((t) => t.id));
2632
+ for (const task of tasks) {
2633
+ if (task.status !== "delegated") continue;
2634
+ const sid = shortId(task.id);
2635
+ const prefix = taskTag(sid);
2636
+ const activeEntry = active.get(task.id);
2637
+ const idleMs = activeEntry ? Date.now() - activeEntry.lastActivityAt : null;
2638
+ const delegatedAtMs = task.inProgressSince ? Date.now() - new Date(task.inProgressSince).getTime() : null;
2639
+ const exceededIdleTimeout = idleMs !== null && idleMs >= taskStallTimeoutMs;
2640
+ const exceededOrphanTimeout = !activeEntry && delegatedAtMs !== null && delegatedAtMs >= taskStallTimeoutMs;
2641
+ if (!exceededIdleTimeout && !exceededOrphanTimeout) continue;
2642
+ const timeoutLabel = formatTimeoutMinutes(taskStallTimeoutMs);
2643
+ const reason = activeEntry ? `Agent process became idle for more than ${timeoutLabel}` : `Task remained delegated without an active watch process for more than ${timeoutLabel}`;
2644
+ logError(prefix, `"${task.title}" marked as error: ${reason}`);
2645
+ if (activeEntry) {
2646
+ activeEntry.terminatedForError = true;
2647
+ }
2648
+ try {
2649
+ await api.patch(`/api/tasks/${task.id}`, { status: "error" });
2650
+ await postTaskUpdate(task.id, `Task moved to error \u2014 ${reason}`, "system");
2651
+ } catch (err) {
2652
+ logError(prefix, `Failed to mark task as error: ${err.message}`);
2653
+ continue;
2654
+ }
2655
+ failed.set(task.id, reason);
2656
+ queued.delete(task.id);
2657
+ if (activeEntry) {
2658
+ activeEntry.process.kill("SIGTERM");
2659
+ }
2660
+ }
2493
2661
  for (const [taskId, entry] of active) {
2494
2662
  if (taskId.startsWith("proto-") || taskId.startsWith("repo-") || taskId.startsWith("scan-")) continue;
2495
2663
  if (!activeTaskIds.has(taskId)) {
@@ -2522,7 +2690,7 @@ ${divider}`);
2522
2690
  failed.set(task.id, reason);
2523
2691
  continue;
2524
2692
  }
2525
- if (!existsSync6(repoDir)) {
2693
+ if (!existsSync7(repoDir)) {
2526
2694
  const reason = `linked directory "${repoDir}" does not exist`;
2527
2695
  logError(prefix, `"${task.title}": ${reason} \u2014 will not retry`);
2528
2696
  failed.set(task.id, reason);
@@ -2593,7 +2761,7 @@ ${divider}`);
2593
2761
  logWarn(prefix, `"${proto.title}": no linked directory found \u2014 skipping`);
2594
2762
  continue;
2595
2763
  }
2596
- if (!existsSync6(repoDir)) {
2764
+ if (!existsSync7(repoDir)) {
2597
2765
  logError(prefix, `"${proto.title}": linked directory "${repoDir}" does not exist \u2014 skipping`);
2598
2766
  failed.set(key, `directory does not exist: ${repoDir}`);
2599
2767
  continue;
@@ -2987,11 +3155,8 @@ var subtaskCompleteCommand = new Command12("subtask-complete").description("Mark
2987
3155
  console.log(`\u2713 Subtask completed: ${subtask.title}`);
2988
3156
  });
2989
3157
 
2990
- // cli/commands/up.ts
3158
+ // cli/commands/prototype.ts
2991
3159
  import { Command as Command13 } from "commander";
2992
- import { spawn as spawn5 } from "child_process";
2993
- import { resolve as resolve2 } from "path";
2994
- import { fileURLToPath as fileURLToPath2 } from "url";
2995
3160
  var c4 = {
2996
3161
  reset: "\x1B[0m",
2997
3162
  bold: "\x1B[1m",
@@ -3000,113 +3165,28 @@ var c4 = {
3000
3165
  green: "\x1B[32m",
3001
3166
  yellow: "\x1B[33m",
3002
3167
  red: "\x1B[31m",
3003
- magenta: "\x1B[35m",
3168
+ blue: "\x1B[34m",
3004
3169
  gray: "\x1B[90m"
3005
3170
  };
3006
3171
  function paint4(color, text) {
3007
3172
  return `${c4[color]}${text}${c4.reset}`;
3008
3173
  }
3009
- function timestamp2() {
3010
- return paint4("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
3011
- }
3012
- function upTag() {
3013
- return paint4("cyan", "[up]");
3014
- }
3015
- function log(msg) {
3016
- console.log(`${timestamp2()} ${upTag()} ${msg}`);
3017
- }
3018
- function logErr(msg) {
3019
- console.error(`${timestamp2()} ${upTag()} ${paint4("red", "\u2717")} ${msg}`);
3020
- }
3021
- function spawnMr(args) {
3022
- const entry = process.argv[1];
3023
- const child = spawn5(process.execPath, [entry, ...args], {
3024
- stdio: ["ignore", "pipe", "pipe"]
3025
- });
3026
- return child;
3027
- }
3028
- var upCommand = new Command13("up").description("Run mr watch and mr digest together with a single command").option("--interval <seconds>", "Polling interval for both watch and digest", "15").option("--watch-interval <seconds>", "Override polling interval for mr watch").option("--digest-interval <seconds>", "Override polling interval for mr digest").option("--dry-run", "Pass --dry-run to mr watch", false).option("--plan-approval", "Pass --plan-approval to mr watch", false).option("--root <dir>", "Pass --root to mr watch (default: cwd)").option("--agent <agent>", "AI agent to use: claude or codex", "claude").action((opts) => {
3029
- const watchInterval = opts.watchInterval ?? opts.interval;
3030
- const digestInterval = opts.digestInterval ?? opts.interval;
3031
- const agent = opts.agent === "codex" ? "codex" : "claude";
3032
- const watchArgs = ["watch", "--interval", watchInterval, "--agent", agent];
3033
- if (opts.dryRun) watchArgs.push("--dry-run");
3034
- if (opts.planApproval) watchArgs.push("--plan-approval");
3035
- if (opts.root) watchArgs.push("--root", opts.root);
3036
- const digestArgs = ["digest", "--interval", digestInterval, "--agent", agent];
3037
- const banner = [
3038
- ``,
3039
- paint4("cyan", ` \u2554\u2566\u2557\u2566\u2550\u2557 \u2566 \u2566\u2554\u2550\u2557`),
3040
- paint4("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D \u2551 \u2551\u2560\u2550\u255D`),
3041
- paint4("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u2569 `),
3042
- paint4("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\u2500\u2500`),
3043
- paint4("dim", ` mr watch + mr digest \xB7 powered by ${agent}`),
3044
- ``
3045
- ].join("\n");
3046
- console.log(banner);
3047
- const flags = [
3048
- `watch-interval=${paint4("cyan", watchInterval + "s")}`,
3049
- `digest-interval=${paint4("cyan", digestInterval + "s")}`,
3050
- `agent=${paint4("cyan", agent)}`,
3051
- ...opts.dryRun ? [paint4("yellow", "dry-run")] : [],
3052
- ...opts.planApproval ? [paint4("yellow", "plan-approval")] : [],
3053
- ...opts.root ? [`root=${paint4("cyan", resolve2(opts.root))}`] : []
3054
- ].join(" ");
3055
- console.log(` ${flags}
3056
- `);
3057
- const watchProc = spawnMr(watchArgs);
3058
- const digestProc = spawnMr(digestArgs);
3059
- for (const [proc, label] of [[watchProc, "watch"], [digestProc, "digest"]]) {
3060
- proc.stdout?.on("data", (d) => process.stdout.write(d));
3061
- proc.stderr?.on("data", (d) => process.stderr.write(d));
3062
- proc.on("exit", (code, signal) => {
3063
- if (signal !== "SIGTERM" && signal !== "SIGINT") {
3064
- logErr(`mr ${label} exited unexpectedly (code=${code ?? "?"}, signal=${signal ?? "none"})`);
3065
- }
3066
- });
3067
- }
3068
- function shutdown() {
3069
- log("Shutting down\u2026");
3070
- watchProc.kill("SIGTERM");
3071
- digestProc.kill("SIGTERM");
3072
- setTimeout(() => process.exit(0), 500);
3073
- }
3074
- process.on("SIGINT", shutdown);
3075
- process.on("SIGTERM", shutdown);
3076
- });
3077
-
3078
- // cli/commands/prototype.ts
3079
- import { Command as Command14 } from "commander";
3080
- var c5 = {
3081
- reset: "\x1B[0m",
3082
- bold: "\x1B[1m",
3083
- dim: "\x1B[2m",
3084
- cyan: "\x1B[36m",
3085
- green: "\x1B[32m",
3086
- yellow: "\x1B[33m",
3087
- red: "\x1B[31m",
3088
- blue: "\x1B[34m",
3089
- gray: "\x1B[90m"
3090
- };
3091
- function paint5(color, text) {
3092
- return `${c5[color]}${text}${c5.reset}`;
3093
- }
3094
3174
  function statusBadge(status) {
3095
3175
  switch (status) {
3096
3176
  case "pending":
3097
- return paint5("gray", "\u25CB pending");
3177
+ return paint4("gray", "\u25CB pending");
3098
3178
  case "in_progress":
3099
- return paint5("cyan", "\u27F3 in_progress");
3179
+ return paint4("cyan", "\u27F3 in_progress");
3100
3180
  case "completed":
3101
- return paint5("green", "\u2713 completed");
3181
+ return paint4("green", "\u2713 completed");
3102
3182
  case "failed":
3103
- return paint5("red", "\u2717 failed");
3183
+ return paint4("red", "\u2717 failed");
3104
3184
  default:
3105
- return paint5("gray", status);
3185
+ return paint4("gray", status);
3106
3186
  }
3107
3187
  }
3108
- var prototypeCommand = new Command14("prototype").description("Manage prototypes").addCommand(
3109
- new Command14("list").description("List prototypes for the linked project").option("--all", "Show prototypes for all projects").action(async (opts) => {
3188
+ var prototypeCommand = new Command13("prototype").description("Manage prototypes").addCommand(
3189
+ new Command13("list").description("List prototypes for the linked project").option("--all", "Show prototypes for all projects").action(async (opts) => {
3110
3190
  const params = new URLSearchParams();
3111
3191
  if (!opts.all) {
3112
3192
  const projectId = getLinkedProjectId();
@@ -3118,21 +3198,21 @@ var prototypeCommand = new Command14("prototype").description("Manage prototypes
3118
3198
  }
3119
3199
  const prototypes = await api.get(`/api/prototypes?${params.toString()}`);
3120
3200
  if (prototypes.length === 0) {
3121
- console.log(paint5("gray", "No prototypes found."));
3201
+ console.log(paint4("gray", "No prototypes found."));
3122
3202
  return;
3123
3203
  }
3124
3204
  console.log();
3125
3205
  for (const p of prototypes) {
3126
3206
  const date = new Date(p.createdAt).toLocaleDateString();
3127
3207
  console.log(
3128
- ` ${paint5("bold", p.title)} ${statusBadge(p.status)} ${paint5("gray", p.id.slice(0, 8))} ${paint5("dim", date)}`
3208
+ ` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
3129
3209
  );
3130
- console.log(` ${paint5("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
3210
+ console.log(` ${paint4("dim", p.prompt.slice(0, 80) + (p.prompt.length > 80 ? "\u2026" : ""))}`);
3131
3211
  console.log();
3132
3212
  }
3133
3213
  })
3134
3214
  ).addCommand(
3135
- new Command14("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project)").option("--variants <count>", "Number of variants to generate (1-50)", "5").action(async (title, opts) => {
3215
+ new Command13("create").description("Create a new prototype").argument("<title>", "Title of the prototype").requiredOption("--prompt <prompt>", "Design description / prompt").option("--project <projectId>", "Project ID (defaults to linked project)").option("--variants <count>", "Number of variants to generate (1-50)", "5").action(async (title, opts) => {
3136
3216
  const projectId = opts.project ?? getLinkedProjectId();
3137
3217
  if (!projectId) {
3138
3218
  console.error('No project linked. Run "mr link <project-id>" or use --project.');
@@ -3146,37 +3226,37 @@ var prototypeCommand = new Command14("prototype").description("Manage prototypes
3146
3226
  projectId
3147
3227
  });
3148
3228
  console.log();
3149
- console.log(` ${paint5("green", "\u2713")} Created prototype: ${paint5("bold", prototype.title)}`);
3150
- console.log(` ${paint5("gray", "ID:")} ${prototype.id}`);
3151
- console.log(` ${paint5("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
3229
+ console.log(` ${paint4("green", "\u2713")} Created prototype: ${paint4("bold", prototype.title)}`);
3230
+ console.log(` ${paint4("gray", "ID:")} ${prototype.id}`);
3231
+ console.log(` ${paint4("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
3152
3232
  console.log();
3153
3233
  })
3154
3234
  ).addCommand(
3155
- new Command14("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
3235
+ new Command13("start").description("Start prototype generation (sets status to in_progress)").argument("<id>", "Prototype ID").action(async (id) => {
3156
3236
  const prototype = await api.patch(`/api/prototypes/${id}`, {
3157
3237
  status: "in_progress"
3158
3238
  });
3159
3239
  console.log();
3160
- console.log(` ${paint5("cyan", "\u27F3")} Started: ${paint5("bold", prototype.title)}`);
3161
- console.log(` ${paint5("gray", "The watch agent will pick this up shortly.")}`);
3240
+ console.log(` ${paint4("cyan", "\u27F3")} Started: ${paint4("bold", prototype.title)}`);
3241
+ console.log(` ${paint4("gray", "The watch agent will pick this up shortly.")}`);
3162
3242
  console.log();
3163
3243
  })
3164
3244
  ).addCommand(
3165
- new Command14("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
3245
+ new Command13("retry").description("Retry a failed prototype").argument("<id>", "Prototype ID").action(async (id) => {
3166
3246
  const prototype = await api.patch(`/api/prototypes/${id}`, {
3167
3247
  status: "in_progress",
3168
3248
  files: null
3169
3249
  });
3170
3250
  console.log();
3171
- console.log(` ${paint5("cyan", "\u27F3")} Retrying: ${paint5("bold", prototype.title)}`);
3251
+ console.log(` ${paint4("cyan", "\u27F3")} Retrying: ${paint4("bold", prototype.title)}`);
3172
3252
  console.log();
3173
3253
  })
3174
3254
  );
3175
3255
 
3176
3256
  // cli/commands/setup.ts
3177
- import { Command as Command15 } from "commander";
3257
+ import { Command as Command14 } from "commander";
3178
3258
  import { exec as exec2 } from "child_process";
3179
- var c6 = {
3259
+ var c5 = {
3180
3260
  reset: "\x1B[0m",
3181
3261
  bold: "\x1B[1m",
3182
3262
  dim: "\x1B[2m",
@@ -3187,8 +3267,8 @@ var c6 = {
3187
3267
  magenta: "\x1B[35m",
3188
3268
  gray: "\x1B[90m"
3189
3269
  };
3190
- function paint6(color, text) {
3191
- return `${c6[color]}${text}${c6.reset}`;
3270
+ function paint5(color, text) {
3271
+ return `${c5[color]}${text}${c5.reset}`;
3192
3272
  }
3193
3273
  function commandExists(cmd) {
3194
3274
  return new Promise((resolve7) => {
@@ -3432,60 +3512,60 @@ async function checkApiConnectivity() {
3432
3512
  }
3433
3513
  }
3434
3514
  function printResults(checks) {
3435
- const maxNameLen = Math.max(...checks.map((c11) => c11.name.length));
3515
+ const maxNameLen = Math.max(...checks.map((c10) => c10.name.length));
3436
3516
  let allOk = true;
3437
3517
  for (const check of checks) {
3438
3518
  const isOptional = check.optional ?? false;
3439
- const icon = check.ok ? paint6("green", "\u2713") : isOptional ? paint6("yellow", "\u25CB") : paint6("red", "\u2717");
3519
+ const icon = check.ok ? paint5("green", "\u2713") : isOptional ? paint5("yellow", "\u25CB") : paint5("red", "\u2717");
3440
3520
  const name = check.name.padEnd(maxNameLen);
3441
- const msg = check.ok ? paint6("green", check.message) : isOptional ? paint6("yellow", check.message) : paint6("red", check.message);
3521
+ const msg = check.ok ? paint5("green", check.message) : isOptional ? paint5("yellow", check.message) : paint5("red", check.message);
3442
3522
  console.log(` ${icon} ${name} ${msg}`);
3443
3523
  if (!check.ok && !isOptional) allOk = false;
3444
3524
  }
3445
3525
  return allOk;
3446
3526
  }
3447
3527
  async function autoFix(checks, agent) {
3448
- const { spawn: spawn9 } = await import("child_process");
3449
- const ghInstalled = checks.find((c11) => c11.name === "GitHub CLI (gh)").ok;
3450
- const ghAuthed = checks.find((c11) => c11.name === "GitHub CLI auth").ok;
3451
- const mrAuthed = checks.find((c11) => c11.name === "Mr. Manager CLI auth").ok;
3452
- const claudeCheck = checks.find((c11) => c11.name === "Claude Code (claude)");
3528
+ const { spawn: spawn8 } = await import("child_process");
3529
+ const ghInstalled = checks.find((c10) => c10.name === "GitHub CLI (gh)").ok;
3530
+ const ghAuthed = checks.find((c10) => c10.name === "GitHub CLI auth").ok;
3531
+ const mrAuthed = checks.find((c10) => c10.name === "Mr. Manager CLI auth").ok;
3532
+ const claudeCheck = checks.find((c10) => c10.name === "Claude Code (claude)");
3453
3533
  if (claudeCheck && !claudeCheck.ok && agent === "claude") {
3454
- console.log(paint6("cyan", " Installing Claude Code..."));
3455
- console.log(paint6("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
3534
+ console.log(paint5("cyan", " Installing Claude Code..."));
3535
+ console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
3456
3536
  await new Promise((resolve7) => {
3457
- const child = spawn9("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
3537
+ const child = spawn8("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
3458
3538
  child.on("exit", () => resolve7());
3459
3539
  });
3460
3540
  console.log("");
3461
3541
  }
3462
3542
  if (ghInstalled && !ghAuthed) {
3463
- console.log(paint6("cyan", " Running gh auth login..."));
3543
+ console.log(paint5("cyan", " Running gh auth login..."));
3464
3544
  await new Promise((resolve7) => {
3465
- const child = spawn9("gh", ["auth", "login"], { stdio: "inherit" });
3545
+ const child = spawn8("gh", ["auth", "login"], { stdio: "inherit" });
3466
3546
  child.on("exit", () => resolve7());
3467
3547
  });
3468
3548
  console.log("");
3469
3549
  }
3470
3550
  if (!mrAuthed) {
3471
- console.log(paint6("cyan", " Running mr login..."));
3551
+ console.log(paint5("cyan", " Running mr login..."));
3472
3552
  const entry = process.argv[1];
3473
3553
  await new Promise((resolve7) => {
3474
- const child = spawn9(process.execPath, [entry, "login"], { stdio: "inherit" });
3554
+ const child = spawn8(process.execPath, [entry, "login"], { stdio: "inherit" });
3475
3555
  child.on("exit", () => resolve7());
3476
3556
  });
3477
3557
  console.log("");
3478
3558
  }
3479
3559
  }
3480
- var setupCommand = new Command15("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
3560
+ var setupCommand = new Command14("setup").description("Check that all dependencies for mr watch are installed and configured").option("--fix", "Attempt to auto-fix issues where possible", false).option("--agent <agent>", "AI agent to check: claude, codex, or gemini (default: claude)", "claude").action(async (opts) => {
3481
3561
  const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
3482
3562
  const banner = [
3483
3563
  ``,
3484
- paint6("cyan", ` \u2554\u2566\u2557\u2551\u2550\u2557 \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557\u2551 \u2551\u2554\u2550\u2557`),
3485
- paint6("magenta", ` \u2551\u2551\u2551\u2560\u2566\u2563 \u255A\u2550\u2557\u2551\u2563 \u2551 \u2551 \u2551\u2560\u2550\u2563`),
3486
- paint6("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u255A\u2550\u255D \u2569 \u255A\u2550\u255D\u2569 `),
3487
- paint6("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`),
3488
- paint6("dim", ` environment check for mr watch (agent: ${agent})`),
3564
+ paint5("cyan", ` \u2554\u2566\u2557\u2551\u2550\u2557 \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2566\u2557\u2551 \u2551\u2554\u2550\u2557`),
3565
+ paint5("magenta", ` \u2551\u2551\u2551\u2560\u2566\u2563 \u255A\u2550\u2557\u2551\u2563 \u2551 \u2551 \u2551\u2560\u2550\u2563`),
3566
+ paint5("cyan", ` \u2569 \u2569\u2569\u255A\u2550 \u255A\u2550\u255D\u255A\u2550\u255D \u2569 \u255A\u2550\u255D\u2569 `),
3567
+ paint5("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`),
3568
+ paint5("dim", ` environment check for mr watch (agent: ${agent})`),
3489
3569
  ``
3490
3570
  ].join("\n");
3491
3571
  console.log(banner);
@@ -3505,18 +3585,18 @@ var setupCommand = new Command15("setup").description("Check that all dependenci
3505
3585
  const allOk = printResults(checks);
3506
3586
  console.log("");
3507
3587
  if (allOk) {
3508
- console.log(paint6("green", " All checks passed! You're ready to run mr watch."));
3588
+ console.log(paint5("green", " All checks passed! You're ready to run mr watch."));
3509
3589
  if (agent === "claude") {
3510
- console.log(paint6("dim", " Tip: check other agents with --agent codex or --agent gemini"));
3590
+ console.log(paint5("dim", " Tip: check other agents with --agent codex or --agent gemini"));
3511
3591
  }
3512
3592
  console.log("");
3513
3593
  return;
3514
3594
  }
3515
- const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
3595
+ const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
3516
3596
  if (fixes.length > 0) {
3517
- console.log(paint6("yellow", " To fix:"));
3597
+ console.log(paint5("yellow", " To fix:"));
3518
3598
  for (const fix of fixes) {
3519
- console.log(` ${paint6("dim", "\u2192")} ${paint6("bold", fix.name)}: ${fix.fix}`);
3599
+ console.log(` ${paint5("dim", "\u2192")} ${paint5("bold", fix.name)}: ${fix.fix}`);
3520
3600
  }
3521
3601
  console.log("");
3522
3602
  }
@@ -3527,8 +3607,8 @@ var setupCommand = new Command15("setup").description("Check that all dependenci
3527
3607
  });
3528
3608
 
3529
3609
  // cli/commands/update.ts
3530
- import { Command as Command16 } from "commander";
3531
- var updateCommand = new Command16("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) => {
3610
+ import { Command as Command15 } from "commander";
3611
+ 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) => {
3532
3612
  await api.post(`/api/tasks/${taskId}/updates`, {
3533
3613
  message,
3534
3614
  source: opts.source
@@ -3537,17 +3617,17 @@ var updateCommand = new Command16("update").description("Post a status update to
3537
3617
  });
3538
3618
 
3539
3619
  // cli/commands/screenshot.ts
3540
- import { Command as Command17 } from "commander";
3541
- import { readFileSync as readFileSync6, existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
3542
- import { join as join8 } from "path";
3620
+ import { Command as Command16 } from "commander";
3621
+ import { readFileSync as readFileSync6, existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
3622
+ import { join as join7 } from "path";
3543
3623
  import { tmpdir } from "os";
3544
- var screenshotCommand = new Command17("screenshot").description(
3624
+ var screenshotCommand = new Command16("screenshot").description(
3545
3625
  "Take or attach a screenshot to a task update (agents use this to show their work)"
3546
3626
  ).argument("<task-id>", "Task ID").argument("[file]", "Path to an image file (if omitted, uses headless browser to screenshot the app)").option("-m, --message <message>", "Optional message to include with the screenshot").option("-u, --url <url>", "Custom URL to screenshot (defaults to the task's project page)").action(async (taskId, file, opts) => {
3547
3627
  let filePath = file;
3548
3628
  let tempFile = null;
3549
3629
  if (!filePath) {
3550
- tempFile = join8(tmpdir(), `mr-screenshot-${Date.now()}.png`);
3630
+ tempFile = join7(tmpdir(), `mr-screenshot-${Date.now()}.png`);
3551
3631
  try {
3552
3632
  const config2 = loadConfig();
3553
3633
  let targetUrl = opts.url;
@@ -3578,13 +3658,13 @@ var screenshotCommand = new Command17("screenshot").description(
3578
3658
  console.error("[screenshot] Make sure the browse daemon is set up: mr browse setup");
3579
3659
  process.exit(1);
3580
3660
  }
3581
- if (!existsSync7(tempFile)) {
3661
+ if (!existsSync8(tempFile)) {
3582
3662
  console.error("[screenshot] Screenshot file was not created");
3583
3663
  process.exit(1);
3584
3664
  }
3585
3665
  filePath = tempFile;
3586
3666
  }
3587
- if (!existsSync7(filePath)) {
3667
+ if (!existsSync8(filePath)) {
3588
3668
  console.error(`File not found: ${filePath}`);
3589
3669
  process.exit(1);
3590
3670
  }
@@ -3626,16 +3706,16 @@ var screenshotCommand = new Command17("screenshot").description(
3626
3706
  source: "agent"
3627
3707
  });
3628
3708
  console.log(`\u2713 Screenshot uploaded and attached to task`);
3629
- if (tempFile && existsSync7(tempFile)) {
3709
+ if (tempFile && existsSync8(tempFile)) {
3630
3710
  unlinkSync2(tempFile);
3631
3711
  }
3632
3712
  });
3633
3713
 
3634
3714
  // cli/commands/resume.ts
3635
- import { Command as Command18 } from "commander";
3636
- import { spawn as spawn6 } from "child_process";
3715
+ import { Command as Command17 } from "commander";
3716
+ import { spawn as spawn5 } from "child_process";
3637
3717
  import { resolve as resolve3 } from "path";
3638
- var c7 = {
3718
+ var c6 = {
3639
3719
  reset: "\x1B[0m",
3640
3720
  bold: "\x1B[1m",
3641
3721
  dim: "\x1B[2m",
@@ -3646,15 +3726,15 @@ var c7 = {
3646
3726
  magenta: "\x1B[35m",
3647
3727
  gray: "\x1B[90m"
3648
3728
  };
3649
- function paint7(color, text) {
3650
- return `${c7[color]}${text}${c7.reset}`;
3729
+ function paint6(color, text) {
3730
+ return `${c6[color]}${text}${c6.reset}`;
3651
3731
  }
3652
- var resumeCommand = new Command18("resume").description("Resume an interactive Claude session for a task (non-headless)").argument("<task-id>", "Task ID whose Claude session to resume").option("--dir <directory>", "Override the working directory for the session").action(async (taskId, opts) => {
3732
+ var resumeCommand = new Command17("resume").description("Resume an interactive Claude session for a task (non-headless)").argument("<task-id>", "Task ID whose Claude session to resume").option("--dir <directory>", "Override the working directory for the session").action(async (taskId, opts) => {
3653
3733
  const task = await api.get(`/api/tasks/${taskId}`);
3654
3734
  if (!task.claudeSessionId) {
3655
3735
  console.error(
3656
3736
  `
3657
- ${paint7("red", "\u2717")} No Claude session found for task ${paint7("dim", taskId.slice(0, 8))}`
3737
+ ${paint6("red", "\u2717")} No Claude session found for task ${paint6("dim", taskId.slice(0, 8))}`
3658
3738
  );
3659
3739
  console.error(
3660
3740
  ` The task may not have been dispatched yet.
@@ -3665,16 +3745,16 @@ var resumeCommand = new Command18("resume").description("Resume an interactive C
3665
3745
  if (task.status === "completed" || task.status === "todo") {
3666
3746
  console.error(
3667
3747
  `
3668
- ${paint7("yellow", "\u26A0")} Task ${paint7("dim", taskId.slice(0, 8))} has already completed.`
3748
+ ${paint6("yellow", "\u26A0")} Task ${paint6("dim", taskId.slice(0, 8))} has already completed.`
3669
3749
  );
3670
3750
  console.error(
3671
- ` Session ID: ${paint7("cyan", task.claudeSessionId)}`
3751
+ ` Session ID: ${paint6("cyan", task.claudeSessionId)}`
3672
3752
  );
3673
3753
  console.error(
3674
- ` View the session log in the Mr. Manager web UI, or run:`
3754
+ ` To resume the session, run:`
3675
3755
  );
3676
3756
  console.error(
3677
- ` ${paint7("dim", `claude --resume ${task.claudeSessionId}`)}
3757
+ ` ${paint6("dim", `claude --resume ${task.claudeSessionId}`)}
3678
3758
  `
3679
3759
  );
3680
3760
  process.exit(0);
@@ -3693,16 +3773,16 @@ var resumeCommand = new Command18("resume").description("Resume an interactive C
3693
3773
  const sid = taskId.slice(0, 8);
3694
3774
  console.log([
3695
3775
  ``,
3696
- paint7("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 `) + paint7("bold", "resume"),
3697
- paint7("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D `) + paint7("dim", "\u2500".repeat(44)),
3698
- paint7("magenta", ` \u2569 \u2569\u2569\u255A\u2550 `) + task.title,
3699
- ` ${paint7("gray", sid)}`,
3776
+ paint6("magenta", ` \u2554\u2566\u2557\u2566\u2550\u2557 `) + paint6("bold", "resume"),
3777
+ paint6("magenta", ` \u2551\u2551\u2551\u2560\u2566\u255D `) + paint6("dim", "\u2500".repeat(44)),
3778
+ paint6("magenta", ` \u2569 \u2569\u2569\u255A\u2550 `) + task.title,
3779
+ ` ${paint6("gray", sid)}`,
3700
3780
  ``,
3701
- ` ${paint7("dim", "session")} ${paint7("cyan", task.claudeSessionId)}`,
3702
- ` ${paint7("dim", "cwd")} ${paint7("cyan", cwd)}`,
3781
+ ` ${paint6("dim", "session")} ${paint6("cyan", task.claudeSessionId)}`,
3782
+ ` ${paint6("dim", "cwd")} ${paint6("cyan", cwd)}`,
3703
3783
  ``
3704
3784
  ].join("\n"));
3705
- const child = spawn6("claude", ["--resume", task.claudeSessionId], {
3785
+ const child = spawn5("claude", ["--resume", task.claudeSessionId], {
3706
3786
  cwd,
3707
3787
  stdio: "inherit"
3708
3788
  });
@@ -3711,18 +3791,18 @@ var resumeCommand = new Command18("resume").description("Resume an interactive C
3711
3791
  });
3712
3792
  child.on("error", (err) => {
3713
3793
  console.error(`
3714
- ${paint7("red", "\u2717")} Failed to launch Claude: ${err.message}
3794
+ ${paint6("red", "\u2717")} Failed to launch Claude: ${err.message}
3715
3795
  `);
3716
3796
  process.exit(1);
3717
3797
  });
3718
3798
  });
3719
3799
 
3720
3800
  // cli/commands/browse.ts
3721
- import { Command as Command19 } from "commander";
3722
- import { execSync as execSync5, spawn as spawn7 } from "child_process";
3723
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
3724
- import { join as join9 } from "path";
3725
- var BROWSE_DIR2 = join9(import.meta.dirname, "..", "..", "browse");
3801
+ import { Command as Command18 } from "commander";
3802
+ import { execSync as execSync5, spawn as spawn6 } from "child_process";
3803
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
3804
+ import { join as join8 } from "path";
3805
+ var BROWSE_DIR2 = join8(import.meta.dirname, "..", "..", "browse");
3726
3806
  function isProcessAlive(pid) {
3727
3807
  try {
3728
3808
  process.kill(pid, 0);
@@ -3760,10 +3840,10 @@ async function ensureDevServer() {
3760
3840
  }
3761
3841
  const port = await findAvailablePort2(3e3);
3762
3842
  console.log(`[browse] Starting dev server on port ${port}...`);
3763
- const devProc = spawn7("npm", ["run", "dev", "--", "--port", String(port)], {
3843
+ const devProc = spawn6("npm", ["run", "dev", "--", "--port", String(port)], {
3764
3844
  stdio: ["ignore", "pipe", "pipe"],
3765
3845
  detached: true,
3766
- cwd: join9(import.meta.dirname, "..", ".."),
3846
+ cwd: join8(import.meta.dirname, "..", ".."),
3767
3847
  env: { ...process.env }
3768
3848
  });
3769
3849
  devProc.unref();
@@ -3782,7 +3862,7 @@ async function ensureDevServer() {
3782
3862
  }
3783
3863
  throw new Error("Dev server failed to start within 30s");
3784
3864
  }
3785
- var browseCommand = new Command19("browse").description("Control a headless browser for QA and testing").argument("[command]", "Browse command (goto, click, fill, screenshot, etc.)").argument("[args...]", "Command arguments").option(
3865
+ var browseCommand = new Command18("browse").description("Control a headless browser for QA and testing").argument("[command]", "Browse command (goto, click, fill, screenshot, etc.)").argument("[args...]", "Command arguments").option(
3786
3866
  "--task-id <id>",
3787
3867
  "Attach screenshot to a task update (only for screenshot command)"
3788
3868
  ).option("--dev", "Auto-start local dev server before browsing").allowUnknownOption(true).action(
@@ -3825,7 +3905,7 @@ var browseCommand = new Command19("browse").description("Control a headless brow
3825
3905
  }
3826
3906
  if (command === "screenshot" && opts.taskId) {
3827
3907
  const screenshotPath = stdout.match(/Screenshot saved: (.+)/)?.[1];
3828
- if (!screenshotPath || !existsSync8(screenshotPath)) {
3908
+ if (!screenshotPath || !existsSync9(screenshotPath)) {
3829
3909
  console.error("[browse] Could not find screenshot file");
3830
3910
  process.exit(1);
3831
3911
  }
@@ -3865,12 +3945,12 @@ var browseCommand = new Command19("browse").description("Control a headless brow
3865
3945
  );
3866
3946
 
3867
3947
  // cli/commands/set-path.ts
3868
- import { Command as Command20 } from "commander";
3948
+ import { Command as Command19 } from "commander";
3869
3949
  import { resolve as resolve4 } from "path";
3870
- import { existsSync as existsSync9 } from "fs";
3871
- var setPathCommand = new Command20("set-path").description("Set or update the local repo path for a project").argument("<project-id>", "Project ID").argument("<path>", "Absolute or relative path to the local repo").action(async (projectId, pathArg) => {
3950
+ import { existsSync as existsSync10 } from "fs";
3951
+ var setPathCommand = new Command19("set-path").description("Set or update the local repo path for a project").argument("<project-id>", "Project ID").argument("<path>", "Absolute or relative path to the local repo").action(async (projectId, pathArg) => {
3872
3952
  const absolutePath = resolve4(pathArg);
3873
- if (!existsSync9(absolutePath)) {
3953
+ if (!existsSync10(absolutePath)) {
3874
3954
  console.error(`Error: Path does not exist: ${absolutePath}`);
3875
3955
  process.exit(1);
3876
3956
  }
@@ -3886,9 +3966,9 @@ var setPathCommand = new Command20("set-path").description("Set or update the lo
3886
3966
  });
3887
3967
 
3888
3968
  // cli/commands/test.ts
3889
- import { Command as Command21 } from "commander";
3890
- import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3891
- var testCommand = new Command21("test").description("Run automated browser test for a task's MR/PR").argument("<task-id>", "Task ID to test").option("--plan <file>", "Path to a custom test plan JSON file").action(async (taskId, opts) => {
3969
+ import { Command as Command20 } from "commander";
3970
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
3971
+ var testCommand = new Command20("test").description("Run automated browser test for a task's MR/PR").argument("<task-id>", "Task ID to test").option("--plan <file>", "Path to a custom test plan JSON file").action(async (taskId, opts) => {
3892
3972
  const config = loadConfig();
3893
3973
  console.log("[test] Fetching task...");
3894
3974
  let task;
@@ -3909,7 +3989,7 @@ var testCommand = new Command21("test").description("Run automated browser test
3909
3989
  );
3910
3990
  process.exit(1);
3911
3991
  }
3912
- if (!existsSync10(localPath)) {
3992
+ if (!existsSync11(localPath)) {
3913
3993
  console.error(
3914
3994
  `[test] Local repo path not found at ${localPath}. Run \`mr set-path\` to update.`
3915
3995
  );
@@ -4008,11 +4088,11 @@ var testCommand = new Command21("test").description("Run automated browser test
4008
4088
  });
4009
4089
 
4010
4090
  // cli/commands/features.ts
4011
- import { Command as Command22 } from "commander";
4012
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync11 } from "fs";
4091
+ import { Command as Command21 } from "commander";
4092
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
4013
4093
  import { resolve as resolve5, sep as sep2 } from "path";
4014
4094
  var FEATURES_FILE3 = ".mr-features.md";
4015
- var c8 = {
4095
+ var c7 = {
4016
4096
  reset: "\x1B[0m",
4017
4097
  bold: "\x1B[1m",
4018
4098
  dim: "\x1B[2m",
@@ -4022,8 +4102,8 @@ var c8 = {
4022
4102
  magenta: "\x1B[35m",
4023
4103
  gray: "\x1B[90m"
4024
4104
  };
4025
- function paint8(color, text) {
4026
- return `${c8[color]}${text}${c8.reset}`;
4105
+ function paint7(color, text) {
4106
+ return `${c7[color]}${text}${c7.reset}`;
4027
4107
  }
4028
4108
  function resolveProjectRoot2() {
4029
4109
  const cwd = process.cwd();
@@ -4039,10 +4119,10 @@ function getFeaturesPath() {
4039
4119
  }
4040
4120
  function readFeatures2() {
4041
4121
  const path = getFeaturesPath();
4042
- if (!existsSync11(path)) return null;
4122
+ if (!existsSync12(path)) return null;
4043
4123
  return readFileSync9(path, "utf-8");
4044
4124
  }
4045
- var featuresCommand = new Command22("features").description("View or update the project features & goals document (.mr-features.md)").option("--update <content>", "Replace the features document with the given content").option("--file <path>", "Read content from a file and use it to update the features document").option("--path", "Print the path to the features file").action(async (opts) => {
4125
+ var featuresCommand = new Command21("features").description("View or update the project features & goals document (.mr-features.md)").option("--update <content>", "Replace the features document with the given content").option("--file <path>", "Read content from a file and use it to update the features document").option("--path", "Print the path to the features file").action(async (opts) => {
4046
4126
  if (opts.path) {
4047
4127
  console.log(getFeaturesPath());
4048
4128
  return;
@@ -4051,30 +4131,30 @@ var featuresCommand = new Command22("features").description("View or update the
4051
4131
  const content2 = readFileSync9(resolve5(opts.file), "utf-8");
4052
4132
  const featuresPath = getFeaturesPath();
4053
4133
  writeFileSync5(featuresPath, content2);
4054
- console.log(`${paint8("green", "\u2713")} Updated ${paint8("cyan", featuresPath)} from ${paint8("cyan", opts.file)}`);
4134
+ console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)} from ${paint7("cyan", opts.file)}`);
4055
4135
  return;
4056
4136
  }
4057
4137
  if (opts.update) {
4058
4138
  const featuresPath = getFeaturesPath();
4059
4139
  writeFileSync5(featuresPath, opts.update);
4060
- console.log(`${paint8("green", "\u2713")} Updated ${paint8("cyan", featuresPath)}`);
4140
+ console.log(`${paint7("green", "\u2713")} Updated ${paint7("cyan", featuresPath)}`);
4061
4141
  return;
4062
4142
  }
4063
4143
  const content = readFeatures2();
4064
4144
  if (!content) {
4065
- console.log(paint8("dim", `No features document found. One will be created when an agent completes a task.`));
4066
- console.log(paint8("dim", `Path: ${getFeaturesPath()}`));
4145
+ console.log(paint7("dim", `No features document found. One will be created when an agent completes a task.`));
4146
+ console.log(paint7("dim", `Path: ${getFeaturesPath()}`));
4067
4147
  return;
4068
4148
  }
4069
4149
  console.log(content);
4070
4150
  });
4071
4151
 
4072
4152
  // cli/commands/no-mr.ts
4073
- import { Command as Command23 } from "commander";
4153
+ import { Command as Command22 } from "commander";
4074
4154
  import { writeFileSync as writeFileSync6 } from "fs";
4075
4155
  import { resolve as resolve6 } from "path";
4076
4156
  var NO_MR_FILE = ".mr-no-mr";
4077
- var noMrCommand = new Command23("no-mr").description("Signal that a task does not require a merge/pull request and describe what was done instead").argument("<task-id>", "Task ID").argument("<description>", "Description of what was done instead of creating an MR/PR").action(async (taskId, description) => {
4157
+ var noMrCommand = new Command22("no-mr").description("Signal that a task does not require a merge/pull request and describe what was done instead").argument("<task-id>", "Task ID").argument("<description>", "Description of what was done instead of creating an MR/PR").action(async (taskId, description) => {
4078
4158
  const filePath = resolve6(process.cwd(), NO_MR_FILE);
4079
4159
  writeFileSync6(filePath, description, "utf-8");
4080
4160
  await api.post(`/api/tasks/${taskId}/updates`, {
@@ -4086,8 +4166,8 @@ var noMrCommand = new Command23("no-mr").description("Signal that a task does no
4086
4166
  });
4087
4167
 
4088
4168
  // cli/commands/mobile.ts
4089
- import { Command as Command24 } from "commander";
4090
- function paint9(color, text) {
4169
+ import { Command as Command23 } from "commander";
4170
+ function paint8(color, text) {
4091
4171
  const colors = {
4092
4172
  cyan: "\x1B[36m",
4093
4173
  green: "\x1B[32m",
@@ -4098,7 +4178,7 @@ function paint9(color, text) {
4098
4178
  };
4099
4179
  return `${colors[color] ?? ""}${text}${colors.reset}`;
4100
4180
  }
4101
- var mobileCommand = new Command24("mobile").description(
4181
+ var mobileCommand = new Command23("mobile").description(
4102
4182
  "Start the Web-to-Mobile Conversion Wizard for the linked project"
4103
4183
  ).argument("[project-id]", "Project ID (defaults to linked project)").option("--framework <framework>", "Framework: react-native or native").option("--url <url>", "Web app URL for analysis").action(
4104
4184
  async (projectIdArg, opts) => {
@@ -4110,7 +4190,7 @@ var mobileCommand = new Command24("mobile").description(
4110
4190
  process.exit(1);
4111
4191
  }
4112
4192
  console.log(
4113
- paint9("cyan", "mobile") + paint9("dim", " \u2014 starting conversion wizard")
4193
+ paint8("cyan", "mobile") + paint8("dim", " \u2014 starting conversion wizard")
4114
4194
  );
4115
4195
  const task = await api.post("/api/tasks", {
4116
4196
  title: "Convert to Mobile App",
@@ -4141,13 +4221,13 @@ var mobileCommand = new Command24("mobile").description(
4141
4221
  }
4142
4222
  console.log(
4143
4223
  `
4144
- ${paint9("green", "\u2713")} Wizard initialized. Open the web UI to continue:
4224
+ ${paint8("green", "\u2713")} Wizard initialized. Open the web UI to continue:
4145
4225
  \u2192 Task ID: ${task.id}
4146
4226
  \u2192 Use the "Convert to Mobile" button on the project page`
4147
4227
  );
4148
4228
  }
4149
4229
  );
4150
- var statusSubcommand = new Command24("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
4230
+ var statusSubcommand = new Command23("status").description("Show mobile conversion status for a task").argument("<task-id>", "Parent conversion task ID").action(async (taskId) => {
4151
4231
  const resources = await api.get(
4152
4232
  `/api/tasks/${taskId}/resources`
4153
4233
  );
@@ -4160,7 +4240,7 @@ var statusSubcommand = new Command24("status").description("Show mobile conversi
4160
4240
  }
4161
4241
  try {
4162
4242
  const state = JSON.parse(wizardState.content);
4163
- console.log(paint9("cyan", "Mobile Conversion Status"));
4243
+ console.log(paint8("cyan", "Mobile Conversion Status"));
4164
4244
  console.log(` Phase: ${state.phase}`);
4165
4245
  if (state.framework) {
4166
4246
  console.log(` Framework: ${state.framework}`);
@@ -4183,14 +4263,14 @@ var statusSubcommand = new Command24("status").description("Show mobile conversi
4183
4263
  mobileCommand.addCommand(statusSubcommand);
4184
4264
 
4185
4265
  // cli/commands/scan.ts
4186
- import { Command as Command25 } from "commander";
4266
+ import { Command as Command24 } from "commander";
4187
4267
 
4188
4268
  // lib/scanner/index.ts
4189
- import { spawn as spawn8 } from "child_process";
4269
+ import { spawn as spawn7 } from "child_process";
4190
4270
 
4191
4271
  // lib/scanner/config.ts
4192
- import { readFileSync as readFileSync10, existsSync as existsSync12 } from "fs";
4193
- import { join as join10 } from "path";
4272
+ import { readFileSync as readFileSync10, existsSync as existsSync13 } from "fs";
4273
+ import { join as join9 } from "path";
4194
4274
  var ALL_FINDING_TYPES = [
4195
4275
  "idea",
4196
4276
  "bug",
@@ -4206,8 +4286,8 @@ var DEFAULTS = {
4206
4286
  findingTypes: ALL_FINDING_TYPES
4207
4287
  };
4208
4288
  function loadScanConfig(projectPath) {
4209
- const configPath2 = join10(projectPath, ".mr-scan.json");
4210
- if (!existsSync12(configPath2)) {
4289
+ const configPath2 = join9(projectPath, ".mr-scan.json");
4290
+ if (!existsSync13(configPath2)) {
4211
4291
  return { ...DEFAULTS };
4212
4292
  }
4213
4293
  try {
@@ -4253,13 +4333,13 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
4253
4333
  }
4254
4334
 
4255
4335
  // lib/scanner/codebase-analysis.ts
4256
- import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync13 } from "fs";
4257
- import { join as join11, relative } from "path";
4336
+ import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync14 } from "fs";
4337
+ import { join as join10, relative } from "path";
4258
4338
  import { execSync as execSync6 } from "child_process";
4259
4339
  function resolveDir(projectPath, candidates) {
4260
4340
  for (const candidate of candidates) {
4261
- const dir = join11(projectPath, candidate);
4262
- if (existsSync13(dir)) return dir;
4341
+ const dir = join10(projectPath, candidate);
4342
+ if (existsSync14(dir)) return dir;
4263
4343
  }
4264
4344
  return null;
4265
4345
  }
@@ -4278,10 +4358,10 @@ function discoverRoutes(projectPath) {
4278
4358
  segment = `:${segment.slice(1, -1)}`;
4279
4359
  }
4280
4360
  if (segment.startsWith("(")) {
4281
- walk(join11(dir, entry.name), routePath);
4361
+ walk(join10(dir, entry.name), routePath);
4282
4362
  continue;
4283
4363
  }
4284
- walk(join11(dir, entry.name), `${routePath}/${segment}`);
4364
+ walk(join10(dir, entry.name), `${routePath}/${segment}`);
4285
4365
  }
4286
4366
  if (entry.name === "page.tsx" || entry.name === "page.ts") {
4287
4367
  routes.push(routePath || "/");
@@ -4292,8 +4372,8 @@ function discoverRoutes(projectPath) {
4292
4372
  return routes;
4293
4373
  }
4294
4374
  function extractModels(projectPath) {
4295
- const schemaPath = join11(projectPath, "prisma", "schema.prisma");
4296
- if (existsSync13(schemaPath)) {
4375
+ const schemaPath = join10(projectPath, "prisma", "schema.prisma");
4376
+ if (existsSync14(schemaPath)) {
4297
4377
  const content = readFileSync11(schemaPath, "utf-8");
4298
4378
  const models2 = [];
4299
4379
  const modelRegex = /^model\s+(\w+)\s*\{/gm;
@@ -4306,14 +4386,14 @@ function extractModels(projectPath) {
4306
4386
  const models = [];
4307
4387
  const drizzleDirs = ["src/db", "src/schema", "db", "drizzle"];
4308
4388
  for (const dir of drizzleDirs) {
4309
- const fullDir = join11(projectPath, dir);
4310
- if (!existsSync13(fullDir)) continue;
4389
+ const fullDir = join10(projectPath, dir);
4390
+ if (!existsSync14(fullDir)) continue;
4311
4391
  try {
4312
4392
  const entries = readdirSync2(fullDir, { withFileTypes: true });
4313
4393
  for (const entry of entries) {
4314
4394
  if (!entry.isFile() || !entry.name.endsWith(".ts") && !entry.name.endsWith(".js")) continue;
4315
4395
  try {
4316
- const content = readFileSync11(join11(fullDir, entry.name), "utf-8");
4396
+ const content = readFileSync11(join10(fullDir, entry.name), "utf-8");
4317
4397
  const tableRegex = /(?:pg|mysql|sqlite)Table\(\s*["'](\w+)["']/g;
4318
4398
  let match;
4319
4399
  while ((match = tableRegex.exec(content)) !== null) {
@@ -4336,9 +4416,9 @@ function discoverComponents(projectPath) {
4336
4416
  for (const entry of entries) {
4337
4417
  if (entry.isDirectory()) {
4338
4418
  if (entry.name === "ui") continue;
4339
- walk(join11(dir, entry.name));
4419
+ walk(join10(dir, entry.name));
4340
4420
  } else if (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) {
4341
- components.push(relative(projectPath, join11(dir, entry.name)));
4421
+ components.push(relative(projectPath, join10(dir, entry.name)));
4342
4422
  }
4343
4423
  }
4344
4424
  }
@@ -4348,15 +4428,15 @@ function discoverComponents(projectPath) {
4348
4428
  function extractInternalLinks(projectPath) {
4349
4429
  const links = /* @__PURE__ */ new Set();
4350
4430
  function searchDir(dir) {
4351
- if (!existsSync13(dir)) return;
4431
+ if (!existsSync14(dir)) return;
4352
4432
  const entries = readdirSync2(dir, { withFileTypes: true });
4353
4433
  for (const entry of entries) {
4354
4434
  if (entry.isDirectory()) {
4355
4435
  if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "ui") continue;
4356
- searchDir(join11(dir, entry.name));
4436
+ searchDir(join10(dir, entry.name));
4357
4437
  } else if (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) {
4358
4438
  try {
4359
- const content = readFileSync11(join11(dir, entry.name), "utf-8");
4439
+ const content = readFileSync11(join10(dir, entry.name), "utf-8");
4360
4440
  const hrefRegex = /href=["'`](\/[^"'`]*?)["'`]/g;
4361
4441
  const pushRegex = /router\.push\(["'`](\/[^"'`]*?)["'`]\)/g;
4362
4442
  let match;
@@ -4372,10 +4452,10 @@ function extractInternalLinks(projectPath) {
4372
4452
  }
4373
4453
  }
4374
4454
  for (const candidate of ["app", "src/app"]) {
4375
- searchDir(join11(projectPath, candidate));
4455
+ searchDir(join10(projectPath, candidate));
4376
4456
  }
4377
4457
  for (const candidate of ["components", "src/components"]) {
4378
- searchDir(join11(projectPath, candidate));
4458
+ searchDir(join10(projectPath, candidate));
4379
4459
  }
4380
4460
  return Array.from(links);
4381
4461
  }
@@ -4652,10 +4732,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
4652
4732
  ${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
4653
4733
 
4654
4734
  **Components:**
4655
- ${codebaseAnalysis.components.slice(0, 30).map((c11) => `- ${c11}`).join("\n")}
4735
+ ${codebaseAnalysis.components.slice(0, 30).map((c10) => `- ${c10}`).join("\n")}
4656
4736
 
4657
4737
  **Recent Git Commits:**
4658
- ${codebaseAnalysis.recentCommits.slice(0, 15).map((c11) => `- ${c11}`).join("\n")}
4738
+ ${codebaseAnalysis.recentCommits.slice(0, 15).map((c10) => `- ${c10}`).join("\n")}
4659
4739
 
4660
4740
  **Completed Tasks:**
4661
4741
  ${context.completedTasks.slice(0, 20).map((t) => `- ${t.title}`).join("\n") || "None"}
@@ -4865,7 +4945,7 @@ async function fetchScanContext(opts) {
4865
4945
  }
4866
4946
  function runClaude(prompt2) {
4867
4947
  return new Promise((resolve7, reject) => {
4868
- const child = spawn8("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
4948
+ const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
4869
4949
  stdio: ["ignore", "pipe", "pipe"]
4870
4950
  });
4871
4951
  let output = "";
@@ -4921,7 +5001,7 @@ function parseSynthesisOutput(output) {
4921
5001
  }
4922
5002
 
4923
5003
  // cli/commands/scan.ts
4924
- var c9 = {
5004
+ var c8 = {
4925
5005
  reset: "\x1B[0m",
4926
5006
  bold: "\x1B[1m",
4927
5007
  dim: "\x1B[2m",
@@ -4932,53 +5012,53 @@ var c9 = {
4932
5012
  magenta: "\x1B[35m",
4933
5013
  gray: "\x1B[90m"
4934
5014
  };
4935
- function paint10(color, text) {
4936
- return `${c9[color]}${text}${c9.reset}`;
5015
+ function paint9(color, text) {
5016
+ return `${c8[color]}${text}${c8.reset}`;
4937
5017
  }
4938
- function timestamp3() {
4939
- return paint10("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
5018
+ function timestamp2() {
5019
+ return paint9("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
4940
5020
  }
4941
5021
  function scanTag() {
4942
- return paint10("magenta", "[scan]");
5022
+ return paint9("magenta", "[scan]");
4943
5023
  }
4944
- function log2(msg) {
4945
- console.log(`${timestamp3()} ${scanTag()} ${msg}`);
5024
+ function log(msg) {
5025
+ console.log(`${timestamp2()} ${scanTag()} ${msg}`);
4946
5026
  }
4947
5027
  function logOk(msg) {
4948
- console.log(`${timestamp3()} ${scanTag()} ${paint10("green", "\u2713")} ${msg}`);
5028
+ console.log(`${timestamp2()} ${scanTag()} ${paint9("green", "\u2713")} ${msg}`);
4949
5029
  }
4950
- function logErr2(msg) {
4951
- console.error(`${timestamp3()} ${scanTag()} ${paint10("red", "\u2717")} ${msg}`);
5030
+ function logErr(msg) {
5031
+ console.error(`${timestamp2()} ${scanTag()} ${paint9("red", "\u2717")} ${msg}`);
4952
5032
  }
4953
- var scanCommand = new Command25("scan").description("Run a product scan on the current project \u2014 analyzes codebase, crawls the app, and surfaces findings").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing scan report ID (created by UI trigger)").option("--no-crawl", "Skip live crawl (codebase analysis only)").action(async (opts) => {
5033
+ 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) => {
4954
5034
  const config = loadConfig();
4955
5035
  if (!config.apiKey) {
4956
- logErr2('Not authenticated. Run "mr login" first.');
5036
+ logErr('Not authenticated. Run "mr login" first.');
4957
5037
  process.exit(1);
4958
5038
  }
4959
5039
  const banner = [
4960
5040
  ``,
4961
- paint10("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
4962
- paint10("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
4963
- paint10("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
4964
- paint10("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
4965
- paint10("dim", ` autonomous product scanner`),
5041
+ paint9("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
5042
+ paint9("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
5043
+ paint9("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
5044
+ paint9("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
5045
+ paint9("dim", ` autonomous product scanner`),
4966
5046
  ``
4967
5047
  ].join("\n");
4968
5048
  console.log(banner);
4969
5049
  const projectId = opts.project || getLinkedProjectId();
4970
5050
  if (!projectId) {
4971
- logErr2('No project linked. Run "mr link" or pass --project <id>.');
5051
+ logErr('No project linked. Run "mr link" or pass --project <id>.');
4972
5052
  process.exit(1);
4973
5053
  }
4974
5054
  let project;
4975
5055
  try {
4976
5056
  project = await api.get(`/api/projects/${projectId}`);
4977
5057
  } catch {
4978
- logErr2(`Failed to fetch project ${projectId}`);
5058
+ logErr(`Failed to fetch project ${projectId}`);
4979
5059
  process.exit(1);
4980
5060
  }
4981
- log2(`Scanning project: ${paint10("cyan", project.name)}`);
5061
+ log(`Scanning project: ${paint9("cyan", project.name)}`);
4982
5062
  let projectPath = project.localPath;
4983
5063
  if (!projectPath) {
4984
5064
  for (const [dir, pid] of Object.entries(config.directories)) {
@@ -4994,12 +5074,12 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
4994
5074
  let reportId;
4995
5075
  if (opts.report) {
4996
5076
  reportId = opts.report;
4997
- log2(`Using existing scan report ${paint10("yellow", reportId.slice(0, 8))}`);
5077
+ log(`Using existing scan report ${paint9("yellow", reportId.slice(0, 8))}`);
4998
5078
  } else {
4999
5079
  try {
5000
5080
  const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
5001
5081
  if (scans.length > 0) {
5002
- logErr2("A scan is already in progress for this project. Wait for it to complete.");
5082
+ logErr("A scan is already in progress for this project. Wait for it to complete.");
5003
5083
  process.exit(1);
5004
5084
  }
5005
5085
  } catch {
@@ -5010,9 +5090,9 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
5010
5090
  status: "pending"
5011
5091
  });
5012
5092
  reportId = report.id;
5013
- log2(`Created scan report ${paint10("yellow", reportId.slice(0, 8))}`);
5093
+ log(`Created scan report ${paint9("yellow", reportId.slice(0, 8))}`);
5014
5094
  } catch (err) {
5015
- logErr2(`Failed to create scan report: ${err.message}`);
5095
+ logErr(`Failed to create scan report: ${err.message}`);
5016
5096
  process.exit(1);
5017
5097
  }
5018
5098
  }
@@ -5023,7 +5103,7 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
5023
5103
  try {
5024
5104
  const current = await api.get(`/api/scans/${reportId}`);
5025
5105
  if (current.status === "cancelled") {
5026
- log2(paint10("yellow", "Scan was cancelled \u2014 aborting."));
5106
+ log(paint9("yellow", "Scan was cancelled \u2014 aborting."));
5027
5107
  process.exit(0);
5028
5108
  }
5029
5109
  } catch {
@@ -5037,9 +5117,9 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
5037
5117
  apiUrl: config.apiUrl,
5038
5118
  apiKey: config.apiKey,
5039
5119
  runBrowse: runBrowseCommand2,
5040
- onLog: log2,
5120
+ onLog: log,
5041
5121
  onProgress: (phase, detail) => {
5042
- log2(`${paint10("dim", `[${phase}]`)} ${detail}`);
5122
+ log(`${paint9("dim", `[${phase}]`)} ${detail}`);
5043
5123
  }
5044
5124
  });
5045
5125
  let wasCancelled = false;
@@ -5051,7 +5131,7 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
5051
5131
  } catch {
5052
5132
  }
5053
5133
  if (wasCancelled) {
5054
- log2(paint10("yellow", "Scan was cancelled by user \u2014 discarding results."));
5134
+ log(paint9("yellow", "Scan was cancelled by user \u2014 discarding results."));
5055
5135
  process.exit(0);
5056
5136
  }
5057
5137
  await api.patch(`/api/scans/${reportId}`, {
@@ -5062,37 +5142,37 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
5062
5142
  scanDurationMs: result.scanDurationMs,
5063
5143
  routesCrawled: result.routesCrawled
5064
5144
  });
5065
- logOk(`Scan complete \u2014 ${paint10("cyan", String(result.findings.length))} findings`);
5145
+ logOk(`Scan complete \u2014 ${paint9("cyan", String(result.findings.length))} findings`);
5066
5146
  console.log("");
5067
- console.log(` ${paint10("bold", "Summary:")} ${result.summary}`);
5147
+ console.log(` ${paint9("bold", "Summary:")} ${result.summary}`);
5068
5148
  console.log("");
5069
5149
  const high = result.findings.filter((f) => f.priority === "high");
5070
5150
  const medium = result.findings.filter((f) => f.priority === "medium");
5071
5151
  const low = result.findings.filter((f) => f.priority === "low");
5072
5152
  if (high.length > 0) {
5073
- console.log(` ${paint10("bold", paint10("red", `High Priority (${high.length})`))}`);
5153
+ console.log(` ${paint9("bold", paint9("red", `High Priority (${high.length})`))}`);
5074
5154
  for (const f of high) {
5075
- console.log(` ${paint10("red", "\u25CF")} [${f.type}] ${f.title}`);
5076
- console.log(` ${paint10("dim", f.description.slice(0, 120))}`);
5155
+ console.log(` ${paint9("red", "\u25CF")} [${f.type}] ${f.title}`);
5156
+ console.log(` ${paint9("dim", f.description.slice(0, 120))}`);
5077
5157
  }
5078
5158
  console.log("");
5079
5159
  }
5080
5160
  if (medium.length > 0) {
5081
- console.log(` ${paint10("bold", paint10("yellow", `Medium Priority (${medium.length})`))}`);
5161
+ console.log(` ${paint9("bold", paint9("yellow", `Medium Priority (${medium.length})`))}`);
5082
5162
  for (const f of medium) {
5083
- console.log(` ${paint10("yellow", "\u25CF")} [${f.type}] ${f.title}`);
5163
+ console.log(` ${paint9("yellow", "\u25CF")} [${f.type}] ${f.title}`);
5084
5164
  }
5085
5165
  console.log("");
5086
5166
  }
5087
5167
  if (low.length > 0) {
5088
- console.log(` ${paint10("dim", `Low Priority (${low.length})`)} `);
5168
+ console.log(` ${paint9("dim", `Low Priority (${low.length})`)} `);
5089
5169
  for (const f of low) {
5090
- console.log(` ${paint10("dim", `\u25CB [${f.type}] ${f.title}`)}`);
5170
+ console.log(` ${paint9("dim", `\u25CB [${f.type}] ${f.title}`)}`);
5091
5171
  }
5092
5172
  console.log("");
5093
5173
  }
5094
5174
  } catch (err) {
5095
- logErr2(`Scan failed: ${err.message}`);
5175
+ logErr(`Scan failed: ${err.message}`);
5096
5176
  try {
5097
5177
  await api.patch(`/api/scans/${reportId}`, {
5098
5178
  status: "failed",
@@ -5105,8 +5185,8 @@ var scanCommand = new Command25("scan").description("Run a product scan on the c
5105
5185
  });
5106
5186
 
5107
5187
  // cli/commands/idea.ts
5108
- import { Command as Command26 } from "commander";
5109
- var c10 = {
5188
+ import { Command as Command25 } from "commander";
5189
+ var c9 = {
5110
5190
  reset: "\x1B[0m",
5111
5191
  bold: "\x1B[1m",
5112
5192
  dim: "\x1B[2m",
@@ -5118,27 +5198,27 @@ var c10 = {
5118
5198
  gray: "\x1B[90m",
5119
5199
  magenta: "\x1B[35m"
5120
5200
  };
5121
- function paint11(color, text) {
5122
- return `${c10[color]}${text}${c10.reset}`;
5201
+ function paint10(color, text) {
5202
+ return `${c9[color]}${text}${c9.reset}`;
5123
5203
  }
5124
5204
  function statusBadge2(status) {
5125
5205
  switch (status) {
5126
5206
  case "draft":
5127
- return paint11("gray", "\u25CB draft");
5207
+ return paint10("gray", "\u25CB draft");
5128
5208
  case "generating":
5129
- return paint11("cyan", "\u27F3 generating");
5209
+ return paint10("cyan", "\u27F3 generating");
5130
5210
  case "generated":
5131
- return paint11("green", "\u2713 generated");
5211
+ return paint10("green", "\u2713 generated");
5132
5212
  case "promoted":
5133
- return paint11("magenta", "\u2191 promoted");
5213
+ return paint10("magenta", "\u2191 promoted");
5134
5214
  case "archived":
5135
- return paint11("dim", "\u2298 archived");
5215
+ return paint10("dim", "\u2298 archived");
5136
5216
  default:
5137
- return paint11("gray", status);
5217
+ return paint10("gray", status);
5138
5218
  }
5139
5219
  }
5140
- var ideaCommand = new Command26("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
5141
- new Command26("list").description("List ideas for the linked project").option("--all", "Show ideas for all projects").option("--status <status>", "Filter by status").action(async (opts) => {
5220
+ var ideaCommand = new Command25("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
5221
+ new Command25("list").description("List ideas for the linked project").option("--all", "Show ideas for all projects").option("--status <status>", "Filter by status").action(async (opts) => {
5142
5222
  const params = new URLSearchParams();
5143
5223
  if (!opts.all) {
5144
5224
  const projectId = getLinkedProjectId();
@@ -5149,23 +5229,23 @@ var ideaCommand = new Command26("idea").description("Manage ideas \u2014 brainst
5149
5229
  if (opts.status) params.set("status", opts.status);
5150
5230
  const ideas = await api.get(`/api/ideas?${params.toString()}`);
5151
5231
  if (ideas.length === 0) {
5152
- console.log(paint11("gray", "No ideas found."));
5232
+ console.log(paint10("gray", "No ideas found."));
5153
5233
  return;
5154
5234
  }
5155
5235
  console.log();
5156
5236
  for (const idea of ideas) {
5157
5237
  const date = new Date(idea.createdAt).toLocaleDateString();
5158
5238
  console.log(
5159
- ` ${paint11("bold", idea.title)} ${statusBadge2(idea.status)} ${paint11("gray", idea.id.slice(0, 8))} ${paint11("dim", date)}`
5239
+ ` ${paint10("bold", idea.title)} ${statusBadge2(idea.status)} ${paint10("gray", idea.id.slice(0, 8))} ${paint10("dim", date)}`
5160
5240
  );
5161
5241
  if (idea.description) {
5162
- console.log(` ${paint11("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
5242
+ console.log(` ${paint10("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
5163
5243
  }
5164
5244
  console.log();
5165
5245
  }
5166
5246
  })
5167
5247
  ).addCommand(
5168
- new Command26("create").description("Create a new idea").argument("<title>", "Title of the idea").option("--description <desc>", "Description of the idea").option("--project <projectId>", "Project ID (defaults to linked project)").option("--generate", "Immediately start generating plan & prototype").action(async (title, opts) => {
5248
+ new Command25("create").description("Create a new idea").argument("<title>", "Title of the idea").option("--description <desc>", "Description of the idea").option("--project <projectId>", "Project ID (defaults to linked project)").option("--generate", "Immediately start generating plan & prototype").action(async (title, opts) => {
5169
5249
  const projectId = opts.project ?? getLinkedProjectId() ?? null;
5170
5250
  const idea = await api.post("/api/ideas", {
5171
5251
  title,
@@ -5173,65 +5253,65 @@ var ideaCommand = new Command26("idea").description("Manage ideas \u2014 brainst
5173
5253
  projectId
5174
5254
  });
5175
5255
  console.log();
5176
- console.log(` ${paint11("green", "\u2713")} Created idea: ${paint11("bold", idea.title)}`);
5177
- console.log(` ${paint11("gray", "ID:")} ${idea.id}`);
5256
+ console.log(` ${paint10("green", "\u2713")} Created idea: ${paint10("bold", idea.title)}`);
5257
+ console.log(` ${paint10("gray", "ID:")} ${idea.id}`);
5178
5258
  if (opts.generate) {
5179
5259
  await api.post(`/api/ideas/${idea.id}/generate`);
5180
- console.log(` ${paint11("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
5260
+ console.log(` ${paint10("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
5181
5261
  }
5182
5262
  console.log();
5183
5263
  })
5184
5264
  ).addCommand(
5185
- new Command26("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
5265
+ new Command25("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
5186
5266
  const idea = await api.post(`/api/ideas/${id}/generate`);
5187
5267
  console.log();
5188
- console.log(` ${paint11("cyan", "\u27F3")} Generating: ${paint11("bold", idea.title)}`);
5189
- console.log(` ${paint11("gray", "The watch agent will pick this up shortly.")}`);
5268
+ console.log(` ${paint10("cyan", "\u27F3")} Generating: ${paint10("bold", idea.title)}`);
5269
+ console.log(` ${paint10("gray", "The watch agent will pick this up shortly.")}`);
5190
5270
  console.log();
5191
5271
  })
5192
5272
  ).addCommand(
5193
- new Command26("feedback").description("Send feedback to iterate on an idea's generated content").argument("<id>", "Idea ID").argument("<feedback>", "Feedback text").action(async (id, feedback) => {
5273
+ new Command25("feedback").description("Send feedback to iterate on an idea's generated content").argument("<id>", "Idea ID").argument("<feedback>", "Feedback text").action(async (id, feedback) => {
5194
5274
  const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
5195
5275
  console.log();
5196
- console.log(` ${paint11("cyan", "\u27F3")} Feedback sent for: ${paint11("bold", idea.title)}`);
5197
- console.log(` ${paint11("gray", "The watch agent will re-generate with your feedback.")}`);
5276
+ console.log(` ${paint10("cyan", "\u27F3")} Feedback sent for: ${paint10("bold", idea.title)}`);
5277
+ console.log(` ${paint10("gray", "The watch agent will re-generate with your feedback.")}`);
5198
5278
  console.log();
5199
5279
  })
5200
5280
  ).addCommand(
5201
- new Command26("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
5281
+ new Command25("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
5202
5282
  const result = await api.post(`/api/ideas/${id}/promote`);
5203
5283
  console.log();
5204
- console.log(` ${paint11("green", "\u2713")} Promoted idea to task: ${paint11("bold", result.task.title)}`);
5205
- console.log(` ${paint11("gray", "Task ID:")} ${result.task.id}`);
5284
+ console.log(` ${paint10("green", "\u2713")} Promoted idea to task: ${paint10("bold", result.task.title)}`);
5285
+ console.log(` ${paint10("gray", "Task ID:")} ${result.task.id}`);
5206
5286
  console.log();
5207
5287
  })
5208
5288
  ).addCommand(
5209
- new Command26("spin-up").description("Spin up a new project with a GitHub repo from an idea").argument("<id>", "Idea ID").option("--name <name>", "Custom project name (defaults to idea title)").action(async (id, opts) => {
5289
+ new Command25("spin-up").description("Spin up a new project with a GitHub repo from an idea").argument("<id>", "Idea ID").option("--name <name>", "Custom project name (defaults to idea title)").action(async (id, opts) => {
5210
5290
  const body = {};
5211
5291
  if (opts.name) body.name = opts.name;
5212
5292
  const result = await api.post(`/api/ideas/${id}/spin-up`, body);
5213
5293
  console.log();
5214
- console.log(` ${paint11("green", "\u2713")} Spinning up project: ${paint11("bold", result.project.name)}`);
5215
- console.log(` ${paint11("gray", "Project ID:")} ${result.project.id}`);
5294
+ console.log(` ${paint10("green", "\u2713")} Spinning up project: ${paint10("bold", result.project.name)}`);
5295
+ console.log(` ${paint10("gray", "Project ID:")} ${result.project.id}`);
5216
5296
  if (result.tasks && result.tasks.length > 0) {
5217
- console.log(` ${paint11("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
5297
+ console.log(` ${paint10("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
5218
5298
  for (const task of result.tasks) {
5219
- console.log(` ${paint11("gray", "\u2022")} ${task.title}`);
5299
+ console.log(` ${paint10("gray", "\u2022")} ${task.title}`);
5220
5300
  }
5221
5301
  }
5222
- console.log(` ${paint11("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
5302
+ console.log(` ${paint10("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
5223
5303
  console.log();
5224
5304
  })
5225
5305
  );
5226
5306
 
5227
5307
  // cli/commands/doctor.ts
5228
- import { Command as Command27 } from "commander";
5229
- import { existsSync as existsSync14 } from "fs";
5230
- import { homedir as homedir3 } from "os";
5231
- import { join as join12 } from "path";
5308
+ import { Command as Command26 } from "commander";
5309
+ import { existsSync as existsSync15 } from "fs";
5310
+ import { homedir as homedir2 } from "os";
5311
+ import { join as join11 } from "path";
5232
5312
  async function checkConfigExists() {
5233
- const configPath2 = join12(homedir3(), ".mr-manager", "config.json");
5234
- const exists = existsSync14(configPath2);
5313
+ const configPath2 = join11(homedir2(), ".mr-manager", "config.json");
5314
+ const exists = existsSync15(configPath2);
5235
5315
  if (!exists) {
5236
5316
  return {
5237
5317
  name: "Config file",
@@ -5273,12 +5353,12 @@ async function checkProjectLink() {
5273
5353
  optional: true
5274
5354
  };
5275
5355
  }
5276
- var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
5356
+ var doctorCommand = new Command26("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
5277
5357
  const banner = [
5278
5358
  ``,
5279
- paint6("cyan", ` MR DOCTOR`),
5280
- paint6("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`),
5281
- paint6("dim", ` diagnosing your mr environment`),
5359
+ paint5("cyan", ` MR DOCTOR`),
5360
+ paint5("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`),
5361
+ paint5("dim", ` diagnosing your mr environment`),
5282
5362
  ``
5283
5363
  ].join("\n");
5284
5364
  console.log(banner);
@@ -5300,15 +5380,15 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
5300
5380
  const allOk = printResults(checks);
5301
5381
  console.log("");
5302
5382
  if (allOk) {
5303
- console.log(paint6("green", " Everything looks good!"));
5383
+ console.log(paint5("green", " Everything looks good!"));
5304
5384
  console.log("");
5305
5385
  return;
5306
5386
  }
5307
- const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
5387
+ const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
5308
5388
  if (fixes.length > 0) {
5309
- console.log(paint6("yellow", " To fix:"));
5389
+ console.log(paint5("yellow", " To fix:"));
5310
5390
  for (const fix of fixes) {
5311
- console.log(` ${paint6("dim", "\u2192")} ${paint6("bold", fix.name)}: ${fix.fix}`);
5391
+ console.log(` ${paint5("dim", "\u2192")} ${paint5("bold", fix.name)}: ${fix.fix}`);
5312
5392
  }
5313
5393
  console.log("");
5314
5394
  }
@@ -5316,13 +5396,13 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
5316
5396
  });
5317
5397
 
5318
5398
  // cli/index.ts
5319
- var configPath = join13(homedir4(), ".mr-manager", "config.json");
5320
- var isFirstRun = !existsSync15(configPath);
5399
+ var configPath = join12(homedir3(), ".mr-manager", "config.json");
5400
+ var isFirstRun = !existsSync16(configPath);
5321
5401
  var userArgs = process.argv.slice(2);
5322
5402
  var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
5323
5403
  var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
5324
5404
  if (isFirstRun && !shouldBypass) {
5325
- const c11 = {
5405
+ const c10 = {
5326
5406
  reset: "\x1B[0m",
5327
5407
  bold: "\x1B[1m",
5328
5408
  dim: "\x1B[2m",
@@ -5332,29 +5412,29 @@ if (isFirstRun && !shouldBypass) {
5332
5412
  magenta: "\x1B[35m"
5333
5413
  };
5334
5414
  console.log("");
5335
- 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}`);
5336
- 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}`);
5337
- 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}`);
5338
- 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}`);
5415
+ console.log(`${c10.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${c10.reset}`);
5416
+ console.log(`${c10.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${c10.reset}`);
5417
+ console.log(`${c10.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c10.reset}`);
5418
+ console.log(`${c10.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${c10.reset}`);
5339
5419
  console.log("");
5340
- console.log(`${c11.bold} Welcome to Mr. Manager!${c11.reset}`);
5341
- console.log(`${c11.dim} Let's get you set up in a few quick steps.${c11.reset}`);
5420
+ console.log(`${c10.bold} Welcome to Mr. Manager!${c10.reset}`);
5421
+ console.log(`${c10.dim} Let's get you set up in a few quick steps.${c10.reset}`);
5342
5422
  console.log("");
5343
- console.log(` ${c11.yellow}Step 1:${c11.reset} Authenticate via Google OAuth`);
5344
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr login${c11.reset}`);
5423
+ console.log(` ${c10.yellow}Step 1:${c10.reset} Authenticate via Google OAuth`);
5424
+ console.log(` ${c10.dim}Run:${c10.reset} ${c10.cyan}mr login${c10.reset}`);
5345
5425
  console.log("");
5346
- console.log(` ${c11.yellow}Step 2:${c11.reset} Verify your environment`);
5347
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr setup${c11.reset}`);
5426
+ console.log(` ${c10.yellow}Step 2:${c10.reset} Verify your environment`);
5427
+ console.log(` ${c10.dim}Run:${c10.reset} ${c10.cyan}mr setup${c10.reset}`);
5348
5428
  console.log("");
5349
- console.log(` ${c11.yellow}Step 3:${c11.reset} Link a repo and start watching`);
5350
- console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr link${c11.reset} ${c11.dim}&&${c11.reset} ${c11.cyan}mr watch${c11.reset}`);
5429
+ console.log(` ${c10.yellow}Step 3:${c10.reset} Link a repo and start watching`);
5430
+ console.log(` ${c10.dim}Run:${c10.reset} ${c10.cyan}mr link${c10.reset} ${c10.dim}&&${c10.reset} ${c10.cyan}mr watch${c10.reset}`);
5351
5431
  console.log("");
5352
- console.log(`${c11.dim} Or run ${c11.reset}${c11.cyan}mr login${c11.reset}${c11.dim} to get started now.${c11.reset}`);
5432
+ console.log(`${c10.dim} Or run ${c10.reset}${c10.cyan}mr login${c10.reset}${c10.dim} to get started now.${c10.reset}`);
5353
5433
  console.log("");
5354
5434
  process.exit(0);
5355
5435
  }
5356
- var program = new Command28();
5357
- program.name("mr").description("Mr. Manager - Task and project management CLI").version("0.2.0");
5436
+ var program = new Command27();
5437
+ program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
5358
5438
  program.addCommand(initCommand);
5359
5439
  program.addCommand(authCommand);
5360
5440
  program.addCommand(loginCommand);
@@ -5370,7 +5450,6 @@ program.addCommand(undelegateCommand);
5370
5450
  program.addCommand(createCommand);
5371
5451
  program.addCommand(completeCommand);
5372
5452
  program.addCommand(subtaskCompleteCommand);
5373
- program.addCommand(upCommand);
5374
5453
  program.addCommand(prototypeCommand);
5375
5454
  program.addCommand(setupCommand);
5376
5455
  program.addCommand(updateCommand);