@gmickel/gno 1.25.1 → 1.27.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 +12 -5
- package/assets/skill/SKILL.md +37 -17
- package/browser-extension/artifacts/{gno-browser-clipper-v1.25.1.zip → gno-browser-clipper-v1.27.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.27.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +161 -6
- package/spec/db/schema.sql +1 -1
- package/spec/evals-agentic.md +17 -0
- package/spec/mcp.md +25 -6
- package/spec/output-schemas/ask.schema.json +3 -0
- package/spec/output-schemas/project-profile-apply.schema.json +209 -0
- package/spec/output-schemas/project-profile-command.schema.json +160 -0
- package/spec/output-schemas/query-diagnose.schema.json +68 -5
- package/spec/output-schemas/search-results.schema.json +87 -1
- package/spec/output-schemas/setup-profile-result.schema.json +87 -0
- package/spec/output-schemas/status.schema.json +24 -0
- package/spec/project-profile.schema.json +303 -0
- package/src/app/context-runtime-contract.ts +4 -1
- package/src/app/context-runtime-types.ts +2 -0
- package/src/app/context-runtime.ts +26 -0
- package/src/app/verified-ask.ts +6 -1
- package/src/cli/commands/ask.ts +8 -1
- package/src/cli/commands/collection/add.ts +39 -45
- package/src/cli/commands/collection/remove.ts +28 -28
- package/src/cli/commands/collection/rename.ts +55 -73
- package/src/cli/commands/context/add.ts +37 -26
- package/src/cli/commands/context/rm.ts +47 -20
- package/src/cli/commands/init.ts +55 -125
- package/src/cli/commands/models/use.ts +43 -38
- package/src/cli/commands/profile-apply.ts +334 -0
- package/src/cli/commands/profile.ts +409 -0
- package/src/cli/commands/query.ts +6 -3
- package/src/cli/commands/search.ts +6 -1
- package/src/cli/commands/setup-activation.ts +205 -54
- package/src/cli/commands/setup-profile.ts +223 -0
- package/src/cli/commands/setup.ts +3 -0
- package/src/cli/commands/status.ts +43 -7
- package/src/cli/program.ts +112 -9
- package/src/config/content-types.ts +82 -0
- package/src/config/index.ts +11 -0
- package/src/config/project-profile.ts +374 -0
- package/src/config/saver.ts +16 -7
- package/src/config/types.ts +53 -2
- package/src/core/config-mutation.ts +138 -76
- package/src/core/config-write-lock.ts +89 -0
- package/src/core/context-compiler.ts +38 -1
- package/src/core/context-identity.ts +16 -0
- package/src/core/context-resolver.ts +2 -12
- package/src/core/folder-setup-planning.ts +6 -21
- package/src/core/folder-setup.ts +30 -2
- package/src/core/path-rules.ts +53 -0
- package/src/core/project-affinity-surface.ts +102 -7
- package/src/core/project-profile-apply-state.ts +268 -0
- package/src/core/project-profile-apply-validation.ts +95 -0
- package/src/core/project-profile-apply.ts +408 -0
- package/src/core/project-profile-canonical.ts +71 -0
- package/src/core/project-profile-diff.ts +302 -0
- package/src/core/project-profile-discovery.ts +519 -0
- package/src/core/project-profile-file.ts +37 -0
- package/src/core/project-profile-parser.ts +98 -0
- package/src/core/project-profile.ts +490 -0
- package/src/core/retrieval-replay-candidate.ts +6 -1
- package/src/ingestion/sync-options.ts +6 -2
- package/src/ingestion/sync.ts +21 -29
- package/src/ingestion/types.ts +1 -1
- package/src/ingestion/walker.ts +85 -44
- package/src/llm/cache.ts +21 -0
- package/src/mcp/tools/ask.ts +1 -0
- package/src/mcp/tools/index.ts +4 -0
- package/src/mcp/tools/query.ts +4 -2
- package/src/mcp/tools/search.ts +3 -0
- package/src/mcp/tools/status.ts +4 -0
- package/src/pipeline/content-type-boost.ts +264 -0
- package/src/pipeline/diagnose.ts +46 -19
- package/src/pipeline/explain.ts +15 -2
- package/src/pipeline/hybrid.ts +170 -74
- package/src/pipeline/rerank.ts +45 -15
- package/src/pipeline/search.ts +29 -11
- package/src/pipeline/types.ts +13 -4
- package/src/pipeline/vsearch.ts +30 -10
- package/src/sdk/client.ts +19 -3
- package/src/sdk/index.ts +1 -0
- package/src/sdk/types.ts +21 -5
- package/src/serve/config-sync.ts +2 -2
- package/src/serve/resident-runtime.ts +1 -0
- package/src/serve/routes/api.ts +18 -3
- package/src/serve/status-model.ts +2 -0
- package/src/serve/status.ts +4 -0
- package/src/store/migrations/021-multi-context-identity.ts +37 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +86 -0
- package/src/store/types.ts +3 -2
- package/browser-extension/artifacts/gno-browser-clipper-v1.25.1.zip.sha256 +0 -1
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
* @module src/cli/commands/status
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { ContentTypeBoostStatus } from "../../config/content-types";
|
|
8
9
|
import type { ActivationStatus } from "../../core/activation-status";
|
|
9
10
|
import type { IndexStatus } from "../../store/types";
|
|
10
11
|
|
|
11
12
|
import { getIndexDbPath, getModelsCachePath } from "../../app/constants";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
buildContentTypeBoostStatus,
|
|
15
|
+
getConfigPaths,
|
|
16
|
+
isInitialized,
|
|
17
|
+
loadConfig,
|
|
18
|
+
} from "../../config";
|
|
13
19
|
import { isConnectorActivationComplete } from "../../core/activation-connector-health";
|
|
14
20
|
import { buildActivationStatus } from "../../core/activation-status";
|
|
15
21
|
import { ModelCache } from "../../llm/cache";
|
|
@@ -36,7 +42,12 @@ export interface StatusOptions {
|
|
|
36
42
|
* Result of status command.
|
|
37
43
|
*/
|
|
38
44
|
export type StatusResult =
|
|
39
|
-
| {
|
|
45
|
+
| {
|
|
46
|
+
success: true;
|
|
47
|
+
status: IndexStatus;
|
|
48
|
+
activation: ActivationStatus;
|
|
49
|
+
contentTypeBoost: ContentTypeBoostStatus;
|
|
50
|
+
}
|
|
40
51
|
| { success: false; error: string };
|
|
41
52
|
|
|
42
53
|
function connectorProjectionLine(activation: ActivationStatus): string | null {
|
|
@@ -63,7 +74,8 @@ function isStatusHealthy(
|
|
|
63
74
|
*/
|
|
64
75
|
function formatTerminal(
|
|
65
76
|
indexStatus: IndexStatus,
|
|
66
|
-
activation: ActivationStatus
|
|
77
|
+
activation: ActivationStatus,
|
|
78
|
+
contentTypeBoost: ContentTypeBoostStatus
|
|
67
79
|
): string {
|
|
68
80
|
const lines: string[] = [];
|
|
69
81
|
|
|
@@ -101,6 +113,11 @@ function formatTerminal(
|
|
|
101
113
|
lines.push(`Last updated: ${indexStatus.lastUpdatedAt}`);
|
|
102
114
|
}
|
|
103
115
|
|
|
116
|
+
lines.push(
|
|
117
|
+
`Content-type boosts: ${contentTypeBoost.rules.length ? contentTypeBoost.rules.map((rule) => `${rule.id}=${rule.searchBoost}`).join(", ") : "none"}`
|
|
118
|
+
);
|
|
119
|
+
lines.push(`Ranking fingerprint: ${contentTypeBoost.rulesFingerprint}`);
|
|
120
|
+
|
|
104
121
|
lines.push(
|
|
105
122
|
`Health: ${isStatusHealthy(indexStatus, activation) ? "OK" : "DEGRADED"}`
|
|
106
123
|
);
|
|
@@ -138,7 +155,8 @@ function formatTerminal(
|
|
|
138
155
|
*/
|
|
139
156
|
function formatMarkdown(
|
|
140
157
|
indexStatus: IndexStatus,
|
|
141
|
-
activation: ActivationStatus
|
|
158
|
+
activation: ActivationStatus,
|
|
159
|
+
contentTypeBoost: ContentTypeBoostStatus
|
|
142
160
|
): string {
|
|
143
161
|
const lines: string[] = [];
|
|
144
162
|
|
|
@@ -174,6 +192,10 @@ function formatMarkdown(
|
|
|
174
192
|
if (indexStatus.lastUpdatedAt) {
|
|
175
193
|
lines.push(`- **Last updated**: ${indexStatus.lastUpdatedAt}`);
|
|
176
194
|
}
|
|
195
|
+
lines.push(
|
|
196
|
+
`- **Content-type boosts**: ${contentTypeBoost.rules.length ? contentTypeBoost.rules.map((rule) => `${rule.id}=${rule.searchBoost}`).join(", ") : "none"}`
|
|
197
|
+
);
|
|
198
|
+
lines.push(`- **Ranking fingerprint**: ${contentTypeBoost.rulesFingerprint}`);
|
|
177
199
|
|
|
178
200
|
lines.push("");
|
|
179
201
|
lines.push("## Lexical activation");
|
|
@@ -254,7 +276,12 @@ export async function status(
|
|
|
254
276
|
}
|
|
255
277
|
);
|
|
256
278
|
|
|
257
|
-
return {
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
status: statusResult.value,
|
|
282
|
+
activation,
|
|
283
|
+
contentTypeBoost: buildContentTypeBoostStatus(config.contentTypes ?? []),
|
|
284
|
+
};
|
|
258
285
|
} finally {
|
|
259
286
|
await store.close();
|
|
260
287
|
}
|
|
@@ -294,6 +321,7 @@ export function formatStatus(
|
|
|
294
321
|
embeddingBacklog: s.embeddingBacklog,
|
|
295
322
|
lastUpdated: s.lastUpdatedAt,
|
|
296
323
|
healthy: isStatusHealthy(s, result.activation),
|
|
324
|
+
contentTypeBoost: result.contentTypeBoost,
|
|
297
325
|
activation: result.activation,
|
|
298
326
|
},
|
|
299
327
|
null,
|
|
@@ -302,8 +330,16 @@ export function formatStatus(
|
|
|
302
330
|
}
|
|
303
331
|
|
|
304
332
|
if (options.md) {
|
|
305
|
-
return formatMarkdown(
|
|
333
|
+
return formatMarkdown(
|
|
334
|
+
result.status,
|
|
335
|
+
result.activation,
|
|
336
|
+
result.contentTypeBoost
|
|
337
|
+
);
|
|
306
338
|
}
|
|
307
339
|
|
|
308
|
-
return formatTerminal(
|
|
340
|
+
return formatTerminal(
|
|
341
|
+
result.status,
|
|
342
|
+
result.activation,
|
|
343
|
+
result.contentTypeBoost
|
|
344
|
+
);
|
|
309
345
|
}
|
package/src/cli/program.ts
CHANGED
|
@@ -1151,6 +1151,7 @@ function wireSearchCommands(program: Command): void {
|
|
|
1151
1151
|
.option("--context-budget-bytes <num>", "verified Context byte budget")
|
|
1152
1152
|
.option("--min-score <score>", "minimum retrieval score (0-1)")
|
|
1153
1153
|
.option("--graph", "include bounded graph expansion")
|
|
1154
|
+
.option("--explain", "include retrieval scoring explanation")
|
|
1154
1155
|
.option(
|
|
1155
1156
|
"--project-root <path>",
|
|
1156
1157
|
"trusted project root (repeatable; replaces cwd affinity)",
|
|
@@ -1259,6 +1260,7 @@ function wireSearchCommands(program: Command): void {
|
|
|
1259
1260
|
noExpand: depthPolicy.noExpand,
|
|
1260
1261
|
noRerank: depthPolicy.noRerank,
|
|
1261
1262
|
candidateLimit: depthPolicy.candidateLimit,
|
|
1263
|
+
explain: Boolean(cmdOpts.explain),
|
|
1262
1264
|
// Per spec: --answer defaults to false, --no-answer forces retrieval-only
|
|
1263
1265
|
// Commander creates separate cmdOpts.noAnswer for --no-answer flag
|
|
1264
1266
|
answer: Boolean(cmdOpts.answer),
|
|
@@ -1291,6 +1293,77 @@ function wireSearchCommands(program: Command): void {
|
|
|
1291
1293
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1292
1294
|
|
|
1293
1295
|
function wireOnboardingCommands(program: Command): void {
|
|
1296
|
+
const profileCmd = program
|
|
1297
|
+
.command("profile")
|
|
1298
|
+
.description("Inspect a project-local retrieval profile");
|
|
1299
|
+
|
|
1300
|
+
const wireProfileRead = (
|
|
1301
|
+
command: "check" | "show" | "diff",
|
|
1302
|
+
description: string
|
|
1303
|
+
): void => {
|
|
1304
|
+
profileCmd
|
|
1305
|
+
.command(`${command} [path]`)
|
|
1306
|
+
.description(description)
|
|
1307
|
+
.option("--json", "JSON output")
|
|
1308
|
+
.action(
|
|
1309
|
+
async (path: string | undefined, cmdOpts: Record<string, unknown>) => {
|
|
1310
|
+
const globals = getGlobals();
|
|
1311
|
+
const json = Boolean(cmdOpts.json) || globals.json;
|
|
1312
|
+
const { formatProjectProfileResult, runProjectProfileCommand } =
|
|
1313
|
+
await import("./commands/profile");
|
|
1314
|
+
const outcome = await runProjectProfileCommand({
|
|
1315
|
+
command,
|
|
1316
|
+
path,
|
|
1317
|
+
configPath: globals.config,
|
|
1318
|
+
offline: globals.offline,
|
|
1319
|
+
});
|
|
1320
|
+
process.stdout.write(
|
|
1321
|
+
`${formatProjectProfileResult(outcome.result, { json })}\n`
|
|
1322
|
+
);
|
|
1323
|
+
if (outcome.exitCode !== 0) {
|
|
1324
|
+
throw new CliError(
|
|
1325
|
+
outcome.exitCode === 1 ? "VALIDATION" : "RUNTIME",
|
|
1326
|
+
"Project profile inspection failed",
|
|
1327
|
+
{ silent: true }
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
);
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
wireProfileRead("check", "Validate a project-local retrieval profile");
|
|
1335
|
+
wireProfileRead("show", "Show normalized project-local retrieval state");
|
|
1336
|
+
wireProfileRead("diff", "Compare a project profile with local config");
|
|
1337
|
+
profileCmd
|
|
1338
|
+
.command("apply [path]")
|
|
1339
|
+
.description("Apply a project profile without implicit deletion")
|
|
1340
|
+
.option("--json", "JSON output")
|
|
1341
|
+
.action(
|
|
1342
|
+
async (path: string | undefined, cmdOpts: Record<string, unknown>) => {
|
|
1343
|
+
const globals = getGlobals();
|
|
1344
|
+
const json = Boolean(cmdOpts.json) || globals.json;
|
|
1345
|
+
const {
|
|
1346
|
+
formatProjectProfileApplyResult,
|
|
1347
|
+
runProjectProfileApplyCommand,
|
|
1348
|
+
} = await import("./commands/profile-apply");
|
|
1349
|
+
const outcome = await runProjectProfileApplyCommand({
|
|
1350
|
+
path,
|
|
1351
|
+
configPath: globals.config,
|
|
1352
|
+
indexName: globals.index,
|
|
1353
|
+
});
|
|
1354
|
+
process.stdout.write(
|
|
1355
|
+
`${formatProjectProfileApplyResult(outcome.result, { json })}\n`
|
|
1356
|
+
);
|
|
1357
|
+
if (outcome.exitCode !== 0) {
|
|
1358
|
+
throw new CliError(
|
|
1359
|
+
outcome.exitCode === 1 ? "VALIDATION" : "RUNTIME",
|
|
1360
|
+
"Project profile apply failed",
|
|
1361
|
+
{ silent: true }
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
);
|
|
1366
|
+
|
|
1294
1367
|
// init - Initialize GNO
|
|
1295
1368
|
program
|
|
1296
1369
|
.command("init [path]")
|
|
@@ -1320,6 +1393,7 @@ function wireOnboardingCommands(program: Command): void {
|
|
|
1320
1393
|
| undefined,
|
|
1321
1394
|
language: cmdOpts.language as string | undefined,
|
|
1322
1395
|
yes: globals.yes,
|
|
1396
|
+
configPath: globals.config,
|
|
1323
1397
|
});
|
|
1324
1398
|
|
|
1325
1399
|
if (!result.success) {
|
|
@@ -1367,13 +1441,20 @@ function wireOnboardingCommands(program: Command): void {
|
|
|
1367
1441
|
collectRepeatableValue,
|
|
1368
1442
|
[]
|
|
1369
1443
|
)
|
|
1444
|
+
.option(
|
|
1445
|
+
"--apply-profile",
|
|
1446
|
+
"apply a valid project profile before lexical setup"
|
|
1447
|
+
)
|
|
1370
1448
|
.option("--no-semantic", "skip background semantic indexing")
|
|
1371
1449
|
.option("--json", "JSON output")
|
|
1372
1450
|
.action(async (folder: string, cmdOpts: Record<string, unknown>) => {
|
|
1373
1451
|
const globals = getGlobals();
|
|
1374
1452
|
const json = Boolean(cmdOpts.json) || globals.json;
|
|
1375
|
-
const {
|
|
1376
|
-
|
|
1453
|
+
const {
|
|
1454
|
+
formatSetupOutputResult,
|
|
1455
|
+
formatSetupProfileAdvisory,
|
|
1456
|
+
setupWithActivation,
|
|
1457
|
+
} = await import("./commands/setup-activation");
|
|
1377
1458
|
const exclusions = cmdOpts.exclude as string[];
|
|
1378
1459
|
const outcome = await setupWithActivation({
|
|
1379
1460
|
folder,
|
|
@@ -1381,6 +1462,7 @@ function wireOnboardingCommands(program: Command): void {
|
|
|
1381
1462
|
exclude: exclusions.length > 0 ? exclusions : undefined,
|
|
1382
1463
|
authorizeSecretRisk: Boolean(cmdOpts.authorizeSecretRisk),
|
|
1383
1464
|
connectorIds: cmdOpts.connector as string[],
|
|
1465
|
+
applyProfile: Boolean(cmdOpts.applyProfile),
|
|
1384
1466
|
semantic: cmdOpts.semantic !== false,
|
|
1385
1467
|
indexName: globals.index,
|
|
1386
1468
|
configPath: globals.config,
|
|
@@ -1391,6 +1473,17 @@ function wireOnboardingCommands(program: Command): void {
|
|
|
1391
1473
|
progress: (stage) => {
|
|
1392
1474
|
process.stderr.write(`setup: ${stage}\n`);
|
|
1393
1475
|
},
|
|
1476
|
+
onProfileAdvisory: (profile) => {
|
|
1477
|
+
if (json || globals.quiet) return;
|
|
1478
|
+
const advisory = formatSetupProfileAdvisory(profile, {
|
|
1479
|
+
applyRequested: Boolean(cmdOpts.applyProfile),
|
|
1480
|
+
});
|
|
1481
|
+
if (advisory) process.stderr.write(`${advisory}\n`);
|
|
1482
|
+
},
|
|
1483
|
+
onProfileApply: (profile) => {
|
|
1484
|
+
if (json || globals.quiet) return;
|
|
1485
|
+
process.stderr.write(`setup: project profile ${profile.status}\n`);
|
|
1486
|
+
},
|
|
1394
1487
|
});
|
|
1395
1488
|
const output = formatSetupOutputResult(outcome.result, { json });
|
|
1396
1489
|
if (json || outcome.exitCode === 0) {
|
|
@@ -1958,6 +2051,7 @@ function wireManagementCommands(program: Command): void {
|
|
|
1958
2051
|
include: cmdOpts.include as string | undefined,
|
|
1959
2052
|
exclude: cmdOpts.exclude as string | undefined,
|
|
1960
2053
|
update: cmdOpts.update as string | undefined,
|
|
2054
|
+
configPath: getGlobals().config,
|
|
1961
2055
|
});
|
|
1962
2056
|
});
|
|
1963
2057
|
|
|
@@ -1982,7 +2076,7 @@ function wireManagementCommands(program: Command): void {
|
|
|
1982
2076
|
.description("Remove a collection")
|
|
1983
2077
|
.action(async (name: string) => {
|
|
1984
2078
|
const { collectionRemove } = await import("./commands/collection");
|
|
1985
|
-
await collectionRemove(name);
|
|
2079
|
+
await collectionRemove(name, { configPath: getGlobals().config });
|
|
1986
2080
|
});
|
|
1987
2081
|
|
|
1988
2082
|
collectionCmd
|
|
@@ -1990,7 +2084,9 @@ function wireManagementCommands(program: Command): void {
|
|
|
1990
2084
|
.description("Rename a collection")
|
|
1991
2085
|
.action(async (oldName: string, newName: string) => {
|
|
1992
2086
|
const { collectionRename } = await import("./commands/collection");
|
|
1993
|
-
await collectionRename(oldName, newName
|
|
2087
|
+
await collectionRename(oldName, newName, {
|
|
2088
|
+
configPath: getGlobals().config,
|
|
2089
|
+
});
|
|
1994
2090
|
});
|
|
1995
2091
|
|
|
1996
2092
|
collectionCmd
|
|
@@ -2017,9 +2113,11 @@ function wireManagementCommands(program: Command): void {
|
|
|
2017
2113
|
.description("Add context metadata for a scope")
|
|
2018
2114
|
.action(async (scope: string, text: string) => {
|
|
2019
2115
|
const { contextAdd } = await import("./commands/context");
|
|
2020
|
-
const exitCode = await contextAdd(scope, text
|
|
2116
|
+
const exitCode = await contextAdd(scope, text, {
|
|
2117
|
+
configPath: getGlobals().config,
|
|
2118
|
+
});
|
|
2021
2119
|
if (exitCode !== 0) {
|
|
2022
|
-
throw new CliError("
|
|
2120
|
+
throw new CliError("VALIDATION", "Failed to add context");
|
|
2023
2121
|
}
|
|
2024
2122
|
});
|
|
2025
2123
|
|
|
@@ -2301,11 +2399,16 @@ function wireManagementCommands(program: Command): void {
|
|
|
2301
2399
|
});
|
|
2302
2400
|
|
|
2303
2401
|
contextCmd
|
|
2304
|
-
.command("rm <
|
|
2402
|
+
.command("rm <scope> [text]")
|
|
2305
2403
|
.description("Remove context item")
|
|
2306
|
-
.action(async (
|
|
2404
|
+
.action(async (scope: string, text?: string) => {
|
|
2307
2405
|
const { contextRm } = await import("./commands/context");
|
|
2308
|
-
await contextRm(
|
|
2406
|
+
const exitCode = await contextRm(scope, text, {
|
|
2407
|
+
configPath: getGlobals().config,
|
|
2408
|
+
});
|
|
2409
|
+
if (exitCode !== 0) {
|
|
2410
|
+
throw new CliError("VALIDATION", "Failed to remove context");
|
|
2411
|
+
}
|
|
2309
2412
|
});
|
|
2310
2413
|
|
|
2311
2414
|
// models subcommands
|
|
@@ -8,6 +8,7 @@ import type { NotePresetId } from "../core/note-presets";
|
|
|
8
8
|
import type { Config, ContentTypeConfig } from "./types";
|
|
9
9
|
|
|
10
10
|
import { NOTE_PRESETS } from "../core/note-presets";
|
|
11
|
+
import { CONTENT_TYPE_SEARCH_BOOST_NEUTRAL } from "./types";
|
|
11
12
|
|
|
12
13
|
export type ConfigWarningCode =
|
|
13
14
|
| "UNKNOWN_CONTENT_TYPE_PRESET"
|
|
@@ -21,6 +22,7 @@ export interface ConfigWarning {
|
|
|
21
22
|
|
|
22
23
|
export interface NormalizedContentTypeRule extends ContentTypeConfig {
|
|
23
24
|
preset: NotePresetId;
|
|
25
|
+
searchBoost: number;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface ContentTypeNormalizationResult {
|
|
@@ -33,6 +35,14 @@ export interface ConfigNormalizationResult {
|
|
|
33
35
|
warnings: ConfigWarning[];
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
export interface ContentTypeBoostStatus {
|
|
39
|
+
rulesFingerprint: string;
|
|
40
|
+
rules: Array<{
|
|
41
|
+
id: string;
|
|
42
|
+
searchBoost: number;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
const NOTE_PRESET_IDS = new Set<NotePresetId>(
|
|
37
47
|
NOTE_PRESETS.map((preset) => preset.id)
|
|
38
48
|
);
|
|
@@ -48,6 +58,10 @@ function isNotePresetId(value: string): value is NotePresetId {
|
|
|
48
58
|
return NOTE_PRESET_IDS.has(value as NotePresetId);
|
|
49
59
|
}
|
|
50
60
|
|
|
61
|
+
function normalizeSearchBoost(value: number | undefined): number {
|
|
62
|
+
return value ?? CONTENT_TYPE_SEARCH_BOOST_NEUTRAL;
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
export function normalizeContentTypes(
|
|
52
66
|
contentTypes: ContentTypeConfig[]
|
|
53
67
|
): ContentTypeNormalizationResult {
|
|
@@ -89,6 +103,7 @@ export function normalizeContentTypes(
|
|
|
89
103
|
graphHints: contentType.graphHints
|
|
90
104
|
? contentType.graphHints.map(normalizeGraphHint).filter(Boolean)
|
|
91
105
|
: undefined,
|
|
106
|
+
searchBoost: normalizeSearchBoost(contentType.searchBoost),
|
|
92
107
|
});
|
|
93
108
|
continue;
|
|
94
109
|
}
|
|
@@ -100,6 +115,7 @@ export function normalizeContentTypes(
|
|
|
100
115
|
graphHints: contentType.graphHints
|
|
101
116
|
? contentType.graphHints.map(normalizeGraphHint).filter(Boolean)
|
|
102
117
|
: undefined,
|
|
118
|
+
searchBoost: normalizeSearchBoost(contentType.searchBoost),
|
|
103
119
|
});
|
|
104
120
|
}
|
|
105
121
|
|
|
@@ -112,6 +128,34 @@ export function normalizeContentTypes(
|
|
|
112
128
|
return { rules, warnings };
|
|
113
129
|
}
|
|
114
130
|
|
|
131
|
+
export interface ContentTypeRuleResolution {
|
|
132
|
+
rule: NormalizedContentTypeRule;
|
|
133
|
+
source: "configured-id" | "prefix";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Resolve one canonical configured rule. A configured frontmatter type wins;
|
|
138
|
+
* otherwise normalized rule order provides longest-prefix matching. Arbitrary
|
|
139
|
+
* category text is intentionally not an input and boosts never stack.
|
|
140
|
+
*/
|
|
141
|
+
export function resolveContentTypeRule(
|
|
142
|
+
configuredId: string | undefined,
|
|
143
|
+
relativePath: string,
|
|
144
|
+
rules: NormalizedContentTypeRule[]
|
|
145
|
+
): ContentTypeRuleResolution | undefined {
|
|
146
|
+
if (configuredId) {
|
|
147
|
+
const configuredRule = rules.find((rule) => rule.id === configuredId);
|
|
148
|
+
if (configuredRule) {
|
|
149
|
+
return { rule: configuredRule, source: "configured-id" };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const prefixRule = rules.find((rule) =>
|
|
154
|
+
rule.prefixes.some((prefix) => relativePath.startsWith(prefix))
|
|
155
|
+
);
|
|
156
|
+
return prefixRule ? { rule: prefixRule, source: "prefix" } : undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
115
159
|
export function normalizeConfigContentTypes(
|
|
116
160
|
config: Config
|
|
117
161
|
): ConfigNormalizationResult {
|
|
@@ -127,6 +171,44 @@ export function normalizeConfigContentTypes(
|
|
|
127
171
|
|
|
128
172
|
export function fingerprintContentTypeRules(
|
|
129
173
|
rules: NormalizedContentTypeRule[]
|
|
174
|
+
): string {
|
|
175
|
+
const canonical = rules.map((rule) => {
|
|
176
|
+
const base = {
|
|
177
|
+
id: rule.id,
|
|
178
|
+
preset: rule.preset,
|
|
179
|
+
prefixes: rule.prefixes,
|
|
180
|
+
graphHints: rule.graphHints ?? [],
|
|
181
|
+
};
|
|
182
|
+
return rule.searchBoost === CONTENT_TYPE_SEARCH_BOOST_NEUTRAL
|
|
183
|
+
? base
|
|
184
|
+
: { ...base, searchBoost: rule.searchBoost };
|
|
185
|
+
});
|
|
186
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
187
|
+
hasher.update(JSON.stringify(canonical));
|
|
188
|
+
return hasher.digest("hex");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Redacted live-ranking projection for status surfaces. Prefixes remain local
|
|
193
|
+
* configuration details; rule IDs and factors are enough to audit behavior.
|
|
194
|
+
*/
|
|
195
|
+
export function buildContentTypeBoostStatus(
|
|
196
|
+
contentTypes: ContentTypeConfig[]
|
|
197
|
+
): ContentTypeBoostStatus {
|
|
198
|
+
const { rules } = normalizeContentTypes(contentTypes);
|
|
199
|
+
return {
|
|
200
|
+
rulesFingerprint: fingerprintContentTypeRules(rules),
|
|
201
|
+
rules: rules.map(({ id, searchBoost }) => ({ id, searchBoost })),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Fingerprint only fields that derive persisted document metadata.
|
|
207
|
+
* Search boosts are evaluated from live config at query time, so changing one
|
|
208
|
+
* must not force content conversion or vector rebuilds.
|
|
209
|
+
*/
|
|
210
|
+
export function fingerprintContentTypeMetadataRules(
|
|
211
|
+
rules: NormalizedContentTypeRule[]
|
|
130
212
|
): string {
|
|
131
213
|
const canonical = rules.map((rule) => ({
|
|
132
214
|
id: rule.id,
|
package/src/config/index.ts
CHANGED
|
@@ -6,13 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
export { createDefaultConfig } from "./defaults";
|
|
8
8
|
export {
|
|
9
|
+
type ContentTypeRuleResolution,
|
|
10
|
+
type ContentTypeBoostStatus,
|
|
9
11
|
type ConfigWarning,
|
|
12
|
+
buildContentTypeBoostStatus,
|
|
13
|
+
fingerprintContentTypeMetadataRules,
|
|
10
14
|
fingerprintContentTypeRules,
|
|
11
15
|
formatConfigWarning,
|
|
12
16
|
formatConfigWarnings,
|
|
13
17
|
normalizeConfigContentTypes,
|
|
14
18
|
normalizeContentTypes,
|
|
15
19
|
type NormalizedContentTypeRule,
|
|
20
|
+
resolveContentTypeRule,
|
|
16
21
|
writeConfigWarningsToStderr,
|
|
17
22
|
} from "./content-types";
|
|
18
23
|
// Loading
|
|
@@ -42,10 +47,14 @@ export {
|
|
|
42
47
|
type SaveResult,
|
|
43
48
|
saveConfig,
|
|
44
49
|
saveConfigToPath,
|
|
50
|
+
saveTextToPath,
|
|
45
51
|
} from "./saver";
|
|
46
52
|
// Types and schemas
|
|
47
53
|
export {
|
|
48
54
|
CONFIG_VERSION,
|
|
55
|
+
CONTENT_TYPE_SEARCH_BOOST_MAX,
|
|
56
|
+
CONTENT_TYPE_SEARCH_BOOST_MIN,
|
|
57
|
+
CONTENT_TYPE_SEARCH_BOOST_NEUTRAL,
|
|
49
58
|
type Collection,
|
|
50
59
|
CollectionSchema,
|
|
51
60
|
type Config,
|
|
@@ -66,6 +75,8 @@ export {
|
|
|
66
75
|
getCollectionFromScope,
|
|
67
76
|
isValidLanguageHint,
|
|
68
77
|
parseScope,
|
|
78
|
+
type ProjectProfileBinding,
|
|
79
|
+
ProjectProfileBindingSchema,
|
|
69
80
|
type ScopeType,
|
|
70
81
|
ScopeTypeSchema,
|
|
71
82
|
} from "./types";
|