mrvn-cli 0.3.1 → 0.3.2

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.js CHANGED
@@ -21572,16 +21572,23 @@ function inline(text) {
21572
21572
  return s;
21573
21573
  }
21574
21574
  function layout(opts, body) {
21575
- const navItems = [
21575
+ const topItems = [
21576
21576
  { href: "/", label: "Overview" },
21577
21577
  { href: "/board", label: "Board" },
21578
21578
  { href: "/gar", label: "GAR Report" }
21579
21579
  ];
21580
- const typeNavItems = opts.navTypes.map((type) => ({
21581
- href: `/docs/${type}`,
21582
- label: typeLabel(type) + "s"
21583
- }));
21584
21580
  const isActive = (href) => opts.activePath === href || href !== "/" && opts.activePath.startsWith(href) ? " active" : "";
21581
+ const groupsHtml = opts.navGroups.map((group) => {
21582
+ const links = group.types.map((type) => {
21583
+ const href = `/docs/${type}`;
21584
+ return `<a href="${href}" class="${isActive(href)}">${typeLabel(type)}s</a>`;
21585
+ }).join("\n ");
21586
+ return `
21587
+ <div class="nav-group">
21588
+ <div class="nav-group-label">${escapeHtml(group.label)}</div>
21589
+ ${links}
21590
+ </div>`;
21591
+ }).join("\n");
21585
21592
  return `<!DOCTYPE html>
21586
21593
  <html lang="en">
21587
21594
  <head>
@@ -21598,8 +21605,8 @@ function layout(opts, body) {
21598
21605
  <div class="project-name">${escapeHtml(opts.projectName)}</div>
21599
21606
  </div>
21600
21607
  <nav>
21601
- ${navItems.map((n) => `<a href="${n.href}" class="${isActive(n.href)}">${n.label}</a>`).join("\n ")}
21602
- ${typeNavItems.map((n) => `<a href="${n.href}" class="${isActive(n.href)}">${n.label}</a>`).join("\n ")}
21608
+ ${topItems.map((n) => `<a href="${n.href}" class="${isActive(n.href)}">${n.label}</a>`).join("\n ")}
21609
+ ${groupsHtml}
21603
21610
  </nav>
21604
21611
  </aside>
21605
21612
  <main class="main">
@@ -21700,6 +21707,21 @@ a:hover { text-decoration: underline; }
21700
21707
  border-right: 2px solid var(--accent);
21701
21708
  }
21702
21709
 
21710
+ .nav-group {
21711
+ margin-top: 0.75rem;
21712
+ padding-top: 0.75rem;
21713
+ border-top: 1px solid var(--border);
21714
+ }
21715
+
21716
+ .nav-group-label {
21717
+ padding: 0.25rem 1.25rem 0.25rem;
21718
+ font-size: 0.65rem;
21719
+ text-transform: uppercase;
21720
+ letter-spacing: 0.08em;
21721
+ color: var(--text-dim);
21722
+ font-weight: 600;
21723
+ }
21724
+
21703
21725
  .main {
21704
21726
  margin-left: 220px;
21705
21727
  flex: 1;
@@ -22314,7 +22336,7 @@ function boardPage(data) {
22314
22336
  }
22315
22337
 
22316
22338
  // src/web/router.ts
22317
- function handleRequest(req, res, store, projectName) {
22339
+ function handleRequest(req, res, store, projectName, navGroups) {
22318
22340
  const parsed = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
22319
22341
  const pathname = parsed.pathname;
22320
22342
  const navTypes = store.registeredTypes;
@@ -22330,25 +22352,25 @@ function handleRequest(req, res, store, projectName) {
22330
22352
  if (pathname === "/") {
22331
22353
  const data = getOverviewData(store);
22332
22354
  const body = overviewPage(data);
22333
- respond(res, layout({ title: "Overview", activePath: "/", projectName, navTypes }, body));
22355
+ respond(res, layout({ title: "Overview", activePath: "/", projectName, navGroups }, body));
22334
22356
  return;
22335
22357
  }
22336
22358
  if (pathname === "/gar") {
22337
22359
  const report = getGarData(store, projectName);
22338
22360
  const body = garPage(report);
22339
- respond(res, layout({ title: "GAR Report", activePath: "/gar", projectName, navTypes }, body));
22361
+ respond(res, layout({ title: "GAR Report", activePath: "/gar", projectName, navGroups }, body));
22340
22362
  return;
22341
22363
  }
22342
22364
  const boardMatch = pathname.match(/^\/board(?:\/([^/]+))?$/);
22343
22365
  if (boardMatch) {
22344
22366
  const type = boardMatch[1];
22345
22367
  if (type && !navTypes.includes(type)) {
22346
- notFound(res, projectName, navTypes, pathname);
22368
+ notFound(res, projectName, navGroups, pathname);
22347
22369
  return;
22348
22370
  }
22349
22371
  const data = getBoardData(store, type);
22350
22372
  const body = boardPage(data);
22351
- respond(res, layout({ title: "Board", activePath: "/board", projectName, navTypes }, body));
22373
+ respond(res, layout({ title: "Board", activePath: "/board", projectName, navGroups }, body));
22352
22374
  return;
22353
22375
  }
22354
22376
  const detailMatch = pathname.match(/^\/docs\/([^/]+)\/([^/]+)$/);
@@ -22356,11 +22378,11 @@ function handleRequest(req, res, store, projectName) {
22356
22378
  const [, type, id] = detailMatch;
22357
22379
  const doc = getDocumentDetail(store, type, id);
22358
22380
  if (!doc) {
22359
- notFound(res, projectName, navTypes, pathname);
22381
+ notFound(res, projectName, navGroups, pathname);
22360
22382
  return;
22361
22383
  }
22362
22384
  const body = documentDetailPage(doc);
22363
- respond(res, layout({ title: `${id} \u2014 ${doc.frontmatter.title}`, activePath: `/docs/${type}`, projectName, navTypes }, body));
22385
+ respond(res, layout({ title: `${id} \u2014 ${doc.frontmatter.title}`, activePath: `/docs/${type}`, projectName, navGroups }, body));
22364
22386
  return;
22365
22387
  }
22366
22388
  const listMatch = pathname.match(/^\/docs\/([^/]+)$/);
@@ -22370,14 +22392,14 @@ function handleRequest(req, res, store, projectName) {
22370
22392
  const filterOwner = parsed.searchParams.get("owner") ?? void 0;
22371
22393
  const data = getDocumentListData(store, type, filterStatus, filterOwner);
22372
22394
  if (!data) {
22373
- notFound(res, projectName, navTypes, pathname);
22395
+ notFound(res, projectName, navGroups, pathname);
22374
22396
  return;
22375
22397
  }
22376
22398
  const body = documentsPage(data);
22377
- respond(res, layout({ title: `${type}`, activePath: `/docs/${type}`, projectName, navTypes }, body));
22399
+ respond(res, layout({ title: `${type}`, activePath: `/docs/${type}`, projectName, navGroups }, body));
22378
22400
  return;
22379
22401
  }
22380
- notFound(res, projectName, navTypes, pathname);
22402
+ notFound(res, projectName, navGroups, pathname);
22381
22403
  } catch (err) {
22382
22404
  console.error("[marvin web] Error handling request:", err);
22383
22405
  res.writeHead(500, { "Content-Type": "text/html" });
@@ -22388,13 +22410,14 @@ function respond(res, html) {
22388
22410
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
22389
22411
  res.end(html);
22390
22412
  }
22391
- function notFound(res, projectName, navTypes, activePath) {
22413
+ function notFound(res, projectName, navGroups, activePath) {
22392
22414
  const body = `<div class="empty"><h2>404</h2><p>Page not found.</p><p><a href="/">Go to overview</a></p></div>`;
22393
22415
  res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
22394
- res.end(layout({ title: "Not Found", activePath, projectName, navTypes }, body));
22416
+ res.end(layout({ title: "Not Found", activePath, projectName, navGroups }, body));
22395
22417
  }
22396
22418
 
22397
22419
  // src/web/server.ts
22420
+ var CORE_TYPES = ["decision", "action", "question"];
22398
22421
  async function startWebServer(opts) {
22399
22422
  const project = loadProject();
22400
22423
  const plugin = resolvePlugin(project.config.methodology);
@@ -22407,8 +22430,24 @@ async function startWebServer(opts) {
22407
22430
  ...skillRegs
22408
22431
  ]);
22409
22432
  const projectName = project.config.name;
22433
+ const commonTypes = new Set(COMMON_REGISTRATIONS.map((r) => r.type));
22434
+ const pluginOnlyTypes = pluginRegs.map((r) => r.type).filter((t) => !commonTypes.has(t));
22435
+ const skillTypes = skillRegs.map((r) => r.type);
22436
+ const navGroups = [
22437
+ { label: "Governance", types: CORE_TYPES },
22438
+ { label: "Project", types: [...commonTypes] }
22439
+ ];
22440
+ if (pluginOnlyTypes.length > 0) {
22441
+ navGroups.push({
22442
+ label: plugin?.name ?? "Plugin",
22443
+ types: pluginOnlyTypes
22444
+ });
22445
+ }
22446
+ if (skillTypes.length > 0) {
22447
+ navGroups.push({ label: "Skills", types: skillTypes });
22448
+ }
22410
22449
  const server = http.createServer((req, res) => {
22411
- handleRequest(req, res, store, projectName);
22450
+ handleRequest(req, res, store, projectName, navGroups);
22412
22451
  });
22413
22452
  server.listen(opts.port, () => {
22414
22453
  const url2 = `http://localhost:${opts.port}`;
@@ -22452,7 +22491,7 @@ function createProgram() {
22452
22491
  const program = new Command();
22453
22492
  program.name("marvin").description(
22454
22493
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
22455
- ).version("0.3.1");
22494
+ ).version("0.3.2");
22456
22495
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
22457
22496
  await initCommand();
22458
22497
  });