fdeops 3.5.5 → 3.5.6

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/bin/fde.js +73 -13
  2. package/package.json +1 -1
package/bin/fde.js CHANGED
@@ -475,7 +475,7 @@ const DASH_SECTIONS = [
475
475
  function dashStyles() {
476
476
  return [
477
477
  // design system: quiet chrome, high density, daily-use second brain
478
- ':root{--bg:#f9fafb;--card:#fff;--ink:#111827;--2:#374151;--3:#6b7280;--muted:#9ca3af;--line:#f3f4f6;--line2:#e5e7eb;',
478
+ ':root{--bg:#f9fafb;--card:#fff;--ink:#111827;--2:#374151;--3:#4b5563;--muted:#6b7280;--line:#f3f4f6;--line2:#e5e7eb;',
479
479
  '--green:#10b981;--amber:#f59e0b;--red:#ef4444;--accent:#6366f1;',
480
480
  '--shadow-s:0 1px 2px rgba(0,0,0,.04);--shadow:0 1px 3px rgba(0,0,0,.06);--shadow-l:0 4px 12px rgba(0,0,0,.08);',
481
481
  '--r:8px}',
@@ -500,6 +500,21 @@ function dashStyles() {
500
500
  '.search{width:100%;padding:10px 14px 10px 36px;font-size:13px;border:1px solid var(--line2);border-radius:var(--r);background:var(--card);transition:border-color .15s,box-shadow .15s;outline:none}',
501
501
  '.search:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(99,102,241,.1)}',
502
502
  '.search::placeholder{color:var(--muted)}',
503
+ // directive - the one line that says where to start today
504
+ '.directive{display:flex;align-items:center;gap:10px;margin:0 0 20px;padding:12px 16px;background:#fef2f2;border:1px solid #fecaca;border-radius:var(--r);font-size:13.5px;line-height:1.5;color:#7f1d1d}',
505
+ '.directive b{font-weight:700;color:#7f1d1d}',
506
+ '.directive.amber{background:#fffbeb;border-color:#fde68a;color:#78350f}',
507
+ '.directive.amber b{color:#78350f}',
508
+ '.directive-dot{width:9px;height:9px;border-radius:50%;background:var(--red);flex-shrink:0;box-shadow:0 0 0 3px rgba(239,68,68,.18)}',
509
+ '.directive.amber .directive-dot{background:var(--amber);box-shadow:0 0 0 3px rgba(245,158,11,.18)}',
510
+ // attention rows - red engagements dominate the page, full width
511
+ '.attn-stack{display:flex;flex-direction:column;gap:10px}',
512
+ '.attn{background:#fef2f2;border:1px solid #fecaca;border-left:4px solid var(--red);border-radius:var(--r);padding:16px 18px;cursor:pointer;transition:box-shadow .15s,border-color .15s}',
513
+ '.attn:hover{border-color:#f87171;box-shadow:var(--shadow-l)}',
514
+ '.attn-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}',
515
+ '.attn-head h3{font-size:15px;font-weight:700;color:var(--ink)}',
516
+ '.attn .risk{margin-top:10px}',
517
+ '.attn .next{margin-top:8px;font-size:13px;line-height:1.5;color:var(--2)}.attn .next b{color:var(--3);font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:.3px;margin-right:4px}',
503
518
  // grid
504
519
  '.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}',
505
520
  // cards - clean, scannable
@@ -518,6 +533,9 @@ function dashStyles() {
518
533
  '.card .next{margin-top:10px;font-size:12.5px;line-height:1.5;color:var(--2)}.card .next b{color:var(--3);font-weight:600;font-size:10px;text-transform:uppercase;letter-spacing:.3px;margin-right:2px}',
519
534
  '.card .risk{margin-top:8px;font-size:11.5px;color:#991b1b;padding:5px 8px;background:#fef2f2;border-radius:4px;border-left:2px solid var(--red);line-height:1.4}',
520
535
  '.card .risk.amber-risk{color:#92400e;background:#fffbeb;border-left-color:var(--amber)}',
536
+ // demote placeholder cards (no next action) - quiet until they earn attention
537
+ '.card.muted{opacity:.55}',
538
+ '.card.muted:hover{opacity:1}',
521
539
  // section labels
522
540
  'h2.section{margin:28px 0 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.6px;color:var(--muted)}',
523
541
  // detail accordion - tight, content-forward
@@ -582,25 +600,65 @@ function cmdDashboard(args) {
582
600
  const now = new Date()
583
601
  const stamp = now.toISOString().slice(0, 16).replace('T', ' ') + ' UTC'
584
602
 
585
- const cards = engagements.map(e => {
586
- const id = 'eng-' + slugify(e.name)
603
+ // enrich each engagement: next action + search index (reused in rows, cards, directive)
604
+ engagements.forEach(e => {
587
605
  const ctx = readEng(e.dir, 'context.md')
588
- const next = (sectionBody(ctx, 'Next action').split('\n').find(l => l.trim()) || '').trim()
589
- const trustClass = e.signals.trust === 'RED' ? 'red' : e.signals.trust
590
- const trustLabel = e.signals.trust === 'RED' ? 'RED' : e.signals.trust
591
- const searchBlob = escapeHtml(stripPrivate(e.name + ' ' + ctx + ' ' + readEng(e.dir, 'risks.md') + ' ' + readEng(e.dir, 'stakeholders.md')).toLowerCase())
606
+ e.next = (sectionBody(ctx, 'Next action').split('\n').find(l => l.trim()) || '').trim()
607
+ e.hasNext = !!e.next
608
+ e.searchBlob = escapeHtml(stripPrivate(e.name + ' ' + ctx + ' ' + readEng(e.dir, 'risks.md') + ' ' + readEng(e.dir, 'stakeholders.md')).toLowerCase())
609
+ })
610
+
611
+ // triage order: red first (most stale first), then amber, then green; real work above placeholders
612
+ const tierRank = { RED: 0, amber: 1, green: 2 }
613
+ const ordered = engagements.slice().sort((a, b) =>
614
+ (tierRank[a.signals.trust] - tierRank[b.signals.trust])
615
+ || ((b.hasNext ? 1 : 0) - (a.hasNext ? 1 : 0))
616
+ || ((b.signals.ageDays || 0) - (a.signals.ageDays || 0))
617
+ || a.name.localeCompare(b.name))
618
+ const reds = ordered.filter(e => e.signals.trust === 'RED')
619
+ const rest = ordered.filter(e => e.signals.trust !== 'RED')
620
+
621
+ // one directive line: where the FDE starts today, and why
622
+ const lead = reds[0] || rest.find(e => e.signals.trust === 'amber' && e.hasNext) || rest[0]
623
+ let directive = '', directiveClass = 'directive'
624
+ if (lead) {
625
+ const why = stripPrivate(lead.signals.topRisk || lead.next || (lead.signals.trust === 'RED' ? 'trust signal is red' : 'oldest open thread')).trim().slice(0, 90)
626
+ const age = lead.signals.ageDays != null ? `${lead.signals.ageDays}d since touched` : ''
627
+ directive = `Start here: <b>${inlineMd(lead.name)}</b> - ${inlineMd(why)}${age ? ' · ' + age : ''}`
628
+ directiveClass = reds.length ? 'directive' : 'directive amber'
629
+ }
630
+
631
+ // red engagements: full-width attention rows that dominate the page
632
+ const attention = reds.map(e => {
633
+ const id = 'eng-' + slugify(e.name)
592
634
  return [
593
- `<div class="card ${trustClass}" data-target="${id}" data-search="${searchBlob}">`,
635
+ `<div class="attn" data-target="${id}" data-search="${e.searchBlob}">`,
636
+ `<div class="attn-head"><span class="dot red"></span><span class="trust-label t-red">RED</span>`,
594
637
  `<h3>${inlineMd(e.name)}</h3>`,
595
- `<div class="row"><span class="dot ${trustClass}"></span><span class="trust-label t-${trustClass}">${trustLabel}</span>`,
596
638
  `<span class="badge">${inlineMd(e.signals.phase)}</span><span class="meta">updated ${e.signals.updated}</span></div>`,
597
- next ? `<div class="next"><b>Next</b> ${inlineMd(next)}</div>` : '<div class="next meta">next action not set</div>',
598
639
  e.signals.topRisk ? `<div class="risk">${inlineMd(e.signals.topRisk)}</div>` : '',
640
+ e.next ? `<div class="next"><b>Next</b> ${inlineMd(e.next)}</div>` : '',
641
+ `</div>`,
642
+ ].join('')
643
+ }).join('\n')
644
+
645
+ // amber/green: quieter portfolio grid; cards without a next action sink and dim
646
+ const cards = rest.map(e => {
647
+ const id = 'eng-' + slugify(e.name)
648
+ const trustClass = e.signals.trust
649
+ const muted = e.hasNext ? '' : ' muted'
650
+ return [
651
+ `<div class="card ${trustClass}${muted}" data-target="${id}" data-search="${e.searchBlob}">`,
652
+ `<h3>${inlineMd(e.name)}</h3>`,
653
+ `<div class="row"><span class="dot ${trustClass}"></span><span class="trust-label t-${trustClass}">${trustClass}</span>`,
654
+ `<span class="badge">${inlineMd(e.signals.phase)}</span><span class="meta">updated ${e.signals.updated}</span></div>`,
655
+ e.next ? `<div class="next"><b>Next</b> ${inlineMd(e.next)}</div>` : '<div class="next meta">next action not set</div>',
656
+ e.signals.topRisk ? `<div class="risk amber-risk">${inlineMd(e.signals.topRisk)}</div>` : '',
599
657
  `</div>`,
600
658
  ].join('')
601
659
  }).join('\n')
602
660
 
603
- const details = engagements.map(e => {
661
+ const details = ordered.map(e => {
604
662
  const id = 'eng-' + slugify(e.name)
605
663
  const trustClass = e.signals.trust === 'RED' ? 'red' : e.signals.trust
606
664
  const subs = DASH_SECTIONS.map(([file, title]) => {
@@ -643,8 +701,10 @@ function cmdDashboard(args) {
643
701
  '</div></div></header>',
644
702
  '<div class="wrap">',
645
703
  engagements.length ? '<div class="search-wrap"><svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg><input id="q" class="search" placeholder="Search across all engagements..."></div>' : '',
646
- '<h2 class="section">Portfolio</h2>',
647
- engagements.length ? `<div class="grid">${cards}</div>` : emptyState,
704
+ directive ? `<div class="${directiveClass}"><span class="directive-dot"></span><span>${directive}</span></div>` : '',
705
+ reds.length ? '<h2 class="section">Needs attention</h2><div class="attn-stack">' + attention + '</div>' : '',
706
+ engagements.length ? `<h2 class="section">${reds.length ? 'Rest of portfolio' : 'Portfolio'}</h2>` : '',
707
+ engagements.length ? (rest.length ? `<div class="grid">${cards}</div>` : '<p class="empty">Every active engagement needs attention - see above.</p>') : emptyState,
648
708
  engagements.length ? '<h2 class="section">Engagement detail</h2>' + details : '',
649
709
  '</div>',
650
710
  `<footer>fdeops · fieldbook is a deterministic render of your <code>.fde/</code> memory - edit the markdown, re-run <code>fde dashboard</code>. Source of truth stays in the files.</footer>`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fdeops",
3
- "version": "3.5.5",
3
+ "version": "3.5.6",
4
4
  "description": "Field kit for engineers embedded in client work - a real CLI (recon, memory, portfolio), one @fde skill with field judgment on top, and hooks that make it automatic. Claude Code plugin and any agent that loads skills.",
5
5
  "bin": {
6
6
  "fdeops": "bin/install.js",