@dunnewold-labs/mr-manager 0.4.40 → 0.4.43
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 +461 -216
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// cli/index.ts
|
|
4
|
-
import { Command as
|
|
5
|
-
import { existsSync as
|
|
4
|
+
import { Command as Command34 } from "commander";
|
|
5
|
+
import { existsSync as existsSync19 } from "fs";
|
|
6
6
|
import { homedir as homedir3 } from "os";
|
|
7
7
|
import { join as join12 } from "path";
|
|
8
8
|
|
|
@@ -185,7 +185,7 @@ import { fileURLToPath } from "url";
|
|
|
185
185
|
// cli/package.json
|
|
186
186
|
var package_default = {
|
|
187
187
|
name: "@dunnewold-labs/mr-manager",
|
|
188
|
-
version: "0.4.
|
|
188
|
+
version: "0.4.43",
|
|
189
189
|
description: "Mr. Manager - Task and project management CLI",
|
|
190
190
|
bin: {
|
|
191
191
|
mr: "./dist/index.mjs"
|
|
@@ -900,7 +900,7 @@ async function runTest(options) {
|
|
|
900
900
|
recordingEnabled = true,
|
|
901
901
|
recordingContext = "test-run"
|
|
902
902
|
} = options;
|
|
903
|
-
const
|
|
903
|
+
const log4 = onProgress || (() => {
|
|
904
904
|
});
|
|
905
905
|
const result = {
|
|
906
906
|
status: "passed",
|
|
@@ -917,30 +917,30 @@ async function runTest(options) {
|
|
|
917
917
|
let wtPath = null;
|
|
918
918
|
const worktreeName = `mr-test-${taskId.slice(0, 8)}`;
|
|
919
919
|
const timeoutHandle = setTimeout(() => {
|
|
920
|
-
|
|
920
|
+
log4("Test timed out after 5 minutes");
|
|
921
921
|
if (devProc) devProc.kill("SIGTERM");
|
|
922
922
|
}, 5 * 60 * 1e3);
|
|
923
923
|
try {
|
|
924
|
-
|
|
924
|
+
log4("Extracting branch from MR/PR link...");
|
|
925
925
|
const branch = extractBranchFromLink(taskLink, localPath);
|
|
926
926
|
if (!branch) {
|
|
927
927
|
throw new Error(`Could not extract branch from link: ${taskLink}`);
|
|
928
928
|
}
|
|
929
|
-
|
|
930
|
-
|
|
929
|
+
log4(`Branch: ${branch}`);
|
|
930
|
+
log4("Creating git worktree...");
|
|
931
931
|
wtPath = createWorktree(localPath, branch, worktreeName).path;
|
|
932
|
-
|
|
933
|
-
|
|
932
|
+
log4(`Worktree created at ${wtPath}`);
|
|
933
|
+
log4("Installing dependencies...");
|
|
934
934
|
try {
|
|
935
935
|
installDependencies(wtPath);
|
|
936
936
|
} catch (err) {
|
|
937
|
-
|
|
937
|
+
log4(`Warning: dependency install failed: ${err.message}`);
|
|
938
938
|
}
|
|
939
|
-
|
|
939
|
+
log4("Starting dev server...");
|
|
940
940
|
const port = await findAvailablePort(4e3);
|
|
941
941
|
devProc = await startDevServer(wtPath, port);
|
|
942
942
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
943
|
-
|
|
943
|
+
log4(`Dev server running on ${baseUrl}`);
|
|
944
944
|
let recordingStarted = false;
|
|
945
945
|
if (recordingEnabled) {
|
|
946
946
|
try {
|
|
@@ -949,28 +949,28 @@ async function runTest(options) {
|
|
|
949
949
|
throw new Error(recordingStart.stdout || "recording-start failed");
|
|
950
950
|
}
|
|
951
951
|
recordingStarted = true;
|
|
952
|
-
|
|
952
|
+
log4("Proof recording started");
|
|
953
953
|
} catch (err) {
|
|
954
954
|
result.proof = {
|
|
955
955
|
state: "proof_missing_capture_failed",
|
|
956
956
|
details: `Proof recording could not start: ${err.message}`
|
|
957
957
|
};
|
|
958
|
-
|
|
958
|
+
log4(result.proof.details);
|
|
959
959
|
}
|
|
960
960
|
} else {
|
|
961
961
|
result.proof = {
|
|
962
962
|
state: "proof_missing_disabled",
|
|
963
963
|
details: "Proof recording disabled for this run."
|
|
964
964
|
};
|
|
965
|
-
|
|
965
|
+
log4(result.proof.details);
|
|
966
966
|
}
|
|
967
967
|
await browseRunner(["goto", baseUrl]);
|
|
968
968
|
const plan = customPlan || buildDefaultTestPlan(baseUrl);
|
|
969
|
-
|
|
969
|
+
log4(`Executing ${plan.length}-step test plan...`);
|
|
970
970
|
for (let i = 0; i < plan.length; i++) {
|
|
971
971
|
const step = plan[i];
|
|
972
972
|
const stepDesc = step.description || `${step.command} ${(step.args || []).join(" ")}`;
|
|
973
|
-
|
|
973
|
+
log4(`Step ${i + 1}/${plan.length}: ${stepDesc}`);
|
|
974
974
|
try {
|
|
975
975
|
if (step.command.startsWith("assert")) {
|
|
976
976
|
const assertResult = await evaluateAssertion(step, i, browseRunner);
|
|
@@ -1020,7 +1020,7 @@ async function runTest(options) {
|
|
|
1020
1020
|
}
|
|
1021
1021
|
} catch (err) {
|
|
1022
1022
|
result.errors.push(`Step ${i + 1} (${step.command}): ${err.message}`);
|
|
1023
|
-
|
|
1023
|
+
log4(`Step ${i + 1} error: ${err.message}`);
|
|
1024
1024
|
}
|
|
1025
1025
|
}
|
|
1026
1026
|
if (recordingStarted) {
|
|
@@ -1032,7 +1032,7 @@ async function runTest(options) {
|
|
|
1032
1032
|
throw new Error(recordingStop.stdout || "recording-stop did not return a file path");
|
|
1033
1033
|
}
|
|
1034
1034
|
result.proof.localPath = savedPath;
|
|
1035
|
-
|
|
1035
|
+
log4(`Proof recording finalized at ${savedPath}`);
|
|
1036
1036
|
if (uploadVideo) {
|
|
1037
1037
|
const videoUrl = await uploadVideo(savedPath);
|
|
1038
1038
|
if (videoUrl) {
|
|
@@ -1054,14 +1054,14 @@ async function runTest(options) {
|
|
|
1054
1054
|
}
|
|
1055
1055
|
]
|
|
1056
1056
|
};
|
|
1057
|
-
|
|
1057
|
+
log4(`Proof recording uploaded to ${videoUrl}`);
|
|
1058
1058
|
} else {
|
|
1059
1059
|
result.proof = {
|
|
1060
1060
|
state: "proof_missing_upload_failed",
|
|
1061
1061
|
details: "Proof recording captured, but upload failed.",
|
|
1062
1062
|
localPath: savedPath
|
|
1063
1063
|
};
|
|
1064
|
-
|
|
1064
|
+
log4(result.proof.details);
|
|
1065
1065
|
}
|
|
1066
1066
|
} else {
|
|
1067
1067
|
result.proof = {
|
|
@@ -1069,14 +1069,14 @@ async function runTest(options) {
|
|
|
1069
1069
|
details: "Proof recording captured, but no upload handler was configured.",
|
|
1070
1070
|
localPath: savedPath
|
|
1071
1071
|
};
|
|
1072
|
-
|
|
1072
|
+
log4(result.proof.details);
|
|
1073
1073
|
}
|
|
1074
1074
|
} catch (err) {
|
|
1075
1075
|
result.proof = {
|
|
1076
1076
|
state: "proof_missing_capture_failed",
|
|
1077
1077
|
details: `Proof recording could not be finalized: ${err.message}`
|
|
1078
1078
|
};
|
|
1079
|
-
|
|
1079
|
+
log4(result.proof.details);
|
|
1080
1080
|
}
|
|
1081
1081
|
}
|
|
1082
1082
|
const totalAssertions = result.assertions.length;
|
|
@@ -4939,7 +4939,7 @@ async function checkApiConnectivity() {
|
|
|
4939
4939
|
}
|
|
4940
4940
|
}
|
|
4941
4941
|
function printResults(checks) {
|
|
4942
|
-
const maxNameLen = Math.max(...checks.map((
|
|
4942
|
+
const maxNameLen = Math.max(...checks.map((c14) => c14.name.length));
|
|
4943
4943
|
let allOk = true;
|
|
4944
4944
|
for (const check of checks) {
|
|
4945
4945
|
const isOptional = check.optional ?? false;
|
|
@@ -4952,16 +4952,16 @@ function printResults(checks) {
|
|
|
4952
4952
|
return allOk;
|
|
4953
4953
|
}
|
|
4954
4954
|
async function autoFix(checks, agent) {
|
|
4955
|
-
const { spawn:
|
|
4956
|
-
const ghInstalled = checks.find((
|
|
4957
|
-
const ghAuthed = checks.find((
|
|
4958
|
-
const mrAuthed = checks.find((
|
|
4959
|
-
const claudeCheck = checks.find((
|
|
4955
|
+
const { spawn: spawn10 } = await import("child_process");
|
|
4956
|
+
const ghInstalled = checks.find((c14) => c14.name === "GitHub CLI (gh)").ok;
|
|
4957
|
+
const ghAuthed = checks.find((c14) => c14.name === "GitHub CLI auth").ok;
|
|
4958
|
+
const mrAuthed = checks.find((c14) => c14.name === "Mr. Manager CLI auth").ok;
|
|
4959
|
+
const claudeCheck = checks.find((c14) => c14.name === "Claude Code (claude)");
|
|
4960
4960
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
4961
4961
|
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
4962
4962
|
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
4963
4963
|
await new Promise((resolve9) => {
|
|
4964
|
-
const child =
|
|
4964
|
+
const child = spawn10("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
|
|
4965
4965
|
child.on("exit", () => resolve9());
|
|
4966
4966
|
});
|
|
4967
4967
|
console.log("");
|
|
@@ -4969,7 +4969,7 @@ async function autoFix(checks, agent) {
|
|
|
4969
4969
|
if (ghInstalled && !ghAuthed) {
|
|
4970
4970
|
console.log(paint5("cyan", " Running gh auth login..."));
|
|
4971
4971
|
await new Promise((resolve9) => {
|
|
4972
|
-
const child =
|
|
4972
|
+
const child = spawn10("gh", ["auth", "login"], { stdio: "inherit" });
|
|
4973
4973
|
child.on("exit", () => resolve9());
|
|
4974
4974
|
});
|
|
4975
4975
|
console.log("");
|
|
@@ -4978,7 +4978,7 @@ async function autoFix(checks, agent) {
|
|
|
4978
4978
|
console.log(paint5("cyan", " Running mr login..."));
|
|
4979
4979
|
const entry = process.argv[1];
|
|
4980
4980
|
await new Promise((resolve9) => {
|
|
4981
|
-
const child =
|
|
4981
|
+
const child = spawn10(process.execPath, [entry, "login"], { stdio: "inherit" });
|
|
4982
4982
|
child.on("exit", () => resolve9());
|
|
4983
4983
|
});
|
|
4984
4984
|
console.log("");
|
|
@@ -5019,7 +5019,7 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
|
|
|
5019
5019
|
console.log("");
|
|
5020
5020
|
return;
|
|
5021
5021
|
}
|
|
5022
|
-
const fixes = checks.filter((
|
|
5022
|
+
const fixes = checks.filter((c14) => !c14.ok && c14.fix && !c14.optional);
|
|
5023
5023
|
if (fixes.length > 0) {
|
|
5024
5024
|
console.log(paint5("yellow", " To fix:"));
|
|
5025
5025
|
for (const fix of fixes) {
|
|
@@ -5763,20 +5763,24 @@ var noMrCommand = new Command24("no-mr").description("Signal that a task does no
|
|
|
5763
5763
|
});
|
|
5764
5764
|
|
|
5765
5765
|
// cli/commands/review.ts
|
|
5766
|
+
import { Command as Command26 } from "commander";
|
|
5767
|
+
import { spawn as spawn8, execSync as execSync6 } from "child_process";
|
|
5768
|
+
import { existsSync as existsSync14, statSync as statSync2 } from "fs";
|
|
5769
|
+
|
|
5770
|
+
// cli/commands/review-apply.ts
|
|
5766
5771
|
import { Command as Command25 } from "commander";
|
|
5767
5772
|
import { spawn as spawn7, execSync as execSync5 } from "child_process";
|
|
5768
|
-
import { existsSync as existsSync13
|
|
5773
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5769
5774
|
var c8 = {
|
|
5770
5775
|
reset: "\x1B[0m",
|
|
5771
|
-
bold: "\x1B[1m",
|
|
5772
5776
|
dim: "\x1B[2m",
|
|
5773
5777
|
cyan: "\x1B[36m",
|
|
5774
5778
|
green: "\x1B[32m",
|
|
5775
5779
|
yellow: "\x1B[33m",
|
|
5776
5780
|
red: "\x1B[31m",
|
|
5777
|
-
|
|
5781
|
+
blue: "\x1B[34m",
|
|
5778
5782
|
gray: "\x1B[90m",
|
|
5779
|
-
|
|
5783
|
+
magenta: "\x1B[35m"
|
|
5780
5784
|
};
|
|
5781
5785
|
function paint8(color, text) {
|
|
5782
5786
|
return `${c8[color]}${text}${c8.reset}`;
|
|
@@ -5785,7 +5789,7 @@ function timestamp2() {
|
|
|
5785
5789
|
return paint8("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
5786
5790
|
}
|
|
5787
5791
|
function tag() {
|
|
5788
|
-
return paint8("blue", "[review]");
|
|
5792
|
+
return paint8("blue", "[review apply]");
|
|
5789
5793
|
}
|
|
5790
5794
|
function log(msg) {
|
|
5791
5795
|
console.log(`${timestamp2()} ${tag()} ${msg}`);
|
|
@@ -5796,32 +5800,270 @@ function logOk(msg) {
|
|
|
5796
5800
|
function logErr(msg) {
|
|
5797
5801
|
console.error(`${timestamp2()} ${tag()} ${paint8("red", "\u2717")} ${msg}`);
|
|
5798
5802
|
}
|
|
5799
|
-
var
|
|
5803
|
+
var reviewApplyCommand = new Command25("apply").description("Apply review comments and findings using the Claude agent").argument("<id>", "Code review ID (the one shown in the Reviews UI)").option("--no-push", "Skip pushing the fix commit to the remote").option("--no-commit", "Apply changes but don't commit (for dry-run review)").action(async (id, opts) => {
|
|
5800
5804
|
const config = loadConfig();
|
|
5801
5805
|
if (!config.apiKey) {
|
|
5802
5806
|
logErr('Not authenticated. Run "mr login" first.');
|
|
5803
5807
|
process.exit(1);
|
|
5804
5808
|
}
|
|
5809
|
+
let review;
|
|
5810
|
+
try {
|
|
5811
|
+
review = await api.get(`/api/reviews/${id}`);
|
|
5812
|
+
} catch (err) {
|
|
5813
|
+
logErr(`Could not load review ${id}: ${err.message}`);
|
|
5814
|
+
process.exit(1);
|
|
5815
|
+
}
|
|
5816
|
+
const comments = review.comments ?? [];
|
|
5817
|
+
const findings = review.findings ?? [];
|
|
5818
|
+
const excluded = new Set(review.excludedFiles ?? []);
|
|
5819
|
+
if (comments.length === 0 && findings.length === 0) {
|
|
5820
|
+
logErr("Review has no comments or findings to act on.");
|
|
5821
|
+
process.exit(1);
|
|
5822
|
+
}
|
|
5823
|
+
const actionableComments = comments.filter((c14) => !excluded.has(c14.file));
|
|
5824
|
+
const actionableFindings = findings.filter((f) => !excluded.has(f.file));
|
|
5825
|
+
if (actionableComments.length === 0 && actionableFindings.length === 0) {
|
|
5826
|
+
logErr("All files with comments/findings are excluded \u2014 nothing to do.");
|
|
5827
|
+
process.exit(1);
|
|
5828
|
+
}
|
|
5829
|
+
let project;
|
|
5830
|
+
try {
|
|
5831
|
+
project = await api.get(`/api/projects/${review.projectId}`);
|
|
5832
|
+
} catch {
|
|
5833
|
+
logErr(`Could not load project ${review.projectId}`);
|
|
5834
|
+
process.exit(1);
|
|
5835
|
+
}
|
|
5836
|
+
let projectPath = project.localPath;
|
|
5837
|
+
if (!projectPath) {
|
|
5838
|
+
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
5839
|
+
if (pid === review.projectId) {
|
|
5840
|
+
projectPath = dir;
|
|
5841
|
+
break;
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
}
|
|
5845
|
+
if (!projectPath) projectPath = process.cwd();
|
|
5846
|
+
if (!existsSync13(projectPath) || !existsSync13(`${projectPath}/.git`)) {
|
|
5847
|
+
logErr(`Project path not a git checkout: ${projectPath}`);
|
|
5848
|
+
logErr(`Set the project's localPath or run from inside the repo.`);
|
|
5849
|
+
process.exit(1);
|
|
5850
|
+
}
|
|
5851
|
+
try {
|
|
5852
|
+
execSync5(`git fetch origin ${review.branch}`, { cwd: projectPath, stdio: "ignore" });
|
|
5853
|
+
} catch {
|
|
5854
|
+
}
|
|
5855
|
+
try {
|
|
5856
|
+
execSync5(`git checkout ${review.branch}`, { cwd: projectPath, stdio: "pipe" });
|
|
5857
|
+
} catch (err) {
|
|
5858
|
+
logErr(`Could not checkout branch ${review.branch}: ${err.message}`);
|
|
5859
|
+
process.exit(1);
|
|
5860
|
+
}
|
|
5861
|
+
log(`Project: ${paint8("cyan", project.name)}`);
|
|
5862
|
+
log(`Branch: ${paint8("cyan", review.branch)}`);
|
|
5863
|
+
log(`Comments: ${paint8("yellow", String(actionableComments.length))}, findings: ${paint8("yellow", String(actionableFindings.length))}`);
|
|
5864
|
+
if (excluded.size > 0) {
|
|
5865
|
+
log(`Excluded files: ${paint8("dim", [...excluded].join(", "))}`);
|
|
5866
|
+
}
|
|
5867
|
+
try {
|
|
5868
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "processing", fixErrorMessage: null });
|
|
5869
|
+
} catch {
|
|
5870
|
+
}
|
|
5871
|
+
const prompt2 = buildApplyPrompt({
|
|
5872
|
+
branch: review.branch,
|
|
5873
|
+
baseBranch: review.baseBranch,
|
|
5874
|
+
comments: actionableComments,
|
|
5875
|
+
findings: actionableFindings,
|
|
5876
|
+
excluded: [...excluded]
|
|
5877
|
+
});
|
|
5878
|
+
log("Asking Claude to apply the changes\u2026");
|
|
5879
|
+
try {
|
|
5880
|
+
await runClaudeInteractive(prompt2, projectPath);
|
|
5881
|
+
} catch (err) {
|
|
5882
|
+
const message = err.message || "Unknown error";
|
|
5883
|
+
logErr(`Agent fix failed: ${message}`);
|
|
5884
|
+
try {
|
|
5885
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "failed", fixErrorMessage: message });
|
|
5886
|
+
} catch {
|
|
5887
|
+
}
|
|
5888
|
+
process.exit(1);
|
|
5889
|
+
}
|
|
5890
|
+
const dirty = execSync5("git status --porcelain", { cwd: projectPath, encoding: "utf-8" }).trim();
|
|
5891
|
+
if (!dirty) {
|
|
5892
|
+
log(paint8("yellow", "Agent didn't change any files."));
|
|
5893
|
+
try {
|
|
5894
|
+
await api.patch(`/api/reviews/${id}`, {
|
|
5895
|
+
fixStatus: "completed",
|
|
5896
|
+
fixErrorMessage: "No file changes were made by the agent."
|
|
5897
|
+
});
|
|
5898
|
+
} catch {
|
|
5899
|
+
}
|
|
5900
|
+
return;
|
|
5901
|
+
}
|
|
5902
|
+
if (opts.commit === false) {
|
|
5903
|
+
logOk("Changes left unstaged for review (--no-commit).");
|
|
5904
|
+
try {
|
|
5905
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "completed" });
|
|
5906
|
+
} catch {
|
|
5907
|
+
}
|
|
5908
|
+
return;
|
|
5909
|
+
}
|
|
5910
|
+
const commitMessage = buildCommitMessage({
|
|
5911
|
+
commentsCount: actionableComments.length,
|
|
5912
|
+
findingsCount: actionableFindings.length
|
|
5913
|
+
});
|
|
5914
|
+
try {
|
|
5915
|
+
execSync5("git add -A", { cwd: projectPath });
|
|
5916
|
+
execSync5(`git commit -m ${JSON.stringify(commitMessage)}`, { cwd: projectPath, stdio: "pipe" });
|
|
5917
|
+
} catch (err) {
|
|
5918
|
+
const message = err.message || "Unknown error";
|
|
5919
|
+
logErr(`Commit failed: ${message}`);
|
|
5920
|
+
try {
|
|
5921
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "failed", fixErrorMessage: message });
|
|
5922
|
+
} catch {
|
|
5923
|
+
}
|
|
5924
|
+
process.exit(1);
|
|
5925
|
+
}
|
|
5926
|
+
const sha = execSync5("git rev-parse HEAD", { cwd: projectPath, encoding: "utf-8" }).trim();
|
|
5927
|
+
logOk(`Committed ${paint8("yellow", sha.slice(0, 10))}`);
|
|
5928
|
+
if (opts.push !== false) {
|
|
5929
|
+
try {
|
|
5930
|
+
execSync5(`git push origin ${review.branch}`, { cwd: projectPath, stdio: "pipe" });
|
|
5931
|
+
logOk(`Pushed to origin/${review.branch}`);
|
|
5932
|
+
} catch (err) {
|
|
5933
|
+
const message = err.message || "Unknown error";
|
|
5934
|
+
logErr(`Push failed: ${message}`);
|
|
5935
|
+
try {
|
|
5936
|
+
await api.patch(`/api/reviews/${id}`, {
|
|
5937
|
+
fixStatus: "failed",
|
|
5938
|
+
fixErrorMessage: `Committed ${sha.slice(0, 10)} but push failed: ${message}`,
|
|
5939
|
+
fixCommitSha: sha
|
|
5940
|
+
});
|
|
5941
|
+
} catch {
|
|
5942
|
+
}
|
|
5943
|
+
process.exit(1);
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
try {
|
|
5947
|
+
await api.patch(`/api/reviews/${id}`, {
|
|
5948
|
+
fixStatus: "completed",
|
|
5949
|
+
fixCommitSha: sha,
|
|
5950
|
+
fixErrorMessage: null
|
|
5951
|
+
});
|
|
5952
|
+
} catch {
|
|
5953
|
+
}
|
|
5954
|
+
logOk("Done.");
|
|
5955
|
+
});
|
|
5956
|
+
function buildApplyPrompt(args) {
|
|
5957
|
+
const lines = [];
|
|
5958
|
+
lines.push(`You are a code review agent. The user has reviewed the diff on branch "${args.branch}" (against "${args.baseBranch}") and left the feedback below.`);
|
|
5959
|
+
lines.push("");
|
|
5960
|
+
lines.push("Make the requested changes by editing files in the current working directory. Be conservative \u2014 only change what the comments and findings call out.");
|
|
5961
|
+
lines.push("");
|
|
5962
|
+
if (args.excluded.length > 0) {
|
|
5963
|
+
lines.push("Do NOT modify these files (they were excluded by the reviewer):");
|
|
5964
|
+
for (const path of args.excluded) lines.push(` - ${path}`);
|
|
5965
|
+
lines.push("");
|
|
5966
|
+
}
|
|
5967
|
+
if (args.comments.length > 0) {
|
|
5968
|
+
lines.push("USER COMMENTS:");
|
|
5969
|
+
for (const c14 of args.comments) {
|
|
5970
|
+
const where = c14.line ? `${c14.file}:${c14.line}` : c14.file;
|
|
5971
|
+
lines.push(`- [${where}] ${c14.body}`);
|
|
5972
|
+
}
|
|
5973
|
+
lines.push("");
|
|
5974
|
+
}
|
|
5975
|
+
if (args.findings.length > 0) {
|
|
5976
|
+
lines.push("AUTOMATED FINDINGS TO ADDRESS:");
|
|
5977
|
+
for (const f of args.findings) {
|
|
5978
|
+
const where = f.line ? `${f.file}:${f.line}` : f.file;
|
|
5979
|
+
lines.push(`- [${f.severity}/${f.type}] [${where}] ${f.title}`);
|
|
5980
|
+
if (f.description) lines.push(` ${f.description}`);
|
|
5981
|
+
if (f.suggestion) lines.push(` Suggested: ${f.suggestion}`);
|
|
5982
|
+
}
|
|
5983
|
+
lines.push("");
|
|
5984
|
+
}
|
|
5985
|
+
lines.push("Make the edits now. Do not run the test suite. Stage no commits \u2014 just edit files. If a request can't be safely actioned, skip it and continue.");
|
|
5986
|
+
return lines.join("\n");
|
|
5987
|
+
}
|
|
5988
|
+
function buildCommitMessage(args) {
|
|
5989
|
+
const parts = [];
|
|
5990
|
+
if (args.commentsCount > 0) parts.push(`${args.commentsCount} review comment${args.commentsCount === 1 ? "" : "s"}`);
|
|
5991
|
+
if (args.findingsCount > 0) parts.push(`${args.findingsCount} finding${args.findingsCount === 1 ? "" : "s"}`);
|
|
5992
|
+
const summary = parts.length > 0 ? parts.join(" + ") : "review feedback";
|
|
5993
|
+
return `chore: apply ${summary}
|
|
5994
|
+
|
|
5995
|
+
Generated by mr review apply.`;
|
|
5996
|
+
}
|
|
5997
|
+
function runClaudeInteractive(prompt2, cwd) {
|
|
5998
|
+
return new Promise((resolve9, reject) => {
|
|
5999
|
+
const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6000
|
+
cwd,
|
|
6001
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
6002
|
+
});
|
|
6003
|
+
child.on("exit", (code) => {
|
|
6004
|
+
if (code === 0) resolve9();
|
|
6005
|
+
else reject(new Error(`claude exited with code ${code}`));
|
|
6006
|
+
});
|
|
6007
|
+
});
|
|
6008
|
+
}
|
|
6009
|
+
|
|
6010
|
+
// cli/commands/review.ts
|
|
6011
|
+
var c9 = {
|
|
6012
|
+
reset: "\x1B[0m",
|
|
6013
|
+
bold: "\x1B[1m",
|
|
6014
|
+
dim: "\x1B[2m",
|
|
6015
|
+
cyan: "\x1B[36m",
|
|
6016
|
+
green: "\x1B[32m",
|
|
6017
|
+
yellow: "\x1B[33m",
|
|
6018
|
+
red: "\x1B[31m",
|
|
6019
|
+
magenta: "\x1B[35m",
|
|
6020
|
+
gray: "\x1B[90m",
|
|
6021
|
+
blue: "\x1B[34m"
|
|
6022
|
+
};
|
|
6023
|
+
function paint9(color, text) {
|
|
6024
|
+
return `${c9[color]}${text}${c9.reset}`;
|
|
6025
|
+
}
|
|
6026
|
+
function timestamp3() {
|
|
6027
|
+
return paint9("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
6028
|
+
}
|
|
6029
|
+
function tag2() {
|
|
6030
|
+
return paint9("blue", "[review]");
|
|
6031
|
+
}
|
|
6032
|
+
function log2(msg) {
|
|
6033
|
+
console.log(`${timestamp3()} ${tag2()} ${msg}`);
|
|
6034
|
+
}
|
|
6035
|
+
function logOk2(msg) {
|
|
6036
|
+
console.log(`${timestamp3()} ${tag2()} ${paint9("green", "\u2713")} ${msg}`);
|
|
6037
|
+
}
|
|
6038
|
+
function logErr2(msg) {
|
|
6039
|
+
console.error(`${timestamp3()} ${tag2()} ${paint9("red", "\u2717")} ${msg}`);
|
|
6040
|
+
}
|
|
6041
|
+
var reviewCommand = new Command26("review").description("Run an automated code review on a branch").option("--project <id>", "Project ID (defaults to linked project)").option("--report <id>", "Use an existing review report ID (created by UI trigger)").option("--branch <name>", "Branch to review (defaults to current branch)").option("--base <name>", "Base branch to diff against (defaults to main)").option("--pr-url <url>", "Pull/merge request URL; when set, diff is fetched via gh/glab and no local checkout is needed").option("--pr-number <n>", "Pull/merge request number (used with --pr-url)").addCommand(reviewApplyCommand).action(async (opts) => {
|
|
6042
|
+
const config = loadConfig();
|
|
6043
|
+
if (!config.apiKey) {
|
|
6044
|
+
logErr2('Not authenticated. Run "mr login" first.');
|
|
6045
|
+
process.exit(1);
|
|
6046
|
+
}
|
|
5805
6047
|
const banner = [
|
|
5806
6048
|
``,
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
6049
|
+
paint9("blue", ` \u2566\u2550\u2557\u2554\u2550\u2557\u2566 \u2566\u2566\u2554\u2550\u2557\u2566 \u2566`),
|
|
6050
|
+
paint9("blue", ` \u2560\u2566\u255D\u2551\u2563 \u255A\u2557\u2554\u255D\u2551\u2551\u2563 \u2551\u2551\u2551`),
|
|
6051
|
+
paint9("blue", ` \u2569\u255A\u2550\u255A\u2550\u255D \u255A\u255D \u2569\u255A\u2550\u255D\u255A\u2569\u255D`),
|
|
6052
|
+
paint9("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
6053
|
+
paint9("dim", ` automated code review`),
|
|
5812
6054
|
``
|
|
5813
6055
|
].join("\n");
|
|
5814
6056
|
console.log(banner);
|
|
5815
6057
|
const projectId = opts.project || getLinkedProjectId();
|
|
5816
6058
|
if (!projectId) {
|
|
5817
|
-
|
|
6059
|
+
logErr2('No project linked. Run "mr link" or pass --project <id>.');
|
|
5818
6060
|
process.exit(1);
|
|
5819
6061
|
}
|
|
5820
6062
|
let project;
|
|
5821
6063
|
try {
|
|
5822
6064
|
project = await api.get(`/api/projects/${projectId}`);
|
|
5823
6065
|
} catch {
|
|
5824
|
-
|
|
6066
|
+
logErr2(`Failed to fetch project ${projectId}`);
|
|
5825
6067
|
process.exit(1);
|
|
5826
6068
|
}
|
|
5827
6069
|
const baseBranch = opts.base || "main";
|
|
@@ -5835,19 +6077,19 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5835
6077
|
if (canUseRemote && remote) {
|
|
5836
6078
|
const num = prNumber ?? remote.number;
|
|
5837
6079
|
if (!num) {
|
|
5838
|
-
|
|
6080
|
+
logErr2(`Could not determine PR number from URL: ${prUrl}`);
|
|
5839
6081
|
process.exit(1);
|
|
5840
6082
|
}
|
|
5841
|
-
|
|
6083
|
+
log2(`Fetching diff from ${paint9("cyan", remote.host)} for ${paint9("yellow", `#${num}`)} in ${paint9("dim", `${remote.owner}/${remote.repo}`)}`);
|
|
5842
6084
|
try {
|
|
5843
6085
|
diff = fetchRemoteDiff(remote, num);
|
|
5844
6086
|
} catch (err) {
|
|
5845
|
-
|
|
5846
|
-
|
|
6087
|
+
logErr2(`Failed to fetch diff from ${remote.host}: ${err.message}`);
|
|
6088
|
+
logErr2(`Ensure ${remote.host === "github" ? "`gh`" : "`glab`"} is installed and authenticated.`);
|
|
5847
6089
|
process.exit(1);
|
|
5848
6090
|
}
|
|
5849
6091
|
if (!branch) branch = `pr-${num}`;
|
|
5850
|
-
|
|
6092
|
+
log2(`Reviewing PR ${paint9("cyan", `#${num}`)} against ${paint9("dim", baseBranch)}`);
|
|
5851
6093
|
} else {
|
|
5852
6094
|
let projectPath = project.localPath;
|
|
5853
6095
|
if (!projectPath) {
|
|
@@ -5861,42 +6103,42 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5861
6103
|
if (!projectPath) {
|
|
5862
6104
|
projectPath = process.cwd();
|
|
5863
6105
|
}
|
|
5864
|
-
if (!
|
|
5865
|
-
|
|
5866
|
-
|
|
6106
|
+
if (!existsSync14(projectPath)) {
|
|
6107
|
+
logErr2(`Project path does not exist: ${projectPath}`);
|
|
6108
|
+
logErr2(`Update the project's localPath, attach a PR URL to the review, or run "mr link" from the correct directory.`);
|
|
5867
6109
|
process.exit(1);
|
|
5868
6110
|
}
|
|
5869
6111
|
try {
|
|
5870
6112
|
if (!statSync2(projectPath).isDirectory()) {
|
|
5871
|
-
|
|
6113
|
+
logErr2(`Project path is not a directory: ${projectPath}`);
|
|
5872
6114
|
process.exit(1);
|
|
5873
6115
|
}
|
|
5874
6116
|
} catch (err) {
|
|
5875
|
-
|
|
6117
|
+
logErr2(`Cannot stat project path ${projectPath}: ${err.message}`);
|
|
5876
6118
|
process.exit(1);
|
|
5877
6119
|
}
|
|
5878
|
-
if (!
|
|
5879
|
-
|
|
5880
|
-
|
|
6120
|
+
if (!existsSync14(`${projectPath}/.git`)) {
|
|
6121
|
+
logErr2(`Project path is not a git repository: ${projectPath}`);
|
|
6122
|
+
logErr2(`Update the project's localPath to point to the local checkout, or attach a PR URL to the review.`);
|
|
5881
6123
|
process.exit(1);
|
|
5882
6124
|
}
|
|
5883
|
-
|
|
6125
|
+
log2(`Using project path: ${paint9("dim", projectPath)}`);
|
|
5884
6126
|
if (!branch) {
|
|
5885
6127
|
try {
|
|
5886
|
-
branch =
|
|
6128
|
+
branch = execSync6("git rev-parse --abbrev-ref HEAD", {
|
|
5887
6129
|
cwd: projectPath,
|
|
5888
6130
|
encoding: "utf-8"
|
|
5889
6131
|
}).trim();
|
|
5890
6132
|
} catch {
|
|
5891
|
-
|
|
6133
|
+
logErr2("Could not determine current branch. Pass --branch <name>.");
|
|
5892
6134
|
process.exit(1);
|
|
5893
6135
|
}
|
|
5894
6136
|
}
|
|
5895
|
-
|
|
6137
|
+
log2(`Reviewing branch: ${paint9("cyan", branch)} against ${paint9("dim", baseBranch)}`);
|
|
5896
6138
|
const pathspec = `-- . ':!*.lock' ':!package-lock.json' ':!pnpm-lock.yaml'`;
|
|
5897
6139
|
const tryDiff = (range) => {
|
|
5898
6140
|
try {
|
|
5899
|
-
return
|
|
6141
|
+
return execSync6(`git diff ${range} ${pathspec}`, {
|
|
5900
6142
|
cwd: projectPath,
|
|
5901
6143
|
encoding: "utf-8",
|
|
5902
6144
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -5908,7 +6150,7 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5908
6150
|
};
|
|
5909
6151
|
const tryFetch = (ref) => {
|
|
5910
6152
|
try {
|
|
5911
|
-
|
|
6153
|
+
execSync6(`git fetch origin ${ref}`, { cwd: projectPath, encoding: "utf-8", stdio: "pipe" });
|
|
5912
6154
|
} catch {
|
|
5913
6155
|
}
|
|
5914
6156
|
};
|
|
@@ -5919,24 +6161,24 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5919
6161
|
result = tryDiff(`origin/${baseBranch}...${branch}`) ?? tryDiff(`origin/${baseBranch}...origin/${branch}`) ?? tryDiff(`${baseBranch}...origin/${branch}`);
|
|
5920
6162
|
}
|
|
5921
6163
|
if (result === null) {
|
|
5922
|
-
|
|
5923
|
-
|
|
6164
|
+
logErr2(`Failed to get diff between ${baseBranch} and ${branch} in ${projectPath}.`);
|
|
6165
|
+
logErr2(`Branch may not exist locally or on origin. Try fetching it manually, or attach a PR URL to the review.`);
|
|
5924
6166
|
process.exit(1);
|
|
5925
6167
|
}
|
|
5926
6168
|
diff = result;
|
|
5927
6169
|
}
|
|
5928
|
-
|
|
6170
|
+
log2(`Project: ${paint9("cyan", project.name)}`);
|
|
5929
6171
|
if (!diff) {
|
|
5930
|
-
|
|
6172
|
+
logOk2("No changes found between branches. Nothing to review.");
|
|
5931
6173
|
process.exit(0);
|
|
5932
6174
|
}
|
|
5933
6175
|
diff = stripLockFileDiffs(diff);
|
|
5934
6176
|
const filesChanged = (diff.match(/^diff --git/gm) ?? []).length;
|
|
5935
|
-
|
|
6177
|
+
log2(`Diff size: ${paint9("yellow", `${diff.length.toLocaleString()} chars`)}, ${paint9("yellow", `${filesChanged} files`)}`);
|
|
5936
6178
|
let reportId;
|
|
5937
6179
|
if (opts.report) {
|
|
5938
6180
|
reportId = opts.report;
|
|
5939
|
-
|
|
6181
|
+
log2(`Using existing review report ${paint9("yellow", reportId.slice(0, 8))}`);
|
|
5940
6182
|
} else {
|
|
5941
6183
|
try {
|
|
5942
6184
|
const report = await api.post("/api/reviews", {
|
|
@@ -5945,14 +6187,17 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5945
6187
|
baseBranch
|
|
5946
6188
|
});
|
|
5947
6189
|
reportId = report.id;
|
|
5948
|
-
|
|
6190
|
+
log2(`Created review report ${paint9("yellow", reportId.slice(0, 8))}`);
|
|
5949
6191
|
} catch (err) {
|
|
5950
|
-
|
|
6192
|
+
logErr2(`Failed to create review report: ${err.message}`);
|
|
5951
6193
|
process.exit(1);
|
|
5952
6194
|
}
|
|
5953
6195
|
}
|
|
5954
6196
|
try {
|
|
5955
|
-
await api.patch(`/api/reviews/${reportId}`, {
|
|
6197
|
+
await api.patch(`/api/reviews/${reportId}`, {
|
|
6198
|
+
status: "processing",
|
|
6199
|
+
diff
|
|
6200
|
+
});
|
|
5956
6201
|
} catch {
|
|
5957
6202
|
}
|
|
5958
6203
|
const startTime = Date.now();
|
|
@@ -5960,10 +6205,10 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5960
6205
|
let truncatedDiff = diff;
|
|
5961
6206
|
if (diff.length > MAX_DIFF_CHARS) {
|
|
5962
6207
|
truncatedDiff = diff.slice(0, MAX_DIFF_CHARS) + "\n\n... (diff truncated, review covers first " + MAX_DIFF_CHARS.toLocaleString() + " characters)";
|
|
5963
|
-
|
|
6208
|
+
log2(paint9("yellow", `Diff truncated to ${MAX_DIFF_CHARS.toLocaleString()} chars for review`));
|
|
5964
6209
|
}
|
|
5965
6210
|
try {
|
|
5966
|
-
|
|
6211
|
+
log2("Running code review with Claude...");
|
|
5967
6212
|
const prompt2 = buildReviewPrompt(branch, baseBranch, truncatedDiff);
|
|
5968
6213
|
const output = await runClaude(prompt2);
|
|
5969
6214
|
const result = parseReviewOutput(output);
|
|
@@ -5975,7 +6220,7 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5975
6220
|
} catch {
|
|
5976
6221
|
}
|
|
5977
6222
|
if (wasCancelled) {
|
|
5978
|
-
|
|
6223
|
+
log2(paint9("yellow", "Review was cancelled \u2014 discarding results."));
|
|
5979
6224
|
process.exit(0);
|
|
5980
6225
|
}
|
|
5981
6226
|
await api.patch(`/api/reviews/${reportId}`, {
|
|
@@ -5985,28 +6230,28 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5985
6230
|
filesReviewed: filesChanged,
|
|
5986
6231
|
reviewDurationMs: duration
|
|
5987
6232
|
});
|
|
5988
|
-
|
|
5989
|
-
|
|
6233
|
+
logOk2(`Review completed in ${paint9("cyan", formatDuration(duration))}`);
|
|
6234
|
+
logOk2(`Found ${paint9("yellow", String(result.findings.length))} findings`);
|
|
5990
6235
|
if (result.findings.length > 0) {
|
|
5991
6236
|
console.log("");
|
|
5992
6237
|
const critical = result.findings.filter((f) => f.severity === "critical").length;
|
|
5993
6238
|
const high = result.findings.filter((f) => f.severity === "high").length;
|
|
5994
6239
|
const medium = result.findings.filter((f) => f.severity === "medium").length;
|
|
5995
6240
|
const low = result.findings.filter((f) => f.severity === "low").length;
|
|
5996
|
-
if (critical > 0) console.log(` ${
|
|
5997
|
-
if (high > 0) console.log(` ${
|
|
5998
|
-
if (medium > 0) console.log(` ${
|
|
5999
|
-
if (low > 0) console.log(` ${
|
|
6241
|
+
if (critical > 0) console.log(` ${paint9("red", "\u25CF")} ${critical} critical`);
|
|
6242
|
+
if (high > 0) console.log(` ${paint9("red", "\u25CF")} ${high} high`);
|
|
6243
|
+
if (medium > 0) console.log(` ${paint9("yellow", "\u25CF")} ${medium} medium`);
|
|
6244
|
+
if (low > 0) console.log(` ${paint9("dim", "\u25CF")} ${low} low`);
|
|
6000
6245
|
console.log("");
|
|
6001
6246
|
}
|
|
6002
6247
|
if (result.summary) {
|
|
6003
|
-
console.log(
|
|
6248
|
+
console.log(paint9("dim", " " + result.summary));
|
|
6004
6249
|
console.log("");
|
|
6005
6250
|
}
|
|
6006
6251
|
} catch (err) {
|
|
6007
6252
|
const duration = Date.now() - startTime;
|
|
6008
6253
|
const errorMessage = err.message || "Unknown error";
|
|
6009
|
-
|
|
6254
|
+
logErr2(`Review failed: ${errorMessage}`);
|
|
6010
6255
|
try {
|
|
6011
6256
|
await api.patch(`/api/reviews/${reportId}`, {
|
|
6012
6257
|
status: "failed",
|
|
@@ -6045,13 +6290,13 @@ function parsePrUrl(url) {
|
|
|
6045
6290
|
}
|
|
6046
6291
|
function fetchRemoteDiff(remote, number) {
|
|
6047
6292
|
if (remote.host === "github") {
|
|
6048
|
-
return
|
|
6293
|
+
return execSync6(`gh pr diff ${number} --repo ${remote.owner}/${remote.repo}`, {
|
|
6049
6294
|
encoding: "utf-8",
|
|
6050
6295
|
maxBuffer: 20 * 1024 * 1024
|
|
6051
6296
|
}).trim();
|
|
6052
6297
|
}
|
|
6053
6298
|
const project = `${remote.owner}/${remote.repo}`;
|
|
6054
|
-
return
|
|
6299
|
+
return execSync6(`glab mr diff ${number} --repo ${project} --raw`, {
|
|
6055
6300
|
encoding: "utf-8",
|
|
6056
6301
|
maxBuffer: 20 * 1024 * 1024
|
|
6057
6302
|
}).trim();
|
|
@@ -6122,7 +6367,7 @@ ${diff}
|
|
|
6122
6367
|
}
|
|
6123
6368
|
function runClaude(prompt2) {
|
|
6124
6369
|
return new Promise((resolve9, reject) => {
|
|
6125
|
-
const child =
|
|
6370
|
+
const child = spawn8("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6126
6371
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6127
6372
|
});
|
|
6128
6373
|
let output = "";
|
|
@@ -6174,13 +6419,13 @@ function parseReviewOutput(output) {
|
|
|
6174
6419
|
}
|
|
6175
6420
|
|
|
6176
6421
|
// cli/commands/scan.ts
|
|
6177
|
-
import { Command as
|
|
6422
|
+
import { Command as Command27 } from "commander";
|
|
6178
6423
|
|
|
6179
6424
|
// lib/scanner/index.ts
|
|
6180
|
-
import { spawn as
|
|
6425
|
+
import { spawn as spawn9 } from "child_process";
|
|
6181
6426
|
|
|
6182
6427
|
// lib/scanner/config.ts
|
|
6183
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
6428
|
+
import { readFileSync as readFileSync10, existsSync as existsSync15 } from "fs";
|
|
6184
6429
|
import { join as join9 } from "path";
|
|
6185
6430
|
var ALL_FINDING_TYPES = [
|
|
6186
6431
|
"idea",
|
|
@@ -6198,7 +6443,7 @@ var DEFAULTS = {
|
|
|
6198
6443
|
};
|
|
6199
6444
|
function loadScanConfig(projectPath) {
|
|
6200
6445
|
const configPath2 = join9(projectPath, ".mr-scan.json");
|
|
6201
|
-
if (!
|
|
6446
|
+
if (!existsSync15(configPath2)) {
|
|
6202
6447
|
return { ...DEFAULTS };
|
|
6203
6448
|
}
|
|
6204
6449
|
try {
|
|
@@ -6244,13 +6489,13 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
|
|
|
6244
6489
|
}
|
|
6245
6490
|
|
|
6246
6491
|
// lib/scanner/codebase-analysis.ts
|
|
6247
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as
|
|
6492
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync16 } from "fs";
|
|
6248
6493
|
import { join as join10, relative } from "path";
|
|
6249
|
-
import { execSync as
|
|
6494
|
+
import { execSync as execSync7 } from "child_process";
|
|
6250
6495
|
function resolveDir(projectPath, candidates) {
|
|
6251
6496
|
for (const candidate of candidates) {
|
|
6252
6497
|
const dir = join10(projectPath, candidate);
|
|
6253
|
-
if (
|
|
6498
|
+
if (existsSync16(dir)) return dir;
|
|
6254
6499
|
}
|
|
6255
6500
|
return null;
|
|
6256
6501
|
}
|
|
@@ -6284,7 +6529,7 @@ function discoverRoutes(projectPath) {
|
|
|
6284
6529
|
}
|
|
6285
6530
|
function extractModels(projectPath) {
|
|
6286
6531
|
const schemaPath = join10(projectPath, "prisma", "schema.prisma");
|
|
6287
|
-
if (
|
|
6532
|
+
if (existsSync16(schemaPath)) {
|
|
6288
6533
|
const content = readFileSync11(schemaPath, "utf-8");
|
|
6289
6534
|
const models2 = [];
|
|
6290
6535
|
const modelRegex = /^model\s+(\w+)\s*\{/gm;
|
|
@@ -6298,7 +6543,7 @@ function extractModels(projectPath) {
|
|
|
6298
6543
|
const drizzleDirs = ["src/db", "src/schema", "db", "drizzle"];
|
|
6299
6544
|
for (const dir of drizzleDirs) {
|
|
6300
6545
|
const fullDir = join10(projectPath, dir);
|
|
6301
|
-
if (!
|
|
6546
|
+
if (!existsSync16(fullDir)) continue;
|
|
6302
6547
|
try {
|
|
6303
6548
|
const entries = readdirSync2(fullDir, { withFileTypes: true });
|
|
6304
6549
|
for (const entry of entries) {
|
|
@@ -6339,7 +6584,7 @@ function discoverComponents(projectPath) {
|
|
|
6339
6584
|
function extractInternalLinks(projectPath) {
|
|
6340
6585
|
const links = /* @__PURE__ */ new Set();
|
|
6341
6586
|
function searchDir(dir) {
|
|
6342
|
-
if (!
|
|
6587
|
+
if (!existsSync16(dir)) return;
|
|
6343
6588
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
6344
6589
|
for (const entry of entries) {
|
|
6345
6590
|
if (entry.isDirectory()) {
|
|
@@ -6372,7 +6617,7 @@ function extractInternalLinks(projectPath) {
|
|
|
6372
6617
|
}
|
|
6373
6618
|
function getRecentCommits(projectPath, count = 20) {
|
|
6374
6619
|
try {
|
|
6375
|
-
const output =
|
|
6620
|
+
const output = execSync7(
|
|
6376
6621
|
`git log --oneline -${count} --no-decorate`,
|
|
6377
6622
|
{ cwd: projectPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6378
6623
|
);
|
|
@@ -6640,10 +6885,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
|
|
|
6640
6885
|
${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
|
|
6641
6886
|
|
|
6642
6887
|
**Components:**
|
|
6643
|
-
${codebaseAnalysis.components.slice(0, 15).map((
|
|
6888
|
+
${codebaseAnalysis.components.slice(0, 15).map((c14) => `- ${c14}`).join("\n")}
|
|
6644
6889
|
|
|
6645
6890
|
**Recent Git Commits:**
|
|
6646
|
-
${codebaseAnalysis.recentCommits.slice(0, 8).map((
|
|
6891
|
+
${codebaseAnalysis.recentCommits.slice(0, 8).map((c14) => `- ${c14}`).join("\n")}
|
|
6647
6892
|
|
|
6648
6893
|
**Completed Tasks:**
|
|
6649
6894
|
${context.completedTasks.slice(0, 10).map((t) => `- ${t.title}`).join("\n") || "None"}
|
|
@@ -6858,7 +7103,7 @@ async function fetchScanContext(opts) {
|
|
|
6858
7103
|
}
|
|
6859
7104
|
function runClaude2(prompt2) {
|
|
6860
7105
|
return new Promise((resolve9, reject) => {
|
|
6861
|
-
const child =
|
|
7106
|
+
const child = spawn9("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6862
7107
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6863
7108
|
});
|
|
6864
7109
|
let output = "";
|
|
@@ -6914,7 +7159,7 @@ function parseSynthesisOutput(output) {
|
|
|
6914
7159
|
}
|
|
6915
7160
|
|
|
6916
7161
|
// cli/commands/scan.ts
|
|
6917
|
-
var
|
|
7162
|
+
var c10 = {
|
|
6918
7163
|
reset: "\x1B[0m",
|
|
6919
7164
|
bold: "\x1B[1m",
|
|
6920
7165
|
dim: "\x1B[2m",
|
|
@@ -6925,53 +7170,53 @@ var c9 = {
|
|
|
6925
7170
|
magenta: "\x1B[35m",
|
|
6926
7171
|
gray: "\x1B[90m"
|
|
6927
7172
|
};
|
|
6928
|
-
function
|
|
6929
|
-
return `${
|
|
7173
|
+
function paint10(color, text) {
|
|
7174
|
+
return `${c10[color]}${text}${c10.reset}`;
|
|
6930
7175
|
}
|
|
6931
|
-
function
|
|
6932
|
-
return
|
|
7176
|
+
function timestamp4() {
|
|
7177
|
+
return paint10("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
6933
7178
|
}
|
|
6934
7179
|
function scanTag() {
|
|
6935
|
-
return
|
|
7180
|
+
return paint10("magenta", "[scan]");
|
|
6936
7181
|
}
|
|
6937
|
-
function
|
|
6938
|
-
console.log(`${
|
|
7182
|
+
function log3(msg) {
|
|
7183
|
+
console.log(`${timestamp4()} ${scanTag()} ${msg}`);
|
|
6939
7184
|
}
|
|
6940
|
-
function
|
|
6941
|
-
console.log(`${
|
|
7185
|
+
function logOk3(msg) {
|
|
7186
|
+
console.log(`${timestamp4()} ${scanTag()} ${paint10("green", "\u2713")} ${msg}`);
|
|
6942
7187
|
}
|
|
6943
|
-
function
|
|
6944
|
-
console.error(`${
|
|
7188
|
+
function logErr3(msg) {
|
|
7189
|
+
console.error(`${timestamp4()} ${scanTag()} ${paint10("red", "\u2717")} ${msg}`);
|
|
6945
7190
|
}
|
|
6946
|
-
var scanCommand = new
|
|
7191
|
+
var scanCommand = new Command27("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("--prompt <prompt>", "Custom scan direction/prompt to focus the scan on").option("--no-crawl", "Skip live crawl (codebase analysis only)").action(async (opts) => {
|
|
6947
7192
|
const config = loadConfig();
|
|
6948
7193
|
if (!config.apiKey) {
|
|
6949
|
-
|
|
7194
|
+
logErr3('Not authenticated. Run "mr login" first.');
|
|
6950
7195
|
process.exit(1);
|
|
6951
7196
|
}
|
|
6952
7197
|
const banner = [
|
|
6953
7198
|
``,
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
7199
|
+
paint10("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
|
|
7200
|
+
paint10("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
|
|
7201
|
+
paint10("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
|
|
7202
|
+
paint10("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
7203
|
+
paint10("dim", ` autonomous product scanner`),
|
|
6959
7204
|
``
|
|
6960
7205
|
].join("\n");
|
|
6961
7206
|
console.log(banner);
|
|
6962
7207
|
const projectId = opts.project || getLinkedProjectId();
|
|
6963
7208
|
if (!projectId) {
|
|
6964
|
-
|
|
7209
|
+
logErr3('No project linked. Run "mr link" or pass --project <id>.');
|
|
6965
7210
|
process.exit(1);
|
|
6966
7211
|
}
|
|
6967
7212
|
let project;
|
|
6968
7213
|
try {
|
|
6969
7214
|
project = await api.get(`/api/projects/${projectId}`);
|
|
6970
7215
|
} catch {
|
|
6971
|
-
|
|
7216
|
+
logErr3(`Failed to fetch project ${projectId}`);
|
|
6972
7217
|
process.exit(1);
|
|
6973
7218
|
}
|
|
6974
|
-
|
|
7219
|
+
log3(`Scanning project: ${paint10("cyan", project.name)}`);
|
|
6975
7220
|
let projectPath = project.localPath;
|
|
6976
7221
|
if (!projectPath) {
|
|
6977
7222
|
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
@@ -6988,7 +7233,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
6988
7233
|
let customPrompt = typeof opts.prompt === "string" && opts.prompt.trim().length > 0 ? opts.prompt.trim() : null;
|
|
6989
7234
|
if (opts.report) {
|
|
6990
7235
|
reportId = opts.report;
|
|
6991
|
-
|
|
7236
|
+
log3(`Using existing scan report ${paint10("yellow", reportId.slice(0, 8))}`);
|
|
6992
7237
|
try {
|
|
6993
7238
|
const existing = await api.get(`/api/scans/${reportId}`);
|
|
6994
7239
|
if (!customPrompt && existing.customPrompt) {
|
|
@@ -7000,7 +7245,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7000
7245
|
try {
|
|
7001
7246
|
const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
|
|
7002
7247
|
if (scans.length > 0) {
|
|
7003
|
-
|
|
7248
|
+
logErr3("A scan is already in progress for this project. Wait for it to complete.");
|
|
7004
7249
|
process.exit(1);
|
|
7005
7250
|
}
|
|
7006
7251
|
} catch {
|
|
@@ -7012,9 +7257,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7012
7257
|
customPrompt
|
|
7013
7258
|
});
|
|
7014
7259
|
reportId = report.id;
|
|
7015
|
-
|
|
7260
|
+
log3(`Created scan report ${paint10("yellow", reportId.slice(0, 8))}`);
|
|
7016
7261
|
} catch (err) {
|
|
7017
|
-
|
|
7262
|
+
logErr3(`Failed to create scan report: ${err.message}`);
|
|
7018
7263
|
process.exit(1);
|
|
7019
7264
|
}
|
|
7020
7265
|
}
|
|
@@ -7025,7 +7270,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7025
7270
|
try {
|
|
7026
7271
|
const current = await api.get(`/api/scans/${reportId}`);
|
|
7027
7272
|
if (current.status === "cancelled") {
|
|
7028
|
-
|
|
7273
|
+
log3(paint10("yellow", "Scan was cancelled \u2014 aborting."));
|
|
7029
7274
|
process.exit(0);
|
|
7030
7275
|
}
|
|
7031
7276
|
} catch {
|
|
@@ -7039,9 +7284,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7039
7284
|
apiUrl: config.apiUrl,
|
|
7040
7285
|
apiKey: config.apiKey,
|
|
7041
7286
|
runBrowse: runBrowseCommand2,
|
|
7042
|
-
onLog:
|
|
7287
|
+
onLog: log3,
|
|
7043
7288
|
onProgress: (phase, detail) => {
|
|
7044
|
-
|
|
7289
|
+
log3(`${paint10("dim", `[${phase}]`)} ${detail}`);
|
|
7045
7290
|
},
|
|
7046
7291
|
customPrompt
|
|
7047
7292
|
});
|
|
@@ -7054,7 +7299,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7054
7299
|
} catch {
|
|
7055
7300
|
}
|
|
7056
7301
|
if (wasCancelled) {
|
|
7057
|
-
|
|
7302
|
+
log3(paint10("yellow", "Scan was cancelled by user \u2014 discarding results."));
|
|
7058
7303
|
process.exit(0);
|
|
7059
7304
|
}
|
|
7060
7305
|
await api.patch(`/api/scans/${reportId}`, {
|
|
@@ -7065,37 +7310,37 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7065
7310
|
scanDurationMs: result.scanDurationMs,
|
|
7066
7311
|
routesCrawled: result.routesCrawled
|
|
7067
7312
|
});
|
|
7068
|
-
|
|
7313
|
+
logOk3(`Scan complete \u2014 ${paint10("cyan", String(result.findings.length))} findings`);
|
|
7069
7314
|
console.log("");
|
|
7070
|
-
console.log(` ${
|
|
7315
|
+
console.log(` ${paint10("bold", "Summary:")} ${result.summary}`);
|
|
7071
7316
|
console.log("");
|
|
7072
7317
|
const high = result.findings.filter((f) => f.priority === "high");
|
|
7073
7318
|
const medium = result.findings.filter((f) => f.priority === "medium");
|
|
7074
7319
|
const low = result.findings.filter((f) => f.priority === "low");
|
|
7075
7320
|
if (high.length > 0) {
|
|
7076
|
-
console.log(` ${
|
|
7321
|
+
console.log(` ${paint10("bold", paint10("red", `High Priority (${high.length})`))}`);
|
|
7077
7322
|
for (const f of high) {
|
|
7078
|
-
console.log(` ${
|
|
7079
|
-
console.log(` ${
|
|
7323
|
+
console.log(` ${paint10("red", "\u25CF")} [${f.type}] ${f.title}`);
|
|
7324
|
+
console.log(` ${paint10("dim", f.description.slice(0, 120))}`);
|
|
7080
7325
|
}
|
|
7081
7326
|
console.log("");
|
|
7082
7327
|
}
|
|
7083
7328
|
if (medium.length > 0) {
|
|
7084
|
-
console.log(` ${
|
|
7329
|
+
console.log(` ${paint10("bold", paint10("yellow", `Medium Priority (${medium.length})`))}`);
|
|
7085
7330
|
for (const f of medium) {
|
|
7086
|
-
console.log(` ${
|
|
7331
|
+
console.log(` ${paint10("yellow", "\u25CF")} [${f.type}] ${f.title}`);
|
|
7087
7332
|
}
|
|
7088
7333
|
console.log("");
|
|
7089
7334
|
}
|
|
7090
7335
|
if (low.length > 0) {
|
|
7091
|
-
console.log(` ${
|
|
7336
|
+
console.log(` ${paint10("dim", `Low Priority (${low.length})`)} `);
|
|
7092
7337
|
for (const f of low) {
|
|
7093
|
-
console.log(` ${
|
|
7338
|
+
console.log(` ${paint10("dim", `\u25CB [${f.type}] ${f.title}`)}`);
|
|
7094
7339
|
}
|
|
7095
7340
|
console.log("");
|
|
7096
7341
|
}
|
|
7097
7342
|
} catch (err) {
|
|
7098
|
-
|
|
7343
|
+
logErr3(`Scan failed: ${err.message}`);
|
|
7099
7344
|
try {
|
|
7100
7345
|
await api.patch(`/api/scans/${reportId}`, {
|
|
7101
7346
|
status: "failed",
|
|
@@ -7108,13 +7353,13 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7108
7353
|
});
|
|
7109
7354
|
|
|
7110
7355
|
// cli/commands/doctor.ts
|
|
7111
|
-
import { Command as
|
|
7112
|
-
import { existsSync as
|
|
7356
|
+
import { Command as Command28 } from "commander";
|
|
7357
|
+
import { existsSync as existsSync17 } from "fs";
|
|
7113
7358
|
import { homedir as homedir2 } from "os";
|
|
7114
7359
|
import { join as join11 } from "path";
|
|
7115
7360
|
async function checkConfigExists() {
|
|
7116
7361
|
const configPath2 = join11(homedir2(), ".mr-manager", "config.json");
|
|
7117
|
-
const exists =
|
|
7362
|
+
const exists = existsSync17(configPath2);
|
|
7118
7363
|
if (!exists) {
|
|
7119
7364
|
return {
|
|
7120
7365
|
name: "Config file",
|
|
@@ -7156,7 +7401,7 @@ async function checkProjectLink() {
|
|
|
7156
7401
|
optional: true
|
|
7157
7402
|
};
|
|
7158
7403
|
}
|
|
7159
|
-
var doctorCommand = new
|
|
7404
|
+
var doctorCommand = new Command28("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
7160
7405
|
const banner = [
|
|
7161
7406
|
``,
|
|
7162
7407
|
paint5("cyan", ` MR DOCTOR`),
|
|
@@ -7187,7 +7432,7 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
|
|
|
7187
7432
|
console.log("");
|
|
7188
7433
|
return;
|
|
7189
7434
|
}
|
|
7190
|
-
const fixes = checks.filter((
|
|
7435
|
+
const fixes = checks.filter((c14) => !c14.ok && c14.fix && !c14.optional);
|
|
7191
7436
|
if (fixes.length > 0) {
|
|
7192
7437
|
console.log(paint5("yellow", " To fix:"));
|
|
7193
7438
|
for (const fix of fixes) {
|
|
@@ -7199,14 +7444,14 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
|
|
|
7199
7444
|
});
|
|
7200
7445
|
|
|
7201
7446
|
// cli/commands/prompt-audit.ts
|
|
7202
|
-
import { Command as
|
|
7447
|
+
import { Command as Command29 } from "commander";
|
|
7203
7448
|
import { resolve as resolve8 } from "path";
|
|
7204
|
-
import { existsSync as
|
|
7449
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
|
|
7205
7450
|
function auditLine(label, tokens) {
|
|
7206
7451
|
const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
|
|
7207
7452
|
return ` ${label.padEnd(30)} ${formatTokenCount(tokens).padStart(8)} ${bar}`;
|
|
7208
7453
|
}
|
|
7209
|
-
var promptAuditCommand = new
|
|
7454
|
+
var promptAuditCommand = new Command29("prompt-audit").description("Dry-run prompt construction and report estimated token counts by job type").option("--task <id>", "Audit prompts for a specific task ID").option("--all", "Audit all supported job types with representative data", false).option("--json", "Output as JSON instead of plain text", false).action(async (opts) => {
|
|
7210
7455
|
const results = [];
|
|
7211
7456
|
if (opts.task) {
|
|
7212
7457
|
try {
|
|
@@ -7260,7 +7505,7 @@ ${task.notes}` : "";
|
|
|
7260
7505
|
const repoDir = Object.entries(config.directories).find(([, pid]) => pid === task.projectId)?.[0];
|
|
7261
7506
|
if (repoDir) {
|
|
7262
7507
|
const featuresPath = resolve8(repoDir, ".mr-features.md");
|
|
7263
|
-
if (
|
|
7508
|
+
if (existsSync18(featuresPath)) {
|
|
7264
7509
|
const featuresContent = readFileSync12(featuresPath, "utf-8");
|
|
7265
7510
|
sections.push({ name: "features-doc", tokens: estimateTokens(featuresContent) });
|
|
7266
7511
|
}
|
|
@@ -7415,9 +7660,9 @@ ${r.jobType} [${r.identifier}]`);
|
|
|
7415
7660
|
});
|
|
7416
7661
|
|
|
7417
7662
|
// cli/commands/skill.ts
|
|
7418
|
-
import { Command as
|
|
7663
|
+
import { Command as Command30 } from "commander";
|
|
7419
7664
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
7420
|
-
var
|
|
7665
|
+
var c11 = {
|
|
7421
7666
|
reset: "\x1B[0m",
|
|
7422
7667
|
bold: "\x1B[1m",
|
|
7423
7668
|
dim: "\x1B[2m",
|
|
@@ -7450,7 +7695,7 @@ function formatSize(bytes) {
|
|
|
7450
7695
|
if (bytes < 1024) return `${bytes}b`;
|
|
7451
7696
|
return `${(bytes / 1024).toFixed(1)}kb`;
|
|
7452
7697
|
}
|
|
7453
|
-
var skillCommand = new
|
|
7698
|
+
var skillCommand = new Command30("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
|
|
7454
7699
|
skillCommand.command("list").alias("ls").description("List all skills").option("--category <category>", "Filter by category").action(async (opts) => {
|
|
7455
7700
|
const params = new URLSearchParams();
|
|
7456
7701
|
if (opts.category) params.set("category", opts.category);
|
|
@@ -7458,17 +7703,17 @@ skillCommand.command("list").alias("ls").description("List all skills").option("
|
|
|
7458
7703
|
`/api/skills${params.toString() ? `?${params}` : ""}`
|
|
7459
7704
|
);
|
|
7460
7705
|
if (skills.length === 0) {
|
|
7461
|
-
console.log(`${
|
|
7706
|
+
console.log(`${c11.dim}No skills found.${c11.reset}`);
|
|
7462
7707
|
return;
|
|
7463
7708
|
}
|
|
7464
7709
|
for (const skill of skills) {
|
|
7465
|
-
const cat = skill.category ? ` ${
|
|
7466
|
-
const scope = skill.projectId ? ` ${
|
|
7467
|
-
console.log(` ${
|
|
7710
|
+
const cat = skill.category ? ` ${c11.dim}[${skill.category}]${c11.reset}` : "";
|
|
7711
|
+
const scope = skill.projectId ? ` ${c11.dim}(project)${c11.reset}` : ` ${c11.dim}(global)${c11.reset}`;
|
|
7712
|
+
console.log(` ${c11.cyan}${skill.name}${c11.reset}${cat}${scope}`);
|
|
7468
7713
|
if (skill.description) {
|
|
7469
|
-
console.log(` ${
|
|
7714
|
+
console.log(` ${c11.dim}${skill.description}${c11.reset}`);
|
|
7470
7715
|
}
|
|
7471
|
-
console.log(` ${
|
|
7716
|
+
console.log(` ${c11.dim}id: ${skill.id}${c11.reset}`);
|
|
7472
7717
|
}
|
|
7473
7718
|
});
|
|
7474
7719
|
skillCommand.command("create").description("Create a new skill from a markdown file or inline content").argument("<name>", "Skill name").option("-d, --description <desc>", "Short description").option("-c, --category <cat>", "Category (e.g. Deployment, Testing)").option("-f, --file <path>", "Read content from a markdown file").option("--content <text>", "Inline markdown content").option("-p, --project", "Scope to the linked project").action(async (name, opts) => {
|
|
@@ -7504,7 +7749,7 @@ skillCommand.command("create").description("Create a new skill from a markdown f
|
|
|
7504
7749
|
projectId
|
|
7505
7750
|
});
|
|
7506
7751
|
console.log(
|
|
7507
|
-
`${
|
|
7752
|
+
`${c11.green}Created skill:${c11.reset} ${c11.bold}${skill.name}${c11.reset} ${c11.dim}(${skill.id})${c11.reset}`
|
|
7508
7753
|
);
|
|
7509
7754
|
});
|
|
7510
7755
|
skillCommand.command("update").description(
|
|
@@ -7565,14 +7810,14 @@ skillCommand.command("update").description(
|
|
|
7565
7810
|
const beforeContent = existing.content ?? "";
|
|
7566
7811
|
const afterContent = typeof patch.content === "string" ? patch.content : beforeContent;
|
|
7567
7812
|
const contentChanged = typeof patch.content === "string" && patch.content !== beforeContent;
|
|
7568
|
-
const sizeDiff = typeof patch.content === "string" ? ` ${
|
|
7813
|
+
const sizeDiff = typeof patch.content === "string" ? ` ${c11.dim}(${formatSize(Buffer.byteLength(beforeContent, "utf-8"))} -> ${formatSize(Buffer.byteLength(afterContent, "utf-8"))})${c11.reset}` : "";
|
|
7569
7814
|
const priorRevisions = existing.revisions?.length ?? 0;
|
|
7570
7815
|
const revisionsAfter = priorRevisions + (contentChanged ? 1 : 0);
|
|
7571
|
-
const revisionNote = contentChanged ? ` ${
|
|
7816
|
+
const revisionNote = contentChanged ? ` ${c11.dim}kept ${revisionsAfter} prior revision${revisionsAfter === 1 ? "" : "s"}${c11.reset}` : "";
|
|
7572
7817
|
console.log(
|
|
7573
|
-
`${
|
|
7818
|
+
`${c11.green}Updated skill:${c11.reset} ${c11.bold}${updated.name}${c11.reset}${sizeDiff}${revisionNote}`
|
|
7574
7819
|
);
|
|
7575
|
-
console.log(` ${
|
|
7820
|
+
console.log(` ${c11.dim}id: ${updated.id}${c11.reset}`);
|
|
7576
7821
|
});
|
|
7577
7822
|
skillCommand.command("delete").alias("rm").description("Delete a skill").argument("<idOrName>", "Skill id or name").option("--yes", "Skip confirmation prompt").action(async (idOrName, opts) => {
|
|
7578
7823
|
let skill;
|
|
@@ -7585,7 +7830,7 @@ skillCommand.command("delete").alias("rm").description("Delete a skill").argumen
|
|
|
7585
7830
|
if (!opts.yes) {
|
|
7586
7831
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
7587
7832
|
const answer = (await rl.question(
|
|
7588
|
-
`Delete skill ${
|
|
7833
|
+
`Delete skill ${c11.bold}${skill.name}${c11.reset} ${c11.dim}(${skill.id})${c11.reset}? [y/N] `
|
|
7589
7834
|
)).trim().toLowerCase();
|
|
7590
7835
|
rl.close();
|
|
7591
7836
|
if (answer !== "y" && answer !== "yes") {
|
|
@@ -7595,7 +7840,7 @@ skillCommand.command("delete").alias("rm").description("Delete a skill").argumen
|
|
|
7595
7840
|
}
|
|
7596
7841
|
await api.del(`/api/skills/${skill.id}`);
|
|
7597
7842
|
console.log(
|
|
7598
|
-
`${
|
|
7843
|
+
`${c11.yellow}Deleted skill:${c11.reset} ${c11.bold}${skill.name}${c11.reset} ${c11.dim}(${skill.id})${c11.reset}`
|
|
7599
7844
|
);
|
|
7600
7845
|
});
|
|
7601
7846
|
skillCommand.command("generate").alias("gen").description("Generate a new skill using AI from a text prompt").argument("<prompt>", "Describe the skill to generate").option("-p, --project", "Scope to the linked project").action(async (prompt2, opts) => {
|
|
@@ -7609,22 +7854,22 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
7609
7854
|
process.exit(1);
|
|
7610
7855
|
}
|
|
7611
7856
|
}
|
|
7612
|
-
console.log(`${
|
|
7857
|
+
console.log(`${c11.dim}Generating skill...${c11.reset}`);
|
|
7613
7858
|
try {
|
|
7614
7859
|
const skill = await api.post("/api/skills/generate", {
|
|
7615
7860
|
prompt: prompt2,
|
|
7616
7861
|
projectId
|
|
7617
7862
|
});
|
|
7618
7863
|
console.log(
|
|
7619
|
-
`${
|
|
7864
|
+
`${c11.green}Generated skill:${c11.reset} ${c11.bold}${skill.name}${c11.reset}`
|
|
7620
7865
|
);
|
|
7621
7866
|
if (skill.description) {
|
|
7622
|
-
console.log(` ${
|
|
7867
|
+
console.log(` ${c11.dim}${skill.description}${c11.reset}`);
|
|
7623
7868
|
}
|
|
7624
7869
|
if (skill.category) {
|
|
7625
|
-
console.log(` ${
|
|
7870
|
+
console.log(` ${c11.dim}Category: ${skill.category}${c11.reset}`);
|
|
7626
7871
|
}
|
|
7627
|
-
console.log(` ${
|
|
7872
|
+
console.log(` ${c11.dim}id: ${skill.id}${c11.reset}`);
|
|
7628
7873
|
} catch (err) {
|
|
7629
7874
|
console.error(`Failed to generate skill: ${err.message}`);
|
|
7630
7875
|
process.exit(1);
|
|
@@ -7632,8 +7877,8 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
7632
7877
|
});
|
|
7633
7878
|
|
|
7634
7879
|
// cli/commands/resource.ts
|
|
7635
|
-
import { Command as
|
|
7636
|
-
var
|
|
7880
|
+
import { Command as Command31 } from "commander";
|
|
7881
|
+
var c12 = {
|
|
7637
7882
|
reset: "\x1B[0m",
|
|
7638
7883
|
bold: "\x1B[1m",
|
|
7639
7884
|
dim: "\x1B[2m",
|
|
@@ -7643,16 +7888,16 @@ var c11 = {
|
|
|
7643
7888
|
magenta: "\x1B[35m"
|
|
7644
7889
|
};
|
|
7645
7890
|
var TYPE_COLORS = {
|
|
7646
|
-
plan:
|
|
7647
|
-
research:
|
|
7648
|
-
"test-plan":
|
|
7649
|
-
note:
|
|
7891
|
+
plan: c12.cyan,
|
|
7892
|
+
research: c12.magenta,
|
|
7893
|
+
"test-plan": c12.yellow,
|
|
7894
|
+
note: c12.green
|
|
7650
7895
|
};
|
|
7651
7896
|
function typeLabel(type) {
|
|
7652
|
-
const color = TYPE_COLORS[type] ??
|
|
7653
|
-
return `${color}${type}${
|
|
7897
|
+
const color = TYPE_COLORS[type] ?? c12.dim;
|
|
7898
|
+
return `${color}${type}${c12.reset}`;
|
|
7654
7899
|
}
|
|
7655
|
-
var resourceCommand = new
|
|
7900
|
+
var resourceCommand = new Command31("resource").description("Manage resources \u2014 documents, plans, research, and notes");
|
|
7656
7901
|
resourceCommand.command("list").alias("ls").description("List resources for the linked project (or all)").option("--all", "List all resources across projects").action(async (opts) => {
|
|
7657
7902
|
const params = new URLSearchParams();
|
|
7658
7903
|
if (opts.all) {
|
|
@@ -7665,13 +7910,13 @@ resourceCommand.command("list").alias("ls").description("List resources for the
|
|
|
7665
7910
|
`/api/resources${params.toString() ? `?${params}` : ""}`
|
|
7666
7911
|
);
|
|
7667
7912
|
if (resources.length === 0) {
|
|
7668
|
-
console.log(`${
|
|
7913
|
+
console.log(`${c12.dim}No resources found.${c12.reset}`);
|
|
7669
7914
|
return;
|
|
7670
7915
|
}
|
|
7671
7916
|
for (const r of resources) {
|
|
7672
|
-
const project = r.projectName ? ` ${
|
|
7673
|
-
console.log(` ${typeLabel(r.type)} ${
|
|
7674
|
-
console.log(` ${
|
|
7917
|
+
const project = r.projectName ? ` ${c12.dim}[${r.projectName}]${c12.reset}` : "";
|
|
7918
|
+
console.log(` ${typeLabel(r.type)} ${c12.bold}${r.title}${c12.reset}${project}`);
|
|
7919
|
+
console.log(` ${c12.dim}id: ${r.id}${c12.reset}`);
|
|
7675
7920
|
}
|
|
7676
7921
|
});
|
|
7677
7922
|
resourceCommand.command("create").description("Create a new resource from a file or inline content").argument("<title>", "Resource title").option("-t, --type <type>", "Resource type (note, plan, research, test-plan)", "note").option("-f, --file <path>", "Read content from a file").option("--content <text>", "Inline content").option("--task <taskId>", "Attach to an existing task instead of creating a standalone resource").option("-p, --project", "Scope to the linked project").action(async (title, opts) => {
|
|
@@ -7696,9 +7941,9 @@ resourceCommand.command("create").description("Create a new resource from a file
|
|
|
7696
7941
|
content: content.trim()
|
|
7697
7942
|
});
|
|
7698
7943
|
console.log(
|
|
7699
|
-
`${
|
|
7944
|
+
`${c12.green}Created resource:${c12.reset} ${c12.bold}${title}${c12.reset} ${c12.dim}(${resource.id})${c12.reset}`
|
|
7700
7945
|
);
|
|
7701
|
-
console.log(` ${
|
|
7946
|
+
console.log(` ${c12.dim}Attached to task ${opts.task}${c12.reset}`);
|
|
7702
7947
|
return;
|
|
7703
7948
|
}
|
|
7704
7949
|
let projectId;
|
|
@@ -7718,7 +7963,7 @@ resourceCommand.command("create").description("Create a new resource from a file
|
|
|
7718
7963
|
projectId
|
|
7719
7964
|
});
|
|
7720
7965
|
console.log(
|
|
7721
|
-
`${
|
|
7966
|
+
`${c12.green}Created resource:${c12.reset} ${c12.bold}${title}${c12.reset} ${c12.dim}(${result.resource.id})${c12.reset}`
|
|
7722
7967
|
);
|
|
7723
7968
|
});
|
|
7724
7969
|
resourceCommand.command("generate").alias("gen").description("Generate a resource using AI from a text prompt").argument("<prompt>", "Describe the resource to generate").option("-t, --type <type>", "Resource type (plan or research)", "research").option("-p, --project", "Scope to the linked project").action(async (prompt2, opts) => {
|
|
@@ -7737,30 +7982,30 @@ resourceCommand.command("generate").alias("gen").description("Generate a resourc
|
|
|
7737
7982
|
process.exit(1);
|
|
7738
7983
|
}
|
|
7739
7984
|
}
|
|
7740
|
-
console.log(`${
|
|
7985
|
+
console.log(`${c12.dim}Generating ${type}...${c12.reset}`);
|
|
7741
7986
|
const result = await api.post("/api/resources", {
|
|
7742
7987
|
type,
|
|
7743
7988
|
prompt: prompt2,
|
|
7744
7989
|
projectId
|
|
7745
7990
|
});
|
|
7746
7991
|
console.log(
|
|
7747
|
-
`${
|
|
7992
|
+
`${c12.green}Queued:${c12.reset} ${c12.bold}${result.task.title}${c12.reset}`
|
|
7748
7993
|
);
|
|
7749
|
-
console.log(` ${
|
|
7994
|
+
console.log(` ${c12.dim}task: ${result.task.id}${c12.reset}`);
|
|
7750
7995
|
});
|
|
7751
7996
|
|
|
7752
7997
|
// cli/commands/tests.ts
|
|
7753
|
-
import { Command as
|
|
7754
|
-
var
|
|
7998
|
+
import { Command as Command32 } from "commander";
|
|
7999
|
+
var c13 = {
|
|
7755
8000
|
reset: "\x1B[0m",
|
|
7756
8001
|
dim: "\x1B[2m",
|
|
7757
8002
|
yellow: "\x1B[33m"
|
|
7758
8003
|
};
|
|
7759
|
-
var testsCommand = new
|
|
8004
|
+
var testsCommand = new Command32("tests").description("List MR Test scenarios for the linked project").action(async () => {
|
|
7760
8005
|
const projectId = getLinkedProjectId();
|
|
7761
8006
|
if (!projectId) {
|
|
7762
8007
|
console.error(
|
|
7763
|
-
`${
|
|
8008
|
+
`${c13.yellow}No project linked to this directory.${c13.reset} Run "mr link <project-id>" first.`
|
|
7764
8009
|
);
|
|
7765
8010
|
process.exit(1);
|
|
7766
8011
|
}
|
|
@@ -7773,13 +8018,13 @@ var testsCommand = new Command31("tests").description("List MR Test scenarios fo
|
|
|
7773
8018
|
process.exit(1);
|
|
7774
8019
|
}
|
|
7775
8020
|
if (scenarios.length === 0) {
|
|
7776
|
-
console.log(`${
|
|
8021
|
+
console.log(`${c13.dim}No test scenarios found for this project.${c13.reset}`);
|
|
7777
8022
|
return;
|
|
7778
8023
|
}
|
|
7779
8024
|
for (const scenario of scenarios) {
|
|
7780
8025
|
console.log(`### ${scenario.name}`);
|
|
7781
8026
|
if (scenario.description) {
|
|
7782
|
-
console.log(`${
|
|
8027
|
+
console.log(`${c13.dim}${scenario.description}${c13.reset}`);
|
|
7783
8028
|
console.log();
|
|
7784
8029
|
}
|
|
7785
8030
|
console.log(scenario.content);
|
|
@@ -7788,9 +8033,9 @@ var testsCommand = new Command31("tests").description("List MR Test scenarios fo
|
|
|
7788
8033
|
});
|
|
7789
8034
|
|
|
7790
8035
|
// cli/commands/walkthrough.ts
|
|
7791
|
-
import { Command as
|
|
8036
|
+
import { Command as Command33 } from "commander";
|
|
7792
8037
|
var SKILL_NAME = "Generate Walkthrough Video";
|
|
7793
|
-
var walkthroughCommand = new
|
|
8038
|
+
var walkthroughCommand = new Command33("walkthrough").description("Attach the Generate Walkthrough Video skill to a task and queue it").argument("<task-id>", "Task ID to generate a walkthrough for").option("--prod", "Allow recording against production (requires explicit confirmation)").action(async (taskId, opts) => {
|
|
7794
8039
|
const skills = await api.get("/api/skills");
|
|
7795
8040
|
const skill = skills.find((s) => s.name === SKILL_NAME);
|
|
7796
8041
|
if (!skill) {
|
|
@@ -7818,12 +8063,12 @@ var walkthroughCommand = new Command32("walkthrough").description("Attach the Ge
|
|
|
7818
8063
|
|
|
7819
8064
|
// cli/index.ts
|
|
7820
8065
|
var configPath = join12(homedir3(), ".mr-manager", "config.json");
|
|
7821
|
-
var isFirstRun = !
|
|
8066
|
+
var isFirstRun = !existsSync19(configPath);
|
|
7822
8067
|
var userArgs = process.argv.slice(2);
|
|
7823
8068
|
var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
|
|
7824
8069
|
var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
|
|
7825
8070
|
if (isFirstRun && !shouldBypass) {
|
|
7826
|
-
const
|
|
8071
|
+
const c14 = {
|
|
7827
8072
|
reset: "\x1B[0m",
|
|
7828
8073
|
bold: "\x1B[1m",
|
|
7829
8074
|
dim: "\x1B[2m",
|
|
@@ -7833,28 +8078,28 @@ if (isFirstRun && !shouldBypass) {
|
|
|
7833
8078
|
magenta: "\x1B[35m"
|
|
7834
8079
|
};
|
|
7835
8080
|
console.log("");
|
|
7836
|
-
console.log(`${
|
|
7837
|
-
console.log(`${
|
|
7838
|
-
console.log(`${
|
|
7839
|
-
console.log(`${
|
|
8081
|
+
console.log(`${c14.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${c14.reset}`);
|
|
8082
|
+
console.log(`${c14.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${c14.reset}`);
|
|
8083
|
+
console.log(`${c14.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c14.reset}`);
|
|
8084
|
+
console.log(`${c14.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${c14.reset}`);
|
|
7840
8085
|
console.log("");
|
|
7841
|
-
console.log(`${
|
|
7842
|
-
console.log(`${
|
|
8086
|
+
console.log(`${c14.bold} Welcome to Mr. Manager!${c14.reset}`);
|
|
8087
|
+
console.log(`${c14.dim} Let's get you set up in a few quick steps.${c14.reset}`);
|
|
7843
8088
|
console.log("");
|
|
7844
|
-
console.log(` ${
|
|
7845
|
-
console.log(` ${
|
|
8089
|
+
console.log(` ${c14.yellow}Step 1:${c14.reset} Authenticate via Google OAuth`);
|
|
8090
|
+
console.log(` ${c14.dim}Run:${c14.reset} ${c14.cyan}mr login${c14.reset}`);
|
|
7846
8091
|
console.log("");
|
|
7847
|
-
console.log(` ${
|
|
7848
|
-
console.log(` ${
|
|
8092
|
+
console.log(` ${c14.yellow}Step 2:${c14.reset} Verify your environment`);
|
|
8093
|
+
console.log(` ${c14.dim}Run:${c14.reset} ${c14.cyan}mr setup${c14.reset}`);
|
|
7849
8094
|
console.log("");
|
|
7850
|
-
console.log(` ${
|
|
7851
|
-
console.log(` ${
|
|
8095
|
+
console.log(` ${c14.yellow}Step 3:${c14.reset} Link a repo and start watching`);
|
|
8096
|
+
console.log(` ${c14.dim}Run:${c14.reset} ${c14.cyan}mr link${c14.reset} ${c14.dim}&&${c14.reset} ${c14.cyan}mr watch${c14.reset}`);
|
|
7852
8097
|
console.log("");
|
|
7853
|
-
console.log(`${
|
|
8098
|
+
console.log(`${c14.dim} Or run ${c14.reset}${c14.cyan}mr login${c14.reset}${c14.dim} to get started now.${c14.reset}`);
|
|
7854
8099
|
console.log("");
|
|
7855
8100
|
process.exit(0);
|
|
7856
8101
|
}
|
|
7857
|
-
var program = new
|
|
8102
|
+
var program = new Command34();
|
|
7858
8103
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
7859
8104
|
program.addCommand(initCommand);
|
|
7860
8105
|
program.addCommand(authCommand);
|