@aaronsb/kg-cli 0.6.1
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/README.md +112 -0
- package/dist/api/client.d.ts +867 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +1362 -0
- package/dist/api/client.js.map +1 -0
- package/dist/cli/admin/backup.d.ts +9 -0
- package/dist/cli/admin/backup.d.ts.map +1 -0
- package/dist/cli/admin/backup.js +363 -0
- package/dist/cli/admin/backup.js.map +1 -0
- package/dist/cli/admin/index.d.ts +7 -0
- package/dist/cli/admin/index.d.ts.map +1 -0
- package/dist/cli/admin/index.js +52 -0
- package/dist/cli/admin/index.js.map +1 -0
- package/dist/cli/admin/scheduler.d.ts +7 -0
- package/dist/cli/admin/scheduler.d.ts.map +1 -0
- package/dist/cli/admin/scheduler.js +125 -0
- package/dist/cli/admin/scheduler.js.map +1 -0
- package/dist/cli/admin/status.d.ts +7 -0
- package/dist/cli/admin/status.d.ts.map +1 -0
- package/dist/cli/admin/status.js +134 -0
- package/dist/cli/admin/status.js.map +1 -0
- package/dist/cli/admin/utils.d.ts +34 -0
- package/dist/cli/admin/utils.d.ts.map +1 -0
- package/dist/cli/admin/utils.js +441 -0
- package/dist/cli/admin/utils.js.map +1 -0
- package/dist/cli/ai-config/embedding.d.ts +11 -0
- package/dist/cli/ai-config/embedding.d.ts.map +1 -0
- package/dist/cli/ai-config/embedding.js +598 -0
- package/dist/cli/ai-config/embedding.js.map +1 -0
- package/dist/cli/ai-config/extraction.d.ts +11 -0
- package/dist/cli/ai-config/extraction.d.ts.map +1 -0
- package/dist/cli/ai-config/extraction.js +206 -0
- package/dist/cli/ai-config/extraction.js.map +1 -0
- package/dist/cli/ai-config/index.d.ts +21 -0
- package/dist/cli/ai-config/index.d.ts.map +1 -0
- package/dist/cli/ai-config/index.js +27 -0
- package/dist/cli/ai-config/index.js.map +1 -0
- package/dist/cli/ai-config/keys.d.ts +11 -0
- package/dist/cli/ai-config/keys.d.ts.map +1 -0
- package/dist/cli/ai-config/keys.js +182 -0
- package/dist/cli/ai-config/keys.js.map +1 -0
- package/dist/cli/ai-config/utils.d.ts +13 -0
- package/dist/cli/ai-config/utils.d.ts.map +1 -0
- package/dist/cli/ai-config/utils.js +84 -0
- package/dist/cli/ai-config/utils.js.map +1 -0
- package/dist/cli/artifact.d.ts +8 -0
- package/dist/cli/artifact.d.ts.map +1 -0
- package/dist/cli/artifact.js +296 -0
- package/dist/cli/artifact.js.map +1 -0
- package/dist/cli/auth-admin.d.ts +11 -0
- package/dist/cli/auth-admin.d.ts.map +1 -0
- package/dist/cli/auth-admin.js +415 -0
- package/dist/cli/auth-admin.js.map +1 -0
- package/dist/cli/colors.d.ts +105 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +164 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/commands.d.ts +6 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +164 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +6 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +694 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/curve-viz.d.ts +89 -0
- package/dist/cli/curve-viz.d.ts.map +1 -0
- package/dist/cli/curve-viz.js +228 -0
- package/dist/cli/curve-viz.js.map +1 -0
- package/dist/cli/database.d.ts +6 -0
- package/dist/cli/database.d.ts.map +1 -0
- package/dist/cli/database.js +324 -0
- package/dist/cli/database.js.map +1 -0
- package/dist/cli/document.d.ts +6 -0
- package/dist/cli/document.d.ts.map +1 -0
- package/dist/cli/document.js +458 -0
- package/dist/cli/document.js.map +1 -0
- package/dist/cli/group.d.ts +8 -0
- package/dist/cli/group.d.ts.map +1 -0
- package/dist/cli/group.js +174 -0
- package/dist/cli/group.js.map +1 -0
- package/dist/cli/health.d.ts +6 -0
- package/dist/cli/health.d.ts.map +1 -0
- package/dist/cli/health.js +34 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/help-formatter.d.ts +16 -0
- package/dist/cli/help-formatter.d.ts.map +1 -0
- package/dist/cli/help-formatter.js +248 -0
- package/dist/cli/help-formatter.js.map +1 -0
- package/dist/cli/help.d.ts +9 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +227 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/ingest.d.ts +6 -0
- package/dist/cli/ingest.d.ts.map +1 -0
- package/dist/cli/ingest.js +722 -0
- package/dist/cli/ingest.js.map +1 -0
- package/dist/cli/jobs.d.ts +6 -0
- package/dist/cli/jobs.d.ts.map +1 -0
- package/dist/cli/jobs.js +663 -0
- package/dist/cli/jobs.js.map +1 -0
- package/dist/cli/login.d.ts +21 -0
- package/dist/cli/login.d.ts.map +1 -0
- package/dist/cli/login.js +221 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/logout.d.ts +16 -0
- package/dist/cli/logout.d.ts.map +1 -0
- package/dist/cli/logout.js +141 -0
- package/dist/cli/logout.js.map +1 -0
- package/dist/cli/mcp-config.d.ts +10 -0
- package/dist/cli/mcp-config.d.ts.map +1 -0
- package/dist/cli/mcp-config.js +358 -0
- package/dist/cli/mcp-config.js.map +1 -0
- package/dist/cli/oauth.d.ts +15 -0
- package/dist/cli/oauth.d.ts.map +1 -0
- package/dist/cli/oauth.js +296 -0
- package/dist/cli/oauth.js.map +1 -0
- package/dist/cli/ontology.d.ts +6 -0
- package/dist/cli/ontology.d.ts.map +1 -0
- package/dist/cli/ontology.js +231 -0
- package/dist/cli/ontology.js.map +1 -0
- package/dist/cli/polarity.d.ts +6 -0
- package/dist/cli/polarity.d.ts.map +1 -0
- package/dist/cli/polarity.js +295 -0
- package/dist/cli/polarity.js.map +1 -0
- package/dist/cli/projection.d.ts +8 -0
- package/dist/cli/projection.d.ts.map +1 -0
- package/dist/cli/projection.js +297 -0
- package/dist/cli/projection.js.map +1 -0
- package/dist/cli/query-def.d.ts +8 -0
- package/dist/cli/query-def.d.ts.map +1 -0
- package/dist/cli/query-def.js +163 -0
- package/dist/cli/query-def.js.map +1 -0
- package/dist/cli/rbac.d.ts +12 -0
- package/dist/cli/rbac.d.ts.map +1 -0
- package/dist/cli/rbac.js +615 -0
- package/dist/cli/rbac.js.map +1 -0
- package/dist/cli/search.d.ts +6 -0
- package/dist/cli/search.d.ts.map +1 -0
- package/dist/cli/search.js +829 -0
- package/dist/cli/search.js.map +1 -0
- package/dist/cli/source.d.ts +6 -0
- package/dist/cli/source.d.ts.map +1 -0
- package/dist/cli/source.js +202 -0
- package/dist/cli/source.js.map +1 -0
- package/dist/cli/verb-router.d.ts +25 -0
- package/dist/cli/verb-router.d.ts.map +1 -0
- package/dist/cli/verb-router.js +415 -0
- package/dist/cli/verb-router.js.map +1 -0
- package/dist/cli/vocabulary/config.d.ts +7 -0
- package/dist/cli/vocabulary/config.d.ts.map +1 -0
- package/dist/cli/vocabulary/config.js +201 -0
- package/dist/cli/vocabulary/config.js.map +1 -0
- package/dist/cli/vocabulary/consolidate.d.ts +8 -0
- package/dist/cli/vocabulary/consolidate.d.ts.map +1 -0
- package/dist/cli/vocabulary/consolidate.js +192 -0
- package/dist/cli/vocabulary/consolidate.js.map +1 -0
- package/dist/cli/vocabulary/embeddings.d.ts +9 -0
- package/dist/cli/vocabulary/embeddings.d.ts.map +1 -0
- package/dist/cli/vocabulary/embeddings.js +205 -0
- package/dist/cli/vocabulary/embeddings.js.map +1 -0
- package/dist/cli/vocabulary/epistemic.d.ts +7 -0
- package/dist/cli/vocabulary/epistemic.d.ts.map +1 -0
- package/dist/cli/vocabulary/epistemic.js +315 -0
- package/dist/cli/vocabulary/epistemic.js.map +1 -0
- package/dist/cli/vocabulary/index.d.ts +7 -0
- package/dist/cli/vocabulary/index.d.ts.map +1 -0
- package/dist/cli/vocabulary/index.js +45 -0
- package/dist/cli/vocabulary/index.js.map +1 -0
- package/dist/cli/vocabulary/profiles.d.ts +7 -0
- package/dist/cli/vocabulary/profiles.d.ts.map +1 -0
- package/dist/cli/vocabulary/profiles.js +171 -0
- package/dist/cli/vocabulary/profiles.js.map +1 -0
- package/dist/cli/vocabulary/similarity.d.ts +9 -0
- package/dist/cli/vocabulary/similarity.d.ts.map +1 -0
- package/dist/cli/vocabulary/similarity.js +199 -0
- package/dist/cli/vocabulary/similarity.js.map +1 -0
- package/dist/cli/vocabulary/status.d.ts +8 -0
- package/dist/cli/vocabulary/status.d.ts.map +1 -0
- package/dist/cli/vocabulary/status.js +280 -0
- package/dist/cli/vocabulary/status.js.map +1 -0
- package/dist/cli/vocabulary/sync.d.ts +7 -0
- package/dist/cli/vocabulary/sync.d.ts.map +1 -0
- package/dist/cli/vocabulary/sync.js +111 -0
- package/dist/cli/vocabulary/sync.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth/auth-client.d.ts +247 -0
- package/dist/lib/auth/auth-client.d.ts.map +1 -0
- package/dist/lib/auth/auth-client.js +305 -0
- package/dist/lib/auth/auth-client.js.map +1 -0
- package/dist/lib/auth/challenge.d.ts +39 -0
- package/dist/lib/auth/challenge.d.ts.map +1 -0
- package/dist/lib/auth/challenge.js +125 -0
- package/dist/lib/auth/challenge.js.map +1 -0
- package/dist/lib/auth/client-credentials-flow.d.ts +58 -0
- package/dist/lib/auth/client-credentials-flow.d.ts.map +1 -0
- package/dist/lib/auth/client-credentials-flow.js +118 -0
- package/dist/lib/auth/client-credentials-flow.js.map +1 -0
- package/dist/lib/auth/device-flow.d.ts +75 -0
- package/dist/lib/auth/device-flow.d.ts.map +1 -0
- package/dist/lib/auth/device-flow.js +177 -0
- package/dist/lib/auth/device-flow.js.map +1 -0
- package/dist/lib/auth/index.d.ts +14 -0
- package/dist/lib/auth/index.d.ts.map +1 -0
- package/dist/lib/auth/index.js +34 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/oauth-types.d.ts +69 -0
- package/dist/lib/auth/oauth-types.d.ts.map +1 -0
- package/dist/lib/auth/oauth-types.js +10 -0
- package/dist/lib/auth/oauth-types.js.map +1 -0
- package/dist/lib/auth/oauth-utils.d.ts +51 -0
- package/dist/lib/auth/oauth-utils.d.ts.map +1 -0
- package/dist/lib/auth/oauth-utils.js +110 -0
- package/dist/lib/auth/oauth-utils.js.map +1 -0
- package/dist/lib/auth/token-manager.d.ts +87 -0
- package/dist/lib/auth/token-manager.d.ts.map +1 -0
- package/dist/lib/auth/token-manager.js +139 -0
- package/dist/lib/auth/token-manager.js.map +1 -0
- package/dist/lib/auth/token-refresh.d.ts +63 -0
- package/dist/lib/auth/token-refresh.d.ts.map +1 -0
- package/dist/lib/auth/token-refresh.js +141 -0
- package/dist/lib/auth/token-refresh.js.map +1 -0
- package/dist/lib/config.d.ts +286 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +537 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/job-stream.d.ts +53 -0
- package/dist/lib/job-stream.d.ts.map +1 -0
- package/dist/lib/job-stream.js +153 -0
- package/dist/lib/job-stream.js.map +1 -0
- package/dist/lib/mcp-allowlist.d.ts +101 -0
- package/dist/lib/mcp-allowlist.d.ts.map +1 -0
- package/dist/lib/mcp-allowlist.js +340 -0
- package/dist/lib/mcp-allowlist.js.map +1 -0
- package/dist/lib/table-example.d.ts +7 -0
- package/dist/lib/table-example.d.ts.map +1 -0
- package/dist/lib/table-example.js +105 -0
- package/dist/lib/table-example.js.map +1 -0
- package/dist/lib/table.d.ts +95 -0
- package/dist/lib/table.d.ts.map +1 -0
- package/dist/lib/table.js +263 -0
- package/dist/lib/table.js.map +1 -0
- package/dist/lib/terminal-images.d.ts +66 -0
- package/dist/lib/terminal-images.d.ts.map +1 -0
- package/dist/lib/terminal-images.js +268 -0
- package/dist/lib/terminal-images.js.map +1 -0
- package/dist/mcp/formatters.d.ts +100 -0
- package/dist/mcp/formatters.d.ts.map +1 -0
- package/dist/mcp/formatters.js +1411 -0
- package/dist/mcp/formatters.js.map +1 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +1810 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/types/index.d.ts +742 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,1411 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server Markdown Formatters
|
|
4
|
+
*
|
|
5
|
+
* Formats API responses as markdown-style text for AI agents.
|
|
6
|
+
* Optimized for token efficiency - minimal Unicode, plain structure.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.formatSearchResults = formatSearchResults;
|
|
10
|
+
exports.formatConceptDetails = formatConceptDetails;
|
|
11
|
+
exports.formatConnectionPaths = formatConnectionPaths;
|
|
12
|
+
exports.formatRelatedConcepts = formatRelatedConcepts;
|
|
13
|
+
exports.formatJobList = formatJobList;
|
|
14
|
+
exports.formatJobStatus = formatJobStatus;
|
|
15
|
+
exports.formatInspectFileResult = formatInspectFileResult;
|
|
16
|
+
exports.formatIngestFileResult = formatIngestFileResult;
|
|
17
|
+
exports.formatIngestDirectoryResult = formatIngestDirectoryResult;
|
|
18
|
+
exports.formatDatabaseStats = formatDatabaseStats;
|
|
19
|
+
exports.formatDatabaseInfo = formatDatabaseInfo;
|
|
20
|
+
exports.formatDatabaseHealth = formatDatabaseHealth;
|
|
21
|
+
exports.formatSystemStatus = formatSystemStatus;
|
|
22
|
+
exports.formatApiHealth = formatApiHealth;
|
|
23
|
+
exports.formatMcpAllowedPaths = formatMcpAllowedPaths;
|
|
24
|
+
exports.formatEpistemicStatusList = formatEpistemicStatusList;
|
|
25
|
+
exports.formatEpistemicStatusDetails = formatEpistemicStatusDetails;
|
|
26
|
+
exports.formatEpistemicStatusMeasurement = formatEpistemicStatusMeasurement;
|
|
27
|
+
exports.formatSourceSearchResults = formatSourceSearchResults;
|
|
28
|
+
exports.formatPolarityAxisResults = formatPolarityAxisResults;
|
|
29
|
+
exports.formatDocumentSearchResults = formatDocumentSearchResults;
|
|
30
|
+
exports.formatDocumentList = formatDocumentList;
|
|
31
|
+
exports.formatDocumentContent = formatDocumentContent;
|
|
32
|
+
exports.formatDocumentConcepts = formatDocumentConcepts;
|
|
33
|
+
exports.formatDocumentConceptsDetailed = formatDocumentConceptsDetailed;
|
|
34
|
+
/**
|
|
35
|
+
* Format grounding strength as text (token-efficient)
|
|
36
|
+
* This is the fallback when grounding_display is not available.
|
|
37
|
+
*/
|
|
38
|
+
function formatGroundingStrength(grounding) {
|
|
39
|
+
const groundingValue = grounding.toFixed(3);
|
|
40
|
+
const percentValue = grounding * 100;
|
|
41
|
+
// Use ≈ symbol when value is very close to zero but not exactly zero
|
|
42
|
+
const groundingPercent = (Math.abs(percentValue) < 0.1 && percentValue !== 0)
|
|
43
|
+
? `≈${percentValue >= 0 ? '0' : '-0'}`
|
|
44
|
+
: percentValue.toFixed(0);
|
|
45
|
+
let level;
|
|
46
|
+
if (grounding >= 0.7)
|
|
47
|
+
level = 'Strong';
|
|
48
|
+
else if (grounding >= 0.3)
|
|
49
|
+
level = 'Moderate';
|
|
50
|
+
else if (grounding >= 0)
|
|
51
|
+
level = 'Weak';
|
|
52
|
+
else if (grounding >= -0.3)
|
|
53
|
+
level = 'Negative';
|
|
54
|
+
else
|
|
55
|
+
level = 'Contradicted';
|
|
56
|
+
return `${level} (${groundingValue}, ${groundingPercent}%)`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Format grounding with confidence-awareness (grounding × confidence two-dimensional model)
|
|
60
|
+
*
|
|
61
|
+
* Uses grounding_display when available (categorical label from API).
|
|
62
|
+
* Includes numeric confidence_score alongside the label for quantitative insight.
|
|
63
|
+
* Falls back to raw grounding score display for backwards compatibility.
|
|
64
|
+
*/
|
|
65
|
+
function formatGroundingWithConfidence(grounding, groundingDisplay, confidenceScore = null) {
|
|
66
|
+
// Format confidence score as percentage if available
|
|
67
|
+
const confScoreStr = confidenceScore !== undefined && confidenceScore !== null
|
|
68
|
+
? ` [${(confidenceScore * 100).toFixed(0)}% conf]`
|
|
69
|
+
: '';
|
|
70
|
+
// If we have a grounding_display label from the API, use it directly
|
|
71
|
+
if (groundingDisplay) {
|
|
72
|
+
return `${groundingDisplay}${confScoreStr}`;
|
|
73
|
+
}
|
|
74
|
+
// Fall back to raw grounding score display if available
|
|
75
|
+
if (grounding !== undefined && grounding !== null) {
|
|
76
|
+
return formatGroundingStrength(grounding);
|
|
77
|
+
}
|
|
78
|
+
// No grounding information available
|
|
79
|
+
return 'Unexplored';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Format search results as markdown
|
|
83
|
+
*/
|
|
84
|
+
function formatSearchResults(result) {
|
|
85
|
+
let output = `# Search: "${result.query}"\n\n`;
|
|
86
|
+
output += `Found ${result.count} concepts (threshold: ${(result.threshold_used || 0.7) * 100}%)\n\n`;
|
|
87
|
+
if (result.count === 0) {
|
|
88
|
+
output += 'No concepts found matching this query.\n';
|
|
89
|
+
return output;
|
|
90
|
+
}
|
|
91
|
+
result.results.forEach((concept, i) => {
|
|
92
|
+
output += `## ${i + 1}. ${concept.label}\n`;
|
|
93
|
+
if (concept.description) {
|
|
94
|
+
output += `${concept.description}\n\n`;
|
|
95
|
+
}
|
|
96
|
+
output += `ID: ${concept.concept_id}\n`;
|
|
97
|
+
output += `Similarity: ${(concept.score * 100).toFixed(1)}%\n`;
|
|
98
|
+
output += `Documents: ${concept.documents.join(', ')}\n`;
|
|
99
|
+
output += `Evidence: ${concept.evidence_count} instances\n`;
|
|
100
|
+
if (concept.grounding_strength !== undefined || concept.grounding_display) {
|
|
101
|
+
output += `Grounding: ${formatGroundingWithConfidence(concept.grounding_strength, concept.grounding_display, concept.confidence_score)}\n`;
|
|
102
|
+
}
|
|
103
|
+
if (concept.diversity_score !== undefined && concept.diversity_score !== null && concept.diversity_related_count !== undefined) {
|
|
104
|
+
output += `Diversity: ${(concept.diversity_score * 100).toFixed(1)}% (${concept.diversity_related_count} related concepts)\n`;
|
|
105
|
+
}
|
|
106
|
+
if (concept.authenticated_diversity !== undefined && concept.authenticated_diversity !== null) {
|
|
107
|
+
const authDiv = concept.authenticated_diversity;
|
|
108
|
+
const sign = authDiv >= 0 ? '+' : '';
|
|
109
|
+
// Near-zero values (|authDiv| < 0.05) are "unclear" - grounding too weak to authenticate
|
|
110
|
+
const status = Math.abs(authDiv) < 0.05 ? 'unclear ◯' :
|
|
111
|
+
authDiv > 0.3 ? 'diverse support ✅' :
|
|
112
|
+
authDiv > 0 ? 'some support ✓' :
|
|
113
|
+
authDiv > -0.3 ? 'weak contradiction ⚠' :
|
|
114
|
+
'diverse contradiction ❌';
|
|
115
|
+
output += `Authenticated: ${sign}${(Math.abs(authDiv) * 100).toFixed(1)}% (${status})\n`;
|
|
116
|
+
}
|
|
117
|
+
if (concept.sample_evidence && concept.sample_evidence.length > 0) {
|
|
118
|
+
output += `\nSample Evidence (${concept.sample_evidence.length} of ${concept.evidence_count}):\n`;
|
|
119
|
+
concept.sample_evidence.forEach((inst, idx) => {
|
|
120
|
+
const truncated = inst.quote.length > 120 ? inst.quote.substring(0, 120) + '...' : inst.quote;
|
|
121
|
+
output += `${idx + 1}. ${inst.document} (para ${inst.paragraph}): "${truncated}"\n`;
|
|
122
|
+
// ADR-057: Indicate if this evidence has an image
|
|
123
|
+
if (inst.has_image && inst.source_id) {
|
|
124
|
+
output += ` [IMAGE] Use get_source_image("${inst.source_id}") to view original image\n`;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
output += `Tip: Use get_concept_details("${concept.concept_id}") for all evidence\n`;
|
|
128
|
+
}
|
|
129
|
+
output += '\n';
|
|
130
|
+
});
|
|
131
|
+
if (result.below_threshold_count && result.below_threshold_count > 0 && result.suggested_threshold) {
|
|
132
|
+
const thresholdPercent = (result.suggested_threshold * 100).toFixed(0);
|
|
133
|
+
output += `Note: ${result.below_threshold_count} additional concepts available at ${thresholdPercent}% threshold\n`;
|
|
134
|
+
}
|
|
135
|
+
return output;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Format concept details as markdown
|
|
139
|
+
* @param concept - The concept details to format
|
|
140
|
+
* @param truncateEvidence - Whether to truncate full_text context to 200 chars (default: true)
|
|
141
|
+
*/
|
|
142
|
+
function formatConceptDetails(concept, truncateEvidence = true) {
|
|
143
|
+
let output = `# Concept: ${concept.label}\n\n`;
|
|
144
|
+
if (concept.description) {
|
|
145
|
+
output += `${concept.description}\n\n`;
|
|
146
|
+
}
|
|
147
|
+
output += `ID: ${concept.concept_id}\n`;
|
|
148
|
+
output += `Search Terms: ${concept.search_terms.join(', ')}\n`;
|
|
149
|
+
output += `Documents: ${concept.documents.join(', ')}\n`;
|
|
150
|
+
if (concept.grounding_strength !== undefined || concept.grounding_display) {
|
|
151
|
+
output += `Grounding: ${formatGroundingWithConfidence(concept.grounding_strength, concept.grounding_display, concept.confidence_score)}\n`;
|
|
152
|
+
}
|
|
153
|
+
if (concept.diversity_score !== undefined && concept.diversity_score !== null && concept.diversity_related_count !== undefined) {
|
|
154
|
+
output += `Diversity: ${(concept.diversity_score * 100).toFixed(1)}% (${concept.diversity_related_count} related concepts)\n`;
|
|
155
|
+
}
|
|
156
|
+
if (concept.authenticated_diversity !== undefined && concept.authenticated_diversity !== null) {
|
|
157
|
+
const authDiv = concept.authenticated_diversity;
|
|
158
|
+
const sign = authDiv >= 0 ? '+' : '';
|
|
159
|
+
// Near-zero values (|authDiv| < 0.05) are "unclear" - grounding too weak to authenticate
|
|
160
|
+
const status = Math.abs(authDiv) < 0.05 ? 'unclear ◯' :
|
|
161
|
+
authDiv > 0.3 ? 'diverse support ✅' :
|
|
162
|
+
authDiv > 0 ? 'some support ✓' :
|
|
163
|
+
authDiv > -0.3 ? 'weak contradiction ⚠' :
|
|
164
|
+
'diverse contradiction ❌';
|
|
165
|
+
output += `Authenticated: ${sign}${(Math.abs(authDiv) * 100).toFixed(1)}% (${status})\n`;
|
|
166
|
+
}
|
|
167
|
+
output += `\n## Evidence (${concept.instances.length} instances)\n\n`;
|
|
168
|
+
concept.instances.forEach((inst, i) => {
|
|
169
|
+
output += `${i + 1}. ${inst.document} (para ${inst.paragraph}): "${inst.quote}"\n`;
|
|
170
|
+
// Include full context for chapter/verse citation if available
|
|
171
|
+
if (inst.full_text) {
|
|
172
|
+
// Extract chapter/verse from full_text (e.g., "# Chapter 46\n\n1. So Israel...")
|
|
173
|
+
const chapterMatch = inst.full_text.match(/^#\s*Chapter\s+(\d+)/i);
|
|
174
|
+
if (chapterMatch) {
|
|
175
|
+
output += ` Context: Chapter ${chapterMatch[1]}\n`;
|
|
176
|
+
}
|
|
177
|
+
// Show full context or truncated based on parameter
|
|
178
|
+
const cleanedContext = inst.full_text.replace(/^#[^\n]*\n+/, '');
|
|
179
|
+
if (cleanedContext && cleanedContext !== inst.quote) {
|
|
180
|
+
if (truncateEvidence && cleanedContext.length > 200) {
|
|
181
|
+
// Truncate to 200 chars for token efficiency
|
|
182
|
+
output += ` Full context: ${cleanedContext.substring(0, 200)}...\n`;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Show complete context
|
|
186
|
+
output += ` Full context: ${cleanedContext}\n`;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// ADR-057: Indicate if this evidence has an image
|
|
191
|
+
if (inst.has_image && inst.source_id) {
|
|
192
|
+
output += ` Source: ${inst.source_id} [IMAGE AVAILABLE]\n`;
|
|
193
|
+
output += ` Use get_source_image("${inst.source_id}") to view and verify the original image\n`;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
if (concept.relationships.length > 0) {
|
|
197
|
+
output += `\n## Relationships (${concept.relationships.length})\n\n`;
|
|
198
|
+
concept.relationships.forEach(rel => {
|
|
199
|
+
const confidence = rel.confidence ? ` (${(rel.confidence * 100).toFixed(0)}%)` : '';
|
|
200
|
+
output += `${rel.rel_type} -> ${rel.to_label}${confidence}\n`;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
output += '\nNo outgoing relationships\n';
|
|
205
|
+
}
|
|
206
|
+
return output;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Format connection paths as markdown
|
|
210
|
+
* Enhanced to match CLI output with full concept details, evidence samples, and path visualization
|
|
211
|
+
*/
|
|
212
|
+
function formatConnectionPaths(result) {
|
|
213
|
+
let output = `# Connection: ${result.from_concept?.label || result.from_query} -> ${result.to_concept?.label || result.to_query}\n\n`;
|
|
214
|
+
output += `From Match: ${(result.from_similarity * 100).toFixed(1)}%\n`;
|
|
215
|
+
output += `To Match: ${(result.to_similarity * 100).toFixed(1)}%\n`;
|
|
216
|
+
output += `Max Hops: ${result.max_hops}\n\n`;
|
|
217
|
+
if (result.count === 0) {
|
|
218
|
+
output += `No connection found within ${result.max_hops} hops\n`;
|
|
219
|
+
return output;
|
|
220
|
+
}
|
|
221
|
+
output += `Found ${result.count} path(s):\n\n`;
|
|
222
|
+
result.paths.forEach((path, pathIdx) => {
|
|
223
|
+
output += `## Path ${pathIdx + 1} (${path.hops} hop${path.hops !== 1 ? 's' : ''})\n\n`;
|
|
224
|
+
// Full path visualization with arrows
|
|
225
|
+
output += '### Path Overview\n\n';
|
|
226
|
+
const pathSegments = [];
|
|
227
|
+
path.nodes.forEach((node, j) => {
|
|
228
|
+
pathSegments.push(node.label);
|
|
229
|
+
if (j < path.relationships.length) {
|
|
230
|
+
pathSegments.push(`↓ ${path.relationships[j]}`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
output += pathSegments.join('\n') + '\n\n';
|
|
234
|
+
// Detailed concept information for each node
|
|
235
|
+
path.nodes.forEach((node, nodeIdx) => {
|
|
236
|
+
output += `### ${nodeIdx + 1}. ${node.label}\n\n`;
|
|
237
|
+
// Concept ID and description
|
|
238
|
+
output += `**ID:** ${node.id}\n`;
|
|
239
|
+
if (node.description) {
|
|
240
|
+
output += `**Description:** ${node.description}\n`;
|
|
241
|
+
}
|
|
242
|
+
// Grounding strength with confidence-awareness
|
|
243
|
+
if (node.grounding_strength !== undefined || node.grounding_display) {
|
|
244
|
+
output += `**Grounding:** ${formatGroundingWithConfidence(node.grounding_strength, node.grounding_display, node.confidence_score)}\n`;
|
|
245
|
+
}
|
|
246
|
+
// Diversity metrics if available
|
|
247
|
+
if (node.diversity_score !== undefined && node.diversity_score !== null && node.diversity_related_count !== undefined) {
|
|
248
|
+
output += `**Diversity:** ${(node.diversity_score * 100).toFixed(1)}% (${node.diversity_related_count} related concepts)\n`;
|
|
249
|
+
}
|
|
250
|
+
// Authenticated diversity if available
|
|
251
|
+
if (node.authenticated_diversity !== undefined && node.authenticated_diversity !== null) {
|
|
252
|
+
const authDiv = node.authenticated_diversity;
|
|
253
|
+
const sign = authDiv >= 0 ? '+' : '';
|
|
254
|
+
// Near-zero values (|authDiv| < 0.05) are "unclear" - grounding too weak to authenticate
|
|
255
|
+
const status = Math.abs(authDiv) < 0.05 ? 'unclear ◯' :
|
|
256
|
+
authDiv > 0.3 ? 'diverse support ✅' :
|
|
257
|
+
authDiv > 0 ? 'some support ✓' :
|
|
258
|
+
authDiv > -0.3 ? 'weak contradiction ⚠' :
|
|
259
|
+
'diverse contradiction ❌';
|
|
260
|
+
output += `**Authenticated:** ${sign}${(Math.abs(authDiv) * 100).toFixed(1)}% (${status})\n`;
|
|
261
|
+
}
|
|
262
|
+
// Evidence samples (limit to 3 for token efficiency)
|
|
263
|
+
if (node.sample_evidence && node.sample_evidence.length > 0) {
|
|
264
|
+
const evidenceCount = node.sample_evidence.length;
|
|
265
|
+
output += `\n**Evidence (${evidenceCount} sample${evidenceCount !== 1 ? 's' : ''}):**\n\n`;
|
|
266
|
+
node.sample_evidence.slice(0, 3).forEach((inst, idx) => {
|
|
267
|
+
const truncated = inst.quote.length > 150 ? inst.quote.substring(0, 150) + '...' : inst.quote;
|
|
268
|
+
output += `${idx + 1}. ${inst.document} (para ${inst.paragraph}):\n`;
|
|
269
|
+
output += ` "${truncated}"\n`;
|
|
270
|
+
// ADR-057: Image availability
|
|
271
|
+
if (inst.has_image && inst.source_id) {
|
|
272
|
+
output += ` [IMAGE AVAILABLE] Use get_source_image("${inst.source_id}") to view\n`;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
if (evidenceCount > 3) {
|
|
276
|
+
output += ` ... and ${evidenceCount - 3} more samples\n`;
|
|
277
|
+
output += ` Use get_concept_details("${node.id}") for all evidence\n`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Show relationship to next node
|
|
281
|
+
if (nodeIdx < path.relationships.length) {
|
|
282
|
+
output += `\n**→ ${path.relationships[nodeIdx]}**\n`;
|
|
283
|
+
}
|
|
284
|
+
output += '\n';
|
|
285
|
+
});
|
|
286
|
+
output += '---\n\n';
|
|
287
|
+
});
|
|
288
|
+
return output;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Format related concepts as markdown
|
|
292
|
+
*/
|
|
293
|
+
function formatRelatedConcepts(result) {
|
|
294
|
+
let output = `# Related Concepts\n\n`;
|
|
295
|
+
output += `From: ${result.concept_id}\n`;
|
|
296
|
+
output += `Max Depth: ${result.max_depth}\n`;
|
|
297
|
+
output += `Found: ${result.count} concepts\n\n`;
|
|
298
|
+
if (result.count === 0) {
|
|
299
|
+
output += 'No related concepts found\n';
|
|
300
|
+
return output;
|
|
301
|
+
}
|
|
302
|
+
let currentDistance = -1;
|
|
303
|
+
result.results.forEach(concept => {
|
|
304
|
+
if (concept.distance !== currentDistance) {
|
|
305
|
+
currentDistance = concept.distance;
|
|
306
|
+
output += `\n## Distance ${currentDistance}\n\n`;
|
|
307
|
+
}
|
|
308
|
+
output += `${concept.label} (${concept.concept_id})\n`;
|
|
309
|
+
output += `Path: ${concept.path_types.join(' -> ')}\n`;
|
|
310
|
+
});
|
|
311
|
+
return output;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Format job list as markdown (token-efficient summary view)
|
|
315
|
+
*/
|
|
316
|
+
function formatJobList(jobs) {
|
|
317
|
+
let output = `# Ingestion Jobs\n\n`;
|
|
318
|
+
output += `Total: ${jobs.length} job(s)\n\n`;
|
|
319
|
+
if (jobs.length === 0) {
|
|
320
|
+
output += 'No jobs found.\n';
|
|
321
|
+
return output;
|
|
322
|
+
}
|
|
323
|
+
// Group by status for quick overview
|
|
324
|
+
const byStatus = {};
|
|
325
|
+
jobs.forEach(job => {
|
|
326
|
+
const status = job.status;
|
|
327
|
+
if (!byStatus[status])
|
|
328
|
+
byStatus[status] = [];
|
|
329
|
+
byStatus[status].push(job);
|
|
330
|
+
});
|
|
331
|
+
// Status summary
|
|
332
|
+
output += '## Status Summary\n\n';
|
|
333
|
+
const statusOrder = ['processing', 'awaiting_approval', 'pending', 'approved', 'queued', 'completed', 'failed', 'cancelled'];
|
|
334
|
+
statusOrder.forEach(status => {
|
|
335
|
+
if (byStatus[status] && byStatus[status].length > 0) {
|
|
336
|
+
const icon = status === 'completed' ? '✓' :
|
|
337
|
+
status === 'processing' ? '▶' :
|
|
338
|
+
status === 'awaiting_approval' ? '⏳' :
|
|
339
|
+
status === 'failed' ? '✗' :
|
|
340
|
+
status === 'cancelled' ? '⊘' : '○';
|
|
341
|
+
output += `- ${icon} **${status}**: ${byStatus[status].length}\n`;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
output += '\n';
|
|
345
|
+
// Detailed list
|
|
346
|
+
output += '## Jobs\n\n';
|
|
347
|
+
jobs.forEach((job, idx) => {
|
|
348
|
+
const statusIcon = job.status === 'completed' ? '✓' :
|
|
349
|
+
job.status === 'processing' ? '▶' :
|
|
350
|
+
job.status === 'awaiting_approval' ? '⏳' :
|
|
351
|
+
job.status === 'failed' ? '✗' :
|
|
352
|
+
job.status === 'cancelled' ? '⊘' : '○';
|
|
353
|
+
// Header with status and filename
|
|
354
|
+
const filename = job.filename || job.analysis?.file_stats?.filename || 'Unknown';
|
|
355
|
+
output += `### ${idx + 1}. ${statusIcon} ${filename}\n\n`;
|
|
356
|
+
// Core info
|
|
357
|
+
output += `- **Job ID:** ${job.job_id}\n`;
|
|
358
|
+
output += `- **Status:** ${job.status}`;
|
|
359
|
+
if (job.progress?.stage) {
|
|
360
|
+
output += ` (${job.progress.stage})`;
|
|
361
|
+
}
|
|
362
|
+
output += '\n';
|
|
363
|
+
if (job.ontology) {
|
|
364
|
+
output += `- **Ontology:** ${job.ontology}\n`;
|
|
365
|
+
}
|
|
366
|
+
// Progress for processing jobs
|
|
367
|
+
if (job.status === 'processing' && job.progress) {
|
|
368
|
+
const p = job.progress;
|
|
369
|
+
if (p.percent !== undefined) {
|
|
370
|
+
output += `- **Progress:** ${p.percent}%`;
|
|
371
|
+
if (p.chunks_total !== undefined) {
|
|
372
|
+
output += ` (${p.chunks_processed || 0}/${p.chunks_total} chunks)`;
|
|
373
|
+
}
|
|
374
|
+
output += '\n';
|
|
375
|
+
}
|
|
376
|
+
if (p.concepts_created !== undefined) {
|
|
377
|
+
output += `- **Created:** ${p.concepts_created} concepts, ${p.sources_created || 0} sources\n`;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Cost for awaiting_approval
|
|
381
|
+
if (job.status === 'awaiting_approval' && job.analysis?.cost_estimate?.total) {
|
|
382
|
+
const cost = job.analysis.cost_estimate.total;
|
|
383
|
+
output += `- **Est. Cost:** $${cost.cost_low.toFixed(3)} - $${cost.cost_high.toFixed(3)}\n`;
|
|
384
|
+
}
|
|
385
|
+
// Results for completed jobs
|
|
386
|
+
if (job.status === 'completed' && job.result?.stats) {
|
|
387
|
+
const s = job.result.stats;
|
|
388
|
+
output += `- **Result:** ${s.concepts_created || 0} concepts, ${s.relationships_created || 0} relationships\n`;
|
|
389
|
+
if (job.result.cost?.total) {
|
|
390
|
+
output += `- **Cost:** ${job.result.cost.total}\n`;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// Error for failed jobs
|
|
394
|
+
if (job.status === 'failed' && job.error) {
|
|
395
|
+
const truncatedError = job.error.length > 100 ? job.error.substring(0, 100) + '...' : job.error;
|
|
396
|
+
output += `- **Error:** ${truncatedError}\n`;
|
|
397
|
+
}
|
|
398
|
+
output += '\n';
|
|
399
|
+
});
|
|
400
|
+
// Usage hints
|
|
401
|
+
output += '## Actions\n\n';
|
|
402
|
+
output += '- Use `job` tool with action "status" and job_id for full details\n';
|
|
403
|
+
output += '- Use `job` tool with action "approve" to start awaiting jobs\n';
|
|
404
|
+
output += '- Use `job` tool with action "cancel" to cancel pending jobs\n';
|
|
405
|
+
return output;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Format job status as markdown (token-efficient)
|
|
409
|
+
*/
|
|
410
|
+
function formatJobStatus(job) {
|
|
411
|
+
let output = `# Job: ${job.job_id}\n\n`;
|
|
412
|
+
output += `Type: ${job.job_type}\n`;
|
|
413
|
+
output += `Status: ${job.status}`;
|
|
414
|
+
// Add stage info if available
|
|
415
|
+
if (job.progress?.stage) {
|
|
416
|
+
output += ` (${job.progress.stage})`;
|
|
417
|
+
}
|
|
418
|
+
output += '\n';
|
|
419
|
+
// Ontology and processing mode
|
|
420
|
+
if (job.ontology) {
|
|
421
|
+
output += `Ontology: ${job.ontology}\n`;
|
|
422
|
+
}
|
|
423
|
+
if (job.processing_mode) {
|
|
424
|
+
output += `Processing Mode: ${job.processing_mode}\n`;
|
|
425
|
+
}
|
|
426
|
+
// Progress information
|
|
427
|
+
if (job.progress) {
|
|
428
|
+
const p = job.progress;
|
|
429
|
+
output += '\n## Progress\n\n';
|
|
430
|
+
if (p.percent !== undefined) {
|
|
431
|
+
output += `Completion: ${p.percent}%\n`;
|
|
432
|
+
}
|
|
433
|
+
if (p.chunks_total !== undefined) {
|
|
434
|
+
const processed = p.chunks_processed || 0;
|
|
435
|
+
output += `Chunks: ${processed}/${p.chunks_total}\n`;
|
|
436
|
+
}
|
|
437
|
+
if (p.concepts_created !== undefined || p.concepts_linked !== undefined) {
|
|
438
|
+
if (p.concepts_created !== undefined) {
|
|
439
|
+
output += `Concepts Created: ${p.concepts_created}\n`;
|
|
440
|
+
}
|
|
441
|
+
if (p.concepts_linked !== undefined) {
|
|
442
|
+
output += `Concepts Linked: ${p.concepts_linked}\n`;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (p.sources_created !== undefined) {
|
|
446
|
+
output += `Sources Created: ${p.sources_created}\n`;
|
|
447
|
+
}
|
|
448
|
+
if (p.instances_created !== undefined) {
|
|
449
|
+
output += `Instances Created: ${p.instances_created}\n`;
|
|
450
|
+
}
|
|
451
|
+
if (p.relationships_created !== undefined) {
|
|
452
|
+
output += `Relationships Created: ${p.relationships_created}\n`;
|
|
453
|
+
}
|
|
454
|
+
if (p.message) {
|
|
455
|
+
output += `Message: ${p.message}\n`;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Cost estimate (for awaiting_approval status)
|
|
459
|
+
if (job.status === 'awaiting_approval' && job.analysis?.cost_estimate) {
|
|
460
|
+
const cost = job.analysis.cost_estimate;
|
|
461
|
+
output += '\n## Cost Estimate\n\n';
|
|
462
|
+
if (cost.total) {
|
|
463
|
+
output += `Total: $${cost.total.cost_low.toFixed(4)} - $${cost.total.cost_high.toFixed(4)} ${cost.total.currency}\n`;
|
|
464
|
+
}
|
|
465
|
+
if (cost.extraction) {
|
|
466
|
+
output += `Extraction: $${cost.extraction.cost_low.toFixed(4)} - $${cost.extraction.cost_high.toFixed(4)}\n`;
|
|
467
|
+
}
|
|
468
|
+
if (cost.embeddings) {
|
|
469
|
+
output += `Embeddings: $${cost.embeddings.cost_low.toFixed(4)} - $${cost.embeddings.cost_high.toFixed(4)}\n`;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Result (for completed jobs)
|
|
473
|
+
if (job.result) {
|
|
474
|
+
output += '\n## Result\n\n';
|
|
475
|
+
if (job.result.stats) {
|
|
476
|
+
output += `Concepts Created: ${job.result.stats.concepts_created || 0}\n`;
|
|
477
|
+
output += `Concepts Linked: ${job.result.stats.concepts_linked || 0}\n`;
|
|
478
|
+
output += `Sources Created: ${job.result.stats.sources_created || 0}\n`;
|
|
479
|
+
output += `Instances Created: ${job.result.stats.instances_created || 0}\n`;
|
|
480
|
+
output += `Relationships Created: ${job.result.stats.relationships_created || 0}\n`;
|
|
481
|
+
output += `Chunks Processed: ${job.result.stats.chunks_processed || 0}\n`;
|
|
482
|
+
}
|
|
483
|
+
if (job.result.cost) {
|
|
484
|
+
output += `Cost: ${job.result.cost.total}\n`;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Error (for failed jobs)
|
|
488
|
+
if (job.error) {
|
|
489
|
+
output += '\n## Error\n\n';
|
|
490
|
+
output += `${job.error}\n`;
|
|
491
|
+
}
|
|
492
|
+
// Timestamps
|
|
493
|
+
output += '\n## Timeline\n\n';
|
|
494
|
+
output += `Created: ${job.created_at}\n`;
|
|
495
|
+
if (job.started_at) {
|
|
496
|
+
output += `Started: ${job.started_at}\n`;
|
|
497
|
+
}
|
|
498
|
+
if (job.completed_at) {
|
|
499
|
+
output += `Completed: ${job.completed_at}\n`;
|
|
500
|
+
}
|
|
501
|
+
if (job.approved_at) {
|
|
502
|
+
output += `Approved: ${job.approved_at} (by ${job.approved_by || 'unknown'})\n`;
|
|
503
|
+
}
|
|
504
|
+
if (job.expires_at) {
|
|
505
|
+
output += `Expires: ${job.expires_at}\n`;
|
|
506
|
+
}
|
|
507
|
+
return output;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Format inspect-file result as markdown
|
|
511
|
+
*/
|
|
512
|
+
function formatInspectFileResult(result) {
|
|
513
|
+
let output = `# File Inspection: ${result.path}\n\n`;
|
|
514
|
+
// Validation status
|
|
515
|
+
if (result.validation.allowed) {
|
|
516
|
+
output += `Status: ✓ ALLOWED\n\n`;
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
output += `Status: ✗ DENIED\n`;
|
|
520
|
+
output += `Reason: ${result.validation.reason}\n`;
|
|
521
|
+
if (result.validation.hint) {
|
|
522
|
+
output += `Hint: ${result.validation.hint}\n`;
|
|
523
|
+
}
|
|
524
|
+
output += '\n';
|
|
525
|
+
}
|
|
526
|
+
// File existence
|
|
527
|
+
if (result.exists) {
|
|
528
|
+
output += `## File Metadata\n\n`;
|
|
529
|
+
if (result.metadata) {
|
|
530
|
+
const m = result.metadata;
|
|
531
|
+
output += `Type: ${m.type}\n`;
|
|
532
|
+
output += `Size: ${m.size_mb.toFixed(2)} MB (${m.size_bytes.toLocaleString()} bytes)\n`;
|
|
533
|
+
output += `MIME Type: ${m.mime_type}\n`;
|
|
534
|
+
output += `Modified: ${new Date(m.modified).toLocaleString()}\n`;
|
|
535
|
+
output += `Image: ${m.is_image ? 'Yes' : 'No'}\n`;
|
|
536
|
+
output += `Permissions: ${m.permissions.readable ? 'readable' : ''}${m.permissions.readable && m.permissions.writable ? ', ' : ''}${m.permissions.writable ? 'writable' : ''}\n`;
|
|
537
|
+
}
|
|
538
|
+
if (result.error) {
|
|
539
|
+
output += `\nError: ${result.error}\n`;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
output += `File does not exist\n`;
|
|
544
|
+
}
|
|
545
|
+
return output;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Format ingest-file result as markdown
|
|
549
|
+
*/
|
|
550
|
+
function formatIngestFileResult(result) {
|
|
551
|
+
// Handle batch ingestion
|
|
552
|
+
if (result.batch) {
|
|
553
|
+
let output = `# Batch File Ingestion\n\n`;
|
|
554
|
+
output += `Ontology: ${result.ontology}\n`;
|
|
555
|
+
output += `Total Files: ${result.total_files}\n`;
|
|
556
|
+
output += `Successful: ${result.successful}\n`;
|
|
557
|
+
output += `Failed: ${result.failed}\n\n`;
|
|
558
|
+
if (result.results && result.results.length > 0) {
|
|
559
|
+
output += `## Submitted Files\n\n`;
|
|
560
|
+
result.results.forEach((r, idx) => {
|
|
561
|
+
const filename = r.file.split('/').pop();
|
|
562
|
+
const typeLabel = r.type === 'image' ? '🖼️' : '📄';
|
|
563
|
+
if (r.status === 'submitted' && r.job_id) {
|
|
564
|
+
output += `${idx + 1}. ✓ ${typeLabel} ${filename}\n`;
|
|
565
|
+
output += ` Job ID: ${r.job_id}\n`;
|
|
566
|
+
}
|
|
567
|
+
else if (r.status === 'duplicate') {
|
|
568
|
+
output += `${idx + 1}. ⊘ ${typeLabel} ${filename} (already ingested)\n`;
|
|
569
|
+
}
|
|
570
|
+
else if (r.status === 'not_implemented') {
|
|
571
|
+
output += `${idx + 1}. ⏸ ${filename} (${r.type} - not implemented yet)\n`;
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
if (result.errors && result.errors.length > 0) {
|
|
576
|
+
output += `\n## Errors\n\n`;
|
|
577
|
+
result.errors.forEach((e, idx) => {
|
|
578
|
+
const filename = e.file.split('/').pop();
|
|
579
|
+
output += `${idx + 1}. ✗ ${filename}\n`;
|
|
580
|
+
output += ` ${e.error}\n`;
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return output;
|
|
584
|
+
}
|
|
585
|
+
// Handle single file ingestion
|
|
586
|
+
const typeIcon = result.type === 'image' ? '🖼️' : '📄';
|
|
587
|
+
let output = `# File Ingestion: ${typeIcon} ${result.file}\n\n`;
|
|
588
|
+
if (result.status === 'not_implemented') {
|
|
589
|
+
output += `Status: Not Implemented\n`;
|
|
590
|
+
output += `Type: ${result.type}\n`;
|
|
591
|
+
output += `Message: ${result.message}\n`;
|
|
592
|
+
if (result.next_phase) {
|
|
593
|
+
output += `\nNext Phase: ${result.next_phase}\n`;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else if (result.status === 'submitted') {
|
|
597
|
+
output += `Status: ✓ Submitted Successfully\n\n`;
|
|
598
|
+
output += `## Job Details\n\n`;
|
|
599
|
+
output += `Job ID: ${result.job_id}\n`;
|
|
600
|
+
output += `Ontology: ${result.ontology}\n`;
|
|
601
|
+
output += `Type: ${result.type === 'image' ? '🖼️ Image' : '📄 Text'}\n`;
|
|
602
|
+
output += `Size: ${(result.size_bytes / 1024).toFixed(2)} KB\n\n`;
|
|
603
|
+
output += `Track progress: Use job tool with action "status" and job_id "${result.job_id}"\n`;
|
|
604
|
+
}
|
|
605
|
+
else if (result.status === 'duplicate') {
|
|
606
|
+
output += `Status: Duplicate Detected\n\n`;
|
|
607
|
+
output += `This file has already been ingested.\n`;
|
|
608
|
+
if (result.duplicate_job_id) {
|
|
609
|
+
output += `Existing Job ID: ${result.duplicate_job_id}\n`;
|
|
610
|
+
}
|
|
611
|
+
output += `\nTo force re-ingestion, set force=true\n`;
|
|
612
|
+
}
|
|
613
|
+
return output;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Format ingest-directory result as markdown
|
|
617
|
+
*/
|
|
618
|
+
function formatIngestDirectoryResult(result) {
|
|
619
|
+
let output = `# Directory Ingestion: ${result.directory}\n\n`;
|
|
620
|
+
if (result.status === 'not_implemented') {
|
|
621
|
+
output += `Status: Not Implemented (Preview Mode)\n`;
|
|
622
|
+
output += `Ontology: ${result.ontology}\n\n`;
|
|
623
|
+
output += `## Scan Results\n\n`;
|
|
624
|
+
output += `Files Found: ${result.files_found}\n`;
|
|
625
|
+
output += `Files Skipped: ${result.files_skipped} (blocked by security patterns)\n\n`;
|
|
626
|
+
if (result.files && result.files.length > 0) {
|
|
627
|
+
const pagination = result.pagination;
|
|
628
|
+
const startNum = pagination ? pagination.offset + 1 : 1;
|
|
629
|
+
const endNum = pagination ? pagination.offset + result.files.length : result.files.length;
|
|
630
|
+
output += `## Files (showing ${startNum}-${endNum} of ${result.files_found})\n\n`;
|
|
631
|
+
result.files.forEach((file, idx) => {
|
|
632
|
+
const basename = file.split('/').pop();
|
|
633
|
+
const fileNum = pagination ? pagination.offset + idx + 1 : idx + 1;
|
|
634
|
+
output += `${fileNum}. ${basename}\n`;
|
|
635
|
+
});
|
|
636
|
+
// Pagination navigation
|
|
637
|
+
if (pagination && (pagination.offset > 0 || pagination.has_more)) {
|
|
638
|
+
output += `\n## Navigation\n\n`;
|
|
639
|
+
if (pagination.offset > 0) {
|
|
640
|
+
const prevOffset = Math.max(0, pagination.offset - pagination.limit);
|
|
641
|
+
output += `Previous: Use offset=${prevOffset}, limit=${pagination.limit}\n`;
|
|
642
|
+
}
|
|
643
|
+
if (pagination.has_more) {
|
|
644
|
+
const nextOffset = pagination.offset + pagination.limit;
|
|
645
|
+
output += `Next: Use offset=${nextOffset}, limit=${pagination.limit}\n`;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (result.next_phase) {
|
|
650
|
+
output += `\n${result.next_phase}\n`;
|
|
651
|
+
}
|
|
652
|
+
output += `\nFor now, use ingest-file on individual files from the list above.\n`;
|
|
653
|
+
}
|
|
654
|
+
else if (result.status === 'submitted') {
|
|
655
|
+
output += `Status: ✓ Batch Submission Started\n\n`;
|
|
656
|
+
output += `## Summary\n\n`;
|
|
657
|
+
output += `Ontology: ${result.ontology}\n`;
|
|
658
|
+
output += `Files Queued: ${result.files_queued}\n`;
|
|
659
|
+
output += `Files Skipped: ${result.files_skipped}\n\n`;
|
|
660
|
+
if (result.job_ids && result.job_ids.length > 0) {
|
|
661
|
+
output += `## Job IDs\n\n`;
|
|
662
|
+
result.job_ids.forEach((id, idx) => {
|
|
663
|
+
output += `${idx + 1}. ${id}\n`;
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return output;
|
|
668
|
+
}
|
|
669
|
+
// ============================================================================
|
|
670
|
+
// MCP Resource Formatters
|
|
671
|
+
// ============================================================================
|
|
672
|
+
function formatDatabaseStats(result) {
|
|
673
|
+
let output = `# Database Statistics\n\n`;
|
|
674
|
+
if (result.graph_name) {
|
|
675
|
+
output += `Graph: ${result.graph_name}\n\n`;
|
|
676
|
+
}
|
|
677
|
+
output += `## Nodes\n\n`;
|
|
678
|
+
output += `Concepts: ${result.concept_count?.toLocaleString() || 0}\n`;
|
|
679
|
+
output += `Sources: ${result.source_count?.toLocaleString() || 0}\n`;
|
|
680
|
+
output += `Instances: ${result.instance_count?.toLocaleString() || 0}\n`;
|
|
681
|
+
output += `Total: ${result.total_node_count?.toLocaleString() || 0}\n\n`;
|
|
682
|
+
output += `## Relationships\n\n`;
|
|
683
|
+
output += `Total: ${result.total_edge_count?.toLocaleString() || 0}\n\n`;
|
|
684
|
+
if (result.ontologies && result.ontologies.length > 0) {
|
|
685
|
+
output += `## Ontologies (${result.ontologies.length})\n\n`;
|
|
686
|
+
result.ontologies.forEach((ont, idx) => {
|
|
687
|
+
output += `${idx + 1}. ${ont.ontology_name} (${ont.concept_count} concepts)\n`;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
return output;
|
|
691
|
+
}
|
|
692
|
+
function formatDatabaseInfo(result) {
|
|
693
|
+
let output = `# Database Information\n\n`;
|
|
694
|
+
if (result.database) {
|
|
695
|
+
output += `Database: ${result.database}\n`;
|
|
696
|
+
}
|
|
697
|
+
if (result.version) {
|
|
698
|
+
output += `PostgreSQL: ${result.version}\n`;
|
|
699
|
+
}
|
|
700
|
+
if (result.age_version) {
|
|
701
|
+
output += `Apache AGE: ${result.age_version}\n`;
|
|
702
|
+
}
|
|
703
|
+
if (result.graph_name) {
|
|
704
|
+
output += `Graph: ${result.graph_name}\n`;
|
|
705
|
+
}
|
|
706
|
+
return output;
|
|
707
|
+
}
|
|
708
|
+
function formatDatabaseHealth(result) {
|
|
709
|
+
let output = `# Database Health\n\n`;
|
|
710
|
+
const status = result.status || result.healthy;
|
|
711
|
+
if (status === 'healthy' || status === true) {
|
|
712
|
+
output += `Status: ✓ Healthy\n`;
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
output += `Status: ✗ Unhealthy\n`;
|
|
716
|
+
}
|
|
717
|
+
if (result.graph_available !== undefined) {
|
|
718
|
+
output += `Graph Available: ${result.graph_available ? '✓ Yes' : '✗ No'}\n`;
|
|
719
|
+
}
|
|
720
|
+
if (result.connection) {
|
|
721
|
+
output += `Connection: ${result.connection}\n`;
|
|
722
|
+
}
|
|
723
|
+
return output;
|
|
724
|
+
}
|
|
725
|
+
function formatSystemStatus(result) {
|
|
726
|
+
let output = `# System Status\n\n`;
|
|
727
|
+
if (result.scheduler) {
|
|
728
|
+
output += `## Job Scheduler\n\n`;
|
|
729
|
+
output += `Status: ${result.scheduler.running ? '✓ Running' : '✗ Stopped'}\n`;
|
|
730
|
+
if (result.scheduler.active_jobs !== undefined) {
|
|
731
|
+
output += `Active Jobs: ${result.scheduler.active_jobs}\n`;
|
|
732
|
+
}
|
|
733
|
+
if (result.scheduler.pending_jobs !== undefined) {
|
|
734
|
+
output += `Pending Jobs: ${result.scheduler.pending_jobs}\n`;
|
|
735
|
+
}
|
|
736
|
+
output += `\n`;
|
|
737
|
+
}
|
|
738
|
+
if (result.resources) {
|
|
739
|
+
output += `## Resource Usage\n\n`;
|
|
740
|
+
if (result.resources.cpu_percent !== undefined) {
|
|
741
|
+
output += `CPU: ${result.resources.cpu_percent}%\n`;
|
|
742
|
+
}
|
|
743
|
+
if (result.resources.memory_percent !== undefined) {
|
|
744
|
+
output += `Memory: ${result.resources.memory_percent}%\n`;
|
|
745
|
+
}
|
|
746
|
+
if (result.resources.disk_percent !== undefined) {
|
|
747
|
+
output += `Disk: ${result.resources.disk_percent}%\n`;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
return output;
|
|
751
|
+
}
|
|
752
|
+
function formatApiHealth(result) {
|
|
753
|
+
let output = `# API Health\n\n`;
|
|
754
|
+
if (result.status === 'healthy' || result.healthy === true) {
|
|
755
|
+
output += `Status: ✓ Healthy\n`;
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
output += `Status: ✗ Unhealthy\n`;
|
|
759
|
+
}
|
|
760
|
+
if (result.timestamp) {
|
|
761
|
+
output += `Timestamp: ${new Date(result.timestamp).toLocaleString()}\n`;
|
|
762
|
+
}
|
|
763
|
+
if (result.version) {
|
|
764
|
+
output += `Version: ${result.version}\n`;
|
|
765
|
+
}
|
|
766
|
+
return output;
|
|
767
|
+
}
|
|
768
|
+
function formatMcpAllowedPaths(result) {
|
|
769
|
+
if (!result.configured) {
|
|
770
|
+
let output = `# MCP File Access Allowlist\n\n`;
|
|
771
|
+
output += `Status: ✗ Not Configured\n\n`;
|
|
772
|
+
if (result.message) {
|
|
773
|
+
output += `${result.message}\n\n`;
|
|
774
|
+
}
|
|
775
|
+
if (result.hint) {
|
|
776
|
+
output += `Hint: ${result.hint}\n`;
|
|
777
|
+
}
|
|
778
|
+
return output;
|
|
779
|
+
}
|
|
780
|
+
let output = `# MCP File Access Allowlist\n\n`;
|
|
781
|
+
output += `Status: ✓ Configured\n`;
|
|
782
|
+
output += `Version: ${result.version}\n\n`;
|
|
783
|
+
if (result.allowed_directories && result.allowed_directories.length > 0) {
|
|
784
|
+
output += `## Allowed Directories (${result.allowed_directories.length})\n\n`;
|
|
785
|
+
result.allowed_directories.forEach((dir, idx) => {
|
|
786
|
+
output += `${idx + 1}. ${dir}\n`;
|
|
787
|
+
});
|
|
788
|
+
output += `\n`;
|
|
789
|
+
}
|
|
790
|
+
if (result.allowed_patterns && result.allowed_patterns.length > 0) {
|
|
791
|
+
output += `## Allowed Patterns (${result.allowed_patterns.length})\n\n`;
|
|
792
|
+
result.allowed_patterns.forEach((pattern, idx) => {
|
|
793
|
+
output += `${idx + 1}. ${pattern}\n`;
|
|
794
|
+
});
|
|
795
|
+
output += `\n`;
|
|
796
|
+
}
|
|
797
|
+
if (result.blocked_patterns && result.blocked_patterns.length > 0) {
|
|
798
|
+
output += `## Blocked Patterns (${result.blocked_patterns.length})\n\n`;
|
|
799
|
+
result.blocked_patterns.forEach((pattern, idx) => {
|
|
800
|
+
output += `${idx + 1}. ${pattern}\n`;
|
|
801
|
+
});
|
|
802
|
+
output += `\n`;
|
|
803
|
+
}
|
|
804
|
+
output += `## Limits\n\n`;
|
|
805
|
+
output += `Max File Size: ${result.max_file_size_mb} MB\n`;
|
|
806
|
+
output += `Max Files Per Directory: ${result.max_files_per_directory}\n`;
|
|
807
|
+
if (result.config_path) {
|
|
808
|
+
output += `\nConfig Path: ${result.config_path}\n`;
|
|
809
|
+
}
|
|
810
|
+
return output;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Format epistemic status interpretation hint
|
|
814
|
+
*/
|
|
815
|
+
function formatEpistemicStatusInterpretation(status) {
|
|
816
|
+
const interpretations = {
|
|
817
|
+
'WELL_GROUNDED': 'Well-established knowledge with strong evidence support (avg grounding >0.8). Highly reliable for reasoning.',
|
|
818
|
+
'MIXED_GROUNDING': 'Variable validation - grounding ranges 0.15-0.8 (15-80% net support). Represents mixed evidence or evolving understanding.',
|
|
819
|
+
'WEAK_GROUNDING': 'Weak positive grounding 0.0-0.15 (0-15% net support). Developing evidence, emerging knowledge. Use for exploratory reasoning.',
|
|
820
|
+
'POORLY_GROUNDED': 'Weak negative grounding -0.5-0.0 (0-50% net contradiction). Uncertain, liminal knowledge. Unclear epistemic status.',
|
|
821
|
+
'CONTRADICTED': 'Strong negative grounding <-0.5 (>50% net contradiction). Refuted claims, contradicted by evidence.',
|
|
822
|
+
'HISTORICAL': 'Temporal vocabulary with past-tense markers. Important for understanding evolution of concepts over time.',
|
|
823
|
+
'INSUFFICIENT_DATA': 'Less than 3 measurements available. Need more graph data to establish epistemic status.',
|
|
824
|
+
};
|
|
825
|
+
return interpretations[status] || 'Unknown epistemic status classification.';
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Format epistemic status list (ADR-065)
|
|
829
|
+
*/
|
|
830
|
+
function formatEpistemicStatusList(result) {
|
|
831
|
+
let output = '# Epistemic Status Classification\n\n';
|
|
832
|
+
output += `Total vocabulary types: ${result.total}\n\n`;
|
|
833
|
+
if (!result.types || result.types.length === 0) {
|
|
834
|
+
output += '**No epistemic status data available.**\n\n';
|
|
835
|
+
output += 'Run measurement first using the "measure" action to calculate epistemic status for all vocabulary types.\n\n';
|
|
836
|
+
output += '**What This Means:** Epistemic status reflects how well-established each relationship type is based on evidence grounding. ';
|
|
837
|
+
output += 'Without measurement, you cannot filter relationships by reliability or identify contested knowledge areas.\n';
|
|
838
|
+
return output;
|
|
839
|
+
}
|
|
840
|
+
// Add staleness header (ADR-065 Phase 2 counter-based tracking)
|
|
841
|
+
if (result.last_measurement_at) {
|
|
842
|
+
const measurementDate = new Date(result.last_measurement_at).toLocaleString();
|
|
843
|
+
output += `**Last Measurement:** ${measurementDate}\n`;
|
|
844
|
+
const delta = result.vocabulary_changes_since_measurement ?? 0;
|
|
845
|
+
let stalenessText = '';
|
|
846
|
+
if (delta === 0) {
|
|
847
|
+
stalenessText = 'No changes since measurement (fresh)';
|
|
848
|
+
}
|
|
849
|
+
else if (delta < 5) {
|
|
850
|
+
stalenessText = `${delta} vocabulary change${delta > 1 ? 's' : ''} since measurement`;
|
|
851
|
+
}
|
|
852
|
+
else if (delta < 10) {
|
|
853
|
+
stalenessText = `${delta} vocabulary changes since measurement (consider re-measuring)`;
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
stalenessText = `${delta} vocabulary changes since measurement (re-measurement recommended)`;
|
|
857
|
+
}
|
|
858
|
+
output += `**Staleness:** ${stalenessText}\n\n`;
|
|
859
|
+
}
|
|
860
|
+
// Summary by classification
|
|
861
|
+
const classificationCounts = {};
|
|
862
|
+
result.types.forEach((type) => {
|
|
863
|
+
const status = type.epistemic_status || 'UNKNOWN';
|
|
864
|
+
classificationCounts[status] = (classificationCounts[status] || 0) + 1;
|
|
865
|
+
});
|
|
866
|
+
output += '## Classification Summary\n\n';
|
|
867
|
+
Object.entries(classificationCounts)
|
|
868
|
+
.sort((a, b) => b[1] - a[1]) // Sort by count descending
|
|
869
|
+
.forEach(([status, count]) => {
|
|
870
|
+
output += `- **${status}**: ${count} types\n`;
|
|
871
|
+
});
|
|
872
|
+
output += '\n';
|
|
873
|
+
// Detailed table (removed "Measured At" column - all types measured together)
|
|
874
|
+
output += '## Vocabulary Types\n\n';
|
|
875
|
+
output += '| Relationship Type | Status | Avg Grounding | Sampled Edges |\n';
|
|
876
|
+
output += '|-------------------|--------|---------------|---------------|\n';
|
|
877
|
+
result.types.forEach((type) => {
|
|
878
|
+
const avgGrounding = type.stats?.avg_grounding !== undefined
|
|
879
|
+
? type.stats.avg_grounding.toFixed(3)
|
|
880
|
+
: '--';
|
|
881
|
+
const sampledEdges = type.stats?.sampled_edges !== undefined
|
|
882
|
+
? type.stats.sampled_edges.toString()
|
|
883
|
+
: '--';
|
|
884
|
+
output += `| ${type.relationship_type} | ${type.epistemic_status} | ${avgGrounding} | ${sampledEdges} |\n`;
|
|
885
|
+
});
|
|
886
|
+
output += '\n## Interpretation Guide\n\n';
|
|
887
|
+
output += '**How to use this data:**\n';
|
|
888
|
+
output += '- **WELL_GROUNDED types** → Use for high-confidence reasoning and reliable knowledge extraction (>80% net support)\n';
|
|
889
|
+
output += '- **MIXED_GROUNDING types** → Variable validation (15-80% net support), explore dialectical patterns or uncertainty\n';
|
|
890
|
+
output += '- **WEAK_GROUNDING types** → Emerging evidence (0-15% net support), use for exploratory reasoning\n';
|
|
891
|
+
output += '- **POORLY_GROUNDED types** → Uncertain knowledge (0-50% net contradiction), unclear epistemic status\n';
|
|
892
|
+
output += '- **CONTRADICTED types** → Refuted claims (>50% net contradiction), contradicted by evidence\n';
|
|
893
|
+
output += '- **INSUFFICIENT_DATA types** → Need more document ingestion to establish epistemic patterns\n\n';
|
|
894
|
+
output += '**Next Steps:**\n';
|
|
895
|
+
output += '- Use `epistemic_status` with action "show" to get detailed statistics for a specific type\n';
|
|
896
|
+
output += '- Filter concept searches by epistemic status to curate high-confidence vs exploratory subgraphs\n';
|
|
897
|
+
output += '- Ingest more documents to move types from INSUFFICIENT_DATA to measurable classifications\n';
|
|
898
|
+
return output;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Format epistemic status details for a specific type (ADR-065)
|
|
902
|
+
*/
|
|
903
|
+
function formatEpistemicStatusDetails(result) {
|
|
904
|
+
const relType = result.relationship_type || 'Unknown';
|
|
905
|
+
const status = result.epistemic_status || 'UNKNOWN';
|
|
906
|
+
let output = `# Epistemic Status: ${relType}\n\n`;
|
|
907
|
+
output += `**Classification:** ${status}\n\n`;
|
|
908
|
+
output += `**Interpretation:** ${formatEpistemicStatusInterpretation(status)}\n\n`;
|
|
909
|
+
if (result.stats) {
|
|
910
|
+
output += '## Grounding Statistics\n\n';
|
|
911
|
+
output += `- **Average Grounding:** ${result.stats.avg_grounding.toFixed(3)} `;
|
|
912
|
+
if (result.stats.avg_grounding > 0.8) {
|
|
913
|
+
output += '(Strong support - well-established)\n';
|
|
914
|
+
}
|
|
915
|
+
else if (result.stats.avg_grounding > 0.15) {
|
|
916
|
+
output += '(Mixed validation - debated or uncertain)\n';
|
|
917
|
+
}
|
|
918
|
+
else if (result.stats.avg_grounding >= 0) {
|
|
919
|
+
output += '(Weak support - emerging or poorly grounded)\n';
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
output += '(Contradicted - refuted or historical)\n';
|
|
923
|
+
}
|
|
924
|
+
if (result.stats.std_grounding !== undefined) {
|
|
925
|
+
output += `- **Standard Deviation:** ${result.stats.std_grounding.toFixed(3)} `;
|
|
926
|
+
if (result.stats.std_grounding > 0.3) {
|
|
927
|
+
output += '(High variance - highly contested)\n';
|
|
928
|
+
}
|
|
929
|
+
else if (result.stats.std_grounding > 0.15) {
|
|
930
|
+
output += '(Moderate variance - some disagreement)\n';
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
output += '(Low variance - consistent validation)\n';
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
output += `- **Range:** ${result.stats.min_grounding.toFixed(3)} to ${result.stats.max_grounding.toFixed(3)}\n`;
|
|
937
|
+
output += `- **Measurements:** ${result.stats.measured_concepts} concepts sampled\n`;
|
|
938
|
+
output += `- **Sampled Edges:** ${result.stats.sampled_edges} of ${result.stats.total_edges} total\n`;
|
|
939
|
+
}
|
|
940
|
+
// Add measurement context with staleness (ADR-065 Phase 2)
|
|
941
|
+
output += '\n## Measurement Context\n\n';
|
|
942
|
+
if (result.status_measured_at) {
|
|
943
|
+
output += `- **Measured At:** ${new Date(result.status_measured_at).toLocaleString()}\n`;
|
|
944
|
+
}
|
|
945
|
+
const delta = result.vocabulary_changes_since_measurement ?? 0;
|
|
946
|
+
let stalenessText = '';
|
|
947
|
+
if (delta === 0) {
|
|
948
|
+
stalenessText = 'No changes since measurement (fresh)';
|
|
949
|
+
}
|
|
950
|
+
else if (delta < 5) {
|
|
951
|
+
stalenessText = `${delta} vocabulary change${delta > 1 ? 's' : ''} since measurement`;
|
|
952
|
+
}
|
|
953
|
+
else if (delta < 10) {
|
|
954
|
+
stalenessText = `${delta} vocabulary changes since measurement (consider re-measuring)`;
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
stalenessText = `${delta} vocabulary changes since measurement (re-measurement recommended)`;
|
|
958
|
+
}
|
|
959
|
+
output += `- **Staleness:** ${stalenessText}\n`;
|
|
960
|
+
output += `- **Note:** Results are temporal - rerun measurement as graph evolves\n`;
|
|
961
|
+
if (result.rationale) {
|
|
962
|
+
output += `\n## Classification Rationale\n\n${result.rationale}\n`;
|
|
963
|
+
}
|
|
964
|
+
output += '\n## Practical Implications\n\n';
|
|
965
|
+
if (status === 'WELL_GROUNDED') {
|
|
966
|
+
output += '**This relationship type is highly reliable.**\n';
|
|
967
|
+
output += '- Use in high-confidence reasoning chains\n';
|
|
968
|
+
output += '- Good candidate for automated inference\n';
|
|
969
|
+
output += '- Represents well-established domain knowledge\n';
|
|
970
|
+
}
|
|
971
|
+
else if (status === 'MIXED_GROUNDING') {
|
|
972
|
+
output += '**This relationship type has variable validation.**\n';
|
|
973
|
+
output += '- Represents mixed evidence or evolving understanding\n';
|
|
974
|
+
output += '- Explore both supporting and contradicting evidence\n';
|
|
975
|
+
output += '- Good for identifying knowledge gaps or areas of uncertainty\n';
|
|
976
|
+
}
|
|
977
|
+
else if (status === 'WEAK_GROUNDING') {
|
|
978
|
+
output += '**This relationship type has emerging evidence.**\n';
|
|
979
|
+
output += '- Weak positive grounding (0.0-0.15) indicates developing knowledge\n';
|
|
980
|
+
output += '- May strengthen with more document ingestion\n';
|
|
981
|
+
output += '- Use for exploratory reasoning, but verify claims\n';
|
|
982
|
+
}
|
|
983
|
+
else if (status === 'POORLY_GROUNDED') {
|
|
984
|
+
output += '**This relationship type has uncertain validation.**\n';
|
|
985
|
+
output += '- Weak negative grounding (-0.5-0.0) indicates unclear support\n';
|
|
986
|
+
output += '- May represent liminal or contested knowledge\n';
|
|
987
|
+
output += '- Use cautiously - verify before using in reasoning\n';
|
|
988
|
+
}
|
|
989
|
+
else if (status === 'CONTRADICTED') {
|
|
990
|
+
output += '**This relationship type is contradicted by evidence.**\n';
|
|
991
|
+
output += '- May represent refuted claims or historical misconceptions\n';
|
|
992
|
+
output += '- Use cautiously - validate before using in reasoning\n';
|
|
993
|
+
output += '- Useful for understanding evolution of knowledge\n';
|
|
994
|
+
}
|
|
995
|
+
else if (status === 'INSUFFICIENT_DATA') {
|
|
996
|
+
output += '**Not enough data to establish epistemic pattern.**\n';
|
|
997
|
+
output += '- Need more documents using this relationship type\n';
|
|
998
|
+
output += '- Current measurements: <3 successful samples\n';
|
|
999
|
+
output += '- Re-measure after ingesting more content\n';
|
|
1000
|
+
}
|
|
1001
|
+
return output;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Format epistemic status measurement results (ADR-065)
|
|
1005
|
+
*/
|
|
1006
|
+
function formatEpistemicStatusMeasurement(result) {
|
|
1007
|
+
let output = '# Epistemic Status Measurement Results\n\n';
|
|
1008
|
+
output += `**Measured:** ${result.total_types} vocabulary types\n`;
|
|
1009
|
+
output += `**Stored:** ${result.stored_count} types updated in database\n\n`;
|
|
1010
|
+
if (result.classifications && Object.keys(result.classifications).length > 0) {
|
|
1011
|
+
output += '## Classification Distribution\n\n';
|
|
1012
|
+
Object.entries(result.classifications)
|
|
1013
|
+
.sort((a, b) => b[1] - a[1]) // Sort by count descending
|
|
1014
|
+
.forEach(([status, count]) => {
|
|
1015
|
+
output += `- **${status}**: ${count}\n`;
|
|
1016
|
+
});
|
|
1017
|
+
output += '\n';
|
|
1018
|
+
}
|
|
1019
|
+
if (result.sample_results && result.sample_results.length > 0) {
|
|
1020
|
+
output += '## Sample Results (Top 10)\n\n';
|
|
1021
|
+
output += '| Type | Status | Avg Grounding | Interpretation |\n';
|
|
1022
|
+
output += '|------|--------|---------------|----------------|\n';
|
|
1023
|
+
result.sample_results.forEach((sample) => {
|
|
1024
|
+
const avgGrounding = sample.stats?.avg_grounding !== undefined
|
|
1025
|
+
? sample.stats.avg_grounding.toFixed(3)
|
|
1026
|
+
: '--';
|
|
1027
|
+
let interpretation = '';
|
|
1028
|
+
if (sample.epistemic_status === 'WELL_GROUNDED') {
|
|
1029
|
+
interpretation = '✓ Reliable';
|
|
1030
|
+
}
|
|
1031
|
+
else if (sample.epistemic_status === 'MIXED_GROUNDING') {
|
|
1032
|
+
interpretation = '⚠ Variable';
|
|
1033
|
+
}
|
|
1034
|
+
else if (sample.epistemic_status === 'WEAK_GROUNDING') {
|
|
1035
|
+
interpretation = '~ Emerging';
|
|
1036
|
+
}
|
|
1037
|
+
else if (sample.epistemic_status === 'POORLY_GROUNDED') {
|
|
1038
|
+
interpretation = '? Uncertain';
|
|
1039
|
+
}
|
|
1040
|
+
else if (sample.epistemic_status === 'CONTRADICTED') {
|
|
1041
|
+
interpretation = '✗ Refuted';
|
|
1042
|
+
}
|
|
1043
|
+
else if (sample.epistemic_status === 'INSUFFICIENT_DATA') {
|
|
1044
|
+
interpretation = '? Need data';
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
interpretation = '- Other';
|
|
1048
|
+
}
|
|
1049
|
+
output += `| ${sample.relationship_type} | ${sample.epistemic_status} | ${avgGrounding} | ${interpretation} |\n`;
|
|
1050
|
+
});
|
|
1051
|
+
output += '\n';
|
|
1052
|
+
}
|
|
1053
|
+
output += '## What This Means\n\n';
|
|
1054
|
+
output += 'Epistemic status measurement evaluates how well-established each vocabulary relationship type is based on:\n';
|
|
1055
|
+
output += '1. **Grounding strength** of target concepts (evidence support)\n';
|
|
1056
|
+
output += '2. **Consistency** across multiple samples (standard deviation)\n';
|
|
1057
|
+
output += '3. **Sample size** (measured vs total edges)\n\n';
|
|
1058
|
+
output += '**Key Insights:**\n';
|
|
1059
|
+
output += '- **WELL_GROUNDED types** represent well-established knowledge patterns (>80% net support)\n';
|
|
1060
|
+
output += '- **MIXED_GROUNDING types** show variable validation or mixed evidence (15-80% net support)\n';
|
|
1061
|
+
output += '- **WEAK_GROUNDING types** represent emerging knowledge (0-15% net support)\n';
|
|
1062
|
+
output += '- **POORLY_GROUNDED types** have uncertain validation (0-50% net contradiction)\n';
|
|
1063
|
+
output += '- **CONTRADICTED types** may represent refuted claims (>50% net contradiction)\n';
|
|
1064
|
+
output += '- **INSUFFICIENT_DATA types** need more document ingestion\n\n';
|
|
1065
|
+
output += '**Next Actions:**\n';
|
|
1066
|
+
output += '1. Review MIXED_GROUNDING types to identify knowledge gaps or dialectical patterns\n';
|
|
1067
|
+
output += '2. Use WELL_GROUNDED types for high-confidence reasoning and inference\n';
|
|
1068
|
+
output += '3. Investigate CONTRADICTED types to understand knowledge evolution\n';
|
|
1069
|
+
output += '4. Ingest more documents to move INSUFFICIENT_DATA types to measurable states\n';
|
|
1070
|
+
return output;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Format source search results as markdown (ADR-068 Phase 5)
|
|
1074
|
+
*
|
|
1075
|
+
* Optimized for MCP/AI consumption - shows matched chunks with offsets
|
|
1076
|
+
* and related concepts extracted from those sources.
|
|
1077
|
+
*/
|
|
1078
|
+
function formatSourceSearchResults(result) {
|
|
1079
|
+
let output = `# Source Search: "${result.query}"\n\n`;
|
|
1080
|
+
output += `Found ${result.count} source passage(s) (threshold: ${(result.threshold_used || 0.7) * 100}%)\n\n`;
|
|
1081
|
+
if (result.count === 0) {
|
|
1082
|
+
output += 'No source passages found matching this query.\n\n';
|
|
1083
|
+
output += '**Tips:**\n';
|
|
1084
|
+
output += '- Source search uses text embeddings, not concept embeddings\n';
|
|
1085
|
+
output += '- Try broader queries or lower similarity thresholds\n';
|
|
1086
|
+
output += '- Use concept search to find concepts, then view their evidence\n';
|
|
1087
|
+
return output;
|
|
1088
|
+
}
|
|
1089
|
+
result.results.forEach((source, i) => {
|
|
1090
|
+
output += `## ${i + 1}. ${source.document} (para ${source.paragraph})\n\n`;
|
|
1091
|
+
output += `- **Source ID:** ${source.source_id}\n`;
|
|
1092
|
+
output += `- **Similarity:** ${(source.similarity * 100).toFixed(1)}%\n`;
|
|
1093
|
+
if (source.is_stale) {
|
|
1094
|
+
output += `- **Status:** ⚠ Stale embedding (source text changed since embedding)\n`;
|
|
1095
|
+
}
|
|
1096
|
+
output += `\n**Matched Chunk** [offset ${source.matched_chunk.start_offset}:${source.matched_chunk.end_offset}]:\n\n`;
|
|
1097
|
+
output += `> ${source.matched_chunk.chunk_text}\n\n`;
|
|
1098
|
+
if (source.full_text) {
|
|
1099
|
+
const truncated = source.full_text.length > 300
|
|
1100
|
+
? source.full_text.substring(0, 300) + '...'
|
|
1101
|
+
: source.full_text;
|
|
1102
|
+
output += `**Full Context:**\n\n${truncated}\n\n`;
|
|
1103
|
+
}
|
|
1104
|
+
if (source.concepts && source.concepts.length > 0) {
|
|
1105
|
+
output += `**Concepts Extracted** (${source.concepts.length}):\n\n`;
|
|
1106
|
+
source.concepts.slice(0, 5).forEach(concept => {
|
|
1107
|
+
output += `- **${concept.label}** (${concept.concept_id})\n`;
|
|
1108
|
+
if (concept.description) {
|
|
1109
|
+
output += ` ${concept.description}\n`;
|
|
1110
|
+
}
|
|
1111
|
+
output += ` Evidence: "${concept.instance_quote}"\n`;
|
|
1112
|
+
});
|
|
1113
|
+
if (source.concepts.length > 5) {
|
|
1114
|
+
output += `\n... and ${source.concepts.length - 5} more concepts\n`;
|
|
1115
|
+
}
|
|
1116
|
+
output += '\n';
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
output += '**Next Steps:**\n';
|
|
1120
|
+
output += '- Use concept IDs with `concept` tool (action: "details") to explore further\n';
|
|
1121
|
+
output += '- Use `concept` tool (action: "connect") to find relationships between concepts\n';
|
|
1122
|
+
output += '- Adjust similarity threshold if results are too broad or too narrow\n';
|
|
1123
|
+
return output;
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Format polarity axis analysis results as markdown (ADR-070)
|
|
1127
|
+
*
|
|
1128
|
+
* Optimized for MCP/AI consumption - shows axis definition, projections,
|
|
1129
|
+
* statistics, and grounding correlation patterns.
|
|
1130
|
+
*/
|
|
1131
|
+
function formatPolarityAxisResults(result) {
|
|
1132
|
+
let output = `# Polarity Axis Analysis\n\n`;
|
|
1133
|
+
// Axis definition
|
|
1134
|
+
output += `## Polarity Axis: ${result.axis.positive_pole.label} ↔ ${result.axis.negative_pole.label}\n\n`;
|
|
1135
|
+
output += `**Positive Pole:** ${result.axis.positive_pole.label}\n`;
|
|
1136
|
+
output += ` Grounding: ${formatGroundingStrength(result.axis.positive_pole.grounding)}\n`;
|
|
1137
|
+
output += ` ID: ${result.axis.positive_pole.concept_id}\n\n`;
|
|
1138
|
+
output += `**Negative Pole:** ${result.axis.negative_pole.label}\n`;
|
|
1139
|
+
output += ` Grounding: ${formatGroundingStrength(result.axis.negative_pole.grounding)}\n`;
|
|
1140
|
+
output += ` ID: ${result.axis.negative_pole.concept_id}\n\n`;
|
|
1141
|
+
output += `**Axis Magnitude:** ${result.axis.magnitude.toFixed(4)}\n`;
|
|
1142
|
+
const qualityLabel = result.axis.axis_quality === 'strong'
|
|
1143
|
+
? '✓ Strong (poles are semantically distinct)'
|
|
1144
|
+
: '⚠ Weak (poles may be too similar)';
|
|
1145
|
+
output += `**Axis Quality:** ${qualityLabel}\n\n`;
|
|
1146
|
+
// Statistics
|
|
1147
|
+
output += `## Statistics\n\n`;
|
|
1148
|
+
output += `- **Total Concepts:** ${result.statistics.total_concepts}\n`;
|
|
1149
|
+
output += `- **Position Range:** [${result.statistics.position_range[0].toFixed(3)}, ${result.statistics.position_range[1].toFixed(3)}]\n`;
|
|
1150
|
+
output += `- **Mean Position:** ${result.statistics.mean_position.toFixed(3)} `;
|
|
1151
|
+
if (result.statistics.mean_position > 0.2) {
|
|
1152
|
+
output += '(skewed toward positive pole)\n';
|
|
1153
|
+
}
|
|
1154
|
+
else if (result.statistics.mean_position < -0.2) {
|
|
1155
|
+
output += '(skewed toward negative pole)\n';
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
output += '(balanced)\n';
|
|
1159
|
+
}
|
|
1160
|
+
output += `- **Mean Axis Distance:** ${result.statistics.mean_axis_distance.toFixed(3)} (orthogonal spread)\n\n`;
|
|
1161
|
+
// Direction distribution
|
|
1162
|
+
output += `**Direction Distribution:**\n`;
|
|
1163
|
+
output += `- Positive (>0.3): ${result.statistics.direction_distribution.positive} concepts\n`;
|
|
1164
|
+
output += `- Neutral (-0.3 to 0.3): ${result.statistics.direction_distribution.neutral} concepts\n`;
|
|
1165
|
+
output += `- Negative (<-0.3): ${result.statistics.direction_distribution.negative} concepts\n\n`;
|
|
1166
|
+
// Grounding correlation
|
|
1167
|
+
output += `## Grounding Correlation\n\n`;
|
|
1168
|
+
output += `**Pearson r:** ${result.grounding_correlation.pearson_r.toFixed(3)}\n`;
|
|
1169
|
+
output += `**p-value:** ${result.grounding_correlation.p_value.toFixed(4)}\n`;
|
|
1170
|
+
output += `**Interpretation:** ${result.grounding_correlation.interpretation}\n\n`;
|
|
1171
|
+
// Add practical interpretation
|
|
1172
|
+
const r = result.grounding_correlation.pearson_r;
|
|
1173
|
+
if (Math.abs(r) < 0.1) {
|
|
1174
|
+
output += `→ No correlation: Position and grounding are independent\n\n`;
|
|
1175
|
+
}
|
|
1176
|
+
else if (r > 0.3) {
|
|
1177
|
+
output += `→ Positive correlation: Concepts near positive pole tend to have higher grounding\n\n`;
|
|
1178
|
+
}
|
|
1179
|
+
else if (r < -0.3) {
|
|
1180
|
+
output += `→ Negative correlation: Concepts near negative pole tend to have higher grounding\n\n`;
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
output += `→ Weak correlation: Position and grounding are loosely related\n\n`;
|
|
1184
|
+
}
|
|
1185
|
+
// Projections (top concepts for each direction)
|
|
1186
|
+
if (result.projections && result.projections.length > 0) {
|
|
1187
|
+
output += `## Concept Projections (${result.projections.length} total)\n\n`;
|
|
1188
|
+
// Sort by position
|
|
1189
|
+
const sorted = [...result.projections].sort((a, b) => b.position - a.position);
|
|
1190
|
+
// Show top 5 positive
|
|
1191
|
+
const positive = sorted.filter(p => p.direction === 'positive').slice(0, 5);
|
|
1192
|
+
if (positive.length > 0) {
|
|
1193
|
+
output += `### Positive Direction (toward ${result.axis.positive_pole.label})\n\n`;
|
|
1194
|
+
positive.forEach((proj, i) => {
|
|
1195
|
+
output += `${i + 1}. **${proj.label}**\n`;
|
|
1196
|
+
output += ` Position: ${proj.position.toFixed(3)} | `;
|
|
1197
|
+
output += `Grounding: ${formatGroundingStrength(proj.grounding)} | `;
|
|
1198
|
+
output += `Axis distance: ${proj.axis_distance.toFixed(4)}\n`;
|
|
1199
|
+
output += ` ID: ${proj.concept_id}\n`;
|
|
1200
|
+
});
|
|
1201
|
+
output += '\n';
|
|
1202
|
+
}
|
|
1203
|
+
// Show neutral concepts (if any)
|
|
1204
|
+
const neutral = sorted.filter(p => p.direction === 'neutral').slice(0, 3);
|
|
1205
|
+
if (neutral.length > 0) {
|
|
1206
|
+
output += `### Neutral (balanced between poles)\n\n`;
|
|
1207
|
+
neutral.forEach((proj, i) => {
|
|
1208
|
+
output += `${i + 1}. **${proj.label}**\n`;
|
|
1209
|
+
output += ` Position: ${proj.position.toFixed(3)} | `;
|
|
1210
|
+
output += `Grounding: ${formatGroundingStrength(proj.grounding)} | `;
|
|
1211
|
+
output += `Axis distance: ${proj.axis_distance.toFixed(4)}\n`;
|
|
1212
|
+
output += ` ID: ${proj.concept_id}\n`;
|
|
1213
|
+
});
|
|
1214
|
+
output += '\n';
|
|
1215
|
+
}
|
|
1216
|
+
// Show top 5 negative
|
|
1217
|
+
const negative = sorted.filter(p => p.direction === 'negative').slice(-5).reverse();
|
|
1218
|
+
if (negative.length > 0) {
|
|
1219
|
+
output += `### Negative Direction (toward ${result.axis.negative_pole.label})\n\n`;
|
|
1220
|
+
negative.forEach((proj, i) => {
|
|
1221
|
+
output += `${i + 1}. **${proj.label}**\n`;
|
|
1222
|
+
output += ` Position: ${proj.position.toFixed(3)} | `;
|
|
1223
|
+
output += `Grounding: ${formatGroundingStrength(proj.grounding)} | `;
|
|
1224
|
+
output += `Axis distance: ${proj.axis_distance.toFixed(4)}\n`;
|
|
1225
|
+
output += ` ID: ${proj.concept_id}\n`;
|
|
1226
|
+
});
|
|
1227
|
+
output += '\n';
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
output += `## How to Use This Analysis\n\n`;
|
|
1231
|
+
output += `**Understanding Positions:**\n`;
|
|
1232
|
+
output += `- Position closer to +1.0 → More aligned with "${result.axis.positive_pole.label}"\n`;
|
|
1233
|
+
output += `- Position closer to -1.0 → More aligned with "${result.axis.negative_pole.label}"\n`;
|
|
1234
|
+
output += `- Position near 0.0 → Balanced or orthogonal to this dimension\n\n`;
|
|
1235
|
+
output += `**Axis Distance (orthogonality):**\n`;
|
|
1236
|
+
output += `- Low distance → Concept lies close to the axis (well-explained by this dimension)\n`;
|
|
1237
|
+
output += `- High distance → Concept is orthogonal (other dimensions more relevant)\n\n`;
|
|
1238
|
+
output += `**Next Steps:**\n`;
|
|
1239
|
+
output += `- Use concept IDs with \`concept\` tool (action: "details") to explore individual concepts\n`;
|
|
1240
|
+
output += `- Use \`concept\` tool (action: "connect") to find paths between concepts on the axis\n`;
|
|
1241
|
+
output += `- Try different pole pairs to explore other semantic dimensions\n`;
|
|
1242
|
+
output += `- Compare grounding patterns across positions to identify reliability trends\n`;
|
|
1243
|
+
return output;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Format document search results (ADR-084)
|
|
1247
|
+
*/
|
|
1248
|
+
function formatDocumentSearchResults(result) {
|
|
1249
|
+
let output = `# Document Search: "${result.query || 'documents'}"\n\n`;
|
|
1250
|
+
output += `Found ${result.total_matches} document(s)\n\n`;
|
|
1251
|
+
if (result.documents.length === 0) {
|
|
1252
|
+
output += 'No documents found matching this query.\n';
|
|
1253
|
+
return output;
|
|
1254
|
+
}
|
|
1255
|
+
result.documents.forEach((doc, i) => {
|
|
1256
|
+
output += `## ${i + 1}. ${doc.filename}\n\n`;
|
|
1257
|
+
output += `Document ID: ${doc.document_id}\n`;
|
|
1258
|
+
output += `Ontology: ${doc.ontology}\n`;
|
|
1259
|
+
output += `Similarity: ${(doc.best_similarity * 100).toFixed(1)}%\n`;
|
|
1260
|
+
output += `Source Chunks: ${doc.source_count}\n`;
|
|
1261
|
+
if (doc.concept_ids && doc.concept_ids.length > 0) {
|
|
1262
|
+
const conceptPreview = doc.concept_ids.slice(0, 5).join(', ');
|
|
1263
|
+
const more = doc.concept_ids.length > 5 ? ` (+${doc.concept_ids.length - 5} more)` : '';
|
|
1264
|
+
output += `Concepts: ${conceptPreview}${more}\n`;
|
|
1265
|
+
}
|
|
1266
|
+
output += '\n';
|
|
1267
|
+
});
|
|
1268
|
+
// Usage hints
|
|
1269
|
+
output += '## Next Steps\n\n';
|
|
1270
|
+
output += '- Use `document` tool with action "show" to view document content\n';
|
|
1271
|
+
output += '- Use `document` tool with action "concepts" to see all extracted concepts\n';
|
|
1272
|
+
output += '- Use `concept` tool to explore individual concepts in detail\n';
|
|
1273
|
+
return output;
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Format document list (ADR-084)
|
|
1277
|
+
*/
|
|
1278
|
+
function formatDocumentList(result) {
|
|
1279
|
+
let output = `# Documents\n\n`;
|
|
1280
|
+
output += `Showing ${result.documents.length} of ${result.total} documents\n\n`;
|
|
1281
|
+
if (result.documents.length === 0) {
|
|
1282
|
+
output += 'No documents found.\n';
|
|
1283
|
+
return output;
|
|
1284
|
+
}
|
|
1285
|
+
result.documents.forEach((doc, i) => {
|
|
1286
|
+
output += `${i + 1}. **${doc.filename}**\n`;
|
|
1287
|
+
output += ` - ID: ${doc.document_id.substring(0, 50)}...\n`;
|
|
1288
|
+
output += ` - Ontology: ${doc.ontology}\n`;
|
|
1289
|
+
output += ` - Type: ${doc.content_type || 'document'}\n`;
|
|
1290
|
+
output += ` - Sources: ${doc.source_count}, Concepts: ${doc.concept_count}\n`;
|
|
1291
|
+
output += '\n';
|
|
1292
|
+
});
|
|
1293
|
+
if (result.total > result.documents.length) {
|
|
1294
|
+
output += `Use offset=${result.offset + result.documents.length} to see more.\n`;
|
|
1295
|
+
}
|
|
1296
|
+
return output;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Format document content (ADR-084)
|
|
1300
|
+
*/
|
|
1301
|
+
function formatDocumentContent(result) {
|
|
1302
|
+
let output = `# Document Content\n\n`;
|
|
1303
|
+
output += `Type: ${result.content_type}\n`;
|
|
1304
|
+
output += `Chunks: ${result.chunks.length}\n\n`;
|
|
1305
|
+
if (result.content_type === 'image') {
|
|
1306
|
+
if (result.content.prose) {
|
|
1307
|
+
output += '## Image Description\n\n';
|
|
1308
|
+
output += result.content.prose + '\n\n';
|
|
1309
|
+
}
|
|
1310
|
+
if (result.content.image) {
|
|
1311
|
+
output += `[Image data: ${result.content.image.length} bytes base64]\n`;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
else {
|
|
1315
|
+
if (result.content.document) {
|
|
1316
|
+
output += '## Content\n\n';
|
|
1317
|
+
output += result.content.document + '\n';
|
|
1318
|
+
}
|
|
1319
|
+
else if (result.content.error) {
|
|
1320
|
+
output += `Error: ${result.content.error}\n`;
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
output += 'No content available.\n';
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return output;
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Format document concepts (ADR-084)
|
|
1330
|
+
*/
|
|
1331
|
+
function formatDocumentConcepts(result) {
|
|
1332
|
+
let output = `# Concepts: ${result.filename}\n\n`;
|
|
1333
|
+
output += `Document: ${result.document_id}\n`;
|
|
1334
|
+
output += `Total: ${result.total} concept(s)\n\n`;
|
|
1335
|
+
if (result.concepts.length === 0) {
|
|
1336
|
+
output += 'No concepts found for this document.\n';
|
|
1337
|
+
return output;
|
|
1338
|
+
}
|
|
1339
|
+
result.concepts.forEach((concept, i) => {
|
|
1340
|
+
output += `${i + 1}. **${concept.name}**\n`;
|
|
1341
|
+
output += ` - ID: ${concept.concept_id}\n`;
|
|
1342
|
+
output += ` - Source: ${concept.source_id}\n`;
|
|
1343
|
+
output += ` - Instances: ${concept.instance_count}\n`;
|
|
1344
|
+
output += '\n';
|
|
1345
|
+
});
|
|
1346
|
+
// Usage hints
|
|
1347
|
+
output += '## Next Steps\n\n';
|
|
1348
|
+
output += '- Use `concept` tool with action "details" for full concept information\n';
|
|
1349
|
+
output += '- Use `concept` tool with action "connect" to find relationships between concepts\n';
|
|
1350
|
+
output += '- Or use `include_details: true` to get full details in one call\n';
|
|
1351
|
+
return output;
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Format document concepts with full details (ADR-084)
|
|
1355
|
+
* Used when include_details=true - fetches all concept info in one call
|
|
1356
|
+
*/
|
|
1357
|
+
function formatDocumentConceptsDetailed(docResult, conceptDetails) {
|
|
1358
|
+
let output = `# Concepts: ${docResult.filename}\n\n`;
|
|
1359
|
+
output += `Document: ${docResult.document_id}\n`;
|
|
1360
|
+
output += `Total: ${conceptDetails.length} unique concept(s)\n\n`;
|
|
1361
|
+
if (conceptDetails.length === 0) {
|
|
1362
|
+
output += 'No concepts found for this document.\n';
|
|
1363
|
+
return output;
|
|
1364
|
+
}
|
|
1365
|
+
conceptDetails.forEach((concept, i) => {
|
|
1366
|
+
if (concept.error) {
|
|
1367
|
+
output += `## ${i + 1}. ${concept.label}\n\n`;
|
|
1368
|
+
output += `ID: ${concept.concept_id}\n`;
|
|
1369
|
+
output += `Status: Failed to load\n\n`;
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
output += `## ${i + 1}. ${concept.label}\n\n`;
|
|
1373
|
+
if (concept.description) {
|
|
1374
|
+
output += `${concept.description}\n\n`;
|
|
1375
|
+
}
|
|
1376
|
+
output += `ID: ${concept.concept_id}\n`;
|
|
1377
|
+
output += `Documents: ${concept.documents?.join(', ') || 'Unknown'}\n`;
|
|
1378
|
+
output += `Evidence: ${concept.instances?.length || 0} instances\n`;
|
|
1379
|
+
// Grounding with confidence
|
|
1380
|
+
if (concept.grounding_strength !== undefined || concept.grounding_display) {
|
|
1381
|
+
const grounding = concept.grounding_display || formatGroundingStrength(concept.grounding_strength);
|
|
1382
|
+
output += `Grounding: ${grounding}\n`;
|
|
1383
|
+
}
|
|
1384
|
+
// Sample evidence (max 2)
|
|
1385
|
+
if (concept.instances && concept.instances.length > 0) {
|
|
1386
|
+
output += `\n### Evidence Samples\n\n`;
|
|
1387
|
+
concept.instances.slice(0, 2).forEach((inst, idx) => {
|
|
1388
|
+
const truncated = inst.quote.length > 120 ? inst.quote.substring(0, 120) + '...' : inst.quote;
|
|
1389
|
+
output += `${idx + 1}. ${inst.document} (para ${inst.paragraph}):\n`;
|
|
1390
|
+
output += ` "${truncated}"\n`;
|
|
1391
|
+
});
|
|
1392
|
+
if (concept.instances.length > 2) {
|
|
1393
|
+
output += ` ... and ${concept.instances.length - 2} more\n`;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
// Relationships (max 5)
|
|
1397
|
+
if (concept.relationships && concept.relationships.length > 0) {
|
|
1398
|
+
output += `\n### Relationships (${concept.relationships.length})\n\n`;
|
|
1399
|
+
concept.relationships.slice(0, 5).forEach((rel) => {
|
|
1400
|
+
const confidence = rel.confidence ? ` (${(rel.confidence * 100).toFixed(0)}%)` : '';
|
|
1401
|
+
output += `${rel.rel_type} -> ${rel.to_label}${confidence}\n`;
|
|
1402
|
+
});
|
|
1403
|
+
if (concept.relationships.length > 5) {
|
|
1404
|
+
output += `... and ${concept.relationships.length - 5} more\n`;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
output += '\n---\n\n';
|
|
1408
|
+
});
|
|
1409
|
+
return output;
|
|
1410
|
+
}
|
|
1411
|
+
//# sourceMappingURL=formatters.js.map
|