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
@@ -15,6 +15,14 @@ const fs = require('fs');
15
15
  const path = require('path');
16
16
  const crypto = require('crypto');
17
17
 
18
+ // Import safe utilities
19
+ const { safeJsonParse, writeJson } = require('./flow-utils');
20
+
21
+ // Utility: ISO timestamp
22
+ function now() {
23
+ return new Date().toISOString();
24
+ }
25
+
18
26
  // Core functions are injected via init() to avoid circular dependencies
19
27
  let digestCore = null;
20
28
 
@@ -44,8 +52,9 @@ function isRequirement(s) { requireInit(); return digestCore.isRequirement(s); }
44
52
  function isVagueStatement(s) { requireInit(); return digestCore.isVagueStatement(s); }
45
53
  function analyzeComplexity() { requireInit(); return digestCore.analyzeComplexity(); }
46
54
 
47
- // State directory
48
- const STATE_DIR = path.join(process.cwd(), '.workflow', 'state', 'digests');
55
+ // Temp directory for processing (cleaned up after completion)
56
+ const TMP_DIR = path.join(process.cwd(), '.workflow', 'tmp', 'long-input');
57
+ const STATE_DIR = TMP_DIR; // Alias for backward compatibility
49
58
 
50
59
  // ==========================================================================
51
60
  // E3-S2: Story Generation with Source Tracing
@@ -579,11 +588,7 @@ function loadStory(storyId) {
579
588
  }
580
589
 
581
590
  const storyPath = path.join(activeDigest.session.digest_path, 'stories', `${storyId}.json`);
582
- try {
583
- return JSON.parse(fs.readFileSync(storyPath, 'utf8'));
584
- } catch (err) {
585
- return null;
586
- }
591
+ return safeJsonParse(storyPath, null);
587
592
  }
588
593
 
