@claudecam/server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db/index.js +68 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/queries.js +658 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.sql +259 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/routes/agents.js +68 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/correlation-audit.js +31 -0
- package/dist/routes/correlation-audit.js.map +1 -0
- package/dist/routes/events.js +81 -0
- package/dist/routes/events.js.map +1 -0
- package/dist/routes/files.js +24 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/parse-prd.js +38 -0
- package/dist/routes/parse-prd.js.map +1 -0
- package/dist/routes/projects.js +96 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/registry.js +88 -0
- package/dist/routes/registry.js.map +1 -0
- package/dist/routes/session-groups.js +182 -0
- package/dist/routes/session-groups.js.map +1 -0
- package/dist/routes/sessions.js +109 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/sprints.js +58 -0
- package/dist/routes/sprints.js.map +1 -0
- package/dist/routes/stats.js +63 -0
- package/dist/routes/stats.js.map +1 -0
- package/dist/routes/stream.js +21 -0
- package/dist/routes/stream.js.map +1 -0
- package/dist/routes/tasks.js +198 -0
- package/dist/routes/tasks.js.map +1 -0
- package/dist/services/correlation-engine.js +577 -0
- package/dist/services/correlation-engine.js.map +1 -0
- package/dist/services/event-processor.js +857 -0
- package/dist/services/event-processor.js.map +1 -0
- package/dist/services/prd-parser.js +142 -0
- package/dist/services/prd-parser.js.map +1 -0
- package/dist/services/project-manager.js +351 -0
- package/dist/services/project-manager.js.map +1 -0
- package/dist/services/project-router.js +56 -0
- package/dist/services/project-router.js.map +1 -0
- package/dist/services/session-manager.js +76 -0
- package/dist/services/session-manager.js.map +1 -0
- package/dist/services/sse-manager.js +115 -0
- package/dist/services/sse-manager.js.map +1 -0
- package/dist/services/string-similarity.js +256 -0
- package/dist/services/string-similarity.js.map +1 -0
- package/dist/services/task-completion.js +251 -0
- package/dist/services/task-completion.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-router.js","sourceRoot":"","sources":["../../src/services/project-router.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AA4B1B;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,MAAM,KAAK,GAAG,sBAAsB;SACjC,eAAe,EAAE;SACjB,GAAG,CAAC,UAAU,CAA4B,CAAC;IAC9C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,UAAU,CAAC;IAEnC,MAAM,MAAM,GAAG,sBAAsB;SAClC,qBAAqB,EAAE;SACvB,GAAG,CAAC,UAAU,CAA4B,CAAC;IAC9C,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,UAAU,CAAC;IAErC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAAiB,EACjB,gBAAwB;IAExB,MAAM,SAAS,GAAG,sBAAsB,CAAC,gBAAgB,CAAC,CAAC;IAC3D,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,QAAQ,GAAG,4BAA4B;SAC1C,YAAY,EAAE;SACd,GAAG,CAAC,SAAS,CAA2B,CAAC;IAC5C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,UAAU,CAAC;IAEzC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,4BAA4B,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;IACnE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,OAAO,GAAG,4BAA4B;SACzC,YAAY,EAAE;SACd,GAAG,CAAC,SAAS,CAA2B,CAAC;IAC5C,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB;IACrD,MAAM,QAAQ,GAAG,4BAA4B;SAC1C,YAAY,EAAE;SACd,GAAG,CAAC,SAAS,CAAiB,CAAC;IAClC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { sessionQueries, agentQueries } from '../db/queries.js';
|
|
3
|
+
function rowToSession(row) {
|
|
4
|
+
return {
|
|
5
|
+
id: row.id,
|
|
6
|
+
startedAt: row.started_at,
|
|
7
|
+
endedAt: row.ended_at ?? undefined,
|
|
8
|
+
workingDirectory: row.working_directory,
|
|
9
|
+
status: row.status,
|
|
10
|
+
agentCount: row.agent_count,
|
|
11
|
+
eventCount: row.event_count,
|
|
12
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function createSession(workingDirectory, id) {
|
|
16
|
+
const sessionId = id || randomUUID();
|
|
17
|
+
const now = new Date().toISOString();
|
|
18
|
+
sessionQueries.insert().run(sessionId, now, workingDirectory, 'active', 0, 0, null);
|
|
19
|
+
return {
|
|
20
|
+
id: sessionId,
|
|
21
|
+
startedAt: now,
|
|
22
|
+
workingDirectory,
|
|
23
|
+
status: 'active',
|
|
24
|
+
agentCount: 0,
|
|
25
|
+
eventCount: 0,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function getSession(id) {
|
|
29
|
+
const row = sessionQueries.getById().get(id);
|
|
30
|
+
return row ? rowToSession(row) : null;
|
|
31
|
+
}
|
|
32
|
+
export function listSessions(options = {}) {
|
|
33
|
+
const limit = options.limit ?? 10;
|
|
34
|
+
const offset = options.offset ?? 0;
|
|
35
|
+
let rows;
|
|
36
|
+
if (options.status) {
|
|
37
|
+
rows = sessionQueries.getByStatus().all(options.status, limit, offset);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
rows = sessionQueries.getAll().all(limit, offset);
|
|
41
|
+
}
|
|
42
|
+
return rows.map(rowToSession);
|
|
43
|
+
}
|
|
44
|
+
export function listSessionsByProject(projectId, options = {}) {
|
|
45
|
+
const limit = options.limit ?? 10;
|
|
46
|
+
const offset = options.offset ?? 0;
|
|
47
|
+
const rows = sessionQueries.getByProject().all(projectId, limit, offset);
|
|
48
|
+
return rows.map(rowToSession);
|
|
49
|
+
}
|
|
50
|
+
export function deleteSession(id) {
|
|
51
|
+
const result = sessionQueries.deleteById().run(id);
|
|
52
|
+
return result.changes > 0;
|
|
53
|
+
}
|
|
54
|
+
export function getSessionWithDetails(id) {
|
|
55
|
+
const session = getSession(id);
|
|
56
|
+
if (!session)
|
|
57
|
+
return null;
|
|
58
|
+
const agents = agentQueries.getBySession().all(id);
|
|
59
|
+
const mappedAgents = agents.map(a => ({
|
|
60
|
+
id: a['id'],
|
|
61
|
+
sessionId: a['session_id'],
|
|
62
|
+
name: a['name'],
|
|
63
|
+
type: a['type'],
|
|
64
|
+
status: a['status'],
|
|
65
|
+
firstSeenAt: a['first_seen_at'],
|
|
66
|
+
lastActivityAt: a['last_activity_at'],
|
|
67
|
+
currentTask: a['current_task'] ?? undefined,
|
|
68
|
+
toolCallCount: a['tool_call_count'],
|
|
69
|
+
errorCount: a['error_count'],
|
|
70
|
+
}));
|
|
71
|
+
return {
|
|
72
|
+
...session,
|
|
73
|
+
agents: mappedAgents,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=session-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../src/services/session-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { SSE_HEARTBEAT_INTERVAL_MS } from '@claudecam/shared';
|
|
2
|
+
class SSEManager {
|
|
3
|
+
clients = new Map();
|
|
4
|
+
heartbeatTimer = null;
|
|
5
|
+
projectSessionResolver = null;
|
|
6
|
+
/**
|
|
7
|
+
* Set the resolver that maps project IDs to session IDs.
|
|
8
|
+
* This avoids circular imports between sse-manager and queries.
|
|
9
|
+
* Placeholder for Sprint 8 Project Router implementation.
|
|
10
|
+
*/
|
|
11
|
+
setProjectSessionResolver(resolver) {
|
|
12
|
+
this.projectSessionResolver = resolver;
|
|
13
|
+
}
|
|
14
|
+
addClient(id, res, sessionFilter, projectFilter) {
|
|
15
|
+
res.writeHead(200, {
|
|
16
|
+
'Content-Type': 'text/event-stream',
|
|
17
|
+
'Cache-Control': 'no-cache',
|
|
18
|
+
'Connection': 'keep-alive',
|
|
19
|
+
'X-Accel-Buffering': 'no',
|
|
20
|
+
});
|
|
21
|
+
res.write(`event: connected\ndata: ${JSON.stringify({ clientId: id, timestamp: new Date().toISOString() })}\n\n`);
|
|
22
|
+
this.clients.set(id, { id, res, sessionFilter, projectFilter });
|
|
23
|
+
res.on('close', () => {
|
|
24
|
+
this.clients.delete(id);
|
|
25
|
+
});
|
|
26
|
+
if (!this.heartbeatTimer) {
|
|
27
|
+
this.startHeartbeat();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
broadcast(eventType, data, sessionId) {
|
|
31
|
+
const payload = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
32
|
+
for (const client of this.clients.values()) {
|
|
33
|
+
if (this.shouldDeliverToClient(client, sessionId)) {
|
|
34
|
+
try {
|
|
35
|
+
client.res.write(payload);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
this.clients.delete(client.id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
getConnectionCount() {
|
|
44
|
+
return this.clients.size;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Determine if a message should be delivered to a specific client.
|
|
48
|
+
* Supports session-level and project-level filtering.
|
|
49
|
+
*/
|
|
50
|
+
shouldDeliverToClient(client, sessionId) {
|
|
51
|
+
// No filter on client = receives everything
|
|
52
|
+
if (!client.sessionFilter && !client.projectFilter) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
// Session filter: direct match
|
|
56
|
+
if (client.sessionFilter && sessionId && client.sessionFilter === sessionId) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
// Project filter: check if sessionId belongs to the project
|
|
60
|
+
if (client.projectFilter && sessionId && this.projectSessionResolver) {
|
|
61
|
+
try {
|
|
62
|
+
const projectSessionIds = this.projectSessionResolver(client.projectFilter);
|
|
63
|
+
if (projectSessionIds.includes(sessionId)) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// If resolver fails, skip
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// If client has a filter but sessionId doesn't match, skip
|
|
72
|
+
// (unless no sessionId was provided, in which case deliver to all)
|
|
73
|
+
if (!sessionId) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
startHeartbeat() {
|
|
79
|
+
this.heartbeatTimer = setInterval(() => {
|
|
80
|
+
const payload = `event: heartbeat\ndata: ${JSON.stringify({
|
|
81
|
+
timestamp: new Date().toISOString(),
|
|
82
|
+
connections: this.clients.size,
|
|
83
|
+
})}\n\n`;
|
|
84
|
+
for (const client of this.clients.values()) {
|
|
85
|
+
try {
|
|
86
|
+
client.res.write(payload);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
this.clients.delete(client.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (this.clients.size === 0 && this.heartbeatTimer) {
|
|
93
|
+
clearInterval(this.heartbeatTimer);
|
|
94
|
+
this.heartbeatTimer = null;
|
|
95
|
+
}
|
|
96
|
+
}, SSE_HEARTBEAT_INTERVAL_MS);
|
|
97
|
+
}
|
|
98
|
+
shutdown() {
|
|
99
|
+
if (this.heartbeatTimer) {
|
|
100
|
+
clearInterval(this.heartbeatTimer);
|
|
101
|
+
this.heartbeatTimer = null;
|
|
102
|
+
}
|
|
103
|
+
for (const client of this.clients.values()) {
|
|
104
|
+
try {
|
|
105
|
+
client.res.end();
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// ignore
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.clients.clear();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export const sseManager = new SSEManager();
|
|
115
|
+
//# sourceMappingURL=sse-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-manager.js","sourceRoot":"","sources":["../../src/services/sse-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String similarity algorithms for the Correlation Engine.
|
|
3
|
+
*
|
|
4
|
+
* Pure TypeScript implementations with ZERO external dependencies.
|
|
5
|
+
* All functions are exported for testing.
|
|
6
|
+
*
|
|
7
|
+
* Algorithms:
|
|
8
|
+
* - Jaro-Winkler distance (string-level similarity)
|
|
9
|
+
* - Token-based similarity (best token-to-token Jaro-Winkler)
|
|
10
|
+
* - Combined similarity (weighted blend of full-string + token)
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Normalization helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* Decomposition map for common accented characters.
|
|
17
|
+
*
|
|
18
|
+
* Covers Portuguese (BR) diacritics and other Latin accents.
|
|
19
|
+
* We avoid `String.prototype.normalize('NFD')` + regex because some
|
|
20
|
+
* older runtimes handle combining marks inconsistently. An explicit map
|
|
21
|
+
* is deterministic and fast for the characters we care about.
|
|
22
|
+
*/
|
|
23
|
+
const ACCENT_MAP = {
|
|
24
|
+
'\u00E0': 'a', '\u00E1': 'a', '\u00E2': 'a', '\u00E3': 'a', '\u00E4': 'a', '\u00E5': 'a', // a-graves/accents
|
|
25
|
+
'\u00E8': 'e', '\u00E9': 'e', '\u00EA': 'e', '\u00EB': 'e', // e-accents
|
|
26
|
+
'\u00EC': 'i', '\u00ED': 'i', '\u00EE': 'i', '\u00EF': 'i', // i-accents
|
|
27
|
+
'\u00F2': 'o', '\u00F3': 'o', '\u00F4': 'o', '\u00F5': 'o', '\u00F6': 'o', // o-accents
|
|
28
|
+
'\u00F9': 'u', '\u00FA': 'u', '\u00FB': 'u', '\u00FC': 'u', // u-accents
|
|
29
|
+
'\u00E7': 'c', // c-cedilla
|
|
30
|
+
'\u00F1': 'n', // n-tilde
|
|
31
|
+
'\u00FD': 'y', '\u00FF': 'y', // y-accents
|
|
32
|
+
// Uppercase equivalents (after toLowerCase these shouldn't appear, but just in case)
|
|
33
|
+
'\u00C0': 'a', '\u00C1': 'a', '\u00C2': 'a', '\u00C3': 'a', '\u00C4': 'a', '\u00C5': 'a',
|
|
34
|
+
'\u00C8': 'e', '\u00C9': 'e', '\u00CA': 'e', '\u00CB': 'e',
|
|
35
|
+
'\u00CC': 'i', '\u00CD': 'i', '\u00CE': 'i', '\u00CF': 'i',
|
|
36
|
+
'\u00D2': 'o', '\u00D3': 'o', '\u00D4': 'o', '\u00D5': 'o', '\u00D6': 'o',
|
|
37
|
+
'\u00D9': 'u', '\u00DA': 'u', '\u00DB': 'u', '\u00DC': 'u',
|
|
38
|
+
'\u00C7': 'c',
|
|
39
|
+
'\u00D1': 'n',
|
|
40
|
+
'\u00DD': 'y',
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Normalise a string for comparison:
|
|
44
|
+
* - lowercase
|
|
45
|
+
* - replace accented characters with ASCII equivalents
|
|
46
|
+
* - trim leading/trailing whitespace
|
|
47
|
+
* - collapse multiple whitespace into a single space
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeString(s) {
|
|
50
|
+
let result = s.toLowerCase();
|
|
51
|
+
// Replace accented chars via map (fast path for common Latin chars)
|
|
52
|
+
let normalized = '';
|
|
53
|
+
for (let i = 0; i < result.length; i++) {
|
|
54
|
+
const ch = result[i];
|
|
55
|
+
const replacement = ACCENT_MAP[ch];
|
|
56
|
+
normalized += replacement !== undefined ? replacement : ch;
|
|
57
|
+
}
|
|
58
|
+
result = normalized;
|
|
59
|
+
// Collapse whitespace and trim
|
|
60
|
+
result = result.replace(/\s+/g, ' ').trim();
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Tokenize a string into words.
|
|
65
|
+
*
|
|
66
|
+
* Splits on whitespace, underscores, hyphens, dots, slashes, colons, and
|
|
67
|
+
* other common separators.
|
|
68
|
+
*
|
|
69
|
+
* IMPORTANT: NO minimum length filter. Short tokens like "UI", "API", "DB",
|
|
70
|
+
* "SSE", "CI" are critical for matching technical task titles.
|
|
71
|
+
*/
|
|
72
|
+
export function tokenize(s) {
|
|
73
|
+
const normalized = normalizeString(s);
|
|
74
|
+
if (normalized.length === 0)
|
|
75
|
+
return [];
|
|
76
|
+
return normalized
|
|
77
|
+
.split(/[\s_\-/\\.:,;|()[\]{}]+/)
|
|
78
|
+
.filter(token => token.length > 0);
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Jaro-Winkler similarity
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
/**
|
|
84
|
+
* Compute the Jaro similarity between two strings.
|
|
85
|
+
*
|
|
86
|
+
* The Jaro similarity is defined as:
|
|
87
|
+
* jaro = (1/3) * (m/|s1| + m/|s2| + (m - t)/m)
|
|
88
|
+
*
|
|
89
|
+
* where:
|
|
90
|
+
* m = number of matching characters
|
|
91
|
+
* t = number of transpositions / 2
|
|
92
|
+
* Characters are considered matching if they are the same and within
|
|
93
|
+
* floor(max(|s1|, |s2|) / 2) - 1 positions of each other.
|
|
94
|
+
*
|
|
95
|
+
* Returns a value between 0 (no similarity) and 1 (identical).
|
|
96
|
+
*/
|
|
97
|
+
function jaroSimilarity(s1, s2) {
|
|
98
|
+
if (s1.length === 0 && s2.length === 0)
|
|
99
|
+
return 1.0;
|
|
100
|
+
if (s1.length === 0 || s2.length === 0)
|
|
101
|
+
return 0.0;
|
|
102
|
+
if (s1 === s2)
|
|
103
|
+
return 1.0;
|
|
104
|
+
const maxLen = Math.max(s1.length, s2.length);
|
|
105
|
+
// The match window: characters must be within this distance
|
|
106
|
+
const matchWindow = Math.max(Math.floor(maxLen / 2) - 1, 0);
|
|
107
|
+
const s1Matches = new Array(s1.length).fill(false);
|
|
108
|
+
const s2Matches = new Array(s2.length).fill(false);
|
|
109
|
+
let matches = 0;
|
|
110
|
+
let transpositions = 0;
|
|
111
|
+
// Find matching characters
|
|
112
|
+
for (let i = 0; i < s1.length; i++) {
|
|
113
|
+
const start = Math.max(0, i - matchWindow);
|
|
114
|
+
const end = Math.min(i + matchWindow + 1, s2.length);
|
|
115
|
+
for (let j = start; j < end; j++) {
|
|
116
|
+
if (s2Matches[j] || s1[i] !== s2[j])
|
|
117
|
+
continue;
|
|
118
|
+
s1Matches[i] = true;
|
|
119
|
+
s2Matches[j] = true;
|
|
120
|
+
matches++;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (matches === 0)
|
|
125
|
+
return 0.0;
|
|
126
|
+
// Count transpositions
|
|
127
|
+
let k = 0;
|
|
128
|
+
for (let i = 0; i < s1.length; i++) {
|
|
129
|
+
if (!s1Matches[i])
|
|
130
|
+
continue;
|
|
131
|
+
while (!s2Matches[k])
|
|
132
|
+
k++;
|
|
133
|
+
if (s1[i] !== s2[k])
|
|
134
|
+
transpositions++;
|
|
135
|
+
k++;
|
|
136
|
+
}
|
|
137
|
+
const jaro = (matches / s1.length +
|
|
138
|
+
matches / s2.length +
|
|
139
|
+
(matches - transpositions / 2) / matches) /
|
|
140
|
+
3;
|
|
141
|
+
return jaro;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Compute the Jaro-Winkler similarity between two strings.
|
|
145
|
+
*
|
|
146
|
+
* Jaro-Winkler adds a prefix bonus to the base Jaro score. Strings that
|
|
147
|
+
* share a common prefix of up to 4 characters receive a boost, which is
|
|
148
|
+
* especially useful for matching task titles that start with the same verb
|
|
149
|
+
* (e.g. "Implement auth" vs "Implement authentication").
|
|
150
|
+
*
|
|
151
|
+
* The formula is:
|
|
152
|
+
* jaroWinkler = jaro + (prefixLen * scalingFactor * (1 - jaro))
|
|
153
|
+
*
|
|
154
|
+
* where scalingFactor = 0.1 (standard Winkler constant) and
|
|
155
|
+
* prefixLen is capped at 4.
|
|
156
|
+
*
|
|
157
|
+
* @returns A value between 0 (no similarity) and 1 (identical).
|
|
158
|
+
*/
|
|
159
|
+
export function jaroWinkler(a, b) {
|
|
160
|
+
const s1 = normalizeString(a);
|
|
161
|
+
const s2 = normalizeString(b);
|
|
162
|
+
const jaro = jaroSimilarity(s1, s2);
|
|
163
|
+
// Calculate common prefix length (up to 4 characters)
|
|
164
|
+
const maxPrefix = Math.min(4, s1.length, s2.length);
|
|
165
|
+
let prefixLen = 0;
|
|
166
|
+
for (let i = 0; i < maxPrefix; i++) {
|
|
167
|
+
if (s1[i] === s2[i]) {
|
|
168
|
+
prefixLen++;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Winkler modification: boost score for common prefix
|
|
175
|
+
const scalingFactor = 0.1;
|
|
176
|
+
const jaroWinklerScore = jaro + prefixLen * scalingFactor * (1 - jaro);
|
|
177
|
+
return Math.min(jaroWinklerScore, 1.0);
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Token-based similarity
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
/**
|
|
183
|
+
* Compute the best token-to-token similarity between two strings.
|
|
184
|
+
*
|
|
185
|
+
* Strategy:
|
|
186
|
+
* 1. Tokenize both strings.
|
|
187
|
+
* 2. For each token in `a`, find the best Jaro-Winkler match in `b`.
|
|
188
|
+
* 3. Compute coverage-weighted score: tokens from the SHORTER set drive
|
|
189
|
+
* the score (so "auth module" vs "Implement auth module for the app"
|
|
190
|
+
* scores high because both "auth" and "module" match well).
|
|
191
|
+
*
|
|
192
|
+
* This handles:
|
|
193
|
+
* - Word reordering: "auth module" vs "module auth" scores ~1.0
|
|
194
|
+
* - Partial overlap: "auth module" vs "auth module implementation" scores high
|
|
195
|
+
* - Morphological variants: "implement" vs "implementation" via Jaro-Winkler
|
|
196
|
+
*
|
|
197
|
+
* @returns A value between 0 (no token overlap) and 1 (perfect match).
|
|
198
|
+
*/
|
|
199
|
+
export function tokenSimilarity(a, b) {
|
|
200
|
+
const tokensA = tokenize(a);
|
|
201
|
+
const tokensB = tokenize(b);
|
|
202
|
+
if (tokensA.length === 0 && tokensB.length === 0)
|
|
203
|
+
return 1.0;
|
|
204
|
+
if (tokensA.length === 0 || tokensB.length === 0)
|
|
205
|
+
return 0.0;
|
|
206
|
+
// Use the shorter token set as the "query" to maximize coverage
|
|
207
|
+
const [query, corpus] = tokensA.length <= tokensB.length
|
|
208
|
+
? [tokensA, tokensB]
|
|
209
|
+
: [tokensB, tokensA];
|
|
210
|
+
let totalScore = 0;
|
|
211
|
+
for (const qToken of query) {
|
|
212
|
+
let bestTokenScore = 0;
|
|
213
|
+
for (const cToken of corpus) {
|
|
214
|
+
// Use raw jaroSimilarity on already-normalized tokens (tokenize calls normalizeString)
|
|
215
|
+
const score = jaroSimilarity(qToken, cToken);
|
|
216
|
+
if (score > bestTokenScore) {
|
|
217
|
+
bestTokenScore = score;
|
|
218
|
+
}
|
|
219
|
+
// Early exit if we found a perfect match
|
|
220
|
+
if (bestTokenScore >= 1.0)
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
totalScore += bestTokenScore;
|
|
224
|
+
}
|
|
225
|
+
// Coverage factor: penalize if the corpus is much larger than the query.
|
|
226
|
+
// A query of 2 tokens matching 2 out of 10 corpus tokens should score
|
|
227
|
+
// lower than matching 2 out of 3.
|
|
228
|
+
const rawScore = totalScore / query.length;
|
|
229
|
+
const coverageRatio = query.length / corpus.length;
|
|
230
|
+
// Blend: 80% raw match quality + 20% coverage
|
|
231
|
+
// This ensures "auth" vs "implement auth module for the app" (1/5 coverage)
|
|
232
|
+
// scores lower than "auth module" vs "auth module impl" (2/3 coverage)
|
|
233
|
+
return rawScore * (0.8 + 0.2 * coverageRatio);
|
|
234
|
+
}
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Combined similarity
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
/**
|
|
239
|
+
* Compute the combined similarity score between two strings.
|
|
240
|
+
*
|
|
241
|
+
* Blends full-string Jaro-Winkler with token-based similarity:
|
|
242
|
+
* combined = 0.6 * jaroWinkler(a, b) + 0.4 * tokenSimilarity(a, b)
|
|
243
|
+
*
|
|
244
|
+
* The full-string component captures overall character-level similarity
|
|
245
|
+
* (good for typos, accents, minor variations). The token component
|
|
246
|
+
* captures semantic overlap at the word level (good for reordering,
|
|
247
|
+
* extra words, abbreviations).
|
|
248
|
+
*
|
|
249
|
+
* @returns A value between 0 and 1.
|
|
250
|
+
*/
|
|
251
|
+
export function combinedSimilarity(a, b) {
|
|
252
|
+
const fullStringScore = jaroWinkler(a, b);
|
|
253
|
+
const tokenScore = tokenSimilarity(a, b);
|
|
254
|
+
return 0.6 * fullStringScore + 0.4 * tokenScore;
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=string-similarity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-similarity.js","sourceRoot":"","sources":["../../src/services/string-similarity.ts"],"names":[],"mappings":"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"}
|