@gmickel/gno 1.8.0 → 1.10.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/README.md +6 -2
- package/assets/skill/SKILL.md +41 -16
- package/assets/skill/cli-reference.md +39 -0
- package/assets/skill/examples.md +41 -0
- package/assets/skill/mcp-reference.md +13 -1
- package/package.json +1 -1
- package/src/cli/commands/capture.ts +9 -6
- package/src/cli/commands/graph.ts +89 -2
- package/src/cli/commands/index-cmd.ts +17 -6
- package/src/cli/commands/links.ts +237 -54
- package/src/cli/commands/query.ts +212 -0
- package/src/cli/commands/ref-parser.ts +8 -103
- package/src/cli/commands/shared.ts +17 -1
- package/src/cli/commands/update.ts +17 -6
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +176 -9
- package/src/config/content-types.ts +154 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +14 -0
- package/src/config/loader.ts +11 -2
- package/src/config/types.ts +37 -1
- package/src/core/config-mutation.ts +14 -2
- package/src/core/graph-query.ts +137 -0
- package/src/core/graph-resolver.ts +117 -0
- package/src/core/note-presets.ts +61 -5
- package/src/core/ref-parser.ts +145 -0
- package/src/ingestion/frontmatter.ts +170 -2
- package/src/ingestion/index.ts +2 -0
- package/src/ingestion/sync-options.ts +29 -0
- package/src/ingestion/sync.ts +385 -17
- package/src/ingestion/types.ts +14 -0
- package/src/mcp/tools/add-collection.ts +8 -5
- package/src/mcp/tools/capture.ts +5 -2
- package/src/mcp/tools/get.ts +1 -1
- package/src/mcp/tools/index-cmd.ts +13 -7
- package/src/mcp/tools/index.ts +97 -10
- package/src/mcp/tools/links.ts +83 -1
- package/src/mcp/tools/multi-get.ts +1 -1
- package/src/mcp/tools/query.ts +207 -0
- package/src/mcp/tools/sync.ts +12 -6
- package/src/mcp/tools/workspace-write.ts +16 -10
- package/src/pipeline/diagnose.ts +302 -0
- package/src/pipeline/filters.ts +119 -0
- package/src/pipeline/hybrid.ts +92 -17
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +34 -0
- package/src/pipeline/vsearch.ts +4 -0
- package/src/publish/export-service.ts +1 -1
- package/src/sdk/client.ts +51 -24
- package/src/sdk/documents.ts +2 -2
- package/src/serve/background-runtime.ts +18 -4
- package/src/serve/config-sync.ts +9 -2
- package/src/serve/routes/api.ts +244 -24
- package/src/serve/routes/graph.ts +173 -1
- package/src/serve/server.ts +24 -1
- package/src/serve/watch-service.ts +12 -2
- package/src/store/migrations/009-content-type-rule-fingerprint.ts +36 -0
- package/src/store/migrations/010-typed-edges.ts +67 -0
- package/src/store/migrations/011-doc-edge-traversal-indexes.ts +30 -0
- package/src/store/migrations/index.ts +16 -1
- package/src/store/sqlite/adapter.ts +853 -114
- package/src/store/types.ts +147 -0
|
@@ -12,7 +12,7 @@ import { acquireWriteLock, type WriteLockHandle } from "../../core/file-lock";
|
|
|
12
12
|
import { JobError } from "../../core/job-manager";
|
|
13
13
|
import { normalizeCollectionName } from "../../core/validation";
|
|
14
14
|
import { embedBacklog } from "../../embed";
|
|
15
|
-
import { defaultSyncService } from "../../ingestion";
|
|
15
|
+
import { defaultSyncService, withContentTypeRules } from "../../ingestion";
|
|
16
16
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
17
17
|
import { resolveModelUri } from "../../llm/registry";
|
|
18
18
|
import {
|
|
@@ -95,11 +95,14 @@ export function handleIndex(
|
|
|
95
95
|
? [collection.name]
|
|
96
96
|
: ctx.collections.map((entry) => entry.name);
|
|
97
97
|
|
|
98
|
-
const options =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
const options = withContentTypeRules(
|
|
99
|
+
{
|
|
100
|
+
gitPull: args.gitPull ?? false,
|
|
101
|
+
// Security: MCP never runs updateCmd by default
|
|
102
|
+
runUpdateCmd: false,
|
|
103
|
+
},
|
|
104
|
+
ctx.config
|
|
105
|
+
);
|
|
103
106
|
|
|
104
107
|
const modelUri = resolveModelUri(
|
|
105
108
|
ctx.config,
|
|
@@ -203,7 +206,10 @@ export function handleIndex(
|
|
|
203
206
|
collections,
|
|
204
207
|
status: "started",
|
|
205
208
|
phases: ["sync", "embed"],
|
|
206
|
-
options
|
|
209
|
+
options: {
|
|
210
|
+
gitPull: options.gitPull ?? false,
|
|
211
|
+
runUpdateCmd: options.runUpdateCmd ?? false,
|
|
212
|
+
},
|
|
207
213
|
};
|
|
208
214
|
|
|
209
215
|
return result;
|
package/src/mcp/tools/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { z } from "zod";
|
|
|
11
11
|
import type { ToolContext } from "../server";
|
|
12
12
|
|
|
13
13
|
import { CAPTURE_MAX_TEXT_BYTES } from "../../core/capture";
|
|
14
|
+
import { NOTE_PRESETS, type NotePresetId } from "../../core/note-presets";
|
|
14
15
|
import { normalizeTag } from "../../core/tags";
|
|
15
16
|
import { handleAddCollection } from "./add-collection";
|
|
16
17
|
import { handleCapture } from "./capture";
|
|
@@ -22,6 +23,7 @@ import { handleJobStatus } from "./job-status";
|
|
|
22
23
|
import {
|
|
23
24
|
handleBacklinks,
|
|
24
25
|
handleGraph,
|
|
26
|
+
handleGraphQuery,
|
|
25
27
|
handleGraphNeighbors,
|
|
26
28
|
handleGraphPath,
|
|
27
29
|
handleLinks,
|
|
@@ -30,7 +32,7 @@ import {
|
|
|
30
32
|
import { handleListJobs } from "./list-jobs";
|
|
31
33
|
import { handleListTags } from "./list-tags";
|
|
32
34
|
import { handleMultiGet } from "./multi-get";
|
|
33
|
-
import { handleQuery } from "./query";
|
|
35
|
+
import { handleQuery, handleQueryDiagnose } from "./query";
|
|
34
36
|
import { handleRemoveCollection } from "./remove-collection";
|
|
35
37
|
import { handleSearch } from "./search";
|
|
36
38
|
import { handleStatus } from "./status";
|
|
@@ -63,6 +65,8 @@ export const MCP_TOOL_DESCRIPTIONS = {
|
|
|
63
65
|
"Vector semantic search. Finds conceptually similar docs with different wording. Best after embeddings are current; use intent to disambiguate short terms. Use gno_query for default hybrid retrieval.",
|
|
64
66
|
query:
|
|
65
67
|
"Hybrid search (BM25 + vector + optional expansion/reranking). Recommended default. Use intent for ambiguous terms, queryModes to combine term/intent/hyde strategies, fast=true for quick lookup, thorough=true when recall matters, and candidateLimit to trade latency for coverage.",
|
|
68
|
+
queryDiagnose:
|
|
69
|
+
"Diagnose why one target document does or does not appear for a query. Use when an important doc is missing, a filter may exclude it, or you need stage-by-stage BM25/vector/fusion/graph/rerank evidence before changing retrieval strategy.",
|
|
66
70
|
get: "Retrieve one document by gno:// URI, docid (#abc123), or collection/path. After search results include line, pass fromLine and lineCount to fetch only the relevant range before expanding to the full document.",
|
|
67
71
|
multiGet:
|
|
68
72
|
"Retrieve multiple documents by refs array or glob pattern. Use after gno_search/gno_query to batch top result URIs/docids; set maxBytes and lineNumbers to control context size.",
|
|
@@ -142,7 +146,12 @@ const searchInputSchema = z.object({
|
|
|
142
146
|
.describe("Require ANY of these tags (OR filter)"),
|
|
143
147
|
});
|
|
144
148
|
|
|
145
|
-
const
|
|
149
|
+
const notePresetIds = NOTE_PRESETS.map((preset) => preset.id) as [
|
|
150
|
+
NotePresetId,
|
|
151
|
+
...NotePresetId[],
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
export const captureInputSchema = z.object({
|
|
146
155
|
collection: z
|
|
147
156
|
.string()
|
|
148
157
|
.min(1, "Collection cannot be empty")
|
|
@@ -173,14 +182,7 @@ const captureInputSchema = z.object({
|
|
|
173
182
|
.optional()
|
|
174
183
|
.describe("How to handle name collisions"),
|
|
175
184
|
presetId: z
|
|
176
|
-
.enum(
|
|
177
|
-
"blank",
|
|
178
|
-
"project-note",
|
|
179
|
-
"research-note",
|
|
180
|
-
"decision-note",
|
|
181
|
-
"prompt-pattern",
|
|
182
|
-
"source-summary",
|
|
183
|
-
])
|
|
185
|
+
.enum(notePresetIds)
|
|
184
186
|
.optional()
|
|
185
187
|
.describe("Optional note preset scaffold"),
|
|
186
188
|
overwrite: z
|
|
@@ -500,6 +502,16 @@ export const queryInputSchema = z.object({
|
|
|
500
502
|
tagsAny: z.array(z.string()).optional().describe("Require ANY of these tags"),
|
|
501
503
|
});
|
|
502
504
|
|
|
505
|
+
export const queryDiagnoseInputSchema = queryInputSchema.extend({
|
|
506
|
+
target: z
|
|
507
|
+
.string()
|
|
508
|
+
.trim()
|
|
509
|
+
.min(1, "Target reference cannot be empty")
|
|
510
|
+
.describe(
|
|
511
|
+
"Target document reference to diagnose (gno URI, docid, or collection/path)"
|
|
512
|
+
),
|
|
513
|
+
});
|
|
514
|
+
|
|
503
515
|
const getInputSchema = z.object({
|
|
504
516
|
ref: z
|
|
505
517
|
.string()
|
|
@@ -716,6 +728,67 @@ const graphPathInputSchema = graphInputSchema.extend({
|
|
|
716
728
|
.describe("Maximum relationship hops to search"),
|
|
717
729
|
});
|
|
718
730
|
|
|
731
|
+
export const graphQueryInputSchema = z.object({
|
|
732
|
+
ref: z
|
|
733
|
+
.string()
|
|
734
|
+
.trim()
|
|
735
|
+
.min(1, "Reference cannot be empty")
|
|
736
|
+
.describe(
|
|
737
|
+
"Root document reference for typed-edge traversal: gno URI, docid, or collection/path"
|
|
738
|
+
),
|
|
739
|
+
direction: z
|
|
740
|
+
.enum(["both", "out", "in"])
|
|
741
|
+
.default("both")
|
|
742
|
+
.describe("Which typed edges to traverse"),
|
|
743
|
+
edgeType: z
|
|
744
|
+
.string()
|
|
745
|
+
.trim()
|
|
746
|
+
.min(1)
|
|
747
|
+
.optional()
|
|
748
|
+
.describe("Semantic edge type filter, e.g. mentions or works_at"),
|
|
749
|
+
relation: z
|
|
750
|
+
.string()
|
|
751
|
+
.trim()
|
|
752
|
+
.min(1)
|
|
753
|
+
.optional()
|
|
754
|
+
.describe("Alias for edgeType; must match edgeType when both are set"),
|
|
755
|
+
maxDepth: z
|
|
756
|
+
.number()
|
|
757
|
+
.int()
|
|
758
|
+
.min(1)
|
|
759
|
+
.max(6)
|
|
760
|
+
.optional()
|
|
761
|
+
.describe("Maximum typed-edge hops to traverse"),
|
|
762
|
+
depth: z
|
|
763
|
+
.number()
|
|
764
|
+
.int()
|
|
765
|
+
.min(1)
|
|
766
|
+
.max(6)
|
|
767
|
+
.optional()
|
|
768
|
+
.describe("Alias for maxDepth"),
|
|
769
|
+
maxNodes: z
|
|
770
|
+
.number()
|
|
771
|
+
.int()
|
|
772
|
+
.min(1)
|
|
773
|
+
.max(1000)
|
|
774
|
+
.default(100)
|
|
775
|
+
.describe("Returned node cap"),
|
|
776
|
+
frontierLimit: z
|
|
777
|
+
.number()
|
|
778
|
+
.int()
|
|
779
|
+
.min(1)
|
|
780
|
+
.max(1000)
|
|
781
|
+
.default(100)
|
|
782
|
+
.describe("Per-depth frontier cap"),
|
|
783
|
+
visitedLimit: z
|
|
784
|
+
.number()
|
|
785
|
+
.int()
|
|
786
|
+
.min(1)
|
|
787
|
+
.max(5000)
|
|
788
|
+
.default(500)
|
|
789
|
+
.describe("SQL traversal visited-row cap"),
|
|
790
|
+
});
|
|
791
|
+
|
|
719
792
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
720
793
|
// Tool Result Type
|
|
721
794
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -847,6 +920,13 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
847
920
|
(args) => handleQuery(args, ctx)
|
|
848
921
|
);
|
|
849
922
|
|
|
923
|
+
server.tool(
|
|
924
|
+
"gno_query_diagnose",
|
|
925
|
+
MCP_TOOL_DESCRIPTIONS.queryDiagnose,
|
|
926
|
+
queryDiagnoseInputSchema.shape,
|
|
927
|
+
(args) => handleQueryDiagnose(args, ctx)
|
|
928
|
+
);
|
|
929
|
+
|
|
850
930
|
server.tool(
|
|
851
931
|
"gno_get",
|
|
852
932
|
MCP_TOOL_DESCRIPTIONS.get,
|
|
@@ -903,6 +983,13 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
903
983
|
(args) => handleGraph(args, ctx)
|
|
904
984
|
);
|
|
905
985
|
|
|
986
|
+
server.tool(
|
|
987
|
+
"gno_graph_query",
|
|
988
|
+
"Run bounded traversal over typed doc_edges from one root document. Use for explicit relationship questions like 'what does Alice work_at within 2 hops?' or to inspect typed graph hints; returns schemaVersion, root, nodes, edges, caps, and truncation.",
|
|
989
|
+
graphQueryInputSchema.shape,
|
|
990
|
+
(args) => handleGraphQuery(args, ctx)
|
|
991
|
+
);
|
|
992
|
+
|
|
906
993
|
server.tool(
|
|
907
994
|
"gno_graph_neighbors",
|
|
908
995
|
"Find graph neighbors around a document/node. Use for relationship questions, missed obvious related docs, or unfamiliar corpus navigation after gno_query identifies a seed; returns incoming/outgoing wiki, markdown, and optional similarity edges. Follow with gno_get for targeted reads.",
|
package/src/mcp/tools/links.ts
CHANGED
|
@@ -10,14 +10,18 @@ import type {
|
|
|
10
10
|
BacklinkRow,
|
|
11
11
|
DocLinkRow,
|
|
12
12
|
DocumentRow,
|
|
13
|
+
GraphQueryDirection,
|
|
14
|
+
GraphQueryResult,
|
|
13
15
|
GraphLink,
|
|
14
16
|
GraphNode,
|
|
15
17
|
GraphResult,
|
|
16
18
|
} from "../../store/types";
|
|
17
19
|
import type { ToolContext } from "../server";
|
|
18
20
|
|
|
19
|
-
import {
|
|
21
|
+
import { normalizeContentTypes } from "../../config";
|
|
20
22
|
import { MCP_ERRORS } from "../../core/errors";
|
|
23
|
+
import { diagnoseGraphQuery } from "../../core/graph-query";
|
|
24
|
+
import { parseRef } from "../../core/ref-parser";
|
|
21
25
|
import { normalizeCollectionName } from "../../core/validation";
|
|
22
26
|
import { getActivePreset } from "../../llm/registry";
|
|
23
27
|
import { createVectorIndexPort } from "../../store/vector";
|
|
@@ -648,6 +652,18 @@ interface GraphPathInput extends GraphInput {
|
|
|
648
652
|
maxDepth?: number;
|
|
649
653
|
}
|
|
650
654
|
|
|
655
|
+
interface GraphQueryInput {
|
|
656
|
+
ref: string;
|
|
657
|
+
direction?: GraphQueryDirection;
|
|
658
|
+
edgeType?: string;
|
|
659
|
+
relation?: string;
|
|
660
|
+
maxDepth?: number;
|
|
661
|
+
depth?: number;
|
|
662
|
+
maxNodes?: number;
|
|
663
|
+
frontierLimit?: number;
|
|
664
|
+
visitedLimit?: number;
|
|
665
|
+
}
|
|
666
|
+
|
|
651
667
|
interface GraphNeighbor {
|
|
652
668
|
node: GraphNode;
|
|
653
669
|
direction: "out" | "in";
|
|
@@ -860,6 +876,72 @@ function formatGraphNeighborsResult(data: GraphNeighborsResult): string {
|
|
|
860
876
|
return lines.join("\n");
|
|
861
877
|
}
|
|
862
878
|
|
|
879
|
+
function formatGraphQueryResult(data: GraphQueryResult): string {
|
|
880
|
+
const lines = [
|
|
881
|
+
`Typed graph query from ${data.root.uri}: ${data.nodes.length} nodes, ${data.edges.length} edges`,
|
|
882
|
+
`Direction: ${data.meta.direction}, depth: ${data.meta.maxDepth}`,
|
|
883
|
+
];
|
|
884
|
+
if (data.meta.edgeType) {
|
|
885
|
+
lines.push(`Edge type: ${data.meta.edgeType}`);
|
|
886
|
+
}
|
|
887
|
+
if (data.meta.truncated) {
|
|
888
|
+
lines.push("Traversal truncated by configured caps");
|
|
889
|
+
}
|
|
890
|
+
for (const edge of data.edges.slice(0, 20)) {
|
|
891
|
+
lines.push(
|
|
892
|
+
` ${edge.source} -> ${edge.target} (${edge.edgeType}, ${edge.confidence}, ${edge.edgeSource})`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
return lines.join("\n");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
export function handleGraphQuery(
|
|
899
|
+
args: GraphQueryInput,
|
|
900
|
+
ctx: ToolContext
|
|
901
|
+
): Promise<ToolResult> {
|
|
902
|
+
return runTool(
|
|
903
|
+
ctx,
|
|
904
|
+
"gno_graph_query",
|
|
905
|
+
async () => {
|
|
906
|
+
const edgeTypeValue = args.edgeType?.trim();
|
|
907
|
+
const relationValue = args.relation?.trim();
|
|
908
|
+
if (edgeTypeValue && relationValue && edgeTypeValue !== relationValue) {
|
|
909
|
+
throw new Error(
|
|
910
|
+
"VALIDATION: edgeType and relation are aliases and must match when both are provided"
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
if (
|
|
914
|
+
args.maxDepth !== undefined &&
|
|
915
|
+
args.depth !== undefined &&
|
|
916
|
+
args.maxDepth !== args.depth
|
|
917
|
+
) {
|
|
918
|
+
throw new Error(
|
|
919
|
+
"VALIDATION: maxDepth and depth are aliases and must match when both are provided"
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const result = await diagnoseGraphQuery(ctx.store, args.ref, {
|
|
924
|
+
direction: args.direction ?? "both",
|
|
925
|
+
edgeType: edgeTypeValue || relationValue || undefined,
|
|
926
|
+
maxDepth: args.maxDepth ?? args.depth,
|
|
927
|
+
maxNodes: args.maxNodes,
|
|
928
|
+
frontierLimit: args.frontierLimit,
|
|
929
|
+
visitedLimit: args.visitedLimit,
|
|
930
|
+
contentTypeRules: normalizeContentTypes(ctx.config.contentTypes ?? [])
|
|
931
|
+
.rules,
|
|
932
|
+
});
|
|
933
|
+
if (!result.success) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`${result.isValidation ? "VALIDATION" : "RUNTIME"}: ${result.error}`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return result.data;
|
|
940
|
+
},
|
|
941
|
+
formatGraphQueryResult
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
|
|
863
945
|
export function handleGraphNeighbors(
|
|
864
946
|
args: GraphNeighborsInput,
|
|
865
947
|
ctx: ToolContext
|
|
@@ -10,7 +10,7 @@ import type { DocumentRow, StorePort } from "../../store/types";
|
|
|
10
10
|
import type { ToolContext } from "../server";
|
|
11
11
|
|
|
12
12
|
import { decorateUriForIndex, parseUri } from "../../app/constants";
|
|
13
|
-
import { parseRef } from "../../
|
|
13
|
+
import { parseRef } from "../../core/ref-parser";
|
|
14
14
|
import { runTool, type ToolResult } from "./index";
|
|
15
15
|
|
|
16
16
|
interface MultiGetInput {
|
package/src/mcp/tools/query.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
GenerationPort,
|
|
12
12
|
RerankPort,
|
|
13
13
|
} from "../../llm/types";
|
|
14
|
+
import type { QueryDiagnoseResult } from "../../pipeline/diagnose";
|
|
14
15
|
import type {
|
|
15
16
|
QueryModeInput,
|
|
16
17
|
SearchResult,
|
|
@@ -20,11 +21,16 @@ import type { ToolContext } from "../server";
|
|
|
20
21
|
|
|
21
22
|
import { decorateUriForIndex, parseUri } from "../../app/constants";
|
|
22
23
|
import { createNonTtyProgressRenderer } from "../../cli/progress";
|
|
24
|
+
import {
|
|
25
|
+
fingerprintContentTypeRules,
|
|
26
|
+
normalizeContentTypes,
|
|
27
|
+
} from "../../config";
|
|
23
28
|
import { resolveDepthPolicy } from "../../core/depth-policy";
|
|
24
29
|
import { normalizeStructuredQueryInput } from "../../core/structured-query";
|
|
25
30
|
import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
|
|
26
31
|
import { resolveDownloadPolicy } from "../../llm/policy";
|
|
27
32
|
import { getActivePreset, resolveModelUri } from "../../llm/registry";
|
|
33
|
+
import { diagnoseQueryTarget } from "../../pipeline/diagnose";
|
|
28
34
|
import { type HybridSearchDeps, searchHybrid } from "../../pipeline/hybrid";
|
|
29
35
|
import {
|
|
30
36
|
createVectorIndexPort,
|
|
@@ -56,6 +62,10 @@ interface QueryInput {
|
|
|
56
62
|
tagsAny?: string[];
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
interface QueryDiagnoseInput extends QueryInput {
|
|
66
|
+
target: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
/**
|
|
60
70
|
* Enrich results with absPath derived from each result's URI.
|
|
61
71
|
*/
|
|
@@ -128,6 +138,29 @@ function formatSearchResults(data: SearchResults): string {
|
|
|
128
138
|
return lines.join("\n");
|
|
129
139
|
}
|
|
130
140
|
|
|
141
|
+
function formatQueryDiagnoseResult(data: QueryDiagnoseResult): string {
|
|
142
|
+
if (data.target.status !== "diagnosed") {
|
|
143
|
+
return `Target ${data.target.ref}: ${data.target.status}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const lines = [
|
|
147
|
+
`Query diagnose for ${data.target.uri ?? data.target.ref}`,
|
|
148
|
+
`Mode: ${data.meta.mode}, results: ${data.meta.totalResults}`,
|
|
149
|
+
"",
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
for (const stage of data.stages) {
|
|
153
|
+
const rank = stage.rank === null ? "-" : `#${stage.rank}`;
|
|
154
|
+
const score = stage.score === null ? "-" : Number(stage.score).toFixed(3);
|
|
155
|
+
const reason = stage.dropReason ? `, ${stage.dropReason}` : "";
|
|
156
|
+
lines.push(
|
|
157
|
+
`${stage.id}: ${stage.status}, present=${stage.present}, rank=${rank}, score=${score}${reason}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
131
164
|
/**
|
|
132
165
|
* Handle gno_query tool call.
|
|
133
166
|
*/
|
|
@@ -311,3 +344,177 @@ export function handleQuery(
|
|
|
311
344
|
formatSearchResults
|
|
312
345
|
);
|
|
313
346
|
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Handle gno_query_diagnose tool call.
|
|
350
|
+
*/
|
|
351
|
+
export function handleQueryDiagnose(
|
|
352
|
+
args: QueryDiagnoseInput,
|
|
353
|
+
ctx: ToolContext
|
|
354
|
+
): Promise<ToolResult> {
|
|
355
|
+
return runTool(
|
|
356
|
+
ctx,
|
|
357
|
+
"gno_query_diagnose",
|
|
358
|
+
// oxlint-disable-next-line max-lines-per-function -- mirrors query setup for diagnostic trace ports
|
|
359
|
+
async () => {
|
|
360
|
+
let collection: string | undefined;
|
|
361
|
+
if (args.collection) {
|
|
362
|
+
const canonical = ctx.collections.find(
|
|
363
|
+
(c) => c.name.toLowerCase() === args.collection?.toLowerCase()
|
|
364
|
+
);
|
|
365
|
+
if (!canonical) {
|
|
366
|
+
throw new Error(`Collection not found: ${args.collection}`);
|
|
367
|
+
}
|
|
368
|
+
collection = canonical.name;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const normalizedInput = normalizeStructuredQueryInput(
|
|
372
|
+
args.query,
|
|
373
|
+
args.queryModes ?? []
|
|
374
|
+
);
|
|
375
|
+
if (!normalizedInput.ok) {
|
|
376
|
+
throw new Error(normalizedInput.error.message);
|
|
377
|
+
}
|
|
378
|
+
const queryText = normalizedInput.value.query;
|
|
379
|
+
const queryModes =
|
|
380
|
+
normalizedInput.value.queryModes.length > 0
|
|
381
|
+
? normalizedInput.value.queryModes
|
|
382
|
+
: undefined;
|
|
383
|
+
|
|
384
|
+
const preset = getActivePreset(ctx.config);
|
|
385
|
+
const llm = new LlmAdapter(ctx.config);
|
|
386
|
+
const policy = resolveDownloadPolicy(process.env, {});
|
|
387
|
+
const downloadProgress = createNonTtyProgressRenderer();
|
|
388
|
+
|
|
389
|
+
let embedPort: EmbeddingPort | null = null;
|
|
390
|
+
let expandPort: GenerationPort | null = null;
|
|
391
|
+
let rerankPort: RerankPort | null = null;
|
|
392
|
+
let vectorIndex: VectorIndexPort | null = null;
|
|
393
|
+
const embedUri = resolveModelUri(
|
|
394
|
+
ctx.config,
|
|
395
|
+
"embed",
|
|
396
|
+
undefined,
|
|
397
|
+
collection
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const hasStructuredModes = Boolean(queryModes?.length);
|
|
402
|
+
const depthPolicy = resolveDepthPolicy({
|
|
403
|
+
presetId: preset.id,
|
|
404
|
+
fast: args.fast,
|
|
405
|
+
thorough: args.thorough,
|
|
406
|
+
expand: args.expand,
|
|
407
|
+
rerank: args.rerank,
|
|
408
|
+
candidateLimit: args.candidateLimit,
|
|
409
|
+
hasStructuredModes,
|
|
410
|
+
});
|
|
411
|
+
const { noExpand, noRerank } = depthPolicy;
|
|
412
|
+
|
|
413
|
+
if (!args.fast) {
|
|
414
|
+
const embedResult = await llm.createEmbeddingPort(embedUri, {
|
|
415
|
+
policy,
|
|
416
|
+
onProgress: (progress) => downloadProgress("embed", progress),
|
|
417
|
+
});
|
|
418
|
+
if (embedResult.ok) {
|
|
419
|
+
embedPort = embedResult.value;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!noExpand && !hasStructuredModes) {
|
|
424
|
+
const genResult = await llm.createExpansionPort(
|
|
425
|
+
resolveModelUri(ctx.config, "expand", undefined, collection),
|
|
426
|
+
{
|
|
427
|
+
policy,
|
|
428
|
+
onProgress: (progress) => downloadProgress("expand", progress),
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
if (genResult.ok) {
|
|
432
|
+
expandPort = genResult.value;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (!noRerank) {
|
|
437
|
+
const rerankResult = await llm.createRerankPort(
|
|
438
|
+
resolveModelUri(ctx.config, "rerank", undefined, collection),
|
|
439
|
+
{
|
|
440
|
+
policy,
|
|
441
|
+
onProgress: (progress) => downloadProgress("rerank", progress),
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
if (rerankResult.ok) {
|
|
445
|
+
rerankPort = rerankResult.value;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (embedPort) {
|
|
450
|
+
const embedInitResult = await embedPort.init();
|
|
451
|
+
if (embedInitResult.ok) {
|
|
452
|
+
const dimensions = embedPort.dimensions();
|
|
453
|
+
const db = ctx.store.getRawDb();
|
|
454
|
+
const vectorResult = await createVectorIndexPort(db, {
|
|
455
|
+
model: embedUri,
|
|
456
|
+
dimensions,
|
|
457
|
+
});
|
|
458
|
+
if (vectorResult.ok) {
|
|
459
|
+
vectorIndex = vectorResult.value;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const deps: HybridSearchDeps = {
|
|
465
|
+
store: ctx.store,
|
|
466
|
+
config: ctx.config,
|
|
467
|
+
vectorIndex,
|
|
468
|
+
embedPort,
|
|
469
|
+
expandPort,
|
|
470
|
+
rerankPort,
|
|
471
|
+
};
|
|
472
|
+
const contentTypeRules = normalizeContentTypes(
|
|
473
|
+
ctx.config.contentTypes ?? []
|
|
474
|
+
).rules;
|
|
475
|
+
|
|
476
|
+
const result = await diagnoseQueryTarget(deps, queryText, {
|
|
477
|
+
target: args.target,
|
|
478
|
+
limit: args.limit ?? 5,
|
|
479
|
+
minScore: args.minScore,
|
|
480
|
+
collection,
|
|
481
|
+
queryLanguageHint: args.lang,
|
|
482
|
+
intent: args.intent,
|
|
483
|
+
candidateLimit: depthPolicy.candidateLimit,
|
|
484
|
+
exclude: args.exclude,
|
|
485
|
+
since: args.since,
|
|
486
|
+
until: args.until,
|
|
487
|
+
categories: args.categories,
|
|
488
|
+
author: args.author,
|
|
489
|
+
noExpand,
|
|
490
|
+
noRerank,
|
|
491
|
+
graph: args.graph === true,
|
|
492
|
+
noGraph: args.noGraph || args.fast,
|
|
493
|
+
queryModes,
|
|
494
|
+
tagsAll: normalizeTagFilters(args.tagsAll),
|
|
495
|
+
tagsAny: normalizeTagFilters(args.tagsAny),
|
|
496
|
+
contentTypeRules,
|
|
497
|
+
contentTypeRulesFingerprint:
|
|
498
|
+
fingerprintContentTypeRules(contentTypeRules),
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
if (!result.ok) {
|
|
502
|
+
throw new Error(result.error.message);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return result.value;
|
|
506
|
+
} finally {
|
|
507
|
+
if (embedPort) {
|
|
508
|
+
await embedPort.dispose();
|
|
509
|
+
}
|
|
510
|
+
if (expandPort) {
|
|
511
|
+
await expandPort.dispose();
|
|
512
|
+
}
|
|
513
|
+
if (rerankPort) {
|
|
514
|
+
await rerankPort.dispose();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
formatQueryDiagnoseResult
|
|
519
|
+
);
|
|
520
|
+
}
|
package/src/mcp/tools/sync.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { MCP_ERRORS } from "../../core/errors";
|
|
|
12
12
|
import { acquireWriteLock, type WriteLockHandle } from "../../core/file-lock";
|
|
13
13
|
import { JobError } from "../../core/job-manager";
|
|
14
14
|
import { normalizeCollectionName } from "../../core/validation";
|
|
15
|
-
import { defaultSyncService } from "../../ingestion";
|
|
15
|
+
import { defaultSyncService, withContentTypeRules } from "../../ingestion";
|
|
16
16
|
import { runTool, type ToolResult } from "./index";
|
|
17
17
|
|
|
18
18
|
interface SyncInput {
|
|
@@ -101,10 +101,13 @@ export function handleSync(
|
|
|
101
101
|
? [collection.name]
|
|
102
102
|
: ctx.collections.map((entry) => entry.name);
|
|
103
103
|
|
|
104
|
-
const options =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
const options = withContentTypeRules(
|
|
105
|
+
{
|
|
106
|
+
gitPull: args.gitPull ?? false,
|
|
107
|
+
runUpdateCmd: args.runUpdateCmd ?? false,
|
|
108
|
+
},
|
|
109
|
+
ctx.config
|
|
110
|
+
);
|
|
108
111
|
|
|
109
112
|
const jobId = await ctx.jobManager.startJobWithLock(
|
|
110
113
|
"sync",
|
|
@@ -133,7 +136,10 @@ export function handleSync(
|
|
|
133
136
|
jobId,
|
|
134
137
|
collections,
|
|
135
138
|
status: "started",
|
|
136
|
-
options
|
|
139
|
+
options: {
|
|
140
|
+
gitPull: options.gitPull ?? false,
|
|
141
|
+
runUpdateCmd: options.runUpdateCmd ?? false,
|
|
142
|
+
},
|
|
137
143
|
};
|
|
138
144
|
|
|
139
145
|
return result;
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
planMoveRefactor,
|
|
28
28
|
planRenameRefactor,
|
|
29
29
|
} from "../../core/file-refactors";
|
|
30
|
-
import { defaultSyncService } from "../../ingestion";
|
|
30
|
+
import { defaultSyncService, withContentTypeRules } from "../../ingestion";
|
|
31
31
|
import { runTool, type ToolResult } from "./index";
|
|
32
32
|
|
|
33
33
|
interface CreateFolderInput {
|
|
@@ -210,9 +210,11 @@ export function handleRenameNote(
|
|
|
210
210
|
const currentPath = join(collection.path, doc.relPath);
|
|
211
211
|
const nextPath = join(collection.path, plan.nextRelPath);
|
|
212
212
|
await renameFilePath(currentPath, nextPath);
|
|
213
|
-
await defaultSyncService.syncCollection(
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
await defaultSyncService.syncCollection(
|
|
214
|
+
collection,
|
|
215
|
+
ctx.store,
|
|
216
|
+
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
217
|
+
);
|
|
216
218
|
return {
|
|
217
219
|
uri: plan.nextUri,
|
|
218
220
|
relPath: plan.nextRelPath,
|
|
@@ -253,9 +255,11 @@ export function handleMoveNote(
|
|
|
253
255
|
const nextPath = join(collection.path, plan.nextRelPath);
|
|
254
256
|
await mkdir(dirname(nextPath), { recursive: true });
|
|
255
257
|
await renameFilePath(currentPath, nextPath);
|
|
256
|
-
await defaultSyncService.syncCollection(
|
|
257
|
-
|
|
258
|
-
|
|
258
|
+
await defaultSyncService.syncCollection(
|
|
259
|
+
collection,
|
|
260
|
+
ctx.store,
|
|
261
|
+
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
262
|
+
);
|
|
259
263
|
return {
|
|
260
264
|
uri: plan.nextUri,
|
|
261
265
|
relPath: plan.nextRelPath,
|
|
@@ -304,9 +308,11 @@ export function handleDuplicateNote(
|
|
|
304
308
|
const nextPath = join(collection.path, plan.nextRelPath);
|
|
305
309
|
await mkdir(dirname(nextPath), { recursive: true });
|
|
306
310
|
await copyFilePath(currentPath, nextPath);
|
|
307
|
-
await defaultSyncService.syncCollection(
|
|
308
|
-
|
|
309
|
-
|
|
311
|
+
await defaultSyncService.syncCollection(
|
|
312
|
+
collection,
|
|
313
|
+
ctx.store,
|
|
314
|
+
withContentTypeRules({ runUpdateCmd: false }, ctx.config)
|
|
315
|
+
);
|
|
310
316
|
return {
|
|
311
317
|
uri: plan.nextUri,
|
|
312
318
|
relPath: plan.nextRelPath,
|