@unbrained/pm-cli 2026.9.2 → 2026.9.3
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +17 -0
- package/dist/cli/after-command-context-usage.js +16 -3
- package/dist/cli-bundle/bundle-manifest.json +154 -154
- package/dist/cli-bundle/chunks/{chunk-T6H5BZCP.js → chunk-34HASX3S.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-AY324UJ5.js +9 -0
- package/dist/cli-bundle/chunks/{chunk-TAG4VC22.js → chunk-DKGP3QLE.js} +27 -27
- package/dist/cli-bundle/chunks/chunk-E5RBBCXO.js +230 -0
- package/dist/cli-bundle/chunks/{chunk-DTGWEL23.js → chunk-EDPIS7EB.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-X4RCGLTX.js → chunk-IEHHE6GA.js} +5 -5
- package/dist/cli-bundle/chunks/{chunk-UDW64J2W.js → chunk-L7B7343B.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-FZ4Z2J4F.js → chunk-LENC656S.js} +41 -41
- package/dist/cli-bundle/chunks/{chunk-ZLJIFSOE.js → chunk-LISESA7W.js} +33 -33
- package/dist/cli-bundle/chunks/{chunk-3WICXLVT.js → chunk-UPPM2FOZ.js} +2 -2
- package/dist/cli-bundle/chunks/{chunk-Y7VOMSZT.js → chunk-Y4PJ6HPK.js} +2 -2
- package/dist/cli-bundle/chunks/{register-list-query-XYNX4ASQ.js → register-list-query-W2ZTOMKP.js} +2 -2
- package/dist/cli-bundle/chunks/{register-mutation-WFQ7MJTX.js → register-mutation-RVND5NCG.js} +2 -2
- package/dist/cli-bundle/chunks/{register-operations-JVTI7JPP.js → register-operations-TTROIVC4.js} +2 -2
- package/dist/cli-bundle/chunks/{register-setup-KVNC5KPL.js → register-setup-D3ZFHY3L.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-3ZBI35HU.js +176 -0
- package/dist/cli-bundle/focused-chunks/{chunk-SVXJYD6G.js → chunk-4HJFY4MB.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-4RRHC3S4.js +6 -0
- package/dist/cli-bundle/focused-chunks/chunk-4WR46RMG.js +9 -0
- package/dist/cli-bundle/focused-chunks/chunk-5RFXUPN5.js +3 -0
- package/dist/cli-bundle/focused-chunks/chunk-BWKHEIVF.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-4O4CJAR6.js → chunk-D7KXFBXV.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-MKHG3T76.js → chunk-GHCZ3YFR.js} +3 -3
- package/dist/cli-bundle/focused-chunks/{chunk-KB5OFSFD.js → chunk-GT7MXDLW.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-KBLZRU62.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-KZ2NB3ET.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-M5M6SVHS.js +3 -0
- package/dist/cli-bundle/focused-chunks/{chunk-BEVHCEWG.js → chunk-OL5EH6I3.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-MEASX544.js → chunk-Q45NHPF2.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-O67VDLOT.js → chunk-QKHCSUGJ.js} +42 -42
- package/dist/cli-bundle/focused-chunks/chunk-S27XMBIN.js +8 -0
- package/dist/cli-bundle/focused-chunks/chunk-TXE4TSHM.js +12 -0
- package/dist/cli-bundle/focused-chunks/chunk-UCLO3XDU.js +2 -0
- package/dist/cli-bundle/main.js +15 -15
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +28 -28
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +35 -33
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +9 -9
- package/dist/core/history/event-classification.d.ts +21 -0
- package/dist/core/history/event-classification.js +109 -0
- package/dist/core/history/event-index.d.ts +14 -3
- package/dist/core/history/event-index.js +543 -88
- package/dist/core/history/history.js +47 -18
- package/dist/core/history/workspace-history.js +7 -6
- package/dist/core/lock/lock.d.ts +22 -2
- package/dist/core/lock/lock.js +121 -19
- package/dist/core/output/output.js +31 -7
- package/dist/core/shared/time.d.ts +2 -0
- package/dist/core/shared/time.js +8 -2
- package/dist/core/store/item-metadata-query-index.js +5 -5
- package/dist/sdk/context/recency.d.ts +9 -0
- package/dist/sdk/context/recency.js +34 -0
- package/dist/sdk/context/usage-egress.d.ts +9 -0
- package/dist/sdk/context/usage-egress.js +30 -0
- package/dist/sdk/context-intent-contracts.js +7 -3
- package/dist/sdk/context-relevance.d.ts +23 -0
- package/dist/sdk/context-relevance.js +94 -23
- package/dist/sdk/context-signal-store.d.ts +7 -3
- package/dist/sdk/context-signal-store.js +155 -42
- package/dist/sdk/context-usage.d.ts +54 -0
- package/dist/sdk/context-usage.js +245 -16
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -14
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -2
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.js +4 -2
- package/dist/sdk/init.d.ts +2 -0
- package/dist/sdk/init.js +18 -9
- package/dist/sdk/query/context.d.ts +3 -1
- package/dist/sdk/query/context.js +21 -9
- package/dist/sdk/query/next.js +18 -9
- package/dist/sdk/runtime-input.d.ts +2 -0
- package/dist/sdk/runtime-input.js +25 -2
- package/dist/sdk/runtime.d.ts +1 -0
- package/dist/sdk/runtime.js +17 -26
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -2
- package/docs/CONTEXT_RELEVANCE.md +44 -1
- package/docs/PERFORMANCE.md +6 -3
- package/docs/READ_OUTPUT_CONTRACTS.md +9 -1
- package/docs/SDK.md +11 -8
- package/docs/SDK_CONTEXT.md +7 -0
- package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +4 -3
- package/marketplace.json +2 -2
- package/package.json +4 -4
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/sdk/public-surface.json +241 -24
- package/dist/cli-bundle/chunks/chunk-GOACULJW.js +0 -13
- package/dist/cli-bundle/chunks/chunk-NOPL37X4.js +0 -205
- package/dist/cli-bundle/focused-chunks/chunk-242AXYB2.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-2FEZ5SKM.js +0 -3
- package/dist/cli-bundle/focused-chunks/chunk-6GCRSLPG.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-7VATS5QY.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-JEFKLWMM.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-PF3E6Q36.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-QDFJIRKN.js +0 -4
- package/dist/cli-bundle/focused-chunks/chunk-RMRCQPWU.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-S4U5CN5Q.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-T2LXXSOD.js +0 -158
- package/dist/cli-bundle/focused-chunks/chunk-Z2USIBR2.js +0 -5
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
* history, while stale, absent, or corrupt snapshots rebuild transparently.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
!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]="
|
|
9
|
+
!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]="9b112b71-e0b8-5502-89f6-b4da6eb85f6b")}catch(e){}}();
|
|
10
10
|
import { isFileAbsentError } from "../core/fs/fs-utils.js";
|
|
11
11
|
import fs from "node:fs/promises";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { createHash, randomUUID } from "node:crypto";
|
|
14
14
|
import { buildItemContextRelevanceCandidates, } from "./context-relevance.js";
|
|
15
15
|
import { readItemMetadataDerivedIndexState } from "./item-metadata-index.js";
|
|
16
|
+
import { readLatestSubstantiveHistoryEvents } from "../core/history/event-index.js";
|
|
17
|
+
import { canonicalizeContextRecencyCoordinate } from "./context/recency.js";
|
|
16
18
|
/** Current serialized feature-store envelope version. */
|
|
17
|
-
export const CONTEXT_SIGNAL_STORE_FORMAT_VERSION =
|
|
19
|
+
export const CONTEXT_SIGNAL_STORE_FORMAT_VERSION = 3;
|
|
18
20
|
/** Current canonical signal-vector version. */
|
|
19
|
-
export const CONTEXT_SIGNAL_SET_VERSION =
|
|
21
|
+
export const CONTEXT_SIGNAL_SET_VERSION = 2;
|
|
20
22
|
const STORED_CONTEXT_SIGNAL_NAMES = [
|
|
21
23
|
"recency",
|
|
22
24
|
"activity_density",
|
|
@@ -25,6 +27,11 @@ const STORED_CONTEXT_SIGNAL_NAMES = [
|
|
|
25
27
|
"risk_pressure",
|
|
26
28
|
"knowledge_density",
|
|
27
29
|
];
|
|
30
|
+
const CONTEXT_RECENCY_SOURCES = new Set([
|
|
31
|
+
"substantive_history",
|
|
32
|
+
"release_cohort",
|
|
33
|
+
"created_at",
|
|
34
|
+
]);
|
|
28
35
|
/** Canonical actionable contracts shared by CLI, SDK, MCP, and package hosts. */
|
|
29
36
|
export const CONTEXT_SIGNAL_STORE_WARNING_DETAILS = {
|
|
30
37
|
context_signal_store_invalid: {
|
|
@@ -50,7 +57,10 @@ function isRecord(value) {
|
|
|
50
57
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
58
|
}
|
|
52
59
|
function isNormalizedSignal(value) {
|
|
53
|
-
return typeof value === "number" &&
|
|
60
|
+
return (typeof value === "number" &&
|
|
61
|
+
Number.isFinite(value) &&
|
|
62
|
+
value >= 0 &&
|
|
63
|
+
value <= 1);
|
|
54
64
|
}
|
|
55
65
|
function compactSignals(signals) {
|
|
56
66
|
const compact = {};
|
|
@@ -88,7 +98,10 @@ function deriveGraphProximity(items) {
|
|
|
88
98
|
}
|
|
89
99
|
}
|
|
90
100
|
const maximum = Math.max(0, ...degree.values());
|
|
91
|
-
return Object.fromEntries(items.map((item) => [
|
|
101
|
+
return Object.fromEntries(items.map((item) => [
|
|
102
|
+
item.id,
|
|
103
|
+
maximum === 0 ? 0 : degree.get(item.id) / maximum,
|
|
104
|
+
]));
|
|
92
105
|
}
|
|
93
106
|
function stableSnapshotOptions(items, options) {
|
|
94
107
|
return {
|
|
@@ -101,19 +114,77 @@ function stableSnapshotOptions(items, options) {
|
|
|
101
114
|
graphProximity: options.graphProximity ?? deriveGraphProximity(items),
|
|
102
115
|
};
|
|
103
116
|
}
|
|
117
|
+
function recencyEvidenceFingerprint(candidates) {
|
|
118
|
+
const hash = createHash("sha256");
|
|
119
|
+
for (const { id, signal_provenance: { recency: evidence }, } of [...candidates].sort((left, right) => left.id.localeCompare(right.id))) {
|
|
120
|
+
hash.update(JSON.stringify([
|
|
121
|
+
id,
|
|
122
|
+
evidence.source,
|
|
123
|
+
evidence.coordinate,
|
|
124
|
+
evidence.history_op ?? null,
|
|
125
|
+
evidence.event_class ?? null,
|
|
126
|
+
]));
|
|
127
|
+
}
|
|
128
|
+
return `sha256:${hash.digest("hex")}`;
|
|
129
|
+
}
|
|
104
130
|
function parseSnapshotItem(value) {
|
|
105
|
-
if (!isRecord(value)
|
|
131
|
+
if (!isRecord(value))
|
|
132
|
+
return null;
|
|
133
|
+
const signalProvenance = value.signal_provenance;
|
|
134
|
+
if (![
|
|
135
|
+
typeof value.id === "string",
|
|
136
|
+
isRecord(value.signals),
|
|
137
|
+
isRecord(signalProvenance),
|
|
138
|
+
].every(Boolean))
|
|
139
|
+
return null;
|
|
140
|
+
const recency = signalProvenance.recency;
|
|
141
|
+
if (!isRecord(recency))
|
|
142
|
+
return null;
|
|
143
|
+
const coordinate = canonicalizeContextRecencyCoordinate(recency.coordinate);
|
|
144
|
+
if (coordinate === null)
|
|
106
145
|
return null;
|
|
107
|
-
}
|
|
108
|
-
const signals = {};
|
|
109
146
|
const supportedSignals = new Set(STORED_CONTEXT_SIGNAL_NAMES);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
const signalEntries = Object.entries(value.signals);
|
|
148
|
+
if (!signalEntries.every(([name, signal]) => supportedSignals.has(name) && isNormalizedSignal(signal))) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
if (![
|
|
152
|
+
typeof recency.source === "string" &&
|
|
153
|
+
CONTEXT_RECENCY_SOURCES.has(recency.source),
|
|
154
|
+
["undefined", "string"].includes(typeof recency.history_op),
|
|
155
|
+
[undefined, "substantive", "maintenance"].includes(recency.event_class),
|
|
156
|
+
recency.source === "substantive_history"
|
|
157
|
+
? recency.event_class === undefined ||
|
|
158
|
+
recency.event_class === "substantive"
|
|
159
|
+
: recency.history_op === undefined && recency.event_class === undefined,
|
|
160
|
+
].every(Boolean)) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
id: value.id,
|
|
165
|
+
signals: Object.fromEntries(signalEntries),
|
|
166
|
+
signal_provenance: {
|
|
167
|
+
recency: {
|
|
168
|
+
source: recency.source,
|
|
169
|
+
coordinate,
|
|
170
|
+
history_op: recency.history_op,
|
|
171
|
+
event_class: recency.event_class,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function parseSnapshotItems(values, expectedRecencyEvidenceFingerprint) {
|
|
177
|
+
const items = Array.from(values, parseSnapshotItem);
|
|
178
|
+
if (items.some((item) => item === null))
|
|
179
|
+
return null;
|
|
180
|
+
const validItems = items;
|
|
181
|
+
if (new Set(validItems.map((item) => item.id)).size !== validItems.length ||
|
|
182
|
+
validItems.some((item) => item.id.trim().length === 0) ||
|
|
183
|
+
expectedRecencyEvidenceFingerprint !==
|
|
184
|
+
recencyEvidenceFingerprint(validItems)) {
|
|
185
|
+
return null;
|
|
115
186
|
}
|
|
116
|
-
return
|
|
187
|
+
return validItems.sort((left, right) => left.id.localeCompare(right.id));
|
|
117
188
|
}
|
|
118
189
|
/** Validate an untrusted serialized snapshot without accepting partial envelopes. */
|
|
119
190
|
export function parseContextSignalSnapshot(value) {
|
|
@@ -122,56 +193,68 @@ export function parseContextSignalSnapshot(value) {
|
|
|
122
193
|
value.signal_set_version !== CONTEXT_SIGNAL_SET_VERSION ||
|
|
123
194
|
typeof value.source_cursor !== "string" ||
|
|
124
195
|
value.source_cursor.trim().length === 0 ||
|
|
196
|
+
typeof value.recency_evidence_fingerprint !== "string" ||
|
|
197
|
+
!/^sha256:[a-f0-9]{64}$/u.test(value.recency_evidence_fingerprint) ||
|
|
125
198
|
typeof value.generated_at !== "string" ||
|
|
126
199
|
!Number.isFinite(Date.parse(value.generated_at)) ||
|
|
127
200
|
(value.source !== "derived_index" && value.source !== "scan_fallback") ||
|
|
128
201
|
!Array.isArray(value.items)) {
|
|
129
202
|
return null;
|
|
130
203
|
}
|
|
131
|
-
const items = value.items.
|
|
132
|
-
if (items
|
|
204
|
+
const items = parseSnapshotItems(value.items, value.recency_evidence_fingerprint);
|
|
205
|
+
if (items === null)
|
|
133
206
|
return null;
|
|
134
|
-
}
|
|
135
|
-
const validItems = items;
|
|
136
|
-
if (new Set(validItems.map((item) => item.id)).size !== validItems.length ||
|
|
137
|
-
validItems.some((item) => item.id.trim().length === 0)) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
207
|
return {
|
|
141
208
|
format_version: CONTEXT_SIGNAL_STORE_FORMAT_VERSION,
|
|
142
209
|
signal_set_version: CONTEXT_SIGNAL_SET_VERSION,
|
|
143
210
|
source_cursor: value.source_cursor,
|
|
211
|
+
recency_evidence_fingerprint: value.recency_evidence_fingerprint,
|
|
144
212
|
generated_at: value.generated_at,
|
|
145
213
|
source: value.source,
|
|
146
|
-
items
|
|
214
|
+
items,
|
|
147
215
|
};
|
|
148
216
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
217
|
+
function validateSnapshotOptions(options) {
|
|
218
|
+
if (typeof options.sourceCursor !== "string" ||
|
|
219
|
+
options.sourceCursor.trim().length === 0) {
|
|
152
220
|
throw new TypeError("Context signal source cursor must be non-empty");
|
|
153
221
|
}
|
|
154
222
|
if (!Number.isFinite(Date.parse(options.now))) {
|
|
155
223
|
throw new TypeError("Context signal snapshot clock must be a valid timestamp");
|
|
156
224
|
}
|
|
157
|
-
if (options.source !== "derived_index" &&
|
|
225
|
+
if (options.source !== "derived_index" &&
|
|
226
|
+
options.source !== "scan_fallback") {
|
|
158
227
|
throw new TypeError("Context signal snapshot source must be derived_index or scan_fallback");
|
|
159
228
|
}
|
|
160
|
-
|
|
161
|
-
|
|
229
|
+
}
|
|
230
|
+
function snapshotFromCandidates(candidates, options, fingerprint = recencyEvidenceFingerprint(candidates)) {
|
|
231
|
+
const candidateIds = candidates.map(({ id }) => id);
|
|
232
|
+
if (new Set(candidateIds).size !== candidateIds.length ||
|
|
233
|
+
candidateIds.some((id) => id.trim().length === 0)) {
|
|
234
|
+
throw new TypeError("Context signal snapshot item IDs must be unique and non-empty");
|
|
235
|
+
}
|
|
236
|
+
const rows = candidates
|
|
237
|
+
.map(({ id, signals, signal_provenance }) => ({
|
|
162
238
|
id,
|
|
163
239
|
signals: compactSignals(signals),
|
|
240
|
+
signal_provenance,
|
|
164
241
|
}))
|
|
165
242
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
166
243
|
return Object.freeze({
|
|
167
244
|
format_version: CONTEXT_SIGNAL_STORE_FORMAT_VERSION,
|
|
168
245
|
signal_set_version: CONTEXT_SIGNAL_SET_VERSION,
|
|
169
246
|
source_cursor: options.sourceCursor,
|
|
247
|
+
recency_evidence_fingerprint: fingerprint,
|
|
170
248
|
generated_at: options.now,
|
|
171
249
|
source: options.source,
|
|
172
250
|
items: Object.freeze(rows.map((row) => Object.freeze(row))),
|
|
173
251
|
});
|
|
174
252
|
}
|
|
253
|
+
/** Fold authoritative items into a deterministic, immutable signal snapshot. */
|
|
254
|
+
export function buildContextSignalSnapshot(items, options) {
|
|
255
|
+
validateSnapshotOptions(options);
|
|
256
|
+
return snapshotFromCandidates(buildItemContextRelevanceCandidates(items, stableSnapshotOptions(items, options)), options);
|
|
257
|
+
}
|
|
175
258
|
/** Filesystem adapter using same-directory atomic replacement. */
|
|
176
259
|
export class JsonFileContextSignalStoreAdapter {
|
|
177
260
|
filePath;
|
|
@@ -200,7 +283,10 @@ export class JsonFileContextSignalStoreAdapter {
|
|
|
200
283
|
await fs.mkdir(directory, { recursive: true });
|
|
201
284
|
const temporaryPath = path.join(directory, `.${path.basename(this.filePath)}.${randomUUID()}.tmp`);
|
|
202
285
|
try {
|
|
203
|
-
await fs.writeFile(temporaryPath, `${JSON.stringify(snapshot)}\n`, {
|
|
286
|
+
await fs.writeFile(temporaryPath, `${JSON.stringify(snapshot)}\n`, {
|
|
287
|
+
encoding: "utf8",
|
|
288
|
+
flag: "wx",
|
|
289
|
+
});
|
|
204
290
|
await fs.rename(temporaryPath, this.filePath);
|
|
205
291
|
}
|
|
206
292
|
finally {
|
|
@@ -222,6 +308,7 @@ export class ContextSignalStore {
|
|
|
222
308
|
}
|
|
223
309
|
/** Read matching signals or rebuild them from authoritative items. */
|
|
224
310
|
async readOrRebuild(items, options) {
|
|
311
|
+
validateSnapshotOptions(options);
|
|
225
312
|
const warnings = [];
|
|
226
313
|
let snapshot = null;
|
|
227
314
|
try {
|
|
@@ -234,10 +321,15 @@ export class ContextSignalStore {
|
|
|
234
321
|
catch {
|
|
235
322
|
warnings.push("context_signal_store_invalid");
|
|
236
323
|
}
|
|
237
|
-
const authoritativeIds = items
|
|
324
|
+
const authoritativeIds = items
|
|
325
|
+
.map((item) => item.id)
|
|
326
|
+
.sort((left, right) => left.localeCompare(right));
|
|
238
327
|
const snapshotIds = snapshot?.items.map((item) => item.id) ?? [];
|
|
328
|
+
const authoritativeCandidates = buildItemContextRelevanceCandidates(items, stableSnapshotOptions(items, options));
|
|
329
|
+
const authoritativeFingerprint = recencyEvidenceFingerprint(authoritativeCandidates);
|
|
239
330
|
const fresh = snapshot !== null &&
|
|
240
331
|
snapshot.source_cursor === options.sourceCursor &&
|
|
332
|
+
snapshot.recency_evidence_fingerprint === authoritativeFingerprint &&
|
|
241
333
|
snapshot.source === options.source &&
|
|
242
334
|
snapshotIds.length === authoritativeIds.length &&
|
|
243
335
|
snapshotIds.every((id, index) => id === authoritativeIds[index]);
|
|
@@ -249,7 +341,7 @@ export class ContextSignalStore {
|
|
|
249
341
|
if (snapshot !== null) {
|
|
250
342
|
warnings.push("context_signal_store_stale");
|
|
251
343
|
}
|
|
252
|
-
resolvedSnapshot =
|
|
344
|
+
resolvedSnapshot = snapshotFromCandidates(authoritativeCandidates, options, authoritativeFingerprint);
|
|
253
345
|
try {
|
|
254
346
|
await this.adapter.write(resolvedSnapshot);
|
|
255
347
|
}
|
|
@@ -257,13 +349,16 @@ export class ContextSignalStore {
|
|
|
257
349
|
warnings.push("context_signal_store_write_failed");
|
|
258
350
|
}
|
|
259
351
|
}
|
|
260
|
-
const
|
|
261
|
-
const dynamicCandidates = buildItemContextRelevanceCandidates(items, options);
|
|
352
|
+
const snapshotItemsById = new Map(resolvedSnapshot.items.map((item) => [item.id, item]));
|
|
262
353
|
return {
|
|
263
354
|
snapshot: resolvedSnapshot,
|
|
264
|
-
candidates:
|
|
355
|
+
candidates: authoritativeCandidates.map((candidate) => ({
|
|
265
356
|
...candidate,
|
|
266
|
-
signals: {
|
|
357
|
+
signals: {
|
|
358
|
+
...candidate.signals,
|
|
359
|
+
...snapshotItemsById.get(candidate.id)?.signals,
|
|
360
|
+
},
|
|
361
|
+
signal_provenance: snapshotItemsById.get(candidate.id).signal_provenance,
|
|
267
362
|
})),
|
|
268
363
|
cache_status: fresh ? "fresh" : "rebuilt",
|
|
269
364
|
warnings,
|
|
@@ -304,11 +399,29 @@ export async function readWorkspaceContextSignals(items, options) {
|
|
|
304
399
|
const indexState = hasSourceCursor
|
|
305
400
|
? null
|
|
306
401
|
: await readItemMetadataDerivedIndexState(options.pmRoot);
|
|
307
|
-
const sourceCursor = options.sourceCursor ??
|
|
308
|
-
|
|
309
|
-
(
|
|
310
|
-
const
|
|
311
|
-
|
|
402
|
+
const sourceCursor = options.sourceCursor ??
|
|
403
|
+
indexState?.source_cursor ??
|
|
404
|
+
fallbackWorkspaceCursor(items);
|
|
405
|
+
const source = options.source ?? (indexState === null ? "scan_fallback" : "derived_index");
|
|
406
|
+
const store = new ContextSignalStore(new JsonFileContextSignalStoreAdapter(path.join(options.pmRoot, "runtime", options.storeKey
|
|
407
|
+
? `context-signals-${options.storeKey}.json`
|
|
408
|
+
: "context-signals.json")));
|
|
409
|
+
const recencyEvidence = options.recencyEvidence ??
|
|
410
|
+
Object.fromEntries(Object.entries(await readLatestSubstantiveHistoryEvents(options.pmRoot, items.map((item) => item.id))).map(([id, event]) => [
|
|
411
|
+
id,
|
|
412
|
+
{
|
|
413
|
+
source: "substantive_history",
|
|
414
|
+
coordinate: event.entry.ts,
|
|
415
|
+
history_op: event.entry.op,
|
|
416
|
+
event_class: "substantive",
|
|
417
|
+
},
|
|
418
|
+
]));
|
|
419
|
+
return store.readOrRebuild(items, {
|
|
420
|
+
...options,
|
|
421
|
+
recencyEvidence,
|
|
422
|
+
sourceCursor,
|
|
423
|
+
source,
|
|
424
|
+
});
|
|
312
425
|
}
|
|
313
426
|
//# sourceMappingURL=context-signal-store.js.map
|
|
314
|
-
//# debugId=
|
|
427
|
+
//# debugId=9b112b71-e0b8-5502-89f6-b4da6eb85f6b
|
|
@@ -8,14 +8,40 @@ export interface ContextUsageServingRow {
|
|
|
8
8
|
/** Whether the packer included the item. */
|
|
9
9
|
included: boolean;
|
|
10
10
|
}
|
|
11
|
+
/** Correlation receipt joining a pre-egress serve to its final delivery. */
|
|
12
|
+
export interface ContextUsageServingReceipt {
|
|
13
|
+
/** Versioned immutable serve identifier. */
|
|
14
|
+
serve_id: string;
|
|
15
|
+
/** Caller whose feedback model owns the event. */
|
|
16
|
+
author: string;
|
|
17
|
+
/** Read surface that assembled the candidates. */
|
|
18
|
+
surface: ContextRelevanceSurface;
|
|
19
|
+
/** Propensity-complete ranked rows assembled before egress. */
|
|
20
|
+
rows: readonly ContextUsageServingRow[];
|
|
21
|
+
}
|
|
11
22
|
/** Append-only serving or subsequent-touch event. */
|
|
12
23
|
export type ContextUsageEvent = {
|
|
13
24
|
kind: "serve";
|
|
25
|
+
schema_version?: 2;
|
|
26
|
+
serve_id?: string;
|
|
14
27
|
at: string;
|
|
15
28
|
author: string;
|
|
16
29
|
surface: ContextRelevanceSurface;
|
|
17
30
|
profile: string;
|
|
18
31
|
rows: ContextUsageServingRow[];
|
|
32
|
+
result_omitted?: boolean;
|
|
33
|
+
packed_item_ids?: string[];
|
|
34
|
+
/** Legacy pre-egress field ignored by the v2 affinity fold. */
|
|
35
|
+
delivered_item_ids?: string[];
|
|
36
|
+
} | {
|
|
37
|
+
kind: "delivery";
|
|
38
|
+
schema_version: 2;
|
|
39
|
+
serve_id: string;
|
|
40
|
+
at: string;
|
|
41
|
+
author: string;
|
|
42
|
+
surface: ContextRelevanceSurface;
|
|
43
|
+
result_omitted: boolean;
|
|
44
|
+
delivered_item_ids: string[];
|
|
19
45
|
} | {
|
|
20
46
|
kind: "touch";
|
|
21
47
|
at: string;
|
|
@@ -44,7 +70,26 @@ export interface ContextUsageAffinity {
|
|
|
44
70
|
positive_judgments: number;
|
|
45
71
|
/** Number of retained serving events inspected. */
|
|
46
72
|
serving_events: number;
|
|
73
|
+
/** Legacy serving events ignored because their inclusion cannot be trusted. */
|
|
74
|
+
untrusted_serving_events: number;
|
|
47
75
|
}
|
|
76
|
+
/** Attach a JSON-invisible serving receipt to a result for later egress. */
|
|
77
|
+
export declare function attachContextUsageServingReceipt(result: unknown, receipt: ContextUsageServingReceipt | null): void;
|
|
78
|
+
/** Copy an attached serving receipt across a result projection boundary. */
|
|
79
|
+
export declare function propagateContextUsageServingReceipt(source: unknown, target: unknown): void;
|
|
80
|
+
/**
|
|
81
|
+
* Collect every item identifier visible in a final context or next result.
|
|
82
|
+
* The traversal is shape-aware so summary counters and unrelated identifier
|
|
83
|
+
* fields cannot enter the privacy-minimal usage ledger.
|
|
84
|
+
*/
|
|
85
|
+
export declare function collectContextUsageDeliveredItemIds(result: unknown, surface: ContextRelevanceSurface): string[];
|
|
86
|
+
/** Resolve and append one post-egress decision carried by a context result. */
|
|
87
|
+
export declare function finalizeContextUsageDelivery(options: {
|
|
88
|
+
/** Tracker root owning the derived ledger. */
|
|
89
|
+
pmRoot: string;
|
|
90
|
+
/** Final projected response returned or rendered to the caller. */
|
|
91
|
+
result: unknown;
|
|
92
|
+
}): Promise<boolean>;
|
|
48
93
|
/** Named validation failure emitted by context-usage SDK inputs. */
|
|
49
94
|
export declare class ContextUsageValidationError extends TypeError {
|
|
50
95
|
/** Stable machine-readable error code for package and CLI hosts. */
|
|
@@ -58,6 +103,15 @@ export declare function recordContextUsageServing(options: ContextUsageLedgerOpt
|
|
|
58
103
|
surface: ContextRelevanceSurface;
|
|
59
104
|
profile: string;
|
|
60
105
|
rows: ContextUsageServingRow[];
|
|
106
|
+
}): Promise<ContextUsageServingReceipt | null>;
|
|
107
|
+
/** Append the exact post-egress delivery decision for one serving receipt. */
|
|
108
|
+
export declare function recordContextUsageDelivery(options: ContextUsageLedgerOptions & {
|
|
109
|
+
/** Correlation receipt returned by {@link recordContextUsageServing}. */
|
|
110
|
+
receipt: ContextUsageServingReceipt | null;
|
|
111
|
+
/** Item ids present in the final response payload. */
|
|
112
|
+
deliveredItemIds: readonly string[];
|
|
113
|
+
/** Whether the result was suppressed by the final output budget. */
|
|
114
|
+
resultOmitted: boolean;
|
|
61
115
|
}): Promise<void>;
|
|
62
116
|
/** Records one subsequent item read or mutation outcome. */
|
|
63
117
|
export declare function recordContextUsageTouch(options: ContextUsageLedgerOptions & {
|