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