opencode-memory-plugin 0.1.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 (102) hide show
  1. package/README.md +73 -0
  2. package/dist/compression/compressor.d.ts +86 -0
  3. package/dist/compression/compressor.d.ts.map +1 -0
  4. package/dist/compression/compressor.js +142 -0
  5. package/dist/compression/compressor.js.map +1 -0
  6. package/dist/compression/parser.d.ts +73 -0
  7. package/dist/compression/parser.d.ts.map +1 -0
  8. package/dist/compression/parser.js +139 -0
  9. package/dist/compression/parser.js.map +1 -0
  10. package/dist/compression/pipeline.d.ts +73 -0
  11. package/dist/compression/pipeline.d.ts.map +1 -0
  12. package/dist/compression/pipeline.js +205 -0
  13. package/dist/compression/pipeline.js.map +1 -0
  14. package/dist/compression/privacy.d.ts +8 -0
  15. package/dist/compression/privacy.d.ts.map +1 -0
  16. package/dist/compression/privacy.js +30 -0
  17. package/dist/compression/privacy.js.map +1 -0
  18. package/dist/compression/prompts.d.ts +24 -0
  19. package/dist/compression/prompts.d.ts.map +1 -0
  20. package/dist/compression/prompts.js +106 -0
  21. package/dist/compression/prompts.js.map +1 -0
  22. package/dist/compression/quality.d.ts +48 -0
  23. package/dist/compression/quality.d.ts.map +1 -0
  24. package/dist/compression/quality.js +159 -0
  25. package/dist/compression/quality.js.map +1 -0
  26. package/dist/config.d.ts +114 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +265 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/context/generator.d.ts +28 -0
  31. package/dist/context/generator.d.ts.map +1 -0
  32. package/dist/context/generator.js +80 -0
  33. package/dist/context/generator.js.map +1 -0
  34. package/dist/hooks/chat-message.d.ts +14 -0
  35. package/dist/hooks/chat-message.d.ts.map +1 -0
  36. package/dist/hooks/chat-message.js +35 -0
  37. package/dist/hooks/chat-message.js.map +1 -0
  38. package/dist/hooks/compaction.d.ts +13 -0
  39. package/dist/hooks/compaction.d.ts.map +1 -0
  40. package/dist/hooks/compaction.js +22 -0
  41. package/dist/hooks/compaction.js.map +1 -0
  42. package/dist/hooks/events.d.ts +52 -0
  43. package/dist/hooks/events.d.ts.map +1 -0
  44. package/dist/hooks/events.js +138 -0
  45. package/dist/hooks/events.js.map +1 -0
  46. package/dist/hooks/system-transform.d.ts +14 -0
  47. package/dist/hooks/system-transform.d.ts.map +1 -0
  48. package/dist/hooks/system-transform.js +26 -0
  49. package/dist/hooks/system-transform.js.map +1 -0
  50. package/dist/hooks/tool-after.d.ts +26 -0
  51. package/dist/hooks/tool-after.d.ts.map +1 -0
  52. package/dist/hooks/tool-after.js +88 -0
  53. package/dist/hooks/tool-after.js.map +1 -0
  54. package/dist/index.d.ts +11 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +79 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/logger.d.ts +60 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +91 -0
  61. package/dist/logger.js.map +1 -0
  62. package/dist/storage/db.d.ts +22 -0
  63. package/dist/storage/db.d.ts.map +1 -0
  64. package/dist/storage/db.js +198 -0
  65. package/dist/storage/db.js.map +1 -0
  66. package/dist/storage/schema.d.ts +2473 -0
  67. package/dist/storage/schema.d.ts.map +1 -0
  68. package/dist/storage/schema.js +100 -0
  69. package/dist/storage/schema.js.map +1 -0
  70. package/dist/storage/store.d.ts +376 -0
  71. package/dist/storage/store.d.ts.map +1 -0
  72. package/dist/storage/store.js +1025 -0
  73. package/dist/storage/store.js.map +1 -0
  74. package/dist/tools/memory-forget.d.ts +11 -0
  75. package/dist/tools/memory-forget.d.ts.map +1 -0
  76. package/dist/tools/memory-forget.js +249 -0
  77. package/dist/tools/memory-forget.js.map +1 -0
  78. package/dist/tools/memory-get.d.ts +10 -0
  79. package/dist/tools/memory-get.d.ts.map +1 -0
  80. package/dist/tools/memory-get.js +50 -0
  81. package/dist/tools/memory-get.js.map +1 -0
  82. package/dist/tools/memory-search.d.ts +11 -0
  83. package/dist/tools/memory-search.d.ts.map +1 -0
  84. package/dist/tools/memory-search.js +38 -0
  85. package/dist/tools/memory-search.js.map +1 -0
  86. package/dist/tools/memory-stats.d.ts +39 -0
  87. package/dist/tools/memory-stats.d.ts.map +1 -0
  88. package/dist/tools/memory-stats.js +121 -0
  89. package/dist/tools/memory-stats.js.map +1 -0
  90. package/dist/tools/memory-timeline.d.ts +10 -0
  91. package/dist/tools/memory-timeline.d.ts.map +1 -0
  92. package/dist/tools/memory-timeline.js +49 -0
  93. package/dist/tools/memory-timeline.js.map +1 -0
  94. package/dist/types.d.ts +178 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +4 -0
  97. package/dist/types.js.map +1 -0
  98. package/dist/utils.d.ts +130 -0
  99. package/dist/utils.d.ts.map +1 -0
  100. package/dist/utils.js +308 -0
  101. package/dist/utils.js.map +1 -0
  102. package/package.json +36 -0
