@unbrained/pm-cli 2026.3.12 → 2026.5.1
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/.agents/pm/extensions/.managed-extensions.json +42 -0
- package/.agents/pm/extensions/beads/index.js +109 -0
- package/.agents/pm/extensions/beads/manifest.json +7 -0
- package/{dist/cli/commands/beads.js → .agents/pm/extensions/beads/runtime.js} +31 -21
- package/.agents/pm/extensions/beads/runtime.ts +702 -0
- package/.agents/pm/extensions/todos/index.js +126 -0
- package/.agents/pm/extensions/todos/manifest.json +7 -0
- package/{dist/extensions/builtins/todos/import-export.js → .agents/pm/extensions/todos/runtime.js} +39 -29
- package/.agents/pm/extensions/todos/runtime.ts +568 -0
- package/AGENTS.md +196 -92
- package/CHANGELOG.md +399 -0
- package/CODE_OF_CONDUCT.md +42 -0
- package/CONTRIBUTING.md +144 -0
- package/PRD.md +512 -164
- package/README.md +1053 -2
- package/SECURITY.md +51 -0
- package/dist/cli/commands/activity.d.ts +5 -0
- package/dist/cli/commands/activity.js +66 -3
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/aggregate.d.ts +54 -0
- package/dist/cli/commands/aggregate.js +181 -0
- package/dist/cli/commands/aggregate.js.map +1 -0
- package/dist/cli/commands/append.js +4 -1
- package/dist/cli/commands/append.js.map +1 -1
- package/dist/cli/commands/calendar.d.ts +109 -0
- package/dist/cli/commands/calendar.js +797 -0
- package/dist/cli/commands/calendar.js.map +1 -0
- package/dist/cli/commands/claim.d.ts +5 -1
- package/dist/cli/commands/claim.js +42 -21
- package/dist/cli/commands/claim.js.map +1 -1
- package/dist/cli/commands/close.d.ts +1 -0
- package/dist/cli/commands/close.js +54 -5
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/comments-audit.d.ts +91 -0
- package/dist/cli/commands/comments-audit.js +195 -0
- package/dist/cli/commands/comments-audit.js.map +1 -0
- package/dist/cli/commands/comments.d.ts +1 -0
- package/dist/cli/commands/comments.js +70 -21
- package/dist/cli/commands/comments.js.map +1 -1
- package/dist/cli/commands/completion.d.ts +10 -4
- package/dist/cli/commands/completion.js +1184 -137
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/config.d.ts +35 -3
- package/dist/cli/commands/config.js +968 -13
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/context.d.ts +86 -0
- package/dist/cli/commands/context.js +299 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/contracts.d.ts +78 -0
- package/dist/cli/commands/contracts.js +920 -0
- package/dist/cli/commands/contracts.js.map +1 -0
- package/dist/cli/commands/create.d.ts +48 -14
- package/dist/cli/commands/create.js +1331 -160
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/dedupe-audit.d.ts +81 -0
- package/dist/cli/commands/dedupe-audit.js +330 -0
- package/dist/cli/commands/dedupe-audit.js.map +1 -0
- package/dist/cli/commands/deps.d.ts +52 -0
- package/dist/cli/commands/deps.js +204 -0
- package/dist/cli/commands/deps.js.map +1 -0
- package/dist/cli/commands/docs.d.ts +19 -0
- package/dist/cli/commands/docs.js +212 -13
- package/dist/cli/commands/docs.js.map +1 -1
- package/dist/cli/commands/extension.d.ts +122 -0
- package/dist/cli/commands/extension.js +1850 -0
- package/dist/cli/commands/extension.js.map +1 -0
- package/dist/cli/commands/files.d.ts +52 -1
- package/dist/cli/commands/files.js +443 -13
- package/dist/cli/commands/files.js.map +1 -1
- package/dist/cli/commands/gc.d.ts +11 -1
- package/dist/cli/commands/gc.js +89 -11
- package/dist/cli/commands/gc.js.map +1 -1
- package/dist/cli/commands/get.d.ts +13 -0
- package/dist/cli/commands/get.js +35 -3
- package/dist/cli/commands/get.js.map +1 -1
- package/dist/cli/commands/health.d.ts +10 -2
- package/dist/cli/commands/health.js +774 -23
- package/dist/cli/commands/health.js.map +1 -1
- package/dist/cli/commands/history.d.ts +20 -0
- package/dist/cli/commands/history.js +152 -6
- package/dist/cli/commands/history.js.map +1 -1
- package/dist/cli/commands/index.d.ts +16 -3
- package/dist/cli/commands/index.js +16 -3
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +7 -2
- package/dist/cli/commands/init.js +137 -5
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/learnings.d.ts +17 -0
- package/dist/cli/commands/learnings.js +129 -0
- package/dist/cli/commands/learnings.js.map +1 -0
- package/dist/cli/commands/list.d.ts +29 -1
- package/dist/cli/commands/list.js +289 -53
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/normalize.d.ts +51 -0
- package/dist/cli/commands/normalize.js +298 -0
- package/dist/cli/commands/normalize.js.map +1 -0
- package/dist/cli/commands/notes.d.ts +17 -0
- package/dist/cli/commands/notes.js +129 -0
- package/dist/cli/commands/notes.js.map +1 -0
- package/dist/cli/commands/reindex.d.ts +1 -0
- package/dist/cli/commands/reindex.js +208 -32
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/restore.js +164 -30
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/search.d.ts +14 -1
- package/dist/cli/commands/search.js +475 -81
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/stats.js +26 -10
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/templates.d.ts +26 -0
- package/dist/cli/commands/templates.js +179 -0
- package/dist/cli/commands/templates.js.map +1 -0
- package/dist/cli/commands/test-all.d.ts +19 -1
- package/dist/cli/commands/test-all.js +161 -13
- package/dist/cli/commands/test-all.js.map +1 -1
- package/dist/cli/commands/test-runs.d.ts +63 -0
- package/dist/cli/commands/test-runs.js +179 -0
- package/dist/cli/commands/test-runs.js.map +1 -0
- package/dist/cli/commands/test.d.ts +75 -1
- package/dist/cli/commands/test.js +1360 -41
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/commands/update-many.d.ts +57 -0
- package/dist/cli/commands/update-many.js +631 -0
- package/dist/cli/commands/update-many.js.map +1 -0
- package/dist/cli/commands/update.d.ts +30 -0
- package/dist/cli/commands/update.js +1393 -84
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/validate.d.ts +30 -0
- package/dist/cli/commands/validate.js +1140 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/error-guidance.d.ts +33 -0
- package/dist/cli/error-guidance.js +337 -0
- package/dist/cli/error-guidance.js.map +1 -0
- package/dist/cli/extension-command-options.d.ts +1 -0
- package/dist/cli/extension-command-options.js +92 -0
- package/dist/cli/extension-command-options.js.map +1 -1
- package/dist/cli/help-content.d.ts +20 -0
- package/dist/cli/help-content.js +543 -0
- package/dist/cli/help-content.js.map +1 -0
- package/dist/cli/main.js +3625 -445
- package/dist/cli/main.js.map +1 -1
- package/dist/core/extensions/index.d.ts +13 -1
- package/dist/core/extensions/index.js +108 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/item-fields.d.ts +2 -0
- package/dist/core/extensions/item-fields.js +79 -0
- package/dist/core/extensions/item-fields.js.map +1 -0
- package/dist/core/extensions/loader.d.ts +322 -9
- package/dist/core/extensions/loader.js +911 -20
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runtime-registrations.d.ts +5 -0
- package/dist/core/extensions/runtime-registrations.js +51 -0
- package/dist/core/extensions/runtime-registrations.js.map +1 -0
- package/dist/core/history/history-stream-policy.d.ts +20 -0
- package/dist/core/history/history-stream-policy.js +53 -0
- package/dist/core/history/history-stream-policy.js.map +1 -0
- package/dist/core/history/history.js +90 -1
- package/dist/core/history/history.js.map +1 -1
- package/dist/core/item/id.js +4 -1
- package/dist/core/item/id.js.map +1 -1
- package/dist/core/item/index.d.ts +1 -0
- package/dist/core/item/index.js +1 -0
- package/dist/core/item/index.js.map +1 -1
- package/dist/core/item/item-format.d.ts +11 -5
- package/dist/core/item/item-format.js +507 -24
- package/dist/core/item/item-format.js.map +1 -1
- package/dist/core/item/parent-reference-policy.d.ts +6 -0
- package/dist/core/item/parent-reference-policy.js +32 -0
- package/dist/core/item/parent-reference-policy.js.map +1 -0
- package/dist/core/item/parse.d.ts +5 -0
- package/dist/core/item/parse.js +216 -19
- package/dist/core/item/parse.js.map +1 -1
- package/dist/core/item/sprint-release-format.d.ts +6 -0
- package/dist/core/item/sprint-release-format.js +33 -0
- package/dist/core/item/sprint-release-format.js.map +1 -0
- package/dist/core/item/status.d.ts +3 -0
- package/dist/core/item/status.js +24 -0
- package/dist/core/item/status.js.map +1 -0
- package/dist/core/item/type-registry.d.ts +37 -0
- package/dist/core/item/type-registry.js +706 -0
- package/dist/core/item/type-registry.js.map +1 -0
- package/dist/core/lock/lock.d.ts +1 -1
- package/dist/core/lock/lock.js +101 -12
- package/dist/core/lock/lock.js.map +1 -1
- package/dist/core/output/command-aware.d.ts +1 -0
- package/dist/core/output/command-aware.js +394 -0
- package/dist/core/output/command-aware.js.map +1 -0
- package/dist/core/output/output.d.ts +3 -0
- package/dist/core/output/output.js +124 -6
- package/dist/core/output/output.js.map +1 -1
- package/dist/core/schema/runtime-field-filters.d.ts +3 -0
- package/dist/core/schema/runtime-field-filters.js +39 -0
- package/dist/core/schema/runtime-field-filters.js.map +1 -0
- package/dist/core/schema/runtime-field-values.d.ts +8 -0
- package/dist/core/schema/runtime-field-values.js +154 -0
- package/dist/core/schema/runtime-field-values.js.map +1 -0
- package/dist/core/schema/runtime-schema.d.ts +68 -0
- package/dist/core/schema/runtime-schema.js +554 -0
- package/dist/core/schema/runtime-schema.js.map +1 -0
- package/dist/core/search/cache.d.ts +13 -1
- package/dist/core/search/cache.js +123 -14
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/semantic-defaults.d.ts +6 -0
- package/dist/core/search/semantic-defaults.js +120 -0
- package/dist/core/search/semantic-defaults.js.map +1 -0
- package/dist/core/search/vector-stores.js +3 -1
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/shared/command-types.d.ts +2 -0
- package/dist/core/shared/conflict-markers.d.ts +7 -0
- package/dist/core/shared/conflict-markers.js +27 -0
- package/dist/core/shared/conflict-markers.js.map +1 -0
- package/dist/core/shared/constants.d.ts +15 -4
- package/dist/core/shared/constants.js +141 -1
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/shared/errors.d.ts +10 -1
- package/dist/core/shared/errors.js +3 -1
- package/dist/core/shared/errors.js.map +1 -1
- package/dist/core/shared/text-normalization.d.ts +4 -0
- package/dist/core/shared/text-normalization.js +33 -0
- package/dist/core/shared/text-normalization.js.map +1 -0
- package/dist/core/shared/time.d.ts +1 -2
- package/dist/core/shared/time.js +98 -11
- package/dist/core/shared/time.js.map +1 -1
- package/dist/core/store/index.d.ts +1 -0
- package/dist/core/store/index.js +1 -0
- package/dist/core/store/index.js.map +1 -1
- package/dist/core/store/item-format-migration.d.ts +9 -0
- package/dist/core/store/item-format-migration.js +87 -0
- package/dist/core/store/item-format-migration.js.map +1 -0
- package/dist/core/store/item-store.d.ts +13 -4
- package/dist/core/store/item-store.js +238 -51
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/paths.d.ts +21 -3
- package/dist/core/store/paths.js +59 -4
- package/dist/core/store/paths.js.map +1 -1
- package/dist/core/store/settings.d.ts +14 -1
- package/dist/core/store/settings.js +463 -7
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/consent.d.ts +2 -0
- package/dist/core/telemetry/consent.js +79 -0
- package/dist/core/telemetry/consent.js.map +1 -0
- package/dist/core/telemetry/runtime.d.ts +38 -0
- package/dist/core/telemetry/runtime.js +733 -0
- package/dist/core/telemetry/runtime.js.map +1 -0
- package/dist/core/test/background-runs.d.ts +117 -0
- package/dist/core/test/background-runs.js +760 -0
- package/dist/core/test/background-runs.js.map +1 -0
- package/dist/core/test/item-test-run-tracking.d.ts +9 -0
- package/dist/core/test/item-test-run-tracking.js +50 -0
- package/dist/core/test/item-test-run-tracking.js.map +1 -0
- package/dist/sdk/cli-contracts.d.ts +92 -0
- package/dist/sdk/cli-contracts.js +2357 -0
- package/dist/sdk/cli-contracts.js.map +1 -0
- package/dist/sdk/index.d.ts +34 -0
- package/dist/sdk/index.js +23 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/types.d.ts +197 -3
- package/dist/types.js +48 -1
- package/dist/types.js.map +1 -1
- package/docs/ARCHITECTURE.md +368 -39
- package/docs/EXTENSIONS.md +454 -49
- package/docs/RELEASING.md +68 -19
- package/docs/SDK.md +123 -0
- package/docs/examples/starter-extension/README.md +48 -0
- package/docs/examples/starter-extension/index.js +191 -0
- package/docs/examples/starter-extension/manifest.json +17 -0
- package/docs/examples/starter-extension/package.json +10 -0
- package/package.json +33 -6
- package/.pi/extensions/pm-cli/index.ts +0 -778
- package/dist/cli/commands/beads.d.ts +0 -16
- package/dist/cli/commands/beads.js.map +0 -1
- package/dist/cli/commands/install.d.ts +0 -18
- package/dist/cli/commands/install.js +0 -87
- package/dist/cli/commands/install.js.map +0 -1
- package/dist/core/extensions/builtins.d.ts +0 -3
- package/dist/core/extensions/builtins.js +0 -47
- package/dist/core/extensions/builtins.js.map +0 -1
- package/dist/extensions/builtins/beads/index.d.ts +0 -8
- package/dist/extensions/builtins/beads/index.js +0 -33
- package/dist/extensions/builtins/beads/index.js.map +0 -1
- package/dist/extensions/builtins/todos/import-export.d.ts +0 -26
- package/dist/extensions/builtins/todos/import-export.js.map +0 -1
- package/dist/extensions/builtins/todos/index.d.ts +0 -8
- package/dist/extensions/builtins/todos/index.js +0 -38
- package/dist/extensions/builtins/todos/index.js.map +0 -1
|
@@ -1,25 +1,65 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { runActiveOnReadHooks } from "../../core/extensions/index.js";
|
|
3
|
+
import { getActiveExtensionRegistrations, runActiveOnReadHooks } from "../../core/extensions/index.js";
|
|
4
|
+
import { resolveRegisteredSearchProvider, resolveRegisteredVectorStoreAdapter, } from "../../core/extensions/runtime-registrations.js";
|
|
5
|
+
import { resolveItemTypeRegistry, resolveTypeName } from "../../core/item/type-registry.js";
|
|
4
6
|
import { executeEmbeddingRequest, resolveEmbeddingProviders, } from "../../core/search/providers.js";
|
|
7
|
+
import { resolveSettingsWithSemanticRuntimeDefaults } from "../../core/search/semantic-defaults.js";
|
|
5
8
|
import { executeVectorQuery, resolveVectorStores, } from "../../core/search/vector-stores.js";
|
|
6
9
|
import { pathExists } from "../../core/fs/fs-utils.js";
|
|
7
10
|
import { parseItemDocument } from "../../core/item/item-format.js";
|
|
8
|
-
import {
|
|
11
|
+
import { normalizeStatusInput } from "../../core/item/status.js";
|
|
12
|
+
import { collectRuntimeFilterValues, matchesRuntimeFilters } from "../../core/schema/runtime-field-filters.js";
|
|
13
|
+
import { resolveRuntimeFieldRegistry, resolveRuntimeStatusRegistry, } from "../../core/schema/runtime-schema.js";
|
|
14
|
+
import { EXIT_CODE } from "../../core/shared/constants.js";
|
|
9
15
|
import { PmCliError } from "../../core/shared/errors.js";
|
|
16
|
+
import { tokenizeAlphaNumeric } from "../../core/shared/text-normalization.js";
|
|
10
17
|
import { compareTimestampStrings, nowIso, resolveIsoOrRelative } from "../../core/shared/time.js";
|
|
11
18
|
import { listAllFrontMatter } from "../../core/store/item-store.js";
|
|
12
|
-
import { getSettingsPath, resolveGlobalPmRoot, resolvePmRoot } from "../../core/store/paths.js";
|
|
19
|
+
import { getSettingsPath, resolveGlobalPmRoot, resolvePmRoot, getItemPath } from "../../core/store/paths.js";
|
|
13
20
|
import { readSettings } from "../../core/store/settings.js";
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const DEFAULT_COMPACT_SEARCH_FIELDS = [
|
|
22
|
+
"id",
|
|
23
|
+
"title",
|
|
24
|
+
"status",
|
|
25
|
+
"type",
|
|
26
|
+
"priority",
|
|
27
|
+
"updated_at",
|
|
28
|
+
"score",
|
|
29
|
+
"matched_fields",
|
|
30
|
+
];
|
|
31
|
+
const LONG_QUERY_TOKEN_THRESHOLD = 4;
|
|
32
|
+
const LONG_QUERY_TITLE_EXACT_BONUS = 120;
|
|
33
|
+
const LONG_QUERY_PHRASE_MULTIPLIER = 6;
|
|
34
|
+
const IMPLICIT_AUTO_HYBRID_EMBEDDING_TIMEOUT_MS = 8_000;
|
|
35
|
+
const IMPLICIT_AUTO_HYBRID_VECTOR_TIMEOUT_MS = 8_000;
|
|
36
|
+
function isTerminal(status, statusRegistry) {
|
|
37
|
+
const normalized = normalizeStatusInput(status, statusRegistry) ?? status;
|
|
38
|
+
return statusRegistry.terminal_statuses.has(normalized);
|
|
39
|
+
}
|
|
40
|
+
function classifyImplicitSemanticFallbackReason(error) {
|
|
41
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
42
|
+
if (message.includes("timed out") || message.includes("timeout")) {
|
|
43
|
+
return "timeout";
|
|
44
|
+
}
|
|
45
|
+
if (message.includes("econnrefused") ||
|
|
46
|
+
message.includes("connection refused") ||
|
|
47
|
+
message.includes("connect ") ||
|
|
48
|
+
message.includes("enotfound") ||
|
|
49
|
+
message.includes("eai_again")) {
|
|
50
|
+
return "connection";
|
|
51
|
+
}
|
|
52
|
+
return "error";
|
|
53
|
+
}
|
|
54
|
+
function buildImplicitSemanticFallbackWarning(error) {
|
|
55
|
+
const reason = classifyImplicitSemanticFallbackReason(error);
|
|
56
|
+
if (reason === "timeout") {
|
|
57
|
+
return "search_implicit_semantic_fallback:timeout:using_keyword_mode";
|
|
58
|
+
}
|
|
59
|
+
if (reason === "connection") {
|
|
60
|
+
return "search_implicit_semantic_fallback:connection:using_keyword_mode";
|
|
61
|
+
}
|
|
62
|
+
return "search_implicit_semantic_fallback:error:using_keyword_mode";
|
|
23
63
|
}
|
|
24
64
|
function parseMode(raw, context) {
|
|
25
65
|
if (raw === undefined) {
|
|
@@ -34,6 +74,18 @@ function parseMode(raw, context) {
|
|
|
34
74
|
function parseIncludeLinked(raw) {
|
|
35
75
|
return raw === true;
|
|
36
76
|
}
|
|
77
|
+
function parseTitleExact(raw) {
|
|
78
|
+
return raw === true;
|
|
79
|
+
}
|
|
80
|
+
function parsePhraseExact(raw) {
|
|
81
|
+
return raw === true;
|
|
82
|
+
}
|
|
83
|
+
function normalizeSearchPhrase(value) {
|
|
84
|
+
return value
|
|
85
|
+
.toLowerCase()
|
|
86
|
+
.replace(/\s+/g, " ")
|
|
87
|
+
.trim();
|
|
88
|
+
}
|
|
37
89
|
function parsePriority(raw) {
|
|
38
90
|
if (raw === undefined)
|
|
39
91
|
return undefined;
|
|
@@ -43,19 +95,19 @@ function parsePriority(raw) {
|
|
|
43
95
|
}
|
|
44
96
|
return parsed;
|
|
45
97
|
}
|
|
46
|
-
function parseType(raw) {
|
|
98
|
+
function parseType(raw, typeRegistry) {
|
|
47
99
|
if (raw === undefined)
|
|
48
100
|
return undefined;
|
|
49
|
-
const parsed =
|
|
101
|
+
const parsed = resolveTypeName(raw, typeRegistry);
|
|
50
102
|
if (!parsed) {
|
|
51
|
-
throw new PmCliError(
|
|
103
|
+
throw new PmCliError(`Type filter must be one of ${typeRegistry.types.join("|")}`, EXIT_CODE.USAGE);
|
|
52
104
|
}
|
|
53
105
|
return parsed;
|
|
54
106
|
}
|
|
55
|
-
function parseDeadline(raw) {
|
|
107
|
+
function parseDeadline(raw, fieldLabel) {
|
|
56
108
|
if (raw === undefined)
|
|
57
109
|
return undefined;
|
|
58
|
-
return resolveIsoOrRelative(raw);
|
|
110
|
+
return resolveIsoOrRelative(raw, new Date(), fieldLabel);
|
|
59
111
|
}
|
|
60
112
|
function parseLimit(raw) {
|
|
61
113
|
if (raw === undefined)
|
|
@@ -66,19 +118,94 @@ function parseLimit(raw) {
|
|
|
66
118
|
}
|
|
67
119
|
return Math.floor(parsed);
|
|
68
120
|
}
|
|
121
|
+
function parseFieldSelectors(raw) {
|
|
122
|
+
if (raw === undefined) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
const selectors = raw
|
|
126
|
+
.split(",")
|
|
127
|
+
.map((entry) => entry.trim())
|
|
128
|
+
.filter((entry) => entry.length > 0);
|
|
129
|
+
if (selectors.length === 0) {
|
|
130
|
+
throw new PmCliError("Search --fields requires a comma-separated list of field names", EXIT_CODE.USAGE);
|
|
131
|
+
}
|
|
132
|
+
return [...new Set(selectors)];
|
|
133
|
+
}
|
|
134
|
+
function parseProjectionConfig(options) {
|
|
135
|
+
const compactRequested = options.compact === true;
|
|
136
|
+
const fullRequested = options.full === true;
|
|
137
|
+
const fieldSelectors = parseFieldSelectors(options.fields);
|
|
138
|
+
const enabledModes = Number(compactRequested) + Number(fullRequested) + Number(fieldSelectors !== undefined);
|
|
139
|
+
if (enabledModes > 1) {
|
|
140
|
+
throw new PmCliError("Search projection options are mutually exclusive. Use one of --compact, --full, or --fields.", EXIT_CODE.USAGE);
|
|
141
|
+
}
|
|
142
|
+
if (compactRequested) {
|
|
143
|
+
return {
|
|
144
|
+
mode: "compact",
|
|
145
|
+
fields: [...DEFAULT_COMPACT_SEARCH_FIELDS],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (fullRequested) {
|
|
149
|
+
return {
|
|
150
|
+
mode: "full",
|
|
151
|
+
fields: [],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (fieldSelectors) {
|
|
155
|
+
return {
|
|
156
|
+
mode: "fields",
|
|
157
|
+
fields: fieldSelectors,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
mode: "full",
|
|
162
|
+
fields: [],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
69
165
|
function parseTokens(query) {
|
|
70
|
-
const normalized = query
|
|
166
|
+
const normalized = normalizeSearchPhrase(query);
|
|
71
167
|
if (!normalized) {
|
|
72
168
|
throw new PmCliError("Search query must not be empty", EXIT_CODE.USAGE);
|
|
73
169
|
}
|
|
74
170
|
return normalized.split(/\s+/).filter(Boolean);
|
|
75
171
|
}
|
|
76
|
-
function
|
|
77
|
-
const
|
|
172
|
+
function collectExactPhraseFields(document) {
|
|
173
|
+
const item = document.front_matter;
|
|
174
|
+
return [
|
|
175
|
+
item.title,
|
|
176
|
+
item.description,
|
|
177
|
+
item.status,
|
|
178
|
+
item.tags.join(" "),
|
|
179
|
+
document.body,
|
|
180
|
+
(item.comments ?? []).map((entry) => entry.text).join(" "),
|
|
181
|
+
(item.notes ?? []).map((entry) => entry.text).join(" "),
|
|
182
|
+
(item.learnings ?? []).map((entry) => entry.text).join(" "),
|
|
183
|
+
(item.dependencies ?? []).map((entry) => `${entry.id} ${entry.kind}`).join(" "),
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
function documentContainsExactPhrase(document, normalizedQuery) {
|
|
187
|
+
return collectExactPhraseFields(document).some((fieldValue) => normalizeSearchPhrase(fieldValue).includes(normalizedQuery));
|
|
188
|
+
}
|
|
189
|
+
function applyExactQueryFilters(items, normalizedQuery, options) {
|
|
190
|
+
if (!options.titleExact && !options.phraseExact) {
|
|
191
|
+
return items;
|
|
192
|
+
}
|
|
193
|
+
return items.filter((document) => {
|
|
194
|
+
if (options.titleExact && normalizeSearchPhrase(document.front_matter.title) !== normalizedQuery) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (options.phraseExact && !documentContainsExactPhrase(document, normalizedQuery)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function applyFilters(items, options, typeRegistry, runtimeFieldFilters) {
|
|
204
|
+
const typeFilter = parseType(options.type, typeRegistry);
|
|
78
205
|
const tagFilter = options.tag?.trim().toLowerCase();
|
|
79
206
|
const priorityFilter = parsePriority(options.priority);
|
|
80
|
-
const deadlineBefore = parseDeadline(options.deadlineBefore);
|
|
81
|
-
const deadlineAfter = parseDeadline(options.deadlineAfter);
|
|
207
|
+
const deadlineBefore = parseDeadline(options.deadlineBefore, "deadline-before");
|
|
208
|
+
const deadlineAfter = parseDeadline(options.deadlineAfter, "deadline-after");
|
|
82
209
|
return items.filter((document) => {
|
|
83
210
|
const item = document.front_matter;
|
|
84
211
|
if (typeFilter && item.type !== typeFilter)
|
|
@@ -91,6 +218,8 @@ function applyFilters(items, options) {
|
|
|
91
218
|
return false;
|
|
92
219
|
if (deadlineAfter && (!item.deadline || compareTimestampStrings(item.deadline, deadlineAfter) < 0))
|
|
93
220
|
return false;
|
|
221
|
+
if (!matchesRuntimeFilters(item, runtimeFieldFilters))
|
|
222
|
+
return false;
|
|
94
223
|
return true;
|
|
95
224
|
});
|
|
96
225
|
}
|
|
@@ -107,10 +236,7 @@ function countOccurrences(haystack, needle) {
|
|
|
107
236
|
}
|
|
108
237
|
}
|
|
109
238
|
function tokenizeForExactTokenMatch(value) {
|
|
110
|
-
return value
|
|
111
|
-
.toLowerCase()
|
|
112
|
-
.split(/[^a-z0-9]+/)
|
|
113
|
-
.filter((token) => token.length > 0);
|
|
239
|
+
return tokenizeAlphaNumeric(value);
|
|
114
240
|
}
|
|
115
241
|
function collectLinkedPaths(item) {
|
|
116
242
|
const fromFiles = (item.files ?? []).map((entry) => ({
|
|
@@ -193,7 +319,7 @@ async function loadLinkedCorpus(document, projectRoot, globalRoot) {
|
|
|
193
319
|
}
|
|
194
320
|
return chunks.join("\n");
|
|
195
321
|
}
|
|
196
|
-
function scoreDocument(document, tokens, linkedCorpus, tuning) {
|
|
322
|
+
function scoreDocument(document, tokens, normalizedQuery, linkedCorpus, tuning) {
|
|
197
323
|
const item = document.front_matter;
|
|
198
324
|
const titleTokenCounts = new Map();
|
|
199
325
|
for (const token of tokenizeForExactTokenMatch(item.title)) {
|
|
@@ -232,6 +358,22 @@ function scoreDocument(document, tokens, linkedCorpus, tuning) {
|
|
|
232
358
|
}
|
|
233
359
|
}
|
|
234
360
|
}
|
|
361
|
+
const isLongPhraseQuery = tokens.length >= LONG_QUERY_TOKEN_THRESHOLD && normalizedQuery.includes(" ");
|
|
362
|
+
if (isLongPhraseQuery) {
|
|
363
|
+
const normalizedTitle = normalizeSearchPhrase(item.title);
|
|
364
|
+
if (normalizedTitle === normalizedQuery) {
|
|
365
|
+
score += LONG_QUERY_TITLE_EXACT_BONUS;
|
|
366
|
+
matched.add("title");
|
|
367
|
+
}
|
|
368
|
+
for (const field of searchableFields) {
|
|
369
|
+
const normalizedField = normalizeSearchPhrase(field.value);
|
|
370
|
+
const phraseOccurrences = countOccurrences(normalizedField, normalizedQuery);
|
|
371
|
+
if (phraseOccurrences > 0) {
|
|
372
|
+
score += phraseOccurrences * field.weight * LONG_QUERY_PHRASE_MULTIPLIER;
|
|
373
|
+
matched.add(field.name);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
235
377
|
if (score <= 0) {
|
|
236
378
|
return null;
|
|
237
379
|
}
|
|
@@ -241,13 +383,13 @@ function scoreDocument(document, tokens, linkedCorpus, tuning) {
|
|
|
241
383
|
matched_fields: [...matched].sort((a, b) => a.localeCompare(b)),
|
|
242
384
|
};
|
|
243
385
|
}
|
|
244
|
-
function sortHits(items) {
|
|
386
|
+
function sortHits(items, statusRegistry) {
|
|
245
387
|
return [...items].sort((a, b) => {
|
|
246
388
|
const byScore = b.score - a.score;
|
|
247
389
|
if (byScore !== 0)
|
|
248
390
|
return byScore;
|
|
249
|
-
const aTerminal = isTerminal(a.item.status);
|
|
250
|
-
const bTerminal = isTerminal(b.item.status);
|
|
391
|
+
const aTerminal = isTerminal(a.item.status, statusRegistry);
|
|
392
|
+
const bTerminal = isTerminal(b.item.status, statusRegistry);
|
|
251
393
|
if (aTerminal !== bTerminal) {
|
|
252
394
|
return aTerminal ? 1 : -1;
|
|
253
395
|
}
|
|
@@ -260,8 +402,8 @@ function sortHits(items) {
|
|
|
260
402
|
return a.item.id.localeCompare(b.item.id);
|
|
261
403
|
});
|
|
262
404
|
}
|
|
263
|
-
function buildHybridLexicalScore(document, tokens, includeLinked, linkedCorpusById, tuning) {
|
|
264
|
-
return scoreDocument(document, tokens, includeLinked ? linkedCorpusById.get(document.front_matter.id) ?? "" : "", tuning);
|
|
405
|
+
function buildHybridLexicalScore(document, tokens, normalizedQuery, includeLinked, linkedCorpusById, tuning) {
|
|
406
|
+
return scoreDocument(document, tokens, normalizedQuery, includeLinked ? linkedCorpusById.get(document.front_matter.id) ?? "" : "", tuning);
|
|
265
407
|
}
|
|
266
408
|
function normalizeScoreMap(scoreById) {
|
|
267
409
|
if (scoreById.size === 0) {
|
|
@@ -337,7 +479,8 @@ export function resolveSearchTuning(settings) {
|
|
|
337
479
|
linked_content_weight: resolveWeight(tuning.linked_content_weight, defaults.linked_content_weight),
|
|
338
480
|
};
|
|
339
481
|
}
|
|
340
|
-
function emptySearchResult(query, mode, options, includeLinked, scoreThreshold, hybridSemanticWeight) {
|
|
482
|
+
function emptySearchResult(query, mode, options, includeLinked, scoreThreshold, hybridSemanticWeight, projection, warnings) {
|
|
483
|
+
const projectionFields = projection.mode === "full" ? null : [...projection.fields];
|
|
341
484
|
return {
|
|
342
485
|
query: query.trim(),
|
|
343
486
|
mode,
|
|
@@ -351,25 +494,115 @@ function emptySearchResult(query, mode, options, includeLinked, scoreThreshold,
|
|
|
351
494
|
deadline_before: options.deadlineBefore ?? null,
|
|
352
495
|
deadline_after: options.deadlineAfter ?? null,
|
|
353
496
|
include_linked: includeLinked,
|
|
497
|
+
title_exact: options.titleExact === true,
|
|
498
|
+
phrase_exact: options.phraseExact === true,
|
|
354
499
|
score_threshold: scoreThreshold,
|
|
355
500
|
hybrid_semantic_weight: mode === "hybrid" ? hybridSemanticWeight : null,
|
|
356
501
|
limit: options.limit ?? null,
|
|
502
|
+
projection: projection.mode,
|
|
503
|
+
fields: projectionFields,
|
|
504
|
+
},
|
|
505
|
+
projection: {
|
|
506
|
+
mode: projection.mode,
|
|
507
|
+
fields: projectionFields,
|
|
357
508
|
},
|
|
358
509
|
now: nowIso(),
|
|
510
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
359
511
|
};
|
|
360
512
|
}
|
|
361
|
-
function requireSemanticDependencies(requestedMode, providerResolution, vectorResolution) {
|
|
513
|
+
function requireSemanticDependencies(requestedMode, providerResolution, vectorResolution, hasExtensionVectorQuery) {
|
|
362
514
|
if (!providerResolution.active) {
|
|
363
515
|
throw new PmCliError(`Search mode '${requestedMode}' requires a configured embedding provider in settings.providers.openai or settings.providers.ollama`, EXIT_CODE.USAGE);
|
|
364
516
|
}
|
|
365
|
-
if (!vectorResolution.active) {
|
|
366
|
-
throw new PmCliError(`Search mode '${requestedMode}' requires a configured vector store in settings.vector_store.qdrant or settings.vector_store.
|
|
517
|
+
if (!vectorResolution.active && !hasExtensionVectorQuery) {
|
|
518
|
+
throw new PmCliError(`Search mode '${requestedMode}' requires a configured vector store in settings.vector_store.qdrant/settings.vector_store.lancedb or an extension adapter selected by settings.vector_store.adapter`, EXIT_CODE.USAGE);
|
|
367
519
|
}
|
|
368
520
|
return {
|
|
369
521
|
provider: providerResolution.active,
|
|
370
|
-
vectorStore: vectorResolution.active,
|
|
522
|
+
vectorStore: vectorResolution.active ?? null,
|
|
371
523
|
};
|
|
372
524
|
}
|
|
525
|
+
function toOptionalNonEmptyString(value) {
|
|
526
|
+
if (typeof value !== "string") {
|
|
527
|
+
return undefined;
|
|
528
|
+
}
|
|
529
|
+
const normalized = value.trim();
|
|
530
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
531
|
+
}
|
|
532
|
+
function resolveExtensionSearchProvider(settings) {
|
|
533
|
+
const registrations = getActiveExtensionRegistrations();
|
|
534
|
+
const providerName = toOptionalNonEmptyString(settings.search?.provider);
|
|
535
|
+
const resolved = resolveRegisteredSearchProvider(registrations, providerName);
|
|
536
|
+
if (!resolved) {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
const runtimeDefinition = resolved.runtime_definition ?? resolved.definition;
|
|
540
|
+
const query = runtimeDefinition.query;
|
|
541
|
+
if (typeof query !== "function") {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
const registeredName = toOptionalNonEmptyString(runtimeDefinition.name) ??
|
|
545
|
+
toOptionalNonEmptyString(resolved.definition.name) ??
|
|
546
|
+
providerName;
|
|
547
|
+
if (!registeredName) {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
providerName: registeredName,
|
|
552
|
+
query: query,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function resolveExtensionVectorAdapter(settings) {
|
|
556
|
+
const registrations = getActiveExtensionRegistrations();
|
|
557
|
+
const adapterName = toOptionalNonEmptyString(settings.vector_store?.adapter);
|
|
558
|
+
const resolved = resolveRegisteredVectorStoreAdapter(registrations, adapterName);
|
|
559
|
+
if (!resolved) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
const runtimeDefinition = resolved.runtime_definition ?? resolved.definition;
|
|
563
|
+
const query = runtimeDefinition.query;
|
|
564
|
+
if (typeof query !== "function") {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
query: query,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function normalizeExtensionProviderHits(providerName, raw, filteredById) {
|
|
572
|
+
const rawHits = Array.isArray(raw)
|
|
573
|
+
? raw
|
|
574
|
+
: raw?.hits;
|
|
575
|
+
if (!Array.isArray(rawHits)) {
|
|
576
|
+
throw new PmCliError(`Extension search provider "${providerName}" must return an array of hits or { hits: [...] }`, EXIT_CODE.GENERIC_FAILURE);
|
|
577
|
+
}
|
|
578
|
+
const seen = new Set();
|
|
579
|
+
const hits = [];
|
|
580
|
+
for (const rawHit of rawHits) {
|
|
581
|
+
if (typeof rawHit !== "object" || rawHit === null) {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
const id = toOptionalNonEmptyString(rawHit.id);
|
|
585
|
+
const score = rawHit.score;
|
|
586
|
+
if (!id || typeof score !== "number" || !Number.isFinite(score) || seen.has(id)) {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const document = filteredById.get(id);
|
|
590
|
+
if (!document) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const matchedFieldsRaw = rawHit.matched_fields;
|
|
594
|
+
const matchedFields = Array.isArray(matchedFieldsRaw) && matchedFieldsRaw.every((entry) => typeof entry === "string")
|
|
595
|
+
? [...new Set(matchedFieldsRaw.map((entry) => entry.trim()).filter((entry) => entry.length > 0))].sort((a, b) => a.localeCompare(b))
|
|
596
|
+
: [`provider:${providerName}`];
|
|
597
|
+
seen.add(id);
|
|
598
|
+
hits.push({
|
|
599
|
+
item: document.front_matter,
|
|
600
|
+
score,
|
|
601
|
+
matched_fields: matchedFields,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return hits;
|
|
605
|
+
}
|
|
373
606
|
function buildSemanticHits(vectorHits, filteredById) {
|
|
374
607
|
const semanticHits = [];
|
|
375
608
|
const semanticScores = new Map();
|
|
@@ -426,9 +659,32 @@ function combineHybridHits(filteredById, semanticScores, keywordHits, hybridSema
|
|
|
426
659
|
}
|
|
427
660
|
async function computeSemanticOrHybridHits(context) {
|
|
428
661
|
const semanticLimit = context.limit ?? context.maxResults;
|
|
429
|
-
const
|
|
662
|
+
const embeddingOptions = context.embeddingTimeoutMs !== undefined ? { timeout_ms: context.embeddingTimeoutMs } : {};
|
|
663
|
+
const vectorQueryOptions = context.vectorQueryTimeoutMs !== undefined ? { timeout_ms: context.vectorQueryTimeoutMs } : {};
|
|
664
|
+
const queryVectors = await executeEmbeddingRequest(context.provider, context.query.trim(), embeddingOptions);
|
|
430
665
|
const semanticVector = queryVectors[0];
|
|
431
|
-
|
|
666
|
+
let vectorHits;
|
|
667
|
+
if (context.extensionVectorAdapter?.query) {
|
|
668
|
+
try {
|
|
669
|
+
vectorHits = await Promise.resolve(context.extensionVectorAdapter.query({
|
|
670
|
+
vector: semanticVector,
|
|
671
|
+
limit: semanticLimit,
|
|
672
|
+
settings: context.settings,
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
if (!context.vectorStore) {
|
|
677
|
+
throw new PmCliError(`Extension vector adapter query failed and no built-in fallback store is configured (${error instanceof Error ? error.message : String(error)})`, EXIT_CODE.GENERIC_FAILURE);
|
|
678
|
+
}
|
|
679
|
+
vectorHits = await executeVectorQuery(context.vectorStore, semanticVector, semanticLimit, vectorQueryOptions);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
else if (context.vectorStore) {
|
|
683
|
+
vectorHits = await executeVectorQuery(context.vectorStore, semanticVector, semanticLimit, vectorQueryOptions);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
throw new PmCliError("Semantic search requires either a configured vector store or an extension vector adapter query handler", EXIT_CODE.USAGE);
|
|
687
|
+
}
|
|
432
688
|
const filteredById = new Map(context.filteredDocuments.map((document) => [document.front_matter.id, document]));
|
|
433
689
|
const { semanticHits, semanticScores } = buildSemanticHits(vectorHits, filteredById);
|
|
434
690
|
if (context.requestedMode === "semantic") {
|
|
@@ -436,96 +692,234 @@ async function computeSemanticOrHybridHits(context) {
|
|
|
436
692
|
}
|
|
437
693
|
return combineHybridHits(filteredById, semanticScores, context.keywordHits, context.hybridSemanticWeight);
|
|
438
694
|
}
|
|
439
|
-
|
|
440
|
-
|
|
695
|
+
function alternateFormat(itemFormat) {
|
|
696
|
+
return itemFormat === "toon" ? "json_markdown" : "toon";
|
|
697
|
+
}
|
|
698
|
+
async function loadDocuments(pmRoot, itemFormat, typeToFolder, schema) {
|
|
699
|
+
const listWarnings = [];
|
|
700
|
+
const items = await listAllFrontMatter(pmRoot, itemFormat, typeToFolder, listWarnings, schema);
|
|
701
|
+
const warnings = [...new Set(listWarnings)].sort((left, right) => left.localeCompare(right));
|
|
441
702
|
const documents = [];
|
|
442
703
|
for (const item of items) {
|
|
443
|
-
const
|
|
444
|
-
|
|
704
|
+
const preferredPath = getItemPath(pmRoot, item.type, item.id, itemFormat, typeToFolder);
|
|
705
|
+
try {
|
|
706
|
+
const raw = await fs.readFile(preferredPath, "utf8");
|
|
707
|
+
await runActiveOnReadHooks({
|
|
708
|
+
path: preferredPath,
|
|
709
|
+
scope: "project",
|
|
710
|
+
});
|
|
711
|
+
documents.push(parseItemDocument(raw, { format: itemFormat, schema, onWarning: (warning) => listWarnings.push(warning) }));
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
// Fallback to the alternate format when preferred format path is absent.
|
|
716
|
+
}
|
|
717
|
+
const fallbackFormat = alternateFormat(itemFormat);
|
|
718
|
+
const fallbackPath = getItemPath(pmRoot, item.type, item.id, fallbackFormat, typeToFolder);
|
|
719
|
+
const raw = await fs.readFile(fallbackPath, "utf8");
|
|
445
720
|
await runActiveOnReadHooks({
|
|
446
|
-
path:
|
|
721
|
+
path: fallbackPath,
|
|
447
722
|
scope: "project",
|
|
448
723
|
});
|
|
449
|
-
|
|
450
|
-
|
|
724
|
+
documents.push(parseItemDocument(raw, { format: fallbackFormat, schema, onWarning: (warning) => listWarnings.push(warning) }));
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
documents,
|
|
728
|
+
warnings,
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
function readSearchFieldValue(hit, field) {
|
|
732
|
+
const normalized = field.trim();
|
|
733
|
+
if (normalized.length === 0) {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
if (normalized === "score") {
|
|
737
|
+
return hit.score;
|
|
738
|
+
}
|
|
739
|
+
if (normalized === "matched_fields") {
|
|
740
|
+
return hit.matched_fields;
|
|
741
|
+
}
|
|
742
|
+
if (normalized.startsWith("item.")) {
|
|
743
|
+
const itemKey = normalized.slice("item.".length);
|
|
744
|
+
if (itemKey.length === 0) {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
const itemRecord = hit.item;
|
|
748
|
+
return itemRecord[itemKey] ?? null;
|
|
749
|
+
}
|
|
750
|
+
const hitRecord = hit;
|
|
751
|
+
const itemRecord = hit.item;
|
|
752
|
+
if (Object.prototype.hasOwnProperty.call(itemRecord, normalized)) {
|
|
753
|
+
return itemRecord[normalized] ?? null;
|
|
451
754
|
}
|
|
452
|
-
|
|
755
|
+
if (Object.prototype.hasOwnProperty.call(hitRecord, normalized)) {
|
|
756
|
+
return hitRecord[normalized] ?? null;
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
function projectSearchHits(hits, projection) {
|
|
761
|
+
if (projection.mode === "full") {
|
|
762
|
+
return hits;
|
|
763
|
+
}
|
|
764
|
+
return hits.map((hit) => {
|
|
765
|
+
const projected = {};
|
|
766
|
+
for (const field of projection.fields) {
|
|
767
|
+
projected[field] = readSearchFieldValue(hit, field);
|
|
768
|
+
}
|
|
769
|
+
return projected;
|
|
770
|
+
});
|
|
453
771
|
}
|
|
454
772
|
export async function runSearch(query, options, global) {
|
|
455
773
|
const includeLinked = parseIncludeLinked(options.includeLinked);
|
|
774
|
+
const titleExact = parseTitleExact(options.titleExact);
|
|
775
|
+
const phraseExact = parsePhraseExact(options.phraseExact);
|
|
456
776
|
const tokens = parseTokens(query);
|
|
777
|
+
const normalizedQuery = normalizeSearchPhrase(query);
|
|
457
778
|
const limit = parseLimit(options.limit);
|
|
779
|
+
const projection = parseProjectionConfig(options);
|
|
780
|
+
const modeWasExplicit = typeof options.mode === "string" && options.mode.trim().length > 0;
|
|
458
781
|
const pmRoot = resolvePmRoot(process.cwd(), global.path);
|
|
459
782
|
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
460
783
|
throw new PmCliError(`Tracker is not initialized at ${pmRoot}. Run pm init first.`, EXIT_CODE.NOT_FOUND);
|
|
461
784
|
}
|
|
462
|
-
const
|
|
785
|
+
const storedSettings = await readSettings(pmRoot);
|
|
786
|
+
const runtimeDefaultsResolution = resolveSettingsWithSemanticRuntimeDefaults(storedSettings);
|
|
787
|
+
const settings = runtimeDefaultsResolution.settings;
|
|
788
|
+
const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
|
|
789
|
+
const runtimeFieldRegistry = resolveRuntimeFieldRegistry(settings.schema);
|
|
790
|
+
const runtimeFieldFilters = collectRuntimeFilterValues(options, runtimeFieldRegistry, "search");
|
|
791
|
+
const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
|
|
463
792
|
const maxResults = resolveSearchMaxResults(settings);
|
|
464
793
|
const scoreThreshold = resolveSearchScoreThreshold(settings);
|
|
465
794
|
const hybridSemanticWeight = resolveHybridSemanticWeight(settings);
|
|
466
795
|
const tuning = resolveSearchTuning(settings);
|
|
467
796
|
const providerResolution = resolveEmbeddingProviders(settings);
|
|
468
797
|
const vectorResolution = resolveVectorStores(settings);
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
798
|
+
const extensionSearchProvider = resolveExtensionSearchProvider(settings);
|
|
799
|
+
const extensionVectorAdapter = resolveExtensionVectorAdapter(settings);
|
|
800
|
+
let effectiveMode = parseMode(options.mode, {
|
|
801
|
+
hasProvider: providerResolution.active !== null || extensionSearchProvider !== null,
|
|
802
|
+
hasVectorStore: vectorResolution.active !== null || extensionVectorAdapter !== null,
|
|
803
|
+
});
|
|
804
|
+
const loadedDocuments = await loadDocuments(pmRoot, settings.item_format ?? "json_markdown", typeRegistry.type_to_folder, settings.schema);
|
|
805
|
+
const warnings = loadedDocuments.warnings;
|
|
806
|
+
const allDocuments = loadedDocuments.documents;
|
|
807
|
+
const metadataFilteredDocuments = applyFilters(allDocuments, options, typeRegistry, runtimeFieldFilters);
|
|
808
|
+
const filteredDocuments = applyExactQueryFilters(metadataFilteredDocuments, normalizedQuery, {
|
|
809
|
+
titleExact,
|
|
810
|
+
phraseExact,
|
|
472
811
|
});
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
if (requestedMode === "keyword" && (filteredDocuments.length === 0 || limit === 0)) {
|
|
476
|
-
return emptySearchResult(query, requestedMode, options, includeLinked, scoreThreshold, hybridSemanticWeight);
|
|
812
|
+
if (effectiveMode === "keyword" && (filteredDocuments.length === 0 || limit === 0)) {
|
|
813
|
+
return emptySearchResult(query, effectiveMode, options, includeLinked, scoreThreshold, hybridSemanticWeight, projection, warnings);
|
|
477
814
|
}
|
|
478
815
|
const projectRoot = process.cwd();
|
|
479
816
|
const globalRoot = resolveGlobalPmRoot(projectRoot);
|
|
480
817
|
const linkedCorpusById = new Map();
|
|
481
|
-
if (includeLinked && (
|
|
818
|
+
if (includeLinked && (effectiveMode === "keyword" || effectiveMode === "hybrid")) {
|
|
482
819
|
for (const document of filteredDocuments) {
|
|
483
820
|
linkedCorpusById.set(document.front_matter.id, await loadLinkedCorpus(document, projectRoot, globalRoot));
|
|
484
821
|
}
|
|
485
822
|
}
|
|
486
823
|
const keywordHits = filteredDocuments
|
|
487
|
-
.map((document) => buildHybridLexicalScore(document, tokens,
|
|
824
|
+
.map((document) => buildHybridLexicalScore(document, tokens, normalizedQuery, effectiveMode !== "semantic", linkedCorpusById, tuning))
|
|
488
825
|
.filter((entry) => entry !== null);
|
|
489
826
|
let hits = keywordHits;
|
|
490
|
-
if (
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
filteredDocuments,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
827
|
+
if (effectiveMode !== "keyword") {
|
|
828
|
+
try {
|
|
829
|
+
if (!extensionSearchProvider) {
|
|
830
|
+
requireSemanticDependencies(effectiveMode, providerResolution, vectorResolution, extensionVectorAdapter !== null);
|
|
831
|
+
}
|
|
832
|
+
if (filteredDocuments.length === 0 || limit === 0) {
|
|
833
|
+
return emptySearchResult(query, effectiveMode, options, includeLinked, scoreThreshold, hybridSemanticWeight, projection, warnings);
|
|
834
|
+
}
|
|
835
|
+
const filteredById = new Map(filteredDocuments.map((document) => [document.front_matter.id, document]));
|
|
836
|
+
const canUseBuiltInSemantic = providerResolution.active !== null && (vectorResolution.active !== null || extensionVectorAdapter !== null);
|
|
837
|
+
if (extensionSearchProvider) {
|
|
838
|
+
try {
|
|
839
|
+
const providerResponse = await Promise.resolve(extensionSearchProvider.query({
|
|
840
|
+
query,
|
|
841
|
+
mode: effectiveMode,
|
|
842
|
+
tokens,
|
|
843
|
+
options,
|
|
844
|
+
settings,
|
|
845
|
+
documents: filteredDocuments,
|
|
846
|
+
}));
|
|
847
|
+
hits = normalizeExtensionProviderHits(extensionSearchProvider.providerName, providerResponse, filteredById);
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
if (!canUseBuiltInSemantic) {
|
|
851
|
+
throw new PmCliError(`Extension search provider "${extensionSearchProvider.providerName}" failed: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODE.GENERIC_FAILURE);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (hits === keywordHits) {
|
|
856
|
+
const implicitAutoHybridMode = runtimeDefaultsResolution.auto_ollama_defaults_applied && !modeWasExplicit && effectiveMode === "hybrid";
|
|
857
|
+
const { provider, vectorStore } = requireSemanticDependencies(effectiveMode, providerResolution, vectorResolution, extensionVectorAdapter !== null);
|
|
858
|
+
hits = await computeSemanticOrHybridHits({
|
|
859
|
+
requestedMode: effectiveMode,
|
|
860
|
+
query,
|
|
861
|
+
filteredDocuments,
|
|
862
|
+
keywordHits,
|
|
863
|
+
hybridSemanticWeight,
|
|
864
|
+
limit,
|
|
865
|
+
maxResults,
|
|
866
|
+
provider,
|
|
867
|
+
vectorStore,
|
|
868
|
+
extensionVectorAdapter,
|
|
869
|
+
settings,
|
|
870
|
+
...(implicitAutoHybridMode
|
|
871
|
+
? {
|
|
872
|
+
embeddingTimeoutMs: IMPLICIT_AUTO_HYBRID_EMBEDDING_TIMEOUT_MS,
|
|
873
|
+
vectorQueryTimeoutMs: IMPLICIT_AUTO_HYBRID_VECTOR_TIMEOUT_MS,
|
|
874
|
+
}
|
|
875
|
+
: {}),
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
const canFallbackToKeyword = runtimeDefaultsResolution.auto_ollama_defaults_applied && !modeWasExplicit && effectiveMode === "hybrid";
|
|
881
|
+
if (!canFallbackToKeyword) {
|
|
882
|
+
throw error;
|
|
883
|
+
}
|
|
884
|
+
effectiveMode = "keyword";
|
|
885
|
+
hits = keywordHits;
|
|
886
|
+
warnings.push(buildImplicitSemanticFallbackWarning(error));
|
|
887
|
+
}
|
|
506
888
|
}
|
|
507
889
|
const thresholded = hits.filter((entry) => entry.score >= scoreThreshold);
|
|
508
|
-
const sorted = sortHits(thresholded);
|
|
509
|
-
const
|
|
510
|
-
const limited =
|
|
890
|
+
const sorted = sortHits(thresholded, statusRegistry);
|
|
891
|
+
const resolvedLimit = effectiveMode === "keyword" ? limit : (limit ?? maxResults);
|
|
892
|
+
const limited = resolvedLimit === undefined ? sorted : sorted.slice(0, resolvedLimit);
|
|
893
|
+
const projectedItems = projectSearchHits(limited, projection);
|
|
894
|
+
const projectionFields = projection.mode === "full" ? null : [...projection.fields];
|
|
511
895
|
return {
|
|
512
896
|
query: query.trim(),
|
|
513
|
-
mode:
|
|
514
|
-
items:
|
|
515
|
-
count:
|
|
897
|
+
mode: effectiveMode,
|
|
898
|
+
items: projectedItems,
|
|
899
|
+
count: projectedItems.length,
|
|
516
900
|
filters: {
|
|
517
|
-
mode:
|
|
901
|
+
mode: effectiveMode,
|
|
518
902
|
type: options.type ?? null,
|
|
519
903
|
tag: options.tag ?? null,
|
|
520
904
|
priority: options.priority ?? null,
|
|
521
905
|
deadline_before: options.deadlineBefore ?? null,
|
|
522
906
|
deadline_after: options.deadlineAfter ?? null,
|
|
523
907
|
include_linked: includeLinked,
|
|
908
|
+
title_exact: titleExact,
|
|
909
|
+
phrase_exact: phraseExact,
|
|
524
910
|
score_threshold: scoreThreshold,
|
|
525
|
-
hybrid_semantic_weight:
|
|
911
|
+
hybrid_semantic_weight: effectiveMode === "hybrid" ? hybridSemanticWeight : null,
|
|
526
912
|
limit: options.limit ?? null,
|
|
913
|
+
runtime_filters: runtimeFieldFilters,
|
|
914
|
+
projection: projection.mode,
|
|
915
|
+
fields: projectionFields,
|
|
916
|
+
},
|
|
917
|
+
projection: {
|
|
918
|
+
mode: projection.mode,
|
|
919
|
+
fields: projectionFields,
|
|
527
920
|
},
|
|
528
921
|
now: nowIso(),
|
|
922
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
529
923
|
};
|
|
530
924
|
}
|
|
531
925
|
//# sourceMappingURL=search.js.map
|