engrm 0.4.4 → 0.4.5
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/hooks/session-start.js +120 -5
- package/package.json +1 -1
|
@@ -2496,7 +2496,7 @@ function formatSplashScreen(data) {
|
|
|
2496
2496
|
}
|
|
2497
2497
|
function formatVisibleStartupBrief(context) {
|
|
2498
2498
|
const lines = [];
|
|
2499
|
-
const latest = context
|
|
2499
|
+
const latest = pickBestSummary(context);
|
|
2500
2500
|
if (latest) {
|
|
2501
2501
|
const sections = [
|
|
2502
2502
|
["Investigated", latest.investigated],
|
|
@@ -2511,8 +2511,8 @@ function formatVisibleStartupBrief(context) {
|
|
|
2511
2511
|
}
|
|
2512
2512
|
}
|
|
2513
2513
|
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2514
|
+
const stale = pickRelevantStaleDecision(context, latest);
|
|
2515
|
+
if (stale) {
|
|
2516
2516
|
lines.push(`${c2.yellow}Watch:${c2.reset} ${truncateInline(`Decision still looks unfinished: ${stale.title}`, 170)}`);
|
|
2517
2517
|
}
|
|
2518
2518
|
if (lines.length === 0 && context.observations.length > 0) {
|
|
@@ -2526,12 +2526,127 @@ function formatVisibleStartupBrief(context) {
|
|
|
2526
2526
|
function toSplashBullet(value, maxLen) {
|
|
2527
2527
|
if (!value)
|
|
2528
2528
|
return null;
|
|
2529
|
-
const cleaned = value.split(`
|
|
2530
|
-
`).map((line) => line.trim().replace(/^[-*]\s*/, "")).filter(Boolean).join("; ");
|
|
2529
|
+
const cleaned = dedupeFragments(value.split(`
|
|
2530
|
+
`).map((line) => line.trim().replace(/^[-*]\s*/, "")).filter(Boolean).join("; "));
|
|
2531
2531
|
if (!cleaned)
|
|
2532
2532
|
return null;
|
|
2533
2533
|
return truncateInline(cleaned, maxLen);
|
|
2534
2534
|
}
|
|
2535
|
+
function pickBestSummary(context) {
|
|
2536
|
+
const summaries = context.summaries || [];
|
|
2537
|
+
if (!summaries.length)
|
|
2538
|
+
return null;
|
|
2539
|
+
return [...summaries].sort((a, b) => scoreSummary(b) - scoreSummary(a))[0] ?? null;
|
|
2540
|
+
}
|
|
2541
|
+
function scoreSummary(summary) {
|
|
2542
|
+
let score = 0;
|
|
2543
|
+
if (summary.request)
|
|
2544
|
+
score += 3;
|
|
2545
|
+
if (summary.investigated)
|
|
2546
|
+
score += 4;
|
|
2547
|
+
if (summary.learned)
|
|
2548
|
+
score += 5;
|
|
2549
|
+
if (summary.completed)
|
|
2550
|
+
score += 5;
|
|
2551
|
+
if (summary.next_steps)
|
|
2552
|
+
score += 4;
|
|
2553
|
+
score += Math.min(4, sectionItemCount(summary.completed) + sectionItemCount(summary.learned));
|
|
2554
|
+
return score;
|
|
2555
|
+
}
|
|
2556
|
+
function sectionItemCount(value) {
|
|
2557
|
+
if (!value)
|
|
2558
|
+
return 0;
|
|
2559
|
+
return value.split(`
|
|
2560
|
+
`).map((line) => line.trim()).filter(Boolean).length;
|
|
2561
|
+
}
|
|
2562
|
+
function dedupeFragments(text) {
|
|
2563
|
+
const parts = text.split(";").map((part) => part.trim()).filter(Boolean);
|
|
2564
|
+
const seen = new Set;
|
|
2565
|
+
const deduped = [];
|
|
2566
|
+
for (const part of parts) {
|
|
2567
|
+
const normalized = part.toLowerCase();
|
|
2568
|
+
if (seen.has(normalized))
|
|
2569
|
+
continue;
|
|
2570
|
+
seen.add(normalized);
|
|
2571
|
+
deduped.push(part);
|
|
2572
|
+
}
|
|
2573
|
+
return deduped.join("; ");
|
|
2574
|
+
}
|
|
2575
|
+
function pickRelevantStaleDecision(context, summary) {
|
|
2576
|
+
const stale = context.staleDecisions || [];
|
|
2577
|
+
if (!stale.length)
|
|
2578
|
+
return null;
|
|
2579
|
+
const summaryText = [
|
|
2580
|
+
summary?.request,
|
|
2581
|
+
summary?.investigated,
|
|
2582
|
+
summary?.learned,
|
|
2583
|
+
summary?.completed,
|
|
2584
|
+
summary?.next_steps
|
|
2585
|
+
].filter(Boolean).join(" ");
|
|
2586
|
+
let best = null;
|
|
2587
|
+
let bestScore = 0;
|
|
2588
|
+
for (const item of stale) {
|
|
2589
|
+
if ((item.days_ago ?? 999) > 21)
|
|
2590
|
+
continue;
|
|
2591
|
+
const overlap = keywordOverlap(item.title || "", summaryText);
|
|
2592
|
+
const similarity = item.best_match_similarity ?? 0;
|
|
2593
|
+
const score = overlap * 4 + similarity;
|
|
2594
|
+
if (score > bestScore && overlap > 0) {
|
|
2595
|
+
best = item;
|
|
2596
|
+
bestScore = score;
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return best;
|
|
2600
|
+
}
|
|
2601
|
+
function keywordOverlap(a, b) {
|
|
2602
|
+
if (!a || !b)
|
|
2603
|
+
return 0;
|
|
2604
|
+
const stop = new Set([
|
|
2605
|
+
"the",
|
|
2606
|
+
"and",
|
|
2607
|
+
"for",
|
|
2608
|
+
"with",
|
|
2609
|
+
"from",
|
|
2610
|
+
"into",
|
|
2611
|
+
"this",
|
|
2612
|
+
"that",
|
|
2613
|
+
"was",
|
|
2614
|
+
"were",
|
|
2615
|
+
"have",
|
|
2616
|
+
"has",
|
|
2617
|
+
"had",
|
|
2618
|
+
"but",
|
|
2619
|
+
"not",
|
|
2620
|
+
"you",
|
|
2621
|
+
"your",
|
|
2622
|
+
"our",
|
|
2623
|
+
"their",
|
|
2624
|
+
"about",
|
|
2625
|
+
"added",
|
|
2626
|
+
"fixed",
|
|
2627
|
+
"created",
|
|
2628
|
+
"updated",
|
|
2629
|
+
"modified",
|
|
2630
|
+
"changed",
|
|
2631
|
+
"investigate",
|
|
2632
|
+
"next",
|
|
2633
|
+
"steps",
|
|
2634
|
+
"decision",
|
|
2635
|
+
"still",
|
|
2636
|
+
"looks",
|
|
2637
|
+
"unfinished"
|
|
2638
|
+
]);
|
|
2639
|
+
const wordsA = new Set(a.toLowerCase().match(/[a-z0-9_+-]{4,}/g)?.filter((w) => !stop.has(w)) || []);
|
|
2640
|
+
const wordsB = new Set(b.toLowerCase().match(/[a-z0-9_+-]{4,}/g)?.filter((w) => !stop.has(w)) || []);
|
|
2641
|
+
if (!wordsA.size || !wordsB.size)
|
|
2642
|
+
return 0;
|
|
2643
|
+
let overlap = 0;
|
|
2644
|
+
for (const word of wordsA) {
|
|
2645
|
+
if (wordsB.has(word))
|
|
2646
|
+
overlap++;
|
|
2647
|
+
}
|
|
2648
|
+
return overlap / Math.max(1, Math.min(wordsA.size, wordsB.size));
|
|
2649
|
+
}
|
|
2535
2650
|
function truncateInline(text, maxLen) {
|
|
2536
2651
|
const compact = text.replace(/\s+/g, " ").trim();
|
|
2537
2652
|
if (compact.length <= maxLen)
|