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.
- package/.claude/commands/wogi-start.md +124 -0
- package/.claude/docs/claude-code-compatibility.md +51 -0
- package/.claude/docs/explore-agents.md +11 -0
- package/.claude/settings.json +12 -1
- package/.workflow/models/registry.json +1 -1
- package/bin/flow +11 -1
- package/lib/workspace-contracts.js +599 -0
- package/lib/workspace-intelligence.js +600 -0
- package/lib/workspace-messages.js +441 -0
- package/lib/workspace-routing.js +485 -0
- package/lib/workspace-sync.js +339 -0
- package/lib/workspace.js +1073 -0
- package/package.json +4 -4
- package/scripts/MEMORY-ARCHITECTURE.md +1 -1
- package/scripts/base-workflow-step.js +136 -0
- package/scripts/flow-adaptive-learning.js +8 -9
- package/scripts/flow-aggregate.js +11 -6
- package/scripts/flow-api-index.js +4 -6
- package/scripts/flow-assumption-detector.js +0 -2
- package/scripts/flow-audit.js +15 -2
- package/scripts/flow-auto-context.js +8 -12
- package/scripts/flow-auto-learn.js +49 -49
- package/scripts/flow-background.js +5 -6
- package/scripts/flow-bridge-state.js +8 -10
- package/scripts/flow-bulk-loop.js +1 -3
- package/scripts/flow-bulk-orchestrator.js +1 -3
- package/scripts/flow-cascade-completion.js +0 -2
- package/scripts/flow-cascade.js +4 -4
- package/scripts/flow-checkpoint.js +10 -13
- package/scripts/flow-code-intelligence.js +10 -12
- package/scripts/flow-community-sync.js +4 -4
- package/scripts/flow-community.js +12 -20
- package/scripts/flow-config-defaults.js +28 -2
- package/scripts/flow-config-interactive.js +9 -5
- package/scripts/flow-config-loader.js +49 -92
- package/scripts/flow-config-substitution.js +0 -2
- package/scripts/flow-context-estimator.js +4 -4
- package/scripts/flow-context-init.js +10 -12
- package/scripts/flow-context-manager.js +0 -2
- package/scripts/flow-context-scoring.js +2 -2
- package/scripts/flow-contract-scan.js +6 -9
- package/scripts/flow-correct.js +29 -27
- package/scripts/flow-correction-detector.js +5 -1
- package/scripts/flow-damage-control.js +47 -54
- package/scripts/flow-decisions-merge.js +4 -14
- package/scripts/flow-diff.js +5 -8
- package/scripts/flow-done-gates.js +786 -0
- package/scripts/flow-done-report.js +123 -0
- package/scripts/flow-done.js +71 -717
- package/scripts/flow-entropy-monitor.js +1 -3
- package/scripts/flow-eval-calibration.js +257 -0
- package/scripts/flow-eval-judge.js +10 -1
- package/scripts/flow-eval.js +14 -5
- package/scripts/flow-extraction-review.js +1 -0
- package/scripts/flow-failure-categories.js +0 -2
- package/scripts/flow-figma-confirm.js +5 -9
- package/scripts/flow-figma-generate.js +8 -10
- package/scripts/flow-figma-index.js +8 -10
- package/scripts/flow-figma-match.js +3 -5
- package/scripts/flow-figma-mcp-server.js +2 -4
- package/scripts/flow-figma-orchestrator.js +2 -3
- package/scripts/flow-figma-registry.js +2 -3
- package/scripts/flow-framework-resolver.js +0 -2
- package/scripts/flow-function-index.js +4 -6
- package/scripts/flow-gate-confidence.js +2 -2
- package/scripts/flow-gitignore.js +0 -2
- package/scripts/flow-guided-edit.js +5 -6
- package/scripts/flow-health.js +5 -6
- package/scripts/flow-hook-errors.js +6 -0
- package/scripts/flow-hook-status.js +263 -0
- package/scripts/flow-hooks.js +17 -29
- package/scripts/flow-http-client.js +9 -8
- package/scripts/flow-hybrid-interactive.js +7 -12
- package/scripts/flow-hybrid-test.js +12 -13
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-io.js +21 -4
- package/scripts/flow-knowledge-router.js +9 -3
- package/scripts/flow-learning-orchestrator.js +318 -13
- package/scripts/flow-links.js +5 -7
- package/scripts/flow-long-input-association.js +275 -0
- package/scripts/flow-long-input-chunking.js +1 -0
- package/scripts/flow-long-input-cli.js +0 -2
- package/scripts/flow-long-input-complexity.js +0 -2
- package/scripts/flow-long-input-constants.js +0 -2
- package/scripts/flow-long-input-contradictions.js +351 -0
- package/scripts/flow-long-input-detection.js +0 -2
- package/scripts/flow-long-input-passes.js +885 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-long-input-voice.js +0 -2
- package/scripts/flow-long-input.js +425 -3005
- package/scripts/flow-loop-retry-learning.js +2 -3
- package/scripts/flow-lsp.js +3 -3
- package/scripts/flow-mcp-docs.js +3 -4
- package/scripts/flow-memory-db.js +6 -8
- package/scripts/flow-memory-sync.js +18 -11
- package/scripts/flow-metrics.js +1 -2
- package/scripts/flow-model-adapter.js +2 -3
- package/scripts/flow-model-config.js +72 -104
- package/scripts/flow-model-router.js +2 -2
- package/scripts/flow-model-types.js +0 -2
- package/scripts/flow-multi-approach.js +5 -6
- package/scripts/flow-orchestrate-context.js +3 -7
- package/scripts/flow-orchestrate-rollback.js +3 -8
- package/scripts/flow-orchestrate-state.js +8 -14
- package/scripts/flow-orchestrate-templates.js +2 -6
- package/scripts/flow-orchestrate-validator.js +5 -9
- package/scripts/flow-orchestrate.js +126 -103
- package/scripts/flow-output.js +0 -2
- package/scripts/flow-parallel.js +1 -1
- package/scripts/flow-paths.js +23 -2
- package/scripts/flow-pattern-enforcer.js +30 -28
- package/scripts/flow-pattern-extractor.js +3 -4
- package/scripts/flow-pending.js +0 -2
- package/scripts/flow-permissions.js +2 -3
- package/scripts/flow-plugin-registry.js +10 -12
- package/scripts/flow-prd-manager.js +1 -1
- package/scripts/flow-progress.js +7 -9
- package/scripts/flow-prompt-composer.js +3 -3
- package/scripts/flow-prompt-template.js +2 -2
- package/scripts/flow-providers.js +7 -4
- package/scripts/flow-registry-manager.js +7 -12
- package/scripts/flow-regression.js +9 -11
- package/scripts/flow-roadmap.js +2 -2
- package/scripts/flow-run-trace.js +16 -15
- package/scripts/flow-safety.js +2 -5
- package/scripts/flow-scanner-base.js +5 -7
- package/scripts/flow-scenario-engine.js +1 -5
- package/scripts/flow-security.js +29 -0
- package/scripts/flow-session-end.js +32 -41
- package/scripts/flow-session-learning.js +53 -49
- package/scripts/flow-setup-hooks.js +2 -3
- package/scripts/flow-skill-create.js +7 -12
- package/scripts/flow-skill-generator.js +12 -16
- package/scripts/flow-skill-learn.js +17 -8
- package/scripts/flow-skill-matcher.js +1 -2
- package/scripts/flow-spec-generator.js +2 -4
- package/scripts/flow-stack-wizard.js +5 -7
- package/scripts/flow-standards-learner.js +35 -16
- package/scripts/flow-start.js +2 -0
- package/scripts/flow-stats-collector.js +2 -2
- package/scripts/flow-status.js +10 -10
- package/scripts/flow-statusline-setup.js +2 -2
- package/scripts/flow-step-changelog.js +2 -3
- package/scripts/flow-step-comments.js +66 -81
- package/scripts/flow-step-complexity.js +50 -70
- package/scripts/flow-step-coverage.js +3 -5
- package/scripts/flow-step-knowledge.js +2 -3
- package/scripts/flow-step-pr-tests.js +64 -74
- package/scripts/flow-step-regression.js +3 -5
- package/scripts/flow-step-review.js +86 -103
- package/scripts/flow-step-security.js +111 -121
- package/scripts/flow-step-silent-failures.js +56 -83
- package/scripts/flow-step-simplifier.js +52 -70
- package/scripts/flow-story.js +4 -7
- package/scripts/flow-strict-adherence.js +3 -4
- package/scripts/flow-task-checkpoint.js +36 -5
- package/scripts/flow-task-enforcer.js +2 -24
- package/scripts/flow-tech-debt.js +1 -1
- package/scripts/flow-template-extractor.js +1 -0
- package/scripts/flow-templates.js +11 -13
- package/scripts/flow-test-api.js +9 -13
- package/scripts/flow-test-discovery.js +1 -1
- package/scripts/flow-test-generate.js +5 -9
- package/scripts/flow-test-integrity.js +3 -7
- package/scripts/flow-test-ui.js +5 -9
- package/scripts/flow-testing-deps.js +1 -3
- package/scripts/flow-tiered-learning.js +4 -4
- package/scripts/flow-todowrite-sync.js +1 -1
- package/scripts/flow-tokens.js +0 -2
- package/scripts/flow-verification-profile.js +6 -10
- package/scripts/flow-verify.js +12 -16
- package/scripts/flow-version-check.js +4 -12
- package/scripts/flow-webmcp-generator.js +3 -5
- package/scripts/flow-workflow-steps.js +0 -2
- package/scripts/flow-workflow.js +9 -11
- package/scripts/hooks/adapters/claude-code.js +31 -0
- package/scripts/hooks/core/config-change.js +1 -0
- package/scripts/hooks/core/extension-registry.js +0 -2
- package/scripts/hooks/core/instructions-loaded.js +1 -1
- package/scripts/hooks/core/observation-capture.js +5 -5
- package/scripts/hooks/core/phase-gate.js +5 -0
- package/scripts/hooks/core/post-compact.js +1 -12
- package/scripts/hooks/core/research-gate.js +2 -12
- package/scripts/hooks/core/routing-gate.js +6 -0
- package/scripts/hooks/core/task-completed.js +12 -0
- package/scripts/hooks/core/task-created.js +83 -0
- package/scripts/hooks/core/worktree-lifecycle.js +1 -1
- package/scripts/hooks/entry/claude-code/config-change.js +6 -29
- package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
- package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
- package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
- package/scripts/hooks/entry/claude-code/session-end.js +4 -28
- package/scripts/hooks/entry/claude-code/session-start.js +205 -243
- package/scripts/hooks/entry/claude-code/setup.js +8 -49
- package/scripts/hooks/entry/claude-code/stop.js +40 -72
- package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
- package/scripts/hooks/entry/claude-code/task-created.js +15 -0
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
- package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
- package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
- package/scripts/hooks/entry/shared/hook-runner.js +99 -0
- package/scripts/hooks/entry/shared/read-stdin.js +0 -2
- package/scripts/postinstall.js +2 -0
- package/scripts/registries/api-registry.js +0 -2
- package/scripts/registries/component-registry.js +5 -9
- package/scripts/registries/contract-scanner.js +2 -9
- package/scripts/registries/function-registry.js +0 -2
- package/scripts/registries/schema-registry.js +14 -18
- 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
|
+
};
|