@cloudstreamsoftware/claude-tools 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +152 -37
  2. package/agents/INDEX.md +183 -0
  3. package/agents/architect.md +247 -0
  4. package/agents/build-error-resolver.md +555 -0
  5. package/agents/catalyst-deployer.md +132 -0
  6. package/agents/code-reviewer.md +121 -0
  7. package/agents/compliance-auditor.md +148 -0
  8. package/agents/creator-architect.md +395 -0
  9. package/agents/deluge-reviewer.md +98 -0
  10. package/agents/doc-updater.md +471 -0
  11. package/agents/e2e-runner.md +711 -0
  12. package/agents/planner.md +122 -0
  13. package/agents/refactor-cleaner.md +309 -0
  14. package/agents/security-reviewer.md +582 -0
  15. package/agents/tdd-guide.md +302 -0
  16. package/bin/cloudstream-setup.js +16 -6
  17. package/config/versions.json +63 -0
  18. package/dist/hooks/hooks.json +209 -0
  19. package/dist/index.js +47 -0
  20. package/dist/lib/asset-value.js +609 -0
  21. package/dist/lib/client-manager.js +300 -0
  22. package/dist/lib/command-matcher.js +242 -0
  23. package/dist/lib/cross-session-patterns.js +754 -0
  24. package/dist/lib/intent-classifier.js +1075 -0
  25. package/dist/lib/package-manager.js +374 -0
  26. package/dist/lib/recommendation-engine.js +597 -0
  27. package/dist/lib/session-memory.js +489 -0
  28. package/dist/lib/skill-effectiveness.js +486 -0
  29. package/dist/lib/skill-matcher.js +595 -0
  30. package/dist/lib/tutorial-metrics.js +242 -0
  31. package/dist/lib/tutorial-progress.js +209 -0
  32. package/dist/lib/tutorial-renderer.js +431 -0
  33. package/dist/lib/utils.js +380 -0
  34. package/dist/lib/verify-formatter.js +143 -0
  35. package/dist/lib/workflow-state.js +249 -0
  36. package/hooks/hooks.json +209 -0
  37. package/package.json +5 -1
  38. package/scripts/aggregate-sessions.js +290 -0
  39. package/scripts/branch-name-validator.js +291 -0
  40. package/scripts/build.js +101 -0
  41. package/scripts/commands/client-switch.js +231 -0
  42. package/scripts/deprecate-skill.js +610 -0
  43. package/scripts/diagnose.js +324 -0
  44. package/scripts/doc-freshness.js +168 -0
  45. package/scripts/generate-weekly-digest.js +393 -0
  46. package/scripts/health-check.js +270 -0
  47. package/scripts/hooks/credential-check.js +101 -0
  48. package/scripts/hooks/evaluate-session.js +81 -0
  49. package/scripts/hooks/pre-compact.js +66 -0
  50. package/scripts/hooks/prompt-analyzer.js +276 -0
  51. package/scripts/hooks/prompt-router.js +422 -0
  52. package/scripts/hooks/quality-gate-enforcer.js +371 -0
  53. package/scripts/hooks/session-end.js +156 -0
  54. package/scripts/hooks/session-start.js +195 -0
  55. package/scripts/hooks/skill-injector.js +333 -0
  56. package/scripts/hooks/suggest-compact.js +58 -0
  57. package/scripts/lib/asset-value.js +609 -0
  58. package/scripts/lib/client-manager.js +300 -0
  59. package/scripts/lib/command-matcher.js +242 -0
  60. package/scripts/lib/cross-session-patterns.js +754 -0
  61. package/scripts/lib/intent-classifier.js +1075 -0
  62. package/scripts/lib/package-manager.js +374 -0
  63. package/scripts/lib/recommendation-engine.js +597 -0
  64. package/scripts/lib/session-memory.js +489 -0
  65. package/scripts/lib/skill-effectiveness.js +486 -0
  66. package/scripts/lib/skill-matcher.js +595 -0
  67. package/scripts/lib/tutorial-metrics.js +242 -0
  68. package/scripts/lib/tutorial-progress.js +209 -0
  69. package/scripts/lib/tutorial-renderer.js +431 -0
  70. package/scripts/lib/utils.js +380 -0
  71. package/scripts/lib/verify-formatter.js +143 -0
  72. package/scripts/lib/workflow-state.js +249 -0
  73. package/scripts/onboard.js +363 -0
  74. package/scripts/quarterly-report.js +692 -0
  75. package/scripts/setup-package-manager.js +204 -0
  76. package/scripts/sync-upstream.js +391 -0
  77. package/scripts/test.js +108 -0
  78. package/scripts/tutorial-runner.js +351 -0
  79. package/scripts/validate-all.js +201 -0
  80. package/scripts/verifiers/agents.js +245 -0
  81. package/scripts/verifiers/config.js +186 -0
  82. package/scripts/verifiers/environment.js +123 -0
  83. package/scripts/verifiers/hooks.js +188 -0
  84. package/scripts/verifiers/index.js +38 -0
  85. package/scripts/verifiers/persistence.js +140 -0
  86. package/scripts/verifiers/plugin.js +215 -0
  87. package/scripts/verifiers/skills.js +209 -0
  88. package/scripts/verify-setup.js +164 -0
  89. package/skills/INDEX.md +157 -0
  90. package/skills/backend-patterns/SKILL.md +586 -0
  91. package/skills/backend-patterns/catalyst-patterns.md +128 -0
  92. package/skills/bigquery-patterns/SKILL.md +27 -0
  93. package/skills/bigquery-patterns/performance-optimization.md +518 -0
  94. package/skills/bigquery-patterns/query-patterns.md +372 -0
  95. package/skills/bigquery-patterns/schema-design.md +78 -0
  96. package/skills/cloudstream-project-template/SKILL.md +20 -0
  97. package/skills/cloudstream-project-template/structure.md +65 -0
  98. package/skills/coding-standards/SKILL.md +524 -0
  99. package/skills/coding-standards/deluge-standards.md +83 -0
  100. package/skills/compliance-patterns/SKILL.md +28 -0
  101. package/skills/compliance-patterns/hipaa/audit-requirements.md +251 -0
  102. package/skills/compliance-patterns/hipaa/baa-process.md +298 -0
  103. package/skills/compliance-patterns/hipaa/data-archival-strategy.md +387 -0
  104. package/skills/compliance-patterns/hipaa/phi-handling.md +52 -0
  105. package/skills/compliance-patterns/pci-dss/saq-a-requirements.md +307 -0
  106. package/skills/compliance-patterns/pci-dss/tokenization-patterns.md +382 -0
  107. package/skills/compliance-patterns/pci-dss/zoho-checkout-patterns.md +56 -0
  108. package/skills/compliance-patterns/soc2/access-controls.md +344 -0
  109. package/skills/compliance-patterns/soc2/audit-logging.md +458 -0
  110. package/skills/compliance-patterns/soc2/change-management.md +403 -0
  111. package/skills/compliance-patterns/soc2/deluge-execution-logging.md +407 -0
  112. package/skills/consultancy-workflows/SKILL.md +19 -0
  113. package/skills/consultancy-workflows/client-isolation.md +21 -0
  114. package/skills/consultancy-workflows/documentation-automation.md +454 -0
  115. package/skills/consultancy-workflows/handoff-procedures.md +257 -0
  116. package/skills/consultancy-workflows/knowledge-capture.md +513 -0
  117. package/skills/consultancy-workflows/time-tracking.md +26 -0
  118. package/skills/continuous-learning/SKILL.md +84 -0
  119. package/skills/continuous-learning/config.json +18 -0
  120. package/skills/continuous-learning/evaluate-session.sh +60 -0
  121. package/skills/continuous-learning-v2/SKILL.md +126 -0
  122. package/skills/continuous-learning-v2/config.json +61 -0
  123. package/skills/frontend-patterns/SKILL.md +635 -0
  124. package/skills/frontend-patterns/zoho-widget-patterns.md +103 -0
  125. package/skills/gcp-data-engineering/SKILL.md +36 -0
  126. package/skills/gcp-data-engineering/bigquery/performance-optimization.md +337 -0
  127. package/skills/gcp-data-engineering/dataflow/error-handling.md +496 -0
  128. package/skills/gcp-data-engineering/dataflow/pipeline-patterns.md +444 -0
  129. package/skills/gcp-data-engineering/dbt/model-organization.md +63 -0
  130. package/skills/gcp-data-engineering/dbt/testing-patterns.md +503 -0
  131. package/skills/gcp-data-engineering/medallion-architecture/bronze-layer.md +60 -0
  132. package/skills/gcp-data-engineering/medallion-architecture/gold-layer.md +311 -0
  133. package/skills/gcp-data-engineering/medallion-architecture/layer-transitions.md +517 -0
  134. package/skills/gcp-data-engineering/medallion-architecture/silver-layer.md +305 -0
  135. package/skills/gcp-data-engineering/zoho-to-gcp/data-extraction.md +543 -0
  136. package/skills/gcp-data-engineering/zoho-to-gcp/real-time-vs-batch.md +337 -0
  137. package/skills/security-review/SKILL.md +498 -0
  138. package/skills/security-review/compliance-checklist.md +53 -0
  139. package/skills/strategic-compact/SKILL.md +67 -0
  140. package/skills/tdd-workflow/SKILL.md +413 -0
  141. package/skills/tdd-workflow/zoho-testing.md +124 -0
  142. package/skills/tutorial/SKILL.md +249 -0
  143. package/skills/tutorial/docs/ACCESSIBILITY.md +169 -0
  144. package/skills/tutorial/lessons/00-philosophy-and-workflow.md +198 -0
  145. package/skills/tutorial/lessons/01-basics.md +81 -0
  146. package/skills/tutorial/lessons/02-training.md +86 -0
  147. package/skills/tutorial/lessons/03-commands.md +109 -0
  148. package/skills/tutorial/lessons/04-workflows.md +115 -0
  149. package/skills/tutorial/lessons/05-compliance.md +116 -0
  150. package/skills/tutorial/lessons/06-zoho.md +121 -0
  151. package/skills/tutorial/lessons/07-hooks-system.md +277 -0
  152. package/skills/tutorial/lessons/08-mcp-servers.md +316 -0
  153. package/skills/tutorial/lessons/09-client-management.md +215 -0
  154. package/skills/tutorial/lessons/10-testing-e2e.md +260 -0
  155. package/skills/tutorial/lessons/11-skills-deep-dive.md +272 -0
  156. package/skills/tutorial/lessons/12-rules-system.md +326 -0
  157. package/skills/tutorial/lessons/13-golden-standard-graduation.md +213 -0
  158. package/skills/tutorial/lessons/14-fork-setup-and-sync.md +312 -0
  159. package/skills/tutorial/lessons/15-living-examples-system.md +221 -0
  160. package/skills/tutorial/tracks/accelerated/README.md +134 -0
  161. package/skills/tutorial/tracks/accelerated/assessment/checkpoint-1.md +161 -0
  162. package/skills/tutorial/tracks/accelerated/assessment/checkpoint-2.md +175 -0
  163. package/skills/tutorial/tracks/accelerated/day-1-core-concepts.md +234 -0
  164. package/skills/tutorial/tracks/accelerated/day-2-essential-commands.md +270 -0
  165. package/skills/tutorial/tracks/accelerated/day-3-workflow-mastery.md +305 -0
  166. package/skills/tutorial/tracks/accelerated/day-4-compliance-zoho.md +304 -0
  167. package/skills/tutorial/tracks/accelerated/day-5-hooks-skills.md +344 -0
  168. package/skills/tutorial/tracks/accelerated/day-6-client-testing.md +386 -0
  169. package/skills/tutorial/tracks/accelerated/day-7-graduation.md +369 -0
  170. package/skills/zoho-patterns/CHANGELOG.md +108 -0
  171. package/skills/zoho-patterns/SKILL.md +446 -0
  172. package/skills/zoho-patterns/analytics/dashboard-patterns.md +352 -0
  173. package/skills/zoho-patterns/analytics/zoho-to-bigquery-pipeline.md +427 -0
  174. package/skills/zoho-patterns/catalyst/appsail-deployment.md +349 -0
  175. package/skills/zoho-patterns/catalyst/context-close-patterns.md +354 -0
  176. package/skills/zoho-patterns/catalyst/cron-batch-processing.md +374 -0
  177. package/skills/zoho-patterns/catalyst/function-patterns.md +439 -0
  178. package/skills/zoho-patterns/creator/form-design.md +304 -0
  179. package/skills/zoho-patterns/creator/publish-api-patterns.md +313 -0
  180. package/skills/zoho-patterns/creator/widget-integration.md +306 -0
  181. package/skills/zoho-patterns/creator/workflow-automation.md +253 -0
  182. package/skills/zoho-patterns/deluge/api-patterns.md +468 -0
  183. package/skills/zoho-patterns/deluge/batch-processing.md +403 -0
  184. package/skills/zoho-patterns/deluge/cross-app-integration.md +356 -0
  185. package/skills/zoho-patterns/deluge/error-handling.md +423 -0
  186. package/skills/zoho-patterns/deluge/syntax-reference.md +65 -0
  187. package/skills/zoho-patterns/integration/cors-proxy-architecture.md +426 -0
  188. package/skills/zoho-patterns/integration/crm-books-native-sync.md +277 -0
  189. package/skills/zoho-patterns/integration/oauth-token-management.md +461 -0
  190. package/skills/zoho-patterns/integration/zoho-flow-patterns.md +334 -0
