omegon 0.6.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 (160) hide show
  1. package/.gitattributes +3 -0
  2. package/AGENTS.md +16 -0
  3. package/LICENSE +15 -0
  4. package/README.md +289 -0
  5. package/bin/pi.mjs +30 -0
  6. package/extensions/00-secrets/index.ts +1126 -0
  7. package/extensions/01-auth/auth.ts +401 -0
  8. package/extensions/01-auth/index.ts +289 -0
  9. package/extensions/auto-compact.ts +42 -0
  10. package/extensions/bootstrap/deps.ts +291 -0
  11. package/extensions/bootstrap/index.ts +811 -0
  12. package/extensions/chronos/chronos.sh +487 -0
  13. package/extensions/chronos/index.ts +148 -0
  14. package/extensions/cleave/assessment.ts +754 -0
  15. package/extensions/cleave/bridge.ts +31 -0
  16. package/extensions/cleave/conflicts.ts +250 -0
  17. package/extensions/cleave/dispatcher.ts +808 -0
  18. package/extensions/cleave/guardrails.ts +426 -0
  19. package/extensions/cleave/index.ts +3121 -0
  20. package/extensions/cleave/lifecycle-emitter.ts +20 -0
  21. package/extensions/cleave/openspec.ts +811 -0
  22. package/extensions/cleave/planner.ts +260 -0
  23. package/extensions/cleave/review.ts +579 -0
  24. package/extensions/cleave/skills.ts +355 -0
  25. package/extensions/cleave/types.ts +261 -0
  26. package/extensions/cleave/workspace.ts +861 -0
  27. package/extensions/cleave/worktree.ts +243 -0
  28. package/extensions/core-renderers.ts +253 -0
  29. package/extensions/dashboard/context-gauge.ts +58 -0
  30. package/extensions/dashboard/file-watch.ts +14 -0
  31. package/extensions/dashboard/footer.ts +1145 -0
  32. package/extensions/dashboard/git.ts +185 -0
  33. package/extensions/dashboard/index.ts +478 -0
  34. package/extensions/dashboard/memory-audit.ts +34 -0
  35. package/extensions/dashboard/overlay-data.ts +705 -0
  36. package/extensions/dashboard/overlay.ts +365 -0
  37. package/extensions/dashboard/render-utils.ts +54 -0
  38. package/extensions/dashboard/types.ts +191 -0
  39. package/extensions/dashboard/uri-helper.ts +45 -0
  40. package/extensions/debug.ts +69 -0
  41. package/extensions/defaults.ts +282 -0
  42. package/extensions/design-tree/dashboard-state.ts +161 -0
  43. package/extensions/design-tree/design-card.ts +362 -0
  44. package/extensions/design-tree/index.ts +2130 -0
  45. package/extensions/design-tree/lifecycle-emitter.ts +41 -0
  46. package/extensions/design-tree/tree.ts +1607 -0
  47. package/extensions/design-tree/types.ts +163 -0
  48. package/extensions/distill.ts +127 -0
  49. package/extensions/effort/index.ts +395 -0
  50. package/extensions/effort/tiers.ts +146 -0
  51. package/extensions/effort/types.ts +105 -0
  52. package/extensions/lib/git-state.ts +227 -0
  53. package/extensions/lib/local-models.ts +157 -0
  54. package/extensions/lib/model-preferences.ts +51 -0
  55. package/extensions/lib/model-routing.ts +720 -0
  56. package/extensions/lib/operator-fallback.ts +205 -0
  57. package/extensions/lib/operator-profile.ts +360 -0
  58. package/extensions/lib/slash-command-bridge.ts +253 -0
  59. package/extensions/lib/typebox-helpers.ts +16 -0
  60. package/extensions/local-inference/index.ts +727 -0
  61. package/extensions/mcp-bridge/README.md +220 -0
  62. package/extensions/mcp-bridge/index.ts +951 -0
  63. package/extensions/mcp-bridge/lib.ts +365 -0
  64. package/extensions/mcp-bridge/mcp.json +3 -0
  65. package/extensions/mcp-bridge/package.json +11 -0
  66. package/extensions/model-budget.ts +752 -0
  67. package/extensions/offline-driver.ts +403 -0
  68. package/extensions/openspec/archive-gate.ts +164 -0
  69. package/extensions/openspec/branch-cleanup.ts +64 -0
  70. package/extensions/openspec/dashboard-state.ts +50 -0
  71. package/extensions/openspec/index.ts +1917 -0
  72. package/extensions/openspec/lifecycle-emitter.ts +65 -0
  73. package/extensions/openspec/lifecycle-files.ts +70 -0
  74. package/extensions/openspec/lifecycle.ts +50 -0
  75. package/extensions/openspec/reconcile.ts +187 -0
  76. package/extensions/openspec/spec.ts +1385 -0
  77. package/extensions/openspec/types.ts +98 -0
  78. package/extensions/project-memory/DESIGN-global-mind.md +198 -0
  79. package/extensions/project-memory/README.md +202 -0
  80. package/extensions/project-memory/api-types.ts +382 -0
  81. package/extensions/project-memory/compaction-policy.ts +29 -0
  82. package/extensions/project-memory/core.ts +164 -0
  83. package/extensions/project-memory/embeddings.ts +230 -0
  84. package/extensions/project-memory/extraction-v2.ts +861 -0
  85. package/extensions/project-memory/factstore.ts +2177 -0
  86. package/extensions/project-memory/index.ts +3459 -0
  87. package/extensions/project-memory/injection-metrics.ts +91 -0
  88. package/extensions/project-memory/jsonl-io.ts +12 -0
  89. package/extensions/project-memory/lifecycle.ts +331 -0
  90. package/extensions/project-memory/migration.ts +293 -0
  91. package/extensions/project-memory/package.json +9 -0
  92. package/extensions/project-memory/sci-renderers.ts +7 -0
  93. package/extensions/project-memory/template.ts +103 -0
  94. package/extensions/project-memory/triggers.ts +52 -0
  95. package/extensions/project-memory/types.ts +102 -0
  96. package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
  97. package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
  98. package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
  99. package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
  100. package/extensions/render/composition/package-lock.json +534 -0
  101. package/extensions/render/composition/package.json +22 -0
  102. package/extensions/render/composition/render.mjs +246 -0
  103. package/extensions/render/composition/test-comp.tsx +87 -0
  104. package/extensions/render/composition/types.ts +24 -0
  105. package/extensions/render/excalidraw/UPSTREAM.md +81 -0
  106. package/extensions/render/excalidraw/elements.ts +764 -0
  107. package/extensions/render/excalidraw/index.ts +66 -0
  108. package/extensions/render/excalidraw/types.ts +223 -0
  109. package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
  110. package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
  111. package/extensions/render/excalidraw-renderer/render_template.html +59 -0
  112. package/extensions/render/index.ts +830 -0
  113. package/extensions/render/native-diagrams/index.ts +57 -0
  114. package/extensions/render/native-diagrams/motifs.ts +542 -0
  115. package/extensions/render/native-diagrams/raster.ts +8 -0
  116. package/extensions/render/native-diagrams/scene.ts +75 -0
  117. package/extensions/render/native-diagrams/spec.ts +204 -0
  118. package/extensions/render/native-diagrams/svg.ts +116 -0
  119. package/extensions/sci-ui.ts +304 -0
  120. package/extensions/session-log.ts +174 -0
  121. package/extensions/shared-state.ts +146 -0
  122. package/extensions/spinner-verbs.ts +91 -0
  123. package/extensions/style.ts +281 -0
  124. package/extensions/terminal-title.ts +191 -0
  125. package/extensions/tool-profile/index.ts +291 -0
  126. package/extensions/tool-profile/profiles.ts +290 -0
  127. package/extensions/types.d.ts +9 -0
  128. package/extensions/vault/index.ts +185 -0
  129. package/extensions/version-check.ts +90 -0
  130. package/extensions/view/index.ts +859 -0
  131. package/extensions/view/uri-resolver.ts +148 -0
  132. package/extensions/web-search/index.ts +182 -0
  133. package/extensions/web-search/providers.ts +121 -0
  134. package/extensions/web-ui/index.ts +110 -0
  135. package/extensions/web-ui/server.ts +265 -0
  136. package/extensions/web-ui/state.ts +462 -0
  137. package/extensions/web-ui/static/index.html +145 -0
  138. package/extensions/web-ui/types.ts +284 -0
  139. package/package.json +76 -0
  140. package/prompts/init.md +75 -0
  141. package/prompts/new-repo.md +54 -0
  142. package/prompts/oci-login.md +56 -0
  143. package/prompts/status.md +50 -0
  144. package/settings.json +4 -0
  145. package/skills/cleave/SKILL.md +218 -0
  146. package/skills/git/SKILL.md +209 -0
  147. package/skills/git/_reference/ci-validation.md +204 -0
  148. package/skills/oci/SKILL.md +338 -0
  149. package/skills/openspec/SKILL.md +346 -0
  150. package/skills/pi-extensions/SKILL.md +191 -0
  151. package/skills/pi-tui/SKILL.md +517 -0
  152. package/skills/python/SKILL.md +189 -0
  153. package/skills/rust/SKILL.md +268 -0
  154. package/skills/security/SKILL.md +206 -0
  155. package/skills/style/SKILL.md +264 -0
  156. package/skills/typescript/SKILL.md +225 -0
  157. package/skills/vault/SKILL.md +102 -0
  158. package/themes/alpharius-legacy.json +85 -0
  159. package/themes/alpharius.conf +59 -0
  160. package/themes/alpharius.json +88 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * project-memory/api-types — Omega /api/memory/* HTTP contract
3
+ *
4
+ * This file is the source of truth for the wire protocol between:
5
+ * - The TypeScript auspex bridge (TS extension, pi API surface)
6
+ * - The Rust Omega daemon (/api/memory/* Axum routes)
7
+ *
8
+ * Field names are snake_case throughout to match Rust serde conventions
9
+ * (#[serde(rename_all = "snake_case")] or #[serde(deny_unknown_fields)]).
10
+ *
11
+ * When the Rust implementation is built, each interface here becomes a
12
+ * corresponding Rust struct deriving Serialize, Deserialize, with field
13
+ * names identical to these. Any deviation is a bug in the Rust port.
14
+ *
15
+ * Versioning: the HTTP API is versioned via the Accept header or a /v1/ prefix.
16
+ * Breaking changes increment the major version. Additive fields use Option<T>
17
+ * with serde defaults in Rust and optional typing here.
18
+ */
19
+
20
+ import type { SectionName } from "./template.ts";
21
+ import type { DecayProfileName } from "./core.ts";
22
+
23
+ // ─── Core record types ────────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * A memory fact as returned by the API.
27
+ * Mirrors the `facts` SQLite table — all fields except `embedding` (DB-only).
28
+ *
29
+ * Rust: src/memory/types.rs::Fact
30
+ */
31
+ export interface FactRecord {
32
+ id: string;
33
+ mind: string;
34
+ content: string;
35
+ section: SectionName;
36
+ status: FactStatus;
37
+ confidence: number;
38
+ reinforcement_count: number;
39
+ decay_rate: number;
40
+ /** Discriminant for the decay profile used at write time. Persisted in DB.
41
+ * Allows correct confidence computation at read time. Default: "standard". */
42
+ decay_profile: DecayProfileName;
43
+ last_reinforced: string; // ISO 8601
44
+ created_at: string; // ISO 8601
45
+ /** Lamport logical timestamp. Incremented on every mutation.
46
+ * On git-sync conflict: higher version always wins. Default on import: 0. */
47
+ version: number;
48
+ superseded_by?: string; // fact ID; present only when status === "superseded"
49
+ source?: string; // origin annotation: "lifecycle:openspec:X" | "extraction" | "manual"
50
+ }
51
+
52
+ export type FactStatus = "active" | "archived" | "superseded";
53
+
54
+ /**
55
+ * A session episode narrative.
56
+ * Rust: src/memory/types.rs::Episode
57
+ */
58
+ export interface EpisodeRecord {
59
+ id: string;
60
+ mind: string;
61
+ date: string; // ISO 8601 date
62
+ title: string;
63
+ narrative: string; // free-form prose summary
64
+ created_at: string; // ISO 8601
65
+ /** Design node IDs touched this session. Optional; populated by extraction subagent. */
66
+ affected_nodes?: string[];
67
+ /** OpenSpec change names touched this session. */
68
+ affected_changes?: string[];
69
+ /** git diff --stat summary of files changed. */
70
+ files_changed?: string[];
71
+ /** Semantic tags: "architecture", "bugfix", "refactor", "investigation", etc. */
72
+ tags?: string[];
73
+ /** Rough activity measure — number of LLM tool calls in this session. */
74
+ tool_calls_count?: number;
75
+ }
76
+
77
+ /**
78
+ * A directional relationship between two facts.
79
+ * Rust: src/memory/types.rs::Edge
80
+ */
81
+ export interface EdgeRecord {
82
+ id: string;
83
+ source_id: string;
84
+ target_id: string;
85
+ relation: string; // "depends_on", "contradicts", "generalizes", etc.
86
+ description?: string;
87
+ weight: number; // 0.0–1.0; higher = stronger relationship
88
+ created_at: string; // ISO 8601
89
+ }
90
+
91
+ // ─── POST /api/memory/facts ───────────────────────────────────────────────────
92
+
93
+ export interface StoreFactRequest {
94
+ mind: string;
95
+ content: string;
96
+ section: SectionName;
97
+ /** Decay profile to use for this fact. Defaults to "standard". */
98
+ decay_profile?: DecayProfileName;
99
+ /** Optional initial confidence override (0.0–1.0). Default: 1.0. */
100
+ confidence?: number;
101
+ /** Origin annotation for provenance tracking. */
102
+ source?: string;
103
+ }
104
+
105
+ export interface StoreFactResponse {
106
+ /** The ID of the stored or matched fact. */
107
+ id: string;
108
+ /** What happened: new fact stored, existing fact reinforced, or exact duplicate found. */
109
+ action: "stored" | "reinforced" | "deduplicated";
110
+ fact: FactRecord;
111
+ }
112
+
113
+ // ─── GET /api/memory/context ──────────────────────────────────────────────────
114
+
115
+ export interface ContextRequest {
116
+ mind: string;
117
+ /** Raw text query used for semantic fact selection. Omega embeds it.
118
+ * Required when mode === "semantic". */
119
+ query?: string;
120
+ mode: InjectionMode;
121
+ /** Pinned fact IDs — always included first, before semantic or bulk selection. */
122
+ working_memory?: string[];
123
+ /** Maximum character budget for the rendered context block. Default: 12000.
124
+ * Omega stops adding facts when the budget would be exceeded. */
125
+ max_chars?: number;
126
+ /** Per-section maximum fact counts. Overrides Omega's default caps. */
127
+ section_caps?: Partial<Record<SectionName, number>>;
128
+ /** Number of recent session episodes to include. Default: 1. 0 to suppress. */
129
+ episodes?: number;
130
+ /** Whether to include global knowledge (cross-project facts).
131
+ * In semantic mode: included only if relevant (similarity ≥ 0.45).
132
+ * In bulk mode: excluded unless explicitly true. Default: false. */
133
+ include_global?: boolean;
134
+ }
135
+
136
+ export type InjectionMode = "semantic" | "bulk";
137
+
138
+ export interface ContextResponse {
139
+ /** Pre-rendered markdown block suitable for direct LLM context injection. */
140
+ markdown: string;
141
+ stats: ContextStats;
142
+ }
143
+
144
+ export interface ContextStats {
145
+ facts_injected: number;
146
+ mode: InjectionMode;
147
+ episodes_injected: number;
148
+ global_facts_injected: number;
149
+ char_count: number;
150
+ /** True if some facts were dropped to stay within max_chars budget. */
151
+ budget_exhausted: boolean;
152
+ }
153
+
154
+ // ─── POST /api/memory/recall ──────────────────────────────────────────────────
155
+
156
+ export interface RecallRequest {
157
+ mind: string;
158
+ /** Raw text query. Omega embeds it and runs cosine similarity × decay scoring. */
159
+ query: string;
160
+ /** Number of results. Default: 10. */
161
+ k?: number;
162
+ /** Minimum cosine similarity. Default: 0.3. */
163
+ min_similarity?: number;
164
+ /** Optional section filter. */
165
+ section?: SectionName;
166
+ }
167
+
168
+ export interface RecallResponse {
169
+ facts: ScoredFact[];
170
+ }
171
+
172
+ export interface ScoredFact extends FactRecord {
173
+ /** Raw cosine similarity between query vector and fact vector (0.0–1.0). */
174
+ similarity: number;
175
+ /** Combined score: similarity × decay-adjusted confidence. Used for ranking. */
176
+ score: number;
177
+ }
178
+
179
+ // ─── GET /api/memory/facts ────────────────────────────────────────────────────
180
+
181
+ export interface ListFactsRequest {
182
+ mind: string;
183
+ section?: SectionName;
184
+ status?: FactStatus;
185
+ }
186
+
187
+ export interface ListFactsResponse {
188
+ facts: FactRecord[];
189
+ total: number;
190
+ }
191
+
192
+ // ─── PATCH /api/memory/facts/:id ─────────────────────────────────────────────
193
+
194
+ export interface UpdateFactRequest {
195
+ action: UpdateFactAction;
196
+ /** For "supersede": the replacement fact content. */
197
+ content?: string;
198
+ /** For "supersede": the section for the replacement fact.
199
+ * Defaults to the original fact's section. */
200
+ section?: SectionName;
201
+ /** For "supersede": the decay profile for the replacement fact.
202
+ * Defaults to the original fact's decay profile. */
203
+ decay_profile?: DecayProfileName;
204
+ }
205
+
206
+ export type UpdateFactAction = "reinforce" | "archive" | "supersede";
207
+
208
+ export interface UpdateFactResponse {
209
+ action: UpdateFactAction;
210
+ fact: FactRecord;
211
+ /** Present when action === "supersede". The new replacement fact. */
212
+ new_fact?: FactRecord;
213
+ }
214
+
215
+ // ─── POST /api/memory/edges ───────────────────────────────────────────────────
216
+
217
+ export interface CreateEdgeRequest {
218
+ source_id: string;
219
+ target_id: string;
220
+ /** Short verb phrase: "depends_on", "contradicts", "enables", "generalizes", etc. */
221
+ relation: string;
222
+ description?: string;
223
+ }
224
+
225
+ export interface CreateEdgeResponse {
226
+ edge: EdgeRecord;
227
+ /** True if an existing edge was reinforced (weight increased) rather than created. */
228
+ reinforced: boolean;
229
+ }
230
+
231
+ // ─── GET /api/memory/export ───────────────────────────────────────────────────
232
+
233
+ /**
234
+ * Response: Content-Type: application/x-ndjson
235
+ * Each line is one JSONL record: FactRecord | EpisodeRecord | EdgeRecord | MindRecord
236
+ * Line ordering: minds first, then facts, then edges, then episodes.
237
+ * Deterministic output (sorted by id within each type) for stable git diffs.
238
+ */
239
+ export interface ExportOptions {
240
+ mind: string;
241
+ /** Include archived and superseded facts. Default: true (full history). */
242
+ include_archived?: boolean;
243
+ }
244
+
245
+ // ─── POST /api/memory/import ─────────────────────────────────────────────────
246
+
247
+ export interface ImportRequest {
248
+ /** Raw JSONL content. Each line is a fact, episode, edge, or mind record.
249
+ * Conflict resolution: higher `version` (Lamport timestamp) always wins.
250
+ * Facts without `version` field get version=0 on import. */
251
+ jsonl: string;
252
+ }
253
+
254
+ export interface ImportResponse {
255
+ imported: number; // new records
256
+ reinforced: number; // existing records with lower version updated
257
+ skipped: number; // same or lower version, no change
258
+ errors: number; // lines that failed to parse
259
+ }
260
+
261
+ // ─── POST /api/memory/episodes ───────────────────────────────────────────────
262
+
263
+ export interface StoreEpisodeRequest {
264
+ mind: string;
265
+ title: string;
266
+ narrative: string;
267
+ /** ISO 8601 date. Default: today. */
268
+ date?: string;
269
+ affected_nodes?: string[];
270
+ affected_changes?: string[];
271
+ files_changed?: string[];
272
+ tags?: string[];
273
+ tool_calls_count?: number;
274
+ }
275
+
276
+ export interface StoreEpisodeResponse {
277
+ episode: EpisodeRecord;
278
+ }
279
+
280
+ // ─── GET /api/memory/episodes ────────────────────────────────────────────────
281
+
282
+ export interface ListEpisodesRequest {
283
+ mind: string;
284
+ /** Number of most recent episodes to return. Default: 3. */
285
+ k?: number;
286
+ /** Optional semantic query — returns episodes ranked by narrative similarity. */
287
+ query?: string;
288
+ }
289
+
290
+ export interface ListEpisodesResponse {
291
+ episodes: EpisodeRecord[];
292
+ }
293
+
294
+ // ─── POST /api/memory/parse-extraction ───────────────────────────────────────
295
+
296
+ /**
297
+ * Validate and parse raw extraction subagent output.
298
+ * The TS extraction subagent sends LLM-generated JSONL here for typed validation.
299
+ * Omega parses each line against the ExtractionAction schema and returns
300
+ * validated actions + a list of lines that failed parsing.
301
+ *
302
+ * Rust: src/memory/extraction.rs::parse_extraction_output
303
+ */
304
+ export interface ParseExtractionRequest {
305
+ /** Raw JSONL output from the extraction subagent. */
306
+ raw_output: string;
307
+ }
308
+
309
+ export interface ParseExtractionResponse {
310
+ actions: ExtractionAction[];
311
+ /** Lines that failed schema validation — for debugging. */
312
+ errors: string[];
313
+ }
314
+
315
+ export type ExtractionAction =
316
+ | { type: "store"; content: string; section: SectionName; decay_profile?: DecayProfileName }
317
+ | { type: "archive"; id: string }
318
+ | { type: "supersede"; id: string; content: string; section?: SectionName }
319
+ | { type: "connect"; source_id: string; target_id: string; relation: string; description?: string };
320
+
321
+ // ─── GET /api/memory/stats ────────────────────────────────────────────────────
322
+
323
+ export interface MemoryStatsResponse {
324
+ mind: string;
325
+ total_facts: number;
326
+ active_facts: number;
327
+ archived_facts: number;
328
+ superseded_facts: number;
329
+ facts_with_vectors: number;
330
+ embedding_model?: string; // null if no vectors exist
331
+ embedding_dims?: number;
332
+ episodes: number;
333
+ edges: number;
334
+ db_size_bytes: number;
335
+ /** Lamport clock high-water mark — max version seen in this DB. */
336
+ version_hwm: number;
337
+ }
338
+
339
+ // ─── JSONL wire format ────────────────────────────────────────────────────────
340
+
341
+ /**
342
+ * The canonical JSONL line types for git-sync.
343
+ * Each line is one of these discriminated union members.
344
+ * Rust: src/memory/jsonl.rs — use serde(tag = "type") for tagged deserialization.
345
+ */
346
+ export type JsonlRecord =
347
+ | ({ type: "fact" } & FactRecord)
348
+ | ({ type: "episode" } & EpisodeRecord)
349
+ | ({ type: "edge" } & EdgeRecord)
350
+ | ({ type: "mind" } & MindRecord);
351
+
352
+ export interface MindRecord {
353
+ name: string;
354
+ created_at: string;
355
+ description?: string;
356
+ }
357
+
358
+ // ─── Embedding metadata ───────────────────────────────────────────────────────
359
+
360
+ /**
361
+ * Embedding model registration.
362
+ * Stored in `embedding_metadata` table — one row per model ever used.
363
+ * Facts in `facts_vec` carry a `model_name` FK to this table.
364
+ *
365
+ * Mismatch between query vector dims and stored model dims is a hard error:
366
+ * EmbeddingDimensionMismatch { expected: u32, got: u32, model: String }
367
+ *
368
+ * Rust: src/memory/vectors.rs::EmbeddingMetadata
369
+ */
370
+ export interface EmbeddingMetadata {
371
+ model_name: string; // e.g., "qwen3-embedding:0.6b"
372
+ dims: number; // e.g., 384
373
+ inserted_at: string; // ISO 8601 — when first used
374
+ }
375
+
376
+ export interface EmbeddingDimensionMismatchError {
377
+ error: "EmbeddingDimensionMismatch";
378
+ expected_dims: number;
379
+ got_dims: number;
380
+ stored_model: string;
381
+ message: string;
382
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Pure helpers for compaction policy and prompt hygiene.
3
+ */
4
+
5
+ import type { MemoryConfig } from "./types.ts";
6
+
7
+ /**
8
+ * Redact transient clipboard image temp paths captured from pi's clipboard paste flow.
9
+ * These files live under macOS temp directories and become stale/noisy immediately.
10
+ */
11
+ export function sanitizeCompactionText(input: string): string {
12
+ return input.replace(
13
+ /\/var\/folders\/[A-Za-z0-9_-]+\/[A-Za-z0-9_-]+\/T\/pi-clipboard-[A-Fa-f0-9-]+\.(?:png|jpe?g|gif|webp)/g,
14
+ "[clipboard image attachment]",
15
+ );
16
+ }
17
+
18
+ /**
19
+ * Whether project-memory should intercept compaction before pi core.
20
+ * Local interception is only enabled for explicit local policy or fallback retry.
21
+ */
22
+ export function shouldInterceptCompaction(
23
+ effortCompaction: "local" | "retribution" | "victory" | "gloriana" | undefined,
24
+ config: MemoryConfig,
25
+ useLocalCompaction: boolean,
26
+ ): boolean {
27
+ const liveCompactionLocal = effortCompaction ? effortCompaction === "local" : config.compactionLocalFirst;
28
+ return (liveCompactionLocal || useLocalCompaction) && config.compactionLocalFallback;
29
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * project-memory/core — Pure computation functions
3
+ *
4
+ * Zero dependencies: no DB, no pi API, no Node.js built-ins except crypto
5
+ * (available in all JS environments). This module is the direct Rust port target.
6
+ *
7
+ * Rust equivalents:
8
+ * computeConfidence → src/memory/decay.rs::compute_confidence
9
+ * cosineSimilarity → src/memory/vectors.rs::cosine_similarity
10
+ * vectorToBlob → src/memory/vectors.rs::vector_to_blob
11
+ * blobToVector → src/memory/vectors.rs::blob_to_vector
12
+ * contentHash → src/memory/store.rs::content_hash
13
+ * normalizeForHash → src/memory/store.rs::normalize_for_hash
14
+ *
15
+ * Any behavioural change here must be reflected in the Rust implementation
16
+ * and verified by cross-impl tests (same inputs → same outputs).
17
+ */
18
+
19
+ import * as crypto from "node:crypto";
20
+
21
+ // ─── Decay profiles ──────────────────────────────────────────────────────────
22
+
23
+ /** Project-level decay. Base half-life 14d; each reinforcement extends by 1.8×. */
24
+ export const DECAY = {
25
+ baseRate: 0.05, // ≈ ln(2)/14 — single unreinforced fact fades in ~2 weeks
26
+ reinforcementFactor: 1.8,
27
+ minimumConfidence: 0.1,
28
+ halfLifeDays: 14,
29
+ } as const;
30
+
31
+ /** Global-level decay. Shorter base (30d); cross-project reinforcement dramatically extends. */
32
+ export const GLOBAL_DECAY = {
33
+ baseRate: Math.LN2 / 30,
34
+ reinforcementFactor: 2.5,
35
+ minimumConfidence: 0.1,
36
+ halfLifeDays: 30,
37
+ } as const;
38
+
39
+ /**
40
+ * Recent Work decay — ephemeral session breadcrumbs.
41
+ * halfLifeDays=2: written Monday, gone by Wednesday at ~50%.
42
+ * reinforcementFactor=1.0: reinforcement does NOT extend half-life.
43
+ */
44
+ export const RECENT_WORK_DECAY = {
45
+ baseRate: Math.LN2 / 2,
46
+ reinforcementFactor: 1.0,
47
+ minimumConfidence: 0.01,
48
+ halfLifeDays: 2,
49
+ } as const;
50
+
51
+ export type DecayProfile = typeof DECAY | typeof GLOBAL_DECAY | typeof RECENT_WORK_DECAY;
52
+
53
+ /** Stored profile discriminant — persisted in the `decay_profile` DB column. */
54
+ export type DecayProfileName = "standard" | "global" | "recent_work";
55
+
56
+ /** Map DB column value → DecayProfile object. Exhaustive — all names must be handled. */
57
+ export function resolveDecayProfile(name: DecayProfileName): DecayProfile {
58
+ switch (name) {
59
+ case "standard": return DECAY;
60
+ case "global": return GLOBAL_DECAY;
61
+ case "recent_work": return RECENT_WORK_DECAY;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Maximum effective half-life regardless of reinforcement count.
67
+ * Prevents immortal facts — even highly reinforced facts decay eventually.
68
+ * Facts needing longer survival must be pinned via memory_focus.
69
+ */
70
+ const MAX_HALF_LIFE_DAYS = 90;
71
+
72
+ /**
73
+ * Compute current confidence for a fact based on time since last reinforcement.
74
+ *
75
+ * halfLife = profile.halfLifeDays × (profile.reinforcementFactor ^ (reinforcement_count - 1))
76
+ * halfLife = clamp(halfLife, 0, MAX_HALF_LIFE_DAYS)
77
+ * confidence = e^(−ln(2) × daysSinceReinforced / halfLife)
78
+ *
79
+ * Rust port: src/memory/decay.rs::compute_confidence
80
+ * Must produce bit-compatible results for the same float inputs.
81
+ */
82
+ export function computeConfidence(
83
+ daysSinceReinforced: number,
84
+ reinforcementCount: number,
85
+ profile: DecayProfile = DECAY,
86
+ ): number {
87
+ const rawHalfLife = profile.halfLifeDays * Math.pow(profile.reinforcementFactor, reinforcementCount - 1);
88
+ const halfLife = Math.min(rawHalfLife, MAX_HALF_LIFE_DAYS);
89
+ const confidence = Math.exp(-Math.LN2 * daysSinceReinforced / halfLife);
90
+ return Math.max(confidence, 0);
91
+ }
92
+
93
+ // ─── Vector math ─────────────────────────────────────────────────────────────
94
+
95
+ /**
96
+ * Cosine similarity between two Float32Arrays.
97
+ * Returns 0 if either vector has zero norm or dimensions differ.
98
+ *
99
+ * Rust port: src/memory/vectors.rs::cosine_similarity
100
+ * LLVM auto-vectorizes the inner loop on x86 (SSE/AVX) and ARM (NEON).
101
+ */
102
+ export function cosineSimilarity(a: Float32Array, b: Float32Array): number {
103
+ if (a.length !== b.length) return 0;
104
+ let dot = 0, normA = 0, normB = 0;
105
+ for (let i = 0; i < a.length; i++) {
106
+ dot += a[i] * b[i];
107
+ normA += a[i] * a[i];
108
+ normB += b[i] * b[i];
109
+ }
110
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
111
+ return denom === 0 ? 0 : dot / denom;
112
+ }
113
+
114
+ /**
115
+ * Serialize Float32Array to Buffer for SQLite BLOB storage.
116
+ * Layout: raw IEEE 754 little-endian f32 array (matches Rust's [f32] memory layout).
117
+ *
118
+ * Rust port: src/memory/vectors.rs — use bytemuck::cast_slice or std::mem::transmute.
119
+ */
120
+ export function vectorToBlob(vec: Float32Array): Buffer {
121
+ return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
122
+ }
123
+
124
+ /**
125
+ * Deserialize Buffer from SQLite BLOB to Float32Array.
126
+ * Allocates a fresh aligned ArrayBuffer — safe regardless of Buffer alignment.
127
+ *
128
+ * Rust port: src/memory/vectors.rs — bytemuck::cast_slice::<u8, f32>.
129
+ */
130
+ export function blobToVector(blob: Buffer): Float32Array {
131
+ const aligned = new ArrayBuffer(blob.length);
132
+ const view = new Uint8Array(aligned);
133
+ view.set(blob);
134
+ return new Float32Array(aligned);
135
+ }
136
+
137
+ // ─── Content hashing ─────────────────────────────────────────────────────────
138
+
139
+ /**
140
+ * Normalize content for dedup hashing.
141
+ * Strips leading bullet dash, trims whitespace, lowercases, collapses runs of spaces.
142
+ *
143
+ * Rust port: src/memory/store.rs::normalize_for_hash
144
+ */
145
+ export function normalizeForHash(content: string): string {
146
+ return content
147
+ .replace(/^-\s*/, "")
148
+ .trim()
149
+ .toLowerCase()
150
+ .replace(/\s+/g, " ");
151
+ }
152
+
153
+ /**
154
+ * Compute a 16-hex-char content hash for deduplication.
155
+ * Uses sha256 truncated to 64 bits — collision probability negligible at expected fact counts.
156
+ *
157
+ * Rust port: src/memory/store.rs::content_hash — sha2::Sha256, hex encode, truncate to 16.
158
+ */
159
+ export function contentHash(content: string): string {
160
+ return crypto.createHash("sha256")
161
+ .update(normalizeForHash(content))
162
+ .digest("hex")
163
+ .slice(0, 16);
164
+ }