@ai2070/memex 0.9.0

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.
Files changed (46) hide show
  1. package/.github/workflows/ci.yml +31 -0
  2. package/.github/workflows/release.yml +35 -0
  3. package/API.md +1078 -0
  4. package/LICENSE +190 -0
  5. package/README.md +574 -0
  6. package/package.json +30 -0
  7. package/src/bulk.ts +128 -0
  8. package/src/envelope.ts +52 -0
  9. package/src/errors.ts +27 -0
  10. package/src/graph.ts +15 -0
  11. package/src/helpers.ts +51 -0
  12. package/src/index.ts +142 -0
  13. package/src/integrity.ts +378 -0
  14. package/src/intent.ts +311 -0
  15. package/src/query.ts +357 -0
  16. package/src/reducer.ts +177 -0
  17. package/src/replay.ts +32 -0
  18. package/src/retrieval.ts +306 -0
  19. package/src/serialization.ts +34 -0
  20. package/src/stats.ts +62 -0
  21. package/src/task.ts +373 -0
  22. package/src/transplant.ts +488 -0
  23. package/src/types.ts +248 -0
  24. package/tests/bugfix-and-coverage.test.ts +958 -0
  25. package/tests/bugfix-holes.test.ts +856 -0
  26. package/tests/bulk.test.ts +256 -0
  27. package/tests/edge-cases-v2.test.ts +355 -0
  28. package/tests/edge-cases.test.ts +661 -0
  29. package/tests/envelope.test.ts +92 -0
  30. package/tests/graph.test.ts +41 -0
  31. package/tests/helpers.test.ts +120 -0
  32. package/tests/integrity.test.ts +371 -0
  33. package/tests/intent.test.ts +276 -0
  34. package/tests/query-advanced.test.ts +252 -0
  35. package/tests/query.test.ts +623 -0
  36. package/tests/reducer.test.ts +342 -0
  37. package/tests/replay.test.ts +145 -0
  38. package/tests/retrieval.test.ts +691 -0
  39. package/tests/serialization.test.ts +118 -0
  40. package/tests/setup.test.ts +7 -0
  41. package/tests/stats.test.ts +163 -0
  42. package/tests/task.test.ts +322 -0
  43. package/tests/transplant.test.ts +385 -0
  44. package/tests/types.test.ts +231 -0
  45. package/tsconfig.json +18 -0
  46. package/vitest.config.ts +7 -0
