laminark 0.1.0 → 2.21.7

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 (52) hide show
  1. package/.claude-plugin/marketplace.json +15 -0
  2. package/README.md +71 -36
  3. package/package.json +7 -9
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/dist/hooks/handler.d.ts +1 -3
  6. package/plugin/dist/hooks/handler.d.ts.map +1 -1
  7. package/plugin/dist/hooks/handler.js +22 -310
  8. package/plugin/dist/hooks/handler.js.map +1 -1
  9. package/plugin/dist/index.d.ts +1 -3
  10. package/plugin/dist/index.d.ts.map +1 -1
  11. package/plugin/dist/index.js +392 -1895
  12. package/plugin/dist/index.js.map +1 -1
  13. package/plugin/dist/{observations-CorAAc1A.d.mts → observations-Ch0nc47i.d.mts} +1 -23
  14. package/plugin/dist/observations-Ch0nc47i.d.mts.map +1 -0
  15. package/plugin/dist/{tool-registry-e710BvXq.mjs → tool-registry-CZ3mJ4iR.mjs} +13 -932
  16. package/plugin/dist/tool-registry-CZ3mJ4iR.mjs.map +1 -0
  17. package/plugin/hooks/hooks.json +6 -6
  18. package/plugin/scripts/README.md +1 -19
  19. package/plugin/scripts/bump-version.sh +3 -1
  20. package/plugin/scripts/ensure-deps.sh +2 -5
  21. package/plugin/scripts/install.sh +39 -115
  22. package/plugin/scripts/local-install.sh +58 -93
  23. package/plugin/scripts/setup-tmpdir.sh +65 -0
  24. package/plugin/scripts/uninstall.sh +38 -76
  25. package/plugin/scripts/update.sh +69 -20
  26. package/plugin/scripts/verify-install.sh +25 -69
  27. package/plugin/ui/activity.js +0 -12
  28. package/plugin/ui/app.js +54 -24
  29. package/plugin/ui/graph.js +186 -413
  30. package/plugin/ui/help.js +172 -876
  31. package/plugin/ui/index.html +242 -506
  32. package/plugin/ui/settings.js +17 -781
  33. package/plugin/ui/styles.css +44 -990
  34. package/plugin/ui/timeline.js +2 -2
  35. package/plugin/CLAUDE.md +0 -10
  36. package/plugin/commands/recall.md +0 -55
  37. package/plugin/commands/remember.md +0 -34
  38. package/plugin/commands/resume.md +0 -45
  39. package/plugin/commands/stash.md +0 -34
  40. package/plugin/commands/status.md +0 -33
  41. package/plugin/dist/observations-CorAAc1A.d.mts.map +0 -1
  42. package/plugin/dist/tool-registry-e710BvXq.mjs.map +0 -1
  43. package/plugin/laminark.db +0 -0
  44. package/plugin/package.json +0 -17
  45. package/plugin/scripts/dev-sync.sh +0 -58
  46. package/plugin/ui/help/activity-feed.png +0 -0
  47. package/plugin/ui/help/analysis-panel.png +0 -0
  48. package/plugin/ui/help/graph-toolbar.png +0 -0
  49. package/plugin/ui/help/graph-view.png +0 -0
  50. package/plugin/ui/help/settings.png +0 -0
  51. package/plugin/ui/help/timeline.png +0 -0
  52. package/plugin/ui/tools.js +0 -826
@@ -1,5 +1,5 @@
1
1
  import { i as getProjectHash, n as getDatabaseConfig } from "../config-t8LZeB-u.mjs";
2
- import { E as traverseFrom, F as openDatabase, M as SessionRepository, N as ObservationRepository, O as SaveGuard, P as rowToObservation, R as debug, S as getNodeByNameAndType, a as ResearchBufferRepository, c as inferScope, i as NotificationStore, j as SearchEngine, k as jaccardSimilarity, l as inferToolType, n as PathRepository, o as BranchRepository, p as runAutoCleanup, r as initPathSchema, s as extractServerName, t as ToolRegistryRepository } from "../tool-registry-e710BvXq.mjs";
2
+ import { C as rowToObservation, D as debug, S as ObservationRepository, _ as SaveGuard, a as ResearchBufferRepository, b as SearchEngine, c as inferToolType, d as getNodeByNameAndType, h as traverseFrom, i as NotificationStore, n as PathRepository, o as extractServerName, r as initPathSchema, s as inferScope, t as ToolRegistryRepository, v as jaccardSimilarity, w as openDatabase, x as SessionRepository } from "../tool-registry-CZ3mJ4iR.mjs";
3
3
  import { existsSync, readFileSync, readdirSync } from "node:fs";
