wogiflow 1.0.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/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- package/templates/trace.md +69 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Auto Learning System
|
|
5
|
+
*
|
|
6
|
+
* Automatically captures learnings from:
|
|
7
|
+
* - Session reviews (code quality, security, architecture issues)
|
|
8
|
+
* - Bug fixes (patterns that were violated)
|
|
9
|
+
*
|
|
10
|
+
* Logs to feedback-patterns.md and suggests/auto-promotes to decisions.md
|
|
11
|
+
* when patterns reach the promotion threshold.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const {
|
|
17
|
+
PATHS,
|
|
18
|
+
PROJECT_ROOT,
|
|
19
|
+
fileExists,
|
|
20
|
+
getConfig,
|
|
21
|
+
color,
|
|
22
|
+
success,
|
|
23
|
+
info,
|
|
24
|
+
warn
|
|
25
|
+
} = require('./flow-utils');
|
|
26
|
+
|
|
27
|
+
// ============================================================
|
|
28
|
+
// Constants
|
|
29
|
+
// ============================================================
|
|
30
|
+
|
|
31
|
+
const FEEDBACK_PATTERNS_PATH = path.join(PATHS.state, 'feedback-patterns.md');
|
|
32
|
+
const DECISIONS_PATH = path.join(PATHS.state, 'decisions.md');
|
|
33
|
+
|
|
34
|
+
// Map issue types to normalized pattern names
|
|
35
|
+
const ISSUE_TO_PATTERN = {
|
|
36
|
+
// Security issues
|
|
37
|
+
'security:eval_usage': 'no-eval',
|
|
38
|
+
'security:innerhtml_xss': 'sanitize-html-output',
|
|
39
|
+
'security:hardcoded_credentials': 'use-env-for-secrets',
|
|
40
|
+
'security:missing_error_handling': 'handle-async-errors',
|
|
41
|
+
'security:command_injection': 'sanitize-shell-commands',
|
|
42
|
+
'security:sql_injection': 'use-parameterized-queries',
|
|
43
|
+
|
|
44
|
+
// Implementation issues
|
|
45
|
+
'implementation:magic_numbers': 'use-named-constants',
|
|
46
|
+
'implementation:deep_nesting': 'flatten-nested-logic',
|
|
47
|
+
'implementation:duplicate_strings': 'extract-string-constants',
|
|
48
|
+
'implementation:long_function': 'split-long-functions',
|
|
49
|
+
'implementation:complex_condition': 'simplify-conditions',
|
|
50
|
+
|
|
51
|
+
// Architecture issues
|
|
52
|
+
'architecture:god_object': 'split-large-files',
|
|
53
|
+
'architecture:mixed_concerns': 'separate-concerns',
|
|
54
|
+
'architecture:tight_coupling': 'reduce-coupling',
|
|
55
|
+
'architecture:circular_dependency': 'break-circular-deps',
|
|
56
|
+
|
|
57
|
+
// Basic issues
|
|
58
|
+
'basic:console_log': 'remove-console-logs',
|
|
59
|
+
'basic:todo_fixme': 'resolve-todos-before-merge',
|
|
60
|
+
'basic:empty_catch': 'handle-catch-blocks',
|
|
61
|
+
'basic:debugger': 'remove-debugger-statements',
|
|
62
|
+
|
|
63
|
+
// From previous session reviews
|
|
64
|
+
'file_read_no_try_catch': 'try-catch-file-reads',
|
|
65
|
+
'json_parse_unsafe': 'use-safe-json-parse',
|
|
66
|
+
'glob_path_separator': 'glob-no-path-separator',
|
|
67
|
+
'template_prototype': 'template-prototype-protection',
|
|
68
|
+
'json_no_validation': 'validate-json-structure'
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Default configuration
|
|
72
|
+
const DEFAULT_CONFIG = {
|
|
73
|
+
enabled: true,
|
|
74
|
+
captureFrom: {
|
|
75
|
+
sessionReview: true,
|
|
76
|
+
bugFix: true
|
|
77
|
+
},
|
|
78
|
+
confidenceThreshold: 80,
|
|
79
|
+
promotionThreshold: 3,
|
|
80
|
+
autoPromote: false
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Table format constants (DRY)
|
|
84
|
+
const TABLE_FORMAT = {
|
|
85
|
+
header: '| Date | Pattern | Source | Count | Confidence | Status |',
|
|
86
|
+
separator: '|------|---------|--------|-------|------------|--------|',
|
|
87
|
+
sectionHeader: '## Auto-Captured Patterns',
|
|
88
|
+
sectionRegex: /## Auto-Captured Patterns\s*\n\n\|[^\n]+\|\s*\n\|[-|\s]+\|\s*\n([\s\S]*?)(?=\n## |\n---|\n$)/
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Confidence update weight (0-1, higher means more weight to recent observations)
|
|
92
|
+
const CONFIDENCE_WEIGHT_RECENT = 0.7;
|
|
93
|
+
|
|
94
|
+
// ============================================================
|
|
95
|
+
// Configuration
|
|
96
|
+
// ============================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get auto-learning configuration
|
|
100
|
+
* @returns {Object} Config with defaults applied
|
|
101
|
+
*/
|
|
102
|
+
function getAutoLearnConfig() {
|
|
103
|
+
const config = getConfig();
|
|
104
|
+
return {
|
|
105
|
+
...DEFAULT_CONFIG,
|
|
106
|
+
...(config?.autoLearning || {})
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================================
|
|
111
|
+
// Pattern Normalization
|
|
112
|
+
// ============================================================
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Convert issue to normalized pattern name
|
|
116
|
+
* @param {Object} issue - Issue from session review
|
|
117
|
+
* @returns {string} Normalized pattern name
|
|
118
|
+
*/
|
|
119
|
+
function normalizeIssueToPattern(issue) {
|
|
120
|
+
// Create key from perspective:type
|
|
121
|
+
const perspective = issue.perspective || issue.category || 'unknown';
|
|
122
|
+
const type = issue.type || slugify(issue.description || '');
|
|
123
|
+
const key = `${perspective}:${type}`;
|
|
124
|
+
|
|
125
|
+
// Check explicit mapping
|
|
126
|
+
if (ISSUE_TO_PATTERN[key]) {
|
|
127
|
+
return ISSUE_TO_PATTERN[key];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Check if type alone matches
|
|
131
|
+
if (ISSUE_TO_PATTERN[type]) {
|
|
132
|
+
return ISSUE_TO_PATTERN[type];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Generate from description (kebab-case, max 5 words)
|
|
136
|
+
const desc = issue.description || issue.type || 'unknown-pattern';
|
|
137
|
+
return slugify(desc.split(' ').slice(0, 5).join(' '));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Convert string to kebab-case
|
|
142
|
+
* @param {string} str - Input string
|
|
143
|
+
* @returns {string} Kebab-case string
|
|
144
|
+
*/
|
|
145
|
+
function slugify(str) {
|
|
146
|
+
return str
|
|
147
|
+
.toLowerCase()
|
|
148
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
149
|
+
.replace(/\s+/g, '-')
|
|
150
|
+
.replace(/-+/g, '-')
|
|
151
|
+
.replace(/^-|-$/g, '')
|
|
152
|
+
.slice(0, 50);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================
|
|
156
|
+
// Feedback Patterns File Management
|
|
157
|
+
// ============================================================
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Parse feedback-patterns.md to extract auto-captured patterns
|
|
161
|
+
* @returns {Array} Array of pattern objects
|
|
162
|
+
*/
|
|
163
|
+
function loadAutoPatterns() {
|
|
164
|
+
if (!fileExists(FEEDBACK_PATTERNS_PATH)) {
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const content = fs.readFileSync(FEEDBACK_PATTERNS_PATH, 'utf-8');
|
|
170
|
+
|
|
171
|
+
// Find Auto-Captured Patterns section (using DRY constant)
|
|
172
|
+
const sectionMatch = content.match(TABLE_FORMAT.sectionRegex);
|
|
173
|
+
if (!sectionMatch) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const tableContent = sectionMatch[1];
|
|
178
|
+
const patterns = [];
|
|
179
|
+
|
|
180
|
+
// Parse table rows
|
|
181
|
+
const rows = tableContent.trim().split('\n').filter(line => line.startsWith('|'));
|
|
182
|
+
for (const row of rows) {
|
|
183
|
+
const cells = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
184
|
+
if (cells.length >= 5) {
|
|
185
|
+
patterns.push({
|
|
186
|
+
date: cells[0],
|
|
187
|
+
pattern: cells[1],
|
|
188
|
+
source: cells[2],
|
|
189
|
+
count: parseInt(cells[3], 10) || 1,
|
|
190
|
+
confidence: parseInt(cells[4], 10) || 80,
|
|
191
|
+
status: cells[5] || 'Monitor'
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return patterns;
|
|
197
|
+
} catch (err) {
|
|
198
|
+
warn(`Could not parse feedback-patterns.md: ${err.message}`);
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Save auto-captured patterns back to feedback-patterns.md
|
|
205
|
+
* @param {Array} patterns - Array of pattern objects
|
|
206
|
+
*/
|
|
207
|
+
function saveAutoPatterns(patterns) {
|
|
208
|
+
if (!fileExists(FEEDBACK_PATTERNS_PATH)) {
|
|
209
|
+
warn(`Could not save patterns: feedback-patterns.md not found`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
let content = fs.readFileSync(FEEDBACK_PATTERNS_PATH, 'utf-8');
|
|
215
|
+
|
|
216
|
+
// Build new table (using DRY constants)
|
|
217
|
+
const rows = patterns.map(p =>
|
|
218
|
+
`| ${p.date} | ${p.pattern} | ${p.source} | ${p.count} | ${p.confidence}% | ${p.status} |`
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const newSection = `${TABLE_FORMAT.sectionHeader}\n\n${TABLE_FORMAT.header}\n${TABLE_FORMAT.separator}\n${rows.join('\n')}`;
|
|
222
|
+
|
|
223
|
+
// Replace or add section
|
|
224
|
+
if (content.includes('## Auto-Captured Patterns')) {
|
|
225
|
+
content = content.replace(
|
|
226
|
+
/## Auto-Captured Patterns[\s\S]*?(?=\n## |\n---\n\n## |$)/,
|
|
227
|
+
newSection + '\n\n'
|
|
228
|
+
);
|
|
229
|
+
} else {
|
|
230
|
+
// Add before "## Promotion History" or at the end
|
|
231
|
+
if (content.includes('## Promotion History')) {
|
|
232
|
+
content = content.replace('## Promotion History', newSection + '\n\n---\n\n## Promotion History');
|
|
233
|
+
} else {
|
|
234
|
+
content = content.trimEnd() + '\n\n---\n\n' + newSection + '\n';
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fs.writeFileSync(FEEDBACK_PATTERNS_PATH, content, 'utf-8');
|
|
239
|
+
} catch (err) {
|
|
240
|
+
warn(`Could not save feedback-patterns.md: ${err.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ============================================================
|
|
245
|
+
// Core Learning Functions
|
|
246
|
+
// ============================================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Capture learnings from session review issues
|
|
250
|
+
* @param {Array} issues - Issues from session review
|
|
251
|
+
*/
|
|
252
|
+
function captureFromSessionReview(issues) {
|
|
253
|
+
const config = getAutoLearnConfig();
|
|
254
|
+
|
|
255
|
+
if (!config.enabled || !config.captureFrom.sessionReview) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Filter by confidence threshold
|
|
260
|
+
const validIssues = issues.filter(i =>
|
|
261
|
+
(i.confidence || 80) >= config.confidenceThreshold
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
if (validIssues.length === 0) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const patterns = loadAutoPatterns();
|
|
269
|
+
const today = new Date().toISOString().split('T')[0];
|
|
270
|
+
let capturedCount = 0;
|
|
271
|
+
const promotionCandidates = [];
|
|
272
|
+
|
|
273
|
+
for (const issue of validIssues) {
|
|
274
|
+
const patternName = normalizeIssueToPattern(issue);
|
|
275
|
+
const confidence = issue.confidence || 80;
|
|
276
|
+
|
|
277
|
+
// Check if pattern already exists
|
|
278
|
+
const existing = patterns.find(p => p.pattern === patternName);
|
|
279
|
+
|
|
280
|
+
if (existing) {
|
|
281
|
+
// Increment count and update confidence (weighted average favoring recent)
|
|
282
|
+
existing.count += 1;
|
|
283
|
+
existing.confidence = Math.round(
|
|
284
|
+
(1 - CONFIDENCE_WEIGHT_RECENT) * existing.confidence +
|
|
285
|
+
CONFIDENCE_WEIGHT_RECENT * confidence
|
|
286
|
+
);
|
|
287
|
+
existing.date = today;
|
|
288
|
+
|
|
289
|
+
// Check promotion threshold
|
|
290
|
+
if (existing.count >= config.promotionThreshold && existing.status === 'Monitor') {
|
|
291
|
+
existing.status = 'Ready';
|
|
292
|
+
promotionCandidates.push(existing);
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
// Add new pattern
|
|
296
|
+
patterns.push({
|
|
297
|
+
date: today,
|
|
298
|
+
pattern: patternName,
|
|
299
|
+
source: 'session-review',
|
|
300
|
+
count: 1,
|
|
301
|
+
confidence: confidence,
|
|
302
|
+
status: 'Monitor'
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
capturedCount++;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Save updated patterns
|
|
310
|
+
saveAutoPatterns(patterns);
|
|
311
|
+
|
|
312
|
+
// Report
|
|
313
|
+
if (capturedCount > 0) {
|
|
314
|
+
info(`Auto-learned ${capturedCount} pattern(s) from session review`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Handle promotions
|
|
318
|
+
for (const candidate of promotionCandidates) {
|
|
319
|
+
handlePromotion(candidate, config);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Capture learnings from bug fix completion
|
|
325
|
+
* @param {string} taskId - Task ID
|
|
326
|
+
* @param {Array} files - Modified files
|
|
327
|
+
* @param {string} description - Task description
|
|
328
|
+
*/
|
|
329
|
+
function captureFromBugFix(taskId, files, description) {
|
|
330
|
+
const config = getAutoLearnConfig();
|
|
331
|
+
|
|
332
|
+
if (!config.enabled || !config.captureFrom.bugFix) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Analyze the bug fix to detect patterns
|
|
337
|
+
const detectedPatterns = analyzeBugFix(files, description);
|
|
338
|
+
|
|
339
|
+
if (detectedPatterns.length === 0) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const patterns = loadAutoPatterns();
|
|
344
|
+
const today = new Date().toISOString().split('T')[0];
|
|
345
|
+
const promotionCandidates = [];
|
|
346
|
+
|
|
347
|
+
for (const detected of detectedPatterns) {
|
|
348
|
+
const existing = patterns.find(p => p.pattern === detected.pattern);
|
|
349
|
+
|
|
350
|
+
if (existing) {
|
|
351
|
+
existing.count += 1;
|
|
352
|
+
existing.date = today;
|
|
353
|
+
|
|
354
|
+
if (existing.count >= config.promotionThreshold && existing.status === 'Monitor') {
|
|
355
|
+
existing.status = 'Ready';
|
|
356
|
+
promotionCandidates.push(existing);
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
patterns.push({
|
|
360
|
+
date: today,
|
|
361
|
+
pattern: detected.pattern,
|
|
362
|
+
source: 'bug-fix',
|
|
363
|
+
count: 1,
|
|
364
|
+
confidence: detected.confidence || 85,
|
|
365
|
+
status: 'Monitor'
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
saveAutoPatterns(patterns);
|
|
371
|
+
|
|
372
|
+
if (detectedPatterns.length > 0) {
|
|
373
|
+
info(`Auto-learned ${detectedPatterns.length} pattern(s) from bug fix ${taskId}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for (const candidate of promotionCandidates) {
|
|
377
|
+
handlePromotion(candidate, config);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Analyze bug fix files to detect violated patterns
|
|
383
|
+
* @param {Array} files - Modified files
|
|
384
|
+
* @param {string} description - Task description
|
|
385
|
+
* @returns {Array} Detected patterns
|
|
386
|
+
*/
|
|
387
|
+
function analyzeBugFix(files, description) {
|
|
388
|
+
const detected = [];
|
|
389
|
+
const descLower = (description || '').toLowerCase();
|
|
390
|
+
|
|
391
|
+
// Keyword-based pattern detection from description
|
|
392
|
+
const keywordPatterns = [
|
|
393
|
+
{ keywords: ['try-catch', 'error handling', 'exception'], pattern: 'handle-async-errors' },
|
|
394
|
+
{ keywords: ['json parse', 'json.parse', 'parsing error'], pattern: 'use-safe-json-parse' },
|
|
395
|
+
{ keywords: ['null check', 'undefined', 'cannot read property'], pattern: 'null-safety-checks' },
|
|
396
|
+
{ keywords: ['path traversal', 'directory traversal'], pattern: 'validate-file-paths' },
|
|
397
|
+
{ keywords: ['xss', 'innerhtml', 'script injection'], pattern: 'sanitize-html-output' },
|
|
398
|
+
{ keywords: ['sql injection', 'query injection'], pattern: 'use-parameterized-queries' },
|
|
399
|
+
{ keywords: ['race condition', 'concurrent', 'async race'], pattern: 'handle-race-conditions' },
|
|
400
|
+
{ keywords: ['memory leak', 'cleanup', 'dispose'], pattern: 'cleanup-resources' },
|
|
401
|
+
{ keywords: ['validation', 'invalid input', 'input validation'], pattern: 'validate-input' },
|
|
402
|
+
{ keywords: ['timeout', 'deadline', 'hung'], pattern: 'add-timeouts' }
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
for (const kp of keywordPatterns) {
|
|
406
|
+
if (kp.keywords.some(k => descLower.includes(k))) {
|
|
407
|
+
detected.push({ pattern: kp.pattern, confidence: 85 });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// If no keywords matched, try to extract from file changes
|
|
412
|
+
// (Could be enhanced to actually read the diff)
|
|
413
|
+
if (detected.length === 0 && files.length > 0) {
|
|
414
|
+
// Generic "bug fix" pattern based on file type
|
|
415
|
+
const jsFiles = files.filter(f => f.endsWith('.js') || f.endsWith('.ts'));
|
|
416
|
+
if (jsFiles.length > 0) {
|
|
417
|
+
detected.push({ pattern: 'review-similar-code', confidence: 70 });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return detected;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ============================================================
|
|
425
|
+
// Promotion Handling
|
|
426
|
+
// ============================================================
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Handle pattern promotion
|
|
430
|
+
* @param {Object} pattern - Pattern to promote
|
|
431
|
+
* @param {Object} config - Auto-learn config
|
|
432
|
+
*/
|
|
433
|
+
function handlePromotion(pattern, config) {
|
|
434
|
+
if (config.autoPromote) {
|
|
435
|
+
// Auto-promote to decisions.md
|
|
436
|
+
promoteToDecisions(pattern);
|
|
437
|
+
success(`Auto-promoted pattern "${pattern.pattern}" to decisions.md`);
|
|
438
|
+
} else {
|
|
439
|
+
// Notify user
|
|
440
|
+
console.log('');
|
|
441
|
+
console.log(color('yellow', `Pattern ready for promotion: "${pattern.pattern}"`));
|
|
442
|
+
console.log(` Occurrences: ${pattern.count}`);
|
|
443
|
+
console.log(` Confidence: ${pattern.confidence}%`);
|
|
444
|
+
console.log(` Source: ${pattern.source}`);
|
|
445
|
+
console.log(color('dim', ' Run "flow aggregate" to promote, or edit decisions.md manually'));
|
|
446
|
+
console.log('');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Promote pattern to decisions.md
|
|
452
|
+
* @param {Object} pattern - Pattern to promote
|
|
453
|
+
*/
|
|
454
|
+
function promoteToDecisions(pattern) {
|
|
455
|
+
if (!fileExists(DECISIONS_PATH)) {
|
|
456
|
+
warn(`Could not promote pattern: decisions.md not found`);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
let content = fs.readFileSync(DECISIONS_PATH, 'utf-8');
|
|
462
|
+
|
|
463
|
+
// Find Coding Standards section
|
|
464
|
+
const sectionHeader = '## Coding Standards';
|
|
465
|
+
|
|
466
|
+
if (!content.includes(sectionHeader)) {
|
|
467
|
+
warn(`Could not promote pattern: Coding Standards section not found in decisions.md`);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Generate rule entry (escape markdown special characters in pattern name)
|
|
472
|
+
const today = new Date().toISOString().split('T')[0];
|
|
473
|
+
const escapedPattern = pattern.pattern.replace(/[#*_\[\]()\\]/g, '\\$&');
|
|
474
|
+
const ruleEntry = `\n### ${escapedPattern} (${today})
|
|
475
|
+
**Source**: Auto-learned from ${pattern.count} occurrences (${pattern.source})
|
|
476
|
+
**Rule**: [Describe the pattern rule here]
|
|
477
|
+
`;
|
|
478
|
+
|
|
479
|
+
// Insert after Coding Standards header
|
|
480
|
+
const insertPoint = content.indexOf(sectionHeader) + sectionHeader.length;
|
|
481
|
+
const nextSection = content.indexOf('\n## ', insertPoint);
|
|
482
|
+
|
|
483
|
+
if (nextSection > insertPoint) {
|
|
484
|
+
// Insert before next section
|
|
485
|
+
content = content.slice(0, nextSection) + ruleEntry + content.slice(nextSection);
|
|
486
|
+
} else {
|
|
487
|
+
// Append to section
|
|
488
|
+
content = content.slice(0, insertPoint) + ruleEntry + content.slice(insertPoint);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
fs.writeFileSync(DECISIONS_PATH, content, 'utf-8');
|
|
492
|
+
|
|
493
|
+
// Update pattern status
|
|
494
|
+
const patterns = loadAutoPatterns();
|
|
495
|
+
const updated = patterns.find(p => p.pattern === pattern.pattern);
|
|
496
|
+
if (updated) {
|
|
497
|
+
updated.status = 'Promoted';
|
|
498
|
+
saveAutoPatterns(patterns);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Sync rules
|
|
502
|
+
try {
|
|
503
|
+
require('./flow-rules-sync');
|
|
504
|
+
} catch (syncErr) {
|
|
505
|
+
// Log the failure for debugging
|
|
506
|
+
info(`Note: Rules sync skipped - ${syncErr.code === 'MODULE_NOT_FOUND' ? 'module not found' : syncErr.message}`);
|
|
507
|
+
}
|
|
508
|
+
} catch (err) {
|
|
509
|
+
warn(`Could not promote to decisions.md: ${err.message}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ============================================================
|
|
514
|
+
// CLI
|
|
515
|
+
// ============================================================
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Show auto-learning status
|
|
519
|
+
*/
|
|
520
|
+
function showStatus() {
|
|
521
|
+
const config = getAutoLearnConfig();
|
|
522
|
+
const patterns = loadAutoPatterns();
|
|
523
|
+
|
|
524
|
+
console.log('');
|
|
525
|
+
console.log(color('cyan', '='.repeat(50)));
|
|
526
|
+
console.log(color('cyan', ' AUTO-LEARNING STATUS'));
|
|
527
|
+
console.log(color('cyan', '='.repeat(50)));
|
|
528
|
+
console.log('');
|
|
529
|
+
|
|
530
|
+
// Config
|
|
531
|
+
console.log(color('cyan', 'Configuration'));
|
|
532
|
+
console.log(` Enabled: ${config.enabled ? 'Yes' : 'No'}`);
|
|
533
|
+
console.log(` Capture from: ${Object.entries(config.captureFrom).filter(([,v]) => v).map(([k]) => k).join(', ')}`);
|
|
534
|
+
console.log(` Confidence threshold: ${config.confidenceThreshold}%`);
|
|
535
|
+
console.log(` Promotion threshold: ${config.promotionThreshold} occurrences`);
|
|
536
|
+
console.log(` Auto-promote: ${config.autoPromote ? 'Yes' : 'No'}`);
|
|
537
|
+
console.log('');
|
|
538
|
+
|
|
539
|
+
// Patterns
|
|
540
|
+
console.log(color('cyan', 'Captured Patterns'));
|
|
541
|
+
if (patterns.length === 0) {
|
|
542
|
+
console.log(' No patterns captured yet');
|
|
543
|
+
} else {
|
|
544
|
+
const ready = patterns.filter(p => p.status === 'Ready');
|
|
545
|
+
const monitoring = patterns.filter(p => p.status === 'Monitor');
|
|
546
|
+
const promoted = patterns.filter(p => p.status === 'Promoted');
|
|
547
|
+
|
|
548
|
+
if (ready.length > 0) {
|
|
549
|
+
console.log(color('yellow', ` Ready for promotion (${ready.length}):`));
|
|
550
|
+
for (const p of ready) {
|
|
551
|
+
console.log(` - ${p.pattern} (${p.count}x, ${p.confidence}%)`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (monitoring.length > 0) {
|
|
556
|
+
console.log(` Monitoring (${monitoring.length}):`);
|
|
557
|
+
for (const p of monitoring) {
|
|
558
|
+
console.log(` - ${p.pattern} (${p.count}x, ${p.confidence}%)`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (promoted.length > 0) {
|
|
563
|
+
console.log(color('green', ` Promoted (${promoted.length}):`));
|
|
564
|
+
for (const p of promoted) {
|
|
565
|
+
console.log(` - ${p.pattern}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
console.log('');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ============================================================
|
|
574
|
+
// Main
|
|
575
|
+
// ============================================================
|
|
576
|
+
|
|
577
|
+
async function main() {
|
|
578
|
+
const args = process.argv.slice(2);
|
|
579
|
+
const command = args[0] || 'status';
|
|
580
|
+
|
|
581
|
+
switch (command) {
|
|
582
|
+
case 'status':
|
|
583
|
+
showStatus();
|
|
584
|
+
break;
|
|
585
|
+
|
|
586
|
+
case 'test':
|
|
587
|
+
// Test capture with mock issues
|
|
588
|
+
captureFromSessionReview([
|
|
589
|
+
{ perspective: 'security', type: 'missing_error_handling', description: 'Missing try-catch', confidence: 85 },
|
|
590
|
+
{ perspective: 'implementation', type: 'magic_numbers', description: 'Magic number 42', confidence: 82 }
|
|
591
|
+
]);
|
|
592
|
+
success('Test capture completed');
|
|
593
|
+
break;
|
|
594
|
+
|
|
595
|
+
default:
|
|
596
|
+
console.log('Usage: flow auto-learn [status|test]');
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ============================================================
|
|
601
|
+
// Exports
|
|
602
|
+
// ============================================================
|
|
603
|
+
|
|
604
|
+
module.exports = {
|
|
605
|
+
captureFromSessionReview,
|
|
606
|
+
captureFromBugFix,
|
|
607
|
+
normalizeIssueToPattern,
|
|
608
|
+
getAutoLearnConfig,
|
|
609
|
+
loadAutoPatterns,
|
|
610
|
+
showStatus
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
if (require.main === module) {
|
|
614
|
+
main();
|
|
615
|
+
}
|