@llm-dev-ops/agentics-cli 1.8.2 → 1.9.0

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.
@@ -331,13 +331,17 @@ function estimateFinancialsFromQuery(query) {
331
331
  savingsMultiplier = 50;
332
332
  industryLabel = 'professional services';
333
333
  }
334
- const pilotCost = Math.round(employees * costPerEmployee / 1000) * 1000;
335
- const annualSavings = Math.round(employees * savingsMultiplier / 1000) * 1000;
334
+ // ── ADR-PIPELINE-038: Extract the operational unit from the query ──
335
+ // Instead of always using "employees", find what the system actually manages:
336
+ // vehicles, suppliers, patients, locations, transactions, etc.
337
+ const unitInfo = extractOperationalUnit(query, lower, employees);
338
+ const pilotCost = Math.round(unitInfo.count * costPerEmployee / 1000) * 1000;
339
+ const annualSavings = Math.round(unitInfo.count * savingsMultiplier / 1000) * 1000;
336
340
  const paybackMonths = Math.max(6, Math.min(24, Math.round(pilotCost / (annualSavings / 12))));
337
341
  const npv5yr = Math.round(annualSavings * 3.5);
338
342
  const roiPct = Math.round((annualSavings / pilotCost) * 100);
339
- const methodology = `${employees.toLocaleString()} employees × $${costPerEmployee}/employee ${industryLabel} benchmark`;
340
- const savingsMethod = `${employees.toLocaleString()} employees × $${savingsMultiplier}/employee annual efficiency gain`;
343
+ const methodology = `${unitInfo.count.toLocaleString()} ${unitInfo.label} × $${costPerEmployee}/${unitInfo.singular} ${industryLabel} benchmark`;
344
+ const savingsMethod = `${unitInfo.count.toLocaleString()} ${unitInfo.label} × $${savingsMultiplier}/${unitInfo.singular} annual efficiency gain`;
341
345
  return {
342
346
  budget: `${fmt(pilotCost)} - ${fmt(pilotCost * 1.5)} (${methodology})`,
343
347
  roi: `${roiPct}% projected (${fmt(annualSavings)} savings ÷ ${fmt(pilotCost)} investment)`,
@@ -349,6 +353,40 @@ function estimateFinancialsFromQuery(query) {
349
353
  isEstimated: true,
350
354
  };
351
355
  }
