memorix 1.2.1 → 1.2.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/CHANGELOG.md +23 -0
- package/README.md +14 -2
- package/README.zh-CN.md +14 -2
- package/dist/cli/index.js +15424 -13780
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1337 -536
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8458 -8087
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +23 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +1365 -542
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +49 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
- package/docs/API_REFERENCE.md +24 -4
- package/docs/README.md +1 -1
- package/docs/dev-log/progress.txt +101 -11
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +15 -5
- package/src/cli/commands/context.ts +3 -2
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/explain.ts +9 -3
- package/src/cli/commands/handoff.ts +21 -7
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-image.ts +5 -3
- package/src/cli/commands/lock.ts +11 -10
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +19 -14
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +16 -6
- package/src/cli/commands/reasoning.ts +17 -3
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +8 -2
- package/src/cli/commands/session.ts +44 -10
- package/src/cli/commands/skills.ts +10 -5
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +26 -17
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +63 -10
- package/src/cli/identity.ts +89 -0
- package/src/cli/index.ts +96 -19
- package/src/cli/invocation.ts +115 -0
- package/src/cli/tui/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/session-service.ts +3 -2
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/codegraph/auto-context.ts +31 -2
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/project-context.ts +2 -0
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/dashboard/server.ts +46 -9
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/workset.ts +179 -10
- package/src/memory/admission.ts +57 -0
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +6 -1
- package/src/memory/export-import.ts +11 -3
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/retention.ts +22 -2
- package/src/memory/session.ts +29 -11
- package/src/memory/visibility.ts +80 -0
- package/src/orchestrate/memorix-bridge.ts +38 -0
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +18 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/maintenance-runner.ts +2 -0
- package/src/runtime/project-maintenance.ts +89 -0
- package/src/sdk.ts +35 -5
- package/src/server.ts +267 -83
- package/src/store/orama-store.ts +61 -6
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/handoff.ts +7 -0
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
package/src/knowledge/workset.ts
CHANGED
|
@@ -6,6 +6,14 @@ import { KnowledgeWorkspaceStore } from './workspace-store.js';
|
|
|
6
6
|
import { loadKnowledgeWorkspace } from './workspace.js';
|
|
7
7
|
import { WorkflowStore } from './workflow-store.js';
|
|
8
8
|
import { selectWorkflows } from './workflows.js';
|
|
9
|
+
import type {
|
|
10
|
+
ContextCandidateFreshness,
|
|
11
|
+
ContextCandidateKind,
|
|
12
|
+
ContextDeliveryTarget,
|
|
13
|
+
ContextReceipt,
|
|
14
|
+
ContextReceiptOmission,
|
|
15
|
+
ContextReceiptSelection,
|
|
16
|
+
} from './context-assembly.js';
|
|
9
17
|
import type { CodeGraphProviderQuality, ExternalCodeGraphOutline } from '../codegraph/types.js';
|
|
10
18
|
import type { KnowledgeClaim, ClaimEvidenceRef } from './types.js';
|
|
11
19
|
import type { KnowledgePageRecord, KnowledgeWorkspace } from './workspace-types.js';
|
|
@@ -98,6 +106,8 @@ export interface TaskWorkset {
|
|
|
98
106
|
tokenCount: number;
|
|
99
107
|
omitted: string[];
|
|
100
108
|
};
|
|
109
|
+
/** Privacy-safe selection metadata for diagnostics, never appended to the prompt. */
|
|
110
|
+
receipt: ContextReceipt;
|
|
101
111
|
prompt: string;
|
|
102
112
|
}
|
|
103
113
|
|
|
@@ -128,6 +138,8 @@ export interface BuildTaskWorksetInput {
|
|
|
128
138
|
};
|
|
129
139
|
runtimeCautions?: WorksetCaution[];
|
|
130
140
|
maxTokens?: number;
|
|
141
|
+
/** Delivery surface controls receipt semantics without changing prompt shape. */
|
|
142
|
+
deliveryTarget?: ContextDeliveryTarget;
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
function unique(values: string[]): string[] {
|
|
@@ -228,35 +240,104 @@ function appendLine(
|
|
|
228
240
|
maxTokens: number,
|
|
229
241
|
omitted: string[],
|
|
230
242
|
omittedKind: string,
|
|
243
|
+
selected?: ContextReceiptSelection[],
|
|
244
|
+
receiptSelection?: ContextReceiptSelection,
|
|
231
245
|
): boolean {
|
|
232
246
|
const next = lines.length ? lines.join('\n') + '\n' + candidate : candidate;
|
|
233
247
|
if (countTextTokens(next) <= maxTokens) {
|
|
234
248
|
lines.push(candidate);
|
|
249
|
+
if (receiptSelection) selected?.push(receiptSelection);
|
|
235
250
|
return true;
|
|
236
251
|
}
|
|
237
252
|
omitted.push(omittedKind);
|
|
238
253
|
return false;
|
|
239
254
|
}
|
|
240
255
|
|
|
256
|
+
function freshnessForMemory(status: WorksetMemorySource['status']): ContextCandidateFreshness {
|
|
257
|
+
if (status === 'current' || status === 'suspect' || status === 'stale') return status;
|
|
258
|
+
return 'unknown';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function receiptOmissionKind(raw: string): ContextCandidateKind | undefined {
|
|
262
|
+
if (raw.includes('task')) return 'task';
|
|
263
|
+
if (raw.includes('fact')) return 'current-fact';
|
|
264
|
+
if (raw.includes('state')) return 'code-state';
|
|
265
|
+
if (raw.includes('semantic')) return 'semantic-code';
|
|
266
|
+
if (raw.includes('start')) return 'start-here';
|
|
267
|
+
if (raw.includes('memory')) return 'memory';
|
|
268
|
+
if (raw.includes('claim')) return 'claim';
|
|
269
|
+
if (raw.includes('knowledge-page')) return 'knowledge-page';
|
|
270
|
+
if (raw.includes('workflow')) return 'workflow';
|
|
271
|
+
if (raw.includes('verification')) return 'verification';
|
|
272
|
+
if (raw.includes('caution')) return 'caution';
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function receiptOmissions(omitted: string[], hiddenCautionMemoryCount: number): ContextReceiptOmission[] {
|
|
277
|
+
const counts = new Map<ContextCandidateKind, number>();
|
|
278
|
+
for (const raw of omitted) {
|
|
279
|
+
const kind = receiptOmissionKind(raw);
|
|
280
|
+
if (!kind || raw.endsWith('-heading')) continue;
|
|
281
|
+
counts.set(kind, (counts.get(kind) ?? 0) + 1);
|
|
282
|
+
}
|
|
283
|
+
const receipt: ContextReceiptOmission[] = [...counts.entries()].map(([kind, count]) => ({
|
|
284
|
+
kind,
|
|
285
|
+
reason: 'token-budget',
|
|
286
|
+
count,
|
|
287
|
+
}));
|
|
288
|
+
if (hiddenCautionMemoryCount > 0) {
|
|
289
|
+
receipt.push({
|
|
290
|
+
kind: 'caution',
|
|
291
|
+
reason: 'hidden-by-task-lens',
|
|
292
|
+
count: hiddenCautionMemoryCount,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return receipt;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function scheduledActions(cautions: WorksetCaution[]): string[] {
|
|
299
|
+
return cautions
|
|
300
|
+
.filter(caution => caution.kind === 'codegraph-refresh-queued')
|
|
301
|
+
.map(caution => short(caution.message, 28));
|
|
302
|
+
}
|
|
303
|
+
|
|
241
304
|
/**
|
|
242
305
|
* Render a bounded prompt from whole evidence items. It never cuts a page,
|
|
243
306
|
* workflow, or claim body into an untraceable partial fragment.
|
|
244
307
|
*/
|
|
245
|
-
export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'budget'> & {
|
|
308
|
+
export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'budget' | 'receipt'> & {
|
|
246
309
|
budget?: Partial<TaskWorkset['budget']>;
|
|
247
|
-
}): {
|
|
310
|
+
}): {
|
|
311
|
+
prompt: string;
|
|
312
|
+
tokenCount: number;
|
|
313
|
+
/** Compatibility summary: unique omission categories. */
|
|
314
|
+
omitted: string[];
|
|
315
|
+
/** Internal receipt input: one entry per candidate that did not fit. */
|
|
316
|
+
omittedItems: string[];
|
|
317
|
+
selected: ContextReceiptSelection[];
|
|
318
|
+
} {
|
|
248
319
|
const maxTokens = input.budget?.maxTokens ?? 180;
|
|
249
320
|
const omitted: string[] = [];
|
|
321
|
+
const selected: ContextReceiptSelection[] = [];
|
|
250
322
|
const lines: string[] = ['Memorix Autopilot Brief'];
|
|
251
323
|
const task = short(input.task || 'Continue the current task.', 34);
|
|
252
|
-
appendLine(lines, 'Task: ' + task, maxTokens, omitted, 'task-detail'
|
|
324
|
+
appendLine(lines, 'Task: ' + task, maxTokens, omitted, 'task-detail', selected, {
|
|
325
|
+
kind: 'task',
|
|
326
|
+
reason: 'current task supplied by the caller',
|
|
327
|
+
trust: 'source-backed',
|
|
328
|
+
});
|
|
253
329
|
appendLine(lines, 'Task lens: ' + input.lens, maxTokens, omitted, 'lens');
|
|
254
330
|
|
|
255
331
|
if (input.cautions.length > 0 || input.cautionMemory.length > 0) {
|
|
256
332
|
appendLine(lines, '', maxTokens, omitted, 'caution-heading');
|
|
257
333
|
appendLine(lines, 'Cautions', maxTokens, omitted, 'caution-heading');
|
|
258
334
|
for (const caution of input.cautions.slice(0, 6)) {
|
|
259
|
-
appendLine(lines, '- ' + short(caution.message, 22), maxTokens, omitted, 'caution'
|
|
335
|
+
appendLine(lines, '- ' + short(caution.message, 22), maxTokens, omitted, 'caution', selected, {
|
|
336
|
+
kind: 'caution',
|
|
337
|
+
id: 'caution:' + caution.kind,
|
|
338
|
+
reason: 'current project caution',
|
|
339
|
+
trust: 'source-backed',
|
|
340
|
+
});
|
|
260
341
|
}
|
|
261
342
|
for (const memory of input.cautionMemory.slice(0, 3)) {
|
|
262
343
|
const location = memory.path
|
|
@@ -269,6 +350,14 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
269
350
|
maxTokens,
|
|
270
351
|
omitted,
|
|
271
352
|
'caution-memory',
|
|
353
|
+
selected,
|
|
354
|
+
{
|
|
355
|
+
kind: 'memory',
|
|
356
|
+
id: 'memory:' + memory.id,
|
|
357
|
+
reason: 'task-relevant memory requiring source verification',
|
|
358
|
+
freshness: freshnessForMemory(memory.status),
|
|
359
|
+
trust: 'historical',
|
|
360
|
+
},
|
|
272
361
|
);
|
|
273
362
|
}
|
|
274
363
|
if (input.hiddenCautionMemoryCount > 0) {
|
|
@@ -280,14 +369,23 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
280
369
|
appendLine(lines, '', maxTokens, omitted, 'facts-heading');
|
|
281
370
|
appendLine(lines, 'Current project facts', maxTokens, omitted, 'facts-heading');
|
|
282
371
|
for (const fact of input.currentFacts.slice(0, 4)) {
|
|
283
|
-
appendLine(lines, '- ' + short(fact, 40), maxTokens, omitted, 'current-fact'
|
|
372
|
+
appendLine(lines, '- ' + short(fact, 40), maxTokens, omitted, 'current-fact', selected, {
|
|
373
|
+
kind: 'current-fact',
|
|
374
|
+
reason: 'current project state',
|
|
375
|
+
trust: 'source-backed',
|
|
376
|
+
});
|
|
284
377
|
}
|
|
285
378
|
}
|
|
286
379
|
|
|
287
380
|
if (input.codeState) {
|
|
288
381
|
appendLine(lines, '', maxTokens, omitted, 'state-heading');
|
|
289
382
|
appendLine(lines, 'Project state', maxTokens, omitted, 'state-heading');
|
|
290
|
-
appendLine(lines, input.codeState, maxTokens, omitted, 'code-state'
|
|
383
|
+
appendLine(lines, input.codeState, maxTokens, omitted, 'code-state', selected, {
|
|
384
|
+
kind: 'code-state',
|
|
385
|
+
...(input.provenance.snapshotId ? { id: 'snapshot:' + input.provenance.snapshotId } : {}),
|
|
386
|
+
reason: 'latest available Code State snapshot',
|
|
387
|
+
trust: 'source-backed',
|
|
388
|
+
});
|
|
291
389
|
}
|
|
292
390
|
|
|
293
391
|
if (input.semanticCode && (input.semanticCode.entryPoints.length > 0 || input.semanticCode.relations.length > 0)) {
|
|
@@ -301,6 +399,13 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
301
399
|
maxTokens,
|
|
302
400
|
omitted,
|
|
303
401
|
'semantic-relation',
|
|
402
|
+
selected,
|
|
403
|
+
{
|
|
404
|
+
kind: 'semantic-code',
|
|
405
|
+
id: 'code:' + location,
|
|
406
|
+
reason: 'validated optional semantic code relation',
|
|
407
|
+
trust: 'derived',
|
|
408
|
+
},
|
|
304
409
|
);
|
|
305
410
|
}
|
|
306
411
|
if (input.semanticCode.relations.length === 0) {
|
|
@@ -312,6 +417,13 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
312
417
|
maxTokens,
|
|
313
418
|
omitted,
|
|
314
419
|
'semantic-entry',
|
|
420
|
+
selected,
|
|
421
|
+
{
|
|
422
|
+
kind: 'semantic-code',
|
|
423
|
+
id: 'code:' + location,
|
|
424
|
+
reason: 'validated optional semantic code entry point',
|
|
425
|
+
trust: 'derived',
|
|
426
|
+
},
|
|
315
427
|
);
|
|
316
428
|
}
|
|
317
429
|
}
|
|
@@ -321,7 +433,12 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
321
433
|
appendLine(lines, '', maxTokens, omitted, 'start-heading');
|
|
322
434
|
appendLine(lines, 'Start here', maxTokens, omitted, 'start-heading');
|
|
323
435
|
for (const source of input.startHere.slice(0, 5)) {
|
|
324
|
-
appendLine(lines, '- ' + source, maxTokens, omitted, 'start-here'
|
|
436
|
+
appendLine(lines, '- ' + source, maxTokens, omitted, 'start-here', selected, {
|
|
437
|
+
kind: 'start-here',
|
|
438
|
+
id: 'path:' + source,
|
|
439
|
+
reason: 'task-lensed starting point',
|
|
440
|
+
trust: 'derived',
|
|
441
|
+
});
|
|
325
442
|
}
|
|
326
443
|
}
|
|
327
444
|
|
|
@@ -338,6 +455,14 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
338
455
|
maxTokens,
|
|
339
456
|
omitted,
|
|
340
457
|
'reliable-memory',
|
|
458
|
+
selected,
|
|
459
|
+
{
|
|
460
|
+
kind: 'memory',
|
|
461
|
+
id: 'memory:' + memory.id,
|
|
462
|
+
reason: 'current code-bound memory',
|
|
463
|
+
freshness: freshnessForMemory(memory.status),
|
|
464
|
+
trust: 'historical',
|
|
465
|
+
},
|
|
341
466
|
);
|
|
342
467
|
}
|
|
343
468
|
}
|
|
@@ -346,10 +471,27 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
346
471
|
appendLine(lines, '', maxTokens, omitted, 'knowledge-heading');
|
|
347
472
|
appendLine(lines, 'Project knowledge', maxTokens, omitted, 'knowledge-heading');
|
|
348
473
|
for (const claim of input.claims.slice(0, 3)) {
|
|
349
|
-
appendLine(lines, '- ' + claim.assertion + ' [' + claim.id + ']', maxTokens, omitted, 'claim'
|
|
474
|
+
appendLine(lines, '- ' + claim.assertion + ' [' + claim.id + ']', maxTokens, omitted, 'claim', selected, {
|
|
475
|
+
kind: 'claim',
|
|
476
|
+
id: 'claim:' + claim.id,
|
|
477
|
+
reason: 'source-qualified task match',
|
|
478
|
+
trust: 'source-backed',
|
|
479
|
+
});
|
|
350
480
|
}
|
|
351
481
|
for (const page of input.pages.slice(0, 2)) {
|
|
352
|
-
|
|
482
|
+
const supportsDeliveredClaim = page.claimIds.some(claimId => selected.some(item => (
|
|
483
|
+
item.kind === 'claim' && item.id === 'claim:' + claimId
|
|
484
|
+
)));
|
|
485
|
+
if (!supportsDeliveredClaim) {
|
|
486
|
+
omitted.push('knowledge-page-dependency');
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
appendLine(lines, '- page: ' + page.relativePath, maxTokens, omitted, 'knowledge-page', selected, {
|
|
490
|
+
kind: 'knowledge-page',
|
|
491
|
+
id: 'page:' + page.id,
|
|
492
|
+
reason: 'approved page linked to a selected claim',
|
|
493
|
+
trust: 'source-backed',
|
|
494
|
+
});
|
|
353
495
|
}
|
|
354
496
|
}
|
|
355
497
|
|
|
@@ -363,6 +505,13 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
363
505
|
maxTokens,
|
|
364
506
|
omitted,
|
|
365
507
|
'workflow',
|
|
508
|
+
selected,
|
|
509
|
+
{
|
|
510
|
+
kind: 'workflow',
|
|
511
|
+
id: 'workflow:' + workflow.id,
|
|
512
|
+
reason: 'task-matching project workflow',
|
|
513
|
+
trust: 'source-backed',
|
|
514
|
+
},
|
|
366
515
|
);
|
|
367
516
|
}
|
|
368
517
|
}
|
|
@@ -371,7 +520,11 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
371
520
|
appendLine(lines, '', maxTokens, omitted, 'verification-heading');
|
|
372
521
|
appendLine(lines, 'Verify', maxTokens, omitted, 'verification-heading');
|
|
373
522
|
for (const check of input.verification.slice(0, 4)) {
|
|
374
|
-
appendLine(lines, '- ' + short(check, 20), maxTokens, omitted, 'verification'
|
|
523
|
+
appendLine(lines, '- ' + short(check, 20), maxTokens, omitted, 'verification', selected, {
|
|
524
|
+
kind: 'verification',
|
|
525
|
+
reason: 'task-lensed verification guidance',
|
|
526
|
+
trust: 'derived',
|
|
527
|
+
});
|
|
375
528
|
}
|
|
376
529
|
}
|
|
377
530
|
|
|
@@ -379,6 +532,8 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
379
532
|
prompt: lines.join('\n'),
|
|
380
533
|
tokenCount: countTextTokens(lines.join('\n')),
|
|
381
534
|
omitted: unique(omitted),
|
|
535
|
+
omittedItems: omitted,
|
|
536
|
+
selected,
|
|
382
537
|
};
|
|
383
538
|
}
|
|
384
539
|
|
|
@@ -388,6 +543,7 @@ export function renderTaskWorksetPrompt(input: Omit<TaskWorkset, 'prompt' | 'bud
|
|
|
388
543
|
* context response.
|
|
389
544
|
*/
|
|
390
545
|
export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<TaskWorkset> {
|
|
546
|
+
const startedAt = Date.now();
|
|
391
547
|
const task = input.task?.trim() ?? '';
|
|
392
548
|
const maxTokens = Math.max(96, Math.min(Math.floor(input.maxTokens ?? 180), 320));
|
|
393
549
|
const cautions = [...(input.runtimeCautions ?? []), ...snapshotCautions(input)];
|
|
@@ -503,6 +659,18 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
503
659
|
...base,
|
|
504
660
|
budget: { maxTokens },
|
|
505
661
|
});
|
|
662
|
+
const receipt: ContextReceipt = {
|
|
663
|
+
version: '1.2.2',
|
|
664
|
+
target: input.deliveryTarget ?? 'project-context',
|
|
665
|
+
elapsedMs: Math.max(0, Date.now() - startedAt),
|
|
666
|
+
budget: {
|
|
667
|
+
maxTokens,
|
|
668
|
+
tokenCount: rendered.tokenCount,
|
|
669
|
+
},
|
|
670
|
+
selected: rendered.selected,
|
|
671
|
+
omitted: receiptOmissions(rendered.omittedItems, base.hiddenCautionMemoryCount),
|
|
672
|
+
scheduledActions: scheduledActions(normalizedCautions),
|
|
673
|
+
};
|
|
506
674
|
return {
|
|
507
675
|
...base,
|
|
508
676
|
budget: {
|
|
@@ -510,6 +678,7 @@ export async function buildTaskWorkset(input: BuildTaskWorksetInput): Promise<Ta
|
|
|
510
678
|
tokenCount: rendered.tokenCount,
|
|
511
679
|
omitted: rendered.omitted,
|
|
512
680
|
},
|
|
681
|
+
receipt,
|
|
513
682
|
prompt: rendered.prompt,
|
|
514
683
|
};
|
|
515
684
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Observation, ObservationAdmissionState, ObservationType } from '../types.js';
|
|
2
|
+
import { resolveObservationVisibility } from './visibility.js';
|
|
3
|
+
|
|
4
|
+
const DURABLE_AUTOMATIC_TYPES = new Set<ObservationType>([
|
|
5
|
+
'decision',
|
|
6
|
+
'gotcha',
|
|
7
|
+
'problem-solution',
|
|
8
|
+
'trade-off',
|
|
9
|
+
'why-it-exists',
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Legacy observations predate admission metadata and retain their existing
|
|
14
|
+
* delivery behavior. New automatic captures must explicitly earn delivery.
|
|
15
|
+
*/
|
|
16
|
+
export function isEligibleForAutomaticDelivery(observation: Pick<Observation, 'admissionState'>): boolean {
|
|
17
|
+
return observation.admissionState !== 'ephemeral' && observation.admissionState !== 'candidate';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isCandidateObservation(observation: Pick<Observation, 'admissionState'>): boolean {
|
|
21
|
+
return observation.admissionState === 'candidate';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isEligibleForKnowledgePromotion(
|
|
25
|
+
observation: Pick<Observation, 'admissionState' | 'valueCategory' | 'visibility'>,
|
|
26
|
+
): boolean {
|
|
27
|
+
return isEligibleForAutomaticDelivery(observation)
|
|
28
|
+
&& observation.valueCategory !== 'ephemeral'
|
|
29
|
+
&& resolveObservationVisibility(observation) === 'project';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CandidateQualification {
|
|
33
|
+
admissionState: ObservationAdmissionState;
|
|
34
|
+
admissionReason: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Automatic hook records can become default-deliverable only after a current
|
|
39
|
+
* Code Memory link backs a non-ephemeral candidate. This deliberately does
|
|
40
|
+
* not create a claim: claims keep their explicit/Git source boundary.
|
|
41
|
+
*/
|
|
42
|
+
export function qualifyCandidateFromCurrentCode(input: {
|
|
43
|
+
observation: Pick<Observation, 'admissionState' | 'valueCategory' | 'type'>;
|
|
44
|
+
currentCodeReferenceCount: number;
|
|
45
|
+
}): CandidateQualification | undefined {
|
|
46
|
+
if (!isCandidateObservation(input.observation)) return undefined;
|
|
47
|
+
if (input.observation.valueCategory === 'ephemeral') return undefined;
|
|
48
|
+
if (input.currentCodeReferenceCount <= 0) return undefined;
|
|
49
|
+
|
|
50
|
+
const typeLabel = DURABLE_AUTOMATIC_TYPES.has(input.observation.type)
|
|
51
|
+
? 'high-value automatic record'
|
|
52
|
+
: 'automatic record';
|
|
53
|
+
return {
|
|
54
|
+
admissionState: 'qualified',
|
|
55
|
+
admissionReason: `${typeLabel} qualified against ${input.currentCodeReferenceCount} current Code Memory reference(s)`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import type { Observation } from '../types.js';
|
|
19
19
|
import { getObservationStore } from '../store/obs-store.js';
|
|
20
|
+
import { isEligibleForAutomaticDelivery } from './admission.js';
|
|
21
|
+
import { resolveObservationVisibility } from './visibility.js';
|
|
20
22
|
|
|
21
23
|
/** Default similarity threshold for merging (0.0-1.0) */
|
|
22
24
|
const DEFAULT_SIMILARITY_THRESHOLD = 0.45;
|
|
@@ -134,10 +136,19 @@ async function loadConsolidationPage(
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
function findClusters(observations: Observation[], threshold: number): ConsolidationCluster[] {
|
|
137
|
-
|
|
139
|
+
// Pending automatic evidence must stay individually inspectable until its
|
|
140
|
+
// source-backed qualification step completes. Consolidating it first would
|
|
141
|
+
// erase the evidence grain the control plane still needs to audit.
|
|
142
|
+
// Consolidation is a project-level maintenance action. Personal notes and
|
|
143
|
+
// targeted handoffs must remain individually inspectable and are never
|
|
144
|
+
// merged by a background job or another agent's manual cleanup.
|
|
145
|
+
const eligible = observations
|
|
146
|
+
.filter(isEligibleForAutomaticDelivery)
|
|
147
|
+
.filter((observation) => resolveObservationVisibility(observation) === 'project');
|
|
148
|
+
if (eligible.length < MIN_CLUSTER_SIZE) return [];
|
|
138
149
|
|
|
139
150
|
const groups = new Map<string, Observation[]>();
|
|
140
|
-
for (const obs of
|
|
151
|
+
for (const obs of eligible) {
|
|
141
152
|
const key = `${obs.entityName}::${obs.type}`;
|
|
142
153
|
const group = groups.get(key) ?? [];
|
|
143
154
|
group.push(obs);
|
|
@@ -85,6 +85,7 @@ export function evidenceBasisLine(basis: EvidenceBasis, commitHash?: string): st
|
|
|
85
85
|
export interface ProvenanceFields {
|
|
86
86
|
sourceDetail?: string;
|
|
87
87
|
valueCategory?: string;
|
|
88
|
+
admissionState?: string;
|
|
88
89
|
/** Legacy fallback: observations ingested before Phase 1 only have source='git'. */
|
|
89
90
|
source?: string;
|
|
90
91
|
}
|
|
@@ -112,9 +113,13 @@ export function resolveSourceDetail(
|
|
|
112
113
|
* Classify a single observation or index entry into a disclosure layer.
|
|
113
114
|
*/
|
|
114
115
|
export function classifyLayer(fields: ProvenanceFields): DisclosureLayer {
|
|
115
|
-
const { valueCategory } = fields;
|
|
116
|
+
const { valueCategory, admissionState } = fields;
|
|
116
117
|
const sd = resolveSourceDetail(fields.sourceDetail, fields.source);
|
|
117
118
|
|
|
119
|
+
// New automatic records must earn delivery through the control plane.
|
|
120
|
+
// Preserve legacy behavior for records created before admission metadata.
|
|
121
|
+
if (admissionState === 'candidate' || admissionState === 'ephemeral') return 'L1';
|
|
122
|
+
|
|
118
123
|
// Core-valued memories are always promoted to L2, regardless of source.
|
|
119
124
|
if (valueCategory === 'core') return 'L2';
|
|
120
125
|
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* Import: JSON format only (full fidelity restore)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { Observation } from '../types.js';
|
|
13
|
+
import type { Observation, ObservationReader } from '../types.js';
|
|
14
14
|
import type { Session } from '../types.js';
|
|
15
|
+
import { filterReadableObservations } from './visibility.js';
|
|
15
16
|
import { getObservationStore } from '../store/obs-store.js';
|
|
16
17
|
import { getSessionStore } from '../store/session-store.js';
|
|
17
18
|
|
|
@@ -41,12 +42,18 @@ const OBSERVATION_ICONS: Record<string, string> = {
|
|
|
41
42
|
export async function exportAsJson(
|
|
42
43
|
projectDir: string,
|
|
43
44
|
projectId: string,
|
|
45
|
+
reader?: ObservationReader,
|
|
44
46
|
): Promise<MemorixExport> {
|
|
45
47
|
const store = getObservationStore();
|
|
46
48
|
const allObs = await store.loadAll();
|
|
47
49
|
const allSessions = await getSessionStore().loadAll();
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
// Agent-facing callers supply a reader; trusted maintenance callers may
|
|
52
|
+
// intentionally omit it for a complete local backup or migration.
|
|
53
|
+
const projectObs = filterReadableObservations(
|
|
54
|
+
allObs.filter(o => o.projectId === projectId),
|
|
55
|
+
reader,
|
|
56
|
+
);
|
|
50
57
|
const projectSessions = allSessions.filter(s => s.projectId === projectId);
|
|
51
58
|
|
|
52
59
|
// Type breakdown
|
|
@@ -75,8 +82,9 @@ export async function exportAsJson(
|
|
|
75
82
|
export async function exportAsMarkdown(
|
|
76
83
|
projectDir: string,
|
|
77
84
|
projectId: string,
|
|
85
|
+
reader?: ObservationReader,
|
|
78
86
|
): Promise<string> {
|
|
79
|
-
const data = await exportAsJson(projectDir, projectId);
|
|
87
|
+
const data = await exportAsJson(projectDir, projectId, reader);
|
|
80
88
|
const lines: string[] = [];
|
|
81
89
|
|
|
82
90
|
lines.push(`# Memorix Export: ${projectId}`);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { auditMemoryQuality, type MemoryQualityAuditReport } from './quality-audit.js';
|
|
2
2
|
import type { Observation } from '../types.js';
|
|
3
3
|
import { getRetentionZone } from './retention.js';
|
|
4
|
+
import { isEligibleForAutomaticDelivery } from './admission.js';
|
|
4
5
|
|
|
5
6
|
export interface GraphContextPacketOptions {
|
|
6
7
|
projectId: string;
|
|
@@ -225,6 +226,8 @@ function scoreForPacket(obs: Observation, queryTokens: string[], referenceTime:
|
|
|
225
226
|
source: obs.source ?? 'agent',
|
|
226
227
|
sourceDetail: obs.sourceDetail ?? '',
|
|
227
228
|
valueCategory: obs.valueCategory ?? '',
|
|
229
|
+
admissionState: obs.admissionState ?? '',
|
|
230
|
+
admissionReason: obs.admissionReason ?? '',
|
|
228
231
|
}, referenceTime) !== 'active') {
|
|
229
232
|
score -= 2;
|
|
230
233
|
}
|
|
@@ -348,8 +351,11 @@ export function buildGraphContextPacket(
|
|
|
348
351
|
...audit.issues.orphans.map((entry) => entry.id),
|
|
349
352
|
...audit.issues.retentionCandidates.map((entry) => entry.id),
|
|
350
353
|
]);
|
|
351
|
-
const
|
|
352
|
-
|
|
354
|
+
const deliveryEligible = observations.filter(
|
|
355
|
+
(obs) => obs.projectId === options.projectId && isEligibleForAutomaticDelivery(obs),
|
|
356
|
+
);
|
|
357
|
+
const filteredObservations = deliveryEligible.filter((obs) => !riskIds.has(obs.id));
|
|
358
|
+
const baseObservations = filteredObservations.length > 0 ? filteredObservations : deliveryEligible;
|
|
353
359
|
const memories = pickMemories(baseObservations, options.projectId, options.query, options.limit ?? 5, referenceTime);
|
|
354
360
|
const entities = buildEntities(memories);
|
|
355
361
|
const entityNames = new Set(entities.map((entity) => entity.name));
|