@unbrained/pm-cli 2026.5.29 → 2026.5.31
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/CHANGELOG.md +54 -12
- package/dist/cli/bootstrap-args.d.ts +1 -1
- package/dist/cli/bootstrap-args.js +59 -9
- package/dist/cli/bootstrap-args.js.map +1 -1
- package/dist/cli/commands/activity.js +4 -4
- package/dist/cli/commands/activity.js.map +1 -1
- package/dist/cli/commands/calendar.js +83 -11
- package/dist/cli/commands/calendar.js.map +1 -1
- package/dist/cli/commands/close.js +4 -4
- package/dist/cli/commands/close.js.map +1 -1
- package/dist/cli/commands/completion.js +4 -4
- package/dist/cli/commands/completion.js.map +1 -1
- package/dist/cli/commands/create.js +50 -7
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/deps.js +4 -4
- package/dist/cli/commands/deps.js.map +1 -1
- package/dist/cli/commands/extension/install-sources.d.ts +3 -0
- package/dist/cli/commands/extension/install-sources.js +29 -4
- package/dist/cli/commands/extension/install-sources.js.map +1 -1
- package/dist/cli/commands/extension.js +26 -5
- package/dist/cli/commands/extension.js.map +1 -1
- package/dist/cli/commands/get.js +6 -3
- package/dist/cli/commands/get.js.map +1 -1
- package/dist/cli/commands/list.js +25 -6
- package/dist/cli/commands/list.js.map +1 -1
- package/dist/cli/commands/reindex.js +3 -3
- package/dist/cli/commands/reindex.js.map +1 -1
- package/dist/cli/commands/search.d.ts +17 -0
- package/dist/cli/commands/search.js +40 -10
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/stats.js +4 -4
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/commands/test-all.js +4 -4
- package/dist/cli/commands/test-all.js.map +1 -1
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +39 -11
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/register-list-query.js +10 -12
- package/dist/cli/register-list-query.js.map +1 -1
- package/dist/cli/register-mutation.js +20 -22
- package/dist/cli/register-mutation.js.map +1 -1
- package/dist/cli/register-operations.js +32 -25
- package/dist/cli/register-operations.js.map +1 -1
- package/dist/cli/register-setup.js +7 -9
- package/dist/cli/register-setup.js.map +1 -1
- package/dist/cli/registration-helpers.js +6 -3
- package/dist/cli/registration-helpers.js.map +1 -1
- package/dist/cli/search-refresh.d.ts +2 -0
- package/dist/cli/search-refresh.js +13 -0
- package/dist/cli/search-refresh.js.map +1 -0
- package/dist/cli-bundle/chunks/activity-OWYSUZOY.js +17 -0
- package/dist/cli-bundle/chunks/activity-OWYSUZOY.js.map +7 -0
- package/dist/cli-bundle/chunks/aggregate-ADUOSFRO.js +209 -0
- package/dist/cli-bundle/chunks/aggregate-ADUOSFRO.js.map +7 -0
- package/dist/cli-bundle/chunks/append-JD2DETU7.js +73 -0
- package/dist/cli-bundle/chunks/append-JD2DETU7.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-3HUE7XOF.js +27 -0
- package/dist/cli-bundle/chunks/chunk-3HUE7XOF.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-3M73P3G6.js +1636 -0
- package/dist/cli-bundle/chunks/chunk-3M73P3G6.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-4AEZR5EJ.js +189 -0
- package/dist/cli-bundle/chunks/chunk-4AEZR5EJ.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-5HSEP75M.js +330 -0
- package/dist/cli-bundle/chunks/chunk-5HSEP75M.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-6HT2F4TK.js +1770 -0
- package/dist/cli-bundle/chunks/chunk-6HT2F4TK.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-6YCV7WSL.js +552 -0
- package/dist/cli-bundle/chunks/chunk-6YCV7WSL.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-7Y53ZJIQ.js +202 -0
- package/dist/cli-bundle/chunks/chunk-7Y53ZJIQ.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-A7E7PUVX.js +157 -0
- package/dist/cli-bundle/chunks/chunk-A7E7PUVX.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-AKOOMO3T.js +90 -0
- package/dist/cli-bundle/chunks/chunk-AKOOMO3T.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-BUPWJ3IJ.js +950 -0
- package/dist/cli-bundle/chunks/chunk-BUPWJ3IJ.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-CEFIVZPG.js +79 -0
- package/dist/cli-bundle/chunks/chunk-CEFIVZPG.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-D7AOMPRH.js +349 -0
- package/dist/cli-bundle/chunks/chunk-D7AOMPRH.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-EAOG77HM.js +56 -0
- package/dist/cli-bundle/chunks/chunk-EAOG77HM.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-EGUGFSNX.js +47 -0
- package/dist/cli-bundle/chunks/chunk-EGUGFSNX.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-FM26RGSU.js +56 -0
- package/dist/cli-bundle/chunks/chunk-FM26RGSU.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-FQE6XZDB.js +435 -0
- package/dist/cli-bundle/chunks/chunk-FQE6XZDB.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-G57E5D7T.js +185 -0
- package/dist/cli-bundle/chunks/chunk-G57E5D7T.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-GN7IJPCD.js +3621 -0
- package/dist/cli-bundle/chunks/chunk-GN7IJPCD.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-JFWUHHS2.js +233 -0
- package/dist/cli-bundle/chunks/chunk-JFWUHHS2.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-JTWRXG4B.js +1861 -0
- package/dist/cli-bundle/chunks/chunk-JTWRXG4B.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-JULRTHJQ.js +648 -0
- package/dist/cli-bundle/chunks/chunk-JULRTHJQ.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-KBQY3YG5.js +122 -0
- package/dist/cli-bundle/chunks/chunk-KBQY3YG5.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-L2FXEKDB.js +185 -0
- package/dist/cli-bundle/chunks/chunk-L2FXEKDB.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-LRBOHPUE.js +189 -0
- package/dist/cli-bundle/chunks/chunk-LRBOHPUE.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-LSXGH6NE.js +5153 -0
- package/dist/cli-bundle/chunks/chunk-LSXGH6NE.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-MD7K4JYF.js +1345 -0
- package/dist/cli-bundle/chunks/chunk-MD7K4JYF.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-NGN7UV5K.js +341 -0
- package/dist/cli-bundle/chunks/chunk-NGN7UV5K.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-O7IWB35H.js +18 -0
- package/dist/cli-bundle/chunks/chunk-O7IWB35H.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-OGZEG5TT.js +2929 -0
- package/dist/cli-bundle/chunks/chunk-OGZEG5TT.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-P53TN6IA.js +674 -0
- package/dist/cli-bundle/chunks/chunk-P53TN6IA.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-PVNQLCIS.js +176 -0
- package/dist/cli-bundle/chunks/chunk-PVNQLCIS.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-PXUAZUUY.js +1680 -0
- package/dist/cli-bundle/chunks/chunk-PXUAZUUY.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-QAJBIGRA.js +20 -0
- package/dist/cli-bundle/chunks/chunk-QAJBIGRA.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-SEXQN4KL.js +60 -0
- package/dist/cli-bundle/chunks/chunk-SEXQN4KL.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-U7FMKRXH.js +362 -0
- package/dist/cli-bundle/chunks/chunk-U7FMKRXH.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-VGICHR7D.js +141 -0
- package/dist/cli-bundle/chunks/chunk-VGICHR7D.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-WDPXDQFG.js +632 -0
- package/dist/cli-bundle/chunks/chunk-WDPXDQFG.js.map +7 -0
- package/dist/cli-bundle/chunks/chunk-WJ2NAC37.js +252 -0
- package/dist/cli-bundle/chunks/chunk-WJ2NAC37.js.map +7 -0
- package/dist/cli-bundle/chunks/claim-EE4TKKCV.js +133 -0
- package/dist/cli-bundle/chunks/claim-EE4TKKCV.js.map +7 -0
- package/dist/cli-bundle/chunks/close-XY4CSAYF.js +14 -0
- package/dist/cli-bundle/chunks/close-XY4CSAYF.js.map +7 -0
- package/dist/cli-bundle/chunks/comments-AAKJQ6LW.js +104 -0
- package/dist/cli-bundle/chunks/comments-AAKJQ6LW.js.map +7 -0
- package/dist/cli-bundle/chunks/config-V3FZOKLI.js +1577 -0
- package/dist/cli-bundle/chunks/config-V3FZOKLI.js.map +7 -0
- package/dist/cli-bundle/chunks/context-RZ5QPNJB.js +1458 -0
- package/dist/cli-bundle/chunks/context-RZ5QPNJB.js.map +7 -0
- package/dist/cli-bundle/chunks/contracts-YLMH33VU.js +1245 -0
- package/dist/cli-bundle/chunks/contracts-YLMH33VU.js.map +7 -0
- package/dist/cli-bundle/chunks/create-PZVHRIZQ.js +28 -0
- package/dist/cli-bundle/chunks/create-PZVHRIZQ.js.map +7 -0
- package/dist/cli-bundle/chunks/delete-RHCTEORS.js +59 -0
- package/dist/cli-bundle/chunks/delete-RHCTEORS.js.map +7 -0
- package/dist/cli-bundle/chunks/deps-MZYCMLAO.js +229 -0
- package/dist/cli-bundle/chunks/deps-MZYCMLAO.js.map +7 -0
- package/dist/cli-bundle/chunks/docs-FXXUCZYU.js +35 -0
- package/dist/cli-bundle/chunks/docs-FXXUCZYU.js.map +7 -0
- package/dist/cli-bundle/chunks/extension-B4WL7BQU.js +25 -0
- package/dist/cli-bundle/chunks/extension-B4WL7BQU.js.map +7 -0
- package/dist/cli-bundle/chunks/files-WVOX67PE.js +286 -0
- package/dist/cli-bundle/chunks/files-WVOX67PE.js.map +7 -0
- package/dist/cli-bundle/chunks/gc-JWNF66EC.js +181 -0
- package/dist/cli-bundle/chunks/gc-JWNF66EC.js.map +7 -0
- package/dist/cli-bundle/chunks/get-LTK3HOCQ.js +198 -0
- package/dist/cli-bundle/chunks/get-LTK3HOCQ.js.map +7 -0
- package/dist/cli-bundle/chunks/health-TCWL55KM.js +1233 -0
- package/dist/cli-bundle/chunks/health-TCWL55KM.js.map +7 -0
- package/dist/cli-bundle/chunks/history-A2JUDXUE.js +21 -0
- package/dist/cli-bundle/chunks/history-A2JUDXUE.js.map +7 -0
- package/dist/cli-bundle/chunks/history-redact-ED6RFCPL.js +20 -0
- package/dist/cli-bundle/chunks/history-redact-ED6RFCPL.js.map +7 -0
- package/dist/cli-bundle/chunks/history-repair-ZWSTDRH2.js +198 -0
- package/dist/cli-bundle/chunks/history-repair-ZWSTDRH2.js.map +7 -0
- package/dist/cli-bundle/chunks/init-M2WUB4YH.js +16 -0
- package/dist/cli-bundle/chunks/init-M2WUB4YH.js.map +7 -0
- package/dist/cli-bundle/chunks/learnings-NXTWE3FR.js +48 -0
- package/dist/cli-bundle/chunks/learnings-NXTWE3FR.js.map +7 -0
- package/dist/cli-bundle/chunks/list-OGWQPC26.js +20 -0
- package/dist/cli-bundle/chunks/list-OGWQPC26.js.map +7 -0
- package/dist/cli-bundle/chunks/notes-DFS5ZGOO.js +48 -0
- package/dist/cli-bundle/chunks/notes-DFS5ZGOO.js.map +7 -0
- package/dist/cli-bundle/chunks/plan-67AB2PLM.js +1175 -0
- package/dist/cli-bundle/chunks/plan-67AB2PLM.js.map +7 -0
- package/dist/cli-bundle/chunks/register-list-query-UP3VRUFS.js +232 -0
- package/dist/cli-bundle/chunks/register-list-query-UP3VRUFS.js.map +7 -0
- package/dist/cli-bundle/chunks/register-mutation-YWSBF545.js +712 -0
- package/dist/cli-bundle/chunks/register-mutation-YWSBF545.js.map +7 -0
- package/dist/cli-bundle/chunks/register-operations-3BDTGSDH.js +374 -0
- package/dist/cli-bundle/chunks/register-operations-3BDTGSDH.js.map +7 -0
- package/dist/cli-bundle/chunks/register-setup-V56GDRUS.js +246 -0
- package/dist/cli-bundle/chunks/register-setup-V56GDRUS.js.map +7 -0
- package/dist/cli-bundle/chunks/restore-MB2ISME7.js +18 -0
- package/dist/cli-bundle/chunks/restore-MB2ISME7.js.map +7 -0
- package/dist/cli-bundle/chunks/schema-Q622KC46.js +119 -0
- package/dist/cli-bundle/chunks/schema-Q622KC46.js.map +7 -0
- package/dist/cli-bundle/chunks/search-HENBL456.js +1135 -0
- package/dist/cli-bundle/chunks/search-HENBL456.js.map +7 -0
- package/dist/cli-bundle/chunks/stats-N6H3YP2F.js +124 -0
- package/dist/cli-bundle/chunks/stats-N6H3YP2F.js.map +7 -0
- package/dist/cli-bundle/chunks/test-VNBDRJ4Z.js +32 -0
- package/dist/cli-bundle/chunks/test-VNBDRJ4Z.js.map +7 -0
- package/dist/cli-bundle/chunks/test-all-G2AZOHXI.js +332 -0
- package/dist/cli-bundle/chunks/test-all-G2AZOHXI.js.map +7 -0
- package/dist/cli-bundle/chunks/test-runs-GX5HTPSF.js +939 -0
- package/dist/cli-bundle/chunks/test-runs-GX5HTPSF.js.map +7 -0
- package/dist/cli-bundle/chunks/update-H6SCWNMA.js +22 -0
- package/dist/cli-bundle/chunks/update-H6SCWNMA.js.map +7 -0
- package/dist/cli-bundle/chunks/update-many-46HRSP2Q.js +676 -0
- package/dist/cli-bundle/chunks/update-many-46HRSP2Q.js.map +7 -0
- package/dist/cli-bundle/chunks/upgrade-H5HHM5UV.js +271 -0
- package/dist/cli-bundle/chunks/upgrade-H5HHM5UV.js.map +7 -0
- package/dist/cli-bundle/chunks/validate-TMCVKIGZ.js +1110 -0
- package/dist/cli-bundle/chunks/validate-TMCVKIGZ.js.map +7 -0
- package/dist/cli-bundle/main.js +6952 -0
- package/dist/cli-bundle/main.js.map +7 -0
- package/dist/cli.js +1 -1
- package/dist/core/config/nested-settings.d.ts +2 -0
- package/dist/core/config/nested-settings.js +17 -2
- package/dist/core/config/nested-settings.js.map +1 -1
- package/dist/core/item/item-type-definition.js +6 -2
- package/dist/core/item/item-type-definition.js.map +1 -1
- package/dist/core/item/type-registry.d.ts +4 -0
- package/dist/core/item/type-registry.js +10 -2
- package/dist/core/item/type-registry.js.map +1 -1
- package/dist/core/item/type-synonyms.d.ts +20 -0
- package/dist/core/item/type-synonyms.js +42 -0
- package/dist/core/item/type-synonyms.js.map +1 -0
- package/dist/core/search/background-refresh.d.ts +53 -0
- package/dist/core/search/background-refresh.js +286 -0
- package/dist/core/search/background-refresh.js.map +1 -0
- package/dist/core/search/cache.d.ts +20 -1
- package/dist/core/search/cache.js +76 -5
- package/dist/core/search/cache.js.map +1 -1
- package/dist/core/search/semantic-defaults.js +73 -32
- package/dist/core/search/semantic-defaults.js.map +1 -1
- package/dist/core/search/vector-stores.js +7 -3
- package/dist/core/search/vector-stores.js.map +1 -1
- package/dist/core/sentry/helpers.d.ts +1 -1
- package/dist/core/sentry/helpers.js +4 -4
- package/dist/core/sentry/helpers.js.map +1 -1
- package/dist/core/shared/constants.d.ts +2 -2
- package/dist/core/shared/constants.js +3 -3
- package/dist/core/shared/constants.js.map +1 -1
- package/dist/core/shared/html-entity-decode.d.ts +2 -1
- package/dist/core/shared/html-entity-decode.js +14 -9
- package/dist/core/shared/html-entity-decode.js.map +1 -1
- package/dist/core/shared/time.js +52 -2
- package/dist/core/shared/time.js.map +1 -1
- package/dist/core/store/front-matter-cache.d.ts +24 -0
- package/dist/core/store/front-matter-cache.js +119 -8
- package/dist/core/store/front-matter-cache.js.map +1 -1
- package/dist/core/store/item-format-migration.js +25 -15
- package/dist/core/store/item-format-migration.js.map +1 -1
- package/dist/core/store/item-store.d.ts +7 -0
- package/dist/core/store/item-store.js +13 -3
- package/dist/core/store/item-store.js.map +1 -1
- package/dist/core/store/settings-validator.d.ts +1 -0
- package/dist/core/store/settings-validator.js +7 -2
- package/dist/core/store/settings-validator.js.map +1 -1
- package/dist/core/store/settings.js +19 -3
- package/dist/core/store/settings.js.map +1 -1
- package/dist/core/telemetry/runtime.d.ts +10 -0
- package/dist/core/telemetry/runtime.js +86 -2
- package/dist/core/telemetry/runtime.js.map +1 -1
- package/dist/mcp/server.js +3 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +4 -4
- package/dist/sdk/cli-contracts/tool-parameter-tables.js.map +1 -1
- package/dist/sdk/cli-contracts.js +29 -8
- package/dist/sdk/cli-contracts.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/docs/CONFIGURATION.md +11 -0
- package/docs/RELEASING.md +1 -1
- package/package.json +7 -5
- package/packages/pm-calendar/extensions/calendar/index.js +27 -2
- package/packages/pm-calendar/extensions/calendar/index.ts +28 -2
- package/scripts/bundle-cli.mjs +39 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared lock id for any process that rewrites the local vector store. Reindex
|
|
3
|
+
* and the background mutation-refresh worker both acquire this so they never
|
|
4
|
+
* write concurrently and corrupt the store. Exported so `pm reindex` reuses the
|
|
5
|
+
* same id rather than duplicating the literal.
|
|
6
|
+
*/
|
|
7
|
+
export declare const REINDEX_LOCK_ID = "reindex";
|
|
8
|
+
/**
|
|
9
|
+
* Mutation search refresh runs inline (blocking) under test runners, inside the
|
|
10
|
+
* background worker child itself, or when explicitly requested. Otherwise the
|
|
11
|
+
* semantic refresh is dispatched to a detached child so mutations return without
|
|
12
|
+
* waiting on embeddings. Mirrors the telemetry-flush inline gate so the full test
|
|
13
|
+
* suite stays deterministic.
|
|
14
|
+
*/
|
|
15
|
+
export declare function shouldRunSearchRefreshInForeground(): boolean;
|
|
16
|
+
declare function pendingQueuePath(pmRoot: string): string;
|
|
17
|
+
declare function acquireQueueGate(pmRoot: string): boolean;
|
|
18
|
+
declare function releaseQueueGate(pmRoot: string): void;
|
|
19
|
+
/** Merge mutated item ids into the persistent pending-refresh queue. */
|
|
20
|
+
export declare function enqueuePendingRefreshIds(pmRoot: string, itemIds: string[]): Promise<void>;
|
|
21
|
+
/** Atomically read and clear the pending-refresh queue, returning drained ids. */
|
|
22
|
+
export declare function drainPendingRefreshIds(pmRoot: string): Promise<string[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Enqueue mutated ids and dispatch a detached worker child to refresh semantic
|
|
25
|
+
* embeddings without blocking the mutation. The reindex lock the worker acquires
|
|
26
|
+
* serializes concurrent refreshes, so the spawn itself stays unconditional and
|
|
27
|
+
* cheap; a worker that loses the lock exits and its ids are drained by the holder.
|
|
28
|
+
*/
|
|
29
|
+
export declare function scheduleBackgroundSemanticRefresh(pmRoot: string, itemIds: string[]): Promise<void>;
|
|
30
|
+
export interface SemanticRefreshWorkerResult {
|
|
31
|
+
processed: string[];
|
|
32
|
+
rounds: number;
|
|
33
|
+
warnings: string[];
|
|
34
|
+
}
|
|
35
|
+
type SemanticRefreshFn = (pmRoot: string, itemIds: string[]) => Promise<{
|
|
36
|
+
refreshed: string[];
|
|
37
|
+
skipped: string[];
|
|
38
|
+
warnings: string[];
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Drain the pending queue and refresh semantic vectors under the reindex lock so
|
|
42
|
+
* the local vector store is never rewritten concurrently. If the lock is held by
|
|
43
|
+
* another worker we exit immediately — that holder drains our enqueued ids. After
|
|
44
|
+
* releasing we re-check the queue once and re-dispatch if a sibling enqueued ids
|
|
45
|
+
* during our final window, guaranteeing eventual processing.
|
|
46
|
+
*/
|
|
47
|
+
export declare function runSemanticRefreshWorker(pmRoot: string, refresh: SemanticRefreshFn): Promise<SemanticRefreshWorkerResult>;
|
|
48
|
+
export declare const _backgroundRefreshInternals: {
|
|
49
|
+
pendingQueuePath: typeof pendingQueuePath;
|
|
50
|
+
acquireQueueGate: typeof acquireQueueGate;
|
|
51
|
+
releaseQueueGate: typeof releaseQueueGate;
|
|
52
|
+
};
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d8990236-9b78-570d-bafa-5da0ce621293")}catch(e){}}();
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { mkdirSync, rmSync, statSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { pathExists, readFileIfExists, writeFileAtomic } from "../fs/fs-utils.js";
|
|
7
|
+
import { acquireLock } from "../lock/lock.js";
|
|
8
|
+
import { resolvePmPackageRootFromModule } from "../packages/root.js";
|
|
9
|
+
import { readSettings } from "../store/settings.js";
|
|
10
|
+
import { toErrorMessage } from "../shared/primitives.js";
|
|
11
|
+
import { resolveSettingsWithSemanticRuntimeDefaults } from "./semantic-defaults.js";
|
|
12
|
+
/**
|
|
13
|
+
* Shared lock id for any process that rewrites the local vector store. Reindex
|
|
14
|
+
* and the background mutation-refresh worker both acquire this so they never
|
|
15
|
+
* write concurrently and corrupt the store. Exported so `pm reindex` reuses the
|
|
16
|
+
* same id rather than duplicating the literal.
|
|
17
|
+
*/
|
|
18
|
+
export const REINDEX_LOCK_ID = "reindex";
|
|
19
|
+
const PENDING_QUEUE_REL_PATH = "search/pending-refresh.json";
|
|
20
|
+
const PENDING_QUEUE_GATE_REL_PATH = "search/pending-refresh.gate.lock";
|
|
21
|
+
const PENDING_QUEUE_GATE_STALE_MS = 30_000;
|
|
22
|
+
// Bounded reindex-lock retry for the background worker (~3.2s worst case).
|
|
23
|
+
const LOCK_ACQUIRE_ATTEMPTS = 8;
|
|
24
|
+
const LOCK_ACQUIRE_BACKOFF_MS = 400;
|
|
25
|
+
const SEARCH_REFRESH_INLINE_ENV = "PM_SEARCH_REFRESH_INLINE";
|
|
26
|
+
const SEARCH_REFRESH_CHILD_ENV = "PM_SEARCH_REFRESH_CHILD";
|
|
27
|
+
function parseBooleanTrueLike(value) {
|
|
28
|
+
if (typeof value !== "string") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const normalized = value.trim().toLowerCase();
|
|
32
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Mutation search refresh runs inline (blocking) under test runners, inside the
|
|
36
|
+
* background worker child itself, or when explicitly requested. Otherwise the
|
|
37
|
+
* semantic refresh is dispatched to a detached child so mutations return without
|
|
38
|
+
* waiting on embeddings. Mirrors the telemetry-flush inline gate so the full test
|
|
39
|
+
* suite stays deterministic.
|
|
40
|
+
*/
|
|
41
|
+
export function shouldRunSearchRefreshInForeground() {
|
|
42
|
+
if (parseBooleanTrueLike(process.env[SEARCH_REFRESH_INLINE_ENV])) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
if (parseBooleanTrueLike(process.env[SEARCH_REFRESH_CHILD_ENV])) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
const nodeEnv = (process.env.NODE_ENV ?? "").trim().toLowerCase();
|
|
49
|
+
return (typeof process.env.VITEST === "string" ||
|
|
50
|
+
typeof process.env.VITEST_WORKER_ID === "string" ||
|
|
51
|
+
nodeEnv === "test");
|
|
52
|
+
}
|
|
53
|
+
function pendingQueuePath(pmRoot) {
|
|
54
|
+
return path.join(pmRoot, PENDING_QUEUE_REL_PATH);
|
|
55
|
+
}
|
|
56
|
+
function pendingQueueGatePath(pmRoot) {
|
|
57
|
+
return path.join(pmRoot, PENDING_QUEUE_GATE_REL_PATH);
|
|
58
|
+
}
|
|
59
|
+
function acquireQueueGate(pmRoot) {
|
|
60
|
+
const gatePath = pendingQueueGatePath(pmRoot);
|
|
61
|
+
try {
|
|
62
|
+
if (Date.now() - statSync(gatePath).mtimeMs < PENDING_QUEUE_GATE_STALE_MS) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
rmSync(gatePath, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Missing gate is the common case; fall through to create it.
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(path.dirname(gatePath), { recursive: true });
|
|
72
|
+
mkdirSync(gatePath);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function releaseQueueGate(pmRoot) {
|
|
80
|
+
try {
|
|
81
|
+
rmSync(pendingQueueGatePath(pmRoot), { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Best effort; a stale gate self-heals after PENDING_QUEUE_GATE_STALE_MS.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function withQueueGate(pmRoot, fn, fallback) {
|
|
88
|
+
// Briefly serialize read-modify-write access to the pending queue across the
|
|
89
|
+
// mutation process and the worker child. Acquisition is best effort; if a
|
|
90
|
+
// healthy gate is held we skip rather than risk a torn queue.
|
|
91
|
+
for (let attempt = 0; attempt < 50; attempt += 1) {
|
|
92
|
+
if (acquireQueueGate(pmRoot)) {
|
|
93
|
+
try {
|
|
94
|
+
return await fn();
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
releaseQueueGate(pmRoot);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
await delay(20);
|
|
101
|
+
}
|
|
102
|
+
return fallback;
|
|
103
|
+
}
|
|
104
|
+
function delay(ms) {
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
setTimeout(resolve, ms).unref?.();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function readPendingQueueIds(pmRoot) {
|
|
110
|
+
const raw = await readFileIfExists(pendingQueuePath(pmRoot));
|
|
111
|
+
if (!raw || raw.trim().length === 0) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const parsed = JSON.parse(raw);
|
|
116
|
+
if (!Array.isArray(parsed.ids)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
return parsed.ids.filter((id) => typeof id === "string" && id.length > 0);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function writePendingQueueIds(pmRoot, ids) {
|
|
126
|
+
await writeFileAtomic(pendingQueuePath(pmRoot), `${JSON.stringify({ ids })}\n`);
|
|
127
|
+
}
|
|
128
|
+
/** Merge mutated item ids into the persistent pending-refresh queue. */
|
|
129
|
+
export async function enqueuePendingRefreshIds(pmRoot, itemIds) {
|
|
130
|
+
const incoming = itemIds.filter((id) => typeof id === "string" && id.trim().length > 0);
|
|
131
|
+
if (incoming.length === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const mergeIncoming = async () => {
|
|
135
|
+
const existing = await readPendingQueueIds(pmRoot);
|
|
136
|
+
const merged = [...new Set([...existing, ...incoming])].sort((left, right) => left.localeCompare(right));
|
|
137
|
+
await writePendingQueueIds(pmRoot, merged);
|
|
138
|
+
};
|
|
139
|
+
const gated = await withQueueGate(pmRoot, async () => {
|
|
140
|
+
await mergeIncoming();
|
|
141
|
+
return true;
|
|
142
|
+
}, false);
|
|
143
|
+
if (!gated) {
|
|
144
|
+
// The gate stayed contended past the retry budget. Never silently drop a
|
|
145
|
+
// mutation's refresh work: fall back to a best-effort unguarded merge.
|
|
146
|
+
// writeFileAtomic keeps the file valid; the only risk is losing a concurrent
|
|
147
|
+
// writer's delta, which its own next dispatch and the vector_index_stale
|
|
148
|
+
// warning both recover.
|
|
149
|
+
await mergeIncoming();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Atomically read and clear the pending-refresh queue, returning drained ids. */
|
|
153
|
+
export async function drainPendingRefreshIds(pmRoot) {
|
|
154
|
+
return withQueueGate(pmRoot, async () => {
|
|
155
|
+
const ids = await readPendingQueueIds(pmRoot);
|
|
156
|
+
if (ids.length > 0) {
|
|
157
|
+
await writePendingQueueIds(pmRoot, []);
|
|
158
|
+
}
|
|
159
|
+
return ids;
|
|
160
|
+
}, []);
|
|
161
|
+
}
|
|
162
|
+
function searchRefreshRunnerPath() {
|
|
163
|
+
return path.join(resolvePmPackageRootFromModule(import.meta.url, ["../../.."]), "dist", "cli", "search-refresh.js");
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Spawn the detached refresh worker child for this pm root. Best effort: a
|
|
167
|
+
* failed dispatch must never keep the CLI alive or fail a mutation; the next
|
|
168
|
+
* mutation re-dispatches and `vector_index_stale` warns about the catch-up
|
|
169
|
+
* window in the meantime.
|
|
170
|
+
*/
|
|
171
|
+
function dispatchRefreshChild(pmRoot) {
|
|
172
|
+
try {
|
|
173
|
+
const child = spawn(process.execPath, [searchRefreshRunnerPath()], {
|
|
174
|
+
detached: true,
|
|
175
|
+
stdio: "ignore",
|
|
176
|
+
env: {
|
|
177
|
+
...process.env,
|
|
178
|
+
PM_PATH: pmRoot,
|
|
179
|
+
[SEARCH_REFRESH_CHILD_ENV]: "1",
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
// A detached child can emit an async 'error' event (e.g. spawn EAGAIN/ENOENT,
|
|
183
|
+
// resource limits). Swallow it so it never becomes an unhandled error that
|
|
184
|
+
// crashes the parent CLI; dispatch is best effort.
|
|
185
|
+
child.on("error", () => { });
|
|
186
|
+
child.unref();
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Best effort — see doc comment above.
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Enqueue mutated ids and dispatch a detached worker child to refresh semantic
|
|
194
|
+
* embeddings without blocking the mutation. The reindex lock the worker acquires
|
|
195
|
+
* serializes concurrent refreshes, so the spawn itself stays unconditional and
|
|
196
|
+
* cheap; a worker that loses the lock exits and its ids are drained by the holder.
|
|
197
|
+
*/
|
|
198
|
+
export async function scheduleBackgroundSemanticRefresh(pmRoot, itemIds) {
|
|
199
|
+
await enqueuePendingRefreshIds(pmRoot, itemIds);
|
|
200
|
+
dispatchRefreshChild(pmRoot);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Drain the pending queue and refresh semantic vectors under the reindex lock so
|
|
204
|
+
* the local vector store is never rewritten concurrently. If the lock is held by
|
|
205
|
+
* another worker we exit immediately — that holder drains our enqueued ids. After
|
|
206
|
+
* releasing we re-check the queue once and re-dispatch if a sibling enqueued ids
|
|
207
|
+
* during our final window, guaranteeing eventual processing.
|
|
208
|
+
*/
|
|
209
|
+
export async function runSemanticRefreshWorker(pmRoot, refresh) {
|
|
210
|
+
const warnings = [];
|
|
211
|
+
const processed = [];
|
|
212
|
+
if (!(await pathExists(pmRoot))) {
|
|
213
|
+
return { processed, rounds: 0, warnings };
|
|
214
|
+
}
|
|
215
|
+
let settings;
|
|
216
|
+
try {
|
|
217
|
+
settings = resolveSettingsWithSemanticRuntimeDefaults(await readSettings(pmRoot)).settings;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
return { processed, rounds: 0, warnings: [`search_background_refresh_settings_read_failed:${toErrorMessage(error)}`] };
|
|
221
|
+
}
|
|
222
|
+
// Retry the reindex lock with backoff rather than exiting on the first
|
|
223
|
+
// conflict: a sibling refresh worker holds it only briefly (~one embed), so a
|
|
224
|
+
// few retries pick up the queued ids in this run instead of stranding them
|
|
225
|
+
// until the next mutation. If a long `pm reindex` keeps it, we give up — that
|
|
226
|
+
// full pass re-embeds everything anyway and the next dispatch retries.
|
|
227
|
+
let release = null;
|
|
228
|
+
for (let attempt = 0; attempt < LOCK_ACQUIRE_ATTEMPTS && !release; attempt += 1) {
|
|
229
|
+
try {
|
|
230
|
+
release = await acquireLock(pmRoot, REINDEX_LOCK_ID, settings.locks.ttl_seconds, process.env.PM_AUTHOR ?? "pm-search-refresh", false, settings.governance.force_required_for_stale_lock);
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
if (attempt < LOCK_ACQUIRE_ATTEMPTS - 1) {
|
|
234
|
+
await delay(LOCK_ACQUIRE_BACKOFF_MS);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!release) {
|
|
239
|
+
return { processed, rounds: 0, warnings };
|
|
240
|
+
}
|
|
241
|
+
let rounds = 0;
|
|
242
|
+
let failed = false;
|
|
243
|
+
try {
|
|
244
|
+
for (;;) {
|
|
245
|
+
const ids = await drainPendingRefreshIds(pmRoot);
|
|
246
|
+
if (ids.length === 0) {
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
rounds += 1;
|
|
250
|
+
try {
|
|
251
|
+
const result = await refresh(pmRoot, ids);
|
|
252
|
+
processed.push(...result.refreshed);
|
|
253
|
+
warnings.push(...result.warnings);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
warnings.push(`search_background_refresh_failed:${toErrorMessage(error)}`);
|
|
257
|
+
// Re-enqueue so the work is retried by a later dispatch rather than lost.
|
|
258
|
+
await enqueuePendingRefreshIds(pmRoot, ids);
|
|
259
|
+
failed = true;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
await release?.();
|
|
266
|
+
}
|
|
267
|
+
// Close the race where a sibling enqueued ids between our last empty drain and
|
|
268
|
+
// releasing the lock: re-dispatch so those ids are eventually processed. Skip
|
|
269
|
+
// this after a refresh failure — the failed ids were re-enqueued and an
|
|
270
|
+
// immediate respawn would just fail again, spinning indefinitely. They wait
|
|
271
|
+
// for the next mutation's dispatch instead.
|
|
272
|
+
const remaining = failed ? [] : await readPendingQueueIds(pmRoot);
|
|
273
|
+
if (remaining.length > 0) {
|
|
274
|
+
dispatchRefreshChild(pmRoot);
|
|
275
|
+
}
|
|
276
|
+
return { processed: [...new Set(processed)].sort((a, b) => a.localeCompare(b)), rounds, warnings };
|
|
277
|
+
}
|
|
278
|
+
// Expose internals for focused tests without widening the production surface
|
|
279
|
+
// used by the mutation hot path.
|
|
280
|
+
export const _backgroundRefreshInternals = {
|
|
281
|
+
pendingQueuePath,
|
|
282
|
+
acquireQueueGate,
|
|
283
|
+
releaseQueueGate,
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=background-refresh.js.map
|
|
286
|
+
//# debugId=d8990236-9b78-570d-bafa-5da0ce621293
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-refresh.js","sources":["core/search/background-refresh.ts"],"sourceRoot":"/","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { mkdirSync, rmSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { pathExists, readFileIfExists, writeFileAtomic } from \"../fs/fs-utils.js\";\nimport { acquireLock } from \"../lock/lock.js\";\nimport { resolvePmPackageRootFromModule } from \"../packages/root.js\";\nimport { readSettings } from \"../store/settings.js\";\nimport { toErrorMessage } from \"../shared/primitives.js\";\nimport { resolveSettingsWithSemanticRuntimeDefaults } from \"./semantic-defaults.js\";\n\n/**\n * Shared lock id for any process that rewrites the local vector store. Reindex\n * and the background mutation-refresh worker both acquire this so they never\n * write concurrently and corrupt the store. Exported so `pm reindex` reuses the\n * same id rather than duplicating the literal.\n */\nexport const REINDEX_LOCK_ID = \"reindex\";\n\nconst PENDING_QUEUE_REL_PATH = \"search/pending-refresh.json\";\nconst PENDING_QUEUE_GATE_REL_PATH = \"search/pending-refresh.gate.lock\";\nconst PENDING_QUEUE_GATE_STALE_MS = 30_000;\n\n// Bounded reindex-lock retry for the background worker (~3.2s worst case).\nconst LOCK_ACQUIRE_ATTEMPTS = 8;\nconst LOCK_ACQUIRE_BACKOFF_MS = 400;\n\nconst SEARCH_REFRESH_INLINE_ENV = \"PM_SEARCH_REFRESH_INLINE\";\nconst SEARCH_REFRESH_CHILD_ENV = \"PM_SEARCH_REFRESH_CHILD\";\n\nfunction parseBooleanTrueLike(value: string | undefined): boolean {\n if (typeof value !== \"string\") {\n return false;\n }\n const normalized = value.trim().toLowerCase();\n return normalized === \"1\" || normalized === \"true\" || normalized === \"yes\" || normalized === \"on\";\n}\n\n/**\n * Mutation search refresh runs inline (blocking) under test runners, inside the\n * background worker child itself, or when explicitly requested. Otherwise the\n * semantic refresh is dispatched to a detached child so mutations return without\n * waiting on embeddings. Mirrors the telemetry-flush inline gate so the full test\n * suite stays deterministic.\n */\nexport function shouldRunSearchRefreshInForeground(): boolean {\n if (parseBooleanTrueLike(process.env[SEARCH_REFRESH_INLINE_ENV])) {\n return true;\n }\n if (parseBooleanTrueLike(process.env[SEARCH_REFRESH_CHILD_ENV])) {\n return true;\n }\n const nodeEnv = (process.env.NODE_ENV ?? \"\").trim().toLowerCase();\n return (\n typeof process.env.VITEST === \"string\" ||\n typeof process.env.VITEST_WORKER_ID === \"string\" ||\n nodeEnv === \"test\"\n );\n}\n\nfunction pendingQueuePath(pmRoot: string): string {\n return path.join(pmRoot, PENDING_QUEUE_REL_PATH);\n}\n\nfunction pendingQueueGatePath(pmRoot: string): string {\n return path.join(pmRoot, PENDING_QUEUE_GATE_REL_PATH);\n}\n\nfunction acquireQueueGate(pmRoot: string): boolean {\n const gatePath = pendingQueueGatePath(pmRoot);\n try {\n if (Date.now() - statSync(gatePath).mtimeMs < PENDING_QUEUE_GATE_STALE_MS) {\n return false;\n }\n rmSync(gatePath, { recursive: true, force: true });\n } catch {\n // Missing gate is the common case; fall through to create it.\n }\n try {\n mkdirSync(path.dirname(gatePath), { recursive: true });\n mkdirSync(gatePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction releaseQueueGate(pmRoot: string): void {\n try {\n rmSync(pendingQueueGatePath(pmRoot), { recursive: true, force: true });\n } catch {\n // Best effort; a stale gate self-heals after PENDING_QUEUE_GATE_STALE_MS.\n }\n}\n\nasync function withQueueGate<T>(pmRoot: string, fn: () => Promise<T>, fallback: T): Promise<T> {\n // Briefly serialize read-modify-write access to the pending queue across the\n // mutation process and the worker child. Acquisition is best effort; if a\n // healthy gate is held we skip rather than risk a torn queue.\n for (let attempt = 0; attempt < 50; attempt += 1) {\n if (acquireQueueGate(pmRoot)) {\n try {\n return await fn();\n } finally {\n releaseQueueGate(pmRoot);\n }\n }\n await delay(20);\n }\n return fallback;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms).unref?.();\n });\n}\n\nasync function readPendingQueueIds(pmRoot: string): Promise<string[]> {\n const raw = await readFileIfExists(pendingQueuePath(pmRoot));\n if (!raw || raw.trim().length === 0) {\n return [];\n }\n try {\n const parsed = JSON.parse(raw) as { ids?: unknown };\n if (!Array.isArray(parsed.ids)) {\n return [];\n }\n return parsed.ids.filter((id): id is string => typeof id === \"string\" && id.length > 0);\n } catch {\n return [];\n }\n}\n\nasync function writePendingQueueIds(pmRoot: string, ids: string[]): Promise<void> {\n await writeFileAtomic(pendingQueuePath(pmRoot), `${JSON.stringify({ ids })}\\n`);\n}\n\n/** Merge mutated item ids into the persistent pending-refresh queue. */\nexport async function enqueuePendingRefreshIds(pmRoot: string, itemIds: string[]): Promise<void> {\n const incoming = itemIds.filter((id) => typeof id === \"string\" && id.trim().length > 0);\n if (incoming.length === 0) {\n return;\n }\n const mergeIncoming = async (): Promise<void> => {\n const existing = await readPendingQueueIds(pmRoot);\n const merged = [...new Set([...existing, ...incoming])].sort((left, right) => left.localeCompare(right));\n await writePendingQueueIds(pmRoot, merged);\n };\n const gated = await withQueueGate(\n pmRoot,\n async () => {\n await mergeIncoming();\n return true;\n },\n false,\n );\n if (!gated) {\n // The gate stayed contended past the retry budget. Never silently drop a\n // mutation's refresh work: fall back to a best-effort unguarded merge.\n // writeFileAtomic keeps the file valid; the only risk is losing a concurrent\n // writer's delta, which its own next dispatch and the vector_index_stale\n // warning both recover.\n await mergeIncoming();\n }\n}\n\n/** Atomically read and clear the pending-refresh queue, returning drained ids. */\nexport async function drainPendingRefreshIds(pmRoot: string): Promise<string[]> {\n return withQueueGate(\n pmRoot,\n async () => {\n const ids = await readPendingQueueIds(pmRoot);\n if (ids.length > 0) {\n await writePendingQueueIds(pmRoot, []);\n }\n return ids;\n },\n [],\n );\n}\n\nfunction searchRefreshRunnerPath(): string {\n return path.join(\n resolvePmPackageRootFromModule(import.meta.url, [\"../../..\"]),\n \"dist\",\n \"cli\",\n \"search-refresh.js\",\n );\n}\n\n/**\n * Spawn the detached refresh worker child for this pm root. Best effort: a\n * failed dispatch must never keep the CLI alive or fail a mutation; the next\n * mutation re-dispatches and `vector_index_stale` warns about the catch-up\n * window in the meantime.\n */\nfunction dispatchRefreshChild(pmRoot: string): void {\n try {\n const child = spawn(process.execPath, [searchRefreshRunnerPath()], {\n detached: true,\n stdio: \"ignore\",\n env: {\n ...process.env,\n PM_PATH: pmRoot,\n [SEARCH_REFRESH_CHILD_ENV]: \"1\",\n },\n });\n // A detached child can emit an async 'error' event (e.g. spawn EAGAIN/ENOENT,\n // resource limits). Swallow it so it never becomes an unhandled error that\n // crashes the parent CLI; dispatch is best effort.\n child.on(\"error\", () => {});\n child.unref();\n } catch {\n // Best effort — see doc comment above.\n }\n}\n\n/**\n * Enqueue mutated ids and dispatch a detached worker child to refresh semantic\n * embeddings without blocking the mutation. The reindex lock the worker acquires\n * serializes concurrent refreshes, so the spawn itself stays unconditional and\n * cheap; a worker that loses the lock exits and its ids are drained by the holder.\n */\nexport async function scheduleBackgroundSemanticRefresh(pmRoot: string, itemIds: string[]): Promise<void> {\n await enqueuePendingRefreshIds(pmRoot, itemIds);\n dispatchRefreshChild(pmRoot);\n}\n\nexport interface SemanticRefreshWorkerResult {\n processed: string[];\n rounds: number;\n warnings: string[];\n}\n\ntype SemanticRefreshFn = (\n pmRoot: string,\n itemIds: string[],\n) => Promise<{ refreshed: string[]; skipped: string[]; warnings: string[] }>;\n\n/**\n * Drain the pending queue and refresh semantic vectors under the reindex lock so\n * the local vector store is never rewritten concurrently. If the lock is held by\n * another worker we exit immediately — that holder drains our enqueued ids. After\n * releasing we re-check the queue once and re-dispatch if a sibling enqueued ids\n * during our final window, guaranteeing eventual processing.\n */\nexport async function runSemanticRefreshWorker(\n pmRoot: string,\n refresh: SemanticRefreshFn,\n): Promise<SemanticRefreshWorkerResult> {\n const warnings: string[] = [];\n const processed: string[] = [];\n if (!(await pathExists(pmRoot))) {\n return { processed, rounds: 0, warnings };\n }\n\n let settings: Awaited<ReturnType<typeof readSettings>>;\n try {\n settings = resolveSettingsWithSemanticRuntimeDefaults(await readSettings(pmRoot)).settings;\n } catch (error: unknown) {\n return { processed, rounds: 0, warnings: [`search_background_refresh_settings_read_failed:${toErrorMessage(error)}`] };\n }\n\n // Retry the reindex lock with backoff rather than exiting on the first\n // conflict: a sibling refresh worker holds it only briefly (~one embed), so a\n // few retries pick up the queued ids in this run instead of stranding them\n // until the next mutation. If a long `pm reindex` keeps it, we give up — that\n // full pass re-embeds everything anyway and the next dispatch retries.\n let release: (() => Promise<void>) | null = null;\n for (let attempt = 0; attempt < LOCK_ACQUIRE_ATTEMPTS && !release; attempt += 1) {\n try {\n release = await acquireLock(\n pmRoot,\n REINDEX_LOCK_ID,\n settings.locks.ttl_seconds,\n process.env.PM_AUTHOR ?? \"pm-search-refresh\",\n false,\n settings.governance.force_required_for_stale_lock,\n );\n } catch {\n if (attempt < LOCK_ACQUIRE_ATTEMPTS - 1) {\n await delay(LOCK_ACQUIRE_BACKOFF_MS);\n }\n }\n }\n if (!release) {\n return { processed, rounds: 0, warnings };\n }\n\n let rounds = 0;\n let failed = false;\n try {\n for (;;) {\n const ids = await drainPendingRefreshIds(pmRoot);\n if (ids.length === 0) {\n break;\n }\n rounds += 1;\n try {\n const result = await refresh(pmRoot, ids);\n processed.push(...result.refreshed);\n warnings.push(...result.warnings);\n } catch (error: unknown) {\n warnings.push(`search_background_refresh_failed:${toErrorMessage(error)}`);\n // Re-enqueue so the work is retried by a later dispatch rather than lost.\n await enqueuePendingRefreshIds(pmRoot, ids);\n failed = true;\n break;\n }\n }\n } finally {\n await release?.();\n }\n\n // Close the race where a sibling enqueued ids between our last empty drain and\n // releasing the lock: re-dispatch so those ids are eventually processed. Skip\n // this after a refresh failure — the failed ids were re-enqueued and an\n // immediate respawn would just fail again, spinning indefinitely. They wait\n // for the next mutation's dispatch instead.\n const remaining = failed ? [] : await readPendingQueueIds(pmRoot);\n if (remaining.length > 0) {\n dispatchRefreshChild(pmRoot);\n }\n\n return { processed: [...new Set(processed)].sort((a, b) => a.localeCompare(b)), rounds, warnings };\n}\n\n// Expose internals for focused tests without widening the production surface\n// used by the mutation hot path.\nexport const _backgroundRefreshInternals = {\n pendingQueuePath,\n acquireQueueGate,\n releaseQueueGate,\n};\n"],"names":[],"mappings":"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","debugId":"d8990236-9b78-570d-bafa-5da0ce621293"}
|
|
@@ -13,6 +13,17 @@ export interface SemanticMutationRefreshResult {
|
|
|
13
13
|
export interface SearchMutationArtifactRefreshResult extends SearchCacheInvalidationResult {
|
|
14
14
|
refreshed: string[];
|
|
15
15
|
skipped: string[];
|
|
16
|
+
/** True when the semantic refresh was dispatched to a detached background worker. */
|
|
17
|
+
scheduled?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface RefreshSearchArtifactsForMutationOptions {
|
|
20
|
+
/**
|
|
21
|
+
* When true and semantic search is active, the (slow) embedding refresh is
|
|
22
|
+
* dispatched to a detached background worker instead of awaited inline so the
|
|
23
|
+
* mutation returns immediately. The synchronous keyword-cache invalidation
|
|
24
|
+
* still runs first. Callers pass this only outside test runners.
|
|
25
|
+
*/
|
|
26
|
+
background?: boolean;
|
|
16
27
|
}
|
|
17
28
|
export interface SemanticRefreshOptions {
|
|
18
29
|
settings?: Awaited<ReturnType<typeof readSettings>>;
|
|
@@ -26,4 +37,12 @@ export declare function readVectorizationStatusLedger(pmRoot: string): Promise<V
|
|
|
26
37
|
export declare function writeVectorizationStatusLedger(pmRoot: string, entries: Record<string, string>): Promise<void>;
|
|
27
38
|
export declare function invalidateSearchCacheArtifacts(pmRoot: string): Promise<SearchCacheInvalidationResult>;
|
|
28
39
|
export declare function refreshSemanticEmbeddingsForMutatedItems(pmRoot: string, itemIds: string[], options?: SemanticRefreshOptions): Promise<SemanticMutationRefreshResult>;
|
|
29
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Returns true when a semantic embedding refresh would do real work for these
|
|
42
|
+
* settings (an embedding provider AND a vector store both resolve). Used to
|
|
43
|
+
* decide whether a mutation's refresh is worth dispatching to a background
|
|
44
|
+
* worker — when semantic search is not configured the inline path is already a
|
|
45
|
+
* fast no-op and no child is spawned.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isSemanticRefreshActive(settings: Awaited<ReturnType<typeof readSettings>>, applyRuntimeDefaults: boolean): boolean;
|
|
48
|
+
export declare function refreshSearchArtifactsForMutation(pmRoot: string, itemIds: string[], options?: RefreshSearchArtifactsForMutationOptions): Promise<SearchMutationArtifactRefreshResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d04073fd-fa5a-5454-8e71-11bc477005b0")}catch(e){}}();
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { getActiveExtensionRegistrations } from "../extensions/index.js";
|
|
5
5
|
import { pathExists, readFileIfExists, removeFileIfExists, writeFileAtomic } from "../fs/fs-utils.js";
|
|
@@ -12,6 +12,7 @@ import { executeEmbeddingBatchesWithRetry } from "./embedding-batches.js";
|
|
|
12
12
|
import { buildSemanticCorpusInput } from "./corpus.js";
|
|
13
13
|
import { resolveEmbeddingProviders } from "./providers.js";
|
|
14
14
|
import { resolveSettingsWithSemanticRuntimeDefaults } from "./semantic-defaults.js";
|
|
15
|
+
import { scheduleBackgroundSemanticRefresh } from "./background-refresh.js";
|
|
15
16
|
import { executeVectorDelete, executeVectorUpsert, resolveVectorStores } from "./vector-stores.js";
|
|
16
17
|
import { nowIso } from "../shared/time.js";
|
|
17
18
|
export const SEARCH_CACHE_ARTIFACT_PATHS = ["index/manifest.json", "search/embeddings.jsonl"];
|
|
@@ -290,10 +291,80 @@ export async function refreshSemanticEmbeddingsForMutatedItems(pmRoot, itemIds,
|
|
|
290
291
|
warnings: [...workload.warnings, ...refreshedResult.warnings, ...pruneResult.warnings, ...ledgerWarnings],
|
|
291
292
|
};
|
|
292
293
|
}
|
|
293
|
-
|
|
294
|
+
/**
|
|
295
|
+
* Returns true when a semantic embedding refresh would do real work for these
|
|
296
|
+
* settings (an embedding provider AND a vector store both resolve). Used to
|
|
297
|
+
* decide whether a mutation's refresh is worth dispatching to a background
|
|
298
|
+
* worker — when semantic search is not configured the inline path is already a
|
|
299
|
+
* fast no-op and no child is spawned.
|
|
300
|
+
*/
|
|
301
|
+
export function isSemanticRefreshActive(settings, applyRuntimeDefaults) {
|
|
302
|
+
const effectiveSettings = applyRuntimeDefaults
|
|
303
|
+
? resolveSettingsWithSemanticRuntimeDefaults(settings).settings
|
|
304
|
+
: settings;
|
|
305
|
+
return Boolean(resolveEmbeddingProviders(effectiveSettings).active && resolveVectorStores(effectiveSettings).active);
|
|
306
|
+
}
|
|
307
|
+
export async function refreshSearchArtifactsForMutation(pmRoot, itemIds, options = {}) {
|
|
294
308
|
const invalidation = await invalidateSearchCacheArtifacts(pmRoot);
|
|
295
|
-
const
|
|
296
|
-
|
|
309
|
+
const normalizedItemIds = toUniqueSorted(itemIds);
|
|
310
|
+
if (normalizedItemIds.length === 0) {
|
|
311
|
+
return {
|
|
312
|
+
invalidated: invalidation.invalidated,
|
|
313
|
+
refreshed: [],
|
|
314
|
+
skipped: [],
|
|
315
|
+
warnings: invalidation.warnings,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (!(await pathExists(getSettingsPath(pmRoot)))) {
|
|
319
|
+
const semanticRefresh = await refreshSemanticEmbeddingsForMutatedItems(pmRoot, normalizedItemIds);
|
|
320
|
+
return {
|
|
321
|
+
invalidated: invalidation.invalidated,
|
|
322
|
+
refreshed: semanticRefresh.refreshed,
|
|
323
|
+
skipped: semanticRefresh.skipped,
|
|
324
|
+
warnings: [...invalidation.warnings, ...semanticRefresh.warnings],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
let settings;
|
|
328
|
+
try {
|
|
329
|
+
settings = await readSettings(pmRoot);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
return {
|
|
333
|
+
invalidated: invalidation.invalidated,
|
|
334
|
+
refreshed: [],
|
|
335
|
+
skipped: normalizedItemIds,
|
|
336
|
+
warnings: [
|
|
337
|
+
...invalidation.warnings,
|
|
338
|
+
`search_semantic_refresh_skipped:settings_read_failed:${toErrorMessage(error)}`,
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (settings.search.mutation_refresh_policy === "cache_only") {
|
|
343
|
+
return {
|
|
344
|
+
invalidated: invalidation.invalidated,
|
|
345
|
+
refreshed: [],
|
|
346
|
+
skipped: normalizedItemIds,
|
|
347
|
+
warnings: invalidation.warnings,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const applyRuntimeDefaults = settings.search.mutation_refresh_policy === "semantic_auto";
|
|
351
|
+
// Keyword cache is already invalidated synchronously above (so keyword search
|
|
352
|
+
// stays immediately correct). When requested, dispatch the slow embedding
|
|
353
|
+
// refresh to a detached worker so the mutation returns instantly; the
|
|
354
|
+
// vector_index_stale health/search warning covers the catch-up window.
|
|
355
|
+
if (options.background && isSemanticRefreshActive(settings, applyRuntimeDefaults)) {
|
|
356
|
+
await scheduleBackgroundSemanticRefresh(pmRoot, normalizedItemIds);
|
|
357
|
+
return {
|
|
358
|
+
invalidated: invalidation.invalidated,
|
|
359
|
+
refreshed: [],
|
|
360
|
+
skipped: [],
|
|
361
|
+
warnings: [...invalidation.warnings, "search_semantic_refresh_scheduled_background"],
|
|
362
|
+
scheduled: true,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const semanticRefresh = await refreshSemanticEmbeddingsForMutatedItems(pmRoot, normalizedItemIds, {
|
|
366
|
+
settings,
|
|
367
|
+
apply_runtime_defaults: applyRuntimeDefaults,
|
|
297
368
|
});
|
|
298
369
|
return {
|
|
299
370
|
invalidated: invalidation.invalidated,
|
|
@@ -303,4 +374,4 @@ export async function refreshSearchArtifactsForMutation(pmRoot, itemIds) {
|
|
|
303
374
|
};
|
|
304
375
|
}
|
|
305
376
|
//# sourceMappingURL=cache.js.map
|
|
306
|
-
//# debugId=
|
|
377
|
+
//# debugId=d04073fd-fa5a-5454-8e71-11bc477005b0
|