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
@@ -22,7 +22,8 @@ const {
22
22
  success,
23
23
  readFile,
24
24
  fileExists,
25
- printHeader
25
+ printHeader,
26
+ safeJsonParse
26
27
  } = require('./flow-utils');
27
28
 
28
29
  // ============================================================
@@ -134,9 +135,71 @@ const DEFAULTS = {
134
135
  criticalAt: 0.85, // 85% - critical threshold
135
136
  contextWindow: 200000, // Claude's context window
136
137
  checkOnSessionStart: true,
137
- checkAfterTask: true
138
+ checkAfterTask: true,
139
+ // Tracking method: 'estimated' (default), 'native', or 'auto'
140
+ // - 'estimated': Uses token estimation from state files
141
+ // - 'native': Uses Claude Code's native tracking (v1.0.52+)
142
+ // - 'auto': Uses native if available, falls back to estimated
143
+ trackingMethod: 'auto'
138
144
  };
139
145
 
146
+ // Path to native context info (written by Claude Code hooks if configured)
147
+ const NATIVE_CONTEXT_FILE = path.join(STATE_DIR, 'context-info.json');
148
+
149
+ /**
150
+ * Try to read native context info from Claude Code
151
+ * Returns null if not available
152
+ */
153
+ function getNativeContextInfo() {
154
+ if (!fs.existsSync(NATIVE_CONTEXT_FILE)) {
155
+ return null;
156
+ }
157
+
158
+ // Use safeJsonParse for prototype pollution protection
159
+ const data = safeJsonParse(NATIVE_CONTEXT_FILE, null);
160
+ if (!data) {
161
+ return null;
162
+ }
163
+
164
+ // Check if data is recent (within last 5 minutes)
165
+ if (data.timestamp) {
166
+ const age = Date.now() - new Date(data.timestamp).getTime();
167
+ if (age > 5 * 60 * 1000) {
168
+ return null; // Data too old
169
+ }
170
+ }
171
+
172
+ // Validate required fields
173
+ if (typeof data.usedPercentage === 'number') {
174
+ return {
175
+ usedPercentage: data.usedPercentage,
176
+ remainingPercentage: data.remainingPercentage || (100 - data.usedPercentage),
177
+ timestamp: data.timestamp,
178
+ source: 'native'
179
+ };
180
+ }
181
+
182
+ return null;
183
+ }
184
+
185
+ /**
186
+ * Write native context info (called from hooks)
187
+ */
188
+ function writeNativeContextInfo(usedPercentage, remainingPercentage) {
189
+ try {
190
+ const data = {
191
+ usedPercentage,
192
+ remainingPercentage,
193
+ timestamp: new Date().toISOString(),
194
+ source: 'claude-code'
195
+ };
196
+ fs.writeFileSync(NATIVE_CONTEXT_FILE, JSON.stringify(data, null, 2));
197
+ return true;
198
+ } catch (err) {
199
+ return false;
200
+ }
201
+ }
202
+
140
203
  /**
141
204
  * Get context monitor configuration
142
205
  */
