obsidian-anchor 0.2.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/LICENSE +21 -0
- package/README.md +209 -0
- package/dist/chunk/chunker.js +169 -0
- package/dist/chunk/chunker.js.map +1 -0
- package/dist/chunk/markdown.js +49 -0
- package/dist/chunk/markdown.js.map +1 -0
- package/dist/chunk/types.js +5 -0
- package/dist/chunk/types.js.map +1 -0
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -0
- package/dist/container.js +84 -0
- package/dist/container.js.map +1 -0
- package/dist/edit/safeEdit.js +130 -0
- package/dist/edit/safeEdit.js.map +1 -0
- package/dist/edit/snapshots.js +31 -0
- package/dist/edit/snapshots.js.map +1 -0
- package/dist/embeddings/local.js +61 -0
- package/dist/embeddings/local.js.map +1 -0
- package/dist/embeddings/openai.js +63 -0
- package/dist/embeddings/openai.js.map +1 -0
- package/dist/embeddings/provider.js +5 -0
- package/dist/embeddings/provider.js.map +1 -0
- package/dist/index/indexer.js +108 -0
- package/dist/index/indexer.js.map +1 -0
- package/dist/index/walk.js +28 -0
- package/dist/index/walk.js.map +1 -0
- package/dist/index/watcher.js +115 -0
- package/dist/index/watcher.js.map +1 -0
- package/dist/index.js +192 -0
- package/dist/index.js.map +1 -0
- package/dist/server.js +61 -0
- package/dist/server.js.map +1 -0
- package/dist/store/chunkStore.js +82 -0
- package/dist/store/chunkStore.js.map +1 -0
- package/dist/store/db.js +59 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/schema.js +67 -0
- package/dist/store/schema.js.map +1 -0
- package/dist/store/search.js +33 -0
- package/dist/store/search.js.map +1 -0
- package/dist/store/types.js +3 -0
- package/dist/store/types.js.map +1 -0
- package/dist/store/vectorStore.js +77 -0
- package/dist/store/vectorStore.js.map +1 -0
- package/dist/tools/cite.js +51 -0
- package/dist/tools/cite.js.map +1 -0
- package/dist/tools/restoreNote.js +49 -0
- package/dist/tools/restoreNote.js.map +1 -0
- package/dist/tools/safeEdit.js +65 -0
- package/dist/tools/safeEdit.js.map +1 -0
- package/dist/tools/searchNotes.js +47 -0
- package/dist/tools/searchNotes.js.map +1 -0
- package/dist/tools/verifyGrounding.js +66 -0
- package/dist/tools/verifyGrounding.js.map +1 -0
- package/dist/util/hash.js +6 -0
- package/dist/util/hash.js.map +1 -0
- package/dist/util/logger.js +41 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/timeout.js +19 -0
- package/dist/util/timeout.js.map +1 -0
- package/dist/verify/anthropic.js +126 -0
- package/dist/verify/anthropic.js.map +1 -0
- package/dist/verify/decompose.js +100 -0
- package/dist/verify/decompose.js.map +1 -0
- package/dist/verify/localVerifier.js +89 -0
- package/dist/verify/localVerifier.js.map +1 -0
- package/dist/verify/pipeline.js +165 -0
- package/dist/verify/pipeline.js.map +1 -0
- package/dist/verify/score.js +64 -0
- package/dist/verify/score.js.map +1 -0
- package/dist/verify/types.js +3 -0
- package/dist/verify/types.js.map +1 -0
- package/dist/verify/verifier.js +2 -0
- package/dist/verify/verifier.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const CITATION_SHAPE = {
|
|
3
|
+
citation: z.string(),
|
|
4
|
+
path: z.string(),
|
|
5
|
+
anchor: z.string(),
|
|
6
|
+
quote: z.string(),
|
|
7
|
+
score: z.number(),
|
|
8
|
+
};
|
|
9
|
+
export function registerCite(server, ctx) {
|
|
10
|
+
server.registerTool("cite", {
|
|
11
|
+
title: "Cite",
|
|
12
|
+
description: "Attach forced citations to a statement about the vault. Returns the supporting note " +
|
|
13
|
+
"passages when the notes back the statement, or an honest 'no supporting notes found' " +
|
|
14
|
+
"otherwise. Use this to ground any claim you make about the user's notes; if it is not " +
|
|
15
|
+
"cited, do not state it as fact.",
|
|
16
|
+
inputSchema: { claim: z.string().min(1).max(8000) },
|
|
17
|
+
outputSchema: {
|
|
18
|
+
cited: z.boolean(),
|
|
19
|
+
statement: z.string(),
|
|
20
|
+
citations: z.array(z.object(CITATION_SHAPE)),
|
|
21
|
+
note: z.string(),
|
|
22
|
+
},
|
|
23
|
+
}, async ({ claim }) => {
|
|
24
|
+
if (ctx.indexing)
|
|
25
|
+
await ctx.indexing;
|
|
26
|
+
const result = await ctx.grounding.verify(claim);
|
|
27
|
+
const citations = result.perClaim
|
|
28
|
+
.filter((perClaim) => perClaim.verdict === "supported")
|
|
29
|
+
.flatMap((perClaim) => perClaim.evidence)
|
|
30
|
+
.map((evidence) => ({
|
|
31
|
+
citation: `${evidence.path}${evidence.anchor}`,
|
|
32
|
+
path: evidence.path,
|
|
33
|
+
anchor: evidence.anchor,
|
|
34
|
+
quote: evidence.quote,
|
|
35
|
+
score: evidence.score,
|
|
36
|
+
}));
|
|
37
|
+
const contradicted = result.perClaim.some((perClaim) => perClaim.verdict === "contradicted");
|
|
38
|
+
const cited = citations.length > 0 && result.verdict !== "refused";
|
|
39
|
+
const note = cited
|
|
40
|
+
? `Supported by ${citations.length} note passage(s).`
|
|
41
|
+
: contradicted
|
|
42
|
+
? "Your notes contradict this — do not state it as fact."
|
|
43
|
+
: "No supporting notes found — do not state this as fact.";
|
|
44
|
+
const payload = { cited, statement: claim, citations, note };
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
47
|
+
structuredContent: payload,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cite.js","sourceRoot":"","sources":["../../src/tools/cite.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,cAAc,GAAG;IACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;CAClB,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,MAAiB,EAAE,GAAe;IAC7D,MAAM,CAAC,YAAY,CACjB,MAAM,EACN;QACE,KAAK,EAAE,MAAM;QACb,WAAW,EACT,sFAAsF;YACtF,uFAAuF;YACvF,wFAAwF;YACxF,iCAAiC;QACnC,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QACnD,YAAY,EAAE;YACZ,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE;YAClB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;YACrB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;YAC5C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;SACjB;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAClB,IAAI,GAAG,CAAC,QAAQ;YAAE,MAAM,GAAG,CAAC,QAAQ,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ;aAC9B,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,KAAK,WAAW,CAAC;aACtD,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;aACxC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAClB,QAAQ,EAAE,GAAG,QAAQ,CAAC,IAAI,GAAG,QAAQ,CAAC,MAAM,EAAE;YAC9C,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,KAAK,EAAE,QAAQ,CAAC,KAAK;SACtB,CAAC,CAAC,CAAC;QACN,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,KAAK,cAAc,CAAC,CAAC;QAC7F,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC;QACnE,MAAM,IAAI,GAAG,KAAK;YAChB,CAAC,CAAC,gBAAgB,SAAS,CAAC,MAAM,mBAAmB;YACrD,CAAC,CAAC,YAAY;gBACZ,CAAC,CAAC,uDAAuD;gBACzD,CAAC,CAAC,wDAAwD,CAAC;QAE/D,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QAC7D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnE,iBAAiB,EAAE,OAAO;SAC3B,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerRestoreNote(server, ctx) {
|
|
3
|
+
server.registerTool("restore_note", {
|
|
4
|
+
title: "Restore note",
|
|
5
|
+
description: "Roll a note back to a rollback snapshot saved by safe_edit (pass the snapshot path from " +
|
|
6
|
+
"a previous safe_edit result). The current content is snapshotted first, so the restore is " +
|
|
7
|
+
"itself undoable, and the note is re-indexed.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
path: z.string().min(1).max(1024),
|
|
10
|
+
snapshot: z.string().min(1).max(1024),
|
|
11
|
+
},
|
|
12
|
+
outputSchema: {
|
|
13
|
+
path: z.string(),
|
|
14
|
+
restoredFrom: z.string(),
|
|
15
|
+
preRestoreSnapshot: z.string().nullable(),
|
|
16
|
+
reindexedChunks: z.number().nullable(),
|
|
17
|
+
message: z.string(),
|
|
18
|
+
},
|
|
19
|
+
}, async ({ path, snapshot }) => {
|
|
20
|
+
if (ctx.indexing)
|
|
21
|
+
await ctx.indexing;
|
|
22
|
+
try {
|
|
23
|
+
const result = await ctx.safeEdit.restore(path, snapshot);
|
|
24
|
+
const payload = {
|
|
25
|
+
...result,
|
|
26
|
+
message: `Restored ${result.path} from ${result.restoredFrom}.`,
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
30
|
+
structuredContent: payload,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: `restore_note error: ${message}` }],
|
|
37
|
+
structuredContent: {
|
|
38
|
+
path,
|
|
39
|
+
restoredFrom: snapshot,
|
|
40
|
+
preRestoreSnapshot: null,
|
|
41
|
+
reindexedChunks: null,
|
|
42
|
+
message: `Error: ${message}`,
|
|
43
|
+
},
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=restoreNote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"restoreNote.js","sourceRoot":"","sources":["../../src/tools/restoreNote.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,GAAe;IACpE,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,0FAA0F;YAC1F,4FAA4F;YAC5F,8CAA8C;QAChD,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;YACjC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;SACtC;QACD,YAAY,EAAE;YACZ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;YACxB,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACzC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;SACpB;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC3B,IAAI,GAAG,CAAC,QAAQ;YAAE,MAAM,GAAG,CAAC,QAAQ,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC1D,MAAM,OAAO,GAAG;gBACd,GAAG,MAAM;gBACT,OAAO,EAAE,YAAY,MAAM,CAAC,IAAI,SAAS,MAAM,CAAC,YAAY,GAAG;aAChE,CAAC;YACF,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;gBACnE,iBAAiB,EAAE,OAAO;aAC3B,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,OAAO,EAAE,EAAE,CAAC;gBACnE,iBAAiB,EAAE;oBACjB,IAAI;oBACJ,YAAY,EAAE,QAAQ;oBACtB,kBAAkB,EAAE,IAAI;oBACxB,eAAe,EAAE,IAAI;oBACrB,OAAO,EAAE,UAAU,OAAO,EAAE;iBAC7B;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerSafeEdit(server, ctx) {
|
|
3
|
+
server.registerTool("safe_edit", {
|
|
4
|
+
title: "Safe edit",
|
|
5
|
+
description: "Edit a note by replacing an exact snippet. Called WITHOUT `confirm`, it returns a dry-run " +
|
|
6
|
+
"diff and a confirmation token without touching the file. Call it again with the same arguments " +
|
|
7
|
+
"plus `confirm` set to that token to apply the change — a rollback snapshot is saved and the note " +
|
|
8
|
+
"is re-indexed. The token also rejects the edit if the note changed since the preview.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
path: z.string().min(1).max(1024),
|
|
11
|
+
oldText: z.string().min(1).max(50_000),
|
|
12
|
+
newText: z.string().max(50_000),
|
|
13
|
+
confirm: z.string().optional(),
|
|
14
|
+
},
|
|
15
|
+
outputSchema: {
|
|
16
|
+
mode: z.enum(["dry-run", "applied"]),
|
|
17
|
+
path: z.string(),
|
|
18
|
+
message: z.string(),
|
|
19
|
+
diff: z.string().optional(),
|
|
20
|
+
token: z.string().optional(),
|
|
21
|
+
snapshot: z.string().optional(),
|
|
22
|
+
reindexedChunks: z.number().nullable().optional(),
|
|
23
|
+
},
|
|
24
|
+
}, async ({ path, oldText, newText, confirm }) => {
|
|
25
|
+
if (ctx.indexing)
|
|
26
|
+
await ctx.indexing;
|
|
27
|
+
try {
|
|
28
|
+
const result = await ctx.safeEdit.edit(path, oldText, newText, confirm);
|
|
29
|
+
if (result.mode === "dry-run") {
|
|
30
|
+
const payload = {
|
|
31
|
+
mode: "dry-run",
|
|
32
|
+
path: result.path,
|
|
33
|
+
diff: result.diff,
|
|
34
|
+
token: result.token,
|
|
35
|
+
message: "Dry run — review the diff. To apply, call safe_edit again with the same arguments plus " +
|
|
36
|
+
`confirm: "${result.token}".`,
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
40
|
+
structuredContent: payload,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const payload = {
|
|
44
|
+
mode: "applied",
|
|
45
|
+
path: result.path,
|
|
46
|
+
snapshot: result.snapshot,
|
|
47
|
+
reindexedChunks: result.reindexedChunks,
|
|
48
|
+
message: `Applied. Rollback snapshot saved at ${result.snapshot}.`,
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
52
|
+
structuredContent: payload,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: `safe_edit error: ${message}` }],
|
|
59
|
+
structuredContent: { mode: "dry-run", path, message: `Error: ${message}` },
|
|
60
|
+
isError: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=safeEdit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeEdit.js","sourceRoot":"","sources":["../../src/tools/safeEdit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const RESULT_SHAPE = {
|
|
3
|
+
/** Ready-to-use Obsidian citation, e.g. "Projects/Auth.md#^k93a". */
|
|
4
|
+
citation: z.string(),
|
|
5
|
+
path: z.string(),
|
|
6
|
+
anchor: z.string(),
|
|
7
|
+
heading: z.string().nullable(),
|
|
8
|
+
blockId: z.string().nullable(),
|
|
9
|
+
/** Relevance score in 0..1 (cosine similarity). */
|
|
10
|
+
score: z.number(),
|
|
11
|
+
/** Verbatim chunk text (the citable evidence span). */
|
|
12
|
+
quote: z.string(),
|
|
13
|
+
};
|
|
14
|
+
export function registerSearchNotes(server, ctx) {
|
|
15
|
+
server.registerTool("search_notes", {
|
|
16
|
+
title: "Search notes",
|
|
17
|
+
description: "Semantic search over the Obsidian vault. Returns matching chunks, each with a precise " +
|
|
18
|
+
"Obsidian citation (note path + heading/block anchor) and a relevance score. Use this to " +
|
|
19
|
+
"find evidence, then verify_grounding before asserting anything as fact.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
query: z.string().min(1).max(1000),
|
|
22
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
23
|
+
},
|
|
24
|
+
outputSchema: { results: z.array(z.object(RESULT_SHAPE)) },
|
|
25
|
+
}, async ({ query, limit }) => {
|
|
26
|
+
if (ctx.indexing)
|
|
27
|
+
await ctx.indexing;
|
|
28
|
+
const hits = await ctx.search.search(query, limit ?? ctx.config.knn);
|
|
29
|
+
const payload = { results: hits.map(toResult) };
|
|
30
|
+
return {
|
|
31
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
32
|
+
structuredContent: payload,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function toResult(chunk) {
|
|
37
|
+
return {
|
|
38
|
+
citation: `${chunk.notePath}${chunk.anchor}`,
|
|
39
|
+
path: chunk.notePath,
|
|
40
|
+
anchor: chunk.anchor,
|
|
41
|
+
heading: chunk.headingPath,
|
|
42
|
+
blockId: chunk.blockId,
|
|
43
|
+
score: Math.round(chunk.score * 1000) / 1000,
|
|
44
|
+
quote: chunk.text.replace(/\r/g, ""),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=searchNotes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"searchNotes.js","sourceRoot":"","sources":["../../src/tools/searchNotes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,MAAM,YAAY,GAAG;IACnB,qEAAqE;IACrE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,mDAAmD;IACnD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,uDAAuD;IACvD,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;CAClB,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,GAAe;IACpE,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,wFAAwF;YACxF,0FAA0F;YAC1F,yEAAyE;QAC3E,WAAW,EAAE;YACX,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;YAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;SAClD;QACD,YAAY,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE;KAC3D,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE;QACzB,IAAI,GAAG,CAAC,QAAQ;YAAE,MAAM,GAAG,CAAC,QAAQ,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrE,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YACnE,iBAAiB,EAAE,OAAO;SAC3B,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAkB;IAClC,OAAO;QACL,QAAQ,EAAE,GAAG,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,EAAE;QAC5C,IAAI,EAAE,KAAK,CAAC,QAAQ;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,WAAW;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI;QAC5C,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;KACrC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const EVIDENCE_SHAPE = {
|
|
3
|
+
path: z.string(),
|
|
4
|
+
anchor: z.string(),
|
|
5
|
+
blockId: z.string().nullable(),
|
|
6
|
+
heading: z.string().nullable(),
|
|
7
|
+
quote: z.string(),
|
|
8
|
+
score: z.number(),
|
|
9
|
+
};
|
|
10
|
+
const PER_CLAIM_SHAPE = {
|
|
11
|
+
claim: z.string(),
|
|
12
|
+
verdict: z.enum(["supported", "contradicted", "neutral"]),
|
|
13
|
+
score: z.number(),
|
|
14
|
+
evidence: z.array(z.object(EVIDENCE_SHAPE)),
|
|
15
|
+
};
|
|
16
|
+
export function registerVerifyGrounding(server, ctx) {
|
|
17
|
+
server.registerTool("verify_grounding", {
|
|
18
|
+
title: "Verify grounding",
|
|
19
|
+
description: "Check whether a claim (or short answer) is actually supported by the user's notes. " +
|
|
20
|
+
"Returns a grounding score 0-1 and a per-claim verdict (supported / contradicted / " +
|
|
21
|
+
"neutral) with Obsidian-cited evidence, and lists claims the notes do not support. " +
|
|
22
|
+
"Call this BEFORE asserting anything about the vault as fact; treat a refusal as " +
|
|
23
|
+
"'this is not in the notes'. Optionally pass `sources` (note passages, e.g. from " +
|
|
24
|
+
"search_notes) to verify against directly instead of retrieving.",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
claim: z.string().min(1).max(8000),
|
|
27
|
+
sources: z
|
|
28
|
+
.array(z.object({ path: z.string(), anchor: z.string(), text: z.string().max(50_000) }))
|
|
29
|
+
.max(50)
|
|
30
|
+
.optional(),
|
|
31
|
+
},
|
|
32
|
+
outputSchema: {
|
|
33
|
+
grounded: z.boolean(),
|
|
34
|
+
score: z.number(),
|
|
35
|
+
verdict: z.enum(["grounded", "flagged", "refused"]),
|
|
36
|
+
summary: z.string(),
|
|
37
|
+
perClaim: z.array(z.object(PER_CLAIM_SHAPE)),
|
|
38
|
+
refusedClaims: z.array(z.string()),
|
|
39
|
+
},
|
|
40
|
+
}, async ({ claim, sources }) => {
|
|
41
|
+
if (ctx.indexing)
|
|
42
|
+
await ctx.indexing;
|
|
43
|
+
const provided = sources?.map((source, index) => toScoredChunk(source, index));
|
|
44
|
+
const result = await ctx.grounding.verify(claim, provided);
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
47
|
+
structuredContent: { ...result },
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function toScoredChunk(source, index) {
|
|
52
|
+
return {
|
|
53
|
+
chunkId: -1 - index,
|
|
54
|
+
notePath: source.path,
|
|
55
|
+
headingPath: null,
|
|
56
|
+
blockId: null,
|
|
57
|
+
anchor: source.anchor,
|
|
58
|
+
ordinal: index,
|
|
59
|
+
charStart: 0,
|
|
60
|
+
charEnd: source.text.length,
|
|
61
|
+
text: source.text,
|
|
62
|
+
distance: 0,
|
|
63
|
+
score: 1,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=verifyGrounding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifyGrounding.js","sourceRoot":"","sources":["../../src/tools/verifyGrounding.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/util/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,oEAAoE;AACpE,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Structured stderr logger.
|
|
2
|
+
//
|
|
3
|
+
// stdout is reserved exclusively for the MCP stdio transport (JSON-RPC). Any
|
|
4
|
+
// stray write to stdout corrupts the protocol stream, so every diagnostic
|
|
5
|
+
// message Anchor emits MUST go to stderr. This module is the only sanctioned
|
|
6
|
+
// way to log; `console.*` is banned in src/ via ESLint.
|
|
7
|
+
const LEVEL_ORDER = {
|
|
8
|
+
debug: 10,
|
|
9
|
+
info: 20,
|
|
10
|
+
warn: 30,
|
|
11
|
+
error: 40,
|
|
12
|
+
};
|
|
13
|
+
function resolveThreshold() {
|
|
14
|
+
const raw = (process.env.ANCHOR_LOG_LEVEL ?? "info").toLowerCase();
|
|
15
|
+
return LEVEL_ORDER[raw] ?? LEVEL_ORDER.info;
|
|
16
|
+
}
|
|
17
|
+
const threshold = resolveThreshold();
|
|
18
|
+
function safeJson(value) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return String(value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function write(level, message, fields) {
|
|
27
|
+
if (LEVEL_ORDER[level] < threshold)
|
|
28
|
+
return;
|
|
29
|
+
let line = `[anchor] ${level.toUpperCase()} ${message}`;
|
|
30
|
+
if (fields && Object.keys(fields).length > 0) {
|
|
31
|
+
line += ` ${safeJson(fields)}`;
|
|
32
|
+
}
|
|
33
|
+
process.stderr.write(`${line}\n`);
|
|
34
|
+
}
|
|
35
|
+
export const logger = {
|
|
36
|
+
debug: (message, fields) => write("debug", message, fields),
|
|
37
|
+
info: (message, fields) => write("info", message, fields),
|
|
38
|
+
warn: (message, fields) => write("warn", message, fields),
|
|
39
|
+
error: (message, fields) => write("error", message, fields),
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,6EAA6E;AAC7E,wDAAwD;AAIxD,MAAM,WAAW,GAA6B;IAC5C,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;CACV,CAAC;AAEF,SAAS,gBAAgB;IACvB,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACnE,OAAO,WAAW,CAAC,GAAe,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC;AAC1D,CAAC;AAED,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;AAErC,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,KAAe,EAAE,OAAe,EAAE,MAAe;IAC9D,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,SAAS;QAAE,OAAO;IAC3C,IAAI,IAAI,GAAG,YAAY,KAAK,CAAC,WAAW,EAAE,IAAI,OAAO,EAAE,CAAC;IACxD,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7C,IAAI,IAAI,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IACjC,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,OAAe,EAAE,MAAe,EAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC;IAClF,IAAI,EAAE,CAAC,OAAe,EAAE,MAAe,EAAQ,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;IAChF,IAAI,EAAE,CAAC,OAAe,EAAE,MAAe,EAAQ,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;IAChF,KAAK,EAAE,CAAC,OAAe,EAAE,MAAe,EAAQ,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC;CACnF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rejects if `promise` does not settle within `ms`. The underlying work is not
|
|
3
|
+
* cancelled (JS has no general cancellation), but the caller is unblocked.
|
|
4
|
+
*/
|
|
5
|
+
export function withTimeout(promise, ms, label) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const timer = setTimeout(() => {
|
|
8
|
+
reject(new Error(`${label} timed out after ${String(ms)}ms`));
|
|
9
|
+
}, ms);
|
|
10
|
+
promise.then((value) => {
|
|
11
|
+
clearTimeout(timer);
|
|
12
|
+
resolve(value);
|
|
13
|
+
}, (error) => {
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=timeout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeout.js","sourceRoot":"","sources":["../../src/util/timeout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAI,OAAmB,EAAE,EAAU,EAAE,KAAa;IAC3E,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAChE,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,OAAO,CAAC,IAAI,CACV,CAAC,KAAK,EAAE,EAAE;YACR,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;YACjB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACpE,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { logger } from "../util/logger.js";
|
|
3
|
+
// Haiku is a deliberate choice (locked in the plan): the verifier runs a narrow,
|
|
4
|
+
// high-volume, closed entailment classification where Haiku's speed/cost win —
|
|
5
|
+
// not an arbitrary downgrade of the default model.
|
|
6
|
+
const MODEL = "claude-haiku-4-5";
|
|
7
|
+
const MAX_TOKENS = 1024;
|
|
8
|
+
const SYSTEM_PROMPT = [
|
|
9
|
+
"You are a strict grounding verifier.",
|
|
10
|
+
"Given a CLAIM and a list of numbered PASSAGES from the user's notes, decide for EACH passage whether it",
|
|
11
|
+
"SUPPORTS (entails) the claim, CONTRADICTS (refutes) it, or is NEUTRAL (unrelated or insufficient to decide).",
|
|
12
|
+
"Judge ONLY from the passage text — never use outside knowledge.",
|
|
13
|
+
"Return one judgement per passage with a confidence in [0,1].",
|
|
14
|
+
].join(" ");
|
|
15
|
+
// Raw JSON Schema (avoids coupling to any zod-helper version).
|
|
16
|
+
const OUTPUT_FORMAT = {
|
|
17
|
+
type: "json_schema",
|
|
18
|
+
schema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
properties: {
|
|
22
|
+
judgements: {
|
|
23
|
+
type: "array",
|
|
24
|
+
items: {
|
|
25
|
+
type: "object",
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
properties: {
|
|
28
|
+
passageIndex: { type: "integer" },
|
|
29
|
+
label: { type: "string", enum: ["supported", "contradicted", "neutral"] },
|
|
30
|
+
confidence: { type: "number" },
|
|
31
|
+
},
|
|
32
|
+
required: ["passageIndex", "label", "confidence"],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
required: ["judgements"],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Anthropic-backed grounding verifier (Claude Haiku). Auto-selected when an API
|
|
41
|
+
* key is present; the local NLI verifier is the no-key default. Same closed
|
|
42
|
+
* entailment task, higher accuracy.
|
|
43
|
+
*/
|
|
44
|
+
export class AnthropicVerifier {
|
|
45
|
+
id = "anthropic-haiku";
|
|
46
|
+
client;
|
|
47
|
+
constructor(client) {
|
|
48
|
+
this.client = client ?? new Anthropic();
|
|
49
|
+
}
|
|
50
|
+
async entail(input) {
|
|
51
|
+
if (input.passages.length === 0)
|
|
52
|
+
return [];
|
|
53
|
+
const passageBlock = input.passages.map((p) => `[${String(p.index)}] ${p.text}`).join("\n\n");
|
|
54
|
+
const userText = `CLAIM: ${input.claim}\n\nPASSAGES:\n${passageBlock}`;
|
|
55
|
+
let judgements;
|
|
56
|
+
try {
|
|
57
|
+
const response = await this.client.messages.create({
|
|
58
|
+
model: MODEL,
|
|
59
|
+
max_tokens: MAX_TOKENS,
|
|
60
|
+
temperature: 0,
|
|
61
|
+
system: SYSTEM_PROMPT,
|
|
62
|
+
messages: [{ role: "user", content: userText }],
|
|
63
|
+
output_config: { format: OUTPUT_FORMAT },
|
|
64
|
+
});
|
|
65
|
+
judgements = parseJudgements(response.content);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// Conservative on failure: unverified => not grounded (neutral). The SDK
|
|
69
|
+
// already retries transient 429/5xx, so this is for persistent failures.
|
|
70
|
+
logger.warn("Anthropic verifier call failed; treating passages as neutral", {
|
|
71
|
+
error: error instanceof Error ? error.message : String(error),
|
|
72
|
+
});
|
|
73
|
+
return input.passages.map((p) => neutral(p.index));
|
|
74
|
+
}
|
|
75
|
+
const byIndex = new Map();
|
|
76
|
+
for (const judgement of judgements) {
|
|
77
|
+
byIndex.set(judgement.passageIndex, {
|
|
78
|
+
label: judgement.label,
|
|
79
|
+
confidence: clamp01(judgement.confidence),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return input.passages.map((passage) => {
|
|
83
|
+
const judgement = byIndex.get(passage.index);
|
|
84
|
+
return judgement === undefined
|
|
85
|
+
? neutral(passage.index)
|
|
86
|
+
: { passageIndex: passage.index, label: judgement.label, confidence: judgement.confidence };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const LABELS = new Set(["supported", "contradicted", "neutral"]);
|
|
91
|
+
/** Extracts and validates the judgements array from the model's JSON response. */
|
|
92
|
+
function parseJudgements(content) {
|
|
93
|
+
const text = content.find((block) => block.type === "text" && typeof block.text === "string")?.text;
|
|
94
|
+
if (text === undefined)
|
|
95
|
+
return [];
|
|
96
|
+
const parsed = JSON.parse(text);
|
|
97
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
98
|
+
return [];
|
|
99
|
+
const rawList = parsed.judgements;
|
|
100
|
+
if (!Array.isArray(rawList))
|
|
101
|
+
return [];
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const item of rawList) {
|
|
104
|
+
if (typeof item !== "object" || item === null)
|
|
105
|
+
continue;
|
|
106
|
+
const record = item;
|
|
107
|
+
const passageIndex = record.passageIndex;
|
|
108
|
+
const label = record.label;
|
|
109
|
+
const confidence = record.confidence;
|
|
110
|
+
if (typeof passageIndex === "number" && typeof label === "string" && LABELS.has(label)) {
|
|
111
|
+
out.push({
|
|
112
|
+
passageIndex,
|
|
113
|
+
label: label,
|
|
114
|
+
confidence: typeof confidence === "number" ? confidence : 0,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
function neutral(passageIndex) {
|
|
121
|
+
return { passageIndex, label: "neutral", confidence: 0 };
|
|
122
|
+
}
|
|
123
|
+
function clamp01(value) {
|
|
124
|
+
return Math.max(0, Math.min(1, value));
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/verify/anthropic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a free-text answer into atomic, individually-verifiable claims.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately conservative: it prefers a few well-formed claims over many
|
|
5
|
+
* fragments (over-splitting hurts grounding accuracy). No model is used — the
|
|
6
|
+
* built-in sentence segmenter plus light heuristics.
|
|
7
|
+
*/
|
|
8
|
+
export function decompose(text) {
|
|
9
|
+
const trimmed = text.trim();
|
|
10
|
+
if (trimmed === "")
|
|
11
|
+
return [];
|
|
12
|
+
const claims = [];
|
|
13
|
+
for (const sentence of segmentSentences(trimmed)) {
|
|
14
|
+
for (const clause of splitConjunctions(sentence.trim())) {
|
|
15
|
+
const cleaned = clause.trim();
|
|
16
|
+
if (isVerifiable(cleaned)) {
|
|
17
|
+
claims.push({ id: claims.length, text: cleaned });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Fallback: a terse, punctuation-free line is still a claim worth checking.
|
|
22
|
+
if (claims.length === 0)
|
|
23
|
+
claims.push({ id: 0, text: trimmed });
|
|
24
|
+
return claims;
|
|
25
|
+
}
|
|
26
|
+
function segmentSentences(text) {
|
|
27
|
+
const ctor = Intl.Segmenter;
|
|
28
|
+
if (ctor) {
|
|
29
|
+
return Array.from(new ctor("en", { granularity: "sentence" }).segment(text), (s) => s.segment);
|
|
30
|
+
}
|
|
31
|
+
// Fallback for runtimes without Intl.Segmenter.
|
|
32
|
+
return text.split(/(?<=[.!?])\s+/);
|
|
33
|
+
}
|
|
34
|
+
function isVerifiable(sentence) {
|
|
35
|
+
if (sentence.length < 3)
|
|
36
|
+
return false;
|
|
37
|
+
if (sentence.endsWith("?"))
|
|
38
|
+
return false; // questions assert nothing
|
|
39
|
+
return sentence.split(/\s+/).filter(Boolean).length >= 2;
|
|
40
|
+
}
|
|
41
|
+
// Common verbs/copulas — enough to tell a clause from a fragment in note prose.
|
|
42
|
+
const VERBS = new Set([
|
|
43
|
+
"is", "are", "was", "were", "be", "been", "being", "do", "does", "did",
|
|
44
|
+
"use", "uses", "used", "using", "have", "has", "had",
|
|
45
|
+
"run", "runs", "ran", "running", "deploy", "deploys", "deployed",
|
|
46
|
+
"cost", "costs", "lead", "leads", "led", "support", "supports", "supported",
|
|
47
|
+
"require", "requires", "required", "include", "includes", "included",
|
|
48
|
+
"offer", "offers", "offered", "generate", "generates", "generated",
|
|
49
|
+
"schedule", "schedules", "scheduled", "send", "sends", "sent",
|
|
50
|
+
"work", "works", "worked", "choose", "chooses", "chose", "chosen",
|
|
51
|
+
"add", "adds", "added", "hash", "hashes", "hashed", "store", "stores", "stored",
|
|
52
|
+
"expire", "expires", "expired", "encrypt", "encrypts", "encrypted",
|
|
53
|
+
"slip", "slips", "slipped", "ship", "ships", "shipped", "build", "builds", "built",
|
|
54
|
+
"handle", "handles", "handled", "process", "processes", "processed",
|
|
55
|
+
"collect", "collects", "collected", "limit", "limits", "limited",
|
|
56
|
+
"make", "makes", "made", "provide", "provides", "provided", "lives", "live",
|
|
57
|
+
]);
|
|
58
|
+
function hasVerb(clause) {
|
|
59
|
+
return (clause.match(/[A-Za-z'-]+/g) ?? []).some((word) => VERBS.has(word.toLowerCase()));
|
|
60
|
+
}
|
|
61
|
+
/** The leading "subject + verb" of a clause, e.g. "We use ..." → "We use". */
|
|
62
|
+
function subjectVerbPrefix(clause) {
|
|
63
|
+
const words = clause.split(/\s+/);
|
|
64
|
+
for (let i = 0; i < words.length; i += 1) {
|
|
65
|
+
const bare = (words[i] ?? "").replace(/[^A-Za-z'-]/g, "").toLowerCase();
|
|
66
|
+
if (VERBS.has(bare))
|
|
67
|
+
return words.slice(0, i + 1).join(" ");
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Splits a coordinated sentence ("We use Redis for caching and Stripe for
|
|
73
|
+
* payments") into independently-verifiable clauses, distributing the leading
|
|
74
|
+
* subject+verb to any verbless fragment. Conservative: if a clean clause can't
|
|
75
|
+
* be formed it keeps the sentence whole. Splitting can only make grounding
|
|
76
|
+
* harder (every clause must ground on its own), never invent a false grounding.
|
|
77
|
+
*/
|
|
78
|
+
function splitConjunctions(sentence) {
|
|
79
|
+
const parts = sentence
|
|
80
|
+
.split(/\s*,?\s+and\s+/i)
|
|
81
|
+
.map((part) => part.trim())
|
|
82
|
+
.filter(Boolean);
|
|
83
|
+
if (parts.length < 2)
|
|
84
|
+
return [sentence];
|
|
85
|
+
const prefix = subjectVerbPrefix(parts[0] ?? "");
|
|
86
|
+
const clauses = [];
|
|
87
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
88
|
+
let clause = parts[i] ?? "";
|
|
89
|
+
if (i > 0 && !hasVerb(clause)) {
|
|
90
|
+
if (prefix === null)
|
|
91
|
+
return [sentence]; // can't repair the fragment → don't split
|
|
92
|
+
clause = `${prefix} ${clause}`;
|
|
93
|
+
}
|
|
94
|
+
if (!hasVerb(clause))
|
|
95
|
+
return [sentence]; // still not a clause → abort the split
|
|
96
|
+
clauses.push(clause);
|
|
97
|
+
}
|
|
98
|
+
return clauses;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=decompose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decompose.js","sourceRoot":"","sources":["../../src/verify/decompose.ts"],"names":[],"mappings":"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"}
|