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,600 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Active Pattern Enforcement
|
|
5
|
+
*
|
|
6
|
+
* Ensures that learned patterns from decisions.md, app-map.md, and skills
|
|
7
|
+
* are actively injected into prompts and enforced during code generation.
|
|
8
|
+
*
|
|
9
|
+
* Key Features:
|
|
10
|
+
* - Extracts relevant patterns based on task context
|
|
11
|
+
* - Injects patterns prominently into prompts
|
|
12
|
+
* - Validates output against patterns
|
|
13
|
+
* - Requires citation of patterns in generated code
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const { injectPatterns, validateAgainstPatterns } = require('./flow-pattern-enforcer');
|
|
17
|
+
* const enrichedPrompt = injectPatterns(prompt, task, projectRoot);
|
|
18
|
+
*
|
|
19
|
+
* Part of v1.8.0 - Active Learning Enforcement
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { getProjectRoot, getConfig } = require('./flow-utils');
|
|
25
|
+
|
|
26
|
+
// ============================================================
|
|
27
|
+
// Configuration
|
|
28
|
+
// ============================================================
|
|
29
|
+
|
|
30
|
+
const PROJECT_ROOT = getProjectRoot();
|
|
31
|
+
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
32
|
+
const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
|
|
33
|
+
|
|
34
|
+
// ============================================================
|
|
35
|
+
// Pattern Extraction
|
|
36
|
+
// ============================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load all patterns from decisions.md
|
|
40
|
+
*/
|
|
41
|
+
function loadDecisionPatterns(projectRoot = PROJECT_ROOT) {
|
|
42
|
+
const decisionsPath = path.join(projectRoot, '.workflow', 'state', 'decisions.md');
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(decisionsPath)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const content = fs.readFileSync(decisionsPath, 'utf-8');
|
|
49
|
+
const patterns = [];
|
|
50
|
+
|
|
51
|
+
// Extract each section as a pattern category
|
|
52
|
+
const sections = content.match(/## ([^\n]+)\n([\s\S]*?)(?=\n## |$)/g) || [];
|
|
53
|
+
|
|
54
|
+
for (const section of sections) {
|
|
55
|
+
const match = section.match(/## ([^\n]+)\n([\s\S]*)/);
|
|
56
|
+
if (match) {
|
|
57
|
+
const category = match[1].trim();
|
|
58
|
+
const rules = match[2].trim();
|
|
59
|
+
|
|
60
|
+
// Extract individual rules (lines starting with - or *)
|
|
61
|
+
const ruleLines = rules.match(/^[\s]*[-*]\s+.+$/gm) || [];
|
|
62
|
+
|
|
63
|
+
patterns.push({
|
|
64
|
+
category,
|
|
65
|
+
rules: ruleLines.map(r => r.replace(/^[\s]*[-*]\s+/, '').trim()),
|
|
66
|
+
raw: rules
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return patterns;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load components from app-map.md
|
|
76
|
+
*/
|
|
77
|
+
function loadAppMapComponents(projectRoot = PROJECT_ROOT) {
|
|
78
|
+
const appMapPath = path.join(projectRoot, '.workflow', 'state', 'app-map.md');
|
|
79
|
+
|
|
80
|
+
if (!fs.existsSync(appMapPath)) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const content = fs.readFileSync(appMapPath, 'utf-8');
|
|
85
|
+
const components = [];
|
|
86
|
+
|
|
87
|
+
// Extract table rows (| Component | Variants | ... |)
|
|
88
|
+
const tableRows = content.match(/^\|[^|]+\|[^|]+\|.+\|$/gm) || [];
|
|
89
|
+
|
|
90
|
+
for (const row of tableRows) {
|
|
91
|
+
if (row.includes('---') || row.toLowerCase().includes('component')) continue;
|
|
92
|
+
|
|
93
|
+
const cells = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
94
|
+
if (cells.length >= 2) {
|
|
95
|
+
components.push({
|
|
96
|
+
name: cells[0],
|
|
97
|
+
variants: cells[1] ? cells[1].split(',').map(v => v.trim()) : [],
|
|
98
|
+
description: cells[2] || '',
|
|
99
|
+
path: cells[3] || ''
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return components;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load skill patterns for a given file type
|
|
109
|
+
*/
|
|
110
|
+
function loadSkillPatterns(projectRoot, fileExtension, taskDescription = '') {
|
|
111
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
112
|
+
if (!fs.existsSync(skillsDir)) return null;
|
|
113
|
+
|
|
114
|
+
// Map file extensions to skills
|
|
115
|
+
const extensionToSkill = {
|
|
116
|
+
'.module.ts': 'nestjs',
|
|
117
|
+
'.controller.ts': 'nestjs',
|
|
118
|
+
'.service.ts': 'nestjs',
|
|
119
|
+
'.tsx': 'react',
|
|
120
|
+
'.jsx': 'react',
|
|
121
|
+
'.vue': 'vue',
|
|
122
|
+
'.py': 'python',
|
|
123
|
+
'.rs': 'rust',
|
|
124
|
+
'.go': 'go'
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Find matching skill
|
|
128
|
+
let skillName = null;
|
|
129
|
+
for (const [ext, skill] of Object.entries(extensionToSkill)) {
|
|
130
|
+
if (fileExtension.endsWith(ext)) {
|
|
131
|
+
skillName = skill;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Also check task description for framework mentions
|
|
137
|
+
if (!skillName && taskDescription) {
|
|
138
|
+
const frameworks = ['nestjs', 'react', 'vue', 'angular', 'express', 'fastapi', 'django'];
|
|
139
|
+
for (const fw of frameworks) {
|
|
140
|
+
if (taskDescription.toLowerCase().includes(fw)) {
|
|
141
|
+
skillName = fw;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!skillName) return null;
|
|
148
|
+
|
|
149
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
150
|
+
if (!fs.existsSync(skillDir)) return null;
|
|
151
|
+
|
|
152
|
+
const patterns = { skillName, patterns: null, antiPatterns: null };
|
|
153
|
+
|
|
154
|
+
// Load patterns
|
|
155
|
+
const patternsPath = path.join(skillDir, 'knowledge', 'patterns.md');
|
|
156
|
+
if (fs.existsSync(patternsPath)) {
|
|
157
|
+
patterns.patterns = fs.readFileSync(patternsPath, 'utf-8');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Load anti-patterns
|
|
161
|
+
const antiPatternsPath = path.join(skillDir, 'knowledge', 'anti-patterns.md');
|
|
162
|
+
if (fs.existsSync(antiPatternsPath)) {
|
|
163
|
+
patterns.antiPatterns = fs.readFileSync(antiPatternsPath, 'utf-8');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return patterns;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Extract patterns relevant to a specific task
|
|
171
|
+
*/
|
|
172
|
+
function extractRelevantPatterns(task, projectRoot = PROJECT_ROOT) {
|
|
173
|
+
const relevant = {
|
|
174
|
+
decisions: [],
|
|
175
|
+
components: [],
|
|
176
|
+
skill: null,
|
|
177
|
+
keywords: []
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Extract keywords from task
|
|
181
|
+
const taskText = `${task.description || ''} ${task.file || ''} ${task.action || ''}`.toLowerCase();
|
|
182
|
+
relevant.keywords = taskText
|
|
183
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
184
|
+
.split(/\s+/)
|
|
185
|
+
.filter(w => w.length > 2);
|
|
186
|
+
|
|
187
|
+
// Load all patterns
|
|
188
|
+
const decisionPatterns = loadDecisionPatterns(projectRoot);
|
|
189
|
+
const appMapComponents = loadAppMapComponents(projectRoot);
|
|
190
|
+
|
|
191
|
+
// Filter decision patterns by relevance
|
|
192
|
+
for (const pattern of decisionPatterns) {
|
|
193
|
+
const categoryLower = pattern.category.toLowerCase();
|
|
194
|
+
const rulesLower = pattern.rules.join(' ').toLowerCase();
|
|
195
|
+
|
|
196
|
+
// Check for keyword matches
|
|
197
|
+
const isRelevant = relevant.keywords.some(kw =>
|
|
198
|
+
categoryLower.includes(kw) || rulesLower.includes(kw)
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Always include certain categories
|
|
202
|
+
const alwaysInclude = ['naming', 'file', 'import', 'general', 'coding'];
|
|
203
|
+
const shouldAlwaysInclude = alwaysInclude.some(ai => categoryLower.includes(ai));
|
|
204
|
+
|
|
205
|
+
if (isRelevant || shouldAlwaysInclude) {
|
|
206
|
+
relevant.decisions.push(pattern);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Filter components by relevance
|
|
211
|
+
for (const component of appMapComponents) {
|
|
212
|
+
const componentLower = `${component.name} ${component.variants.join(' ')} ${component.description}`.toLowerCase();
|
|
213
|
+
|
|
214
|
+
if (relevant.keywords.some(kw => componentLower.includes(kw))) {
|
|
215
|
+
relevant.components.push(component);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Load skill patterns if applicable
|
|
220
|
+
if (task.file) {
|
|
221
|
+
relevant.skill = loadSkillPatterns(projectRoot, task.file, task.description);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return relevant;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ============================================================
|
|
228
|
+
// Pattern Injection
|
|
229
|
+
// ============================================================
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Format patterns for prompt injection
|
|
233
|
+
*/
|
|
234
|
+
function formatPatternsForPrompt(relevantPatterns, config = {}) {
|
|
235
|
+
const { requireCitation = false } = config;
|
|
236
|
+
let output = '';
|
|
237
|
+
|
|
238
|
+
// Header
|
|
239
|
+
output += `\n## ⚠️ MANDATORY PATTERNS - MUST FOLLOW ⚠️\n\n`;
|
|
240
|
+
output += `The following patterns are REQUIRED. Violations will be rejected.\n\n`;
|
|
241
|
+
|
|
242
|
+
// Decision patterns
|
|
243
|
+
if (relevantPatterns.decisions.length > 0) {
|
|
244
|
+
output += `### Project Rules (from decisions.md)\n\n`;
|
|
245
|
+
|
|
246
|
+
for (const pattern of relevantPatterns.decisions) {
|
|
247
|
+
output += `**${pattern.category}**\n`;
|
|
248
|
+
for (const rule of pattern.rules.slice(0, 5)) { // Limit rules per category
|
|
249
|
+
output += `- ${rule}\n`;
|
|
250
|
+
}
|
|
251
|
+
output += '\n';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Existing components
|
|
256
|
+
if (relevantPatterns.components.length > 0) {
|
|
257
|
+
output += `### Existing Components (from app-map.md) - REUSE THESE\n\n`;
|
|
258
|
+
output += `| Component | Variants | Path |\n`;
|
|
259
|
+
output += `|-----------|----------|------|\n`;
|
|
260
|
+
|
|
261
|
+
for (const comp of relevantPatterns.components.slice(0, 10)) {
|
|
262
|
+
output += `| ${comp.name} | ${comp.variants.join(', ')} | ${comp.path} |\n`;
|
|
263
|
+
}
|
|
264
|
+
output += '\n';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Skill patterns
|
|
268
|
+
if (relevantPatterns.skill) {
|
|
269
|
+
output += `### ${relevantPatterns.skill.skillName} Patterns\n\n`;
|
|
270
|
+
|
|
271
|
+
if (relevantPatterns.skill.patterns) {
|
|
272
|
+
output += `**DO:**\n${relevantPatterns.skill.patterns.slice(0, 1000)}\n\n`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (relevantPatterns.skill.antiPatterns) {
|
|
276
|
+
output += `**DON'T:**\n${relevantPatterns.skill.antiPatterns.slice(0, 500)}\n\n`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Citation requirement
|
|
281
|
+
if (requireCitation) {
|
|
282
|
+
output += `### Citation Requirement\n\n`;
|
|
283
|
+
output += `You MUST include a comment citing which pattern you're following:\n`;
|
|
284
|
+
output += `\`\`\`typescript\n`;
|
|
285
|
+
output += `// Following: "Use kebab-case for files" (decisions.md)\n`;
|
|
286
|
+
output += `// Reusing: Button component (app-map.md)\n`;
|
|
287
|
+
output += `\`\`\`\n\n`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return output;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Inject patterns into a prompt
|
|
295
|
+
*/
|
|
296
|
+
function injectPatterns(prompt, task, projectRoot = PROJECT_ROOT) {
|
|
297
|
+
const config = getConfig();
|
|
298
|
+
const enforcement = config.enforcement || {};
|
|
299
|
+
|
|
300
|
+
const relevantPatterns = extractRelevantPatterns(task, projectRoot);
|
|
301
|
+
|
|
302
|
+
// Skip if no patterns found
|
|
303
|
+
if (relevantPatterns.decisions.length === 0 &&
|
|
304
|
+
relevantPatterns.components.length === 0 &&
|
|
305
|
+
!relevantPatterns.skill) {
|
|
306
|
+
return prompt;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const patternSection = formatPatternsForPrompt(relevantPatterns, {
|
|
310
|
+
requireCitation: enforcement.requirePatternCitation || false
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Inject patterns at the beginning of the prompt (high visibility)
|
|
314
|
+
return patternSection + '\n---\n\n' + prompt;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================================
|
|
318
|
+
// Pattern Validation
|
|
319
|
+
// ============================================================
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Validation rules based on pattern categories
|
|
323
|
+
*/
|
|
324
|
+
const VALIDATION_RULES = {
|
|
325
|
+
'naming': [
|
|
326
|
+
{ pattern: /PascalCase/i, check: (code) => /[A-Z][a-z]+[A-Z]/.test(code), inverse: false },
|
|
327
|
+
{ pattern: /kebab-case/i, check: (code, files) => files?.every(f => /^[a-z0-9-]+\.[a-z]+$/.test(path.basename(f))), inverse: false },
|
|
328
|
+
{ pattern: /camelCase/i, check: (code) => /[a-z]+[A-Z][a-z]+/.test(code), inverse: false }
|
|
329
|
+
],
|
|
330
|
+
'import': [
|
|
331
|
+
{ pattern: /absolute.*@\//i, check: (code) => code.includes('@/'), inverse: false },
|
|
332
|
+
{ pattern: /relative.*\.\.\//i, check: (code) => !code.includes('../'), inverse: true }
|
|
333
|
+
]
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Validate code against extracted patterns
|
|
338
|
+
*/
|
|
339
|
+
function validateAgainstPatterns(code, patterns, files = []) {
|
|
340
|
+
const violations = [];
|
|
341
|
+
const passes = [];
|
|
342
|
+
|
|
343
|
+
for (const pattern of patterns.decisions) {
|
|
344
|
+
const categoryLower = pattern.category.toLowerCase();
|
|
345
|
+
|
|
346
|
+
for (const rule of pattern.rules) {
|
|
347
|
+
const ruleLower = rule.toLowerCase();
|
|
348
|
+
|
|
349
|
+
// Check naming conventions
|
|
350
|
+
if (categoryLower.includes('naming') || ruleLower.includes('naming')) {
|
|
351
|
+
if (ruleLower.includes('kebab-case') && files.length > 0) {
|
|
352
|
+
const nonKebab = files.filter(f => !/^[a-z0-9-]+\.[a-z]+$/.test(path.basename(f)));
|
|
353
|
+
if (nonKebab.length > 0) {
|
|
354
|
+
violations.push({
|
|
355
|
+
rule: rule,
|
|
356
|
+
category: pattern.category,
|
|
357
|
+
message: `Files not in kebab-case: ${nonKebab.join(', ')}`
|
|
358
|
+
});
|
|
359
|
+
} else {
|
|
360
|
+
passes.push({ rule: rule, category: pattern.category });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check import patterns
|
|
366
|
+
if (categoryLower.includes('import') || ruleLower.includes('import')) {
|
|
367
|
+
if (ruleLower.includes('absolute') && ruleLower.includes('@/')) {
|
|
368
|
+
if (!code.includes('@/') && code.includes('../')) {
|
|
369
|
+
violations.push({
|
|
370
|
+
rule: rule,
|
|
371
|
+
category: pattern.category,
|
|
372
|
+
message: 'Using relative imports instead of absolute @/ imports'
|
|
373
|
+
});
|
|
374
|
+
} else if (code.includes('@/')) {
|
|
375
|
+
passes.push({ rule: rule, category: pattern.category });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Check forbidden patterns
|
|
381
|
+
if (ruleLower.includes('never') || ruleLower.includes('don\'t') || ruleLower.includes('avoid')) {
|
|
382
|
+
// Extract what to avoid
|
|
383
|
+
const avoidMatch = ruleLower.match(/(?:never|don't|avoid)\s+(?:use\s+)?(.+?)(?:\.|$)/);
|
|
384
|
+
if (avoidMatch) {
|
|
385
|
+
const forbidden = avoidMatch[1].trim();
|
|
386
|
+
if (code.toLowerCase().includes(forbidden)) {
|
|
387
|
+
violations.push({
|
|
388
|
+
rule: rule,
|
|
389
|
+
category: pattern.category,
|
|
390
|
+
message: `Code contains forbidden pattern: "${forbidden}"`
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Check component reuse
|
|
399
|
+
if (patterns.components.length > 0) {
|
|
400
|
+
const createdNew = code.match(/(?:function|const|class)\s+([A-Z][a-zA-Z]+)/g) || [];
|
|
401
|
+
|
|
402
|
+
for (const created of createdNew) {
|
|
403
|
+
const name = created.replace(/(?:function|const|class)\s+/, '');
|
|
404
|
+
const existing = patterns.components.find(c =>
|
|
405
|
+
c.name.toLowerCase() === name.toLowerCase()
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
if (existing) {
|
|
409
|
+
violations.push({
|
|
410
|
+
rule: `Reuse existing component: ${existing.name}`,
|
|
411
|
+
category: 'Component Reuse',
|
|
412
|
+
message: `Created new "${name}" but "${existing.name}" already exists at ${existing.path}`
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
valid: violations.length === 0,
|
|
420
|
+
violations,
|
|
421
|
+
passes,
|
|
422
|
+
summary: violations.length === 0
|
|
423
|
+
? `✓ All ${passes.length} pattern checks passed`
|
|
424
|
+
: `✗ ${violations.length} violations, ${passes.length} passes`
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Check if code includes required citations
|
|
430
|
+
*/
|
|
431
|
+
function validateCitations(code, patterns) {
|
|
432
|
+
const citations = code.match(/\/\/\s*(?:Following|Reusing|Pattern):\s*.+/gi) || [];
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
hasCitations: citations.length > 0,
|
|
436
|
+
citations: citations,
|
|
437
|
+
message: citations.length > 0
|
|
438
|
+
? `Found ${citations.length} pattern citations`
|
|
439
|
+
: 'No pattern citations found (required when enforcement.requirePatternCitation is true)'
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ============================================================
|
|
444
|
+
// Session Context Loading
|
|
445
|
+
// ============================================================
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Generate session start summary showing loaded patterns
|
|
449
|
+
*/
|
|
450
|
+
function generateSessionSummary(projectRoot = PROJECT_ROOT) {
|
|
451
|
+
const decisions = loadDecisionPatterns(projectRoot);
|
|
452
|
+
const components = loadAppMapComponents(projectRoot);
|
|
453
|
+
const config = getConfig();
|
|
454
|
+
|
|
455
|
+
let summary = '\n';
|
|
456
|
+
summary += '┌─────────────────────────────────────────────────────────────┐\n';
|
|
457
|
+
summary += '│ 📋 PROJECT CONTEXT LOADED │\n';
|
|
458
|
+
summary += '├─────────────────────────────────────────────────────────────┤\n';
|
|
459
|
+
|
|
460
|
+
// Decisions summary
|
|
461
|
+
const ruleCount = decisions.reduce((acc, d) => acc + d.rules.length, 0);
|
|
462
|
+
summary += `│ decisions.md: ${ruleCount} rules in ${decisions.length} categories\n`;
|
|
463
|
+
|
|
464
|
+
for (const d of decisions.slice(0, 3)) {
|
|
465
|
+
summary += `│ • ${d.category}: ${d.rules.length} rules\n`;
|
|
466
|
+
}
|
|
467
|
+
if (decisions.length > 3) {
|
|
468
|
+
summary += `│ • ... and ${decisions.length - 3} more categories\n`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Components summary
|
|
472
|
+
summary += `│\n│ app-map.md: ${components.length} components registered\n`;
|
|
473
|
+
|
|
474
|
+
for (const c of components.slice(0, 3)) {
|
|
475
|
+
summary += `│ • ${c.name} (${c.variants.length} variants)\n`;
|
|
476
|
+
}
|
|
477
|
+
if (components.length > 3) {
|
|
478
|
+
summary += `│ • ... and ${components.length - 3} more components\n`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Skills summary
|
|
482
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
483
|
+
if (fs.existsSync(skillsDir)) {
|
|
484
|
+
const skills = fs.readdirSync(skillsDir).filter(d =>
|
|
485
|
+
fs.statSync(path.join(skillsDir, d)).isDirectory() && !d.startsWith('_')
|
|
486
|
+
);
|
|
487
|
+
if (skills.length > 0) {
|
|
488
|
+
summary += `│\n│ .claude/skills/: ${skills.join(', ')}\n`;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
summary += '│\n│ ⚠️ THESE RULES ARE MANDATORY FOR ALL WORK │\n';
|
|
493
|
+
summary += '└─────────────────────────────────────────────────────────────┘\n';
|
|
494
|
+
|
|
495
|
+
return summary;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ============================================================
|
|
499
|
+
// Exports
|
|
500
|
+
// ============================================================
|
|
501
|
+
|
|
502
|
+
module.exports = {
|
|
503
|
+
// Pattern loading
|
|
504
|
+
loadDecisionPatterns,
|
|
505
|
+
loadAppMapComponents,
|
|
506
|
+
loadSkillPatterns,
|
|
507
|
+
extractRelevantPatterns,
|
|
508
|
+
|
|
509
|
+
// Pattern injection
|
|
510
|
+
formatPatternsForPrompt,
|
|
511
|
+
injectPatterns,
|
|
512
|
+
|
|
513
|
+
// Validation
|
|
514
|
+
validateAgainstPatterns,
|
|
515
|
+
validateCitations,
|
|
516
|
+
|
|
517
|
+
// Session helpers
|
|
518
|
+
generateSessionSummary
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// ============================================================
|
|
522
|
+
// CLI
|
|
523
|
+
// ============================================================
|
|
524
|
+
|
|
525
|
+
if (require.main === module) {
|
|
526
|
+
const args = process.argv.slice(2);
|
|
527
|
+
const command = args[0];
|
|
528
|
+
|
|
529
|
+
switch (command) {
|
|
530
|
+
case 'summary': {
|
|
531
|
+
console.log(generateSessionSummary());
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
case 'patterns': {
|
|
536
|
+
const patterns = loadDecisionPatterns();
|
|
537
|
+
console.log('\nDecision Patterns:\n');
|
|
538
|
+
for (const p of patterns) {
|
|
539
|
+
console.log(`## ${p.category}`);
|
|
540
|
+
for (const r of p.rules) {
|
|
541
|
+
console.log(` - ${r}`);
|
|
542
|
+
}
|
|
543
|
+
console.log('');
|
|
544
|
+
}
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
case 'components': {
|
|
549
|
+
const components = loadAppMapComponents();
|
|
550
|
+
console.log('\nRegistered Components:\n');
|
|
551
|
+
for (const c of components) {
|
|
552
|
+
console.log(` ${c.name}: ${c.variants.join(', ') || 'no variants'}`);
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
case 'validate': {
|
|
558
|
+
const filePath = args[1];
|
|
559
|
+
if (!filePath) {
|
|
560
|
+
console.error('Usage: flow-pattern-enforcer validate <file>');
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const code = fs.readFileSync(filePath, 'utf-8');
|
|
565
|
+
const patterns = extractRelevantPatterns({ file: filePath, description: '' });
|
|
566
|
+
const result = validateAgainstPatterns(code, patterns, [filePath]);
|
|
567
|
+
|
|
568
|
+
console.log('\nValidation Result:\n');
|
|
569
|
+
console.log(result.summary);
|
|
570
|
+
|
|
571
|
+
if (result.violations.length > 0) {
|
|
572
|
+
console.log('\nViolations:');
|
|
573
|
+
for (const v of result.violations) {
|
|
574
|
+
console.log(` ✗ [${v.category}] ${v.rule}`);
|
|
575
|
+
console.log(` ${v.message}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
default: {
|
|
582
|
+
console.log(`
|
|
583
|
+
Wogi Flow - Pattern Enforcer
|
|
584
|
+
|
|
585
|
+
Usage:
|
|
586
|
+
node flow-pattern-enforcer.js <command>
|
|
587
|
+
|
|
588
|
+
Commands:
|
|
589
|
+
summary Show session context summary
|
|
590
|
+
patterns List all decision patterns
|
|
591
|
+
components List registered components
|
|
592
|
+
validate <file> Validate a file against patterns
|
|
593
|
+
|
|
594
|
+
Examples:
|
|
595
|
+
node flow-pattern-enforcer.js summary
|
|
596
|
+
node flow-pattern-enforcer.js validate src/components/Button.tsx
|
|
597
|
+
`);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|