4
4
  import { basename, join } from "node:path";
5
5
  import { homedir } from "node:os";
@@ -514,14 +514,14 @@ function assembleSessionContext(db, projectHash, toolRegistry) {
514
514
  //#region src/hooks/config-scanner.ts
515
515
  /**
516
516
  * Extracts a description from YAML frontmatter in a Markdown file.
517
- * Reads only the first 2000 bytes for performance.
517
+ * Reads only the first 500 bytes for performance.
518
518
  */
519
519
  function extractDescription(filePath) {
520
520
  try {
521
521
  const fmMatch = readFileSync(filePath, {
522
522
  encoding: "utf-8",
523
523
  flag: "r"
524
- }).slice(0, 2e3).match(/^---\n([\s\S]*?)\n---/);
524
+ }).slice(0, 500).match(/^---\n([\s\S]*?)\n---/);
525
525
  if (!fmMatch) return null;
526
526
  const descMatch = fmMatch[1].match(/description:\s*(.+)/);
527
527
  return descMatch ? descMatch[1].trim() : null;
@@ -530,31 +530,6 @@ function extractDescription(filePath) {
530
530
  }
531
531
  }
532
532
  /**
533
- * Extracts trigger hints from a command/skill file for proactive suggestion matching.
534
- * Reads YAML frontmatter `description` + content from `<objective>` blocks.
535
- * Returns a concatenated string or null if nothing found.
536
- */
537
- function extractTriggerHints(filePath) {
538
- try {
539
- const content = readFileSync(filePath, {
540
- encoding: "utf-8",
541
- flag: "r"
542
- });
543
- const head = content.slice(0, 2e3);
544
- const parts = [];
545
- const fmMatch = head.match(/^---\n([\s\S]*?)\n---/);
546
- if (fmMatch) {
547
- const descMatch = fmMatch[1].match(/description:\s*(.+)/);
548
- if (descMatch) parts.push(descMatch[1].trim());
549
- }
550
- const objMatch = content.match(/<objective>([\s\S]*?)<\/objective>/);
551
- if (objMatch) parts.push(objMatch[1].trim());
552
- return parts.length > 0 ? parts.join(" ") : null;
553
- } catch {
554
- return null;
555
- }
556
- }
557
- /**
558
533
  * Scans an .mcp.json file for MCP server entries.
559
534
  * Each server key becomes a wildcard tool entry (individual tool names are not in config).
560
535
  */
@@ -571,8 +546,7 @@ function scanMcpJson(filePath, scope, projectHash, tools) {
571
546
  source: `config:${filePath}`,
572
547
  projectHash,
573
548
  description: null,
574
- serverName,
575
- triggerHints: null
549
+ serverName
576
550
  });
