wogiflow 2.4.2 → 2.4.4

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 (210) hide show
  1. package/.claude/commands/wogi-start.md +124 -0
  2. package/.claude/docs/claude-code-compatibility.md +51 -0
  3. package/.claude/docs/explore-agents.md +11 -0
  4. package/.claude/settings.json +12 -1
  5. package/.workflow/models/registry.json +1 -1
  6. package/bin/flow +11 -1
  7. package/lib/workspace-contracts.js +599 -0
  8. package/lib/workspace-intelligence.js +600 -0
  9. package/lib/workspace-messages.js +441 -0
  10. package/lib/workspace-routing.js +485 -0
  11. package/lib/workspace-sync.js +339 -0
  12. package/lib/workspace.js +1073 -0
  13. package/package.json +4 -4
  14. package/scripts/MEMORY-ARCHITECTURE.md +1 -1
  15. package/scripts/base-workflow-step.js +136 -0
  16. package/scripts/flow-adaptive-learning.js +8 -9
  17. package/scripts/flow-aggregate.js +11 -6
  18. package/scripts/flow-api-index.js +4 -6
  19. package/scripts/flow-assumption-detector.js +0 -2
  20. package/scripts/flow-audit.js +15 -2
  21. package/scripts/flow-auto-context.js +8 -12
  22. package/scripts/flow-auto-learn.js +49 -49
  23. package/scripts/flow-background.js +5 -6
  24. package/scripts/flow-bridge-state.js +8 -10
  25. package/scripts/flow-bulk-loop.js +1 -3
  26. package/scripts/flow-bulk-orchestrator.js +1 -3
  27. package/scripts/flow-cascade-completion.js +0 -2
  28. package/scripts/flow-cascade.js +4 -4
  29. package/scripts/flow-checkpoint.js +10 -13
  30. package/scripts/flow-code-intelligence.js +10 -12
  31. package/scripts/flow-community-sync.js +4 -4
  32. package/scripts/flow-community.js +12 -20
  33. package/scripts/flow-config-defaults.js +28 -2
  34. package/scripts/flow-config-interactive.js +9 -5
  35. package/scripts/flow-config-loader.js +49 -92
  36. package/scripts/flow-config-substitution.js +0 -2
  37. package/scripts/flow-context-estimator.js +4 -4
  38. package/scripts/flow-context-init.js +10 -12
  39. package/scripts/flow-context-manager.js +0 -2
  40. package/scripts/flow-context-scoring.js +2 -2
  41. package/scripts/flow-contract-scan.js +6 -9
  42. package/scripts/flow-correct.js +29 -27
  43. package/scripts/flow-correction-detector.js +5 -1
  44. package/scripts/flow-damage-control.js +47 -54
  45. package/scripts/flow-decisions-merge.js +4 -14
  46. package/scripts/flow-diff.js +5 -8
  47. package/scripts/flow-done-gates.js +786 -0
  48. package/scripts/flow-done-report.js +123 -0
  49. package/scripts/flow-done.js +71 -717
  50. package/scripts/flow-entropy-monitor.js +1 -3
  51. package/scripts/flow-eval-calibration.js +257 -0
  52. package/scripts/flow-eval-judge.js +10 -1
  53. package/scripts/flow-eval.js +14 -5
  54. package/scripts/flow-extraction-review.js +1 -0
  55. package/scripts/flow-failure-categories.js +0 -2
  56. package/scripts/flow-figma-confirm.js +5 -9
  57. package/scripts/flow-figma-generate.js +8 -10
  58. package/scripts/flow-figma-index.js +8 -10
  59. package/scripts/flow-figma-match.js +3 -5
  60. package/scripts/flow-figma-mcp-server.js +2 -4
  61. package/scripts/flow-figma-orchestrator.js +2 -3
  62. package/scripts/flow-figma-registry.js +2 -3
  63. package/scripts/flow-framework-resolver.js +0 -2
  64. package/scripts/flow-function-index.js +4 -6
  65. package/scripts/flow-gate-confidence.js +2 -2
  66. package/scripts/flow-gitignore.js +0 -2
  67. package/scripts/flow-guided-edit.js +5 -6
  68. package/scripts/flow-health.js +5 -6
  69. package/scripts/flow-hook-errors.js +6 -0
  70. package/scripts/flow-hook-status.js +263 -0
  71. package/scripts/flow-hooks.js +17 -29
  72. package/scripts/flow-http-client.js +9 -8
  73. package/scripts/flow-hybrid-interactive.js +7 -12
  74. package/scripts/flow-hybrid-test.js +12 -13
  75. package/scripts/flow-instruction-richness.js +1 -1
  76. package/scripts/flow-io.js +21 -4
  77. package/scripts/flow-knowledge-router.js +9 -3
  78. package/scripts/flow-learning-orchestrator.js +318 -13
  79. package/scripts/flow-links.js +5 -7
  80. package/scripts/flow-long-input-association.js +275 -0
  81. package/scripts/flow-long-input-chunking.js +1 -0
  82. package/scripts/flow-long-input-cli.js +0 -2
  83. package/scripts/flow-long-input-complexity.js +0 -2
  84. package/scripts/flow-long-input-constants.js +0 -2
  85. package/scripts/flow-long-input-contradictions.js +351 -0
  86. package/scripts/flow-long-input-detection.js +0 -2
  87. package/scripts/flow-long-input-passes.js +885 -0
  88. package/scripts/flow-long-input-stories.js +1 -1
  89. package/scripts/flow-long-input-voice.js +0 -2
  90. package/scripts/flow-long-input.js +425 -3005
  91. package/scripts/flow-loop-retry-learning.js +2 -3
  92. package/scripts/flow-lsp.js +3 -3
  93. package/scripts/flow-mcp-docs.js +3 -4
  94. package/scripts/flow-memory-db.js +6 -8
  95. package/scripts/flow-memory-sync.js +18 -11
  96. package/scripts/flow-metrics.js +1 -2
  97. package/scripts/flow-model-adapter.js +2 -3
  98. package/scripts/flow-model-config.js +72 -104
  99. package/scripts/flow-model-router.js +2 -2
  100. package/scripts/flow-model-types.js +0 -2
  101. package/scripts/flow-multi-approach.js +5 -6
  102. package/scripts/flow-orchestrate-context.js +3 -7
  103. package/scripts/flow-orchestrate-rollback.js +3 -8
  104. package/scripts/flow-orchestrate-state.js +8 -14
  105. package/scripts/flow-orchestrate-templates.js +2 -6
  106. package/scripts/flow-orchestrate-validator.js +5 -9
  107. package/scripts/flow-orchestrate.js +126 -103
  108. package/scripts/flow-output.js +0 -2
  109. package/scripts/flow-parallel.js +1 -1
  110. package/scripts/flow-paths.js +23 -2
  111. package/scripts/flow-pattern-enforcer.js +30 -28
  112. package/scripts/flow-pattern-extractor.js +3 -4
  113. package/scripts/flow-pending.js +0 -2
  114. package/scripts/flow-permissions.js +2 -3
  115. package/scripts/flow-plugin-registry.js +10 -12
  116. package/scripts/flow-prd-manager.js +1 -1
  117. package/scripts/flow-progress.js +7 -9
  118. package/scripts/flow-prompt-composer.js +3 -3
  119. package/scripts/flow-prompt-template.js +2 -2
  120. package/scripts/flow-providers.js +7 -4
  121. package/scripts/flow-registry-manager.js +7 -12
  122. package/scripts/flow-regression.js +9 -11
  123. package/scripts/flow-roadmap.js +2 -2
  124. package/scripts/flow-run-trace.js +16 -15
  125. package/scripts/flow-safety.js +2 -5
  126. package/scripts/flow-scanner-base.js +5 -7
  127. package/scripts/flow-scenario-engine.js +1 -5
  128. package/scripts/flow-security.js +29 -0
  129. package/scripts/flow-session-end.js +32 -41
  130. package/scripts/flow-session-learning.js +53 -49
  131. package/scripts/flow-setup-hooks.js +2 -3
  132. package/scripts/flow-skill-create.js +7 -12
  133. package/scripts/flow-skill-generator.js +12 -16
  134. package/scripts/flow-skill-learn.js +17 -8
  135. package/scripts/flow-skill-matcher.js +1 -2
  136. package/scripts/flow-spec-generator.js +2 -4
  137. package/scripts/flow-stack-wizard.js +5 -7
  138. package/scripts/flow-standards-learner.js +35 -16
  139. package/scripts/flow-start.js +2 -0
  140. package/scripts/flow-stats-collector.js +2 -2
  141. package/scripts/flow-status.js +10 -10
  142. package/scripts/flow-statusline-setup.js +2 -2
  143. package/scripts/flow-step-changelog.js +2 -3
  144. package/scripts/flow-step-comments.js +66 -81
  145. package/scripts/flow-step-complexity.js +50 -70
  146. package/scripts/flow-step-coverage.js +3 -5
  147. package/scripts/flow-step-knowledge.js +2 -3
  148. package/scripts/flow-step-pr-tests.js +64 -74
  149. package/scripts/flow-step-regression.js +3 -5
  150. package/scripts/flow-step-review.js +86 -103
  151. package/scripts/flow-step-security.js +111 -121
  152. package/scripts/flow-step-silent-failures.js +56 -83
  153. package/scripts/flow-step-simplifier.js +52 -70
  154. package/scripts/flow-story.js +4 -7
  155. package/scripts/flow-strict-adherence.js +3 -4
  156. package/scripts/flow-task-checkpoint.js +36 -5
  157. package/scripts/flow-task-enforcer.js +2 -24
  158. package/scripts/flow-tech-debt.js +1 -1
  159. package/scripts/flow-template-extractor.js +1 -0
  160. package/scripts/flow-templates.js +11 -13
  161. package/scripts/flow-test-api.js +9 -13
  162. package/scripts/flow-test-discovery.js +1 -1
  163. package/scripts/flow-test-generate.js +5 -9
  164. package/scripts/flow-test-integrity.js +3 -7
  165. package/scripts/flow-test-ui.js +5 -9
  166. package/scripts/flow-testing-deps.js +1 -3
  167. package/scripts/flow-tiered-learning.js +4 -4
  168. package/scripts/flow-todowrite-sync.js +1 -1
  169. package/scripts/flow-tokens.js +0 -2
  170. package/scripts/flow-verification-profile.js +6 -10
  171. package/scripts/flow-verify.js +12 -16
  172. package/scripts/flow-version-check.js +4 -12
  173. package/scripts/flow-webmcp-generator.js +3 -5
  174. package/scripts/flow-workflow-steps.js +0 -2
  175. package/scripts/flow-workflow.js +9 -11
  176. package/scripts/hooks/adapters/claude-code.js +31 -0
  177. package/scripts/hooks/core/config-change.js +1 -0
  178. package/scripts/hooks/core/extension-registry.js +0 -2
  179. package/scripts/hooks/core/instructions-loaded.js +1 -1
  180. package/scripts/hooks/core/observation-capture.js +5 -5
  181. package/scripts/hooks/core/phase-gate.js +5 -0
  182. package/scripts/hooks/core/post-compact.js +1 -12
  183. package/scripts/hooks/core/research-gate.js +2 -12
  184. package/scripts/hooks/core/routing-gate.js +6 -0
  185. package/scripts/hooks/core/task-completed.js +12 -0
  186. package/scripts/hooks/core/task-created.js +83 -0
  187. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  188. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  189. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  190. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  191. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  192. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  193. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  194. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  195. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  196. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  197. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  198. package/scripts/hooks/entry/claude-code/task-created.js +15 -0
  199. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  200. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  201. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  202. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  203. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  204. package/scripts/postinstall.js +2 -0
  205. package/scripts/registries/api-registry.js +0 -2
  206. package/scripts/registries/component-registry.js +5 -9
  207. package/scripts/registries/contract-scanner.js +2 -9
  208. package/scripts/registries/function-registry.js +0 -2
  209. package/scripts/registries/schema-registry.js +14 -18
  210. package/scripts/registries/service-registry.js +23 -27
