@cloudstreamsoftware/claude-tools 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -37
- package/agents/INDEX.md +183 -0
- package/agents/architect.md +247 -0
- package/agents/build-error-resolver.md +555 -0
- package/agents/catalyst-deployer.md +132 -0
- package/agents/code-reviewer.md +121 -0
- package/agents/compliance-auditor.md +148 -0
- package/agents/creator-architect.md +395 -0
- package/agents/deluge-reviewer.md +98 -0
- package/agents/doc-updater.md +471 -0
- package/agents/e2e-runner.md +711 -0
- package/agents/planner.md +122 -0
- package/agents/refactor-cleaner.md +309 -0
- package/agents/security-reviewer.md +582 -0
- package/agents/tdd-guide.md +302 -0
- package/config/versions.json +63 -0
- package/dist/hooks/hooks.json +209 -0
- package/dist/index.js +47 -0
- package/dist/lib/asset-value.js +609 -0
- package/dist/lib/client-manager.js +300 -0
- package/dist/lib/command-matcher.js +242 -0
- package/dist/lib/cross-session-patterns.js +754 -0
- package/dist/lib/intent-classifier.js +1075 -0
- package/dist/lib/package-manager.js +374 -0
- package/dist/lib/recommendation-engine.js +597 -0
- package/dist/lib/session-memory.js +489 -0
- package/dist/lib/skill-effectiveness.js +486 -0
- package/dist/lib/skill-matcher.js +595 -0
- package/dist/lib/tutorial-metrics.js +242 -0
- package/dist/lib/tutorial-progress.js +209 -0
- package/dist/lib/tutorial-renderer.js +431 -0
- package/dist/lib/utils.js +380 -0
- package/dist/lib/verify-formatter.js +143 -0
- package/dist/lib/workflow-state.js +249 -0
- package/hooks/hooks.json +209 -0
- package/package.json +5 -1
- package/scripts/aggregate-sessions.js +290 -0
- package/scripts/branch-name-validator.js +291 -0
- package/scripts/build.js +101 -0
- package/scripts/commands/client-switch.js +231 -0
- package/scripts/deprecate-skill.js +610 -0
- package/scripts/diagnose.js +324 -0
- package/scripts/doc-freshness.js +168 -0
- package/scripts/generate-weekly-digest.js +393 -0
- package/scripts/health-check.js +270 -0
- package/scripts/hooks/credential-check.js +101 -0
- package/scripts/hooks/evaluate-session.js +81 -0
- package/scripts/hooks/pre-compact.js +66 -0
- package/scripts/hooks/prompt-analyzer.js +276 -0
- package/scripts/hooks/prompt-router.js +422 -0
- package/scripts/hooks/quality-gate-enforcer.js +371 -0
- package/scripts/hooks/session-end.js +156 -0
- package/scripts/hooks/session-start.js +195 -0
- package/scripts/hooks/skill-injector.js +333 -0
- package/scripts/hooks/suggest-compact.js +58 -0
- package/scripts/lib/asset-value.js +609 -0
- package/scripts/lib/client-manager.js +300 -0
- package/scripts/lib/command-matcher.js +242 -0
- package/scripts/lib/cross-session-patterns.js +754 -0
- package/scripts/lib/intent-classifier.js +1075 -0
- package/scripts/lib/package-manager.js +374 -0
- package/scripts/lib/recommendation-engine.js +597 -0
- package/scripts/lib/session-memory.js +489 -0
- package/scripts/lib/skill-effectiveness.js +486 -0
- package/scripts/lib/skill-matcher.js +595 -0
- package/scripts/lib/tutorial-metrics.js +242 -0
- package/scripts/lib/tutorial-progress.js +209 -0
- package/scripts/lib/tutorial-renderer.js +431 -0
- package/scripts/lib/utils.js +380 -0
- package/scripts/lib/verify-formatter.js +143 -0
- package/scripts/lib/workflow-state.js +249 -0
- package/scripts/onboard.js +363 -0
- package/scripts/quarterly-report.js +692 -0
- package/scripts/setup-package-manager.js +204 -0
- package/scripts/sync-upstream.js +391 -0
- package/scripts/test.js +108 -0
- package/scripts/tutorial-runner.js +351 -0
- package/scripts/validate-all.js +201 -0
- package/scripts/verifiers/agents.js +245 -0
- package/scripts/verifiers/config.js +186 -0
- package/scripts/verifiers/environment.js +123 -0
- package/scripts/verifiers/hooks.js +188 -0
- package/scripts/verifiers/index.js +38 -0
- package/scripts/verifiers/persistence.js +140 -0
- package/scripts/verifiers/plugin.js +215 -0
- package/scripts/verifiers/skills.js +209 -0
- package/scripts/verify-setup.js +164 -0
- package/skills/INDEX.md +157 -0
- package/skills/backend-patterns/SKILL.md +586 -0
- package/skills/backend-patterns/catalyst-patterns.md +128 -0
- package/skills/bigquery-patterns/SKILL.md +27 -0
- package/skills/bigquery-patterns/performance-optimization.md +518 -0
- package/skills/bigquery-patterns/query-patterns.md +372 -0
- package/skills/bigquery-patterns/schema-design.md +78 -0
- package/skills/cloudstream-project-template/SKILL.md +20 -0
- package/skills/cloudstream-project-template/structure.md +65 -0
- package/skills/coding-standards/SKILL.md +524 -0
- package/skills/coding-standards/deluge-standards.md +83 -0
- package/skills/compliance-patterns/SKILL.md +28 -0
- package/skills/compliance-patterns/hipaa/audit-requirements.md +251 -0
- package/skills/compliance-patterns/hipaa/baa-process.md +298 -0
- package/skills/compliance-patterns/hipaa/data-archival-strategy.md +387 -0
- package/skills/compliance-patterns/hipaa/phi-handling.md +52 -0
- package/skills/compliance-patterns/pci-dss/saq-a-requirements.md +307 -0
- package/skills/compliance-patterns/pci-dss/tokenization-patterns.md +382 -0
- package/skills/compliance-patterns/pci-dss/zoho-checkout-patterns.md +56 -0
- package/skills/compliance-patterns/soc2/access-controls.md +344 -0
- package/skills/compliance-patterns/soc2/audit-logging.md +458 -0
- package/skills/compliance-patterns/soc2/change-management.md +403 -0
- package/skills/compliance-patterns/soc2/deluge-execution-logging.md +407 -0
- package/skills/consultancy-workflows/SKILL.md +19 -0
- package/skills/consultancy-workflows/client-isolation.md +21 -0
- package/skills/consultancy-workflows/documentation-automation.md +454 -0
- package/skills/consultancy-workflows/handoff-procedures.md +257 -0
- package/skills/consultancy-workflows/knowledge-capture.md +513 -0
- package/skills/consultancy-workflows/time-tracking.md +26 -0
- package/skills/continuous-learning/SKILL.md +84 -0
- package/skills/continuous-learning/config.json +18 -0
- package/skills/continuous-learning/evaluate-session.sh +60 -0
- package/skills/continuous-learning-v2/SKILL.md +126 -0
- package/skills/continuous-learning-v2/config.json +61 -0
- package/skills/frontend-patterns/SKILL.md +635 -0
- package/skills/frontend-patterns/zoho-widget-patterns.md +103 -0
- package/skills/gcp-data-engineering/SKILL.md +36 -0
- package/skills/gcp-data-engineering/bigquery/performance-optimization.md +337 -0
- package/skills/gcp-data-engineering/dataflow/error-handling.md +496 -0
- package/skills/gcp-data-engineering/dataflow/pipeline-patterns.md +444 -0
- package/skills/gcp-data-engineering/dbt/model-organization.md +63 -0
- package/skills/gcp-data-engineering/dbt/testing-patterns.md +503 -0
- package/skills/gcp-data-engineering/medallion-architecture/bronze-layer.md +60 -0
- package/skills/gcp-data-engineering/medallion-architecture/gold-layer.md +311 -0
- package/skills/gcp-data-engineering/medallion-architecture/layer-transitions.md +517 -0
- package/skills/gcp-data-engineering/medallion-architecture/silver-layer.md +305 -0
- package/skills/gcp-data-engineering/zoho-to-gcp/data-extraction.md +543 -0
- package/skills/gcp-data-engineering/zoho-to-gcp/real-time-vs-batch.md +337 -0
- package/skills/security-review/SKILL.md +498 -0
- package/skills/security-review/compliance-checklist.md +53 -0
- package/skills/strategic-compact/SKILL.md +67 -0
- package/skills/tdd-workflow/SKILL.md +413 -0
- package/skills/tdd-workflow/zoho-testing.md +124 -0
- package/skills/tutorial/SKILL.md +249 -0
- package/skills/tutorial/docs/ACCESSIBILITY.md +169 -0
- package/skills/tutorial/lessons/00-philosophy-and-workflow.md +198 -0
- package/skills/tutorial/lessons/01-basics.md +81 -0
- package/skills/tutorial/lessons/02-training.md +86 -0
- package/skills/tutorial/lessons/03-commands.md +109 -0
- package/skills/tutorial/lessons/04-workflows.md +115 -0
- package/skills/tutorial/lessons/05-compliance.md +116 -0
- package/skills/tutorial/lessons/06-zoho.md +121 -0
- package/skills/tutorial/lessons/07-hooks-system.md +277 -0
- package/skills/tutorial/lessons/08-mcp-servers.md +316 -0
- package/skills/tutorial/lessons/09-client-management.md +215 -0
- package/skills/tutorial/lessons/10-testing-e2e.md +260 -0
- package/skills/tutorial/lessons/11-skills-deep-dive.md +272 -0
- package/skills/tutorial/lessons/12-rules-system.md +326 -0
- package/skills/tutorial/lessons/13-golden-standard-graduation.md +213 -0
- package/skills/tutorial/lessons/14-fork-setup-and-sync.md +312 -0
- package/skills/tutorial/lessons/15-living-examples-system.md +221 -0
- package/skills/tutorial/tracks/accelerated/README.md +134 -0
- package/skills/tutorial/tracks/accelerated/assessment/checkpoint-1.md +161 -0
- package/skills/tutorial/tracks/accelerated/assessment/checkpoint-2.md +175 -0
- package/skills/tutorial/tracks/accelerated/day-1-core-concepts.md +234 -0
- package/skills/tutorial/tracks/accelerated/day-2-essential-commands.md +270 -0
- package/skills/tutorial/tracks/accelerated/day-3-workflow-mastery.md +305 -0
- package/skills/tutorial/tracks/accelerated/day-4-compliance-zoho.md +304 -0
- package/skills/tutorial/tracks/accelerated/day-5-hooks-skills.md +344 -0
- package/skills/tutorial/tracks/accelerated/day-6-client-testing.md +386 -0
- package/skills/tutorial/tracks/accelerated/day-7-graduation.md +369 -0
- package/skills/zoho-patterns/CHANGELOG.md +108 -0
- package/skills/zoho-patterns/SKILL.md +446 -0
- package/skills/zoho-patterns/analytics/dashboard-patterns.md +352 -0
- package/skills/zoho-patterns/analytics/zoho-to-bigquery-pipeline.md +427 -0
- package/skills/zoho-patterns/catalyst/appsail-deployment.md +349 -0
- package/skills/zoho-patterns/catalyst/context-close-patterns.md +354 -0
- package/skills/zoho-patterns/catalyst/cron-batch-processing.md +374 -0
- package/skills/zoho-patterns/catalyst/function-patterns.md +439 -0
- package/skills/zoho-patterns/creator/form-design.md +304 -0
- package/skills/zoho-patterns/creator/publish-api-patterns.md +313 -0
- package/skills/zoho-patterns/creator/widget-integration.md +306 -0
- package/skills/zoho-patterns/creator/workflow-automation.md +253 -0
- package/skills/zoho-patterns/deluge/api-patterns.md +468 -0
- package/skills/zoho-patterns/deluge/batch-processing.md +403 -0
- package/skills/zoho-patterns/deluge/cross-app-integration.md +356 -0
- package/skills/zoho-patterns/deluge/error-handling.md +423 -0
- package/skills/zoho-patterns/deluge/syntax-reference.md +65 -0
- package/skills/zoho-patterns/integration/cors-proxy-architecture.md +426 -0
- package/skills/zoho-patterns/integration/crm-books-native-sync.md +277 -0
- package/skills/zoho-patterns/integration/oauth-token-management.md +461 -0
- 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
|
+
};
|