356
+ /**
357
+ * Extract the primary operational unit from the query.
358
+ * Instead of always defaulting to "employees", find what the system actually operates on.
359
+ * Returns the unit count, label (plural), and singular form.
360
+ */
361
+ function extractOperationalUnit(query, lower, employeeCount) {
362
+ // Try to find an explicit count + unit in the query
363
+ const unitPatterns = [
364
+ { pattern: /(\d[\d,]*)\s*(?:vehicles?|trucks?|vans?|cars?)/i, label: 'vehicles', singular: 'vehicle', fallbackRatio: 0.05 },
365
+ { pattern: /(\d[\d,]*)\s*(?:suppliers?|vendors?)/i, label: 'suppliers', singular: 'supplier', fallbackRatio: 0.013 },
366
+ { pattern: /(\d[\d,]*)\s*(?:patients?|beds?)/i, label: 'patients', singular: 'patient', fallbackRatio: 5 },
367
+ { pattern: /(\d[\d,]*)\s*(?:locations?|sites?|offices?|stores?|branches?)/i, label: 'locations', singular: 'location', fallbackRatio: 0.005 },
368
+ { pattern: /(\d[\d,]*)\s*(?:assets?|machines?|devices?)/i, label: 'assets', singular: 'asset', fallbackRatio: 0.1 },
369
+ { pattern: /(\d[\d,]*)\s*(?:products?|skus?|items?)/i, label: 'products', singular: 'product', fallbackRatio: 0.5 },
370
+ { pattern: /(\d[\d,]*)\s*(?:customers?|accounts?|clients?)/i, label: 'customers', singular: 'customer', fallbackRatio: 10 },
371
+ { pattern: /(\d[\d,]*)\s*(?:shipments?|orders?|transactions?)/i, label: 'transactions', singular: 'transaction', fallbackRatio: 50 },
372
+ { pattern: /(\d[\d,]*)\s*(?:contracts?|agreements?)/i, label: 'contracts', singular: 'contract', fallbackRatio: 0.02 },
373
+ ];
374
+ for (const { pattern, label, singular, fallbackRatio } of unitPatterns) {
375
+ const match = query.match(pattern);
376
+ if (match?.[1]) {
377
+ const count = parseInt(match[1].replace(/,/g, ''), 10);
378
+ if (count > 0)
379
+ return { count, label, singular };
380
+ }
381
+ // If the unit type is mentioned but without a number, estimate from employee count
382
+ if (new RegExp(`\\b${label.replace(/s$/, '')}`, 'i').test(lower)) {
383
+ const estimated = Math.max(10, Math.round(employeeCount * fallbackRatio));
384
+ return { count: estimated, label, singular };
385
+ }
386
+ }
387
+ // Default: use employees (the original behavior)
388
+ return { count: employeeCount, label: 'employees', singular: 'employee' };
389
+ }
352
390
  /**
353
391
  * ADR-PIPELINE-029 §2: Domain-specific bottom-up financial models.
354
392
  * These use query signals to build unit-economics-based estimates
@@ -794,6 +832,26 @@ function generateDomainRisks(query, extracted) {
794
832
  mitigation: 'Include competitive benchmarking in board reporting; set public electrification targets aligned with or exceeding peer commitments; communicate transition progress to investors and customers',
795
833
  });
796
834
  }
835
+ // ── Generic domain-contextual risks for ANY domain not covered above ──
836
+ // Extract the core domain noun from the query and generate risks referencing it.
837
+ // This ensures every domain gets contextual risks, not just hardcoded ones.
838
+ if (risks.length < 5) {
839
+ const domainNoun = extractCoreDomainNoun(q);
840
+ if (domainNoun && !risks.some(r => r.risk.toLowerCase().includes(domainNoun.toLowerCase()))) {
841
+ risks.push({
842
+ risk: `${domainNoun} data may be incomplete, inconsistent, or outdated — analysis quality depends directly on input data quality from ${primarySystem}`,
843
+ category: 'Data', likelihood: 'Medium', impact: 'High', score: 6,
844
+ mitigation: `Data profiling and quality assessment during discovery phase; define minimum data completeness thresholds for ${domainNoun.toLowerCase()} analysis`,
845
+ });
846
+ }
847
+ if (risks.length < 5) {
848
+ risks.push({
849
+ risk: `Stakeholder alignment on ${domainNoun.toLowerCase()} priorities — different departments may have conflicting objectives for how to act on AI-generated insights`,
850
+ category: 'Organizational', likelihood: 'Medium', impact: 'Medium', score: 4,
851
+ mitigation: 'Cross-functional steering committee with clear decision authority; pilot scope agreed by all stakeholders before launch',
852
+ });
853
+ }
854
+ }
797
855
  // Always include vendor dependency
798
856
  risks.push({
799
857
  risk: 'Technology vendor dependency — platform availability, pricing changes, or API deprecation',
@@ -802,6 +860,98 @@ function generateDomainRisks(query, extracted) {
802
860
  });
803
861
  return risks.sort((a, b) => b.score - a.score);
804
862
  }
863
+ /**
864
+ * Extract the core domain noun from a query — "fleet electrification", "packaging waste",
865
+ * "water loss", "employee sustainability", etc. Used to generate contextual risks and
866
+ * financial language for domains not explicitly covered by hardcoded patterns.
867
+ */
868
+ function extractCoreDomainNoun(query) {
869
+ // Try structured patterns first
870
+ const patterns = [
871
+ /(?:reduce|optimize|improve|manage|analyze|monitor)\s+([\w\s]{3,30}?)(?:\.|,|$|\s+(?:across|from|in|for|by|through|using|while|without))/i,
872
+ /(?:challenge.*?is)\s+([\w\s]{3,30}?)(?:\.|,|$)/i,
873
+ /(?:help\s+(?:us|the|our)\s+(?:better\s+)?(?:understand|manage|reduce|optimize|plan|improve))\s+([\w\s]{3,30}?)(?:\.|,|$)/i,
874
+ /(?:AI could help.*?)\s+([\w\s]{5,30}?)(?:\.|,|$)/i,
875
+ ];
876
+ for (const pat of patterns) {
877
+ const m = query.match(pat);
878
+ if (m?.[1]) {
879
+ const noun = m[1].trim().replace(/^(the|a|an|our|their)\s+/i, '');
880
+ if (noun.length > 3 && noun.length < 40)
881
+ return noun.charAt(0).toUpperCase() + noun.slice(1);
882
+ }
883
+ }
884
+ // Fallback: extract the most specific noun phrase from the first 200 chars
885
+ const firstPart = query.slice(0, 200).toLowerCase();
886
+ const domainKeywords = firstPart.match(/\b(fleet|packaging|water|emission|procurement|maintenance|inventory|compliance|quality|safety|logistics|delivery|supply chain|workforce|clinical|financial|asset)\s+\w+/i);
887
+ if (domainKeywords?.[0])
888
+ return domainKeywords[0].charAt(0).toUpperCase() + domainKeywords[0].slice(1);
889
+ return 'operational process';
890
+ }
891
+ // ============================================================================
892
+ // ADR-PIPELINE-040: LLM Synthesis for Executive Documents
893
+ // ============================================================================
894
+ const LLM_SYNTHESIS_PROMPT = `You are a senior partner at McKinsey & Company rewriting an enterprise decision document.
895
+
896
+ Rules:
897
+ - Lead with the recommendation and quantified impact (Pyramid Principle)
898
+ - Every financial figure MUST show its derivation (unit × rate × volume = total)
899
+ - Frame "do nothing" as a quantified business risk with competitive and regulatory context
900
+ - Reference specific data from the analysis, never generic benchmarks
901
+ - Ensure timeline consistency across all sections
902
+ - Name specific stakeholder ROLES (VP Operations, Fleet Director, CFO), never "Workforce" or "Team"
903
+ - Include regulatory context relevant to the domain (EU standards, EPA requirements, industry mandates)
904
+ - Include competitive context (what peers/competitors are doing in this space)
905
+ - No sentence fragments, no prompt echo, no template placeholders, no "to be determined" phrases
906
+ - Every section must add insight, not just arrange data
907
+
908
+ Rewrite the document below to be credible in a boardroom at a Fortune 500 company.`;
909
+ /**
910
+ * ADR-PIPELINE-040: Attempt LLM synthesis of an executive document.
911
+ * Takes the template-generated skeleton and rewrites it into consulting-grade prose.
912
+ * Falls back to the skeleton unchanged if LLM is not available.
913
+ */
914
+ export async function synthesizeExecutiveDocument(skeleton, query, documentType) {
915
+ // Try Claude binary first (works without API key, uses user's subscription)
916
+ try {
917
+ const { execSync } = await import('node:child_process');
918
+ const claudeBin = process.env['AGENTICS_CLAUDE_BIN'] || 'claude';
919
+ const input = `${LLM_SYNTHESIS_PROMPT}\n\nDocument type: ${documentType}\n\nOriginal business context:\n${query.slice(0, 1000)}\n\nDocument to rewrite:\n${skeleton}`;
920
+ const result = execSync(`${claudeBin} --print -`, { input, encoding: 'utf-8', timeout: 60_000, maxBuffer: 500_000, stdio: ['pipe', 'pipe', 'pipe'] });
921
+ if (result && result.trim().length > skeleton.length * 0.5) {
922
+ process.stderr.write(`[exec-docs] LLM synthesis succeeded for ${documentType} (${result.length} chars)\n`);
923
+ return result.trim();
924
+ }
925
+ }
926
+ catch {
927
+ // Claude binary not available — try API
928
+ }
929
+ // Try Anthropic API
930
+ const apiKey = process.env['ANTHROPIC_API_KEY'];
931
+ if (apiKey) {
932
+ try {
933
+ const { execSync } = await import('node:child_process');
934
+ const payload = JSON.stringify({
935
+ model: process.env['AGENTICS_CLAUDE_MODEL'] || 'claude-sonnet-4-20250514',
936
+ max_tokens: 4096,
937
+ messages: [{ role: 'user', content: `${LLM_SYNTHESIS_PROMPT}\n\nDocument type: ${documentType}\n\nBusiness context:\n${query.slice(0, 1000)}\n\nDocument:\n${skeleton}` }],
938
+ });
939
+ const result = execSync(`curl -s -X POST https://api.anthropic.com/v1/messages -H "content-type: application/json" -H "x-api-key: ${apiKey}" -H "anthropic-version: 2023-06-01" -d @-`, { input: payload, encoding: 'utf-8', timeout: 60_000, maxBuffer: 500_000, stdio: ['pipe', 'pipe', 'pipe'] });
940
+ const parsed = JSON.parse(result);
941
+ const text = parsed?.content?.[0]?.text;
942
+ if (text && text.length > skeleton.length * 0.5) {
943
+ process.stderr.write(`[exec-docs] API synthesis succeeded for ${documentType} (${text.length} chars)\n`);
944
+ return text;
945
+ }
946
+ }
947
+ catch {
948
+ // API call failed — fall through to skeleton
949
+ }
950
+ }
951
+ // Tier 3: return skeleton unchanged
952
+ process.stderr.write(`[exec-docs] LLM synthesis unavailable for ${documentType} — using template skeleton\n`);
953
+ return skeleton;
954
+ }
805
955
  // ============================================================================
