@dunnewold-labs/mr-manager 0.4.46 → 0.4.48
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 +123 -10
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -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.48",
|
|
189
189
|
description: "Mr. Manager - Task and project management CLI",
|
|
190
190
|
bin: {
|
|
191
191
|
mr: "./dist/index.mjs"
|
|
@@ -550,7 +550,7 @@ var contextCommand = new Command8("context").description("Output project context
|
|
|
550
550
|
import { Command as Command9 } from "commander";
|
|
551
551
|
import { spawn as spawn4, exec } from "child_process";
|
|
552
552
|
import { randomUUID } from "crypto";
|
|
553
|
-
import { resolve as resolve2 } from "path";
|
|
553
|
+
import { resolve as resolve2, extname } from "path";
|
|
554
554
|
import { readFileSync as readFileSync5, readdirSync, unlinkSync, existsSync as existsSync7, statSync, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, rmdirSync } from "fs";
|
|
555
555
|
import * as readline from "readline";
|
|
556
556
|
|
|
@@ -2341,12 +2341,94 @@ function describeVariance(variance) {
|
|
|
2341
2341
|
]
|
|
2342
2342
|
};
|
|
2343
2343
|
}
|
|
2344
|
+
var EXEMPLAR_POOLS = [
|
|
2345
|
+
["Linear", "Vercel", "Arc", "Things 3"],
|
|
2346
|
+
["Stripe", "Notion", "Figma", "Cron"],
|
|
2347
|
+
["Raycast", "Superhuman", "Height", "Campsite"],
|
|
2348
|
+
["Apple", "Readymag", "Family", "Stripe Press"],
|
|
2349
|
+
[
|
|
2350
|
+
"Awwwards Site of the Day winners",
|
|
2351
|
+
"indie SaaS",
|
|
2352
|
+
"editorial / magazine sites",
|
|
2353
|
+
"design-studio portfolios"
|
|
2354
|
+
]
|
|
2355
|
+
];
|
|
2356
|
+
function pickExemplars(seed) {
|
|
2357
|
+
let hash = 0;
|
|
2358
|
+
for (let i = 0; i < seed.length; i++) {
|
|
2359
|
+
hash = hash * 31 + seed.charCodeAt(i) | 0;
|
|
2360
|
+
}
|
|
2361
|
+
return EXEMPLAR_POOLS[Math.abs(hash) % EXEMPLAR_POOLS.length].join(", ");
|
|
2362
|
+
}
|
|
2363
|
+
function buildReferenceSection(referenceImagePaths) {
|
|
2364
|
+
if (referenceImagePaths.length === 0) return [];
|
|
2365
|
+
return [
|
|
2366
|
+
`## Reference Designs (CRITICAL \u2014 these define the visual direction)`,
|
|
2367
|
+
``,
|
|
2368
|
+
`The user attached ${referenceImagePaths.length} reference image(s), saved on disk at:`,
|
|
2369
|
+
...referenceImagePaths.map((p) => `- \`${p}\``),
|
|
2370
|
+
``,
|
|
2371
|
+
`Before designing anything, open and study each reference image (read the files at the paths above with your image-reading capability).`,
|
|
2372
|
+
`The references define the intended look and feel: layout structure, color palette, typography, spacing, component styling, density, and overall mood. Match them closely.`,
|
|
2373
|
+
`Where the references conflict with any generic style guidance below, THE REFERENCES WIN \u2014 do not override the user's attached direction with a default "design-led SaaS" look.`,
|
|
2374
|
+
`Treat multiple references as one combined moodboard: extract the consistent visual language across them.`,
|
|
2375
|
+
`You may still vary layout and composition across variants per the Design Variance section \u2014 but every variant must clearly belong to the reference designs' visual world.`,
|
|
2376
|
+
``
|
|
2377
|
+
];
|
|
2378
|
+
}
|
|
2379
|
+
async function downloadReferenceImages(proto, baseDir, prefix) {
|
|
2380
|
+
const refs = proto.referenceImages;
|
|
2381
|
+
if (!Array.isArray(refs) || refs.length === 0) return { paths: [], dir: null };
|
|
2382
|
+
const dir = resolve2(baseDir, `.mr-proto-refs-${shortId(proto.id)}`);
|
|
2383
|
+
try {
|
|
2384
|
+
mkdirSync3(dir, { recursive: true });
|
|
2385
|
+
} catch {
|
|
2386
|
+
return { paths: [], dir: null };
|
|
2387
|
+
}
|
|
2388
|
+
const paths = [];
|
|
2389
|
+
for (let i = 0; i < refs.length; i++) {
|
|
2390
|
+
const ref = refs[i];
|
|
2391
|
+
if (!ref || typeof ref.url !== "string") continue;
|
|
2392
|
+
try {
|
|
2393
|
+
const res = await fetch(ref.url);
|
|
2394
|
+
if (!res.ok) {
|
|
2395
|
+
logWarn(prefix, `Reference image ${i + 1} \u2192 HTTP ${res.status}, skipping`);
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
2399
|
+
let ext = extname(new URL(ref.url).pathname).toLowerCase();
|
|
2400
|
+
if (!ext || ext.length > 5) ext = ".png";
|
|
2401
|
+
const dest = resolve2(dir, `reference-${i + 1}${ext}`);
|
|
2402
|
+
writeFileSync3(dest, buf);
|
|
2403
|
+
paths.push(dest);
|
|
2404
|
+
} catch (err) {
|
|
2405
|
+
logWarn(prefix, `Failed to download reference image ${i + 1}: ${err.message}`);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
return { paths, dir: paths.length > 0 ? dir : null };
|
|
2409
|
+
}
|
|
2410
|
+
function cleanupRefDir(dir) {
|
|
2411
|
+
if (!dir) return;
|
|
2412
|
+
try {
|
|
2413
|
+
for (const f of readdirSync(dir)) {
|
|
2414
|
+
try {
|
|
2415
|
+
unlinkSync(resolve2(dir, f));
|
|
2416
|
+
} catch {
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
rmdirSync(dir);
|
|
2420
|
+
} catch {
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2344
2423
|
function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
2345
2424
|
const prototypeType = proto.prototypeType ?? "web_app";
|
|
2346
2425
|
const variantsToProduce = options.variantsToProduce ?? proto.variantCount;
|
|
2347
2426
|
const startIndex = options.variantStartIndex ?? 1;
|
|
2348
2427
|
const variance = typeof proto.designVariance === "number" ? proto.designVariance : 70;
|
|
2349
2428
|
const varianceInfo = describeVariance(variance);
|
|
2429
|
+
const referenceImagePaths = options.referenceImagePaths ?? [];
|
|
2430
|
+
const hasReferences = referenceImagePaths.length > 0;
|
|
2431
|
+
const exemplars = pickExemplars(proto.id);
|
|
2350
2432
|
const variantSteps = [];
|
|
2351
2433
|
for (let i = 0; i < variantsToProduce; i++) {
|
|
2352
2434
|
const idx = startIndex + i;
|
|
@@ -2354,7 +2436,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2354
2436
|
variantSteps.push(
|
|
2355
2437
|
`### Variant ${idx}: ${filename}`,
|
|
2356
2438
|
`1. ${varianceInfo.perVariantDirective}`,
|
|
2357
|
-
`2. Spend a moment composing a real, opinionated design before you start typing HTML. Imagine the screen
|
|
2439
|
+
`2. Spend a moment composing a real, opinionated design before you start typing HTML. ${hasReferences ? "Imagine the screen sitting alongside the reference designs the user attached \u2014 it should look like it belongs in the same set." : `Imagine the screen on Dribbble, holding its own next to ${exemplars}.`}`,
|
|
2358
2440
|
`3. Write the complete self-contained HTML to \`${repoDir}/${filename}\` using the Write tool.`,
|
|
2359
2441
|
`4. Verify the file was created by reading the first few lines of \`${repoDir}/${filename}\`.`,
|
|
2360
2442
|
``
|
|
@@ -2367,7 +2449,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2367
2449
|
const sharedQualityBar = [
|
|
2368
2450
|
`## Craft & Polish Bar (this is the most important section \u2014 read carefully)`,
|
|
2369
2451
|
``,
|
|
2370
|
-
`These are PORTFOLIO-QUALITY prototypes, not throwaway sketches.
|
|
2452
|
+
hasReferences ? `These are PORTFOLIO-QUALITY prototypes, not throwaway sketches. They must match the visual direction of the reference designs the user attached (see the Reference Designs section above) while hitting a high craft bar. Study the references first \u2014 everything below is secondary to them.` : `These are PORTFOLIO-QUALITY prototypes, not throwaway sketches. The reviewer will judge them at a glance against the craft level of ${exemplars}, and indie SaaS that wins design awards. Your output must look like it belongs in that company \u2014 but interpret the brief on its own terms, don't imitate any one of them.`,
|
|
2371
2453
|
``,
|
|
2372
2454
|
`**Composition & layout**`,
|
|
2373
2455
|
`- Use a real grid with intentional spacing (multiples of 4 or 8). No cramped or arbitrary padding.`,
|
|
@@ -2376,12 +2458,12 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2376
2458
|
`- Consider "uncommon" navigation: side-scrolling, vertical headers, command-palette-first interfaces, or minimalist "zen" modes.`,
|
|
2377
2459
|
``,
|
|
2378
2460
|
`**Typography**`,
|
|
2379
|
-
`- Pair a real display face with a clean text face
|
|
2461
|
+
hasReferences ? `- Match the typography of the reference designs \u2014 identify the display and text faces they use (or the closest Google Fonts equivalents) and pull them in via \`<link>\`. Tailwind CDN does NOT ship custom fonts \u2014 link them yourself.` : `- Pair a real display face with a clean text face, pulled from Google Fonts via \`<link>\`. Good starting points: Inter, Geist, Space Grotesk, Instrument Serif, Fraunces, JetBrains Mono, IBM Plex, Manrope, DM Sans, Playfair Display, Bricolage Grotesque \u2014 but don't feel limited to this list; pick faces that fit the brief and vary them across variants. Tailwind CDN does NOT ship custom fonts \u2014 link them yourself.`,
|
|
2380
2462
|
`- Use real type scales (e.g. 12 / 14 / 16 / 20 / 28 / 40 / 64). Tighten tracking on display sizes (-0.02em to -0.05em). Use weight contrast (300 vs 600), not just size.`,
|
|
2381
2463
|
`- Pay attention to line-height (1.5-1.6 for body, 1.1-1.2 for headers).`,
|
|
2382
2464
|
``,
|
|
2383
2465
|
`**Color & material**`,
|
|
2384
|
-
`-
|
|
2466
|
+
hasReferences ? `- Derive the palette from the reference designs \u2014 sample their actual colors (backgrounds, ink, accents) rather than defaulting to a generic scheme. Avoid default Tailwind blue-500 / gray-900 unless the references use them.` : `- Pick a deliberate palette per variant. Avoid default Tailwind blue-500 / gray-900. Off-whites, warm neutrals, deep inks, accent pops, gradients, and noise textures are all fair game \u2014 specific values like #F9F9F9 or #0A0A0B are illustrations, not a required palette. Commit to a distinct color story per variant.`,
|
|
2385
2467
|
`- Use depth: soft shadows (e.g. \`0 10px 30px rgba(0,0,0,0.05)\`), subtle borders (1px @ low opacity), inner glows, glassmorphism (backdrop-blur), grain overlays, or mesh gradients. No flat-default look unless that's the chosen aesthetic.`,
|
|
2386
2468
|
`- Use "subtle motion" even in static colors: very soft gradients that feel like lighting, not just a color ramp.`,
|
|
2387
2469
|
``,
|
|
@@ -2405,7 +2487,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2405
2487
|
];
|
|
2406
2488
|
const typeConfig = {
|
|
2407
2489
|
web_app: {
|
|
2408
|
-
role: "You are a senior product designer at a top-tier design-led startup, comfortable in Figma and shipping production HTML/CSS.
|
|
2490
|
+
role: "You are a senior product designer at a top-tier design-led startup, comfortable in Figma and shipping production HTML/CSS.",
|
|
2409
2491
|
guidelines: [
|
|
2410
2492
|
`- **Layout DNA**: bento grid, editorial split, command-bar driven, three-pane mail-style, dashboard with sidebar + canvas, marketing landing with pinned hero, terminal/CLI-inspired, doc-style long-form.`,
|
|
2411
2493
|
`- **Aesthetic**: Swiss minimal, brutalist editorial, glassmorphism, neumorphic, retro 90s web revival, hand-drawn / sketchy, dark IDE, pastel friendly, monochrome high-contrast, neo-skeuomorphic, "soft-ui" with deep shadows.`,
|
|
@@ -2434,7 +2516,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2434
2516
|
]
|
|
2435
2517
|
},
|
|
2436
2518
|
desktop_app: {
|
|
2437
|
-
role: "You are a senior desktop product designer who
|
|
2519
|
+
role: "You are a senior desktop product designer who ships polished, best-in-class, native-feeling apps and doesn't settle for generic.",
|
|
2438
2520
|
guidelines: [
|
|
2439
2521
|
`- **Window chrome**: include realistic traffic-light controls or Windows controls, an integrated title bar, sidebar/main split, optional inspector pane.`,
|
|
2440
2522
|
`- **Layout DNA**: three-pane mail-style, sidebar + canvas + inspector, command-bar driven (kbar / Raycast), tabbed workspace, ribbon toolbar, split-pane editor, agenda + detail, "spatial" layout with floating windows.`,
|
|
@@ -2473,6 +2555,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2473
2555
|
logo: "Logo"
|
|
2474
2556
|
};
|
|
2475
2557
|
const agentNote = options.agentLabel ? `You are one of several models collaborating on this prototype set. You are the "${options.agentLabel}" track, responsible for variants ${startIndex} through ${startIndex + variantsToProduce - 1}. Bring YOUR creative perspective \u2014 don't try to be a generic averaged designer.` : `You are the sole designer on this prototype set \u2014 bring a strong, opinionated point of view.`;
|
|
2558
|
+
const creativeAnchor = hasReferences ? `The user attached reference designs for this prototype \u2014 they are the single most important input. Treat them as the definitive brief for look and feel (details in the Reference Designs section below).` : `Aim for the craft level of ${exemplars} \u2014 but interpret this brief on its own terms, don't imitate any one product.`;
|
|
2476
2559
|
const creativeDirection = [
|
|
2477
2560
|
`## Creative Direction & Polish`,
|
|
2478
2561
|
`- **Be Opinionated**: Do not design generic "safe" UIs. Imagine you are designing for a product that wants to win "Site of the Day" on Awwwards or be featured in a "Best of Linear-style design" gallery.`,
|
|
@@ -2486,6 +2569,8 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2486
2569
|
``,
|
|
2487
2570
|
agentNote,
|
|
2488
2571
|
``,
|
|
2572
|
+
creativeAnchor,
|
|
2573
|
+
``,
|
|
2489
2574
|
`Working directory: ${repoDir}`,
|
|
2490
2575
|
``,
|
|
2491
2576
|
`## Prototype Request`,
|
|
@@ -2496,6 +2581,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2496
2581
|
`## Design Prompt`,
|
|
2497
2582
|
`${proto.prompt}`,
|
|
2498
2583
|
``,
|
|
2584
|
+
...buildReferenceSection(referenceImagePaths),
|
|
2499
2585
|
`## Instructions`,
|
|
2500
2586
|
``,
|
|
2501
2587
|
`Generate exactly ${variantsToProduce} HTML file(s) covering variants ${startIndex} through ${startIndex + variantsToProduce - 1}: ${variantList.join(", ")}. Follow the steps below IN ORDER. Do NOT collapse or skip variants. Each file must be completely self-contained (inline all CSS and JS, plus any Google Fonts \`<link>\` you reference). Tailwind CDN is acceptable.`,
|
|
@@ -2536,6 +2622,7 @@ function buildPrototypePrompt(proto, repoDir, options = {}) {
|
|
|
2536
2622
|
].join("\n");
|
|
2537
2623
|
}
|
|
2538
2624
|
function buildRefinementPrompt(proto, parentFiles, repoDir, options = {}) {
|
|
2625
|
+
const referenceImagePaths = options.referenceImagePaths ?? [];
|
|
2539
2626
|
const variantsToProduce = options.variantsToProduce ?? proto.variantCount;
|
|
2540
2627
|
const startIndex = options.variantStartIndex ?? 1;
|
|
2541
2628
|
const variance = typeof proto.designVariance === "number" ? proto.designVariance : 70;
|
|
@@ -2600,6 +2687,7 @@ function buildRefinementPrompt(proto, parentFiles, repoDir, options = {}) {
|
|
|
2600
2687
|
`## Original Design Prompt`,
|
|
2601
2688
|
`${proto.prompt}`,
|
|
2602
2689
|
``,
|
|
2690
|
+
...buildReferenceSection(referenceImagePaths),
|
|
2603
2691
|
`## User Feedback (IMPORTANT \u2014 this is what you must address)`,
|
|
2604
2692
|
`${proto.refinementFeedback}`,
|
|
2605
2693
|
``,
|
|
@@ -3408,6 +3496,14 @@ var watchCommand = new Command9("watch").description(
|
|
|
3408
3496
|
} catch {
|
|
3409
3497
|
}
|
|
3410
3498
|
}
|
|
3499
|
+
const { paths: refImagePaths, dir: refImageDir } = await downloadReferenceImages(
|
|
3500
|
+
proto,
|
|
3501
|
+
repoDir,
|
|
3502
|
+
prefix
|
|
3503
|
+
);
|
|
3504
|
+
if (refImagePaths.length > 0) {
|
|
3505
|
+
logDispatch(prefix, `${refImagePaths.length} reference image(s) attached`);
|
|
3506
|
+
}
|
|
3411
3507
|
const validAgents = ["claude", "codex", "gemini"];
|
|
3412
3508
|
const requested = Array.isArray(proto.selectedAgents) ? proto.selectedAgents.filter(
|
|
3413
3509
|
(a) => validAgents.includes(a)
|
|
@@ -3428,13 +3524,15 @@ var watchCommand = new Command9("watch").description(
|
|
|
3428
3524
|
return buildRefinementPrompt(proto, parentFiles, sliceDir, {
|
|
3429
3525
|
variantStartIndex: startIndex,
|
|
3430
3526
|
variantsToProduce,
|
|
3431
|
-
agentLabel
|
|
3527
|
+
agentLabel,
|
|
3528
|
+
referenceImagePaths: refImagePaths
|
|
3432
3529
|
});
|
|
3433
3530
|
}
|
|
3434
3531
|
return buildPrototypePrompt(proto, sliceDir, {
|
|
3435
3532
|
variantStartIndex: startIndex,
|
|
3436
3533
|
variantsToProduce,
|
|
3437
|
-
agentLabel
|
|
3534
|
+
agentLabel,
|
|
3535
|
+
referenceImagePaths: refImagePaths
|
|
3438
3536
|
});
|
|
3439
3537
|
};
|
|
3440
3538
|
if (dedupedRequested.length <= 1) {
|
|
@@ -3444,6 +3542,7 @@ var watchCommand = new Command9("watch").description(
|
|
|
3444
3542
|
logError(prefix, `No available agents found for ${preferred}`);
|
|
3445
3543
|
await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" }).catch(() => {
|
|
3446
3544
|
});
|
|
3545
|
+
cleanupRefDir(refImageDir);
|
|
3447
3546
|
queued.delete(key);
|
|
3448
3547
|
return;
|
|
3449
3548
|
}
|
|
@@ -3528,6 +3627,7 @@ var watchCommand = new Command9("watch").description(
|
|
|
3528
3627
|
}
|
|
3529
3628
|
}
|
|
3530
3629
|
} finally {
|
|
3630
|
+
cleanupRefDir(refImageDir);
|
|
3531
3631
|
queued.delete(key);
|
|
3532
3632
|
finishing.delete(key);
|
|
3533
3633
|
}
|
|
@@ -3563,6 +3663,7 @@ var watchCommand = new Command9("watch").description(
|
|
|
3563
3663
|
logError(prefix, `Could not assign any variants to selected agents`);
|
|
3564
3664
|
await api.patch(`/api/prototypes/${proto.id}`, { status: "failed" }).catch(() => {
|
|
3565
3665
|
});
|
|
3666
|
+
cleanupRefDir(refImageDir);
|
|
3566
3667
|
queued.delete(key);
|
|
3567
3668
|
return;
|
|
3568
3669
|
}
|
|
@@ -3684,6 +3785,7 @@ var watchCommand = new Command9("watch").description(
|
|
|
3684
3785
|
} catch {
|
|
3685
3786
|
}
|
|
3686
3787
|
} finally {
|
|
3788
|
+
cleanupRefDir(refImageDir);
|
|
3687
3789
|
queued.delete(key);
|
|
3688
3790
|
finishing.delete(key);
|
|
3689
3791
|
}
|
|
@@ -4456,6 +4558,17 @@ ${summaryBlock}${lines.join("\n")}`;
|
|
|
4456
4558
|
}
|
|
4457
4559
|
const vcs = review.branchUrl.includes("gitlab") ? "gitlab" : "github";
|
|
4458
4560
|
const prLabel = vcs === "gitlab" ? "MR" : "PR";
|
|
4561
|
+
const preMergeStatus = await checkPrStatus(review.branchUrl, repoDir, vcs);
|
|
4562
|
+
if (preMergeStatus?.merged) {
|
|
4563
|
+
logSuccess(prefix, `${prLabel} already merged \u2014 recording auto-merge as complete`);
|
|
4564
|
+
try {
|
|
4565
|
+
await api.patch(`/api/reviews/${review.id}`, { autoMergeStatus: "merged" });
|
|
4566
|
+
await postTaskUpdate(taskId, `Auto-merge complete \u2014 ${prLabel} was already merged`, "system");
|
|
4567
|
+
} catch (err) {
|
|
4568
|
+
logError(prefix, `Failed to record auto-merge success: ${err.message}`);
|
|
4569
|
+
}
|
|
4570
|
+
continue;
|
|
4571
|
+
}
|
|
4459
4572
|
logDispatch(prefix, `Sanity review clean \u2014 merging ${prLabel} ${paint("cyan", review.branchUrl)}`);
|
|
4460
4573
|
const mergeResult = await mergePrViaCli(review.branchUrl, repoDir, vcs);
|
|
4461
4574
|
if (!mergeResult.ok) {
|