589
594
  /**
@@ -598,13 +603,7 @@ function loadAllStories() {
598
603
  const storiesPath = path.join(activeDigest.session.digest_path, 'stories');
599
604
  try {
600
605
  const files = fs.readdirSync(storiesPath).filter(f => f.endsWith('.json'));
601
- return files.map(f => {
602
- try {
603
- return JSON.parse(fs.readFileSync(path.join(storiesPath, f), 'utf8'));
604
- } catch (err) {
605
- return null;
606
- }
607
- }).filter(Boolean);
606
+ return files.map(f => safeJsonParse(path.join(storiesPath, f), null)).filter(Boolean);
608
607
  } catch (err) {
609
608
  return [];
610
609
  }
@@ -698,11 +697,7 @@ function loadQueue() {
698
697
  }
699
698
 
700
699
  const queuePath = path.join(activeDigest.session.digest_path, 'presentation-queue.json');
701
- try {
702
- return JSON.parse(fs.readFileSync(queuePath, 'utf8'));
703
- } catch (err) {
704
- return null;
705
- }
700
+ return safeJsonParse(queuePath, null);
706
701
  }
707
702
 
708
703
  /**
@@ -1088,11 +1083,7 @@ function loadEditSessions() {
1088
1083
  }
1089
1084
 
1090
1085
  const sessionsPath = path.join(activeDigest.session.digest_path, 'edit-sessions.json');
1091
- try {
1092
- return JSON.parse(fs.readFileSync(sessionsPath, 'utf8'));
1093
- } catch (err) {
1094
- return { active_session: null, sessions: [] };
1095
- }
1086
+ return safeJsonParse(sessionsPath, { active_session: null, sessions: [] });
1096
1087
  }
1097
1088
 
1098
1089
  /**
@@ -1928,18 +1919,14 @@ function createFeatureTask(stories, featureName) {
1928
1919
  function addTasksToReadyJson(tasks, options = {}) {
1929
1920
  const readyPath = path.join(process.cwd(), '.workflow', 'state', 'ready.json');
1930
1921
 
1931
- let readyData;
1932
- try {
1933
- readyData = JSON.parse(fs.readFileSync(readyPath, 'utf8'));
1934
- } catch (err) {
1935
- readyData = {
1936
- lastUpdated: now(),
1937
- ready: [],
1938
- inProgress: [],
1939
- blocked: [],
1940
- recentlyCompleted: []
1941
- };
1942
- }
1922
+ const defaultReady = {
1923
+ lastUpdated: now(),
1924
+ ready: [],
1925
+ inProgress: [],
1926
+ blocked: [],
1927
+ recentlyCompleted: []
1928
+ };
1929
+ const readyData = safeJsonParse(readyPath, defaultReady);
1943
1930
 
1944
1931
  // Check for duplicates by source story_id
1945
1932
  const existingStoryIds = new Set(
@@ -2099,6 +2086,12 @@ function finalizeDigestion(options = {}) {
2099
2086
  };
2100
2087
  saveActiveDigest(activeDigest);
2101
2088
 
2089
+ // 6. Cleanup temp files (processing artifacts no longer needed)
2090
+ let cleanupResult = { cleaned: false };
2091
+ if (!options.keepTempFiles) {
2092
+ cleanupResult = cleanupTempFiles(activeDigest.session.digest_id);
2093
+ }
2094
+
2102
2095
  return {
2103
2096
  success: true,
2104
2097
  approved_count: exportResult.summary.total_approved,
@@ -2106,10 +2099,62 @@ function finalizeDigestion(options = {}) {
2106
2099
  tasks_skipped: addResult.skipped || 0,
2107
2100
  files_exported: fileExport?.exported.length || 0,
2108
2101
  validation: exportResult.validation,
2109
- digest_status: 'completed'
2102
+ digest_status: 'completed',
2103
+ temp_cleanup: cleanupResult.cleaned ? 'cleaned' : 'kept'
2110
2104
  };
2111
2105
  }
2112
2106
 
2107
+ /**
2108
+ * Cleanup temp processing files after successful completion
2109
+ * Removes the digest-specific directory from .workflow/tmp/long-input/
2110
+ */
2111
+ function cleanupTempFiles(digestId) {
2112
+ if (!digestId) {
2113
+ return { cleaned: false, error: 'No digest ID provided' };
2114
+ }
2115
+
2116
+ // Path traversal validation - digestId must be a valid digest format
2117
+ // Format: digest-[8 hex chars]
2118
+ if (!/^digest-[a-f0-9]{8}$/.test(digestId)) {
2119
+ return { cleaned: false, error: 'Invalid digest ID format' };
2120
+ }
2121
+
2122
+ const digestPath = path.join(TMP_DIR, digestId);
2123
+
2124
+ // Additional safety: ensure resolved path is within TMP_DIR
2125
+ const resolvedPath = path.resolve(digestPath);
2126
+ const resolvedTmpDir = path.resolve(TMP_DIR);
2127
+ if (!resolvedPath.startsWith(resolvedTmpDir + path.sep)) {
2128
+ return { cleaned: false, error: 'Path traversal attempt detected' };
2129
+ }
2130
+
2131
+ if (!fs.existsSync(digestPath)) {
2132
+ return { cleaned: false, error: 'Digest directory not found' };
2133
+ }
2134
+
2135
+ try {
2136
+ // Remove the digest directory and all its contents
2137
+ fs.rmSync(digestPath, { recursive: true, force: true });
2138
+
2139
+ // Also remove active-digest.json if it points to this digest
2140
+ const activeFile = path.join(TMP_DIR, 'active-digest.json');
2141
+ if (fs.existsSync(activeFile)) {
2142
+ const active = safeJsonParse(activeFile, null);
2143
+ if (active && active.session?.digest_id === digestId) {
2144
+ try {
2145
+ fs.unlinkSync(activeFile);
2146
+ } catch (unlinkErr) {
2147
+ // Ignore errors unlinking active file - main cleanup succeeded
2148
+ }
2149
+ }
2150
+ }
2151
+
2152
+ return { cleaned: true, path: digestPath };
2153
+ } catch (err) {
2154
+ return { cleaned: false, error: err.message };
2155
+ }
2156
+ }
2157
+
2113
2158
  // ============================================================================
2114
2159
  // Module Exports
2115
2160
  // ============================================================================
