@balpal4495/quorum 3.6.0 → 3.7.0
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/commands/compass.js +9 -3
- package/bin/mcp/tools.js +10 -6
- package/bin/ui/app.html +243 -12
- package/package.json +1 -1
package/bin/commands/compass.js
CHANGED
|
@@ -753,7 +753,13 @@ async function loadLastArtifact(chronicleDir) {
|
|
|
753
753
|
|
|
754
754
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
755
755
|
|
|
756
|
-
|
|
756
|
+
/**
|
|
757
|
+
* @param {string[]} argv
|
|
758
|
+
* @param {Function|null} [injectedLlm] - Pre-detected LLM provider. When
|
|
759
|
+
* supplied, provider detection is skipped entirely (avoids the ~1.5 s Ollama
|
|
760
|
+
* probe on every MCP/serve request).
|
|
761
|
+
*/
|
|
762
|
+
export async function run(argv, injectedLlm) {
|
|
757
763
|
const [subcommand, ...rest] = argv
|
|
758
764
|
|
|
759
765
|
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
@@ -794,8 +800,8 @@ export async function run(argv) {
|
|
|
794
800
|
}
|
|
795
801
|
|
|
796
802
|
const NO_LLM_CMDS = new Set(["map", "opportunities", "behavior", "propose", "outcome"])
|
|
797
|
-
const
|
|
798
|
-
|
|
803
|
+
const llm = injectedLlm
|
|
804
|
+
?? (NO_LLM_CMDS.has(subcommand) ? null : (await detectProvider())?.llm)
|
|
799
805
|
|
|
800
806
|
// ── Shared context helper ─────────────────────────────────────────────────
|
|
801
807
|
|
package/bin/mcp/tools.js
CHANGED
|
@@ -304,22 +304,26 @@ export async function toolCheck({ outcome, design, projectRoot } = {}) {
|
|
|
304
304
|
export async function toolCompass({ subcommand = "brief", goal, idea, projectRoot } = {}) {
|
|
305
305
|
if (!_llm) return NO_LLM("quorum_compass")
|
|
306
306
|
|
|
307
|
-
const { chronicleDir } = await resolve(projectRoot)
|
|
308
|
-
// Delegate to the compass CLI command handler for now
|
|
309
307
|
const { run: compassRun } = await import("../commands/compass.js")
|
|
310
|
-
|
|
308
|
+
|
|
309
|
+
// Capture stdout — always request JSON so there are no ANSI codes
|
|
311
310
|
const captured = []
|
|
312
311
|
const origWrite = process.stdout.write.bind(process.stdout)
|
|
313
312
|
process.stdout.write = (chunk, ...rest) => { captured.push(String(chunk)); return true }
|
|
314
313
|
try {
|
|
315
|
-
const extraArgs = []
|
|
314
|
+
const extraArgs = ["--json"]
|
|
316
315
|
if (subcommand === "pathways" && goal) extraArgs.push("--goal", goal)
|
|
317
316
|
if (subcommand === "score" && idea) extraArgs.push("--idea", idea)
|
|
318
|
-
|
|
317
|
+
// Pass _llm directly to skip the ~1.5 s provider re-detection on every request
|
|
318
|
+
await compassRun([subcommand, ...extraArgs], _llm)
|
|
319
319
|
} finally {
|
|
320
320
|
process.stdout.write = origWrite
|
|
321
321
|
}
|
|
322
|
-
|
|
322
|
+
|
|
323
|
+
const raw = captured.join("").trim()
|
|
324
|
+
let data = null
|
|
325
|
+
try { data = JSON.parse(raw) } catch { /* fallback to raw string below */ }
|
|
326
|
+
return { subcommand, data, output: data ? null : raw }
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
/**
|
package/bin/ui/app.html
CHANGED
|
@@ -408,18 +408,149 @@
|
|
|
408
408
|
.hint-stalled { color: var(--red); }
|
|
409
409
|
|
|
410
410
|
/* ── Compass ── */
|
|
411
|
-
.
|
|
411
|
+
.cp-header {
|
|
412
|
+
display: flex;
|
|
413
|
+
align-items: baseline;
|
|
414
|
+
gap: 10px;
|
|
415
|
+
margin-bottom: 14px;
|
|
416
|
+
}
|
|
417
|
+
.cp-title {
|
|
418
|
+
font-size: 15px;
|
|
419
|
+
font-weight: 700;
|
|
420
|
+
color: var(--text);
|
|
421
|
+
}
|
|
422
|
+
.cp-badge {
|
|
423
|
+
font-size: 11px;
|
|
424
|
+
padding: 2px 8px;
|
|
425
|
+
border-radius: 20px;
|
|
426
|
+
font-weight: 600;
|
|
427
|
+
letter-spacing: .03em;
|
|
428
|
+
}
|
|
429
|
+
.cp-badge--hi { background: rgba(52,201,122,.15); color: var(--green); }
|
|
430
|
+
.cp-badge--mid { background: rgba(224,185,82,.15); color: var(--yellow); }
|
|
431
|
+
.cp-badge--lo { background: rgba(224,82,82,.15); color: var(--red); }
|
|
432
|
+
.cp-direction {
|
|
433
|
+
font-size: 14px;
|
|
434
|
+
font-weight: 500;
|
|
435
|
+
color: var(--text);
|
|
436
|
+
line-height: 1.6;
|
|
437
|
+
margin-bottom: 20px;
|
|
438
|
+
padding: 12px 14px;
|
|
439
|
+
background: rgba(124,110,255,.08);
|
|
440
|
+
border-left: 3px solid var(--accent);
|
|
441
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
442
|
+
}
|
|
443
|
+
.cp-section {
|
|
444
|
+
margin-bottom: 16px;
|
|
445
|
+
}
|
|
446
|
+
.cp-section-label {
|
|
447
|
+
font-size: 11px;
|
|
448
|
+
font-weight: 700;
|
|
449
|
+
text-transform: uppercase;
|
|
450
|
+
letter-spacing: .08em;
|
|
451
|
+
color: var(--muted);
|
|
452
|
+
margin-bottom: 6px;
|
|
453
|
+
}
|
|
454
|
+
.cp-list {
|
|
455
|
+
list-style: none;
|
|
456
|
+
display: flex;
|
|
457
|
+
flex-direction: column;
|
|
458
|
+
gap: 5px;
|
|
459
|
+
}
|
|
460
|
+
.cp-item {
|
|
461
|
+
display: flex;
|
|
462
|
+
gap: 8px;
|
|
463
|
+
font-size: 13px;
|
|
464
|
+
line-height: 1.5;
|
|
465
|
+
}
|
|
466
|
+
.cp-icon {
|
|
467
|
+
flex-shrink: 0;
|
|
468
|
+
width: 16px;
|
|
469
|
+
text-align: center;
|
|
470
|
+
margin-top: 1px;
|
|
471
|
+
}
|
|
472
|
+
.cp-known .cp-icon { color: var(--green); }
|
|
473
|
+
.cp-inferred .cp-icon { color: var(--yellow); }
|
|
474
|
+
.cp-unknown .cp-icon { color: var(--muted); }
|
|
475
|
+
.cp-next-step {
|
|
476
|
+
margin-top: 20px;
|
|
477
|
+
padding: 12px 14px;
|
|
412
478
|
background: var(--surface);
|
|
413
479
|
border: 1px solid var(--border);
|
|
414
480
|
border-radius: var(--radius);
|
|
415
|
-
|
|
416
|
-
|
|
481
|
+
display: flex;
|
|
482
|
+
flex-direction: column;
|
|
483
|
+
gap: 4px;
|
|
484
|
+
}
|
|
485
|
+
.cp-next-label {
|
|
486
|
+
font-size: 11px;
|
|
487
|
+
font-weight: 700;
|
|
488
|
+
text-transform: uppercase;
|
|
489
|
+
letter-spacing: .08em;
|
|
490
|
+
color: var(--muted);
|
|
491
|
+
}
|
|
492
|
+
.cp-next-text {
|
|
493
|
+
font-size: 13px;
|
|
494
|
+
color: var(--text);
|
|
495
|
+
line-height: 1.5;
|
|
496
|
+
}
|
|
497
|
+
/* map / bets / opportunities */
|
|
498
|
+
.cp-card {
|
|
499
|
+
background: var(--surface);
|
|
500
|
+
border: 1px solid var(--border);
|
|
501
|
+
border-radius: var(--radius);
|
|
502
|
+
padding: 14px;
|
|
503
|
+
margin-bottom: 10px;
|
|
504
|
+
}
|
|
505
|
+
.cp-card-header {
|
|
506
|
+
display: flex;
|
|
507
|
+
align-items: center;
|
|
508
|
+
gap: 10px;
|
|
509
|
+
margin-bottom: 6px;
|
|
510
|
+
}
|
|
511
|
+
.cp-card-title {
|
|
512
|
+
font-size: 13px;
|
|
513
|
+
font-weight: 600;
|
|
514
|
+
color: var(--text);
|
|
515
|
+
flex: 1;
|
|
516
|
+
}
|
|
517
|
+
.cp-card-score {
|
|
417
518
|
font-size: 12px;
|
|
418
|
-
|
|
519
|
+
font-weight: 700;
|
|
520
|
+
padding: 2px 8px;
|
|
521
|
+
border-radius: 20px;
|
|
522
|
+
}
|
|
523
|
+
.cp-score-hi { background: rgba(52,201,122,.15); color: var(--green); }
|
|
524
|
+
.cp-score-mid { background: rgba(224,185,82,.15); color: var(--yellow); }
|
|
525
|
+
.cp-score-lo { background: rgba(110,110,126,.15); color: var(--muted); }
|
|
526
|
+
.cp-card-body {
|
|
527
|
+
font-size: 13px;
|
|
419
528
|
color: var(--muted);
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
529
|
+
line-height: 1.5;
|
|
530
|
+
}
|
|
531
|
+
.cp-card-meta {
|
|
532
|
+
font-size: 12px;
|
|
533
|
+
color: var(--muted);
|
|
534
|
+
margin-top: 6px;
|
|
535
|
+
}
|
|
536
|
+
.cp-card-meta span { margin-right: 12px; }
|
|
537
|
+
.cp-area-tag {
|
|
538
|
+
display: inline-block;
|
|
539
|
+
font-size: 11px;
|
|
540
|
+
padding: 1px 6px;
|
|
541
|
+
border-radius: 4px;
|
|
542
|
+
background: rgba(82,168,224,.12);
|
|
543
|
+
color: var(--blue);
|
|
544
|
+
margin-right: 4px;
|
|
545
|
+
}
|
|
546
|
+
.cp-gap-item {
|
|
547
|
+
font-size: 13px;
|
|
548
|
+
color: var(--yellow);
|
|
549
|
+
padding: 8px 12px;
|
|
550
|
+
border-left: 2px solid var(--yellow);
|
|
551
|
+
margin-bottom: 6px;
|
|
552
|
+
background: rgba(224,185,82,.05);
|
|
553
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
423
554
|
}
|
|
424
555
|
|
|
425
556
|
/* ── Edit modal ── */
|
|
@@ -1072,17 +1203,117 @@ async function loadCompass() {
|
|
|
1072
1203
|
}
|
|
1073
1204
|
}
|
|
1074
1205
|
|
|
1206
|
+
function confidenceBadge(conf) {
|
|
1207
|
+
const pct = Math.round((conf ?? 0) * 100)
|
|
1208
|
+
const cls = pct >= 70 ? 'cp-badge--hi' : pct >= 45 ? 'cp-badge--mid' : 'cp-badge--lo'
|
|
1209
|
+
return `<span class="cp-badge ${cls}">${pct}% confidence</span>`
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function cpList(items, cssClass, icon) {
|
|
1213
|
+
if (!items?.length) return ''
|
|
1214
|
+
return `<ul class="cp-list">${items.map(t => `<li class="cp-item ${cssClass}"><span class="cp-icon">${icon}</span><span>${esc(t)}</span></li>`).join('')}</ul>`
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function scoreClass(total) {
|
|
1218
|
+
return total >= 75 ? 'cp-score-hi' : total >= 55 ? 'cp-score-mid' : 'cp-score-lo'
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function renderBrief(d) {
|
|
1222
|
+
let html = `<div class="cp-header"><span class="cp-title">Direction Brief</span>${confidenceBadge(d.confidence)}</div>`
|
|
1223
|
+
if (d.product_direction) html += `<div class="cp-direction">${esc(d.product_direction)}</div>`
|
|
1224
|
+
|
|
1225
|
+
if (d.known_from_chronicle?.length) {
|
|
1226
|
+
html += `<div class="cp-section"><div class="cp-section-label">From Chronicle</div>${cpList(d.known_from_chronicle, 'cp-known', '✓')}</div>`
|
|
1227
|
+
}
|
|
1228
|
+
if (d.known_from_behavior?.length) {
|
|
1229
|
+
html += `<div class="cp-section"><div class="cp-section-label">From code / docs</div>${cpList(d.known_from_behavior, 'cp-known', '✓')}</div>`
|
|
1230
|
+
}
|
|
1231
|
+
if (d.inferred?.length) {
|
|
1232
|
+
html += `<div class="cp-section"><div class="cp-section-label">Inferred</div>${cpList(d.inferred, 'cp-inferred', '~')}</div>`
|
|
1233
|
+
}
|
|
1234
|
+
if (d.unknowns?.length) {
|
|
1235
|
+
html += `<div class="cp-section"><div class="cp-section-label">Unknowns</div>${cpList(d.unknowns, 'cp-unknown', '?')}</div>`
|
|
1236
|
+
}
|
|
1237
|
+
if (d.assumptions?.length) {
|
|
1238
|
+
html += `<div class="cp-section"><div class="cp-section-label">Assumptions</div>${cpList(d.assumptions, 'cp-unknown', '·')}</div>`
|
|
1239
|
+
}
|
|
1240
|
+
if (d.recommended_next_step) {
|
|
1241
|
+
html += `<div class="cp-next-step"><span class="cp-next-label">Next step</span><span class="cp-next-text">${esc(d.recommended_next_step)}</span></div>`
|
|
1242
|
+
}
|
|
1243
|
+
return html
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
function renderMap(d) {
|
|
1247
|
+
let html = `<div class="cp-header"><span class="cp-title">Behaviour Map</span>${confidenceBadge(d.confidence)}</div>`
|
|
1248
|
+
if (d.behaviors?.length) {
|
|
1249
|
+
html += `<div class="cp-section"><div class="cp-section-label">Behaviours (${d.behaviors.length})</div>`
|
|
1250
|
+
for (const b of d.behaviors.slice(0, 30)) {
|
|
1251
|
+
html += `<div class="cp-card" style="padding:10px 14px">`
|
|
1252
|
+
html += `<div class="cp-card-header"><span class="cp-card-title">${esc(b.name)}</span><span class="cp-area-tag">${esc(b.area)}</span></div>`
|
|
1253
|
+
html += `<div class="cp-card-body">${esc(b.current_behavior)}</div></div>`
|
|
1254
|
+
}
|
|
1255
|
+
html += `</div>`
|
|
1256
|
+
}
|
|
1257
|
+
if (d.gaps?.length) {
|
|
1258
|
+
html += `<div class="cp-section"><div class="cp-section-label">Gaps (${d.gaps.length})</div>`
|
|
1259
|
+
for (const g of d.gaps) html += `<div class="cp-gap-item">${esc(g.gap)}</div>`
|
|
1260
|
+
html += `</div>`
|
|
1261
|
+
}
|
|
1262
|
+
if (!d.behaviors?.length && !d.gaps?.length) html += `<div class="empty">No behaviours found in this project.</div>`
|
|
1263
|
+
return html
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function renderBets(items) {
|
|
1267
|
+
if (!items?.length) return `<div class="empty">No bets generated.</div>`
|
|
1268
|
+
let html = `<div class="cp-header"><span class="cp-title">Strategic Bets</span></div>`
|
|
1269
|
+
for (const b of items) {
|
|
1270
|
+
const total = b.scores?.total ?? 0
|
|
1271
|
+
html += `<div class="cp-card">`
|
|
1272
|
+
html += `<div class="cp-card-header"><span class="cp-card-title">${esc(b.title)}</span><span class="cp-card-score ${scoreClass(total)}">${total}</span></div>`
|
|
1273
|
+
if (b.thesis) html += `<div class="cp-card-body">${esc(b.thesis)}</div>`
|
|
1274
|
+
if (b.first_experiment) html += `<div class="cp-card-meta"><span>First test: ${esc(b.first_experiment)}</span></div>`
|
|
1275
|
+
if (b.kill_criteria?.[0]) html += `<div class="cp-card-meta" style="color:var(--red)">Kill if: ${esc(b.kill_criteria[0])}</div>`
|
|
1276
|
+
if (b.assumptions?.length) html += `<div class="cp-card-meta">Assumes: ${esc(b.assumptions[0])}</div>`
|
|
1277
|
+
html += `</div>`
|
|
1278
|
+
}
|
|
1279
|
+
return html
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function renderOpportunities(items) {
|
|
1283
|
+
if (!items?.length) return `<div class="empty">No gaps or opportunities found.</div>`
|
|
1284
|
+
let html = `<div class="cp-header"><span class="cp-title">Opportunities (${items.length})</span></div>`
|
|
1285
|
+
for (const o of items) {
|
|
1286
|
+
html += `<div class="cp-card">`
|
|
1287
|
+
html += `<div class="cp-card-header"><span class="cp-card-title">${esc(o.title ?? o.gap)}</span><span class="cp-area-tag">${esc(o.area)}</span></div>`
|
|
1288
|
+
if (o.why_it_matters) html += `<div class="cp-card-body">${esc(o.why_it_matters)}</div>`
|
|
1289
|
+
if (o.suggested_next_step) html += `<div class="cp-card-meta">Next: ${esc(o.suggested_next_step)}</div>`
|
|
1290
|
+
html += `</div>`
|
|
1291
|
+
}
|
|
1292
|
+
return html
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1075
1295
|
function renderCompass(data, subcommand) {
|
|
1076
1296
|
const el = document.getElementById("compassView")
|
|
1077
1297
|
if (data.status === "no-llm") {
|
|
1078
1298
|
el.innerHTML = `<div class="empty">${esc(data.message)}<small>Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY and restart quorum serve.</small></div>`
|
|
1079
1299
|
return
|
|
1080
1300
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1301
|
+
|
|
1302
|
+
const d = data.data // structured JSON from compass --json
|
|
1303
|
+
if (!d) {
|
|
1304
|
+
// fallback: raw text (shouldn't happen, but safe)
|
|
1305
|
+
el.innerHTML = `<pre style="font-size:12px;line-height:1.6;white-space:pre-wrap;color:var(--muted)">${esc(data.output ?? JSON.stringify(data, null, 2))}</pre>`
|
|
1306
|
+
return
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
let inner = ''
|
|
1310
|
+
if (subcommand === 'brief') inner = renderBrief(d)
|
|
1311
|
+
else if (subcommand === 'map') inner = renderMap(d)
|
|
1312
|
+
else if (subcommand === 'bets') inner = renderBets(Array.isArray(d) ? d : d.bets ?? [d])
|
|
1313
|
+
else if (subcommand === 'opportunities') inner = renderOpportunities(Array.isArray(d) ? d : [])
|
|
1314
|
+
else inner = `<pre style="font-size:12px;line-height:1.6;white-space:pre-wrap;color:var(--muted)">${esc(JSON.stringify(d, null, 2))}</pre>`
|
|
1315
|
+
|
|
1316
|
+
el.innerHTML = `<div style="padding-bottom:16px">${inner}</div>`
|
|
1086
1317
|
}
|
|
1087
1318
|
</script>
|
|
1088
1319
|
</body>
|