@jungjaehoon/mama-core 1.1.2 ā 1.1.3
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/dist/config-loader.d.ts +61 -0
- package/dist/config-loader.js +187 -0
- package/dist/db-adapter/base-adapter.d.ts +76 -0
- package/dist/db-adapter/base-adapter.js +11 -0
- package/dist/db-adapter/index.d.ts +24 -0
- package/dist/db-adapter/index.js +29 -0
- package/dist/db-adapter/sqlite-adapter.d.ts +73 -0
- package/dist/db-adapter/sqlite-adapter.js +330 -0
- package/dist/db-adapter/statement.d.ts +119 -0
- package/dist/db-adapter/statement.js +113 -0
- package/dist/db-manager.d.ts +252 -0
- package/dist/db-manager.js +622 -0
- package/dist/debug-logger.d.ts +32 -0
- package/dist/debug-logger.js +83 -0
- package/dist/decision-formatter.d.ts +172 -0
- package/dist/decision-formatter.js +894 -0
- package/dist/decision-tracker.d.ts +218 -0
- package/dist/decision-tracker.js +531 -0
- package/dist/embedding-cache.d.ts +83 -0
- package/dist/embedding-cache.js +182 -0
- package/dist/embedding-client.d.ts +43 -0
- package/dist/embedding-client.js +132 -0
- package/dist/embedding-server/index.d.ts +65 -0
- package/dist/embedding-server/index.js +397 -0
- package/dist/embedding-server/mobile/auth.d.ts +53 -0
- package/dist/embedding-server/mobile/auth.js +140 -0
- package/dist/embedding-server/mobile/daemon.d.ts +128 -0
- package/dist/embedding-server/mobile/daemon.js +303 -0
- package/dist/embedding-server/mobile/output-parser.d.ts +115 -0
- package/dist/embedding-server/mobile/output-parser.js +241 -0
- package/dist/embedding-server/mobile/session-api.d.ts +57 -0
- package/dist/embedding-server/mobile/session-api.js +261 -0
- package/dist/embedding-server/mobile/session-manager.d.ts +135 -0
- package/dist/embedding-server/mobile/session-manager.js +333 -0
- package/dist/embedding-server/mobile/websocket-handler.d.ts +127 -0
- package/dist/embedding-server/mobile/websocket-handler.js +435 -0
- package/dist/embeddings.d.ts +75 -0
- package/dist/embeddings.js +262 -0
- package/dist/errors.d.ts +131 -0
- package/dist/errors.js +225 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +193 -0
- package/dist/mama-api.d.ts +954 -0
- package/dist/mama-api.js +2210 -0
- package/dist/memory-inject.d.ts +24 -0
- package/dist/memory-inject.js +116 -0
- package/dist/memory-store.d.ts +103 -0
- package/dist/memory-store.js +129 -0
- package/dist/notification-manager.d.ts +7 -0
- package/dist/notification-manager.js +12 -0
- package/dist/ollama-client.d.ts +51 -0
- package/dist/ollama-client.js +308 -0
- package/dist/outcome-tracker.d.ts +165 -0
- package/dist/outcome-tracker.js +315 -0
- package/dist/progress-indicator.d.ts +48 -0
- package/dist/progress-indicator.js +82 -0
- package/dist/query-intent.d.ts +27 -0
- package/dist/query-intent.js +144 -0
- package/dist/relevance-scorer.d.ts +124 -0
- package/dist/relevance-scorer.js +243 -0
- package/dist/test-utils.d.ts +66 -0
- package/dist/test-utils.js +166 -0
- package/dist/tier-validator.d.ts +55 -0
- package/dist/tier-validator.js +216 -0
- package/dist/time-formatter.d.ts +25 -0
- package/dist/time-formatter.js +93 -0
- package/package.json +3 -2
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MAMA (Memory-Augmented MCP Architecture) - Decision Context Formatter
|
|
4
|
+
*
|
|
5
|
+
* Formats decision history with token budget enforcement and top-N selection
|
|
6
|
+
* Tasks: 6.1-6.6, 8.1-8.5 (Context formatting with top-N selection)
|
|
7
|
+
* AC #1: Context under 500 tokens
|
|
8
|
+
* AC #4: Rolling summary for large histories
|
|
9
|
+
* AC #5: Top-N selection with summary
|
|
10
|
+
*
|
|
11
|
+
* @module decision-formatter
|
|
12
|
+
* @version 2.0
|
|
13
|
+
* @date 2025-11-14
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.formatContext = formatContext;
|
|
17
|
+
exports.formatLegacyContext = formatLegacyContext;
|
|
18
|
+
exports.ensureTokenBudget = ensureTokenBudget;
|
|
19
|
+
exports.estimateTokens = estimateTokens;
|
|
20
|
+
exports.formatInstantAnswer = formatInstantAnswer;
|
|
21
|
+
exports.extractQuickAnswer = extractQuickAnswer;
|
|
22
|
+
exports.extractCodeExample = extractCodeExample;
|
|
23
|
+
exports.formatTrustContext = formatTrustContext;
|
|
24
|
+
exports.formatTeaser = formatTeaser;
|
|
25
|
+
exports.formatRecall = formatRecall;
|
|
26
|
+
exports.formatList = formatList;
|
|
27
|
+
const relevance_scorer_js_1 = require("./relevance-scorer.js");
|
|
28
|
+
/**
|
|
29
|
+
* Safely parse JSON string, returning fallback on error
|
|
30
|
+
*/
|
|
31
|
+
function safeParseJson(str, fallback) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(str);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Format decision context for Claude injection with top-N selection
|
|
41
|
+
*
|
|
42
|
+
* Task 6.1-6.2, 8.1-8.5: Build context format template with top-N selection
|
|
43
|
+
* AC #1: Format decision history
|
|
44
|
+
* AC #4: Handle large histories with rolling summary
|
|
45
|
+
* AC #5: Top-N selection with summary (top 3 full detail, rest summarized)
|
|
46
|
+
*
|
|
47
|
+
* Story 014.7.10 - Task 5: Fallback Formatting
|
|
48
|
+
* Tries Instant Answer format first (if trust_context available), falls back to legacy
|
|
49
|
+
*
|
|
50
|
+
* @param decisions - Decision chain (sorted by relevance)
|
|
51
|
+
* @param options - Formatting options
|
|
52
|
+
* @returns Formatted context for injection
|
|
53
|
+
*/
|
|
54
|
+
function formatContext(decisions, options = {}) {
|
|
55
|
+
const { maxTokens = 500, useTopN = decisions.length >= 4, // Auto-enable for 4+ decisions
|
|
56
|
+
topN = 3, useTeaser = true, // New: Use Teaser format to encourage interaction
|
|
57
|
+
} = options;
|
|
58
|
+
if (!decisions || decisions.length === 0) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
// New approach: Teaser format (curiosity-driven)
|
|
62
|
+
// MAMA = Librarian: Shows book previews, Claude decides to read
|
|
63
|
+
if (useTeaser) {
|
|
64
|
+
// Show top 3 results (Google-style)
|
|
65
|
+
const teaserList = formatTeaserList(decisions, topN);
|
|
66
|
+
if (teaserList) {
|
|
67
|
+
return teaserList;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Fallback: Legacy format
|
|
71
|
+
return formatLegacyContext(decisions, { maxTokens, useTopN, topN });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Format decisions using legacy format (no trust context)
|
|
75
|
+
*
|
|
76
|
+
* Story 014.7.10 - Task 5.1: Fallback formatting
|
|
77
|
+
* AC #3: Graceful degradation for decisions without trust_context
|
|
78
|
+
*
|
|
79
|
+
* @param decisions - Decision chain (sorted by relevance)
|
|
80
|
+
* @param options - Formatting options
|
|
81
|
+
* @returns Formatted context (legacy format)
|
|
82
|
+
*/
|
|
83
|
+
function formatLegacyContext(decisions, options = {}) {
|
|
84
|
+
if (!decisions || decisions.length === 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const { maxTokens = 500, useTopN = decisions.length >= 4, topN = 3 } = options;
|
|
88
|
+
// Task 8.1: Use top-N selection for 4+ decisions (AC #5)
|
|
89
|
+
let context;
|
|
90
|
+
if (useTopN && decisions.length > topN) {
|
|
91
|
+
// Task 8.1: Modify to use top-N selection
|
|
92
|
+
context = formatWithTopN(decisions, topN);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Find current decision (superseded_by = NULL or missing)
|
|
96
|
+
const current = decisions.find((d) => !d.superseded_by) || decisions[0];
|
|
97
|
+
const history = decisions.filter((d) => d.id !== current.id);
|
|
98
|
+
// Task 6.2: Build context format template (legacy)
|
|
99
|
+
if (decisions.length <= 3) {
|
|
100
|
+
// Small history: Full details
|
|
101
|
+
context = formatSmallHistory(current, history);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Large history: Rolling summary
|
|
105
|
+
context = formatLargeHistory(current, history);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Task 6.3, 8.4: Ensure token budget stays under 500 tokens
|
|
109
|
+
return ensureTokenBudget(context, maxTokens);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Format with top-N selection
|
|
113
|
+
*
|
|
114
|
+
* Task 8.2-8.3: Full detail for top 3, summary for rest
|
|
115
|
+
* AC #5: Top-N selection with summary
|
|
116
|
+
*
|
|
117
|
+
* @param decisions - All decisions (sorted by relevance)
|
|
118
|
+
* @param topN - Number of decisions for full detail
|
|
119
|
+
* @returns Formatted context
|
|
120
|
+
*/
|
|
121
|
+
function formatWithTopN(decisions, topN) {
|
|
122
|
+
// Use formatTopNContext from relevance-scorer.js
|
|
123
|
+
const { full, summary } = (0, relevance_scorer_js_1.formatTopNContext)(decisions.map((d) => ({
|
|
124
|
+
...d,
|
|
125
|
+
created_at: typeof d.created_at === 'string' ? Date.parse(d.created_at) : d.created_at,
|
|
126
|
+
updated_at: typeof d.updated_at === 'string'
|
|
127
|
+
? Date.parse(d.updated_at)
|
|
128
|
+
: typeof d.updated_at === 'number'
|
|
129
|
+
? d.updated_at
|
|
130
|
+
: undefined,
|
|
131
|
+
reasoning: d.reasoning === null ? undefined : d.reasoning,
|
|
132
|
+
})), topN);
|
|
133
|
+
const current = full[0]; // Highest relevance
|
|
134
|
+
const topic = current?.topic || 'Unknown';
|
|
135
|
+
// Task 8.2: Full detail for top 3 decisions
|
|
136
|
+
let context = `
|
|
137
|
+
š§ DECISION HISTORY: ${topic}
|
|
138
|
+
|
|
139
|
+
Top ${full.length} Most Relevant Decisions:
|
|
140
|
+
`.trim();
|
|
141
|
+
for (let i = 0; i < full.length; i++) {
|
|
142
|
+
const d = full[i];
|
|
143
|
+
const duration = calculateDuration(d.created_at);
|
|
144
|
+
const outcomeEmoji = getOutcomeEmoji(d.outcome ?? null);
|
|
145
|
+
const relevancePercent = Math.round((d.relevanceScore || 0) * 100);
|
|
146
|
+
context += `\n\n${i + 1}. ${d.decision} (${duration}, relevance: ${relevancePercent}%) ${outcomeEmoji}`;
|
|
147
|
+
context += `\n Reasoning: ${d.reasoning || 'N/A'}`;
|
|
148
|
+
if (d.outcome === 'FAILED') {
|
|
149
|
+
context += `\n ā ļø Failure: ${d.failure_reason || 'Unknown reason'}`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Task 8.3: Summary for rest (count, duration, key failures only)
|
|
153
|
+
if (summary && summary.count > 0) {
|
|
154
|
+
context += `\n\nāāāāāāāāāāāāāāāāāāāāāāā`;
|
|
155
|
+
context += `\nHistory: ${summary.count} additional decisions over ${summary.duration_days} days`;
|
|
156
|
+
if (summary.failures && summary.failures.length > 0) {
|
|
157
|
+
context += `\n\nā ļø Other Failures:`;
|
|
158
|
+
for (const failure of summary.failures) {
|
|
159
|
+
context += `\n- ${failure.decision}: ${failure.reason || 'Unknown'}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return context;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Format small decision history (3 or fewer)
|
|
167
|
+
*/
|
|
168
|
+
function formatSmallHistory(current, history) {
|
|
169
|
+
const duration = calculateDuration(current.created_at);
|
|
170
|
+
let context = `
|
|
171
|
+
š§ DECISION HISTORY: ${current.topic}
|
|
172
|
+
|
|
173
|
+
Current: ${current.decision} (${duration}, confidence: ${current.confidence})
|
|
174
|
+
Reasoning: ${current.reasoning || 'N/A'}
|
|
175
|
+
`.trim();
|
|
176
|
+
// Add history details
|
|
177
|
+
if (history.length > 0) {
|
|
178
|
+
context += '\n\nPrevious Decisions:\n';
|
|
179
|
+
for (const decision of history) {
|
|
180
|
+
const durationDays = calculateDurationDays(decision.created_at, decision.updated_at || Date.now());
|
|
181
|
+
const outcomeEmoji = getOutcomeEmoji(decision.outcome ?? null);
|
|
182
|
+
context += `- ${decision.decision} (${durationDays} days) ${outcomeEmoji}\n`;
|
|
183
|
+
if (decision.outcome === 'FAILED') {
|
|
184
|
+
context += ` Reason: ${decision.failure_reason || 'Unknown'}\n`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return context;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Format large decision history (4+ decisions)
|
|
192
|
+
*
|
|
193
|
+
* Task 6.2: Rolling summary for large histories
|
|
194
|
+
* AC #4: Highlight top 3 failures
|
|
195
|
+
*/
|
|
196
|
+
function formatLargeHistory(current, history) {
|
|
197
|
+
// Include current decision in total duration calculation
|
|
198
|
+
const allDecisions = [current, ...history];
|
|
199
|
+
const totalDuration = calculateTotalDuration(allDecisions);
|
|
200
|
+
// Extract failures
|
|
201
|
+
const failures = history.filter((d) => d.outcome === 'FAILED');
|
|
202
|
+
const topFailures = failures.slice(0, 3);
|
|
203
|
+
// Get last evolution
|
|
204
|
+
const lastEvolution = history.length > 0 ? history[0] : null;
|
|
205
|
+
let context = `
|
|
206
|
+
š§ DECISION HISTORY: ${current.topic}
|
|
207
|
+
|
|
208
|
+
Current: ${current.decision} (confidence: ${current.confidence})
|
|
209
|
+
Reasoning: ${current.reasoning || 'N/A'}
|
|
210
|
+
|
|
211
|
+
History: ${history.length + 1} decisions over ${totalDuration}
|
|
212
|
+
`.trim();
|
|
213
|
+
// Add key failures
|
|
214
|
+
if (topFailures.length > 0) {
|
|
215
|
+
context += '\n\nā ļø Key Failures (avoid these):\n';
|
|
216
|
+
for (const failure of topFailures) {
|
|
217
|
+
context += `- ${failure.decision}: ${failure.failure_reason || 'Unknown reason'}\n`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Add last evolution
|
|
221
|
+
if (lastEvolution) {
|
|
222
|
+
context += `\nLast evolution: ${lastEvolution.decision} ā ${current.decision}`;
|
|
223
|
+
if (current.reasoning) {
|
|
224
|
+
const reasonSummary = current.reasoning.substring(0, 100);
|
|
225
|
+
context += ` (${reasonSummary}${current.reasoning.length > 100 ? '...' : ''})`;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return context;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Ensure token budget is enforced
|
|
232
|
+
*
|
|
233
|
+
* Task 6.3-6.5: Token budget enforcement
|
|
234
|
+
* AC #1: Context stays under 500 tokens
|
|
235
|
+
*/
|
|
236
|
+
function ensureTokenBudget(text, maxTokens) {
|
|
237
|
+
// Task 6.4: Token estimation (~1 token per 4 characters)
|
|
238
|
+
const estimatedTokens = estimateTokens(text);
|
|
239
|
+
if (estimatedTokens <= maxTokens) {
|
|
240
|
+
return text;
|
|
241
|
+
}
|
|
242
|
+
// Task 6.5: Truncate to fit budget
|
|
243
|
+
const ratio = maxTokens / estimatedTokens;
|
|
244
|
+
const truncated = text.substring(0, Math.floor(text.length * ratio));
|
|
245
|
+
return truncated + '\n\n... (truncated to fit token budget)';
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Estimate token count from text
|
|
249
|
+
*
|
|
250
|
+
* Task 6.4: Simple token estimation
|
|
251
|
+
* Heuristic: ~1 token per 4 characters
|
|
252
|
+
*/
|
|
253
|
+
function estimateTokens(text) {
|
|
254
|
+
// Task 6.4: ~1 token per 4 characters
|
|
255
|
+
return Math.ceil(text.length / 4);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Calculate human-readable duration
|
|
259
|
+
*/
|
|
260
|
+
function calculateDuration(timestamp) {
|
|
261
|
+
// Handle Unix timestamp (number or numeric string) and ISO 8601 string
|
|
262
|
+
let ts;
|
|
263
|
+
if (typeof timestamp === 'string') {
|
|
264
|
+
// Try parsing as number first (e.g., "1763971277689")
|
|
265
|
+
const num = Number(timestamp);
|
|
266
|
+
ts = isNaN(num) ? Date.parse(timestamp) : num;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
ts = timestamp;
|
|
270
|
+
}
|
|
271
|
+
if (isNaN(ts) || ts === null || ts === undefined) {
|
|
272
|
+
return 'unknown';
|
|
273
|
+
}
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
const diffMs = now - ts;
|
|
276
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
277
|
+
if (diffDays === 0) {
|
|
278
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
279
|
+
if (diffHours === 0) {
|
|
280
|
+
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
281
|
+
return `${diffMins} min${diffMins !== 1 ? 's' : ''} ago`;
|
|
282
|
+
}
|
|
283
|
+
return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;
|
|
284
|
+
}
|
|
285
|
+
return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Calculate duration between two timestamps in days
|
|
289
|
+
*/
|
|
290
|
+
function calculateDurationDays(start, end) {
|
|
291
|
+
let startTs;
|
|
292
|
+
let endTs;
|
|
293
|
+
if (typeof start === 'string') {
|
|
294
|
+
const num = Number(start);
|
|
295
|
+
startTs = isNaN(num) ? Date.parse(start) : num;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
startTs = start;
|
|
299
|
+
}
|
|
300
|
+
if (typeof end === 'string') {
|
|
301
|
+
const num = Number(end);
|
|
302
|
+
endTs = isNaN(num) ? Date.parse(end) : num;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
endTs = end;
|
|
306
|
+
}
|
|
307
|
+
if (isNaN(startTs) || isNaN(endTs)) {
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
const diffMs = endTs - startTs;
|
|
311
|
+
return Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Calculate total duration across decision history
|
|
315
|
+
*/
|
|
316
|
+
function calculateTotalDuration(history) {
|
|
317
|
+
if (history.length === 0) {
|
|
318
|
+
return 'N/A';
|
|
319
|
+
}
|
|
320
|
+
// Convert all timestamps to numbers for comparison
|
|
321
|
+
const timestamps = history
|
|
322
|
+
.map((d) => {
|
|
323
|
+
const created = typeof d.created_at === 'string' ? Date.parse(d.created_at) : d.created_at;
|
|
324
|
+
const updated = d.updated_at
|
|
325
|
+
? typeof d.updated_at === 'string'
|
|
326
|
+
? Date.parse(d.updated_at)
|
|
327
|
+
: d.updated_at
|
|
328
|
+
: created;
|
|
329
|
+
return { created, updated };
|
|
330
|
+
})
|
|
331
|
+
.filter((t) => !isNaN(t.created) && !isNaN(t.updated));
|
|
332
|
+
if (timestamps.length === 0) {
|
|
333
|
+
return 'N/A';
|
|
334
|
+
}
|
|
335
|
+
const earliest = Math.min(...timestamps.map((t) => t.created));
|
|
336
|
+
const latest = Math.max(...timestamps.map((t) => t.updated));
|
|
337
|
+
const durationDays = calculateDurationDays(earliest, latest);
|
|
338
|
+
if (durationDays < 7) {
|
|
339
|
+
return `${durationDays} days`;
|
|
340
|
+
}
|
|
341
|
+
else if (durationDays < 30) {
|
|
342
|
+
const weeks = Math.floor(durationDays / 7);
|
|
343
|
+
return `${weeks} week${weeks !== 1 ? 's' : ''}`;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
const months = Math.floor(durationDays / 30);
|
|
347
|
+
return `${months} month${months !== 1 ? 's' : ''}`;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get emoji for outcome
|
|
352
|
+
*/
|
|
353
|
+
function getOutcomeEmoji(outcome) {
|
|
354
|
+
const emojiMap = {
|
|
355
|
+
SUCCESS: 'ā
',
|
|
356
|
+
FAILED: 'ā',
|
|
357
|
+
PARTIAL: 'ā ļø',
|
|
358
|
+
ONGOING: 'ā³',
|
|
359
|
+
};
|
|
360
|
+
return outcome ? emojiMap[outcome] || '' : '';
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Format context in Claude-friendly Instant Answer format
|
|
364
|
+
*
|
|
365
|
+
* Story 014.7.10: Claude-Friendly Context Formatting
|
|
366
|
+
* AC #1: Instant Answer format with trust components
|
|
367
|
+
*/
|
|
368
|
+
function formatInstantAnswer(decision, options = {}) {
|
|
369
|
+
const { maxTokens = 500 } = options;
|
|
370
|
+
if (!decision) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
// Extract quick answer (first line of decision)
|
|
374
|
+
const quickAnswer = extractQuickAnswer(decision);
|
|
375
|
+
if (!quickAnswer) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
// Extract code example (from reasoning)
|
|
379
|
+
const codeExample = extractCodeExample(decision);
|
|
380
|
+
// Format trust context
|
|
381
|
+
const trustSection = formatTrustContext(typeof decision.trust_context === 'string'
|
|
382
|
+
? parseTrustContext(decision.trust_context)
|
|
383
|
+
: decision.trust_context);
|
|
384
|
+
// Build output
|
|
385
|
+
let output = `ā” INSTANT ANSWER\n\n${quickAnswer}`;
|
|
386
|
+
if (codeExample) {
|
|
387
|
+
output += `\n\n${codeExample}`;
|
|
388
|
+
}
|
|
389
|
+
if (trustSection) {
|
|
390
|
+
output += `\n\n${trustSection}`;
|
|
391
|
+
}
|
|
392
|
+
// Token budget check
|
|
393
|
+
if (estimateTokens(output) > maxTokens) {
|
|
394
|
+
output = truncateToFit(output, maxTokens);
|
|
395
|
+
}
|
|
396
|
+
return output;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Extract quick answer from decision
|
|
400
|
+
*/
|
|
401
|
+
function extractQuickAnswer(decision) {
|
|
402
|
+
if (!decision.decision || typeof decision.decision !== 'string') {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const text = decision.decision.trim();
|
|
406
|
+
if (text.length === 0) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
// Extract first line
|
|
410
|
+
const lines = text.split('\n');
|
|
411
|
+
const firstLine = lines[0].trim();
|
|
412
|
+
// Check if first line contains multiple real sentences
|
|
413
|
+
const sentenceMatch = firstLine.match(/^.+?[.!?](?=\s+[A-Z])/);
|
|
414
|
+
if (sentenceMatch) {
|
|
415
|
+
return sentenceMatch[0].trim();
|
|
416
|
+
}
|
|
417
|
+
// Single sentence or no sentence boundary - use full first line if reasonable
|
|
418
|
+
if (firstLine.length <= 150) {
|
|
419
|
+
return firstLine;
|
|
420
|
+
}
|
|
421
|
+
// First line too long - truncate to 100 chars
|
|
422
|
+
return firstLine.substring(0, 100) + '...';
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Extract code example from reasoning
|
|
426
|
+
*/
|
|
427
|
+
function extractCodeExample(decision) {
|
|
428
|
+
if (!decision.reasoning || typeof decision.reasoning !== 'string') {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
// Match markdown code blocks
|
|
432
|
+
const codeBlockRegex = /```[\s\S]*?```/;
|
|
433
|
+
const match = decision.reasoning.match(codeBlockRegex);
|
|
434
|
+
if (match) {
|
|
435
|
+
return match[0];
|
|
436
|
+
}
|
|
437
|
+
// Check if decision field contains code patterns
|
|
438
|
+
if (decision.decision && typeof decision.decision === 'string') {
|
|
439
|
+
const hasCode = decision.decision.includes('mama.save(') ||
|
|
440
|
+
decision.decision.includes('await ') ||
|
|
441
|
+
decision.decision.includes('=>');
|
|
442
|
+
if (hasCode) {
|
|
443
|
+
return `\`\`\`javascript\n${decision.decision}\n\`\`\``;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Format trust context section
|
|
450
|
+
*
|
|
451
|
+
* Story 014.7.10 AC #2: Trust Context display
|
|
452
|
+
*/
|
|
453
|
+
function formatTrustContext(trustCtx) {
|
|
454
|
+
if (!trustCtx) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
const lines = ['ā'.repeat(40), 'š WHY TRUST THIS?', ''];
|
|
458
|
+
let hasContent = false;
|
|
459
|
+
// 1. Source transparency
|
|
460
|
+
if (trustCtx.source) {
|
|
461
|
+
const { file, line, author, timestamp } = trustCtx.source;
|
|
462
|
+
const timeAgo = timestamp ? calculateDuration(timestamp) : 'unknown';
|
|
463
|
+
lines.push(`š Source: ${file}:${line} (${timeAgo}, by ${author})`);
|
|
464
|
+
hasContent = true;
|
|
465
|
+
}
|
|
466
|
+
// 2. Causality
|
|
467
|
+
if (trustCtx.causality && trustCtx.causality.impact) {
|
|
468
|
+
lines.push(`š Reason: ${trustCtx.causality.impact}`);
|
|
469
|
+
hasContent = true;
|
|
470
|
+
}
|
|
471
|
+
// 3. Verifiability
|
|
472
|
+
if (trustCtx.verification) {
|
|
473
|
+
const { test_file, result } = trustCtx.verification;
|
|
474
|
+
const status = result === 'success' ? 'passed' : result;
|
|
475
|
+
lines.push(`ā
Verified: ${test_file} ${status}`);
|
|
476
|
+
hasContent = true;
|
|
477
|
+
}
|
|
478
|
+
// 4. Context relevance
|
|
479
|
+
if (trustCtx.context_match && trustCtx.context_match.user_intent) {
|
|
480
|
+
lines.push(`šÆ Applies to: ${trustCtx.context_match.user_intent}`);
|
|
481
|
+
hasContent = true;
|
|
482
|
+
}
|
|
483
|
+
// 5. Track record
|
|
484
|
+
if (trustCtx.track_record) {
|
|
485
|
+
const { recent_successes, recent_failures } = trustCtx.track_record;
|
|
486
|
+
const successCount = recent_successes?.length || 0;
|
|
487
|
+
const failureCount = recent_failures?.length || 0;
|
|
488
|
+
const total = successCount + failureCount;
|
|
489
|
+
if (total > 0) {
|
|
490
|
+
lines.push(`š Track record: ${successCount}/${total} recent successes`);
|
|
491
|
+
hasContent = true;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!hasContent) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
lines.push('ā'.repeat(40));
|
|
498
|
+
return lines.join('\n');
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Truncate output to fit token budget
|
|
502
|
+
*/
|
|
503
|
+
function truncateToFit(output, maxTokens) {
|
|
504
|
+
const sections = output.split('\n\n');
|
|
505
|
+
const quickAnswer = sections[0];
|
|
506
|
+
let result = quickAnswer;
|
|
507
|
+
let remainingTokens = maxTokens - estimateTokens(result);
|
|
508
|
+
// Try to add code example
|
|
509
|
+
const codeIndex = sections.findIndex((s) => s.startsWith('```'));
|
|
510
|
+
if (codeIndex > 0) {
|
|
511
|
+
const codeSection = sections[codeIndex];
|
|
512
|
+
const codeTokens = estimateTokens(codeSection);
|
|
513
|
+
if (codeTokens <= remainingTokens) {
|
|
514
|
+
result += '\n\n' + codeSection;
|
|
515
|
+
remainingTokens -= codeTokens;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// Try to add trust section (trimmed if needed)
|
|
519
|
+
const trustIndex = sections.findIndex((s) => s.startsWith('ā'));
|
|
520
|
+
if (trustIndex > 0 && remainingTokens > 50) {
|
|
521
|
+
const trustSection = sections[trustIndex];
|
|
522
|
+
const trustTokens = estimateTokens(trustSection);
|
|
523
|
+
if (trustTokens <= remainingTokens) {
|
|
524
|
+
result += '\n\n' + trustSection;
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
const trustLines = trustSection.split('\n');
|
|
528
|
+
let trimmed = trustLines[0] + '\n' + trustLines[1] + '\n';
|
|
529
|
+
for (let i = 2; i < trustLines.length - 1; i++) {
|
|
530
|
+
const line = trustLines[i] + '\n';
|
|
531
|
+
if (estimateTokens(trimmed + line) <= remainingTokens - 10) {
|
|
532
|
+
trimmed += line;
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
trimmed += trustLines[trustLines.length - 1];
|
|
539
|
+
result += '\n\n' + trimmed;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Format multiple decisions as Google-style search results
|
|
546
|
+
*/
|
|
547
|
+
function formatTeaserList(decisions, topN = 3) {
|
|
548
|
+
if (!decisions || decisions.length === 0) {
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
const topDecisions = decisions.slice(0, topN);
|
|
552
|
+
const count = topDecisions.length;
|
|
553
|
+
let output = `š” MAMA found ${count} related topic${count > 1 ? 's' : ''}:\n`;
|
|
554
|
+
for (let i = 0; i < topDecisions.length; i++) {
|
|
555
|
+
const d = topDecisions[i];
|
|
556
|
+
const relevance = Math.round((d.similarity || d.confidence || 0) * 100);
|
|
557
|
+
// Preview (max 60 chars)
|
|
558
|
+
const preview = d.decision.length > 60 ? d.decision.substring(0, 60) + '...' : d.decision;
|
|
559
|
+
output += `\n${i + 1}. ${d.topic} (${relevance}% match)`;
|
|
560
|
+
output += `\n "${preview}"`;
|
|
561
|
+
// Recency metadata (NEW - Gaussian Decay)
|
|
562
|
+
if (d.recency_age_days !== undefined && d.created_at) {
|
|
563
|
+
const timeAgo = calculateDuration(d.created_at);
|
|
564
|
+
const recencyScore = d.recency_score ? Math.round(d.recency_score * 100) : null;
|
|
565
|
+
const finalScore = d.final_score ? Math.round(d.final_score * 100) : null;
|
|
566
|
+
output += `\n ā° ${timeAgo}`;
|
|
567
|
+
if (recencyScore !== null && finalScore !== null) {
|
|
568
|
+
output += ` | Recency: ${recencyScore}% | Final: ${finalScore}%`;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
output += `\n š mama.recall('${d.topic}')`;
|
|
572
|
+
if (i < topDecisions.length - 1) {
|
|
573
|
+
output += '\n';
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return output;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Format decision as curiosity-inducing teaser
|
|
580
|
+
*/
|
|
581
|
+
function formatTeaser(decision) {
|
|
582
|
+
if (!decision) {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
const timeAgo = calculateDuration(decision.created_at);
|
|
586
|
+
// Extract preview (first 60 chars)
|
|
587
|
+
const preview = decision.decision.length > 60 ? decision.decision.substring(0, 60) + '...' : decision.decision;
|
|
588
|
+
// Extract files from trust_context or show generic
|
|
589
|
+
let files = 'Multiple files';
|
|
590
|
+
const trustCtx = typeof decision.trust_context === 'string'
|
|
591
|
+
? parseTrustContext(decision.trust_context)
|
|
592
|
+
: decision.trust_context;
|
|
593
|
+
if (trustCtx?.source?.file) {
|
|
594
|
+
const fileStr = trustCtx.source.file;
|
|
595
|
+
const fileList = fileStr.split(',').map((f) => f.trim());
|
|
596
|
+
if (fileList.length === 1) {
|
|
597
|
+
files = fileList[0];
|
|
598
|
+
}
|
|
599
|
+
else if (fileList.length === 2) {
|
|
600
|
+
files = fileList.join(', ');
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
files = `${fileList[0]}, ${fileList[1]} (+${fileList.length - 2})`;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const teaser = `
|
|
607
|
+
š” MAMA has related info
|
|
608
|
+
|
|
609
|
+
š Topic: ${decision.topic}
|
|
610
|
+
š Preview: "${preview}"
|
|
611
|
+
š Files: ${files}
|
|
612
|
+
ā° Updated: ${timeAgo}
|
|
613
|
+
|
|
614
|
+
š Read more: mama.recall('${decision.topic}')
|
|
615
|
+
`.trim();
|
|
616
|
+
return teaser;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Format mama.recall() results in readable format
|
|
620
|
+
*/
|
|
621
|
+
function formatRecall(decisions, semanticEdges = null) {
|
|
622
|
+
if (!decisions || decisions.length === 0) {
|
|
623
|
+
return 'ā No decisions found';
|
|
624
|
+
}
|
|
625
|
+
// Single decision: full detail
|
|
626
|
+
if (decisions.length === 1) {
|
|
627
|
+
return formatSingleDecision(decisions[0]);
|
|
628
|
+
}
|
|
629
|
+
// Multiple decisions: history view
|
|
630
|
+
return formatDecisionHistory(decisions, semanticEdges);
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Format single decision with full detail
|
|
634
|
+
*/
|
|
635
|
+
function formatSingleDecision(decision) {
|
|
636
|
+
const timeAgo = calculateDuration(decision.created_at);
|
|
637
|
+
const confidencePercent = Math.round((decision.confidence || 0) * 100);
|
|
638
|
+
const outcomeEmoji = getOutcomeEmoji(decision.outcome ?? null);
|
|
639
|
+
const outcomeText = decision.outcome || 'Not yet tracked';
|
|
640
|
+
let output = `
|
|
641
|
+
š Decision: ${decision.topic}
|
|
642
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
643
|
+
|
|
644
|
+
${decision.reasoning || decision.decision}
|
|
645
|
+
`.trim();
|
|
646
|
+
// Metadata section
|
|
647
|
+
output += `\n\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā`;
|
|
648
|
+
output += `\nš Confidence: ${confidencePercent}%`;
|
|
649
|
+
output += `\nā° Created: ${timeAgo}`;
|
|
650
|
+
output += `\n${outcomeEmoji} Outcome: ${outcomeText}`;
|
|
651
|
+
if (decision.outcome === 'FAILED' && decision.failure_reason) {
|
|
652
|
+
output += `\nā ļø Failure reason: ${decision.failure_reason}`;
|
|
653
|
+
}
|
|
654
|
+
// Narrative fields section (Story 2.2)
|
|
655
|
+
if (decision.evidence || decision.alternatives || decision.risks) {
|
|
656
|
+
output += '\n\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā';
|
|
657
|
+
output += '\nš Narrative Details\n';
|
|
658
|
+
if (decision.evidence) {
|
|
659
|
+
const evidenceList = Array.isArray(decision.evidence)
|
|
660
|
+
? decision.evidence
|
|
661
|
+
: typeof decision.evidence === 'string'
|
|
662
|
+
? safeParseJson(decision.evidence, [decision.evidence])
|
|
663
|
+
: [decision.evidence];
|
|
664
|
+
if (evidenceList.length > 0) {
|
|
665
|
+
output += '\nš Evidence:';
|
|
666
|
+
evidenceList.forEach((item, idx) => {
|
|
667
|
+
output += `\n ${idx + 1}. ${item}`;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (decision.alternatives) {
|
|
672
|
+
const altList = Array.isArray(decision.alternatives)
|
|
673
|
+
? decision.alternatives
|
|
674
|
+
: typeof decision.alternatives === 'string'
|
|
675
|
+
? safeParseJson(decision.alternatives, [decision.alternatives])
|
|
676
|
+
: [decision.alternatives];
|
|
677
|
+
if (altList.length > 0) {
|
|
678
|
+
output += '\n\nš Alternatives Considered:';
|
|
679
|
+
altList.forEach((item, idx) => {
|
|
680
|
+
output += `\n ${idx + 1}. ${item}`;
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (decision.risks) {
|
|
685
|
+
output += `\n\nā ļø Risks: ${decision.risks}`;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// Trust context section (if available)
|
|
689
|
+
const trustCtx = typeof decision.trust_context === 'string'
|
|
690
|
+
? parseTrustContext(decision.trust_context)
|
|
691
|
+
: decision.trust_context;
|
|
692
|
+
if (trustCtx) {
|
|
693
|
+
output += '\n\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā';
|
|
694
|
+
output += '\nš Trust Context\n';
|
|
695
|
+
if (trustCtx.source) {
|
|
696
|
+
const { file, line, author } = trustCtx.source;
|
|
697
|
+
output += `\nš Source: ${file}${line ? ':' + line : ''} (by ${author || 'unknown'})`;
|
|
698
|
+
}
|
|
699
|
+
if (trustCtx.causality?.impact) {
|
|
700
|
+
output += `\nš Impact: ${trustCtx.causality.impact}`;
|
|
701
|
+
}
|
|
702
|
+
if (trustCtx.verification) {
|
|
703
|
+
const { test_file, result } = trustCtx.verification;
|
|
704
|
+
const status = result === 'success' ? 'ā
passed' : `ā ļø ${result}`;
|
|
705
|
+
output += `\n${status}: ${test_file || 'Verified'}`;
|
|
706
|
+
}
|
|
707
|
+
if (trustCtx.track_record) {
|
|
708
|
+
const { success_rate, sample_size } = trustCtx.track_record;
|
|
709
|
+
if (sample_size && sample_size > 0) {
|
|
710
|
+
const rate = Math.round((success_rate || 0) * 100);
|
|
711
|
+
output += `\nš Track record: ${rate}% success (${sample_size} samples)`;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return output;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Format decision history (multiple decisions)
|
|
719
|
+
*/
|
|
720
|
+
function formatDecisionHistory(decisions, semanticEdges = null) {
|
|
721
|
+
const topic = decisions[0].topic;
|
|
722
|
+
const latest = decisions[0];
|
|
723
|
+
const older = decisions.slice(1);
|
|
724
|
+
let output = `
|
|
725
|
+
š Decision History: ${topic}
|
|
726
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
727
|
+
|
|
728
|
+
Latest Decision (${calculateDuration(latest.created_at)}):
|
|
729
|
+
${latest.decision}
|
|
730
|
+
`.trim();
|
|
731
|
+
// Show brief reasoning if available
|
|
732
|
+
if (latest.reasoning) {
|
|
733
|
+
const briefReasoning = latest.reasoning.split('\n')[0].substring(0, 150);
|
|
734
|
+
output += `\n\nReasoning: ${briefReasoning}${latest.reasoning.length > 150 ? '...' : ''}`;
|
|
735
|
+
}
|
|
736
|
+
output += `\n\nConfidence: ${Math.round((latest.confidence || 0) * 100)}%`;
|
|
737
|
+
// Show narrative fields for latest decision (Story 2.2)
|
|
738
|
+
if (latest.evidence || latest.alternatives || latest.risks) {
|
|
739
|
+
if (latest.evidence) {
|
|
740
|
+
const evidenceList = Array.isArray(latest.evidence)
|
|
741
|
+
? latest.evidence
|
|
742
|
+
: typeof latest.evidence === 'string'
|
|
743
|
+
? safeParseJson(latest.evidence, [latest.evidence])
|
|
744
|
+
: [latest.evidence];
|
|
745
|
+
if (evidenceList.length > 0) {
|
|
746
|
+
output += '\n\nš Evidence:';
|
|
747
|
+
evidenceList.slice(0, 3).forEach((item, idx) => {
|
|
748
|
+
output += `\n ${idx + 1}. ${item}`;
|
|
749
|
+
});
|
|
750
|
+
if (evidenceList.length > 3) {
|
|
751
|
+
output += `\n ... and ${evidenceList.length - 3} more`;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (latest.alternatives) {
|
|
756
|
+
const altList = Array.isArray(latest.alternatives)
|
|
757
|
+
? latest.alternatives
|
|
758
|
+
: typeof latest.alternatives === 'string'
|
|
759
|
+
? safeParseJson(latest.alternatives, [latest.alternatives])
|
|
760
|
+
: [latest.alternatives];
|
|
761
|
+
if (altList.length > 0) {
|
|
762
|
+
output += '\n\nš Alternatives: ';
|
|
763
|
+
output += altList.slice(0, 2).join('; ');
|
|
764
|
+
if (altList.length > 2) {
|
|
765
|
+
output += `... (+${altList.length - 2} more)`;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
if (latest.risks) {
|
|
770
|
+
const risksPreview = latest.risks.length > 100 ? latest.risks.substring(0, 100) + '...' : latest.risks;
|
|
771
|
+
output += `\n\nā ļø Risks: ${risksPreview}`;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// Show older decisions (supersedes chain)
|
|
775
|
+
if (older.length > 0) {
|
|
776
|
+
output += '\n\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā';
|
|
777
|
+
output += `\nPrevious Decisions (${older.length}):\n`;
|
|
778
|
+
for (let i = 0; i < Math.min(older.length, 5); i++) {
|
|
779
|
+
const d = older[i];
|
|
780
|
+
const timeAgo = calculateDuration(d.created_at);
|
|
781
|
+
const emoji = getOutcomeEmoji(d.outcome ?? null);
|
|
782
|
+
output += `\n${i + 2}. ${d.decision} (${timeAgo}) ${emoji}`;
|
|
783
|
+
if (d.outcome === 'FAILED' && d.failure_reason) {
|
|
784
|
+
output += `\n ā ļø ${d.failure_reason}`;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (older.length > 5) {
|
|
788
|
+
output += `\n\n... and ${older.length - 5} more`;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
// Show semantic edges (related decisions)
|
|
792
|
+
if (semanticEdges) {
|
|
793
|
+
const totalEdges = (semanticEdges.refines?.length || 0) +
|
|
794
|
+
(semanticEdges.refined_by?.length || 0) +
|
|
795
|
+
(semanticEdges.contradicts?.length || 0) +
|
|
796
|
+
(semanticEdges.contradicted_by?.length || 0);
|
|
797
|
+
if (totalEdges > 0) {
|
|
798
|
+
output += '\n\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā';
|
|
799
|
+
output += `\nš Related Decisions (${totalEdges}):\n`;
|
|
800
|
+
// Refines (builds upon)
|
|
801
|
+
if (semanticEdges.refines && semanticEdges.refines.length > 0) {
|
|
802
|
+
output += '\n⨠Refines (builds upon):';
|
|
803
|
+
semanticEdges.refines.slice(0, 3).forEach((e) => {
|
|
804
|
+
const preview = e.decision.substring(0, 60);
|
|
805
|
+
output += `\n ⢠${e.topic}: ${preview}${e.decision.length > 60 ? '...' : ''}`;
|
|
806
|
+
});
|
|
807
|
+
if (semanticEdges.refines.length > 3) {
|
|
808
|
+
output += `\n ... and ${semanticEdges.refines.length - 3} more`;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
// Refined by (later improvements)
|
|
812
|
+
if (semanticEdges.refined_by && semanticEdges.refined_by.length > 0) {
|
|
813
|
+
output += '\n\nš Refined by (later improvements):';
|
|
814
|
+
semanticEdges.refined_by.slice(0, 3).forEach((e) => {
|
|
815
|
+
const preview = e.decision.substring(0, 60);
|
|
816
|
+
output += `\n ⢠${e.topic}: ${preview}${e.decision.length > 60 ? '...' : ''}`;
|
|
817
|
+
});
|
|
818
|
+
if (semanticEdges.refined_by.length > 3) {
|
|
819
|
+
output += `\n ... and ${semanticEdges.refined_by.length - 3} more`;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
// Contradicts
|
|
823
|
+
if (semanticEdges.contradicts && semanticEdges.contradicts.length > 0) {
|
|
824
|
+
output += '\n\nā” Contradicts:';
|
|
825
|
+
semanticEdges.contradicts.forEach((e) => {
|
|
826
|
+
const preview = e.decision.substring(0, 60);
|
|
827
|
+
output += `\n ⢠${e.topic}: ${preview}${e.decision.length > 60 ? '...' : ''}`;
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
// Contradicted by
|
|
831
|
+
if (semanticEdges.contradicted_by && semanticEdges.contradicted_by.length > 0) {
|
|
832
|
+
output += '\n\nā Contradicted by:';
|
|
833
|
+
semanticEdges.contradicted_by.forEach((e) => {
|
|
834
|
+
const preview = e.decision.substring(0, 60);
|
|
835
|
+
output += `\n ⢠${e.topic}: ${preview}${e.decision.length > 60 ? '...' : ''}`;
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
output += '\n\nš” Tip: Review individual decisions for full context';
|
|
841
|
+
return output;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Parse trust_context (might be JSON string)
|
|
845
|
+
*/
|
|
846
|
+
function parseTrustContext(trustContext) {
|
|
847
|
+
if (!trustContext) {
|
|
848
|
+
return null;
|
|
849
|
+
}
|
|
850
|
+
// Already parsed
|
|
851
|
+
if (typeof trustContext === 'object') {
|
|
852
|
+
return trustContext;
|
|
853
|
+
}
|
|
854
|
+
// Parse JSON string
|
|
855
|
+
if (typeof trustContext === 'string') {
|
|
856
|
+
try {
|
|
857
|
+
return JSON.parse(trustContext);
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Format recent decisions list (all topics, chronological)
|
|
867
|
+
*/
|
|
868
|
+
function formatList(decisions, options = {}) {
|
|
869
|
+
const { limit = 20 } = options;
|
|
870
|
+
if (!decisions || decisions.length === 0) {
|
|
871
|
+
return 'ā No decisions found';
|
|
872
|
+
}
|
|
873
|
+
// Limit results
|
|
874
|
+
const items = decisions.slice(0, limit);
|
|
875
|
+
let output = `š Recent Decisions (Last ${items.length})\n`;
|
|
876
|
+
output += 'ā'.repeat(60) + '\n';
|
|
877
|
+
for (let i = 0; i < items.length; i++) {
|
|
878
|
+
const d = items[i];
|
|
879
|
+
const timeAgo = calculateDuration(d.created_at);
|
|
880
|
+
const type = d.user_involvement === 'approved' ? 'š¤ User' : 'š¤ Assistant';
|
|
881
|
+
const status = d.outcome ? getOutcomeEmoji(d.outcome) + ' ' + d.outcome : 'ā³ Pending';
|
|
882
|
+
const confidence = Math.round((d.confidence || 0) * 100);
|
|
883
|
+
// Preview (max 60 chars)
|
|
884
|
+
const preview = d.decision.length > 60 ? d.decision.substring(0, 60) + '...' : d.decision;
|
|
885
|
+
output += `\n${i + 1}. [${timeAgo}] ${type}\n`;
|
|
886
|
+
output += ` š ${d.topic}\n`;
|
|
887
|
+
output += ` š” ${preview}\n`;
|
|
888
|
+
output += ` š ${confidence}% confidence | ${status}\n`;
|
|
889
|
+
}
|
|
890
|
+
output += '\n' + 'ā'.repeat(60);
|
|
891
|
+
output += `\nš” Tip: Use mama.recall('topic') for full details\n`;
|
|
892
|
+
return output;
|
|
893
|
+
}
|
|
894
|
+
//# sourceMappingURL=decision-formatter.js.map
|