@@ -150,6 +213,7 @@ function getContextMonitorConfig() {
150
213
 
151
214
  /**
152
215
  * Check context health and return status
216
+ * Supports both native Claude Code tracking and estimated tracking
153
217
  */
154
218
  function checkContextHealth() {
155
219
  const config = getContextMonitorConfig();
@@ -160,21 +224,57 @@ function checkContextHealth() {
160
224
  currentTokens: 0,
161
225
  contextWindow: config.contextWindow,
162
226
  usage: 0,
163
- recommendation: null
227
+ recommendation: null,
228
+ trackingMethod: 'disabled'
164
229
  };
165
230
  }
166
231
 
167
- const { breakdown, total } = getContextBreakdown();
168
- const usage = total / config.contextWindow;
232
+ // Determine tracking method
233
+ const trackingMethod = config.trackingMethod || 'auto';
234
+ let usage, total, breakdown, trackingSource;
235
+
236
+ // Try native tracking first (if configured)
237
+ if (trackingMethod === 'native' || trackingMethod === 'auto') {
238
+ const nativeInfo = getNativeContextInfo();
239
+ if (nativeInfo) {
240
+ usage = nativeInfo.usedPercentage / 100;
241
+ total = Math.round(usage * config.contextWindow);
242
+ breakdown = { 'native-tracking': total };
243
+ trackingSource = 'native';
244
+ }
245
+ }
246
+
247
+ // Fall back to estimated tracking
248
+ if (!trackingSource && (trackingMethod === 'estimated' || trackingMethod === 'auto')) {
249
+ const contextData = getContextBreakdown();
250
+ breakdown = contextData.breakdown;
251
+ total = contextData.total;
252
+ usage = total / config.contextWindow;
253
+ trackingSource = 'estimated';
254
+ }
255
+
256
+ // If native was required but not available
257
+ if (!trackingSource && trackingMethod === 'native') {
258
+ return {
259
+ status: 'unavailable',
260
+ currentTokens: 0,
261
+ contextWindow: config.contextWindow,
262
+ usage: 0,
263
+ usagePercent: 0,
264
+ recommendation: 'Native tracking not available. Run status line setup or switch to "estimated" mode.',
265
+ trackingMethod: 'native',
266
+ trackingSource: null
267
+ };
268
+ }
169
269
 
170
270
  let status, recommendation;
171
271
 
172
272
  if (usage >= config.criticalAt) {
173
273
  status = 'critical';
174
- recommendation = 'Run /compact NOW to avoid context overflow';
274
+ recommendation = 'Run /wogi-compact NOW to avoid context overflow';
175
275
  } else if (usage >= config.warnAt) {
176
276
  status = 'warning';
177
- recommendation = 'Consider running /compact soon';
277
+ recommendation = 'Consider running /wogi-compact soon';
178
278
  } else {
179
279
  status = 'healthy';
180
280
  recommendation = null;
@@ -191,7 +291,9 @@ function checkContextHealth() {
191
291
  thresholds: {
192
292
  warn: config.warnAt,
193
293
  critical: config.criticalAt
194
- }
294
+ },
295
+ trackingMethod,
296
+ trackingSource
195
297
  };
196
298
  }
197
299
 
@@ -241,12 +343,29 @@ function showContextBreakdown() {
241
343
  healthy: 'green',
242
344
  warning: 'yellow',
243
345
  critical: 'red',
244
- disabled: 'dim'
346
+ disabled: 'dim',
347
+ unavailable: 'yellow'
245
348
  };
246
349
  const statusColor = statusColors[health.status] || 'white';
247
350
  console.log(`Status: ${color(statusColor, health.status.toUpperCase())}`);
351
+
352
+ // Show tracking method
353
+ if (health.trackingSource) {
354
+ const sourceLabel = health.trackingSource === 'native' ? 'Claude Code Native' : 'Estimated';
355
+ console.log(`Tracking: ${color('dim', sourceLabel)}`);
356
+ }
248
357
  console.log('');
249
358
 
359
+ // Handle unavailable status
360
+ if (health.status === 'unavailable') {
361
+ console.log(color('yellow', 'Native tracking is configured but not available.'));
362
+ console.log(color('dim', 'Options:'));
363
+ console.log(color('dim', ' 1. Run /wogi-statusline-setup to configure status line'));
364
+ console.log(color('dim', ' 2. Set trackingMethod: "estimated" in config.json'));
365
+ console.log(color('dim', ' 3. Set trackingMethod: "auto" to auto-fallback'));
366
+ return;
367
+ }
368
+
250
369
  // Progress bar
251
370
  const barWidth = 40;
252
371
  const filled = Math.min(Math.round(health.usage * barWidth), barWidth);
