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.
Files changed (45) hide show
  1. package/.workflow/specs/architecture.md.template +24 -0
  2. package/.workflow/specs/stack.md.template +33 -0
  3. package/.workflow/specs/testing.md.template +36 -0
  4. package/README.md +90 -1
  5. package/package.json +1 -1
  6. package/scripts/MEMORY-ARCHITECTURE.md +150 -0
  7. package/scripts/flow +20 -19
  8. package/scripts/flow-auto-context.js +97 -3
  9. package/scripts/flow-conflict-resolver.js +735 -0
  10. package/scripts/flow-context-gatherer.js +520 -0
  11. package/scripts/flow-context-monitor.js +148 -19
  12. package/scripts/flow-damage-control.js +5 -1
  13. package/scripts/flow-export-profile +168 -1
  14. package/scripts/flow-import-profile +257 -6
  15. package/scripts/flow-instruction-richness.js +182 -18
  16. package/scripts/flow-knowledge-router.js +2 -0
  17. package/scripts/flow-knowledge-sync.js +2 -0
  18. package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
  19. package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
  20. package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
  21. package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
  22. package/scripts/flow-memory-db.js +386 -1
  23. package/scripts/flow-memory-sync.js +2 -0
  24. package/scripts/flow-model-adapter.js +53 -29
  25. package/scripts/flow-model-router.js +246 -1
  26. package/scripts/flow-morning.js +94 -0
  27. package/scripts/flow-onboard +223 -10
  28. package/scripts/flow-orchestrate-validation.js +539 -0
  29. package/scripts/flow-orchestrate.js +16 -507
  30. package/scripts/flow-pattern-extractor.js +1265 -0
  31. package/scripts/flow-prompt-composer.js +222 -2
  32. package/scripts/flow-quality-guard.js +594 -0
  33. package/scripts/flow-section-index.js +713 -0
  34. package/scripts/flow-section-resolver.js +484 -0
  35. package/scripts/flow-session-end.js +188 -2
  36. package/scripts/flow-skill-create.js +19 -3
  37. package/scripts/flow-skill-matcher.js +122 -7
  38. package/scripts/flow-statusline-setup.js +218 -0
  39. package/scripts/flow-step-review.js +19 -0
  40. package/scripts/flow-tech-debt.js +734 -0
  41. package/scripts/flow-utils.js +2 -0
  42. package/scripts/hooks/core/long-input-gate.js +293 -0
  43. package/scripts/flow-parallel-detector.js +0 -399
  44. package/scripts/flow-parallel-dispatch.js +0 -987
  45. /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 team-frontend.zip --skip-learnings"
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
- PROFILE_FILE="$1"
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
- context += fs.readFileSync(contextPath, 'utf-8');
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
- const projectMd = fs.readFileSync(projectPath, 'utf-8');
155
- // Extract just the summary section
156
- const summaryMatch = projectMd.match(/## Summary[\s\S]*?(?=##|$)/);
157
- if (summaryMatch) {
158
- context += '\n\n### Project Summary\n' + summaryMatch[0];
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
- const content = fs.readFileSync(decisionsPath, 'utf-8');
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
- const content = fs.readFileSync(absPath, 'utf-8');
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 large
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 richness = getInstructionRichness(level);
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 (Local LLM is FREE!)');
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
- console.log('\n💡 Remember: Local LLM tokens are FREE! Include MORE context');
825
- console.log(' when in doubt. Failed executions cost more than extra context.\n');
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.)
@@ -3,6 +3,8 @@
3
3
  /**
4
4
  * Wogi Flow - Knowledge Sync
5
5
  *
6
+ * See MEMORY-ARCHITECTURE.md for how this fits with other memory/knowledge modules.
7
+ *
6
8
  * Detects drift in knowledge files (stack.md, architecture.md, testing.md)
7
9
  * by tracking hashes of project indicator files.
8
10
  *
@@ -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 STATE_DIR = path.join(process.cwd(), '.workflow', 'state', 'digests');
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
  */