@dunnewold-labs/mr-manager 0.4.28 → 0.4.29
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 +126 -507
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// cli/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command28 } from "commander";
|
|
5
5
|
import { existsSync as existsSync17 } from "fs";
|
|
6
6
|
import { homedir as homedir3 } from "os";
|
|
7
7
|
import { join as join12 } from "path";
|
|
@@ -185,7 +185,7 @@ import { fileURLToPath } from "url";
|
|
|
185
185
|
// cli/package.json
|
|
186
186
|
var package_default = {
|
|
187
187
|
name: "@dunnewold-labs/mr-manager",
|
|
188
|
-
version: "0.4.
|
|
188
|
+
version: "0.4.29",
|
|
189
189
|
description: "Mr. Manager - Task and project management CLI",
|
|
190
190
|
bin: {
|
|
191
191
|
mr: "./dist/index.mjs"
|
|
@@ -1162,6 +1162,19 @@ function taskLikelyDoesNotNeedCodeChanges(task) {
|
|
|
1162
1162
|
return NON_CODE_TASK_KEYWORDS.some((keyword) => haystack.includes(keyword)) || NON_CODE_TASK_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
1163
1163
|
}
|
|
1164
1164
|
|
|
1165
|
+
// lib/task-branch.ts
|
|
1166
|
+
function isPrOrMrUrl(input) {
|
|
1167
|
+
try {
|
|
1168
|
+
const url = new URL(input.trim());
|
|
1169
|
+
const path = url.pathname;
|
|
1170
|
+
if (url.hostname.includes("github") && /\/pull\/\d+/.test(path)) return true;
|
|
1171
|
+
if (url.hostname.includes("gitlab") && /\/merge_requests\/\d+/.test(path)) return true;
|
|
1172
|
+
} catch {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1165
1178
|
// cli/browse-runner.ts
|
|
1166
1179
|
import { execSync as execSync3, spawn as spawn3 } from "child_process";
|
|
1167
1180
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -1482,6 +1495,15 @@ function taskBranchName(task) {
|
|
|
1482
1495
|
}
|
|
1483
1496
|
return `${ownerPrefix(task)}/${slugify(task.title)}`;
|
|
1484
1497
|
}
|
|
1498
|
+
function resolveBranchFromPrUrl(prUrl, repoDir, vcs) {
|
|
1499
|
+
const cmd = vcs === "gitlab" ? `glab mr view "${prUrl}" --output json 2>/dev/null | jq -r '.source_branch // empty'` : `gh pr view "${prUrl}" --json headRefName -q .headRefName 2>/dev/null`;
|
|
1500
|
+
return new Promise((resolve9) => {
|
|
1501
|
+
exec(cmd, { cwd: repoDir }, (err, stdout) => {
|
|
1502
|
+
const branch = stdout?.trim();
|
|
1503
|
+
resolve9(branch || null);
|
|
1504
|
+
});
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1485
1507
|
function formatElapsed(ms) {
|
|
1486
1508
|
const totalMinutes = Math.max(1, Math.floor(ms / 6e4));
|
|
1487
1509
|
const hours = Math.floor(totalMinutes / 60);
|
|
@@ -2119,9 +2141,6 @@ function protoTag(sid) {
|
|
|
2119
2141
|
function repoTag(sid) {
|
|
2120
2142
|
return paint("cyan", `[repo:${sid}]`);
|
|
2121
2143
|
}
|
|
2122
|
-
function ideaTag(sid) {
|
|
2123
|
-
return paint("magenta", `[idea:${sid}]`);
|
|
2124
|
-
}
|
|
2125
2144
|
function buildRepoCreationPrompt(project, rootDir, vcs = "github") {
|
|
2126
2145
|
const apiBase = process.env.MR_API_BASE ?? "http://localhost:3000";
|
|
2127
2146
|
const apiKey = process.env.MR_API_KEY ?? "";
|
|
@@ -2400,83 +2419,6 @@ function buildRefinementPrompt(proto, parentFiles, repoDir) {
|
|
|
2400
2419
|
`- Do NOT exit until ALL ${proto.variantCount} files have been written and verified.`
|
|
2401
2420
|
].join("\n");
|
|
2402
2421
|
}
|
|
2403
|
-
function buildIdeaPrompt(idea, repoDir) {
|
|
2404
|
-
const feedbackSection = idea.feedback ? [
|
|
2405
|
-
`## Previous Feedback`,
|
|
2406
|
-
``,
|
|
2407
|
-
`The user provided this feedback on a previous generation:`,
|
|
2408
|
-
`${idea.feedback}`,
|
|
2409
|
-
``,
|
|
2410
|
-
`Please incorporate this feedback into your new generation.`,
|
|
2411
|
-
``
|
|
2412
|
-
] : [];
|
|
2413
|
-
const existingPlanSection = idea.plan && idea.feedback ? [
|
|
2414
|
-
`## Previous Plan`,
|
|
2415
|
-
``,
|
|
2416
|
-
`Here is the plan from the previous iteration:`,
|
|
2417
|
-
`\`\`\`markdown`,
|
|
2418
|
-
idea.plan,
|
|
2419
|
-
`\`\`\``,
|
|
2420
|
-
``
|
|
2421
|
-
] : [];
|
|
2422
|
-
return [
|
|
2423
|
-
`You are a product strategist and UI designer. Your job is to take a rough idea and generate two outputs:`,
|
|
2424
|
-
`1. An implementation plan (saved as \`idea-plan.md\`)`,
|
|
2425
|
-
`2. A visual prototype (saved as \`idea-prototype.html\`)`,
|
|
2426
|
-
``,
|
|
2427
|
-
`Working directory: ${repoDir}`,
|
|
2428
|
-
``,
|
|
2429
|
-
`## Idea`,
|
|
2430
|
-
`Title: ${idea.title}`,
|
|
2431
|
-
`ID: ${idea.id}`,
|
|
2432
|
-
...idea.description ? [``, `Description: ${idea.description}`] : [],
|
|
2433
|
-
``,
|
|
2434
|
-
...feedbackSection,
|
|
2435
|
-
...existingPlanSection,
|
|
2436
|
-
`## Instructions`,
|
|
2437
|
-
``,
|
|
2438
|
-
`### Step 1: Generate the Plan`,
|
|
2439
|
-
``,
|
|
2440
|
-
`Write a comprehensive implementation plan to \`${repoDir}/idea-plan.md\` covering:`,
|
|
2441
|
-
`- **Problem statement**: What problem does this idea solve?`,
|
|
2442
|
-
`- **Proposed solution**: High-level approach`,
|
|
2443
|
-
`- **Key features**: Bulleted list of features/capabilities`,
|
|
2444
|
-
`- **Technical approach**: How it could be built (components, data model, integrations)`,
|
|
2445
|
-
`- **Implementation phases**: Phased rollout plan with milestones`,
|
|
2446
|
-
`- **Open questions**: Things that need clarification`,
|
|
2447
|
-
``,
|
|
2448
|
-
`### Step 2: Generate Follow-up Tasks`,
|
|
2449
|
-
``,
|
|
2450
|
-
`Write a JSON file to \`${repoDir}/idea-tasks.json\` containing an array of follow-up tasks that will need to be done after the project repo is created. These are setup and implementation tasks the user will need to complete. Examples: "Add environment variables (.env)", "Set up CI/CD pipeline", "Configure database", "Add authentication", etc.`,
|
|
2451
|
-
``,
|
|
2452
|
-
`Format: a JSON array of objects with "title" (short task name) and optional "notes" (additional context):`,
|
|
2453
|
-
`\`\`\`json`,
|
|
2454
|
-
`[`,
|
|
2455
|
-
` { "title": "Add environment variables (.env)", "notes": "Create .env file with required API keys and database URL" },`,
|
|
2456
|
-
` { "title": "Set up CI/CD pipeline" }`,
|
|
2457
|
-
`]`,
|
|
2458
|
-
`\`\`\``,
|
|
2459
|
-
``,
|
|
2460
|
-
`Generate 3-8 tasks that are specific to this idea. Focus on actionable setup and implementation steps.`,
|
|
2461
|
-
``,
|
|
2462
|
-
`### Step 3: Generate the Prototype`,
|
|
2463
|
-
``,
|
|
2464
|
-
`Write a self-contained HTML prototype to \`${repoDir}/idea-prototype.html\` that visually demonstrates the idea.`,
|
|
2465
|
-
`- Must be completely self-contained (inline CSS/JS, Tailwind CDN is acceptable)`,
|
|
2466
|
-
`- Should be interactive where possible (click handlers, animations, sample data)`,
|
|
2467
|
-
`- Should look polished and professional \u2014 this will be shown to stakeholders`,
|
|
2468
|
-
`- Include realistic sample data that demonstrates the feature`,
|
|
2469
|
-
``,
|
|
2470
|
-
`### Step 4: Verify`,
|
|
2471
|
-
``,
|
|
2472
|
-
`List the files in ${repoDir} and confirm \`idea-plan.md\`, \`idea-tasks.json\`, and \`idea-prototype.html\` all exist.`,
|
|
2473
|
-
``,
|
|
2474
|
-
`IMPORTANT RULES:`,
|
|
2475
|
-
`- Write EXACTLY three files: idea-plan.md, idea-tasks.json, and idea-prototype.html`,
|
|
2476
|
-
`- Do NOT upload or POST the files anywhere \u2014 the watch handler will upload them automatically`,
|
|
2477
|
-
`- Do NOT exit until both files have been written and verified`
|
|
2478
|
-
].join("\n");
|
|
2479
|
-
}
|
|
2480
2422
|
function buildAgentArgs(agent, prompt2, mode, sessionId, name, resumeSession = false, systemPrompt, maxTurns, claudeModel) {
|
|
2481
2423
|
if (agent === "codex") {
|
|
2482
2424
|
const args = [];
|
|
@@ -2619,14 +2561,13 @@ function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name
|
|
|
2619
2561
|
}
|
|
2620
2562
|
var watchCommand = new Command8("watch").description(
|
|
2621
2563
|
"Watch for in-progress tasks and autonomously dispatch an AI coding agent to work on them"
|
|
2622
|
-
).option("--interval <seconds>", "Polling interval in seconds", "15").option("--dry-run", "Show what would be dispatched without spawning the agent", false).option("--plan-approval", "Show the agent's plan and ask for approval before executing", false).option("--root <dir>", "Root directory filter for linked repos (default: cwd)").option("--agent <agent>", "AI agent to use: claude, codex, or gemini", "claude").option("--scan-at <HH:MM>", "Run a product scan daily at this time (e.g., 02:00)").
|
|
2564
|
+
).option("--interval <seconds>", "Polling interval in seconds", "15").option("--dry-run", "Show what would be dispatched without spawning the agent", false).option("--plan-approval", "Show the agent's plan and ask for approval before executing", false).option("--root <dir>", "Root directory filter for linked repos (default: cwd)").option("--agent <agent>", "AI agent to use: claude, codex, or gemini", "claude").option("--scan-at <HH:MM>", "Run a product scan daily at this time (e.g., 02:00)").action(async (opts) => {
|
|
2623
2565
|
const intervalMs = parseInt(opts.interval, 10) * 1e3;
|
|
2624
2566
|
const dryRun = opts.dryRun;
|
|
2625
2567
|
const planApproval = opts.planApproval;
|
|
2626
2568
|
const rootDir = opts.root ? resolve2(opts.root) : process.cwd();
|
|
2627
2569
|
const agent = opts.agent === "codex" ? "codex" : opts.agent === "gemini" ? "gemini" : "claude";
|
|
2628
2570
|
const scanAt = opts.scanAt;
|
|
2629
|
-
const globalQuietMode = opts.quiet;
|
|
2630
2571
|
const taskStallTimeoutMs = getTaskStallTimeoutMs();
|
|
2631
2572
|
const hungTaskTimeoutMinutes = Math.max(5, parseInt(process.env.MR_WATCH_HUNG_TASK_TIMEOUT_MINUTES ?? "60", 10) || 60);
|
|
2632
2573
|
const hungTaskTimeoutMs = hungTaskTimeoutMinutes * 6e4;
|
|
@@ -2647,7 +2588,6 @@ var watchCommand = new Command8("watch").description(
|
|
|
2647
2588
|
`stall-timeout=${paint("cyan", formatTimeoutMinutes(taskStallTimeoutMs))}`,
|
|
2648
2589
|
...planApproval ? [paint("yellow", "plan-approval")] : [],
|
|
2649
2590
|
...dryRun ? [paint("yellow", "dry-run")] : [],
|
|
2650
|
-
...globalQuietMode ? [paint("yellow", "quiet")] : [],
|
|
2651
2591
|
...scanAt ? [`scan-at=${paint("cyan", scanAt)}`] : [],
|
|
2652
2592
|
`hung-timeout=${paint("cyan", `${hungTaskTimeoutMinutes}m`)}`
|
|
2653
2593
|
].join(" ");
|
|
@@ -2721,7 +2661,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2721
2661
|
logWarn(watchTag(), `Network unavailable \u2014 pausing active tasks until connectivity returns (${reason})`);
|
|
2722
2662
|
}
|
|
2723
2663
|
for (const taskId of active.keys()) {
|
|
2724
|
-
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "
|
|
2664
|
+
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
|
|
2725
2665
|
if (nonTaskPrefixes.some((prefix) => taskId.startsWith(prefix))) continue;
|
|
2726
2666
|
pauseTaskForNetwork(taskId, reason);
|
|
2727
2667
|
}
|
|
@@ -2768,13 +2708,19 @@ var watchCommand = new Command8("watch").description(
|
|
|
2768
2708
|
const sid = shortId(task.id);
|
|
2769
2709
|
const slug = slugify(task.title);
|
|
2770
2710
|
const owner = ownerPrefix(task);
|
|
2771
|
-
|
|
2711
|
+
let branchName = taskBranchName(task);
|
|
2772
2712
|
const legacyBranchName = `mr/${sid}/${slug}`;
|
|
2773
2713
|
const prefix = taskTag(sid);
|
|
2774
2714
|
const vcs = detectVcs(repoDir)?.provider ?? "github";
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2715
|
+
if (!task.attachedBranch?.trim() && task.attachedBranchLink && isPrOrMrUrl(task.attachedBranchLink)) {
|
|
2716
|
+
const resolved = await resolveBranchFromPrUrl(task.attachedBranchLink, repoDir, vcs);
|
|
2717
|
+
if (resolved) {
|
|
2718
|
+
branchName = resolved;
|
|
2719
|
+
logInfo(prefix, `Resolved branch ${paint("cyan", resolved)} from attached ${vcs === "gitlab" ? "MR" : "PR"}`);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
logDispatch(prefix, `"${paint("bold", task.title)}" ${paint("gray", repoDir)} ${paint("dim", `[${vcs}]`)}`);
|
|
2723
|
+
await postTaskUpdate(task.id, `Agent dispatched \u2014 starting work on "${task.title}"`, "system");
|
|
2778
2724
|
let subtasks = [];
|
|
2779
2725
|
try {
|
|
2780
2726
|
subtasks = await api.get(`/api/tasks/${task.id}/subtasks`);
|
|
@@ -2840,7 +2786,6 @@ var watchCommand = new Command8("watch").description(
|
|
|
2840
2786
|
);
|
|
2841
2787
|
}
|
|
2842
2788
|
const prompt2 = buildExecutionPrompt(task, repoDir, subtasks, vcs, protoRefs, feedbackUpdates, existingResources, skillRefs, executionDir, startWithoutWorktree, !startWithoutWorktree && isGitRepo(repoDir) ? branchName : void 0);
|
|
2843
|
-
const taskIsQuietForEntry = task.quietMode || globalQuietMode;
|
|
2844
2789
|
const activeEntry = {
|
|
2845
2790
|
process: void 0,
|
|
2846
2791
|
title: task.title,
|
|
@@ -2849,8 +2794,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2849
2794
|
cleanupWorktreePath,
|
|
2850
2795
|
startedAt: Date.now(),
|
|
2851
2796
|
lastActivityAt: Date.now(),
|
|
2852
|
-
outputBytes: 0
|
|
2853
|
-
quietMode: taskIsQuietForEntry
|
|
2797
|
+
outputBytes: 0
|
|
2854
2798
|
};
|
|
2855
2799
|
const touchActivity = () => {
|
|
2856
2800
|
activeEntry.lastActivityAt = Date.now();
|
|
@@ -2879,12 +2823,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
2879
2823
|
const shouldResumeClaudeSession = attemptAgent === "claude" && !!task.claudeSessionId && !resumeAlreadyRetried && (hasFeedback || pausedForNetwork?.resumeSession === true);
|
|
2880
2824
|
const sessionId = attemptAgent === "claude" ? shouldResumeClaudeSession ? task.claudeSessionId : randomUUID() : void 0;
|
|
2881
2825
|
const effectiveClaudeModel = attemptAgent === "claude" ? taskClaudeModel : void 0;
|
|
2882
|
-
const
|
|
2883
|
-
const systemSections = isQuiet ? ["quiet-mode", ...EXECUTION_SYSTEM_SECTIONS] : [...EXECUTION_SYSTEM_SECTIONS];
|
|
2826
|
+
const systemSections = ["quiet-mode", ...EXECUTION_SYSTEM_SECTIONS];
|
|
2884
2827
|
const executionSystemPrompt = composeSystemPrompt(systemSections);
|
|
2885
|
-
if (isQuiet) {
|
|
2886
|
-
logInfo(prefix, `${paint("yellow", "quiet mode")} \u2014 agent will suppress conversational output`);
|
|
2887
|
-
}
|
|
2888
2828
|
const child = spawnAgent(
|
|
2889
2829
|
attemptAgent,
|
|
2890
2830
|
executionDir,
|
|
@@ -2932,36 +2872,47 @@ var watchCommand = new Command8("watch").description(
|
|
|
2932
2872
|
logWarn(prefix, `${attemptAgent} paused after network loss (${failureDetail})`);
|
|
2933
2873
|
return;
|
|
2934
2874
|
}
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
);
|
|
2943
|
-
|
|
2944
|
-
return;
|
|
2875
|
+
try {
|
|
2876
|
+
const currentTask = await api.get(`/api/tasks/${task.id}`);
|
|
2877
|
+
if (currentTask.status === "completed" || currentTask.status === "review" || currentTask.status === "error") {
|
|
2878
|
+
logWarn(prefix, `Task already in "${currentTask.status}" state after agent exit \u2014 skipping retry`);
|
|
2879
|
+
activeEntry.terminatedForError = true;
|
|
2880
|
+
}
|
|
2881
|
+
} catch {
|
|
2882
|
+
logWarn(prefix, `Could not verify task status \u2014 skipping retry to avoid stale re-dispatch`);
|
|
2883
|
+
activeEntry.terminatedForError = true;
|
|
2945
2884
|
}
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2885
|
+
if (!activeEntry.terminatedForError) {
|
|
2886
|
+
if (shouldResumeClaudeSession && !resumeAlreadyRetried) {
|
|
2887
|
+
resumeAlreadyRetried = true;
|
|
2888
|
+
logWarn(prefix, `Claude session resume failed (${failureDetail}) \u2014 retrying with fresh session`);
|
|
2889
|
+
await postTaskUpdate(
|
|
2890
|
+
task.id,
|
|
2891
|
+
`Claude session resume failed \u2014 retrying with fresh session`,
|
|
2892
|
+
"system"
|
|
2893
|
+
);
|
|
2894
|
+
await launchAttempt("claude");
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
2898
|
+
if (nextAgent) {
|
|
2899
|
+
logWarn(prefix, `${attemptAgent} failed (${failureDetail}) \u2014 retrying with ${nextAgent}`);
|
|
2900
|
+
await postTaskUpdate(
|
|
2901
|
+
task.id,
|
|
2902
|
+
`${attemptAgent} failed (${failureDetail}) \u2014 retrying with ${nextAgent}`,
|
|
2903
|
+
"system"
|
|
2904
|
+
);
|
|
2905
|
+
attemptIndex += 1;
|
|
2906
|
+
await launchAttempt(nextAgent);
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2957
2909
|
}
|
|
2958
2910
|
}
|
|
2959
2911
|
finishing.add(task.id);
|
|
2960
2912
|
const elapsedMs = Date.now() - activeEntry.startedAt;
|
|
2961
|
-
const modeLabel = activeEntry.quietMode ? "quiet" : "standard";
|
|
2962
2913
|
const outputTokenEstimate = Math.ceil(activeEntry.outputBytes / 4);
|
|
2963
2914
|
console.log(
|
|
2964
|
-
`${timestamp()} ${prefix} ${paint("dim", `[output]
|
|
2915
|
+
`${timestamp()} ${prefix} ${paint("dim", `[output] output=~${formatTokenCount(outputTokenEstimate)} elapsed=${Math.round(elapsedMs / 1e3)}s exit=${code}`)}`
|
|
2965
2916
|
);
|
|
2966
2917
|
try {
|
|
2967
2918
|
if (code === 0) {
|
|
@@ -3301,7 +3252,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
3301
3252
|
active.delete(key);
|
|
3302
3253
|
}
|
|
3303
3254
|
const failedAttempt = code !== 0 || spawnFailureReason !== null;
|
|
3304
|
-
if (failedAttempt) {
|
|
3255
|
+
if (failedAttempt && !activeEntry.terminatedForError) {
|
|
3305
3256
|
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
3306
3257
|
if (nextAgent) {
|
|
3307
3258
|
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
@@ -3411,7 +3362,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
3411
3362
|
active.delete(key);
|
|
3412
3363
|
}
|
|
3413
3364
|
const failedAttempt = code !== 0 || spawnFailureReason !== null;
|
|
3414
|
-
if (failedAttempt) {
|
|
3365
|
+
if (failedAttempt && !activeEntry.terminatedForError) {
|
|
3415
3366
|
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
3416
3367
|
if (nextAgent) {
|
|
3417
3368
|
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
@@ -3448,158 +3399,6 @@ var watchCommand = new Command8("watch").description(
|
|
|
3448
3399
|
};
|
|
3449
3400
|
await launchAttempt(attemptOrder[attemptIndex]);
|
|
3450
3401
|
}
|
|
3451
|
-
async function dispatchIdeaJob(idea, repoDir) {
|
|
3452
|
-
const sid = shortId(idea.id);
|
|
3453
|
-
const prefix = ideaTag(sid);
|
|
3454
|
-
logDispatch(prefix, `"${paint("bold", idea.title)}"${idea.feedback ? paint("cyan", " (iteration)") : ""} ${paint("gray", repoDir)}`);
|
|
3455
|
-
for (const f of ["idea-plan.md", "idea-tasks.json", "idea-prototype.html"]) {
|
|
3456
|
-
try {
|
|
3457
|
-
unlinkSync(resolve2(repoDir, f));
|
|
3458
|
-
} catch {
|
|
3459
|
-
}
|
|
3460
|
-
}
|
|
3461
|
-
const prompt2 = buildIdeaPrompt(idea, repoDir);
|
|
3462
|
-
const key = `idea-${idea.id}`;
|
|
3463
|
-
const attemptOrder = await resolveAgentChain(agent);
|
|
3464
|
-
if (attemptOrder.length === 0) {
|
|
3465
|
-
logError(prefix, `No available agents found for fallback chain starting at ${agent}`);
|
|
3466
|
-
queued.delete(key);
|
|
3467
|
-
try {
|
|
3468
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3469
|
-
} catch {
|
|
3470
|
-
}
|
|
3471
|
-
return;
|
|
3472
|
-
}
|
|
3473
|
-
const activeEntry = {
|
|
3474
|
-
process: void 0,
|
|
3475
|
-
title: idea.title,
|
|
3476
|
-
repoDir,
|
|
3477
|
-
startedAt: Date.now(),
|
|
3478
|
-
lastActivityAt: Date.now()
|
|
3479
|
-
};
|
|
3480
|
-
let attemptIndex = 0;
|
|
3481
|
-
const launchAttempt = async (attemptAgent) => {
|
|
3482
|
-
let spawnFailureReason = null;
|
|
3483
|
-
const child = spawnAgent(
|
|
3484
|
-
attemptAgent,
|
|
3485
|
-
repoDir,
|
|
3486
|
-
prompt2,
|
|
3487
|
-
prefix,
|
|
3488
|
-
void 0,
|
|
3489
|
-
void 0,
|
|
3490
|
-
idea.title,
|
|
3491
|
-
false,
|
|
3492
|
-
(err) => {
|
|
3493
|
-
spawnFailureReason = err.message;
|
|
3494
|
-
}
|
|
3495
|
-
);
|
|
3496
|
-
activeEntry.process = child;
|
|
3497
|
-
activeEntry.currentAgent = attemptAgent;
|
|
3498
|
-
active.set(key, activeEntry);
|
|
3499
|
-
child.on("exit", async (code) => {
|
|
3500
|
-
if (active.get(key)?.process === child) {
|
|
3501
|
-
active.delete(key);
|
|
3502
|
-
}
|
|
3503
|
-
const failedAttempt = code !== 0 || spawnFailureReason !== null;
|
|
3504
|
-
if (failedAttempt) {
|
|
3505
|
-
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
3506
|
-
if (nextAgent) {
|
|
3507
|
-
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
3508
|
-
logWarn(prefix, `${attemptAgent} failed (${failureDetail}) \u2014 retrying idea generation with ${nextAgent}`);
|
|
3509
|
-
attemptIndex += 1;
|
|
3510
|
-
await launchAttempt(nextAgent);
|
|
3511
|
-
return;
|
|
3512
|
-
}
|
|
3513
|
-
}
|
|
3514
|
-
finishing.add(key);
|
|
3515
|
-
try {
|
|
3516
|
-
if (code === 0) {
|
|
3517
|
-
try {
|
|
3518
|
-
let plan;
|
|
3519
|
-
let protoHtml;
|
|
3520
|
-
let followUpTasks;
|
|
3521
|
-
const planPath = resolve2(repoDir, "idea-plan.md");
|
|
3522
|
-
const tasksPath = resolve2(repoDir, "idea-tasks.json");
|
|
3523
|
-
const protoPath = resolve2(repoDir, "idea-prototype.html");
|
|
3524
|
-
try {
|
|
3525
|
-
plan = readFileSync5(planPath, "utf-8");
|
|
3526
|
-
} catch {
|
|
3527
|
-
}
|
|
3528
|
-
try {
|
|
3529
|
-
protoHtml = readFileSync5(protoPath, "utf-8");
|
|
3530
|
-
} catch {
|
|
3531
|
-
}
|
|
3532
|
-
try {
|
|
3533
|
-
const tasksRaw = readFileSync5(tasksPath, "utf-8");
|
|
3534
|
-
const parsed = JSON.parse(tasksRaw);
|
|
3535
|
-
if (Array.isArray(parsed)) {
|
|
3536
|
-
followUpTasks = parsed.filter((t) => t && typeof t === "object" && "title" in t);
|
|
3537
|
-
}
|
|
3538
|
-
} catch {
|
|
3539
|
-
}
|
|
3540
|
-
if (!plan && !protoHtml) {
|
|
3541
|
-
logError(prefix, `No output files found in ${repoDir}`);
|
|
3542
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3543
|
-
return;
|
|
3544
|
-
}
|
|
3545
|
-
const updateData = { status: "generated" };
|
|
3546
|
-
if (plan) updateData.plan = plan;
|
|
3547
|
-
if (followUpTasks) updateData.followUpTasks = followUpTasks;
|
|
3548
|
-
if (protoHtml) {
|
|
3549
|
-
try {
|
|
3550
|
-
const proto = await api.post("/api/prototypes", {
|
|
3551
|
-
title: `${idea.title} \u2014 Idea Prototype`,
|
|
3552
|
-
prompt: idea.description || idea.title,
|
|
3553
|
-
variantCount: 1,
|
|
3554
|
-
projectId: idea.projectId ?? null
|
|
3555
|
-
});
|
|
3556
|
-
await api.patch(`/api/prototypes/${proto.id}`, {
|
|
3557
|
-
status: "completed",
|
|
3558
|
-
files: [{ name: "idea-prototype.html", content: protoHtml }]
|
|
3559
|
-
});
|
|
3560
|
-
updateData.generatedPrototypeId = proto.id;
|
|
3561
|
-
logSuccess(prefix, `Prototype created: ${paint("gray", proto.id.slice(0, 8))}`);
|
|
3562
|
-
} catch (err) {
|
|
3563
|
-
logError(prefix, `Failed to create prototype: ${err.message}`);
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
await api.patch(`/api/ideas/${idea.id}`, updateData);
|
|
3567
|
-
logSuccess(prefix, `"${paint("bold", idea.title)}" generation complete`);
|
|
3568
|
-
try {
|
|
3569
|
-
unlinkSync(planPath);
|
|
3570
|
-
} catch {
|
|
3571
|
-
}
|
|
3572
|
-
try {
|
|
3573
|
-
unlinkSync(tasksPath);
|
|
3574
|
-
} catch {
|
|
3575
|
-
}
|
|
3576
|
-
try {
|
|
3577
|
-
unlinkSync(protoPath);
|
|
3578
|
-
} catch {
|
|
3579
|
-
}
|
|
3580
|
-
} catch (err) {
|
|
3581
|
-
logError(prefix, `Failed to upload idea output: ${err.message}`);
|
|
3582
|
-
try {
|
|
3583
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3584
|
-
} catch {
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
} else {
|
|
3588
|
-
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
3589
|
-
logError(prefix, `"${paint("bold", idea.title)}" generation failed via ${attemptAgent} (${failureDetail})`);
|
|
3590
|
-
try {
|
|
3591
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3592
|
-
} catch {
|
|
3593
|
-
}
|
|
3594
|
-
}
|
|
3595
|
-
} finally {
|
|
3596
|
-
queued.delete(key);
|
|
3597
|
-
finishing.delete(key);
|
|
3598
|
-
}
|
|
3599
|
-
});
|
|
3600
|
-
};
|
|
3601
|
-
await launchAttempt(attemptOrder[attemptIndex]);
|
|
3602
|
-
}
|
|
3603
3402
|
function dispatchScan(scan, prefix, key) {
|
|
3604
3403
|
logDispatch(prefix, `Running scan for project ${paint("cyan", scan.projectId.slice(0, 8))}`);
|
|
3605
3404
|
const scanProc = spawn4(process.execPath, [process.argv[1], "scan", "--project", scan.projectId, "--report", scan.id], {
|
|
@@ -3736,6 +3535,7 @@ ${divider}`);
|
|
|
3736
3535
|
const running = active.get(task.id);
|
|
3737
3536
|
if (running) {
|
|
3738
3537
|
logWarn(prefix, `Task exceeded hang timeout after ${formatElapsed(Date.now() - running.startedAt)}, terminating agent\u2026`);
|
|
3538
|
+
running.terminatedForError = true;
|
|
3739
3539
|
running.process.kill("SIGTERM");
|
|
3740
3540
|
active.delete(task.id);
|
|
3741
3541
|
}
|
|
@@ -3792,7 +3592,7 @@ ${divider}`);
|
|
|
3792
3592
|
queued.delete(taskId);
|
|
3793
3593
|
}
|
|
3794
3594
|
}
|
|
3795
|
-
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "
|
|
3595
|
+
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
|
|
3796
3596
|
for (const taskId of failed.keys()) {
|
|
3797
3597
|
if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
|
|
3798
3598
|
if (!activeTaskIds.has(taskId)) failed.delete(taskId);
|
|
@@ -3861,6 +3661,7 @@ ${divider}`);
|
|
|
3861
3661
|
for (const [key, entry] of active) {
|
|
3862
3662
|
if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) {
|
|
3863
3663
|
logWarn(watchTag(), `Prototype ${paint("yellow", key)} no longer in_progress, terminating\u2026`);
|
|
3664
|
+
entry.terminatedForError = true;
|
|
3864
3665
|
entry.process.kill("SIGTERM");
|
|
3865
3666
|
active.delete(key);
|
|
3866
3667
|
queued.delete(key);
|
|
@@ -4074,59 +3875,6 @@ ${divider}`);
|
|
|
4074
3875
|
}
|
|
4075
3876
|
dispatchRepoCreation(project, rootDir);
|
|
4076
3877
|
}
|
|
4077
|
-
let generatingIdeas = [];
|
|
4078
|
-
try {
|
|
4079
|
-
generatingIdeas = await api.get("/api/ideas?status=generating");
|
|
4080
|
-
} catch (err) {
|
|
4081
|
-
logError(watchTag(), `Failed to fetch generating ideas: ${err.message}`);
|
|
4082
|
-
}
|
|
4083
|
-
const inProgressIdeaKeys = new Set(generatingIdeas.map((i) => `idea-${i.id}`));
|
|
4084
|
-
for (const [key, entry] of active) {
|
|
4085
|
-
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) {
|
|
4086
|
-
logWarn(watchTag(), `Idea ${paint("yellow", key)} no longer generating, terminating\u2026`);
|
|
4087
|
-
entry.process.kill("SIGTERM");
|
|
4088
|
-
active.delete(key);
|
|
4089
|
-
queued.delete(key);
|
|
4090
|
-
}
|
|
4091
|
-
}
|
|
4092
|
-
for (const key of failed.keys()) {
|
|
4093
|
-
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) failed.delete(key);
|
|
4094
|
-
}
|
|
4095
|
-
for (const key of queued) {
|
|
4096
|
-
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key) && !finishing.has(key)) queued.delete(key);
|
|
4097
|
-
}
|
|
4098
|
-
const activeIdeaCount = [...active.keys()].filter((k) => k.startsWith("idea-")).length;
|
|
4099
|
-
let ideaSlots = Math.max(0, 1 - activeIdeaCount);
|
|
4100
|
-
for (const idea of generatingIdeas) {
|
|
4101
|
-
if (ideaSlots <= 0) break;
|
|
4102
|
-
const key = `idea-${idea.id}`;
|
|
4103
|
-
if (queued.has(key)) continue;
|
|
4104
|
-
if (finishing.has(key)) continue;
|
|
4105
|
-
if (failed.has(key)) continue;
|
|
4106
|
-
const sid = shortId(idea.id);
|
|
4107
|
-
const prefix = ideaTag(sid);
|
|
4108
|
-
let repoDir;
|
|
4109
|
-
if (idea.projectId) {
|
|
4110
|
-
const dir = findDirectoryForProject(config, idea.projectId, rootDir);
|
|
4111
|
-
if (!dir) {
|
|
4112
|
-
logWarn(prefix, `"${idea.title}": no linked directory found \u2014 skipping`);
|
|
4113
|
-
continue;
|
|
4114
|
-
}
|
|
4115
|
-
repoDir = dir;
|
|
4116
|
-
} else {
|
|
4117
|
-
repoDir = rootDir;
|
|
4118
|
-
}
|
|
4119
|
-
queued.add(key);
|
|
4120
|
-
ideaSlots--;
|
|
4121
|
-
if (dryRun) {
|
|
4122
|
-
logInfo(
|
|
4123
|
-
watchTag(),
|
|
4124
|
-
`${paint("yellow", "[dry-run]")} would dispatch idea "${paint("bold", idea.title)}" in ${paint("cyan", repoDir)}`
|
|
4125
|
-
);
|
|
4126
|
-
continue;
|
|
4127
|
-
}
|
|
4128
|
-
dispatchIdeaJob(idea, repoDir);
|
|
4129
|
-
}
|
|
4130
3878
|
let pendingScans = [];
|
|
4131
3879
|
try {
|
|
4132
3880
|
pendingScans = await api.get("/api/scans?status=pending&limit=5");
|
|
@@ -4751,7 +4499,7 @@ async function checkApiConnectivity() {
|
|
|
4751
4499
|
}
|
|
4752
4500
|
}
|
|
4753
4501
|
function printResults(checks) {
|
|
4754
|
-
const maxNameLen = Math.max(...checks.map((
|
|
4502
|
+
const maxNameLen = Math.max(...checks.map((c11) => c11.name.length));
|
|
4755
4503
|
let allOk = true;
|
|
4756
4504
|
for (const check of checks) {
|
|
4757
4505
|
const isOptional = check.optional ?? false;
|
|
@@ -4765,10 +4513,10 @@ function printResults(checks) {
|
|
|
4765
4513
|
}
|
|
4766
4514
|
async function autoFix(checks, agent) {
|
|
4767
4515
|
const { spawn: spawn8 } = await import("child_process");
|
|
4768
|
-
const ghInstalled = checks.find((
|
|
4769
|
-
const ghAuthed = checks.find((
|
|
4770
|
-
const mrAuthed = checks.find((
|
|
4771
|
-
const claudeCheck = checks.find((
|
|
4516
|
+
const ghInstalled = checks.find((c11) => c11.name === "GitHub CLI (gh)").ok;
|
|
4517
|
+
const ghAuthed = checks.find((c11) => c11.name === "GitHub CLI auth").ok;
|
|
4518
|
+
const mrAuthed = checks.find((c11) => c11.name === "Mr. Manager CLI auth").ok;
|
|
4519
|
+
const claudeCheck = checks.find((c11) => c11.name === "Claude Code (claude)");
|
|
4772
4520
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
4773
4521
|
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
4774
4522
|
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
@@ -4831,7 +4579,7 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
|
|
|
4831
4579
|
console.log("");
|
|
4832
4580
|
return;
|
|
4833
4581
|
}
|
|
4834
|
-
const fixes = checks.filter((
|
|
4582
|
+
const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
|
|
4835
4583
|
if (fixes.length > 0) {
|
|
4836
4584
|
console.log(paint5("yellow", " To fix:"));
|
|
4837
4585
|
for (const fix of fixes) {
|
|
@@ -6024,10 +5772,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
|
|
|
6024
5772
|
${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
|
|
6025
5773
|
|
|
6026
5774
|
**Components:**
|
|
6027
|
-
${codebaseAnalysis.components.slice(0, 15).map((
|
|
5775
|
+
${codebaseAnalysis.components.slice(0, 15).map((c11) => `- ${c11}`).join("\n")}
|
|
6028
5776
|
|
|
6029
5777
|
**Recent Git Commits:**
|
|
6030
|
-
${codebaseAnalysis.recentCommits.slice(0, 8).map((
|
|
5778
|
+
${codebaseAnalysis.recentCommits.slice(0, 8).map((c11) => `- ${c11}`).join("\n")}
|
|
6031
5779
|
|
|
6032
5780
|
**Completed Tasks:**
|
|
6033
5781
|
${context.completedTasks.slice(0, 10).map((t) => `- ${t.title}`).join("\n") || "None"}
|
|
@@ -6474,128 +6222,8 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
|
|
|
6474
6222
|
}
|
|
6475
6223
|
});
|
|
6476
6224
|
|
|
6477
|
-
// cli/commands/idea.ts
|
|
6478
|
-
import { Command as Command24 } from "commander";
|
|
6479
|
-
var c9 = {
|
|
6480
|
-
reset: "\x1B[0m",
|
|
6481
|
-
bold: "\x1B[1m",
|
|
6482
|
-
dim: "\x1B[2m",
|
|
6483
|
-
cyan: "\x1B[36m",
|
|
6484
|
-
green: "\x1B[32m",
|
|
6485
|
-
yellow: "\x1B[33m",
|
|
6486
|
-
red: "\x1B[31m",
|
|
6487
|
-
blue: "\x1B[34m",
|
|
6488
|
-
gray: "\x1B[90m",
|
|
6489
|
-
magenta: "\x1B[35m"
|
|
6490
|
-
};
|
|
6491
|
-
function paint9(color, text) {
|
|
6492
|
-
return `${c9[color]}${text}${c9.reset}`;
|
|
6493
|
-
}
|
|
6494
|
-
function statusBadge2(status) {
|
|
6495
|
-
switch (status) {
|
|
6496
|
-
case "draft":
|
|
6497
|
-
return paint9("gray", "\u25CB draft");
|
|
6498
|
-
case "generating":
|
|
6499
|
-
return paint9("cyan", "\u27F3 generating");
|
|
6500
|
-
case "generated":
|
|
6501
|
-
return paint9("green", "\u2713 generated");
|
|
6502
|
-
case "promoted":
|
|
6503
|
-
return paint9("magenta", "\u2191 promoted");
|
|
6504
|
-
case "archived":
|
|
6505
|
-
return paint9("dim", "\u2298 archived");
|
|
6506
|
-
default:
|
|
6507
|
-
return paint9("gray", status);
|
|
6508
|
-
}
|
|
6509
|
-
}
|
|
6510
|
-
var ideaCommand = new Command24("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
|
|
6511
|
-
new Command24("list").description("List ideas for the linked project").option("--all", "Show ideas for all projects").option("--status <status>", "Filter by status").action(async (opts) => {
|
|
6512
|
-
const params = new URLSearchParams();
|
|
6513
|
-
if (!opts.all) {
|
|
6514
|
-
const projectId = getLinkedProjectId();
|
|
6515
|
-
if (projectId) {
|
|
6516
|
-
params.set("projectId", projectId);
|
|
6517
|
-
}
|
|
6518
|
-
}
|
|
6519
|
-
if (opts.status) params.set("status", opts.status);
|
|
6520
|
-
const ideas = await api.get(`/api/ideas?${params.toString()}`);
|
|
6521
|
-
if (ideas.length === 0) {
|
|
6522
|
-
console.log(paint9("gray", "No ideas found."));
|
|
6523
|
-
return;
|
|
6524
|
-
}
|
|
6525
|
-
console.log();
|
|
6526
|
-
for (const idea of ideas) {
|
|
6527
|
-
const date = new Date(idea.createdAt).toLocaleDateString();
|
|
6528
|
-
console.log(
|
|
6529
|
-
` ${paint9("bold", idea.title)} ${statusBadge2(idea.status)} ${paint9("gray", idea.id.slice(0, 8))} ${paint9("dim", date)}`
|
|
6530
|
-
);
|
|
6531
|
-
if (idea.description) {
|
|
6532
|
-
console.log(` ${paint9("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
|
|
6533
|
-
}
|
|
6534
|
-
console.log();
|
|
6535
|
-
}
|
|
6536
|
-
})
|
|
6537
|
-
).addCommand(
|
|
6538
|
-
new Command24("create").description("Create a new idea").argument("<title>", "Title of the idea").option("--description <desc>", "Description of the idea").option("--project <projectId>", "Project ID (defaults to linked project)").option("--generate", "Immediately start generating plan & prototype").action(async (title, opts) => {
|
|
6539
|
-
const projectId = opts.project ?? getLinkedProjectId() ?? null;
|
|
6540
|
-
const idea = await api.post("/api/ideas", {
|
|
6541
|
-
title,
|
|
6542
|
-
description: opts.description,
|
|
6543
|
-
projectId
|
|
6544
|
-
});
|
|
6545
|
-
console.log();
|
|
6546
|
-
console.log(` ${paint9("green", "\u2713")} Created idea: ${paint9("bold", idea.title)}`);
|
|
6547
|
-
console.log(` ${paint9("gray", "ID:")} ${idea.id}`);
|
|
6548
|
-
if (opts.generate) {
|
|
6549
|
-
await api.post(`/api/ideas/${idea.id}/generate`);
|
|
6550
|
-
console.log(` ${paint9("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
|
|
6551
|
-
}
|
|
6552
|
-
console.log();
|
|
6553
|
-
})
|
|
6554
|
-
).addCommand(
|
|
6555
|
-
new Command24("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
|
|
6556
|
-
const idea = await api.post(`/api/ideas/${id}/generate`);
|
|
6557
|
-
console.log();
|
|
6558
|
-
console.log(` ${paint9("cyan", "\u27F3")} Generating: ${paint9("bold", idea.title)}`);
|
|
6559
|
-
console.log(` ${paint9("gray", "The watch agent will pick this up shortly.")}`);
|
|
6560
|
-
console.log();
|
|
6561
|
-
})
|
|
6562
|
-
).addCommand(
|
|
6563
|
-
new Command24("feedback").description("Send feedback to iterate on an idea's generated content").argument("<id>", "Idea ID").argument("<feedback>", "Feedback text").action(async (id, feedback) => {
|
|
6564
|
-
const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
|
|
6565
|
-
console.log();
|
|
6566
|
-
console.log(` ${paint9("cyan", "\u27F3")} Feedback sent for: ${paint9("bold", idea.title)}`);
|
|
6567
|
-
console.log(` ${paint9("gray", "The watch agent will re-generate with your feedback.")}`);
|
|
6568
|
-
console.log();
|
|
6569
|
-
})
|
|
6570
|
-
).addCommand(
|
|
6571
|
-
new Command24("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
|
|
6572
|
-
const result = await api.post(`/api/ideas/${id}/promote`);
|
|
6573
|
-
console.log();
|
|
6574
|
-
console.log(` ${paint9("green", "\u2713")} Promoted idea to task: ${paint9("bold", result.task.title)}`);
|
|
6575
|
-
console.log(` ${paint9("gray", "Task ID:")} ${result.task.id}`);
|
|
6576
|
-
console.log();
|
|
6577
|
-
})
|
|
6578
|
-
).addCommand(
|
|
6579
|
-
new Command24("spin-up").description("Spin up a new project with a GitHub repo from an idea").argument("<id>", "Idea ID").option("--name <name>", "Custom project name (defaults to idea title)").action(async (id, opts) => {
|
|
6580
|
-
const body = {};
|
|
6581
|
-
if (opts.name) body.name = opts.name;
|
|
6582
|
-
const result = await api.post(`/api/ideas/${id}/spin-up`, body);
|
|
6583
|
-
console.log();
|
|
6584
|
-
console.log(` ${paint9("green", "\u2713")} Spinning up project: ${paint9("bold", result.project.name)}`);
|
|
6585
|
-
console.log(` ${paint9("gray", "Project ID:")} ${result.project.id}`);
|
|
6586
|
-
if (result.tasks && result.tasks.length > 0) {
|
|
6587
|
-
console.log(` ${paint9("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
|
|
6588
|
-
for (const task of result.tasks) {
|
|
6589
|
-
console.log(` ${paint9("gray", "\u2022")} ${task.title}`);
|
|
6590
|
-
}
|
|
6591
|
-
}
|
|
6592
|
-
console.log(` ${paint9("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
|
|
6593
|
-
console.log();
|
|
6594
|
-
})
|
|
6595
|
-
);
|
|
6596
|
-
|
|
6597
6225
|
// cli/commands/doctor.ts
|
|
6598
|
-
import { Command as
|
|
6226
|
+
import { Command as Command24 } from "commander";
|
|
6599
6227
|
import { existsSync as existsSync15 } from "fs";
|
|
6600
6228
|
import { homedir as homedir2 } from "os";
|
|
6601
6229
|
import { join as join11 } from "path";
|
|
@@ -6643,7 +6271,7 @@ async function checkProjectLink() {
|
|
|
6643
6271
|
optional: true
|
|
6644
6272
|
};
|
|
6645
6273
|
}
|
|
6646
|
-
var doctorCommand = new
|
|
6274
|
+
var doctorCommand = new Command24("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
6647
6275
|
const banner = [
|
|
6648
6276
|
``,
|
|
6649
6277
|
paint5("cyan", ` MR DOCTOR`),
|
|
@@ -6674,7 +6302,7 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
|
|
|
6674
6302
|
console.log("");
|
|
6675
6303
|
return;
|
|
6676
6304
|
}
|
|
6677
|
-
const fixes = checks.filter((
|
|
6305
|
+
const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
|
|
6678
6306
|
if (fixes.length > 0) {
|
|
6679
6307
|
console.log(paint5("yellow", " To fix:"));
|
|
6680
6308
|
for (const fix of fixes) {
|
|
@@ -6686,14 +6314,14 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
|
|
|
6686
6314
|
});
|
|
6687
6315
|
|
|
6688
6316
|
// cli/commands/prompt-audit.ts
|
|
6689
|
-
import { Command as
|
|
6317
|
+
import { Command as Command25 } from "commander";
|
|
6690
6318
|
import { resolve as resolve8 } from "path";
|
|
6691
6319
|
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
|
|
6692
6320
|
function auditLine(label, tokens) {
|
|
6693
6321
|
const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
|
|
6694
6322
|
return ` ${label.padEnd(30)} ${formatTokenCount(tokens).padStart(8)} ${bar}`;
|
|
6695
6323
|
}
|
|
6696
|
-
var promptAuditCommand = new
|
|
6324
|
+
var promptAuditCommand = new Command25("prompt-audit").description("Dry-run prompt construction and report estimated token counts by job type").option("--task <id>", "Audit prompts for a specific task ID").option("--all", "Audit all supported job types with representative data", false).option("--json", "Output as JSON instead of plain text", false).action(async (opts) => {
|
|
6697
6325
|
const results = [];
|
|
6698
6326
|
if (opts.task) {
|
|
6699
6327
|
try {
|
|
@@ -6853,14 +6481,6 @@ ${task.notes}` : "";
|
|
|
6853
6481
|
{ name: "instructions", tokens: 500 }
|
|
6854
6482
|
]
|
|
6855
6483
|
},
|
|
6856
|
-
{
|
|
6857
|
-
jobType: "idea",
|
|
6858
|
-
description: "Idea generation prompt",
|
|
6859
|
-
sections: [
|
|
6860
|
-
{ name: "core-prompt", tokens: 500 },
|
|
6861
|
-
{ name: "idea-desc", tokens: 200 }
|
|
6862
|
-
]
|
|
6863
|
-
},
|
|
6864
6484
|
{
|
|
6865
6485
|
jobType: "repo-creation",
|
|
6866
6486
|
description: "Repository creation prompt",
|
|
@@ -6906,8 +6526,8 @@ ${r.jobType} [${r.identifier}]`);
|
|
|
6906
6526
|
});
|
|
6907
6527
|
|
|
6908
6528
|
// cli/commands/skill.ts
|
|
6909
|
-
import { Command as
|
|
6910
|
-
var
|
|
6529
|
+
import { Command as Command26 } from "commander";
|
|
6530
|
+
var c9 = {
|
|
6911
6531
|
reset: "\x1B[0m",
|
|
6912
6532
|
bold: "\x1B[1m",
|
|
6913
6533
|
dim: "\x1B[2m",
|
|
@@ -6915,7 +6535,7 @@ var c10 = {
|
|
|
6915
6535
|
green: "\x1B[32m",
|
|
6916
6536
|
yellow: "\x1B[33m"
|
|
6917
6537
|
};
|
|
6918
|
-
var skillCommand = new
|
|
6538
|
+
var skillCommand = new Command26("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
|
|
6919
6539
|
skillCommand.command("list").alias("ls").description("List all skills").option("--category <category>", "Filter by category").action(async (opts) => {
|
|
6920
6540
|
const params = new URLSearchParams();
|
|
6921
6541
|
if (opts.category) params.set("category", opts.category);
|
|
@@ -6923,17 +6543,17 @@ skillCommand.command("list").alias("ls").description("List all skills").option("
|
|
|
6923
6543
|
`/api/skills${params.toString() ? `?${params}` : ""}`
|
|
6924
6544
|
);
|
|
6925
6545
|
if (skills.length === 0) {
|
|
6926
|
-
console.log(`${
|
|
6546
|
+
console.log(`${c9.dim}No skills found.${c9.reset}`);
|
|
6927
6547
|
return;
|
|
6928
6548
|
}
|
|
6929
6549
|
for (const skill of skills) {
|
|
6930
|
-
const cat = skill.category ? ` ${
|
|
6931
|
-
const scope = skill.projectId ? ` ${
|
|
6932
|
-
console.log(` ${
|
|
6550
|
+
const cat = skill.category ? ` ${c9.dim}[${skill.category}]${c9.reset}` : "";
|
|
6551
|
+
const scope = skill.projectId ? ` ${c9.dim}(project)${c9.reset}` : ` ${c9.dim}(global)${c9.reset}`;
|
|
6552
|
+
console.log(` ${c9.cyan}${skill.name}${c9.reset}${cat}${scope}`);
|
|
6933
6553
|
if (skill.description) {
|
|
6934
|
-
console.log(` ${
|
|
6554
|
+
console.log(` ${c9.dim}${skill.description}${c9.reset}`);
|
|
6935
6555
|
}
|
|
6936
|
-
console.log(` ${
|
|
6556
|
+
console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
|
|
6937
6557
|
}
|
|
6938
6558
|
});
|
|
6939
6559
|
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) => {
|
|
@@ -6969,7 +6589,7 @@ skillCommand.command("create").description("Create a new skill from a markdown f
|
|
|
6969
6589
|
projectId
|
|
6970
6590
|
});
|
|
6971
6591
|
console.log(
|
|
6972
|
-
`${
|
|
6592
|
+
`${c9.green}Created skill:${c9.reset} ${c9.bold}${skill.name}${c9.reset} ${c9.dim}(${skill.id})${c9.reset}`
|
|
6973
6593
|
);
|
|
6974
6594
|
});
|
|
6975
6595
|
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) => {
|
|
@@ -6983,22 +6603,22 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
6983
6603
|
process.exit(1);
|
|
6984
6604
|
}
|
|
6985
6605
|
}
|
|
6986
|
-
console.log(`${
|
|
6606
|
+
console.log(`${c9.dim}Generating skill...${c9.reset}`);
|
|
6987
6607
|
try {
|
|
6988
6608
|
const skill = await api.post("/api/skills/generate", {
|
|
6989
6609
|
prompt: prompt2,
|
|
6990
6610
|
projectId
|
|
6991
6611
|
});
|
|
6992
6612
|
console.log(
|
|
6993
|
-
`${
|
|
6613
|
+
`${c9.green}Generated skill:${c9.reset} ${c9.bold}${skill.name}${c9.reset}`
|
|
6994
6614
|
);
|
|
6995
6615
|
if (skill.description) {
|
|
6996
|
-
console.log(` ${
|
|
6616
|
+
console.log(` ${c9.dim}${skill.description}${c9.reset}`);
|
|
6997
6617
|
}
|
|
6998
6618
|
if (skill.category) {
|
|
6999
|
-
console.log(` ${
|
|
6619
|
+
console.log(` ${c9.dim}Category: ${skill.category}${c9.reset}`);
|
|
7000
6620
|
}
|
|
7001
|
-
console.log(` ${
|
|
6621
|
+
console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
|
|
7002
6622
|
} catch (err) {
|
|
7003
6623
|
console.error(`Failed to generate skill: ${err.message}`);
|
|
7004
6624
|
process.exit(1);
|
|
@@ -7006,17 +6626,17 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
7006
6626
|
});
|
|
7007
6627
|
|
|
7008
6628
|
// cli/commands/tests.ts
|
|
7009
|
-
import { Command as
|
|
7010
|
-
var
|
|
6629
|
+
import { Command as Command27 } from "commander";
|
|
6630
|
+
var c10 = {
|
|
7011
6631
|
reset: "\x1B[0m",
|
|
7012
6632
|
dim: "\x1B[2m",
|
|
7013
6633
|
yellow: "\x1B[33m"
|
|
7014
6634
|
};
|
|
7015
|
-
var testsCommand = new
|
|
6635
|
+
var testsCommand = new Command27("tests").description("List MR Test scenarios for the linked project").action(async () => {
|
|
7016
6636
|
const projectId = getLinkedProjectId();
|
|
7017
6637
|
if (!projectId) {
|
|
7018
6638
|
console.error(
|
|
7019
|
-
`${
|
|
6639
|
+
`${c10.yellow}No project linked to this directory.${c10.reset} Run "mr link <project-id>" first.`
|
|
7020
6640
|
);
|
|
7021
6641
|
process.exit(1);
|
|
7022
6642
|
}
|
|
@@ -7029,13 +6649,13 @@ var testsCommand = new Command28("tests").description("List MR Test scenarios fo
|
|
|
7029
6649
|
process.exit(1);
|
|
7030
6650
|
}
|
|
7031
6651
|
if (scenarios.length === 0) {
|
|
7032
|
-
console.log(`${
|
|
6652
|
+
console.log(`${c10.dim}No test scenarios found for this project.${c10.reset}`);
|
|
7033
6653
|
return;
|
|
7034
6654
|
}
|
|
7035
6655
|
for (const scenario of scenarios) {
|
|
7036
6656
|
console.log(`### ${scenario.name}`);
|
|
7037
6657
|
if (scenario.description) {
|
|
7038
|
-
console.log(`${
|
|
6658
|
+
console.log(`${c10.dim}${scenario.description}${c10.reset}`);
|
|
7039
6659
|
console.log();
|
|
7040
6660
|
}
|
|
7041
6661
|
console.log(scenario.content);
|
|
@@ -7050,7 +6670,7 @@ var userArgs = process.argv.slice(2);
|
|
|
7050
6670
|
var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
|
|
7051
6671
|
var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
|
|
7052
6672
|
if (isFirstRun && !shouldBypass) {
|
|
7053
|
-
const
|
|
6673
|
+
const c11 = {
|
|
7054
6674
|
reset: "\x1B[0m",
|
|
7055
6675
|
bold: "\x1B[1m",
|
|
7056
6676
|
dim: "\x1B[2m",
|
|
@@ -7060,28 +6680,28 @@ if (isFirstRun && !shouldBypass) {
|
|
|
7060
6680
|
magenta: "\x1B[35m"
|
|
7061
6681
|
};
|
|
7062
6682
|
console.log("");
|
|
7063
|
-
console.log(`${
|
|
7064
|
-
console.log(`${
|
|
7065
|
-
console.log(`${
|
|
7066
|
-
console.log(`${
|
|
6683
|
+
console.log(`${c11.cyan} \u2554\u2566\u2557\u2566\u2550\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557\u2554\u2550\u2557\u2566\u2550\u2557${c11.reset}`);
|
|
6684
|
+
console.log(`${c11.magenta} \u2551\u2551\u2551\u2560\u2566\u255D \u2551\u2551\u2551\u2560\u2550\u2563\u2551\u2551\u2551\u2560\u2550\u2563\u2551 \u2566\u2551\u2563 \u2560\u2566\u255D${c11.reset}`);
|
|
6685
|
+
console.log(`${c11.cyan} \u2569 \u2569\u2569\u255A\u2550 \u2569 \u2569\u2569 \u2569\u255D\u255A\u255D\u2569 \u2569\u255A\u2550\u255D\u255A\u2550\u255D\u2569\u255A\u2550${c11.reset}`);
|
|
6686
|
+
console.log(`${c11.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c11.reset}`);
|
|
7067
6687
|
console.log("");
|
|
7068
|
-
console.log(`${
|
|
7069
|
-
console.log(`${
|
|
6688
|
+
console.log(`${c11.bold} Welcome to Mr. Manager!${c11.reset}`);
|
|
6689
|
+
console.log(`${c11.dim} Let's get you set up in a few quick steps.${c11.reset}`);
|
|
7070
6690
|
console.log("");
|
|
7071
|
-
console.log(` ${
|
|
7072
|
-
console.log(` ${
|
|
6691
|
+
console.log(` ${c11.yellow}Step 1:${c11.reset} Authenticate via Google OAuth`);
|
|
6692
|
+
console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr login${c11.reset}`);
|
|
7073
6693
|
console.log("");
|
|
7074
|
-
console.log(` ${
|
|
7075
|
-
console.log(` ${
|
|
6694
|
+
console.log(` ${c11.yellow}Step 2:${c11.reset} Verify your environment`);
|
|
6695
|
+
console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr setup${c11.reset}`);
|
|
7076
6696
|
console.log("");
|
|
7077
|
-
console.log(` ${
|
|
7078
|
-
console.log(` ${
|
|
6697
|
+
console.log(` ${c11.yellow}Step 3:${c11.reset} Link a repo and start watching`);
|
|
6698
|
+
console.log(` ${c11.dim}Run:${c11.reset} ${c11.cyan}mr link${c11.reset} ${c11.dim}&&${c11.reset} ${c11.cyan}mr watch${c11.reset}`);
|
|
7079
6699
|
console.log("");
|
|
7080
|
-
console.log(`${
|
|
6700
|
+
console.log(`${c11.dim} Or run ${c11.reset}${c11.cyan}mr login${c11.reset}${c11.dim} to get started now.${c11.reset}`);
|
|
7081
6701
|
console.log("");
|
|
7082
6702
|
process.exit(0);
|
|
7083
6703
|
}
|
|
7084
|
-
var program = new
|
|
6704
|
+
var program = new Command28();
|
|
7085
6705
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
7086
6706
|
program.addCommand(initCommand);
|
|
7087
6707
|
program.addCommand(authCommand);
|
|
@@ -7109,7 +6729,6 @@ program.addCommand(testCommand);
|
|
|
7109
6729
|
program.addCommand(featuresCommand);
|
|
7110
6730
|
program.addCommand(noMrCommand);
|
|
7111
6731
|
program.addCommand(scanCommand);
|
|
7112
|
-
program.addCommand(ideaCommand);
|
|
7113
6732
|
program.addCommand(doctorCommand);
|
|
7114
6733
|
program.addCommand(promptAuditCommand);
|
|
7115
6734
|
program.addCommand(skillCommand);
|