wogiflow 2.4.2 → 2.4.3
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/docs/claude-code-compatibility.md +27 -0
- package/.claude/settings.json +1 -1
- package/.workflow/models/registry.json +1 -1
- 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 +0 -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.js +5 -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 +2 -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/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/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/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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Long Input Processing - Multi-pass extraction system
|
|
4
|
+
* Long Input Processing - Multi-pass extraction system (Orchestrator)
|
|
5
5
|
*
|
|
6
6
|
* Ensures nothing is missed from long/complex inputs (transcripts, prompts,
|
|
7
7
|
* specs, documents). Uses a 4-pass extraction system:
|
|
@@ -10,105 +10,72 @@
|
|
|
10
10
|
* Pass 3: Orphan check
|
|
11
11
|
* Pass 4: Contradiction resolution
|
|
12
12
|
*
|
|
13
|
+
* This file is the orchestrator — domain logic is in:
|
|
14
|
+
* flow-long-input-association.js — statement splitting, association, statement map I/O
|
|
15
|
+
* flow-long-input-contradictions.js — contradiction detection, orphan resolution, clarification I/O
|
|
16
|
+
* flow-long-input-passes.js — runPass2/3/4, runFullPipeline, quickProcess
|
|
17
|
+
* flow-long-input-parsing.js — VTT/SRT/meeting parsing
|
|
18
|
+
* flow-long-input-language.js — language detection
|
|
19
|
+
* flow-long-input-stories.js — story generation, presentation, editing, export
|
|
20
|
+
* flow-long-input-chunking.js — durable sessions, chunking
|
|
21
|
+
* flow-long-input-constants.js — shared patterns and constants
|
|
22
|
+
* flow-long-input-voice.js — voice input processing
|
|
23
|
+
* flow-long-input-detection.js — large input detection, content classification
|
|
24
|
+
* flow-long-input-complexity.js — complexity scoring
|
|
25
|
+
*
|
|
13
26
|
* Renamed from flow-transcript-digest.js in v1.8.0
|
|
14
27
|
*/
|
|
15
28
|
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const { estimateTokens, generateHashId, getConfig } = require('./flow-utils');
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
const { estimateTokens, generateHashId, getConfig, safeJsonParse, PATHS } = require('./flow-utils');
|
|
19
32
|
const { success: printSuccess, warn: printWarn } = require('./flow-output');
|
|
20
33
|
|
|
21
|
-
// Import extracted modules
|
|
34
|
+
// Import extracted sub-modules
|
|
22
35
|
const transcriptParsing = require('./flow-long-input-parsing');
|
|
23
36
|
const transcriptLanguage = require('./flow-long-input-language');
|
|
24
37
|
const transcriptStories = require('./flow-long-input-stories');
|
|
25
38
|
const transcriptChunking = require('./flow-long-input-chunking');
|
|
26
|
-
|
|
27
|
-
// Import extracted constant/function modules
|
|
28
39
|
const longInputConstants = require('./flow-long-input-constants');
|
|
29
40
|
const longInputVoice = require('./flow-long-input-voice');
|
|
30
41
|
const longInputDetection = require('./flow-long-input-detection');
|
|
31
42
|
const longInputComplexity = require('./flow-long-input-complexity');
|
|
43
|
+
const longInputAssociation = require('./flow-long-input-association');
|
|
44
|
+
const longInputContradictions = require('./flow-long-input-contradictions');
|
|
45
|
+
const longInputPasses = require('./flow-long-input-passes');
|
|
32
46
|
|
|
33
|
-
// Destructure
|
|
47
|
+
// Destructure language functions
|
|
34
48
|
const {
|
|
35
|
-
detectLanguage,
|
|
36
|
-
detectMultipleLanguages,
|
|
37
|
-
getLanguageInfo,
|
|
38
|
-
LANGUAGE_INFO
|
|
49
|
+
detectLanguage, detectMultipleLanguages, getLanguageInfo, LANGUAGE_INFO
|
|
39
50
|
} = transcriptLanguage;
|
|
40
51
|
|
|
41
|
-
// Destructure
|
|
52
|
+
// Destructure parsing functions
|
|
42
53
|
const {
|
|
43
|
-
parseVTT,
|
|
44
|
-
|
|
45
|
-
parseSubtitle,
|
|
46
|
-
mergeCues,
|
|
47
|
-
formatCuesAsText,
|
|
48
|
-
getSubtitleStats,
|
|
49
|
-
parseZoom,
|
|
50
|
-
parseTeams,
|
|
51
|
-
parseMeeting,
|
|
52
|
-
mergeMeetingEntries,
|
|
53
|
-
formatMeetingAsText,
|
|
54
|
-
getMeetingStats
|
|
54
|
+
parseVTT, parseSRT, parseSubtitle, mergeCues, formatCuesAsText, getSubtitleStats,
|
|
55
|
+
parseZoom, parseTeams, parseMeeting, mergeMeetingEntries, formatMeetingAsText, getMeetingStats
|
|
55
56
|
} = transcriptParsing;
|
|
56
57
|
|
|
57
|
-
// Destructure
|
|
58
|
+
// Destructure chunking functions
|
|
58
59
|
const {
|
|
59
|
-
loadDurableSessions,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
switchDurableSession,
|
|
63
|
-
archiveDurableSession,
|
|
64
|
-
deleteDurableSession,
|
|
65
|
-
generateRecoverySummaryForSession,
|
|
66
|
-
getTimeSince,
|
|
67
|
-
needsChunking,
|
|
68
|
-
planChunks,
|
|
69
|
-
getChunkingStatus
|
|
60
|
+
loadDurableSessions, listDurableSessions, getDurableSession, switchDurableSession,
|
|
61
|
+
archiveDurableSession, deleteDurableSession, generateRecoverySummaryForSession,
|
|
62
|
+
getTimeSince, needsChunking, planChunks, getChunkingStatus
|
|
70
63
|
} = transcriptChunking;
|
|
71
64
|
|
|
72
|
-
// Destructure additional language utilities
|
|
73
65
|
const { listSupportedLanguages } = transcriptLanguage;
|
|
74
66
|
|
|
75
|
-
// Destructure
|
|
67
|
+
// Destructure story functions
|
|
76
68
|
const {
|
|
77
|
-
generateStoryFromTopic,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
getPresentationStatus,
|
|
85
|
-
getNextStory,
|
|
86
|
-
getCurrentStory,
|
|
87
|
-
approveCurrentStory,
|
|
88
|
-
rejectCurrentStory,
|
|
89
|
-
skipCurrentStory,
|
|
90
|
-
formatStorySummary,
|
|
91
|
-
formatActionsPrompt,
|
|
92
|
-
getCompletionSummary,
|
|
93
|
-
resetPresentation,
|
|
94
|
-
// Edit session functions
|
|
95
|
-
startEditSession,
|
|
96
|
-
editUserStory,
|
|
97
|
-
editCriterion,
|
|
98
|
-
addCriterion,
|
|
99
|
-
removeCriterion,
|
|
100
|
-
getEditChanges,
|
|
101
|
-
commitEditSession,
|
|
102
|
-
cancelEditSession,
|
|
103
|
-
getEditHistory,
|
|
104
|
-
listEditableStories,
|
|
105
|
-
// Export functions
|
|
106
|
-
previewExport,
|
|
107
|
-
exportApprovedStories,
|
|
108
|
-
finalizeDigestion
|
|
69
|
+
generateStoryFromTopic, generateAllStories, saveStory, loadStory, loadAllStories,
|
|
70
|
+
formatStoryAsMarkdown, getPresentationStatus, getNextStory, getCurrentStory,
|
|
71
|
+
approveCurrentStory, rejectCurrentStory, skipCurrentStory, formatStorySummary,
|
|
72
|
+
formatActionsPrompt, getCompletionSummary, resetPresentation,
|
|
73
|
+
startEditSession, editUserStory, editCriterion, addCriterion, removeCriterion,
|
|
74
|
+
getEditChanges, commitEditSession, cancelEditSession, getEditHistory, listEditableStories,
|
|
75
|
+
previewExport, exportApprovedStories, finalizeDigestion
|
|
109
76
|
} = transcriptStories;
|
|
110
77
|
|
|
111
|
-
// Destructure constants
|
|
78
|
+
// Destructure constants
|
|
112
79
|
const {
|
|
113
80
|
FILLER_PATTERNS, REQUIREMENT_PATTERNS, SEMANTIC_EXPANSIONS,
|
|
114
81
|
CORRECTION_PATTERNS, ADDITIVE_PATTERNS,
|
|
@@ -117,14 +84,14 @@ const {
|
|
|
117
84
|
UI_PATTERNS, DATA_PATTERNS, INTERACTION_PATTERNS, COMPLEXITY_LEVELS
|
|
118
85
|
} = longInputConstants;
|
|
119
86
|
|
|
120
|
-
// Destructure voice
|
|
87
|
+
// Destructure voice functions
|
|
121
88
|
const {
|
|
122
89
|
isVoiceInput, removeFillers, applySelfCorrections, normalizeNumbers,
|
|
123
90
|
detectUncertainty, detectYesNo, addPunctuation, normalizeVoiceInput,
|
|
124
91
|
calculateVoiceConfidence, processVoiceAnswer
|
|
125
92
|
} = longInputVoice;
|
|
126
93
|
|
|
127
|
-
// Destructure detection functions
|
|
94
|
+
// Destructure detection functions
|
|
128
95
|
const {
|
|
129
96
|
measureInputMetrics, isVTTFormat, isSRTFormat, detectMeetingFormat,
|
|
130
97
|
detectInputFormat, analyzeInput, evaluateTrigger,
|
|
@@ -133,7 +100,7 @@ const {
|
|
|
133
100
|
getDetailedClassification, shouldExcludeContent
|
|
134
101
|
} = longInputDetection;
|
|
135
102
|
|
|
136
|
-
// Destructure complexity functions
|
|
103
|
+
// Destructure complexity functions
|
|
137
104
|
const {
|
|
138
105
|
countEntityTypes, extractEntities, getComplexityLevel,
|
|
139
106
|
calculateComplexityScore, isRequirement, isVagueStatement,
|
|
@@ -142,24 +109,35 @@ const {
|
|
|
142
109
|
generateEpicStructure, recommendOutputStructure
|
|
143
110
|
} = longInputComplexity;
|
|
144
111
|
|
|
112
|
+
// Destructure association functions
|
|
113
|
+
const {
|
|
114
|
+
isMeaningfulStatement, splitIntoStatements, calculateAssociationConfidence,
|
|
115
|
+
associateStatements, saveStatementMap, loadStatementMap
|
|
116
|
+
} = longInputAssociation;
|
|
117
|
+
|
|
118
|
+
// Destructure contradiction functions
|
|
119
|
+
const {
|
|
120
|
+
detectContradictions, resolveOrphan, detectCorrectionPhrase, isAdditive,
|
|
121
|
+
calculateResolutionConfidence, generateContradictionQuestion,
|
|
122
|
+
saveClarifications, loadClarifications
|
|
123
|
+
} = longInputContradictions;
|
|
124
|
+
|
|
125
|
+
// Destructure passes functions
|
|
126
|
+
const {
|
|
127
|
+
extractKeyPhrase, createTopicFromOrphans, ensureGeneralTopic,
|
|
128
|
+
saveOrphans, loadOrphans, runPass2, runPass3, runPass4,
|
|
129
|
+
runFullPipeline, quickProcess, generateQuickSummary
|
|
130
|
+
} = longInputPasses;
|
|
131
|
+
|
|
145
132
|
// Paths - temp processing files go to .workflow/tmp/, cleaned up after completion
|
|
146
133
|
const TMP_DIR = path.join(process.cwd(), '.workflow', 'tmp', 'long-input');
|
|
147
134
|
const STATE_DIR = TMP_DIR; // Alias for backward compatibility during migration
|
|
148
135
|
const ACTIVE_DIGEST_FILE = path.join(TMP_DIR, 'active-digest.json');
|
|
149
|
-
// Colors for CLI output
|
|
150
|
-
const c = {
|
|
151
|
-
reset: '\x1b[0m',
|
|
152
|
-
dim: '\x1b[2m',
|
|
153
|
-
green: '\x1b[32m',
|
|
154
|
-
yellow: '\x1b[33m',
|
|
155
|
-
blue: '\x1b[34m',
|
|
156
|
-
cyan: '\x1b[36m',
|
|
157
|
-
red: '\x1b[31m'
|
|
158
|
-
};
|
|
159
136
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
137
|
+
// ============================================
|
|
138
|
+
// Core session/utility functions (stay here)
|
|
139
|
+
// ============================================
|
|
140
|
+
|
|
163
141
|
function loadConfig() {
|
|
164
142
|
try {
|
|
165
143
|
const config = getConfig();
|
|
@@ -169,34 +147,22 @@ function loadConfig() {
|
|
|
169
147
|
}
|
|
170
148
|
}
|
|
171
149
|
|
|
172
|
-
/**
|
|
173
|
-
* Generate unique digest ID
|
|
174
|
-
*/
|
|
175
150
|
function generateDigestId() {
|
|
176
151
|
return generateHashId('digest', '', '');
|
|
177
152
|
}
|
|
178
153
|
|
|
179
|
-
/**
|
|
180
|
-
* Get current timestamp in ISO format
|
|
181
|
-
*/
|
|
182
154
|
function now() {
|
|
183
155
|
return new Date().toISOString();
|
|
184
156
|
}
|
|
185
157
|
|
|
186
|
-
/**
|
|
187
|
-
* Load active digest session
|
|
188
|
-
*/
|
|
189
158
|
function loadActiveDigest() {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
} catch (_err) {
|
|
159
|
+
const data = safeJsonParse(ACTIVE_DIGEST_FILE, null);
|
|
160
|
+
if (!data || !data.session) {
|
|
193
161
|
return { session: { status: 'inactive' } };
|
|
194
162
|
}
|
|
163
|
+
return data;
|
|
195
164
|
}
|
|
196
165
|
|
|
197
|
-
/**
|
|
198
|
-
* Save active digest session
|
|
199
|
-
*/
|
|
200
166
|
function saveActiveDigest(data) {
|
|
201
167
|
const content = JSON.stringify(data, null, 2);
|
|
202
168
|
const tmpPath = ACTIVE_DIGEST_FILE + '.tmp';
|
|
@@ -204,105 +170,63 @@ function saveActiveDigest(data) {
|
|
|
204
170
|
fs.renameSync(tmpPath, ACTIVE_DIGEST_FILE);
|
|
205
171
|
}
|
|
206
172
|
|
|
207
|
-
/**
|
|
208
|
-
* Create new digest session
|
|
209
|
-
*/
|
|
210
173
|
function createSession(transcript, options = {}) {
|
|
211
174
|
const digestId = generateDigestId();
|
|
212
|
-
const digestPath = path.join(
|
|
175
|
+
const digestPath = path.join(PATHS.state, digestId);
|
|
213
176
|
|
|
214
|
-
// Create digest directory
|
|
215
177
|
fs.mkdirSync(digestPath, { recursive: true });
|
|
216
|
-
|
|
217
|
-
// Save transcript
|
|
218
178
|
fs.writeFileSync(path.join(digestPath, 'transcript.md'), transcript);
|
|
219
179
|
|
|
220
|
-
// Initialize topics.json
|
|
221
180
|
const topics = {
|
|
222
181
|
topics: [],
|
|
223
182
|
metadata: {
|
|
224
|
-
total_topics: 0,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
generated_topics: 0,
|
|
228
|
-
detected_at: null,
|
|
229
|
-
last_updated: now(),
|
|
230
|
-
transcript_word_count: countWords(transcript),
|
|
231
|
-
detection_method: 'pass-1-extraction'
|
|
183
|
+
total_topics: 0, active_topics: 0, clarified_topics: 0, generated_topics: 0,
|
|
184
|
+
detected_at: null, last_updated: now(),
|
|
185
|
+
transcript_word_count: countWords(transcript), detection_method: 'pass-1-extraction'
|
|
232
186
|
}
|
|
233
187
|
};
|
|
234
188
|
fs.writeFileSync(path.join(digestPath, 'topics.json'), JSON.stringify(topics, null, 2));
|
|
235
189
|
|
|
236
|
-
// Initialize statement-map.json
|
|
237
190
|
const statementMap = {
|
|
238
191
|
statements: [],
|
|
239
192
|
metadata: {
|
|
240
|
-
total_statements: 0,
|
|
241
|
-
|
|
242
|
-
mapped_statements: 0,
|
|
243
|
-
orphan_statements: 0,
|
|
244
|
-
contradictions_detected: 0,
|
|
245
|
-
contradictions_resolved: 0,
|
|
193
|
+
total_statements: 0, meaningful_statements: 0, mapped_statements: 0,
|
|
194
|
+
orphan_statements: 0, contradictions_detected: 0, contradictions_resolved: 0,
|
|
246
195
|
coverage_percentage: 0
|
|
247
196
|
}
|
|
248
197
|
};
|
|
249
198
|
fs.writeFileSync(path.join(digestPath, 'statement-map.json'), JSON.stringify(statementMap, null, 2));
|
|
250
199
|
|
|
251
|
-
// Initialize clarifications.json
|
|
252
200
|
const clarifications = {
|
|
253
|
-
questions: [],
|
|
254
|
-
contradictions: [],
|
|
201
|
+
questions: [], contradictions: [],
|
|
255
202
|
metadata: {
|
|
256
|
-
total_questions: 0,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
total_contradictions: 0,
|
|
260
|
-
resolved_contradictions: 0,
|
|
261
|
-
auto_resolved_count: 0,
|
|
262
|
-
user_resolved_count: 0
|
|
203
|
+
total_questions: 0, answered_questions: 0, pending_questions: 0,
|
|
204
|
+
total_contradictions: 0, resolved_contradictions: 0,
|
|
205
|
+
auto_resolved_count: 0, user_resolved_count: 0
|
|
263
206
|
}
|
|
264
207
|
};
|
|
265
208
|
fs.writeFileSync(path.join(digestPath, 'clarifications.json'), JSON.stringify(clarifications, null, 2));
|
|
266
209
|
|
|
267
|
-
// Initialize conversation.json (E2-S4)
|
|
268
210
|
const conversation = {
|
|
269
|
-
session_id: digestId,
|
|
270
|
-
started_at: now(),
|
|
271
|
-
last_interaction: now(),
|
|
211
|
+
session_id: digestId, started_at: now(), last_interaction: now(),
|
|
272
212
|
interactions: [{
|
|
273
|
-
id: `i-${Date.now().toString(36)}`,
|
|
274
|
-
|
|
275
|
-
timestamp: now(),
|
|
276
|
-
data: {
|
|
277
|
-
word_count: countWords(transcript),
|
|
278
|
-
content_type: options.contentType || 'unknown'
|
|
279
|
-
}
|
|
213
|
+
id: `i-${Date.now().toString(36)}`, type: 'session_started', timestamp: now(),
|
|
214
|
+
data: { word_count: countWords(transcript), content_type: options.contentType || 'unknown' }
|
|
280
215
|
}],
|
|
281
216
|
checkpoints: []
|
|
282
217
|
};
|
|
283
218
|
fs.writeFileSync(path.join(digestPath, 'conversation.json'), JSON.stringify(conversation, null, 2));
|
|
284
219
|
|
|
285
|
-
// Initialize orphans.json
|
|
286
220
|
const orphans = {
|
|
287
221
|
orphans: [],
|
|
288
|
-
coverage: {
|
|
289
|
-
total_meaningful: 0,
|
|
290
|
-
mapped: 0,
|
|
291
|
-
orphans_remaining: 0,
|
|
292
|
-
percentage: 0
|
|
293
|
-
}
|
|
222
|
+
coverage: { total_meaningful: 0, mapped: 0, orphans_remaining: 0, percentage: 0 }
|
|
294
223
|
};
|
|
295
224
|
fs.writeFileSync(path.join(digestPath, 'orphans.json'), JSON.stringify(orphans, null, 2));
|
|
296
225
|
|
|
297
|
-
// Update active digest
|
|
298
226
|
const activeDigest = {
|
|
299
227
|
session: {
|
|
300
|
-
id: digestId,
|
|
301
|
-
|
|
302
|
-
last_activity: now(),
|
|
303
|
-
status: 'active',
|
|
304
|
-
phase: 'ingestion',
|
|
305
|
-
digest_path: digestPath
|
|
228
|
+
id: digestId, started_at: now(), last_activity: now(),
|
|
229
|
+
status: 'active', phase: 'ingestion', digest_path: digestPath
|
|
306
230
|
},
|
|
307
231
|
phases: {
|
|
308
232
|
ingestion: { status: 'completed', started_at: now(), completed_at: now() },
|
|
@@ -315,3179 +239,691 @@ function createSession(transcript, options = {}) {
|
|
|
315
239
|
approval: { status: 'pending', started_at: null, completed_at: null, stories_approved: 0, stories_pending: 0, current_story_index: 0 }
|
|
316
240
|
},
|
|
317
241
|
input: {
|
|
318
|
-
source: options.source || 'paste',
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
word_count: countWords(transcript),
|
|
322
|
-
chunked: false,
|
|
323
|
-
chunk_count: 0
|
|
242
|
+
source: options.source || 'paste', format: options.format || 'plain',
|
|
243
|
+
language: options.language || null, word_count: countWords(transcript),
|
|
244
|
+
chunked: false, chunk_count: 0
|
|
324
245
|
},
|
|
325
|
-
output: {
|
|
326
|
-
stories_created: [],
|
|
327
|
-
tasks_added_to_ready: []
|
|
328
|
-
}
|
|
246
|
+
output: { stories_created: [], tasks_added_to_ready: [] }
|
|
329
247
|
};
|
|
330
248
|
|
|
331
249
|
saveActiveDigest(activeDigest);
|
|
332
|
-
|
|
333
250
|
return { digestId, digestPath, activeDigest };
|
|
334
251
|
}
|
|
335
252
|
|
|
336
|
-
/**
|
|
337
|
-
* Count words in text
|
|
338
|
-
*/
|
|
339
253
|
function countWords(text) {
|
|
340
254
|
return text.split(/\s+/).filter(w => w.length > 0).length;
|
|
341
255
|
}
|
|
342
256
|
|
|
343
|
-
/**
|
|
344
|
-
* Update phase status
|
|
345
|
-
*/
|
|
346
257
|
function updatePhase(phase, status, data = {}) {
|
|
347
258
|
const activeDigest = loadActiveDigest();
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
console.error('No active digest session');
|
|
351
|
-
return null;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
activeDigest.phases[phase] = {
|
|
355
|
-
...activeDigest.phases[phase],
|
|
356
|
-
status,
|
|
357
|
-
...data
|
|
358
|
-
};
|
|
359
|
-
|
|
259
|
+
if (!activeDigest.phases) { console.error('No active digest session'); return null; }
|
|
260
|
+
activeDigest.phases[phase] = { ...activeDigest.phases[phase], status, ...data };
|
|
360
261
|
if (status === 'in_progress' && !activeDigest.phases[phase].started_at) {
|
|
361
262
|
activeDigest.phases[phase].started_at = now();
|
|
362
263
|
}
|
|
363
|
-
|
|
364
|
-
if (status === 'completed') {
|
|
365
|
-
activeDigest.phases[phase].completed_at = now();
|
|
366
|
-
}
|
|
367
|
-
|
|
264
|
+
if (status === 'completed') { activeDigest.phases[phase].completed_at = now(); }
|
|
368
265
|
activeDigest.session.last_activity = now();
|
|
369
266
|
activeDigest.session.phase = phase;
|
|
370
|
-
|
|
371
267
|
saveActiveDigest(activeDigest);
|
|
372
268
|
return activeDigest;
|
|
373
269
|
}
|
|
374
270
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Check for requirement signals
|
|
392
|
-
for (const pattern of REQUIREMENT_PATTERNS) {
|
|
393
|
-
if (pattern.test(trimmed)) {
|
|
394
|
-
return { meaningful: true, reason: 'requirement_signal' };
|
|
271
|
+
function saveTopics(topics) {
|
|
272
|
+
const activeDigest = loadActiveDigest();
|
|
273
|
+
if (!activeDigest.session.digest_path) { throw new Error('No active digest session'); }
|
|
274
|
+
const topicsPath = path.join(activeDigest.session.digest_path, 'topics.json');
|
|
275
|
+
const topicsData = {
|
|
276
|
+
topics: topics.topics || topics,
|
|
277
|
+
metadata: {
|
|
278
|
+
total_topics: (topics.topics || topics).length,
|
|
279
|
+
active_topics: (topics.topics || topics).filter(t => t.status === 'active').length,
|
|
280
|
+
clarified_topics: (topics.topics || topics).filter(t => t.clarification_complete).length,
|
|
281
|
+
generated_topics: (topics.topics || topics).filter(t => t.stories_generated).length,
|
|
282
|
+
detected_at: now(), last_updated: now(),
|
|
283
|
+
transcript_word_count: activeDigest.input?.word_count ?? 0,
|
|
284
|
+
detection_method: 'pass-1-extraction'
|
|
395
285
|
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (wordCount < 4) {
|
|
401
|
-
return { meaningful: false, reason: 'too_brief' };
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Default to meaningful if substantial enough
|
|
405
|
-
return { meaningful: true, reason: 'substantial' };
|
|
286
|
+
};
|
|
287
|
+
fs.writeFileSync(topicsPath, JSON.stringify(topicsData, null, 2));
|
|
288
|
+
updatePhase('topic_extraction', 'completed', { topics_found: topicsData.topics.length });
|
|
289
|
+
return topicsData;
|
|
406
290
|
}
|
|
407
291
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
// Split by sentence boundaries and speaker changes
|
|
416
|
-
const segments = text.split(/(?<=[.!?])\s+|(?=^[A-Z][a-z]+:|\[\d{2}:\d{2})/gm);
|
|
417
|
-
|
|
418
|
-
for (const segment of segments) {
|
|
419
|
-
const trimmed = segment.trim();
|
|
420
|
-
if (!trimmed) continue;
|
|
421
|
-
|
|
422
|
-
// Extract speaker if present
|
|
423
|
-
const speakerMatch = trimmed.match(/^([A-Z][a-z]+):\s*/);
|
|
424
|
-
const speaker = speakerMatch ? speakerMatch[1] : null;
|
|
425
|
-
const content = speakerMatch ? trimmed.slice(speakerMatch[0].length) : trimmed;
|
|
426
|
-
|
|
427
|
-
// Extract timestamp if present
|
|
428
|
-
const timestampMatch = trimmed.match(/^\[?(\d{2}:\d{2}(?::\d{2})?)\]?\s*/);
|
|
429
|
-
const timestamp = timestampMatch ? timestampMatch[1] : null;
|
|
430
|
-
|
|
431
|
-
if (content.trim()) {
|
|
432
|
-
statements.push({
|
|
433
|
-
text: content.trim(),
|
|
434
|
-
speaker,
|
|
435
|
-
timestamp,
|
|
436
|
-
position
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
position += segment.length;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
return statements;
|
|
292
|
+
function loadTopics() {
|
|
293
|
+
const activeDigest = loadActiveDigest();
|
|
294
|
+
if (!activeDigest.session.digest_path) { return null; }
|
|
295
|
+
const topicsPath = path.join(activeDigest.session.digest_path, 'topics.json');
|
|
296
|
+
const data = safeJsonParse(topicsPath, null);
|
|
297
|
+
if (!data) return null;
|
|
298
|
+
return data;
|
|
444
299
|
}
|
|
445
300
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
let confidence = 0.5; // Base confidence
|
|
451
|
-
const reasons = [];
|
|
452
|
-
|
|
453
|
-
const statementLower = statement.text.toLowerCase();
|
|
454
|
-
const topicTitle = topic.title.toLowerCase();
|
|
455
|
-
|
|
456
|
-
// Entity match - highest confidence
|
|
457
|
-
if (topic.entities) {
|
|
458
|
-
for (const entity of topic.entities) {
|
|
459
|
-
if (statementLower.includes(entity.toLowerCase())) {
|
|
460
|
-
confidence = Math.max(confidence, 0.9);
|
|
461
|
-
reasons.push(`entity_match:${entity}`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Title word match
|
|
467
|
-
const titleWords = topicTitle.split(/\s+/).filter(w => w.length > 3);
|
|
468
|
-
for (const word of titleWords) {
|
|
469
|
-
if (statementLower.includes(word)) {
|
|
470
|
-
confidence = Math.max(confidence, 0.8);
|
|
471
|
-
reasons.push(`title_match:${word}`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Keyword match
|
|
476
|
-
if (topic.keywords) {
|
|
477
|
-
for (const keyword of topic.keywords) {
|
|
478
|
-
if (statementLower.includes(keyword.toLowerCase())) {
|
|
479
|
-
confidence = Math.max(confidence, 0.75);
|
|
480
|
-
reasons.push(`keyword_match:${keyword}`);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return { confidence, reasons };
|
|
301
|
+
function getStatus() {
|
|
302
|
+
const activeDigest = loadActiveDigest();
|
|
303
|
+
if (activeDigest.session.status === 'inactive') { return { active: false }; }
|
|
304
|
+
return { active: true, id: activeDigest.session.id, phase: activeDigest.session.phase, phases: activeDigest.phases, input: activeDigest.input };
|
|
486
305
|
}
|
|
487
306
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const mappedStatement = {
|
|
500
|
-
id: `s-${String(statementId).padStart(3, '0')}`,
|
|
501
|
-
text: stmt.text,
|
|
502
|
-
position: stmt.position,
|
|
503
|
-
timestamp: stmt.timestamp,
|
|
504
|
-
speaker: stmt.speaker,
|
|
505
|
-
meaningful: meaningfulCheck.meaningful
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
if (!meaningfulCheck.meaningful) {
|
|
509
|
-
mappedStatement.topic_id = null;
|
|
510
|
-
mappedStatement.skip_reason = meaningfulCheck.reason;
|
|
511
|
-
} else {
|
|
512
|
-
// Find best matching topic
|
|
513
|
-
let bestMatch = { topicId: null, confidence: 0, reasons: [] };
|
|
514
|
-
|
|
515
|
-
for (const topic of topics) {
|
|
516
|
-
const { confidence, reasons } = calculateAssociationConfidence(stmt, topic);
|
|
517
|
-
if (confidence > bestMatch.confidence) {
|
|
518
|
-
bestMatch = { topicId: topic.id, confidence, reasons };
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Use context continuity if no strong match
|
|
523
|
-
if (bestMatch.confidence < 0.6 && currentTopicId) {
|
|
524
|
-
bestMatch = {
|
|
525
|
-
topicId: currentTopicId,
|
|
526
|
-
confidence: 0.6,
|
|
527
|
-
reasons: ['context_continuity']
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
mappedStatement.topic_id = bestMatch.topicId;
|
|
532
|
-
mappedStatement.confidence = bestMatch.confidence;
|
|
533
|
-
mappedStatement.association_reason = bestMatch.reasons.join(',') || 'context_continuity';
|
|
534
|
-
mappedStatement.clarification_needed = bestMatch.confidence < 0.7;
|
|
535
|
-
|
|
536
|
-
if (mappedStatement.clarification_needed) {
|
|
537
|
-
mappedStatement.clarification_question =
|
|
538
|
-
`You mentioned "${stmt.text.slice(0, 50)}..." - which feature does this relate to?`;
|
|
539
|
-
}
|
|
307
|
+
function shouldTriggerDigestion(text) {
|
|
308
|
+
const config = loadConfig();
|
|
309
|
+
const threshold = config.autoTriggerThreshold ?? 2000;
|
|
310
|
+
const wordCount = countWords(text);
|
|
311
|
+
if (wordCount < threshold) { return { trigger: false, reason: 'below_threshold', wordCount }; }
|
|
312
|
+
const contentType = classifyContent(text);
|
|
313
|
+
if (contentType.type === 'requirements' || contentType.type === 'transcript') { return { trigger: true, reason: 'auto', wordCount, contentType }; }
|
|
314
|
+
if (contentType.type === 'code') { return { trigger: false, reason: 'code_detected', wordCount, contentType }; }
|
|
315
|
+
return { trigger: 'ask', reason: 'ambiguous', wordCount, contentType };
|
|
316
|
+
}
|
|
540
317
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
318
|
+
function classifyContent(text) {
|
|
319
|
+
const codePatterns = [/```[\s\S]*```/g, /function\s+\w+\s*\(/g, /const\s+\w+\s*=/g, /import\s+.*from/g, /class\s+\w+/g];
|
|
320
|
+
let codeMatches = 0;
|
|
321
|
+
for (const pattern of codePatterns) { const matches = text.match(pattern); if (matches) codeMatches += matches.length; }
|
|
322
|
+
if (codeMatches > 10) { return { type: 'code', confidence: 0.8 }; }
|
|
323
|
+
const reqPatterns = [/we need/gi, /should have/gi, /must support/gi, /add a feature/gi, /implement/gi, /the \w+ should/gi];
|
|
324
|
+
let reqMatches = 0;
|
|
325
|
+
for (const pattern of reqPatterns) { const matches = text.match(pattern); if (matches) reqMatches += matches.length; }
|
|
326
|
+
if (reqMatches > 5) { return { type: 'requirements', confidence: 0.85 }; }
|
|
327
|
+
const transcriptPatterns = [/^\d{2}:\d{2}/gm, /^speaker \d+:/gim, /^\[.*\]:/gm, /^[A-Z][a-z]+:/gm];
|
|
328
|
+
let transcriptMatches = 0;
|
|
329
|
+
for (const pattern of transcriptPatterns) { const matches = text.match(pattern); if (matches) transcriptMatches += matches.length; }
|
|
330
|
+
if (transcriptMatches > 10) { return { type: 'transcript', confidence: 0.9 }; }
|
|
331
|
+
return { type: 'unknown', confidence: 0.5 };
|
|
332
|
+
}
|
|
546
333
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
334
|
+
// ============================================
|
|
335
|
+
// Multi-language question support (E5-S2)
|
|
336
|
+
// ============================================
|
|
550
337
|
|
|
551
|
-
|
|
338
|
+
function getQuestionTemplates(languageCode) {
|
|
339
|
+
if (QUESTION_TEMPLATES_BY_LANGUAGE[languageCode]) { return QUESTION_TEMPLATES_BY_LANGUAGE[languageCode]; }
|
|
340
|
+
return QUESTION_TEMPLATES_BY_LANGUAGE.en;
|
|
552
341
|
}
|
|
553
342
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
['left', 'right'],
|
|
564
|
-
['top', 'bottom'],
|
|
565
|
-
['show', 'hide'],
|
|
566
|
-
['enable', 'disable'],
|
|
567
|
-
['add', 'remove'],
|
|
568
|
-
['include', 'exclude'],
|
|
569
|
-
['before', 'after'],
|
|
570
|
-
['above', 'below']
|
|
571
|
-
];
|
|
572
|
-
|
|
573
|
-
for (let i = 0; i < meaningfulStatements.length; i++) {
|
|
574
|
-
for (let j = i + 1; j < meaningfulStatements.length; j++) {
|
|
575
|
-
const stmt1 = meaningfulStatements[i];
|
|
576
|
-
const stmt2 = meaningfulStatements[j];
|
|
577
|
-
|
|
578
|
-
// Only check statements in same topic
|
|
579
|
-
if (stmt1.topic_id !== stmt2.topic_id) continue;
|
|
580
|
-
|
|
581
|
-
const text1 = stmt1.text.toLowerCase();
|
|
582
|
-
const text2 = stmt2.text.toLowerCase();
|
|
583
|
-
|
|
584
|
-
// Check for opposite words
|
|
585
|
-
for (const [word1, word2] of opposites) {
|
|
586
|
-
if ((text1.includes(word1) && text2.includes(word2)) ||
|
|
587
|
-
(text1.includes(word2) && text2.includes(word1))) {
|
|
588
|
-
contradictions.push({
|
|
589
|
-
statement1_id: stmt1.id,
|
|
590
|
-
statement2_id: stmt2.id,
|
|
591
|
-
type: 'opposite_values',
|
|
592
|
-
attribute: `${word1}/${word2}`,
|
|
593
|
-
resolution: 'pending'
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// Check for number conflicts (same attribute, different values)
|
|
599
|
-
const numbers1 = text1.match(/\d+/g);
|
|
600
|
-
const numbers2 = text2.match(/\d+/g);
|
|
601
|
-
if (numbers1 && numbers2) {
|
|
602
|
-
// Simple heuristic: if both mention numbers in similar context
|
|
603
|
-
const commonWords = text1.split(/\s+/).filter(w => text2.includes(w) && w.length > 3);
|
|
604
|
-
if (commonWords.length > 2 && numbers1[0] !== numbers2[0]) {
|
|
605
|
-
contradictions.push({
|
|
606
|
-
statement1_id: stmt1.id,
|
|
607
|
-
statement2_id: stmt2.id,
|
|
608
|
-
type: 'quantity_conflict',
|
|
609
|
-
values: [numbers1[0], numbers2[0]],
|
|
610
|
-
resolution: 'pending'
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
}
|
|
343
|
+
function generateLocalizedQuestion(templateKey, detailKey, entity, language = 'en') {
|
|
344
|
+
const isLanguageSupported = Object.hasOwn(QUESTION_TEMPLATES_BY_LANGUAGE, language);
|
|
345
|
+
const effectiveLang = isLanguageSupported ? language : 'en';
|
|
346
|
+
const templates = getQuestionTemplates(language);
|
|
347
|
+
const template = templates[templateKey]?.[detailKey];
|
|
348
|
+
if (!template) {
|
|
349
|
+
const enTemplate = QUESTION_TEMPLATES[templateKey]?.[detailKey];
|
|
350
|
+
if (enTemplate) {
|
|
351
|
+
return { question: enTemplate.question.replace('{entity}', entity), examples: enTemplate.examples || null, priority: enTemplate.priority || 'P2', language: 'en', fallback: true };
|
|
614
352
|
}
|
|
353
|
+
return null;
|
|
615
354
|
}
|
|
616
|
-
|
|
617
|
-
return contradictions;
|
|
355
|
+
return { question: template.question.replace('{entity}', entity), examples: template.examples || null, priority: template.priority || 'P2', language: effectiveLang, fallback: !isLanguageSupported };
|
|
618
356
|
}
|
|
619
357
|
|
|
620
|
-
|
|
621
|
-
* Save statement map to digest
|
|
622
|
-
*/
|
|
623
|
-
function saveStatementMap(statementMap) {
|
|
358
|
+
function detectSessionLanguage() {
|
|
624
359
|
const activeDigest = loadActiveDigest();
|
|
625
|
-
if (!activeDigest.session.digest_path) {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
contradictions: statementMap.contradictions || [],
|
|
639
|
-
metadata: {
|
|
640
|
-
total_statements: statementMap.statements.length,
|
|
641
|
-
meaningful_statements: meaningful.length,
|
|
642
|
-
mapped_statements: mapped.length,
|
|
643
|
-
orphan_statements: orphans.length,
|
|
644
|
-
contradictions_detected: (statementMap.contradictions || []).length,
|
|
645
|
-
contradictions_resolved: 0,
|
|
646
|
-
coverage_percentage: meaningful.length > 0
|
|
647
|
-
? Math.round((mapped.length / meaningful.length) * 100 * 10) / 10
|
|
648
|
-
: 0
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
|
|
652
|
-
fs.writeFileSync(mapPath, JSON.stringify(data, null, 2));
|
|
653
|
-
|
|
654
|
-
// Update phase
|
|
655
|
-
updatePhase('statement_mapping', 'completed', {
|
|
656
|
-
statements_mapped: mapped.length,
|
|
657
|
-
orphans_found: orphans.length,
|
|
658
|
-
contradictions_found: (statementMap.contradictions || []).length
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
return data;
|
|
360
|
+
if (!activeDigest.session.digest_path) { throw new Error('No active digest session'); }
|
|
361
|
+
const digestDir = activeDigest.session.digest_path;
|
|
362
|
+
const transcriptPath = path.join(digestDir, 'transcript.txt');
|
|
363
|
+
if (!fs.existsSync(transcriptPath)) { return { detected: false, reason: 'No transcript file found' }; }
|
|
364
|
+
const transcript = fs.readFileSync(transcriptPath, 'utf8');
|
|
365
|
+
const langResult = detectLanguage(transcript);
|
|
366
|
+
const multiResult = detectMultipleLanguages(transcript, { segmentSize: 500 });
|
|
367
|
+
activeDigest.session.detected_language = langResult.language;
|
|
368
|
+
activeDigest.session.language_confidence = langResult.confidence;
|
|
369
|
+
activeDigest.session.is_multilingual = multiResult.isMultilingual;
|
|
370
|
+
activeDigest.session.language_distribution = multiResult.distribution || {};
|
|
371
|
+
saveActiveDigest(activeDigest);
|
|
372
|
+
return { detected: true, language: langResult.language, languageName: LANGUAGE_INFO[langResult.language]?.name || 'Unknown', confidence: langResult.confidence, isMultilingual: multiResult.isMultilingual, distribution: multiResult.distribution };
|
|
662
373
|
}
|
|
663
374
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
function loadStatementMap() {
|
|
375
|
+
function getTopicLanguage(topicId) {
|
|
376
|
+
const topics = loadTopics();
|
|
377
|
+
const stmtMap = loadStatementMap();
|
|
668
378
|
const activeDigest = loadActiveDigest();
|
|
669
|
-
if (!activeDigest.session
|
|
670
|
-
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
379
|
+
if (!topics || !stmtMap) { return activeDigest.session?.detected_language || 'en'; }
|
|
380
|
+
const topic = topics.topics.find(t => t.id === topicId);
|
|
381
|
+
if (!topic) { return activeDigest.session?.detected_language || 'en'; }
|
|
382
|
+
if (topic.language) { return topic.language; }
|
|
383
|
+
const topicStatements = stmtMap.statements.filter(s => s.topic_id === topicId && s.meaningful);
|
|
384
|
+
if (topicStatements.length === 0) { return activeDigest.session?.detected_language || 'en'; }
|
|
385
|
+
const combinedText = topicStatements.map(s => s.text).join('\n');
|
|
386
|
+
const result = detectLanguage(combinedText);
|
|
387
|
+
return result.language;
|
|
679
388
|
}
|
|
680
389
|
|
|
681
|
-
|
|
682
|
-
* Process Pass 2: Statement Association
|
|
683
|
-
*/
|
|
684
|
-
function runPass2() {
|
|
390
|
+
function setLanguagePreference(languageCode) {
|
|
685
391
|
const activeDigest = loadActiveDigest();
|
|
686
|
-
if (!activeDigest.session.digest_path) {
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
// Load topics from Pass 1
|
|
695
|
-
const topicsData = loadTopics();
|
|
696
|
-
if (!topicsData || !topicsData.topics.length) {
|
|
697
|
-
throw new Error('No topics found - run Pass 1 first');
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Update phase status
|
|
701
|
-
updatePhase('statement_mapping', 'in_progress');
|
|
702
|
-
|
|
703
|
-
// Split into statements
|
|
704
|
-
const statements = splitIntoStatements(transcript);
|
|
705
|
-
|
|
706
|
-
// Associate with topics
|
|
707
|
-
const mappedStatements = associateStatements(statements, topicsData.topics);
|
|
708
|
-
|
|
709
|
-
// Detect contradictions
|
|
710
|
-
const contradictions = detectContradictions(mappedStatements);
|
|
711
|
-
|
|
712
|
-
// Mark contradicting statements
|
|
713
|
-
for (const contradiction of contradictions) {
|
|
714
|
-
const stmt1 = mappedStatements.find(s => s.id === contradiction.statement1_id);
|
|
715
|
-
const stmt2 = mappedStatements.find(s => s.id === contradiction.statement2_id);
|
|
716
|
-
if (stmt1) stmt1.contradicts = contradiction.statement2_id;
|
|
717
|
-
if (stmt2) stmt2.contradicts = contradiction.statement1_id;
|
|
718
|
-
}
|
|
392
|
+
if (!activeDigest.session.digest_path) { throw new Error('No active digest session'); }
|
|
393
|
+
const info = getLanguageInfo(languageCode);
|
|
394
|
+
if (!info.supported) { throw new Error(`Unsupported language code: ${languageCode}`); }
|
|
395
|
+
activeDigest.session.preferred_language = languageCode;
|
|
396
|
+
saveActiveDigest(activeDigest);
|
|
397
|
+
return { set: true, language: languageCode, languageName: info.name };
|
|
398
|
+
}
|
|
719
399
|
|
|
720
|
-
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
400
|
+
function getEffectiveLanguage(topicId = null) {
|
|
401
|
+
const activeDigest = loadActiveDigest();
|
|
402
|
+
if (activeDigest.session?.preferred_language) { return activeDigest.session.preferred_language; }
|
|
403
|
+
if (topicId) { const topicLang = getTopicLanguage(topicId); if (topicLang && QUESTION_TEMPLATES_BY_LANGUAGE[topicLang]) { return topicLang; } }
|
|
404
|
+
if (activeDigest.session?.detected_language && QUESTION_TEMPLATES_BY_LANGUAGE[activeDigest.session.detected_language]) { return activeDigest.session.detected_language; }
|
|
405
|
+
return 'en';
|
|
406
|
+
}
|
|
725
407
|
|
|
726
|
-
|
|
408
|
+
function getSessionLanguageInfo() {
|
|
409
|
+
const activeDigest = loadActiveDigest();
|
|
410
|
+
return {
|
|
411
|
+
detected: activeDigest.session?.detected_language || null, detectedName: LANGUAGE_INFO[activeDigest.session?.detected_language]?.name || null,
|
|
412
|
+
confidence: activeDigest.session?.language_confidence || null, preferred: activeDigest.session?.preferred_language || null,
|
|
413
|
+
preferredName: LANGUAGE_INFO[activeDigest.session?.preferred_language]?.name || null, isMultilingual: activeDigest.session?.is_multilingual || false,
|
|
414
|
+
distribution: activeDigest.session?.language_distribution || {}, effective: getEffectiveLanguage()
|
|
415
|
+
};
|
|
727
416
|
}
|
|
728
417
|
|
|
729
418
|
// ============================================
|
|
730
|
-
//
|
|
419
|
+
// Question Generation (E2-S1)
|
|
731
420
|
// ============================================
|
|
732
421
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const stopWords = ['the', 'a', 'an', 'is', 'are', 'should', 'must', 'will', 'can', 'be', 'it', 'we', 'i', 'to', 'for', 'of', 'in', 'on', 'with'];
|
|
739
|
-
const words = text.toLowerCase()
|
|
740
|
-
.replace(/[^\w\s]/g, '')
|
|
741
|
-
.split(/\s+/)
|
|
742
|
-
.filter(w => w.length > 2 && !stopWords.includes(w));
|
|
743
|
-
|
|
744
|
-
// Get first 2-3 meaningful words
|
|
745
|
-
const keyWords = words.slice(0, 3);
|
|
746
|
-
if (keyWords.length === 0) return 'Misc';
|
|
747
|
-
|
|
748
|
-
// Capitalize
|
|
749
|
-
return keyWords.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
422
|
+
function isDetailProvided(detail, topicId, statements) {
|
|
423
|
+
const topicStatements = statements.filter(s => s.topic_id === topicId && s.meaningful);
|
|
424
|
+
const pattern = DETAIL_PATTERNS[detail];
|
|
425
|
+
if (!pattern) return false;
|
|
426
|
+
return topicStatements.some(s => pattern.test(s.text));
|
|
750
427
|
}
|
|
751
428
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const reasons = [];
|
|
758
|
-
const statementLower = statement.text.toLowerCase();
|
|
759
|
-
|
|
760
|
-
// Standard matching first
|
|
761
|
-
const { confidence: baseConf, reasons: baseReasons } = calculateAssociationConfidence(statement, topic);
|
|
762
|
-
if (baseConf > confidence) {
|
|
763
|
-
confidence = baseConf;
|
|
764
|
-
reasons.push(...baseReasons);
|
|
765
|
-
}
|
|
429
|
+
function extractEntityFromStatement(statement, pattern) {
|
|
430
|
+
const match = statement.text.match(pattern.pattern);
|
|
431
|
+
if (match && pattern.entity !== null) { return match[pattern.entity]; }
|
|
432
|
+
return pattern.type;
|
|
433
|
+
}
|
|
766
434
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
for (const related of allTerms) {
|
|
777
|
-
if (topicTitle.includes(related) || topicKeywords.includes(related)) {
|
|
778
|
-
confidence = Math.max(confidence, 0.7);
|
|
779
|
-
reasons.push(`semantic_expansion:${syn}->${related}`);
|
|
780
|
-
}
|
|
435
|
+
function analyzeCompleteness(statement, topicId, allStatements) {
|
|
436
|
+
const gaps = [];
|
|
437
|
+
const text = statement.text.toLowerCase();
|
|
438
|
+
for (const entityPattern of ENTITY_PATTERNS) {
|
|
439
|
+
if (entityPattern.pattern.test(text)) {
|
|
440
|
+
const entity = extractEntityFromStatement(statement, entityPattern);
|
|
441
|
+
for (const detail of entityPattern.missing) {
|
|
442
|
+
if (!isDetailProvided(detail, topicId, allStatements)) {
|
|
443
|
+
gaps.push({ type: entityPattern.type, entity, detail, statementId: statement.id });
|
|
781
444
|
}
|
|
782
445
|
}
|
|
783
446
|
}
|
|
784
447
|
}
|
|
785
|
-
|
|
786
|
-
return { confidence, reasons };
|
|
448
|
+
return gaps;
|
|
787
449
|
}
|
|
788
450
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
function resolveOrphan(orphan, topics) {
|
|
793
|
-
const candidates = [];
|
|
794
|
-
|
|
795
|
-
// Try enhanced matching against all topics
|
|
796
|
-
for (const topic of topics) {
|
|
797
|
-
const { confidence, reasons } = calculateExpandedConfidence(orphan, topic);
|
|
798
|
-
if (confidence >= 0.5) {
|
|
799
|
-
candidates.push({ topic, confidence, reasons });
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// Sort by confidence
|
|
804
|
-
candidates.sort((a, b) => b.confidence - a.confidence);
|
|
805
|
-
|
|
806
|
-
// Resolution decision
|
|
807
|
-
if (candidates.length === 0) {
|
|
808
|
-
return {
|
|
809
|
-
resolved: false,
|
|
810
|
-
method: 'no_match',
|
|
811
|
-
confidence: 0
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const best = candidates[0];
|
|
816
|
-
|
|
817
|
-
// Clear winner
|
|
818
|
-
if (best.confidence >= 0.6 && (candidates.length === 1 || best.confidence - candidates[1].confidence > 0.15)) {
|
|
819
|
-
return {
|
|
820
|
-
resolved: true,
|
|
821
|
-
method: 'semantic_expansion',
|
|
822
|
-
topic_id: best.topic.id,
|
|
823
|
-
confidence: best.confidence,
|
|
824
|
-
reasons: best.reasons
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
// Ambiguous - multiple close matches
|
|
829
|
-
if (candidates.length > 1 && candidates[0].confidence - candidates[1].confidence < 0.1) {
|
|
830
|
-
return {
|
|
831
|
-
resolved: false,
|
|
832
|
-
method: 'ambiguous',
|
|
833
|
-
possible_topics: candidates.slice(0, 3).map(c => c.topic.id),
|
|
834
|
-
confidence: best.confidence
|
|
835
|
-
};
|
|
451
|
+
function detectVagueness(statement) {
|
|
452
|
+
for (const vague of VAGUE_PATTERNS) {
|
|
453
|
+
if (vague.pattern.test(statement.text)) { return { isVague: true, key: vague.key, question: vague.question }; }
|
|
836
454
|
}
|
|
837
|
-
|
|
838
|
-
// Low confidence winner
|
|
839
|
-
return {
|
|
840
|
-
resolved: best.confidence >= 0.5,
|
|
841
|
-
method: best.confidence >= 0.5 ? 'context_reanalysis' : 'low_confidence',
|
|
842
|
-
topic_id: best.confidence >= 0.5 ? best.topic.id : null,
|
|
843
|
-
confidence: best.confidence,
|
|
844
|
-
reasons: best.reasons
|
|
845
|
-
};
|
|
455
|
+
return { isVague: false };
|
|
846
456
|
}
|
|
847
457
|
|
|
848
|
-
/**
|
|
849
|
-
* Create a new topic from orphan statements
|
|
850
|
-
*/
|
|
851
|
-
function createTopicFromOrphans(orphans, _existingTopics) {
|
|
852
|
-
// Guard against empty orphans array
|
|
853
|
-
if (!orphans || orphans.length === 0) {
|
|
854
|
-
const topicId = generateHashId('t-auto', '', '');
|
|
855
|
-
return {
|
|
856
|
-
id: topicId,
|
|
857
|
-
title: 'Miscellaneous',
|
|
858
|
-
description: 'Auto-generated topic for uncategorized statements',
|
|
859
|
-
source: 'orphan_resolution',
|
|
860
|
-
entities: [],
|
|
861
|
-
keywords: [],
|
|
862
|
-
statements: [],
|
|
863
|
-
needs_review: true,
|
|
864
|
-
confidence: 0.5,
|
|
865
|
-
created_at: now()
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const topicId = generateHashId('t-auto', '', '');
|
|
870
|
-
|
|
871
|
-
// Extract common keywords from orphans
|
|
872
|
-
const allWords = orphans.flatMap(o =>
|
|
873
|
-
o.text.toLowerCase()
|
|
874
|
-
.replace(/[^\w\s]/g, '')
|
|
875
|
-
.split(/\s+/)
|
|
876
|
-
.filter(w => w.length > 3)
|
|
877
|
-
);
|
|
878
|
-
|
|
879
|
-
// Count word frequencies
|
|
880
|
-
const wordCounts = {};
|
|
881
|
-
for (const word of allWords) {
|
|
882
|
-
wordCounts[word] = (wordCounts[word] || 0) + 1;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
// Get most common words as keywords
|
|
886
|
-
const keywords = Object.entries(wordCounts)
|
|
887
|
-
.sort((a, b) => b[1] - a[1])
|
|
888
|
-
.slice(0, 5)
|
|
889
|
-
.map(([word]) => word);
|
|
890
|
-
|
|
891
|
-
// Generate title from first orphan
|
|
892
|
-
const title = extractKeyPhrase(orphans[0].text);
|
|
893
|
-
|
|
894
|
-
return {
|
|
895
|
-
id: topicId,
|
|
896
|
-
title: title,
|
|
897
|
-
description: `Auto-generated from ${orphans.length} orphan statement(s)`,
|
|
898
|
-
source: 'orphan_resolution',
|
|
899
|
-
entities: [],
|
|
900
|
-
keywords,
|
|
901
|
-
statements: orphans.map(o => o.id),
|
|
902
|
-
needs_review: true,
|
|
903
|
-
confidence: 0.7,
|
|
904
|
-
created_at: now()
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/**
|
|
909
|
-
* Ensure General topic exists
|
|
910
|
-
*/
|
|
911
|
-
function ensureGeneralTopic(topics) {
|
|
912
|
-
let general = topics.find(t => t.id === 't-general');
|
|
913
|
-
if (!general) {
|
|
914
|
-
general = {
|
|
915
|
-
id: 't-general',
|
|
916
|
-
title: 'General Requirements',
|
|
917
|
-
description: 'Miscellaneous requirements that apply broadly or do not fit specific features',
|
|
918
|
-
source: 'catch_all',
|
|
919
|
-
entities: [],
|
|
920
|
-
keywords: ['general', 'overall', 'misc'],
|
|
921
|
-
statements: [],
|
|
922
|
-
needs_review: true,
|
|
923
|
-
confidence: 1.0
|
|
924
|
-
};
|
|
925
|
-
topics.push(general);
|
|
926
|
-
}
|
|
927
|
-
return general;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
/**
|
|
931
|
-
* Save orphan resolution results
|
|
932
|
-
*/
|
|
933
|
-
function saveOrphans(orphansData) {
|
|
934
|
-
const activeDigest = loadActiveDigest();
|
|
935
|
-
if (!activeDigest.session.digest_path) {
|
|
936
|
-
throw new Error('No active digest session');
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
const orphansPath = path.join(activeDigest.session.digest_path, 'orphans.json');
|
|
940
|
-
fs.writeFileSync(orphansPath, JSON.stringify(orphansData, null, 2));
|
|
941
|
-
return orphansData;
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Load orphan data
|
|
946
|
-
*/
|
|
947
|
-
function loadOrphans() {
|
|
948
|
-
const activeDigest = loadActiveDigest();
|
|
949
|
-
if (!activeDigest.session.digest_path) {
|
|
950
|
-
return null;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const orphansPath = path.join(activeDigest.session.digest_path, 'orphans.json');
|
|
954
|
-
try {
|
|
955
|
-
return JSON.parse(fs.readFileSync(orphansPath, 'utf8'));
|
|
956
|
-
} catch (_err) {
|
|
957
|
-
return null;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
/**
|
|
962
|
-
* Process Pass 3: Orphan Check
|
|
963
|
-
*/
|
|
964
|
-
function runPass3() {
|
|
965
|
-
const activeDigest = loadActiveDigest();
|
|
966
|
-
if (!activeDigest.session.digest_path) {
|
|
967
|
-
throw new Error('No active digest session');
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
// Load statement map
|
|
971
|
-
const stmtMap = loadStatementMap();
|
|
972
|
-
if (!stmtMap) {
|
|
973
|
-
throw new Error('No statement map found - run Pass 2 first');
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
// Load topics
|
|
977
|
-
const topicsData = loadTopics();
|
|
978
|
-
if (!topicsData) {
|
|
979
|
-
throw new Error('No topics found');
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// Update phase
|
|
983
|
-
updatePhase('orphan_check', 'in_progress');
|
|
984
|
-
|
|
985
|
-
// Find orphans
|
|
986
|
-
const orphanStatements = stmtMap.statements.filter(s => s.meaningful && s.topic_id === null);
|
|
987
|
-
|
|
988
|
-
if (orphanStatements.length === 0) {
|
|
989
|
-
// No orphans - 100% coverage
|
|
990
|
-
const result = {
|
|
991
|
-
orphans: [],
|
|
992
|
-
resolved: [],
|
|
993
|
-
new_topics_created: [],
|
|
994
|
-
coverage: {
|
|
995
|
-
total_meaningful: stmtMap.metadata.meaningful_statements,
|
|
996
|
-
mapped: stmtMap.metadata.meaningful_statements,
|
|
997
|
-
clarification_needed: 0,
|
|
998
|
-
percentage: 100,
|
|
999
|
-
target: 100
|
|
1000
|
-
}
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
saveOrphans(result);
|
|
1004
|
-
updatePhase('orphan_check', 'completed', { orphans_resolved: 0 });
|
|
1005
|
-
return result;
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
const resolved = [];
|
|
1009
|
-
const stillOrphans = [];
|
|
1010
|
-
const newTopics = [];
|
|
1011
|
-
let topics = [...topicsData.topics];
|
|
1012
|
-
|
|
1013
|
-
// First pass: try to resolve each orphan
|
|
1014
|
-
for (const orphan of orphanStatements) {
|
|
1015
|
-
const resolution = resolveOrphan(orphan, topics);
|
|
1016
|
-
|
|
1017
|
-
if (resolution.resolved) {
|
|
1018
|
-
// Update statement in map
|
|
1019
|
-
orphan.topic_id = resolution.topic_id;
|
|
1020
|
-
orphan.confidence = resolution.confidence;
|
|
1021
|
-
orphan.association_reason = resolution.reasons?.join(',') || resolution.method;
|
|
1022
|
-
|
|
1023
|
-
resolved.push({
|
|
1024
|
-
id: orphan.id,
|
|
1025
|
-
original_topic_id: null,
|
|
1026
|
-
resolved_topic_id: resolution.topic_id,
|
|
1027
|
-
resolution_method: resolution.method,
|
|
1028
|
-
confidence: resolution.confidence
|
|
1029
|
-
});
|
|
1030
|
-
} else {
|
|
1031
|
-
stillOrphans.push({
|
|
1032
|
-
...orphan,
|
|
1033
|
-
resolution_attempted: true,
|
|
1034
|
-
resolution_result: resolution.method,
|
|
1035
|
-
possible_topics: resolution.possible_topics || [],
|
|
1036
|
-
needs_clarification: true,
|
|
1037
|
-
clarification_question: `You mentioned "${orphan.text.slice(0, 50)}..." - which feature does this relate to?`
|
|
1038
|
-
});
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// Second pass: cluster remaining orphans that might form new topics
|
|
1043
|
-
const unresolved = stillOrphans.filter(o => o.resolution_result === 'no_match');
|
|
1044
|
-
if (unresolved.length >= 2) {
|
|
1045
|
-
// Simple clustering: group orphans with similar words
|
|
1046
|
-
const clusters = [];
|
|
1047
|
-
const used = new Set();
|
|
1048
|
-
|
|
1049
|
-
for (let i = 0; i < unresolved.length; i++) {
|
|
1050
|
-
if (used.has(i)) continue;
|
|
1051
|
-
|
|
1052
|
-
const cluster = [unresolved[i]];
|
|
1053
|
-
used.add(i);
|
|
1054
|
-
|
|
1055
|
-
const words1 = new Set(unresolved[i].text.toLowerCase().split(/\s+/).filter(w => w.length > 3));
|
|
1056
|
-
|
|
1057
|
-
for (let j = i + 1; j < unresolved.length; j++) {
|
|
1058
|
-
if (used.has(j)) continue;
|
|
1059
|
-
|
|
1060
|
-
const words2 = new Set(unresolved[j].text.toLowerCase().split(/\s+/).filter(w => w.length > 3));
|
|
1061
|
-
const common = [...words1].filter(w => words2.has(w));
|
|
1062
|
-
|
|
1063
|
-
if (common.length >= 2) {
|
|
1064
|
-
cluster.push(unresolved[j]);
|
|
1065
|
-
used.add(j);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
if (cluster.length >= 2) {
|
|
1070
|
-
clusters.push(cluster);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
// Create new topics from clusters
|
|
1075
|
-
for (const cluster of clusters) {
|
|
1076
|
-
const newTopic = createTopicFromOrphans(cluster, topics);
|
|
1077
|
-
topics.push(newTopic);
|
|
1078
|
-
newTopics.push({
|
|
1079
|
-
id: newTopic.id,
|
|
1080
|
-
title: newTopic.title,
|
|
1081
|
-
statements_assigned: cluster.map(o => o.id)
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
// Update statements
|
|
1085
|
-
for (const orphan of cluster) {
|
|
1086
|
-
const stmtInMap = stmtMap.statements.find(s => s.id === orphan.id);
|
|
1087
|
-
if (stmtInMap) {
|
|
1088
|
-
stmtInMap.topic_id = newTopic.id;
|
|
1089
|
-
stmtInMap.confidence = 0.7;
|
|
1090
|
-
stmtInMap.association_reason = 'topic_clustering';
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
resolved.push({
|
|
1094
|
-
id: orphan.id,
|
|
1095
|
-
original_topic_id: null,
|
|
1096
|
-
resolved_topic_id: newTopic.id,
|
|
1097
|
-
resolution_method: 'topic_clustering',
|
|
1098
|
-
confidence: 0.7
|
|
1099
|
-
});
|
|
1100
|
-
|
|
1101
|
-
// Remove from stillOrphans
|
|
1102
|
-
const idx = stillOrphans.findIndex(o => o.id === orphan.id);
|
|
1103
|
-
if (idx >= 0) stillOrphans.splice(idx, 1);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
// Third pass: assign remaining low-priority orphans to General
|
|
1109
|
-
const veryLowConfidence = stillOrphans.filter(o =>
|
|
1110
|
-
o.resolution_result === 'no_match' || o.confidence < 0.3
|
|
1111
|
-
);
|
|
1112
|
-
|
|
1113
|
-
if (veryLowConfidence.length > 0) {
|
|
1114
|
-
const general = ensureGeneralTopic(topics);
|
|
1115
|
-
|
|
1116
|
-
for (const orphan of veryLowConfidence) {
|
|
1117
|
-
const stmtInMap = stmtMap.statements.find(s => s.id === orphan.id);
|
|
1118
|
-
if (stmtInMap) {
|
|
1119
|
-
stmtInMap.topic_id = general.id;
|
|
1120
|
-
stmtInMap.confidence = 0.5;
|
|
1121
|
-
stmtInMap.association_reason = 'general_assignment';
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
resolved.push({
|
|
1125
|
-
id: orphan.id,
|
|
1126
|
-
original_topic_id: null,
|
|
1127
|
-
resolved_topic_id: general.id,
|
|
1128
|
-
resolution_method: 'general_assignment',
|
|
1129
|
-
confidence: 0.5
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
// Remove from stillOrphans
|
|
1133
|
-
const idx = stillOrphans.findIndex(o => o.id === orphan.id);
|
|
1134
|
-
if (idx >= 0) stillOrphans.splice(idx, 1);
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
// Update topics if new ones were created
|
|
1139
|
-
if (newTopics.length > 0) {
|
|
1140
|
-
saveTopics({ topics });
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
// Save updated statement map
|
|
1144
|
-
const meaningful = stmtMap.statements.filter(s => s.meaningful);
|
|
1145
|
-
const mapped = meaningful.filter(s => s.topic_id !== null);
|
|
1146
|
-
stmtMap.metadata.mapped_statements = mapped.length;
|
|
1147
|
-
stmtMap.metadata.orphan_statements = stillOrphans.length;
|
|
1148
|
-
stmtMap.metadata.coverage_percentage = meaningful.length > 0
|
|
1149
|
-
? Math.round((mapped.length / meaningful.length) * 100 * 10) / 10
|
|
1150
|
-
: 0;
|
|
1151
|
-
|
|
1152
|
-
const mapPath = path.join(activeDigest.session.digest_path, 'statement-map.json');
|
|
1153
|
-
fs.writeFileSync(mapPath, JSON.stringify(stmtMap, null, 2));
|
|
1154
|
-
|
|
1155
|
-
// Prepare result
|
|
1156
|
-
const result = {
|
|
1157
|
-
orphans: stillOrphans,
|
|
1158
|
-
resolved,
|
|
1159
|
-
new_topics_created: newTopics,
|
|
1160
|
-
coverage: {
|
|
1161
|
-
total_meaningful: meaningful.length,
|
|
1162
|
-
mapped: mapped.length,
|
|
1163
|
-
clarification_needed: stillOrphans.length,
|
|
1164
|
-
percentage: stmtMap.metadata.coverage_percentage,
|
|
1165
|
-
target: 100
|
|
1166
|
-
}
|
|
1167
|
-
};
|
|
1168
|
-
|
|
1169
|
-
saveOrphans(result);
|
|
1170
|
-
updatePhase('orphan_check', 'completed', {
|
|
1171
|
-
orphans_resolved: resolved.length,
|
|
1172
|
-
new_topics: newTopics.length,
|
|
1173
|
-
remaining_orphans: stillOrphans.length
|
|
1174
|
-
});
|
|
1175
|
-
|
|
1176
|
-
return result;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// ============================================
|
|
1180
|
-
// Pass 4: Contradiction Resolution
|
|
1181
|
-
// ============================================
|
|
1182
|
-
|
|
1183
|
-
/**
|
|
1184
|
-
* Check if statement contains a correction phrase
|
|
1185
|
-
*/
|
|
1186
|
-
function detectCorrectionPhrase(text) {
|
|
1187
|
-
for (const { pattern, name, weight } of CORRECTION_PATTERNS) {
|
|
1188
|
-
if (pattern.test(text)) {
|
|
1189
|
-
return { detected: true, phrase: name, weight };
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
return { detected: false, phrase: null, weight: 0 };
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
* Check if statement is additive (not a real contradiction)
|
|
1197
|
-
*/
|
|
1198
|
-
function isAdditive(text) {
|
|
1199
|
-
return ADDITIVE_PATTERNS.some(pattern => pattern.test(text));
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
/**
|
|
1203
|
-
* Calculate resolution confidence for a contradiction
|
|
1204
|
-
*/
|
|
1205
|
-
function calculateResolutionConfidence(stmt1, stmt2, contradiction) {
|
|
1206
|
-
let confidence = 0.5;
|
|
1207
|
-
let reasons = [];
|
|
1208
|
-
|
|
1209
|
-
// Check for correction phrase in stmt2 (later statement)
|
|
1210
|
-
const correction = detectCorrectionPhrase(stmt2.text);
|
|
1211
|
-
if (correction.detected) {
|
|
1212
|
-
confidence += correction.weight;
|
|
1213
|
-
reasons.push(`correction_phrase:${correction.phrase}`);
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// Same speaker increases confidence
|
|
1217
|
-
if (stmt1.speaker && stmt2.speaker && stmt1.speaker === stmt2.speaker) {
|
|
1218
|
-
confidence += 0.15;
|
|
1219
|
-
reasons.push('same_speaker');
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// Position difference - later statements typically override
|
|
1223
|
-
const positionDiff = stmt2.position - stmt1.position;
|
|
1224
|
-
if (positionDiff > 500) { // Significant distance
|
|
1225
|
-
confidence += 0.1;
|
|
1226
|
-
reasons.push('later_position');
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// Check if stmt2 explicitly references the attribute
|
|
1230
|
-
const attr = contradiction.attribute;
|
|
1231
|
-
if (attr) {
|
|
1232
|
-
const [word1, word2] = attr.split('/');
|
|
1233
|
-
if (stmt2.text.toLowerCase().includes(word1) || stmt2.text.toLowerCase().includes(word2)) {
|
|
1234
|
-
confidence += 0.1;
|
|
1235
|
-
reasons.push('explicit_attribute_reference');
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// Check for additive pattern - might not be a real contradiction
|
|
1240
|
-
if (isAdditive(stmt2.text)) {
|
|
1241
|
-
confidence = 0.3; // Low confidence - likely not a contradiction
|
|
1242
|
-
reasons = ['additive_pattern'];
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
return {
|
|
1246
|
-
confidence: Math.min(confidence, 1.0),
|
|
1247
|
-
reasons,
|
|
1248
|
-
winner: confidence >= 0.5 ? stmt2.id : null,
|
|
1249
|
-
isAdditive: isAdditive(stmt2.text)
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
/**
|
|
1254
|
-
* Generate clarification question for unresolved contradiction
|
|
1255
|
-
*/
|
|
1256
|
-
function generateContradictionQuestion(stmt1, stmt2, contradiction) {
|
|
1257
|
-
const attr = contradiction.attribute || 'value';
|
|
1258
|
-
const [val1, val2] = attr.split('/');
|
|
1259
|
-
|
|
1260
|
-
// Extract the actual values from statements if possible
|
|
1261
|
-
const extractValue = (text, hints) => {
|
|
1262
|
-
for (const hint of hints || []) {
|
|
1263
|
-
if (text.toLowerCase().includes(hint.toLowerCase())) {
|
|
1264
|
-
return hint;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
return text.slice(0, 50);
|
|
1268
|
-
};
|
|
1269
|
-
|
|
1270
|
-
const value1 = val1 || extractValue(stmt1.text, []);
|
|
1271
|
-
const value2 = val2 || extractValue(stmt2.text, []);
|
|
1272
|
-
|
|
1273
|
-
return {
|
|
1274
|
-
question: `You mentioned "${stmt1.text.slice(0, 60)}" but later said "${stmt2.text.slice(0, 60)}". Which do you prefer?`,
|
|
1275
|
-
options: [
|
|
1276
|
-
{ id: 'opt-1', text: value1, statement_id: stmt1.id },
|
|
1277
|
-
{ id: 'opt-2', text: value2, statement_id: stmt2.id },
|
|
1278
|
-
{ id: 'opt-3', text: 'Both are needed', resolution: 'keep_both' }
|
|
1279
|
-
],
|
|
1280
|
-
attribute: attr
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
/**
|
|
1285
|
-
* Save clarifications to file
|
|
1286
|
-
*/
|
|
1287
|
-
function saveClarifications(clarifications) {
|
|
1288
|
-
const activeDigest = loadActiveDigest();
|
|
1289
|
-
if (!activeDigest.session.digest_path) {
|
|
1290
|
-
throw new Error('No active digest session');
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
const clarPath = path.join(activeDigest.session.digest_path, 'clarifications.json');
|
|
1294
|
-
fs.writeFileSync(clarPath, JSON.stringify(clarifications, null, 2));
|
|
1295
|
-
return clarifications;
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
/**
|
|
1299
|
-
* Load clarifications from file
|
|
1300
|
-
*/
|
|
1301
|
-
function loadClarifications() {
|
|
1302
|
-
const activeDigest = loadActiveDigest();
|
|
1303
|
-
if (!activeDigest.session.digest_path) {
|
|
1304
|
-
return null;
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
const clarPath = path.join(activeDigest.session.digest_path, 'clarifications.json');
|
|
1308
|
-
try {
|
|
1309
|
-
return JSON.parse(fs.readFileSync(clarPath, 'utf8'));
|
|
1310
|
-
} catch (_err) {
|
|
1311
|
-
return {
|
|
1312
|
-
questions: [],
|
|
1313
|
-
contradictions: [],
|
|
1314
|
-
metadata: {
|
|
1315
|
-
total_questions: 0,
|
|
1316
|
-
answered_questions: 0,
|
|
1317
|
-
pending_questions: 0,
|
|
1318
|
-
total_contradictions: 0,
|
|
1319
|
-
resolved_contradictions: 0,
|
|
1320
|
-
auto_resolved_count: 0,
|
|
1321
|
-
user_resolved_count: 0
|
|
1322
|
-
}
|
|
1323
|
-
};
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
/**
|
|
1328
|
-
* Process Pass 4: Contradiction Resolution
|
|
1329
|
-
*/
|
|
1330
|
-
function runPass4() {
|
|
1331
|
-
const activeDigest = loadActiveDigest();
|
|
1332
|
-
if (!activeDigest.session.digest_path) {
|
|
1333
|
-
throw new Error('No active digest session');
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
// Load statement map
|
|
1337
|
-
const stmtMap = loadStatementMap();
|
|
1338
|
-
if (!stmtMap) {
|
|
1339
|
-
throw new Error('No statement map found - run Pass 2 first');
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
const contradictions = stmtMap.contradictions || [];
|
|
1343
|
-
if (contradictions.length === 0) {
|
|
1344
|
-
const result = {
|
|
1345
|
-
resolved: [],
|
|
1346
|
-
pending: [],
|
|
1347
|
-
additive: [],
|
|
1348
|
-
stats: {
|
|
1349
|
-
total: 0,
|
|
1350
|
-
auto_resolved: 0,
|
|
1351
|
-
needs_clarification: 0,
|
|
1352
|
-
additive_not_contradiction: 0
|
|
1353
|
-
}
|
|
1354
|
-
};
|
|
1355
|
-
updatePhase('contradiction_resolution', 'completed', result.stats);
|
|
1356
|
-
return result;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
// Update phase
|
|
1360
|
-
updatePhase('contradiction_resolution', 'in_progress');
|
|
1361
|
-
|
|
1362
|
-
const resolved = [];
|
|
1363
|
-
const pending = [];
|
|
1364
|
-
const additive = [];
|
|
1365
|
-
|
|
1366
|
-
// Load or create clarifications
|
|
1367
|
-
let clarifications = loadClarifications();
|
|
1368
|
-
|
|
1369
|
-
// Process each contradiction
|
|
1370
|
-
for (const contradiction of contradictions) {
|
|
1371
|
-
const stmt1 = stmtMap.statements.find(s => s.id === contradiction.statement1_id);
|
|
1372
|
-
const stmt2 = stmtMap.statements.find(s => s.id === contradiction.statement2_id);
|
|
1373
|
-
|
|
1374
|
-
if (!stmt1 || !stmt2) continue;
|
|
1375
|
-
|
|
1376
|
-
const resolution = calculateResolutionConfidence(stmt1, stmt2, contradiction);
|
|
1377
|
-
|
|
1378
|
-
if (resolution.isAdditive) {
|
|
1379
|
-
// Not actually a contradiction
|
|
1380
|
-
contradiction.resolution = 'not_contradiction';
|
|
1381
|
-
contradiction.reason = 'additive_pattern';
|
|
1382
|
-
additive.push({
|
|
1383
|
-
statement1_id: contradiction.statement1_id,
|
|
1384
|
-
statement2_id: contradiction.statement2_id,
|
|
1385
|
-
reason: 'Both statements are valid (additive)'
|
|
1386
|
-
});
|
|
1387
|
-
|
|
1388
|
-
// Remove from contradictions
|
|
1389
|
-
continue;
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
if (resolution.confidence >= 0.8) {
|
|
1393
|
-
// Auto-resolve
|
|
1394
|
-
contradiction.resolution = 'auto_resolved';
|
|
1395
|
-
contradiction.winner = resolution.winner;
|
|
1396
|
-
contradiction.reason = resolution.reasons.join(',');
|
|
1397
|
-
contradiction.confidence = resolution.confidence;
|
|
1398
|
-
contradiction.resolved_at = now();
|
|
1399
|
-
|
|
1400
|
-
// Mark loser as superseded
|
|
1401
|
-
const loser = resolution.winner === stmt2.id ? stmt1 : stmt2;
|
|
1402
|
-
const winner = resolution.winner === stmt2.id ? stmt2 : stmt1;
|
|
1403
|
-
|
|
1404
|
-
loser.superseded = true;
|
|
1405
|
-
loser.superseded_by = winner.id;
|
|
1406
|
-
loser.superseded_reason = resolution.reasons[0] || 'auto_resolved';
|
|
1407
|
-
|
|
1408
|
-
winner.supersedes = loser.id;
|
|
1409
|
-
winner.is_correction = true;
|
|
1410
|
-
|
|
1411
|
-
resolved.push({
|
|
1412
|
-
statement1_id: contradiction.statement1_id,
|
|
1413
|
-
statement2_id: contradiction.statement2_id,
|
|
1414
|
-
winner: resolution.winner,
|
|
1415
|
-
confidence: resolution.confidence,
|
|
1416
|
-
reason: resolution.reasons.join(',')
|
|
1417
|
-
});
|
|
1418
|
-
} else {
|
|
1419
|
-
// Needs clarification
|
|
1420
|
-
contradiction.resolution = 'clarification_needed';
|
|
1421
|
-
contradiction.confidence = resolution.confidence;
|
|
1422
|
-
|
|
1423
|
-
const question = generateContradictionQuestion(stmt1, stmt2, contradiction);
|
|
1424
|
-
|
|
1425
|
-
// Add to clarifications
|
|
1426
|
-
const clarId = `c-${String(clarifications.contradictions.length + 1).padStart(3, '0')}`;
|
|
1427
|
-
clarifications.contradictions.push({
|
|
1428
|
-
id: clarId,
|
|
1429
|
-
type: contradiction.type,
|
|
1430
|
-
attribute: contradiction.attribute,
|
|
1431
|
-
statements: [contradiction.statement1_id, contradiction.statement2_id],
|
|
1432
|
-
topic_id: stmt1.topic_id || stmt2.topic_id,
|
|
1433
|
-
question: question.question,
|
|
1434
|
-
options: question.options,
|
|
1435
|
-
status: 'pending',
|
|
1436
|
-
created_at: now()
|
|
1437
|
-
});
|
|
1438
|
-
|
|
1439
|
-
contradiction.clarification_id = clarId;
|
|
1440
|
-
|
|
1441
|
-
pending.push({
|
|
1442
|
-
statement1_id: contradiction.statement1_id,
|
|
1443
|
-
statement2_id: contradiction.statement2_id,
|
|
1444
|
-
clarification_id: clarId,
|
|
1445
|
-
confidence: resolution.confidence
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
// Filter out additive patterns from contradictions list
|
|
1451
|
-
stmtMap.contradictions = contradictions.filter(c => c.resolution !== 'not_contradiction');
|
|
1452
|
-
|
|
1453
|
-
// Update clarifications metadata
|
|
1454
|
-
clarifications.metadata.total_contradictions = contradictions.length;
|
|
1455
|
-
clarifications.metadata.auto_resolved_count = resolved.length;
|
|
1456
|
-
clarifications.metadata.pending_questions = pending.length;
|
|
1457
|
-
|
|
1458
|
-
// Save updated files
|
|
1459
|
-
const mapPath = path.join(activeDigest.session.digest_path, 'statement-map.json');
|
|
1460
|
-
fs.writeFileSync(mapPath, JSON.stringify(stmtMap, null, 2));
|
|
1461
|
-
|
|
1462
|
-
saveClarifications(clarifications);
|
|
1463
|
-
|
|
1464
|
-
const stats = {
|
|
1465
|
-
total: contradictions.length,
|
|
1466
|
-
auto_resolved: resolved.length,
|
|
1467
|
-
needs_clarification: pending.length,
|
|
1468
|
-
additive_not_contradiction: additive.length
|
|
1469
|
-
};
|
|
1470
|
-
|
|
1471
|
-
updatePhase('contradiction_resolution', 'completed', stats);
|
|
1472
|
-
|
|
1473
|
-
return {
|
|
1474
|
-
resolved,
|
|
1475
|
-
pending,
|
|
1476
|
-
additive,
|
|
1477
|
-
stats
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
// ============================================
|
|
1482
|
-
// Question Generation (E2-S1)
|
|
1483
|
-
// ============================================
|
|
1484
|
-
|
|
1485
|
-
// ==========================================================================
|
|
1486
|
-
// E5-S2: Multi-language Question Templates
|
|
1487
|
-
// ==========================================================================
|
|
1488
|
-
|
|
1489
|
-
/**
|
|
1490
|
-
* Get question templates for a specific language (E5-S2)
|
|
1491
|
-
*/
|
|
1492
|
-
function getQuestionTemplates(languageCode) {
|
|
1493
|
-
// Check if we have templates for this language
|
|
1494
|
-
if (QUESTION_TEMPLATES_BY_LANGUAGE[languageCode]) {
|
|
1495
|
-
return QUESTION_TEMPLATES_BY_LANGUAGE[languageCode];
|
|
1496
|
-
}
|
|
1497
|
-
// Fall back to English
|
|
1498
|
-
return QUESTION_TEMPLATES_BY_LANGUAGE.en;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
/**
|
|
1502
|
-
* Generate a localized question (E5-S2)
|
|
1503
|
-
*/
|
|
1504
|
-
function generateLocalizedQuestion(templateKey, detailKey, entity, language = 'en') {
|
|
1505
|
-
// Check if language is directly supported
|
|
1506
|
-
const isLanguageSupported = QUESTION_TEMPLATES_BY_LANGUAGE.hasOwnProperty(language);
|
|
1507
|
-
const effectiveLang = isLanguageSupported ? language : 'en';
|
|
1508
|
-
|
|
1509
|
-
const templates = getQuestionTemplates(language);
|
|
1510
|
-
const template = templates[templateKey]?.[detailKey];
|
|
1511
|
-
|
|
1512
|
-
if (!template) {
|
|
1513
|
-
// Fall back to English if template not found
|
|
1514
|
-
const enTemplate = QUESTION_TEMPLATES[templateKey]?.[detailKey];
|
|
1515
|
-
if (enTemplate) {
|
|
1516
|
-
return {
|
|
1517
|
-
question: enTemplate.question.replace('{entity}', entity),
|
|
1518
|
-
examples: enTemplate.examples || null,
|
|
1519
|
-
priority: enTemplate.priority || 'P2',
|
|
1520
|
-
language: 'en',
|
|
1521
|
-
fallback: true
|
|
1522
|
-
};
|
|
1523
|
-
}
|
|
1524
|
-
return null;
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
return {
|
|
1528
|
-
question: template.question.replace('{entity}', entity),
|
|
1529
|
-
examples: template.examples || null,
|
|
1530
|
-
priority: template.priority || 'P2',
|
|
1531
|
-
language: effectiveLang,
|
|
1532
|
-
fallback: !isLanguageSupported
|
|
1533
|
-
};
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
/**
|
|
1537
|
-
* Detect and store session language (E5-S2)
|
|
1538
|
-
*/
|
|
1539
|
-
function detectSessionLanguage() {
|
|
1540
|
-
const activeDigest = loadActiveDigest();
|
|
1541
|
-
if (!activeDigest.session.digest_path) {
|
|
1542
|
-
throw new Error('No active digest session');
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
// Load the original transcript
|
|
1546
|
-
const digestDir = activeDigest.session.digest_path;
|
|
1547
|
-
const transcriptPath = path.join(digestDir, 'transcript.txt');
|
|
1548
|
-
|
|
1549
|
-
if (!fs.existsSync(transcriptPath)) {
|
|
1550
|
-
return {
|
|
1551
|
-
detected: false,
|
|
1552
|
-
reason: 'No transcript file found'
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
const transcript = fs.readFileSync(transcriptPath, 'utf8');
|
|
1557
|
-
|
|
1558
|
-
// Detect primary language
|
|
1559
|
-
const langResult = detectLanguage(transcript);
|
|
1560
|
-
|
|
1561
|
-
// Detect if multi-language
|
|
1562
|
-
const multiResult = detectMultipleLanguages(transcript, { segmentSize: 500 });
|
|
1563
|
-
|
|
1564
|
-
// Update session with language info
|
|
1565
|
-
activeDigest.session.detected_language = langResult.language;
|
|
1566
|
-
activeDigest.session.language_confidence = langResult.confidence;
|
|
1567
|
-
activeDigest.session.is_multilingual = multiResult.isMultilingual;
|
|
1568
|
-
activeDigest.session.language_distribution = multiResult.distribution || {};
|
|
1569
|
-
|
|
1570
|
-
saveActiveDigest(activeDigest);
|
|
1571
|
-
|
|
1572
|
-
return {
|
|
1573
|
-
detected: true,
|
|
1574
|
-
language: langResult.language,
|
|
1575
|
-
languageName: LANGUAGE_INFO[langResult.language]?.name || 'Unknown',
|
|
1576
|
-
confidence: langResult.confidence,
|
|
1577
|
-
isMultilingual: multiResult.isMultilingual,
|
|
1578
|
-
distribution: multiResult.distribution
|
|
1579
|
-
};
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
/**
|
|
1583
|
-
* Get language for a topic (E5-S2)
|
|
1584
|
-
*/
|
|
1585
|
-
function getTopicLanguage(topicId) {
|
|
1586
|
-
const topics = loadTopics();
|
|
1587
|
-
const stmtMap = loadStatementMap();
|
|
1588
|
-
const activeDigest = loadActiveDigest();
|
|
1589
|
-
|
|
1590
|
-
if (!topics || !stmtMap) {
|
|
1591
|
-
return activeDigest.session?.detected_language || 'en';
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
// Find the topic
|
|
1595
|
-
const topic = topics.topics.find(t => t.id === topicId);
|
|
1596
|
-
if (!topic) {
|
|
1597
|
-
return activeDigest.session?.detected_language || 'en';
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
// If topic has a stored language, use it
|
|
1601
|
-
if (topic.language) {
|
|
1602
|
-
return topic.language;
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
// Detect language from topic's statements
|
|
1606
|
-
const topicStatements = stmtMap.statements.filter(s => s.topic_id === topicId && s.meaningful);
|
|
1607
|
-
if (topicStatements.length === 0) {
|
|
1608
|
-
return activeDigest.session?.detected_language || 'en';
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
// Combine statement text and detect
|
|
1612
|
-
const combinedText = topicStatements.map(s => s.text).join('\n');
|
|
1613
|
-
const result = detectLanguage(combinedText);
|
|
1614
|
-
|
|
1615
|
-
return result.language;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
/**
|
|
1619
|
-
* Set user language preference (E5-S2)
|
|
1620
|
-
*/
|
|
1621
|
-
function setLanguagePreference(languageCode) {
|
|
1622
|
-
const activeDigest = loadActiveDigest();
|
|
1623
|
-
if (!activeDigest.session.digest_path) {
|
|
1624
|
-
throw new Error('No active digest session');
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
// Validate language code
|
|
1628
|
-
const info = getLanguageInfo(languageCode);
|
|
1629
|
-
if (!info.supported) {
|
|
1630
|
-
throw new Error(`Unsupported language code: ${languageCode}`);
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
activeDigest.session.preferred_language = languageCode;
|
|
1634
|
-
saveActiveDigest(activeDigest);
|
|
1635
|
-
|
|
1636
|
-
return {
|
|
1637
|
-
set: true,
|
|
1638
|
-
language: languageCode,
|
|
1639
|
-
languageName: info.name
|
|
1640
|
-
};
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
/**
|
|
1644
|
-
* Get effective language for question generation (E5-S2)
|
|
1645
|
-
*/
|
|
1646
|
-
function getEffectiveLanguage(topicId = null) {
|
|
1647
|
-
const activeDigest = loadActiveDigest();
|
|
1648
|
-
|
|
1649
|
-
// Priority 1: User preference
|
|
1650
|
-
if (activeDigest.session?.preferred_language) {
|
|
1651
|
-
return activeDigest.session.preferred_language;
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
// Priority 2: Topic-specific language
|
|
1655
|
-
if (topicId) {
|
|
1656
|
-
const topicLang = getTopicLanguage(topicId);
|
|
1657
|
-
if (topicLang && QUESTION_TEMPLATES_BY_LANGUAGE[topicLang]) {
|
|
1658
|
-
return topicLang;
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
// Priority 3: Session detected language
|
|
1663
|
-
if (activeDigest.session?.detected_language &&
|
|
1664
|
-
QUESTION_TEMPLATES_BY_LANGUAGE[activeDigest.session.detected_language]) {
|
|
1665
|
-
return activeDigest.session.detected_language;
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// Default: English
|
|
1669
|
-
return 'en';
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
/**
|
|
1673
|
-
* Get session language info (E5-S2)
|
|
1674
|
-
*/
|
|
1675
|
-
function getSessionLanguageInfo() {
|
|
1676
|
-
const activeDigest = loadActiveDigest();
|
|
1677
|
-
|
|
1678
|
-
return {
|
|
1679
|
-
detected: activeDigest.session?.detected_language || null,
|
|
1680
|
-
detectedName: LANGUAGE_INFO[activeDigest.session?.detected_language]?.name || null,
|
|
1681
|
-
confidence: activeDigest.session?.language_confidence || null,
|
|
1682
|
-
preferred: activeDigest.session?.preferred_language || null,
|
|
1683
|
-
preferredName: LANGUAGE_INFO[activeDigest.session?.preferred_language]?.name || null,
|
|
1684
|
-
isMultilingual: activeDigest.session?.is_multilingual || false,
|
|
1685
|
-
distribution: activeDigest.session?.language_distribution || {},
|
|
1686
|
-
effective: getEffectiveLanguage()
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
// ==========================================================================
|
|
1691
|
-
// E3-S1: Complexity Detection Patterns
|
|
1692
|
-
// ==========================================================================
|
|
1693
|
-
|
|
1694
|
-
/**
|
|
1695
|
-
* Check if a detail is already mentioned in topic statements
|
|
1696
|
-
*/
|
|
1697
|
-
function isDetailProvided(detail, topicId, statements) {
|
|
1698
|
-
const topicStatements = statements.filter(s => s.topic_id === topicId && s.meaningful);
|
|
1699
|
-
const pattern = DETAIL_PATTERNS[detail];
|
|
1700
|
-
if (!pattern) return false;
|
|
1701
|
-
return topicStatements.some(s => pattern.test(s.text));
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
/**
|
|
1705
|
-
* Extract entity name from statement
|
|
1706
|
-
*/
|
|
1707
|
-
function extractEntityFromStatement(statement, pattern) {
|
|
1708
|
-
const match = statement.text.match(pattern.pattern);
|
|
1709
|
-
if (match && pattern.entity !== null) {
|
|
1710
|
-
return match[pattern.entity];
|
|
1711
|
-
}
|
|
1712
|
-
return pattern.type;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
/**
|
|
1716
|
-
* Analyze statement for completeness gaps
|
|
1717
|
-
*/
|
|
1718
|
-
function analyzeCompleteness(statement, topicId, allStatements) {
|
|
1719
|
-
const gaps = [];
|
|
1720
|
-
const text = statement.text.toLowerCase();
|
|
1721
|
-
|
|
1722
|
-
for (const entityPattern of ENTITY_PATTERNS) {
|
|
1723
|
-
if (entityPattern.pattern.test(text)) {
|
|
1724
|
-
const entity = extractEntityFromStatement(statement, entityPattern);
|
|
1725
|
-
|
|
1726
|
-
for (const detail of entityPattern.missing) {
|
|
1727
|
-
if (!isDetailProvided(detail, topicId, allStatements)) {
|
|
1728
|
-
gaps.push({
|
|
1729
|
-
type: entityPattern.type,
|
|
1730
|
-
entity,
|
|
1731
|
-
detail,
|
|
1732
|
-
statementId: statement.id
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
return gaps;
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
/**
|
|
1743
|
-
* Check if statement is vague
|
|
1744
|
-
*/
|
|
1745
|
-
function detectVagueness(statement) {
|
|
1746
|
-
for (const vague of VAGUE_PATTERNS) {
|
|
1747
|
-
if (vague.pattern.test(statement.text)) {
|
|
1748
|
-
return {
|
|
1749
|
-
isVague: true,
|
|
1750
|
-
key: vague.key,
|
|
1751
|
-
question: vague.question
|
|
1752
|
-
};
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
return { isVague: false };
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
/**
|
|
1759
|
-
* Generate question ID
|
|
1760
|
-
*/
|
|
1761
458
|
let questionCounter = 0;
|
|
1762
|
-
function generateQuestionId() {
|
|
1763
|
-
questionCounter++;
|
|
1764
|
-
return `q-${String(questionCounter).padStart(3, '0')}`;
|
|
1765
|
-
}
|
|
459
|
+
function generateQuestionId() { questionCounter++; return `q-${String(questionCounter).padStart(3, '0')}`; }
|
|
1766
460
|
|
|
1767
|
-
/**
|
|
1768
|
-
* Generate questions for a topic
|
|
1769
|
-
*/
|
|
1770
461
|
function generateQuestionsForTopic(topic, statements, allStatements) {
|
|
1771
462
|
const questions = [];
|
|
1772
463
|
const topicStatements = statements.filter(s => s.topic_id === topic.id && s.meaningful && !s.superseded);
|
|
1773
|
-
|
|
1774
464
|
for (const statement of topicStatements) {
|
|
1775
|
-
// Check completeness
|
|
1776
465
|
const gaps = analyzeCompleteness(statement, topic.id, allStatements);
|
|
1777
466
|
for (const gap of gaps) {
|
|
1778
467
|
const template = QUESTION_TEMPLATES[gap.type]?.[gap.detail];
|
|
1779
468
|
if (template) {
|
|
1780
|
-
questions.push({
|
|
1781
|
-
id: generateQuestionId(),
|
|
1782
|
-
type: 'completeness',
|
|
1783
|
-
topic_id: topic.id,
|
|
1784
|
-
topic_title: topic.title,
|
|
1785
|
-
statement_id: statement.id,
|
|
1786
|
-
question: template.question.replace('{entity}', gap.entity),
|
|
1787
|
-
detail: gap.detail,
|
|
1788
|
-
examples: template.examples || null,
|
|
1789
|
-
priority: template.priority || 'P2',
|
|
1790
|
-
status: 'pending',
|
|
1791
|
-
answer: null,
|
|
1792
|
-
created_at: now()
|
|
1793
|
-
});
|
|
469
|
+
questions.push({ id: generateQuestionId(), type: 'completeness', topic_id: topic.id, topic_title: topic.title, statement_id: statement.id, question: template.question.replace('{entity}', gap.entity), detail: gap.detail, examples: template.examples || null, priority: template.priority || 'P2', status: 'pending', answer: null, created_at: now() });
|
|
1794
470
|
}
|
|
1795
471
|
}
|
|
1796
|
-
|
|
1797
|
-
// Check vagueness
|
|
1798
472
|
const vagueness = detectVagueness(statement);
|
|
1799
473
|
if (vagueness.isVague) {
|
|
1800
|
-
questions.push({
|
|
1801
|
-
id: generateQuestionId(),
|
|
1802
|
-
type: 'specificity',
|
|
1803
|
-
topic_id: topic.id,
|
|
1804
|
-
topic_title: topic.title,
|
|
1805
|
-
statement_id: statement.id,
|
|
1806
|
-
question: vagueness.question,
|
|
1807
|
-
original_statement: statement.text,
|
|
1808
|
-
priority: 'P2',
|
|
1809
|
-
status: 'pending',
|
|
1810
|
-
answer: null,
|
|
1811
|
-
created_at: now()
|
|
1812
|
-
});
|
|
474
|
+
questions.push({ id: generateQuestionId(), type: 'specificity', topic_id: topic.id, topic_title: topic.title, statement_id: statement.id, question: vagueness.question, original_statement: statement.text, priority: 'P2', status: 'pending', answer: null, created_at: now() });
|
|
1813
475
|
}
|
|
1814
476
|
}
|
|
1815
|
-
|
|
1816
477
|
return questions;
|
|
1817
478
|
}
|
|
1818
479
|
|
|
1819
|
-
/**
|
|
1820
|
-
* Run question generation
|
|
1821
|
-
*/
|
|
1822
480
|
function generateAllQuestions() {
|
|
1823
481
|
const activeDigest = loadActiveDigest();
|
|
1824
|
-
if (!activeDigest.session.digest_path) {
|
|
1825
|
-
throw new Error('No active digest session');
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
// Load data
|
|
482
|
+
if (!activeDigest.session.digest_path) { throw new Error('No active digest session'); }
|
|
1829
483
|
const topics = loadTopics();
|
|
1830
484
|
const stmtMap = loadStatementMap();
|
|
1831
485
|
let clarifications = loadClarifications();
|
|
1832
|
-
|
|
1833
|
-
if (!
|
|
1834
|
-
throw new Error('Topics and statement map required - run passes 1-2 first');
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
// Initialize clarifications if null (no file exists yet)
|
|
1838
|
-
if (!clarifications) {
|
|
1839
|
-
clarifications = {
|
|
1840
|
-
questions: [],
|
|
1841
|
-
contradictions: [],
|
|
1842
|
-
by_topic: {},
|
|
1843
|
-
metadata: {
|
|
1844
|
-
total_questions: 0,
|
|
1845
|
-
pending_questions: 0,
|
|
1846
|
-
answered_questions: 0
|
|
1847
|
-
}
|
|
1848
|
-
};
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
// Reset question counter based on existing questions
|
|
486
|
+
if (!topics || !stmtMap) { throw new Error('Topics and statement map required - run passes 1-2 first'); }
|
|
487
|
+
if (!clarifications) { clarifications = { questions: [], contradictions: [], by_topic: {}, metadata: { total_questions: 0, pending_questions: 0, answered_questions: 0 } }; }
|
|
1852
488
|
questionCounter = clarifications.questions?.length || 0;
|
|
1853
|
-
|
|
1854
|
-
// Generate questions for each topic
|
|
1855
489
|
const allQuestions = [];
|
|
1856
490
|
const byTopic = {};
|
|
1857
|
-
|
|
1858
491
|
for (const topic of topics.topics) {
|
|
1859
492
|
const topicQuestions = generateQuestionsForTopic(topic, stmtMap.statements, stmtMap.statements);
|
|
1860
|
-
|
|
1861
|
-
if (topicQuestions.length > 0) {
|
|
1862
|
-
allQuestions.push(...topicQuestions);
|
|
1863
|
-
byTopic[topic.id] = topicQuestions.map(q => q.id);
|
|
1864
|
-
}
|
|
493
|
+
if (topicQuestions.length > 0) { allQuestions.push(...topicQuestions); byTopic[topic.id] = topicQuestions.map(q => q.id); }
|
|
1865
494
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
clarifications.questions = [
|
|
1869
|
-
...(clarifications.questions || []),
|
|
1870
|
-
...allQuestions
|
|
1871
|
-
];
|
|
1872
|
-
clarifications.by_topic = {
|
|
1873
|
-
...(clarifications.by_topic || {}),
|
|
1874
|
-
...byTopic
|
|
1875
|
-
};
|
|
1876
|
-
|
|
1877
|
-
// Update metadata
|
|
495
|
+
clarifications.questions = [...(clarifications.questions || []), ...allQuestions];
|
|
496
|
+
clarifications.by_topic = { ...(clarifications.by_topic || {}), ...byTopic };
|
|
1878
497
|
const byType = { completeness: 0, specificity: 0, ambiguity: 0 };
|
|
1879
498
|
const byPriority = { P1: 0, P2: 0, P3: 0 };
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
byType[q.type] = (byType[q.type] || 0) + 1;
|
|
1883
|
-
byPriority[q.priority] = (byPriority[q.priority] || 0) + 1;
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
clarifications.metadata = {
|
|
1887
|
-
...clarifications.metadata,
|
|
1888
|
-
total_questions: clarifications.questions.length,
|
|
1889
|
-
pending_questions: clarifications.questions.filter(q => q.status === 'pending').length,
|
|
1890
|
-
answered_questions: clarifications.questions.filter(q => q.status === 'answered').length,
|
|
1891
|
-
by_type: byType,
|
|
1892
|
-
by_priority: byPriority
|
|
1893
|
-
};
|
|
1894
|
-
|
|
1895
|
-
// Save
|
|
499
|
+
for (const q of clarifications.questions) { byType[q.type] = (byType[q.type] || 0) + 1; byPriority[q.priority] = (byPriority[q.priority] || 0) + 1; }
|
|
500
|
+
clarifications.metadata = { ...clarifications.metadata, total_questions: clarifications.questions.length, pending_questions: clarifications.questions.filter(q => q.status === 'pending').length, answered_questions: clarifications.questions.filter(q => q.status === 'answered').length, by_type: byType, by_priority: byPriority };
|
|
1896
501
|
saveClarifications(clarifications);
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
updatePhase('clarification', 'in_progress', {
|
|
1900
|
-
questions_total: allQuestions.length,
|
|
1901
|
-
questions_answered: 0
|
|
1902
|
-
});
|
|
1903
|
-
|
|
1904
|
-
return {
|
|
1905
|
-
questions: allQuestions,
|
|
1906
|
-
by_topic: byTopic,
|
|
1907
|
-
stats: {
|
|
1908
|
-
total: allQuestions.length,
|
|
1909
|
-
by_type: byType,
|
|
1910
|
-
by_priority: byPriority,
|
|
1911
|
-
topics_with_questions: Object.keys(byTopic).length
|
|
1912
|
-
}
|
|
1913
|
-
};
|
|
502
|
+
updatePhase('clarification', 'in_progress', { questions_total: allQuestions.length, questions_answered: 0 });
|
|
503
|
+
return { questions: allQuestions, by_topic: byTopic, stats: { total: allQuestions.length, by_type: byType, by_priority: byPriority, topics_with_questions: Object.keys(byTopic).length } };
|
|
1914
504
|
}
|
|
1915
505
|
|
|
1916
506
|
// ============================================
|
|
1917
|
-
// E2-S2
|
|
507
|
+
// Conversation Loop (E2-S2)
|
|
1918
508
|
// ============================================
|
|
1919
509
|
|
|
1920
|
-
/**
|
|
1921
|
-
* Keywords to extract from questions for matching
|
|
1922
|
-
*/
|
|
1923
510
|
function extractKeywordsFromQuestion(question) {
|
|
1924
511
|
const stopWords = ['what', 'which', 'how', 'should', 'the', 'a', 'an', 'for', 'to', 'of', 'in', 'be', 'are', 'is'];
|
|
1925
512
|
const words = question.text || question.question;
|
|
1926
|
-
return words.toLowerCase()
|
|
1927
|
-
.replace(/[?.,!]/g, '')
|
|
1928
|
-
.split(/\s+/)
|
|
1929
|
-
.filter(w => w.length > 2 && !stopWords.includes(w));
|
|
513
|
+
return words.toLowerCase().replace(/[?.,!]/g, '').split(/\s+/).filter(w => w.length > 2 && !stopWords.includes(w));
|
|
1930
514
|
}
|
|
1931
515
|
|
|
1932
|
-
/**
|
|
1933
|
-
* Parse user response and match answers to questions
|
|
1934
|
-
*/
|
|
1935
516
|
function parseAnswers(userResponse, questions) {
|
|
1936
517
|
const answers = [];
|
|
1937
518
|
const text = userResponse.trim();
|
|
1938
|
-
|
|
1939
|
-
// Try numbered responses first (1. answer, 2. answer)
|
|
1940
519
|
const numberedPattern = /(?:^|\n)\s*(\d+)[.)]\s*(.+?)(?=\n\s*\d+[.)]|\n*$)/gs;
|
|
1941
520
|
const numberedMatches = [...text.matchAll(numberedPattern)];
|
|
1942
|
-
|
|
1943
521
|
if (numberedMatches.length > 0) {
|
|
1944
|
-
for (const match of numberedMatches) {
|
|
1945
|
-
const num = parseInt(match[1], 10);
|
|
1946
|
-
const answer = match[2].trim();
|
|
1947
|
-
if (num >= 1 && num <= questions.length) {
|
|
1948
|
-
answers.push({
|
|
1949
|
-
question_id: questions[num - 1].id,
|
|
1950
|
-
answer,
|
|
1951
|
-
confidence: 0.95,
|
|
1952
|
-
match_method: 'numbered'
|
|
1953
|
-
});
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
522
|
+
for (const match of numberedMatches) { const num = parseInt(match[1], 10); const answer = match[2].trim(); if (num >= 1 && num <= questions.length) { answers.push({ question_id: questions[num - 1].id, answer, confidence: 0.95, match_method: 'numbered' }); } }
|
|
1956
523
|
return answers;
|
|
1957
524
|
}
|
|
1958
|
-
|
|
1959
|
-
// Try explicit keyword matches (for X, the Y should, etc.)
|
|
1960
525
|
for (const question of questions) {
|
|
1961
526
|
const keywords = extractKeywordsFromQuestion(question);
|
|
1962
|
-
|
|
1963
527
|
for (const keyword of keywords) {
|
|
1964
|
-
// Escape special regex characters in keyword
|
|
1965
528
|
const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
const patterns = [
|
|
1969
|
-
new RegExp(`(?:for\\s+(?:the\\s+)?)?${escapedKeyword}[,:]\\s*(.+?)(?:\\.|$|\\n)`, 'i'),
|
|
1970
|
-
new RegExp(`${escapedKeyword}\\s+(?:should\\s+(?:be|have|show)\\s+)?(.+?)(?:\\.|$|\\n)`, 'i')
|
|
1971
|
-
];
|
|
1972
|
-
|
|
1973
|
-
for (const pattern of patterns) {
|
|
1974
|
-
const match = text.match(pattern);
|
|
1975
|
-
if (match && match[1] && match[1].trim().length > 2) {
|
|
1976
|
-
// Check if we already have an answer for this question
|
|
1977
|
-
const existing = answers.find(a => a.question_id === question.id);
|
|
1978
|
-
if (!existing) {
|
|
1979
|
-
answers.push({
|
|
1980
|
-
question_id: question.id,
|
|
1981
|
-
answer: match[1].trim(),
|
|
1982
|
-
confidence: 0.85,
|
|
1983
|
-
match_method: 'keyword',
|
|
1984
|
-
matched_keyword: keyword
|
|
1985
|
-
});
|
|
1986
|
-
}
|
|
1987
|
-
break;
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
529
|
+
const patterns = [new RegExp(`(?:for\\s+(?:the\\s+)?)?${escapedKeyword}[,:]\\s*(.+?)(?:\\.|$|\\n)`, 'i'), new RegExp(`${escapedKeyword}\\s+(?:should\\s+(?:be|have|show)\\s+)?(.+?)(?:\\.|$|\\n)`, 'i')];
|
|
530
|
+
for (const pattern of patterns) { const match = text.match(pattern); if (match && match[1] && match[1].trim().length > 2) { const existing = answers.find(a => a.question_id === question.id); if (!existing) { answers.push({ question_id: question.id, answer: match[1].trim(), confidence: 0.85, match_method: 'keyword', matched_keyword: keyword }); } break; } }
|
|
1990
531
|
}
|
|
1991
532
|
}
|
|
1992
|
-
|
|
1993
|
-
// If only one question and no matches yet, assume entire response is the answer
|
|
1994
|
-
if (questions.length === 1 && answers.length === 0 && text.length > 2) {
|
|
1995
|
-
answers.push({
|
|
1996
|
-
question_id: questions[0].id,
|
|
1997
|
-
answer: text,
|
|
1998
|
-
confidence: 0.8,
|
|
1999
|
-
match_method: 'single_question'
|
|
2000
|
-
});
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
// For sequential responses separated by periods or commas
|
|
533
|
+
if (questions.length === 1 && answers.length === 0 && text.length > 2) { answers.push({ question_id: questions[0].id, answer: text, confidence: 0.8, match_method: 'single_question' }); }
|
|
2004
534
|
if (answers.length === 0 && questions.length > 1) {
|
|
2005
535
|
const segments = text.split(/[.]\s+/).filter(s => s.trim().length > 2);
|
|
2006
|
-
if (segments.length === questions.length) {
|
|
2007
|
-
for (let i = 0; i < segments.length; i++) {
|
|
2008
|
-
answers.push({
|
|
2009
|
-
question_id: questions[i].id,
|
|
2010
|
-
answer: segments[i].trim(),
|
|
2011
|
-
confidence: 0.7,
|
|
2012
|
-
match_method: 'sequential'
|
|
2013
|
-
});
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
536
|
+
if (segments.length === questions.length) { for (let i = 0; i < segments.length; i++) { answers.push({ question_id: questions[i].id, answer: segments[i].trim(), confidence: 0.7, match_method: 'sequential' }); } }
|
|
2016
537
|
}
|
|
2017
|
-
|
|
2018
538
|
return answers;
|
|
2019
539
|
}
|
|
2020
540
|
|
|
2021
|
-
/**
|
|
2022
|
-
* Capture answer for a specific question
|
|
2023
|
-
*/
|
|
2024
541
|
function captureAnswer(questionId, answer, source = 'text') {
|
|
2025
542
|
const clarifications = loadClarifications();
|
|
2026
|
-
if (!clarifications) {
|
|
2027
|
-
throw new Error('No clarifications found');
|
|
2028
|
-
}
|
|
2029
|
-
|
|
543
|
+
if (!clarifications) { throw new Error('No clarifications found'); }
|
|
2030
544
|
const question = clarifications.questions.find(q => q.id === questionId);
|
|
2031
|
-
if (!question) {
|
|
2032
|
-
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
// Update question status
|
|
2036
|
-
question.status = 'answered';
|
|
2037
|
-
question.answer = answer;
|
|
2038
|
-
question.answered_at = now();
|
|
2039
|
-
question.answer_source = source;
|
|
2040
|
-
|
|
2041
|
-
// Update metadata
|
|
545
|
+
if (!question) { throw new Error(`Question ${questionId} not found`); }
|
|
546
|
+
question.status = 'answered'; question.answer = answer; question.answered_at = now(); question.answer_source = source;
|
|
2042
547
|
clarifications.metadata.answered_questions = (clarifications.metadata.answered_questions || 0) + 1;
|
|
2043
548
|
clarifications.metadata.pending_questions = clarifications.questions.filter(q => q.status === 'pending').length;
|
|
2044
|
-
|
|
2045
549
|
saveClarifications(clarifications);
|
|
2046
|
-
|
|
2047
550
|
return question;
|
|
2048
551
|
}
|
|
2049
552
|
|
|
2050
|
-
/**
|
|
2051
|
-
* Create a derived statement from clarification answer
|
|
2052
|
-
*/
|
|
2053
553
|
function createDerivedStatement(question, answer) {
|
|
2054
554
|
const activeDigest = loadActiveDigest();
|
|
2055
|
-
if (!activeDigest.session.digest_path) {
|
|
2056
|
-
throw new Error('No active digest session');
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
// Load statement map
|
|
555
|
+
if (!activeDigest.session.digest_path) { throw new Error('No active digest session'); }
|
|
2060
556
|
const stmtMap = loadStatementMap();
|
|
2061
|
-
if (!stmtMap) {
|
|
2062
|
-
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
// Generate ID
|
|
2066
|
-
const maxId = stmtMap.statements
|
|
2067
|
-
.map(s => parseInt(s.id.replace('s-', '').replace('derived-', ''), 10) || 0)
|
|
2068
|
-
.reduce((max, id) => Math.max(max, id), 0);
|
|
2069
|
-
|
|
557
|
+
if (!stmtMap) { throw new Error('No statement map found'); }
|
|
558
|
+
const maxId = stmtMap.statements.map(s => parseInt(s.id.replace('s-', '').replace('derived-', ''), 10) || 0).reduce((max, id) => Math.max(max, id), 0);
|
|
2070
559
|
const newId = `s-derived-${String(maxId + 1).padStart(3, '0')}`;
|
|
2071
|
-
|
|
2072
|
-
// Create statement text from question and answer
|
|
2073
560
|
let text;
|
|
2074
|
-
if (question.detail) {
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
text = `The ${entity} ${question.detail} should be: ${answer}`;
|
|
2078
|
-
} else {
|
|
2079
|
-
// Specificity question - incorporate answer
|
|
2080
|
-
text = answer;
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
const derivedStatement = {
|
|
2084
|
-
id: newId,
|
|
2085
|
-
text,
|
|
2086
|
-
topic_id: question.topic_id,
|
|
2087
|
-
source: 'clarification',
|
|
2088
|
-
clarification_id: question.id,
|
|
2089
|
-
meaningful: true,
|
|
2090
|
-
confidence: 1.0,
|
|
2091
|
-
created_at: now()
|
|
2092
|
-
};
|
|
2093
|
-
|
|
2094
|
-
// Add to statement map
|
|
2095
|
-
stmtMap.statements.push(derivedStatement);
|
|
2096
|
-
stmtMap.metadata.total_statements++;
|
|
2097
|
-
stmtMap.metadata.meaningful_statements++;
|
|
2098
|
-
stmtMap.metadata.mapped_statements++;
|
|
2099
|
-
|
|
2100
|
-
// Save
|
|
561
|
+
if (question.detail) { const entity = question.question.match(/the (\w+) (table|form|button|list|modal)/i)?.[1] || ''; text = `The ${entity} ${question.detail} should be: ${answer}`; } else { text = answer; }
|
|
562
|
+
const derivedStatement = { id: newId, text, topic_id: question.topic_id, source: 'clarification', clarification_id: question.id, meaningful: true, confidence: 1.0, created_at: now() };
|
|
563
|
+
stmtMap.statements.push(derivedStatement); stmtMap.metadata.total_statements++; stmtMap.metadata.meaningful_statements++; stmtMap.metadata.mapped_statements++;
|
|
2101
564
|
const mapPath = path.join(activeDigest.session.digest_path, 'statement-map.json');
|
|
2102
565
|
fs.writeFileSync(mapPath, JSON.stringify(stmtMap, null, 2));
|
|
2103
|
-
|
|
2104
566
|
return derivedStatement;
|
|
2105
567
|
}
|
|
2106
568
|
|
|
2107
|
-
/**
|
|
2108
|
-
* Check if an answer should generate follow-up questions
|
|
2109
|
-
*/
|
|
2110
569
|
function checkFollowups(answer, question) {
|
|
2111
570
|
const followups = [];
|
|
2112
|
-
|
|
2113
571
|
for (const trigger of FOLLOWUP_TRIGGERS) {
|
|
2114
|
-
if (trigger.pattern.test(answer)) {
|
|
2115
|
-
// Don't generate follow-up if the answer already addresses it
|
|
2116
|
-
const entity = question.detail || 'item';
|
|
2117
|
-
|
|
2118
|
-
followups.push({
|
|
2119
|
-
type: trigger.type,
|
|
2120
|
-
triggered_by: trigger.pattern.source,
|
|
2121
|
-
question: trigger.question.replace('{item}', entity),
|
|
2122
|
-
parent_question_id: question.id,
|
|
2123
|
-
topic_id: question.topic_id,
|
|
2124
|
-
priority: 'P2'
|
|
2125
|
-
});
|
|
2126
|
-
}
|
|
572
|
+
if (trigger.pattern.test(answer)) { const entity = question.detail || 'item'; followups.push({ type: trigger.type, triggered_by: trigger.pattern.source, question: trigger.question.replace('{item}', entity), parent_question_id: question.id, topic_id: question.topic_id, priority: 'P2' }); }
|
|
2127
573
|
}
|
|
2128
|
-
|
|
2129
574
|
return followups;
|
|
2130
575
|
}
|
|
2131
576
|
|
|
2132
|
-
/**
|
|
2133
|
-
* Add follow-up questions to clarifications
|
|
2134
|
-
*/
|
|
2135
577
|
function addFollowupQuestions(followups) {
|
|
2136
578
|
if (followups.length === 0) return [];
|
|
2137
|
-
|
|
2138
579
|
const clarifications = loadClarifications();
|
|
2139
580
|
const addedQuestions = [];
|
|
2140
|
-
|
|
2141
581
|
for (const followup of followups) {
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
q.topic_id === followup.topic_id &&
|
|
2145
|
-
q.question.toLowerCase().includes(followup.question.toLowerCase().slice(0, 30))
|
|
2146
|
-
);
|
|
2147
|
-
|
|
2148
|
-
if (!exists) {
|
|
2149
|
-
const newQuestion = {
|
|
2150
|
-
id: generateQuestionId(),
|
|
2151
|
-
type: 'followup',
|
|
2152
|
-
topic_id: followup.topic_id,
|
|
2153
|
-
parent_question_id: followup.parent_question_id,
|
|
2154
|
-
question: followup.question,
|
|
2155
|
-
priority: followup.priority,
|
|
2156
|
-
status: 'pending',
|
|
2157
|
-
answer: null,
|
|
2158
|
-
created_at: now()
|
|
2159
|
-
};
|
|
2160
|
-
|
|
2161
|
-
clarifications.questions.push(newQuestion);
|
|
2162
|
-
addedQuestions.push(newQuestion);
|
|
2163
|
-
}
|
|
582
|
+
const exists = clarifications.questions.some(q => q.topic_id === followup.topic_id && q.question.toLowerCase().includes(followup.question.toLowerCase().slice(0, 30)));
|
|
583
|
+
if (!exists) { const newQuestion = { id: generateQuestionId(), type: 'followup', topic_id: followup.topic_id, parent_question_id: followup.parent_question_id, question: followup.question, priority: followup.priority, status: 'pending', answer: null, created_at: now() }; clarifications.questions.push(newQuestion); addedQuestions.push(newQuestion); }
|
|
2164
584
|
}
|
|
2165
|
-
|
|
2166
|
-
// Update metadata
|
|
2167
585
|
clarifications.metadata.total_questions = clarifications.questions.length;
|
|
2168
586
|
clarifications.metadata.pending_questions = clarifications.questions.filter(q => q.status === 'pending').length;
|
|
2169
|
-
|
|
2170
587
|
saveClarifications(clarifications);
|
|
2171
588
|
return addedQuestions;
|
|
2172
589
|
}
|
|
2173
590
|
|
|
2174
|
-
/**
|
|
2175
|
-
* Check if all clarifications are complete
|
|
2176
|
-
*/
|
|
2177
591
|
function checkCompletion() {
|
|
2178
592
|
const clarifications = loadClarifications();
|
|
2179
|
-
if (!clarifications) {
|
|
2180
|
-
return { complete: false, error: 'No clarifications found' };
|
|
2181
|
-
}
|
|
2182
|
-
|
|
593
|
+
if (!clarifications) { return { complete: false, error: 'No clarifications found' }; }
|
|
2183
594
|
const pendingQuestions = clarifications.questions.filter(q => q.status === 'pending');
|
|
2184
595
|
const pendingContradictions = clarifications.contradictions.filter(c => c.status === 'pending');
|
|
2185
|
-
|
|
2186
596
|
const complete = pendingQuestions.length === 0 && pendingContradictions.length === 0;
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
complete,
|
|
2190
|
-
pending_questions: pendingQuestions.length,
|
|
2191
|
-
pending_contradictions: pendingContradictions.length,
|
|
2192
|
-
answered_questions: clarifications.questions.filter(q => q.status === 'answered').length,
|
|
2193
|
-
resolved_contradictions: clarifications.contradictions.filter(c => c.status === 'resolved').length,
|
|
2194
|
-
total_questions: clarifications.questions.length,
|
|
2195
|
-
total_contradictions: clarifications.contradictions.length
|
|
2196
|
-
};
|
|
2197
|
-
|
|
2198
|
-
// If complete, update phase
|
|
2199
|
-
if (complete) {
|
|
2200
|
-
updatePhase('clarification', 'completed', {
|
|
2201
|
-
questions_total: result.total_questions,
|
|
2202
|
-
questions_answered: result.answered_questions
|
|
2203
|
-
});
|
|
2204
|
-
}
|
|
2205
|
-
|
|
597
|
+
const result = { complete, pending_questions: pendingQuestions.length, pending_contradictions: pendingContradictions.length, answered_questions: clarifications.questions.filter(q => q.status === 'answered').length, resolved_contradictions: clarifications.contradictions.filter(c => c.status === 'resolved').length, total_questions: clarifications.questions.length, total_contradictions: clarifications.contradictions.length };
|
|
598
|
+
if (complete) { updatePhase('clarification', 'completed', { questions_total: result.total_questions, questions_answered: result.answered_questions }); }
|
|
2206
599
|
return result;
|
|
2207
600
|
}
|
|
2208
601
|
|
|
2209
|
-
/**
|
|
2210
|
-
* Get questions for presentation (grouped by topic, prioritized)
|
|
2211
|
-
*/
|
|
2212
602
|
function getQuestionsForPresentation(topicId = null, limit = 5) {
|
|
2213
603
|
const clarifications = loadClarifications();
|
|
2214
604
|
if (!clarifications) return [];
|
|
2215
|
-
|
|
2216
605
|
let pendingQuestions = clarifications.questions.filter(q => q.status === 'pending');
|
|
2217
|
-
|
|
2218
|
-
// Filter by topic if specified
|
|
2219
|
-
if (topicId) {
|
|
2220
|
-
pendingQuestions = pendingQuestions.filter(q => q.topic_id === topicId);
|
|
2221
|
-
}
|
|
2222
|
-
|
|
2223
|
-
// Sort by priority (P1 first) then by creation time
|
|
606
|
+
if (topicId) { pendingQuestions = pendingQuestions.filter(q => q.topic_id === topicId); }
|
|
2224
607
|
const priorityOrder = { P1: 0, P2: 1, P3: 2 };
|
|
2225
|
-
pendingQuestions.sort((a, b) => {
|
|
2226
|
-
const pDiff = (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);
|
|
2227
|
-
if (pDiff !== 0) return pDiff;
|
|
2228
|
-
return new Date(a.created_at) - new Date(b.created_at);
|
|
2229
|
-
});
|
|
2230
|
-
|
|
2231
|
-
// Limit
|
|
608
|
+
pendingQuestions.sort((a, b) => { const pDiff = (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2); if (pDiff !== 0) return pDiff; return new Date(a.created_at) - new Date(b.created_at); });
|
|
2232
609
|
return pendingQuestions.slice(0, limit);
|
|
2233
610
|
}
|
|
2234
611
|
|
|
2235
|
-
/**
|
|
2236
|
-
* Format questions for display to user
|
|
2237
|
-
*/
|
|
2238
612
|
function formatQuestionsForUser(questions) {
|
|
2239
613
|
if (questions.length === 0) return null;
|
|
2240
|
-
|
|
2241
|
-
// Group by topic
|
|
2242
614
|
const byTopic = {};
|
|
2243
|
-
for (const q of questions) {
|
|
2244
|
-
const topicKey = q.topic_title || q.topic_id || 'General';
|
|
2245
|
-
if (!byTopic[topicKey]) {
|
|
2246
|
-
byTopic[topicKey] = [];
|
|
2247
|
-
}
|
|
2248
|
-
byTopic[topicKey].push(q);
|
|
2249
|
-
}
|
|
2250
|
-
|
|
615
|
+
for (const q of questions) { const topicKey = q.topic_title || q.topic_id || 'General'; if (!byTopic[topicKey]) { byTopic[topicKey] = []; } byTopic[topicKey].push(q); }
|
|
2251
616
|
let output = '';
|
|
2252
617
|
for (const [topic, qs] of Object.entries(byTopic)) {
|
|
2253
618
|
output += `## Topic: ${topic} (${qs.length} question${qs.length > 1 ? 's' : ''})\n\n`;
|
|
2254
|
-
|
|
2255
|
-
for (let i = 0; i < qs.length; i++) {
|
|
2256
|
-
const q = qs[i];
|
|
2257
|
-
output += `${i + 1}. **[${q.priority}]** ${q.question}\n`;
|
|
2258
|
-
if (q.examples && q.examples.length > 0) {
|
|
2259
|
-
output += ` _Examples: "${q.examples.join('" or "')}"_\n`;
|
|
2260
|
-
}
|
|
2261
|
-
output += '\n';
|
|
2262
|
-
}
|
|
619
|
+
for (let i = 0; i < qs.length; i++) { const q = qs[i]; output += `${i + 1}. **[${q.priority}]** ${q.question}\n`; if (q.examples && q.examples.length > 0) { output += ` _Examples: "${q.examples.join('" or "')}"_\n`; } output += '\n'; }
|
|
2263
620
|
}
|
|
2264
|
-
|
|
2265
621
|
output += '---\n\nYou can answer all at once or one at a time. Just reply naturally!';
|
|
2266
|
-
|
|
2267
622
|
return output;
|
|
2268
623
|
}
|
|
2269
624
|
|
|
2270
|
-
/**
|
|
2271
|
-
* Process user answers in conversation
|
|
2272
|
-
* @param {string} userResponse - User's answer text
|
|
2273
|
-
* @param {object} options - Processing options
|
|
2274
|
-
* @param {boolean} options.forceVoice - Force voice processing
|
|
2275
|
-
*/
|
|
2276
625
|
function processConversationResponse(userResponse, options = {}) {
|
|
2277
626
|
const clarifications = loadClarifications();
|
|
2278
|
-
if (!clarifications) {
|
|
2279
|
-
return { error: 'No active clarification session' };
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
// Get currently pending questions (prioritized)
|
|
627
|
+
if (!clarifications) { return { error: 'No active clarification session' }; }
|
|
2283
628
|
const pendingQuestions = getQuestionsForPresentation(null, 10);
|
|
2284
|
-
if (pendingQuestions.length === 0) {
|
|
2285
|
-
return {
|
|
2286
|
-
complete: true,
|
|
2287
|
-
message: 'All questions have been answered!'
|
|
2288
|
-
};
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
// Process voice input if detected or forced
|
|
629
|
+
if (pendingQuestions.length === 0) { return { complete: true, message: 'All questions have been answered!' }; }
|
|
2292
630
|
let processedInput = userResponse;
|
|
2293
631
|
let voiceProcessing = null;
|
|
2294
|
-
|
|
2295
632
|
const voiceResult = processVoiceAnswer(userResponse, options.forceVoice);
|
|
2296
|
-
if (voiceResult.isVoice) {
|
|
2297
|
-
processedInput = voiceResult.normalized;
|
|
2298
|
-
voiceProcessing = voiceResult.processing;
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
// Parse the user's response (using normalized text if voice)
|
|
633
|
+
if (voiceResult.isVoice) { processedInput = voiceResult.normalized; voiceProcessing = voiceResult.processing; }
|
|
2302
634
|
const parsedAnswers = parseAnswers(processedInput, pendingQuestions);
|
|
2303
|
-
|
|
2304
|
-
const results = {
|
|
2305
|
-
captured: [],
|
|
2306
|
-
derived_statements: [],
|
|
2307
|
-
followups_added: [],
|
|
2308
|
-
remaining_questions: 0,
|
|
2309
|
-
complete: false,
|
|
2310
|
-
voice: voiceProcessing ? {
|
|
2311
|
-
detected: true,
|
|
2312
|
-
original: userResponse,
|
|
2313
|
-
normalized: processedInput,
|
|
2314
|
-
processing: voiceProcessing
|
|
2315
|
-
} : null
|
|
2316
|
-
};
|
|
2317
|
-
|
|
2318
|
-
// Determine source (voice or text)
|
|
635
|
+
const results = { captured: [], derived_statements: [], followups_added: [], remaining_questions: 0, complete: false, voice: voiceProcessing ? { detected: true, original: userResponse, normalized: processedInput, processing: voiceProcessing } : null };
|
|
2319
636
|
const answerSource = voiceProcessing ? 'voice' : 'conversation';
|
|
2320
|
-
|
|
2321
|
-
// Record the answer received interaction
|
|
2322
|
-
recordInteraction('answer_received', {
|
|
2323
|
-
raw_input: userResponse,
|
|
2324
|
-
source: answerSource,
|
|
2325
|
-
voice_processed: !!voiceProcessing,
|
|
2326
|
-
parsed_count: parsedAnswers.length
|
|
2327
|
-
});
|
|
2328
|
-
|
|
2329
|
-
// Process each parsed answer
|
|
637
|
+
recordInteraction('answer_received', { raw_input: userResponse, source: answerSource, voice_processed: !!voiceProcessing, parsed_count: parsedAnswers.length });
|
|
2330
638
|
for (const parsed of parsedAnswers) {
|
|
2331
639
|
const question = pendingQuestions.find(q => q.id === parsed.question_id);
|
|
2332
640
|
if (!question) continue;
|
|
2333
|
-
|
|
2334
|
-
// Capture the answer
|
|
2335
641
|
captureAnswer(parsed.question_id, parsed.answer, answerSource);
|
|
2336
|
-
results.captured.push({
|
|
2337
|
-
question_id: parsed.question_id,
|
|
2338
|
-
question: question.question,
|
|
2339
|
-
answer: parsed.answer,
|
|
2340
|
-
confidence: parsed.confidence
|
|
2341
|
-
});
|
|
2342
|
-
|
|
2343
|
-
// Create derived statement
|
|
642
|
+
results.captured.push({ question_id: parsed.question_id, question: question.question, answer: parsed.answer, confidence: parsed.confidence });
|
|
2344
643
|
const derivedStmt = createDerivedStatement(question, parsed.answer);
|
|
2345
644
|
results.derived_statements.push(derivedStmt);
|
|
2346
|
-
|
|
2347
|
-
// Check for follow-ups
|
|
2348
645
|
const followups = checkFollowups(parsed.answer, question);
|
|
2349
|
-
if (followups.length > 0) {
|
|
2350
|
-
const added = addFollowupQuestions(followups);
|
|
2351
|
-
results.followups_added.push(...added);
|
|
2352
|
-
}
|
|
646
|
+
if (followups.length > 0) { const added = addFollowupQuestions(followups); results.followups_added.push(...added); }
|
|
2353
647
|
}
|
|
2354
|
-
|
|
2355
|
-
// Check completion
|
|
2356
648
|
const completion = checkCompletion();
|
|
2357
649
|
results.complete = completion.complete;
|
|
2358
650
|
results.remaining_questions = completion.pending_questions + completion.pending_contradictions;
|
|
2359
|
-
|
|
2360
|
-
// Get next questions if not complete
|
|
2361
|
-
if (!completion.complete) {
|
|
2362
|
-
results.next_questions = getQuestionsForPresentation(null, 5);
|
|
2363
|
-
results.formatted_questions = formatQuestionsForUser(results.next_questions);
|
|
2364
|
-
}
|
|
2365
|
-
|
|
651
|
+
if (!completion.complete) { results.next_questions = getQuestionsForPresentation(null, 5); results.formatted_questions = formatQuestionsForUser(results.next_questions); }
|
|
2366
652
|
return results;
|
|
2367
653
|
}
|
|
2368
654
|
|
|
2369
|
-
/**
|
|
2370
|
-
* Resolve a contradiction with user's choice
|
|
2371
|
-
*/
|
|
2372
655
|
function resolveContradictionWithChoice(contradictionId, choice) {
|
|
2373
656
|
const clarifications = loadClarifications();
|
|
2374
|
-
if (!clarifications) {
|
|
2375
|
-
throw new Error('No clarifications found');
|
|
2376
|
-
}
|
|
2377
|
-
|
|
657
|
+
if (!clarifications) { throw new Error('No clarifications found'); }
|
|
2378
658
|
const contradiction = clarifications.contradictions.find(c => c.id === contradictionId);
|
|
2379
|
-
if (!contradiction) {
|
|
2380
|
-
throw new Error(`Contradiction ${contradictionId} not found`);
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
// Load statement map to update
|
|
659
|
+
if (!contradiction) { throw new Error(`Contradiction ${contradictionId} not found`); }
|
|
2384
660
|
const stmtMap = loadStatementMap();
|
|
2385
|
-
if (!stmtMap) {
|
|
2386
|
-
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
if (choice === 'keep_both') {
|
|
2390
|
-
// Both are valid - not a real contradiction
|
|
2391
|
-
contradiction.status = 'resolved';
|
|
2392
|
-
contradiction.resolution = 'keep_both';
|
|
2393
|
-
contradiction.resolved_at = now();
|
|
2394
|
-
} else {
|
|
2395
|
-
// One wins, other is superseded
|
|
661
|
+
if (!stmtMap) { throw new Error('No statement map found'); }
|
|
662
|
+
if (choice === 'keep_both') { contradiction.status = 'resolved'; contradiction.resolution = 'keep_both'; contradiction.resolved_at = now(); } else {
|
|
2396
663
|
const winnerStmtId = contradiction.options?.find(o => o.id === choice)?.statement_id;
|
|
2397
664
|
const loserStmtId = contradiction.statements.find(id => id !== winnerStmtId);
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
const winner = stmtMap.statements.find(s => s.id === winnerStmtId);
|
|
2401
|
-
const loser = stmtMap.statements.find(s => s.id === loserStmtId);
|
|
2402
|
-
|
|
2403
|
-
if (winner && loser) {
|
|
2404
|
-
loser.superseded = true;
|
|
2405
|
-
loser.superseded_by = winnerStmtId;
|
|
2406
|
-
loser.superseded_reason = 'user_choice';
|
|
2407
|
-
winner.supersedes = loserStmtId;
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
|
|
2411
|
-
contradiction.status = 'resolved';
|
|
2412
|
-
contradiction.resolution = 'user_choice';
|
|
2413
|
-
contradiction.winner = winnerStmtId;
|
|
2414
|
-
contradiction.resolved_at = now();
|
|
665
|
+
if (winnerStmtId && loserStmtId) { const winner = stmtMap.statements.find(s => s.id === winnerStmtId); const loser = stmtMap.statements.find(s => s.id === loserStmtId); if (winner && loser) { loser.superseded = true; loser.superseded_by = winnerStmtId; loser.superseded_reason = 'user_choice'; winner.supersedes = loserStmtId; } }
|
|
666
|
+
contradiction.status = 'resolved'; contradiction.resolution = 'user_choice'; contradiction.winner = winnerStmtId; contradiction.resolved_at = now();
|
|
2415
667
|
}
|
|
2416
|
-
|
|
2417
|
-
// Update metadata
|
|
2418
668
|
clarifications.metadata.resolved_contradictions = (clarifications.metadata.resolved_contradictions || 0) + 1;
|
|
2419
669
|
clarifications.metadata.user_resolved_count = (clarifications.metadata.user_resolved_count || 0) + 1;
|
|
2420
|
-
|
|
2421
|
-
// Save both
|
|
2422
670
|
const activeDigest = loadActiveDigest();
|
|
2423
671
|
const mapPath = path.join(activeDigest.session.digest_path, 'statement-map.json');
|
|
2424
672
|
fs.writeFileSync(mapPath, JSON.stringify(stmtMap, null, 2));
|
|
2425
673
|
saveClarifications(clarifications);
|
|
2426
|
-
|
|
2427
674
|
return contradiction;
|
|
2428
675
|
}
|
|
2429
676
|
|
|
2430
|
-
// E2-S3 (Voice Answer Integration) extracted to flow-long-input-voice.js
|
|
2431
|
-
|
|
2432
677
|
// ============================================
|
|
2433
|
-
// E2-S4
|
|
678
|
+
// State Persistence (E2-S4)
|
|
2434
679
|
// ============================================
|
|
2435
680
|
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
*/
|
|
2439
|
-
function generateInteractionId() {
|
|
2440
|
-
return `i-${Date.now().toString(36)}`;
|
|
2441
|
-
}
|
|
2442
|
-
|
|
2443
|
-
/**
|
|
2444
|
-
* Generate unique checkpoint ID
|
|
2445
|
-
*/
|
|
2446
|
-
function generateCheckpointId() {
|
|
2447
|
-
return `cp-${Date.now().toString(36)}`;
|
|
2448
|
-
}
|
|
681
|
+
function generateInteractionId() { return `i-${Date.now().toString(36)}`; }
|
|
682
|
+
function generateCheckpointId() { return `cp-${Date.now().toString(36)}`; }
|
|
2449
683
|
|
|
2450
|
-
/**
|
|
2451
|
-
* Load conversation history
|
|
2452
|
-
*/
|
|
2453
684
|
function loadConversation() {
|
|
2454
685
|
const activeDigest = loadActiveDigest();
|
|
2455
|
-
if (!activeDigest.session?.digest_path) {
|
|
2456
|
-
return null;
|
|
2457
|
-
}
|
|
2458
|
-
|
|
686
|
+
if (!activeDigest.session?.digest_path) { return null; }
|
|
2459
687
|
const convPath = path.join(activeDigest.session.digest_path, 'conversation.json');
|
|
2460
|
-
|
|
2461
|
-
return null;
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
return JSON.parse(fs.readFileSync(convPath, 'utf8'));
|
|
688
|
+
return safeJsonParse(convPath, null);
|
|
2465
689
|
}
|
|
2466
690
|
|
|
2467
|
-
/**
|
|
2468
|
-
* Save conversation history
|
|
2469
|
-
*/
|
|
2470
691
|
function saveConversation(conversation) {
|
|
2471
692
|
const activeDigest = loadActiveDigest();
|
|
2472
|
-
if (!activeDigest.session?.digest_path) {
|
|
2473
|
-
throw new Error('No active digest session');
|
|
2474
|
-
}
|
|
2475
|
-
|
|
693
|
+
if (!activeDigest.session?.digest_path) { throw new Error('No active digest session'); }
|
|
2476
694
|
const convPath = path.join(activeDigest.session.digest_path, 'conversation.json');
|
|
2477
695
|
fs.writeFileSync(convPath, JSON.stringify(conversation, null, 2));
|
|
2478
696
|
return conversation;
|
|
2479
697
|
}
|
|
2480
698
|
|
|
2481
|
-
/**
|
|
2482
|
-
* Initialize conversation history for new session
|
|
2483
|
-
*/
|
|
2484
699
|
function initializeConversation(sessionId) {
|
|
2485
|
-
|
|
2486
|
-
session_id: sessionId,
|
|
2487
|
-
started_at: now(),
|
|
2488
|
-
last_interaction: now(),
|
|
2489
|
-
interactions: [],
|
|
2490
|
-
checkpoints: []
|
|
2491
|
-
};
|
|
2492
|
-
|
|
2493
|
-
return saveConversation(conversation);
|
|
700
|
+
return saveConversation({ session_id: sessionId, started_at: now(), last_interaction: now(), interactions: [], checkpoints: [] });
|
|
2494
701
|
}
|
|
2495
702
|
|
|
2496
|
-
/**
|
|
2497
|
-
* Record an interaction in conversation history
|
|
2498
|
-
*/
|
|
2499
703
|
function recordInteraction(type, data = {}) {
|
|
2500
704
|
let conversation = loadConversation();
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
if (activeDigest.session?.id) {
|
|
2505
|
-
conversation = initializeConversation(activeDigest.session.id);
|
|
2506
|
-
} else {
|
|
2507
|
-
return null;
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
const interaction = {
|
|
2512
|
-
id: generateInteractionId(),
|
|
2513
|
-
type,
|
|
2514
|
-
timestamp: now(),
|
|
2515
|
-
data
|
|
2516
|
-
};
|
|
2517
|
-
|
|
2518
|
-
conversation.interactions.push(interaction);
|
|
2519
|
-
conversation.last_interaction = now();
|
|
2520
|
-
|
|
705
|
+
if (!conversation) { const activeDigest = loadActiveDigest(); if (activeDigest.session?.id) { conversation = initializeConversation(activeDigest.session.id); } else { return null; } }
|
|
706
|
+
const interaction = { id: generateInteractionId(), type, timestamp: now(), data };
|
|
707
|
+
conversation.interactions.push(interaction); conversation.last_interaction = now();
|
|
2521
708
|
saveConversation(conversation);
|
|
2522
709
|
return interaction;
|
|
2523
710
|
}
|
|
2524
711
|
|
|
2525
|
-
/**
|
|
2526
|
-
* Create a checkpoint for recovery
|
|
2527
|
-
*/
|
|
2528
712
|
function createCheckpoint(reason = 'manual') {
|
|
2529
713
|
const conversation = loadConversation();
|
|
2530
|
-
if (!conversation) {
|
|
2531
|
-
return null;
|
|
2532
|
-
}
|
|
2533
|
-
|
|
714
|
+
if (!conversation) { return null; }
|
|
2534
715
|
const clarifications = loadClarifications();
|
|
2535
716
|
const topics = loadTopics();
|
|
2536
717
|
const activeDigest = loadActiveDigest();
|
|
2537
|
-
|
|
2538
718
|
const checkpoint = {
|
|
2539
|
-
id: generateCheckpointId(),
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
) || 'unknown' : 'unknown',
|
|
2545
|
-
questions: {
|
|
2546
|
-
total: clarifications?.questions?.length || 0,
|
|
2547
|
-
answered: clarifications?.questions?.filter(q => q.status === 'answered').length || 0,
|
|
2548
|
-
pending: clarifications?.questions?.filter(q => q.status === 'pending').length || 0
|
|
2549
|
-
},
|
|
2550
|
-
contradictions: {
|
|
2551
|
-
total: clarifications?.contradictions?.length || 0,
|
|
2552
|
-
resolved: clarifications?.contradictions?.filter(c => c.status === 'resolved').length || 0
|
|
2553
|
-
},
|
|
2554
|
-
topics: {
|
|
2555
|
-
total: topics?.topics?.length || 0,
|
|
2556
|
-
clarified: topics?.topics?.filter(t => t.clarification_complete).length || 0
|
|
2557
|
-
},
|
|
719
|
+
id: generateCheckpointId(), timestamp: now(), reason,
|
|
720
|
+
phase: activeDigest.phases ? Object.keys(activeDigest.phases).find(p => activeDigest.phases[p]?.status === 'in_progress') || 'unknown' : 'unknown',
|
|
721
|
+
questions: { total: clarifications?.questions?.length || 0, answered: clarifications?.questions?.filter(q => q.status === 'answered').length || 0, pending: clarifications?.questions?.filter(q => q.status === 'pending').length || 0 },
|
|
722
|
+
contradictions: { total: clarifications?.contradictions?.length || 0, resolved: clarifications?.contradictions?.filter(c => c.status === 'resolved').length || 0 },
|
|
723
|
+
topics: { total: topics?.topics?.length || 0, clarified: topics?.topics?.filter(t => t.clarification_complete).length || 0 },
|
|
2558
724
|
awaiting_response: false
|
|
2559
725
|
};
|
|
2560
|
-
|
|
2561
|
-
// Check if we're awaiting response (last interaction was questions_presented)
|
|
2562
726
|
const lastInteraction = conversation.interactions.slice(-1)[0];
|
|
2563
|
-
if (lastInteraction?.type === 'questions_presented') {
|
|
2564
|
-
checkpoint.awaiting_response = true;
|
|
2565
|
-
checkpoint.last_questions_presented = lastInteraction.data.question_ids;
|
|
2566
|
-
}
|
|
2567
|
-
|
|
727
|
+
if (lastInteraction?.type === 'questions_presented') { checkpoint.awaiting_response = true; checkpoint.last_questions_presented = lastInteraction.data.question_ids; }
|
|
2568
728
|
conversation.checkpoints.push(checkpoint);
|
|
2569
729
|
saveConversation(conversation);
|
|
2570
|
-
|
|
2571
730
|
return checkpoint;
|
|
2572
731
|
}
|
|
2573
732
|
|
|
2574
|
-
/**
|
|
2575
|
-
* Detect if there's an interrupted session
|
|
2576
|
-
*/
|
|
2577
733
|
function detectInterruptedSession() {
|
|
2578
734
|
const activeDigest = loadActiveDigest();
|
|
2579
|
-
|
|
2580
|
-
if (
|
|
2581
|
-
return { interrupted: false };
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
// Check if session is already complete
|
|
2585
|
-
if (activeDigest.session?.status === 'completed') {
|
|
2586
|
-
return { interrupted: false };
|
|
2587
|
-
}
|
|
2588
|
-
|
|
735
|
+
if (!activeDigest.session?.digest_path) { return { interrupted: false }; }
|
|
736
|
+
if (activeDigest.session?.status === 'completed') { return { interrupted: false }; }
|
|
2589
737
|
const conversation = loadConversation();
|
|
2590
|
-
if (!conversation) {
|
|
2591
|
-
return { interrupted: false };
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
// Check if there are pending questions
|
|
738
|
+
if (!conversation) { return { interrupted: false }; }
|
|
2595
739
|
const clarifications = loadClarifications();
|
|
2596
|
-
if (!clarifications) {
|
|
2597
|
-
return { interrupted: false };
|
|
2598
|
-
}
|
|
2599
|
-
|
|
740
|
+
if (!clarifications) { return { interrupted: false }; }
|
|
2600
741
|
const pendingQuestions = clarifications.questions?.filter(q => q.status === 'pending') || [];
|
|
2601
|
-
if (pendingQuestions.length === 0) {
|
|
2602
|
-
return { interrupted: false };
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
// Calculate time since last interaction
|
|
742
|
+
if (pendingQuestions.length === 0) { return { interrupted: false }; }
|
|
2606
743
|
const lastInteraction = new Date(conversation.last_interaction);
|
|
2607
744
|
const timeSinceMs = Date.now() - lastInteraction.getTime();
|
|
2608
745
|
const timeSinceMinutes = Math.floor(timeSinceMs / 60000);
|
|
2609
|
-
|
|
2610
|
-
// Get last checkpoint
|
|
2611
746
|
const lastCheckpoint = conversation.checkpoints.slice(-1)[0];
|
|
2612
|
-
|
|
2613
|
-
// Check if we were waiting for user input
|
|
2614
747
|
const lastInteractionData = conversation.interactions.slice(-1)[0];
|
|
2615
748
|
const wasAwaitingResponse = lastInteractionData?.type === 'questions_presented';
|
|
2616
|
-
|
|
2617
|
-
return {
|
|
2618
|
-
interrupted: true,
|
|
2619
|
-
session_id: activeDigest.session.id,
|
|
2620
|
-
digest_path: activeDigest.session.digest_path,
|
|
2621
|
-
reason: wasAwaitingResponse ? 'awaiting_response' : 'incomplete',
|
|
2622
|
-
last_interaction: conversation.last_interaction,
|
|
2623
|
-
time_since_minutes: timeSinceMinutes,
|
|
2624
|
-
time_since_formatted: formatTimeSince(timeSinceMs),
|
|
2625
|
-
checkpoint: lastCheckpoint,
|
|
2626
|
-
pending_questions: pendingQuestions.length,
|
|
2627
|
-
answered_questions: clarifications.questions?.filter(q => q.status === 'answered').length || 0,
|
|
2628
|
-
total_questions: clarifications.questions?.length || 0
|
|
2629
|
-
};
|
|
749
|
+
return { interrupted: true, session_id: activeDigest.session.id, digest_path: activeDigest.session.digest_path, reason: wasAwaitingResponse ? 'awaiting_response' : 'incomplete', last_interaction: conversation.last_interaction, time_since_minutes: timeSinceMinutes, time_since_formatted: formatTimeSince(timeSinceMs), checkpoint: lastCheckpoint, pending_questions: pendingQuestions.length, answered_questions: clarifications.questions?.filter(q => q.status === 'answered').length || 0, total_questions: clarifications.questions?.length || 0 };
|
|
2630
750
|
}
|
|
2631
751
|
|
|
2632
|
-
/**
|
|
2633
|
-
* Format time since last interaction
|
|
2634
|
-
*/
|
|
2635
752
|
function formatTimeSince(ms) {
|
|
2636
|
-
const minutes = Math.floor(ms / 60000);
|
|
2637
|
-
const hours = Math.floor(minutes / 60);
|
|
2638
|
-
const days = Math.floor(hours / 24);
|
|
2639
|
-
|
|
753
|
+
const minutes = Math.floor(ms / 60000); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24);
|
|
2640
754
|
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
2641
755
|
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
2642
756
|
if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
|
|
2643
757
|
return 'just now';
|
|
2644
758
|
}
|
|
2645
759
|
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
const
|
|
2656
|
-
|
|
2657
|
-
const conversation = loadConversation();
|
|
2658
|
-
|
|
2659
|
-
// Get recent answered questions for context
|
|
2660
|
-
const recentAnswers = clarifications.questions
|
|
2661
|
-
.filter(q => q.status === 'answered')
|
|
2662
|
-
.slice(-5)
|
|
2663
|
-
.map(q => ({
|
|
2664
|
-
topic: q.topic_title,
|
|
2665
|
-
question: q.question,
|
|
2666
|
-
answer: q.answer,
|
|
2667
|
-
answered_at: q.answered_at
|
|
2668
|
-
}));
|
|
2669
|
-
|
|
2670
|
-
// Get pending questions
|
|
2671
|
-
const pendingByTopic = {};
|
|
2672
|
-
for (const q of clarifications.questions.filter(q => q.status === 'pending')) {
|
|
2673
|
-
const topicKey = q.topic_title || q.topic_id;
|
|
2674
|
-
if (!pendingByTopic[topicKey]) {
|
|
2675
|
-
pendingByTopic[topicKey] = [];
|
|
2676
|
-
}
|
|
2677
|
-
pendingByTopic[topicKey].push(q);
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
|
-
// Get topics status
|
|
2681
|
-
const topicsStatus = (topics.topics || []).map(t => ({
|
|
2682
|
-
id: t.id,
|
|
2683
|
-
title: t.title,
|
|
2684
|
-
pending_questions: clarifications.questions.filter(q => q.topic_id === t.id && q.status === 'pending').length,
|
|
2685
|
-
answered_questions: clarifications.questions.filter(q => q.topic_id === t.id && q.status === 'answered').length
|
|
2686
|
-
}));
|
|
2687
|
-
|
|
2688
|
-
return {
|
|
2689
|
-
session_id: interrupted.session_id,
|
|
2690
|
-
started_at: conversation.started_at,
|
|
2691
|
-
last_active: interrupted.last_interaction,
|
|
2692
|
-
time_since: interrupted.time_since_formatted,
|
|
2693
|
-
progress: {
|
|
2694
|
-
answered: interrupted.answered_questions,
|
|
2695
|
-
pending: interrupted.pending_questions,
|
|
2696
|
-
total: interrupted.total_questions,
|
|
2697
|
-
percentage: Math.round((interrupted.answered_questions / interrupted.total_questions) * 100)
|
|
2698
|
-
},
|
|
2699
|
-
recent_answers: recentAnswers,
|
|
2700
|
-
pending_by_topic: pendingByTopic,
|
|
2701
|
-
topics_status: topicsStatus,
|
|
2702
|
-
checkpoint: interrupted.checkpoint
|
|
2703
|
-
};
|
|
760
|
+
function generateRecoverySummary() {
|
|
761
|
+
const interrupted = detectInterruptedSession();
|
|
762
|
+
if (!interrupted.interrupted) { return null; }
|
|
763
|
+
const clarifications = loadClarifications();
|
|
764
|
+
const topics = loadTopics();
|
|
765
|
+
const conversation = loadConversation();
|
|
766
|
+
const recentAnswers = clarifications.questions.filter(q => q.status === 'answered').slice(-5).map(q => ({ topic: q.topic_title, question: q.question, answer: q.answer, answered_at: q.answered_at }));
|
|
767
|
+
const pendingByTopic = {};
|
|
768
|
+
for (const q of clarifications.questions.filter(q => q.status === 'pending')) { const topicKey = q.topic_title || q.topic_id; if (!pendingByTopic[topicKey]) { pendingByTopic[topicKey] = []; } pendingByTopic[topicKey].push(q); }
|
|
769
|
+
const topicsStatus = (topics.topics || []).map(t => ({ id: t.id, title: t.title, pending_questions: clarifications.questions.filter(q => q.topic_id === t.id && q.status === 'pending').length, answered_questions: clarifications.questions.filter(q => q.topic_id === t.id && q.status === 'answered').length }));
|
|
770
|
+
return { session_id: interrupted.session_id, started_at: conversation.started_at, last_active: interrupted.last_interaction, time_since: interrupted.time_since_formatted, progress: { answered: interrupted.answered_questions, pending: interrupted.pending_questions, total: interrupted.total_questions, percentage: Math.round((interrupted.answered_questions / interrupted.total_questions) * 100) }, recent_answers: recentAnswers, pending_by_topic: pendingByTopic, topics_status: topicsStatus, checkpoint: interrupted.checkpoint };
|
|
2704
771
|
}
|
|
2705
772
|
|
|
2706
|
-
/**
|
|
2707
|
-
* Resume an interrupted session
|
|
2708
|
-
*/
|
|
2709
773
|
function resumeSession() {
|
|
2710
774
|
const interrupted = detectInterruptedSession();
|
|
2711
|
-
if (!interrupted.interrupted) {
|
|
2712
|
-
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
// Record the resume
|
|
2716
|
-
recordInteraction('session_resumed', {
|
|
2717
|
-
resumed_from: interrupted.checkpoint?.id,
|
|
2718
|
-
time_since: interrupted.time_since_formatted
|
|
2719
|
-
});
|
|
2720
|
-
|
|
2721
|
-
// Create a new checkpoint
|
|
775
|
+
if (!interrupted.interrupted) { return { error: 'No interrupted session to resume' }; }
|
|
776
|
+
recordInteraction('session_resumed', { resumed_from: interrupted.checkpoint?.id, time_since: interrupted.time_since_formatted });
|
|
2722
777
|
createCheckpoint('resume');
|
|
2723
|
-
|
|
2724
|
-
// Get next questions to present
|
|
2725
778
|
const nextQuestions = getQuestionsForPresentation(null, 5);
|
|
2726
|
-
|
|
2727
|
-
return {
|
|
2728
|
-
resumed: true,
|
|
2729
|
-
session_id: interrupted.session_id,
|
|
2730
|
-
summary: generateRecoverySummary(),
|
|
2731
|
-
next_questions: nextQuestions,
|
|
2732
|
-
formatted_questions: formatQuestionsForUser(nextQuestions)
|
|
2733
|
-
};
|
|
779
|
+
return { resumed: true, session_id: interrupted.session_id, summary: generateRecoverySummary(), next_questions: nextQuestions, formatted_questions: formatQuestionsForUser(nextQuestions) };
|
|
2734
780
|
}
|
|
2735
781
|
|
|
2736
|
-
|
|
2737
|
-
* Mark questions as presented (for tracking)
|
|
2738
|
-
*/
|
|
2739
|
-
function markQuestionsPresented(questionIds, topic = null) {
|
|
2740
|
-
recordInteraction('questions_presented', {
|
|
2741
|
-
question_ids: questionIds,
|
|
2742
|
-
topic
|
|
2743
|
-
});
|
|
2744
|
-
|
|
2745
|
-
createCheckpoint('questions_presented');
|
|
2746
|
-
}
|
|
782
|
+
function markQuestionsPresented(questionIds, topic = null) { recordInteraction('questions_presented', { question_ids: questionIds, topic }); createCheckpoint('questions_presented'); }
|
|
2747
783
|
|
|
2748
|
-
/**
|
|
2749
|
-
* Get session history summary
|
|
2750
|
-
*/
|
|
2751
784
|
function getSessionHistory() {
|
|
2752
785
|
const conversation = loadConversation();
|
|
2753
|
-
if (!conversation) {
|
|
2754
|
-
return null;
|
|
2755
|
-
}
|
|
2756
|
-
|
|
786
|
+
if (!conversation) { return null; }
|
|
2757
787
|
const clarifications = loadClarifications();
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
const summary = {
|
|
2761
|
-
session_id: conversation.session_id,
|
|
2762
|
-
started_at: conversation.started_at,
|
|
2763
|
-
last_interaction: conversation.last_interaction,
|
|
2764
|
-
duration_ms: new Date(conversation.last_interaction) - new Date(conversation.started_at),
|
|
2765
|
-
interaction_count: conversation.interactions.length,
|
|
2766
|
-
checkpoint_count: conversation.checkpoints.length,
|
|
2767
|
-
answers_given: clarifications?.questions?.filter(q => q.status === 'answered').length || 0,
|
|
2768
|
-
interactions_by_type: {}
|
|
2769
|
-
};
|
|
2770
|
-
|
|
2771
|
-
for (const interaction of conversation.interactions) {
|
|
2772
|
-
summary.interactions_by_type[interaction.type] = (summary.interactions_by_type[interaction.type] || 0) + 1;
|
|
2773
|
-
}
|
|
2774
|
-
|
|
788
|
+
const summary = { session_id: conversation.session_id, started_at: conversation.started_at, last_interaction: conversation.last_interaction, duration_ms: new Date(conversation.last_interaction) - new Date(conversation.started_at), interaction_count: conversation.interactions.length, checkpoint_count: conversation.checkpoints.length, answers_given: clarifications?.questions?.filter(q => q.status === 'answered').length || 0, interactions_by_type: {} };
|
|
789
|
+
for (const interaction of conversation.interactions) { summary.interactions_by_type[interaction.type] = (summary.interactions_by_type[interaction.type] || 0) + 1; }
|
|
2775
790
|
return summary;
|
|
2776
791
|
}
|
|
2777
792
|
|
|
2778
|
-
/**
|
|
2779
|
-
* Export session state for backup
|
|
2780
|
-
*/
|
|
2781
793
|
function exportSession(format = 'json') {
|
|
2782
794
|
const activeDigest = loadActiveDigest();
|
|
2783
|
-
if (!activeDigest.session?.digest_path) {
|
|
2784
|
-
|
|
2785
|
-
}
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
const statements = loadStatementMap();
|
|
2789
|
-
const clarifications = loadClarifications();
|
|
2790
|
-
const conversation = loadConversation();
|
|
2791
|
-
|
|
2792
|
-
const exportData = {
|
|
2793
|
-
exported_at: now(),
|
|
2794
|
-
session: activeDigest.session,
|
|
2795
|
-
phases: activeDigest.phases,
|
|
2796
|
-
topics,
|
|
2797
|
-
statements,
|
|
2798
|
-
clarifications,
|
|
2799
|
-
conversation
|
|
2800
|
-
};
|
|
2801
|
-
|
|
2802
|
-
if (format === 'json') {
|
|
2803
|
-
return exportData;
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2806
|
-
if (format === 'md') {
|
|
2807
|
-
return formatExportAsMarkdown(exportData);
|
|
2808
|
-
}
|
|
2809
|
-
|
|
795
|
+
if (!activeDigest.session?.digest_path) { return { error: 'No active session' }; }
|
|
796
|
+
const topics = loadTopics(); const statements = loadStatementMap(); const clarifications = loadClarifications(); const conversation = loadConversation();
|
|
797
|
+
const exportData = { exported_at: now(), session: activeDigest.session, phases: activeDigest.phases, topics, statements, clarifications, conversation };
|
|
798
|
+
if (format === 'json') { return exportData; }
|
|
799
|
+
if (format === 'md') { return formatExportAsMarkdown(exportData); }
|
|
2810
800
|
return exportData;
|
|
2811
801
|
}
|
|
2812
802
|
|
|
2813
|
-
/**
|
|
2814
|
-
* Format export as markdown
|
|
2815
|
-
*/
|
|
2816
803
|
function formatExportAsMarkdown(data) {
|
|
2817
804
|
let md = `# Transcript Digest Export\n\n`;
|
|
2818
|
-
md += `**Session ID:** ${data.session.id}\n`;
|
|
2819
|
-
md += `**Exported:** ${data.exported_at}\n\n`;
|
|
2820
|
-
|
|
805
|
+
md += `**Session ID:** ${data.session.id}\n`; md += `**Exported:** ${data.exported_at}\n\n`;
|
|
2821
806
|
md += `## Progress\n\n`;
|
|
2822
807
|
const answered = data.clarifications?.questions?.filter(q => q.status === 'answered').length || 0;
|
|
2823
808
|
const total = data.clarifications?.questions?.length || 0;
|
|
2824
|
-
md += `- Questions answered: ${answered}/${total}\n`;
|
|
2825
|
-
md += `- Topics: ${data.topics?.topics?.length || 0}\n`;
|
|
2826
|
-
md += `- Statements: ${data.statements?.statements?.length || 0}\n\n`;
|
|
2827
|
-
|
|
809
|
+
md += `- Questions answered: ${answered}/${total}\n`; md += `- Topics: ${data.topics?.topics?.length || 0}\n`; md += `- Statements: ${data.statements?.statements?.length || 0}\n\n`;
|
|
2828
810
|
md += `## Topics\n\n`;
|
|
2829
|
-
for (const topic of (data.topics?.topics || [])) {
|
|
2830
|
-
md += `### ${topic.title}\n`;
|
|
2831
|
-
md += `- Entities: ${(topic.entities || []).join(', ')}\n`;
|
|
2832
|
-
md += `- Keywords: ${(topic.keywords || []).join(', ')}\n\n`;
|
|
2833
|
-
}
|
|
2834
|
-
|
|
811
|
+
for (const topic of (data.topics?.topics || [])) { md += `### ${topic.title}\n`; md += `- Entities: ${(topic.entities || []).join(', ')}\n`; md += `- Keywords: ${(topic.keywords || []).join(', ')}\n\n`; }
|
|
2835
812
|
md += `## Answered Questions\n\n`;
|
|
2836
|
-
for (const q of (data.clarifications?.questions || []).filter(q => q.status === 'answered')) {
|
|
2837
|
-
md += `### ${q.topic_title || 'General'}\n`;
|
|
2838
|
-
md += `**Q:** ${q.question}\n`;
|
|
2839
|
-
md += `**A:** ${q.answer}\n\n`;
|
|
2840
|
-
}
|
|
2841
|
-
|
|
813
|
+
for (const q of (data.clarifications?.questions || []).filter(q => q.status === 'answered')) { md += `### ${q.topic_title || 'General'}\n`; md += `**Q:** ${q.question}\n`; md += `**A:** ${q.answer}\n\n`; }
|
|
2842
814
|
md += `## Pending Questions\n\n`;
|
|
2843
|
-
for (const q of (data.clarifications?.questions || []).filter(q => q.status === 'pending')) {
|
|
2844
|
-
md += `- [${q.priority}] ${q.question}\n`;
|
|
2845
|
-
}
|
|
2846
|
-
|
|
815
|
+
for (const q of (data.clarifications?.questions || []).filter(q => q.status === 'pending')) { md += `- [${q.priority}] ${q.question}\n`; }
|
|
2847
816
|
return md;
|
|
2848
817
|
}
|
|
2849
818
|
|
|
2850
|
-
/**
|
|
2851
|
-
* Review all answered questions
|
|
2852
|
-
*/
|
|
2853
819
|
function reviewAnswers() {
|
|
2854
820
|
const clarifications = loadClarifications();
|
|
2855
|
-
if (!clarifications) {
|
|
2856
|
-
return { error: 'No clarifications found' };
|
|
2857
|
-
}
|
|
2858
|
-
|
|
821
|
+
if (!clarifications) { return { error: 'No clarifications found' }; }
|
|
2859
822
|
const answered = clarifications.questions.filter(q => q.status === 'answered');
|
|
2860
|
-
|
|
2861
|
-
// Group by topic
|
|
2862
823
|
const byTopic = {};
|
|
2863
|
-
for (const q of answered) {
|
|
2864
|
-
|
|
2865
|
-
if (!byTopic[topicKey]) {
|
|
2866
|
-
byTopic[topicKey] = [];
|
|
2867
|
-
}
|
|
2868
|
-
byTopic[topicKey].push({
|
|
2869
|
-
id: q.id,
|
|
2870
|
-
question: q.question,
|
|
2871
|
-
answer: q.answer,
|
|
2872
|
-
answered_at: q.answered_at,
|
|
2873
|
-
source: q.answer_source
|
|
2874
|
-
});
|
|
2875
|
-
}
|
|
2876
|
-
|
|
2877
|
-
return {
|
|
2878
|
-
total_answered: answered.length,
|
|
2879
|
-
by_topic: byTopic
|
|
2880
|
-
};
|
|
2881
|
-
}
|
|
2882
|
-
|
|
2883
|
-
/**
|
|
2884
|
-
* Save topics to digest
|
|
2885
|
-
*/
|
|
2886
|
-
function saveTopics(topics) {
|
|
2887
|
-
const activeDigest = loadActiveDigest();
|
|
2888
|
-
if (!activeDigest.session.digest_path) {
|
|
2889
|
-
throw new Error('No active digest session');
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
|
-
const topicsPath = path.join(activeDigest.session.digest_path, 'topics.json');
|
|
2893
|
-
|
|
2894
|
-
// Ensure proper structure
|
|
2895
|
-
const topicsData = {
|
|
2896
|
-
topics: topics.topics || topics,
|
|
2897
|
-
metadata: {
|
|
2898
|
-
total_topics: (topics.topics || topics).length,
|
|
2899
|
-
active_topics: (topics.topics || topics).filter(t => t.status === 'active').length,
|
|
2900
|
-
clarified_topics: (topics.topics || topics).filter(t => t.clarification_complete).length,
|
|
2901
|
-
generated_topics: (topics.topics || topics).filter(t => t.stories_generated).length,
|
|
2902
|
-
detected_at: now(),
|
|
2903
|
-
last_updated: now(),
|
|
2904
|
-
transcript_word_count: activeDigest.input?.word_count || 0,
|
|
2905
|
-
detection_method: 'pass-1-extraction'
|
|
2906
|
-
}
|
|
2907
|
-
};
|
|
2908
|
-
|
|
2909
|
-
fs.writeFileSync(topicsPath, JSON.stringify(topicsData, null, 2));
|
|
2910
|
-
|
|
2911
|
-
// Update phase
|
|
2912
|
-
updatePhase('topic_extraction', 'completed', { topics_found: topicsData.topics.length });
|
|
2913
|
-
|
|
2914
|
-
return topicsData;
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
/**
|
|
2918
|
-
* Load topics from digest
|
|
2919
|
-
*/
|
|
2920
|
-
function loadTopics() {
|
|
2921
|
-
const activeDigest = loadActiveDigest();
|
|
2922
|
-
if (!activeDigest.session.digest_path) {
|
|
2923
|
-
return null;
|
|
2924
|
-
}
|
|
2925
|
-
|
|
2926
|
-
const topicsPath = path.join(activeDigest.session.digest_path, 'topics.json');
|
|
2927
|
-
try {
|
|
2928
|
-
return JSON.parse(fs.readFileSync(topicsPath, 'utf8'));
|
|
2929
|
-
} catch (_err) {
|
|
2930
|
-
return null;
|
|
2931
|
-
}
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
/**
|
|
2935
|
-
* Get digest status
|
|
2936
|
-
*/
|
|
2937
|
-
function getStatus() {
|
|
2938
|
-
const activeDigest = loadActiveDigest();
|
|
2939
|
-
|
|
2940
|
-
if (activeDigest.session.status === 'inactive') {
|
|
2941
|
-
return { active: false };
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
|
-
return {
|
|
2945
|
-
active: true,
|
|
2946
|
-
id: activeDigest.session.id,
|
|
2947
|
-
phase: activeDigest.session.phase,
|
|
2948
|
-
phases: activeDigest.phases,
|
|
2949
|
-
input: activeDigest.input
|
|
2950
|
-
};
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
|
-
/**
|
|
2954
|
-
* Check if input should trigger digestion
|
|
2955
|
-
*/
|
|
2956
|
-
function shouldTriggerDigestion(text) {
|
|
2957
|
-
const config = loadConfig();
|
|
2958
|
-
const threshold = config.autoTriggerThreshold || 2000;
|
|
2959
|
-
const wordCount = countWords(text);
|
|
2960
|
-
|
|
2961
|
-
if (wordCount < threshold) {
|
|
2962
|
-
return { trigger: false, reason: 'below_threshold', wordCount };
|
|
2963
|
-
}
|
|
2964
|
-
|
|
2965
|
-
// Check content type
|
|
2966
|
-
const contentType = classifyContent(text);
|
|
2967
|
-
|
|
2968
|
-
if (contentType.type === 'requirements' || contentType.type === 'transcript') {
|
|
2969
|
-
return { trigger: true, reason: 'auto', wordCount, contentType };
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
if (contentType.type === 'code') {
|
|
2973
|
-
return { trigger: false, reason: 'code_detected', wordCount, contentType };
|
|
2974
|
-
}
|
|
2975
|
-
|
|
2976
|
-
// Ambiguous - suggest asking
|
|
2977
|
-
return { trigger: 'ask', reason: 'ambiguous', wordCount, contentType };
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
/**
|
|
2981
|
-
* Basic content classification
|
|
2982
|
-
*/
|
|
2983
|
-
function classifyContent(text) {
|
|
2984
|
-
// Check for code patterns
|
|
2985
|
-
const codePatterns = [
|
|
2986
|
-
/```[\s\S]*```/g,
|
|
2987
|
-
/function\s+\w+\s*\(/g,
|
|
2988
|
-
/const\s+\w+\s*=/g,
|
|
2989
|
-
/import\s+.*from/g,
|
|
2990
|
-
/class\s+\w+/g
|
|
2991
|
-
];
|
|
2992
|
-
|
|
2993
|
-
let codeMatches = 0;
|
|
2994
|
-
for (const pattern of codePatterns) {
|
|
2995
|
-
const matches = text.match(pattern);
|
|
2996
|
-
if (matches) codeMatches += matches.length;
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
|
-
if (codeMatches > 10) {
|
|
3000
|
-
return { type: 'code', confidence: 0.8 };
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
|
-
// Check for requirements patterns
|
|
3004
|
-
const reqPatterns = [
|
|
3005
|
-
/we need/gi,
|
|
3006
|
-
/should have/gi,
|
|
3007
|
-
/must support/gi,
|
|
3008
|
-
/add a feature/gi,
|
|
3009
|
-
/implement/gi,
|
|
3010
|
-
/the \w+ should/gi
|
|
3011
|
-
];
|
|
3012
|
-
|
|
3013
|
-
let reqMatches = 0;
|
|
3014
|
-
for (const pattern of reqPatterns) {
|
|
3015
|
-
const matches = text.match(pattern);
|
|
3016
|
-
if (matches) reqMatches += matches.length;
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
|
-
if (reqMatches > 5) {
|
|
3020
|
-
return { type: 'requirements', confidence: 0.85 };
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
// Check for transcript patterns
|
|
3024
|
-
const transcriptPatterns = [
|
|
3025
|
-
/^\d{2}:\d{2}/gm, // Timestamps
|
|
3026
|
-
/^speaker \d+:/gim,
|
|
3027
|
-
/^\[.*\]:/gm,
|
|
3028
|
-
/^[A-Z][a-z]+:/gm // Speaker names
|
|
3029
|
-
];
|
|
3030
|
-
|
|
3031
|
-
let transcriptMatches = 0;
|
|
3032
|
-
for (const pattern of transcriptPatterns) {
|
|
3033
|
-
const matches = text.match(pattern);
|
|
3034
|
-
if (matches) transcriptMatches += matches.length;
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
if (transcriptMatches > 10) {
|
|
3038
|
-
return { type: 'transcript', confidence: 0.9 };
|
|
3039
|
-
}
|
|
3040
|
-
|
|
3041
|
-
return { type: 'unknown', confidence: 0.5 };
|
|
824
|
+
for (const q of answered) { const topicKey = q.topic_title || q.topic_id || 'General'; if (!byTopic[topicKey]) { byTopic[topicKey] = []; } byTopic[topicKey].push({ id: q.id, question: q.question, answer: q.answer, answered_at: q.answered_at, source: q.answer_source }); }
|
|
825
|
+
return { total_answered: answered.length, by_topic: byTopic };
|
|
3042
826
|
}
|
|
3043
827
|
|
|
3044
|
-
//
|
|
3045
|
-
//
|
|
3046
|
-
//
|
|
828
|
+
// ============================================
|
|
829
|
+
// Complexity Analysis (stays here — orchestrates sub-modules)
|
|
830
|
+
// ============================================
|
|
3047
831
|
|
|
3048
|
-
/**
|
|
3049
|
-
* Main complexity analysis function
|
|
3050
|
-
*/
|
|
3051
832
|
function analyzeComplexity() {
|
|
3052
833
|
const topics = loadTopics();
|
|
3053
834
|
const statementMap = loadStatementMap();
|
|
3054
835
|
const clarifications = loadClarifications();
|
|
3055
|
-
|
|
3056
|
-
if (!topics || !topics.topics) {
|
|
3057
|
-
return { error: 'No topics found. Run Pass 1 first.' };
|
|
3058
|
-
}
|
|
3059
|
-
|
|
836
|
+
if (!topics || !topics.topics) { return { error: 'No topics found. Run Pass 1 first.' }; }
|
|
3060
837
|
const statements = statementMap?.statements || [];
|
|
3061
|
-
|
|
3062
|
-
// Build digest object
|
|
3063
|
-
const digest = {
|
|
3064
|
-
topics: topics.topics,
|
|
3065
|
-
statements,
|
|
3066
|
-
clarifications: clarifications || { questions: [], contradictions: [] }
|
|
3067
|
-
};
|
|
3068
|
-
|
|
3069
|
-
// Calculate overall complexity
|
|
838
|
+
const digest = { topics: topics.topics, statements, clarifications: clarifications || { questions: [], contradictions: [] } };
|
|
3070
839
|
const overallScore = calculateComplexityScore(digest);
|
|
3071
840
|
const level = getComplexityLevel(overallScore);
|
|
3072
|
-
|
|
3073
|
-
// Analyze each topic
|
|
3074
|
-
const topicAnalysis = topics.topics
|
|
3075
|
-
.filter(t => t.status === 'active')
|
|
3076
|
-
.map(t => analyzeTopicComplexity(t, statements, clarifications));
|
|
3077
|
-
|
|
3078
|
-
// Get output recommendation
|
|
841
|
+
const topicAnalysis = topics.topics.filter(t => t.status === 'active').map(t => analyzeTopicComplexity(t, statements, clarifications));
|
|
3079
842
|
const recommendation = recommendOutputStructure(overallScore, topicAnalysis);
|
|
3080
|
-
|
|
3081
|
-
// Extract entity summary
|
|
3082
843
|
const entitySummary = extractEntities(statements);
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
score: overallScore,
|
|
3088
|
-
level: level.level,
|
|
3089
|
-
description: level.description,
|
|
3090
|
-
confidence: 0.85
|
|
3091
|
-
},
|
|
3092
|
-
factors: {
|
|
3093
|
-
topic_count: topics.topics.filter(t => t.status === 'active').length,
|
|
3094
|
-
statement_count: statements.filter(s => s.meaningful !== false).length,
|
|
3095
|
-
question_count: clarifications?.questions?.length || 0,
|
|
3096
|
-
contradiction_count: clarifications?.contradictions?.length || 0,
|
|
3097
|
-
entity_types: countEntityTypes(statements),
|
|
3098
|
-
ui_components: entitySummary.ui_components.length,
|
|
3099
|
-
data_entities: entitySummary.data_entities.length,
|
|
3100
|
-
interactions: entitySummary.interactions.length
|
|
3101
|
-
},
|
|
3102
|
-
topic_analysis: topicAnalysis,
|
|
3103
|
-
recommendation,
|
|
3104
|
-
entity_summary: entitySummary
|
|
844
|
+
return {
|
|
845
|
+
overall: { score: overallScore, level: level.level, description: level.description, confidence: 0.85 },
|
|
846
|
+
factors: { topic_count: topics.topics.filter(t => t.status === 'active').length, statement_count: statements.filter(s => s.meaningful !== false).length, question_count: clarifications?.questions?.length || 0, contradiction_count: clarifications?.contradictions?.length || 0, entity_types: countEntityTypes(statements), ui_components: entitySummary.ui_components.length, data_entities: entitySummary.data_entities.length, interactions: entitySummary.interactions.length },
|
|
847
|
+
topic_analysis: topicAnalysis, recommendation, entity_summary: entitySummary
|
|
3105
848
|
};
|
|
3106
|
-
|
|
3107
|
-
return result;
|
|
3108
849
|
}
|
|
3109
850
|
|
|
3110
|
-
//
|
|
3111
|
-
|
|
851
|
+
// ============================================
|
|
852
|
+
// Module initialization (wire dependencies)
|
|
853
|
+
// ============================================
|
|
854
|
+
|
|
855
|
+
longInputAssociation.init({
|
|
3112
856
|
loadActiveDigest,
|
|
3113
|
-
|
|
3114
|
-
loadTopics,
|
|
3115
|
-
saveTopics,
|
|
3116
|
-
loadStatementMap,
|
|
3117
|
-
loadClarifications,
|
|
3118
|
-
isRequirement,
|
|
3119
|
-
isVagueStatement,
|
|
3120
|
-
analyzeComplexity,
|
|
3121
|
-
REQUIREMENT_PATTERNS,
|
|
3122
|
-
VAGUE_PATTERNS,
|
|
3123
|
-
ENTITY_PATTERNS
|
|
857
|
+
updatePhase
|
|
3124
858
|
});
|
|
3125
859
|
|
|
3126
|
-
|
|
3127
|
-
transcriptChunking.init({
|
|
860
|
+
longInputContradictions.init({
|
|
3128
861
|
loadActiveDigest,
|
|
3129
|
-
|
|
3130
|
-
countWords,
|
|
3131
|
-
now
|
|
862
|
+
calculateAssociationConfidence
|
|
3132
863
|
});
|
|
3133
864
|
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
865
|
+
longInputPasses.init({
|
|
866
|
+
loadActiveDigest, updatePhase, now, countWords,
|
|
867
|
+
loadTopics, saveTopics,
|
|
868
|
+
splitIntoStatements, isMeaningfulStatement, associateStatements,
|
|
869
|
+
saveStatementMap, loadStatementMap,
|
|
870
|
+
detectContradictions, resolveOrphan,
|
|
871
|
+
calculateResolutionConfidence, generateContradictionQuestion,
|
|
872
|
+
isAdditive, detectCorrectionPhrase,
|
|
873
|
+
saveClarifications, loadClarifications,
|
|
874
|
+
createSession
|
|
3138
875
|
});
|
|
3139
876
|
|
|
3140
|
-
//
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
*
|
|
3148
|
-
* @param {string} input - The input text to process
|
|
3149
|
-
* @param {Object} options - Processing options
|
|
3150
|
-
* @returns {Object} Quick scan results
|
|
3151
|
-
*/
|
|
3152
|
-
function quickProcess(input, _options = {}) {
|
|
3153
|
-
if (!input || typeof input !== 'string') {
|
|
3154
|
-
return { error: 'No input provided' };
|
|
3155
|
-
}
|
|
3156
|
-
|
|
3157
|
-
const startTime = Date.now();
|
|
3158
|
-
|
|
3159
|
-
// 1. Split into statements (returns objects with .text property)
|
|
3160
|
-
const statements = splitIntoStatements(input);
|
|
3161
|
-
// isMeaningfulStatement returns {meaningful: bool, reason: string}, filter on .meaningful
|
|
3162
|
-
const meaningfulStatements = statements.filter(s => isMeaningfulStatement(s.text).meaningful);
|
|
3163
|
-
|
|
3164
|
-
// 2. Quick topic extraction (keyword-based, no full analysis)
|
|
3165
|
-
const topicKeywords = new Set();
|
|
3166
|
-
const topicPatterns = [
|
|
3167
|
-
/\b(add|create|build|implement)\s+(?:a\s+)?(\w+(?:\s+\w+)?)/gi,
|
|
3168
|
-
/\b(\w+)\s+(feature|component|page|button|form|table|list)/gi,
|
|
3169
|
-
/\b(user|admin|guest)\s+(?:can|should|must|wants?)\s+(\w+)/gi
|
|
3170
|
-
];
|
|
3171
|
-
|
|
3172
|
-
for (const statement of meaningfulStatements) {
|
|
3173
|
-
const text = statement.text;
|
|
3174
|
-
for (const pattern of topicPatterns) {
|
|
3175
|
-
const matches = text.matchAll(pattern);
|
|
3176
|
-
for (const match of matches) {
|
|
3177
|
-
const keyword = (match[2] || match[1]).toLowerCase();
|
|
3178
|
-
if (keyword.length > 2) {
|
|
3179
|
-
topicKeywords.add(keyword);
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
|
|
3185
|
-
// 3. Quick contradiction detection
|
|
3186
|
-
const contradictions = [];
|
|
3187
|
-
const seenValues = new Map(); // attribute -> { value, text }
|
|
3188
|
-
|
|
3189
|
-
const valuePatterns = [
|
|
3190
|
-
{ pattern: /(\d+)\s*(columns?|rows?|items?|pages?)/gi, attr: 'count' },
|
|
3191
|
-
{ pattern: /(primary|secondary|danger|success)\s*(?:color|button)/gi, attr: 'style' },
|
|
3192
|
-
{ pattern: /(left|right|center|top|bottom)/gi, attr: 'position' }
|
|
3193
|
-
];
|
|
3194
|
-
|
|
3195
|
-
for (const statement of meaningfulStatements) {
|
|
3196
|
-
const text = statement.text;
|
|
3197
|
-
for (const { pattern, attr } of valuePatterns) {
|
|
3198
|
-
const match = text.match(pattern);
|
|
3199
|
-
if (match && match[1]) {
|
|
3200
|
-
const value = match[1].toLowerCase();
|
|
3201
|
-
const key = `${attr}`;
|
|
3202
|
-
|
|
3203
|
-
if (seenValues.has(key) && seenValues.get(key).value !== value) {
|
|
3204
|
-
// Check for correction phrase
|
|
3205
|
-
const isCorrection = detectCorrectionPhrase(text);
|
|
3206
|
-
|
|
3207
|
-
contradictions.push({
|
|
3208
|
-
attribute: attr,
|
|
3209
|
-
value1: seenValues.get(key).value,
|
|
3210
|
-
value2: value,
|
|
3211
|
-
statement1: seenValues.get(key).text.slice(0, 50),
|
|
3212
|
-
statement2: text.slice(0, 50),
|
|
3213
|
-
autoResolved: isCorrection,
|
|
3214
|
-
resolution: isCorrection ? `Later statement (${value}) supersedes` : 'needs_review'
|
|
3215
|
-
});
|
|
3216
|
-
}
|
|
3217
|
-
|
|
3218
|
-
seenValues.set(key, { value, text });
|
|
3219
|
-
}
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
const elapsed = Date.now() - startTime;
|
|
3224
|
-
|
|
3225
|
-
return {
|
|
3226
|
-
mode: 'quick',
|
|
3227
|
-
success: true,
|
|
3228
|
-
metrics: {
|
|
3229
|
-
totalStatements: statements.length,
|
|
3230
|
-
meaningfulStatements: meaningfulStatements.length,
|
|
3231
|
-
topicsDetected: topicKeywords.size,
|
|
3232
|
-
contradictionsFound: contradictions.length,
|
|
3233
|
-
autoResolved: contradictions.filter(c => c.autoResolved).length,
|
|
3234
|
-
processingTimeMs: elapsed
|
|
3235
|
-
},
|
|
3236
|
-
topics: Array.from(topicKeywords),
|
|
3237
|
-
contradictions: contradictions.filter(c => !c.autoResolved),
|
|
3238
|
-
summary: generateQuickSummary(meaningfulStatements.length, topicKeywords.size, contradictions)
|
|
3239
|
-
};
|
|
3240
|
-
}
|
|
3241
|
-
|
|
3242
|
-
/**
|
|
3243
|
-
* Generate human-readable summary for quick scan
|
|
3244
|
-
*/
|
|
3245
|
-
function generateQuickSummary(statementCount, topicCount, contradictions) {
|
|
3246
|
-
const unresolvedCount = contradictions.filter(c => !c.autoResolved).length;
|
|
3247
|
-
|
|
3248
|
-
let summary = `Quick scan complete: ${statementCount} statements, ${topicCount} topics detected.`;
|
|
3249
|
-
|
|
3250
|
-
if (contradictions.length > 0) {
|
|
3251
|
-
const autoResolved = contradictions.filter(c => c.autoResolved).length;
|
|
3252
|
-
summary += `\n${contradictions.length} potential contradictions found`;
|
|
3253
|
-
if (autoResolved > 0) {
|
|
3254
|
-
summary += ` (${autoResolved} auto-resolved as corrections)`;
|
|
3255
|
-
}
|
|
3256
|
-
if (unresolvedCount > 0) {
|
|
3257
|
-
summary += `.\n${unresolvedCount} need review.`;
|
|
3258
|
-
}
|
|
3259
|
-
} else {
|
|
3260
|
-
summary += '\nNo obvious contradictions detected.';
|
|
3261
|
-
}
|
|
3262
|
-
|
|
3263
|
-
return summary;
|
|
3264
|
-
}
|
|
3265
|
-
|
|
3266
|
-
// =============================================================================
|
|
3267
|
-
// UNIFIED PIPELINE (runs all passes in sequence)
|
|
3268
|
-
// =============================================================================
|
|
3269
|
-
|
|
3270
|
-
/**
|
|
3271
|
-
* Run the full 4-pass pipeline in one call.
|
|
3272
|
-
*
|
|
3273
|
-
* Chains: createSession → runPass2 → runPass3 → runPass4
|
|
3274
|
-
*
|
|
3275
|
-
* Pass 1 (topic extraction) is handled by the AI via the command spec —
|
|
3276
|
-
* the AI reads the confirmed statements and generates topics.json before
|
|
3277
|
-
* calling this function. This function handles Passes 2-4.
|
|
3278
|
-
*
|
|
3279
|
-
* @param {Object} options
|
|
3280
|
-
* @param {string} options.transcript - The full transcript text
|
|
3281
|
-
* @param {Object[]} options.topics - Topics array (from AI or extractTopics)
|
|
3282
|
-
* @param {string} options.contentType - Content type classification
|
|
3283
|
-
* @returns {Object} Consolidated pipeline result
|
|
3284
|
-
*/
|
|
3285
|
-
function runFullPipeline(options = {}) {
|
|
3286
|
-
const { transcript, topics, contentType } = options;
|
|
3287
|
-
|
|
3288
|
-
if (!transcript) throw new Error('transcript is required');
|
|
3289
|
-
if (!topics || !topics.length) throw new Error('topics array is required');
|
|
3290
|
-
|
|
3291
|
-
// Step 1: Create session with transcript
|
|
3292
|
-
const session = createSession(transcript, { contentType: contentType || 'transcript' });
|
|
3293
|
-
const digestId = session.id || session.session?.id;
|
|
3294
|
-
|
|
3295
|
-
// Step 2: Save the topics (normally done by AI in Pass 1)
|
|
3296
|
-
const topicsData = {
|
|
3297
|
-
topics: topics.map((t, i) => ({
|
|
3298
|
-
id: t.id || `topic-${String(i + 1).padStart(3, '0')}`,
|
|
3299
|
-
title: t.title,
|
|
3300
|
-
keywords: t.keywords || [],
|
|
3301
|
-
description: t.description || '',
|
|
3302
|
-
priority: t.priority || 'medium',
|
|
3303
|
-
statement_count: 0,
|
|
3304
|
-
status: 'active'
|
|
3305
|
-
})),
|
|
3306
|
-
metadata: {
|
|
3307
|
-
total_topics: topics.length,
|
|
3308
|
-
active_topics: topics.length,
|
|
3309
|
-
clarified_topics: 0,
|
|
3310
|
-
generated_topics: 0,
|
|
3311
|
-
detected_at: now(),
|
|
3312
|
-
last_updated: now(),
|
|
3313
|
-
transcript_word_count: countWords(transcript),
|
|
3314
|
-
detection_method: 'unified-pipeline'
|
|
3315
|
-
}
|
|
3316
|
-
};
|
|
3317
|
-
saveTopics(topicsData);
|
|
3318
|
-
updatePhase('topic_extraction', 'completed', { topics_found: topics.length });
|
|
3319
|
-
|
|
3320
|
-
// Step 3: Run Pass 2 — statement mapping + contradiction detection
|
|
3321
|
-
let pass2Result;
|
|
3322
|
-
try {
|
|
3323
|
-
pass2Result = runPass2();
|
|
3324
|
-
} catch (err) {
|
|
3325
|
-
return { error: `Pass 2 failed: ${err.message}`, phase: 'statement_mapping' };
|
|
3326
|
-
}
|
|
3327
|
-
|
|
3328
|
-
// Step 4: Run Pass 3 — orphan check and resolution
|
|
3329
|
-
let pass3Result;
|
|
3330
|
-
try {
|
|
3331
|
-
pass3Result = runPass3();
|
|
3332
|
-
} catch (err) {
|
|
3333
|
-
return { error: `Pass 3 failed: ${err.message}`, phase: 'orphan_check' };
|
|
3334
|
-
}
|
|
3335
|
-
|
|
3336
|
-
// Step 5: Run Pass 4 — contradiction resolution
|
|
3337
|
-
let pass4Result;
|
|
3338
|
-
try {
|
|
3339
|
-
pass4Result = runPass4();
|
|
3340
|
-
} catch (err) {
|
|
3341
|
-
return { error: `Pass 4 failed: ${err.message}`, phase: 'contradiction_resolution' };
|
|
3342
|
-
}
|
|
877
|
+
// Initialize story module with core functions
|
|
878
|
+
transcriptStories.init({
|
|
879
|
+
loadActiveDigest, saveActiveDigest, loadTopics, saveTopics,
|
|
880
|
+
loadStatementMap, loadClarifications,
|
|
881
|
+
isRequirement, isVagueStatement, analyzeComplexity,
|
|
882
|
+
REQUIREMENT_PATTERNS, VAGUE_PATTERNS, ENTITY_PATTERNS
|
|
883
|
+
});
|
|
3343
884
|
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
const pendingContradictions = (clarifications?.contradictions || [])
|
|
3347
|
-
.filter(c => c.status === 'pending');
|
|
3348
|
-
|
|
3349
|
-
const orphanQuestions = (pass3Result?.orphans || [])
|
|
3350
|
-
.filter(o => o.needs_clarification)
|
|
3351
|
-
.map(o => ({
|
|
3352
|
-
type: 'orphan',
|
|
3353
|
-
statement_id: o.id,
|
|
3354
|
-
question: o.clarification_question,
|
|
3355
|
-
text: o.text
|
|
3356
|
-
}));
|
|
3357
|
-
|
|
3358
|
-
const allClarificationQuestions = [
|
|
3359
|
-
...pendingContradictions.map(c => ({
|
|
3360
|
-
type: 'contradiction',
|
|
3361
|
-
id: c.id,
|
|
3362
|
-
question: c.question,
|
|
3363
|
-
options: c.options
|
|
3364
|
-
})),
|
|
3365
|
-
...orphanQuestions
|
|
3366
|
-
];
|
|
3367
|
-
|
|
3368
|
-
// Step 7: Load final state for summary
|
|
3369
|
-
const stmtMap = loadStatementMap();
|
|
3370
|
-
const finalTopics = loadTopics();
|
|
885
|
+
// Initialize chunking module with core functions
|
|
886
|
+
transcriptChunking.init({ loadActiveDigest, saveActiveDigest, countWords, now });
|
|
3371
887
|
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
digest_id: digestId,
|
|
3375
|
-
summary: {
|
|
3376
|
-
topics_count: finalTopics?.topics?.length || 0,
|
|
3377
|
-
statements_total: stmtMap?.metadata?.total_statements || 0,
|
|
3378
|
-
statements_meaningful: stmtMap?.metadata?.meaningful_statements || 0,
|
|
3379
|
-
statements_mapped: stmtMap?.metadata?.mapped_statements || 0,
|
|
3380
|
-
orphans_found: pass3Result?.orphans?.length || 0,
|
|
3381
|
-
orphans_resolved: pass3Result?.resolved?.length || 0,
|
|
3382
|
-
new_topics_created: pass3Result?.new_topics_created?.length || 0,
|
|
3383
|
-
contradictions_total: pass4Result?.stats?.total || 0,
|
|
3384
|
-
contradictions_auto_resolved: pass4Result?.stats?.auto_resolved || 0,
|
|
3385
|
-
contradictions_needs_clarification: pass4Result?.stats?.needs_clarification || 0,
|
|
3386
|
-
additive_not_contradiction: pass4Result?.stats?.additive_not_contradiction || 0,
|
|
3387
|
-
coverage_percentage: pass3Result?.coverage?.percentage || 0
|
|
3388
|
-
},
|
|
3389
|
-
clarification_questions: allClarificationQuestions,
|
|
3390
|
-
topics: finalTopics?.topics || [],
|
|
3391
|
-
pass2: pass2Result,
|
|
3392
|
-
pass3: pass3Result,
|
|
3393
|
-
pass4: pass4Result
|
|
3394
|
-
};
|
|
3395
|
-
}
|
|
888
|
+
// Initialize detection module with functions from main module
|
|
889
|
+
longInputDetection.init({ countWords, classifyContent });
|
|
3396
890
|
|
|
3397
891
|
// Export for use as module
|
|
3398
892
|
module.exports = {
|
|
3399
893
|
// Utilities
|
|
3400
894
|
now,
|
|
3401
895
|
// Core session management
|
|
3402
|
-
createSession,
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
isMeaningfulStatement,
|
|
3414
|
-
splitIntoStatements,
|
|
3415
|
-
associateStatements,
|
|
3416
|
-
detectContradictions,
|
|
3417
|
-
saveStatementMap,
|
|
3418
|
-
loadStatementMap,
|
|
3419
|
-
runPass2,
|
|
3420
|
-
// Pass 3: Orphan Check
|
|
3421
|
-
resolveOrphan,
|
|
3422
|
-
createTopicFromOrphans,
|
|
3423
|
-
ensureGeneralTopic,
|
|
3424
|
-
saveOrphans,
|
|
3425
|
-
loadOrphans,
|
|
3426
|
-
runPass3,
|
|
3427
|
-
// Pass 4: Contradiction Resolution
|
|
3428
|
-
detectCorrectionPhrase,
|
|
3429
|
-
isAdditive,
|
|
3430
|
-
calculateResolutionConfidence,
|
|
3431
|
-
generateContradictionQuestion,
|
|
3432
|
-
saveClarifications,
|
|
3433
|
-
loadClarifications,
|
|
3434
|
-
runPass4,
|
|
896
|
+
createSession, loadActiveDigest, saveActiveDigest, updatePhase,
|
|
897
|
+
saveTopics, loadTopics, getStatus, shouldTriggerDigestion, classifyContent, countWords,
|
|
898
|
+
// Pass 2: Statement Association (from flow-long-input-association.js)
|
|
899
|
+
isMeaningfulStatement, splitIntoStatements, associateStatements,
|
|
900
|
+
detectContradictions, saveStatementMap, loadStatementMap, runPass2,
|
|
901
|
+
// Pass 3: Orphan Check (from flow-long-input-passes.js)
|
|
902
|
+
resolveOrphan, createTopicFromOrphans, ensureGeneralTopic,
|
|
903
|
+
saveOrphans, loadOrphans, runPass3,
|
|
904
|
+
// Pass 4: Contradiction Resolution (from flow-long-input-contradictions.js)
|
|
905
|
+
detectCorrectionPhrase, isAdditive, calculateResolutionConfidence,
|
|
906
|
+
generateContradictionQuestion, saveClarifications, loadClarifications, runPass4,
|
|
3435
907
|
// Question Generation (E2-S1)
|
|
3436
|
-
analyzeCompleteness,
|
|
3437
|
-
detectVagueness,
|
|
3438
|
-
generateQuestionsForTopic,
|
|
3439
|
-
generateAllQuestions,
|
|
908
|
+
analyzeCompleteness, detectVagueness, generateQuestionsForTopic, generateAllQuestions,
|
|
3440
909
|
// Conversation Loop (E2-S2)
|
|
3441
|
-
parseAnswers,
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
checkFollowups,
|
|
3445
|
-
addFollowupQuestions,
|
|
3446
|
-
checkCompletion,
|
|
3447
|
-
getQuestionsForPresentation,
|
|
3448
|
-
formatQuestionsForUser,
|
|
3449
|
-
processConversationResponse,
|
|
3450
|
-
resolveContradictionWithChoice,
|
|
910
|
+
parseAnswers, captureAnswer, createDerivedStatement, checkFollowups,
|
|
911
|
+
addFollowupQuestions, checkCompletion, getQuestionsForPresentation,
|
|
912
|
+
formatQuestionsForUser, processConversationResponse, resolveContradictionWithChoice,
|
|
3451
913
|
// Voice Answer Integration (E2-S3)
|
|
3452
|
-
isVoiceInput,
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
normalizeNumbers,
|
|
3456
|
-
detectUncertainty,
|
|
3457
|
-
detectYesNo,
|
|
3458
|
-
addPunctuation,
|
|
3459
|
-
normalizeVoiceInput,
|
|
3460
|
-
calculateVoiceConfidence,
|
|
3461
|
-
processVoiceAnswer,
|
|
914
|
+
isVoiceInput, removeFillers, applySelfCorrections, normalizeNumbers,
|
|
915
|
+
detectUncertainty, detectYesNo, addPunctuation, normalizeVoiceInput,
|
|
916
|
+
calculateVoiceConfidence, processVoiceAnswer,
|
|
3462
917
|
// State Persistence (E2-S4)
|
|
3463
|
-
loadConversation,
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
recordInteraction,
|
|
3467
|
-
createCheckpoint,
|
|
3468
|
-
detectInterruptedSession,
|
|
3469
|
-
generateRecoverySummary,
|
|
3470
|
-
resumeSession,
|
|
3471
|
-
markQuestionsPresented,
|
|
3472
|
-
getSessionHistory,
|
|
3473
|
-
exportSession,
|
|
3474
|
-
reviewAnswers,
|
|
918
|
+
loadConversation, saveConversation, initializeConversation, recordInteraction,
|
|
919
|
+
createCheckpoint, detectInterruptedSession, generateRecoverySummary, resumeSession,
|
|
920
|
+
markQuestionsPresented, getSessionHistory, exportSession, reviewAnswers,
|
|
3475
921
|
// Complexity Detection (E3-S1)
|
|
3476
|
-
countEntityTypes,
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
calculateComplexityScore,
|
|
3480
|
-
isRequirement,
|
|
3481
|
-
isVagueStatement,
|
|
3482
|
-
hasUIComponent,
|
|
3483
|
-
hasDataModel,
|
|
3484
|
-
hasUserInteraction,
|
|
3485
|
-
analyzeTopicComplexity,
|
|
3486
|
-
groupRelatedTopics,
|
|
3487
|
-
generateEpicStructure,
|
|
3488
|
-
recommendOutputStructure,
|
|
922
|
+
countEntityTypes, extractEntities, getComplexityLevel, calculateComplexityScore,
|
|
923
|
+
isRequirement, isVagueStatement, hasUIComponent, hasDataModel, hasUserInteraction,
|
|
924
|
+
analyzeTopicComplexity, groupRelatedTopics, generateEpicStructure, recommendOutputStructure,
|
|
3489
925
|
analyzeComplexity,
|
|
3490
|
-
// Story Generation (E3-S2) - re-exported from flow-
|
|
926
|
+
// Story Generation (E3-S2) - re-exported from flow-long-input-stories.js
|
|
3491
927
|
USER_TYPE_PATTERNS: transcriptStories.USER_TYPE_PATTERNS,
|
|
3492
928
|
SCENARIO_PATTERNS: transcriptStories.SCENARIO_PATTERNS,
|
|
3493
929
|
generateStoryId: transcriptStories.generateStoryId,
|
|
@@ -3509,7 +945,7 @@ module.exports = {
|
|
|
3509
945
|
loadStory: transcriptStories.loadStory,
|
|
3510
946
|
loadAllStories: transcriptStories.loadAllStories,
|
|
3511
947
|
formatStoryAsMarkdown: transcriptStories.formatStoryAsMarkdown,
|
|
3512
|
-
// Presentation Flow (E3-S3)
|
|
948
|
+
// Presentation Flow (E3-S3)
|
|
3513
949
|
loadQueue: transcriptStories.loadQueue,
|
|
3514
950
|
saveQueue: transcriptStories.saveQueue,
|
|
3515
951
|
initializePresentation: transcriptStories.initializePresentation,
|
|
@@ -3523,7 +959,7 @@ module.exports = {
|
|
|
3523
959
|
formatActionsPrompt: transcriptStories.formatActionsPrompt,
|
|
3524
960
|
getCompletionSummary: transcriptStories.getCompletionSummary,
|
|
3525
961
|
resetPresentation: transcriptStories.resetPresentation,
|
|
3526
|
-
// Edit and Change Handling (E3-S4)
|
|
962
|
+
// Edit and Change Handling (E3-S4)
|
|
3527
963
|
generateEditSessionId: transcriptStories.generateEditSessionId,
|
|
3528
964
|
generateChangeId: transcriptStories.generateChangeId,
|
|
3529
965
|
loadEditSessions: transcriptStories.loadEditSessions,
|
|
@@ -3543,7 +979,7 @@ module.exports = {
|
|
|
3543
979
|
getEditChanges: transcriptStories.getEditChanges,
|
|
3544
980
|
getEditHistory: transcriptStories.getEditHistory,
|
|
3545
981
|
listEditableStories: transcriptStories.listEditableStories,
|
|
3546
|
-
// ready.json Integration (E3-S5)
|
|
982
|
+
// ready.json Integration (E3-S5)
|
|
3547
983
|
generateWorkflowId: transcriptStories.generateWorkflowId,
|
|
3548
984
|
generateSubTaskId: transcriptStories.generateSubTaskId,
|
|
3549
985
|
mapPriority: transcriptStories.mapPriority,
|
|
@@ -3559,23 +995,13 @@ module.exports = {
|
|
|
3559
995
|
finalizeDigestion: transcriptStories.finalizeDigestion,
|
|
3560
996
|
generateAndExportStories: transcriptStories.generateAndExportStories,
|
|
3561
997
|
// Large Input Detection (E4-S1)
|
|
3562
|
-
measureInputMetrics,
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
isSRTFormat,
|
|
3566
|
-
detectMeetingFormat,
|
|
3567
|
-
detectInputFormat,
|
|
3568
|
-
analyzeInput,
|
|
3569
|
-
evaluateTrigger,
|
|
3570
|
-
generateRecommendationMessage,
|
|
3571
|
-
detectLargeInput,
|
|
998
|
+
measureInputMetrics, estimateTokens, isVTTFormat, isSRTFormat,
|
|
999
|
+
detectMeetingFormat, detectInputFormat, analyzeInput, evaluateTrigger,
|
|
1000
|
+
generateRecommendationMessage, detectLargeInput,
|
|
3572
1001
|
// Content Type Classification (E4-S2)
|
|
3573
|
-
scoreContentType,
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
getDetailedClassification,
|
|
3577
|
-
shouldExcludeContent,
|
|
3578
|
-
// VTT/SRT Format Parsing (E4-S3) - re-exported from flow-transcript-parsing.js
|
|
1002
|
+
scoreContentType, normalizeScore, classifyContentTypes,
|
|
1003
|
+
getDetailedClassification, shouldExcludeContent,
|
|
1004
|
+
// VTT/SRT Format Parsing (E4-S3)
|
|
3579
1005
|
timestampToMs: transcriptParsing.timestampToMs,
|
|
3580
1006
|
msToTimestamp: transcriptParsing.msToTimestamp,
|
|
3581
1007
|
cleanSubtitleText: transcriptParsing.cleanSubtitleText,
|
|
@@ -3587,7 +1013,7 @@ module.exports = {
|
|
|
3587
1013
|
parseSubtitle: transcriptParsing.parseSubtitle,
|
|
3588
1014
|
formatCuesAsText: transcriptParsing.formatCuesAsText,
|
|
3589
1015
|
getSubtitleStats: transcriptParsing.getSubtitleStats,
|
|
3590
|
-
// Zoom/Teams Parsing (E4-S4)
|
|
1016
|
+
// Zoom/Teams Parsing (E4-S4)
|
|
3591
1017
|
ZOOM_PATTERNS: transcriptParsing.ZOOM_PATTERNS,
|
|
3592
1018
|
TEAMS_PATTERNS: transcriptParsing.TEAMS_PATTERNS,
|
|
3593
1019
|
isSystemMessage: transcriptParsing.isSystemMessage,
|
|
@@ -3604,7 +1030,7 @@ module.exports = {
|
|
|
3604
1030
|
mergeMeetingEntries: transcriptParsing.mergeMeetingEntries,
|
|
3605
1031
|
formatMeetingAsText: transcriptParsing.formatMeetingAsText,
|
|
3606
1032
|
getMeetingStats: transcriptParsing.getMeetingStats,
|
|
3607
|
-
// Language Detection (E5-S1)
|
|
1033
|
+
// Language Detection (E5-S1)
|
|
3608
1034
|
SCRIPT_PATTERNS: transcriptLanguage.SCRIPT_PATTERNS,
|
|
3609
1035
|
LANGUAGE_INFO: transcriptLanguage.LANGUAGE_INFO,
|
|
3610
1036
|
COMMON_WORDS: transcriptLanguage.COMMON_WORDS,
|
|
@@ -3622,14 +1048,9 @@ module.exports = {
|
|
|
3622
1048
|
listSupportedLanguages: transcriptLanguage.listSupportedLanguages,
|
|
3623
1049
|
// Multi-language Clarification (E5-S2)
|
|
3624
1050
|
QUESTION_TEMPLATES_BY_LANGUAGE,
|
|
3625
|
-
getQuestionTemplates,
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
getTopicLanguage,
|
|
3629
|
-
setLanguagePreference,
|
|
3630
|
-
getEffectiveLanguage,
|
|
3631
|
-
getSessionLanguageInfo,
|
|
3632
|
-
// Durable Session Persistence (E5-S3) - re-exported from flow-transcript-chunking.js
|
|
1051
|
+
getQuestionTemplates, generateLocalizedQuestion, detectSessionLanguage,
|
|
1052
|
+
getTopicLanguage, setLanguagePreference, getEffectiveLanguage, getSessionLanguageInfo,
|
|
1053
|
+
// Durable Session Persistence (E5-S3)
|
|
3633
1054
|
DURABLE_DIGEST_PATH: transcriptChunking.DURABLE_DIGEST_PATH,
|
|
3634
1055
|
DURABLE_DIGEST_VERSION: transcriptChunking.DURABLE_DIGEST_VERSION,
|
|
3635
1056
|
loadDurableSessions: transcriptChunking.loadDurableSessions,
|
|
@@ -3649,7 +1070,7 @@ module.exports = {
|
|
|
3649
1070
|
archiveDurableSession: transcriptChunking.archiveDurableSession,
|
|
3650
1071
|
deleteDurableSession: transcriptChunking.deleteDurableSession,
|
|
3651
1072
|
completeDurableSession: transcriptChunking.completeDurableSession,
|
|
3652
|
-
// Large Transcript Chunking (E5-S4)
|
|
1073
|
+
// Large Transcript Chunking (E5-S4)
|
|
3653
1074
|
CHUNKING_DEFAULTS: transcriptChunking.CHUNKING_DEFAULTS,
|
|
3654
1075
|
SPEAKER_BOUNDARY_PATTERNS: transcriptChunking.SPEAKER_BOUNDARY_PATTERNS,
|
|
3655
1076
|
needsChunking: transcriptChunking.needsChunking,
|
|
@@ -3668,8 +1089,7 @@ module.exports = {
|
|
|
3668
1089
|
getChunkContent: transcriptChunking.getChunkContent,
|
|
3669
1090
|
getChunkingStatus: transcriptChunking.getChunkingStatus,
|
|
3670
1091
|
// Quick Processing Mode (for gate integration)
|
|
3671
|
-
quickProcess,
|
|
3672
|
-
generateQuickSummary,
|
|
1092
|
+
quickProcess, generateQuickSummary,
|
|
3673
1093
|
// Unified Pipeline (runs all passes in sequence)
|
|
3674
1094
|
runFullPipeline
|
|
3675
1095
|
};
|