pmp-gywd 3.3.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/LICENSE +27 -0
- package/README.md +567 -0
- package/bin/install.js +348 -0
- package/commands/gywd/add-phase.md +207 -0
- package/commands/gywd/anticipate.md +271 -0
- package/commands/gywd/bootstrap.md +336 -0
- package/commands/gywd/challenge.md +344 -0
- package/commands/gywd/check-drift.md +144 -0
- package/commands/gywd/complete-milestone.md +106 -0
- package/commands/gywd/consider-issues.md +202 -0
- package/commands/gywd/context.md +93 -0
- package/commands/gywd/create-roadmap.md +115 -0
- package/commands/gywd/deps.md +169 -0
- package/commands/gywd/digest.md +138 -0
- package/commands/gywd/discuss-milestone.md +47 -0
- package/commands/gywd/discuss-phase.md +60 -0
- package/commands/gywd/execute-plan.md +161 -0
- package/commands/gywd/extract-decisions.md +325 -0
- package/commands/gywd/health.md +150 -0
- package/commands/gywd/help.md +556 -0
- package/commands/gywd/history.md +278 -0
- package/commands/gywd/impact.md +317 -0
- package/commands/gywd/init.md +95 -0
- package/commands/gywd/insert-phase.md +227 -0
- package/commands/gywd/list-phase-assumptions.md +50 -0
- package/commands/gywd/map-codebase.md +84 -0
- package/commands/gywd/memory.md +159 -0
- package/commands/gywd/new-milestone.md +59 -0
- package/commands/gywd/new-project.md +315 -0
- package/commands/gywd/pause-work.md +123 -0
- package/commands/gywd/plan-fix.md +205 -0
- package/commands/gywd/plan-phase.md +93 -0
- package/commands/gywd/preview-plan.md +139 -0
- package/commands/gywd/profile.md +363 -0
- package/commands/gywd/progress.md +317 -0
- package/commands/gywd/remove-phase.md +338 -0
- package/commands/gywd/research-phase.md +91 -0
- package/commands/gywd/resume-work.md +40 -0
- package/commands/gywd/rollback.md +179 -0
- package/commands/gywd/status.md +42 -0
- package/commands/gywd/sync-github.md +234 -0
- package/commands/gywd/verify-work.md +71 -0
- package/commands/gywd/why.md +251 -0
- package/docs/COMMANDS.md +722 -0
- package/docs/CONTRIBUTING.md +342 -0
- package/docs/EXAMPLES.md +535 -0
- package/docs/GETTING-STARTED.md +262 -0
- package/docs/README.md +55 -0
- package/docs/RELEASING.md +159 -0
- package/get-your-work-done/core/agent-patterns.md +331 -0
- package/get-your-work-done/core/architecture.md +334 -0
- package/get-your-work-done/core/context-model-schema.json +154 -0
- package/get-your-work-done/core/decisions-schema.json +193 -0
- package/get-your-work-done/core/learning-state-schema.json +133 -0
- package/get-your-work-done/core/profile-schema.json +257 -0
- package/get-your-work-done/references/adaptive-decomposition.md +175 -0
- package/get-your-work-done/references/checkpoints.md +287 -0
- package/get-your-work-done/references/confidence-scoring.md +169 -0
- package/get-your-work-done/references/continuation-format.md +255 -0
- package/get-your-work-done/references/git-integration.md +254 -0
- package/get-your-work-done/references/plan-format.md +428 -0
- package/get-your-work-done/references/principles.md +157 -0
- package/get-your-work-done/references/questioning.md +162 -0
- package/get-your-work-done/references/research-pitfalls.md +215 -0
- package/get-your-work-done/references/scope-estimation.md +172 -0
- package/get-your-work-done/references/tdd.md +263 -0
- package/get-your-work-done/templates/codebase/architecture.md +255 -0
- package/get-your-work-done/templates/codebase/concerns.md +310 -0
- package/get-your-work-done/templates/codebase/conventions.md +307 -0
- package/get-your-work-done/templates/codebase/integrations.md +280 -0
- package/get-your-work-done/templates/codebase/stack.md +186 -0
- package/get-your-work-done/templates/codebase/structure.md +285 -0
- package/get-your-work-done/templates/codebase/testing.md +480 -0
- package/get-your-work-done/templates/config.json +18 -0
- package/get-your-work-done/templates/context.md +161 -0
- package/get-your-work-done/templates/continue-here.md +78 -0
- package/get-your-work-done/templates/discovery.md +146 -0
- package/get-your-work-done/templates/issues.md +32 -0
- package/get-your-work-done/templates/milestone-archive.md +123 -0
- package/get-your-work-done/templates/milestone-context.md +93 -0
- package/get-your-work-done/templates/milestone.md +115 -0
- package/get-your-work-done/templates/phase-prompt.md +303 -0
- package/get-your-work-done/templates/project.md +184 -0
- package/get-your-work-done/templates/research.md +529 -0
- package/get-your-work-done/templates/roadmap.md +196 -0
- package/get-your-work-done/templates/state.md +210 -0
- package/get-your-work-done/templates/summary.md +273 -0
- package/get-your-work-done/templates/uat-issues.md +143 -0
- package/get-your-work-done/workflows/complete-milestone.md +643 -0
- package/get-your-work-done/workflows/create-milestone.md +416 -0
- package/get-your-work-done/workflows/create-roadmap.md +481 -0
- package/get-your-work-done/workflows/discovery-phase.md +293 -0
- package/get-your-work-done/workflows/discuss-milestone.md +236 -0
- package/get-your-work-done/workflows/discuss-phase.md +247 -0
- package/get-your-work-done/workflows/execute-phase.md +1625 -0
- package/get-your-work-done/workflows/list-phase-assumptions.md +178 -0
- package/get-your-work-done/workflows/map-codebase.md +434 -0
- package/get-your-work-done/workflows/plan-phase.md +488 -0
- package/get-your-work-done/workflows/research-phase.md +436 -0
- package/get-your-work-done/workflows/resume-project.md +287 -0
- package/get-your-work-done/workflows/transition.md +580 -0
- package/get-your-work-done/workflows/verify-work.md +202 -0
- package/lib/automation/dependency-analyzer.js +635 -0
- package/lib/automation/doc-generator.js +643 -0
- package/lib/automation/index.js +42 -0
- package/lib/automation/test-generator.js +628 -0
- package/lib/context/context-analyzer.js +554 -0
- package/lib/context/context-cache.js +426 -0
- package/lib/context/context-predictor.js +622 -0
- package/lib/context/index.js +44 -0
- package/lib/memory/confidence-calibrator.js +484 -0
- package/lib/memory/feedback-collector.js +551 -0
- package/lib/memory/global-memory.js +465 -0
- package/lib/memory/index.js +75 -0
- package/lib/memory/pattern-aggregator.js +487 -0
- package/lib/memory/team-sync.js +501 -0
- package/lib/profile/index.js +24 -0
- package/lib/profile/pattern-learner.js +303 -0
- package/lib/profile/profile-manager.js +445 -0
- package/lib/questioning/index.js +49 -0
- package/lib/questioning/question-engine.js +311 -0
- package/lib/questioning/question-templates.js +315 -0
- package/lib/validators/command-validator.js +188 -0
- package/lib/validators/index.js +29 -0
- package/lib/validators/schema-validator.js +183 -0
- package/package.json +61 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Feedback storage location
|
|
9
|
+
*/
|
|
10
|
+
const FEEDBACK_DIR = path.join(os.homedir(), '.gywd', 'feedback');
|
|
11
|
+
const FEEDBACK_FILE = path.join(FEEDBACK_DIR, 'history.json');
|
|
12
|
+
const STATS_FILE = path.join(FEEDBACK_DIR, 'stats.json');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Feedback types
|
|
16
|
+
*/
|
|
17
|
+
const FEEDBACK_TYPES = {
|
|
18
|
+
ACCEPTED: 'accepted',
|
|
19
|
+
REJECTED: 'rejected',
|
|
20
|
+
MODIFIED: 'modified',
|
|
21
|
+
IGNORED: 'ignored',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Suggestion categories
|
|
26
|
+
*/
|
|
27
|
+
const SUGGESTION_CATEGORIES = {
|
|
28
|
+
PATTERN: 'pattern',
|
|
29
|
+
CODE: 'code',
|
|
30
|
+
QUESTION: 'question',
|
|
31
|
+
PREDICTION: 'prediction',
|
|
32
|
+
RECOMMENDATION: 'recommendation',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* FeedbackCollector - Tracks suggestion acceptance/rejection
|
|
37
|
+
*
|
|
38
|
+
* Learns from which suggestions were accepted, rejected, or modified
|
|
39
|
+
* to improve future recommendations.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const collector = new FeedbackCollector();
|
|
43
|
+
* collector.init();
|
|
44
|
+
*
|
|
45
|
+
* // Record a suggestion
|
|
46
|
+
* const suggestionId = collector.recordSuggestion({
|
|
47
|
+
* category: 'pattern',
|
|
48
|
+
* type: 'naming',
|
|
49
|
+
* suggestion: 'camelCase',
|
|
50
|
+
* context: { file: 'src/utils.js' }
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // Later, record feedback
|
|
54
|
+
* collector.recordFeedback(suggestionId, 'accepted');
|
|
55
|
+
*/
|
|
56
|
+
class FeedbackCollector {
|
|
57
|
+
constructor() {
|
|
58
|
+
this.history = [];
|
|
59
|
+
this.stats = {
|
|
60
|
+
total: 0,
|
|
61
|
+
byCategory: {},
|
|
62
|
+
byType: {},
|
|
63
|
+
acceptanceRate: 0,
|
|
64
|
+
};
|
|
65
|
+
this.pendingSuggestions = new Map();
|
|
66
|
+
this.initialized = false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initialize the feedback collector
|
|
71
|
+
* @returns {FeedbackCollector} this for chaining
|
|
72
|
+
*/
|
|
73
|
+
init() {
|
|
74
|
+
this._ensureDirectories();
|
|
75
|
+
this._loadData();
|
|
76
|
+
this.initialized = true;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Ensure feedback directories exist
|
|
82
|
+
* @private
|
|
83
|
+
*/
|
|
84
|
+
_ensureDirectories() {
|
|
85
|
+
if (!fs.existsSync(FEEDBACK_DIR)) {
|
|
86
|
+
fs.mkdirSync(FEEDBACK_DIR, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load existing feedback data
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
_loadData() {
|
|
95
|
+
this.history = this._loadFile(FEEDBACK_FILE, []);
|
|
96
|
+
this.stats = this._loadFile(STATS_FILE, {
|
|
97
|
+
total: 0,
|
|
98
|
+
byCategory: {},
|
|
99
|
+
byType: {},
|
|
100
|
+
acceptanceRate: 0,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Load a JSON file safely
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
_loadFile(filePath, defaultValue) {
|
|
109
|
+
try {
|
|
110
|
+
if (fs.existsSync(filePath)) {
|
|
111
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
// Return default on error
|
|
115
|
+
}
|
|
116
|
+
return defaultValue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Save a JSON file
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
_saveFile(filePath, data) {
|
|
124
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Save all data
|
|
129
|
+
*/
|
|
130
|
+
save() {
|
|
131
|
+
this._ensureDirectories();
|
|
132
|
+
this._saveFile(FEEDBACK_FILE, this.history);
|
|
133
|
+
this._saveFile(STATS_FILE, this.stats);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ==================== SUGGESTION RECORDING ====================
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Record a suggestion that was made
|
|
140
|
+
* @param {object} suggestion - Suggestion details
|
|
141
|
+
* @param {string} suggestion.category - Category (pattern, code, question, etc.)
|
|
142
|
+
* @param {string} suggestion.type - Specific type within category
|
|
143
|
+
* @param {string} suggestion.suggestion - The actual suggestion
|
|
144
|
+
* @param {object} [suggestion.context] - Additional context
|
|
145
|
+
* @param {number} [suggestion.confidence] - Confidence level 0-1
|
|
146
|
+
* @returns {string} Suggestion ID for tracking
|
|
147
|
+
*/
|
|
148
|
+
recordSuggestion(suggestion) {
|
|
149
|
+
if (!this.initialized) this.init();
|
|
150
|
+
|
|
151
|
+
const id = `sug-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
152
|
+
|
|
153
|
+
const record = {
|
|
154
|
+
id,
|
|
155
|
+
category: suggestion.category || 'unknown',
|
|
156
|
+
type: suggestion.type || 'unknown',
|
|
157
|
+
suggestion: suggestion.suggestion,
|
|
158
|
+
context: suggestion.context || {},
|
|
159
|
+
confidence: suggestion.confidence || 0.5,
|
|
160
|
+
createdAt: new Date().toISOString(),
|
|
161
|
+
feedback: null,
|
|
162
|
+
feedbackAt: null,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
this.pendingSuggestions.set(id, record);
|
|
166
|
+
return id;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Record feedback for a suggestion
|
|
171
|
+
* @param {string} suggestionId - The suggestion ID
|
|
172
|
+
* @param {string} feedback - Feedback type (accepted, rejected, modified, ignored)
|
|
173
|
+
* @param {object} [details] - Additional feedback details
|
|
174
|
+
* @returns {boolean} Whether feedback was recorded
|
|
175
|
+
*/
|
|
176
|
+
recordFeedback(suggestionId, feedback, details = {}) {
|
|
177
|
+
if (!this.initialized) this.init();
|
|
178
|
+
|
|
179
|
+
const suggestion = this.pendingSuggestions.get(suggestionId);
|
|
180
|
+
if (!suggestion) {
|
|
181
|
+
// Try to find in history
|
|
182
|
+
const existing = this.history.find(h => h.id === suggestionId);
|
|
183
|
+
if (existing && !existing.feedback) {
|
|
184
|
+
existing.feedback = feedback;
|
|
185
|
+
existing.feedbackAt = new Date().toISOString();
|
|
186
|
+
existing.feedbackDetails = details;
|
|
187
|
+
this._updateStats(existing);
|
|
188
|
+
this.save();
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
suggestion.feedback = feedback;
|
|
195
|
+
suggestion.feedbackAt = new Date().toISOString();
|
|
196
|
+
suggestion.feedbackDetails = details;
|
|
197
|
+
|
|
198
|
+
// Move to history
|
|
199
|
+
this.history.push(suggestion);
|
|
200
|
+
this.pendingSuggestions.delete(suggestionId);
|
|
201
|
+
|
|
202
|
+
// Update statistics
|
|
203
|
+
this._updateStats(suggestion);
|
|
204
|
+
this.save();
|
|
205
|
+
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Record quick feedback without prior suggestion tracking
|
|
211
|
+
* @param {object} feedback - Feedback details
|
|
212
|
+
* @returns {string} Feedback ID
|
|
213
|
+
*/
|
|
214
|
+
recordQuickFeedback(feedback) {
|
|
215
|
+
if (!this.initialized) this.init();
|
|
216
|
+
|
|
217
|
+
const id = `fb-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
218
|
+
|
|
219
|
+
const record = {
|
|
220
|
+
id,
|
|
221
|
+
category: feedback.category || 'unknown',
|
|
222
|
+
type: feedback.type || 'unknown',
|
|
223
|
+
suggestion: feedback.suggestion || '',
|
|
224
|
+
context: feedback.context || {},
|
|
225
|
+
confidence: feedback.confidence || 0.5,
|
|
226
|
+
createdAt: new Date().toISOString(),
|
|
227
|
+
feedback: feedback.feedback || FEEDBACK_TYPES.ACCEPTED,
|
|
228
|
+
feedbackAt: new Date().toISOString(),
|
|
229
|
+
feedbackDetails: feedback.details || {},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
this.history.push(record);
|
|
233
|
+
this._updateStats(record);
|
|
234
|
+
this.save();
|
|
235
|
+
|
|
236
|
+
return id;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Update statistics based on feedback
|
|
241
|
+
* @private
|
|
242
|
+
*/
|
|
243
|
+
_updateStats(record) {
|
|
244
|
+
this.stats.total++;
|
|
245
|
+
|
|
246
|
+
// Update by category
|
|
247
|
+
if (!this.stats.byCategory[record.category]) {
|
|
248
|
+
this.stats.byCategory[record.category] = {
|
|
249
|
+
total: 0,
|
|
250
|
+
accepted: 0,
|
|
251
|
+
rejected: 0,
|
|
252
|
+
modified: 0,
|
|
253
|
+
ignored: 0,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
this.stats.byCategory[record.category].total++;
|
|
257
|
+
this.stats.byCategory[record.category][record.feedback] =
|
|
258
|
+
(this.stats.byCategory[record.category][record.feedback] || 0) + 1;
|
|
259
|
+
|
|
260
|
+
// Update by type
|
|
261
|
+
const typeKey = `${record.category}:${record.type}`;
|
|
262
|
+
if (!this.stats.byType[typeKey]) {
|
|
263
|
+
this.stats.byType[typeKey] = {
|
|
264
|
+
total: 0,
|
|
265
|
+
accepted: 0,
|
|
266
|
+
rejected: 0,
|
|
267
|
+
modified: 0,
|
|
268
|
+
ignored: 0,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
this.stats.byType[typeKey].total++;
|
|
272
|
+
this.stats.byType[typeKey][record.feedback] =
|
|
273
|
+
(this.stats.byType[typeKey][record.feedback] || 0) + 1;
|
|
274
|
+
|
|
275
|
+
// Calculate overall acceptance rate
|
|
276
|
+
const accepted = this.history.filter(
|
|
277
|
+
h => h.feedback === FEEDBACK_TYPES.ACCEPTED,
|
|
278
|
+
).length;
|
|
279
|
+
this.stats.acceptanceRate = this.stats.total > 0
|
|
280
|
+
? accepted / this.stats.total
|
|
281
|
+
: 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ==================== QUERY METHODS ====================
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get acceptance rate for a category
|
|
288
|
+
* @param {string} category - Category to check
|
|
289
|
+
* @returns {number} Acceptance rate 0-1
|
|
290
|
+
*/
|
|
291
|
+
getAcceptanceRate(category) {
|
|
292
|
+
if (!this.initialized) this.init();
|
|
293
|
+
|
|
294
|
+
const catStats = this.stats.byCategory[category];
|
|
295
|
+
if (!catStats || catStats.total === 0) return 0.5; // Default
|
|
296
|
+
|
|
297
|
+
return catStats.accepted / catStats.total;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get acceptance rate for a specific type
|
|
302
|
+
* @param {string} category - Category
|
|
303
|
+
* @param {string} type - Type within category
|
|
304
|
+
* @returns {number} Acceptance rate 0-1
|
|
305
|
+
*/
|
|
306
|
+
getTypeAcceptanceRate(category, type) {
|
|
307
|
+
if (!this.initialized) this.init();
|
|
308
|
+
|
|
309
|
+
const typeKey = `${category}:${type}`;
|
|
310
|
+
const typeStats = this.stats.byType[typeKey];
|
|
311
|
+
if (!typeStats || typeStats.total === 0) return 0.5; // Default
|
|
312
|
+
|
|
313
|
+
return typeStats.accepted / typeStats.total;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get feedback history for a category
|
|
318
|
+
* @param {string} category - Category to filter
|
|
319
|
+
* @param {number} [limit=50] - Maximum records to return
|
|
320
|
+
* @returns {Array} Feedback history
|
|
321
|
+
*/
|
|
322
|
+
getHistory(category, limit = 50) {
|
|
323
|
+
if (!this.initialized) this.init();
|
|
324
|
+
|
|
325
|
+
return this.history
|
|
326
|
+
.filter(h => !category || h.category === category)
|
|
327
|
+
.slice(-limit)
|
|
328
|
+
.reverse();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get recent feedback
|
|
333
|
+
* @param {number} [days=7] - Days to look back
|
|
334
|
+
* @returns {Array} Recent feedback
|
|
335
|
+
*/
|
|
336
|
+
getRecentFeedback(days = 7) {
|
|
337
|
+
if (!this.initialized) this.init();
|
|
338
|
+
|
|
339
|
+
const cutoff = new Date();
|
|
340
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
341
|
+
|
|
342
|
+
return this.history.filter(h => new Date(h.feedbackAt) >= cutoff);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get pending suggestions (no feedback yet)
|
|
347
|
+
* @returns {Array} Pending suggestions
|
|
348
|
+
*/
|
|
349
|
+
getPendingSuggestions() {
|
|
350
|
+
if (!this.initialized) this.init();
|
|
351
|
+
return Array.from(this.pendingSuggestions.values());
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get low-performing suggestion types
|
|
356
|
+
* @param {number} [threshold=0.3] - Acceptance rate threshold
|
|
357
|
+
* @returns {Array} Types with low acceptance
|
|
358
|
+
*/
|
|
359
|
+
getLowPerformingTypes(threshold = 0.3) {
|
|
360
|
+
if (!this.initialized) this.init();
|
|
361
|
+
|
|
362
|
+
const lowPerforming = [];
|
|
363
|
+
|
|
364
|
+
for (const [typeKey, stats] of Object.entries(this.stats.byType)) {
|
|
365
|
+
if (stats.total < 3) continue; // Need minimum data
|
|
366
|
+
|
|
367
|
+
const rate = stats.accepted / stats.total;
|
|
368
|
+
if (rate < threshold) {
|
|
369
|
+
const [category, type] = typeKey.split(':');
|
|
370
|
+
lowPerforming.push({
|
|
371
|
+
category,
|
|
372
|
+
type,
|
|
373
|
+
acceptanceRate: rate,
|
|
374
|
+
total: stats.total,
|
|
375
|
+
rejected: stats.rejected,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return lowPerforming.sort((a, b) => a.acceptanceRate - b.acceptanceRate);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Get high-performing suggestion types
|
|
385
|
+
* @param {number} [threshold=0.7] - Acceptance rate threshold
|
|
386
|
+
* @returns {Array} Types with high acceptance
|
|
387
|
+
*/
|
|
388
|
+
getHighPerformingTypes(threshold = 0.7) {
|
|
389
|
+
if (!this.initialized) this.init();
|
|
390
|
+
|
|
391
|
+
const highPerforming = [];
|
|
392
|
+
|
|
393
|
+
for (const [typeKey, stats] of Object.entries(this.stats.byType)) {
|
|
394
|
+
if (stats.total < 3) continue; // Need minimum data
|
|
395
|
+
|
|
396
|
+
const rate = stats.accepted / stats.total;
|
|
397
|
+
if (rate >= threshold) {
|
|
398
|
+
const [category, type] = typeKey.split(':');
|
|
399
|
+
highPerforming.push({
|
|
400
|
+
category,
|
|
401
|
+
type,
|
|
402
|
+
acceptanceRate: rate,
|
|
403
|
+
total: stats.total,
|
|
404
|
+
accepted: stats.accepted,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return highPerforming.sort((a, b) => b.acceptanceRate - a.acceptanceRate);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ==================== CONFIDENCE ADJUSTMENT ====================
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Get adjusted confidence based on historical feedback
|
|
416
|
+
* @param {string} category - Suggestion category
|
|
417
|
+
* @param {string} type - Suggestion type
|
|
418
|
+
* @param {number} baseConfidence - Original confidence
|
|
419
|
+
* @returns {number} Adjusted confidence
|
|
420
|
+
*/
|
|
421
|
+
adjustConfidence(category, type, baseConfidence) {
|
|
422
|
+
if (!this.initialized) this.init();
|
|
423
|
+
|
|
424
|
+
const typeRate = this.getTypeAcceptanceRate(category, type);
|
|
425
|
+
const categoryRate = this.getAcceptanceRate(category);
|
|
426
|
+
|
|
427
|
+
// Blend base confidence with historical performance
|
|
428
|
+
// Weight: 60% base, 25% type rate, 15% category rate
|
|
429
|
+
const adjusted =
|
|
430
|
+
baseConfidence * 0.6 +
|
|
431
|
+
typeRate * 0.25 +
|
|
432
|
+
categoryRate * 0.15;
|
|
433
|
+
|
|
434
|
+
return Math.max(0.1, Math.min(0.99, adjusted));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Should this type of suggestion be suppressed?
|
|
439
|
+
* @param {string} category - Category
|
|
440
|
+
* @param {string} type - Type
|
|
441
|
+
* @param {number} [threshold=0.2] - Suppression threshold
|
|
442
|
+
* @returns {boolean} Whether to suppress
|
|
443
|
+
*/
|
|
444
|
+
shouldSuppress(category, type, threshold = 0.2) {
|
|
445
|
+
if (!this.initialized) this.init();
|
|
446
|
+
|
|
447
|
+
const typeKey = `${category}:${type}`;
|
|
448
|
+
const stats = this.stats.byType[typeKey];
|
|
449
|
+
|
|
450
|
+
if (!stats || stats.total < 5) return false; // Need more data
|
|
451
|
+
|
|
452
|
+
const rate = stats.accepted / stats.total;
|
|
453
|
+
return rate < threshold;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ==================== UTILITIES ====================
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Get overall statistics
|
|
460
|
+
* @returns {object} Statistics
|
|
461
|
+
*/
|
|
462
|
+
getStats() {
|
|
463
|
+
if (!this.initialized) this.init();
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
...this.stats,
|
|
467
|
+
historySize: this.history.length,
|
|
468
|
+
pendingCount: this.pendingSuggestions.size,
|
|
469
|
+
categoriesTracked: Object.keys(this.stats.byCategory).length,
|
|
470
|
+
typesTracked: Object.keys(this.stats.byType).length,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Clear all feedback data
|
|
476
|
+
*/
|
|
477
|
+
clear() {
|
|
478
|
+
this.history = [];
|
|
479
|
+
this.stats = {
|
|
480
|
+
total: 0,
|
|
481
|
+
byCategory: {},
|
|
482
|
+
byType: {},
|
|
483
|
+
acceptanceRate: 0,
|
|
484
|
+
};
|
|
485
|
+
this.pendingSuggestions.clear();
|
|
486
|
+
this.save();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Export feedback data
|
|
491
|
+
* @returns {object} Exportable data
|
|
492
|
+
*/
|
|
493
|
+
export() {
|
|
494
|
+
if (!this.initialized) this.init();
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
version: '1.0.0',
|
|
498
|
+
exportedAt: new Date().toISOString(),
|
|
499
|
+
history: this.history,
|
|
500
|
+
stats: this.stats,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Import feedback data
|
|
506
|
+
* @param {object} data - Data to import
|
|
507
|
+
* @param {boolean} [merge=true] - Merge with existing or replace
|
|
508
|
+
*/
|
|
509
|
+
import(data, merge = true) {
|
|
510
|
+
if (!this.initialized) this.init();
|
|
511
|
+
|
|
512
|
+
if (merge) {
|
|
513
|
+
// Merge histories, avoiding duplicates by ID
|
|
514
|
+
const existingIds = new Set(this.history.map(h => h.id));
|
|
515
|
+
for (const record of (data.history || [])) {
|
|
516
|
+
if (!existingIds.has(record.id)) {
|
|
517
|
+
this.history.push(record);
|
|
518
|
+
this._updateStats(record);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
} else {
|
|
522
|
+
// Replace
|
|
523
|
+
this.history = data.history || [];
|
|
524
|
+
this.stats = data.stats || {
|
|
525
|
+
total: 0,
|
|
526
|
+
byCategory: {},
|
|
527
|
+
byType: {},
|
|
528
|
+
acceptanceRate: 0,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
this.save();
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Get feedback directory path
|
|
537
|
+
* @returns {string} Directory path
|
|
538
|
+
*/
|
|
539
|
+
static getFeedbackDir() {
|
|
540
|
+
return FEEDBACK_DIR;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
module.exports = {
|
|
545
|
+
FeedbackCollector,
|
|
546
|
+
FEEDBACK_TYPES,
|
|
547
|
+
SUGGESTION_CATEGORIES,
|
|
548
|
+
FEEDBACK_DIR,
|
|
549
|
+
FEEDBACK_FILE,
|
|
550
|
+
STATS_FILE,
|
|
551
|
+
};
|