@@ -255,18 +374,23 @@ function showContextBreakdown() {
255
374
  console.log(`${health.currentTokens.toLocaleString()} / ${health.contextWindow.toLocaleString()} tokens`);
256
375
  console.log('');
257
376
 
258
- // Breakdown
259
- console.log(color('cyan', 'Breakdown:'));
260
- const sortedBreakdown = Object.entries(health.breakdown)
261
- .sort((a, b) => b[1] - a[1]);
377
+ // Breakdown (only show for estimated tracking)
378
+ if (health.trackingSource === 'estimated') {
379
+ console.log(color('cyan', 'Breakdown:'));
380
+ const sortedBreakdown = Object.entries(health.breakdown)
381
+ .sort((a, b) => b[1] - a[1]);
262
382
 
263
- for (const [file, tokens] of sortedBreakdown) {
264
- if (tokens > 0) {
265
- const percent = Math.round((tokens / health.currentTokens) * 100);
266
- console.log(` ${file.padEnd(25)} ${tokens.toLocaleString().padStart(8)} tokens (${percent}%)`);
383
+ for (const [file, tokens] of sortedBreakdown) {
384
+ if (tokens > 0) {
385
+ const percent = Math.round((tokens / health.currentTokens) * 100);
386
+ console.log(` ${file.padEnd(25)} ${tokens.toLocaleString().padStart(8)} tokens (${percent}%)`);
387
+ }
267
388
  }
389
+ console.log('');
390
+ } else if (health.trackingSource === 'native') {
391
+ console.log(color('dim', '(Native tracking - breakdown not available)'));
392
+ console.log('');
268
393
  }
269
- console.log('');
270
394
 
271
395
  // Thresholds
272
396
  console.log(color('dim', `Thresholds: warn=${Math.round(health.thresholds.warn * 100)}%, critical=${Math.round(health.thresholds.critical * 100)}%`));
@@ -374,11 +498,16 @@ module.exports = {
374
498
  checkContextHealth,
375
499
  getContextMonitorConfig,
376
500
 
501
+ // Native tracking
502
+ getNativeContextInfo,
503
+ writeNativeContextInfo,
504
+
377
505
  // Warnings
378
506
  warnIfContextHigh,
379
507
  showContextBreakdown,
380
508
  getStatusLine,
381
509
 
382
510
  // Constants
383
- DEFAULTS
511
+ DEFAULTS,
512
+ NATIVE_CONTEXT_FILE
384
513
  };
@@ -8,6 +8,10 @@
8
8
  *
9
9
  * Inspired by Hookify plugin patterns, adapted for multi-CLI compatibility.
10
10
  *
11
+ * LIMITATION: The 'prompt' event type's AI analysis hook is not yet implemented.
12
+ * Currently returns 'allow' for all prompts. See evaluatePromptWithAI() around line 734.
13
+ * TODO: Integrate with an AI API for prompt safety evaluation.
14
+ *
11
15
  * Usage:
12
16
  * flow damage-control check "<command>" Check if command is allowed
13
17
  * flow damage-control event <type> <ctx> Check event against rules
@@ -368,7 +372,7 @@ function loadPatterns() {
368
372
  parsed.rules = parsed.rules || [];
369
373
  return parsed;
370
374
  } catch (err) {
371
- console.error(console.error('Error loading damage-control.yaml:', err.message));
375
+ console.error('Error loading damage-control.yaml:', err.message);
372
376
  return {
373
377
  rules: [],
374
378
  blocked: [],
@@ -26,7 +26,13 @@ show_help() {
26
26
  echo " --rules Include .claude/rules/ and decisions.md"
27
27
  echo " --learnings Include feedback-patterns.md and skill learnings"
28
28
  echo " --templates Include sanitized project.md and roadmap templates"
29
- echo " --full Include everything (all categories)"
29
+ echo " --full Include everything (all categories + patterns)"
30
+ echo ""
31
+ echo "Pattern Extraction (NEW):"
32
+ echo " --extract-patterns Scan codebase and extract patterns"
33
+ echo " --resolve-conflicts Interactive conflict resolution (with --extract-patterns)"
34
+ echo " --analysis-mode MODE Analysis depth: balanced (default), deep"
35
+ echo " --include-examples Include code snippets as examples"
30
36
  echo ""
31
37
  echo "Legacy options:"
32
38
  echo " --include-decisions Include decisions.md only"
@@ -34,6 +40,7 @@ show_help() {
34
40
  echo ""
35
41
  echo "Examples:"
36
42
  echo " flow export-profile my-team --full"
43
+ echo " flow export-profile my-team --extract-patterns --resolve-conflicts"
37
44
  echo " flow export-profile my-team --rules --learnings"
38
45
  echo " flow export-profile my-team --include-decisions"
39
46
  }
@@ -46,12 +53,56 @@ fi
46
53
  PROFILE_NAME="$1"
47
54
  shift
48
55
 
56
+ # Validate profile name to prevent path traversal
57
+ if ! [[ "$PROFILE_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
58
+ echo -e "${RED}Error: Invalid profile name. Use only letters, numbers, underscores, and hyphens.${NC}"
59
+ exit 1
60
+ fi
61
+
62
+ # Safe JSON reading function (prevents code injection by using process.argv)
63
+ safe_read_json() {
64
+ local file="$1"
65
+ local query="$2"
66
+ node -e '
67
+ const fs = require("fs");
68
+ const file = process.argv[1];
69
+ const query = process.argv[2];
70
+ try {
71
+ const content = fs.readFileSync(file, "utf-8");
72
+ // Check for prototype pollution attempts
73
+ if (/__proto__|constructor\s*["'"'"'`:]|prototype\s*["'"'"'`:]/i.test(content)) {
74
+ console.error("Suspicious content detected");
75
+ process.exit(1);
76
+ }
77
+ const p = JSON.parse(content);
78
+ // Safe property access based on query type
79
+ if (query === "conflicts.length") {
80
+ console.log(p.conflicts ? p.conflicts.length : 0);
81
+ } else if (query === "conflicts.json") {
82
+ console.log(JSON.stringify(p.conflicts, null, 2));
83
+ } else if (query === "recommendations.json") {
84
+ console.log(JSON.stringify(p.recommendations, null, 2));
85
+ } else {
86
+ console.error("Unknown query: " + query);
87
+ process.exit(1);
88
+ }
89
+ } catch(e) {
90
+ console.error(e.message);
91
+ process.exit(1);
92
+ }
93
+ ' -- "$file" "$query" 2>/dev/null
94
+ }
95
+
49
96
  # Parse options
50
97
  INCLUDE_DECISIONS=false
51
98
  INCLUDE_APP_MAP=false
52
99
  INCLUDE_RULES=false
53
100
  INCLUDE_LEARNINGS=false
54
101
  INCLUDE_TEMPLATES=false
102
+ EXTRACT_PATTERNS=false
103
+ RESOLVE_CONFLICTS=false
104
+ ANALYSIS_MODE="balanced"
105
+ INCLUDE_EXAMPLES=false
55
106
 
56
107
  while [ $# -gt 0 ]; do
57
108
  case "$1" in
@@ -71,12 +122,26 @@ while [ $# -gt 0 ]; do
71
122
  --templates)
72
123
  INCLUDE_TEMPLATES=true
73
124
  ;;
125
+ --extract-patterns)
126
+ EXTRACT_PATTERNS=true
127
+ ;;
128
+ --resolve-conflicts)
129
+ RESOLVE_CONFLICTS=true
130
+ ;;
131
+ --analysis-mode)
132
+ shift
133
+ ANALYSIS_MODE="$1"
134
+ ;;
135
+ --include-examples)
136
+ INCLUDE_EXAMPLES=true
137
+ ;;
74
138
  --full)
75
139
  INCLUDE_DECISIONS=true
76
140
  INCLUDE_APP_MAP=true
77
141
  INCLUDE_RULES=true
78
142
  INCLUDE_LEARNINGS=true
79
143
  INCLUDE_TEMPLATES=true
144
+ EXTRACT_PATTERNS=true
80
145
  ;;
81
146
  *)
82
147
  echo -e "${RED}Unknown option: $1${NC}"
@@ -86,6 +151,9 @@ while [ $# -gt 0 ]; do
86
151
  shift
87
152
  done
88
153
 
154
+ # Determine scripts directory
155
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
156
+
89
157
  # Create profiles directory
90
158
  mkdir -p "$PROFILES_DIR"
91
159
 
@@ -278,6 +346,105 @@ ROADMAP_EOF
278
346
  fi
279
347
  fi
280
348
 
349
+ # ==========================================
350
+ # PATTERN EXTRACTION (NEW)
351
+ # ==========================================
352
+ if [ "$EXTRACT_PATTERNS" = true ]; then
353
+ echo ""
354
+ echo -e "${YELLOW}Pattern Extraction:${NC}"
355
+
356
+ mkdir -p "$PROFILE_DIR/.workflow/extracted"
357
+
358
+ # Build extractor options
359
+ EXTRACTOR_OPTS="--format json --with-conflicts --analysis-mode $ANALYSIS_MODE"
360
+
361
+ # Run pattern extraction
362
+ echo -e " ${DIM}Scanning codebase for patterns...${NC}"
363
+ PATTERNS_OUTPUT="$PROFILE_DIR/.workflow/extracted/patterns.json"
364
+
365
+ if node "$SCRIPT_DIR/flow-pattern-extractor.js" $EXTRACTOR_OPTS --output "$PATTERNS_OUTPUT" 2>/dev/null; then
366
+ echo -e " ${GREEN}✓${NC} patterns.json"
367
+ CONTENTS="$CONTENTS\n- .workflow/extracted/patterns.json (extracted patterns)"
368
+
369
+ # Check for conflicts (using safe JSON parsing instead of require())
370
+ CONFLICT_COUNT=$(safe_read_json "$PATTERNS_OUTPUT" "conflicts.length" || echo "0")
371
+
372
+ if [ "$CONFLICT_COUNT" -gt 0 ]; then
373
+ echo -e " ${YELLOW}!${NC} Found $CONFLICT_COUNT conflicts"
374
+
375
+ # Run conflict resolution if requested
376
+ if [ "$RESOLVE_CONFLICTS" = true ]; then
377
+ # Extract conflicts to temp file (using safe JSON parsing)
378
+ CONFLICTS_TEMP=$(mktemp)
379
+ safe_read_json "$PATTERNS_OUTPUT" "conflicts.json" > "$CONFLICTS_TEMP"
380
+
381
+ echo -e " ${DIM}Launching conflict resolver...${NC}"
382
+
383
+ RESOLVED_OUTPUT="$PROFILE_DIR/.workflow/extracted/conflicts-resolved.json"
384
+ if node "$SCRIPT_DIR/flow-conflict-resolver.js" --input "$CONFLICTS_TEMP" --output "$RESOLVED_OUTPUT"; then
385
+ echo -e " ${GREEN}✓${NC} conflicts-resolved.json"
386
+ CONTENTS="$CONTENTS\n- .workflow/extracted/conflicts-resolved.json (user resolutions)"
387
+ else
388
+ echo -e " ${YELLOW}!${NC} Conflict resolution skipped or cancelled"
389
+ fi
390
+
391
+ rm -f "$CONFLICTS_TEMP"
392
+ fi
393
+ else
394
+ echo -e " ${GREEN}✓${NC} No conflicting patterns found"
395
+ fi
396
+
397
+ # Extract examples if requested
398
+ if [ "$INCLUDE_EXAMPLES" = true ]; then
399
+ mkdir -p "$PROFILE_DIR/.workflow/extracted/examples"
400
+
401
+ # Extract example snippets from patterns (using safe JSON parsing)
402
+ node -e "
403
+ const fs = require('fs');
404
+ const path = require('path');
405
+
406
+ try {
407
+ const content = fs.readFileSync('$PATTERNS_OUTPUT', 'utf-8');
408
+ // Check for prototype pollution attempts
409
+ if (/__proto__|constructor\\s*[\"'\`:]|prototype\\s*[\"'\`:]/i.test(content)) {
410
+ console.error('Suspicious content detected');
411
+ process.exit(1);
412
+ }
413
+ const p = JSON.parse(content);
414
+
415
+ let count = 0;
416
+ for (const category in p.patterns) {
417
+ for (const pattern of p.patterns[category] || []) {
418
+ if (pattern.examples && pattern.examples.length > 0) {
419
+ const filename = pattern.subcategory.replace(/\\./g, '-') + '.txt';
420
+ const fileContent = pattern.name + '\\n' + '='.repeat(40) + '\\n\\n' +
421
+ pattern.examples.slice(0, 5).join('\\n');
422
+ fs.writeFileSync(
423
+ path.join('$PROFILE_DIR/.workflow/extracted/examples', filename),
424
+ fileContent
425
+ );
426
+ count++;
427
+ }
428
+ }
429
+ }
430
+ console.log(count);
431
+ } catch(e) {
432
+ console.error(e.message);
433
+ process.exit(1);
434
+ }
435
+ " 2>/dev/null || true
436
+
437
+ EXAMPLE_COUNT=$(ls "$PROFILE_DIR/.workflow/extracted/examples/"*.txt 2>/dev/null | wc -l | tr -d ' ')
438
+ if [ "$EXAMPLE_COUNT" -gt 0 ]; then
439
+ echo -e " ${GREEN}✓${NC} examples/ ($EXAMPLE_COUNT pattern examples)"
440
+ CONTENTS="$CONTENTS\n- .workflow/extracted/examples/ (code snippets)"
441
+ fi
442
+ fi
443
+ else
444
+ echo -e " ${RED}✗${NC} Pattern extraction failed (continuing without patterns)"
445
+ fi
446
+ fi
447
+
281
448
  # ==========================================
282
449
  # Create profile info file
283
450
  # ==========================================