@@ -0,0 +1,885 @@
1
+ /**
2
+ * Long Input Processing - Multi-Pass Pipeline
3
+ *
4
+ * Extracted from flow-long-input.js
5
+ * Contains runPass2, runPass3, runPass4, runFullPipeline, quickProcess,
6
+ * and supporting functions for orphan clustering and topic creation.
7
+ */
8
+
9
+ const fs = require('node:fs');
10
+ const path = require('node:path');
11
+ const { generateHashId } = require('./flow-utils');
12
+
13
+ // These are injected via init()
14
+ let _loadActiveDigest = null;
15
+ let _updatePhase = null;
16
+ let _now = null;
17
+ let _countWords = null;
18
+ let _loadTopics = null;
19
+ let _saveTopics = null;
20
+ let _splitIntoStatements = null;
21
+ let _isMeaningfulStatement = null;
22
+ let _associateStatements = null;
23
+ let _saveStatementMap = null;
24
+ let _loadStatementMap = null;
25
+ let _detectContradictions = null;
26
+ let _resolveOrphan = null;
27
+ let _calculateResolutionConfidence = null;
28
+ let _generateContradictionQuestion = null;
29
+ let _isAdditive = null;
30
+ let _detectCorrectionPhrase = null;
31
+ let _saveClarifications = null;
32
+ let _loadClarifications = null;
33
+ let _createSession = null;
34
+
35
+ /**
36
+ * Initialize with core functions from other modules.
37
+ * @param {Object} deps
38
+ */
39
+ function _requireInit(fnName) {
40
+ if (!_loadActiveDigest) {
41
+ throw new Error(`flow-long-input-passes: init() must be called before ${fnName}()`);
42
+ }
43
+ }
44
+
45
+ function init(deps) {
46
+ _loadActiveDigest = deps.loadActiveDigest;
47
+ _updatePhase = deps.updatePhase;
48
+ _now = deps.now;
49
+ _countWords = deps.countWords;
50
+ _loadTopics = deps.loadTopics;
51
+ _saveTopics = deps.saveTopics;
52
+ _splitIntoStatements = deps.splitIntoStatements;
53
+ _isMeaningfulStatement = deps.isMeaningfulStatement;
54
+ _associateStatements = deps.associateStatements;
55
+ _saveStatementMap = deps.saveStatementMap;
56
+ _loadStatementMap = deps.loadStatementMap;
57
+ _detectContradictions = deps.detectContradictions;
58
+ _resolveOrphan = deps.resolveOrphan;
59
+ _calculateResolutionConfidence = deps.calculateResolutionConfidence;
60
+ _generateContradictionQuestion = deps.generateContradictionQuestion;
61
+ _isAdditive = deps.isAdditive;
62
+ _detectCorrectionPhrase = deps.detectCorrectionPhrase;
63
+ _saveClarifications = deps.saveClarifications;
64
+ _loadClarifications = deps.loadClarifications;
65
+ _createSession = deps.createSession;
66
+ }
67
+
68
+ /**
69
+ * Extract key phrase from statement for topic naming
70
+ */
71
+ function extractKeyPhrase(text) {
72
+ // Remove common words
73
+ const stopWords = ['the', 'a', 'an', 'is', 'are', 'should', 'must', 'will', 'can', 'be', 'it', 'we', 'i', 'to', 'for', 'of', 'in', 'on', 'with'];
74
+ const words = text.toLowerCase()
75
+ .replace(/[^\w\s]/g, '')
76
+ .split(/\s+/)
77
+ .filter(w => w.length > 2 && !stopWords.includes(w));
78
+
79
+ // Get first 2-3 meaningful words
80
+ const keyWords = words.slice(0, 3);
81
+ if (keyWords.length === 0) return 'Misc';
82
+
83
+ // Capitalize
84
+ return keyWords.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
85
+ }
86
+
87
+ /**
88
+ * Create a new topic from orphan statements
89
+ */
90
+ function createTopicFromOrphans(orphans, _existingTopics) {
91
+ // Guard against empty orphans array
92
+ if (!orphans || orphans.length === 0) {
93
+ const topicId = generateHashId('t-auto', 'empty', 'fallback');
94
+ return {
95
+ id: topicId,
96
+ title: 'Miscellaneous',
97
+ description: 'Auto-generated topic for uncategorized statements',
98
+ source: 'orphan_resolution',
99
+ entities: [],
100
+ keywords: [],
101
+ statements: [],
102
+ needs_review: true,
103
+ confidence: 0.5,
104
+ created_at: _now()
105
+ };
106
+ }
107
+
108
+ const topicId = generateHashId('t-auto', '', '');
109
+
110
+ // Extract common keywords from orphans
111
+ const allWords = orphans.flatMap(o =>
112
+ o.text.toLowerCase()
113
+ .replace(/[^\w\s]/g, '')
114
+ .split(/\s+/)
115
+ .filter(w => w.length > 3)
116
+ );
117
+
118
+ // Count word frequencies
119
+ const wordCounts = {};
120
+ for (const word of allWords) {
121
+ wordCounts[word] = (wordCounts[word] || 0) + 1;
122
+ }
123
+
124
+ // Get most common words as keywords
125
+ const keywords = Object.entries(wordCounts)
126
+ .sort((a, b) => b[1] - a[1])
127
+ .slice(0, 5)
128
+ .map(([word]) => word);
129
+
130
+ // Generate title from first orphan
131
+ const title = extractKeyPhrase(orphans[0].text);
132
+
133
+ return {
134
+ id: topicId,
135
+ title: title,
136
+ description: `Auto-generated from ${orphans.length} orphan statement(s)`,
137
+ source: 'orphan_resolution',
138
+ entities: [],
139
+ keywords,
140
+ statements: orphans.map(o => o.id),
141
+ needs_review: true,
142
+ confidence: 0.7,
143
+ created_at: _now()
144
+ };
145
+ }
146
+
147
+ /**
148
+ * Ensure General topic exists
149
+ */
150
+ function ensureGeneralTopic(topics) {
151
+ let general = topics.find(t => t.id === 't-general');
152
+ if (!general) {
153
+ general = {
154
+ id: 't-general',
155
+ title: 'General Requirements',
156
+ description: 'Miscellaneous requirements that apply broadly or do not fit specific features',
157
+ source: 'catch_all',
158
+ entities: [],
159
+ keywords: ['general', 'overall', 'misc'],
160
+ statements: [],
161
+ needs_review: true,
162
+ confidence: 1.0
163
+ };
164
+ topics.push(general);
165
+ }
166
+ return general;
167
+ }
168
+
169
+ /**
170
+ * Save orphan resolution results
171
+ */
172
+ function saveOrphans(orphansData) {
173
+ const activeDigest = _loadActiveDigest();
174
+ if (!activeDigest.session.digest_path) {
175
+ throw new Error('No active digest session');
176
+ }
177
+
178
+ const orphansPath = path.join(activeDigest.session.digest_path, 'orphans.json');
179
+ fs.writeFileSync(orphansPath, JSON.stringify(orphansData, null, 2));
180
+ return orphansData;
181
+ }
182
+
183
+ /**
184
+ * Load orphan data
185
+ */
186
+ function loadOrphans() {
187
+ const activeDigest = _loadActiveDigest();
188
+ if (!activeDigest.session.digest_path) {
189
+ return null;
190
+ }
191
+
192
+ const { safeJsonParse } = require('./flow-utils');
193
+ const orphansPath = path.join(activeDigest.session.digest_path, 'orphans.json');
194
+ const data = safeJsonParse(orphansPath, null);
195
+ if (!data) return null;
196
+ return data;
197
+ }
198
+
199
+ /**
200
+ * Process Pass 2: Statement Association
201
+ */
202
+ function runPass2() {
203
+ _requireInit('runPass2');
204
+ const activeDigest = _loadActiveDigest();
205
+ if (!activeDigest.session.digest_path) {
206
+ throw new Error('No active digest session');
207
+ }
208
+
209
+ // Load transcript
210
+ const transcriptPath = path.join(activeDigest.session.digest_path, 'transcript.md');
211
+ const transcript = fs.readFileSync(transcriptPath, 'utf8');
212
+
213
+ // Load topics from Pass 1
214
+ const topicsData = _loadTopics();
215
+ if (!topicsData || !topicsData.topics.length) {
216
+ throw new Error('No topics found - run Pass 1 first');
217
+ }
218
+
219
+ // Update phase status
220
+ _updatePhase('statement_mapping', 'in_progress');
221
+
222
+ // Split into statements
223
+ const statements = _splitIntoStatements(transcript);
224
+
225
+ // Associate with topics
226
+ const mappedStatements = _associateStatements(statements, topicsData.topics);
227
+
228
+ // Detect contradictions
229
+ const contradictions = _detectContradictions(mappedStatements);
230
+
231
+ // Mark contradicting statements
232
+ for (const contradiction of contradictions) {
233
+ const stmt1 = mappedStatements.find(s => s.id === contradiction.statement1_id);
234
+ const stmt2 = mappedStatements.find(s => s.id === contradiction.statement2_id);
235
+ if (stmt1) stmt1.contradicts = contradiction.statement2_id;
236
+ if (stmt2) stmt2.contradicts = contradiction.statement1_id;
237
+ }
238
+
239
+ // Save statement map
240
+ const result = _saveStatementMap({
241
+ statements: mappedStatements,
242
+ contradictions
243
+ });
244
+
245
+ return result;
246
+ }
247
+
248
+ /**
249
+ * Process Pass 3: Orphan Check
250
+ */
251
+ function runPass3() {
252
+ _requireInit('runPass3');
253
+ const activeDigest = _loadActiveDigest();
254
+ if (!activeDigest.session.digest_path) {
255
+ throw new Error('No active digest session');
256
+ }
257
+
258
+ // Load statement map
259
+ const stmtMap = _loadStatementMap();
260
+ if (!stmtMap) {
261
+ throw new Error('No statement map found - run Pass 2 first');
262
+ }
263
+
264
+ // Load topics
265
+ const topicsData = _loadTopics();
266
+ if (!topicsData) {
267
+ throw new Error('No topics found');
268
+ }
269
+
270
+ // Update phase
271
+ _updatePhase('orphan_check', 'in_progress');
272
+
273
+ // Find orphans
274
+ const orphanStatements = stmtMap.statements.filter(s => s.meaningful && s.topic_id === null);
275
+
276
+ if (orphanStatements.length === 0) {
277
+ // No orphans - 100% coverage
278
+ const result = {
279
+ orphans: [],
280
+ resolved: [],
281
+ new_topics_created: [],
282
+ coverage: {
283
+ total_meaningful: stmtMap.metadata.meaningful_statements,
284
+ mapped: stmtMap.metadata.meaningful_statements,
285
+ clarification_needed: 0,
286
+ percentage: 100,
287
+ target: 100
288
+ }
289
+ };
290
+
291
+ saveOrphans(result);
292
+ _updatePhase('orphan_check', 'completed', { orphans_resolved: 0 });
293
+ return result;
294
+ }
295
+
296
+ const resolved = [];
297
+ const stillOrphans = [];
298
+ const newTopics = [];
299
+ let topics = [...topicsData.topics];
300
+
301
+ // First pass: try to resolve each orphan
302
+ for (const orphan of orphanStatements) {
303
+ const resolution = _resolveOrphan(orphan, topics);
304
+
305
+ if (resolution.resolved) {
306
+ // Update statement in map
307
+ orphan.topic_id = resolution.topic_id;
308
+ orphan.confidence = resolution.confidence;
309
+ orphan.association_reason = resolution.reasons?.join(',') || resolution.method;
310
+
311
+ resolved.push({
312
+ id: orphan.id,
313
+ original_topic_id: null,
314
+ resolved_topic_id: resolution.topic_id,
315
+ resolution_method: resolution.method,
316
+ confidence: resolution.confidence
317
+ });
318
+ } else {
319
+ stillOrphans.push({
320
+ ...orphan,
321
+ resolution_attempted: true,
322
+ resolution_result: resolution.method,
323
+ possible_topics: resolution.possible_topics || [],
324
+ needs_clarification: true,
325
+ clarification_question: `You mentioned "${orphan.text.slice(0, 50)}..." - which feature does this relate to?`
326
+ });
327
+ }
328
+ }
329
+
330
+ // Second pass: cluster remaining orphans that might form new topics
331
+ const unresolved = stillOrphans.filter(o => o.resolution_result === 'no_match');
332
+ if (unresolved.length >= 2) {
333
+ // Simple clustering: group orphans with similar words
334
+ const clusters = [];
335
+ const used = new Set();
336
+
337
+ for (let i = 0; i < unresolved.length; i++) {
338
+ if (used.has(i)) continue;
339
+
340
+ const cluster = [unresolved[i]];
341
+ used.add(i);
342
+
343
+ const words1 = new Set(unresolved[i].text.toLowerCase().split(/\s+/).filter(w => w.length > 3));
344
+
345
+ for (let j = i + 1; j < unresolved.length; j++) {
346
+ if (used.has(j)) continue;
347
+
348
+ const words2 = new Set(unresolved[j].text.toLowerCase().split(/\s+/).filter(w => w.length > 3));
349
+ const common = [...words1].filter(w => words2.has(w));
350
+
351
+ if (common.length >= 2) {
352
+ cluster.push(unresolved[j]);
353
+ used.add(j);
354
+ }
355
+ }
356
+
357
+ if (cluster.length >= 2) {
358
+ clusters.push(cluster);
359
+ }
360
+ }
361
+
362
+ // Create new topics from clusters
363
+ for (const cluster of clusters) {
364
+ const newTopic = createTopicFromOrphans(cluster, topics);
365
+ topics.push(newTopic);
366
+ newTopics.push({
367
+ id: newTopic.id,
368
+ title: newTopic.title,
369
+ statements_assigned: cluster.map(o => o.id)
370
+ });
371
+
372
+ // Update statements
373
+ for (const orphan of cluster) {
374
+ const stmtInMap = stmtMap.statements.find(s => s.id === orphan.id);
375
+ if (stmtInMap) {
376
+ stmtInMap.topic_id = newTopic.id;
377
+ stmtInMap.confidence = 0.7;
378
+ stmtInMap.association_reason = 'topic_clustering';
379
+ }
380
+
381
+ resolved.push({
382
+ id: orphan.id,
383
+ original_topic_id: null,
384
+ resolved_topic_id: newTopic.id,
385
+ resolution_method: 'topic_clustering',
386
+ confidence: 0.7
387
+ });
388
+
389
+ // Remove from stillOrphans
390
+ const idx = stillOrphans.findIndex(o => o.id === orphan.id);
391
+ if (idx >= 0) stillOrphans.splice(idx, 1);
392
+ }
393
+ }
394
+ }
395
+
396
+ // Third pass: assign remaining low-priority orphans to General
397
+ const veryLowConfidence = stillOrphans.filter(o =>
398
+ o.resolution_result === 'no_match' || o.confidence < 0.3
399
+ );
400
+
401
+ if (veryLowConfidence.length > 0) {
402
+ const general = ensureGeneralTopic(topics);
403
+
404
+ for (const orphan of veryLowConfidence) {
405
+ const stmtInMap = stmtMap.statements.find(s => s.id === orphan.id);
406
+ if (stmtInMap) {
407
+ stmtInMap.topic_id = general.id;
408
+ stmtInMap.confidence = 0.5;
409
+ stmtInMap.association_reason = 'general_assignment';
410
+ }
411
+
412
+ resolved.push({
413
+ id: orphan.id,
414
+ original_topic_id: null,
415
+ resolved_topic_id: general.id,
416
+ resolution_method: 'general_assignment',
417
+ confidence: 0.5
418
+ });
419
+
420
+ // Remove from stillOrphans
421
+ const idx = stillOrphans.findIndex(o => o.id === orphan.id);
422
+ if (idx >= 0) stillOrphans.splice(idx, 1);
423
+ }
424
+ }
425
+
426
+ // Update topics if new ones were created
427
+ if (newTopics.length > 0) {
428
+ _saveTopics({ topics });
429
+ }
430
+
431
+ // Save updated statement map
432
+ const meaningful = stmtMap.statements.filter(s => s.meaningful);
433
+ const mapped = meaningful.filter(s => s.topic_id !== null);
434
+ stmtMap.metadata.mapped_statements = mapped.length;
435
+ stmtMap.metadata.orphan_statements = stillOrphans.length;
436
+ stmtMap.metadata.coverage_percentage = meaningful.length > 0
437
+ ? Math.round((mapped.length / meaningful.length) * 100 * 10) / 10
438
+ : 0;
439
+
440
+ const mapPath = path.join(activeDigest.session.digest_path, 'statement-map.json');
441
+ fs.writeFileSync(mapPath, JSON.stringify(stmtMap, null, 2));
442
+
443
+ // Prepare result
444
+ const result = {
445
+ orphans: stillOrphans,
446
+ resolved,
447
+ new_topics_created: newTopics,
448
+ coverage: {
449
+ total_meaningful: meaningful.length,
450
+ mapped: mapped.length,
451
+ clarification_needed: stillOrphans.length,
452
+ percentage: stmtMap.metadata.coverage_percentage,
453
+ target: 100
454
+ }
455
+ };
456
+
457
+ saveOrphans(result);
458
+ _updatePhase('orphan_check', 'completed', {
459
+ orphans_resolved: resolved.length,
460
+ new_topics: newTopics.length,
461
+ remaining_orphans: stillOrphans.length
462
+ });
463
+
464
+ return result;
465
+ }
466
+
467
+ /**
468
+ * Process Pass 4: Contradiction Resolution
469
+ */
470
+ function runPass4() {
471
+ _requireInit('runPass4');
472
+ const activeDigest = _loadActiveDigest();
473
+ if (!activeDigest.session.digest_path) {
474
+ throw new Error('No active digest session');
475
+ }
476
+
477
+ // Load statement map
478
+ const stmtMap = _loadStatementMap();
479
+ if (!stmtMap) {
480
+ throw new Error('No statement map found - run Pass 2 first');
481
+ }
482
+
483
+ const contradictions = stmtMap.contradictions || [];
484
+ if (contradictions.length === 0) {
485
+ const result = {
486
+ resolved: [],
487
+ pending: [],
488
+ additive: [],
489
+ stats: {
490
+ total: 0,
491
+ auto_resolved: 0,
492
+ needs_clarification: 0,
493
+ additive_not_contradiction: 0
494
+ }
495
+ };
496
+ _updatePhase('contradiction_resolution', 'completed', result.stats);
497
+ return result;
498
+ }
499
+
500
+ // Update phase
501
+ _updatePhase('contradiction_resolution', 'in_progress');
502
+
503
+ const resolved = [];
504
+ const pending = [];
505
+ const additive = [];
506
+
507
+ // Load or create clarifications
508
+ let clarifications = _loadClarifications();
509
+
510
+ // Process each contradiction
511
+ for (const contradiction of contradictions) {
512
+ const stmt1 = stmtMap.statements.find(s => s.id === contradiction.statement1_id);
513
+ const stmt2 = stmtMap.statements.find(s => s.id === contradiction.statement2_id);
514
+
515
+ if (!stmt1 || !stmt2) continue;
516
+
517
+ const resolution = _calculateResolutionConfidence(stmt1, stmt2, contradiction);
518
+
519
+ if (resolution.isAdditive) {
520
+ // Not actually a contradiction
521
+ contradiction.resolution = 'not_contradiction';
522
+ contradiction.reason = 'additive_pattern';
523
+ additive.push({
524
+ statement1_id: contradiction.statement1_id,
525
+ statement2_id: contradiction.statement2_id,
526
+ reason: 'Both statements are valid (additive)'
527
+ });
528
+
529
+ // Remove from contradictions
530
+ continue;
531
+ }
532
+
533
+ if (resolution.confidence >= 0.8) {
534
+ // Auto-resolve
535
+ contradiction.resolution = 'auto_resolved';
536
+ contradiction.winner = resolution.winner;
537
+ contradiction.reason = resolution.reasons.join(',');
538
+ contradiction.confidence = resolution.confidence;
539
+ contradiction.resolved_at = _now();
540
+
541
+ // Mark loser as superseded
542
+ const loser = resolution.winner === stmt2.id ? stmt1 : stmt2;
543
+ const winner = resolution.winner === stmt2.id ? stmt2 : stmt1;
544
+
545
+ loser.superseded = true;
546
+ loser.superseded_by = winner.id;
547
+ loser.superseded_reason = resolution.reasons[0] || 'auto_resolved';
548
+
549
+ winner.supersedes = loser.id;
550
+ winner.is_correction = true;
551
+
552
+ resolved.push({
553
+ statement1_id: contradiction.statement1_id,
554
+ statement2_id: contradiction.statement2_id,
555
+ winner: resolution.winner,
556
+ confidence: resolution.confidence,
557
+ reason: resolution.reasons.join(',')
558
+ });
559
+ } else {
560
+ // Needs clarification
561
+ contradiction.resolution = 'clarification_needed';
562
+ contradiction.confidence = resolution.confidence;
563
+
564
+ const question = _generateContradictionQuestion(stmt1, stmt2, contradiction);
565
+
566
+ // Add to clarifications
567
+ const clarId = `c-${String(clarifications.contradictions.length + 1).padStart(3, '0')}`;
568
+ clarifications.contradictions.push({
569
+ id: clarId,
570
+ type: contradiction.type,
571
+ attribute: contradiction.attribute,
572
+ statements: [contradiction.statement1_id, contradiction.statement2_id],
573
+ topic_id: stmt1.topic_id || stmt2.topic_id,
574
+ question: question.question,
575
+ options: question.options,
576
+ status: 'pending',
577
+ created_at: _now()
578
+ });
579
+
580
+ contradiction.clarification_id = clarId;
581
+
582
+ pending.push({
583
+ statement1_id: contradiction.statement1_id,
584
+ statement2_id: contradiction.statement2_id,
585
+ clarification_id: clarId,
586
+ confidence: resolution.confidence
587
+ });
588
+ }
589
+ }
590
+
591
+ // Filter out additive patterns from contradictions list
592
+ stmtMap.contradictions = contradictions.filter(c => c.resolution !== 'not_contradiction');
593
+
594
+ // Update clarifications metadata
595
+ clarifications.metadata.total_contradictions = contradictions.length;
596
+ clarifications.metadata.auto_resolved_count = resolved.length;
597
+ clarifications.metadata.pending_questions = pending.length;
598
+
599
+ // Save updated files
600
+ const mapPath = path.join(activeDigest.session.digest_path, 'statement-map.json');
601
+ fs.writeFileSync(mapPath, JSON.stringify(stmtMap, null, 2));
602
+
603
+ _saveClarifications(clarifications);
604
+
605
+ const stats = {
606
+ total: contradictions.length,
607
+ auto_resolved: resolved.length,
608
+ needs_clarification: pending.length,
609
+ additive_not_contradiction: additive.length
610
+ };
611
+
612
+ _updatePhase('contradiction_resolution', 'completed', stats);
613
+
614
+ return {
615
+ resolved,
616
+ pending,
617
+ additive,
618
+ stats
619
+ };
620
+ }
621
+
622
+ /**
623
+ * Run the full 4-pass pipeline in one call.
624
+ *
625
+ * Chains: createSession -> runPass2 -> runPass3 -> runPass4
626
+ *
627
+ * Pass 1 (topic extraction) is handled by the AI via the command spec --
628
+ * the AI reads the confirmed statements and generates topics.json before
629
+ * calling this function. This function handles Passes 2-4.
630
+ *
631
+ * @param {Object} options
632
+ * @param {string} options.transcript - The full transcript text
633
+ * @param {Object[]} options.topics - Topics array (from AI or extractTopics)
634
+ * @param {string} options.contentType - Content type classification
635
+ * @returns {Object} Consolidated pipeline result
636
+ */
637
+ function runFullPipeline(options = {}) {
638
+ _requireInit('runFullPipeline');
639
+ const { transcript, topics, contentType } = options;
640
+
641
+ if (!transcript) throw new Error('transcript is required');
642
+ if (!topics || !topics.length) throw new Error('topics array is required');
643
+
644
+ // Step 1: Create session with transcript
645
+ const session = _createSession(transcript, { contentType: contentType || 'transcript' });
646
+ const digestId = session.id || session.session?.id;
647
+
648
+ // Step 2: Save the topics (normally done by AI in Pass 1)
649
+ const topicsData = {
650
+ topics: topics.map((t, i) => ({
651
+ id: t.id || `topic-${String(i + 1).padStart(3, '0')}`,
652
+ title: t.title,
653
+ keywords: t.keywords || [],
654
+ description: t.description || '',
655
+ priority: t.priority || 'medium',
656
+ statement_count: 0,
657
+ status: 'active'
658
+ })),
659
+ metadata: {
660
+ total_topics: topics.length,
661
+ active_topics: topics.length,
662
+ clarified_topics: 0,
663
+ generated_topics: 0,
664
+ detected_at: _now(),
665
+ last_updated: _now(),
666
+ transcript_word_count: _countWords(transcript),
667
+ detection_method: 'unified-pipeline'
668
+ }
669
+ };
670
+ _saveTopics(topicsData);
671
+ _updatePhase('topic_extraction', 'completed', { topics_found: topics.length });
672
+
673
+ // Step 3: Run Pass 2 -- statement mapping + contradiction detection
674
+ let pass2Result;
675
+ try {
676
+ pass2Result = runPass2();
677
+ } catch (err) {
678
+ return { error: `Pass 2 failed: ${err.message}`, phase: 'statement_mapping' };
679
+ }
680
+
681
+ // Step 4: Run Pass 3 -- orphan check and resolution
682
+ let pass3Result;
683
+ try {
684
+ pass3Result = runPass3();
685
+ } catch (err) {
686
+ return { error: `Pass 3 failed: ${err.message}`, phase: 'orphan_check' };
687
+ }
688
+
689
+ // Step 5: Run Pass 4 -- contradiction resolution
690
+ let pass4Result;
691
+ try {
692
+ pass4Result = runPass4();
693
+ } catch (err) {
694
+ return { error: `Pass 4 failed: ${err.message}`, phase: 'contradiction_resolution' };
695
+ }
696
+
697
+ // Step 6: Collect clarification questions (from contradictions + orphans)
698
+ const clarifications = _loadClarifications();
699
+ const pendingContradictions = (clarifications?.contradictions || [])
700
+ .filter(c => c.status === 'pending');
701
+
702
+ const orphanQuestions = (pass3Result?.orphans || [])
703
+ .filter(o => o.needs_clarification)
704
+ .map(o => ({
705
+ type: 'orphan',
706
+ statement_id: o.id,
707
+ question: o.clarification_question,
708
+ text: o.text
709
+ }));
710
+
711
+ const allClarificationQuestions = [
712
+ ...pendingContradictions.map(c => ({
713
+ type: 'contradiction',
714
+ id: c.id,
715
+ question: c.question,
716
+ options: c.options
717
+ })),
718
+ ...orphanQuestions
719
+ ];
720
+
721
+ // Step 7: Load final state for summary
722
+ const stmtMap = _loadStatementMap();
723
+ const finalTopics = _loadTopics();
724
+
725
+ return {
726
+ success: true,
727
+ digest_id: digestId,
728
+ summary: {
729
+ topics_count: finalTopics?.topics?.length || 0,
730
+ statements_total: stmtMap?.metadata?.total_statements || 0,
731
+ statements_meaningful: stmtMap?.metadata?.meaningful_statements || 0,
732
+ statements_mapped: stmtMap?.metadata?.mapped_statements || 0,
733
+ orphans_found: pass3Result?.orphans?.length || 0,
734
+ orphans_resolved: pass3Result?.resolved?.length || 0,
735
+ new_topics_created: pass3Result?.new_topics_created?.length || 0,
736
+ contradictions_total: pass4Result?.stats?.total || 0,
737
+ contradictions_auto_resolved: pass4Result?.stats?.auto_resolved || 0,
738
+ contradictions_needs_clarification: pass4Result?.stats?.needs_clarification || 0,
739
+ additive_not_contradiction: pass4Result?.stats?.additive_not_contradiction || 0,
740
+ coverage_percentage: pass3Result?.coverage?.percentage || 0
741
+ },
742
+ clarification_questions: allClarificationQuestions,
743
+ topics: finalTopics?.topics || [],
744
+ pass2: pass2Result,
745
+ pass3: pass3Result,
746
+ pass4: pass4Result
747
+ };
748
+ }
749
+
750
+ /**
751
+ * Quick process mode - single-pass extraction without interactive clarification.
752
+ * Used by the long input gate for fast feedback.
753
+ *
754
+ * @param {string} input - The input text to process
755
+ * @param {Object} _options - Processing options
756
+ * @returns {Object} Quick scan results
757
+ */
758
+ function quickProcess(input, _options = {}) {
759
+ if (!input || typeof input !== 'string') {
760
+ return { error: 'No input provided' };
761
+ }
762
+
763
+ const startTime = Date.now();
764
+
765
+ // 1. Split into statements (returns objects with .text property)
766
+ const statements = _splitIntoStatements(input);
767
+ // isMeaningfulStatement returns {meaningful: bool, reason: string}, filter on .meaningful
768
+ const meaningfulStatements = statements.filter(s => _isMeaningfulStatement(s.text).meaningful);
769
+
770
+ // 2. Quick topic extraction (keyword-based, no full analysis)
771
+ const topicKeywords = new Set();
772
+ const topicPatterns = [
773
+ /\b(add|create|build|implement)\s+(?:a\s+)?(\w+(?:\s+\w+)?)/gi,
774
+ /\b(\w+)\s+(feature|component|page|button|form|table|list)/gi,
775
+ /\b(user|admin|guest)\s+(?:can|should|must|wants?)\s+(\w+)/gi
776
+ ];
777
+
778
+ for (const statement of meaningfulStatements) {
779
+ const text = statement.text;
780
+ for (const pattern of topicPatterns) {
781
+ const matches = text.matchAll(pattern);
782
+ for (const match of matches) {
783
+ const keyword = (match[2] || match[1]).toLowerCase();
784
+ if (keyword.length > 2) {
785
+ topicKeywords.add(keyword);
786
+ }
787
+ }
788
+ }
789
+ }
790
+
791
+ // 3. Quick contradiction detection
792
+ const contradictions = [];
793
+ const seenValues = new Map(); // attribute -> { value, text }
794
+
795
+ const valuePatterns = [
796
+ { pattern: /(\d+)\s*(columns?|rows?|items?|pages?)/gi, attr: 'count' },
797
+ { pattern: /(primary|secondary|danger|success)\s*(?:color|button)/gi, attr: 'style' },
798
+ { pattern: /(left|right|center|top|bottom)/gi, attr: 'position' }
799
+ ];
800
+
801
+ for (const statement of meaningfulStatements) {
802
+ const text = statement.text;
803
+ for (const { pattern, attr } of valuePatterns) {
804
+ const match = text.match(pattern);
805
+ if (match && match[1]) {
806
+ const value = match[1].toLowerCase();
807
+ const key = `${attr}`;
808
+
809
+ if (seenValues.has(key) && seenValues.get(key).value !== value) {
810
+ // Check for correction phrase
811
+ const isCorrection = _detectCorrectionPhrase(text);
812
+
813
+ contradictions.push({
814
+ attribute: attr,
815
+ value1: seenValues.get(key).value,
816
+ value2: value,
817
+ statement1: seenValues.get(key).text.slice(0, 50),
818
+ statement2: text.slice(0, 50),
819
+ autoResolved: isCorrection,
820
+ resolution: isCorrection ? `Later statement (${value}) supersedes` : 'needs_review'
821
+ });
822
+ }
823
+
824
+ seenValues.set(key, { value, text });
825
+ }
826
+ }
827
+ }
828
+
829
+ const elapsed = Date.now() - startTime;
830
+
831
+ return {
832
+ mode: 'quick',
833
+ success: true,
834
+ metrics: {
835
+ totalStatements: statements.length,
836
+ meaningfulStatements: meaningfulStatements.length,
837
+ topicsDetected: topicKeywords.size,
838
+ contradictionsFound: contradictions.length,
839
+ autoResolved: contradictions.filter(c => c.autoResolved).length,
840
+ processingTimeMs: elapsed
841
+ },
842
+ topics: Array.from(topicKeywords),
843
+ contradictions: contradictions.filter(c => !c.autoResolved),
844
+ summary: generateQuickSummary(meaningfulStatements.length, topicKeywords.size, contradictions)
845
+ };
846
+ }
847
+
848
+ /**
849
+ * Generate human-readable summary for quick scan
850
+ */
851
+ function generateQuickSummary(statementCount, topicCount, contradictions) {
852
+ const unresolvedCount = contradictions.filter(c => !c.autoResolved).length;
853
+
854
+ let summary = `Quick scan complete: ${statementCount} statements, ${topicCount} topics detected.`;
855
+
856
+ if (contradictions.length > 0) {
857
+ const autoResolved = contradictions.filter(c => c.autoResolved).length;
858
+ summary += `\n${contradictions.length} potential contradictions found`;
859
+ if (autoResolved > 0) {
860
+ summary += ` (${autoResolved} auto-resolved as corrections)`;
861
+ }
862
+ if (unresolvedCount > 0) {
863
+ summary += `.\n${unresolvedCount} need review.`;
864
+ }
865
+ } else {
866
+ summary += '\nNo obvious contradictions detected.';
867
+ }
868
+
869
+ return summary;
870
+ }
871
+
872
+ module.exports = {
873
+ init,
874
+ extractKeyPhrase,
875
+ createTopicFromOrphans,
876
+ ensureGeneralTopic,
877
+ saveOrphans,
878
+ loadOrphans,
879
+ runPass2,
880
+ runPass3,
881
+ runPass4,
882
+ runFullPipeline,
883
+ quickProcess,
884
+ generateQuickSummary
885
+ };