@@ -2190,5 +2235,8 @@ module.exports = {
2190
2235
  formatTaskAsMarkdown,
2191
2236
  exportStoryFiles,
2192
2237
  previewExport,
2193
- finalizeDigestion
2238
+ finalizeDigestion,
2239
+
2240
+ // Temp File Cleanup
2241
+ cleanupTempFiles
2194
2242
  };
@@ -1,11 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Transcript Digestion - Multi-pass extraction system
4
+ * Long Input Processing - Multi-pass extraction system
5
5
  *
6
- * This script manages digest sessions and provides utilities for the
7
- * multi-pass extraction process. The actual extraction is done by Claude
8
- * following the rules in .claude/skills/transcript-digestion/rules/
6
+ * Ensures nothing is missed from long/complex inputs (transcripts, prompts,
7
+ * specs, documents). Uses a 4-pass extraction system:
8
+ * Pass 1: Topic extraction
9
+ * Pass 2: Statement association
10
+ * Pass 3: Orphan check
11
+ * Pass 4: Contradiction resolution
12
+ *
13
+ * Renamed from flow-transcript-digest.js in v1.8.0
9
14
  */
10
15
 
11
16
  const fs = require('fs');
@@ -13,15 +18,94 @@ const path = require('path');
13
18
  const crypto = require('crypto');
14
19
  const { writeJson } = require('./flow-utils');
15
20
 
16
- // Import extracted modules
17
- const transcriptParsing = require('./flow-transcript-parsing');
18
- const transcriptLanguage = require('./flow-transcript-language');
19
- const transcriptStories = require('./flow-transcript-stories');
20
- const transcriptChunking = require('./flow-transcript-chunking');
21
-
22
- // Paths
23
- const STATE_DIR = path.join(process.cwd(), '.workflow', 'state', 'digests');
24
- const ACTIVE_DIGEST_FILE = path.join(STATE_DIR, 'active-digest.json');
21
+ // Import extracted modules (renamed from transcript-* to long-input-*)
22
+ const transcriptParsing = require('./flow-long-input-parsing');
23
+ const transcriptLanguage = require('./flow-long-input-language');
24
+ const transcriptStories = require('./flow-long-input-stories');
25
+ const transcriptChunking = require('./flow-long-input-chunking');
26
+
27
+ // Destructure commonly used language functions
28
+ const {
29
+ detectLanguage,
30
+ detectMultipleLanguages,
31
+ getLanguageInfo,
32
+ LANGUAGE_INFO
33
+ } = transcriptLanguage;
34
+
35
+ // Destructure commonly used parsing functions
36
+ const {
37
+ parseVTT,
38
+ parseSRT,
39
+ parseSubtitle,
40
+ mergeCues,
41
+ formatCuesAsText,
42
+ getSubtitleStats,
43
+ parseZoom,
44
+ parseTeams,
45
+ parseMeeting,
46
+ mergeMeetingEntries,
47
+ formatMeetingAsText,
48
+ getMeetingStats
49
+ } = transcriptParsing;
50
+
51
+ // Destructure commonly used chunking functions
52
+ const {
53
+ loadDurableSessions,
54
+ listDurableSessions,
55
+ getDurableSession,
56
+ switchDurableSession,
57
+ archiveDurableSession,
58
+ deleteDurableSession,
59
+ generateRecoverySummaryForSession,
60
+ getTimeSince,
61
+ needsChunking,
62
+ planChunks,
63
+ getChunkingStatus
64
+ } = transcriptChunking;
65
+
66
+ // Destructure additional language utilities
67
+ const { listSupportedLanguages } = transcriptLanguage;
68
+
69
+ // Destructure commonly used story functions
70
+ const {
71
+ generateStoryFromTopic,
72
+ generateAllStories,
73
+ saveStory,
74
+ loadStory,
75
+ loadAllStories,
76
+ formatStoryAsMarkdown,
77
+ initializePresentation,
78
+ getPresentationStatus,
79
+ getNextStory,
80
+ getCurrentStory,
81
+ approveCurrentStory,
82
+ rejectCurrentStory,
83
+ skipCurrentStory,
84
+ formatStorySummary,
85
+ formatActionsPrompt,
86
+ getCompletionSummary,
87
+ resetPresentation,
88
+ // Edit session functions
89
+ startEditSession,
90
+ editUserStory,
91
+ editCriterion,
92
+ addCriterion,
93
+ removeCriterion,
94
+ getEditChanges,
95
+ commitEditSession,
96
+ cancelEditSession,
97
+ getEditHistory,
98
+ listEditableStories,
99
+ // Export functions
100
+ previewExport,
101
+ exportApprovedStories,
102
+ finalizeDigestion
103
+ } = transcriptStories;
104
+
105
+ // Paths - temp processing files go to .workflow/tmp/, cleaned up after completion
106
+ const TMP_DIR = path.join(process.cwd(), '.workflow', 'tmp', 'long-input');
107
+ const STATE_DIR = TMP_DIR; // Alias for backward compatibility during migration
108
+ const ACTIVE_DIGEST_FILE = path.join(TMP_DIR, 'active-digest.json');
25
109
  const CONFIG_FILE = path.join(process.cwd(), '.workflow', 'config.json');