806
956
  // Executive Summary Renderer (ADR-PIPELINE-024: Pyramid Principle)
807
957
  // ============================================================================
@@ -927,6 +1077,21 @@ export function renderExecutiveSummary(query, simulationResult, platformResults)
927
1077
  lines.push(step);
928
1078
  }
929
1079
  lines.push('');
1080
+ // ADR-PIPELINE-041: Consensus signal disclosure
1081
+ const consensusAgent = platformResults.find(r => r.domain === 'analytics-hub' && r.agent === 'consensus');
1082
+ if (consensusAgent) {
1083
+ const consensusData = extractSignalPayload(consensusAgent.response).data ?? {};
1084
+ const achieved = consensusData['consensusAchieved'] ?? consensusData['consensus_achieved'];
1085
+ const confidence = Number(consensusData['confidence'] ?? consensusData['overallConfidence'] ?? 0);
1086
+ if (achieved === false || confidence < 0.6) {
1087
+ lines.push('## Analysis Confidence Note', '');
1088
+ lines.push(`The multi-agent consensus process achieved **${Math.round(confidence * 100)}% confidence**. ` +
1089
+ `This indicates divergent signals across analytical perspectives. ` +
1090
+ `The recommendation accounts for this uncertainty by proposing a scoped pilot ` +
1091
+ `rather than full commitment, allowing validation before broader investment.`);
1092
+ lines.push('');
1093
+ }
1094
+ }
930
1095
  // Provenance — ADR-PIPELINE-033: include simulation lineage for decision traceability
931
1096
  const sources = platformResults.filter(r => r.status >= 200 && r.status < 300).map(r => `${r.domain}/${r.agent}`);
932
1097
  lines.push('---', '');