@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.
- package/dist/index.mjs +660 -581
- 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
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { homedir as
|
|
7
|
-
import { join as
|
|
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/
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
442
|
-
import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as
|
|
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
|
|
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
|
|
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 (
|
|
517
|
+
if (existsSync4(wtPath)) {
|
|
458
518
|
execSync2(`git checkout ${branch}`, { cwd: wtPath, stdio: "pipe" });
|
|
459
519
|
return wtPath;
|
|
460
520
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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 (
|
|
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 (
|
|
585
|
+
if (existsSync4(join4(wtPath, "bun.lock")) || existsSync4(join4(wtPath, "bun.lockb"))) {
|
|
529
586
|
execSync2("bun install", { cwd: wtPath, stdio: "pipe" });
|
|
530
|
-
} else if (
|
|
587
|
+
} else if (existsSync4(join4(wtPath, "pnpm-lock.yaml"))) {
|
|
531
588
|
execSync2("pnpm install --frozen-lockfile", { cwd: wtPath, stdio: "pipe" });
|
|
532
|
-
} else if (
|
|
589
|
+
} else if (existsSync4(join4(wtPath, "package-lock.json"))) {
|
|
533
590
|
execSync2("npm ci", { cwd: wtPath, stdio: "pipe" });
|
|
534
|
-
} else if (
|
|
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 (
|
|
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 (
|
|
609
|
+
if (existsSync5(join5(wtPath, "bun.lock")) || existsSync5(join5(wtPath, "bun.lockb"))) {
|
|
553
610
|
return { cmd: "bun", args: ["dev"] };
|
|
554
611
|
}
|
|
555
|
-
if (
|
|
612
|
+
if (existsSync5(join5(wtPath, "pnpm-lock.yaml"))) {
|
|
556
613
|
return { cmd: "pnpm", args: ["dev"] };
|
|
557
614
|
}
|
|
558
|
-
if (
|
|
615
|
+
if (existsSync5(join5(wtPath, "package-lock.json"))) {
|
|
559
616
|
return { cmd: "npm", args: ["run", "dev"] };
|
|
560
617
|
}
|
|
561
|
-
if (
|
|
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
|
|
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
|
-
|
|
769
|
+
log2("Test timed out after 5 minutes");
|
|
713
770
|
if (devProc) devProc.kill("SIGTERM");
|
|
714
771
|
}, 5 * 60 * 1e3);
|
|
715
772
|
try {
|
|
716
|
-
|
|
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
|
-
|
|
722
|
-
|
|
778
|
+
log2(`Branch: ${branch}`);
|
|
779
|
+
log2("Creating git worktree...");
|
|
723
780
|
wtPath = createWorktree(localPath, branch, worktreeName);
|
|
724
|
-
|
|
725
|
-
|
|
781
|
+
log2(`Worktree created at ${wtPath}`);
|
|
782
|
+
log2("Installing dependencies...");
|
|
726
783
|
try {
|
|
727
784
|
installDependencies(wtPath);
|
|
728
785
|
} catch (err) {
|
|
729
|
-
|
|
786
|
+
log2(`Warning: dependency install failed: ${err.message}`);
|
|
730
787
|
}
|
|
731
|
-
|
|
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
|
-
|
|
792
|
+
log2(`Dev server running on ${baseUrl}`);
|
|
736
793
|
await browseRunner(["goto", baseUrl]);
|
|
737
794
|
const plan = customPlan || buildDefaultTestPlan(baseUrl);
|
|
738
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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 =
|
|
892
|
-
if (!
|
|
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 &&
|
|
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
|
|
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) =>
|
|
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
|
|
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: ${
|
|
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
|
-
...
|
|
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.
|
|
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 = [
|
|
1912
|
+
const args = [];
|
|
1810
1913
|
if (mode === "execute") {
|
|
1811
|
-
args.push("
|
|
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 =
|
|
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 ?
|
|
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
|
|
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
|
|
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,
|
|
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 =
|
|
2000
|
-
if (
|
|
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
|
-
|
|
2023
|
-
|
|
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
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
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 =
|
|
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}),
|
|
2188
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
2323
|
-
const tasksPath =
|
|
2324
|
-
const protoPath =
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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/
|
|
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
|
-
|
|
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
|
|
3177
|
+
return paint4("gray", "\u25CB pending");
|
|
3098
3178
|
case "in_progress":
|
|
3099
|
-
return
|
|
3179
|
+
return paint4("cyan", "\u27F3 in_progress");
|
|
3100
3180
|
case "completed":
|
|
3101
|
-
return
|
|
3181
|
+
return paint4("green", "\u2713 completed");
|
|
3102
3182
|
case "failed":
|
|
3103
|
-
return
|
|
3183
|
+
return paint4("red", "\u2717 failed");
|
|
3104
3184
|
default:
|
|
3105
|
-
return
|
|
3185
|
+
return paint4("gray", status);
|
|
3106
3186
|
}
|
|
3107
3187
|
}
|
|
3108
|
-
var prototypeCommand = new
|
|
3109
|
-
new
|
|
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(
|
|
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
|
-
` ${
|
|
3208
|
+
` ${paint4("bold", p.title)} ${statusBadge(p.status)} ${paint4("gray", p.id.slice(0, 8))} ${paint4("dim", date)}`
|
|
3129
3209
|
);
|
|
3130
|
-
console.log(` ${
|
|
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
|
|
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(` ${
|
|
3150
|
-
console.log(` ${
|
|
3151
|
-
console.log(` ${
|
|
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
|
|
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(` ${
|
|
3161
|
-
console.log(` ${
|
|
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
|
|
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(` ${
|
|
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
|
|
3257
|
+
import { Command as Command14 } from "commander";
|
|
3178
3258
|
import { exec as exec2 } from "child_process";
|
|
3179
|
-
var
|
|
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
|
|
3191
|
-
return `${
|
|
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((
|
|
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 ?
|
|
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 ?
|
|
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:
|
|
3449
|
-
const ghInstalled = checks.find((
|
|
3450
|
-
const ghAuthed = checks.find((
|
|
3451
|
-
const mrAuthed = checks.find((
|
|
3452
|
-
const claudeCheck = checks.find((
|
|
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(
|
|
3455
|
-
console.log(
|
|
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 =
|
|
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(
|
|
3543
|
+
console.log(paint5("cyan", " Running gh auth login..."));
|
|
3464
3544
|
await new Promise((resolve7) => {
|
|
3465
|
-
const child =
|
|
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(
|
|
3551
|
+
console.log(paint5("cyan", " Running mr login..."));
|
|
3472
3552
|
const entry = process.argv[1];
|
|
3473
3553
|
await new Promise((resolve7) => {
|
|
3474
|
-
const child =
|
|
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
|
|
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
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
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(
|
|
3588
|
+
console.log(paint5("green", " All checks passed! You're ready to run mr watch."));
|
|
3509
3589
|
if (agent === "claude") {
|
|
3510
|
-
console.log(
|
|
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((
|
|
3595
|
+
const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
|
|
3516
3596
|
if (fixes.length > 0) {
|
|
3517
|
-
console.log(
|
|
3597
|
+
console.log(paint5("yellow", " To fix:"));
|
|
3518
3598
|
for (const fix of fixes) {
|
|
3519
|
-
console.log(` ${
|
|
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
|
|
3531
|
-
var updateCommand = new
|
|
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
|
|
3541
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3542
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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 &&
|
|
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
|
|
3636
|
-
import { spawn as
|
|
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
|
|
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
|
|
3650
|
-
return `${
|
|
3729
|
+
function paint6(color, text) {
|
|
3730
|
+
return `${c6[color]}${text}${c6.reset}`;
|
|
3651
3731
|
}
|
|
3652
|
-
var resumeCommand = new
|
|
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
|
-
${
|
|
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
|
-
${
|
|
3748
|
+
${paint6("yellow", "\u26A0")} Task ${paint6("dim", taskId.slice(0, 8))} has already completed.`
|
|
3669
3749
|
);
|
|
3670
3750
|
console.error(
|
|
3671
|
-
` Session ID: ${
|
|
3751
|
+
` Session ID: ${paint6("cyan", task.claudeSessionId)}`
|
|
3672
3752
|
);
|
|
3673
3753
|
console.error(
|
|
3674
|
-
`
|
|
3754
|
+
` To resume the session, run:`
|
|
3675
3755
|
);
|
|
3676
3756
|
console.error(
|
|
3677
|
-
` ${
|
|
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
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
` ${
|
|
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
|
-
` ${
|
|
3702
|
-
` ${
|
|
3781
|
+
` ${paint6("dim", "session")} ${paint6("cyan", task.claudeSessionId)}`,
|
|
3782
|
+
` ${paint6("dim", "cwd")} ${paint6("cyan", cwd)}`,
|
|
3703
3783
|
``
|
|
3704
3784
|
].join("\n"));
|
|
3705
|
-
const child =
|
|
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
|
-
${
|
|
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
|
|
3722
|
-
import { execSync as execSync5, spawn as
|
|
3723
|
-
import { existsSync as
|
|
3724
|
-
import { join as
|
|
3725
|
-
var BROWSE_DIR2 =
|
|
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 =
|
|
3843
|
+
const devProc = spawn6("npm", ["run", "dev", "--", "--port", String(port)], {
|
|
3764
3844
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3765
3845
|
detached: true,
|
|
3766
|
-
cwd:
|
|
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
|
|
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 || !
|
|
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
|
|
3948
|
+
import { Command as Command19 } from "commander";
|
|
3869
3949
|
import { resolve as resolve4 } from "path";
|
|
3870
|
-
import { existsSync as
|
|
3871
|
-
var setPathCommand = new
|
|
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 (!
|
|
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
|
|
3890
|
-
import { readFileSync as readFileSync8, existsSync as
|
|
3891
|
-
var testCommand = new
|
|
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 (!
|
|
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
|
|
4012
|
-
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as
|
|
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
|
|
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
|
|
4026
|
-
return `${
|
|
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 (!
|
|
4122
|
+
if (!existsSync12(path)) return null;
|
|
4043
4123
|
return readFileSync9(path, "utf-8");
|
|
4044
4124
|
}
|
|
4045
|
-
var featuresCommand = new
|
|
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(`${
|
|
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(`${
|
|
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(
|
|
4066
|
-
console.log(
|
|
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
|
|
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
|
|
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
|
|
4090
|
-
function
|
|
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
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
|
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(
|
|
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
|
|
4266
|
+
import { Command as Command24 } from "commander";
|
|
4187
4267
|
|
|
4188
4268
|
// lib/scanner/index.ts
|
|
4189
|
-
import { spawn as
|
|
4269
|
+
import { spawn as spawn7 } from "child_process";
|
|
4190
4270
|
|
|
4191
4271
|
// lib/scanner/config.ts
|
|
4192
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
4193
|
-
import { join as
|
|
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 =
|
|
4210
|
-
if (!
|
|
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
|
|
4257
|
-
import { join as
|
|
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 =
|
|
4262
|
-
if (
|
|
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(
|
|
4361
|
+
walk(join10(dir, entry.name), routePath);
|
|
4282
4362
|
continue;
|
|
4283
4363
|
}
|
|
4284
|
-
walk(
|
|
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 =
|
|
4296
|
-
if (
|
|
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 =
|
|
4310
|
-
if (!
|
|
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(
|
|
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(
|
|
4419
|
+
walk(join10(dir, entry.name));
|
|
4340
4420
|
} else if (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts")) {
|
|
4341
|
-
components.push(relative(projectPath,
|
|
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 (!
|
|
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(
|
|
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(
|
|
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(
|
|
4455
|
+
searchDir(join10(projectPath, candidate));
|
|
4376
4456
|
}
|
|
4377
4457
|
for (const candidate of ["components", "src/components"]) {
|
|
4378
|
-
searchDir(
|
|
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((
|
|
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((
|
|
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 =
|
|
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
|
|
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
|
|
4936
|
-
return `${
|
|
5015
|
+
function paint9(color, text) {
|
|
5016
|
+
return `${c8[color]}${text}${c8.reset}`;
|
|
4937
5017
|
}
|
|
4938
|
-
function
|
|
4939
|
-
return
|
|
5018
|
+
function timestamp2() {
|
|
5019
|
+
return paint9("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
4940
5020
|
}
|
|
4941
5021
|
function scanTag() {
|
|
4942
|
-
return
|
|
5022
|
+
return paint9("magenta", "[scan]");
|
|
4943
5023
|
}
|
|
4944
|
-
function
|
|
4945
|
-
console.log(`${
|
|
5024
|
+
function log(msg) {
|
|
5025
|
+
console.log(`${timestamp2()} ${scanTag()} ${msg}`);
|
|
4946
5026
|
}
|
|
4947
5027
|
function logOk(msg) {
|
|
4948
|
-
console.log(`${
|
|
5028
|
+
console.log(`${timestamp2()} ${scanTag()} ${paint9("green", "\u2713")} ${msg}`);
|
|
4949
5029
|
}
|
|
4950
|
-
function
|
|
4951
|
-
console.error(`${
|
|
5030
|
+
function logErr(msg) {
|
|
5031
|
+
console.error(`${timestamp2()} ${scanTag()} ${paint9("red", "\u2717")} ${msg}`);
|
|
4952
5032
|
}
|
|
4953
|
-
var scanCommand = new
|
|
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
|
-
|
|
5036
|
+
logErr('Not authenticated. Run "mr login" first.');
|
|
4957
5037
|
process.exit(1);
|
|
4958
5038
|
}
|
|
4959
5039
|
const banner = [
|
|
4960
5040
|
``,
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5058
|
+
logErr(`Failed to fetch project ${projectId}`);
|
|
4979
5059
|
process.exit(1);
|
|
4980
5060
|
}
|
|
4981
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5093
|
+
log(`Created scan report ${paint9("yellow", reportId.slice(0, 8))}`);
|
|
5014
5094
|
} catch (err) {
|
|
5015
|
-
|
|
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
|
-
|
|
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:
|
|
5120
|
+
onLog: log,
|
|
5041
5121
|
onProgress: (phase, detail) => {
|
|
5042
|
-
|
|
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
|
-
|
|
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 ${
|
|
5145
|
+
logOk(`Scan complete \u2014 ${paint9("cyan", String(result.findings.length))} findings`);
|
|
5066
5146
|
console.log("");
|
|
5067
|
-
console.log(` ${
|
|
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(` ${
|
|
5153
|
+
console.log(` ${paint9("bold", paint9("red", `High Priority (${high.length})`))}`);
|
|
5074
5154
|
for (const f of high) {
|
|
5075
|
-
console.log(` ${
|
|
5076
|
-
console.log(` ${
|
|
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(` ${
|
|
5161
|
+
console.log(` ${paint9("bold", paint9("yellow", `Medium Priority (${medium.length})`))}`);
|
|
5082
5162
|
for (const f of medium) {
|
|
5083
|
-
console.log(` ${
|
|
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(` ${
|
|
5168
|
+
console.log(` ${paint9("dim", `Low Priority (${low.length})`)} `);
|
|
5089
5169
|
for (const f of low) {
|
|
5090
|
-
console.log(` ${
|
|
5170
|
+
console.log(` ${paint9("dim", `\u25CB [${f.type}] ${f.title}`)}`);
|
|
5091
5171
|
}
|
|
5092
5172
|
console.log("");
|
|
5093
5173
|
}
|
|
5094
5174
|
} catch (err) {
|
|
5095
|
-
|
|
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
|
|
5109
|
-
var
|
|
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
|
|
5122
|
-
return `${
|
|
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
|
|
5207
|
+
return paint10("gray", "\u25CB draft");
|
|
5128
5208
|
case "generating":
|
|
5129
|
-
return
|
|
5209
|
+
return paint10("cyan", "\u27F3 generating");
|
|
5130
5210
|
case "generated":
|
|
5131
|
-
return
|
|
5211
|
+
return paint10("green", "\u2713 generated");
|
|
5132
5212
|
case "promoted":
|
|
5133
|
-
return
|
|
5213
|
+
return paint10("magenta", "\u2191 promoted");
|
|
5134
5214
|
case "archived":
|
|
5135
|
-
return
|
|
5215
|
+
return paint10("dim", "\u2298 archived");
|
|
5136
5216
|
default:
|
|
5137
|
-
return
|
|
5217
|
+
return paint10("gray", status);
|
|
5138
5218
|
}
|
|
5139
5219
|
}
|
|
5140
|
-
var ideaCommand = new
|
|
5141
|
-
new
|
|
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(
|
|
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
|
-
` ${
|
|
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(` ${
|
|
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
|
|
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(` ${
|
|
5177
|
-
console.log(` ${
|
|
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(` ${
|
|
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
|
|
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(` ${
|
|
5189
|
-
console.log(` ${
|
|
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
|
|
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(` ${
|
|
5197
|
-
console.log(` ${
|
|
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
|
|
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(` ${
|
|
5205
|
-
console.log(` ${
|
|
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
|
|
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(` ${
|
|
5215
|
-
console.log(` ${
|
|
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(` ${
|
|
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(` ${
|
|
5299
|
+
console.log(` ${paint10("gray", "\u2022")} ${task.title}`);
|
|
5220
5300
|
}
|
|
5221
5301
|
}
|
|
5222
|
-
console.log(` ${
|
|
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
|
|
5229
|
-
import { existsSync as
|
|
5230
|
-
import { homedir as
|
|
5231
|
-
import { join as
|
|
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 =
|
|
5234
|
-
const exists =
|
|
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
|
|
5356
|
+
var doctorCommand = new Command26("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
5277
5357
|
const banner = [
|
|
5278
5358
|
``,
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
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(
|
|
5383
|
+
console.log(paint5("green", " Everything looks good!"));
|
|
5304
5384
|
console.log("");
|
|
5305
5385
|
return;
|
|
5306
5386
|
}
|
|
5307
|
-
const fixes = checks.filter((
|
|
5387
|
+
const fixes = checks.filter((c10) => !c10.ok && c10.fix && !c10.optional);
|
|
5308
5388
|
if (fixes.length > 0) {
|
|
5309
|
-
console.log(
|
|
5389
|
+
console.log(paint5("yellow", " To fix:"));
|
|
5310
5390
|
for (const fix of fixes) {
|
|
5311
|
-
console.log(` ${
|
|
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 =
|
|
5320
|
-
var isFirstRun = !
|
|
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
|
|
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(`${
|
|
5336
|
-
console.log(`${
|
|
5337
|
-
console.log(`${
|
|
5338
|
-
console.log(`${
|
|
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(`${
|
|
5341
|
-
console.log(`${
|
|
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(` ${
|
|
5344
|
-
console.log(` ${
|
|
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(` ${
|
|
5347
|
-
console.log(` ${
|
|
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(` ${
|
|
5350
|
-
console.log(` ${
|
|
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(`${
|
|
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
|
|
5357
|
-
program.name("mr").description("Mr. Manager - Task and project management CLI").version(
|
|
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);
|