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.
- package/bin/fde.js +73 -13
- 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:#
|
|
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
|
-
|
|
586
|
-
|
|
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
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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="
|
|
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 =
|
|
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
|
-
|
|
647
|
-
|
|
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.
|
|
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",
|