wogiflow 1.0.12 → 1.0.13
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/specs/architecture.md.template +24 -0
- package/.workflow/specs/stack.md.template +33 -0
- package/.workflow/specs/testing.md.template +36 -0
- package/README.md +90 -1
- package/package.json +1 -1
- package/scripts/MEMORY-ARCHITECTURE.md +150 -0
- package/scripts/flow +20 -19
- package/scripts/flow-auto-context.js +97 -3
- package/scripts/flow-conflict-resolver.js +735 -0
- package/scripts/flow-context-gatherer.js +520 -0
- package/scripts/flow-context-monitor.js +148 -19
- package/scripts/flow-damage-control.js +5 -1
- package/scripts/flow-export-profile +168 -1
- package/scripts/flow-import-profile +257 -6
- package/scripts/flow-instruction-richness.js +182 -18
- package/scripts/flow-knowledge-router.js +2 -0
- package/scripts/flow-knowledge-sync.js +2 -0
- package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
- package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
- package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
- package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
- package/scripts/flow-memory-db.js +386 -1
- package/scripts/flow-memory-sync.js +2 -0
- package/scripts/flow-model-adapter.js +53 -29
- package/scripts/flow-model-router.js +246 -1
- package/scripts/flow-morning.js +94 -0
- package/scripts/flow-onboard +223 -10
- package/scripts/flow-orchestrate-validation.js +539 -0
- package/scripts/flow-orchestrate.js +16 -507
- package/scripts/flow-pattern-extractor.js +1265 -0
- package/scripts/flow-prompt-composer.js +222 -2
- package/scripts/flow-quality-guard.js +594 -0
- package/scripts/flow-section-index.js +713 -0
- package/scripts/flow-section-resolver.js +484 -0
- package/scripts/flow-session-end.js +188 -2
- package/scripts/flow-skill-create.js +19 -3
- package/scripts/flow-skill-matcher.js +122 -7
- package/scripts/flow-statusline-setup.js +218 -0
- package/scripts/flow-step-review.js +19 -0
- package/scripts/flow-tech-debt.js +734 -0
- package/scripts/flow-utils.js +2 -0
- package/scripts/hooks/core/long-input-gate.js +293 -0
- package/scripts/flow-parallel-detector.js +0 -399
- package/scripts/flow-parallel-dispatch.js +0 -987
- /package/scripts/{flow-transcript-language.js → flow-long-input-language.js} +0 -0
|
@@ -20,6 +20,7 @@ show_help() {
|
|
|
20
20
|
echo "Import Workflow Profile"
|
|
21
21
|
echo ""
|
|
22
22
|
echo "Usage: flow import-profile <profile.zip> [options]"
|
|
23
|
+
echo " flow import-profile --scan <project-folder> [options]"
|
|
23
24
|
echo ""
|
|
24
25
|
echo "Options:"
|
|
25
26
|
echo " --backup Create backup of current config before importing"
|
|
@@ -29,10 +30,16 @@ show_help() {
|
|
|
29
30
|
echo " --skip-rules Don't import rules (.claude/rules/)"
|
|
30
31
|
echo " --skip-templates Don't import templates"
|
|
31
32
|
echo ""
|
|
33
|
+
echo "Scan Mode (NEW):"
|
|
34
|
+
echo " --scan <folder> Scan another project folder for patterns"
|
|
35
|
+
echo " --resolve-conflicts Interactive conflict resolution (with --scan)"
|
|
36
|
+
echo " --analysis-mode MODE Analysis depth: balanced (default), deep"
|
|
37
|
+
echo ""
|
|
32
38
|
echo "Examples:"
|
|
33
39
|
echo " flow import-profile team-frontend.zip"
|
|
34
40
|
echo " flow import-profile team-frontend.zip --backup"
|
|
35
|
-
echo " flow import-profile
|
|
41
|
+
echo " flow import-profile --scan /path/to/other/project"
|
|
42
|
+
echo " flow import-profile --scan ../my-other-app --resolve-conflicts"
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
if [ -z "$1" ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
|
@@ -40,16 +47,21 @@ if [ -z "$1" ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
|
|
40
47
|
exit 0
|
|
41
48
|
fi
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
shift
|
|
45
|
-
|
|
46
|
-
# Parse options
|
|
50
|
+
# Parse options first to detect --scan mode
|
|
47
51
|
BACKUP=false
|
|
48
52
|
DRY_RUN=false
|
|
49
53
|
FORCE=false
|
|
50
54
|
SKIP_LEARNINGS=false
|
|
51
55
|
SKIP_RULES=false
|
|
52
56
|
SKIP_TEMPLATES=false
|
|
57
|
+
SCAN_MODE=false
|
|
58
|
+
SCAN_PATH=""
|
|
59
|
+
RESOLVE_CONFLICTS=false
|
|
60
|
+
ANALYSIS_MODE="balanced"
|
|
61
|
+
PROFILE_FILE=""
|
|
62
|
+
|
|
63
|
+
# Determine scripts directory
|
|
64
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
53
65
|
|
|
54
66
|
while [ $# -gt 0 ]; do
|
|
55
67
|
case "$1" in
|
|
@@ -71,15 +83,254 @@ while [ $# -gt 0 ]; do
|
|
|
71
83
|
--skip-templates)
|
|
72
84
|
SKIP_TEMPLATES=true
|
|
73
85
|
;;
|
|
74
|
-
|
|
86
|
+
--scan)
|
|
87
|
+
SCAN_MODE=true
|
|
88
|
+
shift
|
|
89
|
+
SCAN_PATH="$1"
|
|
90
|
+
;;
|
|
91
|
+
--resolve-conflicts)
|
|
92
|
+
RESOLVE_CONFLICTS=true
|
|
93
|
+
;;
|
|
94
|
+
--analysis-mode)
|
|
95
|
+
shift
|
|
96
|
+
ANALYSIS_MODE="$1"
|
|
97
|
+
;;
|
|
98
|
+
-*)
|
|
75
99
|
echo -e "${RED}Unknown option: $1${NC}"
|
|
76
100
|
exit 1
|
|
77
101
|
;;
|
|
102
|
+
*)
|
|
103
|
+
# Positional argument - profile file
|
|
104
|
+
if [ -z "$PROFILE_FILE" ]; then
|
|
105
|
+
PROFILE_FILE="$1"
|
|
106
|
+
fi
|
|
107
|
+
;;
|
|
78
108
|
esac
|
|
79
109
|
shift
|
|
80
110
|
done
|
|
81
111
|
|
|
112
|
+
# Safe JSON reading function (prevents file path injection via process.argv)
|
|
113
|
+
# Note: queries are hardcoded in this script, not external input
|
|
114
|
+
# The query is passed via environment variable to avoid shell quoting issues
|
|
115
|
+
safe_read_json() {
|
|
116
|
+
local file="$1"
|
|
117
|
+
local query="$2"
|
|
118
|
+
SAFE_JSON_QUERY="$query" node -e '
|
|
119
|
+
const fs = require("fs");
|
|
120
|
+
const file = process.argv[1];
|
|
121
|
+
try {
|
|
122
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
123
|
+
// Check for prototype pollution attempts
|
|
124
|
+
if (/__proto__|constructor\s*["'"'"'`:]|prototype\s*["'"'"'`:]/i.test(content)) {
|
|
125
|
+
console.error("Suspicious content detected");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const p = JSON.parse(content);
|
|
129
|
+
// Query is passed via environment to avoid complex quoting issues
|
|
130
|
+
const queryFn = new Function("p", process.env.SAFE_JSON_QUERY);
|
|
131
|
+
queryFn(p);
|
|
132
|
+
} catch(e) {
|
|
133
|
+
console.error(e.message);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
' -- "$file" 2>/dev/null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# ==========================================
|
|
140
|
+
# SCAN MODE - Import patterns from another project
|
|
141
|
+
# ==========================================
|
|
142
|
+
if [ "$SCAN_MODE" = true ]; then
|
|
143
|
+
if [ -z "$SCAN_PATH" ]; then
|
|
144
|
+
echo -e "${RED}Error: --scan requires a project folder path${NC}"
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if [ ! -d "$SCAN_PATH" ]; then
|
|
149
|
+
echo -e "${RED}Error: Project folder not found: $SCAN_PATH${NC}"
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
|
|
154
|
+
echo -e "${CYAN}║ Scanning Project for Patterns ║${NC}"
|
|
155
|
+
echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
|
|
156
|
+
echo ""
|
|
157
|
+
echo -e "${DIM}Source: $SCAN_PATH${NC}"
|
|
158
|
+
echo ""
|
|
159
|
+
|
|
160
|
+
# Create temp directory for extraction results
|
|
161
|
+
TEMP_DIR=$(mktemp -d)
|
|
162
|
+
PATTERNS_FILE="$TEMP_DIR/patterns.json"
|
|
163
|
+
|
|
164
|
+
# Run pattern extraction on the source project
|
|
165
|
+
echo -e "${YELLOW}Extracting patterns...${NC}"
|
|
166
|
+
|
|
167
|
+
EXTRACTOR_OPTS="--format json --with-conflicts --analysis-mode $ANALYSIS_MODE"
|
|
168
|
+
|
|
169
|
+
if ! node "$SCRIPT_DIR/flow-pattern-extractor.js" $EXTRACTOR_OPTS --output "$PATTERNS_FILE" --project "$SCAN_PATH" 2>/dev/null; then
|
|
170
|
+
echo -e "${RED}Error: Pattern extraction failed${NC}"
|
|
171
|
+
rm -rf "$TEMP_DIR"
|
|
172
|
+
exit 1
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# Parse extraction results (using safe JSON parsing instead of require())
|
|
176
|
+
PATTERN_COUNT=$(safe_read_json "$PATTERNS_FILE" "let c=0; for(const k in p.patterns){c+=p.patterns[k].length} console.log(c)" || echo "0")
|
|
177
|
+
CONFLICT_COUNT=$(safe_read_json "$PATTERNS_FILE" "console.log(p.conflicts ? p.conflicts.length : 0)" || echo "0")
|
|
178
|
+
FRAMEWORK=$(safe_read_json "$PATTERNS_FILE" "console.log(p.meta.framework || 'unknown')" || echo "unknown")
|
|
179
|
+
|
|
180
|
+
echo ""
|
|
181
|
+
echo -e " ${GREEN}✓${NC} Detected: $FRAMEWORK"
|
|
182
|
+
echo -e " ${GREEN}✓${NC} Extracted: $PATTERN_COUNT patterns"
|
|
183
|
+
|
|
184
|
+
# Handle conflicts
|
|
185
|
+
RESOLVED_FILE=""
|
|
186
|
+
if [ "$CONFLICT_COUNT" -gt 0 ]; then
|
|
187
|
+
echo -e " ${YELLOW}!${NC} Found: $CONFLICT_COUNT conflicts"
|
|
188
|
+
|
|
189
|
+
if [ "$RESOLVE_CONFLICTS" = true ]; then
|
|
190
|
+
# Extract conflicts for resolver (using safe JSON parsing)
|
|
191
|
+
CONFLICTS_TEMP="$TEMP_DIR/conflicts.json"
|
|
192
|
+
safe_read_json "$PATTERNS_FILE" "console.log(JSON.stringify(p.conflicts, null, 2))" > "$CONFLICTS_TEMP"
|
|
193
|
+
|
|
194
|
+
echo ""
|
|
195
|
+
RESOLVED_FILE="$TEMP_DIR/resolved.json"
|
|
196
|
+
|
|
197
|
+
if ! node "$SCRIPT_DIR/flow-conflict-resolver.js" --input "$CONFLICTS_TEMP" --output "$RESOLVED_FILE"; then
|
|
198
|
+
echo -e "${YELLOW}Conflict resolution cancelled or skipped${NC}"
|
|
199
|
+
RESOLVED_FILE=""
|
|
200
|
+
fi
|
|
201
|
+
else
|
|
202
|
+
echo ""
|
|
203
|
+
echo -e "${DIM}Use --resolve-conflicts to interactively resolve conflicts${NC}"
|
|
204
|
+
fi
|
|
205
|
+
else
|
|
206
|
+
echo -e " ${GREEN}✓${NC} No conflicts found"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
echo ""
|
|
210
|
+
|
|
211
|
+
# Dry run stops here
|
|
212
|
+
if [ "$DRY_RUN" = true ]; then
|
|
213
|
+
echo -e "${YELLOW}Dry run - patterns found:${NC}"
|
|
214
|
+
echo ""
|
|
215
|
+
safe_read_json "$PATTERNS_FILE" "
|
|
216
|
+
for (const category in p.patterns) {
|
|
217
|
+
console.log(' ' + category + ':');
|
|
218
|
+
for (const pattern of p.patterns[category] || []) {
|
|
219
|
+
console.log(' - ' + pattern.name + ' (' + pattern.frequency + ' occurrences)');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
" || true
|
|
223
|
+
rm -rf "$TEMP_DIR"
|
|
224
|
+
exit 0
|
|
225
|
+
fi
|
|
226
|
+
|
|
227
|
+
# Confirm import
|
|
228
|
+
if [ "$FORCE" != true ]; then
|
|
229
|
+
echo -e "${CYAN}Will import $PATTERN_COUNT patterns to:${NC}"
|
|
230
|
+
echo -e " → .workflow/state/decisions.md"
|
|
231
|
+
echo -e " → .claude/rules/ (auto-generated)"
|
|
232
|
+
echo ""
|
|
233
|
+
read -p "Import patterns? (y/N) " confirm
|
|
234
|
+
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
|
235
|
+
echo "Cancelled"
|
|
236
|
+
rm -rf "$TEMP_DIR"
|
|
237
|
+
exit 0
|
|
238
|
+
fi
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# Create backup if requested
|
|
242
|
+
if [ "$BACKUP" = true ]; then
|
|
243
|
+
BACKUP_DIR="wogi-backup-$(date +%Y%m%d-%H%M%S)"
|
|
244
|
+
mkdir -p "$BACKUP_DIR"
|
|
245
|
+
[ -f "$WORKFLOW_DIR/state/decisions.md" ] && cp "$WORKFLOW_DIR/state/decisions.md" "$BACKUP_DIR/"
|
|
246
|
+
[ -d ".claude/rules" ] && cp -r ".claude/rules" "$BACKUP_DIR/"
|
|
247
|
+
echo -e "${GREEN}✓${NC} Backup created: $BACKUP_DIR"
|
|
248
|
+
echo ""
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
echo -e "${CYAN}Importing patterns...${NC}"
|
|
252
|
+
echo ""
|
|
253
|
+
|
|
254
|
+
# Generate decisions.md content from patterns (using safe JSON parsing)
|
|
255
|
+
mkdir -p "$WORKFLOW_DIR/state"
|
|
256
|
+
|
|
257
|
+
DECISIONS_CONTENT=$(safe_read_json "$PATTERNS_FILE" "
|
|
258
|
+
const lines = [];
|
|
259
|
+
|
|
260
|
+
lines.push('## Imported Patterns');
|
|
261
|
+
lines.push('Source: $SCAN_PATH');
|
|
262
|
+
lines.push('Imported: ' + new Date().toISOString().split('T')[0]);
|
|
263
|
+
lines.push('');
|
|
264
|
+
|
|
265
|
+
for (const category in p.patterns) {
|
|
266
|
+
lines.push('### ' + category.charAt(0).toUpperCase() + category.slice(1) + ' Patterns');
|
|
267
|
+
lines.push('');
|
|
268
|
+
|
|
269
|
+
for (const pattern of (p.patterns[category] || []).slice(0, 10)) {
|
|
270
|
+
lines.push('**' + pattern.subcategory + '**: ' + pattern.name);
|
|
271
|
+
if (pattern.examples && pattern.examples.length > 0) {
|
|
272
|
+
lines.push(' Examples: \\\\\\\`' + pattern.examples.slice(0, 3).join('\\\\\\\`, \\\\\\\`') + '\\\\\\\`');
|
|
273
|
+
}
|
|
274
|
+
lines.push('');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log(lines.join('\\n'));
|
|
279
|
+
")
|
|
280
|
+
|
|
281
|
+
if [ -f "$WORKFLOW_DIR/state/decisions.md" ]; then
|
|
282
|
+
echo "" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
283
|
+
echo "---" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
284
|
+
echo "" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
285
|
+
echo "$DECISIONS_CONTENT" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
286
|
+
echo -e " ${GREEN}✓${NC} decisions.md ${DIM}(appended)${NC}"
|
|
287
|
+
else
|
|
288
|
+
echo "# Project Decisions" > "$WORKFLOW_DIR/state/decisions.md"
|
|
289
|
+
echo "" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
290
|
+
echo "$DECISIONS_CONTENT" >> "$WORKFLOW_DIR/state/decisions.md"
|
|
291
|
+
echo -e " ${GREEN}✓${NC} decisions.md ${DIM}(created)${NC}"
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
# Generate rules from patterns
|
|
295
|
+
if [ -f "$SCRIPT_DIR/flow-rules-sync.js" ]; then
|
|
296
|
+
echo -e " ${DIM}Syncing rules from patterns...${NC}"
|
|
297
|
+
node "$SCRIPT_DIR/flow-rules-sync.js" 2>/dev/null || true
|
|
298
|
+
echo -e " ${GREEN}✓${NC} .claude/rules/"
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# Save extracted patterns for future reference
|
|
302
|
+
mkdir -p "$WORKFLOW_DIR/extracted"
|
|
303
|
+
cp "$PATTERNS_FILE" "$WORKFLOW_DIR/extracted/imported-patterns.json"
|
|
304
|
+
|
|
305
|
+
if [ -n "$RESOLVED_FILE" ] && [ -f "$RESOLVED_FILE" ]; then
|
|
306
|
+
cp "$RESOLVED_FILE" "$WORKFLOW_DIR/extracted/imported-resolutions.json"
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# Cleanup
|
|
310
|
+
rm -rf "$TEMP_DIR"
|
|
311
|
+
|
|
312
|
+
echo ""
|
|
313
|
+
echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
|
|
314
|
+
echo -e "${GREEN}║ ✓ Patterns imported successfully! ║${NC}"
|
|
315
|
+
echo -e "${GREEN}╚══════════════════════════════════════════════════════════╝${NC}"
|
|
316
|
+
echo ""
|
|
317
|
+
echo -e "${DIM}Imported $PATTERN_COUNT patterns from $SCAN_PATH${NC}"
|
|
318
|
+
echo -e "${DIM}Restart Claude CLI to apply changes.${NC}"
|
|
319
|
+
|
|
320
|
+
exit 0
|
|
321
|
+
fi
|
|
322
|
+
|
|
323
|
+
# ==========================================
|
|
324
|
+
# PROFILE MODE - Import from zip file
|
|
325
|
+
# ==========================================
|
|
326
|
+
|
|
82
327
|
# Check file exists
|
|
328
|
+
if [ -z "$PROFILE_FILE" ]; then
|
|
329
|
+
echo -e "${RED}Error: No profile file specified${NC}"
|
|
330
|
+
echo -e "${DIM}Use --help for usage information${NC}"
|
|
331
|
+
exit 1
|
|
332
|
+
fi
|
|
333
|
+
|
|
83
334
|
if [ ! -f "$PROFILE_FILE" ]; then
|
|
84
335
|
echo -e "${RED}Error: Profile file not found: $PROFILE_FILE${NC}"
|
|
85
336
|
exit 1
|
|
@@ -28,6 +28,81 @@ try {
|
|
|
28
28
|
// LSP module not available, will use fallback
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Model Context Preferences (from registry.json)
|
|
33
|
+
// ============================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Cache for model registry
|
|
37
|
+
*/
|
|
38
|
+
let registryCache = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load model registry from .workflow/models/registry.json
|
|
42
|
+
* @returns {Object|null} Registry data or null if not found
|
|
43
|
+
*/
|
|
44
|
+
function loadModelRegistry() {
|
|
45
|
+
if (registryCache) return registryCache;
|
|
46
|
+
|
|
47
|
+
const { PATHS } = require('./flow-utils');
|
|
48
|
+
const registryPath = path.join(path.dirname(PATHS.config), 'models', 'registry.json');
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
if (fs.existsSync(registryPath)) {
|
|
52
|
+
const content = fs.readFileSync(registryPath, 'utf-8');
|
|
53
|
+
registryCache = JSON.parse(content);
|
|
54
|
+
return registryCache;
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
// Ignore errors, use defaults
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get model context preferences from registry
|
|
64
|
+
* @param {string} modelName - Model name (e.g., "claude-opus-4-5", "opus", "claude-sonnet-4")
|
|
65
|
+
* @returns {Object} Context preferences with defaults
|
|
66
|
+
*/
|
|
67
|
+
function getModelContextPreferences(modelName) {
|
|
68
|
+
const defaults = {
|
|
69
|
+
density: 'standard',
|
|
70
|
+
explicitExamples: true,
|
|
71
|
+
patternHints: true,
|
|
72
|
+
minContextForQuality: 0.5
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (!modelName) return defaults;
|
|
76
|
+
|
|
77
|
+
const registry = loadModelRegistry();
|
|
78
|
+
if (!registry?.models) return defaults;
|
|
79
|
+
|
|
80
|
+
// Normalize model name for lookup
|
|
81
|
+
const normalized = modelName.toLowerCase();
|
|
82
|
+
|
|
83
|
+
// Direct lookup first
|
|
84
|
+
if (registry.models[modelName]?.contextPreferences) {
|
|
85
|
+
return { ...defaults, ...registry.models[modelName].contextPreferences };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Try partial matching (e.g., "opus" matches "claude-opus-4-5")
|
|
89
|
+
for (const [key, model] of Object.entries(registry.models)) {
|
|
90
|
+
if (normalized.includes(key) || key.includes(normalized)) {
|
|
91
|
+
if (model.contextPreferences) {
|
|
92
|
+
return { ...defaults, ...model.contextPreferences };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Also try matching against modelId
|
|
96
|
+
if (model.modelId && (normalized.includes(model.modelId) || model.modelId.includes(normalized))) {
|
|
97
|
+
if (model.contextPreferences) {
|
|
98
|
+
return { ...defaults, ...model.contextPreferences };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return defaults;
|
|
104
|
+
}
|
|
105
|
+
|
|
31
106
|
// ============================================================
|
|
32
107
|
// Instruction Richness Levels (Guidance, NOT Limits)
|
|
33
108
|
// ============================================================
|
|
@@ -104,18 +179,52 @@ const COMPLEXITY_TO_RICHNESS = {
|
|
|
104
179
|
/**
|
|
105
180
|
* Gets the instruction richness configuration for a complexity level
|
|
106
181
|
*
|
|
182
|
+
* Model-aware: Different models need different amounts of context:
|
|
183
|
+
* - Opus (concise): Can use minimal even for medium tasks
|
|
184
|
+
* - Sonnet (standard): Use default mapping
|
|
185
|
+
* - Haiku/Local (comprehensive): Needs rich even for small tasks
|
|
186
|
+
*
|
|
107
187
|
* @param {string} complexityLevel - 'small', 'medium', 'large', or 'xl'
|
|
108
188
|
* @param {Object} config - Optional config overrides from config.json
|
|
189
|
+
* @param {string} model - Optional model name for model-aware richness
|
|
109
190
|
* @returns {Object} - Richness configuration
|
|
110
191
|
*/
|
|
111
|
-
function getInstructionRichness(complexityLevel, config = {}) {
|
|
112
|
-
// Map complexity to richness level
|
|
192
|
+
function getInstructionRichness(complexityLevel, config = {}, model = null) {
|
|
193
|
+
// Map complexity to base richness level
|
|
113
194
|
let richnessLevel = COMPLEXITY_TO_RICHNESS[complexityLevel] || 'standard';
|
|
195
|
+
const levels = ['minimal', 'standard', 'rich', 'maximum'];
|
|
196
|
+
|
|
197
|
+
// Model-aware adjustment
|
|
198
|
+
if (model) {
|
|
199
|
+
const modelPrefs = getModelContextPreferences(model);
|
|
200
|
+
|
|
201
|
+
if (modelPrefs.density === 'concise') {
|
|
202
|
+
// Opus: Can use less context - downgrade one level (but not below minimal)
|
|
203
|
+
// small → minimal (already minimal)
|
|
204
|
+
// medium → minimal (was standard)
|
|
205
|
+
// large → standard (was rich)
|
|
206
|
+
// xl → rich (was maximum)
|
|
207
|
+
const currentIndex = levels.indexOf(richnessLevel);
|
|
208
|
+
if (currentIndex > 0) {
|
|
209
|
+
richnessLevel = levels[currentIndex - 1];
|
|
210
|
+
}
|
|
211
|
+
} else if (modelPrefs.density === 'comprehensive') {
|
|
212
|
+
// Local LLM / Haiku: Needs more context - upgrade one level (but not above maximum)
|
|
213
|
+
// small → standard (was minimal)
|
|
214
|
+
// medium → rich (was standard)
|
|
215
|
+
// large → maximum (was rich)
|
|
216
|
+
// xl → maximum (already maximum)
|
|
217
|
+
const currentIndex = levels.indexOf(richnessLevel);
|
|
218
|
+
if (currentIndex < levels.length - 1) {
|
|
219
|
+
richnessLevel = levels[currentIndex + 1];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// 'standard' density uses default mapping
|
|
223
|
+
}
|
|
114
224
|
|
|
115
225
|
// Check for minimum richness override in config
|
|
116
226
|
const minRichness = config.minRichness;
|
|
117
227
|
if (minRichness) {
|
|
118
|
-
const levels = ['minimal', 'standard', 'rich', 'maximum'];
|
|
119
228
|
const currentIndex = levels.indexOf(richnessLevel);
|
|
120
229
|
const minIndex = levels.indexOf(minRichness);
|
|
121
230
|
if (minIndex > currentIndex) {
|
|
@@ -125,8 +234,13 @@ function getInstructionRichness(complexityLevel, config = {}) {
|
|
|
125
234
|
|
|
126
235
|
const richness = { ...INSTRUCTION_RICHNESS[richnessLevel] };
|
|
127
236
|
|
|
128
|
-
// Add level name for reference
|
|
237
|
+
// Add level name and model info for reference
|
|
129
238
|
richness.level = richnessLevel;
|
|
239
|
+
if (model) {
|
|
240
|
+
const modelPrefs = getModelContextPreferences(model);
|
|
241
|
+
richness.modelDensity = modelPrefs.density;
|
|
242
|
+
richness.patternHintsOnly = modelPrefs.patternHints && !modelPrefs.explicitExamples;
|
|
243
|
+
}
|
|
130
244
|
|
|
131
245
|
return richness;
|
|
132
246
|
}
|
|
@@ -145,17 +259,26 @@ function loadProjectContext(projectRoot) {
|
|
|
145
259
|
let context = '';
|
|
146
260
|
|
|
147
261
|
// Try hybrid-context first (optimized for hybrid mode)
|
|
262
|
+
// Use try-catch even after existsSync (race conditions, permissions)
|
|
148
263
|
if (fs.existsSync(contextPath)) {
|
|
149
|
-
|
|
264
|
+
try {
|
|
265
|
+
context += fs.readFileSync(contextPath, 'utf-8');
|
|
266
|
+
} catch (err) {
|
|
267
|
+
// File may have been deleted/modified between check and read
|
|
268
|
+
}
|
|
150
269
|
}
|
|
151
270
|
|
|
152
271
|
// Add project overview
|
|
153
272
|
if (fs.existsSync(projectPath)) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
273
|
+
try {
|
|
274
|
+
const projectMd = fs.readFileSync(projectPath, 'utf-8');
|
|
275
|
+
// Extract just the summary section
|
|
276
|
+
const summaryMatch = projectMd.match(/## Summary[\s\S]*?(?=##|$)/);
|
|
277
|
+
if (summaryMatch) {
|
|
278
|
+
context += '\n\n### Project Summary\n' + summaryMatch[0];
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
// File may have been deleted/modified between check and read
|
|
159
282
|
}
|
|
160
283
|
}
|
|
161
284
|
|
|
@@ -173,7 +296,13 @@ function loadPatterns(projectRoot) {
|
|
|
173
296
|
return null;
|
|
174
297
|
}
|
|
175
298
|
|
|
176
|
-
|
|
299
|
+
let content;
|
|
300
|
+
try {
|
|
301
|
+
content = fs.readFileSync(decisionsPath, 'utf-8');
|
|
302
|
+
} catch (err) {
|
|
303
|
+
// File may have been deleted/modified between check and read
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
177
306
|
|
|
178
307
|
// Extract ALL ## sections from decisions.md
|
|
179
308
|
// This includes: Naming Conventions, File Structure, Error Handling, etc.
|
|
@@ -310,7 +439,13 @@ async function loadRelevantTypesWithLSP(projectRoot, filePath, options = {}) {
|
|
|
310
439
|
// If we have a target file, extract identifiers and get their types
|
|
311
440
|
const absPath = path.isAbsolute(filePath) ? filePath : path.join(projectRoot, filePath);
|
|
312
441
|
if (fs.existsSync(absPath)) {
|
|
313
|
-
|
|
442
|
+
let content;
|
|
443
|
+
try {
|
|
444
|
+
content = fs.readFileSync(absPath, 'utf-8');
|
|
445
|
+
} catch (err) {
|
|
446
|
+
// File may have been deleted/modified between check and read
|
|
447
|
+
return loadRelevantTypes(projectRoot, filePath, options);
|
|
448
|
+
}
|
|
314
449
|
const identifiers = extractIdentifiersForLSP(content, keywords);
|
|
315
450
|
|
|
316
451
|
// Get types for identified positions
|
|
@@ -759,6 +894,9 @@ module.exports = {
|
|
|
759
894
|
COMPLEXITY_TO_RICHNESS,
|
|
760
895
|
getInstructionRichness,
|
|
761
896
|
getVerbosityGuidance,
|
|
897
|
+
// Model-aware context
|
|
898
|
+
getModelContextPreferences,
|
|
899
|
+
loadModelRegistry,
|
|
762
900
|
// Context loaders
|
|
763
901
|
loadProjectContext,
|
|
764
902
|
loadPatterns,
|
|
@@ -781,26 +919,41 @@ if (require.main === module) {
|
|
|
781
919
|
|
|
782
920
|
if (args.length === 0) {
|
|
783
921
|
console.log(`
|
|
784
|
-
Usage: node flow-instruction-richness.js <complexity-level>
|
|
922
|
+
Usage: node flow-instruction-richness.js <complexity-level> [model]
|
|
785
923
|
|
|
786
924
|
Complexity levels: small, medium, large, xl
|
|
925
|
+
Model (optional): claude-opus-4-5, claude-sonnet-4, claude-haiku-3-5, etc.
|
|
926
|
+
|
|
927
|
+
Model-Aware Context Density:
|
|
928
|
+
- opus (concise): Uses less context, can infer from hints
|
|
929
|
+
- sonnet (standard): Default context levels
|
|
930
|
+
- haiku/local (comprehensive): Uses more context, needs explicit examples
|
|
787
931
|
|
|
788
932
|
Examples:
|
|
789
933
|
node flow-instruction-richness.js small
|
|
790
|
-
node flow-instruction-richness.js
|
|
934
|
+
node flow-instruction-richness.js medium claude-opus-4-5
|
|
935
|
+
node flow-instruction-richness.js medium claude-haiku-3-5
|
|
791
936
|
`);
|
|
792
937
|
process.exit(0);
|
|
793
938
|
}
|
|
794
939
|
|
|
795
940
|
const level = args[0];
|
|
796
|
-
const
|
|
941
|
+
const model = args[1] || null;
|
|
942
|
+
const richness = getInstructionRichness(level, {}, model);
|
|
797
943
|
|
|
798
944
|
console.log('\n═══════════════════════════════════════════════════════════');
|
|
799
|
-
console.log(' INSTRUCTION RICHNESS CONFIG (
|
|
945
|
+
console.log(' INSTRUCTION RICHNESS CONFIG (Model-Aware)');
|
|
800
946
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
801
947
|
|
|
802
948
|
console.log(`Complexity Level: ${level.toUpperCase()}`);
|
|
803
949
|
console.log(`Richness Level: ${richness.level.toUpperCase()}`);
|
|
950
|
+
if (model) {
|
|
951
|
+
console.log(`Model: ${model}`);
|
|
952
|
+
console.log(`Model Density: ${richness.modelDensity || 'standard'}`);
|
|
953
|
+
if (richness.patternHintsOnly) {
|
|
954
|
+
console.log(`Pattern Mode: Hints only (no explicit examples needed)`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
804
957
|
console.log(`\n${richness.description}\n`);
|
|
805
958
|
|
|
806
959
|
console.log('───────────────────────────────────────────────────────────');
|
|
@@ -821,7 +974,18 @@ Examples:
|
|
|
821
974
|
console.log('───────────────────────────────────────────────────────────');
|
|
822
975
|
console.log(getVerbosityGuidance(richness.templateVerbosity));
|
|
823
976
|
|
|
824
|
-
|
|
825
|
-
|
|
977
|
+
if (model) {
|
|
978
|
+
const prefs = getModelContextPreferences(model);
|
|
979
|
+
console.log('\n───────────────────────────────────────────────────────────');
|
|
980
|
+
console.log(' MODEL PREFERENCES');
|
|
981
|
+
console.log('───────────────────────────────────────────────────────────');
|
|
982
|
+
console.log(` Density: ${prefs.density}`);
|
|
983
|
+
console.log(` Explicit Examples: ${prefs.explicitExamples ? 'Yes' : 'No'}`);
|
|
984
|
+
console.log(` Pattern Hints: ${prefs.patternHints ? 'Yes' : 'No'}`);
|
|
985
|
+
console.log(` Min Context for Quality: ${(prefs.minContextForQuality * 100).toFixed(0)}%`);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
console.log('\n💡 Model-aware context: Each model gets the right amount of');
|
|
989
|
+
console.log(' context for optimal quality without waste.\n');
|
|
826
990
|
console.log('═══════════════════════════════════════════════════════════\n');
|
|
827
991
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Wogi Flow - Knowledge Router
|
|
5
5
|
*
|
|
6
|
+
* See MEMORY-ARCHITECTURE.md for how this fits with other memory/knowledge modules.
|
|
7
|
+
*
|
|
6
8
|
* Auto-detects where learnings/corrections should be stored:
|
|
7
9
|
* - model-specific: Applies only to a specific LLM
|
|
8
10
|
* - skill: Related to a specific skill (nestjs, react, etc.)
|
|
@@ -35,9 +35,11 @@ function loadActiveDigest() { requireInit(); return digestCore.loadActiveDigest(
|
|
|
35
35
|
function saveActiveDigest(d) { requireInit(); return digestCore.saveActiveDigest(d); }
|
|
36
36
|
function countWords(t) { requireInit(); return digestCore.countWords(t); }
|
|
37
37
|
function now() { requireInit(); return digestCore.now(); }
|
|
38
|
+
function measureInputMetrics(t) { requireInit(); return digestCore.measureInputMetrics(t); }
|
|
38
39
|
|
|
39
|
-
// Paths
|
|
40
|
-
const
|
|
40
|
+
// Paths - temp processing files, cleaned up after completion
|
|
41
|
+
const TMP_DIR = path.join(process.cwd(), '.workflow', 'tmp', 'long-input');
|
|
42
|
+
const STATE_DIR = TMP_DIR; // Alias for backward compatibility
|
|
41
43
|
|
|
42
44
|
// ==========================================================================
|
|
43
45
|
// E5-S3: Durable Digest Session Persistence
|
|
@@ -34,6 +34,41 @@ const SRT_TIMESTAMP = /(\d{2}):(\d{2}):(\d{2}),(\d{3})\s*-->\s*(\d{2}):(\d{2}):(
|
|
|
34
34
|
const SPEAKER_COLON_PATTERN = /^([A-Z][a-zA-Z\s]+):\s*/;
|
|
35
35
|
const SPEAKER_BRACKET_PATTERN = /^\[([^\]]+)\]\s*/;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Simple word counter utility
|
|
39
|
+
*/
|
|
40
|
+
function countWords(text) {
|
|
41
|
+
return text.split(/\s+/).filter(w => w.length > 0).length;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Detect VTT format
|
|
46
|
+
*/
|
|
47
|
+
function isVTTFormat(text) {
|
|
48
|
+
// Check for WEBVTT header
|
|
49
|
+
if (text.trim().startsWith('WEBVTT')) {
|
|
50
|
+
return { detected: true, confidence: 0.95 };
|
|
51
|
+
}
|
|
52
|
+
// Check for VTT timestamps
|
|
53
|
+
const timestamps = text.match(VTT_TIMESTAMP_FULL) || text.match(VTT_TIMESTAMP_SHORT);
|
|
54
|
+
if (timestamps) {
|
|
55
|
+
return { detected: true, confidence: 0.85 };
|
|
56
|
+
}
|
|
57
|
+
return { detected: false, confidence: 0 };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Detect SRT format
|
|
62
|
+
*/
|
|
63
|
+
function isSRTFormat(text) {
|
|
64
|
+
const timestamps = text.match(SRT_TIMESTAMP);
|
|
65
|
+
const cueNumbers = text.match(/^\d+\s*$/m);
|
|
66
|
+
if (timestamps && cueNumbers) {
|
|
67
|
+
return { detected: true, confidence: 0.9 };
|
|
68
|
+
}
|
|
69
|
+
return { detected: false, confidence: 0 };
|
|
70
|
+
}
|
|
71
|
+
|
|
37
72
|
/**
|
|
38
73
|
* Convert timestamp to milliseconds
|
|
39
74
|
*/
|