@@ -0,0 +1,205 @@
1
+ import { buildCompressionPrompt, buildSessionSummaryPrompt } from "./prompts";
2
+ import { parseObservation, parseSessionSummary } from "./parser";
3
+ import { validateObservation } from "./quality";
4
+ import { delay } from "../utils";
5
+ import { MemoryStore } from "../storage/store";
6
+ import { MemoryLogger } from "../logger";
7
+ import { selectCompressionModel } from "../config";
8
+ /**
9
+ * Processes queued tool outputs and turns them into persistent observations.
10
+ */
11
+ export class CompressionPipeline {
12
+ store;
13
+ compressor;
14
+ client;
15
+ directory;
16
+ pluginConfig;
17
+ logger;
18
+ now;
19
+ activeRun = null;
20
+ constructor(store, compressor, client, directory, pluginConfig, logger, now) {
21
+ this.store = store;
22
+ this.compressor = compressor;
23
+ this.client = client;
24
+ this.directory = directory;
25
+ this.pluginConfig = pluginConfig;
26
+ this.logger = logger;
27
+ this.now = now;
28
+ }
29
+ /**
30
+ * Resets orphaned queue rows back to pending.
31
+ *
32
+ * @returns A promise that resolves after recovery.
33
+ */
34
+ async recoverOrphans() {
35
+ const orphaned = await this.store.getOrphanedMessages(this.pluginConfig.orphanThresholdMs);
36
+ await Promise.all(orphaned.map((message) => this.store.updatePendingStatus(message.id, "pending", message.retryCount, message.errorMessage)));
37
+ }
38
+ /**
39
+ * Processes the queue until no pending messages remain.
40
+ *
41
+ * @returns A promise that resolves when the queue is drained.
42
+ */
43
+ async processQueue() {
44
+ if (this.activeRun) {
45
+ return this.activeRun;
46
+ }
47
+ this.activeRun = this.processLoop();
48
+ try {
49
+ await this.activeRun;
50
+ }
51
+ finally {
52
+ this.activeRun = null;
53
+ }
54
+ }
55
+ /**
56
+ * Generates and persists a session summary when there is new activity.
57
+ *
58
+ * @param sessionId - OpenCode session identifier.
59
+ * @returns The saved summary or null.
60
+ */
61
+ async generateSessionSummary(sessionId) {
62
+ const shouldRefresh = await this.store.hasSessionActivityAfterSummary(sessionId);
63
+ if (!shouldRefresh) {
64
+ return this.store.getSessionSummary(sessionId);
65
+ }
66
+ const observations = await this.store.getSessionObservations(sessionId);
67
+ const prompts = await this.store.getSessionUserPrompts(sessionId);
68
+ if (!observations.length && !prompts.length) {
69
+ return null;
70
+ }
71
+ const runtimeConfig = await this.loadRuntimeConfig();
72
+ const model = selectCompressionModel(this.pluginConfig, runtimeConfig);
73
+ const prompt = buildSessionSummaryPrompt(prompts, observations);
74
+ const result = await this.compressor.summarizeSession({
75
+ sessionId,
76
+ prompt,
77
+ model,
78
+ });
79
+ const summary = parseSessionSummary(result.text, {
80
+ id: this.store.createId(),
81
+ projectId: observations[0]?.projectId ?? prompts[0]?.projectId ?? "",
82
+ projectRoot: observations[0]?.projectRoot ?? prompts[0]?.projectRoot ?? "",
83
+ sessionId,
84
+ observationCount: observations.length,
85
+ createdAt: this.now(),
86
+ modelUsed: result.modelUsed,
87
+ });
88
+ await this.store.saveSessionSummary(summary);
89
+ await this.logger.info("Generated session summary", {
90
+ sessionId,
91
+ observationCount: observations.length,
92
+ });
93
+ return summary;
94
+ }
95
+ /**
96
+ * Waits until the queue is empty for a session or the timeout is reached.
97
+ *
98
+ * @param sessionId - OpenCode session identifier.
99
+ * @param timeoutMs - Maximum wait duration.
100
+ * @returns True when the queue was flushed.
101
+ */
102
+ async flushSession(sessionId, timeoutMs) {
103
+ const startedAt = this.now();
104
+ while (this.now() - startedAt < timeoutMs) {
105
+ await this.processQueue();
106
+ const pending = await this.store.countPendingForSession(sessionId);
107
+ if (pending === 0) {
108
+ return true;
109
+ }
110
+ await delay(this.pluginConfig.queuePollIntervalMs);
111
+ }
112
+ return false;
113
+ }
114
+ /**
115
+ * Processes a single queued tool output.
116
+ *
117
+ * @param pendingMessage - Queue item to process.
118
+ * @returns A promise that resolves when processing finishes.
119
+ */
120
+ async processSingle(pendingMessage) {
121
+ const runtimeConfig = await this.loadRuntimeConfig();
122
+ const model = selectCompressionModel(this.pluginConfig, runtimeConfig);
123
+ const startedAt = this.now();
124
+ await this.store.updatePendingStatus(pendingMessage.id, "processing", pendingMessage.retryCount, pendingMessage.errorMessage);
125
+ try {
126
+ const result = await this.compressor.compressObservation({
127
+ pendingMessage,
128
+ prompt: buildCompressionPrompt(pendingMessage),
129
+ model,
130
+ });
131
+ const observation = parseObservation(result.text, pendingMessage, result.modelUsed);
132
+ const qualityResult = validateObservation(observation, pendingMessage.rawContent);
133
+ observation.quality = qualityResult.quality;
134
+ observation.rawFallback = qualityResult.quality === "low"
135
+ ? pendingMessage.rawContent.slice(0, 2_000)
136
+ : null;
137
+ await this.store.saveObservation(observation);
138
+ await this.store.updatePendingStatus(pendingMessage.id, "processed", pendingMessage.retryCount, null);
139
+ await this.logger.info("Compressed memory observation", {
140
+ sessionId: pendingMessage.sessionId,
141
+ toolName: pendingMessage.toolName,
142
+ rawTokenCount: observation.rawTokenCount,
143
+ compressedTokenCount: observation.compressedTokenCount,
144
+ quality: observation.quality,
145
+ durationMs: this.now() - startedAt,
146
+ });
147
+ if (qualityResult.quality !== "high") {
148
+ await this.logger.warn("Observation quality flagged", {
149
+ sessionId: pendingMessage.sessionId,
150
+ toolName: pendingMessage.toolName,
151
+ quality: qualityResult.quality,
152
+ flags: qualityResult.flags,
153
+ });
154
+ }
155
+ }
156
+ catch (error) {
157
+ const retryCount = pendingMessage.retryCount + 1;
158
+ const message = error instanceof Error ? error.message : String(error);
159
+ const status = retryCount >= this.pluginConfig.maxPendingRetries ? "failed" : "pending";
160
+ await this.store.updatePendingStatus(pendingMessage.id, status, retryCount, message);
161
+ if (status === "pending") {
162
+ await delay(backoffForAttempt(retryCount));
163
+ }
164
+ await this.logger.warn("Failed to compress memory observation", {
165
+ sessionId: pendingMessage.sessionId,
166
+ toolName: pendingMessage.toolName,
167
+ retryCount,
168
+ error: message,
169
+ });
170
+ }
171
+ }
172
+ /**
173
+ * Loads the current merged OpenCode config.
174
+ *
175
+ * @returns Runtime config.
176
+ */
177
+ async loadRuntimeConfig() {
178
+ const result = await this.client.config.get({ query: { directory: this.directory } });
179
+ return result.data ?? {};
180
+ }
181
+ /**
182
+ * Drains pending work in batches.
183
+ *
184
+ * @returns A promise that resolves when the queue is empty.
185
+ */
186
+ async processLoop() {
187
+ while (true) {
188
+ const batch = await this.store.getPendingMessages(["pending"], this.pluginConfig.compressionBatchSize);
189
+ if (!batch.length) {
190
+ return;
191
+ }
192
+ await Promise.allSettled(batch.map((message) => this.processSingle(message)));
193
+ }
194
+ }
195
+ }
196
+ /**
197
+ * Computes exponential backoff for queue retries.
198
+ *
199
+ * @param attempt - Retry attempt number.
200
+ * @returns Delay in milliseconds.
201
+ */
202
+ export function backoffForAttempt(attempt) {
203
+ return Math.min(30_000, 1_000 * 2 ** Math.max(0, attempt - 1));
204
+ }
205
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/compression/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Redacts secrets and privacy tags from tool output before persistence.
3
+ *
4
+ * @param content - Raw tool output.
5
+ * @returns Sanitized content safe for compression.
6
+ */
7
+ export declare function stripSensitiveTokens(content: string): string;
8
+ //# sourceMappingURL=privacy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/compression/privacy.ts"],"names":[],"mappings":"AAeA;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAY5D"}
@@ -0,0 +1,30 @@
1
+ const SENSITIVE_PATTERNS = [
2
+ /(?:api[_-]?key|token|secret|password|credential)\s*[:=]\s*["']?[\w\-./+=]+["']?/gi,
3
+ /(?:Bearer|Basic)\s+[A-Za-z0-9\-._~+/]+=*/g,
4
+ /-----BEGIN\s+[\w\s]+-----[\s\S]*?-----END\s+[\w\s]+-----/g,
5
+ /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
6
+ /\bAKIA[0-9A-Z]{16}\b/g,
7
+ /\b(?:eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+)\b/g,
8
+ /(?:postgres|mysql|mongodb(?:\+srv)?|redis):\/\/[^\s]+/gi,
9
+ ];
10
+ const PRIVATE_TAGS = [
11
+ /<private>[\s\S]*?<\/private>/gi,
12
+ /<claude-mem-context>[\s\S]*?<\/claude-mem-context>/gi,
13
+ ];
14
+ /**
15
+ * Redacts secrets and privacy tags from tool output before persistence.
16
+ *
17
+ * @param content - Raw tool output.
18
+ * @returns Sanitized content safe for compression.
19
+ */
20
+ export function stripSensitiveTokens(content) {
21
+ let sanitized = content;
22
+ for (const pattern of PRIVATE_TAGS) {
23
+ sanitized = sanitized.replace(pattern, "[REDACTED]");
24
+ }
25
+ for (const pattern of SENSITIVE_PATTERNS) {
26
+ sanitized = sanitized.replace(pattern, "[REDACTED]");
27
+ }
28
+ return sanitized;
29
+ }
30
+ //# sourceMappingURL=privacy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/compression/privacy.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG;IACzB,mFAAmF;IACnF,2CAA2C;IAC3C,2DAA2D;IAC3D,6CAA6C;IAC7C,uBAAuB;IACvB,8DAA8D;IAC9D,yDAAyD;CAC1D,CAAA;AAED,MAAM,YAAY,GAAG;IACnB,gCAAgC;IAChC,sDAAsD;CACvD,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,IAAI,SAAS,GAAG,OAAO,CAAA;IAEvB,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;QACnC,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACtD,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;QACzC,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACtD,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { Observation, PendingMessage, SessionSummary, UserPromptRecord } from "../types";
2
+ /**
3
+ * Builds the prompt used to compress a raw tool execution into a structured observation.
4
+ *
5
+ * @param pendingMessage - Pending tool output waiting for compression.
6
+ * @returns A prompt string for the compression model.
7
+ */
8
+ export declare function buildCompressionPrompt(pendingMessage: PendingMessage): string;
9
+ /**
10
+ * Builds the prompt used to summarize a session from prompts and observations.
11
+ *
12
+ * @param prompts - Captured prompts from the session.
13
+ * @param observations - Compressed observations from the session.
14
+ * @returns A prompt string for the summarization model.
15
+ */
16
+ export declare function buildSessionSummaryPrompt(prompts: UserPromptRecord[], observations: Observation[]): string;
17
+ /**
18
+ * Formats a summary for compaction or context injection.
19
+ *
20
+ * @param summaries - Summaries to format.
21
+ * @returns Plain text summary block.
22
+ */
23
+ export declare function formatSummaryBlock(summaries: SessionSummary[]): string;
24
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/compression/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAG7F;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,cAAc,GAAG,MAAM,CA8C7E;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gBAAgB,EAAE,EAC3B,YAAY,EAAE,WAAW,EAAE,GAC1B,MAAM,CAmCR;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,MAAM,CAOtE"}
@@ -0,0 +1,106 @@
1
+ import { formatTimestamp, normalizeWhitespace } from "../utils";
2
+ /**
3
+ * Builds the prompt used to compress a raw tool execution into a structured observation.
4
+ *
5
+ * @param pendingMessage - Pending tool output waiting for compression.
6
+ * @returns A prompt string for the compression model.
7
+ */
8
+ export function buildCompressionPrompt(pendingMessage) {
9
+ const metadata = pendingMessage.rawMetadata
10
+ ? JSON.stringify(pendingMessage.rawMetadata, null, 2)
11
+ : "{}";
12
+ return `You are compressing tool execution output for the feature "port claude-mem to OpenCode according to the DAQ".
13
+
14
+ Produce a compact persistent memory observation for a coding agent.
15
+
16
+ Return ONLY valid JSON with this exact shape:
17
+ {
18
+ "title": "short title, max 10 words",
19
+ "subtitle": "one-line context, max 20 words",
20
+ "narrative": "2-3 concise sentences describing what happened and why it matters",
21
+ "facts": ["specific fact"],
22
+ "concepts": ["searchable keyword"],
23
+ "filesInvolved": ["relative/path.ts"],
24
+ "type": "tool_output"
25
+ }
26
+
27
+ Rules:
28
+ - Keep only durable facts, decisions, failures, and code changes.
29
+ - Ignore boilerplate and repeated command noise.
30
+ - Prefer exact file paths when available.
31
+ - In the "concepts" field, include synonyms and related technical concepts that a developer might search for.
32
+ - Example: if output mentions JWT tokens, include related terms like authentication, auth, session, and token rotation.
33
+ - If this is an error, emphasize root cause and resolution clues.
34
+ - If no useful files are present, return an empty array.
35
+ - Only include file paths that appear verbatim in the raw output.
36
+ - Only include facts that are explicitly stated in the raw output.
37
+ - Never infer, extrapolate, or invent details.
38
+ - Never use markdown fences.
39
+
40
+ Tool: ${pendingMessage.toolName}
41
+ Title: ${pendingMessage.title ?? "(none)"}
42
+ Project root: ${pendingMessage.projectRoot}
43
+ Session: ${pendingMessage.sessionId}
44
+ Captured at: ${formatTimestamp(pendingMessage.createdAt)}
45
+
46
+ Metadata:
47
+ ${metadata}
48
+
49
+ Raw tool output:
50
+ ---
51
+ ${pendingMessage.rawContent}
52
+ ---`;
53
+ }
54
+ /**
55
+ * Builds the prompt used to summarize a session from prompts and observations.
56
+ *
57
+ * @param prompts - Captured prompts from the session.
58
+ * @param observations - Compressed observations from the session.
59
+ * @returns A prompt string for the summarization model.
60
+ */
61
+ export function buildSessionSummaryPrompt(prompts, observations) {
62
+ const promptLines = prompts
63
+ .slice(-8)
64
+ .map((prompt, index) => `- Prompt ${index + 1}: ${normalizeWhitespace(prompt.content)}`)
65
+ .join("\n");
66
+ const observationLines = observations
67
+ .map((observation, index) => `[${index + 1}] ${observation.title} | ${observation.type} | ${observation.narrative}`)
68
+ .join("\n");
69
+ return `You are summarizing a coding session for the feature "port claude-mem to OpenCode according to the DAQ".
70
+
71
+ Return ONLY valid JSON with this exact shape:
72
+ {
73
+ "requested": "what the user asked for in 1-2 sentences",
74
+ "investigated": "what was explored or analyzed in 1-2 sentences",
75
+ "learned": "important findings or decisions in 1-2 sentences",
76
+ "completed": "what was actually done in 1-2 sentences",
77
+ "nextSteps": "the most likely follow-up actions in 1-2 sentences"
78
+ }
79
+
80
+ Rules:
81
+ - Be concise and specific.
82
+ - Prefer facts over speculation.
83
+ - If there is no clear next step, say so briefly.
84
+ - Never use markdown fences.
85
+
86
+ User prompts:
87
+ ${promptLines || "- No prompts captured"}
88
+
89
+ Observations:
90
+ ${observationLines || "- No observations captured"}`;
91
+ }
92
+ /**
93
+ * Formats a summary for compaction or context injection.
94
+ *
95
+ * @param summaries - Summaries to format.
96
+ * @returns Plain text summary block.
97
+ */
98
+ export function formatSummaryBlock(summaries) {
99
+ return summaries
100
+ .map((summary) => {
101
+ const parts = [summary.requested, summary.completed, summary.nextSteps].filter(Boolean);
102
+ return `- Session ${summary.sessionId}: ${parts.join(" ")}`;
103
+ })
104
+ .join("\n");
105
+ }
106
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/compression/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,cAA8B;IACnE,MAAM,QAAQ,GAAG,cAAc,CAAC,WAAW;QACzC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,CAAC,CAAC,IAAI,CAAA;IAER,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4BD,cAAc,CAAC,QAAQ;SACtB,cAAc,CAAC,KAAK,IAAI,QAAQ;gBACzB,cAAc,CAAC,WAAW;WAC/B,cAAc,CAAC,SAAS;eACpB,eAAe,CAAC,cAAc,CAAC,SAAS,CAAC;;;EAGtD,QAAQ;;;;EAIR,cAAc,CAAC,UAAU;IACvB,CAAA;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAA2B,EAC3B,YAA2B;IAE3B,MAAM,WAAW,GAAG,OAAO;SACxB,KAAK,CAAC,CAAC,CAAC,CAAC;SACT,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,YAAY,KAAK,GAAG,CAAC,KAAK,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;SACvF,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,gBAAgB,GAAG,YAAY;SAClC,GAAG,CACF,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,CACrB,IAAI,KAAK,GAAG,CAAC,KAAK,WAAW,CAAC,KAAK,MAAM,WAAW,CAAC,IAAI,MAAM,WAAW,CAAC,SAAS,EAAE,CACzF;SACA,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,OAAO;;;;;;;;;;;;;;;;;;EAkBP,WAAW,IAAI,uBAAuB;;;EAGtC,gBAAgB,IAAI,4BAA4B,EAAE,CAAA;AACpD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAA2B;IAC5D,OAAO,SAAS;SACb,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QACf,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACvF,OAAO,aAAa,OAAO,CAAC,SAAS,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;IAC7D,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { Observation, QualityResult } from "../types";
2
+ /**
3
+ * Validates compressed observations against the raw tool output.
4
+ *
5
+ * @param observation - Parsed observation.
6
+ * @param rawContent - Raw tool output content.
7
+ * @returns Quality bucket and validation flags.
8
+ */
9
+ export declare function validateObservation(observation: Observation, rawContent: string): QualityResult;
10
+ /**
11
+ * Checks whether a file path appears exactly in the raw output.
12
+ *
13
+ * @param rawContent - Raw output.
14
+ * @param filePath - Candidate file path.
15
+ * @returns True if present verbatim.
16
+ */
17
+ export declare function containsVerbatim(rawContent: string, filePath: string): boolean;
18
+ /**
19
+ * Checks whether a concept appears in the raw output directly or via known synonyms.
20
+ *
21
+ * @param concept - Candidate concept.
22
+ * @param normalizedRaw - Lowercase raw content.
23
+ * @returns True when concept is grounded.
24
+ */
25
+ export declare function isGroundedConcept(concept: string, normalizedRaw: string): boolean;
26
+ /**
27
+ * Checks whether a fact has enough keyword overlap with the raw content.
28
+ *
29
+ * @param fact - Candidate fact sentence.
30
+ * @param normalizedRaw - Lowercase raw content.
31
+ * @returns True when fact is grounded.
32
+ */
33
+ export declare function isGroundedFact(fact: string, normalizedRaw: string): boolean;
34
+ /**
35
+ * Extracts meaningful keywords from a fact sentence.
36
+ *
37
+ * @param fact - Fact sentence.
38
+ * @returns Distinct normalized keywords.
39
+ */
40
+ export declare function extractFactKeywords(fact: string): string[];
41
+ /**
42
+ * Tokenizes text for grounding checks.
43
+ *
44
+ * @param value - Raw text.
45
+ * @returns Lowercase tokens.
46
+ */
47
+ export declare function tokenizeGroundingText(value: string): string[];
48
+ //# sourceMappingURL=quality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality.d.ts","sourceRoot":"","sources":["../../src/compression/quality.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAY1D;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,CAmE/F;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAM9E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CA6BjF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAQ3E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAO1D;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7D"}
@@ -0,0 +1,159 @@
1
+ const SYNONYM_GROUPS = [
2
+ ["authentication", "auth", "jwt", "token", "session", "oauth", "oidc"],
3
+ ["database", "db", "sqlite", "postgres", "mysql", "migration", "schema"],
4
+ ["typescript", "ts", "javascript", "js"],
5
+ ["error", "exception", "stacktrace", "failure", "failed"],
6
+ ["build", "compile", "typecheck", "lint", "test"],
7
+ ["api", "endpoint", "http", "request", "response"],
8
+ ["cache", "caching", "memoization"],
9
+ ];
10
+ /**
11
+ * Validates compressed observations against the raw tool output.
12
+ *
13
+ * @param observation - Parsed observation.
14
+ * @param rawContent - Raw tool output content.
15
+ * @returns Quality bucket and validation flags.
16
+ */
17
+ export function validateObservation(observation, rawContent) {
18
+ const severeFlags = [];
19
+ const warningFlags = [];
20
+ const normalizedRaw = rawContent.toLowerCase();
21
+ for (const filePath of observation.filesInvolved) {
22
+ if (!containsVerbatim(rawContent, filePath)) {
23
+ severeFlags.push(`hallucinated_path:${filePath}`);
24
+ }
25
+ }
26
+ let ungroundedConcepts = 0;
27
+ for (const concept of observation.concepts) {
28
+ if (!isGroundedConcept(concept, normalizedRaw)) {
29
+ ungroundedConcepts += 1;
30
+ warningFlags.push(`ungrounded_concept:${concept}`);
31
+ }
32
+ }
33
+ let unmatchedFacts = 0;
34
+ for (const fact of observation.facts) {
35
+ if (!isGroundedFact(fact, normalizedRaw)) {
36
+ unmatchedFacts += 1;
37
+ warningFlags.push("ungrounded_fact");
38
+ }
39
+ }
40
+ if (observation.title.trim().toLowerCase() === observation.narrative.trim().toLowerCase()) {
41
+ severeFlags.push("low_integrity:title_equals_narrative");
42
+ }
43
+ if (rawContent.length > 500 && observation.facts.length === 0) {
44
+ warningFlags.push("low_integrity:missing_facts");
45
+ }
46
+ const hallucinationRatio = observation.facts.length > 0 ? unmatchedFacts / observation.facts.length : 0;
47
+ if (hallucinationRatio > 0.6) {
48
+ severeFlags.push("hallucination_ratio_exceeded");
49
+ }
50
+ else if (hallucinationRatio > 0.3) {
51
+ warningFlags.push("partial_hallucination_risk");
52
+ }
53
+ const conceptRatio = observation.concepts.length > 0 ? ungroundedConcepts / observation.concepts.length : 0;
54
+ if (conceptRatio > 0.7) {
55
+ warningFlags.push("concept_grounding_weak");
56
+ }
57
+ const flags = [...severeFlags, ...warningFlags];
58
+ if (severeFlags.length > 0) {
59
+ return {
60
+ quality: "low",
61
+ flags,
62
+ };
63
+ }
64
+ if (warningFlags.length >= 1) {
65
+ return {
66
+ quality: "medium",
67
+ flags,
68
+ };
69
+ }
70
+ return {
71
+ quality: "high",
72
+ flags,
73
+ };
74
+ }
75
+ /**
76
+ * Checks whether a file path appears exactly in the raw output.
77
+ *
78
+ * @param rawContent - Raw output.
79
+ * @param filePath - Candidate file path.
80
+ * @returns True if present verbatim.
81
+ */
82
+ export function containsVerbatim(rawContent, filePath) {
83
+ if (!filePath) {
84
+ return false;
85
+ }
86
+ return rawContent.includes(filePath);
87
+ }
88
+ /**
89
+ * Checks whether a concept appears in the raw output directly or via known synonyms.
90
+ *
91
+ * @param concept - Candidate concept.
92
+ * @param normalizedRaw - Lowercase raw content.
93
+ * @returns True when concept is grounded.
94
+ */
95
+ export function isGroundedConcept(concept, normalizedRaw) {
96
+ const normalizedConcept = concept.toLowerCase().trim();
97
+ if (!normalizedConcept) {
98
+ return false;
99
+ }
100
+ if (normalizedRaw.includes(normalizedConcept)) {
101
+ return true;
102
+ }
103
+ const conceptTokens = tokenizeGroundingText(normalizedConcept).filter((token) => token.length > 3);
104
+ if (conceptTokens.length > 0 && conceptTokens.some((token) => normalizedRaw.includes(token))) {
105
+ return true;
106
+ }
107
+ for (const group of SYNONYM_GROUPS) {
108
+ const matchesGroup = group.includes(normalizedConcept)
109
+ || conceptTokens.some((token) => group.includes(token));
110
+ if (!matchesGroup) {
111
+ continue;
112
+ }
113
+ if (group.some((alias) => normalizedRaw.includes(alias))) {
114
+ return true;
115
+ }
116
+ }
117
+ return false;
118
+ }
119
+ /**
120
+ * Checks whether a fact has enough keyword overlap with the raw content.
121
+ *
122
+ * @param fact - Candidate fact sentence.
123
+ * @param normalizedRaw - Lowercase raw content.
124
+ * @returns True when fact is grounded.
125
+ */
126
+ export function isGroundedFact(fact, normalizedRaw) {
127
+ const keywords = extractFactKeywords(fact);
128
+ if (!keywords.length) {
129
+ return false;
130
+ }
131
+ const matches = keywords.filter((keyword) => normalizedRaw.includes(keyword)).length;
132
+ return matches >= 1;
133
+ }
134
+ /**
135
+ * Extracts meaningful keywords from a fact sentence.
136
+ *
137
+ * @param fact - Fact sentence.
138
+ * @returns Distinct normalized keywords.
139
+ */
140
+ export function extractFactKeywords(fact) {
141
+ const tokens = tokenizeGroundingText(fact)
142
+ .map((token) => token.trim())
143
+ .filter(Boolean)
144
+ .filter((token) => token.length > 4);
145
+ return [...new Set(tokens)].slice(0, 12);
146
+ }
147
+ /**
148
+ * Tokenizes text for grounding checks.
149
+ *
150
+ * @param value - Raw text.
151
+ * @returns Lowercase tokens.
152
+ */
153
+ export function tokenizeGroundingText(value) {
154
+ return value
155
+ .toLowerCase()
156
+ .split(/[^a-z0-9_./-]+/)
157
+ .filter(Boolean);
158
+ }
159
+ //# sourceMappingURL=quality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality.js","sourceRoot":"","sources":["../../src/compression/quality.ts"],"names":[],"mappings":"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"}