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.
- package/.claude-plugin/marketplace.json +15 -0
- package/README.md +71 -36
- package/package.json +7 -9
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/dist/hooks/handler.d.ts +1 -3
- package/plugin/dist/hooks/handler.d.ts.map +1 -1
- package/plugin/dist/hooks/handler.js +22 -310
- package/plugin/dist/hooks/handler.js.map +1 -1
- package/plugin/dist/index.d.ts +1 -3
- package/plugin/dist/index.d.ts.map +1 -1
- package/plugin/dist/index.js +392 -1895
- package/plugin/dist/index.js.map +1 -1
- package/plugin/dist/{observations-CorAAc1A.d.mts → observations-Ch0nc47i.d.mts} +1 -23
- package/plugin/dist/observations-Ch0nc47i.d.mts.map +1 -0
- package/plugin/dist/{tool-registry-e710BvXq.mjs → tool-registry-CZ3mJ4iR.mjs} +13 -932
- package/plugin/dist/tool-registry-CZ3mJ4iR.mjs.map +1 -0
- package/plugin/hooks/hooks.json +6 -6
- package/plugin/scripts/README.md +1 -19
- package/plugin/scripts/bump-version.sh +3 -1
- package/plugin/scripts/ensure-deps.sh +2 -5
- package/plugin/scripts/install.sh +39 -115
- package/plugin/scripts/local-install.sh +58 -93
- package/plugin/scripts/setup-tmpdir.sh +65 -0
- package/plugin/scripts/uninstall.sh +38 -76
- package/plugin/scripts/update.sh +69 -20
- package/plugin/scripts/verify-install.sh +25 -69
- package/plugin/ui/activity.js +0 -12
- package/plugin/ui/app.js +54 -24
- package/plugin/ui/graph.js +186 -413
- package/plugin/ui/help.js +172 -876
- package/plugin/ui/index.html +242 -506
- package/plugin/ui/settings.js +17 -781
- package/plugin/ui/styles.css +44 -990
- package/plugin/ui/timeline.js +2 -2
- package/plugin/CLAUDE.md +0 -10
- package/plugin/commands/recall.md +0 -55
- package/plugin/commands/remember.md +0 -34
- package/plugin/commands/resume.md +0 -45
- package/plugin/commands/stash.md +0 -34
- package/plugin/commands/status.md +0 -33
- package/plugin/dist/observations-CorAAc1A.d.mts.map +0 -1
- package/plugin/dist/tool-registry-e710BvXq.mjs.map +0 -1
- package/plugin/laminark.db +0 -0
- package/plugin/package.json +0 -17
- package/plugin/scripts/dev-sync.sh +0 -58
- package/plugin/ui/help/activity-feed.png +0 -0
- package/plugin/ui/help/analysis-panel.png +0 -0
- package/plugin/ui/help/graph-toolbar.png +0 -0
- package/plugin/ui/help/graph-view.png +0 -0
- package/plugin/ui/help/settings.png +0 -0
- package/plugin/ui/help/timeline.png +0 -0
- 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 {
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
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
|
|
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
|
|
2109
|
+
handleStop(input, obsRepo, sessionRepo);
|
|
2398
2110
|
break;
|
|
2399
2111
|
default:
|
|
2400
2112
|
debug("hook", "Unknown hook event", { eventName });
|