ahok-skill 1.3.1

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 (141) hide show
  1. package/.prettierrc +8 -0
  2. package/Dockerfile +59 -0
  3. package/RAW_SKILL.md +219 -0
  4. package/README.md +277 -0
  5. package/SKILL.md +58 -0
  6. package/bin/opm.js +268 -0
  7. package/data/openmemory.sqlite +0 -0
  8. package/data/openmemory.sqlite-shm +0 -0
  9. package/data/openmemory.sqlite-wal +0 -0
  10. package/dist/ai/graph.js +293 -0
  11. package/dist/ai/mcp.js +397 -0
  12. package/dist/cli.js +78 -0
  13. package/dist/core/cfg.js +87 -0
  14. package/dist/core/db.js +636 -0
  15. package/dist/core/memory.js +116 -0
  16. package/dist/core/migrate.js +227 -0
  17. package/dist/core/models.js +105 -0
  18. package/dist/core/telemetry.js +57 -0
  19. package/dist/core/types.js +2 -0
  20. package/dist/core/vector/postgres.js +52 -0
  21. package/dist/core/vector/valkey.js +246 -0
  22. package/dist/core/vector_store.js +2 -0
  23. package/dist/index.js +44 -0
  24. package/dist/memory/decay.js +301 -0
  25. package/dist/memory/embed.js +675 -0
  26. package/dist/memory/hsg.js +959 -0
  27. package/dist/memory/reflect.js +131 -0
  28. package/dist/memory/user_summary.js +99 -0
  29. package/dist/migrate.js +9 -0
  30. package/dist/ops/compress.js +255 -0
  31. package/dist/ops/dynamics.js +189 -0
  32. package/dist/ops/extract.js +333 -0
  33. package/dist/ops/ingest.js +214 -0
  34. package/dist/server/index.js +109 -0
  35. package/dist/server/middleware/auth.js +137 -0
  36. package/dist/server/routes/auth.js +186 -0
  37. package/dist/server/routes/compression.js +108 -0
  38. package/dist/server/routes/dashboard.js +399 -0
  39. package/dist/server/routes/docs.js +241 -0
  40. package/dist/server/routes/dynamics.js +312 -0
  41. package/dist/server/routes/ide.js +280 -0
  42. package/dist/server/routes/index.js +33 -0
  43. package/dist/server/routes/keys.js +132 -0
  44. package/dist/server/routes/langgraph.js +61 -0
  45. package/dist/server/routes/memory.js +213 -0
  46. package/dist/server/routes/sources.js +140 -0
  47. package/dist/server/routes/system.js +63 -0
  48. package/dist/server/routes/temporal.js +293 -0
  49. package/dist/server/routes/users.js +101 -0
  50. package/dist/server/routes/vercel.js +57 -0
  51. package/dist/server/server.js +211 -0
  52. package/dist/server.js +3 -0
  53. package/dist/sources/base.js +223 -0
  54. package/dist/sources/github.js +171 -0
  55. package/dist/sources/google_drive.js +166 -0
  56. package/dist/sources/google_sheets.js +112 -0
  57. package/dist/sources/google_slides.js +139 -0
  58. package/dist/sources/index.js +34 -0
  59. package/dist/sources/notion.js +165 -0
  60. package/dist/sources/onedrive.js +143 -0
  61. package/dist/sources/web_crawler.js +166 -0
  62. package/dist/temporal_graph/index.js +20 -0
  63. package/dist/temporal_graph/query.js +240 -0
  64. package/dist/temporal_graph/store.js +116 -0
  65. package/dist/temporal_graph/timeline.js +241 -0
  66. package/dist/temporal_graph/types.js +2 -0
  67. package/dist/utils/chunking.js +60 -0
  68. package/dist/utils/index.js +31 -0
  69. package/dist/utils/keyword.js +94 -0
  70. package/dist/utils/text.js +120 -0
  71. package/nodemon.json +7 -0
  72. package/package.json +50 -0
  73. package/references/api_reference.md +66 -0
  74. package/references/examples.md +45 -0
  75. package/src/ai/graph.ts +363 -0
  76. package/src/ai/mcp.ts +494 -0
  77. package/src/cli.ts +94 -0
  78. package/src/core/cfg.ts +110 -0
  79. package/src/core/db.ts +1052 -0
  80. package/src/core/memory.ts +99 -0
  81. package/src/core/migrate.ts +302 -0
  82. package/src/core/models.ts +107 -0
  83. package/src/core/telemetry.ts +47 -0
  84. package/src/core/types.ts +130 -0
  85. package/src/core/vector/postgres.ts +61 -0
  86. package/src/core/vector/valkey.ts +261 -0
  87. package/src/core/vector_store.ts +9 -0
  88. package/src/index.ts +5 -0
  89. package/src/memory/decay.ts +427 -0
  90. package/src/memory/embed.ts +707 -0
  91. package/src/memory/hsg.ts +1245 -0
  92. package/src/memory/reflect.ts +158 -0
  93. package/src/memory/user_summary.ts +110 -0
  94. package/src/migrate.ts +8 -0
  95. package/src/ops/compress.ts +296 -0
  96. package/src/ops/dynamics.ts +272 -0
  97. package/src/ops/extract.ts +360 -0
  98. package/src/ops/ingest.ts +286 -0
  99. package/src/server/index.ts +159 -0
  100. package/src/server/middleware/auth.ts +156 -0
  101. package/src/server/routes/auth.ts +223 -0
  102. package/src/server/routes/compression.ts +106 -0
  103. package/src/server/routes/dashboard.ts +420 -0
  104. package/src/server/routes/docs.ts +380 -0
  105. package/src/server/routes/dynamics.ts +516 -0
  106. package/src/server/routes/ide.ts +283 -0
  107. package/src/server/routes/index.ts +32 -0
  108. package/src/server/routes/keys.ts +131 -0
  109. package/src/server/routes/langgraph.ts +71 -0
  110. package/src/server/routes/memory.ts +440 -0
  111. package/src/server/routes/sources.ts +111 -0
  112. package/src/server/routes/system.ts +68 -0
  113. package/src/server/routes/temporal.ts +335 -0
  114. package/src/server/routes/users.ts +111 -0
  115. package/src/server/routes/vercel.ts +55 -0
  116. package/src/server/server.js +215 -0
  117. package/src/server.ts +1 -0
  118. package/src/sources/base.ts +257 -0
  119. package/src/sources/github.ts +156 -0
  120. package/src/sources/google_drive.ts +144 -0
  121. package/src/sources/google_sheets.ts +85 -0
  122. package/src/sources/google_slides.ts +115 -0
  123. package/src/sources/index.ts +19 -0
  124. package/src/sources/notion.ts +148 -0
  125. package/src/sources/onedrive.ts +131 -0
  126. package/src/sources/web_crawler.ts +161 -0
  127. package/src/temporal_graph/index.ts +4 -0
  128. package/src/temporal_graph/query.ts +299 -0
  129. package/src/temporal_graph/store.ts +156 -0
  130. package/src/temporal_graph/timeline.ts +319 -0
  131. package/src/temporal_graph/types.ts +41 -0
  132. package/src/utils/chunking.ts +66 -0
  133. package/src/utils/index.ts +25 -0
  134. package/src/utils/keyword.ts +137 -0
  135. package/src/utils/text.ts +115 -0
  136. package/tests/test_api_workspace_management.ts +413 -0
  137. package/tests/test_bulk_delete.ts +267 -0
  138. package/tests/test_omnibus.ts +166 -0
  139. package/tests/test_workspace_management.ts +278 -0
  140. package/tests/verify.ts +104 -0
  141. package/tsconfig.json +15 -0
