@dunnewold-labs/mr-manager 0.4.27 → 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 +150 -496
- 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";
|
|
@@ -1354,6 +1367,18 @@ If a scenario name matches the feature you're testing (by name or keyword):
|
|
|
1354
1367
|
3. Note in your test plan summary which scenario you used.
|
|
1355
1368
|
|
|
1356
1369
|
If no matching scenario exists, infer interactions from the codebase as normal.`;
|
|
1370
|
+
var SYSTEM_SECTION_QUIET_MODE = `## Quiet Mode \u2014 CRITICAL
|
|
1371
|
+
|
|
1372
|
+
You are running in **quiet mode**. This means:
|
|
1373
|
+
|
|
1374
|
+
1. **DO NOT** write any explanatory text, conversation, commentary, or summaries outside of tool calls.
|
|
1375
|
+
2. **DO NOT** describe what you are about to do or what you just did in prose.
|
|
1376
|
+
3. **ONLY** output text when using \`mr update\` for status updates (keep these to 1 short sentence each).
|
|
1377
|
+
4. All your work must happen through tool calls (Read, Edit, Write, Bash, etc.) \u2014 not through conversational output.
|
|
1378
|
+
5. When you would normally explain your reasoning or next steps, simply proceed silently with the tool calls.
|
|
1379
|
+
6. After completing work, exit immediately without a summary.
|
|
1380
|
+
|
|
1381
|
+
This mode exists to minimize output token usage. Every word of conversational text you generate costs tokens. Tool calls and file writes are the only acceptable output.`;
|
|
1357
1382
|
var SYSTEM_SECTIONS = {
|
|
1358
1383
|
"status-updates": SYSTEM_SECTION_STATUS_UPDATES,
|
|
1359
1384
|
"screenshots": SYSTEM_SECTION_SCREENSHOTS,
|
|
@@ -1362,7 +1387,8 @@ var SYSTEM_SECTIONS = {
|
|
|
1362
1387
|
"features-workflow": SYSTEM_SECTION_FEATURES_WORKFLOW,
|
|
1363
1388
|
"prd-format": SYSTEM_SECTION_PRD_FORMAT,
|
|
1364
1389
|
"prd-open-questions": SYSTEM_SECTION_PRD_OPEN_QUESTIONS,
|
|
1365
|
-
"mr-tests": SYSTEM_SECTION_MR_TESTS
|
|
1390
|
+
"mr-tests": SYSTEM_SECTION_MR_TESTS,
|
|
1391
|
+
"quiet-mode": SYSTEM_SECTION_QUIET_MODE
|
|
1366
1392
|
};
|
|
1367
1393
|
function composeSystemPrompt(sections) {
|
|
1368
1394
|
return sections.map((s) => SYSTEM_SECTIONS[s]).join("\n\n");
|
|
@@ -1469,6 +1495,15 @@ function taskBranchName(task) {
|
|
|
1469
1495
|
}
|
|
1470
1496
|
return `${ownerPrefix(task)}/${slugify(task.title)}`;
|
|
1471
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
|
+
}
|
|
1472
1507
|
function formatElapsed(ms) {
|
|
1473
1508
|
const totalMinutes = Math.max(1, Math.floor(ms / 6e4));
|
|
1474
1509
|
const hours = Math.floor(totalMinutes / 60);
|
|
@@ -2106,9 +2141,6 @@ function protoTag(sid) {
|
|
|
2106
2141
|
function repoTag(sid) {
|
|
2107
2142
|
return paint("cyan", `[repo:${sid}]`);
|
|
2108
2143
|
}
|
|
2109
|
-
function ideaTag(sid) {
|
|
2110
|
-
return paint("magenta", `[idea:${sid}]`);
|
|
2111
|
-
}
|
|
2112
2144
|
function buildRepoCreationPrompt(project, rootDir, vcs = "github") {
|
|
2113
2145
|
const apiBase = process.env.MR_API_BASE ?? "http://localhost:3000";
|
|
2114
2146
|
const apiKey = process.env.MR_API_KEY ?? "";
|
|
@@ -2387,83 +2419,6 @@ function buildRefinementPrompt(proto, parentFiles, repoDir) {
|
|
|
2387
2419
|
`- Do NOT exit until ALL ${proto.variantCount} files have been written and verified.`
|
|
2388
2420
|
].join("\n");
|
|
2389
2421
|
}
|
|
2390
|
-
function buildIdeaPrompt(idea, repoDir) {
|
|
2391
|
-
const feedbackSection = idea.feedback ? [
|
|
2392
|
-
`## Previous Feedback`,
|
|
2393
|
-
``,
|
|
2394
|
-
`The user provided this feedback on a previous generation:`,
|
|
2395
|
-
`${idea.feedback}`,
|
|
2396
|
-
``,
|
|
2397
|
-
`Please incorporate this feedback into your new generation.`,
|
|
2398
|
-
``
|
|
2399
|
-
] : [];
|
|
2400
|
-
const existingPlanSection = idea.plan && idea.feedback ? [
|
|
2401
|
-
`## Previous Plan`,
|
|
2402
|
-
``,
|
|
2403
|
-
`Here is the plan from the previous iteration:`,
|
|
2404
|
-
`\`\`\`markdown`,
|
|
2405
|
-
idea.plan,
|
|
2406
|
-
`\`\`\``,
|
|
2407
|
-
``
|
|
2408
|
-
] : [];
|
|
2409
|
-
return [
|
|
2410
|
-
`You are a product strategist and UI designer. Your job is to take a rough idea and generate two outputs:`,
|
|
2411
|
-
`1. An implementation plan (saved as \`idea-plan.md\`)`,
|
|
2412
|
-
`2. A visual prototype (saved as \`idea-prototype.html\`)`,
|
|
2413
|
-
``,
|
|
2414
|
-
`Working directory: ${repoDir}`,
|
|
2415
|
-
``,
|
|
2416
|
-
`## Idea`,
|
|
2417
|
-
`Title: ${idea.title}`,
|
|
2418
|
-
`ID: ${idea.id}`,
|
|
2419
|
-
...idea.description ? [``, `Description: ${idea.description}`] : [],
|
|
2420
|
-
``,
|
|
2421
|
-
...feedbackSection,
|
|
2422
|
-
...existingPlanSection,
|
|
2423
|
-
`## Instructions`,
|
|
2424
|
-
``,
|
|
2425
|
-
`### Step 1: Generate the Plan`,
|
|
2426
|
-
``,
|
|
2427
|
-
`Write a comprehensive implementation plan to \`${repoDir}/idea-plan.md\` covering:`,
|
|
2428
|
-
`- **Problem statement**: What problem does this idea solve?`,
|
|
2429
|
-
`- **Proposed solution**: High-level approach`,
|
|
2430
|
-
`- **Key features**: Bulleted list of features/capabilities`,
|
|
2431
|
-
`- **Technical approach**: How it could be built (components, data model, integrations)`,
|
|
2432
|
-
`- **Implementation phases**: Phased rollout plan with milestones`,
|
|
2433
|
-
`- **Open questions**: Things that need clarification`,
|
|
2434
|
-
``,
|
|
2435
|
-
`### Step 2: Generate Follow-up Tasks`,
|
|
2436
|
-
``,
|
|
2437
|
-
`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.`,
|
|
2438
|
-
``,
|
|
2439
|
-
`Format: a JSON array of objects with "title" (short task name) and optional "notes" (additional context):`,
|
|
2440
|
-
`\`\`\`json`,
|
|
2441
|
-
`[`,
|
|
2442
|
-
` { "title": "Add environment variables (.env)", "notes": "Create .env file with required API keys and database URL" },`,
|
|
2443
|
-
` { "title": "Set up CI/CD pipeline" }`,
|
|
2444
|
-
`]`,
|
|
2445
|
-
`\`\`\``,
|
|
2446
|
-
``,
|
|
2447
|
-
`Generate 3-8 tasks that are specific to this idea. Focus on actionable setup and implementation steps.`,
|
|
2448
|
-
``,
|
|
2449
|
-
`### Step 3: Generate the Prototype`,
|
|
2450
|
-
``,
|
|
2451
|
-
`Write a self-contained HTML prototype to \`${repoDir}/idea-prototype.html\` that visually demonstrates the idea.`,
|
|
2452
|
-
`- Must be completely self-contained (inline CSS/JS, Tailwind CDN is acceptable)`,
|
|
2453
|
-
`- Should be interactive where possible (click handlers, animations, sample data)`,
|
|
2454
|
-
`- Should look polished and professional \u2014 this will be shown to stakeholders`,
|
|
2455
|
-
`- Include realistic sample data that demonstrates the feature`,
|
|
2456
|
-
``,
|
|
2457
|
-
`### Step 4: Verify`,
|
|
2458
|
-
``,
|
|
2459
|
-
`List the files in ${repoDir} and confirm \`idea-plan.md\`, \`idea-tasks.json\`, and \`idea-prototype.html\` all exist.`,
|
|
2460
|
-
``,
|
|
2461
|
-
`IMPORTANT RULES:`,
|
|
2462
|
-
`- Write EXACTLY three files: idea-plan.md, idea-tasks.json, and idea-prototype.html`,
|
|
2463
|
-
`- Do NOT upload or POST the files anywhere \u2014 the watch handler will upload them automatically`,
|
|
2464
|
-
`- Do NOT exit until both files have been written and verified`
|
|
2465
|
-
].join("\n");
|
|
2466
|
-
}
|
|
2467
2422
|
function buildAgentArgs(agent, prompt2, mode, sessionId, name, resumeSession = false, systemPrompt, maxTurns, claudeModel) {
|
|
2468
2423
|
if (agent === "codex") {
|
|
2469
2424
|
const args = [];
|
|
@@ -2571,7 +2526,7 @@ function askYesNo(question) {
|
|
|
2571
2526
|
});
|
|
2572
2527
|
});
|
|
2573
2528
|
}
|
|
2574
|
-
function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name, resumeSession = false, onSpawnError, systemPrompt, maxTurns, claudeModel) {
|
|
2529
|
+
function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name, resumeSession = false, onSpawnError, systemPrompt, maxTurns, claudeModel, onOutputBytes) {
|
|
2575
2530
|
const jobLabel = name ?? "unknown";
|
|
2576
2531
|
console.log(`${timestamp()} ${prefix} ${paint("dim", tokenLogLine("agent", jobLabel, prompt2, systemPrompt))}`);
|
|
2577
2532
|
const { bin, args } = buildAgentArgs(agent, prompt2, "execute", sessionId, name, resumeSession, systemPrompt, maxTurns, claudeModel);
|
|
@@ -2590,12 +2545,14 @@ function spawnAgent(agent, repoDir, prompt2, prefix, onActivity, sessionId, name
|
|
|
2590
2545
|
}
|
|
2591
2546
|
child.stdout?.on("data", (data) => {
|
|
2592
2547
|
onActivity?.();
|
|
2548
|
+
onOutputBytes?.(data.length);
|
|
2593
2549
|
for (const line of data.toString().split("\n")) {
|
|
2594
2550
|
if (line) console.log(`${timestamp()} ${prefix} ${paint("dim", line)}`);
|
|
2595
2551
|
}
|
|
2596
2552
|
});
|
|
2597
2553
|
child.stderr?.on("data", (data) => {
|
|
2598
2554
|
onActivity?.();
|
|
2555
|
+
onOutputBytes?.(data.length);
|
|
2599
2556
|
for (const line of data.toString().split("\n")) {
|
|
2600
2557
|
if (line) logError(prefix, paint("dim", line));
|
|
2601
2558
|
}
|
|
@@ -2704,7 +2661,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
2704
2661
|
logWarn(watchTag(), `Network unavailable \u2014 pausing active tasks until connectivity returns (${reason})`);
|
|
2705
2662
|
}
|
|
2706
2663
|
for (const taskId of active.keys()) {
|
|
2707
|
-
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "
|
|
2664
|
+
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
|
|
2708
2665
|
if (nonTaskPrefixes.some((prefix) => taskId.startsWith(prefix))) continue;
|
|
2709
2666
|
pauseTaskForNetwork(taskId, reason);
|
|
2710
2667
|
}
|
|
@@ -2751,10 +2708,17 @@ var watchCommand = new Command8("watch").description(
|
|
|
2751
2708
|
const sid = shortId(task.id);
|
|
2752
2709
|
const slug = slugify(task.title);
|
|
2753
2710
|
const owner = ownerPrefix(task);
|
|
2754
|
-
|
|
2711
|
+
let branchName = taskBranchName(task);
|
|
2755
2712
|
const legacyBranchName = `mr/${sid}/${slug}`;
|
|
2756
2713
|
const prefix = taskTag(sid);
|
|
2757
2714
|
const vcs = detectVcs(repoDir)?.provider ?? "github";
|
|
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
|
+
}
|
|
2758
2722
|
logDispatch(prefix, `"${paint("bold", task.title)}" ${paint("gray", repoDir)} ${paint("dim", `[${vcs}]`)}`);
|
|
2759
2723
|
await postTaskUpdate(task.id, `Agent dispatched \u2014 starting work on "${task.title}"`, "system");
|
|
2760
2724
|
let subtasks = [];
|
|
@@ -2829,7 +2793,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
2829
2793
|
cleanupRepoDir: cleanupWorktreePath ? repoDir : void 0,
|
|
2830
2794
|
cleanupWorktreePath,
|
|
2831
2795
|
startedAt: Date.now(),
|
|
2832
|
-
lastActivityAt: Date.now()
|
|
2796
|
+
lastActivityAt: Date.now(),
|
|
2797
|
+
outputBytes: 0
|
|
2833
2798
|
};
|
|
2834
2799
|
const touchActivity = () => {
|
|
2835
2800
|
activeEntry.lastActivityAt = Date.now();
|
|
@@ -2858,7 +2823,8 @@ var watchCommand = new Command8("watch").description(
|
|
|
2858
2823
|
const shouldResumeClaudeSession = attemptAgent === "claude" && !!task.claudeSessionId && !resumeAlreadyRetried && (hasFeedback || pausedForNetwork?.resumeSession === true);
|
|
2859
2824
|
const sessionId = attemptAgent === "claude" ? shouldResumeClaudeSession ? task.claudeSessionId : randomUUID() : void 0;
|
|
2860
2825
|
const effectiveClaudeModel = attemptAgent === "claude" ? taskClaudeModel : void 0;
|
|
2861
|
-
const
|
|
2826
|
+
const systemSections = ["quiet-mode", ...EXECUTION_SYSTEM_SECTIONS];
|
|
2827
|
+
const executionSystemPrompt = composeSystemPrompt(systemSections);
|
|
2862
2828
|
const child = spawnAgent(
|
|
2863
2829
|
attemptAgent,
|
|
2864
2830
|
executionDir,
|
|
@@ -2873,7 +2839,10 @@ var watchCommand = new Command8("watch").description(
|
|
|
2873
2839
|
},
|
|
2874
2840
|
executionSystemPrompt,
|
|
2875
2841
|
void 0,
|
|
2876
|
-
effectiveClaudeModel
|
|
2842
|
+
effectiveClaudeModel,
|
|
2843
|
+
(bytes) => {
|
|
2844
|
+
activeEntry.outputBytes += bytes;
|
|
2845
|
+
}
|
|
2877
2846
|
);
|
|
2878
2847
|
activeEntry.process = child;
|
|
2879
2848
|
activeEntry.currentAgent = attemptAgent;
|
|
@@ -2903,31 +2872,48 @@ var watchCommand = new Command8("watch").description(
|
|
|
2903
2872
|
logWarn(prefix, `${attemptAgent} paused after network loss (${failureDetail})`);
|
|
2904
2873
|
return;
|
|
2905
2874
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
);
|
|
2914
|
-
|
|
2915
|
-
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;
|
|
2916
2884
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
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
|
+
}
|
|
2928
2909
|
}
|
|
2929
2910
|
}
|
|
2930
2911
|
finishing.add(task.id);
|
|
2912
|
+
const elapsedMs = Date.now() - activeEntry.startedAt;
|
|
2913
|
+
const outputTokenEstimate = Math.ceil(activeEntry.outputBytes / 4);
|
|
2914
|
+
console.log(
|
|
2915
|
+
`${timestamp()} ${prefix} ${paint("dim", `[output] output=~${formatTokenCount(outputTokenEstimate)} elapsed=${Math.round(elapsedMs / 1e3)}s exit=${code}`)}`
|
|
2916
|
+
);
|
|
2931
2917
|
try {
|
|
2932
2918
|
if (code === 0) {
|
|
2933
2919
|
try {
|
|
@@ -3266,7 +3252,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
3266
3252
|
active.delete(key);
|
|
3267
3253
|
}
|
|
3268
3254
|
const failedAttempt = code !== 0 || spawnFailureReason !== null;
|
|
3269
|
-
if (failedAttempt) {
|
|
3255
|
+
if (failedAttempt && !activeEntry.terminatedForError) {
|
|
3270
3256
|
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
3271
3257
|
if (nextAgent) {
|
|
3272
3258
|
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
@@ -3376,7 +3362,7 @@ var watchCommand = new Command8("watch").description(
|
|
|
3376
3362
|
active.delete(key);
|
|
3377
3363
|
}
|
|
3378
3364
|
const failedAttempt = code !== 0 || spawnFailureReason !== null;
|
|
3379
|
-
if (failedAttempt) {
|
|
3365
|
+
if (failedAttempt && !activeEntry.terminatedForError) {
|
|
3380
3366
|
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
3381
3367
|
if (nextAgent) {
|
|
3382
3368
|
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
@@ -3413,158 +3399,6 @@ var watchCommand = new Command8("watch").description(
|
|
|
3413
3399
|
};
|
|
3414
3400
|
await launchAttempt(attemptOrder[attemptIndex]);
|
|
3415
3401
|
}
|
|
3416
|
-
async function dispatchIdeaJob(idea, repoDir) {
|
|
3417
|
-
const sid = shortId(idea.id);
|
|
3418
|
-
const prefix = ideaTag(sid);
|
|
3419
|
-
logDispatch(prefix, `"${paint("bold", idea.title)}"${idea.feedback ? paint("cyan", " (iteration)") : ""} ${paint("gray", repoDir)}`);
|
|
3420
|
-
for (const f of ["idea-plan.md", "idea-tasks.json", "idea-prototype.html"]) {
|
|
3421
|
-
try {
|
|
3422
|
-
unlinkSync(resolve2(repoDir, f));
|
|
3423
|
-
} catch {
|
|
3424
|
-
}
|
|
3425
|
-
}
|
|
3426
|
-
const prompt2 = buildIdeaPrompt(idea, repoDir);
|
|
3427
|
-
const key = `idea-${idea.id}`;
|
|
3428
|
-
const attemptOrder = await resolveAgentChain(agent);
|
|
3429
|
-
if (attemptOrder.length === 0) {
|
|
3430
|
-
logError(prefix, `No available agents found for fallback chain starting at ${agent}`);
|
|
3431
|
-
queued.delete(key);
|
|
3432
|
-
try {
|
|
3433
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3434
|
-
} catch {
|
|
3435
|
-
}
|
|
3436
|
-
return;
|
|
3437
|
-
}
|
|
3438
|
-
const activeEntry = {
|
|
3439
|
-
process: void 0,
|
|
3440
|
-
title: idea.title,
|
|
3441
|
-
repoDir,
|
|
3442
|
-
startedAt: Date.now(),
|
|
3443
|
-
lastActivityAt: Date.now()
|
|
3444
|
-
};
|
|
3445
|
-
let attemptIndex = 0;
|
|
3446
|
-
const launchAttempt = async (attemptAgent) => {
|
|
3447
|
-
let spawnFailureReason = null;
|
|
3448
|
-
const child = spawnAgent(
|
|
3449
|
-
attemptAgent,
|
|
3450
|
-
repoDir,
|
|
3451
|
-
prompt2,
|
|
3452
|
-
prefix,
|
|
3453
|
-
void 0,
|
|
3454
|
-
void 0,
|
|
3455
|
-
idea.title,
|
|
3456
|
-
false,
|
|
3457
|
-
(err) => {
|
|
3458
|
-
spawnFailureReason = err.message;
|
|
3459
|
-
}
|
|
3460
|
-
);
|
|
3461
|
-
activeEntry.process = child;
|
|
3462
|
-
activeEntry.currentAgent = attemptAgent;
|
|
3463
|
-
active.set(key, activeEntry);
|
|
3464
|
-
child.on("exit", async (code) => {
|
|
3465
|
-
if (active.get(key)?.process === child) {
|
|
3466
|
-
active.delete(key);
|
|
3467
|
-
}
|
|
3468
|
-
const failedAttempt = code !== 0 || spawnFailureReason !== null;
|
|
3469
|
-
if (failedAttempt) {
|
|
3470
|
-
const nextAgent = attemptOrder[attemptIndex + 1];
|
|
3471
|
-
if (nextAgent) {
|
|
3472
|
-
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
3473
|
-
logWarn(prefix, `${attemptAgent} failed (${failureDetail}) \u2014 retrying idea generation with ${nextAgent}`);
|
|
3474
|
-
attemptIndex += 1;
|
|
3475
|
-
await launchAttempt(nextAgent);
|
|
3476
|
-
return;
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
finishing.add(key);
|
|
3480
|
-
try {
|
|
3481
|
-
if (code === 0) {
|
|
3482
|
-
try {
|
|
3483
|
-
let plan;
|
|
3484
|
-
let protoHtml;
|
|
3485
|
-
let followUpTasks;
|
|
3486
|
-
const planPath = resolve2(repoDir, "idea-plan.md");
|
|
3487
|
-
const tasksPath = resolve2(repoDir, "idea-tasks.json");
|
|
3488
|
-
const protoPath = resolve2(repoDir, "idea-prototype.html");
|
|
3489
|
-
try {
|
|
3490
|
-
plan = readFileSync5(planPath, "utf-8");
|
|
3491
|
-
} catch {
|
|
3492
|
-
}
|
|
3493
|
-
try {
|
|
3494
|
-
protoHtml = readFileSync5(protoPath, "utf-8");
|
|
3495
|
-
} catch {
|
|
3496
|
-
}
|
|
3497
|
-
try {
|
|
3498
|
-
const tasksRaw = readFileSync5(tasksPath, "utf-8");
|
|
3499
|
-
const parsed = JSON.parse(tasksRaw);
|
|
3500
|
-
if (Array.isArray(parsed)) {
|
|
3501
|
-
followUpTasks = parsed.filter((t) => t && typeof t === "object" && "title" in t);
|
|
3502
|
-
}
|
|
3503
|
-
} catch {
|
|
3504
|
-
}
|
|
3505
|
-
if (!plan && !protoHtml) {
|
|
3506
|
-
logError(prefix, `No output files found in ${repoDir}`);
|
|
3507
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3508
|
-
return;
|
|
3509
|
-
}
|
|
3510
|
-
const updateData = { status: "generated" };
|
|
3511
|
-
if (plan) updateData.plan = plan;
|
|
3512
|
-
if (followUpTasks) updateData.followUpTasks = followUpTasks;
|
|
3513
|
-
if (protoHtml) {
|
|
3514
|
-
try {
|
|
3515
|
-
const proto = await api.post("/api/prototypes", {
|
|
3516
|
-
title: `${idea.title} \u2014 Idea Prototype`,
|
|
3517
|
-
prompt: idea.description || idea.title,
|
|
3518
|
-
variantCount: 1,
|
|
3519
|
-
projectId: idea.projectId ?? null
|
|
3520
|
-
});
|
|
3521
|
-
await api.patch(`/api/prototypes/${proto.id}`, {
|
|
3522
|
-
status: "completed",
|
|
3523
|
-
files: [{ name: "idea-prototype.html", content: protoHtml }]
|
|
3524
|
-
});
|
|
3525
|
-
updateData.generatedPrototypeId = proto.id;
|
|
3526
|
-
logSuccess(prefix, `Prototype created: ${paint("gray", proto.id.slice(0, 8))}`);
|
|
3527
|
-
} catch (err) {
|
|
3528
|
-
logError(prefix, `Failed to create prototype: ${err.message}`);
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
await api.patch(`/api/ideas/${idea.id}`, updateData);
|
|
3532
|
-
logSuccess(prefix, `"${paint("bold", idea.title)}" generation complete`);
|
|
3533
|
-
try {
|
|
3534
|
-
unlinkSync(planPath);
|
|
3535
|
-
} catch {
|
|
3536
|
-
}
|
|
3537
|
-
try {
|
|
3538
|
-
unlinkSync(tasksPath);
|
|
3539
|
-
} catch {
|
|
3540
|
-
}
|
|
3541
|
-
try {
|
|
3542
|
-
unlinkSync(protoPath);
|
|
3543
|
-
} catch {
|
|
3544
|
-
}
|
|
3545
|
-
} catch (err) {
|
|
3546
|
-
logError(prefix, `Failed to upload idea output: ${err.message}`);
|
|
3547
|
-
try {
|
|
3548
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3549
|
-
} catch {
|
|
3550
|
-
}
|
|
3551
|
-
}
|
|
3552
|
-
} else {
|
|
3553
|
-
const failureDetail = spawnFailureReason ?? `exit code ${code}`;
|
|
3554
|
-
logError(prefix, `"${paint("bold", idea.title)}" generation failed via ${attemptAgent} (${failureDetail})`);
|
|
3555
|
-
try {
|
|
3556
|
-
await api.patch(`/api/ideas/${idea.id}`, { status: "draft" });
|
|
3557
|
-
} catch {
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
} finally {
|
|
3561
|
-
queued.delete(key);
|
|
3562
|
-
finishing.delete(key);
|
|
3563
|
-
}
|
|
3564
|
-
});
|
|
3565
|
-
};
|
|
3566
|
-
await launchAttempt(attemptOrder[attemptIndex]);
|
|
3567
|
-
}
|
|
3568
3402
|
function dispatchScan(scan, prefix, key) {
|
|
3569
3403
|
logDispatch(prefix, `Running scan for project ${paint("cyan", scan.projectId.slice(0, 8))}`);
|
|
3570
3404
|
const scanProc = spawn4(process.execPath, [process.argv[1], "scan", "--project", scan.projectId, "--report", scan.id], {
|
|
@@ -3701,6 +3535,7 @@ ${divider}`);
|
|
|
3701
3535
|
const running = active.get(task.id);
|
|
3702
3536
|
if (running) {
|
|
3703
3537
|
logWarn(prefix, `Task exceeded hang timeout after ${formatElapsed(Date.now() - running.startedAt)}, terminating agent\u2026`);
|
|
3538
|
+
running.terminatedForError = true;
|
|
3704
3539
|
running.process.kill("SIGTERM");
|
|
3705
3540
|
active.delete(task.id);
|
|
3706
3541
|
}
|
|
@@ -3757,7 +3592,7 @@ ${divider}`);
|
|
|
3757
3592
|
queued.delete(taskId);
|
|
3758
3593
|
}
|
|
3759
3594
|
}
|
|
3760
|
-
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "
|
|
3595
|
+
const nonTaskPrefixes = ["proto-", "repo-", "scan-", "test-"];
|
|
3761
3596
|
for (const taskId of failed.keys()) {
|
|
3762
3597
|
if (nonTaskPrefixes.some((p) => taskId.startsWith(p))) continue;
|
|
3763
3598
|
if (!activeTaskIds.has(taskId)) failed.delete(taskId);
|
|
@@ -3826,6 +3661,7 @@ ${divider}`);
|
|
|
3826
3661
|
for (const [key, entry] of active) {
|
|
3827
3662
|
if (key.startsWith("proto-") && !inProgressProtoKeys.has(key)) {
|
|
3828
3663
|
logWarn(watchTag(), `Prototype ${paint("yellow", key)} no longer in_progress, terminating\u2026`);
|
|
3664
|
+
entry.terminatedForError = true;
|
|
3829
3665
|
entry.process.kill("SIGTERM");
|
|
3830
3666
|
active.delete(key);
|
|
3831
3667
|
queued.delete(key);
|
|
@@ -4039,59 +3875,6 @@ ${divider}`);
|
|
|
4039
3875
|
}
|
|
4040
3876
|
dispatchRepoCreation(project, rootDir);
|
|
4041
3877
|
}
|
|
4042
|
-
let generatingIdeas = [];
|
|
4043
|
-
try {
|
|
4044
|
-
generatingIdeas = await api.get("/api/ideas?status=generating");
|
|
4045
|
-
} catch (err) {
|
|
4046
|
-
logError(watchTag(), `Failed to fetch generating ideas: ${err.message}`);
|
|
4047
|
-
}
|
|
4048
|
-
const inProgressIdeaKeys = new Set(generatingIdeas.map((i) => `idea-${i.id}`));
|
|
4049
|
-
for (const [key, entry] of active) {
|
|
4050
|
-
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) {
|
|
4051
|
-
logWarn(watchTag(), `Idea ${paint("yellow", key)} no longer generating, terminating\u2026`);
|
|
4052
|
-
entry.process.kill("SIGTERM");
|
|
4053
|
-
active.delete(key);
|
|
4054
|
-
queued.delete(key);
|
|
4055
|
-
}
|
|
4056
|
-
}
|
|
4057
|
-
for (const key of failed.keys()) {
|
|
4058
|
-
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key)) failed.delete(key);
|
|
4059
|
-
}
|
|
4060
|
-
for (const key of queued) {
|
|
4061
|
-
if (key.startsWith("idea-") && !inProgressIdeaKeys.has(key) && !finishing.has(key)) queued.delete(key);
|
|
4062
|
-
}
|
|
4063
|
-
const activeIdeaCount = [...active.keys()].filter((k) => k.startsWith("idea-")).length;
|
|
4064
|
-
let ideaSlots = Math.max(0, 1 - activeIdeaCount);
|
|
4065
|
-
for (const idea of generatingIdeas) {
|
|
4066
|
-
if (ideaSlots <= 0) break;
|
|
4067
|
-
const key = `idea-${idea.id}`;
|
|
4068
|
-
if (queued.has(key)) continue;
|
|
4069
|
-
if (finishing.has(key)) continue;
|
|
4070
|
-
if (failed.has(key)) continue;
|
|
4071
|
-
const sid = shortId(idea.id);
|
|
4072
|
-
const prefix = ideaTag(sid);
|
|
4073
|
-
let repoDir;
|
|
4074
|
-
if (idea.projectId) {
|
|
4075
|
-
const dir = findDirectoryForProject(config, idea.projectId, rootDir);
|
|
4076
|
-
if (!dir) {
|
|
4077
|
-
logWarn(prefix, `"${idea.title}": no linked directory found \u2014 skipping`);
|
|
4078
|
-
continue;
|
|
4079
|
-
}
|
|
4080
|
-
repoDir = dir;
|
|
4081
|
-
} else {
|
|
4082
|
-
repoDir = rootDir;
|
|
4083
|
-
}
|
|
4084
|
-
queued.add(key);
|
|
4085
|
-
ideaSlots--;
|
|
4086
|
-
if (dryRun) {
|
|
4087
|
-
logInfo(
|
|
4088
|
-
watchTag(),
|
|
4089
|
-
`${paint("yellow", "[dry-run]")} would dispatch idea "${paint("bold", idea.title)}" in ${paint("cyan", repoDir)}`
|
|
4090
|
-
);
|
|
4091
|
-
continue;
|
|
4092
|
-
}
|
|
4093
|
-
dispatchIdeaJob(idea, repoDir);
|
|
4094
|
-
}
|
|
4095
3878
|
let pendingScans = [];
|
|
4096
3879
|
try {
|
|
4097
3880
|
pendingScans = await api.get("/api/scans?status=pending&limit=5");
|
|
@@ -4716,7 +4499,7 @@ async function checkApiConnectivity() {
|
|
|
4716
4499
|
}
|
|
4717
4500
|
}
|
|
4718
4501
|
function printResults(checks) {
|
|
4719
|
-
const maxNameLen = Math.max(...checks.map((
|
|
4502
|
+
const maxNameLen = Math.max(...checks.map((c11) => c11.name.length));
|
|
4720
4503
|
let allOk = true;
|
|
4721
4504
|
for (const check of checks) {
|
|
4722
4505
|
const isOptional = check.optional ?? false;
|
|
@@ -4730,10 +4513,10 @@ function printResults(checks) {
|
|
|
4730
4513
|
}
|
|
4731
4514
|
async function autoFix(checks, agent) {
|
|
4732
4515
|
const { spawn: spawn8 } = await import("child_process");
|
|
4733
|
-
const ghInstalled = checks.find((
|
|
4734
|
-
const ghAuthed = checks.find((
|
|
4735
|
-
const mrAuthed = checks.find((
|
|
4736
|
-
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)");
|
|
4737
4520
|
if (claudeCheck && !claudeCheck.ok && agent === "claude") {
|
|
4738
4521
|
console.log(paint5("cyan", " Installing Claude Code..."));
|
|
4739
4522
|
console.log(paint5("dim", " Running: curl -fsSL https://claude.ai/install.sh | bash"));
|
|
@@ -4796,7 +4579,7 @@ var setupCommand = new Command14("setup").description("Check that all dependenci
|
|
|
4796
4579
|
console.log("");
|
|
4797
4580
|
return;
|
|
4798
4581
|
}
|
|
4799
|
-
const fixes = checks.filter((
|
|
4582
|
+
const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
|
|
4800
4583
|
if (fixes.length > 0) {
|
|
4801
4584
|
console.log(paint5("yellow", " To fix:"));
|
|
4802
4585
|
for (const fix of fixes) {
|
|
@@ -5989,10 +5772,10 @@ ${codebaseAnalysis.routes.map((r) => `- ${r}`).join("\n")}
|
|
|
5989
5772
|
${codebaseAnalysis.prismaModels.map((m) => `- ${m}`).join("\n")}
|
|
5990
5773
|
|
|
5991
5774
|
**Components:**
|
|
5992
|
-
${codebaseAnalysis.components.slice(0, 15).map((
|
|
5775
|
+
${codebaseAnalysis.components.slice(0, 15).map((c11) => `- ${c11}`).join("\n")}
|
|
5993
5776
|
|
|
5994
5777
|
**Recent Git Commits:**
|
|
5995
|
-
${codebaseAnalysis.recentCommits.slice(0, 8).map((
|
|
5778
|
+
${codebaseAnalysis.recentCommits.slice(0, 8).map((c11) => `- ${c11}`).join("\n")}
|
|
5996
5779
|
|
|
5997
5780
|
**Completed Tasks:**
|
|
5998
5781
|
${context.completedTasks.slice(0, 10).map((t) => `- ${t.title}`).join("\n") || "None"}
|
|
@@ -6439,128 +6222,8 @@ var scanCommand = new Command23("scan").description("Run a product scan on the c
|
|
|
6439
6222
|
}
|
|
6440
6223
|
});
|
|
6441
6224
|
|
|
6442
|
-
// cli/commands/idea.ts
|
|
6443
|
-
import { Command as Command24 } from "commander";
|
|
6444
|
-
var c9 = {
|
|
6445
|
-
reset: "\x1B[0m",
|
|
6446
|
-
bold: "\x1B[1m",
|
|
6447
|
-
dim: "\x1B[2m",
|
|
6448
|
-
cyan: "\x1B[36m",
|
|
6449
|
-
green: "\x1B[32m",
|
|
6450
|
-
yellow: "\x1B[33m",
|
|
6451
|
-
red: "\x1B[31m",
|
|
6452
|
-
blue: "\x1B[34m",
|
|
6453
|
-
gray: "\x1B[90m",
|
|
6454
|
-
magenta: "\x1B[35m"
|
|
6455
|
-
};
|
|
6456
|
-
function paint9(color, text) {
|
|
6457
|
-
return `${c9[color]}${text}${c9.reset}`;
|
|
6458
|
-
}
|
|
6459
|
-
function statusBadge2(status) {
|
|
6460
|
-
switch (status) {
|
|
6461
|
-
case "draft":
|
|
6462
|
-
return paint9("gray", "\u25CB draft");
|
|
6463
|
-
case "generating":
|
|
6464
|
-
return paint9("cyan", "\u27F3 generating");
|
|
6465
|
-
case "generated":
|
|
6466
|
-
return paint9("green", "\u2713 generated");
|
|
6467
|
-
case "promoted":
|
|
6468
|
-
return paint9("magenta", "\u2191 promoted");
|
|
6469
|
-
case "archived":
|
|
6470
|
-
return paint9("dim", "\u2298 archived");
|
|
6471
|
-
default:
|
|
6472
|
-
return paint9("gray", status);
|
|
6473
|
-
}
|
|
6474
|
-
}
|
|
6475
|
-
var ideaCommand = new Command24("idea").description("Manage ideas \u2014 brainstorm, generate prototypes & plans").addCommand(
|
|
6476
|
-
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) => {
|
|
6477
|
-
const params = new URLSearchParams();
|
|
6478
|
-
if (!opts.all) {
|
|
6479
|
-
const projectId = getLinkedProjectId();
|
|
6480
|
-
if (projectId) {
|
|
6481
|
-
params.set("projectId", projectId);
|
|
6482
|
-
}
|
|
6483
|
-
}
|
|
6484
|
-
if (opts.status) params.set("status", opts.status);
|
|
6485
|
-
const ideas = await api.get(`/api/ideas?${params.toString()}`);
|
|
6486
|
-
if (ideas.length === 0) {
|
|
6487
|
-
console.log(paint9("gray", "No ideas found."));
|
|
6488
|
-
return;
|
|
6489
|
-
}
|
|
6490
|
-
console.log();
|
|
6491
|
-
for (const idea of ideas) {
|
|
6492
|
-
const date = new Date(idea.createdAt).toLocaleDateString();
|
|
6493
|
-
console.log(
|
|
6494
|
-
` ${paint9("bold", idea.title)} ${statusBadge2(idea.status)} ${paint9("gray", idea.id.slice(0, 8))} ${paint9("dim", date)}`
|
|
6495
|
-
);
|
|
6496
|
-
if (idea.description) {
|
|
6497
|
-
console.log(` ${paint9("dim", idea.description.slice(0, 80) + (idea.description.length > 80 ? "\u2026" : ""))}`);
|
|
6498
|
-
}
|
|
6499
|
-
console.log();
|
|
6500
|
-
}
|
|
6501
|
-
})
|
|
6502
|
-
).addCommand(
|
|
6503
|
-
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) => {
|
|
6504
|
-
const projectId = opts.project ?? getLinkedProjectId() ?? null;
|
|
6505
|
-
const idea = await api.post("/api/ideas", {
|
|
6506
|
-
title,
|
|
6507
|
-
description: opts.description,
|
|
6508
|
-
projectId
|
|
6509
|
-
});
|
|
6510
|
-
console.log();
|
|
6511
|
-
console.log(` ${paint9("green", "\u2713")} Created idea: ${paint9("bold", idea.title)}`);
|
|
6512
|
-
console.log(` ${paint9("gray", "ID:")} ${idea.id}`);
|
|
6513
|
-
if (opts.generate) {
|
|
6514
|
-
await api.post(`/api/ideas/${idea.id}/generate`);
|
|
6515
|
-
console.log(` ${paint9("cyan", "\u27F3")} Generation will begin automatically via the watch agent.`);
|
|
6516
|
-
}
|
|
6517
|
-
console.log();
|
|
6518
|
-
})
|
|
6519
|
-
).addCommand(
|
|
6520
|
-
new Command24("generate").description("Start generating plan & prototype for an idea").argument("<id>", "Idea ID").action(async (id) => {
|
|
6521
|
-
const idea = await api.post(`/api/ideas/${id}/generate`);
|
|
6522
|
-
console.log();
|
|
6523
|
-
console.log(` ${paint9("cyan", "\u27F3")} Generating: ${paint9("bold", idea.title)}`);
|
|
6524
|
-
console.log(` ${paint9("gray", "The watch agent will pick this up shortly.")}`);
|
|
6525
|
-
console.log();
|
|
6526
|
-
})
|
|
6527
|
-
).addCommand(
|
|
6528
|
-
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) => {
|
|
6529
|
-
const idea = await api.post(`/api/ideas/${id}/feedback`, { feedback });
|
|
6530
|
-
console.log();
|
|
6531
|
-
console.log(` ${paint9("cyan", "\u27F3")} Feedback sent for: ${paint9("bold", idea.title)}`);
|
|
6532
|
-
console.log(` ${paint9("gray", "The watch agent will re-generate with your feedback.")}`);
|
|
6533
|
-
console.log();
|
|
6534
|
-
})
|
|
6535
|
-
).addCommand(
|
|
6536
|
-
new Command24("promote").description("Promote an idea to a task").argument("<id>", "Idea ID").action(async (id) => {
|
|
6537
|
-
const result = await api.post(`/api/ideas/${id}/promote`);
|
|
6538
|
-
console.log();
|
|
6539
|
-
console.log(` ${paint9("green", "\u2713")} Promoted idea to task: ${paint9("bold", result.task.title)}`);
|
|
6540
|
-
console.log(` ${paint9("gray", "Task ID:")} ${result.task.id}`);
|
|
6541
|
-
console.log();
|
|
6542
|
-
})
|
|
6543
|
-
).addCommand(
|
|
6544
|
-
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) => {
|
|
6545
|
-
const body = {};
|
|
6546
|
-
if (opts.name) body.name = opts.name;
|
|
6547
|
-
const result = await api.post(`/api/ideas/${id}/spin-up`, body);
|
|
6548
|
-
console.log();
|
|
6549
|
-
console.log(` ${paint9("green", "\u2713")} Spinning up project: ${paint9("bold", result.project.name)}`);
|
|
6550
|
-
console.log(` ${paint9("gray", "Project ID:")} ${result.project.id}`);
|
|
6551
|
-
if (result.tasks && result.tasks.length > 0) {
|
|
6552
|
-
console.log(` ${paint9("green", "\u2713")} Created ${result.tasks.length} follow-up task(s):`);
|
|
6553
|
-
for (const task of result.tasks) {
|
|
6554
|
-
console.log(` ${paint9("gray", "\u2022")} ${task.title}`);
|
|
6555
|
-
}
|
|
6556
|
-
}
|
|
6557
|
-
console.log(` ${paint9("cyan", "\u27F3")} Repo creation is queued \u2014 the watch daemon will pick it up.`);
|
|
6558
|
-
console.log();
|
|
6559
|
-
})
|
|
6560
|
-
);
|
|
6561
|
-
|
|
6562
6225
|
// cli/commands/doctor.ts
|
|
6563
|
-
import { Command as
|
|
6226
|
+
import { Command as Command24 } from "commander";
|
|
6564
6227
|
import { existsSync as existsSync15 } from "fs";
|
|
6565
6228
|
import { homedir as homedir2 } from "os";
|
|
6566
6229
|
import { join as join11 } from "path";
|
|
@@ -6608,7 +6271,7 @@ async function checkProjectLink() {
|
|
|
6608
6271
|
optional: true
|
|
6609
6272
|
};
|
|
6610
6273
|
}
|
|
6611
|
-
var doctorCommand = new
|
|
6274
|
+
var doctorCommand = new Command24("doctor").description("Diagnose Mr. Manager CLI installation and environment").action(async () => {
|
|
6612
6275
|
const banner = [
|
|
6613
6276
|
``,
|
|
6614
6277
|
paint5("cyan", ` MR DOCTOR`),
|
|
@@ -6639,7 +6302,7 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
|
|
|
6639
6302
|
console.log("");
|
|
6640
6303
|
return;
|
|
6641
6304
|
}
|
|
6642
|
-
const fixes = checks.filter((
|
|
6305
|
+
const fixes = checks.filter((c11) => !c11.ok && c11.fix && !c11.optional);
|
|
6643
6306
|
if (fixes.length > 0) {
|
|
6644
6307
|
console.log(paint5("yellow", " To fix:"));
|
|
6645
6308
|
for (const fix of fixes) {
|
|
@@ -6651,14 +6314,14 @@ var doctorCommand = new Command25("doctor").description("Diagnose Mr. Manager CL
|
|
|
6651
6314
|
});
|
|
6652
6315
|
|
|
6653
6316
|
// cli/commands/prompt-audit.ts
|
|
6654
|
-
import { Command as
|
|
6317
|
+
import { Command as Command25 } from "commander";
|
|
6655
6318
|
import { resolve as resolve8 } from "path";
|
|
6656
6319
|
import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
|
|
6657
6320
|
function auditLine(label, tokens) {
|
|
6658
6321
|
const bar = "\u2588".repeat(Math.min(60, Math.round(tokens / 200)));
|
|
6659
6322
|
return ` ${label.padEnd(30)} ${formatTokenCount(tokens).padStart(8)} ${bar}`;
|
|
6660
6323
|
}
|
|
6661
|
-
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) => {
|
|
6662
6325
|
const results = [];
|
|
6663
6326
|
if (opts.task) {
|
|
6664
6327
|
try {
|
|
@@ -6818,14 +6481,6 @@ ${task.notes}` : "";
|
|
|
6818
6481
|
{ name: "instructions", tokens: 500 }
|
|
6819
6482
|
]
|
|
6820
6483
|
},
|
|
6821
|
-
{
|
|
6822
|
-
jobType: "idea",
|
|
6823
|
-
description: "Idea generation prompt",
|
|
6824
|
-
sections: [
|
|
6825
|
-
{ name: "core-prompt", tokens: 500 },
|
|
6826
|
-
{ name: "idea-desc", tokens: 200 }
|
|
6827
|
-
]
|
|
6828
|
-
},
|
|
6829
6484
|
{
|
|
6830
6485
|
jobType: "repo-creation",
|
|
6831
6486
|
description: "Repository creation prompt",
|
|
@@ -6871,8 +6526,8 @@ ${r.jobType} [${r.identifier}]`);
|
|
|
6871
6526
|
});
|
|
6872
6527
|
|
|
6873
6528
|
// cli/commands/skill.ts
|
|
6874
|
-
import { Command as
|
|
6875
|
-
var
|
|
6529
|
+
import { Command as Command26 } from "commander";
|
|
6530
|
+
var c9 = {
|
|
6876
6531
|
reset: "\x1B[0m",
|
|
6877
6532
|
bold: "\x1B[1m",
|
|
6878
6533
|
dim: "\x1B[2m",
|
|
@@ -6880,7 +6535,7 @@ var c10 = {
|
|
|
6880
6535
|
green: "\x1B[32m",
|
|
6881
6536
|
yellow: "\x1B[33m"
|
|
6882
6537
|
};
|
|
6883
|
-
var skillCommand = new
|
|
6538
|
+
var skillCommand = new Command26("skill").description("Manage skills \u2014 reusable playbooks for AI agents");
|
|
6884
6539
|
skillCommand.command("list").alias("ls").description("List all skills").option("--category <category>", "Filter by category").action(async (opts) => {
|
|
6885
6540
|
const params = new URLSearchParams();
|
|
6886
6541
|
if (opts.category) params.set("category", opts.category);
|
|
@@ -6888,17 +6543,17 @@ skillCommand.command("list").alias("ls").description("List all skills").option("
|
|
|
6888
6543
|
`/api/skills${params.toString() ? `?${params}` : ""}`
|
|
6889
6544
|
);
|
|
6890
6545
|
if (skills.length === 0) {
|
|
6891
|
-
console.log(`${
|
|
6546
|
+
console.log(`${c9.dim}No skills found.${c9.reset}`);
|
|
6892
6547
|
return;
|
|
6893
6548
|
}
|
|
6894
6549
|
for (const skill of skills) {
|
|
6895
|
-
const cat = skill.category ? ` ${
|
|
6896
|
-
const scope = skill.projectId ? ` ${
|
|
6897
|
-
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}`);
|
|
6898
6553
|
if (skill.description) {
|
|
6899
|
-
console.log(` ${
|
|
6554
|
+
console.log(` ${c9.dim}${skill.description}${c9.reset}`);
|
|
6900
6555
|
}
|
|
6901
|
-
console.log(` ${
|
|
6556
|
+
console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
|
|
6902
6557
|
}
|
|
6903
6558
|
});
|
|
6904
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) => {
|
|
@@ -6934,7 +6589,7 @@ skillCommand.command("create").description("Create a new skill from a markdown f
|
|
|
6934
6589
|
projectId
|
|
6935
6590
|
});
|
|
6936
6591
|
console.log(
|
|
6937
|
-
`${
|
|
6592
|
+
`${c9.green}Created skill:${c9.reset} ${c9.bold}${skill.name}${c9.reset} ${c9.dim}(${skill.id})${c9.reset}`
|
|
6938
6593
|
);
|
|
6939
6594
|
});
|
|
6940
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) => {
|
|
@@ -6948,22 +6603,22 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
6948
6603
|
process.exit(1);
|
|
6949
6604
|
}
|
|
6950
6605
|
}
|
|
6951
|
-
console.log(`${
|
|
6606
|
+
console.log(`${c9.dim}Generating skill...${c9.reset}`);
|
|
6952
6607
|
try {
|
|
6953
6608
|
const skill = await api.post("/api/skills/generate", {
|
|
6954
6609
|
prompt: prompt2,
|
|
6955
6610
|
projectId
|
|
6956
6611
|
});
|
|
6957
6612
|
console.log(
|
|
6958
|
-
`${
|
|
6613
|
+
`${c9.green}Generated skill:${c9.reset} ${c9.bold}${skill.name}${c9.reset}`
|
|
6959
6614
|
);
|
|
6960
6615
|
if (skill.description) {
|
|
6961
|
-
console.log(` ${
|
|
6616
|
+
console.log(` ${c9.dim}${skill.description}${c9.reset}`);
|
|
6962
6617
|
}
|
|
6963
6618
|
if (skill.category) {
|
|
6964
|
-
console.log(` ${
|
|
6619
|
+
console.log(` ${c9.dim}Category: ${skill.category}${c9.reset}`);
|
|
6965
6620
|
}
|
|
6966
|
-
console.log(` ${
|
|
6621
|
+
console.log(` ${c9.dim}id: ${skill.id}${c9.reset}`);
|
|
6967
6622
|
} catch (err) {
|
|
6968
6623
|
console.error(`Failed to generate skill: ${err.message}`);
|
|
6969
6624
|
process.exit(1);
|
|
@@ -6971,17 +6626,17 @@ skillCommand.command("generate").alias("gen").description("Generate a new skill
|
|
|
6971
6626
|
});
|
|
6972
6627
|
|
|
6973
6628
|
// cli/commands/tests.ts
|
|
6974
|
-
import { Command as
|
|
6975
|
-
var
|
|
6629
|
+
import { Command as Command27 } from "commander";
|
|
6630
|
+
var c10 = {
|
|
6976
6631
|
reset: "\x1B[0m",
|
|
6977
6632
|
dim: "\x1B[2m",
|
|
6978
6633
|
yellow: "\x1B[33m"
|
|
6979
6634
|
};
|
|
6980
|
-
var testsCommand = new
|
|
6635
|
+
var testsCommand = new Command27("tests").description("List MR Test scenarios for the linked project").action(async () => {
|
|
6981
6636
|
const projectId = getLinkedProjectId();
|
|
6982
6637
|
if (!projectId) {
|
|
6983
6638
|
console.error(
|
|
6984
|
-
`${
|
|
6639
|
+
`${c10.yellow}No project linked to this directory.${c10.reset} Run "mr link <project-id>" first.`
|
|
6985
6640
|
);
|
|
6986
6641
|
process.exit(1);
|
|
6987
6642
|
}
|
|
@@ -6994,13 +6649,13 @@ var testsCommand = new Command28("tests").description("List MR Test scenarios fo
|
|
|
6994
6649
|
process.exit(1);
|
|
6995
6650
|
}
|
|
6996
6651
|
if (scenarios.length === 0) {
|
|
6997
|
-
console.log(`${
|
|
6652
|
+
console.log(`${c10.dim}No test scenarios found for this project.${c10.reset}`);
|
|
6998
6653
|
return;
|
|
6999
6654
|
}
|
|
7000
6655
|
for (const scenario of scenarios) {
|
|
7001
6656
|
console.log(`### ${scenario.name}`);
|
|
7002
6657
|
if (scenario.description) {
|
|
7003
|
-
console.log(`${
|
|
6658
|
+
console.log(`${c10.dim}${scenario.description}${c10.reset}`);
|
|
7004
6659
|
console.log();
|
|
7005
6660
|
}
|
|
7006
6661
|
console.log(scenario.content);
|
|
@@ -7015,7 +6670,7 @@ var userArgs = process.argv.slice(2);
|
|
|
7015
6670
|
var bypassCommands = /* @__PURE__ */ new Set(["login", "init", "auth", "help", "--help", "-h", "--version", "-V", "doctor", "setup"]);
|
|
7016
6671
|
var shouldBypass = userArgs.length > 0 && bypassCommands.has(userArgs[0]);
|
|
7017
6672
|
if (isFirstRun && !shouldBypass) {
|
|
7018
|
-
const
|
|
6673
|
+
const c11 = {
|
|
7019
6674
|
reset: "\x1B[0m",
|
|
7020
6675
|
bold: "\x1B[1m",
|
|
7021
6676
|
dim: "\x1B[2m",
|
|
@@ -7025,28 +6680,28 @@ if (isFirstRun && !shouldBypass) {
|
|
|
7025
6680
|
magenta: "\x1B[35m"
|
|
7026
6681
|
};
|
|
7027
6682
|
console.log("");
|
|
7028
|
-
console.log(`${
|
|
7029
|
-
console.log(`${
|
|
7030
|
-
console.log(`${
|
|
7031
|
-
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}`);
|
|
7032
6687
|
console.log("");
|
|
7033
|
-
console.log(`${
|
|
7034
|
-
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}`);
|
|
7035
6690
|
console.log("");
|
|
7036
|
-
console.log(` ${
|
|
7037
|
-
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}`);
|
|
7038
6693
|
console.log("");
|
|
7039
|
-
console.log(` ${
|
|
7040
|
-
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}`);
|
|
7041
6696
|
console.log("");
|
|
7042
|
-
console.log(` ${
|
|
7043
|
-
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}`);
|
|
7044
6699
|
console.log("");
|
|
7045
|
-
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}`);
|
|
7046
6701
|
console.log("");
|
|
7047
6702
|
process.exit(0);
|
|
7048
6703
|
}
|
|
7049
|
-
var program = new
|
|
6704
|
+
var program = new Command28();
|
|
7050
6705
|
program.name("mr").description("Mr. Manager - Task and project management CLI").version(CLI_VERSION);
|
|
7051
6706
|
program.addCommand(initCommand);
|
|
7052
6707
|
program.addCommand(authCommand);
|
|
@@ -7074,7 +6729,6 @@ program.addCommand(testCommand);
|
|
|
7074
6729
|
program.addCommand(featuresCommand);
|
|
7075
6730
|
program.addCommand(noMrCommand);
|
|
7076
6731
|
program.addCommand(scanCommand);
|
|
7077
|
-
program.addCommand(ideaCommand);
|
|
7078
6732
|
program.addCommand(doctorCommand);
|
|
7079
6733
|
program.addCommand(promptAuditCommand);
|
|
7080
6734
|
program.addCommand(skillCommand);
|