577
551
  } catch (err) {
578
552
  debug("scanner", "Failed to scan MCP config", {
@@ -597,8 +571,7 @@ function scanClaudeJson(filePath, tools) {
597
571
  source: "config:~/.claude.json",
598
572
  projectHash: null,
599
573
  description: null,
600
- serverName,
601
- triggerHints: null
574
+ serverName
602
575
  });
603
576
  const projects = config.projects;
604
577
  if (projects && typeof projects === "object") for (const projectEntry of Object.values(projects)) {
@@ -610,8 +583,7 @@ function scanClaudeJson(filePath, tools) {
610
583
  source: "config:~/.claude.json",
611
584
  projectHash: null,
612
585
  description: null,
613
- serverName,
614
- triggerHints: null
586
+ serverName
615
587
  });
616
588
  }
617
589
  } catch (err) {
@@ -631,9 +603,7 @@ function scanCommands(dirPath, scope, projectHash, tools) {
631
603
  const entries = readdirSync(dirPath, { withFileTypes: true });
632
604
  for (const entry of entries) if (entry.isFile() && entry.name.endsWith(".md")) {
633
605
  const cmdName = `/${basename(entry.name, ".md")}`;
634
- const filePath = join(dirPath, entry.name);
635
- const description = extractDescription(filePath);
636
- const triggerHints = extractTriggerHints(filePath);
606
+ const description = extractDescription(join(dirPath, entry.name));
637
607
  tools.push({
638
608
  name: cmdName,
639
609
  toolType: "slash_command",
@@ -641,8 +611,7 @@ function scanCommands(dirPath, scope, projectHash, tools) {
641
611
  source: `config:${dirPath}`,
642
612
  projectHash,
643
613
  description,
644
- serverName: null,
645
- triggerHints
614
+ serverName: null
646
615
  });
647
616
  } else if (entry.isDirectory()) {
648
617
  const subDir = join(dirPath, entry.name);
@@ -650,9 +619,7 @@ function scanCommands(dirPath, scope, projectHash, tools) {
650
619
  const subEntries = readdirSync(subDir, { withFileTypes: true });
651
620
  for (const subEntry of subEntries) if (subEntry.isFile() && subEntry.name.endsWith(".md")) {
652
621
  const cmdName = `/${entry.name}:${basename(subEntry.name, ".md")}`;
653
- const subFilePath = join(subDir, subEntry.name);
654
- const description = extractDescription(subFilePath);
655
- const triggerHints = extractTriggerHints(subFilePath);
622
+ const description = extractDescription(join(subDir, subEntry.name));
656
623
  tools.push({
657
624
  name: cmdName,
658
625
  toolType: "slash_command",
@@ -660,8 +627,7 @@ function scanCommands(dirPath, scope, projectHash, tools) {
660
627
  source: `config:${dirPath}`,
661
628
  projectHash,
662
629
  description,
663
- serverName: null,
664
- triggerHints
630
+ serverName: null
665
631
  });
666
632
  }
667
633
  } catch {}
@@ -684,7 +650,6 @@ function scanSkills(dirPath, scope, projectHash, tools) {
684
650
  const skillMdPath = join(dirPath, entry.name, "SKILL.md");
685
651
  if (existsSync(skillMdPath)) {
686
652
  const description = extractDescription(skillMdPath);
687
- const triggerHints = extractTriggerHints(skillMdPath);
688
653
  tools.push({
689
654
  name: entry.name,
690
655
  toolType: "skill",
@@ -692,8 +657,7 @@ function scanSkills(dirPath, scope, projectHash, tools) {
692
657
  source: `config:${dirPath}`,
693
658
  projectHash,
694
659
  description,
695
- serverName: null,
696
- triggerHints
660
+ serverName: null
697
661
  });
698
662
  }
699
663
  }
@@ -727,8 +691,7 @@ function scanInstalledPlugins(filePath, tools) {
727
691
  source: "config:installed_plugins.json",
728
692
  projectHash: null,
729
693
  description: null,
730
- serverName: null,
731
- triggerHints: null
694
+ serverName: null
732
695
  });
733
696
  if (typeof inst.installPath === "string") scanMcpJson(join(inst.installPath, ".mcp.json"), "plugin", null, tools);
734
697
  }
@@ -953,7 +916,7 @@ function detectRemovedTools(toolRegistry, scannedTools, projectHash) {
953
916
  *
954
917
  * @returns Context string to write to stdout, or null if no context available
955
918
  */
956
- function handleSessionStart(input, sessionRepo, db, projectHash, toolRegistry, pathRepo, branchRepo) {
919
+ function handleSessionStart(input, sessionRepo, db, projectHash, toolRegistry, pathRepo) {
957
920
  const sessionId = input.session_id;
958
921
  if (!sessionId) {
959
922
  debug("session", "SessionStart missing session_id, skipping");
@@ -1038,15 +1001,6 @@ function handleSessionStart(input, sessionRepo, db, projectHash, toolRegistry, p
1038
1001
  } catch {
1039
1002
  debug("session", "Cross-session path check failed (non-fatal)");
1040
1003
  }
1041
- if (branchRepo) try {
1042
- const activeBranch = branchRepo.findRecentActiveBranch();
1043
- if (activeBranch) {
1044
- const branchContext = `\n[Laminark] Active work branch carried over:\n ${activeBranch.title ?? activeBranch.id.slice(0, 12)} (${activeBranch.branch_type})\n Stage: ${activeBranch.arc_stage} | Observations: ${activeBranch.observation_count}\n Use query_branches to see all branches.\n`;
1045
- context = context + branchContext;
1046
- }
1047
- } catch {
1048
- debug("session", "Cross-session branch check failed (non-fatal)");
1049
- }
1050
1004
  return context + (toolRegistry ? "\nCall report_available_tools with all your tools (built-in and MCP) so Laminark can index them for discovery." : "");
1051
1005
  }
1052
1006
  /**
@@ -1075,7 +1029,7 @@ function handleSessionEnd(input, sessionRepo) {
1075
1029
  *
1076
1030
  * If the session has zero observations, this is a graceful no-op.
1077
1031
  */
1078
- function handleStop(input, obsRepo, sessionRepo, db, projectHash) {
1032
+ function handleStop(input, obsRepo, sessionRepo) {
1079
1033
  const sessionId = input.session_id;
1080
1034
  if (!sessionId) {
1081
1035
  debug("session", "Stop missing session_id, skipping");
@@ -1089,15 +1043,6 @@ function handleStop(input, obsRepo, sessionRepo, db, projectHash) {
1089
1043
  summaryLength: result.summary.length
1090
1044
  });
1091
1045
  else debug("session", "No observations to summarize", { sessionId });
1092
- if (db && projectHash) try {
1093
- const cleanup = runAutoCleanup(db, projectHash);
1094
- if (!cleanup.skipped && (cleanup.observationsPurged > 0 || cleanup.orphanNodesRemoved > 0)) debug("session", "Auto-cleanup ran at session end", {
1095
- observationsPurged: cleanup.observationsPurged,
1096
- orphanNodesRemoved: cleanup.orphanNodesRemoved
1097
- });
1098
- } catch {
1099
- debug("session", "Auto-cleanup failed (non-fatal)");
1100
- }
1101
1046
  }
1102
1047
 
1103
1048
  //#endregion
@@ -1642,226 +1587,6 @@ const DEFAULT_ROUTING_CONFIG = {
1642
1587
  patternWindowSize: 5
1643
1588
  };
1644
1589
 
1645
- //#endregion
1646
- //#region src/routing/proactive-suggestions.ts
1647
- /**
1648
- * Loads a lightweight snapshot of current session context.
1649
- * Three small queries, each <3ms on a typical database.
1650
- */
1651
- function loadContextSnapshot(db, projectHash, sessionId) {
1652
- let branch = null;
1653
- try {
1654
- const row = db.prepare(`
1655
- SELECT arc_stage, branch_type, observation_count, tool_pattern
1656
- FROM thought_branches
1657
- WHERE project_hash = ? AND session_id = ? AND status = 'active'
1658
- ORDER BY started_at DESC LIMIT 1
1659
- `).get(projectHash, sessionId);
1660
- if (row) {
1661
- let toolPattern = {};
1662
- try {
1663
- toolPattern = JSON.parse(row.tool_pattern);
1664
- } catch {}
1665
- branch = {
1666
- arcStage: row.arc_stage,
1667
- branchType: row.branch_type,
1668
- observationCount: row.observation_count,
1669
- toolPattern
1670
- };
1671
- }
1672
- } catch {}
1673
- let debugPath = null;
1674
- try {
1675
- const pathRow = db.prepare(`
1676
- SELECT dp.status,
1677
- (SELECT COUNT(*) FROM path_waypoints pw WHERE pw.path_id = dp.id) AS waypoint_count,
1678
- (SELECT COUNT(*) FROM path_waypoints pw WHERE pw.path_id = dp.id AND pw.waypoint_type = 'error') AS error_count
1679
- FROM debug_paths dp
1680
- WHERE dp.project_hash = ? AND dp.status = 'active'
1681
- ORDER BY dp.started_at DESC LIMIT 1
1682
- `).get(projectHash);
1683
- if (pathRow) debugPath = {
1684
- status: pathRow.status,
1685
- waypointCount: pathRow.waypoint_count,
1686
- errorCount: pathRow.error_count
1687
- };
1688
- } catch {}
1689
- let recentClassifications = [];
1690
- try {
1691
- recentClassifications = db.prepare(`
1692
- SELECT classification FROM observations
1693
- WHERE project_hash = ? AND session_id = ? AND deleted_at IS NULL AND classification IS NOT NULL
1694
- ORDER BY created_at DESC LIMIT 5
1695
- `).all(projectHash, sessionId).map((r) => r.classification);
1696
- } catch {}
1697
- return {
1698
- branch,
1699
- debugPath,
1700
- recentClassifications
1701
- };
1702
- }
1703
- /**
1704
- * Rules map context patterns to keyword categories, NOT tool names.
1705
- * The engine then searches the tool registry for matching tools.
1706
- */
1707
- const CONTEXT_RULES = [
1708
- {
1709
- id: "debug-session",
1710
- searchKeywords: [
1711
- "debug",
1712
- "error tracking",
1713
- "issue investigation",
1714
- "systematic debugging"
1715
- ],
1716
- confidence: .8,
1717
- reason: "Diagnosis stage detected with problems but no active debug path",
1718
- matches(ctx) {
1719
- if (!ctx.branch) return false;
1720
- const inDiagnosis = ctx.branch.arcStage === "diagnosis" || ctx.branch.arcStage === "investigation";
1721
- const hasProblems = ctx.recentClassifications.some((c) => c === "problem" || c === "error");
1722
- const noActivePath = !ctx.debugPath;
1723
- return inDiagnosis && hasProblems && noActivePath;
1724
- }
1725
- },
1726
- {
1727
- id: "planning-needed",
1728
- searchKeywords: [
1729
- "plan",
1730
- "design",
1731
- "architecture",
1732
- "implementation strategy"
1733
- ],
1734
- confidence: .7,
1735
- reason: "Investigation phase with 5+ observations suggests planning would help",
1736
- matches(ctx) {
1737
- if (!ctx.branch) return false;
1738
- const inInvestigation = ctx.branch.arcStage === "investigation";
1739
- const enoughObservations = ctx.branch.observationCount >= 5;
1740
- const readTools = (ctx.branch.toolPattern["Read"] ?? 0) + (ctx.branch.toolPattern["Grep"] ?? 0) + (ctx.branch.toolPattern["Glob"] ?? 0);
1741
- const totalTools = Object.values(ctx.branch.toolPattern).reduce((a, b) => a + b, 0);
1742
- const mostlyReads = totalTools > 0 && readTools / totalTools > .6;
1743
- return inInvestigation && enoughObservations && mostlyReads;
1744
- }
1745
- },
1746
- {
1747
- id: "ready-to-commit",
1748
- searchKeywords: [
1749
- "commit",
1750
- "save changes",
1751
- "checkpoint"
1752
- ],
1753
- confidence: .75,
1754
- reason: "Execution stage with recent resolutions — good time to commit",
1755
- matches(ctx) {
1756
- if (!ctx.branch) return false;
1757
- const inExecution = ctx.branch.arcStage === "execution";
1758
- const hasResolutions = ctx.recentClassifications.some((c) => c === "resolution" || c === "success");
1759
- const recentSuccesses = ctx.recentClassifications.filter((c) => c === "success" || c === "resolution").length;
1760
- return inExecution && hasResolutions && recentSuccesses >= 2;
1761
- }
1762
- },
1763
- {
1764
- id: "verify-work",
1765
- searchKeywords: [
1766
- "verify",
1767
- "validate",
1768
- "test",
1769
- "acceptance",
1770
- "UAT"
1771
- ],
1772
- confidence: .7,
1773
- reason: "Feature branch in verification stage",
1774
- matches(ctx) {
1775
- if (!ctx.branch) return false;
1776
- return ctx.branch.branchType === "feature" && ctx.branch.arcStage === "verification";
1777
- }
1778
- },
1779
- {
1780
- id: "resume-debugging",
1781
- searchKeywords: [
1782
- "debug",
1783
- "continue debugging",
1784
- "resume investigation"
1785
- ],
1786
- confidence: .75,
1787
- reason: "Active debug path with multiple errors detected",
1788
- matches(ctx) {
1789
- if (!ctx.branch || !ctx.debugPath) return false;
1790
- const inInvestigation = ctx.branch.arcStage === "investigation" || ctx.branch.arcStage === "diagnosis";
1791
- return ctx.debugPath.status === "active" && inInvestigation && ctx.debugPath.errorCount >= 2;
1792
- }
1793
- },
1794
- {
1795
- id: "check-progress",
1796
- searchKeywords: [
1797
- "progress",
1798
- "status",
1799
- "milestone",
1800
- "overview"
1801
- ],
1802
- confidence: .65,
1803
- reason: "Extended execution — consider reviewing progress",
1804
- matches(ctx) {
1805
- if (!ctx.branch) return false;
1806
- return ctx.branch.arcStage === "execution" && ctx.branch.observationCount >= 10;
1807
- }
1808
- }
1809
- ];
1810
- /**
1811
- * Searches suggestable tools for the best match against a set of keywords.
1812
- * Checks trigger_hints, description, and name for substring matches.
1813
- *
1814
- * This is a lightweight in-memory scan, not a DB query.
1815
- */
1816
- function findMatchingTool(keywords, suggestableTools) {
1817
- let best = null;
1818
- for (const tool of suggestableTools) {
1819
- const searchText = [
1820
- tool.trigger_hints ?? "",
1821
- tool.description ?? "",
1822
- tool.name
1823
- ].join(" ").toLowerCase();
1824
- let matchCount = 0;
1825
- for (const keyword of keywords) if (searchText.includes(keyword.toLowerCase())) matchCount++;
1826
- if (matchCount === 0) continue;
1827
- const relevance = matchCount / keywords.length;
1828
- if (!best || relevance > best.relevance) best = {
1829
- tool,
1830
- relevance
1831
- };
1832
- }
1833
- return best;
1834
- }
1835
- /**
1836
- * Evaluates proactive suggestions by matching context rules against available tools.
1837
- *
1838
- * Returns the highest-confidence match (rule confidence * tool relevance) that
1839
- * exceeds the threshold, or null if nothing qualifies.
1840
- */
1841
- function evaluateProactiveSuggestions(ctx, suggestableTools, threshold) {
1842
- let bestSuggestion = null;
1843
- let bestScore = 0;
1844
- for (const rule of CONTEXT_RULES) try {
1845
- if (!rule.matches(ctx)) continue;
1846
- const toolMatch = findMatchingTool(rule.searchKeywords, suggestableTools);
1847
- if (!toolMatch) continue;
1848
- const combinedScore = rule.confidence * toolMatch.relevance;
1849
- if (combinedScore > bestScore && combinedScore >= threshold) {
1850
- bestScore = combinedScore;
1851
- bestSuggestion = {
1852
- toolName: toolMatch.tool.name,
1853
- toolDescription: toolMatch.tool.description,
1854
- confidence: combinedScore,
1855
- tier: "proactive",
1856
- reason: rule.reason
1857
- };
1858
- }
1859
- } catch (err) {
1860
- debug("proactive", `Rule ${rule.id} failed`, { error: String(err) });
1861
- }
1862
- return bestSuggestion;
1863
- }
1864
-
1865
1590
  //#endregion
1866
1591
  //#region src/routing/heuristic-fallback.ts
1867
1592
  /**
@@ -1996,8 +1721,7 @@ function evaluateHeuristic(recentObservations, suggestableTools, confidenceThres
1996
1721
  /**
1997
1722
  * ConversationRouter orchestrates tool suggestion routing.
1998
1723
  *
1999
- * Combines three tiers of suggestion:
2000
- * - Proactive suggestions: context-aware trigger hint matching (ROUT-05)
1724
+ * Combines two tiers of suggestion:
2001
1725
  * - Learned patterns: historical tool sequence matching (ROUT-01)
2002
1726
  * - Heuristic fallback: keyword-based cold-start matching (ROUT-04)
2003
1727
  *
@@ -2083,21 +1807,14 @@ var ConversationRouter = class {
2083
1807
  if (suggestableTools.length === 0) return;
2084
1808
  const suggestableNames = new Set(suggestableTools.map((t) => t.name));
2085
1809
  let suggestion = null;
2086
- suggestion = evaluateProactiveSuggestions(loadContextSnapshot(this.db, this.projectHash, sessionId), suggestableTools, this.config.confidenceThreshold);
2087
- if (!suggestion) {
2088
- if (this.countRecentEvents() >= this.config.minEventsForLearned) suggestion = evaluateLearnedPatterns(this.db, sessionId, this.projectHash, suggestableNames, this.config.confidenceThreshold);
2089
- }
1810
+ if (this.countRecentEvents() >= this.config.minEventsForLearned) suggestion = evaluateLearnedPatterns(this.db, sessionId, this.projectHash, suggestableNames, this.config.confidenceThreshold);
2090
1811
  if (!suggestion) suggestion = evaluateHeuristic(this.getRecentObservations(sessionId), suggestableTools, this.config.confidenceThreshold);
2091
1812
  if (!suggestion) return;
2092
1813
  if (suggestion.confidence < this.config.confidenceThreshold) return;
2093
1814
  const notifStore = new NotificationStore(this.db);
2094
- let message;
2095
- if (suggestion.tier === "proactive") message = `[Laminark suggests] ${suggestion.reason} -- try ${suggestion.toolName}`;
2096
- else {
2097
- const description = suggestion.toolDescription ? ` -- ${suggestion.toolDescription}` : "";
2098
- const usageHint = suggestion.tier === "learned" ? ` (${suggestion.reason})` : "";
2099
- message = `Tool suggestion: ${suggestion.toolName}${description}${usageHint}`;
2100
- }
1815
+ const description = suggestion.toolDescription ? ` -- ${suggestion.toolDescription}` : "";
1816
+ const usageHint = suggestion.tier === "learned" ? ` (${suggestion.reason})` : "";
1817
+ const message = `Tool suggestion: ${suggestion.toolName}${description}${usageHint}`;
2101
1818
  notifStore.add(this.projectHash, message);
2102
1819
  debug("routing", "Suggestion delivered", {
2103
1820
  tool: suggestion.toolName,
@@ -2234,8 +1951,7 @@ function processPostToolUseFiltered(input, obsRepo, researchBuffer, toolRegistry
2234
1951
  source: "hook:PostToolUse",
2235
1952
  projectHash: projectHash ?? null,
2236
1953
  description: null,
2237
- serverName: extractServerName(toolName),
2238
- triggerHints: null
1954
+ serverName: extractServerName(toolName)
2239
1955
  }, sessionId ?? null, !isFailure);
2240
1956
  if (isFailure) {
2241
1957
  const failures = toolRegistry.getRecentEventsForTool(toolName, projectHash ?? "", 5).filter((e) => e.success === 0).length;
@@ -2371,10 +2087,6 @@ async function main() {
2371
2087
  initPathSchema(laminarkDb.db);
2372
2088
  pathRepo = new PathRepository(laminarkDb.db, projectHash);
2373
2089
  } catch {}
2374
- let branchRepo;
2375
- try {
2376
- branchRepo = new BranchRepository(laminarkDb.db, projectHash);
2377
- } catch {}
2378
2090
  switch (eventName) {
2379
2091
  case "PreToolUse": {
2380
2092
  const preContext = handlePreToolUse(input, laminarkDb.db, projectHash, pathRepo);
@@ -2386,7 +2098,7 @@ async function main() {
2386
2098
  processPostToolUseFiltered(input, obsRepo, researchBuffer, toolRegistry, projectHash, laminarkDb.db);
2387
2099
  break;
2388
2100
  case "SessionStart": {
2389
- const context = handleSessionStart(input, sessionRepo, laminarkDb.db, projectHash, toolRegistry, pathRepo, branchRepo);
2101
+ const context = handleSessionStart(input, sessionRepo, laminarkDb.db, projectHash, toolRegistry, pathRepo);
2390
2102
  if (context) process.stdout.write(context);
2391
2103
  break;
2392
2104
  }
@@ -2394,7 +2106,7 @@ async function main() {
2394
2106
  handleSessionEnd(input, sessionRepo);
2395
2107
  break;
2396
2108
  case "Stop":
2397
- handleStop(input, obsRepo, sessionRepo, laminarkDb.db, projectHash);
2109
+ handleStop(input, obsRepo, sessionRepo);
2398
2110
  break;
2399
2111
  default:
2400
2112
  debug("hook", "Unknown hook event", { eventName });