26
110
 
27
111
  // Colors for CLI output
@@ -7373,7 +7457,7 @@ function main() {
7373
7457
  if (previewResult.validation.errors.length > 0) {
7374
7458
  console.log(`${c.red}Errors:${c.reset}`);
7375
7459
  for (const e of previewResult.validation.errors) {
7376
- console.log(` ${e.story_id}: ${err.message}`);
7460
+ console.log(` ${e.story_id}: ${e.message}`);
7377
7461
  }
7378
7462
  console.log();
7379
7463
  }
@@ -7641,8 +7725,137 @@ ${c.dim}Examples:${c.reset}
7641
7725
  }
7642
7726
  }
7643
7727
 
7728
+ // ==========================================================================
7729
+ // Quick Processing Mode
7730
+ // ==========================================================================
7731
+
7732
+ /**
7733
+ * Quick process mode - single-pass extraction without interactive clarification.
7734
+ * Used by the long input gate for fast feedback.
7735
+ *
7736
+ * @param {string} input - The input text to process
7737
+ * @param {Object} options - Processing options
7738
+ * @returns {Object} Quick scan results
7739
+ */
7740
+ function quickProcess(input, options = {}) {
7741
+ if (!input || typeof input !== 'string') {
7742
+ return { error: 'No input provided' };
7743
+ }
7744
+
7745
+ const startTime = Date.now();
7746
+
7747
+ // 1. Split into statements (returns objects with .text property)
7748
+ const statements = splitIntoStatements(input);
7749
+ // isMeaningfulStatement returns {meaningful: bool, reason: string}, filter on .meaningful
7750
+ const meaningfulStatements = statements.filter(s => isMeaningfulStatement(s.text).meaningful);
7751
+
7752
+ // 2. Quick topic extraction (keyword-based, no full analysis)
7753
+ const topicKeywords = new Set();
7754
+ const topicPatterns = [
7755
+ /\b(add|create|build|implement)\s+(?:a\s+)?(\w+(?:\s+\w+)?)/gi,
7756
+ /\b(\w+)\s+(feature|component|page|button|form|table|list)/gi,
7757
+ /\b(user|admin|guest)\s+(?:can|should|must|wants?)\s+(\w+)/gi
7758
+ ];
7759
+
7760
+ for (const statement of meaningfulStatements) {
7761
+ const text = statement.text;
7762
+ for (const pattern of topicPatterns) {
7763
+ const matches = text.matchAll(pattern);
7764
+ for (const match of matches) {
7765
+ const keyword = (match[2] || match[1]).toLowerCase();
7766
+ if (keyword.length > 2) {
7767
+ topicKeywords.add(keyword);
7768
+ }
7769
+ }
7770
+ }
7771
+ }
7772
+
7773
+ // 3. Quick contradiction detection
7774
+ const contradictions = [];
7775
+ const seenValues = new Map(); // attribute -> { value, text }
7776
+
7777
+ const valuePatterns = [
7778
+ { pattern: /(\d+)\s*(columns?|rows?|items?|pages?)/gi, attr: 'count' },
7779
+ { pattern: /(primary|secondary|danger|success)\s*(?:color|button)/gi, attr: 'style' },
7780
+ { pattern: /(left|right|center|top|bottom)/gi, attr: 'position' }
7781
+ ];
7782
+
7783
+ for (const statement of meaningfulStatements) {
7784
+ const text = statement.text;
7785
+ for (const { pattern, attr } of valuePatterns) {
7786
+ const match = text.match(pattern);
7787
+ if (match && match[1]) {
7788
+ const value = match[1].toLowerCase();
7789
+ const key = `${attr}`;
7790
+
7791
+ if (seenValues.has(key) && seenValues.get(key).value !== value) {
7792
+ // Check for correction phrase
7793
+ const isCorrection = detectCorrectionPhrase(text);
7794
+
7795
+ contradictions.push({
7796
+ attribute: attr,
7797
+ value1: seenValues.get(key).value,
7798
+ value2: value,
7799
+ statement1: seenValues.get(key).text.slice(0, 50),
7800
+ statement2: text.slice(0, 50),
7801
+ autoResolved: isCorrection,
7802
+ resolution: isCorrection ? `Later statement (${value}) supersedes` : 'needs_review'
7803
+ });
7804
+ }
7805
+
7806
+ seenValues.set(key, { value, text });
7807
+ }
7808
+ }
7809
+ }
7810
+
7811
+ const elapsed = Date.now() - startTime;
7812
+
7813
+ return {
7814
+ mode: 'quick',
7815
+ success: true,
7816
+ metrics: {
7817
+ totalStatements: statements.length,
7818
+ meaningfulStatements: meaningfulStatements.length,
7819
+ topicsDetected: topicKeywords.size,
7820
+ contradictionsFound: contradictions.length,
7821
+ autoResolved: contradictions.filter(c => c.autoResolved).length,
7822
+ processingTimeMs: elapsed
7823
+ },
7824
+ topics: Array.from(topicKeywords),
7825
+ contradictions: contradictions.filter(c => !c.autoResolved),
7826
+ summary: generateQuickSummary(meaningfulStatements.length, topicKeywords.size, contradictions)
7827
+ };
7828
+ }
7829
+
7830
+ /**
7831
+ * Generate human-readable summary for quick scan
7832
+ */
7833
+ function generateQuickSummary(statementCount, topicCount, contradictions) {
7834
+ const unresolvedCount = contradictions.filter(c => !c.autoResolved).length;
7835
+
7836
+ let summary = `Quick scan complete: ${statementCount} statements, ${topicCount} topics detected.`;
7837
+
7838
+ if (contradictions.length > 0) {
7839
+ const autoResolved = contradictions.filter(c => c.autoResolved).length;
7840
+ summary += `\n${contradictions.length} potential contradictions found`;
7841
+ if (autoResolved > 0) {
7842
+ summary += ` (${autoResolved} auto-resolved as corrections)`;
7843
+ }
7844
+ if (unresolvedCount > 0) {
7845
+ summary += `.\n${unresolvedCount} need review.`;
7846
+ }
7847
+ } else {
7848
+ summary += '\nNo obvious contradictions detected.';
7849
+ }
7850
+
7851
+ return summary;
7852
+ }
7853
+
7644
7854
  // Export for use as module
7645
7855
  module.exports = {
7856
+ // Utilities
7857
+ now,
7858
+ // Core session management
7646
7859
  createSession,
7647
7860
  loadActiveDigest,
7648
7861
  saveActiveDigest,
@@ -7909,7 +8122,10 @@ module.exports = {
7909
8122
  saveChunkingState: transcriptChunking.saveChunkingState,
7910
8123
  updateChunkStatus: transcriptChunking.updateChunkStatus,
7911
8124
  getChunkContent: transcriptChunking.getChunkContent,
7912
- getChunkingStatus: transcriptChunking.getChunkingStatus
8125
+ getChunkingStatus: transcriptChunking.getChunkingStatus,
8126
+ // Quick Processing Mode (for gate integration)
8127
+ quickProcess,
8128
+ generateQuickSummary
7913
8129
  };
7914
8130
 
7915
8131
  // Run CLI if called directly