kiri-mcp-server 0.12.0 → 0.14.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 +10 -14
- package/config/domain-terms.yml +37 -0
- package/dist/config/domain-terms.yml +37 -0
- package/dist/package.json +6 -1
- package/dist/src/server/boost-profiles.d.ts.map +1 -1
- package/dist/src/server/boost-profiles.js +92 -6
- package/dist/src/server/boost-profiles.js.map +1 -1
- package/dist/src/server/config.d.ts +2 -0
- package/dist/src/server/config.d.ts.map +1 -1
- package/dist/src/server/config.js +56 -0
- package/dist/src/server/config.js.map +1 -1
- package/dist/src/server/context.d.ts +4 -0
- package/dist/src/server/context.d.ts.map +1 -1
- package/dist/src/server/context.js +6 -0
- package/dist/src/server/context.js.map +1 -1
- package/dist/src/server/domain-terms.d.ts +28 -0
- package/dist/src/server/domain-terms.d.ts.map +1 -0
- package/dist/src/server/domain-terms.js +203 -0
- package/dist/src/server/domain-terms.js.map +1 -0
- package/dist/src/server/handlers.d.ts +2 -0
- package/dist/src/server/handlers.d.ts.map +1 -1
- package/dist/src/server/handlers.js +170 -14
- package/dist/src/server/handlers.js.map +1 -1
- package/dist/src/server/observability/metrics.d.ts +12 -0
- package/dist/src/server/observability/metrics.d.ts.map +1 -1
- package/dist/src/server/observability/metrics.js +11 -0
- package/dist/src/server/observability/metrics.js.map +1 -1
- package/dist/src/server/rpc.d.ts.map +1 -1
- package/dist/src/server/rpc.js +37 -2
- package/dist/src/server/rpc.js.map +1 -1
- package/dist/src/server/runtime.d.ts.map +1 -1
- package/dist/src/server/runtime.js +1 -0
- package/dist/src/server/runtime.js.map +1 -1
- package/dist/src/server/services/index.d.ts +2 -0
- package/dist/src/server/services/index.d.ts.map +1 -1
- package/dist/src/server/services/index.js +12 -0
- package/dist/src/server/services/index.js.map +1 -1
- package/dist/src/shared/adaptive-k-categories.d.ts +5 -0
- package/dist/src/shared/adaptive-k-categories.d.ts.map +1 -0
- package/dist/src/shared/adaptive-k-categories.js +17 -0
- package/dist/src/shared/adaptive-k-categories.js.map +1 -0
- package/dist/src/shared/adaptive-k.d.ts +12 -0
- package/dist/src/shared/adaptive-k.d.ts.map +1 -0
- package/dist/src/shared/adaptive-k.js +8 -0
- package/dist/src/shared/adaptive-k.js.map +1 -0
- package/dist/src/shared/config-validate-adaptive-k.d.ts +3 -0
- package/dist/src/shared/config-validate-adaptive-k.d.ts.map +1 -0
- package/dist/src/shared/config-validate-adaptive-k.js +44 -0
- package/dist/src/shared/config-validate-adaptive-k.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
const SAFE_PATH_PATTERN = /^[a-zA-Z0-9_.\-/]+$/;
|
|
7
|
+
const DEFAULT_CANDIDATE_FILES = [
|
|
8
|
+
"config/domain-terms.yml",
|
|
9
|
+
"config/domain-terms.yaml",
|
|
10
|
+
".kiri/domain-terms.yml",
|
|
11
|
+
".kiri/domain-terms.yaml",
|
|
12
|
+
];
|
|
13
|
+
const TermDefinitionSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
aliases: z.array(z.string().trim().min(1)).optional(),
|
|
16
|
+
files: z.array(z.string().trim().min(1)).optional(),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
function normalizeTermId(raw) {
|
|
20
|
+
const trimmed = raw.trim();
|
|
21
|
+
if (!trimmed) {
|
|
22
|
+
throw new Error("Domain term is empty → YAML内のkey/aliasesを確認してください");
|
|
23
|
+
}
|
|
24
|
+
const decamelized = trimmed.replace(/([a-z0-9])([A-Z])/g, "$1-$2");
|
|
25
|
+
const replaced = decamelized.replace(/[\s_]+/g, "-");
|
|
26
|
+
const collapsed = replaced.replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-");
|
|
27
|
+
const normalized = collapsed.replace(/^-|-$/g, "").toLowerCase();
|
|
28
|
+
if (normalized.length < 2) {
|
|
29
|
+
throw new Error(`Domain term "${raw}" is too short after normalization → 2文字以上の識別子にしてください`);
|
|
30
|
+
}
|
|
31
|
+
return normalized;
|
|
32
|
+
}
|
|
33
|
+
function normalizePath(raw) {
|
|
34
|
+
const trimmed = raw.trim();
|
|
35
|
+
if (!trimmed) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const normalized = trimmed.replace(/^\.\/?/, "").replace(/\\/g, "/");
|
|
39
|
+
if (!SAFE_PATH_PATTERN.test(normalized)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
function splitTermParts(term) {
|
|
45
|
+
const parts = term
|
|
46
|
+
.split(/[-_]/)
|
|
47
|
+
.flatMap((p) => p.split(/(?=[A-Z])/)) // simple camelCase split
|
|
48
|
+
.map((p) => p.trim().toLowerCase())
|
|
49
|
+
.filter((p) => p.length >= 3);
|
|
50
|
+
return Array.from(new Set(parts));
|
|
51
|
+
}
|
|
52
|
+
function buildEntries(input) {
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (const [category, items] of Object.entries(input)) {
|
|
55
|
+
for (const item of items) {
|
|
56
|
+
for (const [canonical, definition] of Object.entries(item)) {
|
|
57
|
+
const normalizedCanonical = normalizeTermId(canonical);
|
|
58
|
+
const schemaResult = TermDefinitionSchema.safeParse(definition);
|
|
59
|
+
if (!schemaResult.success) {
|
|
60
|
+
const message = schemaResult.error.issues.map((issue) => issue.message).join(", ");
|
|
61
|
+
throw new Error(`Invalid domain term definition for ${canonical}: ${message} → YAMLの形式を修正してください`);
|
|
62
|
+
}
|
|
63
|
+
const aliases = schemaResult.data.aliases ?? [];
|
|
64
|
+
const normalizedAliases = new Set();
|
|
65
|
+
normalizedAliases.add(normalizedCanonical);
|
|
66
|
+
for (const alias of aliases) {
|
|
67
|
+
const main = normalizeTermId(alias);
|
|
68
|
+
normalizedAliases.add(main);
|
|
69
|
+
for (const part of splitTermParts(alias)) {
|
|
70
|
+
normalizedAliases.add(part);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// canonicalも分割パーツを追加
|
|
74
|
+
for (const part of splitTermParts(canonical)) {
|
|
75
|
+
normalizedAliases.add(part);
|
|
76
|
+
}
|
|
77
|
+
const files = (schemaResult.data.files ?? [])
|
|
78
|
+
.map((file) => normalizePath(file))
|
|
79
|
+
.filter((value) => Boolean(value));
|
|
80
|
+
entries.push({
|
|
81
|
+
canonical,
|
|
82
|
+
normalizedCanonical,
|
|
83
|
+
aliases,
|
|
84
|
+
normalizedAliases: Array.from(normalizedAliases),
|
|
85
|
+
files,
|
|
86
|
+
category,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
function extractCandidateTerms(text) {
|
|
94
|
+
const matches = text.match(/[A-Za-z0-9_-]+/g) ?? [];
|
|
95
|
+
const candidates = new Set();
|
|
96
|
+
for (const token of matches) {
|
|
97
|
+
try {
|
|
98
|
+
const normalized = normalizeTermId(token);
|
|
99
|
+
candidates.add(normalized);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Array.from(candidates);
|
|
106
|
+
}
|
|
107
|
+
export class DomainTermsDictionary {
|
|
108
|
+
lookup;
|
|
109
|
+
constructor(entries) {
|
|
110
|
+
this.lookup = new Map();
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
this.lookup.set(entry.normalizedCanonical, entry);
|
|
113
|
+
for (const alias of entry.normalizedAliases) {
|
|
114
|
+
this.lookup.set(alias, entry);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
expandFromText(text) {
|
|
119
|
+
const candidates = extractCandidateTerms(text);
|
|
120
|
+
return this.expandCandidates(candidates);
|
|
121
|
+
}
|
|
122
|
+
expandCandidates(candidates) {
|
|
123
|
+
const matchedEntries = new Map();
|
|
124
|
+
for (const candidate of candidates) {
|
|
125
|
+
const entry = this.lookup.get(candidate);
|
|
126
|
+
if (entry) {
|
|
127
|
+
matchedEntries.set(entry.normalizedCanonical, entry);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (matchedEntries.size === 0) {
|
|
131
|
+
return { matched: [], aliases: [], fileHints: [] };
|
|
132
|
+
}
|
|
133
|
+
const aliasKeywords = new Set();
|
|
134
|
+
const fileHints = [];
|
|
135
|
+
for (const entry of matchedEntries.values()) {
|
|
136
|
+
for (const alias of entry.normalizedAliases) {
|
|
137
|
+
aliasKeywords.add(alias);
|
|
138
|
+
}
|
|
139
|
+
if (!aliasKeywords.has(entry.normalizedCanonical)) {
|
|
140
|
+
aliasKeywords.add(entry.normalizedCanonical);
|
|
141
|
+
}
|
|
142
|
+
for (const file of entry.files) {
|
|
143
|
+
if (fileHints.some((hint) => hint.path === file)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
fileHints.push({ path: file, source: entry.canonical });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
matched: Array.from(matchedEntries.keys()),
|
|
151
|
+
aliases: Array.from(aliasKeywords),
|
|
152
|
+
fileHints,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function resolveConfigPath(configPath, cwd = process.cwd()) {
|
|
157
|
+
const explicit = configPath ?? process.env.KIRI_DOMAIN_TERMS_CONFIG;
|
|
158
|
+
if (explicit) {
|
|
159
|
+
const resolved = path.isAbsolute(explicit) ? explicit : path.join(cwd, explicit);
|
|
160
|
+
if (!fs.existsSync(resolved)) {
|
|
161
|
+
throw new Error(`Domain terms config not found at ${resolved} → パスを確認するかファイルを配置してください`);
|
|
162
|
+
}
|
|
163
|
+
return resolved;
|
|
164
|
+
}
|
|
165
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
166
|
+
const candidates = [
|
|
167
|
+
...DEFAULT_CANDIDATE_FILES.map((candidate) => path.join(cwd, candidate)),
|
|
168
|
+
path.join(currentDir, "../../../config/domain-terms.yml"),
|
|
169
|
+
path.join(currentDir, "../../../config/domain-terms.yaml"),
|
|
170
|
+
];
|
|
171
|
+
for (const candidate of candidates) {
|
|
172
|
+
if (fs.existsSync(candidate)) {
|
|
173
|
+
return candidate;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
export function loadDomainTerms(options = {}) {
|
|
179
|
+
const resolvedPath = resolveConfigPath(options.configPath, options.cwd);
|
|
180
|
+
if (!resolvedPath) {
|
|
181
|
+
return new DomainTermsDictionary([]);
|
|
182
|
+
}
|
|
183
|
+
const raw = fs.readFileSync(resolvedPath, "utf8");
|
|
184
|
+
const parsed = parse(raw);
|
|
185
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
186
|
+
throw new Error("Invalid domain terms config: top-level must be a mapping → YAMLを修正してください");
|
|
187
|
+
}
|
|
188
|
+
const dictionary = {};
|
|
189
|
+
for (const [category, value] of Object.entries(parsed)) {
|
|
190
|
+
if (!Array.isArray(value)) {
|
|
191
|
+
throw new Error(`Invalid domain terms config: category "${category}" must be an array of terms → YAMLを修正してください`);
|
|
192
|
+
}
|
|
193
|
+
dictionary[category] = value.map((entry, idx) => {
|
|
194
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
195
|
+
throw new Error(`Invalid domain terms config: entry #${idx + 1} in category "${category}" must be a mapping → YAMLを修正してください`);
|
|
196
|
+
}
|
|
197
|
+
return entry;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const entries = buildEntries(dictionary);
|
|
201
|
+
return new DomainTermsDictionary(entries);
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=domain-terms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain-terms.js","sourceRoot":"","sources":["../../../src/server/domain-terms.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,6 +50,8 @@ export interface ContextBundleParams {
|
|
|
50
50
|
includeTokensEstimate?: boolean;
|
|
51
51
|
metadata_filters?: Record<string, string | string[]>;
|
|
52
52
|
requestId?: string;
|
|
53
|
+
path_prefix?: string;
|
|
54
|
+
category?: string;
|
|
53
55
|
}
|
|
54
56
|
export interface ContextBundleItem {
|
|
55
57
|
path: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/server/handlers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../../src/server/handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { checkFTSSchemaExists } from "../indexer/schema.js";
|
|
4
|
+
import { getAdaptiveK } from "../shared/adaptive-k.js";
|
|
4
5
|
import { generateEmbedding, structuralSimilarity } from "../shared/embedding.js";
|
|
5
6
|
import { encode as encodeGPT, tokenizeText } from "../shared/tokenizer.js";
|
|
6
7
|
import { expandAbbreviations } from "./abbreviations.js";
|
|
@@ -337,6 +338,76 @@ function bucketArtifactHints(hints) {
|
|
|
337
338
|
}
|
|
338
339
|
return buckets;
|
|
339
340
|
}
|
|
341
|
+
function selectDomainPathHints(hints) {
|
|
342
|
+
const selected = [];
|
|
343
|
+
for (const hint of hints) {
|
|
344
|
+
if (!SAFE_PATH_PATTERN.test(hint.path)) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (selected.some((entry) => entry.path === hint.path)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
selected.push(hint);
|
|
351
|
+
if (selected.length >= DOMAIN_PATH_HINT_LIMIT) {
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return selected;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* AdaptiveK用カテゴリ自動検出
|
|
359
|
+
*
|
|
360
|
+
* 優先順位:
|
|
361
|
+
* 1. params.categoryが明示的に指定されている場合はそれを使用
|
|
362
|
+
* 2. profile/artifacts/boost_profileから推論
|
|
363
|
+
*
|
|
364
|
+
* @param params - context_bundleのパラメータ
|
|
365
|
+
* @returns 検出されたカテゴリ(undefined = デフォルトK値を使用)
|
|
366
|
+
*/
|
|
367
|
+
function determineCategory(params) {
|
|
368
|
+
// 明示的に指定されている場合はそれを優先
|
|
369
|
+
if (params.category) {
|
|
370
|
+
return params.category;
|
|
371
|
+
}
|
|
372
|
+
const { profile, boost_profile, artifacts } = params;
|
|
373
|
+
// artifacts.failing_testsがある場合はデバッグ作業
|
|
374
|
+
if (artifacts?.failing_tests && artifacts.failing_tests.length > 0) {
|
|
375
|
+
return "debug";
|
|
376
|
+
}
|
|
377
|
+
// profileからの推論
|
|
378
|
+
if (profile === "testfail") {
|
|
379
|
+
return "testfail";
|
|
380
|
+
}
|
|
381
|
+
if (profile === "bugfix" || profile === "typeerror") {
|
|
382
|
+
return "debug";
|
|
383
|
+
}
|
|
384
|
+
if (profile === "refactor") {
|
|
385
|
+
return "api";
|
|
386
|
+
}
|
|
387
|
+
if (profile === "feature") {
|
|
388
|
+
return "feature";
|
|
389
|
+
}
|
|
390
|
+
// boost_profileからの推論
|
|
391
|
+
if (boost_profile === "docs") {
|
|
392
|
+
return "docs";
|
|
393
|
+
}
|
|
394
|
+
// artifacts.editing_pathからの推論
|
|
395
|
+
if (artifacts?.editing_path) {
|
|
396
|
+
const editingPath = artifacts.editing_path.toLowerCase();
|
|
397
|
+
// テストファイルを編集中 → debug
|
|
398
|
+
if (editingPath.includes(".test.") ||
|
|
399
|
+
editingPath.includes(".spec.") ||
|
|
400
|
+
editingPath.includes("__tests__")) {
|
|
401
|
+
return "debug";
|
|
402
|
+
}
|
|
403
|
+
// ドキュメントを編集中 → docs
|
|
404
|
+
if (editingPath.endsWith(".md") || editingPath.includes("/docs/")) {
|
|
405
|
+
return "docs";
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// 推論できない場合はundefined(kDefaultを使用)
|
|
409
|
+
return undefined;
|
|
410
|
+
}
|
|
340
411
|
function isMissingTableError(error, table) {
|
|
341
412
|
if (!(error instanceof Error)) {
|
|
342
413
|
return false;
|
|
@@ -462,6 +533,7 @@ const CLAMP_SNIPPETS_ENABLED = serverConfig.features.clampSnippets;
|
|
|
462
533
|
const FALLBACK_SNIPPET_WINDOW = serverConfig.features.snippetWindow;
|
|
463
534
|
const MAX_RERANK_LIMIT = 50;
|
|
464
535
|
const MAX_ARTIFACT_HINTS = 8;
|
|
536
|
+
const DOMAIN_PATH_HINT_LIMIT = MAX_ARTIFACT_HINTS;
|
|
465
537
|
const SAFE_PATH_PATTERN = /^[a-zA-Z0-9_.\-/]+$/;
|
|
466
538
|
const HINT_PRIORITY_TEXT_MULTIPLIER = serverConfig.hints.priority.textMultiplier;
|
|
467
539
|
const HINT_PRIORITY_PATH_MULTIPLIER = serverConfig.hints.priority.pathMultiplier;
|
|
@@ -873,6 +945,25 @@ function ensureCandidate(map, filePath) {
|
|
|
873
945
|
}
|
|
874
946
|
return candidate;
|
|
875
947
|
}
|
|
948
|
+
function normalizePathPrefix(rawPrefix) {
|
|
949
|
+
// Normalize, strip leading slashes/dots, and ensure trailing slash for exact prefix match
|
|
950
|
+
const normalized = path.posix.normalize(rawPrefix.replace(/\\/g, "/"));
|
|
951
|
+
const stripped = normalized.replace(/^\.\//, "").replace(/^\/+/, "");
|
|
952
|
+
if (stripped === "" || stripped === ".") {
|
|
953
|
+
return "";
|
|
954
|
+
}
|
|
955
|
+
return stripped.endsWith("/") ? stripped : `${stripped}/`;
|
|
956
|
+
}
|
|
957
|
+
function normalizeFilePath(filePath) {
|
|
958
|
+
return path.posix.normalize(filePath.replace(/\\/g, "/")).replace(/^\/+/, "");
|
|
959
|
+
}
|
|
960
|
+
function pathMatchesPrefix(filePath, normalizedPrefix) {
|
|
961
|
+
if (!normalizedPrefix) {
|
|
962
|
+
return true;
|
|
963
|
+
}
|
|
964
|
+
const normalizedPath = normalizeFilePath(filePath);
|
|
965
|
+
return normalizedPath.startsWith(normalizedPrefix);
|
|
966
|
+
}
|
|
876
967
|
async function expandHintCandidatesForHints(params) {
|
|
877
968
|
const { hintPaths, config } = params;
|
|
878
969
|
if (hintPaths.length === 0 || config.perHintLimit <= 0 || config.dbQueryBudget <= 0) {
|
|
@@ -1575,13 +1666,17 @@ async function safeLinkQuery(db, tableAvailability, sql, params) {
|
|
|
1575
1666
|
throw error;
|
|
1576
1667
|
}
|
|
1577
1668
|
}
|
|
1578
|
-
async function fetchMetadataOnlyCandidates(db, tableAvailability, repoId, filters, limit) {
|
|
1669
|
+
async function fetchMetadataOnlyCandidates(db, tableAvailability, repoId, filters, limit, pathPrefix) {
|
|
1579
1670
|
if (!tableAvailability.hasMetadataTables || filters.length === 0 || limit <= 0) {
|
|
1580
1671
|
return [];
|
|
1581
1672
|
}
|
|
1582
1673
|
const filterClauses = buildMetadataFilterConditions(filters);
|
|
1583
1674
|
const whereClauses = ["f.repo_id = ?"];
|
|
1584
1675
|
const params = [repoId];
|
|
1676
|
+
if (pathPrefix) {
|
|
1677
|
+
whereClauses.push("f.path LIKE ?");
|
|
1678
|
+
params.push(`${pathPrefix}%`);
|
|
1679
|
+
}
|
|
1585
1680
|
for (const clause of filterClauses) {
|
|
1586
1681
|
whereClauses.push(clause.sql);
|
|
1587
1682
|
params.push(...clause.params);
|
|
@@ -1606,13 +1701,17 @@ async function fetchMetadataOnlyCandidates(db, tableAvailability, repoId, filter
|
|
|
1606
1701
|
throw error;
|
|
1607
1702
|
}
|
|
1608
1703
|
}
|
|
1609
|
-
async function fetchMetadataKeywordMatches(db, tableAvailability, repoId, keywords, filters, limit, excludePaths) {
|
|
1704
|
+
async function fetchMetadataKeywordMatches(db, tableAvailability, repoId, keywords, filters, limit, excludePaths, pathPrefix) {
|
|
1610
1705
|
if (!tableAvailability.hasMetadataTables || keywords.length === 0 || limit <= 0) {
|
|
1611
1706
|
return [];
|
|
1612
1707
|
}
|
|
1613
1708
|
const keywordClauses = keywords.map(() => "mk.value ILIKE ?").join(" OR ");
|
|
1614
1709
|
const params = [repoId, ...keywords.map((kw) => `%${kw}%`)];
|
|
1615
1710
|
const whereClauses = ["mk.repo_id = ?", `(${keywordClauses})`];
|
|
1711
|
+
if (pathPrefix) {
|
|
1712
|
+
whereClauses.push("f.path LIKE ?");
|
|
1713
|
+
params.push(`${pathPrefix}%`);
|
|
1714
|
+
}
|
|
1616
1715
|
if (excludePaths.size > 0) {
|
|
1617
1716
|
const placeholders = Array.from(excludePaths)
|
|
1618
1717
|
.map(() => "?")
|
|
@@ -2166,6 +2265,7 @@ export async function filesSearch(context, params) {
|
|
|
2166
2265
|
cleanedQuery = cleanedQuery.trim();
|
|
2167
2266
|
hasTextQuery = cleanedQuery.length > 0;
|
|
2168
2267
|
}
|
|
2268
|
+
const pathPrefix = params.path_prefix && params.path_prefix.length > 0 ? params.path_prefix : undefined;
|
|
2169
2269
|
const metadataValueSeed = metadataFilters
|
|
2170
2270
|
.flatMap((filter) => filter.values)
|
|
2171
2271
|
.map((value) => value.trim())
|
|
@@ -2264,7 +2364,7 @@ export async function filesSearch(context, params) {
|
|
|
2264
2364
|
candidateRows.push(...textRows);
|
|
2265
2365
|
}
|
|
2266
2366
|
if (!hasTextQuery && hasAnyMetadataFilters) {
|
|
2267
|
-
const metadataOnlyRows = await fetchMetadataOnlyCandidates(db, context.tableAvailability, repoId, metadataFilters, limit * 2);
|
|
2367
|
+
const metadataOnlyRows = await fetchMetadataOnlyCandidates(db, context.tableAvailability, repoId, metadataFilters, limit * 2, pathPrefix);
|
|
2268
2368
|
for (const row of metadataOnlyRows) {
|
|
2269
2369
|
row.score = 1 + metadataFilters.length * 0.2;
|
|
2270
2370
|
}
|
|
@@ -2274,7 +2374,7 @@ export async function filesSearch(context, params) {
|
|
|
2274
2374
|
const metadataKeywords = splitQueryWords(cleanedQuery.toLowerCase()).map((kw) => kw.toLowerCase());
|
|
2275
2375
|
if (metadataKeywords.length > 0) {
|
|
2276
2376
|
const excludePaths = new Set(candidateRows.map((row) => row.path));
|
|
2277
|
-
const metadataRows = await fetchMetadataKeywordMatches(db, context.tableAvailability, repoId, metadataKeywords, metadataFilters, limit * 2, excludePaths);
|
|
2377
|
+
const metadataRows = await fetchMetadataKeywordMatches(db, context.tableAvailability, repoId, metadataKeywords, metadataFilters, limit * 2, excludePaths, pathPrefix);
|
|
2278
2378
|
candidateRows.push(...metadataRows);
|
|
2279
2379
|
}
|
|
2280
2380
|
}
|
|
@@ -2647,7 +2747,30 @@ async function contextBundleImpl(context, params) {
|
|
|
2647
2747
|
mergedFilters: metadataFilters,
|
|
2648
2748
|
}));
|
|
2649
2749
|
}
|
|
2650
|
-
|
|
2750
|
+
// AdaptiveK: カテゴリに基づいてK値を動的に調整
|
|
2751
|
+
// 1. params.categoryが明示的に指定されていればそれを使用
|
|
2752
|
+
// 2. そうでなければprofile/artifacts/boost_profileから自動検出
|
|
2753
|
+
const detectedCategory = determineCategory(params);
|
|
2754
|
+
const adaptiveK = getAdaptiveK(detectedCategory, serverConfig.adaptiveK);
|
|
2755
|
+
if (process.env.KIRI_TRACE_ADAPTIVE_K === "1") {
|
|
2756
|
+
console.info("[adaptive-k]", JSON.stringify({
|
|
2757
|
+
detectedCategory,
|
|
2758
|
+
selectedK: adaptiveK,
|
|
2759
|
+
userLimit: params.limit ?? null,
|
|
2760
|
+
enabled: serverConfig.adaptiveK.enabled,
|
|
2761
|
+
}));
|
|
2762
|
+
}
|
|
2763
|
+
const adaptiveLimit = normalizeBundleLimit(adaptiveK);
|
|
2764
|
+
const requestedLimit = normalizeBundleLimit(params.limit);
|
|
2765
|
+
// AdaptiveKが無効な場合はrequestLimitをそのまま使用
|
|
2766
|
+
// AdaptiveKが有効な場合:
|
|
2767
|
+
// - ユーザー指定limitがなければadaptiveKを使用
|
|
2768
|
+
// - ユーザー指定がある場合は小さい方を採用(過剰取得防止)
|
|
2769
|
+
const limit = serverConfig.adaptiveK.enabled
|
|
2770
|
+
? params.limit === undefined
|
|
2771
|
+
? adaptiveLimit
|
|
2772
|
+
: Math.min(requestedLimit, adaptiveLimit)
|
|
2773
|
+
: requestedLimit;
|
|
2651
2774
|
const artifacts = params.artifacts ?? {};
|
|
2652
2775
|
const artifactHints = normalizeArtifactHints(artifacts.hints);
|
|
2653
2776
|
const hintBuckets = bucketArtifactHints(artifactHints);
|
|
@@ -2655,6 +2778,9 @@ async function contextBundleImpl(context, params) {
|
|
|
2655
2778
|
const substringHints = hintBuckets.substringHints;
|
|
2656
2779
|
const includeTokensEstimate = params.includeTokensEstimate === true;
|
|
2657
2780
|
const isCompact = params.compact === true;
|
|
2781
|
+
const pathPrefix = params.path_prefix && params.path_prefix.length > 0
|
|
2782
|
+
? normalizePathPrefix(params.path_prefix)
|
|
2783
|
+
: undefined;
|
|
2658
2784
|
// 項目2: トークンバジェット保護警告
|
|
2659
2785
|
// 大量データ+非コンパクトモード+トークン推定なしの場合に警告
|
|
2660
2786
|
// リクエストごとに警告(warnForRequestを使用)
|
|
@@ -2684,6 +2810,13 @@ async function contextBundleImpl(context, params) {
|
|
|
2684
2810
|
.join(" ");
|
|
2685
2811
|
keywordSources.push(filterSeed);
|
|
2686
2812
|
}
|
|
2813
|
+
const baseSeed = keywordSources.join(" ");
|
|
2814
|
+
const domainExpansion = process.env.KIRI_ENABLE_DOMAIN_TERMS === "1"
|
|
2815
|
+
? context.services.domainTerms.expandFromText(baseSeed)
|
|
2816
|
+
: { matched: [], aliases: [], fileHints: [] };
|
|
2817
|
+
if (domainExpansion.aliases.length > 0) {
|
|
2818
|
+
keywordSources.push(domainExpansion.aliases.join(" "));
|
|
2819
|
+
}
|
|
2687
2820
|
const semanticSeed = keywordSources.join(" ");
|
|
2688
2821
|
const queryEmbedding = generateEmbedding(semanticSeed)?.values ?? null;
|
|
2689
2822
|
const extractedTerms = extractKeywords(semanticSeed);
|
|
@@ -2717,6 +2850,12 @@ async function contextBundleImpl(context, params) {
|
|
|
2717
2850
|
.join(" OR ");
|
|
2718
2851
|
// DEBUG: Log SQL query parameters for troubleshooting
|
|
2719
2852
|
traceSearch(`Executing phrase match query with repo_id=${repoId}, phrases=${JSON.stringify(extractedTerms.phrases)}`);
|
|
2853
|
+
const phraseWhereClauses = ["f.repo_id = ?", "f.is_binary = FALSE", `(${phrasePlaceholders})`];
|
|
2854
|
+
const phraseParams = [repoId, ...extractedTerms.phrases];
|
|
2855
|
+
if (pathPrefix) {
|
|
2856
|
+
phraseWhereClauses.push("f.path LIKE ?");
|
|
2857
|
+
phraseParams.push(`${pathPrefix}%`);
|
|
2858
|
+
}
|
|
2720
2859
|
const rows = await db.all(`
|
|
2721
2860
|
SELECT f.path, f.lang, f.ext, f.is_binary, b.content, fe.vector_json, fe.dims AS vector_dims
|
|
2722
2861
|
FROM file f
|
|
@@ -2724,12 +2863,11 @@ async function contextBundleImpl(context, params) {
|
|
|
2724
2863
|
LEFT JOIN file_embedding fe
|
|
2725
2864
|
ON fe.repo_id = f.repo_id
|
|
2726
2865
|
AND fe.path = f.path
|
|
2727
|
-
WHERE
|
|
2728
|
-
AND
|
|
2729
|
-
AND (${phrasePlaceholders})
|
|
2866
|
+
WHERE ${phraseWhereClauses.join(`
|
|
2867
|
+
AND `)}
|
|
2730
2868
|
ORDER BY f.path
|
|
2731
2869
|
LIMIT ?
|
|
2732
|
-
`, [
|
|
2870
|
+
`, [...phraseParams, MAX_MATCHES_PER_KEYWORD * extractedTerms.phrases.length]);
|
|
2733
2871
|
// DEBUG: Log returned paths and verify they match expected repo_id
|
|
2734
2872
|
if (rows.length > 0) {
|
|
2735
2873
|
traceSearch(`Phrase match returned ${rows.length} rows. Sample paths: ${rows
|
|
@@ -2790,6 +2928,16 @@ async function contextBundleImpl(context, params) {
|
|
|
2790
2928
|
const keywordPlaceholders = extractedTerms.keywords
|
|
2791
2929
|
.map(() => "b.content ILIKE '%' || ? || '%'")
|
|
2792
2930
|
.join(" OR ");
|
|
2931
|
+
const keywordWhereClauses = [
|
|
2932
|
+
"f.repo_id = ?",
|
|
2933
|
+
"f.is_binary = FALSE",
|
|
2934
|
+
`(${keywordPlaceholders})`,
|
|
2935
|
+
];
|
|
2936
|
+
const keywordParams = [repoId, ...extractedTerms.keywords];
|
|
2937
|
+
if (pathPrefix) {
|
|
2938
|
+
keywordWhereClauses.push("f.path LIKE ?");
|
|
2939
|
+
keywordParams.push(`${pathPrefix}%`);
|
|
2940
|
+
}
|
|
2793
2941
|
const rows = await db.all(`
|
|
2794
2942
|
SELECT f.path, f.lang, f.ext, f.is_binary, b.content, fe.vector_json, fe.dims AS vector_dims
|
|
2795
2943
|
FROM file f
|
|
@@ -2797,12 +2945,11 @@ async function contextBundleImpl(context, params) {
|
|
|
2797
2945
|
LEFT JOIN file_embedding fe
|
|
2798
2946
|
ON fe.repo_id = f.repo_id
|
|
2799
2947
|
AND fe.path = f.path
|
|
2800
|
-
WHERE
|
|
2801
|
-
AND
|
|
2802
|
-
AND (${keywordPlaceholders})
|
|
2948
|
+
WHERE ${keywordWhereClauses.join(`
|
|
2949
|
+
AND `)}
|
|
2803
2950
|
ORDER BY f.path
|
|
2804
2951
|
LIMIT ?
|
|
2805
|
-
`, [
|
|
2952
|
+
`, [...keywordParams, MAX_MATCHES_PER_KEYWORD * extractedTerms.keywords.length]);
|
|
2806
2953
|
for (const row of rows) {
|
|
2807
2954
|
if (row.content === null) {
|
|
2808
2955
|
continue;
|
|
@@ -2943,9 +3090,15 @@ async function contextBundleImpl(context, params) {
|
|
|
2943
3090
|
sourceHint: hintPath,
|
|
2944
3091
|
origin: "artifact",
|
|
2945
3092
|
}));
|
|
3093
|
+
const domainPathTargets = selectDomainPathHints(domainExpansion.fileHints).map((hint) => ({
|
|
3094
|
+
path: hint.path,
|
|
3095
|
+
sourceHint: hint.source,
|
|
3096
|
+
origin: "dictionary",
|
|
3097
|
+
}));
|
|
2946
3098
|
const dictionaryPathTargets = await fetchDictionaryPathHints(db, context.tableAvailability, repoId, substringHints, HINT_DICTIONARY_LIMIT);
|
|
2947
3099
|
const { list: resolvedPathHintTargets, meta: hintSeedMeta } = createHintSeedMeta([
|
|
2948
3100
|
...artifactPathTargets,
|
|
3101
|
+
...domainPathTargets,
|
|
2949
3102
|
...dictionaryPathTargets,
|
|
2950
3103
|
]);
|
|
2951
3104
|
if (resolvedPathHintTargets.length > 0) {
|
|
@@ -3040,6 +3193,9 @@ async function contextBundleImpl(context, params) {
|
|
|
3040
3193
|
const materializeCandidates = async () => {
|
|
3041
3194
|
const result = [];
|
|
3042
3195
|
for (const candidate of candidates.values()) {
|
|
3196
|
+
if (!pathMatchesPrefix(candidate.path, pathPrefix)) {
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3043
3199
|
if (isSuppressedPath(candidate.path)) {
|
|
3044
3200
|
continue;
|
|
3045
3201
|
}
|
|
@@ -3073,7 +3229,7 @@ async function contextBundleImpl(context, params) {
|
|
|
3073
3229
|
if (!hasAnyMetadataFilters) {
|
|
3074
3230
|
return;
|
|
3075
3231
|
}
|
|
3076
|
-
const metadataRows = await fetchMetadataOnlyCandidates(db, context.tableAvailability, repoId, metadataFilters, limit * 2);
|
|
3232
|
+
const metadataRows = await fetchMetadataOnlyCandidates(db, context.tableAvailability, repoId, metadataFilters, limit * 2, pathPrefix);
|
|
3077
3233
|
if (metadataRows.length === 0) {
|
|
3078
3234
|
return;
|
|
3079
3235
|
}
|