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,488 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Learnings Aggregation
|
|
5
|
+
*
|
|
6
|
+
* Aggregates learnings across all skills and corrections to:
|
|
7
|
+
* - Identify patterns that should be promoted
|
|
8
|
+
* - Surface recurring issues
|
|
9
|
+
* - Suggest knowledge base updates
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* flow aggregate # Show aggregated learnings summary
|
|
13
|
+
* flow aggregate --detailed # Show full details
|
|
14
|
+
* flow aggregate --promote # Interactive promotion wizard
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const readline = require('readline');
|
|
20
|
+
const {
|
|
21
|
+
PATHS,
|
|
22
|
+
PROJECT_ROOT,
|
|
23
|
+
fileExists,
|
|
24
|
+
dirExists,
|
|
25
|
+
readFile,
|
|
26
|
+
writeFile,
|
|
27
|
+
listDirs,
|
|
28
|
+
color,
|
|
29
|
+
success,
|
|
30
|
+
warn,
|
|
31
|
+
info,
|
|
32
|
+
error
|
|
33
|
+
} = require('./flow-utils');
|
|
34
|
+
const { syncDecisionsToRules } = require('./flow-rules-sync');
|
|
35
|
+
|
|
36
|
+
// ============================================================
|
|
37
|
+
// Paths
|
|
38
|
+
// ============================================================
|
|
39
|
+
|
|
40
|
+
const SKILLS_DIR = PATHS.skills;
|
|
41
|
+
const CORRECTIONS_DIR = path.join(PROJECT_ROOT, '.workflow', 'corrections');
|
|
42
|
+
|
|
43
|
+
// ============================================================
|
|
44
|
+
// Data Collection
|
|
45
|
+
// ============================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse learnings from a learnings.md file
|
|
49
|
+
*/
|
|
50
|
+
function parseLearningsFile(filePath) {
|
|
51
|
+
if (!fileExists(filePath)) return [];
|
|
52
|
+
|
|
53
|
+
const content = readFile(filePath, '');
|
|
54
|
+
const learnings = [];
|
|
55
|
+
|
|
56
|
+
// Match learning entries: ### YYYY-MM-DD - Title
|
|
57
|
+
const entryRegex = /^### (\d{4}-\d{2}-\d{2}) - (.+)$/gm;
|
|
58
|
+
let match;
|
|
59
|
+
|
|
60
|
+
while ((match = entryRegex.exec(content)) !== null) {
|
|
61
|
+
const date = match[1];
|
|
62
|
+
const title = match[2];
|
|
63
|
+
const startIndex = match.index + match[0].length;
|
|
64
|
+
|
|
65
|
+
// Find end of entry (next ### or end of file)
|
|
66
|
+
const nextMatch = content.slice(startIndex).match(/^### \d{4}-\d{2}-\d{2}/m);
|
|
67
|
+
const endIndex = nextMatch ? startIndex + nextMatch.index : content.length;
|
|
68
|
+
const entryContent = content.slice(startIndex, endIndex).trim();
|
|
69
|
+
|
|
70
|
+
// Extract fields
|
|
71
|
+
const contextMatch = entryContent.match(/\*\*Context\*\*:\s*(.+)/);
|
|
72
|
+
const issueMatch = entryContent.match(/\*\*Issue\*\*:\s*(.+)/);
|
|
73
|
+
const learningMatch = entryContent.match(/\*\*Learning\*\*:\s*(.+)/);
|
|
74
|
+
const filesMatch = entryContent.match(/\*\*Files\*\*:\s*(.+)/);
|
|
75
|
+
|
|
76
|
+
learnings.push({
|
|
77
|
+
date,
|
|
78
|
+
title,
|
|
79
|
+
context: contextMatch ? contextMatch[1] : '',
|
|
80
|
+
issue: issueMatch ? issueMatch[1] : '',
|
|
81
|
+
learning: learningMatch ? learningMatch[1] : '',
|
|
82
|
+
files: filesMatch ? filesMatch[1].split(',').map(f => f.trim()) : [],
|
|
83
|
+
source: filePath
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return learnings;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse feedback patterns from feedback-patterns.md
|
|
92
|
+
*/
|
|
93
|
+
function parseFeedbackPatterns() {
|
|
94
|
+
if (!fileExists(PATHS.feedbackPatterns)) return [];
|
|
95
|
+
|
|
96
|
+
const content = readFile(PATHS.feedbackPatterns, '');
|
|
97
|
+
const patterns = [];
|
|
98
|
+
|
|
99
|
+
// Parse table rows: | date | correction | count | promoted | status |
|
|
100
|
+
const tableRegex = /^\|\s*(\d{4}-\d{2}-\d{2})\s*\|\s*"?([^|"]+)"?\s*\|\s*(\d+)\s*\|\s*([^|]*)\s*\|\s*([^|]*)\s*\|/gm;
|
|
101
|
+
let match;
|
|
102
|
+
|
|
103
|
+
while ((match = tableRegex.exec(content)) !== null) {
|
|
104
|
+
const [, date, correction, count, promotedTo, status] = match;
|
|
105
|
+
patterns.push({
|
|
106
|
+
date,
|
|
107
|
+
correction: correction.trim(),
|
|
108
|
+
count: parseInt(count, 10),
|
|
109
|
+
promotedTo: promotedTo.trim() || null,
|
|
110
|
+
status: status.trim(),
|
|
111
|
+
source: 'feedback-patterns.md'
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return patterns;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse corrections from corrections directory
|
|
120
|
+
*/
|
|
121
|
+
function parseCorrections() {
|
|
122
|
+
if (!dirExists(CORRECTIONS_DIR)) return [];
|
|
123
|
+
|
|
124
|
+
const corrections = [];
|
|
125
|
+
const files = fs.readdirSync(CORRECTIONS_DIR)
|
|
126
|
+
.filter(f => f.startsWith('CORR-') && f.endsWith('.md'));
|
|
127
|
+
|
|
128
|
+
for (const file of files) {
|
|
129
|
+
const filePath = path.join(CORRECTIONS_DIR, file);
|
|
130
|
+
const content = readFile(filePath, '');
|
|
131
|
+
|
|
132
|
+
const idMatch = content.match(/^# (CORR-\d+) - (.+)$/m);
|
|
133
|
+
const dateMatch = content.match(/\*\*Date\*\*:\s*(.+)$/m);
|
|
134
|
+
const taskMatch = content.match(/\*\*Task\*\*:\s*(.+)$/m);
|
|
135
|
+
const skillMatch = content.match(/\*\*Skill\*\*:\s*(.+)$/m);
|
|
136
|
+
const tagsMatch = content.match(/\*\*Tags\*\*:\s*(.+)$/m);
|
|
137
|
+
|
|
138
|
+
// Extract sections
|
|
139
|
+
const whatHappenedMatch = content.match(/## What Happened\n+([\s\S]*?)(?=\n## |$)/);
|
|
140
|
+
const whatShouldMatch = content.match(/## What Should Happen\n+([\s\S]*?)(?=\n## |$)/);
|
|
141
|
+
const rootCauseMatch = content.match(/## Root Cause\n+([\s\S]*?)(?=\n## |$)/);
|
|
142
|
+
|
|
143
|
+
corrections.push({
|
|
144
|
+
id: idMatch ? idMatch[1] : file.replace('.md', ''),
|
|
145
|
+
title: idMatch ? idMatch[2] : 'Unknown',
|
|
146
|
+
date: dateMatch ? dateMatch[1] : 'Unknown',
|
|
147
|
+
task: taskMatch ? taskMatch[1] : null,
|
|
148
|
+
skill: skillMatch ? skillMatch[1] : null,
|
|
149
|
+
tags: tagsMatch ? tagsMatch[1].match(/#\w+/g) || [] : [],
|
|
150
|
+
whatHappened: whatHappenedMatch ? whatHappenedMatch[1].trim() : '',
|
|
151
|
+
whatShould: whatShouldMatch ? whatShouldMatch[1].trim() : '',
|
|
152
|
+
rootCause: rootCauseMatch ? rootCauseMatch[1].trim() : '',
|
|
153
|
+
source: filePath
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return corrections;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Collect all learnings from all skills
|
|
162
|
+
*/
|
|
163
|
+
function collectSkillLearnings() {
|
|
164
|
+
const allLearnings = [];
|
|
165
|
+
|
|
166
|
+
if (!dirExists(SKILLS_DIR)) return allLearnings;
|
|
167
|
+
|
|
168
|
+
const skillDirs = listDirs(SKILLS_DIR)
|
|
169
|
+
.filter(d => d !== '_template');
|
|
170
|
+
|
|
171
|
+
for (const skillName of skillDirs) {
|
|
172
|
+
const learningsPath = path.join(SKILLS_DIR, skillName, 'knowledge', 'learnings.md');
|
|
173
|
+
const learnings = parseLearningsFile(learningsPath);
|
|
174
|
+
|
|
175
|
+
for (const learning of learnings) {
|
|
176
|
+
allLearnings.push({
|
|
177
|
+
...learning,
|
|
178
|
+
skill: skillName
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return allLearnings;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ============================================================
|
|
187
|
+
// Analysis
|
|
188
|
+
// ============================================================
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Find patterns that occur multiple times
|
|
192
|
+
*/
|
|
193
|
+
function findRecurringPatterns(data) {
|
|
194
|
+
const { learnings, patterns, corrections } = data;
|
|
195
|
+
const recurring = [];
|
|
196
|
+
|
|
197
|
+
// Group by similar issues/learnings
|
|
198
|
+
const issueGroups = {};
|
|
199
|
+
|
|
200
|
+
// From learnings
|
|
201
|
+
for (const l of learnings) {
|
|
202
|
+
const key = normalizeText(l.learning || l.issue || l.title);
|
|
203
|
+
if (!issueGroups[key]) {
|
|
204
|
+
issueGroups[key] = { count: 0, sources: [], dates: [], type: 'learning' };
|
|
205
|
+
}
|
|
206
|
+
issueGroups[key].count++;
|
|
207
|
+
issueGroups[key].sources.push(l.source);
|
|
208
|
+
issueGroups[key].dates.push(l.date);
|
|
209
|
+
issueGroups[key].original = l.learning || l.issue || l.title;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// From corrections
|
|
213
|
+
for (const c of corrections) {
|
|
214
|
+
const key = normalizeText(c.whatShould || c.title);
|
|
215
|
+
if (!issueGroups[key]) {
|
|
216
|
+
issueGroups[key] = { count: 0, sources: [], dates: [], type: 'correction' };
|
|
217
|
+
}
|
|
218
|
+
issueGroups[key].count++;
|
|
219
|
+
issueGroups[key].sources.push(c.source);
|
|
220
|
+
issueGroups[key].dates.push(c.date);
|
|
221
|
+
issueGroups[key].original = c.whatShould || c.title;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Find recurring (3+ times)
|
|
225
|
+
for (const [key, data] of Object.entries(issueGroups)) {
|
|
226
|
+
if (data.count >= 3) {
|
|
227
|
+
recurring.push({
|
|
228
|
+
pattern: data.original,
|
|
229
|
+
count: data.count,
|
|
230
|
+
type: data.type,
|
|
231
|
+
sources: [...new Set(data.sources)],
|
|
232
|
+
lastSeen: data.dates.sort().reverse()[0]
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Also add patterns from feedback-patterns that have count >= 3
|
|
238
|
+
for (const p of patterns) {
|
|
239
|
+
if (p.count >= 3 && !p.promotedTo) {
|
|
240
|
+
recurring.push({
|
|
241
|
+
pattern: p.correction,
|
|
242
|
+
count: p.count,
|
|
243
|
+
type: 'feedback',
|
|
244
|
+
sources: ['feedback-patterns.md'],
|
|
245
|
+
lastSeen: p.date
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return recurring.sort((a, b) => b.count - a.count);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Normalize text for comparison
|
|
255
|
+
*/
|
|
256
|
+
function normalizeText(text) {
|
|
257
|
+
if (!text) return '';
|
|
258
|
+
return text
|
|
259
|
+
.toLowerCase()
|
|
260
|
+
.replace(/[^\w\s]/g, '')
|
|
261
|
+
.replace(/\s+/g, ' ')
|
|
262
|
+
.trim()
|
|
263
|
+
.slice(0, 50);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Group learnings by skill
|
|
268
|
+
*/
|
|
269
|
+
function groupBySkill(learnings) {
|
|
270
|
+
const groups = {};
|
|
271
|
+
|
|
272
|
+
for (const l of learnings) {
|
|
273
|
+
const skill = l.skill || 'general';
|
|
274
|
+
if (!groups[skill]) {
|
|
275
|
+
groups[skill] = [];
|
|
276
|
+
}
|
|
277
|
+
groups[skill].push(l);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return groups;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get recent learnings (last 30 days)
|
|
285
|
+
*/
|
|
286
|
+
function getRecentLearnings(learnings, days = 30) {
|
|
287
|
+
const cutoff = new Date();
|
|
288
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
289
|
+
|
|
290
|
+
return learnings.filter(l => {
|
|
291
|
+
const date = new Date(l.date);
|
|
292
|
+
return date >= cutoff;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ============================================================
|
|
297
|
+
// Output
|
|
298
|
+
// ============================================================
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Print summary
|
|
302
|
+
*/
|
|
303
|
+
function printSummary(data, options = {}) {
|
|
304
|
+
const { learnings, patterns, corrections } = data;
|
|
305
|
+
|
|
306
|
+
console.log(color('cyan', '═══════════════════════════════════════════════════'));
|
|
307
|
+
console.log(color('cyan', ' Learnings Aggregation Summary'));
|
|
308
|
+
console.log(color('cyan', '═══════════════════════════════════════════════════'));
|
|
309
|
+
console.log('');
|
|
310
|
+
|
|
311
|
+
// Overview
|
|
312
|
+
console.log(color('yellow', 'Overview'));
|
|
313
|
+
console.log(` Total learnings: ${learnings.length}`);
|
|
314
|
+
console.log(` Feedback patterns: ${patterns.length}`);
|
|
315
|
+
console.log(` Corrections: ${corrections.length}`);
|
|
316
|
+
console.log('');
|
|
317
|
+
|
|
318
|
+
// By skill
|
|
319
|
+
const bySkill = groupBySkill(learnings);
|
|
320
|
+
if (Object.keys(bySkill).length > 0) {
|
|
321
|
+
console.log(color('yellow', 'By Skill'));
|
|
322
|
+
for (const [skill, items] of Object.entries(bySkill)) {
|
|
323
|
+
console.log(` ${skill}: ${items.length} learnings`);
|
|
324
|
+
}
|
|
325
|
+
console.log('');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Recent activity
|
|
329
|
+
const recent = getRecentLearnings(learnings);
|
|
330
|
+
console.log(color('yellow', 'Recent Activity (30 days)'));
|
|
331
|
+
console.log(` New learnings: ${recent.length}`);
|
|
332
|
+
console.log('');
|
|
333
|
+
|
|
334
|
+
// Patterns needing promotion
|
|
335
|
+
const recurring = findRecurringPatterns(data);
|
|
336
|
+
if (recurring.length > 0) {
|
|
337
|
+
console.log(color('yellow', 'Patterns Ready for Promotion'));
|
|
338
|
+
console.log(color('dim', ' (Occurred 3+ times - should become permanent rules)'));
|
|
339
|
+
console.log('');
|
|
340
|
+
|
|
341
|
+
for (const r of recurring.slice(0, 5)) {
|
|
342
|
+
console.log(` ${color('green', '●')} ${r.pattern.slice(0, 60)}${r.pattern.length > 60 ? '...' : ''}`);
|
|
343
|
+
console.log(` Count: ${r.count} | Last: ${r.lastSeen} | Type: ${r.type}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (recurring.length > 5) {
|
|
347
|
+
console.log(` ... and ${recurring.length - 5} more`);
|
|
348
|
+
}
|
|
349
|
+
console.log('');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Pending feedback patterns
|
|
353
|
+
const pending = patterns.filter(p => p.status === 'Pending' && p.count < 3);
|
|
354
|
+
if (pending.length > 0) {
|
|
355
|
+
console.log(color('yellow', 'Pending Patterns (Need More Occurrences)'));
|
|
356
|
+
for (const p of pending.slice(0, 5)) {
|
|
357
|
+
console.log(` ${color('dim', '○')} ${p.correction} (${p.count}/3)`);
|
|
358
|
+
}
|
|
359
|
+
console.log('');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Detailed view
|
|
363
|
+
if (options.detailed && learnings.length > 0) {
|
|
364
|
+
console.log(color('yellow', 'All Learnings'));
|
|
365
|
+
console.log('');
|
|
366
|
+
for (const l of learnings.slice(0, 20)) {
|
|
367
|
+
console.log(` ${color('cyan', l.date)} | ${l.skill || 'general'}`);
|
|
368
|
+
console.log(` ${l.title}`);
|
|
369
|
+
if (l.learning) console.log(` ${color('dim', l.learning.slice(0, 80))}`);
|
|
370
|
+
console.log('');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Interactive promotion wizard
|
|
377
|
+
*/
|
|
378
|
+
async function runPromotionWizard(data) {
|
|
379
|
+
const recurring = findRecurringPatterns(data);
|
|
380
|
+
|
|
381
|
+
if (recurring.length === 0) {
|
|
382
|
+
console.log('No patterns ready for promotion (need 3+ occurrences).');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const rl = readline.createInterface({
|
|
387
|
+
input: process.stdin,
|
|
388
|
+
output: process.stdout
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const prompt = (q) => new Promise(r => rl.question(q, r));
|
|
392
|
+
|
|
393
|
+
console.log(color('cyan', 'Pattern Promotion Wizard'));
|
|
394
|
+
console.log('');
|
|
395
|
+
console.log('The following patterns have occurred 3+ times and');
|
|
396
|
+
console.log('should be promoted to permanent instruction files.');
|
|
397
|
+
console.log('');
|
|
398
|
+
|
|
399
|
+
for (let i = 0; i < Math.min(5, recurring.length); i++) {
|
|
400
|
+
const r = recurring[i];
|
|
401
|
+
|
|
402
|
+
console.log(`${color('yellow', `[${i + 1}]`)} ${r.pattern}`);
|
|
403
|
+
console.log(` Count: ${r.count} | Type: ${r.type}`);
|
|
404
|
+
console.log('');
|
|
405
|
+
|
|
406
|
+
const action = await prompt('Promote to (d)ecisions.md, (a)gents, (s)kip, (q)uit? ');
|
|
407
|
+
|
|
408
|
+
if (action.toLowerCase() === 'q') {
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (action.toLowerCase() === 'd') {
|
|
413
|
+
appendToDecisions(r.pattern);
|
|
414
|
+
success(`Added to decisions.md`);
|
|
415
|
+
} else if (action.toLowerCase() === 'a') {
|
|
416
|
+
const agent = await prompt('Which agent file? (e.g., developer): ');
|
|
417
|
+
if (agent) {
|
|
418
|
+
appendToAgent(agent, r.pattern);
|
|
419
|
+
success(`Added to agents/${agent}.md`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
console.log('');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
rl.close();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Append pattern to decisions.md
|
|
431
|
+
*/
|
|
432
|
+
function appendToDecisions(pattern) {
|
|
433
|
+
const decisionsPath = PATHS.decisions;
|
|
434
|
+
let content = readFile(decisionsPath, '# Decisions\n\n');
|
|
435
|
+
|
|
436
|
+
const date = new Date().toISOString().split('T')[0];
|
|
437
|
+
const entry = `\n## ${date} - Promoted Pattern\n\n**Rule**: ${pattern}\n**Source**: Aggregated from learnings (3+ occurrences)\n\n`;
|
|
438
|
+
|
|
439
|
+
content += entry;
|
|
440
|
+
writeFile(decisionsPath, content);
|
|
441
|
+
|
|
442
|
+
// Sync to .claude/rules/ for Claude Code integration
|
|
443
|
+
syncDecisionsToRules();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Append pattern to an agent file
|
|
448
|
+
*/
|
|
449
|
+
function appendToAgent(agentName, pattern) {
|
|
450
|
+
const agentPath = path.join(PROJECT_ROOT, 'agents', `${agentName}.md`);
|
|
451
|
+
|
|
452
|
+
if (!fileExists(agentPath)) {
|
|
453
|
+
warn(`Agent file not found: ${agentPath}`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
let content = readFile(agentPath, '');
|
|
458
|
+
const date = new Date().toISOString().split('T')[0];
|
|
459
|
+
const entry = `\n\n## Learned Pattern (${date})\n\n${pattern}\n`;
|
|
460
|
+
|
|
461
|
+
content += entry;
|
|
462
|
+
writeFile(agentPath, content);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ============================================================
|
|
466
|
+
// Main
|
|
467
|
+
// ============================================================
|
|
468
|
+
|
|
469
|
+
function main() {
|
|
470
|
+
const args = process.argv.slice(2);
|
|
471
|
+
const detailed = args.includes('--detailed');
|
|
472
|
+
const promote = args.includes('--promote');
|
|
473
|
+
|
|
474
|
+
// Collect all data
|
|
475
|
+
const data = {
|
|
476
|
+
learnings: collectSkillLearnings(),
|
|
477
|
+
patterns: parseFeedbackPatterns(),
|
|
478
|
+
corrections: parseCorrections()
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
if (promote) {
|
|
482
|
+
runPromotionWizard(data);
|
|
483
|
+
} else {
|
|
484
|
+
printSummary(data, { detailed });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
main();
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Wogi Flow - Archive Old Entries
|
|
4
|
+
# Keeps request-log small by archiving old entries
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
WORKFLOW_DIR=".workflow"
|
|
9
|
+
REQUEST_LOG="$WORKFLOW_DIR/state/request-log.md"
|
|
10
|
+
ARCHIVE_DIR="$WORKFLOW_DIR/archive"
|
|
11
|
+
|
|
12
|
+
# Colors
|
|
13
|
+
GREEN='\033[0;32m'
|
|
14
|
+
YELLOW='\033[1;33m'
|
|
15
|
+
CYAN='\033[0;36m'
|
|
16
|
+
NC='\033[0m'
|
|
17
|
+
|
|
18
|
+
show_help() {
|
|
19
|
+
echo "Archive old request-log entries"
|
|
20
|
+
echo ""
|
|
21
|
+
echo "Usage: flow archive [options]"
|
|
22
|
+
echo ""
|
|
23
|
+
echo "Options:"
|
|
24
|
+
echo " --keep N Keep last N entries (default: 50)"
|
|
25
|
+
echo " --before DATE Archive entries before DATE (YYYY-MM-DD)"
|
|
26
|
+
echo " --dry-run Show what would be archived"
|
|
27
|
+
echo ""
|
|
28
|
+
echo "Archives old entries to .workflow/archive/request-log-YYYY-MM.md"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
KEEP=50
|
|
32
|
+
BEFORE=""
|
|
33
|
+
DRY_RUN=false
|
|
34
|
+
|
|
35
|
+
while [[ $# -gt 0 ]]; do
|
|
36
|
+
case $1 in
|
|
37
|
+
--keep)
|
|
38
|
+
KEEP="$2"
|
|
39
|
+
shift 2
|
|
40
|
+
;;
|
|
41
|
+
--before)
|
|
42
|
+
BEFORE="$2"
|
|
43
|
+
shift 2
|
|
44
|
+
;;
|
|
45
|
+
--dry-run)
|
|
46
|
+
DRY_RUN=true
|
|
47
|
+
shift
|
|
48
|
+
;;
|
|
49
|
+
help|--help|-h)
|
|
50
|
+
show_help
|
|
51
|
+
exit 0
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
shift
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
if [ ! -f "$REQUEST_LOG" ]; then
|
|
60
|
+
echo -e "${YELLOW}No request-log.md found${NC}"
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Count entries
|
|
65
|
+
total_entries=$(grep -c "^### R-" "$REQUEST_LOG" 2>/dev/null || echo "0")
|
|
66
|
+
|
|
67
|
+
if [ "$total_entries" -le "$KEEP" ]; then
|
|
68
|
+
echo -e "${GREEN}✓ Request-log has $total_entries entries (keeping $KEEP)${NC}"
|
|
69
|
+
echo "Nothing to archive."
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
to_archive=$((total_entries - KEEP))
|
|
74
|
+
echo -e "${CYAN}Request-log: $total_entries entries${NC}"
|
|
75
|
+
echo -e "${YELLOW}Archiving: $to_archive oldest entries${NC}"
|
|
76
|
+
echo ""
|
|
77
|
+
|
|
78
|
+
if [ "$DRY_RUN" = true ]; then
|
|
79
|
+
echo "[Dry run - no changes made]"
|
|
80
|
+
echo ""
|
|
81
|
+
echo "Would archive entries R-001 through R-$(printf '%03d' $to_archive)"
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Create archive directory
|
|
86
|
+
mkdir -p "$ARCHIVE_DIR"
|
|
87
|
+
|
|
88
|
+
# Get current month for archive file
|
|
89
|
+
ARCHIVE_FILE="$ARCHIVE_DIR/request-log-$(date +%Y-%m).md"
|
|
90
|
+
|
|
91
|
+
# Extract entries to archive (oldest ones)
|
|
92
|
+
echo -e "${CYAN}Extracting old entries...${NC}"
|
|
93
|
+
|
|
94
|
+
# Use Python for reliable parsing
|
|
95
|
+
python3 << EOF
|
|
96
|
+
import re
|
|
97
|
+
from datetime import datetime
|
|
98
|
+
|
|
99
|
+
with open('$REQUEST_LOG', 'r') as f:
|
|
100
|
+
content = f.read()
|
|
101
|
+
|
|
102
|
+
# Split into entries
|
|
103
|
+
entries = re.split(r'(?=^### R-\d+)', content, flags=re.MULTILINE)
|
|
104
|
+
header = entries[0] if not entries[0].startswith('### R-') else ''
|
|
105
|
+
entries = [e for e in entries if e.startswith('### R-')]
|
|
106
|
+
|
|
107
|
+
keep = $KEEP
|
|
108
|
+
to_archive = entries[:-keep] if keep < len(entries) else []
|
|
109
|
+
to_keep = entries[-keep:] if keep < len(entries) else entries
|
|
110
|
+
|
|
111
|
+
# Archive old entries
|
|
112
|
+
if to_archive:
|
|
113
|
+
with open('$ARCHIVE_FILE', 'a') as f:
|
|
114
|
+
f.write('\n# Archived Entries\n\n')
|
|
115
|
+
for entry in to_archive:
|
|
116
|
+
f.write(entry)
|
|
117
|
+
|
|
118
|
+
# Rewrite request-log with only recent entries
|
|
119
|
+
with open('$REQUEST_LOG', 'w') as f:
|
|
120
|
+
f.write(header)
|
|
121
|
+
for entry in to_keep:
|
|
122
|
+
f.write(entry)
|
|
123
|
+
|
|
124
|
+
print(f'Archived {len(to_archive)} entries to $ARCHIVE_FILE')
|
|
125
|
+
print(f'Kept {len(to_keep)} recent entries')
|
|
126
|
+
else:
|
|
127
|
+
print('Nothing to archive')
|
|
128
|
+
EOF
|
|
129
|
+
|
|
130
|
+
echo ""
|
|
131
|
+
echo -e "${GREEN}✓ Archive complete${NC}"
|
|
132
|
+
echo " Archived to: $ARCHIVE_FILE"
|
|
133
|
+
echo " Remaining entries: $KEEP"
|