@@ -0,0 +1,158 @@
1
+ import { q, log_maint_op } from "../core/db";
2
+ import { add_hsg_memory } from "./hsg";
3
+ import { env } from "../core/cfg";
4
+ import { j } from "../utils";
5
+
6
+ const sim = (t1: string, t2: string): number => {
7
+ // Robust Jaccard overlap
8
+ const s1 = new Set(t1.toLowerCase().split(/\s+/).filter(x => x.length > 0));
9
+ const s2 = new Set(t2.toLowerCase().split(/\s+/).filter(x => x.length > 0));
10
+ if (s1.size === 0 || s2.size === 0) return 0;
11
+
12
+ let inter = 0;
13
+ for (const token of s1) {
14
+ if (s2.has(token)) inter++;
15
+ }
16
+ const union = new Set([...s1, ...s2]).size;
17
+ return union > 0 ? inter / union : 0;
18
+ };
19
+
20
+ const cluster = (mems: any[]): any[] => {
21
+ const cls: any[] = [];
22
+ const used = new Set();
23
+ for (const m of mems) {
24
+ if (
25
+ used.has(m.id) ||
26
+ m.primary_sector === "reflective" ||
27
+ m.metadata?.consolidated
28
+ )
29
+ continue;
30
+ const c = { mem: [m], n: 1 };
31
+ used.add(m.id);
32
+ for (const o of mems) {
33
+ if (used.has(o.id) || m.primary_sector !== o.primary_sector)
34
+ continue;
35
+ if (sim(m.content, o.content) > 0.8) {
36
+ c.mem.push(o);
37
+ c.n++;
38
+ used.add(o.id);
39
+ }
40
+ }
41
+ if (c.n >= 2) cls.push(c);
42
+ }
43
+ return cls;
44
+ };
45
+
46
+ const sal = (c: any): number => {
47
+ const now = Date.now();
48
+ const p = c.n / 10;
49
+ const r =
50
+ c.mem.reduce(
51
+ (s: number, m: any) =>
52
+ s +
53
+ Math.exp(-(now - new Date(m.created_at).getTime()) / 43200000),
54
+ 0,
55
+ ) / c.n;
56
+ const e = c.mem.some(
57
+ (m: any) =>
58
+ m.sectors &&
59
+ Array.isArray(m.sectors) &&
60
+ m.sectors.includes("emotional"),
61
+ )
62
+ ? 1
63
+ : 0;
64
+ return Math.min(1, 0.6 * p + 0.3 * r + 0.1 * e);
65
+ };
66
+
67
+ const summ = (c: any): string => {
68
+ const sec = c.mem[0].primary_sector;
69
+ const n = c.n;
70
+ const txt = c.mem.map((m: any) => m.content.substring(0, 60)).join("; ");
71
+ return `${n} ${sec} pattern: ${txt.substring(0, 200)}`;
72
+ };
73
+
74
+ const mark = async (ids: string[]) => {
75
+ for (const id of ids) {
76
+ const m = await q.get_mem.get(id);
77
+ if (m) {
78
+ const meta = JSON.parse(m.meta || "{}");
79
+ meta.consolidated = true;
80
+ await q.upd_mem.run(
81
+ m.content,
82
+ m.tags,
83
+ JSON.stringify(meta),
84
+ Date.now(),
85
+ id,
86
+ );
87
+ }
88
+ }
89
+ };
90
+
91
+ const boost = async (ids: string[]) => {
92
+ for (const id of ids) {
93
+ const m = await q.get_mem.get(id);
94
+ if (m) await q.upd_mem.run(m.content, m.tags, m.meta, Date.now(), id);
95
+ await q.upd_seen.run(
96
+ id,
97
+ m.last_seen_at,
98
+ Math.min(1, m.salience * 1.1),
99
+ Date.now(),
100
+ );
101
+ }
102
+ };
103
+
104
+ export const run_reflection = async () => {
105
+ console.error("[REFLECT] Starting reflection job...");
106
+ const min = env.reflect_min || 20;
107
+ const mems = await q.all_mem.all(100, 0);
108
+ console.error(
109
+ `[REFLECT] Fetched ${mems.length} memories (min required: ${min})`,
110
+ );
111
+ if (mems.length < min) {
112
+ console.error("[REFLECT] Not enough memories, skipping");
113
+ return { created: 0, reason: "low" };
114
+ }
115
+ const cls = cluster(mems);
116
+ console.error(`[REFLECT] Clustered into ${cls.length} groups`);
117
+ let n = 0;
118
+ for (const c of cls) {
119
+ const txt = summ(c);
120
+ const s = sal(c);
121
+ const src = c.mem.map((m: any) => m.id);
122
+ const meta = {
123
+ type: "auto_reflect",
124
+ sources: src,
125
+ freq: c.n,
126
+ at: new Date().toISOString(),
127
+ };
128
+ console.error(
129
+ `[REFLECT] Creating reflection: ${c.n} memories, salience=${s.toFixed(3)}, sector=${c.mem[0].primary_sector}`,
130
+ );
131
+ await add_hsg_memory(txt, j(["reflect:auto"]), meta);
132
+ await mark(src);
133
+ await boost(src);
134
+ n++;
135
+ }
136
+ if (n > 0) await log_maint_op("reflect", n);
137
+ console.error(`[REFLECT] Job complete: created ${n} reflections`);
138
+ return { created: n, clusters: cls.length };
139
+ };
140
+
141
+ let timer: NodeJS.Timeout | null = null;
142
+
143
+ export const start_reflection = () => {
144
+ if (!env.auto_reflect || timer) return;
145
+ const int = (env.reflect_interval || 10) * 60000;
146
+ timer = setInterval(
147
+ () => run_reflection().catch((e) => console.error("[REFLECT]", e)),
148
+ int,
149
+ );
150
+ console.error(`[REFLECT] Started: every ${env.reflect_interval || 10}m`);
151
+ };
152
+
153
+ export const stop_reflection = () => {
154
+ if (timer) {
155
+ clearInterval(timer);
156
+ timer = null;
157
+ }
158
+ };
@@ -0,0 +1,110 @@
1
+ import { q } from "../core/db";
2
+ import { env } from "../core/cfg";
3
+
4
+ const cos = (a: number[], b: number[]): number => {
5
+ let d = 0,
6
+ ma = 0,
7
+ mb = 0;
8
+ for (let i = 0; i < a.length; i++) {
9
+ d += a[i] * b[i];
10
+ ma += a[i] * a[i];
11
+ mb += b[i] * b[i];
12
+ }
13
+ return d / (Math.sqrt(ma) * Math.sqrt(mb));
14
+ };
15
+
16
+ const gen_user_summary = (mems: any[]): string => {
17
+ if (!mems.length) return "User profile initializing... (No memories recorded yet)";
18
+
19
+ const recent = mems.slice(0, 10);
20
+ const projects = new Set<string>();
21
+ const languages = new Set<string>();
22
+ const files = new Set<string>();
23
+
24
+ let events = 0;
25
+ let saves = 0;
26
+
27
+ for (const m of mems) {
28
+ if (m.meta) {
29
+ try {
30
+ const meta = typeof m.meta === 'string' ? JSON.parse(m.meta) : m.meta;
31
+ if (meta.ide_project_name) projects.add(meta.ide_project_name);
32
+ if (meta.language) languages.add(meta.language);
33
+ if (meta.ide_file_path) files.add(meta.ide_file_path.split(/[\\/]/).pop());
34
+ if (meta.ide_event_type === 'save') saves++;
35
+ } catch (e) { /* ignore */ }
36
+ }
37
+ events++;
38
+ }
39
+
40
+ const project_str = projects.size > 0 ? Array.from(projects).join(", ") : "Unknown Project";
41
+ const lang_str = languages.size > 0 ? Array.from(languages).join(", ") : "General";
42
+ const recent_files = Array.from(files).slice(0, 3).join(", ");
43
+
44
+ const last_active = mems[0].created_at ? new Date(mems[0].created_at).toLocaleString() : "Recently";
45
+
46
+ return `Active in ${project_str} using ${lang_str}. Focused on ${recent_files || "various files"}. (${mems.length} memories, ${saves} saves). Last active: ${last_active}.`;
47
+ };
48
+
49
+ export const gen_user_summary_async = async (
50
+ user_id: string,
51
+ ): Promise<string> => {
52
+ const mems = await q.all_mem_by_user.all(user_id, 100, 0);
53
+ return gen_user_summary(mems);
54
+ };
55
+
56
+ export const update_user_summary = async (user_id: string): Promise<void> => {
57
+ try {
58
+ const summary = await gen_user_summary_async(user_id);
59
+ const now = Date.now();
60
+
61
+ const existing = await q.get_user.get(user_id);
62
+ if (!existing) {
63
+ await q.ins_user.run(user_id, summary, 0, now, now);
64
+ } else {
65
+ await q.upd_user_summary.run(user_id, summary, now);
66
+ }
67
+ } catch (e) {
68
+ console.error(`[USER_SUMMARY] Fatal error for ${user_id}:`, e);
69
+ }
70
+ };
71
+
72
+ export const auto_update_user_summaries = async (): Promise<{
73
+ updated: number;
74
+ }> => {
75
+ const all_mems = await q.all_mem.all(10000, 0);
76
+ const user_ids = new Set(all_mems.map((m) => m.user_id).filter(Boolean));
77
+
78
+ let updated = 0;
79
+ for (const uid of user_ids) {
80
+ try {
81
+ await update_user_summary(uid as string);
82
+ updated++;
83
+ } catch (e) {
84
+ console.error(`[USER_SUMMARY] Failed for ${uid}:`, e);
85
+ }
86
+ }
87
+
88
+ return { updated };
89
+ };
90
+
91
+ let timer: NodeJS.Timeout | null = null;
92
+
93
+ export const start_user_summary_reflection = () => {
94
+ if (timer) return;
95
+ const int = (env.user_summary_interval || 30) * 60000;
96
+ timer = setInterval(
97
+ () =>
98
+ auto_update_user_summaries().catch((e) =>
99
+ console.error("[USER_SUMMARY]", e),
100
+ ),
101
+ int,
102
+ );
103
+ };
104
+
105
+ export const stop_user_summary_reflection = () => {
106
+ if (timer) {
107
+ clearInterval(timer);
108
+ timer = null;
109
+ }
110
+ };
package/src/migrate.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { run_migrations } from "./core/migrate";
2
+
3
+ run_migrations().then(() => {
4
+ process.exit(0);
5
+ }).catch((err) => {
6
+ console.error(err);
7
+ process.exit(1);
8
+ });
@@ -0,0 +1,296 @@
1
+ import { createHash } from "crypto";
2
+
3
+ export interface CompressionMetrics {
4
+ ogTok: number;
5
+ compTok: number;
6
+ ratio: number;
7
+ saved: number;
8
+ pct: number;
9
+ latency: number;
10
+ algo: string;
11
+ ts: number;
12
+ }
13
+
14
+ export interface CompressionResult {
15
+ og: string;
16
+ comp: string;
17
+ metrics: CompressionMetrics;
18
+ hash: string;
19
+ }
20
+
21
+ export interface CompressionStats {
22
+ total: number;
23
+ ogTok: number;
24
+ compTok: number;
25
+ saved: number;
26
+ avgRatio: number;
27
+ latency: number;
28
+ algos: Record<string, number>;
29
+ updated: number;
30
+ }
31
+
32
+ class MemoryCompressionEngine {
33
+ private stats: CompressionStats = {
34
+ total: 0,
35
+ ogTok: 0,
36
+ compTok: 0,
37
+ saved: 0,
38
+ avgRatio: 0,
39
+ latency: 0,
40
+ algos: {},
41
+ updated: Date.now(),
42
+ };
43
+
44
+ private cache = new Map<string, CompressionResult>();
45
+ private readonly MAX = 500;
46
+ private readonly MS = 0.05;
47
+
48
+ private tok(t: string): number {
49
+ if (!t) return 0;
50
+ const w = t.split(/\s+/).length;
51
+ const c = t.length;
52
+ return Math.ceil(c / 4 + w / 2);
53
+ }
54
+
55
+ private sem(t: string): string {
56
+ if (!t || t.length < 50) return t;
57
+ let c = t;
58
+ const s = c.split(/[.!?]+\s+/);
59
+ const u = s.filter((x, i, a) => {
60
+ if (i === 0) return true;
61
+ const n = x.toLowerCase().trim();
62
+ const p = a[i - 1].toLowerCase().trim();
63
+ return n !== p;
64
+ });
65
+ c = u.join(". ").trim();
66
+ const f = [
67
+ /\b(just|really|very|quite|rather|somewhat|somehow)\b/gi,
68
+ /\b(actually|basically|essentially|literally)\b/gi,
69
+ /\b(I think that|I believe that|It seems that|It appears that)\b/gi,
70
+ /\b(in order to)\b/gi,
71
+ ];
72
+ for (const p of f) c = c.replace(p, "");
73
+ c = c.replace(/\s+/g, " ").trim();
74
+ const r: [RegExp, string][] = [
75
+ [/\bat this point in time\b/gi, "now"],
76
+ [/\bdue to the fact that\b/gi, "because"],
77
+ [/\bin the event that\b/gi, "if"],
78
+ [/\bfor the purpose of\b/gi, "to"],
79
+ [/\bin the near future\b/gi, "soon"],
80
+ [/\ba number of\b/gi, "several"],
81
+ [/\bprior to\b/gi, "before"],
82
+ [/\bsubsequent to\b/gi, "after"],
83
+ ];
84
+ for (const [p, x] of r) c = c.replace(p, x);
85
+ return c;
86
+ }
87
+
88
+ private syn(t: string): string {
89
+ if (!t || t.length < 30) return t;
90
+ let c = t;
91
+ const ct: [RegExp, string][] = [
92
+ [/\bdo not\b/gi, "don't"],
93
+ [/\bcannot\b/gi, "can't"],
94
+ [/\bwill not\b/gi, "won't"],
95
+ [/\bshould not\b/gi, "shouldn't"],
96
+ [/\bwould not\b/gi, "wouldn't"],
97
+ [/\bit is\b/gi, "it's"],
98
+ [/\bthat is\b/gi, "that's"],
99
+ [/\bwhat is\b/gi, "what's"],
100
+ [/\bwho is\b/gi, "who's"],
101
+ [/\bthere is\b/gi, "there's"],
102
+ [/\bhas been\b/gi, "been"],
103
+ [/\bhave been\b/gi, "been"],
104
+ ];
105
+ for (const [p, x] of ct) c = c.replace(p, x);
106
+ c = c.replace(/\b(the|a|an)\s+(\w+),\s+(the|a|an)\s+/gi, "$2, ");
107
+ c = c.replace(/\s*{\s*/g, "{").replace(/\s*}\s*/g, "}");
108
+ c = c.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")");
109
+ c = c.replace(/\s*;\s*/g, ";");
110
+ return c;
111
+ }
112
+
113
+ private agg(t: string): string {
114
+ if (!t) return t;
115
+ let c = this.sem(t);
116
+ c = this.syn(c);
117
+ c = c.replace(/[*_~`#]/g, "");
118
+ c = c.replace(/https?:\/\/(www\.)?([^\/\s]+)(\/[^\s]*)?/gi, "$2");
119
+ const a: [RegExp, string][] = [
120
+ [/\bJavaScript\b/gi, "JS"],
121
+ [/\bTypeScript\b/gi, "TS"],
122
+ [/\bPython\b/gi, "Py"],
123
+ [/\bapplication\b/gi, "app"],
124
+ [/\bfunction\b/gi, "fn"],
125
+ [/\bparameter\b/gi, "param"],
126
+ [/\bargument\b/gi, "arg"],
127
+ [/\breturn\b/gi, "ret"],
128
+ [/\bvariable\b/gi, "var"],
129
+ [/\bconstant\b/gi, "const"],
130
+ [/\bdatabase\b/gi, "db"],
131
+ [/\brepository\b/gi, "repo"],
132
+ [/\benvironment\b/gi, "env"],
133
+ [/\bconfiguration\b/gi, "config"],
134
+ [/\bdocumentation\b/gi, "docs"],
135
+ ];
136
+ for (const [p, x] of a) c = c.replace(p, x);
137
+ c = c.replace(/\n{3,}/g, "\n\n");
138
+ c = c
139
+ .split("\n")
140
+ .map((l) => l.trim())
141
+ .join("\n");
142
+ return c.trim();
143
+ }
144
+
145
+ compress(
146
+ t: string,
147
+ a: "semantic" | "syntactic" | "aggressive" = "semantic",
148
+ ): CompressionResult {
149
+ if (!t) {
150
+ return {
151
+ og: t,
152
+ comp: t,
153
+ metrics: this.empty(a),
154
+ hash: this.hash(t),
155
+ };
156
+ }
157
+
158
+ const k = `${a}:${this.hash(t)}`;
159
+ if (this.cache.has(k)) return this.cache.get(k)!;
160
+
161
+ const ot = this.tok(t);
162
+ let c: string;
163
+
164
+ switch (a) {
165
+ case "semantic":
166
+ c = this.sem(t);
167
+ break;
168
+ case "syntactic":
169
+ c = this.syn(t);
170
+ break;
171
+ case "aggressive":
172
+ c = this.agg(t);
173
+ break;
174
+ default:
175
+ c = t;
176
+ }
177
+
178
+ const ct = this.tok(c);
179
+ const sv = ot - ct;
180
+ const r = ct / ot;
181
+ const p = (sv / ot) * 100;
182
+ const l = sv * this.MS;
183
+
184
+ const m: CompressionMetrics = {
185
+ ogTok: ot,
186
+ compTok: ct,
187
+ ratio: r,
188
+ saved: sv,
189
+ pct: p,
190
+ latency: l,
191
+ algo: a,
192
+ ts: Date.now(),
193
+ };
194
+
195
+ const res: CompressionResult = {
196
+ og: t,
197
+ comp: c,
198
+ metrics: m,
199
+ hash: this.hash(t),
200
+ };
201
+ this.up(m);
202
+ this.store(k, res);
203
+ return res;
204
+ }
205
+
206
+ batch(
207
+ ts: string[],
208
+ a: "semantic" | "syntactic" | "aggressive" = "semantic",
209
+ ): CompressionResult[] {
210
+ return ts.map((t) => this.compress(t, a));
211
+ }
212
+
213
+ auto(t: string): CompressionResult {
214
+ if (!t || t.length < 50) return this.compress(t, "semantic");
215
+ const code =
216
+ /\b(function|const|let|var|def|class|import|export)\b/.test(t);
217
+ const urls = /https?:\/\//.test(t);
218
+ const verb = t.split(/\s+/).length > 100;
219
+ let a: "semantic" | "syntactic" | "aggressive";
220
+ if (code || urls) a = "aggressive";
221
+ else if (verb) a = "semantic";
222
+ else a = "syntactic";
223
+ return this.compress(t, a);
224
+ }
225
+
226
+ getStats(): CompressionStats {
227
+ return { ...this.stats };
228
+ }
229
+
230
+ analyze(t: string): Record<string, CompressionMetrics> {
231
+ const r: Record<string, CompressionMetrics> = {};
232
+ for (const a of ["semantic", "syntactic", "aggressive"] as const) {
233
+ const x = this.compress(t, a);
234
+ r[a] = x.metrics;
235
+ }
236
+ return r;
237
+ }
238
+
239
+ reset(): void {
240
+ this.stats = {
241
+ total: 0,
242
+ ogTok: 0,
243
+ compTok: 0,
244
+ saved: 0,
245
+ avgRatio: 0,
246
+ latency: 0,
247
+ algos: {},
248
+ updated: Date.now(),
249
+ };
250
+ }
251
+
252
+ clear(): void {
253
+ this.cache.clear();
254
+ }
255
+
256
+ private empty(a: string): CompressionMetrics {
257
+ return {
258
+ ogTok: 0,
259
+ compTok: 0,
260
+ ratio: 1,
261
+ saved: 0,
262
+ pct: 0,
263
+ latency: 0,
264
+ algo: a,
265
+ ts: Date.now(),
266
+ };
267
+ }
268
+
269
+ private hash(t: string): string {
270
+ return createHash("md5").update(t).digest("hex").substring(0, 16);
271
+ }
272
+
273
+ private up(m: CompressionMetrics): void {
274
+ this.stats.total++;
275
+ this.stats.ogTok += m.ogTok;
276
+ this.stats.compTok += m.compTok;
277
+ this.stats.saved += m.saved;
278
+ this.stats.latency += m.latency;
279
+ if (this.stats.ogTok > 0)
280
+ this.stats.avgRatio = this.stats.compTok / this.stats.ogTok;
281
+ if (!this.stats.algos[m.algo]) this.stats.algos[m.algo] = 0;
282
+ this.stats.algos[m.algo]++;
283
+ this.stats.updated = Date.now();
284
+ }
285
+
286
+ private store(k: string, r: CompressionResult): void {
287
+ if (this.cache.size >= this.MAX) {
288
+ const f = this.cache.keys().next().value;
289
+ if (f) this.cache.delete(f);
290
+ }
291
+ this.cache.set(k, r);
292
+ }
293
+ }
294
+
295
+ export const compressionEngine = new MemoryCompressionEngine();
296
+ export { MemoryCompressionEngine };