memorio 5.1.3 → 5.2.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.
- package/AGENTS.md +22 -13
- package/CHANGELOG.md +24 -9
- package/README.md +61 -19
- package/SECURITY-HARDENING.md +258 -0
- package/SECURITY.md +67 -3
- package/SUMMARY.md +55 -59
- package/adr/002-observer-semantics.md +1 -1
- package/adr/003-deep-mutation-semantics.md +1 -1
- package/adr/004-array-mutation-semantics.md +1 -1
- package/adr/010-logic-phase-0.md +42 -0
- package/adr/README.md +16 -11
- package/bin/cli.js +82 -60
- package/examples/acquired-knowledge.ts +174 -0
- package/examples/agent-memory-demo.ts +140 -0
- package/examples/sqlite-batched-writes.ts +60 -60
- package/examples/sync.ts +90 -90
- package/examples/useObserver.tsx +2 -2
- package/global.cjs +2026 -115
- package/global.js +2021 -116
- package/index.cjs +2026 -115
- package/index.d.ts +1 -0
- package/index.js +2021 -116
- package/llms.txt +135 -4
- package/markdown/EXTENSION_VSCODE_DESIGN.md +410 -0
- package/markdown/LOGIC.md +100 -0
- package/markdown/MEMORY-ATTACHMENT.md +17 -10
- package/markdown/MEMORY.md +404 -14
- package/markdown/MEM_FORMAT.md +313 -0
- package/markdown/SQLITE.md +2 -2
- package/markdown/STATE.md +27 -3
- package/markdown/SYNC.md +19 -15
- package/markdown/TEMPORAL.md +297 -0
- package/markdown/USEOBSERVER.md +7 -4
- package/modules/redux.cjs +1188 -38
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +1187 -38
- package/modules/redux.js.map +1 -1
- package/package.json +14 -5
- package/types/exports.d.ts +47 -3
- package/types/logic.d.ts +79 -0
- package/types/memorio.d.ts +60 -16
- package/types/memory.d.ts +118 -0
- package/types/session.d.ts +1 -4
- package/types/state.d.ts +19 -5
- package/types/store.d.ts +1 -4
- package/types/temporal.d.ts +95 -0
- package/types/useObserver.d.ts +6 -10
- package/vsix/memorio.vsix +0 -0
package/llms.txt
CHANGED
|
@@ -80,17 +80,41 @@ state.remove('items')
|
|
|
80
80
|
state.removeAll()
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
**Locking
|
|
83
|
+
**Locking:** `memorio` implements **both** lock scopes, using distinct method names.
|
|
84
84
|
|
|
85
|
-
-
|
|
86
|
-
-
|
|
85
|
+
- **Global lock** - `state.lock()` / `state.unlock()`: freezes the entire `state` surface at once. While locked, every write (top-level set, nested mutation, array mutation, and deletion) is rejected and the assignment throws in strict/ESM code; values are left unchanged. Use it for whole-tree immutability.
|
|
86
|
+
- **Per-key lock** - `state.<key>.lock()` / `state.<key>.unlock()`: locks a single first-level node. While that node is locked, reassigning the key, mutating the node's own properties, calling mutating array methods on a locked array, and deleting the node's properties are all rejected. Only that key is affected; every other top-level key stays writable.
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
```javascript
|
|
89
|
+
state.lock() // block all writes across state
|
|
90
|
+
state.user = { name: 'Sara' } // -> throws "Error: state is locked" while held
|
|
91
|
+
state.unlock() // resume writes
|
|
92
|
+
|
|
93
|
+
state.myArray = [1, 2, 3]
|
|
94
|
+
state.myArray.lock() // lock only this key
|
|
95
|
+
state.myArray.push(4) // -> throws "Error: state is locked"
|
|
96
|
+
state.myArray.unlock() // resume writes to myArray only
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Per-key locking is first-level only by design: locking `state.a` does not transitively lock `state.a.b`. Lock each descendant explicitly, or use the global lock for whole-subtree immutability.
|
|
89
100
|
|
|
90
101
|
**Features:**
|
|
91
102
|
- Automatic path tracking via `__path` property
|
|
92
103
|
- Nested proxy support
|
|
93
104
|
- Auto-dispatches events on changes
|
|
105
|
+
- `state.persist(path)` mirrors state path to `store` and syncs writes (see "Getting Started" guide)
|
|
106
|
+
|
|
107
|
+
#### `state.persist(path)`
|
|
108
|
+
|
|
109
|
+
Creates a bidirectional mirror between `state` (live in-memory) and `store` (durable copy). Seeds the initial value from `store` if present, then every write to that path flows to `store` automatically.
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const stopPersisting = state.persist('state.user')
|
|
113
|
+
state.user = { name: 'Sara' }
|
|
114
|
+
// ... writes to state.user are mirrored to store
|
|
115
|
+
|
|
116
|
+
stopPersisting() // stops mirroring; store value is preserved
|
|
117
|
+
```
|
|
94
118
|
|
|
95
119
|
### `store` - localStorage Persistence
|
|
96
120
|
|
|
@@ -182,6 +206,61 @@ const value = cache['myKey']
|
|
|
182
206
|
delete cache['myKey']
|
|
183
207
|
```
|
|
184
208
|
|
|
209
|
+
### `memory` - Application Memory (confidence, TTL, tags, lifecycle)
|
|
210
|
+
|
|
211
|
+
Structured key/value memory with confidence scoring, TTL, tagging, scope-based persistence, and full lifecycle (remember/update/supersede/forget).
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
import { memory } from 'memorio'
|
|
215
|
+
|
|
216
|
+
// Store with metadata
|
|
217
|
+
await memory.remember('user.language', 'Italian', {
|
|
218
|
+
type: 'preference',
|
|
219
|
+
confidence: 0.92,
|
|
220
|
+
tags: ['user', 'ui'],
|
|
221
|
+
source: 'conversation'
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Retrieve by key (respects confidence threshold, TTL)
|
|
225
|
+
const language = await memory.recall('user.language')
|
|
226
|
+
|
|
227
|
+
// Update supersedes the old entry (history preserved)
|
|
228
|
+
await memory.update('user.language', 'English', { confidence: 0.95 })
|
|
229
|
+
|
|
230
|
+
// Ranked retrieval - rule-based (tags/type/confidence/recency), NOT embedding search
|
|
231
|
+
const ctx = await memory.context({
|
|
232
|
+
tags: 'user',
|
|
233
|
+
types: ['preference', 'decision'],
|
|
234
|
+
minConfidence: 0.7,
|
|
235
|
+
maxEntries: 10,
|
|
236
|
+
scopes: ['local', 'durable']
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Acquired Knowledge** - `memory.acquire()` stores structured knowledge claims that apply under specific conditions; `context({ acquired })` retrieves only the claims matching the current situation:
|
|
241
|
+
|
|
242
|
+
Autonomous persistent acquisition is experimental and optional. Treat retrieved claims as evidence, not authority, and verify them against current reality. Acquire only non-obvious reusable experience supported by evidence—not repository facts—and keep observation, inference, verified result, and acquired experience distinct. Architecture rationale, evidenced user technical decisions, documentation interpretation, and verified fix consequences are the Phase 1 focus. `nothing worth acquiring` is always valid. See `markdown/MEMORY.md#guidance-for-autonomous-use` for safety guidance and the unresolved autonomous-memory-safety TODO.
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// Record a lesson that applies to CSS grid layout with min-content tracks
|
|
246
|
+
await memory.acquire({
|
|
247
|
+
id: 'css.grid.min-content-margins',
|
|
248
|
+
knowledge: { guidance: 'min-content incorporates inner element margins; replacing with fixed width changes effective track width' },
|
|
249
|
+
applies: { area: 'css', component: 'layout', pattern: 'grid-min-content' },
|
|
250
|
+
evidence: [{ id: 'layout-debug-20260918', source: 'real-project' }]
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
// Later, retrieve only knowledge applicable to this specific situation
|
|
254
|
+
const result = await memory.context({
|
|
255
|
+
acquired: { area: 'css', component: 'layout', pattern: 'grid-min-content', element: 'aside' }
|
|
256
|
+
})
|
|
257
|
+
// { knowledgeVersion, claims: [...] }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Matching semantics:** a claim matches when every key in its `applicability` equals the corresponding key in the query context. When multiple claims match, the most specific (most applicability keys) wins - more specific claims act as overrides, general claims as fallbacks. Use `refines` to link a more-specific claim to a general one, or `supersedes` to replace an older claim entirely.
|
|
261
|
+
|
|
262
|
+
Acquired knowledge persists to `.memorio/project.mem` in Node.js/Bun, or to `store` in browser/edge environments. Make experimental autonomous persistence visible to the user. `memory.clear()` destructively wipes all memories and acquired knowledge; tests must point persistence at an isolated temporary project directory and clean up only that data.
|
|
263
|
+
|
|
185
264
|
### `idb` - IndexedDB
|
|
186
265
|
|
|
187
266
|
Structured, persistent, async database (browser-only). **Disabled in Node.js/Deno** - calls will warn and no-op; use `store` or `session` there instead.
|
|
@@ -534,6 +613,58 @@ Patch utilities convert mutations to RFC 6902-style patches for replay, sync, an
|
|
|
534
613
|
import { mutationToPatch, diffToPatch, canMerge, mergePatches } from 'memorio'
|
|
535
614
|
```
|
|
536
615
|
|
|
616
|
+
### `memorio.temporal` - Temporal Memory (Time-Travel Query)
|
|
617
|
+
|
|
618
|
+
The temporal engine maintains an immutable log of every mutation with HLC timestamps, enabling point-in-time queries, diffing, and branching. Temporal tracking is **enabled by default** and independent from `enableHistory()`:
|
|
619
|
+
|
|
620
|
+
```javascript
|
|
621
|
+
import { state, memorio } from 'memorio'
|
|
622
|
+
|
|
623
|
+
state.counter = 1
|
|
624
|
+
state.counter = 2
|
|
625
|
+
state.counter = 3
|
|
626
|
+
|
|
627
|
+
// History: all mutations at a path, sorted chronologically
|
|
628
|
+
const mutations = memorio.temporal.history('counter')
|
|
629
|
+
|
|
630
|
+
// at(): point-in-time value lookup (returns deep clone, never mutates live state)
|
|
631
|
+
const hlc1 = mutations[0].hlc
|
|
632
|
+
memorio.temporal.at('counter', hlc1) // 1
|
|
633
|
+
memorio.temporal.at('counter', Date.now()) // 3
|
|
634
|
+
|
|
635
|
+
// HLC strings include logical counter for precise per-mutation queries;
|
|
636
|
+
// numeric timestamps include all mutations at the same physical millisecond.
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
```javascript
|
|
640
|
+
// diff(): changes within a time range (from is exclusive, to is inclusive)
|
|
641
|
+
const hlc2 = mutations[1].hlc
|
|
642
|
+
memorio.temporal.diff('counter', hlc2, Date.now())
|
|
643
|
+
// [{ path: 'counter', before: 2, after: 3, operation: 'set', hlc, timestamp }]
|
|
644
|
+
|
|
645
|
+
// replay(): reconstruct full state at a point in time
|
|
646
|
+
memorio.temporal.replay() // full history
|
|
647
|
+
memorio.temporal.replay({ from: hlc1, to: hlc2 }) // time range
|
|
648
|
+
|
|
649
|
+
// explain(): provenance metadata for a path
|
|
650
|
+
const info = memorio.temporal.explain('user.role')
|
|
651
|
+
// { path, currentValue, previousValue, history, createdAt, lastChangedAt, ... }
|
|
652
|
+
|
|
653
|
+
// branch(): isolated temporal world (mutations don't affect live state)
|
|
654
|
+
const branch = memorio.temporal.branch()
|
|
655
|
+
branch.state.counter = 99
|
|
656
|
+
memorio.temporal.at('counter', Date.now()) // still 3 in main state
|
|
657
|
+
branch.discard() // or branch.commit()
|
|
658
|
+
|
|
659
|
+
// Configuration
|
|
660
|
+
memorio.temporal.enable(false) // stop recording
|
|
661
|
+
memorio.temporal.isEnabled() // false
|
|
662
|
+
memorio.temporal.configure({ maxEntries: 50000 })
|
|
663
|
+
memorio.temporal.stats() // { total, byPath }
|
|
664
|
+
memorio.temporal.size() // mutation count
|
|
665
|
+
memorio.temporal.clear() // wipe log (does not affect live state)
|
|
666
|
+
```
|
|
667
|
+
|
|
537
668
|
### `memorio.trace()` - Mutation Log
|
|
538
669
|
|
|
539
670
|
```javascript
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# Memorio VSCode Extension - Technical Design
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
**Proposed** - Phase 1 (IntelliSense + Hover) ready for implementation.
|
|
5
|
+
|
|
6
|
+
## 1. Architecture overview
|
|
7
|
+
|
|
8
|
+
### 1.1 Recommended tooling
|
|
9
|
+
|
|
10
|
+
| Concern | Tool | Rationale |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Language/Runtime | TypeScript + Node.js (VSCode extension host) | VS Code extension API is Node-based; no Bun dependency in extension host |
|
|
13
|
+
| Static analysis | **ts-morph** (not raw TS Compiler API) | 80% less boilerplate than raw TS API for AST traversal + pattern matching. `ts-morph` wraps the TS Compiler API with a friendlier query DSL (`getDescendantsOfKind`, `forEachDescendant`). Performance on large workspaces is acceptable with file-level caching (see 3.3). |
|
|
14
|
+
| Webview UI | React + `reactflow` | Industry standard for interactive graph editors; tree-shakable; well-maintained |
|
|
15
|
+
| Webview bundling | `esbuild` (via `vsce`) | Fast bundling for webview assets; `vsce` handles packaging |
|
|
16
|
+
| State sharing | `vscode.Memento` (ExtensionContext.globalState) | Persists layout preferences per-session, not per-workspace |
|
|
17
|
+
| Communication | `postMessage` + `onDidReceiveMessage` | Standard VS Code webview↔extension host pattern |
|
|
18
|
+
|
|
19
|
+
### 1.2 Architecture decision: single analysis pipeline
|
|
20
|
+
|
|
21
|
+
**Decision**: One AST analysis pass feeds both diagnostics and the graph view. Not two separate pipelines.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Workspace files
|
|
25
|
+
→ ts-morph Project (file-level cache)
|
|
26
|
+
→ MemorioCallScanner (extracts all memorio.* calls)
|
|
27
|
+
→ DiagnosticProvider (writes to vscode.DiagnosticCollection)
|
|
28
|
+
→ GraphBuilder (produces GraphSnapshot)
|
|
29
|
+
→ StoreGraphPanel (webview)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Rationale**: Both features need the same raw data - which memorio APIs are called, from which files, with which arguments. Running two traversals doubles startup cost on large workspaces. The scanner produces an intermediate `MemorioCall[]` structure; both consumers read from it.
|
|
33
|
+
|
|
34
|
+
### 1.3 Performance strategy for large workspaces
|
|
35
|
+
|
|
36
|
+
| Problem | Solution |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Full AST scan on every keystroke | Debounce + file-level cache; only re-scan the changed file |
|
|
39
|
+
| Workspace-wide scan blocks extension host | `vscode.workspace.createFileSystemWatcher` + incremental `TextDocument` change events; scan only open files unless `memorio: Analyze Workspace` is invoked manually |
|
|
40
|
+
| Large files (>5000 lines) | Skip or use syntax-only scan (no type resolution) |
|
|
41
|
+
| ts-morph Project memory | Dispose unused `SourceFile` objects; reuse single `Project` instance with `skipAddingFilesFromTsConfig` and explicit `addSourceFileAtOffset` |
|
|
42
|
+
|
|
43
|
+
## 2. Project structure
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
extension/vscode/
|
|
47
|
+
├── package.json # Extension manifest (contributes, commands, views)
|
|
48
|
+
├── tsconfig.json
|
|
49
|
+
├── src/
|
|
50
|
+
│ ├── extension.ts # Entry point: register commands, providers, webview
|
|
51
|
+
│ ├── analyzer/
|
|
52
|
+
│ │ ├── MemorioCallScanner.ts # AST traversal via ts-morph
|
|
53
|
+
│ │ ├── callTypes.ts # MemorioCall, CallKind, LocationInfo types
|
|
54
|
+
│ │ ├── cache.ts # File-level result cache + debouncer
|
|
55
|
+
│ │ └── patterns.ts # Known memorio API patterns (import paths, call signatures)
|
|
56
|
+
│ ├── graph/
|
|
57
|
+
│ │ ├── GraphBuilder.ts # Converts MemorioCall[] → GraphSnapshot
|
|
58
|
+
│ │ ├── schema.ts # GraphNode, GraphEdge, GraphSnapshot types
|
|
59
|
+
│ │ └── nodeKinds.ts # NodeKind enum + metadata defaults
|
|
60
|
+
│ ├── diagnostics/
|
|
61
|
+
│ │ ├── DiagnosticGenerator.ts # Maps MemorioCall[] → vscode.Diagnostic[]
|
|
62
|
+
│ │ ├── rules.ts # Heuristic rule definitions
|
|
63
|
+
│ │ └── RuleSeverity.ts
|
|
64
|
+
│ ├── webview/
|
|
65
|
+
│ │ ├── StoreGraphPanel.ts # WebviewPanel lifecycle, postMessage routing
|
|
66
|
+
│ │ ├── assets/
|
|
67
|
+
│ │ │ ├── index.html
|
|
68
|
+
│ │ │ └── graph-view.tsx # React + ReactFlow component (bundled)
|
|
69
|
+
│ │ └── messageProtocol.ts # Typed protocol: GraphSnapshot ↔ webview messages
|
|
70
|
+
│ ├── lang/
|
|
71
|
+
│ │ ├── memorioCompletion.ts # TypeScript completions (monkeypatch vscode.languages)
|
|
72
|
+
│ │ └── memorioHover.ts # Hover documentation provider
|
|
73
|
+
│ ├── snippets.ts # Snippet definitions
|
|
74
|
+
│ └── utils/
|
|
75
|
+
│ ├── assets.ts # Icon/path helpers
|
|
76
|
+
│ └── logger.ts # Thin wrapper around vscode.OutputChannel
|
|
77
|
+
└── media/
|
|
78
|
+
├── memorio-icon-dark.svg # Generated (no pre-existing assets in repo)
|
|
79
|
+
└── memorio-icon-light.svg
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**No runtime injection**: All files under `analyzer/`, `graph/`, `diagnostics/` are pure read-only static analysis. They do not import from `memoro` - they pattern-match AST nodes against known API names. This ensures zero impact on user code.
|
|
83
|
+
|
|
84
|
+
## 3. Schema dati del grafo
|
|
85
|
+
|
|
86
|
+
### 3.1 Types
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// --- Node kinds (each maps to a memorio concept) ---
|
|
90
|
+
enum NodeKind {
|
|
91
|
+
State = 'state', // import { state } from 'memorio'
|
|
92
|
+
Store = 'store', // import { store } from 'memorio'
|
|
93
|
+
Session = 'session',
|
|
94
|
+
Cache = 'cache',
|
|
95
|
+
IDB = 'idb',
|
|
96
|
+
SQLite = 'sqlite',
|
|
97
|
+
Memory = 'memory',
|
|
98
|
+
Journal = 'journal',
|
|
99
|
+
Context = 'context', // memorio.createContext(...)
|
|
100
|
+
ReduxBridge = 'redux-bridge', // createMemorioReduxBridge(...)
|
|
101
|
+
Observer = 'observer', // observer('path', cb)
|
|
102
|
+
Schema = 'schema', // registerSchema('name', schema)
|
|
103
|
+
SyncProvider = 'sync-provider', // memory.configure({ provider: {...} })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// --- Core node type ---
|
|
107
|
+
interface GraphNode {
|
|
108
|
+
id: string // Deterministic: `${filePath}:${callId}`
|
|
109
|
+
kind: NodeKind
|
|
110
|
+
label: string // Derived: context name, store name, or kind
|
|
111
|
+
filePath: string // Absolute path to source file
|
|
112
|
+
range: Range // VS Code range for go-to-definition
|
|
113
|
+
meta: NodeMeta
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface NodeMeta {
|
|
117
|
+
persisted: boolean
|
|
118
|
+
encrypted: boolean // true if encryptionKey on context/config
|
|
119
|
+
readonly: boolean // true if only observed, never mutated
|
|
120
|
+
beta: boolean // true for sqlite (alpha), journal (beta)
|
|
121
|
+
schemaRegistered?: string // schema name if registerSchema used
|
|
122
|
+
sizeEstimate?: number // byte estimate if computable
|
|
123
|
+
version?: string // engine version (sqlite: bun:sqlite vs sql.js)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --- Edge types (relationships) ---
|
|
127
|
+
type EdgeKind =
|
|
128
|
+
| 'observes' // observer → store/state
|
|
129
|
+
| 'syncs-to' // memory → sync-provider (push)
|
|
130
|
+
| 'syncs-from' // sync-provider → memory (pull)
|
|
131
|
+
| 'bridges-to' // redux-bridge → redux store
|
|
132
|
+
| 'isolated-from' // context → parent context
|
|
133
|
+
| 'uses-schema' // state/store → schema
|
|
134
|
+
| 'persists-to' // store/session → idb/sqlite
|
|
135
|
+
|
|
136
|
+
interface GraphEdge {
|
|
137
|
+
id: string // `${sourceId}->${targetId}:${kind}`
|
|
138
|
+
source: string // source node id
|
|
139
|
+
target: string // target node id
|
|
140
|
+
kind: EdgeKind
|
|
141
|
+
label?: string // optional: conflict resolution strategy, sync direction
|
|
142
|
+
meta?: EdgeMeta
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface EdgeMeta {
|
|
146
|
+
resolution?: 'hlc' | 'custom' | 'remote-wins' // sync edge only
|
|
147
|
+
direction?: 'push' | 'pull' // sync edge only
|
|
148
|
+
path?: string // observes edge: observed path
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Complete graph snapshot ---
|
|
152
|
+
interface GraphSnapshot {
|
|
153
|
+
version: string // semver: "1.0"
|
|
154
|
+
nodes: GraphNode[]
|
|
155
|
+
edges: GraphEdge[]
|
|
156
|
+
workspaceRoot: string
|
|
157
|
+
generatedAt: number // epoch ms
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 3.2 Edge examples (from real memorio patterns)
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// Source file:
|
|
165
|
+
const ctx = memorio.createContext('tenant:acme')
|
|
166
|
+
const encryptedCtx = memorio.createContext('tenant:acme', { encryptionKey: key })
|
|
167
|
+
ctx.store.set('user', { id: 1 })
|
|
168
|
+
encryptedCtx.store.set('secret', { token: 'abc' })
|
|
169
|
+
|
|
170
|
+
memorio.memory.configure({
|
|
171
|
+
provider: {
|
|
172
|
+
push: ops => fetch('/sync', { method: 'POST', body: JSON.stringify(ops) }),
|
|
173
|
+
pull: since => fetch(`/sync?since=${since}`).then(r => r.json())
|
|
174
|
+
},
|
|
175
|
+
resolveConflict: (local, remote) => local.confidence >= remote.confence ? local : remote
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Produces:
|
|
180
|
+
```
|
|
181
|
+
Nodes:
|
|
182
|
+
context[tenant:acme] kind=Context meta.persisted=false
|
|
183
|
+
context[tenant:acme#enc] kind=Context meta.encrypted=true
|
|
184
|
+
store[tenant:acme] kind=Store meta.persisted=false
|
|
185
|
+
store[tenant:acme#enc] kind=Store meta.encrypted=true
|
|
186
|
+
memory[default] kind=Memory meta.persisted=true
|
|
187
|
+
|
|
188
|
+
Edges:
|
|
189
|
+
store[tenant:acme] → observes state kind=observes
|
|
190
|
+
store[tenant:acme#enc] → isolated-from → context[tenant:acme] kind=isolated-from
|
|
191
|
+
memory[default] → syncs-to → sync-provider kind=syncs-to label="custom"
|
|
192
|
+
memory[default] → syncs-from → sync-provider kind=syncs-from label="custom"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 3.3 Node metadata inference
|
|
196
|
+
|
|
197
|
+
| Metadata | How inferred |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `persisted` | File imports `store` or `session` or `idb` (not `state`/`cache`) |
|
|
200
|
+
| `encrypted` | Presence of `encryptionKey` arg on `createContext`, or `store.config({ encryptionKey })` |
|
|
201
|
+
| `beta` | `NodeKind.SQLite` → true; `NodeKind.Journal` → true; all others false |
|
|
202
|
+
| `schemaRegistered` | `registerSchema(name, schema)` call detected in same file |
|
|
203
|
+
| `sizeEstimate` | Computed from `store.db.size()` / `idb.db.size()` at runtime (if available); null otherwise |
|
|
204
|
+
|
|
205
|
+
## 4. Webview component sketch
|
|
206
|
+
|
|
207
|
+
### 4.1 React Flow node types
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
// Custom node for store/context (badge shows persistence/encryption)
|
|
211
|
+
function StoreNode({ data }: NodeProps<GraphNode>): JSX.Element {
|
|
212
|
+
const badge = (
|
|
213
|
+
<div className="badges">
|
|
214
|
+
{data.meta.encrypted && <span className="badge badge--encrypted">🔒</span>}
|
|
215
|
+
{data.meta.persisted && <span className="badge badge--persisted">💾</span>}
|
|
216
|
+
{data.meta.beta && <span className="badge badge--beta">β</span>}
|
|
217
|
+
</div>
|
|
218
|
+
)
|
|
219
|
+
return (
|
|
220
|
+
<div className="node store-node">
|
|
221
|
+
<div className="node__header">
|
|
222
|
+
<span className="node__icon">🗄️</span>
|
|
223
|
+
<span className="node__label">{data.label}</span>
|
|
224
|
+
</div>
|
|
225
|
+
{badge}
|
|
226
|
+
<div className="node__meta">
|
|
227
|
+
{data.meta.schemaRegistered && <SchemaTag schema={data.meta.schemaRegistered} />}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Timeline node (undo/redo snapshot)
|
|
234
|
+
function TimelineNode({ data }: NodeProps<TimelineNodeData>): JSX.Element {
|
|
235
|
+
return (
|
|
236
|
+
<div className="node timeline-node">
|
|
237
|
+
<div className="node__header">
|
|
238
|
+
<span className="node__icon">📸</span>
|
|
239
|
+
<span className="node__label">Snapshot #{data.snapshotId}</span>
|
|
240
|
+
</div>
|
|
241
|
+
<div className="node__detail">{data.mutationCount} mutations</div>
|
|
242
|
+
</div>
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 4.2 Sidebar detail panel (click a node)
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
function NodeDetailsPanel({ node }: { node: GraphNode | undefined }) {
|
|
251
|
+
if (!node) return <div className="details--empty">Select a node</div>
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div className="details-panel">
|
|
255
|
+
<h3>{node.label}</h3>
|
|
256
|
+
<dl>
|
|
257
|
+
<dt>Layer</dt><dd>{node.kind}</dd>
|
|
258
|
+
<dt>File</dt><dd><a href={`vscode://file/${node.filePath}`}>{node.filePath}</a></dd>
|
|
259
|
+
<dt>Persisted</dt><dd>{node.meta.persisted ? 'Yes' : 'No'}</dd>
|
|
260
|
+
<dt>Encrypted</dt><dd>{node.meta.encrypted ? 'Yes' : 'No'}</dd>
|
|
261
|
+
{node.meta.schemaRegistered && <><dt>Schema</dt><dd>{node.meta.schemaRegistered}</dd></>}
|
|
262
|
+
{node.meta.beta && <><dt>Beta</dt><dd>Experimental API - expect changes</dd></>}
|
|
263
|
+
</dl>
|
|
264
|
+
<button onClick={() => openDefinition(node.filePath, node.range)}>
|
|
265
|
+
Go to definition
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 4.3 Timeline toggle
|
|
273
|
+
|
|
274
|
+
A `Toggle` button in the webview toolbar switches the graph from **Layer view** (default graph) to **Timeline view** (linear undo/redo history for the selected store). Timeline nodes are populated from `trace()` output if the user's code calls `memorio.trace()`.
|
|
275
|
+
|
|
276
|
+
## 5. Commands and settings
|
|
277
|
+
|
|
278
|
+
### 5.1 Commands (`package.json` `contributes.commands`)
|
|
279
|
+
|
|
280
|
+
| Command ID | Title | When |
|
|
281
|
+
|---|---|---|
|
|
282
|
+
| `memorio.analyzeWorkspace` | Memorio: Analyze Workspace | editorTextFocus |
|
|
283
|
+
| `memorio.toggleGraphView` | Memorio: Show Store Graph | view:memorioGraphView |
|
|
284
|
+
| `memorio.refreshGraph` | Memorio: Refresh Graph | webviewFocused |
|
|
285
|
+
| `memorio.toggleTimeline` | Memorio: Toggle Timeline | webviewFocused |
|
|
286
|
+
| `memorio.openLogs` | Memorio: Open Logs | view:memorioGraphView |
|
|
287
|
+
| `memorio.openExtensionSettings` | Memorio: Settings | view:memorioGraphView |
|
|
288
|
+
|
|
289
|
+
### 5.2 View containers
|
|
290
|
+
|
|
291
|
+
| View | Location | Type |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| `memorioGraphView` | `explorer` sidebar | WebviewView (graph panel in sidebar) |
|
|
294
|
+
| `memorioTimelineView` | `memorioGraphView` tab | WebviewPanel (tabbed view in graph view) |
|
|
295
|
+
|
|
296
|
+
### 5.3 Settings (`package.json` `contributes.configuration`)
|
|
297
|
+
|
|
298
|
+
| Setting | Default | Description |
|
|
299
|
+
|---|---|---|
|
|
300
|
+
| `memorio.analysis.includePatterns` | `["**/*.{ts,tsx,js,jsx}"]` | Glob patterns for files to analyze |
|
|
301
|
+
| `memorio.analysis.excludePatterns` | `["**/node_modules/**", "**/dist/**"]` | Glob patterns to skip |
|
|
302
|
+
| `memorio.graph.autoRefresh` | `true` | Auto-refresh graph on file save |
|
|
303
|
+
| `memorio.diagnostics.enable` | `true` | Enable real-time diagnostic warnings |
|
|
304
|
+
| `memorio.graph.saveLayout` | `"session"` | `session` (reset on reload) / `workspace` (persist to .vscode) |
|
|
305
|
+
|
|
306
|
+
### 5.4 Comparison with competitor
|
|
307
|
+
|
|
308
|
+
| Feature | Competitor (Memorio) | This extension |
|
|
309
|
+
|---|---|---|
|
|
310
|
+
| IntelliSense | ✅ autocomplete on memorio imports | ✅ + hover docs with link to README sections |
|
|
311
|
+
| Hover | Basic type info | ✅ Type + options + doc link + example |
|
|
312
|
+
| Snippets | Template snippets only | ✅ + dynamic snippets based on context/schema |
|
|
313
|
+
| Diagnostics | Warning for missing namespace | ✅ + risk pattern detection (__proto__, constructor) + hint on `includeObsolete` |
|
|
314
|
+
| State Explorer | Static text tree | ✅ Interactive React Flow graph with node metadata |
|
|
315
|
+
| - | - | ✅ Timeline view (undo/redo history) |
|
|
316
|
+
| - | - | ✅ Conflict resolution strategy labels on sync edges |
|
|
317
|
+
| - | - | ✅ Incremental workspace analysis (debounced, cached) |
|
|
318
|
+
|
|
319
|
+
## 6. Diagnostic rules (heuristic, AST-based)
|
|
320
|
+
|
|
321
|
+
**⚠️ Declaration**: These diagnostics are heuristic - based on AST pattern matching, not a TypeScript type-checker. They reduce false negatives but may produce false positives. They never block compilation or execution.
|
|
322
|
+
|
|
323
|
+
| Rule ID | Severity | Trigger pattern | Message |
|
|
324
|
+
|---|---|---|---|
|
|
325
|
+
| `memorio-missing-store` | Hint | `observer('state.x', cb)` without prior `store`/`session` import in same file | "State observed but no persistence layer imported - changes won't survive reload" |
|
|
326
|
+
| `memorio-include-obsolete-hint` | Information | `memory.recall(key)` without `{ includeObsolete: true }` | "Consider `includeObsolete: true` to see superseded values for this key" |
|
|
327
|
+
| `memorio-prototype-pollution-risk` | Warning | `state[key] = value` where `key` is a variable (dynamic key on state proxy) | "Dynamic key assignment to state may cause prototype pollution - use literal keys or `typed<T>()`" |
|
|
328
|
+
| `memorio-__proto-assignment` | Error | `state.__proto__` or `state.constructor` or `state.prototype` anywhere | "Assignment to `__proto__`/`constructor`/`prototype` can cause prototype pollution - use `typed<T>()` for schema-safe state" |
|
|
329
|
+
| `memorio-unencrypted-store` | Hint | `store.set('token', value)` without `store.config({ encryptionKey })` | "Token stored without encryption - consider `store.config({ encryptionKey })`" |
|
|
330
|
+
| `memorio-sqlite-beta` | Information | `sqlite` import in production file | "sqlite is beta - API may change in minor releases" |
|
|
331
|
+
|
|
332
|
+
## 7. React Flow component architecture
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
StoreGraphView (React Flow Canvas)
|
|
336
|
+
├── NodeTypes: StoreNode, ContextNode, MemoryNode, SchemaNode, ReduxBridgeNode, TimelineNode
|
|
337
|
+
├── EdgeTypes: default (arrow), SyncEdge (dashed, labeled)
|
|
338
|
+
├── Controls: MiniMap, Controls (zoom), Background (grid)
|
|
339
|
+
├── Toolbar: FilterToggle, TimelineToggle, RefreshButton
|
|
340
|
+
└── Sidebar: NodeDetailsPanel (conditional)
|
|
341
|
+
|
|
342
|
+
FilterToggle → filters by NodeKind and meta flags (beta, encrypted)
|
|
343
|
+
TimelineToggle → swaps edges/nodes to timeline view for selected store
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## 8. Message protocol (extension host ↔ webview)
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
type WebviewMessage =
|
|
350
|
+
| { type: 'graphSnapshot'; data: GraphSnapshot }
|
|
351
|
+
| { type: 'nodeSelected'; nodeId: string }
|
|
352
|
+
| { type: 'goToDefinition'; filePath: string; range: Range }
|
|
353
|
+
| { type: 'refreshRequested' }
|
|
354
|
+
| { type: 'filterChanged'; nodeKinds: NodeKind[] }
|
|
355
|
+
|
|
356
|
+
type HostMessage =
|
|
357
|
+
| { type: 'webviewReady' }
|
|
358
|
+
| { type: 'definitionRequested'; filePath: string; range: Range }
|
|
359
|
+
| { type: 'filterChanged'; nodeKinds: NodeKind[] }
|
|
360
|
+
| { type: 'timelineToggle' }
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## 9. Roadmap a fasi
|
|
364
|
+
|
|
365
|
+
### Fase 1 - IntelliSense + Hover (2-3 developer-days)
|
|
366
|
+
- `memorioCompletion.ts`: autocomplete for `state.*`, `store.*`, `memorio.memory.*`, etc.
|
|
367
|
+
- `memorioHover.ts`: hover shows type + options + doc link
|
|
368
|
+
- Snippets: `ctx-enc` (createContext with encryption), `schema-array` (registerSchema), `memory-sync` (configure with provider)
|
|
369
|
+
- **No graph view yet** - can ship independently, provides immediate value
|
|
370
|
+
|
|
371
|
+
### Fase 2 - Diagnostica (1-2 developer-days)
|
|
372
|
+
- `DiagnosticGenerator.ts` + `rules.ts`
|
|
373
|
+
- `memorio-__proto-assignment`, `memorio-prototype-pollution-risk`, `memorio-missing-store`, `memorio-include-obsolete-hint`
|
|
374
|
+
- Integrates into the MemorioCallScanner from Fase 1
|
|
375
|
+
- **No webview needed**
|
|
376
|
+
|
|
377
|
+
### Fase 3 - Graph view statica (5-7 developer-days)
|
|
378
|
+
- `GraphBuilder.ts` + `StoreGraphPanel.ts`
|
|
379
|
+
- React Flow webview with store/context/memory/sqlite nodes, edge rendering (observes, isolate, bridges)
|
|
380
|
+
- Basic sidebar details (layer, file, persisted, encrypted, beta)
|
|
381
|
+
- Go-to-definition integration
|
|
382
|
+
- **No timeline yet**
|
|
383
|
+
|
|
384
|
+
### Fase 4 - Timeline undo/redo (3-4 developer-days)
|
|
385
|
+
- Toggle button in webview toolbar
|
|
386
|
+
- Timeline nodes from `trace()` output
|
|
387
|
+
- Linear temporal view of snapshots/operations per selected store
|
|
388
|
+
- **Requires timeline node type + TimelineNode component**
|
|
389
|
+
|
|
390
|
+
### Fase 5 - Aggiornamento incrementale + performance (2-3 developer-days)
|
|
391
|
+
- File-level cache (`cache.ts`)
|
|
392
|
+
- Debounced re-analysis on save
|
|
393
|
+
- Large file skipping (syntax-only mode for files >5000 lines)
|
|
394
|
+
- `globalState` (VSCode Memento) layout persistence with `session` mode (no disk writes per design constraint)
|
|
395
|
+
|
|
396
|
+
### Out of scope (future)
|
|
397
|
+
- Remote sync provider connection (would require user's backend integration)
|
|
398
|
+
- Runtime state inspection (would require injecting code into user's process - violates the "no runtime injection" constraint)
|
|
399
|
+
- Export graph to image/PNG (nice-to-have, no core value)
|
|
400
|
+
|
|
401
|
+
## 10. Constraints honored
|
|
402
|
+
|
|
403
|
+
| Constraint | How met |
|
|
404
|
+
|---|---|
|
|
405
|
+
| No security/compliance claims in marketing | Diagnostics use technical descriptions ("may cause prototype pollution"), not "security layer" language |
|
|
406
|
+
| Diagnostics declared as heuristic | Explicit ⚠️ at top of §6, rule docs say "AST pattern matching, not type-checker" |
|
|
407
|
+
| No changes to memorio public API | All analyzer modules are read-only AST pattern matching; no imports from `memorio` |
|
|
408
|
+
| Honest naming | "Store Graph View" (not "State Explorer"), "Dependency graph" only if the graph shows dependencies (which it does: observes → syncs → bridges) |
|
|
409
|
+
| Cross-runtime support | Extension is Node.js-only (VSCode extension host), but documents the cross-platform support for the underlying library |
|
|
410
|
+
| No runtime injection | Static analysis only - zero code executed from analyzed files |
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# `memorio.logic`
|
|
2
|
+
|
|
3
|
+
> **Status:** IMPLEMENTED and VERIFIED — PHASE 0 PASS
|
|
4
|
+
> **Scope:** Current public runtime contract; not a PHASE 1 roadmap
|
|
5
|
+
|
|
6
|
+
`memorio.logic` records structured situation/action/outcome experiences, selects applicable
|
|
7
|
+
precedents, derives simple recommendations, and reports measurements. It reuses acquired-knowledge
|
|
8
|
+
primitives; it is not an autonomous reasoning engine, truth engine, causal graph, or replacement for
|
|
9
|
+
AI reasoning.
|
|
10
|
+
|
|
11
|
+
## Public API
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { logic, LogicEngine } from 'memorio'
|
|
15
|
+
|
|
16
|
+
await logic.experience({
|
|
17
|
+
id: 'exp.locking-1',
|
|
18
|
+
situation: { symptom: 'a nested write bypassed the intended guard' },
|
|
19
|
+
action: { correction: 'apply the node-level guard' },
|
|
20
|
+
outcome: { result: 'the contract test passes' },
|
|
21
|
+
evidence: [{ id: 'locking-contract', source: 'test-suite' }],
|
|
22
|
+
applicability: { layer: 'logic', topic: 'locking' },
|
|
23
|
+
epistemicType: 'verified'
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const precedents = await logic.precedents({
|
|
27
|
+
context: { layer: 'logic', topic: 'locking' }
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The PHASE 0 surface is:
|
|
32
|
+
|
|
33
|
+
- `experience(opts): Promise<void>` — append an immutable experience claim.
|
|
34
|
+
- `precedents(query): Promise<AcquiredClaim[]>` — return selected active claims.
|
|
35
|
+
- `context(query): Promise<AcquiredContext>` — return selected claims plus repository version.
|
|
36
|
+
- `measure(query): Promise<LogicMeasurement>` — report all active Logic claims, selected claims,
|
|
37
|
+
selected IDs, repository version, and the current process-wide temporal mutation count.
|
|
38
|
+
- `learn(query): Promise<LogicRule[]>` — project selected experiences into rules. The recommendation
|
|
39
|
+
is the stored `outcome`; confidence is a fixed mapping from epistemic type, not learned statistics.
|
|
40
|
+
- `stats(): Promise<{ experiences: number; version: number }>` — count repository-level active claims.
|
|
41
|
+
- `clear(): Promise<void>` — delete only the writable `logic.mem` source.
|
|
42
|
+
- `configure({ disabled })` — make the singleton a no-op while disabled.
|
|
43
|
+
|
|
44
|
+
`logic` is a singleton instance of `LogicEngine`. It is available as a named module export and as
|
|
45
|
+
`globalThis.memorio.logic`; explicit global installation also includes `globalThis.logic`.
|
|
46
|
+
|
|
47
|
+
## Persistence and isolation
|
|
48
|
+
|
|
49
|
+
In Node.js/Bun, Logic uses `.memorio/logic/logic.mem`. Acquired project experience uses
|
|
50
|
+
`.memorio/project.mem`.
|
|
51
|
+
|
|
52
|
+
These are intentionally separate `MemDirectoryKnowledgeRepository` instances rooted in different
|
|
53
|
+
directories. Each discovers, combines, locks, versions, clears, validates IDs, and validates revision
|
|
54
|
+
references only within its own repository. Consequently:
|
|
55
|
+
|
|
56
|
+
- Logic does not discover or clear `project.mem`.
|
|
57
|
+
- Project memory does not discover or clear `logic.mem`.
|
|
58
|
+
- Claim-ID uniqueness and `refines`/`supersedes`/`retracts` references are repository-local.
|
|
59
|
+
- Cross-repository revision chains and unified provenance are not implemented.
|
|
60
|
+
|
|
61
|
+
This isolation is a current architectural boundary, not evidence that cross-repository composition
|
|
62
|
+
exists. When no working-directory runtime is available, Logic uses an in-memory repository. Unlike
|
|
63
|
+
acquired project memory, PHASE 0 Logic does not use the browser `store` fallback.
|
|
64
|
+
|
|
65
|
+
## Selection and the `_all` correction
|
|
66
|
+
|
|
67
|
+
Logic context selection uses the same exact structured applicability and most-specific-wins semantics
|
|
68
|
+
as acquired memory. PHASE 0 initially added an `_all` selector sentinel so `stats()` and `measure()`
|
|
69
|
+
could count all claims. Focused design verification found that it violated the selector invariant,
|
|
70
|
+
could collide with a real applicability key, and still selected only maximum-specificity candidates.
|
|
71
|
+
|
|
72
|
+
The sentinel was removed. `stats()` and the `total` field of `measure()` now load the Logic repository
|
|
73
|
+
and count `activeClaims(state)` directly. A regression test covers mixed applicability specificity.
|
|
74
|
+
The chronology is retained in project history; this document describes the corrected current state.
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
initial Logic implementation
|
|
78
|
+
-> contract tests pass
|
|
79
|
+
-> focused design verification
|
|
80
|
+
-> `_all` selector problem discovered
|
|
81
|
+
-> HI + AI decision
|
|
82
|
+
-> `_all` removed
|
|
83
|
+
-> stats()/measure() use repository.load() + activeClaims()
|
|
84
|
+
-> mixed-specificity regression verification
|
|
85
|
+
-> PHASE 0 PASS
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The regression uses three active claims with three, two, and one applicability keys. The corrected
|
|
89
|
+
result is `stats().experiences === 3`; the rejected wildcard approach could incorrectly return `1`.
|
|
90
|
+
|
|
91
|
+
## Verification boundary
|
|
92
|
+
|
|
93
|
+
The PHASE 0 contract test verifies the singleton, API surface, global exposure, experience selection,
|
|
94
|
+
revision, measurement, rule projection, stats, disabled behavior, clearing, repository isolation, and
|
|
95
|
+
the mixed-specificity regression. This verifies mechanics, not the truth of stored experience.
|
|
96
|
+
|
|
97
|
+
## Not implemented
|
|
98
|
+
|
|
99
|
+
PHASE 0 does not implement semantic similarity, autonomous acquisition, autonomous truth evaluation,
|
|
100
|
+
causal inference, cross-repository composition, identity/authority policy, trust scoring, or PHASE 1.
|