@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.
Files changed (2) hide show
  1. package/dist/index.mjs +123 -10
  2. 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.46",
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 on Dribbble or in a Linear / Stripe / Vercel / Arc / Things-style portfolio.`,
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. The reviewer will judge them at a glance against work from Linear, Vercel, Stripe, Arc, Things, Things 3, Notion, Figma, Apple, Cron, Raycast, Superhuman, and indie SaaS that wins design awards. Your output must look like it belongs in that company.`,
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. Pull from Google Fonts via \`<link>\` (Inter, Geist, Space Grotesk, Instrument Serif, Fraunces, S\xF6hne-style fallbacks, JetBrains Mono, IBM Plex, Manrope, DM Sans, Playfair Display, Caladea, Bricolage Grotesque). Tailwind CDN does NOT ship custom fonts \u2014 link them yourself.`,
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
- `- Pick a deliberate palette per variant. Avoid default Tailwind blue-500 / gray-900 unless that IS the design intent. Mix off-whites (#F9F9F9), warm neutrals, deep ink colors (#0A0A0B), accent pops, gradients, noise textures.`,
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. You design at the level of Linear, Vercel, Stripe, and Arc.",
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 has shipped work on Linear, Things 3, Cron, Raycast, Notion, and Arc. You design at the level of those apps and don't settle for generic.",
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dunnewold-labs/mr-manager",
3
- "version": "0.4.46",
3
+ "version": "0.4.48",
4
4
  "description": "Mr. Manager - Task and project management CLI",
5
5
  "bin": {
6
6
  "mr": "./dist/index.mjs"