package/src/bulk.ts ADDED
@@ -0,0 +1,128 @@
1
+ import type {
2
+ GraphState,
3
+ MemoryItem,
4
+ MemoryFilter,
5
+ MemoryLifecycleEvent,
6
+ QueryOptions,
7
+ } from "./types.js";
8
+ import { getItems } from "./query.js";
9
+ import { mergeItem } from "./reducer.js";
10
+
11
+ export interface ScoreAdjustment {
12
+ authority?: number;
13
+ conviction?: number;
14
+ importance?: number;
15
+ }
16
+
17
+ function clamp(value: number): number {
18
+ return Math.max(0, Math.min(1, value));
19
+ }
20
+
21
+ export type ItemTransform = (item: MemoryItem) => Partial<MemoryItem> | null;
22
+
23
+ /**
24
+ * Apply a transform to all matching items in a single pass.
25
+ * Clones the items Map once, not per item.
26
+ */
27
+ export function applyMany(
28
+ state: GraphState,
29
+ filter: MemoryFilter,
30
+ transform: ItemTransform,
31
+ author: string,
32
+ reason?: string,
33
+ options?: QueryOptions,
34
+ ): { state: GraphState; events: MemoryLifecycleEvent[] } {
35
+ const matched = getItems(state, filter, options);
36
+
37
+ if (matched.length === 0) {
38
+ return { state, events: [] };
39
+ }
40
+
41
+ const items = new Map(state.items);
42
+ const allEvents: MemoryLifecycleEvent[] = [];
43
+ let changed = false;
44
+
45
+ for (const item of matched) {
46
+ if (!items.has(item.id)) continue;
47
+
48
+ const partial = transform(item);
49
+
50
+ if (partial === null) {
51
+ items.delete(item.id);
52
+ allEvents.push({
53
+ namespace: "memory",
54
+ type: "memory.retracted",
55
+ item,
56
+ cause_type: "memory.retract",
57
+ });
58
+ changed = true;
59
+ } else if (Object.keys(partial).length > 0) {
60
+ const merged = mergeItem(item, partial);
61
+ items.set(item.id, merged);
62
+ allEvents.push({
63
+ namespace: "memory",
64
+ type: "memory.updated",
65
+ item: merged,
66
+ cause_type: "memory.update",
67
+ });
68
+ changed = true;
69
+ }
70
+ }
71
+
72
+ if (!changed) return { state, events: [] };
73
+
74
+ return { state: { items, edges: state.edges }, events: allEvents };
75
+ }
76
+
77
+ export function bulkAdjustScores(
78
+ state: GraphState,
79
+ criteria: MemoryFilter,
80
+ delta: ScoreAdjustment,
81
+ author: string,
82
+ reason?: string,
83
+ basis?: Record<string, unknown>,
84
+ ): { state: GraphState; events: MemoryLifecycleEvent[] } {
85
+ return applyMany(
86
+ state,
87
+ criteria,
88
+ (item) => {
89
+ const partial: Record<string, unknown> = {};
90
+ if (delta.authority !== undefined) {
91
+ partial.authority = clamp((item.authority ?? 0) + delta.authority);
92
+ }
93
+ if (delta.conviction !== undefined) {
94
+ partial.conviction = clamp((item.conviction ?? 0) + delta.conviction);
95
+ }
96
+ if (delta.importance !== undefined) {
97
+ partial.importance = clamp((item.importance ?? 0) + delta.importance);
98
+ }
99
+ return partial;
100
+ },
101
+ author,
102
+ reason,
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Decay importance on items created before a cutoff time.
108
+ */
109
+ export function decayImportance(
110
+ state: GraphState,
111
+ olderThanMs: number,
112
+ factor: number,
113
+ author: string,
114
+ reason?: string,
115
+ ): { state: GraphState; events: MemoryLifecycleEvent[] } {
116
+ const cutoff = Date.now() - olderThanMs;
117
+ return applyMany(
118
+ state,
119
+ { created: { before: cutoff } },
120
+ (item) => {
121
+ const current = item.importance ?? 0;
122
+ if (current === 0) return {};
123
+ return { importance: clamp(current * factor) };
124
+ },
125
+ author,
126
+ reason ?? "time-based importance decay",
127
+ );
128
+ }
@@ -0,0 +1,52 @@
1
+ import { uuidv7 } from "uuidv7";
2
+ import type {
3
+ EventEnvelope,
4
+ MemoryLifecycleEvent,
5
+ MemoryItem,
6
+ Edge,
7
+ } from "./types.js";
8
+
9
+ export function wrapLifecycleEvent(
10
+ event: MemoryLifecycleEvent,
11
+ causeId: string,
12
+ traceId?: string,
13
+ ): EventEnvelope<MemoryLifecycleEvent & { cause_id: string }> {
14
+ return {
15
+ id: uuidv7(),
16
+ namespace: "memory",
17
+ type: event.type,
18
+ ts: new Date().toISOString(),
19
+ ...(traceId ? { trace_id: traceId } : {}),
20
+ payload: { ...event, cause_id: causeId },
21
+ };
22
+ }
23
+
24
+ export function wrapStateEvent(
25
+ item: MemoryItem,
26
+ causeId: string,
27
+ traceId?: string,
28
+ ): EventEnvelope<{ item: MemoryItem; cause_id: string }> {
29
+ return {
30
+ id: uuidv7(),
31
+ namespace: "memory",
32
+ type: "state.memory",
33
+ ts: new Date().toISOString(),
34
+ ...(traceId ? { trace_id: traceId } : {}),
35
+ payload: { item, cause_id: causeId },
36
+ };
37
+ }
38
+
39
+ export function wrapEdgeStateEvent(
40
+ edge: Edge,
41
+ causeId: string,
42
+ traceId?: string,
43
+ ): EventEnvelope<{ edge: Edge; cause_id: string }> {
44
+ return {
45
+ id: uuidv7(),
46
+ namespace: "memory",
47
+ type: "state.edge",
48
+ ts: new Date().toISOString(),
49
+ ...(traceId ? { trace_id: traceId } : {}),
50
+ payload: { edge, cause_id: causeId },
51
+ };
52
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,27 @@
1
+ export class MemoryNotFoundError extends Error {
2
+ constructor(itemId: string) {
3
+ super(`Memory item not found: ${itemId}`);
4
+ this.name = "MemoryNotFoundError";
5
+ }
6
+ }
7
+
8
+ export class EdgeNotFoundError extends Error {
9
+ constructor(edgeId: string) {
10
+ super(`Edge not found: ${edgeId}`);
11
+ this.name = "EdgeNotFoundError";
12
+ }
13
+ }
14
+
15
+ export class DuplicateMemoryError extends Error {
16
+ constructor(itemId: string) {
17
+ super(`Memory item already exists: ${itemId}`);
18
+ this.name = "DuplicateMemoryError";
19
+ }
20
+ }
21
+
22
+ export class DuplicateEdgeError extends Error {
23
+ constructor(edgeId: string) {
24
+ super(`Edge already exists: ${edgeId}`);
25
+ this.name = "DuplicateEdgeError";
26
+ }
27
+ }
package/src/graph.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { GraphState } from "./types.js";
2
+
3
+ export function createGraphState(): GraphState {
4
+ return {
5
+ items: new Map(),
6
+ edges: new Map(),
7
+ };
8
+ }
9
+
10
+ export function cloneGraphState(state: GraphState): GraphState {
11
+ return {
12
+ items: new Map(state.items),
13
+ edges: new Map(state.edges),
14
+ };
15
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { uuidv7 } from "uuidv7";
2
+ import type { MemoryItem, Edge, EventEnvelope } from "./types.js";
3
+
4
+ function validateScore(value: number | undefined, name: string): void {
5
+ if (value !== undefined && (value < 0 || value > 1)) {
6
+ throw new RangeError(`${name} must be between 0 and 1, got ${value}`);
7
+ }
8
+ }
9
+
10
+ export function createMemoryItem(
11
+ input: Omit<MemoryItem, "id"> & { id?: string },
12
+ ): MemoryItem {
13
+ validateScore(input.authority, "authority");
14
+ validateScore(input.conviction, "conviction");
15
+ validateScore(input.importance, "importance");
16
+
17
+ return {
18
+ ...input,
19
+ id: input.id ?? uuidv7(),
20
+ };
21
+ }
22
+
23
+ export function createEdge(
24
+ input: Omit<Edge, "edge_id" | "active"> & {
25
+ edge_id?: string;
26
+ active?: boolean;
27
+ },
28
+ ): Edge {
29
+ validateScore(input.authority, "authority");
30
+
31
+ return {
32
+ ...input,
33
+ edge_id: input.edge_id ?? uuidv7(),
34
+ active: input.active ?? true,
35
+ };
36
+ }
37
+
38
+ export function createEventEnvelope<T>(
39
+ type: string,
40
+ payload: T,
41
+ opts?: { trace_id?: string },
42
+ ): EventEnvelope<T> {
43
+ return {
44
+ id: uuidv7(),
45
+ namespace: "memory",
46
+ type,
47
+ ts: new Date().toISOString(),
48
+ payload,
49
+ ...(opts?.trace_id ? { trace_id: opts.trace_id } : {}),
50
+ };
51
+ }
package/src/index.ts ADDED
@@ -0,0 +1,142 @@
1
+ // @ai2070/memex — Memory Layer
2
+ // Graph of memory items and edges over an append-only event log
3
+
4
+ export type {
5
+ KnownMemoryKind,
6
+ MemoryKind,
7
+ MemoryItem,
8
+ KnownEdgeKind,
9
+ EdgeKind,
10
+ Edge,
11
+ KnownNamespace,
12
+ Namespace,
13
+ EventEnvelope,
14
+ GraphState,
15
+ MemoryCommand,
16
+ LifecycleEventType,
17
+ MemoryLifecycleEvent,
18
+ MemoryFilter,
19
+ EdgeFilter,
20
+ SortField,
21
+ SortOption,
22
+ QueryOptions,
23
+ DecayInterval,
24
+ DecayType,
25
+ DecayConfig,
26
+ ScoreWeights,
27
+ ScoredItem,
28
+ } from "./types.js";
29
+
30
+ export {
31
+ createMemoryItem,
32
+ createEdge,
33
+ createEventEnvelope,
34
+ } from "./helpers.js";
35
+ export { createGraphState, cloneGraphState } from "./graph.js";
36
+ export {
37
+ MemoryNotFoundError,
38
+ EdgeNotFoundError,
39
+ DuplicateMemoryError,
40
+ DuplicateEdgeError,
41
+ } from "./errors.js";
42
+ export { applyCommand } from "./reducer.js";
43
+ export {
44
+ getItems,
45
+ getEdges,
46
+ getItemById,
47
+ getEdgeById,
48
+ getRelatedItems,
49
+ getParents,
50
+ getChildren,
51
+ getScoredItems,
52
+ extractTimestamp,
53
+ } from "./query.js";
54
+ export type { ScoredQueryOptions } from "./query.js";
55
+ export { applyMany, bulkAdjustScores, decayImportance } from "./bulk.js";
56
+ export type { ItemTransform, ScoreAdjustment } from "./bulk.js";
57
+ export {
58
+ wrapLifecycleEvent,
59
+ wrapStateEvent,
60
+ wrapEdgeStateEvent,
61
+ } from "./envelope.js";
62
+ export { replayCommands, replayFromEnvelopes } from "./replay.js";
63
+ export {
64
+ getContradictions,
65
+ markContradiction,
66
+ resolveContradiction,
67
+ getStaleItems,
68
+ getDependents,
69
+ cascadeRetract,
70
+ markAlias,
71
+ getAliases,
72
+ getAliasGroup,
73
+ getItemsByBudget,
74
+ } from "./integrity.js";
75
+ export type { Contradiction, StaleItem, BudgetOptions } from "./integrity.js";
76
+ export {
77
+ getSupportTree,
78
+ getSupportSet,
79
+ filterContradictions,
80
+ surfaceContradictions,
81
+ applyDiversity,
82
+ smartRetrieve,
83
+ } from "./retrieval.js";
84
+ export type {
85
+ SupportNode,
86
+ DiversityOptions,
87
+ SmartRetrievalOptions,
88
+ } from "./retrieval.js";
89
+ export { toJSON, fromJSON, stringify, parse } from "./serialization.js";
90
+ export type { SerializedGraphState } from "./serialization.js";
91
+ export { getStats } from "./stats.js";
92
+ export type { GraphStats } from "./stats.js";
93
+
94
+ // Intent graph
95
+ export {
96
+ createIntentState,
97
+ createIntent,
98
+ applyIntentCommand,
99
+ getIntents,
100
+ getIntentById,
101
+ IntentNotFoundError,
102
+ DuplicateIntentError,
103
+ InvalidIntentTransitionError,
104
+ } from "./intent.js";
105
+ export type {
106
+ IntentStatus,
107
+ Intent,
108
+ IntentState,
109
+ IntentCommand,
110
+ IntentLifecycleEvent,
111
+ IntentFilter,
112
+ } from "./intent.js";
113
+
114
+ // Task graph
115
+ export {
116
+ createTaskState,
117
+ createTask,
118
+ applyTaskCommand,
119
+ getTasks,
120
+ getTaskById,
121
+ getTasksByIntent,
122
+ TaskNotFoundError,
123
+ DuplicateTaskError,
124
+ InvalidTaskTransitionError,
125
+ } from "./task.js";
126
+ export type {
127
+ TaskStatus,
128
+ Task,
129
+ TaskState,
130
+ TaskCommand,
131
+ TaskLifecycleEvent,
132
+ TaskFilter,
133
+ } from "./task.js";
134
+
135
+ // Transplant (export/import slices)
136
+ export { exportSlice, importSlice } from "./transplant.js";
137
+ export type {
138
+ ExportOptions,
139
+ MemexExport,
140
+ ImportOptions,
141
+ ImportReport,
142
+ } from "./transplant.js";