@girardmedia/bootspring 1.2.0 → 2.0.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.
Files changed (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
@@ -15,7 +15,18 @@ const crypto = require('crypto');
15
15
  // Paths
16
16
  const memoryDir = __dirname;
17
17
  const decisionsFile = path.join(memoryDir, 'decisions.jsonl');
18
+ const outcomesFile = path.join(memoryDir, 'outcomes.jsonl');
18
19
  const patternsFile = path.join(memoryDir, 'patterns.json');
20
+ const indexFile = path.join(memoryDir, 'decisions.index.json');
21
+
22
+ // Compaction thresholds
23
+ const COMPACTION_THRESHOLD_COUNT = 100; // Compact after this many outcomes
24
+ const COMPACTION_THRESHOLD_BYTES = 1024 * 1024; // Compact when outcomes file exceeds 1MB
25
+
26
+ // In-memory decision index for O(1) lookups
27
+ // Maps decisionId -> { lineOffset, timestamp, type }
28
+ let decisionIndex = null;
29
+ let indexDirty = false;
19
30
 
20
31
  /**
21
32
  * Decision types that can be tracked
@@ -75,6 +86,258 @@ function ensureDecisionsFile() {
75
86
  }
76
87
  }
77
88
 
89
+ /**
90
+ * Load the decision index from disk or rebuild from decisions file
91
+ * The index provides O(1) lookups for decision existence checks
92
+ * @returns {Map} The decision index
93
+ */
94
+ function loadDecisionIndex() {
95
+ if (decisionIndex !== null) {
96
+ return decisionIndex;
97
+ }
98
+
99
+ decisionIndex = new Map();
100
+
101
+ // Try to load from index file first
102
+ if (fs.existsSync(indexFile)) {
103
+ try {
104
+ const indexData = JSON.parse(fs.readFileSync(indexFile, 'utf-8'));
105
+ if (indexData.version === '1.0.0' && indexData.entries) {
106
+ for (const [id, meta] of Object.entries(indexData.entries)) {
107
+ decisionIndex.set(id, meta);
108
+ }
109
+ return decisionIndex;
110
+ }
111
+ } catch {
112
+ // Index corrupted, will rebuild
113
+ }
114
+ }
115
+
116
+ // Rebuild index from decisions file
117
+ rebuildIndex();
118
+ return decisionIndex;
119
+ }
120
+
121
+ /**
122
+ * Rebuild the decision index from the decisions file
123
+ * Called when index is missing/corrupted or after compaction
124
+ */
125
+ function rebuildIndex() {
126
+ decisionIndex = new Map();
127
+ ensureDecisionsFile();
128
+
129
+ const content = fs.readFileSync(decisionsFile, 'utf-8').trim();
130
+ if (!content) return;
131
+
132
+ let lineOffset = 0;
133
+ content.split('\n').filter(Boolean).forEach((line, idx) => {
134
+ try {
135
+ const d = JSON.parse(line);
136
+ decisionIndex.set(d.id, {
137
+ lineOffset: idx,
138
+ timestamp: d.timestamp,
139
+ type: d.type,
140
+ confidence: d.confidence
141
+ });
142
+ } catch {
143
+ // Skip malformed lines
144
+ }
145
+ lineOffset++;
146
+ });
147
+
148
+ // Save the rebuilt index
149
+ saveIndex();
150
+ }
151
+
152
+ /**
153
+ * Save the decision index to disk
154
+ */
155
+ function saveIndex() {
156
+ const indexData = {
157
+ version: '1.0.0',
158
+ updated: getTimestamp(),
159
+ count: decisionIndex.size,
160
+ entries: Object.fromEntries(decisionIndex)
161
+ };
162
+ fs.writeFileSync(indexFile, JSON.stringify(indexData));
163
+ indexDirty = false;
164
+ }
165
+
166
+ /**
167
+ * Add a decision to the index
168
+ * @param {string} id Decision ID
169
+ * @param {object} meta Decision metadata
170
+ */
171
+ function addToIndex(id, meta) {
172
+ loadDecisionIndex();
173
+ decisionIndex.set(id, meta);
174
+ indexDirty = true;
175
+
176
+ // Batch index saves - only save every 10 additions or on explicit save
177
+ if (decisionIndex.size % 10 === 0) {
178
+ saveIndex();
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Check if a decision exists in the index (O(1) lookup)
184
+ * @param {string} decisionId Decision ID
185
+ * @returns {boolean} Whether the decision exists
186
+ */
187
+ function decisionExists(decisionId) {
188
+ loadDecisionIndex();
189
+ return decisionIndex.has(decisionId);
190
+ }
191
+
192
+ /**
193
+ * Get decision metadata from index (O(1) lookup)
194
+ * @param {string} decisionId Decision ID
195
+ * @returns {object|null} Decision metadata or null
196
+ */
197
+ function getDecisionMeta(decisionId) {
198
+ loadDecisionIndex();
199
+ return decisionIndex.get(decisionId) || null;
200
+ }
201
+
202
+ /**
203
+ * Invalidate the in-memory index (forces reload on next access)
204
+ */
205
+ function invalidateIndex() {
206
+ decisionIndex = null;
207
+ indexDirty = false;
208
+ }
209
+
210
+ /**
211
+ * Flush any pending index changes to disk
212
+ */
213
+ function flushIndex() {
214
+ if (indexDirty && decisionIndex !== null) {
215
+ saveIndex();
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Ensure outcomes file exists
221
+ */
222
+ function ensureOutcomesFile() {
223
+ if (!fs.existsSync(memoryDir)) {
224
+ fs.mkdirSync(memoryDir, { recursive: true });
225
+ }
226
+ if (!fs.existsSync(outcomesFile)) {
227
+ fs.writeFileSync(outcomesFile, '');
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Read all outcomes from the outcomes file
233
+ * @returns {Map} Map of decisionId -> outcome
234
+ */
235
+ function readOutcomes() {
236
+ ensureOutcomesFile();
237
+ const content = fs.readFileSync(outcomesFile, 'utf-8').trim();
238
+ if (!content) return new Map();
239
+
240
+ const outcomes = new Map();
241
+ content.split('\n').filter(Boolean).forEach(line => {
242
+ try {
243
+ const entry = JSON.parse(line);
244
+ outcomes.set(entry.decisionId, entry);
245
+ } catch {
246
+ // Skip malformed lines
247
+ }
248
+ });
249
+ return outcomes;
250
+ }
251
+
252
+ /**
253
+ * Get the number of outcome entries
254
+ * @returns {number} Number of outcome entries
255
+ */
256
+ function getOutcomeCount() {
257
+ ensureOutcomesFile();
258
+ const content = fs.readFileSync(outcomesFile, 'utf-8').trim();
259
+ if (!content) return 0;
260
+ return content.split('\n').filter(Boolean).length;
261
+ }
262
+
263
+ /**
264
+ * Get the size of the outcomes file in bytes
265
+ * @returns {number} File size in bytes
266
+ */
267
+ function getOutcomesFileSize() {
268
+ ensureOutcomesFile();
269
+ try {
270
+ const stats = fs.statSync(outcomesFile);
271
+ return stats.size;
272
+ } catch {
273
+ return 0;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Check if compaction is needed based on thresholds
279
+ * @returns {boolean} Whether compaction should be performed
280
+ */
281
+ function shouldCompact() {
282
+ const count = getOutcomeCount();
283
+ const size = getOutcomesFileSize();
284
+ return count >= COMPACTION_THRESHOLD_COUNT || size >= COMPACTION_THRESHOLD_BYTES;
285
+ }
286
+
287
+ /**
288
+ * Compact outcomes into decisions file
289
+ * Merges all outcomes into the main decisions file and clears outcomes
290
+ * @param {object} options Compaction options
291
+ * @param {boolean} options.force Force compaction even if thresholds not met
292
+ * @returns {object} Compaction results
293
+ */
294
+ function compact(options = {}) {
295
+ ensureDecisionsFile();
296
+ ensureOutcomesFile();
297
+
298
+ const outcomes = readOutcomes();
299
+ if (outcomes.size === 0) return { compacted: 0, skipped: true };
300
+
301
+ // Read raw decisions
302
+ const content = fs.readFileSync(decisionsFile, 'utf-8').trim();
303
+ if (!content) return { compacted: 0, skipped: true };
304
+
305
+ const decisions = content.split('\n').filter(Boolean).map(line => {
306
+ try {
307
+ return JSON.parse(line);
308
+ } catch {
309
+ return null;
310
+ }
311
+ }).filter(Boolean);
312
+
313
+ // Merge outcomes into decisions
314
+ let compactedCount = 0;
315
+ const updatedDecisions = decisions.map(d => {
316
+ const outcome = outcomes.get(d.id);
317
+ if (outcome) {
318
+ d.outcome = outcome.outcome;
319
+ d.outcome_recorded_at = outcome.outcome_recorded_at;
320
+ compactedCount++;
321
+ }
322
+ return d;
323
+ });
324
+
325
+ // Rewrite decisions file with merged data
326
+ fs.writeFileSync(decisionsFile, updatedDecisions.map(d => JSON.stringify(d)).join('\n') + '\n');
327
+
328
+ // Clear outcomes file
329
+ fs.writeFileSync(outcomesFile, '');
330
+
331
+ // Rebuild index after compaction (line offsets may have changed due to outcome data)
332
+ rebuildIndex();
333
+
334
+ return {
335
+ compacted: compactedCount,
336
+ totalDecisions: decisions.length,
337
+ skipped: false
338
+ };
339
+ }
340
+
78
341
  /**
79
342
  * Ensure patterns file exists
80
343
  */
@@ -126,9 +389,16 @@ function logDecision(options) {
126
389
  files_affected: options.files_affected || []
127
390
  };
128
391
 
129
- // Append to JSONL file
392
+ // Append to JSONL file (O(1) operation)
130
393
  fs.appendFileSync(decisionsFile, JSON.stringify(decision) + '\n');
131
394
 
395
+ // Add to index for O(1) lookups
396
+ addToIndex(decision.id, {
397
+ timestamp: decision.timestamp,
398
+ type: decision.type,
399
+ confidence: decision.confidence
400
+ });
401
+
132
402
  // Update patterns stats
133
403
  updateStats('total_decisions', 1);
134
404
 
@@ -137,6 +407,7 @@ function logDecision(options) {
137
407
 
138
408
  /**
139
409
  * Record the outcome of a decision
410
+ * Uses O(1) index lookup to verify decision exists, then O(1) append for outcome
140
411
  * @param {string} decisionId The decision ID
141
412
  * @param {Object} outcome Outcome details
142
413
  * @param {string} outcome.status Status (success, partial, failure, reverted)
@@ -146,46 +417,111 @@ function logDecision(options) {
146
417
  */
147
418
  function recordOutcome(decisionId, outcome) {
148
419
  ensureDecisionsFile();
420
+ ensureOutcomesFile();
149
421
 
150
- const decisions = readAllDecisions();
151
- let found = false;
422
+ // O(1) lookup using index instead of O(n) file scan
423
+ const decisionMeta = getDecisionMeta(decisionId);
424
+ if (!decisionMeta) {
425
+ return null;
426
+ }
152
427
 
153
- const updatedDecisions = decisions.map(d => {
154
- if (d.id === decisionId) {
155
- found = true;
156
- d.outcome = {
157
- status: outcome.status || OUTCOME_STATUS.PENDING,
158
- metrics: outcome.metrics || {},
159
- notes: outcome.notes || '',
160
- actual_confidence: outcome.actual_confidence || d.confidence
161
- };
162
- d.outcome_recorded_at = getTimestamp();
428
+ // Create outcome entry
429
+ const outcomeEntry = {
430
+ decisionId,
431
+ outcome: {
432
+ status: outcome.status || OUTCOME_STATUS.PENDING,
433
+ metrics: outcome.metrics || {},
434
+ notes: outcome.notes || '',
435
+ actual_confidence: outcome.actual_confidence || decisionMeta.confidence || 0.5
436
+ },
437
+ outcome_recorded_at: getTimestamp()
438
+ };
163
439
 
164
- // Update stats based on outcome
165
- if (outcome.status === OUTCOME_STATUS.SUCCESS) {
166
- updateStats('successful_decisions', 1);
167
- } else if (outcome.status === OUTCOME_STATUS.FAILURE) {
168
- updateStats('failed_decisions', 1);
169
- }
170
- }
171
- return d;
172
- });
440
+ // Append to outcomes file (O(1) operation)
441
+ fs.appendFileSync(outcomesFile, JSON.stringify(outcomeEntry) + '\n');
442
+
443
+ // Update stats based on outcome
444
+ if (outcome.status === OUTCOME_STATUS.SUCCESS) {
445
+ updateStats('successful_decisions', 1);
446
+ } else if (outcome.status === OUTCOME_STATUS.FAILURE) {
447
+ updateStats('failed_decisions', 1);
448
+ }
173
449
 
174
- if (!found) {
450
+ // Auto-compact if thresholds reached (count or size)
451
+ if (shouldCompact()) {
452
+ compact();
453
+ }
454
+
455
+ // For pattern checking and return value, we need the full decision
456
+ // This is acceptable because it's only done once per outcome recording
457
+ // and pattern checking is a secondary operation
458
+ const fullDecision = getDecisionById(decisionId);
459
+ if (fullDecision) {
460
+ const completeDecision = {
461
+ ...fullDecision,
462
+ outcome: outcomeEntry.outcome,
463
+ outcome_recorded_at: outcomeEntry.outcome_recorded_at
464
+ };
465
+
466
+ // Check if this creates a pattern
467
+ checkForPatterns(completeDecision);
468
+
469
+ return completeDecision;
470
+ }
471
+
472
+ // Fallback return with minimal data
473
+ return {
474
+ id: decisionId,
475
+ ...decisionMeta,
476
+ outcome: outcomeEntry.outcome,
477
+ outcome_recorded_at: outcomeEntry.outcome_recorded_at
478
+ };
479
+ }
480
+
481
+ /**
482
+ * Get a single decision by ID
483
+ * Optimized to use index for existence check, then reads full data if needed
484
+ * @param {string} decisionId Decision ID
485
+ * @returns {Object|null} Decision or null if not found
486
+ */
487
+ function getDecisionById(decisionId) {
488
+ // O(1) check if decision exists
489
+ if (!decisionExists(decisionId)) {
175
490
  return null;
176
491
  }
177
492
 
178
- // Rewrite the file with updated decisions
179
- fs.writeFileSync(decisionsFile, updatedDecisions.map(d => JSON.stringify(d)).join('\n') + '\n');
493
+ ensureDecisionsFile();
494
+ const content = fs.readFileSync(decisionsFile, 'utf-8').trim();
495
+ if (!content) return null;
180
496
 
181
- // Check if this creates a pattern
182
- checkForPatterns(updatedDecisions.find(d => d.id === decisionId));
497
+ // Find the decision in the file
498
+ const lines = content.split('\n').filter(Boolean);
499
+ for (const line of lines) {
500
+ try {
501
+ const d = JSON.parse(line);
502
+ if (d.id === decisionId) {
503
+ // Merge with any pending outcome
504
+ const outcomes = readOutcomes();
505
+ const pendingOutcome = outcomes.get(decisionId);
506
+ if (pendingOutcome) {
507
+ return {
508
+ ...d,
509
+ outcome: pendingOutcome.outcome,
510
+ outcome_recorded_at: pendingOutcome.outcome_recorded_at
511
+ };
512
+ }
513
+ return d;
514
+ }
515
+ } catch {
516
+ // Skip malformed lines
517
+ }
518
+ }
183
519
 
184
- return updatedDecisions.find(d => d.id === decisionId);
520
+ return null;
185
521
  }
186
522
 
187
523
  /**
188
- * Read all decisions from the JSONL file
524
+ * Read all decisions from the JSONL file, merged with any pending outcomes
189
525
  * @returns {Array} Array of decisions
190
526
  */
191
527
  function readAllDecisions() {
@@ -194,13 +530,31 @@ function readAllDecisions() {
194
530
  const content = fs.readFileSync(decisionsFile, 'utf-8').trim();
195
531
  if (!content) return [];
196
532
 
197
- return content.split('\n').filter(Boolean).map(line => {
533
+ const decisions = content.split('\n').filter(Boolean).map(line => {
198
534
  try {
199
535
  return JSON.parse(line);
200
536
  } catch {
201
537
  return null;
202
538
  }
203
539
  }).filter(Boolean);
540
+
541
+ // Merge with any pending outcomes
542
+ const outcomes = readOutcomes();
543
+ if (outcomes.size > 0) {
544
+ return decisions.map(d => {
545
+ const outcome = outcomes.get(d.id);
546
+ if (outcome) {
547
+ return {
548
+ ...d,
549
+ outcome: outcome.outcome,
550
+ outcome_recorded_at: outcome.outcome_recorded_at
551
+ };
552
+ }
553
+ return d;
554
+ });
555
+ }
556
+
557
+ return decisions;
204
558
  }
205
559
 
206
560
  /**
@@ -407,6 +761,12 @@ function clearHistory() {
407
761
  if (fs.existsSync(decisionsFile)) {
408
762
  fs.writeFileSync(decisionsFile, '');
409
763
  }
764
+ if (fs.existsSync(outcomesFile)) {
765
+ fs.writeFileSync(outcomesFile, '');
766
+ }
767
+ if (fs.existsSync(indexFile)) {
768
+ fs.unlinkSync(indexFile);
769
+ }
410
770
  if (fs.existsSync(patternsFile)) {
411
771
  fs.writeFileSync(patternsFile, JSON.stringify({
412
772
  version: '1.0.0',
@@ -420,14 +780,1028 @@ function clearHistory() {
420
780
  }
421
781
  }, null, 2));
422
782
  }
783
+ // Clear in-memory index
784
+ invalidateIndex();
785
+ }
786
+
787
+ /**
788
+ * Impact scoring constants
789
+ */
790
+ const IMPACT_LEVELS = {
791
+ CRITICAL: 5, // System-wide or security impact
792
+ HIGH: 4, // Major feature or performance impact
793
+ MEDIUM: 3, // Moderate feature impact
794
+ LOW: 2, // Minor feature impact
795
+ MINIMAL: 1 // Cosmetic or trivial impact
796
+ };
797
+
798
+ /**
799
+ * Time decay configuration for recency weighting
800
+ */
801
+ const TIME_DECAY = {
802
+ HALF_LIFE_DAYS: 14, // Score halves every 14 days
803
+ MIN_WEIGHT: 0.1, // Minimum weight for very old decisions
804
+ MAX_AGE_DAYS: 180 // Decisions older than this use minimum weight
805
+ };
806
+
807
+ /**
808
+ * Impact factors used in scoring
809
+ */
810
+ const IMPACT_FACTORS = {
811
+ // Type-based base scores
812
+ typeWeights: {
813
+ [DECISION_TYPES.ARCHITECTURE]: 1.5,
814
+ [DECISION_TYPES.DATABASE]: 1.4,
815
+ [DECISION_TYPES.SECURITY]: 1.6,
816
+ [DECISION_TYPES.API]: 1.3,
817
+ [DECISION_TYPES.UI]: 1.0,
818
+ [DECISION_TYPES.TESTING]: 1.1,
819
+ [DECISION_TYPES.DEPLOYMENT]: 1.4,
820
+ [DECISION_TYPES.PERFORMANCE]: 1.3,
821
+ [DECISION_TYPES.AGENT_SELECTION]: 1.0,
822
+ [DECISION_TYPES.WORKFLOW_CHOICE]: 1.1,
823
+ [DECISION_TYPES.SKILL_USAGE]: 1.0,
824
+ [DECISION_TYPES.REFACTOR]: 1.2,
825
+ [DECISION_TYPES.BUG_FIX]: 1.1
826
+ },
827
+ // Outcome multipliers
828
+ outcomeMultipliers: {
829
+ [OUTCOME_STATUS.SUCCESS]: 1.0,
830
+ [OUTCOME_STATUS.PARTIAL]: 0.7,
831
+ [OUTCOME_STATUS.FAILURE]: 0.5,
832
+ [OUTCOME_STATUS.REVERTED]: 0.3,
833
+ [OUTCOME_STATUS.PENDING]: 0.8
834
+ },
835
+ // Downstream impact multipliers based on outcome propagation
836
+ downstreamSuccessBonus: 0.15, // Bonus per successful downstream decision
837
+ downstreamFailurePenalty: 0.1, // Penalty per failed downstream decision
838
+ maxDownstreamBonus: 1.0, // Cap for downstream bonuses
839
+ maxDownstreamPenalty: 0.5 // Cap for downstream penalties
840
+ };
841
+
842
+ /**
843
+ * Decision chain tracking - stores parent-child relationships
844
+ * Maps decisionId -> { parent: parentId, children: [childIds] }
845
+ */
846
+ const decisionChainFile = path.join(memoryDir, 'decision-chains.json');
847
+
848
+ /**
849
+ * Impact scores cache file for performance
850
+ */
851
+ const impactScoresFile = path.join(memoryDir, 'impact-scores.json');
852
+
853
+ /**
854
+ * Calculate time decay weight for recency
855
+ * Recent decisions are weighted more heavily
856
+ * @param {string} timestamp - ISO timestamp
857
+ * @returns {number} Decay weight between MIN_WEIGHT and 1.0
858
+ */
859
+ function calculateTimeDecay(timestamp) {
860
+ if (!timestamp) return TIME_DECAY.MIN_WEIGHT;
861
+
862
+ const decisionDate = new Date(timestamp);
863
+ const now = new Date();
864
+ const ageInDays = (now - decisionDate) / (1000 * 60 * 60 * 24);
865
+
866
+ if (ageInDays > TIME_DECAY.MAX_AGE_DAYS) {
867
+ return TIME_DECAY.MIN_WEIGHT;
868
+ }
869
+
870
+ // Exponential decay: weight = 0.5^(age/halfLife)
871
+ const decayFactor = Math.pow(0.5, ageInDays / TIME_DECAY.HALF_LIFE_DAYS);
872
+ return Math.max(TIME_DECAY.MIN_WEIGHT, decayFactor);
873
+ }
874
+
875
+ /**
876
+ * Ensure decision chains file exists
877
+ */
878
+ function ensureDecisionChainsFile() {
879
+ if (!fs.existsSync(decisionChainFile)) {
880
+ fs.writeFileSync(decisionChainFile, JSON.stringify({
881
+ version: '1.0.0',
882
+ updated: getTimestamp(),
883
+ chains: {}
884
+ }, null, 2));
885
+ }
886
+ }
887
+
888
+ /**
889
+ * Load decision chains from file
890
+ * @returns {Object} Decision chains data
891
+ */
892
+ function loadDecisionChains() {
893
+ ensureDecisionChainsFile();
894
+ try {
895
+ return JSON.parse(fs.readFileSync(decisionChainFile, 'utf-8'));
896
+ } catch {
897
+ return { version: '1.0.0', updated: getTimestamp(), chains: {} };
898
+ }
899
+ }
900
+
901
+ /**
902
+ * Save decision chains to file
903
+ * @param {Object} chainsData - Chains data to save
904
+ */
905
+ function saveDecisionChains(chainsData) {
906
+ chainsData.updated = getTimestamp();
907
+ fs.writeFileSync(decisionChainFile, JSON.stringify(chainsData, null, 2));
908
+ }
909
+
910
+ /**
911
+ * Link a decision to its parent decision (creates decision chain)
912
+ * @param {string} childId - The decision that was influenced
913
+ * @param {string} parentId - The decision that influenced it
914
+ * @param {string} relationship - Type of relationship (e.g., 'caused_by', 'follows', 'extends')
915
+ */
916
+ function linkDecisions(childId, parentId, relationship = 'influenced_by') {
917
+ const chainsData = loadDecisionChains();
918
+
919
+ // Initialize entries if needed
920
+ if (!chainsData.chains[childId]) {
921
+ chainsData.chains[childId] = { parents: [], children: [] };
922
+ }
923
+ if (!chainsData.chains[parentId]) {
924
+ chainsData.chains[parentId] = { parents: [], children: [] };
925
+ }
926
+
927
+ // Add relationship
928
+ const existingParent = chainsData.chains[childId].parents.find(p => p.id === parentId);
929
+ if (!existingParent) {
930
+ chainsData.chains[childId].parents.push({
931
+ id: parentId,
932
+ relationship,
933
+ linked_at: getTimestamp()
934
+ });
935
+ }
936
+
937
+ const existingChild = chainsData.chains[parentId].children.find(c => c.id === childId);
938
+ if (!existingChild) {
939
+ chainsData.chains[parentId].children.push({
940
+ id: childId,
941
+ relationship,
942
+ linked_at: getTimestamp()
943
+ });
944
+ }
945
+
946
+ saveDecisionChains(chainsData);
947
+ }
948
+
949
+ /**
950
+ * Get all downstream decisions in the chain (recursive)
951
+ * @param {string} decisionId - Starting decision ID
952
+ * @param {Set} visited - Already visited IDs (for cycle detection)
953
+ * @returns {Array} Array of downstream decision info objects
954
+ */
955
+ function getDownstreamDecisions(decisionId, visited = new Set()) {
956
+ if (visited.has(decisionId)) return [];
957
+ visited.add(decisionId);
958
+
959
+ const chainsData = loadDecisionChains();
960
+ const chainEntry = chainsData.chains[decisionId];
961
+
962
+ if (!chainEntry || !chainEntry.children.length) return [];
963
+
964
+ const downstream = [];
965
+ for (const child of chainEntry.children) {
966
+ const childDecision = getDecisionById(child.id);
967
+ if (childDecision) {
968
+ downstream.push({
969
+ id: child.id,
970
+ relationship: child.relationship,
971
+ decision: childDecision,
972
+ depth: 1
973
+ });
974
+
975
+ // Recursively get children of children
976
+ const grandchildren = getDownstreamDecisions(child.id, visited);
977
+ grandchildren.forEach(gc => {
978
+ gc.depth++;
979
+ downstream.push(gc);
980
+ });
981
+ }
982
+ }
983
+
984
+ return downstream;
985
+ }
986
+
987
+ /**
988
+ * Get all upstream decisions in the chain (recursive)
989
+ * @param {string} decisionId - Starting decision ID
990
+ * @param {Set} visited - Already visited IDs (for cycle detection)
991
+ * @returns {Array} Array of upstream decision info objects
992
+ */
993
+ function getUpstreamDecisions(decisionId, visited = new Set()) {
994
+ if (visited.has(decisionId)) return [];
995
+ visited.add(decisionId);
996
+
997
+ const chainsData = loadDecisionChains();
998
+ const chainEntry = chainsData.chains[decisionId];
999
+
1000
+ if (!chainEntry || !chainEntry.parents.length) return [];
1001
+
1002
+ const upstream = [];
1003
+ for (const parent of chainEntry.parents) {
1004
+ const parentDecision = getDecisionById(parent.id);
1005
+ if (parentDecision) {
1006
+ upstream.push({
1007
+ id: parent.id,
1008
+ relationship: parent.relationship,
1009
+ decision: parentDecision,
1010
+ depth: 1
1011
+ });
1012
+
1013
+ // Recursively get parents of parents
1014
+ const grandparents = getUpstreamDecisions(parent.id, visited);
1015
+ grandparents.forEach(gp => {
1016
+ gp.depth++;
1017
+ upstream.push(gp);
1018
+ });
1019
+ }
1020
+ }
1021
+
1022
+ return upstream;
1023
+ }
1024
+
1025
+ /**
1026
+ * Calculate downstream impact based on outcome propagation
1027
+ * @param {string} decisionId - Decision ID
1028
+ * @returns {Object} Downstream impact details
1029
+ */
1030
+ function calculateDownstreamImpact(decisionId) {
1031
+ const downstream = getDownstreamDecisions(decisionId);
1032
+
1033
+ if (downstream.length === 0) {
1034
+ // Fallback to temporal-based counting for decisions without explicit links
1035
+ return calculateTemporalDownstreamImpact(decisionId);
1036
+ }
1037
+
1038
+ let successCount = 0;
1039
+ let failureCount = 0;
1040
+ let totalWeight = 0;
1041
+
1042
+ for (const d of downstream) {
1043
+ const depthWeight = 1 / d.depth; // Closer decisions have more weight
1044
+ const timeWeight = calculateTimeDecay(d.decision.timestamp);
1045
+ const weight = depthWeight * timeWeight;
1046
+
1047
+ if (d.decision.outcome?.status === OUTCOME_STATUS.SUCCESS) {
1048
+ successCount += weight;
1049
+ } else if (d.decision.outcome?.status === OUTCOME_STATUS.FAILURE ||
1050
+ d.decision.outcome?.status === OUTCOME_STATUS.REVERTED) {
1051
+ failureCount += weight;
1052
+ }
1053
+ totalWeight += weight;
1054
+ }
1055
+
1056
+ const successBonus = Math.min(
1057
+ successCount * IMPACT_FACTORS.downstreamSuccessBonus,
1058
+ IMPACT_FACTORS.maxDownstreamBonus
1059
+ );
1060
+
1061
+ const failurePenalty = Math.min(
1062
+ failureCount * IMPACT_FACTORS.downstreamFailurePenalty,
1063
+ IMPACT_FACTORS.maxDownstreamPenalty
1064
+ );
1065
+
1066
+ return {
1067
+ downstreamCount: downstream.length,
1068
+ weightedSuccesses: Math.round(successCount * 100) / 100,
1069
+ weightedFailures: Math.round(failureCount * 100) / 100,
1070
+ successBonus: Math.round(successBonus * 100) / 100,
1071
+ failurePenalty: Math.round(failurePenalty * 100) / 100,
1072
+ netImpact: Math.round((successBonus - failurePenalty) * 100) / 100,
1073
+ hasExplicitChain: true
1074
+ };
1075
+ }
1076
+
1077
+ /**
1078
+ * Calculate downstream impact based on temporal proximity (fallback)
1079
+ * @param {string} decisionId - Decision ID
1080
+ * @returns {Object} Downstream impact details
1081
+ */
1082
+ function calculateTemporalDownstreamImpact(decisionId) {
1083
+ const decisions = readAllDecisions();
1084
+ const decision = decisions.find(d => d.id === decisionId);
1085
+
1086
+ if (!decision) {
1087
+ return {
1088
+ downstreamCount: 0,
1089
+ weightedSuccesses: 0,
1090
+ weightedFailures: 0,
1091
+ successBonus: 0,
1092
+ failurePenalty: 0,
1093
+ netImpact: 0,
1094
+ hasExplicitChain: false
1095
+ };
1096
+ }
1097
+
1098
+ const timestamp = new Date(decision.timestamp);
1099
+ const relatedTypes = getRelatedTypes(decision.type);
1100
+
1101
+ const downstream = decisions.filter(d => {
1102
+ const dTime = new Date(d.timestamp);
1103
+ return dTime > timestamp && relatedTypes.includes(d.type);
1104
+ });
1105
+
1106
+ let successCount = 0;
1107
+ let failureCount = 0;
1108
+
1109
+ for (const d of downstream) {
1110
+ const timeWeight = calculateTimeDecay(d.timestamp);
1111
+
1112
+ if (d.outcome?.status === OUTCOME_STATUS.SUCCESS) {
1113
+ successCount += timeWeight;
1114
+ } else if (d.outcome?.status === OUTCOME_STATUS.FAILURE ||
1115
+ d.outcome?.status === OUTCOME_STATUS.REVERTED) {
1116
+ failureCount += timeWeight;
1117
+ }
1118
+ }
1119
+
1120
+ const successBonus = Math.min(
1121
+ successCount * IMPACT_FACTORS.downstreamSuccessBonus,
1122
+ IMPACT_FACTORS.maxDownstreamBonus
1123
+ );
1124
+
1125
+ const failurePenalty = Math.min(
1126
+ failureCount * IMPACT_FACTORS.downstreamFailurePenalty,
1127
+ IMPACT_FACTORS.maxDownstreamPenalty
1128
+ );
1129
+
1130
+ return {
1131
+ downstreamCount: downstream.length,
1132
+ weightedSuccesses: Math.round(successCount * 100) / 100,
1133
+ weightedFailures: Math.round(failureCount * 100) / 100,
1134
+ successBonus: Math.round(successBonus * 100) / 100,
1135
+ failurePenalty: Math.round(failurePenalty * 100) / 100,
1136
+ netImpact: Math.round((successBonus - failurePenalty) * 100) / 100,
1137
+ hasExplicitChain: false
1138
+ };
1139
+ }
1140
+
1141
+ /**
1142
+ * Calculate impact score for a decision
1143
+ * Enhanced with time decay, downstream tracking, and outcome propagation
1144
+ * @param {object} decision - Decision object
1145
+ * @param {object} options - Scoring options
1146
+ * @param {boolean} options.includeDownstream - Include downstream impact (default: true)
1147
+ * @param {boolean} options.applyTimeDecay - Apply time decay weighting (default: true)
1148
+ * @returns {object} Impact score details
1149
+ */
1150
+ function calculateImpactScore(decision, options = {}) {
1151
+ const { includeDownstream = true, applyTimeDecay = true } = options;
1152
+
1153
+ if (!decision) return { score: 0, level: 'UNKNOWN', factors: {} };
1154
+
1155
+ // Base score from type
1156
+ const typeWeight = IMPACT_FACTORS.typeWeights[decision.type] || 1.0;
1157
+
1158
+ // Confidence contribution
1159
+ const confidenceScore = decision.confidence || 0.5;
1160
+
1161
+ // Outcome multiplier
1162
+ const outcomeStatus = decision.outcome?.status || OUTCOME_STATUS.PENDING;
1163
+ const outcomeMultiplier = IMPACT_FACTORS.outcomeMultipliers[outcomeStatus] || 0.8;
1164
+
1165
+ // Time decay for recency weighting
1166
+ const timeDecayWeight = applyTimeDecay ? calculateTimeDecay(decision.timestamp) : 1.0;
1167
+
1168
+ // Calculate downstream impact
1169
+ let downstreamImpact = { netImpact: 0, downstreamCount: 0 };
1170
+ if (includeDownstream) {
1171
+ downstreamImpact = calculateDownstreamImpact(decision.id);
1172
+ }
1173
+
1174
+ // Calculate raw score with all factors
1175
+ const baseScore = typeWeight * confidenceScore * outcomeMultiplier;
1176
+ const weightedScore = baseScore * timeDecayWeight;
1177
+ const finalScore = weightedScore + downstreamImpact.netImpact;
1178
+
1179
+ // Normalize to 1-5 scale
1180
+ const normalizedScore = Math.min(Math.max(finalScore * 2.5, 1), 5);
1181
+
1182
+ // Determine level
1183
+ let level = 'MINIMAL';
1184
+ if (normalizedScore >= 4.5) level = 'CRITICAL';
1185
+ else if (normalizedScore >= 3.5) level = 'HIGH';
1186
+ else if (normalizedScore >= 2.5) level = 'MEDIUM';
1187
+ else if (normalizedScore >= 1.5) level = 'LOW';
1188
+
1189
+ return {
1190
+ score: Math.round(normalizedScore * 100) / 100,
1191
+ rawScore: Math.round(finalScore * 100) / 100,
1192
+ level,
1193
+ factors: {
1194
+ typeWeight,
1195
+ confidenceScore,
1196
+ outcomeMultiplier,
1197
+ timeDecayWeight: Math.round(timeDecayWeight * 100) / 100,
1198
+ downstreamImpact: downstreamImpact.netImpact,
1199
+ downstreamCount: downstreamImpact.downstreamCount
1200
+ },
1201
+ downstream: includeDownstream ? downstreamImpact : null
1202
+ };
1203
+ }
1204
+
1205
+ /**
1206
+ * Count downstream decisions affected by this decision (legacy support)
1207
+ * @param {string} decisionId - Decision ID
1208
+ * @returns {number} Count of downstream decisions
1209
+ */
1210
+ function countDownstreamDecisions(decisionId) {
1211
+ const impact = calculateDownstreamImpact(decisionId);
1212
+ return impact.downstreamCount;
1213
+ }
1214
+
1215
+ /**
1216
+ * Get related decision types
1217
+ * @param {string} type - Decision type
1218
+ * @returns {Array} Related types
1219
+ */
1220
+ function getRelatedTypes(type) {
1221
+ const relationships = {
1222
+ [DECISION_TYPES.ARCHITECTURE]: [DECISION_TYPES.DATABASE, DECISION_TYPES.API, DECISION_TYPES.DEPLOYMENT],
1223
+ [DECISION_TYPES.DATABASE]: [DECISION_TYPES.API, DECISION_TYPES.PERFORMANCE],
1224
+ [DECISION_TYPES.API]: [DECISION_TYPES.UI, DECISION_TYPES.TESTING],
1225
+ [DECISION_TYPES.SECURITY]: [DECISION_TYPES.DATABASE, DECISION_TYPES.API, DECISION_TYPES.DEPLOYMENT],
1226
+ [DECISION_TYPES.UI]: [DECISION_TYPES.TESTING, DECISION_TYPES.PERFORMANCE],
1227
+ [DECISION_TYPES.TESTING]: [],
1228
+ [DECISION_TYPES.DEPLOYMENT]: [DECISION_TYPES.PERFORMANCE],
1229
+ [DECISION_TYPES.PERFORMANCE]: [],
1230
+ [DECISION_TYPES.AGENT_SELECTION]: [DECISION_TYPES.WORKFLOW_CHOICE, DECISION_TYPES.SKILL_USAGE],
1231
+ [DECISION_TYPES.WORKFLOW_CHOICE]: [DECISION_TYPES.SKILL_USAGE],
1232
+ [DECISION_TYPES.SKILL_USAGE]: [],
1233
+ [DECISION_TYPES.REFACTOR]: [DECISION_TYPES.TESTING, DECISION_TYPES.PERFORMANCE],
1234
+ [DECISION_TYPES.BUG_FIX]: [DECISION_TYPES.TESTING]
1235
+ };
1236
+
1237
+ return [type, ...(relationships[type] || [])];
1238
+ }
1239
+
1240
+ /**
1241
+ * Track successful outcomes and their decision chains
1242
+ * @param {string} decisionId - Decision ID that succeeded
1243
+ * @returns {Object} Success chain analysis
1244
+ */
1245
+ function trackSuccessChain(decisionId) {
1246
+ const decision = getDecisionById(decisionId);
1247
+ if (!decision || decision.outcome?.status !== OUTCOME_STATUS.SUCCESS) {
1248
+ return null;
1249
+ }
1250
+
1251
+ const upstream = getUpstreamDecisions(decisionId);
1252
+ const downstream = getDownstreamDecisions(decisionId);
1253
+
1254
+ // Find all decisions in the chain that also succeeded
1255
+ const successfulUpstream = upstream.filter(
1256
+ u => u.decision.outcome?.status === OUTCOME_STATUS.SUCCESS
1257
+ );
1258
+ const successfulDownstream = downstream.filter(
1259
+ d => d.decision.outcome?.status === OUTCOME_STATUS.SUCCESS
1260
+ );
1261
+
1262
+ // Calculate chain success rate
1263
+ const totalInChain = upstream.length + downstream.length + 1;
1264
+ const successfulInChain = successfulUpstream.length + successfulDownstream.length + 1;
1265
+ const chainSuccessRate = totalInChain > 0 ? successfulInChain / totalInChain : 1;
1266
+
1267
+ return {
1268
+ decisionId,
1269
+ decision: decision.decision,
1270
+ type: decision.type,
1271
+ chainLength: totalInChain,
1272
+ chainSuccessRate: Math.round(chainSuccessRate * 100) / 100,
1273
+ successfulUpstream: successfulUpstream.map(u => ({
1274
+ id: u.id,
1275
+ decision: u.decision.decision,
1276
+ type: u.decision.type
1277
+ })),
1278
+ successfulDownstream: successfulDownstream.map(d => ({
1279
+ id: d.id,
1280
+ decision: d.decision.decision,
1281
+ type: d.decision.type
1282
+ })),
1283
+ timestamp: decision.timestamp
1284
+ };
1285
+ }
1286
+
1287
+ /**
1288
+ * Get decisions that led to successful outcomes
1289
+ * Identifies patterns and decision chains that correlate with success
1290
+ * @param {Object} options - Filter options
1291
+ * @param {number} options.minSuccessRate - Minimum chain success rate (0-1)
1292
+ * @param {number} options.minChainLength - Minimum chain length
1293
+ * @param {number} options.limit - Maximum results to return
1294
+ * @returns {Array} Successful decision patterns
1295
+ */
1296
+ function getSuccessfulDecisionPatterns(options = {}) {
1297
+ const {
1298
+ minSuccessRate = 0.7,
1299
+ minChainLength = 2,
1300
+ limit = 20
1301
+ } = options;
1302
+
1303
+ const decisions = readAllDecisions();
1304
+ const successfulDecisions = decisions.filter(
1305
+ d => d.outcome?.status === OUTCOME_STATUS.SUCCESS
1306
+ );
1307
+
1308
+ const patterns = [];
1309
+
1310
+ for (const decision of successfulDecisions) {
1311
+ const chain = trackSuccessChain(decision.id);
1312
+ if (chain &&
1313
+ chain.chainSuccessRate >= minSuccessRate &&
1314
+ chain.chainLength >= minChainLength) {
1315
+ patterns.push({
1316
+ ...chain,
1317
+ impact: calculateImpactScore(decision)
1318
+ });
1319
+ }
1320
+ }
1321
+
1322
+ // Sort by impact score and chain success rate
1323
+ return patterns
1324
+ .sort((a, b) => {
1325
+ const scoreA = a.impact.score * a.chainSuccessRate;
1326
+ const scoreB = b.impact.score * b.chainSuccessRate;
1327
+ return scoreB - scoreA;
1328
+ })
1329
+ .slice(0, limit);
1330
+ }
1331
+
1332
+ /**
1333
+ * Get highest impact decisions with enhanced analysis
1334
+ * @param {number} limit - Number of decisions to return
1335
+ * @param {Object} options - Filter options
1336
+ * @param {boolean} options.onlySuccessful - Only include successful decisions
1337
+ * @param {string} options.type - Filter by decision type
1338
+ * @param {number} options.minScore - Minimum impact score
1339
+ * @returns {Array} Decisions sorted by impact score
1340
+ */
1341
+ function getHighImpactDecisions(limit = 10, options = {}) {
1342
+ const {
1343
+ onlySuccessful = false,
1344
+ type = null,
1345
+ minScore = 0
1346
+ } = options;
1347
+
1348
+ let decisions = readAllDecisions();
1349
+
1350
+ // Apply filters
1351
+ if (onlySuccessful) {
1352
+ decisions = decisions.filter(d => d.outcome?.status === OUTCOME_STATUS.SUCCESS);
1353
+ }
1354
+ if (type) {
1355
+ decisions = decisions.filter(d => d.type === type);
1356
+ }
1357
+
1358
+ const withImpact = decisions
1359
+ .map(d => ({
1360
+ ...d,
1361
+ impact: calculateImpactScore(d)
1362
+ }))
1363
+ .filter(d => d.impact.score >= minScore);
1364
+
1365
+ return withImpact
1366
+ .sort((a, b) => b.impact.score - a.impact.score)
1367
+ .slice(0, limit);
1368
+ }
1369
+
1370
+ /**
1371
+ * Generate comprehensive impact report for decision analysis
1372
+ * @param {Object} options - Report options
1373
+ * @param {number} options.days - Number of days to analyze
1374
+ * @param {boolean} options.includeChains - Include decision chain analysis
1375
+ * @param {boolean} options.includePatterns - Include pattern analysis
1376
+ * @returns {Object} Comprehensive impact report
1377
+ */
1378
+ function generateImpactReport(options = {}) {
1379
+ const {
1380
+ days = 30,
1381
+ includeChains = true,
1382
+ includePatterns = true
1383
+ } = options;
1384
+
1385
+ const cutoffDate = new Date();
1386
+ cutoffDate.setDate(cutoffDate.getDate() - days);
1387
+
1388
+ const decisions = readAllDecisions();
1389
+ const recentDecisions = decisions.filter(
1390
+ d => new Date(d.timestamp) >= cutoffDate
1391
+ );
1392
+
1393
+ // Calculate impact for all recent decisions
1394
+ const withImpact = recentDecisions.map(d => ({
1395
+ ...d,
1396
+ impact: calculateImpactScore(d)
1397
+ }));
1398
+
1399
+ // Group by impact level
1400
+ const byLevel = {};
1401
+ for (const level of Object.keys(IMPACT_LEVELS)) {
1402
+ byLevel[level] = withImpact.filter(d => d.impact.level === level);
1403
+ }
1404
+
1405
+ // Group by type
1406
+ const byType = {};
1407
+ for (const d of withImpact) {
1408
+ if (!byType[d.type]) {
1409
+ byType[d.type] = { decisions: [], totalScore: 0, successCount: 0 };
1410
+ }
1411
+ byType[d.type].decisions.push(d);
1412
+ byType[d.type].totalScore += d.impact.score;
1413
+ if (d.outcome?.status === OUTCOME_STATUS.SUCCESS) {
1414
+ byType[d.type].successCount++;
1415
+ }
1416
+ }
1417
+
1418
+ // Calculate type stats
1419
+ const typeStats = Object.entries(byType).map(([type, data]) => ({
1420
+ type,
1421
+ count: data.decisions.length,
1422
+ averageScore: Math.round((data.totalScore / data.decisions.length) * 100) / 100,
1423
+ successRate: Math.round((data.successCount / data.decisions.length) * 100) / 100,
1424
+ highestImpact: data.decisions.sort((a, b) => b.impact.score - a.impact.score)[0]
1425
+ })).sort((a, b) => b.averageScore - a.averageScore);
1426
+
1427
+ // Overall stats
1428
+ const totalScore = withImpact.reduce((sum, d) => sum + d.impact.score, 0);
1429
+ const successfulDecisions = withImpact.filter(
1430
+ d => d.outcome?.status === OUTCOME_STATUS.SUCCESS
1431
+ );
1432
+
1433
+ // Time trend analysis
1434
+ const weeklyTrends = calculateWeeklyTrends(withImpact, days);
1435
+
1436
+ // Build report
1437
+ const report = {
1438
+ generated: getTimestamp(),
1439
+ period: {
1440
+ days,
1441
+ from: cutoffDate.toISOString(),
1442
+ to: new Date().toISOString()
1443
+ },
1444
+ summary: {
1445
+ totalDecisions: withImpact.length,
1446
+ averageImpactScore: withImpact.length > 0
1447
+ ? Math.round((totalScore / withImpact.length) * 100) / 100
1448
+ : 0,
1449
+ successfulDecisions: successfulDecisions.length,
1450
+ successRate: withImpact.length > 0
1451
+ ? Math.round((successfulDecisions.length / withImpact.length) * 100) / 100
1452
+ : 0,
1453
+ highImpactCount: byLevel.HIGH?.length || 0 + (byLevel.CRITICAL?.length || 0)
1454
+ },
1455
+ byLevel: Object.entries(byLevel).map(([level, decisions]) => ({
1456
+ level,
1457
+ count: decisions.length,
1458
+ percentage: withImpact.length > 0
1459
+ ? Math.round((decisions.length / withImpact.length) * 100)
1460
+ : 0
1461
+ })),
1462
+ byType: typeStats,
1463
+ trends: weeklyTrends,
1464
+ topDecisions: withImpact
1465
+ .sort((a, b) => b.impact.score - a.impact.score)
1466
+ .slice(0, 10)
1467
+ .map(d => ({
1468
+ id: d.id,
1469
+ decision: d.decision,
1470
+ type: d.type,
1471
+ score: d.impact.score,
1472
+ level: d.impact.level,
1473
+ outcome: d.outcome?.status,
1474
+ timestamp: d.timestamp
1475
+ }))
1476
+ };
1477
+
1478
+ // Add chain analysis if requested
1479
+ if (includeChains) {
1480
+ const chainsData = loadDecisionChains();
1481
+ const chainedDecisionIds = Object.keys(chainsData.chains);
1482
+ const chainedCount = chainedDecisionIds.length;
1483
+
1484
+ report.chainAnalysis = {
1485
+ totalChainedDecisions: chainedCount,
1486
+ percentageChained: withImpact.length > 0
1487
+ ? Math.round((chainedCount / decisions.length) * 100)
1488
+ : 0,
1489
+ longestChains: findLongestChains(5)
1490
+ };
1491
+ }
1492
+
1493
+ // Add pattern analysis if requested
1494
+ if (includePatterns) {
1495
+ report.successPatterns = getSuccessfulDecisionPatterns({
1496
+ minSuccessRate: 0.7,
1497
+ minChainLength: 2,
1498
+ limit: 10
1499
+ });
1500
+
1501
+ report.highImpactPatterns = identifyHighImpactPatterns(withImpact);
1502
+ }
1503
+
1504
+ return report;
1505
+ }
1506
+
1507
+ /**
1508
+ * Calculate weekly trends for impact scores
1509
+ * @param {Array} decisions - Decisions with impact scores
1510
+ * @param {number} days - Number of days to analyze
1511
+ * @returns {Array} Weekly trend data
1512
+ */
1513
+ function calculateWeeklyTrends(decisions, days) {
1514
+ const weeks = Math.ceil(days / 7);
1515
+ const trends = [];
1516
+ const now = new Date();
1517
+
1518
+ for (let i = 0; i < weeks; i++) {
1519
+ const weekEnd = new Date(now);
1520
+ weekEnd.setDate(weekEnd.getDate() - (i * 7));
1521
+ const weekStart = new Date(weekEnd);
1522
+ weekStart.setDate(weekStart.getDate() - 7);
1523
+
1524
+ const weekDecisions = decisions.filter(d => {
1525
+ const dDate = new Date(d.timestamp);
1526
+ return dDate >= weekStart && dDate < weekEnd;
1527
+ });
1528
+
1529
+ const totalScore = weekDecisions.reduce((sum, d) => sum + d.impact.score, 0);
1530
+ const successCount = weekDecisions.filter(
1531
+ d => d.outcome?.status === OUTCOME_STATUS.SUCCESS
1532
+ ).length;
1533
+
1534
+ trends.unshift({
1535
+ week: weeks - i,
1536
+ weekStart: weekStart.toISOString().split('T')[0],
1537
+ weekEnd: weekEnd.toISOString().split('T')[0],
1538
+ decisionCount: weekDecisions.length,
1539
+ averageScore: weekDecisions.length > 0
1540
+ ? Math.round((totalScore / weekDecisions.length) * 100) / 100
1541
+ : 0,
1542
+ successRate: weekDecisions.length > 0
1543
+ ? Math.round((successCount / weekDecisions.length) * 100) / 100
1544
+ : 0
1545
+ });
1546
+ }
1547
+
1548
+ return trends;
1549
+ }
1550
+
1551
+ /**
1552
+ * Find the longest decision chains
1553
+ * @param {number} limit - Number of chains to return
1554
+ * @returns {Array} Longest chains
1555
+ */
1556
+ function findLongestChains(limit = 5) {
1557
+ const chainsData = loadDecisionChains();
1558
+ const chainLengths = [];
1559
+
1560
+ for (const decisionId of Object.keys(chainsData.chains)) {
1561
+ const upstream = getUpstreamDecisions(decisionId);
1562
+ const downstream = getDownstreamDecisions(decisionId);
1563
+ const totalLength = upstream.length + downstream.length + 1;
1564
+
1565
+ chainLengths.push({
1566
+ decisionId,
1567
+ totalLength,
1568
+ upstreamCount: upstream.length,
1569
+ downstreamCount: downstream.length
1570
+ });
1571
+ }
1572
+
1573
+ return chainLengths
1574
+ .sort((a, b) => b.totalLength - a.totalLength)
1575
+ .slice(0, limit);
1576
+ }
1577
+
1578
+ /**
1579
+ * Identify high-impact patterns from decisions
1580
+ * @param {Array} decisions - Decisions with impact scores
1581
+ * @returns {Object} Pattern analysis
1582
+ */
1583
+ function identifyHighImpactPatterns(decisions) {
1584
+ const highImpact = decisions.filter(d =>
1585
+ d.impact.level === 'HIGH' || d.impact.level === 'CRITICAL'
1586
+ );
1587
+
1588
+ // Group by decision text similarity
1589
+ const patternGroups = {};
1590
+ for (const d of highImpact) {
1591
+ const key = `${d.type}:${normalizeDecisionText(d.decision)}`;
1592
+ if (!patternGroups[key]) {
1593
+ patternGroups[key] = {
1594
+ type: d.type,
1595
+ pattern: d.decision,
1596
+ decisions: [],
1597
+ totalScore: 0,
1598
+ successCount: 0
1599
+ };
1600
+ }
1601
+ patternGroups[key].decisions.push(d);
1602
+ patternGroups[key].totalScore += d.impact.score;
1603
+ if (d.outcome?.status === OUTCOME_STATUS.SUCCESS) {
1604
+ patternGroups[key].successCount++;
1605
+ }
1606
+ }
1607
+
1608
+ // Convert to array and sort by frequency and score
1609
+ return Object.values(patternGroups)
1610
+ .filter(p => p.decisions.length >= 2)
1611
+ .map(p => ({
1612
+ type: p.type,
1613
+ pattern: p.pattern,
1614
+ occurrences: p.decisions.length,
1615
+ averageScore: Math.round((p.totalScore / p.decisions.length) * 100) / 100,
1616
+ successRate: Math.round((p.successCount / p.decisions.length) * 100) / 100
1617
+ }))
1618
+ .sort((a, b) => (b.occurrences * b.averageScore) - (a.occurrences * a.averageScore))
1619
+ .slice(0, 10);
1620
+ }
1621
+
1622
+ /**
1623
+ * Normalize decision text for pattern matching
1624
+ * @param {string} text - Decision text
1625
+ * @returns {string} Normalized text
1626
+ */
1627
+ function normalizeDecisionText(text) {
1628
+ return (text || '')
1629
+ .toLowerCase()
1630
+ .replace(/[^a-z0-9\s]/g, '')
1631
+ .replace(/\s+/g, ' ')
1632
+ .trim()
1633
+ .slice(0, 50);
1634
+ }
1635
+
1636
+ /**
1637
+ * Get impact-boosted patterns for recommendations integration
1638
+ * Returns patterns that have proven high-impact and successful
1639
+ * @param {Object} options - Options
1640
+ * @param {number} options.minScore - Minimum impact score
1641
+ * @param {number} options.minSuccessRate - Minimum success rate
1642
+ * @returns {Array} High-impact patterns for boosting recommendations
1643
+ */
1644
+ function getImpactBoostedPatterns(options = {}) {
1645
+ const {
1646
+ minScore = 3.0,
1647
+ minSuccessRate = 0.6
1648
+ } = options;
1649
+
1650
+ const decisions = readAllDecisions();
1651
+ const withImpact = decisions.map(d => ({
1652
+ ...d,
1653
+ impact: calculateImpactScore(d)
1654
+ }));
1655
+
1656
+ // Group by type and decision pattern
1657
+ const patterns = {};
1658
+ for (const d of withImpact) {
1659
+ if (d.impact.score < minScore) continue;
1660
+
1661
+ const key = `${d.type}:${normalizeDecisionText(d.decision)}`;
1662
+ if (!patterns[key]) {
1663
+ patterns[key] = {
1664
+ type: d.type,
1665
+ decision: d.decision,
1666
+ decisions: [],
1667
+ totalScore: 0,
1668
+ successCount: 0,
1669
+ totalTimeDecayWeight: 0
1670
+ };
1671
+ }
1672
+ patterns[key].decisions.push(d);
1673
+ patterns[key].totalScore += d.impact.score;
1674
+ patterns[key].totalTimeDecayWeight += d.impact.factors.timeDecayWeight;
1675
+ if (d.outcome?.status === OUTCOME_STATUS.SUCCESS) {
1676
+ patterns[key].successCount++;
1677
+ }
1678
+ }
1679
+
1680
+ // Filter and format for recommendations
1681
+ return Object.values(patterns)
1682
+ .filter(p => {
1683
+ const successRate = p.successCount / p.decisions.length;
1684
+ return p.decisions.length >= 2 && successRate >= minSuccessRate;
1685
+ })
1686
+ .map(p => {
1687
+ const successRate = p.successCount / p.decisions.length;
1688
+ const avgScore = p.totalScore / p.decisions.length;
1689
+ const avgRecency = p.totalTimeDecayWeight / p.decisions.length;
1690
+
1691
+ // Calculate boost factor for recommendations
1692
+ // Higher for recent, successful, high-impact patterns
1693
+ const boostFactor = avgScore * successRate * (0.5 + avgRecency * 0.5);
1694
+
1695
+ return {
1696
+ type: p.type,
1697
+ decision: p.decision,
1698
+ occurrences: p.decisions.length,
1699
+ averageImpactScore: Math.round(avgScore * 100) / 100,
1700
+ successRate: Math.round(successRate * 100) / 100,
1701
+ recencyWeight: Math.round(avgRecency * 100) / 100,
1702
+ boostFactor: Math.round(boostFactor * 100) / 100,
1703
+ recommendation: successRate >= 0.8 ? 'STRONGLY_RECOMMENDED' :
1704
+ successRate >= 0.6 ? 'RECOMMENDED' : 'NEUTRAL'
1705
+ };
1706
+ })
1707
+ .sort((a, b) => b.boostFactor - a.boostFactor);
1708
+ }
1709
+
1710
+ /**
1711
+ * Get impact statistics with enhanced analysis
1712
+ * @returns {object} Impact statistics
1713
+ */
1714
+ function getImpactStats() {
1715
+ const decisions = readAllDecisions();
1716
+
1717
+ if (decisions.length === 0) {
1718
+ return {
1719
+ totalDecisions: 0,
1720
+ byLevel: {},
1721
+ averageScore: 0,
1722
+ highestImpact: null,
1723
+ recentTrend: null,
1724
+ impactBoostedPatterns: []
1725
+ };
1726
+ }
1727
+
1728
+ const withImpact = decisions.map(d => ({
1729
+ ...d,
1730
+ impact: calculateImpactScore(d)
1731
+ }));
1732
+
1733
+ const byLevel = {};
1734
+ let totalScore = 0;
1735
+ let highestImpact = null;
1736
+
1737
+ for (const d of withImpact) {
1738
+ const level = d.impact.level;
1739
+ byLevel[level] = (byLevel[level] || 0) + 1;
1740
+ totalScore += d.impact.score;
1741
+
1742
+ if (!highestImpact || d.impact.score > highestImpact.impact.score) {
1743
+ highestImpact = d;
1744
+ }
1745
+ }
1746
+
1747
+ // Calculate recent trend (last 7 days vs previous 7 days)
1748
+ const now = new Date();
1749
+ const oneWeekAgo = new Date(now);
1750
+ oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
1751
+ const twoWeeksAgo = new Date(now);
1752
+ twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14);
1753
+
1754
+ const recentDecisions = withImpact.filter(d => new Date(d.timestamp) >= oneWeekAgo);
1755
+ const previousDecisions = withImpact.filter(d => {
1756
+ const dt = new Date(d.timestamp);
1757
+ return dt >= twoWeeksAgo && dt < oneWeekAgo;
1758
+ });
1759
+
1760
+ const recentAvg = recentDecisions.length > 0
1761
+ ? recentDecisions.reduce((sum, d) => sum + d.impact.score, 0) / recentDecisions.length
1762
+ : 0;
1763
+ const previousAvg = previousDecisions.length > 0
1764
+ ? previousDecisions.reduce((sum, d) => sum + d.impact.score, 0) / previousDecisions.length
1765
+ : 0;
1766
+
1767
+ const trendDirection = recentAvg > previousAvg ? 'improving' :
1768
+ recentAvg < previousAvg ? 'declining' : 'stable';
1769
+
1770
+ return {
1771
+ totalDecisions: decisions.length,
1772
+ byLevel,
1773
+ averageScore: Math.round((totalScore / decisions.length) * 100) / 100,
1774
+ highestImpact: highestImpact ? {
1775
+ id: highestImpact.id,
1776
+ type: highestImpact.type,
1777
+ decision: highestImpact.decision,
1778
+ score: highestImpact.impact.score,
1779
+ level: highestImpact.impact.level
1780
+ } : null,
1781
+ recentTrend: {
1782
+ direction: trendDirection,
1783
+ recentAverage: Math.round(recentAvg * 100) / 100,
1784
+ previousAverage: Math.round(previousAvg * 100) / 100,
1785
+ recentCount: recentDecisions.length,
1786
+ previousCount: previousDecisions.length
1787
+ },
1788
+ impactBoostedPatterns: getImpactBoostedPatterns({ minScore: 3.0, minSuccessRate: 0.6 }).slice(0, 5)
1789
+ };
423
1790
  }
424
1791
 
425
1792
  module.exports = {
1793
+ // Constants
426
1794
  DECISION_TYPES,
427
1795
  OUTCOME_STATUS,
1796
+ IMPACT_LEVELS,
1797
+ TIME_DECAY,
1798
+ IMPACT_FACTORS,
1799
+
1800
+ // Core decision tracking
428
1801
  logDecision,
429
1802
  recordOutcome,
430
1803
  readAllDecisions,
1804
+ getDecisionById,
431
1805
  getDecisionsByType,
432
1806
  getDecisionsByOutcome,
433
1807
  getRecentDecisions,
@@ -435,5 +1809,31 @@ module.exports = {
435
1809
  getRecommendation,
436
1810
  getStats,
437
1811
  getPatterns,
438
- clearHistory
1812
+ clearHistory,
1813
+ compact,
1814
+ shouldCompact,
1815
+ flushIndex,
1816
+ rebuildIndex,
1817
+
1818
+ // Impact scoring (enhanced)
1819
+ calculateImpactScore,
1820
+ calculateTimeDecay,
1821
+ calculateDownstreamImpact,
1822
+ getHighImpactDecisions,
1823
+ getImpactStats,
1824
+
1825
+ // Decision chains
1826
+ linkDecisions,
1827
+ getDownstreamDecisions,
1828
+ getUpstreamDecisions,
1829
+ trackSuccessChain,
1830
+ getSuccessfulDecisionPatterns,
1831
+
1832
+ // Impact reports and analysis
1833
+ generateImpactReport,
1834
+ getImpactBoostedPatterns,
1835
+
1836
+ // Utility functions
1837
+ getRelatedTypes,
1838
+ countDownstreamDecisions
439
1839
  };