@vheins/local-memory-mcp 0.9.6 → 0.9.9
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/{chunk-PWNNK75T.js → chunk-UB4KAJYE.js} +293 -42
- package/dist/dashboard/public/assets/index-C2cM-UXM.css +1 -0
- package/dist/dashboard/public/assets/index-D-xKLFFr.js +86 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +109 -53
- package/dist/mcp/server.js +208 -23
- package/dist/prompts/memory-agent-core.md +1 -1
- package/dist/prompts/task-management-guidelines.md +4 -2
- package/dist/prompts/tool-usage-guidelines.md +4 -2
- package/package.json +1 -1
- package/dist/dashboard/public/assets/index-C9M1BD7U.js +0 -86
- package/dist/dashboard/public/assets/index-CNE1bKgp.css +0 -1
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
11
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/assets/index-D-xKLFFr.js"></script>
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C2cM-UXM.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="app"></div>
|
package/dist/dashboard/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
createFileSink,
|
|
11
11
|
listResources,
|
|
12
12
|
logger
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-UB4KAJYE.js";
|
|
14
14
|
|
|
15
15
|
// src/dashboard/server.ts
|
|
16
16
|
import express from "express";
|
|
@@ -215,7 +215,7 @@ vectors.initialize().catch((err) => {
|
|
|
215
215
|
});
|
|
216
216
|
|
|
217
217
|
// src/dashboard/routes/index.ts
|
|
218
|
-
import { Router as
|
|
218
|
+
import { Router as Router6 } from "express";
|
|
219
219
|
|
|
220
220
|
// src/dashboard/routes/system.routes.ts
|
|
221
221
|
import { Router } from "express";
|
|
@@ -297,6 +297,7 @@ var SystemController = class {
|
|
|
297
297
|
uptime: Math.floor((Date.now() - startTime) / 1e3),
|
|
298
298
|
version: pkg.version,
|
|
299
299
|
memoryCount: stats.totalMemories,
|
|
300
|
+
repoCount: stats.totalRepos,
|
|
300
301
|
pendingRequests: mcpClient.getPendingCount(),
|
|
301
302
|
dbPath: db.getDbPath()
|
|
302
303
|
};
|
|
@@ -321,8 +322,7 @@ var SystemController = class {
|
|
|
321
322
|
try {
|
|
322
323
|
await db.refresh();
|
|
323
324
|
const repo = req.query.repo;
|
|
324
|
-
|
|
325
|
-
const stats = db.system.getDashboardStats(repo);
|
|
325
|
+
const stats = repo ? db.system.getDashboardStats(repo) : db.system.getGlobalDashboardStats();
|
|
326
326
|
res.json(jsonApiRes(stats, "system-stats"));
|
|
327
327
|
} catch (err) {
|
|
328
328
|
const message = err instanceof Error ? err.message : "Internal server error";
|
|
@@ -723,41 +723,33 @@ var TasksController = class {
|
|
|
723
723
|
const attributes = getAttributes(req);
|
|
724
724
|
const existingTask = db.tasks.getTaskById(id);
|
|
725
725
|
if (!existingTask) return res.status(404).json(jsonApiError("Task not found", 404));
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
agent: "dashboard",
|
|
739
|
-
role: "user",
|
|
740
|
-
model: "web-ui",
|
|
741
|
-
previous_status: existingTask.status,
|
|
742
|
-
next_status: attributes.status,
|
|
743
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
744
|
-
});
|
|
745
|
-
} else if (attributes.comment) {
|
|
746
|
-
db.tasks.insertTaskComment({
|
|
747
|
-
id: randomUUID2(),
|
|
748
|
-
task_id: id,
|
|
749
|
-
repo: existingTask.repo,
|
|
750
|
-
comment: attributes.comment,
|
|
751
|
-
agent: "dashboard",
|
|
752
|
-
role: "user",
|
|
753
|
-
model: "web-ui",
|
|
754
|
-
previous_status: null,
|
|
755
|
-
next_status: null,
|
|
756
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
757
|
-
});
|
|
726
|
+
if (!mcpClient.isConnected()) await mcpClient.start();
|
|
727
|
+
const toolArgs = {
|
|
728
|
+
repo: existingTask.repo,
|
|
729
|
+
id,
|
|
730
|
+
agent: "dashboard",
|
|
731
|
+
role: "user",
|
|
732
|
+
model: "web-ui",
|
|
733
|
+
structured: true
|
|
734
|
+
};
|
|
735
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
736
|
+
if (value !== void 0) {
|
|
737
|
+
toolArgs[key] = value;
|
|
758
738
|
}
|
|
759
|
-
}
|
|
760
|
-
|
|
739
|
+
}
|
|
740
|
+
if (attributes.status && attributes.status !== existingTask.status && !toolArgs.comment) {
|
|
741
|
+
toolArgs.comment = `Status updated via dashboard to ${attributes.status}`;
|
|
742
|
+
}
|
|
743
|
+
if (attributes.status === "completed" && toolArgs.est_tokens === void 0) {
|
|
744
|
+
toolArgs.est_tokens = existingTask.est_tokens || 0;
|
|
745
|
+
}
|
|
746
|
+
await mcpClient.callTool("task-update", toolArgs);
|
|
747
|
+
await db.refresh();
|
|
748
|
+
const updatedTask = db.tasks.getTaskById(id);
|
|
749
|
+
if (!updatedTask) {
|
|
750
|
+
return res.status(500).json(jsonApiError("Task updated but could not be reloaded", 500));
|
|
751
|
+
}
|
|
752
|
+
res.json(jsonApiRes(updatedTask, "task"));
|
|
761
753
|
} catch (err) {
|
|
762
754
|
const message = err instanceof Error ? err.message : "Internal server error";
|
|
763
755
|
res.status(500).json(jsonApiError(message));
|
|
@@ -808,23 +800,23 @@ var TasksController = class {
|
|
|
808
800
|
try {
|
|
809
801
|
await db.refresh();
|
|
810
802
|
const { repo } = req.query;
|
|
811
|
-
|
|
803
|
+
const targetRepo = typeof repo === "string" && repo.length > 0 ? repo : null;
|
|
812
804
|
const stats = {
|
|
813
805
|
daily: {
|
|
814
|
-
...db.tasks.getTaskTimeStats(
|
|
815
|
-
history: db.tasks.getTaskComparisonSeries(
|
|
806
|
+
...db.tasks.getTaskTimeStats(targetRepo, "daily"),
|
|
807
|
+
history: db.tasks.getTaskComparisonSeries(targetRepo, "daily")
|
|
816
808
|
},
|
|
817
809
|
weekly: {
|
|
818
|
-
...db.tasks.getTaskTimeStats(
|
|
819
|
-
history: db.tasks.getTaskComparisonSeries(
|
|
810
|
+
...db.tasks.getTaskTimeStats(targetRepo, "weekly"),
|
|
811
|
+
history: db.tasks.getTaskComparisonSeries(targetRepo, "weekly")
|
|
820
812
|
},
|
|
821
813
|
monthly: {
|
|
822
|
-
...db.tasks.getTaskTimeStats(
|
|
823
|
-
history: db.tasks.getTaskComparisonSeries(
|
|
814
|
+
...db.tasks.getTaskTimeStats(targetRepo, "monthly"),
|
|
815
|
+
history: db.tasks.getTaskComparisonSeries(targetRepo, "monthly")
|
|
824
816
|
},
|
|
825
817
|
overall: {
|
|
826
|
-
...db.tasks.getTaskTimeStats(
|
|
827
|
-
history: db.tasks.getTaskComparisonSeries(
|
|
818
|
+
...db.tasks.getTaskTimeStats(targetRepo, "overall"),
|
|
819
|
+
history: db.tasks.getTaskComparisonSeries(targetRepo, "overall")
|
|
828
820
|
}
|
|
829
821
|
};
|
|
830
822
|
res.json(jsonApiRes(stats, "performance-stats"));
|
|
@@ -1039,13 +1031,77 @@ router4.put("/:id", StandardsController.update);
|
|
|
1039
1031
|
router4.delete("/:id", StandardsController.delete);
|
|
1040
1032
|
var standard_routes_default = router4;
|
|
1041
1033
|
|
|
1042
|
-
// src/dashboard/routes/
|
|
1034
|
+
// src/dashboard/routes/coordination.routes.ts
|
|
1035
|
+
import { Router as Router5 } from "express";
|
|
1036
|
+
|
|
1037
|
+
// src/dashboard/controllers/CoordinationController.ts
|
|
1038
|
+
var CoordinationController = class {
|
|
1039
|
+
static async listClaims(req, res) {
|
|
1040
|
+
try {
|
|
1041
|
+
await db.refresh();
|
|
1042
|
+
const { repo, agent, active_only } = req.query;
|
|
1043
|
+
const page = Math.max(1, parseInt(req.query.page || "1", 10));
|
|
1044
|
+
const pageSize = Math.min(100, Math.max(1, parseInt(req.query.pageSize || "20", 10)));
|
|
1045
|
+
if (!repo) return res.status(400).json(jsonApiError("repo is required", 400));
|
|
1046
|
+
const claims = db.handoffs.listClaims({
|
|
1047
|
+
repo,
|
|
1048
|
+
agent: typeof agent === "string" ? agent : void 0,
|
|
1049
|
+
active_only: active_only === void 0 ? true : String(active_only) === "true",
|
|
1050
|
+
limit: pageSize,
|
|
1051
|
+
offset: (page - 1) * pageSize
|
|
1052
|
+
});
|
|
1053
|
+
const total = db.handoffs.listClaims({
|
|
1054
|
+
repo,
|
|
1055
|
+
agent: typeof agent === "string" ? agent : void 0,
|
|
1056
|
+
active_only: active_only === void 0 ? true : String(active_only) === "true",
|
|
1057
|
+
limit: 1e5,
|
|
1058
|
+
offset: 0
|
|
1059
|
+
}).length;
|
|
1060
|
+
res.json(
|
|
1061
|
+
jsonApiRes(claims, "claim", {
|
|
1062
|
+
meta: {
|
|
1063
|
+
page,
|
|
1064
|
+
pageSize,
|
|
1065
|
+
totalItems: total,
|
|
1066
|
+
totalPages: Math.ceil(total / pageSize)
|
|
1067
|
+
}
|
|
1068
|
+
})
|
|
1069
|
+
);
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
1072
|
+
res.status(500).json(jsonApiError(message));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
static async releaseClaim(req, res) {
|
|
1076
|
+
try {
|
|
1077
|
+
const attributes = getAttributes(req);
|
|
1078
|
+
if (!mcpClient.isConnected()) await mcpClient.start();
|
|
1079
|
+
const result = await mcpClient.callTool("claim-release", {
|
|
1080
|
+
...attributes,
|
|
1081
|
+
structured: true
|
|
1082
|
+
});
|
|
1083
|
+
res.json(jsonApiRes(result.structuredContent || result, "claim-release"));
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
1086
|
+
res.status(500).json(jsonApiError(message));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
// src/dashboard/routes/coordination.routes.ts
|
|
1043
1092
|
var router5 = Router5();
|
|
1044
|
-
router5.
|
|
1045
|
-
router5.
|
|
1046
|
-
router5
|
|
1047
|
-
|
|
1048
|
-
|
|
1093
|
+
router5.get("/claims", CoordinationController.listClaims);
|
|
1094
|
+
router5.post("/claims/release", CoordinationController.releaseClaim);
|
|
1095
|
+
var coordination_routes_default = router5;
|
|
1096
|
+
|
|
1097
|
+
// src/dashboard/routes/index.ts
|
|
1098
|
+
var router6 = Router6();
|
|
1099
|
+
router6.use("/", system_routes_default);
|
|
1100
|
+
router6.use("/memories", memory_routes_default);
|
|
1101
|
+
router6.use("/tasks", task_routes_default);
|
|
1102
|
+
router6.use("/standards", standard_routes_default);
|
|
1103
|
+
router6.use("/coordination", coordination_routes_default);
|
|
1104
|
+
var routes_default = router6;
|
|
1049
1105
|
|
|
1050
1106
|
// src/dashboard/server.ts
|
|
1051
1107
|
var __dirname3 = path3.dirname(fileURLToPath3(import.meta.url));
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
CAPABILITIES,
|
|
4
|
+
ClaimListSchema,
|
|
5
|
+
ClaimReleaseSchema,
|
|
4
6
|
HandoffCreateSchema,
|
|
5
7
|
HandoffListSchema,
|
|
6
8
|
HandoffUpdateSchema,
|
|
@@ -55,7 +57,7 @@ import {
|
|
|
55
57
|
toContextSlug,
|
|
56
58
|
updateSessionFromInitialize,
|
|
57
59
|
updateSessionRoots
|
|
58
|
-
} from "../chunk-
|
|
60
|
+
} from "../chunk-UB4KAJYE.js";
|
|
59
61
|
|
|
60
62
|
// src/mcp/server.ts
|
|
61
63
|
import readline from "readline";
|
|
@@ -1663,6 +1665,16 @@ function buildHandoffListSummary(repo, count, status, fromAgent, toAgent) {
|
|
|
1663
1665
|
}
|
|
1664
1666
|
return parts.join("\n");
|
|
1665
1667
|
}
|
|
1668
|
+
function buildClaimListSummary(repo, count, agent, activeOnly) {
|
|
1669
|
+
const parts = [`Found ${count} claim${count === 1 ? "" : "s"} in repo "${repo}".`];
|
|
1670
|
+
if (agent) {
|
|
1671
|
+
parts.push(`Agent filter: ${agent}.`);
|
|
1672
|
+
}
|
|
1673
|
+
if (activeOnly) {
|
|
1674
|
+
parts.push("Showing active claims only.");
|
|
1675
|
+
}
|
|
1676
|
+
return parts.join("\n");
|
|
1677
|
+
}
|
|
1666
1678
|
async function handleHandoffCreate(args, storage) {
|
|
1667
1679
|
const validated = HandoffCreateSchema.parse(args);
|
|
1668
1680
|
const { repo, from_agent, to_agent, task_id, task_code, summary, context, expires_at, structured } = validated;
|
|
@@ -1708,15 +1720,31 @@ async function handleHandoffList(args, storage) {
|
|
|
1708
1720
|
limit,
|
|
1709
1721
|
offset
|
|
1710
1722
|
});
|
|
1711
|
-
const COLUMNS = [
|
|
1723
|
+
const COLUMNS = [
|
|
1724
|
+
"id",
|
|
1725
|
+
"from_agent",
|
|
1726
|
+
"to_agent",
|
|
1727
|
+
"task_id",
|
|
1728
|
+
"task_code",
|
|
1729
|
+
"status",
|
|
1730
|
+
"created_at",
|
|
1731
|
+
"updated_at",
|
|
1732
|
+
"expires_at",
|
|
1733
|
+
"summary",
|
|
1734
|
+
"context"
|
|
1735
|
+
];
|
|
1712
1736
|
const rows = handoffs.map((handoff) => [
|
|
1713
1737
|
handoff.id,
|
|
1714
1738
|
handoff.from_agent,
|
|
1715
1739
|
handoff.to_agent,
|
|
1716
1740
|
handoff.task_id,
|
|
1741
|
+
handoff.task_code ?? null,
|
|
1717
1742
|
handoff.status,
|
|
1718
1743
|
handoff.created_at,
|
|
1719
|
-
handoff.
|
|
1744
|
+
handoff.updated_at,
|
|
1745
|
+
handoff.expires_at,
|
|
1746
|
+
handoff.summary,
|
|
1747
|
+
handoff.context
|
|
1720
1748
|
]);
|
|
1721
1749
|
const structuredData = {
|
|
1722
1750
|
schema: "handoff-list",
|
|
@@ -1806,6 +1834,82 @@ async function handleTaskClaim(args, storage) {
|
|
|
1806
1834
|
}
|
|
1807
1835
|
return response;
|
|
1808
1836
|
}
|
|
1837
|
+
async function handleClaimList(args, storage) {
|
|
1838
|
+
const validated = ClaimListSchema.parse(args);
|
|
1839
|
+
const { repo, agent, active_only, limit, offset, structured } = validated;
|
|
1840
|
+
const claims = storage.handoffs.listClaims({
|
|
1841
|
+
repo,
|
|
1842
|
+
agent,
|
|
1843
|
+
active_only,
|
|
1844
|
+
limit,
|
|
1845
|
+
offset
|
|
1846
|
+
});
|
|
1847
|
+
const COLUMNS = ["id", "task_id", "task_code", "agent", "role", "claimed_at", "released_at", "metadata"];
|
|
1848
|
+
const rows = claims.map((claim) => [
|
|
1849
|
+
claim.id,
|
|
1850
|
+
claim.task_id,
|
|
1851
|
+
claim.task_code ?? null,
|
|
1852
|
+
claim.agent,
|
|
1853
|
+
claim.role,
|
|
1854
|
+
claim.claimed_at,
|
|
1855
|
+
claim.released_at,
|
|
1856
|
+
claim.metadata
|
|
1857
|
+
]);
|
|
1858
|
+
const structuredData = {
|
|
1859
|
+
schema: "claim-list",
|
|
1860
|
+
claims: {
|
|
1861
|
+
columns: [...COLUMNS],
|
|
1862
|
+
rows
|
|
1863
|
+
},
|
|
1864
|
+
count: rows.length,
|
|
1865
|
+
offset
|
|
1866
|
+
};
|
|
1867
|
+
const contentSummary = buildClaimListSummary(repo, rows.length, agent, active_only);
|
|
1868
|
+
return createMcpResponse(structuredData, contentSummary, {
|
|
1869
|
+
contentSummary,
|
|
1870
|
+
includeSerializedStructuredContent: structured
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
async function handleClaimRelease(args, storage) {
|
|
1874
|
+
const validated = ClaimReleaseSchema.parse(args);
|
|
1875
|
+
const { repo, task_id, task_code, agent, structured } = validated;
|
|
1876
|
+
let resolvedTaskId = task_id;
|
|
1877
|
+
let resolvedTaskCode = task_code ?? null;
|
|
1878
|
+
if (resolvedTaskId) {
|
|
1879
|
+
const task = storage.tasks.getTaskById(resolvedTaskId);
|
|
1880
|
+
if (!task || task.repo !== repo) {
|
|
1881
|
+
throw new Error(`Task not found: ${resolvedTaskId} in repo ${repo}`);
|
|
1882
|
+
}
|
|
1883
|
+
resolvedTaskCode = task.task_code;
|
|
1884
|
+
} else if (task_code) {
|
|
1885
|
+
const task = storage.tasks.getTaskByCode(repo, task_code);
|
|
1886
|
+
if (!task) {
|
|
1887
|
+
throw new Error(`Task not found: ${task_code} in repo ${repo}`);
|
|
1888
|
+
}
|
|
1889
|
+
resolvedTaskId = task.id;
|
|
1890
|
+
resolvedTaskCode = task.task_code;
|
|
1891
|
+
}
|
|
1892
|
+
const success = storage.handoffs.releaseClaim(resolvedTaskId, agent);
|
|
1893
|
+
if (!success) {
|
|
1894
|
+
throw new Error(`No active claim found for task ${resolvedTaskCode || resolvedTaskId}`);
|
|
1895
|
+
}
|
|
1896
|
+
const result = {
|
|
1897
|
+
success,
|
|
1898
|
+
repo,
|
|
1899
|
+
task_id: resolvedTaskId,
|
|
1900
|
+
task_code: resolvedTaskCode,
|
|
1901
|
+
agent: agent ?? null
|
|
1902
|
+
};
|
|
1903
|
+
const contentSummary = [
|
|
1904
|
+
`Released claim for task ${resolvedTaskCode || resolvedTaskId}.`,
|
|
1905
|
+
`Repo: ${repo}`,
|
|
1906
|
+
agent ? `Agent: ${agent}` : "Agent: any active claimant"
|
|
1907
|
+
].join("\n");
|
|
1908
|
+
return createMcpResponse(result, contentSummary, {
|
|
1909
|
+
contentSummary,
|
|
1910
|
+
includeSerializedStructuredContent: structured
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1809
1913
|
|
|
1810
1914
|
// src/mcp/tools/standard.store.ts
|
|
1811
1915
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -1901,10 +2005,63 @@ async function handleStandardStore(params, db2, vectors2) {
|
|
|
1901
2005
|
|
|
1902
2006
|
// src/mcp/tools/standard.search.ts
|
|
1903
2007
|
var HYBRID_WEIGHTS_STANDARD = {
|
|
1904
|
-
similarity: 0.
|
|
1905
|
-
vector: 0.
|
|
1906
|
-
|
|
2008
|
+
similarity: 0.4,
|
|
2009
|
+
vector: 0.25,
|
|
2010
|
+
keyword: 0.3,
|
|
2011
|
+
usage: 0.05
|
|
1907
2012
|
};
|
|
2013
|
+
function tokenizeSearchText(value) {
|
|
2014
|
+
return value.toLowerCase().split(/[^a-z0-9]+/g).map((token) => token.trim()).filter((token) => token.length >= 2);
|
|
2015
|
+
}
|
|
2016
|
+
function scoreKeywordRelevance(query, standard) {
|
|
2017
|
+
const queryTokens = Array.from(new Set(tokenizeSearchText(query)));
|
|
2018
|
+
if (queryTokens.length === 0) return 0;
|
|
2019
|
+
const titleText = standard.title.toLowerCase();
|
|
2020
|
+
const contextText = standard.context.toLowerCase();
|
|
2021
|
+
const tagText = standard.tags.join(" ").toLowerCase();
|
|
2022
|
+
const stackText = standard.stack.join(" ").toLowerCase();
|
|
2023
|
+
const contentText = standard.content.toLowerCase();
|
|
2024
|
+
const queryPhrase = query.trim().toLowerCase();
|
|
2025
|
+
let titleMatches = 0;
|
|
2026
|
+
let contextMatches = 0;
|
|
2027
|
+
let tagMatches = 0;
|
|
2028
|
+
let stackMatches = 0;
|
|
2029
|
+
let contentMatches = 0;
|
|
2030
|
+
for (const token of queryTokens) {
|
|
2031
|
+
if (titleText.includes(token)) titleMatches += 1;
|
|
2032
|
+
if (contextText.includes(token)) contextMatches += 1;
|
|
2033
|
+
if (tagText.includes(token)) tagMatches += 1;
|
|
2034
|
+
if (stackText.includes(token)) stackMatches += 1;
|
|
2035
|
+
if (contentText.includes(token)) contentMatches += 1;
|
|
2036
|
+
}
|
|
2037
|
+
const titleCoverage = titleMatches / queryTokens.length;
|
|
2038
|
+
const contextCoverage = contextMatches / queryTokens.length;
|
|
2039
|
+
const tagCoverage = tagMatches / queryTokens.length;
|
|
2040
|
+
const stackCoverage = stackMatches / queryTokens.length;
|
|
2041
|
+
const contentCoverage = contentMatches / queryTokens.length;
|
|
2042
|
+
const exactPhraseBonus = queryPhrase.length >= 6 && (titleText.includes(queryPhrase) || contentText.includes(queryPhrase) || tagText.includes(queryPhrase)) ? 0.2 : 0;
|
|
2043
|
+
return Math.min(
|
|
2044
|
+
1,
|
|
2045
|
+
titleCoverage * 0.45 + contextCoverage * 0.15 + tagCoverage * 0.15 + stackCoverage * 0.05 + contentCoverage * 0.2 + exactPhraseBonus
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
function collectMatchedTerms(query, standard) {
|
|
2049
|
+
const queryTokens = Array.from(new Set(tokenizeSearchText(query)));
|
|
2050
|
+
if (queryTokens.length === 0) return [];
|
|
2051
|
+
const searchableFields = [
|
|
2052
|
+
standard.title.toLowerCase(),
|
|
2053
|
+
standard.context.toLowerCase(),
|
|
2054
|
+
standard.tags.join(" ").toLowerCase(),
|
|
2055
|
+
standard.stack.join(" ").toLowerCase(),
|
|
2056
|
+
standard.content.toLowerCase()
|
|
2057
|
+
];
|
|
2058
|
+
return queryTokens.filter((token) => searchableFields.some((field) => field.includes(token)));
|
|
2059
|
+
}
|
|
2060
|
+
function toConfidence(finalScore, keywordScore) {
|
|
2061
|
+
if (finalScore >= 0.72 || keywordScore >= 0.85) return "high";
|
|
2062
|
+
if (finalScore >= 0.42 || keywordScore >= 0.45) return "medium";
|
|
2063
|
+
return "low";
|
|
2064
|
+
}
|
|
1908
2065
|
async function handleStandardSearch(params, db2, vectors2) {
|
|
1909
2066
|
const validated = StandardSearchSchema.parse(params);
|
|
1910
2067
|
const searchQuery = expandQuery(validated.query || "", void 0);
|
|
@@ -1936,13 +2093,18 @@ async function handleStandardSearch(params, db2, vectors2) {
|
|
|
1936
2093
|
if (similarityResults.length > 0) {
|
|
1937
2094
|
scoredStandards = similarityResults.map((candidate) => {
|
|
1938
2095
|
const vectorScore = vectorScoreMap.get(candidate.id) ?? 0;
|
|
2096
|
+
const keywordScore = scoreKeywordRelevance(validated.query || "", candidate);
|
|
2097
|
+
const matchedTerms = collectMatchedTerms(validated.query || "", candidate);
|
|
1939
2098
|
const usageScore = Math.min(1, candidate.hit_count / 10);
|
|
1940
|
-
const finalScore = candidate.similarity * HYBRID_WEIGHTS_STANDARD.similarity + vectorScore * HYBRID_WEIGHTS_STANDARD.vector + usageScore * HYBRID_WEIGHTS_STANDARD.usage;
|
|
2099
|
+
const finalScore = candidate.similarity * HYBRID_WEIGHTS_STANDARD.similarity + vectorScore * HYBRID_WEIGHTS_STANDARD.vector + keywordScore * HYBRID_WEIGHTS_STANDARD.keyword + usageScore * HYBRID_WEIGHTS_STANDARD.usage;
|
|
1941
2100
|
return {
|
|
1942
2101
|
standard: candidate,
|
|
1943
2102
|
similarityScore: candidate.similarity,
|
|
1944
2103
|
vectorScore,
|
|
1945
|
-
|
|
2104
|
+
keywordScore,
|
|
2105
|
+
matchedTerms,
|
|
2106
|
+
finalScore,
|
|
2107
|
+
confidence: toConfidence(finalScore, keywordScore)
|
|
1946
2108
|
};
|
|
1947
2109
|
});
|
|
1948
2110
|
} else if (vectorResults.length > 0) {
|
|
@@ -1951,43 +2113,58 @@ async function handleStandardSearch(params, db2, vectors2) {
|
|
|
1951
2113
|
scoredStandards = vectorResults.flatMap((row) => {
|
|
1952
2114
|
const standard = standardMap.get(row.id);
|
|
1953
2115
|
if (!standard) return [];
|
|
2116
|
+
const keywordScore = scoreKeywordRelevance(validated.query || "", standard);
|
|
2117
|
+
const matchedTerms = collectMatchedTerms(validated.query || "", standard);
|
|
1954
2118
|
const usageScore = Math.min(1, standard.hit_count / 10);
|
|
2119
|
+
const finalScore = row.score * HYBRID_WEIGHTS_STANDARD.vector + keywordScore * HYBRID_WEIGHTS_STANDARD.keyword + usageScore * (1 - HYBRID_WEIGHTS_STANDARD.vector - HYBRID_WEIGHTS_STANDARD.keyword);
|
|
1955
2120
|
return [
|
|
1956
2121
|
{
|
|
1957
2122
|
standard,
|
|
1958
2123
|
similarityScore: 0,
|
|
1959
2124
|
vectorScore: row.score,
|
|
1960
|
-
|
|
2125
|
+
keywordScore,
|
|
2126
|
+
matchedTerms,
|
|
2127
|
+
finalScore,
|
|
2128
|
+
confidence: toConfidence(finalScore, keywordScore)
|
|
1961
2129
|
}
|
|
1962
2130
|
];
|
|
1963
2131
|
});
|
|
1964
2132
|
}
|
|
1965
2133
|
} catch (error) {
|
|
1966
2134
|
logger.warn("Standard vector search failed, using similarity only", { error: String(error) });
|
|
1967
|
-
scoredStandards = similarityResults.map((candidate) =>
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2135
|
+
scoredStandards = similarityResults.map((candidate) => {
|
|
2136
|
+
const keywordScore = scoreKeywordRelevance(validated.query || "", candidate);
|
|
2137
|
+
const matchedTerms = collectMatchedTerms(validated.query || "", candidate);
|
|
2138
|
+
const finalScore = candidate.similarity * (HYBRID_WEIGHTS_STANDARD.similarity + HYBRID_WEIGHTS_STANDARD.vector * 0.5) + keywordScore * HYBRID_WEIGHTS_STANDARD.keyword + Math.min(1, candidate.hit_count / 10) * HYBRID_WEIGHTS_STANDARD.usage;
|
|
2139
|
+
return {
|
|
2140
|
+
standard: candidate,
|
|
2141
|
+
similarityScore: candidate.similarity,
|
|
2142
|
+
vectorScore: 0,
|
|
2143
|
+
keywordScore,
|
|
2144
|
+
matchedTerms,
|
|
2145
|
+
finalScore,
|
|
2146
|
+
confidence: toConfidence(finalScore, keywordScore)
|
|
2147
|
+
};
|
|
2148
|
+
});
|
|
1973
2149
|
}
|
|
1974
2150
|
scoredStandards.sort((a, b) => b.finalScore - a.finalScore);
|
|
1975
2151
|
const threshold = scoredStandards.length <= 5 ? 0.08 : 0.2;
|
|
1976
|
-
let results = scoredStandards.filter((candidate) => candidate.finalScore >= threshold)
|
|
2152
|
+
let results = scoredStandards.filter((candidate) => candidate.finalScore >= threshold);
|
|
1977
2153
|
if (results.length === 0 && scoredStandards.length > 0) {
|
|
1978
|
-
results = [scoredStandards[0]
|
|
2154
|
+
results = [scoredStandards[0]];
|
|
1979
2155
|
}
|
|
1980
2156
|
const paginatedResults = results.slice(validated.offset, validated.offset + validated.limit);
|
|
1981
|
-
db2.standards.incrementHitCounts(paginatedResults.map((standard) => standard.id));
|
|
2157
|
+
db2.standards.incrementHitCounts(paginatedResults.map(({ standard }) => standard.id));
|
|
1982
2158
|
logger.info("[Tool] standard.search - searched coding standards", {
|
|
1983
2159
|
resultCount: paginatedResults.length,
|
|
1984
2160
|
stack: validated.stack,
|
|
1985
2161
|
language: validated.language,
|
|
1986
2162
|
context: validated.context,
|
|
1987
|
-
version: validated.version
|
|
2163
|
+
version: validated.version,
|
|
2164
|
+
topConfidence: paginatedResults[0]?.confidence
|
|
1988
2165
|
});
|
|
1989
|
-
const COLUMNS = ["code", "id", "title", "context", "language", "scope", "tags", "updated_at"];
|
|
1990
|
-
const rows = paginatedResults.map((standard) => [
|
|
2166
|
+
const COLUMNS = ["code", "id", "title", "context", "language", "scope", "tags", "confidence", "score", "matched_terms", "updated_at"];
|
|
2167
|
+
const rows = paginatedResults.map(({ standard, confidence, finalScore, matchedTerms }) => [
|
|
1991
2168
|
standard.code ?? "-",
|
|
1992
2169
|
standard.id,
|
|
1993
2170
|
standard.title,
|
|
@@ -1995,15 +2172,18 @@ async function handleStandardSearch(params, db2, vectors2) {
|
|
|
1995
2172
|
standard.language || "-",
|
|
1996
2173
|
standard.is_global ? "global" : standard.repo || "-",
|
|
1997
2174
|
standard.tags.join(", "),
|
|
2175
|
+
confidence,
|
|
2176
|
+
Number(finalScore.toFixed(3)),
|
|
2177
|
+
matchedTerms.join(", "),
|
|
1998
2178
|
standard.updated_at
|
|
1999
2179
|
]);
|
|
2000
2180
|
let contentSummary;
|
|
2001
2181
|
if (paginatedResults.length > 0) {
|
|
2002
2182
|
const parts = [
|
|
2003
2183
|
"Standards:",
|
|
2004
|
-
"- code|title|context|language|scope",
|
|
2184
|
+
"- code|confidence|matched_terms|title|context|language|scope",
|
|
2005
2185
|
...paginatedResults.map(
|
|
2006
|
-
(standard) => `- ${standard.code ?? "-"}|${standard.title}|${standard.context}|${standard.language || "-"}|${standard.is_global ? "global" : standard.repo || "-"}`
|
|
2186
|
+
({ standard, confidence, matchedTerms }) => `- ${standard.code ?? "-"}|${confidence}|${matchedTerms.join(", ")}|${standard.title}|${standard.context}|${standard.language || "-"}|${standard.is_global ? "global" : standard.repo || "-"}`
|
|
2007
2187
|
),
|
|
2008
2188
|
"",
|
|
2009
2189
|
"Use standard-detail with code for full content."
|
|
@@ -2283,6 +2463,7 @@ function createRouter(db2, vectors2, options) {
|
|
|
2283
2463
|
"task-create",
|
|
2284
2464
|
"task-create-interactive",
|
|
2285
2465
|
"task-claim",
|
|
2466
|
+
"claim-release",
|
|
2286
2467
|
"task-update",
|
|
2287
2468
|
"task-delete"
|
|
2288
2469
|
]);
|
|
@@ -2327,6 +2508,10 @@ function createRouter(db2, vectors2, options) {
|
|
|
2327
2508
|
return await handleHandoffUpdate(args, db2);
|
|
2328
2509
|
case "task-claim":
|
|
2329
2510
|
return await handleTaskClaim(args, db2);
|
|
2511
|
+
case "claim-list":
|
|
2512
|
+
return await handleClaimList(args, db2);
|
|
2513
|
+
case "claim-release":
|
|
2514
|
+
return await handleClaimRelease(args, db2);
|
|
2330
2515
|
case "standard-store":
|
|
2331
2516
|
return await handleStandardStore(args, db2, vectors2);
|
|
2332
2517
|
case "standard-update":
|
|
@@ -18,7 +18,7 @@ You are a memory-aware agent. Memory is project truth, not a suggestion.
|
|
|
18
18
|
|
|
19
19
|
## Execution Policy
|
|
20
20
|
1. **Orient**: Call `task-list` for active work and `handoff-list` for pending transfers when starting a repository session. Close stale pending handoffs with `handoff-update` when they no longer describe unfinished work.
|
|
21
|
-
2. **Claim**: Use `task-claim` before taking ownership of a concrete task.
|
|
21
|
+
2. **Claim**: Use `task-claim` before taking ownership of a concrete task. Use `claim-list` when ownership is unclear and `claim-release` to clear stale claims during reassignment.
|
|
22
22
|
3. **Search**: Call `memory-search` with `current_file_path` and `current_tags` before coding.
|
|
23
23
|
4. **Standards**: Call `standard-search` when implementation may be governed by coding standards.
|
|
24
24
|
5. **Retrieve**: Use `memory-detail` for full content if search pointer rows are insufficient.
|
|
@@ -9,8 +9,8 @@ agent: Project Manager
|
|
|
9
9
|
## 1. NAVIGATION (`task-list`)
|
|
10
10
|
- **Sync**: Call `task-list` at every session start (default: `in_progress,pending`).
|
|
11
11
|
- **Format**: Compact pointer table: `id`, `task_code`, `title`, `status`, `priority`, `updated_at`, `comments_count`. Use `query` for keyword search.
|
|
12
|
-
- **Retrieve**: Fetch full context via `task-detail` AFTER selecting a task.
|
|
13
|
-
- **Coordination**: Check active ownership with task metadata and `
|
|
12
|
+
- **Retrieve**: Fetch full context via `task-detail` AFTER selecting a task. The hydrated task includes current coordination state such as active claims and pending handoffs.
|
|
13
|
+
- **Coordination**: Check active ownership with task coordination metadata, `task-claim`, and `claim-list`. NEVER work on tasks claimed by others. Focus on ONE task at a time.
|
|
14
14
|
|
|
15
15
|
## 2. DETAIL TOOLS
|
|
16
16
|
- **Tasks**: Call `task-detail` for history/comments (ID or `task_code`).
|
|
@@ -23,6 +23,8 @@ agent: Project Manager
|
|
|
23
23
|
- **Transition Safety**: MUST move from `backlog/pending` → `in_progress` → `completed`. Skipping `in_progress` is forbidden.
|
|
24
24
|
- **Automatic Cleanup**: `task-update` to `completed` or `canceled` automatically releases active claims and expires pending handoffs linked to that task.
|
|
25
25
|
- **Claiming**: Use `task-claim` when taking ownership of a task, with `task_code` when working from human-visible queues.
|
|
26
|
+
- **Claim Inspection**: Use `claim-list` when ownership is unclear or when triaging stale work.
|
|
27
|
+
- **Claim Release**: Use `claim-release` to clear stale ownership explicitly when a task is being handed back or reassigned.
|
|
26
28
|
- **Handoff**: Use `handoff-create` only when pausing or transferring unfinished work. Do not use pending handoffs for completion summaries; close consumed/stale handoffs with `handoff-update`.
|
|
27
29
|
- **Validation**: Only mark `completed` after passing tests or explicitly documenting why verification could not run.
|
|
28
30
|
- **Archiving**: Completion triggers auto-archive to `task_archive` memory with token reporting.
|
|
@@ -18,7 +18,7 @@ Use the tools in the same flow exposed by the dashboard: navigate with compact l
|
|
|
18
18
|
|
|
19
19
|
## 2. Task Flow
|
|
20
20
|
- **Navigate**: Start with `task-list` using active statuses (`in_progress,pending`) or explicit filters.
|
|
21
|
-
- **Hydrate**: Use `task-detail` after selecting a task from the list.
|
|
21
|
+
- **Hydrate**: Use `task-detail` after selecting a task from the list. Full task detail includes comments plus current coordination state such as active claims and pending handoffs.
|
|
22
22
|
- **Mutate**: Use `task-create`, `task-update`, and `task-delete` for lifecycle changes.
|
|
23
23
|
- **Transitions**: Move `backlog/pending/blocked` to `in_progress` before `completed`; include validation evidence and token estimate on completion.
|
|
24
24
|
- **Automatic cleanup**: Moving a task to `completed` or `canceled` automatically releases active claims and expires pending handoffs linked to that task.
|
|
@@ -30,11 +30,13 @@ Use the tools in the same flow exposed by the dashboard: navigate with compact l
|
|
|
30
30
|
- **Scope**: Prefer repo-specific standards for local conventions; use global standards only for cross-repo rules.
|
|
31
31
|
|
|
32
32
|
## 4. Handoff & Claim Flow
|
|
33
|
-
- **Queue**: Use `handoff-list` as the handoff navigation layer. Filter by `status`, `from_agent`, or `to_agent`.
|
|
33
|
+
- **Queue**: Use `handoff-list` as the handoff navigation layer. Filter by `status`, `from_agent`, or `to_agent`. Hydrated handoff rows include `task_code`, `updated_at`, `expires_at`, and structured `context`.
|
|
34
34
|
- **Create handoff**: Use `handoff-create` only when unfinished work needs context transfer. Include `from_agent`, optional `to_agent`, optional `task_code` or `task_id`, concise `summary`, and structured `context` with `next_steps`, `blockers`, or `remaining_work`.
|
|
35
35
|
- **Close handoff**: Use `handoff-update` to move stale or consumed handoffs out of `pending`. Use `accepted` when consumed, `rejected` when declined, and `expired` when obsolete or only a completed-work summary.
|
|
36
36
|
- **No completion handoff**: Do not create pending handoffs for completed-work summaries, release notes, validation notes, or archive records. Put those on `task-update` comments or durable memory.
|
|
37
37
|
- **Claim task**: Use `task-claim` for task ownership. Do not encode claims in memory metadata.
|
|
38
|
+
- **Inspect claims**: Use `claim-list` to inspect active ownership by repo or agent before reassigning work.
|
|
39
|
+
- **Release claims**: Use `claim-release` to explicitly clear stale ownership when work is transferred or abandoned.
|
|
38
40
|
- **Linkage**: Prefer `task_code` for human workflows and `task_id` when already hydrated.
|
|
39
41
|
|
|
40
42
|
## 5. Reference Flow
|