memorix 1.2.8 → 1.2.10
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 +16 -0
- package/README.md +2 -2
- package/dist/cli/index.js +19505 -19136
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/memcode.js +0 -0
- package/dist/index.js +7849 -7628
- package/dist/index.js.map +1 -1
- package/dist/maintenance-jobs-o1rYSFcM.d.ts +36 -0
- package/dist/maintenance-runner.d.ts +1 -34
- package/dist/maintenance-runner.js +5892 -5692
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +16 -0
- package/dist/sdk.js +7782 -7561
- package/dist/sdk.js.map +1 -1
- package/dist/vector-backfill-runner.d.ts +31 -0
- package/dist/vector-backfill-runner.js +12670 -0
- package/dist/vector-backfill-runner.js.map +1 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +2 -2
- package/docs/DEVELOPMENT.md +7 -1
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/SETUP.md +6 -4
- package/docs/hooks-architecture.md +1 -1
- package/package.json +6 -2
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +38 -0
- package/src/cli/commands/agent-integrations.ts +12 -6
- package/src/cli/commands/operator-shared.ts +4 -1
- package/src/cli/commands/serve-http.ts +44 -47
- package/src/cli/commands/setup.ts +200 -23
- package/src/dashboard/server.ts +43 -63
- package/src/memory/observations.ts +108 -79
- package/src/memory/retention.ts +106 -28
- package/src/memory/session.ts +71 -12
- package/src/runtime/vector-backfill-runner.ts +144 -0
- package/src/search/intent-detector.ts +39 -1
- package/src/server.ts +5 -5
|
@@ -243,7 +243,7 @@ memorix setup --agent omp --global
|
|
|
243
243
|
What this installs depends on the target agent:
|
|
244
244
|
|
|
245
245
|
- Claude Code: local `memorix-local` marketplace plugin, best-effort `claude plugin install memorix@memorix-local`, plugin-bundled stdio MCP, hooks, skills, plus `CLAUDE.md` guidance.
|
|
246
|
-
- Codex: local Personal marketplace plugin under
|
|
246
|
+
- Codex: local Personal marketplace plugin under `~/plugins/memorix`, marketplace entry at `~/.agents/plugins/marketplace.json`, best-effort `codex plugin add memorix@personal`, plugin-bundled stdio MCP, skills, and `SessionStart` / `UserPromptSubmit` / `PostToolUse` / `PreCompact` / `Stop` hooks. It does not create project `.codex` files or overwrite model, approval, or sandbox settings. Codex asks the user to review plugin hooks once in `/hooks` before non-managed hooks run.
|
|
247
247
|
- GitHub Copilot CLI: local plugin package under `~/.copilot/plugins/local/memorix`, best-effort `copilot plugin install <local-path>`, plugin-bundled stdio MCP, hooks, and skills.
|
|
248
248
|
- Cursor: Cursor MCP config, `.cursor/rules/memorix.mdc`, skills, and hook guidance through Cursor's project config surfaces.
|
|
249
249
|
- Pi: user Pi package with extension-based hook capture and a Memorix skill, registered through `pi install <path> --approve`; project-local setup uses `-l`.
|
|
@@ -668,7 +668,7 @@ If it reports stale MCP command paths, missing `memorix` MCP entries, missing Cl
|
|
|
668
668
|
memorix repair agents --agent <agent>
|
|
669
669
|
```
|
|
670
670
|
|
|
671
|
-
Repair only rewrites Memorix-owned MCP entries, Memorix-owned guidance blocks, and an explicitly targeted Codex Memorix plugin bundle. It does not edit unrelated user config or turn on a user-disabled Codex plugin; enable
|
|
671
|
+
Repair only rewrites Memorix-owned MCP entries, Memorix-owned guidance blocks, and an explicitly targeted Codex Memorix plugin bundle. It does not edit unrelated user config, grant Codex hook trust, or turn on a user-disabled Codex plugin; review hooks in `/hooks` and enable a disabled plugin in `/plugins`.
|
|
672
672
|
|
|
673
673
|
### 1. Is the workspace a Git repo?
|
|
674
674
|
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -321,7 +321,13 @@ npm publish --access public
|
|
|
321
321
|
|
|
322
322
|
Notes:
|
|
323
323
|
|
|
324
|
-
- `prepublishOnly` runs build + test
|
|
324
|
+
- `prepublishOnly` synchronizes and checks Registry metadata, then runs build + test; it does not contact live model catalog APIs
|
|
325
|
+
- `server.json` is the official MCP Registry record. Keep it synchronized with
|
|
326
|
+
`package.json` by running `npm run sync:mcp-registry`; verify it with
|
|
327
|
+
`npm run check:mcp-registry` before a release.
|
|
328
|
+
- The GitHub publish workflow publishes npm first, then uses GitHub OIDC to
|
|
329
|
+
publish the same verified version to the official MCP Registry. It does not
|
|
330
|
+
require a Registry token or private key in repository secrets.
|
|
325
331
|
- `@memorix/ai`, `@memorix/agent-core`, `@memorix/tui`, and `@memorix/memcode` are internal workspaces. Their code is bundled into the root `memorix` distribution and they must stay `private` unless the project deliberately establishes an owned npm scope and a separate package-support contract.
|
|
326
332
|
- npm publish is usually manual, especially when 2FA is enabled
|
|
327
333
|
- GitHub release automation should not be treated as a substitute for manual runtime validation
|
package/docs/INTEGRATIONS.md
CHANGED
|
@@ -49,7 +49,7 @@ Generated guidance also has scope:
|
|
|
49
49
|
| Agent | Recommended install | Official entry | What gets installed | Notes |
|
|
50
50
|
| --- | --- | --- | --- | --- |
|
|
51
51
|
| Claude Code | `memorix setup --agent claude --global` | Claude Code plugin marketplace | Local `memorix-local` marketplace, plugin-bundled stdio MCP, skills, hooks, plus `CLAUDE.md` guidance | Setup attempts `claude plugin marketplace add` and `claude plugin install memorix@memorix-local`. |
|
|
52
|
-
| Codex | `memorix setup --agent codex --global` | Codex Personal marketplace plugin |
|
|
52
|
+
| Codex | `memorix setup --agent codex --global` | Codex Personal marketplace plugin | User plugin under `~/plugins/memorix`, Personal marketplace entry, plugin-bundled stdio MCP, skills, and hooks | Setup attempts `codex plugin add memorix@personal`. It leaves project `.codex` files and model/approval/sandbox settings alone. `memorix doctor agents --agent codex --scope global` verifies the bundle, marketplace, and enabled state, then reports any one-time hook review Codex still requires; start a new thread after setup. |
|
|
53
53
|
| GitHub Copilot CLI | `memorix setup --agent copilot --global` | Copilot CLI plugin package | Local plugin under `~/.copilot/plugins/local/memorix` with MCP, skills, and hooks | Setup attempts `copilot plugin install <local-path>` when Copilot CLI is available. |
|
|
54
54
|
| Cursor | `memorix setup --agent cursor --global` | Cursor MCP and rules config | Cursor MCP config, `.cursor/rules/memorix.mdc`, skills, and hook guidance | Reload Cursor after setup so it can pick up project config changes. |
|
|
55
55
|
| Gemini CLI | `memorix setup --agent gemini-cli --global` | Gemini CLI extension | Extension under `~/.gemini/extensions/memorix` with MCP, `GEMINI.md` context, hooks, commands, and skills | Antigravity CLI has an official Gemini CLI migration path, but Gemini CLI remains an active standalone Google CLI target. |
|
package/docs/SETUP.md
CHANGED
|
@@ -76,7 +76,7 @@ memorix setup --agent omp --global
|
|
|
76
76
|
What this does:
|
|
77
77
|
|
|
78
78
|
- Claude Code: installs a local marketplace plugin, attempts `claude plugin install memorix@memorix-local`, and writes `CLAUDE.md` guidance.
|
|
79
|
-
- Codex: installs a
|
|
79
|
+
- Codex: installs a user-level Personal marketplace plugin under `~/plugins/memorix`, attempts `codex plugin add memorix@personal`, and keeps project-local `.codex` configuration untouched. The plugin owns its MCP server, skills, and optional hooks without changing your model, approval, or sandbox settings.
|
|
80
80
|
- GitHub Copilot CLI: installs a local plugin package and attempts `copilot plugin install <local-path>`.
|
|
81
81
|
- Cursor: writes Cursor MCP config, rules, skills, and hook guidance.
|
|
82
82
|
- Pi: installs the user-level Memorix Pi package and attempts `pi install <path> --approve`.
|
|
@@ -132,9 +132,9 @@ memorix doctor agents --agent <agent>
|
|
|
132
132
|
memorix repair agents --agent <agent>
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
The doctor checks Memorix-owned MCP entries and guidance files for stale command paths, missing `memorix` MCP servers, missing Claude `alwaysLoad`, and outdated Memory Autopilot rules. For `codex --scope global`,
|
|
135
|
+
The doctor checks Memorix-owned MCP entries and guidance files for stale command paths, missing `memorix` MCP servers, missing Claude `alwaysLoad`, and outdated Memory Autopilot rules. For `codex --scope global`, MCP is owned by the user-level plugin rather than a direct `config.toml` entry. The doctor checks the plugin bundle, Personal marketplace entry, five declared lifecycle hooks, and the installed/enabled state reported by `codex plugin list`; it reports, but never auto-grants, any hook review Codex still requires.
|
|
136
136
|
|
|
137
|
-
After Codex setup, start a new thread so enabled plugins can load. Codex
|
|
137
|
+
After Codex setup, start a new thread so enabled plugins can load. If Codex asks to review plugin hooks, approve or decline them in `/hooks`; this is user-level plugin consent, not project trust. If doctor reports `codex-plugin-disabled`, open `/plugins` and turn Memorix on there; repair does not toggle a user-disabled plugin.
|
|
138
138
|
|
|
139
139
|
### Option B: manual stdio MCP
|
|
140
140
|
|
|
@@ -330,7 +330,9 @@ HTTP variants in Windsurf-like clients may use `serverUrl`:
|
|
|
330
330
|
}
|
|
331
331
|
```
|
|
332
332
|
|
|
333
|
-
### Codex
|
|
333
|
+
### Codex manual MCP compatibility
|
|
334
|
+
|
|
335
|
+
The user-level plugin is the default and should be the only Memorix route for Codex. Use this compatibility fallback only when the Codex plugin system is unavailable; do not add it alongside `memorix@personal`, because that creates duplicate MCP ownership.
|
|
334
336
|
|
|
335
337
|
Config file: `~/.codex/config.toml`
|
|
336
338
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|-------|---------|------|
|
|
28
28
|
| VS Code Copilot | `.github/hooks/*.json` | Claude Code 兼容 |
|
|
29
29
|
| Claude Code | `.claude/settings.json` | 原生 |
|
|
30
|
-
| Codex | Memorix 插件内 `hooks/hooks.json` | Codex plugin hooks;通过 `memorix setup --agent codex`
|
|
30
|
+
| Codex | 用户级 Memorix 插件内 `hooks/hooks.json` | Codex plugin hooks;通过 `memorix setup --agent codex --global` 安装,不写项目 `.codex/hooks.json` |
|
|
31
31
|
| Windsurf | `.windsurf/cascade.json` | Windsurf 格式 |
|
|
32
32
|
| Cursor | `.cursor/hooks.json` | Cursor 格式 |
|
|
33
33
|
| Kiro | `.kiro/hooks/*.hook.md` | Markdown + YAML |
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorix",
|
|
3
|
-
"
|
|
3
|
+
"mcpName": "io.github.AVIDS2/memorix",
|
|
4
|
+
"version": "1.2.10",
|
|
4
5
|
"description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"sideEffects": false,
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"files": [
|
|
34
35
|
"dist",
|
|
35
36
|
"src",
|
|
37
|
+
"server.json",
|
|
36
38
|
"!src/**/*.js",
|
|
37
39
|
"!src/**/*.js.map",
|
|
38
40
|
"!src/**/*.d.ts",
|
|
@@ -63,9 +65,11 @@
|
|
|
63
65
|
"test:e2e": "vitest run tests/e2e/",
|
|
64
66
|
"benchmark:codegraph": "node scripts/benchmark-codegraph-provider.cjs",
|
|
65
67
|
"benchmark:retrieval": "npm run build && node scripts/benchmark-retrieval.mjs",
|
|
68
|
+
"sync:mcp-registry": "node scripts/sync-mcp-registry-manifest.mjs",
|
|
69
|
+
"check:mcp-registry": "node scripts/check-mcp-registry-manifest.mjs",
|
|
66
70
|
"audit:pi-upstream": "node scripts/pi-upstream-audit.mjs",
|
|
67
71
|
"lint": "tsc --noEmit",
|
|
68
|
-
"prepublishOnly": "npm run build && npm test"
|
|
72
|
+
"prepublishOnly": "npm run sync:mcp-registry && npm run check:mcp-registry && npm run build && npm test"
|
|
69
73
|
},
|
|
70
74
|
"keywords": [
|
|
71
75
|
"mcp",
|
package/server.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.AVIDS2/memorix",
|
|
4
|
+
"title": "Memorix",
|
|
5
|
+
"description": "Local-first persistent project memory for AI coding agents across MCP clients and sessions.",
|
|
6
|
+
"repository": {
|
|
7
|
+
"url": "https://github.com/AVIDS2/memorix",
|
|
8
|
+
"source": "github"
|
|
9
|
+
},
|
|
10
|
+
"version": "1.2.10",
|
|
11
|
+
"websiteUrl": "https://github.com/AVIDS2/memorix#readme",
|
|
12
|
+
"icons": [
|
|
13
|
+
{
|
|
14
|
+
"src": "https://raw.githubusercontent.com/AVIDS2/memorix/main/assets/logo.png",
|
|
15
|
+
"mimeType": "image/png",
|
|
16
|
+
"sizes": [
|
|
17
|
+
"1024x1024"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"packages": [
|
|
22
|
+
{
|
|
23
|
+
"registryType": "npm",
|
|
24
|
+
"identifier": "memorix",
|
|
25
|
+
"version": "1.2.10",
|
|
26
|
+
"runtimeHint": "npx",
|
|
27
|
+
"packageArguments": [
|
|
28
|
+
{
|
|
29
|
+
"type": "positional",
|
|
30
|
+
"value": "serve"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"transport": {
|
|
34
|
+
"type": "stdio"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getSetupAgentTargets,
|
|
13
13
|
installPluginPackage,
|
|
14
14
|
installMcpConfig,
|
|
15
|
+
migrateLegacyCodexIntegration,
|
|
15
16
|
tryInstallCodexPlugin,
|
|
16
17
|
type McpConfigAgent,
|
|
17
18
|
} from './setup.js';
|
|
@@ -228,7 +229,7 @@ function asRecordArray(value: unknown): JsonRecord[] {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
function codexPluginPath(): string {
|
|
231
|
-
return `${homedir()}
|
|
232
|
+
return `${homedir()}/plugins/${CODEX_PLUGIN_NAME}`;
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
function codexPluginMcpPath(): string {
|
|
@@ -254,7 +255,10 @@ function isMemorixCodexPlugin(value: JsonRecord): boolean {
|
|
|
254
255
|
|
|
255
256
|
export function parseCodexPluginList(output: string): AgentPluginCheck['runtime'] | null {
|
|
256
257
|
try {
|
|
257
|
-
const
|
|
258
|
+
const start = output.indexOf('{');
|
|
259
|
+
const end = output.lastIndexOf('}');
|
|
260
|
+
if (start < 0 || end < start) return null;
|
|
261
|
+
const parsed = asRecord(JSON.parse(output.slice(start, end + 1)));
|
|
258
262
|
if (!parsed) return null;
|
|
259
263
|
const entries = [...asRecordArray(parsed.installed), ...asRecordArray(parsed.available)];
|
|
260
264
|
const plugin = entries.find(isMemorixCodexPlugin);
|
|
@@ -407,7 +411,7 @@ async function inspectCodexMarketplace(): Promise<AgentPluginCheck> {
|
|
|
407
411
|
|
|
408
412
|
const source = asRecord(entry.source);
|
|
409
413
|
const sourcePath = typeof source?.path === 'string' ? source.path : '';
|
|
410
|
-
const issues = source?.source === 'local' && normalizeMarketplacePath(sourcePath) === '
|
|
414
|
+
const issues = source?.source === 'local' && normalizeMarketplacePath(sourcePath) === 'plugins/memorix'
|
|
411
415
|
? []
|
|
412
416
|
: ['codex-marketplace-entry-stale'];
|
|
413
417
|
return {
|
|
@@ -1060,9 +1064,11 @@ export async function repairAgentIntegrations(options: {
|
|
|
1060
1064
|
} else {
|
|
1061
1065
|
if (!options.dry) {
|
|
1062
1066
|
await installPluginPackage({ agent: 'codex' });
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1067
|
+
const install = tryInstallCodexPlugin();
|
|
1068
|
+
if (install.ok) {
|
|
1069
|
+
await migrateLegacyCodexIntegration({ projectRoot });
|
|
1070
|
+
} else {
|
|
1071
|
+
skipped.push('codex:plugin:global:install-pending');
|
|
1066
1072
|
}
|
|
1067
1073
|
}
|
|
1068
1074
|
changed.push('codex:plugin:global');
|
|
@@ -110,7 +110,10 @@ export async function getCliProjectContext(options?: CliContextOptions): Promise
|
|
|
110
110
|
} catch {
|
|
111
111
|
// CLI reads remain available even if optional background maintenance metadata fails.
|
|
112
112
|
}
|
|
113
|
-
await initObservations(dataDir
|
|
113
|
+
await initObservations(dataDir, {
|
|
114
|
+
embeddingWriteMode: 'deferred',
|
|
115
|
+
projectRoot: project.rootPath,
|
|
116
|
+
});
|
|
114
117
|
await initSessionStore(dataDir);
|
|
115
118
|
const teamStore = await initTeamStore(dataDir);
|
|
116
119
|
|
|
@@ -24,6 +24,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
|
24
24
|
import type { ObservationStore } from '../../store/obs-store.js';
|
|
25
25
|
import { resolveToolProfile } from '../../server/tool-profile.js';
|
|
26
26
|
import { scopeKnowledgeGraphToProject } from '../../memory/graph-scope.js';
|
|
27
|
+
import { projectObservationRetention, summarizeRetentionProjections } from '../../memory/retention.js';
|
|
27
28
|
import { canManageObservation, filterReadableObservations } from '../../memory/visibility.js';
|
|
28
29
|
|
|
29
30
|
export const DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000;
|
|
@@ -760,20 +761,7 @@ export default defineCommand({
|
|
|
760
761
|
await initGraphStore(statsDataDir);
|
|
761
762
|
const graph = { entities: getGraphStore().loadEntities(), relations: getGraphStore().loadRelations() };
|
|
762
763
|
|
|
763
|
-
const observations = await loadDashboardProjectObservations(statsDataDir, statsProjectId, 'active')
|
|
764
|
-
type?: string;
|
|
765
|
-
id?: number;
|
|
766
|
-
title?: string;
|
|
767
|
-
entityName?: string;
|
|
768
|
-
createdAt?: string;
|
|
769
|
-
source?: string;
|
|
770
|
-
commitHash?: string;
|
|
771
|
-
filesModified?: string[];
|
|
772
|
-
relatedEntities?: string[];
|
|
773
|
-
status?: string;
|
|
774
|
-
importance?: number;
|
|
775
|
-
accessCount?: number;
|
|
776
|
-
}>;
|
|
764
|
+
const observations = await loadDashboardProjectObservations(statsDataDir, statsProjectId, 'active');
|
|
777
765
|
const statsStore = await getDashboardObservationStore(statsDataDir);
|
|
778
766
|
const nextId = await statsStore.loadIdCounter();
|
|
779
767
|
const typeCounts: Record<string, number> = {};
|
|
@@ -811,20 +799,17 @@ export default defineCommand({
|
|
|
811
799
|
filesModified: o.filesModified,
|
|
812
800
|
}));
|
|
813
801
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
else if (score >= 1) retentionSummary.stale++;
|
|
826
|
-
else retentionSummary.archive++;
|
|
827
|
-
}
|
|
802
|
+
const retention = summarizeRetentionProjections(
|
|
803
|
+
observations
|
|
804
|
+
.filter((observation) => observation.type !== 'probe')
|
|
805
|
+
.map((observation) => projectObservationRetention(observation, { referenceTime: new Date(now) })),
|
|
806
|
+
);
|
|
807
|
+
const retentionSummary = {
|
|
808
|
+
active: retention.active,
|
|
809
|
+
stale: retention.stale,
|
|
810
|
+
archive: retention.archiveCandidates,
|
|
811
|
+
immune: retention.immune,
|
|
812
|
+
};
|
|
828
813
|
|
|
829
814
|
const sorted = [...observations].filter(o => o.type !== 'probe').sort((a, b) => (b.id || 0) - (a.id || 0)).slice(0, 10);
|
|
830
815
|
|
|
@@ -959,26 +944,38 @@ export default defineCommand({
|
|
|
959
944
|
|
|
960
945
|
if (apiPath === '/retention') {
|
|
961
946
|
const { projectId: retProjectId, dataDir: retDataDir } = await resolveRequestProject(url);
|
|
962
|
-
const observations = await loadDashboardProjectObservations(retDataDir, retProjectId, 'active')
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
947
|
+
const observations = await loadDashboardProjectObservations(retDataDir, retProjectId, 'active');
|
|
948
|
+
const referenceTime = new Date();
|
|
949
|
+
const rows = observations
|
|
950
|
+
.filter((observation) => observation.type !== 'probe')
|
|
951
|
+
.map((observation) => ({
|
|
952
|
+
observation,
|
|
953
|
+
retention: projectObservationRetention(observation, { referenceTime }),
|
|
954
|
+
}))
|
|
955
|
+
.sort((a, b) => b.retention.displayScore - a.retention.displayScore);
|
|
956
|
+
const summary = summarizeRetentionProjections(rows.map((row) => row.retention));
|
|
957
|
+
const items = rows.map(({ observation, retention }) => ({
|
|
958
|
+
id: observation.id,
|
|
959
|
+
title: observation.title,
|
|
960
|
+
type: observation.type,
|
|
961
|
+
entityName: observation.entityName,
|
|
962
|
+
score: retention.displayScore,
|
|
963
|
+
isImmune: retention.immune,
|
|
964
|
+
zone: retention.zone,
|
|
965
|
+
ageHours: retention.ageHours,
|
|
966
|
+
accessCount: retention.accessCount,
|
|
967
|
+
effectiveRetentionDays: retention.effectiveRetentionDays,
|
|
968
|
+
immunityReason: retention.immunityReason,
|
|
969
|
+
}));
|
|
970
|
+
sendJson({
|
|
971
|
+
summary: {
|
|
972
|
+
active: summary.active,
|
|
973
|
+
stale: summary.stale,
|
|
974
|
+
archive: summary.archiveCandidates,
|
|
975
|
+
immune: summary.immune,
|
|
976
|
+
},
|
|
977
|
+
items,
|
|
975
978
|
});
|
|
976
|
-
scored.sort((a, b) => b.score - a.score);
|
|
977
|
-
const activeCount = scored.filter(s => s.score >= 3).length;
|
|
978
|
-
const staleCount = scored.filter(s => s.score < 3 && s.score >= 1).length;
|
|
979
|
-
const archiveCount = scored.filter(s => s.score < 1).length;
|
|
980
|
-
const immuneCount = scored.filter(s => s.isImmune).length;
|
|
981
|
-
sendJson({ summary: { active: activeCount, stale: staleCount, archive: archiveCount, immune: immuneCount }, items: scored });
|
|
982
979
|
return;
|
|
983
980
|
}
|
|
984
981
|
|