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.
Files changed (126) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +567 -0
  3. package/bin/install.js +348 -0
  4. package/commands/gywd/add-phase.md +207 -0
  5. package/commands/gywd/anticipate.md +271 -0
  6. package/commands/gywd/bootstrap.md +336 -0
  7. package/commands/gywd/challenge.md +344 -0
  8. package/commands/gywd/check-drift.md +144 -0
  9. package/commands/gywd/complete-milestone.md +106 -0
  10. package/commands/gywd/consider-issues.md +202 -0
  11. package/commands/gywd/context.md +93 -0
  12. package/commands/gywd/create-roadmap.md +115 -0
  13. package/commands/gywd/deps.md +169 -0
  14. package/commands/gywd/digest.md +138 -0
  15. package/commands/gywd/discuss-milestone.md +47 -0
  16. package/commands/gywd/discuss-phase.md +60 -0
  17. package/commands/gywd/execute-plan.md +161 -0
  18. package/commands/gywd/extract-decisions.md +325 -0
  19. package/commands/gywd/health.md +150 -0
  20. package/commands/gywd/help.md +556 -0
  21. package/commands/gywd/history.md +278 -0
  22. package/commands/gywd/impact.md +317 -0
  23. package/commands/gywd/init.md +95 -0
  24. package/commands/gywd/insert-phase.md +227 -0
  25. package/commands/gywd/list-phase-assumptions.md +50 -0
  26. package/commands/gywd/map-codebase.md +84 -0
  27. package/commands/gywd/memory.md +159 -0
  28. package/commands/gywd/new-milestone.md +59 -0
  29. package/commands/gywd/new-project.md +315 -0
  30. package/commands/gywd/pause-work.md +123 -0
  31. package/commands/gywd/plan-fix.md +205 -0
  32. package/commands/gywd/plan-phase.md +93 -0
  33. package/commands/gywd/preview-plan.md +139 -0
  34. package/commands/gywd/profile.md +363 -0
  35. package/commands/gywd/progress.md +317 -0
  36. package/commands/gywd/remove-phase.md +338 -0
  37. package/commands/gywd/research-phase.md +91 -0
  38. package/commands/gywd/resume-work.md +40 -0
  39. package/commands/gywd/rollback.md +179 -0
  40. package/commands/gywd/status.md +42 -0
  41. package/commands/gywd/sync-github.md +234 -0
  42. package/commands/gywd/verify-work.md +71 -0
  43. package/commands/gywd/why.md +251 -0
  44. package/docs/COMMANDS.md +722 -0
  45. package/docs/CONTRIBUTING.md +342 -0
  46. package/docs/EXAMPLES.md +535 -0
  47. package/docs/GETTING-STARTED.md +262 -0
  48. package/docs/README.md +55 -0
  49. package/docs/RELEASING.md +159 -0
  50. package/get-your-work-done/core/agent-patterns.md +331 -0
  51. package/get-your-work-done/core/architecture.md +334 -0
  52. package/get-your-work-done/core/context-model-schema.json +154 -0
  53. package/get-your-work-done/core/decisions-schema.json +193 -0
  54. package/get-your-work-done/core/learning-state-schema.json +133 -0
  55. package/get-your-work-done/core/profile-schema.json +257 -0
  56. package/get-your-work-done/references/adaptive-decomposition.md +175 -0
  57. package/get-your-work-done/references/checkpoints.md +287 -0
  58. package/get-your-work-done/references/confidence-scoring.md +169 -0
  59. package/get-your-work-done/references/continuation-format.md +255 -0
  60. package/get-your-work-done/references/git-integration.md +254 -0
  61. package/get-your-work-done/references/plan-format.md +428 -0
  62. package/get-your-work-done/references/principles.md +157 -0
  63. package/get-your-work-done/references/questioning.md +162 -0
  64. package/get-your-work-done/references/research-pitfalls.md +215 -0
  65. package/get-your-work-done/references/scope-estimation.md +172 -0
  66. package/get-your-work-done/references/tdd.md +263 -0
  67. package/get-your-work-done/templates/codebase/architecture.md +255 -0
  68. package/get-your-work-done/templates/codebase/concerns.md +310 -0
  69. package/get-your-work-done/templates/codebase/conventions.md +307 -0
  70. package/get-your-work-done/templates/codebase/integrations.md +280 -0
  71. package/get-your-work-done/templates/codebase/stack.md +186 -0
  72. package/get-your-work-done/templates/codebase/structure.md +285 -0
  73. package/get-your-work-done/templates/codebase/testing.md +480 -0
  74. package/get-your-work-done/templates/config.json +18 -0
  75. package/get-your-work-done/templates/context.md +161 -0
  76. package/get-your-work-done/templates/continue-here.md +78 -0
  77. package/get-your-work-done/templates/discovery.md +146 -0
  78. package/get-your-work-done/templates/issues.md +32 -0
  79. package/get-your-work-done/templates/milestone-archive.md +123 -0
  80. package/get-your-work-done/templates/milestone-context.md +93 -0
  81. package/get-your-work-done/templates/milestone.md +115 -0
  82. package/get-your-work-done/templates/phase-prompt.md +303 -0
  83. package/get-your-work-done/templates/project.md +184 -0
  84. package/get-your-work-done/templates/research.md +529 -0
  85. package/get-your-work-done/templates/roadmap.md +196 -0
  86. package/get-your-work-done/templates/state.md +210 -0
  87. package/get-your-work-done/templates/summary.md +273 -0
  88. package/get-your-work-done/templates/uat-issues.md +143 -0
  89. package/get-your-work-done/workflows/complete-milestone.md +643 -0
  90. package/get-your-work-done/workflows/create-milestone.md +416 -0
  91. package/get-your-work-done/workflows/create-roadmap.md +481 -0
  92. package/get-your-work-done/workflows/discovery-phase.md +293 -0
  93. package/get-your-work-done/workflows/discuss-milestone.md +236 -0
  94. package/get-your-work-done/workflows/discuss-phase.md +247 -0
  95. package/get-your-work-done/workflows/execute-phase.md +1625 -0
  96. package/get-your-work-done/workflows/list-phase-assumptions.md +178 -0
  97. package/get-your-work-done/workflows/map-codebase.md +434 -0
  98. package/get-your-work-done/workflows/plan-phase.md +488 -0
  99. package/get-your-work-done/workflows/research-phase.md +436 -0
  100. package/get-your-work-done/workflows/resume-project.md +287 -0
  101. package/get-your-work-done/workflows/transition.md +580 -0
  102. package/get-your-work-done/workflows/verify-work.md +202 -0
  103. package/lib/automation/dependency-analyzer.js +635 -0
  104. package/lib/automation/doc-generator.js +643 -0
  105. package/lib/automation/index.js +42 -0
  106. package/lib/automation/test-generator.js +628 -0
  107. package/lib/context/context-analyzer.js +554 -0
  108. package/lib/context/context-cache.js +426 -0
  109. package/lib/context/context-predictor.js +622 -0
  110. package/lib/context/index.js +44 -0
  111. package/lib/memory/confidence-calibrator.js +484 -0
  112. package/lib/memory/feedback-collector.js +551 -0
  113. package/lib/memory/global-memory.js +465 -0
  114. package/lib/memory/index.js +75 -0
  115. package/lib/memory/pattern-aggregator.js +487 -0
  116. package/lib/memory/team-sync.js +501 -0
  117. package/lib/profile/index.js +24 -0
  118. package/lib/profile/pattern-learner.js +303 -0
  119. package/lib/profile/profile-manager.js +445 -0
  120. package/lib/questioning/index.js +49 -0
  121. package/lib/questioning/question-engine.js +311 -0
  122. package/lib/questioning/question-templates.js +315 -0
  123. package/lib/validators/command-validator.js +188 -0
  124. package/lib/validators/index.js +29 -0
  125. package/lib/validators/schema-validator.js +183 -0
  126. 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
+ };