@@ -0,0 +1,597 @@
1
+ /**
2
+ * Recommendation Engine
3
+ *
4
+ * Transforms aggregated patterns into actionable recommendations.
5
+ * Prioritizes by impact and actionability, tracks dismissals,
6
+ * and measures recommendation effectiveness.
7
+ *
8
+ * Key capabilities:
9
+ * 1. Generate recommendations from pattern analysis
10
+ * 2. Prioritize by impact and actionability
11
+ * 3. Track recommendation lifecycle (created, acted, dismissed)
12
+ * 4. Surface top recommendations at session start
13
+ * 5. Learn from recommendation outcomes
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const {
19
+ getClaudeDir,
20
+ ensureDir,
21
+ readFile,
22
+ writeFile,
23
+ getDateTimeString,
24
+ getDateString,
25
+ log,
26
+ } = require('./utils');
27
+
28
+ // Paths
29
+ const KNOWLEDGE_DIR = path.join(getClaudeDir(), 'knowledge');
30
+ const AGGREGATED_DIR = path.join(KNOWLEDGE_DIR, 'aggregated');
31
+ const RECOMMENDATIONS_FILE = path.join(AGGREGATED_DIR, 'recommendations.json');
32
+ const HISTORY_FILE = path.join(AGGREGATED_DIR, 'recommendation-history.json');
33
+
34
+ // Recommendation types
35
+ const RECOMMENDATION_TYPES = {
36
+ SKILL_GAP: 'skill_gap', // You don't use skill X but it prevents Y
37
+ PATTERN_ALERT: 'pattern_alert', // Recurring issue detected
38
+ IMPROVEMENT: 'improvement', // You're getting better at X
39
+ SKILL_SUGGEST: 'skill_suggest', // Consider learning skill X
40
+ REVIEW_NEEDED: 'review_needed', // Pattern needs review/update
41
+ EFFICIENCY: 'efficiency', // Process optimization opportunity
42
+ };
43
+
44
+ // Priority levels
45
+ const PRIORITIES = {
46
+ HIGH: 'high',
47
+ MEDIUM: 'medium',
48
+ LOW: 'low',
49
+ };
50
+
51
+ // Thresholds
52
+ const THRESHOLDS = {
53
+ DISMISSAL_THRESHOLD: 3, // Stop showing after 3 dismissals
54
+ EXPIRATION_DAYS: 30, // Recommendations expire after 30 days
55
+ HIGH_PRIORITY_COUNT: 5, // 5+ occurrences = high priority
56
+ MEDIUM_PRIORITY_COUNT: 3, // 3+ occurrences = medium priority
57
+ IMPROVEMENT_THRESHOLD: -25, // -25% = improvement worthy of note
58
+ WORSENING_THRESHOLD: 25, // +25% = concerning trend
59
+ };
60
+
61
+ /**
62
+ * Load current recommendations
63
+ * @returns {object} { recommendations, lastUpdated }
64
+ */
65
+ function loadRecommendations() {
66
+ ensureDir(AGGREGATED_DIR);
67
+
68
+ try {
69
+ if (fs.existsSync(RECOMMENDATIONS_FILE)) {
70
+ const content = fs.readFileSync(RECOMMENDATIONS_FILE, 'utf8');
71
+ return JSON.parse(content);
72
+ }
73
+ } catch (err) {
74
+ log(`[RecommendationEngine] Error loading recommendations: ${err.message}`);
75
+ }
76
+
77
+ return { recommendations: [], lastUpdated: null };
78
+ }
79
+
80
+ /**
81
+ * Save recommendations to disk
82
+ * @param {Array} recommendations - Array of recommendation objects
83
+ */
84
+ function saveRecommendations(recommendations) {
85
+ ensureDir(AGGREGATED_DIR);
86
+
87
+ const data = {
88
+ recommendations,
89
+ lastUpdated: getDateTimeString(),
90
+ count: recommendations.length,
91
+ };
92
+
93
+ try {
94
+ fs.writeFileSync(RECOMMENDATIONS_FILE, JSON.stringify(data, null, 2), 'utf8');
95
+ return true;
96
+ } catch (err) {
97
+ log(`[RecommendationEngine] Error saving recommendations: ${err.message}`);
98
+ return false;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Load recommendation history (for learning)
104
+ * @returns {object} { history, stats }
105
+ */
106
+ function loadHistory() {
107
+ try {
108
+ if (fs.existsSync(HISTORY_FILE)) {
109
+ const content = fs.readFileSync(HISTORY_FILE, 'utf8');
110
+ return JSON.parse(content);
111
+ }
112
+ } catch {
113
+ // Start fresh
114
+ }
115
+
116
+ return {
117
+ history: [],
118
+ stats: {
119
+ total: 0,
120
+ acted: 0,
121
+ dismissed: 0,
122
+ expired: 0,
123
+ },
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Save recommendation history
129
+ * @param {object} historyData - History data to save
130
+ */
131
+ function saveHistory(historyData) {
132
+ ensureDir(AGGREGATED_DIR);
133
+
134
+ try {
135
+ fs.writeFileSync(HISTORY_FILE, JSON.stringify(historyData, null, 2), 'utf8');
136
+ return true;
137
+ } catch (err) {
138
+ log(`[RecommendationEngine] Error saving history: ${err.message}`);
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Generate a unique recommendation ID
145
+ * @returns {string} Unique ID
146
+ */
147
+ function generateRecommendationId() {
148
+ const date = getDateString();
149
+ const random = Math.random().toString(36).substring(2, 8);
150
+ return `rec-${date}-${random}`;
151
+ }
152
+
153
+ /**
154
+ * Calculate priority based on pattern data
155
+ * @param {object} pattern - Pattern data
156
+ * @returns {string} Priority level
157
+ */
158
+ function calculatePriority(pattern) {
159
+ const count = pattern.totalCount || pattern.sessionCount || 0;
160
+ const confidence = pattern.confidence || 0;
161
+
162
+ if (count >= THRESHOLDS.HIGH_PRIORITY_COUNT || confidence >= 0.8) {
163
+ return PRIORITIES.HIGH;
164
+ }
165
+
166
+ if (count >= THRESHOLDS.MEDIUM_PRIORITY_COUNT || confidence >= 0.6) {
167
+ return PRIORITIES.MEDIUM;
168
+ }
169
+
170
+ return PRIORITIES.LOW;
171
+ }
172
+
173
+ /**
174
+ * Generate recommendations from analysis data
175
+ * @param {object} analysis - Analysis data from cross-session-patterns
176
+ * @returns {object} { recommendations, summary }
177
+ */
178
+ function generateRecommendations(analysis) {
179
+ const recommendations = [];
180
+ const { history } = loadHistory();
181
+
182
+ // Get dismissed recommendation IDs to avoid re-suggesting
183
+ const dismissedPatterns = new Set();
184
+ for (const item of history) {
185
+ if (item.outcome === 'dismissed' && item.dismissCount >= THRESHOLDS.DISMISSAL_THRESHOLD) {
186
+ dismissedPatterns.add(item.pattern);
187
+ }
188
+ }
189
+
190
+ // 1. Pattern alerts from high-confidence patterns
191
+ if (analysis.patterns?.highConfidence) {
192
+ for (const pattern of analysis.patterns.highConfidence) {
193
+ const patternKey = `${pattern.category}::${pattern.original}`;
194
+
195
+ if (dismissedPatterns.has(patternKey)) continue;
196
+
197
+ recommendations.push({
198
+ id: generateRecommendationId(),
199
+ type: RECOMMENDATION_TYPES.PATTERN_ALERT,
200
+ priority: calculatePriority(pattern),
201
+ category: pattern.category,
202
+ title: `Recurring: ${truncate(pattern.original, 40)}`,
203
+ message: `This issue appeared in ${pattern.sessionCount} sessions. ${pattern.corrected || 'Review and address.'}`,
204
+ action: null,
205
+ context: {
206
+ pattern: patternKey,
207
+ occurrences: pattern.totalCount,
208
+ sessions: pattern.sessionCount,
209
+ confidence: pattern.confidence,
210
+ },
211
+ created: getDateString(),
212
+ expires: getExpirationDate(),
213
+ });
214
+ }
215
+ }
216
+
217
+ // 2. Improvement notifications from trends
218
+ if (analysis.trends?.improving) {
219
+ for (const trend of analysis.trends.improving.slice(0, 3)) {
220
+ if (trend.percentChange <= THRESHOLDS.IMPROVEMENT_THRESHOLD) {
221
+ recommendations.push({
222
+ id: generateRecommendationId(),
223
+ type: RECOMMENDATION_TYPES.IMPROVEMENT,
224
+ priority: PRIORITIES.LOW,
225
+ category: trend.category,
226
+ title: `Improving: ${truncate(trend.original, 40)}`,
227
+ message: `Down ${Math.abs(trend.percentChange)}% recently. Keep it up!`,
228
+ action: null,
229
+ context: {
230
+ pattern: trend.pattern,
231
+ change: trend.percentChange,
232
+ },
233
+ created: getDateString(),
234
+ expires: getExpirationDate(7), // Short expiration for improvements
235
+ });
236
+ }
237
+ }
238
+ }
239
+
240
+ // 3. Worsening trend alerts
241
+ if (analysis.trends?.worsening) {
242
+ for (const trend of analysis.trends.worsening.slice(0, 3)) {
243
+ if (trend.percentChange >= THRESHOLDS.WORSENING_THRESHOLD) {
244
+ const patternKey = trend.pattern;
245
+
246
+ if (dismissedPatterns.has(patternKey)) continue;
247
+
248
+ recommendations.push({
249
+ id: generateRecommendationId(),
250
+ type: RECOMMENDATION_TYPES.PATTERN_ALERT,
251
+ priority: PRIORITIES.HIGH,
252
+ category: trend.category,
253
+ title: `Increasing: ${truncate(trend.original, 40)}`,
254
+ message: `Up ${trend.percentChange}% recently. Consider adding to quality gates.`,
255
+ action: null,
256
+ context: {
257
+ pattern: patternKey,
258
+ change: trend.percentChange,
259
+ wasCount: trend.firstPeriod,
260
+ nowCount: trend.secondPeriod,
261
+ },
262
+ created: getDateString(),
263
+ expires: getExpirationDate(),
264
+ });
265
+ }
266
+ }
267
+ }
268
+
269
+ // 4. Skill gap recommendations from skill-correction matrix
270
+ if (analysis.skillMatrix) {
271
+ const highPreventionSkills = Object.values(analysis.skillMatrix)
272
+ .filter((s) => s.preventionRate > 0.3 && s.applications >= 3)
273
+ .sort((a, b) => b.preventionRate - a.preventionRate);
274
+
275
+ for (const skill of highPreventionSkills.slice(0, 2)) {
276
+ recommendations.push({
277
+ id: generateRecommendationId(),
278
+ type: RECOMMENDATION_TYPES.SKILL_SUGGEST,
279
+ priority: PRIORITIES.MEDIUM,
280
+ category: 'approach',
281
+ title: `Skill: ${skill.skill}`,
282
+ message: `This skill reduces corrections by ${Math.round(skill.preventionRate * 100)}%. Used ${skill.applications} times.`,
283
+ action: `/${skill.skill.replace('skill-', '')}`,
284
+ context: {
285
+ skill: skill.skill,
286
+ preventionRate: skill.preventionRate,
287
+ effectiveness: skill.effectiveness,
288
+ },
289
+ created: getDateString(),
290
+ expires: getExpirationDate(),
291
+ });
292
+ }
293
+ }
294
+
295
+ // 5. Technology-specific recommendations
296
+ if (analysis.techPatterns) {
297
+ for (const [tech, data] of Object.entries(analysis.techPatterns)) {
298
+ if (data.correctionRate > 2 && data.topIssues.length > 0) {
299
+ const topIssue = data.topIssues[0];
300
+
301
+ recommendations.push({
302
+ id: generateRecommendationId(),
303
+ type: RECOMMENDATION_TYPES.EFFICIENCY,
304
+ priority: PRIORITIES.MEDIUM,
305
+ category: topIssue.category,
306
+ title: `${tech}: ${truncate(topIssue.original, 30)}`,
307
+ message: `Most common issue with ${tech} (${topIssue.count} times). Review patterns.`,
308
+ action: null,
309
+ context: {
310
+ technology: tech,
311
+ topIssue: topIssue.original,
312
+ count: topIssue.count,
313
+ },
314
+ created: getDateString(),
315
+ expires: getExpirationDate(),
316
+ });
317
+ }
318
+ }
319
+ }
320
+
321
+ // Sort by priority
322
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
323
+ recommendations.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
324
+
325
+ // Remove duplicates by pattern
326
+ const seen = new Set();
327
+ const uniqueRecommendations = recommendations.filter((r) => {
328
+ const key = r.context?.pattern || r.title;
329
+ if (seen.has(key)) return false;
330
+ seen.add(key);
331
+ return true;
332
+ });
333
+
334
+ // Save recommendations
335
+ saveRecommendations(uniqueRecommendations);
336
+
337
+ return {
338
+ recommendations: uniqueRecommendations,
339
+ summary: {
340
+ total: uniqueRecommendations.length,
341
+ high: uniqueRecommendations.filter((r) => r.priority === 'high').length,
342
+ medium: uniqueRecommendations.filter((r) => r.priority === 'medium').length,
343
+ low: uniqueRecommendations.filter((r) => r.priority === 'low').length,
344
+ },
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Get top recommendations for session start
350
+ * @param {number} limit - Maximum number to return
351
+ * @returns {Array} Top recommendations
352
+ */
353
+ function getSessionStartRecommendations(limit = 3) {
354
+ const { recommendations } = loadRecommendations();
355
+
356
+ if (!recommendations || recommendations.length === 0) {
357
+ return [];
358
+ }
359
+
360
+ const now = new Date();
361
+
362
+ // Filter out expired recommendations
363
+ const active = recommendations.filter((r) => {
364
+ if (!r.expires) return true;
365
+ const expDate = new Date(r.expires);
366
+ return expDate > now;
367
+ });
368
+
369
+ // Sort by priority and recency
370
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
371
+ active.sort((a, b) => {
372
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
373
+ if (priorityDiff !== 0) return priorityDiff;
374
+
375
+ // Within same priority, prefer newer
376
+ return new Date(b.created) - new Date(a.created);
377
+ });
378
+
379
+ return active.slice(0, limit);
380
+ }
381
+
382
+ /**
383
+ * Dismiss a recommendation
384
+ * @param {string} id - Recommendation ID
385
+ * @param {string} reason - Reason for dismissal (optional)
386
+ * @returns {boolean} Success
387
+ */
388
+ function dismissRecommendation(id, reason = '') {
389
+ const { recommendations } = loadRecommendations();
390
+ const historyData = loadHistory();
391
+
392
+ // Find the recommendation
393
+ const rec = recommendations.find((r) => r.id === id);
394
+ if (!rec) return false;
395
+
396
+ // Remove from active recommendations
397
+ const updated = recommendations.filter((r) => r.id !== id);
398
+ saveRecommendations(updated);
399
+
400
+ // Add to history
401
+ const historyEntry = historyData.history.find((h) => h.pattern === rec.context?.pattern);
402
+
403
+ if (historyEntry) {
404
+ historyEntry.dismissCount = (historyEntry.dismissCount || 0) + 1;
405
+ historyEntry.lastDismissed = getDateTimeString();
406
+ historyEntry.outcome = 'dismissed';
407
+ } else {
408
+ historyData.history.push({
409
+ pattern: rec.context?.pattern || rec.title,
410
+ recommendationId: id,
411
+ type: rec.type,
412
+ outcome: 'dismissed',
413
+ dismissCount: 1,
414
+ reason,
415
+ firstSeen: rec.created,
416
+ lastDismissed: getDateTimeString(),
417
+ });
418
+ }
419
+
420
+ historyData.stats.dismissed++;
421
+ saveHistory(historyData);
422
+
423
+ return true;
424
+ }
425
+
426
+ /**
427
+ * Record that a recommendation was acted upon
428
+ * @param {string} id - Recommendation ID
429
+ * @param {string} outcome - Outcome description
430
+ * @returns {boolean} Success
431
+ */
432
+ function recordRecommendationOutcome(id, outcome = 'acted') {
433
+ const { recommendations } = loadRecommendations();
434
+ const historyData = loadHistory();
435
+
436
+ const rec = recommendations.find((r) => r.id === id);
437
+ if (!rec) return false;
438
+
439
+ // Remove from active
440
+ const updated = recommendations.filter((r) => r.id !== id);
441
+ saveRecommendations(updated);
442
+
443
+ // Add to history with positive outcome
444
+ historyData.history.push({
445
+ pattern: rec.context?.pattern || rec.title,
446
+ recommendationId: id,
447
+ type: rec.type,
448
+ outcome: outcome,
449
+ actedOn: getDateTimeString(),
450
+ });
451
+
452
+ historyData.stats.acted++;
453
+ saveHistory(historyData);
454
+
455
+ return true;
456
+ }
457
+
458
+ /**
459
+ * Get recommendation statistics
460
+ * @returns {object} Statistics about recommendations
461
+ */
462
+ function getRecommendationStats() {
463
+ const { recommendations } = loadRecommendations();
464
+ const historyData = loadHistory();
465
+
466
+ const active = recommendations.filter((r) => {
467
+ if (!r.expires) return true;
468
+ return new Date(r.expires) > new Date();
469
+ });
470
+
471
+ return {
472
+ active: active.length,
473
+ byPriority: {
474
+ high: active.filter((r) => r.priority === 'high').length,
475
+ medium: active.filter((r) => r.priority === 'medium').length,
476
+ low: active.filter((r) => r.priority === 'low').length,
477
+ },
478
+ byType: {
479
+ pattern_alert: active.filter((r) => r.type === RECOMMENDATION_TYPES.PATTERN_ALERT).length,
480
+ improvement: active.filter((r) => r.type === RECOMMENDATION_TYPES.IMPROVEMENT).length,
481
+ skill_suggest: active.filter((r) => r.type === RECOMMENDATION_TYPES.SKILL_SUGGEST).length,
482
+ efficiency: active.filter((r) => r.type === RECOMMENDATION_TYPES.EFFICIENCY).length,
483
+ },
484
+ history: historyData.stats,
485
+ };
486
+ }
487
+
488
+ /**
489
+ * Format recommendations for display
490
+ * @param {Array} recommendations - Recommendations to format
491
+ * @returns {string} Formatted string
492
+ */
493
+ function formatRecommendations(recommendations) {
494
+ if (!recommendations || recommendations.length === 0) {
495
+ return 'No recommendations at this time.';
496
+ }
497
+
498
+ let output = '';
499
+
500
+ for (const rec of recommendations) {
501
+ const emoji = rec.priority === 'high' ? '🔴' : rec.priority === 'medium' ? '🟠' : '🟡';
502
+
503
+ output += `${emoji} ${rec.title}\n`;
504
+ output += ` ${rec.message}\n`;
505
+
506
+ if (rec.action) {
507
+ output += ` Action: ${rec.action}\n`;
508
+ }
509
+
510
+ output += '\n';
511
+ }
512
+
513
+ return output;
514
+ }
515
+
516
+ /**
517
+ * Clear expired recommendations
518
+ * @returns {number} Number of recommendations cleared
519
+ */
520
+ function clearExpiredRecommendations() {
521
+ const { recommendations } = loadRecommendations();
522
+ const historyData = loadHistory();
523
+ const now = new Date();
524
+
525
+ const active = [];
526
+ let expired = 0;
527
+
528
+ for (const rec of recommendations) {
529
+ if (rec.expires && new Date(rec.expires) <= now) {
530
+ expired++;
531
+
532
+ // Add to history as expired
533
+ historyData.history.push({
534
+ pattern: rec.context?.pattern || rec.title,
535
+ recommendationId: rec.id,
536
+ type: rec.type,
537
+ outcome: 'expired',
538
+ expiredOn: getDateTimeString(),
539
+ });
540
+ } else {
541
+ active.push(rec);
542
+ }
543
+ }
544
+
545
+ if (expired > 0) {
546
+ saveRecommendations(active);
547
+ historyData.stats.expired += expired;
548
+ saveHistory(historyData);
549
+ }
550
+
551
+ return expired;
552
+ }
553
+
554
+ // Helper functions
555
+
556
+ /**
557
+ * Truncate string to specified length
558
+ */
559
+ function truncate(str, length) {
560
+ if (!str) return '';
561
+ if (str.length <= length) return str;
562
+ return str.substring(0, length - 3) + '...';
563
+ }
564
+
565
+ /**
566
+ * Get expiration date
567
+ * @param {number} days - Days from now (default: 30)
568
+ */
569
+ function getExpirationDate(days = THRESHOLDS.EXPIRATION_DAYS) {
570
+ const date = new Date();
571
+ date.setDate(date.getDate() + days);
572
+ return date.toISOString().split('T')[0];
573
+ }
574
+
575
+ module.exports = {
576
+ // Core functions
577
+ generateRecommendations,
578
+ getSessionStartRecommendations,
579
+
580
+ // Lifecycle management
581
+ dismissRecommendation,
582
+ recordRecommendationOutcome,
583
+ clearExpiredRecommendations,
584
+
585
+ // Data access
586
+ loadRecommendations,
587
+ saveRecommendations,
588
+ getRecommendationStats,
589
+
590
+ // Formatting
591
+ formatRecommendations,
592
+
593
+ // Constants
594
+ RECOMMENDATION_TYPES,
595
+ PRIORITIES,
596
+ THRESHOLDS,
597
+ };