@dunnewold-labs/mr-manager 0.4.40 → 0.4.42
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 +473 -221
- 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.42",
|
|
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;
|
|
@@ -3121,12 +3121,19 @@ var watchCommand = new Command9("watch").description(
|
|
|
3121
3121
|
if (code === 0) {
|
|
3122
3122
|
try {
|
|
3123
3123
|
const noMrPath = resolve2(executionDir, ".mr-no-mr");
|
|
3124
|
-
|
|
3124
|
+
let noMrRequested = false;
|
|
3125
3125
|
let noMrDescription;
|
|
3126
|
-
if (
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3126
|
+
if (existsSync7(noMrPath)) {
|
|
3127
|
+
const noMrMtime = statSync(noMrPath).mtimeMs;
|
|
3128
|
+
if (noMrMtime >= activeEntry.startedAt) {
|
|
3129
|
+
noMrRequested = true;
|
|
3130
|
+
noMrDescription = readFileSync5(noMrPath, "utf-8").trim();
|
|
3131
|
+
unlinkSync(noMrPath);
|
|
3132
|
+
logSuccess(prefix, `No ${vcs === "gitlab" ? "MR" : "PR"} needed \u2014 ${noMrDescription}`);
|
|
3133
|
+
} else {
|
|
3134
|
+
unlinkSync(noMrPath);
|
|
3135
|
+
logWarn(prefix, `Ignoring stale .mr-no-mr left in ${executionDir} (written before this run)`);
|
|
3136
|
+
}
|
|
3130
3137
|
}
|
|
3131
3138
|
const prLabel = vcs === "gitlab" ? "MR" : "PR";
|
|
3132
3139
|
let prUrl = null;
|
|
@@ -4939,7 +4946,7 @@ async function checkApiConnectivity() {
|
|
|
4939
4946
|
}
|
|
4940
4947
|
}
|
|
4941
4948
|
function printResults(checks) {
|
|
4942
|
-
const maxNameLen = Math.max(...checks.map((
|
|
4949
|
+
const maxNameLen = Math.max(...checks.map((c14) => c14.name.length));
|
|
4943
4950
|
let allOk = true;
|
|
4944
4951
|
for (const check of checks) {
|
|
4945
4952
|
const isOptional = check.optional ?? false;
|
|
@@ -4952,16 +4959,16 @@ function printResults(checks) {
|
|
|
4952
4959
|
return allOk;
|
|
4953
4960
|
}
|
|
4954
4961
|
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((
|
|
4962
|
+
const { spawn: spawn10 } = await import("child_process");
|
|
4963
|
+
const ghInstalled = checks.find((c14) => c14.name === "GitHub CLI (gh)").ok;
|
|
4964
|
+
const ghAuthed = checks.find((c14) => c14.name === "GitHub CLI auth").ok;
|
|
4965
|
+
const mrAuthed = checks.find((c14) => c14.name === "Mr. Manager CLI auth").ok;
|
|
4966
|
+
const claudeCheck = checks.find((c14) => c14.name === "Claude Code (claude)");
|
|
4960
4967
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
4961
4968
|
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
4962
4969
|
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
4963
4970
|
await new Promise((resolve9) => {
|
|
4964
|
-
const child =
|
|
4971
|
+
const child = spawn10("bash", ["-c", "curl -fsSL https://claude.ai/install.sh | bash"], { stdio: "inherit" });
|
|
4965
4972
|
child.on("exit", () => resolve9());
|
|
4966
4973
|
});
|
|
4967
4974
|
console.log("");
|
|
@@ -4969,7 +4976,7 @@ async function autoFix(checks, agent) {
|
|
|
4969
4976
|
if (ghInstalled && !ghAuthed) {
|
|
4970
4977
|
console.log(paint5("cyan", " Running gh auth login..."));
|
|
4971
4978
|
await new Promise((resolve9) => {
|
|
4972
|
-
const child =
|
|
4979
|
+
const child = spawn10("gh", ["auth", "login"], { stdio: "inherit" });
|
|
4973
4980
|
child.on("exit", () => resolve9());
|
|
4974
4981
|
});
|
|
4975
4982
|
console.log("");
|
|
@@ -4978,7 +4985,7 @@ async function autoFix(checks, agent) {
|
|
|
4978
4985
|
console.log(paint5("cyan", " Running mr login..."));
|
|
4979
4986
|
const entry = process.argv[1];
|
|
4980
4987
|
await new Promise((resolve9) => {
|
|
4981
|
-
const child =
|
|
4988
|
+
const child = spawn10(process.execPath, [entry, "login"], { stdio: "inherit" });
|
|
4982
4989
|
child.on("exit", () => resolve9());
|
|
4983
4990
|
});
|
|
4984
4991
|
console.log("");
|
|
@@ -5019,7 +5026,7 @@ var setupCommand = new Command16("setup").description("Check that all dependenci
|
|
|
5019
5026
|
console.log("");
|
|
5020
5027
|
return;
|
|
5021
5028
|
}
|
|
5022
|
-
const fixes = checks.filter((
|
|
5029
|
+
const fixes = checks.filter((c14) => !c14.ok && c14.fix && !c14.optional);
|
|
5023
5030
|
if (fixes.length > 0) {
|
|
5024
5031
|
console.log(paint5("yellow", " To fix:"));
|
|
5025
5032
|
for (const fix of fixes) {
|
|
@@ -5763,20 +5770,24 @@ var noMrCommand = new Command24("no-mr").description("Signal that a task does no
|
|
|
5763
5770
|
});
|
|
5764
5771
|
|
|
5765
5772
|
// cli/commands/review.ts
|
|
5773
|
+
import { Command as Command26 } from "commander";
|
|
5774
|
+
import { spawn as spawn8, execSync as execSync6 } from "child_process";
|
|
5775
|
+
import { existsSync as existsSync14, statSync as statSync2 } from "fs";
|
|
5776
|
+
|
|
5777
|
+
// cli/commands/review-apply.ts
|
|
5766
5778
|
import { Command as Command25 } from "commander";
|
|
5767
5779
|
import { spawn as spawn7, execSync as execSync5 } from "child_process";
|
|
5768
|
-
import { existsSync as existsSync13
|
|
5780
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5769
5781
|
var c8 = {
|
|
5770
5782
|
reset: "\x1B[0m",
|
|
5771
|
-
bold: "\x1B[1m",
|
|
5772
5783
|
dim: "\x1B[2m",
|
|
5773
5784
|
cyan: "\x1B[36m",
|
|
5774
5785
|
green: "\x1B[32m",
|
|
5775
5786
|
yellow: "\x1B[33m",
|
|
5776
5787
|
red: "\x1B[31m",
|
|
5777
|
-
|
|
5788
|
+
blue: "\x1B[34m",
|
|
5778
5789
|
gray: "\x1B[90m",
|
|
5779
|
-
|
|
5790
|
+
magenta: "\x1B[35m"
|
|
5780
5791
|
};
|
|
5781
5792
|
function paint8(color, text) {
|
|
5782
5793
|
return `${c8[color]}${text}${c8.reset}`;
|
|
@@ -5785,7 +5796,7 @@ function timestamp2() {
|
|
|
5785
5796
|
return paint8("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
5786
5797
|
}
|
|
5787
5798
|
function tag() {
|
|
5788
|
-
return paint8("blue", "[review]");
|
|
5799
|
+
return paint8("blue", "[review apply]");
|
|
5789
5800
|
}
|
|
5790
5801
|
function log(msg) {
|
|
5791
5802
|
console.log(`${timestamp2()} ${tag()} ${msg}`);
|
|
@@ -5796,32 +5807,270 @@ function logOk(msg) {
|
|
|
5796
5807
|
function logErr(msg) {
|
|
5797
5808
|
console.error(`${timestamp2()} ${tag()} ${paint8("red", "\u2717")} ${msg}`);
|
|
5798
5809
|
}
|
|
5799
|
-
var
|
|
5810
|
+
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
5811
|
const config = loadConfig();
|
|
5801
5812
|
if (!config.apiKey) {
|
|
5802
5813
|
logErr('Not authenticated. Run "mr login" first.');
|
|
5803
5814
|
process.exit(1);
|
|
5804
5815
|
}
|
|
5816
|
+
let review;
|
|
5817
|
+
try {
|
|
5818
|
+
review = await api.get(`/api/reviews/${id}`);
|
|
5819
|
+
} catch (err) {
|
|
5820
|
+
logErr(`Could not load review ${id}: ${err.message}`);
|
|
5821
|
+
process.exit(1);
|
|
5822
|
+
}
|
|
5823
|
+
const comments = review.comments ?? [];
|
|
5824
|
+
const findings = review.findings ?? [];
|
|
5825
|
+
const excluded = new Set(review.excludedFiles ?? []);
|
|
5826
|
+
if (comments.length === 0 && findings.length === 0) {
|
|
5827
|
+
logErr("Review has no comments or findings to act on.");
|
|
5828
|
+
process.exit(1);
|
|
5829
|
+
}
|
|
5830
|
+
const actionableComments = comments.filter((c14) => !excluded.has(c14.file));
|
|
5831
|
+
const actionableFindings = findings.filter((f) => !excluded.has(f.file));
|
|
5832
|
+
if (actionableComments.length === 0 && actionableFindings.length === 0) {
|
|
5833
|
+
logErr("All files with comments/findings are excluded \u2014 nothing to do.");
|
|
5834
|
+
process.exit(1);
|
|
5835
|
+
}
|
|
5836
|
+
let project;
|
|
5837
|
+
try {
|
|
5838
|
+
project = await api.get(`/api/projects/${review.projectId}`);
|
|
5839
|
+
} catch {
|
|
5840
|
+
logErr(`Could not load project ${review.projectId}`);
|
|
5841
|
+
process.exit(1);
|
|
5842
|
+
}
|
|
5843
|
+
let projectPath = project.localPath;
|
|
5844
|
+
if (!projectPath) {
|
|
5845
|
+
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
5846
|
+
if (pid === review.projectId) {
|
|
5847
|
+
projectPath = dir;
|
|
5848
|
+
break;
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
if (!projectPath) projectPath = process.cwd();
|
|
5853
|
+
if (!existsSync13(projectPath) || !existsSync13(`${projectPath}/.git`)) {
|
|
5854
|
+
logErr(`Project path not a git checkout: ${projectPath}`);
|
|
5855
|
+
logErr(`Set the project's localPath or run from inside the repo.`);
|
|
5856
|
+
process.exit(1);
|
|
5857
|
+
}
|
|
5858
|
+
try {
|
|
5859
|
+
execSync5(`git fetch origin ${review.branch}`, { cwd: projectPath, stdio: "ignore" });
|
|
5860
|
+
} catch {
|
|
5861
|
+
}
|
|
5862
|
+
try {
|
|
5863
|
+
execSync5(`git checkout ${review.branch}`, { cwd: projectPath, stdio: "pipe" });
|
|
5864
|
+
} catch (err) {
|
|
5865
|
+
logErr(`Could not checkout branch ${review.branch}: ${err.message}`);
|
|
5866
|
+
process.exit(1);
|
|
5867
|
+
}
|
|
5868
|
+
log(`Project: ${paint8("cyan", project.name)}`);
|
|
5869
|
+
log(`Branch: ${paint8("cyan", review.branch)}`);
|
|
5870
|
+
log(`Comments: ${paint8("yellow", String(actionableComments.length))}, findings: ${paint8("yellow", String(actionableFindings.length))}`);
|
|
5871
|
+
if (excluded.size > 0) {
|
|
5872
|
+
log(`Excluded files: ${paint8("dim", [...excluded].join(", "))}`);
|
|
5873
|
+
}
|
|
5874
|
+
try {
|
|
5875
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "processing", fixErrorMessage: null });
|
|
5876
|
+
} catch {
|
|
5877
|
+
}
|
|
5878
|
+
const prompt2 = buildApplyPrompt({
|
|
5879
|
+
branch: review.branch,
|
|
5880
|
+
baseBranch: review.baseBranch,
|
|
5881
|
+
comments: actionableComments,
|
|
5882
|
+
findings: actionableFindings,
|
|
5883
|
+
excluded: [...excluded]
|
|
5884
|
+
});
|
|
5885
|
+
log("Asking Claude to apply the changes\u2026");
|
|
5886
|
+
try {
|
|
5887
|
+
await runClaudeInteractive(prompt2, projectPath);
|
|
5888
|
+
} catch (err) {
|
|
5889
|
+
const message = err.message || "Unknown error";
|
|
5890
|
+
logErr(`Agent fix failed: ${message}`);
|
|
5891
|
+
try {
|
|
5892
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "failed", fixErrorMessage: message });
|
|
5893
|
+
} catch {
|
|
5894
|
+
}
|
|
5895
|
+
process.exit(1);
|
|
5896
|
+
}
|
|
5897
|
+
const dirty = execSync5("git status --porcelain", { cwd: projectPath, encoding: "utf-8" }).trim();
|
|
5898
|
+
if (!dirty) {
|
|
5899
|
+
log(paint8("yellow", "Agent didn't change any files."));
|
|
5900
|
+
try {
|
|
5901
|
+
await api.patch(`/api/reviews/${id}`, {
|
|
5902
|
+
fixStatus: "completed",
|
|
5903
|
+
fixErrorMessage: "No file changes were made by the agent."
|
|
5904
|
+
});
|
|
5905
|
+
} catch {
|
|
5906
|
+
}
|
|
5907
|
+
return;
|
|
5908
|
+
}
|
|
5909
|
+
if (opts.commit === false) {
|
|
5910
|
+
logOk("Changes left unstaged for review (--no-commit).");
|
|
5911
|
+
try {
|
|
5912
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "completed" });
|
|
5913
|
+
} catch {
|
|
5914
|
+
}
|
|
5915
|
+
return;
|
|
5916
|
+
}
|
|
5917
|
+
const commitMessage = buildCommitMessage({
|
|
5918
|
+
commentsCount: actionableComments.length,
|
|
5919
|
+
findingsCount: actionableFindings.length
|
|
5920
|
+
});
|
|
5921
|
+
try {
|
|
5922
|
+
execSync5("git add -A", { cwd: projectPath });
|
|
5923
|
+
execSync5(`git commit -m ${JSON.stringify(commitMessage)}`, { cwd: projectPath, stdio: "pipe" });
|
|
5924
|
+
} catch (err) {
|
|
5925
|
+
const message = err.message || "Unknown error";
|
|
5926
|
+
logErr(`Commit failed: ${message}`);
|
|
5927
|
+
try {
|
|
5928
|
+
await api.patch(`/api/reviews/${id}`, { fixStatus: "failed", fixErrorMessage: message });
|
|
5929
|
+
} catch {
|
|
5930
|
+
}
|
|
5931
|
+
process.exit(1);
|
|
5932
|
+
}
|
|
5933
|
+
const sha = execSync5("git rev-parse HEAD", { cwd: projectPath, encoding: "utf-8" }).trim();
|
|
5934
|
+
logOk(`Committed ${paint8("yellow", sha.slice(0, 10))}`);
|
|
5935
|
+
if (opts.push !== false) {
|
|
5936
|
+
try {
|
|
5937
|
+
execSync5(`git push origin ${review.branch}`, { cwd: projectPath, stdio: "pipe" });
|
|
5938
|
+
logOk(`Pushed to origin/${review.branch}`);
|
|
5939
|
+
} catch (err) {
|
|
5940
|
+
const message = err.message || "Unknown error";
|
|
5941
|
+
logErr(`Push failed: ${message}`);
|
|
5942
|
+
try {
|
|
5943
|
+
await api.patch(`/api/reviews/${id}`, {
|
|
5944
|
+
fixStatus: "failed",
|
|
5945
|
+
fixErrorMessage: `Committed ${sha.slice(0, 10)} but push failed: ${message}`,
|
|
5946
|
+
fixCommitSha: sha
|
|
5947
|
+
});
|
|
5948
|
+
} catch {
|
|
5949
|
+
}
|
|
5950
|
+
process.exit(1);
|
|
5951
|
+
}
|
|
5952
|
+
}
|
|
5953
|
+
try {
|
|
5954
|
+
await api.patch(`/api/reviews/${id}`, {
|
|
5955
|
+
fixStatus: "completed",
|
|
5956
|
+
fixCommitSha: sha,
|
|
5957
|
+
fixErrorMessage: null
|
|
5958
|
+
});
|
|
5959
|
+
} catch {
|
|
5960
|
+
}
|
|
5961
|
+
logOk("Done.");
|
|
5962
|
+
});
|
|
5963
|
+
function buildApplyPrompt(args) {
|
|
5964
|
+
const lines = [];
|
|
5965
|
+
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.`);
|
|
5966
|
+
lines.push("");
|
|
5967
|
+
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.");
|
|
5968
|
+
lines.push("");
|
|
5969
|
+
if (args.excluded.length > 0) {
|
|
5970
|
+
lines.push("Do NOT modify these files (they were excluded by the reviewer):");
|
|
5971
|
+
for (const path of args.excluded) lines.push(` - ${path}`);
|
|
5972
|
+
lines.push("");
|
|
5973
|
+
}
|
|
5974
|
+
if (args.comments.length > 0) {
|
|
5975
|
+
lines.push("USER COMMENTS:");
|
|
5976
|
+
for (const c14 of args.comments) {
|
|
5977
|
+
const where = c14.line ? `${c14.file}:${c14.line}` : c14.file;
|
|
5978
|
+
lines.push(`- [${where}] ${c14.body}`);
|
|
5979
|
+
}
|
|
5980
|
+
lines.push("");
|
|
5981
|
+
}
|
|
5982
|
+
if (args.findings.length > 0) {
|
|
5983
|
+
lines.push("AUTOMATED FINDINGS TO ADDRESS:");
|
|
5984
|
+
for (const f of args.findings) {
|
|
5985
|
+
const where = f.line ? `${f.file}:${f.line}` : f.file;
|
|
5986
|
+
lines.push(`- [${f.severity}/${f.type}] [${where}] ${f.title}`);
|
|
5987
|
+
if (f.description) lines.push(` ${f.description}`);
|
|
5988
|
+
if (f.suggestion) lines.push(` Suggested: ${f.suggestion}`);
|
|
5989
|
+
}
|
|
5990
|
+
lines.push("");
|
|
5991
|
+
}
|
|
5992
|
+
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.");
|
|
5993
|
+
return lines.join("\n");
|
|
5994
|
+
}
|
|
5995
|
+
function buildCommitMessage(args) {
|
|
5996
|
+
const parts = [];
|
|
5997
|
+
if (args.commentsCount > 0) parts.push(`${args.commentsCount} review comment${args.commentsCount === 1 ? "" : "s"}`);
|
|
5998
|
+
if (args.findingsCount > 0) parts.push(`${args.findingsCount} finding${args.findingsCount === 1 ? "" : "s"}`);
|
|
5999
|
+
const summary = parts.length > 0 ? parts.join(" + ") : "review feedback";
|
|
6000
|
+
return `chore: apply ${summary}
|
|
6001
|
+
|
|
6002
|
+
Generated by mr review apply.`;
|
|
6003
|
+
}
|
|
6004
|
+
function runClaudeInteractive(prompt2, cwd) {
|
|
6005
|
+
return new Promise((resolve9, reject) => {
|
|
6006
|
+
const child = spawn7("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6007
|
+
cwd,
|
|
6008
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
6009
|
+
});
|
|
6010
|
+
child.on("exit", (code) => {
|
|
6011
|
+
if (code === 0) resolve9();
|
|
6012
|
+
else reject(new Error(`claude exited with code ${code}`));
|
|
6013
|
+
});
|
|
6014
|
+
});
|
|
6015
|
+
}
|
|
6016
|
+
|
|
6017
|
+
// cli/commands/review.ts
|
|
6018
|
+
var c9 = {
|
|
6019
|
+
reset: "\x1B[0m",
|
|
6020
|
+
bold: "\x1B[1m",
|
|
6021
|
+
dim: "\x1B[2m",
|
|
6022
|
+
cyan: "\x1B[36m",
|
|
6023
|
+
green: "\x1B[32m",
|
|
6024
|
+
yellow: "\x1B[33m",
|
|
6025
|
+
red: "\x1B[31m",
|
|
6026
|
+
magenta: "\x1B[35m",
|
|
6027
|
+
gray: "\x1B[90m",
|
|
6028
|
+
blue: "\x1B[34m"
|
|
6029
|
+
};
|
|
6030
|
+
function paint9(color, text) {
|
|
6031
|
+
return `${c9[color]}${text}${c9.reset}`;
|
|
6032
|
+
}
|
|
6033
|
+
function timestamp3() {
|
|
6034
|
+
return paint9("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
6035
|
+
}
|
|
6036
|
+
function tag2() {
|
|
6037
|
+
return paint9("blue", "[review]");
|
|
6038
|
+
}
|
|
6039
|
+
function log2(msg) {
|
|
6040
|
+
console.log(`${timestamp3()} ${tag2()} ${msg}`);
|
|
6041
|
+
}
|
|
6042
|
+
function logOk2(msg) {
|
|
6043
|
+
console.log(`${timestamp3()} ${tag2()} ${paint9("green", "\u2713")} ${msg}`);
|
|
6044
|
+
}
|
|
6045
|
+
function logErr2(msg) {
|
|
6046
|
+
console.error(`${timestamp3()} ${tag2()} ${paint9("red", "\u2717")} ${msg}`);
|
|
6047
|
+
}
|
|
6048
|
+
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) => {
|
|
6049
|
+
const config = loadConfig();
|
|
6050
|
+
if (!config.apiKey) {
|
|
6051
|
+
logErr2('Not authenticated. Run "mr login" first.');
|
|
6052
|
+
process.exit(1);
|
|
6053
|
+
}
|
|
5805
6054
|
const banner = [
|
|
5806
6055
|
``,
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
6056
|
+
paint9("blue", ` \u2566\u2550\u2557\u2554\u2550\u2557\u2566 \u2566\u2566\u2554\u2550\u2557\u2566 \u2566`),
|
|
6057
|
+
paint9("blue", ` \u2560\u2566\u255D\u2551\u2563 \u255A\u2557\u2554\u255D\u2551\u2551\u2563 \u2551\u2551\u2551`),
|
|
6058
|
+
paint9("blue", ` \u2569\u255A\u2550\u255A\u2550\u255D \u255A\u255D \u2569\u255A\u2550\u255D\u255A\u2569\u255D`),
|
|
6059
|
+
paint9("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
6060
|
+
paint9("dim", ` automated code review`),
|
|
5812
6061
|
``
|
|
5813
6062
|
].join("\n");
|
|
5814
6063
|
console.log(banner);
|
|
5815
6064
|
const projectId = opts.project || getLinkedProjectId();
|
|
5816
6065
|
if (!projectId) {
|
|
5817
|
-
|
|
6066
|
+
logErr2('No project linked. Run "mr link" or pass --project <id>.');
|
|
5818
6067
|
process.exit(1);
|
|
5819
6068
|
}
|
|
5820
6069
|
let project;
|
|
5821
6070
|
try {
|
|
5822
6071
|
project = await api.get(`/api/projects/${projectId}`);
|
|
5823
6072
|
} catch {
|
|
5824
|
-
|
|
6073
|
+
logErr2(`Failed to fetch project ${projectId}`);
|
|
5825
6074
|
process.exit(1);
|
|
5826
6075
|
}
|
|
5827
6076
|
const baseBranch = opts.base || "main";
|
|
@@ -5835,19 +6084,19 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5835
6084
|
if (canUseRemote && remote) {
|
|
5836
6085
|
const num = prNumber ?? remote.number;
|
|
5837
6086
|
if (!num) {
|
|
5838
|
-
|
|
6087
|
+
logErr2(`Could not determine PR number from URL: ${prUrl}`);
|
|
5839
6088
|
process.exit(1);
|
|
5840
6089
|
}
|
|
5841
|
-
|
|
6090
|
+
log2(`Fetching diff from ${paint9("cyan", remote.host)} for ${paint9("yellow", `#${num}`)} in ${paint9("dim", `${remote.owner}/${remote.repo}`)}`);
|
|
5842
6091
|
try {
|
|
5843
6092
|
diff = fetchRemoteDiff(remote, num);
|
|
5844
6093
|
} catch (err) {
|
|
5845
|
-
|
|
5846
|
-
|
|
6094
|
+
logErr2(`Failed to fetch diff from ${remote.host}: ${err.message}`);
|
|
6095
|
+
logErr2(`Ensure ${remote.host === "github" ? "`gh`" : "`glab`"} is installed and authenticated.`);
|
|
5847
6096
|
process.exit(1);
|
|
5848
6097
|
}
|
|
5849
6098
|
if (!branch) branch = `pr-${num}`;
|
|
5850
|
-
|
|
6099
|
+
log2(`Reviewing PR ${paint9("cyan", `#${num}`)} against ${paint9("dim", baseBranch)}`);
|
|
5851
6100
|
} else {
|
|
5852
6101
|
let projectPath = project.localPath;
|
|
5853
6102
|
if (!projectPath) {
|
|
@@ -5861,42 +6110,42 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5861
6110
|
if (!projectPath) {
|
|
5862
6111
|
projectPath = process.cwd();
|
|
5863
6112
|
}
|
|
5864
|
-
if (!
|
|
5865
|
-
|
|
5866
|
-
|
|
6113
|
+
if (!existsSync14(projectPath)) {
|
|
6114
|
+
logErr2(`Project path does not exist: ${projectPath}`);
|
|
6115
|
+
logErr2(`Update the project's localPath, attach a PR URL to the review, or run "mr link" from the correct directory.`);
|
|
5867
6116
|
process.exit(1);
|
|
5868
6117
|
}
|
|
5869
6118
|
try {
|
|
5870
6119
|
if (!statSync2(projectPath).isDirectory()) {
|
|
5871
|
-
|
|
6120
|
+
logErr2(`Project path is not a directory: ${projectPath}`);
|
|
5872
6121
|
process.exit(1);
|
|
5873
6122
|
}
|
|
5874
6123
|
} catch (err) {
|
|
5875
|
-
|
|
6124
|
+
logErr2(`Cannot stat project path ${projectPath}: ${err.message}`);
|
|
5876
6125
|
process.exit(1);
|
|
5877
6126
|
}
|
|
5878
|
-
if (!
|
|
5879
|
-
|
|
5880
|
-
|
|
6127
|
+
if (!existsSync14(`${projectPath}/.git`)) {
|
|
6128
|
+
logErr2(`Project path is not a git repository: ${projectPath}`);
|
|
6129
|
+
logErr2(`Update the project's localPath to point to the local checkout, or attach a PR URL to the review.`);
|
|
5881
6130
|
process.exit(1);
|
|
5882
6131
|
}
|
|
5883
|
-
|
|
6132
|
+
log2(`Using project path: ${paint9("dim", projectPath)}`);
|
|
5884
6133
|
if (!branch) {
|
|
5885
6134
|
try {
|
|
5886
|
-
branch =
|
|
6135
|
+
branch = execSync6("git rev-parse --abbrev-ref HEAD", {
|
|
5887
6136
|
cwd: projectPath,
|
|
5888
6137
|
encoding: "utf-8"
|
|
5889
6138
|
}).trim();
|
|
5890
6139
|
} catch {
|
|
5891
|
-
|
|
6140
|
+
logErr2("Could not determine current branch. Pass --branch <name>.");
|
|
5892
6141
|
process.exit(1);
|
|
5893
6142
|
}
|
|
5894
6143
|
}
|
|
5895
|
-
|
|
6144
|
+
log2(`Reviewing branch: ${paint9("cyan", branch)} against ${paint9("dim", baseBranch)}`);
|
|
5896
6145
|
const pathspec = `-- . ':!*.lock' ':!package-lock.json' ':!pnpm-lock.yaml'`;
|
|
5897
6146
|
const tryDiff = (range) => {
|
|
5898
6147
|
try {
|
|
5899
|
-
return
|
|
6148
|
+
return execSync6(`git diff ${range} ${pathspec}`, {
|
|
5900
6149
|
cwd: projectPath,
|
|
5901
6150
|
encoding: "utf-8",
|
|
5902
6151
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -5908,7 +6157,7 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5908
6157
|
};
|
|
5909
6158
|
const tryFetch = (ref) => {
|
|
5910
6159
|
try {
|
|
5911
|
-
|
|
6160
|
+
execSync6(`git fetch origin ${ref}`, { cwd: projectPath, encoding: "utf-8", stdio: "pipe" });
|
|
5912
6161
|
} catch {
|
|
5913
6162
|
}
|
|
5914
6163
|
};
|
|
@@ -5919,24 +6168,24 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5919
6168
|
result = tryDiff(`origin/${baseBranch}...${branch}`) ?? tryDiff(`origin/${baseBranch}...origin/${branch}`) ?? tryDiff(`${baseBranch}...origin/${branch}`);
|
|
5920
6169
|
}
|
|
5921
6170
|
if (result === null) {
|
|
5922
|
-
|
|
5923
|
-
|
|
6171
|
+
logErr2(`Failed to get diff between ${baseBranch} and ${branch} in ${projectPath}.`);
|
|
6172
|
+
logErr2(`Branch may not exist locally or on origin. Try fetching it manually, or attach a PR URL to the review.`);
|
|
5924
6173
|
process.exit(1);
|
|
5925
6174
|
}
|
|
5926
6175
|
diff = result;
|
|
5927
6176
|
}
|
|
5928
|
-
|
|
6177
|
+
log2(`Project: ${paint9("cyan", project.name)}`);
|
|
5929
6178
|
if (!diff) {
|
|
5930
|
-
|
|
6179
|
+
logOk2("No changes found between branches. Nothing to review.");
|
|
5931
6180
|
process.exit(0);
|
|
5932
6181
|
}
|
|
5933
6182
|
diff = stripLockFileDiffs(diff);
|
|
5934
6183
|
const filesChanged = (diff.match(/^diff --git/gm) ?? []).length;
|
|
5935
|
-
|
|
6184
|
+
log2(`Diff size: ${paint9("yellow", `${diff.length.toLocaleString()} chars`)}, ${paint9("yellow", `${filesChanged} files`)}`);
|
|
5936
6185
|
let reportId;
|
|
5937
6186
|
if (opts.report) {
|
|
5938
6187
|
reportId = opts.report;
|
|
5939
|
-
|
|
6188
|
+
log2(`Using existing review report ${paint9("yellow", reportId.slice(0, 8))}`);
|
|
5940
6189
|
} else {
|
|
5941
6190
|
try {
|
|
5942
6191
|
const report = await api.post("/api/reviews", {
|
|
@@ -5945,14 +6194,17 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5945
6194
|
baseBranch
|
|
5946
6195
|
});
|
|
5947
6196
|
reportId = report.id;
|
|
5948
|
-
|
|
6197
|
+
log2(`Created review report ${paint9("yellow", reportId.slice(0, 8))}`);
|
|
5949
6198
|
} catch (err) {
|
|
5950
|
-
|
|
6199
|
+
logErr2(`Failed to create review report: ${err.message}`);
|
|
5951
6200
|
process.exit(1);
|
|
5952
6201
|
}
|
|
5953
6202
|
}
|
|
5954
6203
|
try {
|
|
5955
|
-
await api.patch(`/api/reviews/${reportId}`, {
|
|
6204
|
+
await api.patch(`/api/reviews/${reportId}`, {
|
|
6205
|
+
status: "processing",
|
|
6206
|
+
diff
|
|
6207
|
+
});
|
|
5956
6208
|
} catch {
|
|
5957
6209
|
}
|
|
5958
6210
|
const startTime = Date.now();
|
|
@@ -5960,10 +6212,10 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5960
6212
|
let truncatedDiff = diff;
|
|
5961
6213
|
if (diff.length > MAX_DIFF_CHARS) {
|
|
5962
6214
|
truncatedDiff = diff.slice(0, MAX_DIFF_CHARS) + "\n\n... (diff truncated, review covers first " + MAX_DIFF_CHARS.toLocaleString() + " characters)";
|
|
5963
|
-
|
|
6215
|
+
log2(paint9("yellow", `Diff truncated to ${MAX_DIFF_CHARS.toLocaleString()} chars for review`));
|
|
5964
6216
|
}
|
|
5965
6217
|
try {
|
|
5966
|
-
|
|
6218
|
+
log2("Running code review with Claude...");
|
|
5967
6219
|
const prompt2 = buildReviewPrompt(branch, baseBranch, truncatedDiff);
|
|
5968
6220
|
const output = await runClaude(prompt2);
|
|
5969
6221
|
const result = parseReviewOutput(output);
|
|
@@ -5975,7 +6227,7 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5975
6227
|
} catch {
|
|
5976
6228
|
}
|
|
5977
6229
|
if (wasCancelled) {
|
|
5978
|
-
|
|
6230
|
+
log2(paint9("yellow", "Review was cancelled \u2014 discarding results."));
|
|
5979
6231
|
process.exit(0);
|
|
5980
6232
|
}
|
|
5981
6233
|
await api.patch(`/api/reviews/${reportId}`, {
|
|
@@ -5985,28 +6237,28 @@ var reviewCommand = new Command25("review").description("Run an automated code r
|
|
|
5985
6237
|
filesReviewed: filesChanged,
|
|
5986
6238
|
reviewDurationMs: duration
|
|
5987
6239
|
});
|
|
5988
|
-
|
|
5989
|
-
|
|
6240
|
+
logOk2(`Review completed in ${paint9("cyan", formatDuration(duration))}`);
|
|
6241
|
+
logOk2(`Found ${paint9("yellow", String(result.findings.length))} findings`);
|
|
5990
6242
|
if (result.findings.length > 0) {
|
|
5991
6243
|
console.log("");
|
|
5992
6244
|
const critical = result.findings.filter((f) => f.severity === "critical").length;
|
|
5993
6245
|
const high = result.findings.filter((f) => f.severity === "high").length;
|
|
5994
6246
|
const medium = result.findings.filter((f) => f.severity === "medium").length;
|
|
5995
6247
|
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(` ${
|
|
6248
|
+
if (critical > 0) console.log(` ${paint9("red", "\u25CF")} ${critical} critical`);
|
|
6249
|
+
if (high > 0) console.log(` ${paint9("red", "\u25CF")} ${high} high`);
|
|
6250
|
+
if (medium > 0) console.log(` ${paint9("yellow", "\u25CF")} ${medium} medium`);
|
|
6251
|
+
if (low > 0) console.log(` ${paint9("dim", "\u25CF")} ${low} low`);
|
|
6000
6252
|
console.log("");
|
|
6001
6253
|
}
|
|
6002
6254
|
if (result.summary) {
|
|
6003
|
-
console.log(
|
|
6255
|
+
console.log(paint9("dim", " " + result.summary));
|
|
6004
6256
|
console.log("");
|
|
6005
6257
|
}
|
|
6006
6258
|
} catch (err) {
|
|
6007
6259
|
const duration = Date.now() - startTime;
|
|
6008
6260
|
const errorMessage = err.message || "Unknown error";
|
|
6009
|
-
|
|
6261
|
+
logErr2(`Review failed: ${errorMessage}`);
|
|
6010
6262
|
try {
|
|
6011
6263
|
await api.patch(`/api/reviews/${reportId}`, {
|
|
6012
6264
|
status: "failed",
|
|
@@ -6045,13 +6297,13 @@ function parsePrUrl(url) {
|
|
|
6045
6297
|
}
|
|
6046
6298
|
function fetchRemoteDiff(remote, number) {
|
|
6047
6299
|
if (remote.host === "github") {
|
|
6048
|
-
return
|
|
6300
|
+
return execSync6(`gh pr diff ${number} --repo ${remote.owner}/${remote.repo}`, {
|
|
6049
6301
|
encoding: "utf-8",
|
|
6050
6302
|
maxBuffer: 20 * 1024 * 1024
|
|
6051
6303
|
}).trim();
|
|
6052
6304
|
}
|
|
6053
6305
|
const project = `${remote.owner}/${remote.repo}`;
|
|
6054
|
-
return
|
|
6306
|
+
return execSync6(`glab mr diff ${number} --repo ${project} --raw`, {
|
|
6055
6307
|
encoding: "utf-8",
|
|
6056
6308
|
maxBuffer: 20 * 1024 * 1024
|
|
6057
6309
|
}).trim();
|
|
@@ -6122,7 +6374,7 @@ ${diff}
|
|
|
6122
6374
|
}
|
|
6123
6375
|
function runClaude(prompt2) {
|
|
6124
6376
|
return new Promise((resolve9, reject) => {
|
|
6125
|
-
const child =
|
|
6377
|
+
const child = spawn8("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6126
6378
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6127
6379
|
});
|
|
6128
6380
|
let output = "";
|
|
@@ -6174,13 +6426,13 @@ function parseReviewOutput(output) {
|
|
|
6174
6426
|
}
|
|
6175
6427
|
|
|
6176
6428
|
// cli/commands/scan.ts
|
|
6177
|
-
import { Command as
|
|
6429
|
+
import { Command as Command27 } from "commander";
|
|
6178
6430
|
|
|
6179
6431
|
// lib/scanner/index.ts
|
|
6180
|
-
import { spawn as
|
|
6432
|
+
import { spawn as spawn9 } from "child_process";
|
|
6181
6433
|
|
|
6182
6434
|
// lib/scanner/config.ts
|
|
6183
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
6435
|
+
import { readFileSync as readFileSync10, existsSync as existsSync15 } from "fs";
|
|
6184
6436
|
import { join as join9 } from "path";
|
|
6185
6437
|
var ALL_FINDING_TYPES = [
|
|
6186
6438
|
"idea",
|
|
@@ -6198,7 +6450,7 @@ var DEFAULTS = {
|
|
|
6198
6450
|
};
|
|
6199
6451
|
function loadScanConfig(projectPath) {
|
|
6200
6452
|
const configPath2 = join9(projectPath, ".mr-scan.json");
|
|
6201
|
-
if (!
|
|
6453
|
+
if (!existsSync15(configPath2)) {
|
|
6202
6454
|
return { ...DEFAULTS };
|
|
6203
6455
|
}
|
|
6204
6456
|
try {
|
|
@@ -6244,13 +6496,13 @@ async function authenticateBrowseSession(magicUrl, runBrowse) {
|
|
|
6244
6496
|
}
|
|
6245
6497
|
|
|
6246
6498
|
// lib/scanner/codebase-analysis.ts
|
|
6247
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as
|
|
6499
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync11, existsSync as existsSync16 } from "fs";
|
|
6248
6500
|
import { join as join10, relative } from "path";
|
|
6249
|
-
import { execSync as
|
|
6501
|
+
import { execSync as execSync7 } from "child_process";
|
|
6250
6502
|
function resolveDir(projectPath, candidates) {
|
|
6251
6503
|
for (const candidate of candidates) {
|
|
6252
6504
|
const dir = join10(projectPath, candidate);
|
|
6253
|
-
if (
|
|
6505
|
+
if (existsSync16(dir)) return dir;
|
|
6254
6506
|
}
|
|
6255
6507
|
return null;
|
|
6256
6508
|
}
|
|
@@ -6284,7 +6536,7 @@ function discoverRoutes(projectPath) {
|
|
|
6284
6536
|
}
|
|
6285
6537
|
function extractModels(projectPath) {
|
|
6286
6538
|
const schemaPath = join10(projectPath, "prisma", "schema.prisma");
|
|
6287
|
-
if (
|
|
6539
|
+
if (existsSync16(schemaPath)) {
|
|
6288
6540
|
const content = readFileSync11(schemaPath, "utf-8");
|
|
6289
6541
|
const models2 = [];
|
|
6290
6542
|
const modelRegex = /^model\s+(\w+)\s*\{/gm;
|
|
@@ -6298,7 +6550,7 @@ function extractModels(projectPath) {
|
|
|
6298
6550
|
const drizzleDirs = ["src/db", "src/schema", "db", "drizzle"];
|
|
6299
6551
|
for (const dir of drizzleDirs) {
|
|
6300
6552
|
const fullDir = join10(projectPath, dir);
|
|
6301
|
-
if (!
|
|
6553
|
+
if (!existsSync16(fullDir)) continue;
|
|
6302
6554
|
try {
|
|
6303
6555
|
const entries = readdirSync2(fullDir, { withFileTypes: true });
|
|
6304
6556
|
for (const entry of entries) {
|
|
@@ -6339,7 +6591,7 @@ function discoverComponents(projectPath) {
|
|
|
6339
6591
|
function extractInternalLinks(projectPath) {
|
|
6340
6592
|
const links = /* @__PURE__ */ new Set();
|
|
6341
6593
|
function searchDir(dir) {
|
|
6342
|
-
if (!
|
|
6594
|
+
if (!existsSync16(dir)) return;
|
|
6343
6595
|
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
6344
6596
|
for (const entry of entries) {
|
|
6345
6597
|
if (entry.isDirectory()) {
|
|
@@ -6372,7 +6624,7 @@ function extractInternalLinks(projectPath) {
|
|
|
6372
6624
|
}
|
|
6373
6625
|
function getRecentCommits(projectPath, count = 20) {
|
|
6374
6626
|
try {
|
|
6375
|
-
const output =
|
|
6627
|
+
const output = execSync7(
|
|
6376
6628
|
`git log --oneline -${count} --no-decorate`,
|
|
6377
6629
|
{ cwd: projectPath, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
6378
6630
|
);
|
|
@@ -6640,10 +6892,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
|
|
|
6640
6892
|
${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
|
|
6641
6893
|
|
|
6642
6894
|
**Components:**
|
|
6643
|
-
${codebaseAnalysis.components.slice(0, 15).map((
|
|
6895
|
+
${codebaseAnalysis.components.slice(0, 15).map((c14) => `- ${c14}`).join("\n")}
|
|
6644
6896
|
|
|
6645
6897
|
**Recent Git Commits:**
|
|
6646
|
-
${codebaseAnalysis.recentCommits.slice(0, 8).map((
|
|
6898
|
+
${codebaseAnalysis.recentCommits.slice(0, 8).map((c14) => `- ${c14}`).join("\n")}
|
|
6647
6899
|
|
|
6648
6900
|
**Completed Tasks:**
|
|
6649
6901
|
${context.completedTasks.slice(0, 10).map((t) => `- ${t.title}`).join("\n") || "None"}
|
|
@@ -6858,7 +7110,7 @@ async function fetchScanContext(opts) {
|
|
|
6858
7110
|
}
|
|
6859
7111
|
function runClaude2(prompt2) {
|
|
6860
7112
|
return new Promise((resolve9, reject) => {
|
|
6861
|
-
const child =
|
|
7113
|
+
const child = spawn9("claude", ["-p", "--dangerously-skip-permissions", prompt2], {
|
|
6862
7114
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6863
7115
|
});
|
|
6864
7116
|
let output = "";
|
|
@@ -6914,7 +7166,7 @@ function parseSynthesisOutput(output) {
|
|
|
6914
7166
|
}
|
|
6915
7167
|
|
|
6916
7168
|
// cli/commands/scan.ts
|
|
6917
|
-
var
|
|
7169
|
+
var c10 = {
|
|
6918
7170
|
reset: "\x1B[0m",
|
|
6919
7171
|
bold: "\x1B[1m",
|
|
6920
7172
|
dim: "\x1B[2m",
|
|
@@ -6925,53 +7177,53 @@ var c9 = {
|
|
|
6925
7177
|
magenta: "\x1B[35m",
|
|
6926
7178
|
gray: "\x1B[90m"
|
|
6927
7179
|
};
|
|
6928
|
-
function
|
|
6929
|
-
return `${
|
|
7180
|
+
function paint10(color, text) {
|
|
7181
|
+
return `${c10[color]}${text}${c10.reset}`;
|
|
6930
7182
|
}
|
|
6931
|
-
function
|
|
6932
|
-
return
|
|
7183
|
+
function timestamp4() {
|
|
7184
|
+
return paint10("gray", (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false }));
|
|
6933
7185
|
}
|
|
6934
7186
|
function scanTag() {
|
|
6935
|
-
return
|
|
7187
|
+
return paint10("magenta", "[scan]");
|
|
6936
7188
|
}
|
|
6937
|
-
function
|
|
6938
|
-
console.log(`${
|
|
7189
|
+
function log3(msg) {
|
|
7190
|
+
console.log(`${timestamp4()} ${scanTag()} ${msg}`);
|
|
6939
7191
|
}
|
|
6940
|
-
function
|
|
6941
|
-
console.log(`${
|
|
7192
|
+
function logOk3(msg) {
|
|
7193
|
+
console.log(`${timestamp4()} ${scanTag()} ${paint10("green", "\u2713")} ${msg}`);
|
|
6942
7194
|
}
|
|
6943
|
-
function
|
|
6944
|
-
console.error(`${
|
|
7195
|
+
function logErr3(msg) {
|
|
7196
|
+
console.error(`${timestamp4()} ${scanTag()} ${paint10("red", "\u2717")} ${msg}`);
|
|
6945
7197
|
}
|
|
6946
|
-
var scanCommand = new
|
|
7198
|
+
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
7199
|
const config = loadConfig();
|
|
6948
7200
|
if (!config.apiKey) {
|
|
6949
|
-
|
|
7201
|
+
logErr3('Not authenticated. Run "mr login" first.');
|
|
6950
7202
|
process.exit(1);
|
|
6951
7203
|
}
|
|
6952
7204
|
const banner = [
|
|
6953
7205
|
``,
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
7206
|
+
paint10("magenta", ` \u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554`),
|
|
7207
|
+
paint10("magenta", ` \u255A\u2550\u2557\u2551 \u2560\u2550\u2563\u2551\u2551\u2551`),
|
|
7208
|
+
paint10("magenta", ` \u255A\u2550\u255D\u255A\u2550\u255D\u2569 \u2569\u255D\u255A\u255D`),
|
|
7209
|
+
paint10("dim", ` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`),
|
|
7210
|
+
paint10("dim", ` autonomous product scanner`),
|
|
6959
7211
|
``
|
|
6960
7212
|
].join("\n");
|
|
6961
7213
|
console.log(banner);
|
|
6962
7214
|
const projectId = opts.project || getLinkedProjectId();
|
|
6963
7215
|
if (!projectId) {
|
|
6964
|
-
|
|
7216
|
+
logErr3('No project linked. Run "mr link" or pass --project <id>.');
|
|
6965
7217
|
process.exit(1);
|
|
6966
7218
|
}
|
|
6967
7219
|
let project;
|
|
6968
7220
|
try {
|
|
6969
7221
|
project = await api.get(`/api/projects/${projectId}`);
|
|
6970
7222
|
} catch {
|
|
6971
|
-
|
|
7223
|
+
logErr3(`Failed to fetch project ${projectId}`);
|
|
6972
7224
|
process.exit(1);
|
|
6973
7225
|
}
|
|
6974
|
-
|
|
7226
|
+
log3(`Scanning project: ${paint10("cyan", project.name)}`);
|
|
6975
7227
|
let projectPath = project.localPath;
|
|
6976
7228
|
if (!projectPath) {
|
|
6977
7229
|
for (const [dir, pid] of Object.entries(config.directories)) {
|
|
@@ -6988,7 +7240,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
6988
7240
|
let customPrompt = typeof opts.prompt === "string" && opts.prompt.trim().length > 0 ? opts.prompt.trim() : null;
|
|
6989
7241
|
if (opts.report) {
|
|
6990
7242
|
reportId = opts.report;
|
|
6991
|
-
|
|
7243
|
+
log3(`Using existing scan report ${paint10("yellow", reportId.slice(0, 8))}`);
|
|
6992
7244
|
try {
|
|
6993
7245
|
const existing = await api.get(`/api/scans/${reportId}`);
|
|
6994
7246
|
if (!customPrompt && existing.customPrompt) {
|
|
@@ -7000,7 +7252,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7000
7252
|
try {
|
|
7001
7253
|
const scans = await api.get(`/api/scans?projectId=${projectId}&status=processing`);
|
|
7002
7254
|
if (scans.length > 0) {
|
|
7003
|
-
|
|
7255
|
+
logErr3("A scan is already in progress for this project. Wait for it to complete.");
|
|
7004
7256
|
process.exit(1);
|
|
7005
7257
|
}
|
|
7006
7258
|
} catch {
|
|
@@ -7012,9 +7264,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7012
7264
|
customPrompt
|
|
7013
7265
|
});
|
|
7014
7266
|
reportId = report.id;
|
|
7015
|
-
|
|
7267
|
+
log3(`Created scan report ${paint10("yellow", reportId.slice(0, 8))}`);
|
|
7016
7268
|
} catch (err) {
|
|
7017
|
-
|
|
7269
|
+
logErr3(`Failed to create scan report: ${err.message}`);
|
|
7018
7270
|
process.exit(1);
|
|
7019
7271
|
}
|
|
7020
7272
|
}
|
|
@@ -7025,7 +7277,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7025
7277
|
try {
|
|
7026
7278
|
const current = await api.get(`/api/scans/${reportId}`);
|
|
7027
7279
|
if (current.status === "cancelled") {
|
|
7028
|
-
|
|
7280
|
+
log3(paint10("yellow", "Scan was cancelled \u2014 aborting."));
|
|
7029
7281
|
process.exit(0);
|
|
7030
7282
|
}
|
|
7031
7283
|
} catch {
|
|
@@ -7039,9 +7291,9 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7039
7291
|
apiUrl: config.apiUrl,
|
|
7040
7292
|
apiKey: config.apiKey,
|
|
7041
7293
|
runBrowse: runBrowseCommand2,
|
|
7042
|
-
onLog:
|
|
7294
|
+
onLog: log3,
|
|
7043
7295
|
onProgress: (phase, detail) => {
|
|
7044
|
-
|
|
7296
|
+
log3(`${paint10("dim", `[${phase}]`)} ${detail}`);
|
|
7045
7297
|
},
|
|
7046
7298
|
customPrompt
|
|
7047
7299
|
});
|
|
@@ -7054,7 +7306,7 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7054
7306
|
} catch {
|
|
7055
7307
|
}
|
|
7056
7308
|
if (wasCancelled) {
|
|
7057
|
-
|
|
7309
|
+
log3(paint10("yellow", "Scan was cancelled by user \u2014 discarding results."));
|
|
7058
7310
|
process.exit(0);
|
|
7059
7311
|
}
|
|
7060
7312
|
await api.patch(`/api/scans/${reportId}`, {
|
|
@@ -7065,37 +7317,37 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7065
7317
|
scanDurationMs: result.scanDurationMs,
|
|
7066
7318
|
routesCrawled: result.routesCrawled
|
|
7067
7319
|
});
|
|
7068
|
-
|
|
7320
|
+
logOk3(`Scan complete \u2014 ${paint10("cyan", String(result.findings.length))} findings`);
|
|
7069
7321
|
console.log("");
|
|
7070
|
-
console.log(` ${
|
|
7322
|
+
console.log(` ${paint10("bold", "Summary:")} ${result.summary}`);
|
|
7071
7323
|
console.log("");
|
|
7072
7324
|
const high = result.findings.filter((f) => f.priority === "high");
|
|
7073
7325
|
const medium = result.findings.filter((f) => f.priority === "medium");
|
|
7074
7326
|
const low = result.findings.filter((f) => f.priority === "low");
|
|
7075
7327
|
if (high.length > 0) {
|
|
7076
|
-
console.log(` ${
|
|
7328
|
+
console.log(` ${paint10("bold", paint10("red", `High Priority (${high.length})`))}`);
|
|
7077
7329
|
for (const f of high) {
|
|
7078
|
-
console.log(` ${
|
|
7079
|
-
console.log(` ${
|
|
7330
|
+
console.log(` ${paint10("red", "\u25CF")} [${f.type}] ${f.title}`);
|
|
7331
|
+
console.log(` ${paint10("dim", f.description.slice(0, 120))}`);
|
|
7080
7332
|
}
|
|
7081
7333
|
console.log("");
|
|
7082
7334
|
}
|
|
7083
7335
|
if (medium.length > 0) {
|
|
7084
|
-
console.log(` ${
|
|
7336
|
+
console.log(` ${paint10("bold", paint10("yellow", `Medium Priority (${medium.length})`))}`);
|
|
7085
7337
|
for (const f of medium) {
|
|
7086
|
-
console.log(` ${
|
|
7338
|
+
console.log(` ${paint10("yellow", "\u25CF")} [${f.type}] ${f.title}`);
|
|
7087
7339
|
}
|
|
7088
7340
|
console.log("");
|
|
7089
7341
|
}
|
|
7090
7342
|
if (low.length > 0) {
|
|
7091
|
-
console.log(` ${
|
|
7343
|
+
console.log(` ${paint10("dim", `Low Priority (${low.length})`)} `);
|
|
7092
7344
|
for (const f of low) {
|
|
7093
|
-
console.log(` ${
|
|
7345
|
+
console.log(` ${paint10("dim", `\u25CB [${f.type}] ${f.title}`)}`);
|
|
7094
7346
|
}
|
|
7095
7347
|
console.log("");
|
|
7096
7348
|
}
|
|
7097
7349
|
} catch (err) {
|
|
7098
|
-
|
|
7350
|
+
logErr3(`Scan failed: ${err.message}`);
|
|
7099
7351
|
try {
|
|
7100
7352
|
await api.patch(`/api/scans/${reportId}`, {
|
|
7101
7353
|
status: "failed",
|
|
@@ -7108,13 +7360,13 @@ var scanCommand = new Command26("scan").description("Run a product scan on the c
|
|
|
7108
7360
|
});
|
|
7109
7361
|
|
|
7110
7362
|
// cli/commands/doctor.ts
|
|
7111
|
-
import { Command as
|
|
7112
|
-
import { existsSync as
|
|
7363
|
+
import { Command as Command28 } from "commander";
|
|
7364
|
+
import { existsSync as existsSync17 } from "fs";
|
|
7113
7365
|
import { homedir as homedir2 } from "os";
|
|
7114
7366
|
import { join as join11 } from "path";
|
|
7115
7367
|
async function checkConfigExists() {
|
|
7116
7368
|
const configPath2 = join11(homedir2(), ".mr-manager", "config.json");
|
|
7117
|
-
const exists =
|
|
7369
|
+
const exists = existsSync17(configPath2);
|
|
7118
7370
|
if (!exists) {
|
|
7119
7371
|
return {
|
|
7120
7372
|
name: "Config file",
|
|
@@ -7156,7 +7408,7 @@ async function checkProjectLink() {
|
|
|
7156
7408
|
optional: true
|
|
7157
7409
|
};
|
|
7158
7410
|
}
|
|
7159
|
-
var doctorCommand = new
|
|
7411
|
+
var doctorCommand = new Command28("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
7160
7412
|
const banner = [
|
|
7161
7413
|
``,
|
|
7162
7414
|
paint5("cyan", ` MR DOCTOR`),
|
|
@@ -7187,7 +7439,7 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
|
|
|
7187
7439
|
console.log("");
|
|
7188
7440
|
return;
|
|
7189
7441
|
}
|
|
7190
|
-
const fixes = checks.filter((
|
|
7442
|
+
const fixes = checks.filter((c14) => !c14.ok && c14.fix && !c14.optional);
|
|
7191
7443
|
if (fixes.length > 0) {
|
|
7192
7444
|
console.log(paint5("yellow", " To fix:"));
|
|
7193
7445
|
for (const fix of fixes) {
|
|
@@ -7199,14 +7451,14 @@ var doctorCommand = new Command27("doctor").description("Diagnose Mr. Manager CL
|
|
|
7199
7451
|
});
|
|
7200
7452
|
|
|
7201
7453
|
// cli/commands/prompt-audit.ts
|
|
7202
|
-
import { Command as
|
|
7454
|
+
import { Command as Command29 } from "commander";
|
|
7203
7455
|
import { resolve as resolve8 } from "path";
|
|
7204
|
-
import { existsSync as
|
|
7456
|
+
import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
|
|
7205
7457
|
function auditLine(label, tokens) {
|
|
7206
7458
|
const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
|
|
7207
7459
|
return ` ${label.padEnd(30)} ${formatTokenCount(tokens).padStart(8)} ${bar}`;
|
|
7208
7460
|
}
|
|
7209
|
-
var promptAuditCommand = new
|
|
7461
|
+
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
7462
|
const results = [];
|
|
7211
7463
|
if (opts.task) {
|
|
7212
7464
|
try {
|
|
@@ -7260,7 +7512,7 @@ ${task.notes}` : "";
|
|
|
7260
7512
|
const repoDir = Object.entries(config.directories).find(([, pid]) => pid === task.projectId)?.[0];
|
|
7261
7513
|
if (repoDir) {
|
|
7262
7514
|
const featuresPath = resolve8(repoDir, ".mr-features.md");
|
|
7263
|
-
if (
|
|
7515
|
+
if (existsSync18(featuresPath)) {
|
|
7264
7516
|
const featuresContent = readFileSync12(featuresPath, "utf-8");
|
|
7265
7517
|
sections.push({ name: "features-doc", tokens: estimateTokens(featuresContent) });
|
|
7266
7518
|
}
|
|
@@ -7415,9 +7667,9 @@ ${r.jobType} [${r.identifier}]`);
|
|
|
7415
7667
|
});
|
|
7416
7668
|
|
|
7417
7669
|
// cli/commands/skill.ts
|
|
7418
|
-
import { Command as
|
|
7670
|
+
import { Command as Command30 } from "commander";
|
|
7419
7671
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
7420
|
-
var
|
|
7672
|
+
var c11 = {
|
|
7421
7673
|
reset: "\x1B[0m",
|
|
7422
7674
|
bold: "\x1B[1m",
|
|
7423
7675
|
dim: "\x1B[2m",
|
|
@@ -7450,7 +7702,7 @@ function formatSize(bytes) {
|
|
|
7450
7702
|
if (bytes < 1024) return `${bytes}b`;
|
|
7451
7703
|
return `${(bytes / 1024).toFixed(1)}kb`;
|
|
7452
7704
|
}
|
|
7453
|
-
var skillCommand = new
|
|
7705
|
+
var skillCommand = new Command30("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
|
|
7454
7706
|
skillCommand.command("list").alias("ls").description("List all skills").option("--category <category>", "Filter by category").action(async (opts) => {
|
|
7455
7707
|
const params = new URLSearchParams();
|
|
7456
7708
|
if (opts.category) params.set("category", opts.category);
|
|
@@ -7458,17 +7710,17 @@ skillCommand.command("list").alias("ls").description("List all skills").option("
|
|
|
7458
7710
|
`/api/skills${params.toString() ? `?${params}` : ""}`
|
|
7459
7711
|
);
|
|
7460
7712
|
if (skills.length === 0) {
|
|
7461
|
-
console.log(`${
|
|
7713
|
+
console.log(`${c11.dim}No skills found.${c11.reset}`);
|
|
7462
7714
|
return;
|
|
7463
7715
|
}
|
|
7464
7716
|
for (const skill of skills) {
|
|
7465
|
-
const cat = skill.category ? ` ${
|
|
7466
|
-
const scope = skill.projectId ? ` ${
|
|
7467
|
-
console.log(` ${
|
|
7717
|
+
const cat = skill.category ? ` ${c11.dim}[${skill.category}]${c11.reset}` : "";
|
|
7718
|
+
const scope = skill.projectId ? ` ${c11.dim}(project)${c11.reset}` : ` ${c11.dim}(global)${c11.reset}`;
|
|
7719
|
+
console.log(` ${c11.cyan}${skill.name}${c11.reset}${cat}${scope}`);
|
|
7468
7720
|
if (skill.description) {
|
|
7469
|
-
console.log(` ${
|
|
7721
|
+
console.log(` ${c11.dim}${skill.description}${c11.reset}`);
|
|
7470
7722
|
}
|
|
7471
|
-
console.log(` ${
|
|
7723
|
+
console.log(` ${c11.dim}id: ${skill.id}${c11.reset}`);
|
|
7472
7724
|
}
|
|
7473
7725
|
});
|
|
7474
7726
|
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 +7756,7 @@ skillCommand.command("create").description("Create a new skill from a markdown f
|
|
|
7504
7756
|
projectId
|
|
7505
7757
|
});
|
|
7506
7758
|
console.log(
|
|
7507
|
-
`${
|
|
7759
|
+
`${c11.green}Created skill:${c11.reset} ${c11.bold}${skill.name}${c11.reset} ${c11.dim}(${skill.id})${c11.reset}`
|
|
7508
7760
|
);
|
|
7509
7761
|
});
|
|
7510
7762
|
skillCommand.command("update").description(
|
|
@@ -7565,14 +7817,14 @@ skillCommand.command("update").description(
|
|
|
7565
7817
|
const beforeContent = existing.content ?? "";
|
|
7566
7818
|
const afterContent = typeof patch.content === "string" ? patch.content : beforeContent;
|
|
7567
7819
|
const contentChanged = typeof patch.content === "string" && patch.content !== beforeContent;
|
|
7568
|
-
const sizeDiff = typeof patch.content === "string" ? ` ${
|
|
7820
|
+
const sizeDiff = typeof patch.content === "string" ? ` ${c11.dim}(${formatSize(Buffer.byteLength(beforeContent, "utf-8"))} -> ${formatSize(Buffer.byteLength(afterContent, "utf-8"))})${c11.reset}` : "";
|
|
7569
7821
|
const priorRevisions = existing.revisions?.length ?? 0;
|
|
7570
7822
|
const revisionsAfter = priorRevisions + (contentChanged ? 1 : 0);
|
|
7571
|
-
const revisionNote = contentChanged ? ` ${
|
|
7823
|
+
const revisionNote = contentChanged ? ` ${c11.dim}kept ${revisionsAfter} prior revision${revisionsAfter === 1 ? "" : "s"}${c11.reset}` : "";
|
|
7572
7824
|
console.log(
|
|
7573
|
-
`${
|
|
7825
|
+
`${c11.green}Updated skill:${c11.reset} ${c11.bold}${updated.name}${c11.reset}${sizeDiff}${revisionNote}`
|
|
7574
7826
|
);
|
|
7575
|
-
console.log(` ${
|
|
7827
|
+
console.log(` ${c11.dim}id: ${updated.id}${c11.reset}`);
|
|
7576
7828
|
});
|
|
7577
7829
|
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
7830
|
let skill;
|
|
@@ -7585,7 +7837,7 @@ skillCommand.command("delete").alias("rm").description("Delete a skill").argumen
|
|
|
7585
7837
|
if (!opts.yes) {
|
|
7586
7838
|
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
7587
7839
|
const answer = (await rl.question(
|
|
7588
|
-
`Delete skill ${
|
|
7840
|
+
`Delete skill ${c11.bold}${skill.name}${c11.reset} ${c11.dim}(${skill.id})${c11.reset}? [y/N] `
|
|
7589
7841
|
)).trim().toLowerCase();
|
|
7590
7842
|
rl.close();
|
|
7591
7843
|
if (answer !== "y" && answer !== "yes") {
|
|
@@ -7595,7 +7847,7 @@ skillCommand.command("delete").alias("rm").description("Delete a skill").argumen
|
|
|
7595
7847
|
}
|
|
7596
7848
|
await api.del(`/api/skills/${skill.id}`);
|
|
7597
7849
|
console.log(
|
|
7598
|
-
`${
|
|
7850
|
+
`${c11.yellow}Deleted skill:${c11.reset} ${c11.bold}${skill.name}${c11.reset} ${c11.dim}(${skill.id})${c11.reset}`
|
|
7599
7851
|
);
|
|
7600
7852
|
});
|
|
7601
7853
|
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 +7861,22 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
7609
7861
|
process.exit(1);
|
|
7610
7862
|
}
|
|
7611
7863
|
}
|
|
7612
|
-
console.log(`${
|
|
7864
|
+
console.log(`${c11.dim}Generating skill...${c11.reset}`);
|
|
7613
7865
|
try {
|
|
7614
7866
|
const skill = await api.post("/api/skills/generate", {
|
|
7615
7867
|
prompt: prompt2,
|
|
7616
7868
|
projectId
|
|
7617
7869
|
});
|
|
7618
7870
|
console.log(
|
|
7619
|
-
`${
|
|
7871
|
+
`${c11.green}Generated skill:${c11.reset} ${c11.bold}${skill.name}${c11.reset}`
|
|
7620
7872
|
);
|
|
7621
7873
|
if (skill.description) {
|
|
7622
|
-
console.log(` ${
|
|
7874
|
+
console.log(` ${c11.dim}${skill.description}${c11.reset}`);
|
|
7623
7875
|
}
|
|
7624
7876
|
if (skill.category) {
|
|
7625
|
-
console.log(` ${
|
|
7877
|
+
console.log(` ${c11.dim}Category: ${skill.category}${c11.reset}`);
|
|
7626
7878
|
}
|
|
7627
|
-
console.log(` ${
|
|
7879
|
+
console.log(` ${c11.dim}id: ${skill.id}${c11.reset}`);
|
|
7628
7880
|
} catch (err) {
|
|
7629
7881
|
console.error(`Failed to generate skill: ${err.message}`);
|
|
7630
7882
|
process.exit(1);
|
|
@@ -7632,8 +7884,8 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
7632
7884
|
});
|
|
7633
7885
|
|
|
7634
7886
|
// cli/commands/resource.ts
|
|
7635
|
-
import { Command as
|
|
7636
|
-
var
|
|
7887
|
+
import { Command as Command31 } from "commander";
|
|
7888
|
+
var c12 = {
|
|
7637
7889
|
reset: "\x1B[0m",
|
|
7638
7890
|
bold: "\x1B[1m",
|
|
7639
7891
|
dim: "\x1B[2m",
|
|
@@ -7643,16 +7895,16 @@ var c11 = {
|
|
|
7643
7895
|
magenta: "\x1B[35m"
|
|
7644
7896
|
};
|
|
7645
7897
|
var TYPE_COLORS = {
|
|
7646
|
-
plan:
|
|
7647
|
-
research:
|
|
7648
|
-
"test-plan":
|
|
7649
|
-
note:
|
|
7898
|
+
plan: c12.cyan,
|
|
7899
|
+
research: c12.magenta,
|
|
7900
|
+
"test-plan": c12.yellow,
|
|
7901
|
+
note: c12.green
|
|
7650
7902
|
};
|
|
7651
7903
|
function typeLabel(type) {
|
|
7652
|
-
const color = TYPE_COLORS[type] ??
|
|
7653
|
-
return `${color}${type}${
|
|
7904
|
+
const color = TYPE_COLORS[type] ?? c12.dim;
|
|
7905
|
+
return `${color}${type}${c12.reset}`;
|
|
7654
7906
|
}
|
|
7655
|
-
var resourceCommand = new
|
|
7907
|
+
var resourceCommand = new Command31("resource").description("Manage resources \u2014 documents, plans, research, and notes");
|
|
7656
7908
|
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
7909
|
const params = new URLSearchParams();
|
|
7658
7910
|
if (opts.all) {
|
|
@@ -7665,13 +7917,13 @@ resourceCommand.command("list").alias("ls").description("List resources for the
|
|
|
7665
7917
|
`/api/resources${params.toString() ? `?${params}` : ""}`
|
|
7666
7918
|
);
|
|
7667
7919
|
if (resources.length === 0) {
|
|
7668
|
-
console.log(`${
|
|
7920
|
+
console.log(`${c12.dim}No resources found.${c12.reset}`);
|
|
7669
7921
|
return;
|
|
7670
7922
|
}
|
|
7671
7923
|
for (const r of resources) {
|
|
7672
|
-
const project = r.projectName ? ` ${
|
|
7673
|
-
console.log(` ${typeLabel(r.type)} ${
|
|
7674
|
-
console.log(` ${
|
|
7924
|
+
const project = r.projectName ? ` ${c12.dim}[${r.projectName}]${c12.reset}` : "";
|
|
7925
|
+
console.log(` ${typeLabel(r.type)} ${c12.bold}${r.title}${c12.reset}${project}`);
|
|
7926
|
+
console.log(` ${c12.dim}id: ${r.id}${c12.reset}`);
|
|
7675
7927
|
}
|
|
7676
7928
|
});
|
|
7677
7929
|
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 +7948,9 @@ resourceCommand.command("create").description("Create a new resource from a file
|
|
|
7696
7948
|
content: content.trim()
|
|
7697
7949
|
});
|
|
7698
7950
|
console.log(
|
|
7699
|
-
`${
|
|
7951
|
+
`${c12.green}Created resource:${c12.reset} ${c12.bold}${title}${c12.reset} ${c12.dim}(${resource.id})${c12.reset}`
|
|
7700
7952
|
);
|
|
7701
|
-
console.log(` ${
|
|
7953
|
+
console.log(` ${c12.dim}Attached to task ${opts.task}${c12.reset}`);
|
|
7702
7954
|
return;
|
|
7703
7955
|
}
|
|
7704
7956
|
let projectId;
|
|
@@ -7718,7 +7970,7 @@ resourceCommand.command("create").description("Create a new resource from a file
|
|
|
7718
7970
|
projectId
|
|
7719
7971
|
});
|
|
7720
7972
|
console.log(
|
|
7721
|
-
`${
|
|
7973
|
+
`${c12.green}Created resource:${c12.reset} ${c12.bold}${title}${c12.reset} ${c12.dim}(${result.resource.id})${c12.reset}`
|
|
7722
7974
|
);
|
|
7723
7975
|
});
|
|
7724
7976
|
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 +7989,30 @@ resourceCommand.command("generate").alias("gen").description("Generate a resourc
|
|
|
7737
7989
|
process.exit(1);
|
|
7738
7990
|
}
|
|
7739
7991
|
}
|
|
7740
|
-
console.log(`${
|
|
7992
|
+
console.log(`${c12.dim}Generating ${type}...${c12.reset}`);
|
|
7741
7993
|
const result = await api.post("/api/resources", {
|
|
7742
7994
|
type,
|
|
7743
7995
|
prompt: prompt2,
|
|
7744
7996
|
projectId
|
|
7745
7997
|
});
|
|
7746
7998
|
console.log(
|
|
7747
|
-
`${
|
|
7999
|
+
`${c12.green}Queued:${c12.reset} ${c12.bold}${result.task.title}${c12.reset}`
|
|
7748
8000
|
);
|
|
7749
|
-
console.log(` ${
|
|
8001
|
+
console.log(` ${c12.dim}task: ${result.task.id}${c12.reset}`);
|
|
7750
8002
|
});
|
|
7751
8003
|
|
|
7752
8004
|
// cli/commands/tests.ts
|
|
7753
|
-
import { Command as
|
|
7754
|
-
var
|
|
8005
|
+
import { Command as Command32 } from "commander";
|
|
8006
|
+
var c13 = {
|
|
7755
8007
|
reset: "\x1B[0m",
|
|
7756
8008
|
dim: "\x1B[2m",
|
|
7757
8009
|
yellow: "\x1B[33m"
|
|
7758
8010
|
};
|
|
7759
|
-
var testsCommand = new
|
|
8011
|
+
var testsCommand = new Command32("tests").description("List MR Test scenarios for the linked project").action(async () => {
|
|
7760
8012
|
const projectId = getLinkedProjectId();
|
|
7761
8013
|
if (!projectId) {
|
|
7762
8014
|
console.error(
|
|
7763
|
-
`${
|
|
8015
|
+
`${c13.yellow}No project linked to this directory.${c13.reset} Run "mr link <project-id>" first.`
|
|
7764
8016
|
);
|
|
7765
8017
|
process.exit(1);
|
|
7766
8018
|
}
|
|
@@ -7773,13 +8025,13 @@ var testsCommand = new Command31("tests").description("List MR Test scenarios fo
|
|
|
7773
8025
|
process.exit(1);
|
|
7774
8026
|
}
|
|
7775
8027
|
if (scenarios.length === 0) {
|
|
7776
|
-
console.log(`${
|
|
8028
|
+
console.log(`${c13.dim}No test scenarios found for this project.${c13.reset}`);
|
|
7777
8029
|
return;
|
|
7778
8030
|
}
|
|
7779
8031
|
for (const scenario of scenarios) {
|
|
7780
8032
|
console.log(`### ${scenario.name}`);
|
|
7781
8033
|
if (scenario.description) {
|
|
7782
|
-
console.log(`${
|
|
8034
|
+
console.log(`${c13.dim}${scenario.description}${c13.reset}`);
|
|
7783
8035
|
console.log();
|
|
7784
8036
|
}
|
|
7785
8037
|
console.log(scenario.content);
|
|
@@ -7788,9 +8040,9 @@ var testsCommand = new Command31("tests").description("List MR Test scenarios fo
|
|
|
7788
8040
|
});
|
|
7789
8041
|
|
|
7790
8042
|
// cli/commands/walkthrough.ts
|
|
7791
|
-
import { Command as
|
|
8043
|
+
import { Command as Command33 } from "commander";
|
|
7792
8044
|
var SKILL_NAME = "Generate Walkthrough Video";
|
|
7793
|
-
var walkthroughCommand = new
|
|
8045
|
+
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
8046
|
const skills = await api.get("/api/skills");
|
|
7795
8047
|
const skill = skills.find((s) => s.name === SKILL_NAME);
|
|
7796
8048
|
if (!skill) {
|
|
@@ -7818,12 +8070,12 @@ var walkthroughCommand = new Command32("walkthrough").description("Attach the Ge
|
|
|
7818
8070
|
|
|
7819
8071
|
// cli/index.ts
|
|
7820
8072
|
var configPath = join12(homedir3(), ".mr-manager", "config.json");
|
|
7821
|
-
var isFirstRun = !
|
|
8073
|
+
var isFirstRun = !existsSync19(configPath);
|
|
7822
8074
|
var userArgs = process.argv.slice(2);
|
|
7823
8075
|
var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
|
|
7824
8076
|
var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
|
|
7825
8077
|
if (isFirstRun && !shouldBypass) {
|
|
7826
|
-
const
|
|
8078
|
+
const c14 = {
|
|
7827
8079
|
reset: "\x1B[0m",
|
|
7828
8080
|
bold: "\x1B[1m",
|
|
7829
8081
|
dim: "\x1B[2m",
|
|
@@ -7833,28 +8085,28 @@ if (isFirstRun && !shouldBypass) {
|
|
|
7833
8085
|
magenta: "\x1B[35m"
|
|
7834
8086
|
};
|
|
7835
8087
|
console.log("");
|
|
7836
|
-
console.log(`${
|
|
7837
|
-
console.log(`${
|
|
7838
|
-
console.log(`${
|
|
7839
|
-
console.log(`${
|
|
8088
|
+
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}`);
|
|
8089
|
+
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}`);
|
|
8090
|
+
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}`);
|
|
8091
|
+
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
8092
|
console.log("");
|
|
7841
|
-
console.log(`${
|
|
7842
|
-
console.log(`${
|
|
8093
|
+
console.log(`${c14.bold} Welcome to Mr. Manager!${c14.reset}`);
|
|
8094
|
+
console.log(`${c14.dim} Let's get you set up in a few quick steps.${c14.reset}`);
|
|
7843
8095
|
console.log("");
|
|
7844
|
-
console.log(` ${
|
|
7845
|
-
console.log(` ${
|
|
8096
|
+
console.log(` ${c14.yellow}Step 1:${c14.reset} Authenticate via Google OAuth`);
|
|
8097
|
+
console.log(` ${c14.dim}Run:${c14.reset} ${c14.cyan}mr login${c14.reset}`);
|
|
7846
8098
|
console.log("");
|
|
7847
|
-
console.log(` ${
|
|
7848
|
-
console.log(` ${
|
|
8099
|
+
console.log(` ${c14.yellow}Step 2:${c14.reset} Verify your environment`);
|
|
8100
|
+
console.log(` ${c14.dim}Run:${c14.reset} ${c14.cyan}mr setup${c14.reset}`);
|
|
7849
8101
|
console.log("");
|
|
7850
|
-
console.log(` ${
|
|
7851
|
-
console.log(` ${
|
|
8102
|
+
console.log(` ${c14.yellow}Step 3:${c14.reset} Link a repo and start watching`);
|
|
8103
|
+
console.log(` ${c14.dim}Run:${c14.reset} ${c14.cyan}mr link${c14.reset} ${c14.dim}&&${c14.reset} ${c14.cyan}mr watch${c14.reset}`);
|
|
7852
8104
|
console.log("");
|
|
7853
|
-
console.log(`${
|
|
8105
|
+
console.log(`${c14.dim} Or run ${c14.reset}${c14.cyan}mr login${c14.reset}${c14.dim} to get started now.${c14.reset}`);
|
|
7854
8106
|
console.log("");
|
|
7855
8107
|
process.exit(0);
|
|
7856
8108
|
}
|
|
7857
|
-
var program = new
|
|
8109
|
+
var program = new Command34();
|
|
7858
8110
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
7859
8111
|
program.addCommand(initCommand);
|
|
7860
8112
|
program.addCommand(authCommand);
|