@llm-dev-ops/agentics-cli 1.6.7 → 1.6.8

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.
@@ -133,17 +133,188 @@ function extractSuccessProbability(simData) {
133
133
  // Enterprise projects always carry residual risk, and showing 100% destroys credibility.
134
134
  return Math.min(prob, 0.88);
135
135
  }
136
- function extractFinancials(simData, platformResults) {
136
+ /**
137
+ * ADR-PIPELINE-026: Synthesize financial data from ALL sources:
138
+ * 1. Simulation engine result (simData)
139
+ * 2. Agent results (planner, what-if, scenario, decision, costops)
140
+ * 3. Query-based estimates as last resort
141
+ * Documents should NEVER say "To be quantified".
142
+ */
143
+ export function synthesizeFinancials(simData, platformResults, query) {
137
144
  const fm = (safeGet(simData, 'financial_model') ?? safeGet(simData, 'cost_model') ?? safeGet(simData, 'financials'));
138
- const roiAgent = extractAgentData(platformResults, 'costops', 'roi');
139
- const forecastAgent = extractAgentData(platformResults, 'costops', 'forecast');
140
- const budget = safeString(simData, 'budget') || safeString(simData, 'investment') || safeString(simData, 'total_investment') || (fm ? safeString(fm, 'budget') || safeString(fm, 'total_investment') : '') || (roiAgent ? safeString(roiAgent, 'total_cost') || safeString(roiAgent, 'estimated_cost') : '');
141
- const roi = safeString(simData, 'roi') || safeString(simData, 'expected_roi') || (fm ? safeString(fm, 'roi') : '') || (roiAgent ? safeString(roiAgent, 'roi') || safeString(roiAgent, 'expected_roi') : '');
142
- const npv = safeString(simData, 'npv') || safeString(simData, 'net_present_value') || (fm ? safeString(fm, 'npv') || safeString(fm, 'net_present_value') : '') || (forecastAgent ? safeString(forecastAgent, 'npv') : '');
143
- const payback = safeString(simData, 'payback_period') || safeString(simData, 'payback') || safeString(simData, 'roi_timeline') || (fm ? safeString(fm, 'payback_period') || safeString(fm, 'payback') : '') || (roiAgent ? safeString(roiAgent, 'payback_period') || safeString(roiAgent, 'payback') : '');
144
- const revenue = safeString(simData, 'projected_revenue') || safeString(simData, 'revenue_impact') || safeString(simData, 'annual_value') || (fm ? safeString(fm, 'projected_revenue') : '');
145
- const costSavings = safeString(simData, 'cost_savings') || safeString(simData, 'cost_reduction') || safeString(simData, 'savings') || (fm ? safeString(fm, 'cost_savings') : '');
146
- return { budget, roi, npv, payback, revenue, costSavings, hasData: !!(budget || roi || npv || payback || revenue || costSavings) };
145
+ // Collect from all possible agent sources — not just costops
146
+ const financialFields = {};
147
+ const financialKeys = [
148
+ ['budget', 'budget', 'investment', 'total_investment', 'allocated_budget', 'total_budget', 'estimated_cost', 'total_cost', 'project_cost'],
149
+ ['roi', 'roi', 'expected_roi', 'return_on_investment', 'projected_roi'],
150
+ ['npv', 'npv', 'net_present_value', '5_year_npv', 'five_year_npv'],
151
+ ['payback', 'payback_period', 'payback', 'roi_timeline', 'break_even'],
152
+ ['revenue', 'projected_revenue', 'revenue_impact', 'annual_value', 'annual_savings', 'savings'],
153
+ ['costSavings', 'cost_savings', 'cost_reduction', 'savings', 'efficiency_gain'],
154
+ ];
155
+ // Source 1: simData direct fields
156
+ for (const [key, ...aliases] of financialKeys) {
157
+ if (!key)
158
+ continue;
159
+ for (const alias of aliases) {
160
+ const val = safeString(simData, alias);
161
+ if (val && !financialFields[key]) {
162
+ financialFields[key] = val;
163
+ break;
164
+ }
165
+ }
166
+ // Source 1b: nested financial_model
167
+ if (!financialFields[key] && fm) {
168
+ for (const alias of aliases) {
169
+ const val = safeString(fm, alias);
170
+ if (val && !financialFields[key]) {
171
+ financialFields[key] = val;
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ }
177
+ // Source 2: ALL agent results — scan every successful agent for financial fields
178
+ for (const agent of platformResults) {
179
+ if (agent.status < 200 || agent.status >= 300)
180
+ continue;
181
+ const data = extractSignalPayload(agent.response).data;
182
+ if (!data || typeof data !== 'object')
183
+ continue;
184
+ for (const [key, ...aliases] of financialKeys) {
185
+ if (!key || financialFields[key])
186
+ continue;
187
+ for (const alias of aliases) {
188
+ const val = safeString(data, alias);
189
+ if (val) {
190
+ financialFields[key] = val;
191
+ break;
192
+ }
193
+ }
194
+ // Check nested data.financial_model, data.result, data.output
195
+ if (!financialFields[key]) {
196
+ for (const nested of ['financial_model', 'result', 'output', 'analysis', 'data']) {
197
+ const sub = safeGet(data, nested);
198
+ if (!sub)
199
+ continue;
200
+ for (const alias of aliases) {
201
+ const val = safeString(sub, alias);
202
+ if (val) {
203
+ financialFields[key] = val;
204
+ break;
205
+ }
206
+ }
207
+ if (financialFields[key])
208
+ break;
209
+ }
210
+ }
211
+ }
212
+ }
213
+ const hasRealData = !!(financialFields['budget'] || financialFields['roi'] || financialFields['npv'] || financialFields['payback'] || financialFields['revenue'] || financialFields['costSavings']);
214
+ // Source 3: Estimate from query context when nothing else available
215
+ if (!hasRealData) {
216
+ return estimateFinancialsFromQuery(query);
217
+ }
218
+ return {
219
+ budget: financialFields['budget'] ?? '',
220
+ roi: financialFields['roi'] ?? '',
221
+ npv: financialFields['npv'] ?? '',
222
+ payback: financialFields['payback'] ?? '',
223
+ revenue: financialFields['revenue'] ?? '',
224
+ costSavings: financialFields['costSavings'] ?? '',
225
+ hasData: true,
226
+ isEstimated: false,
227
+ };
228
+ }
229
+ /**
230
+ * ADR-PIPELINE-025 §7a: Generate financial estimates from query context.
231
+ * Uses employee count, industry, and scope to produce rough-order-of-magnitude numbers.
232
+ * All clearly labeled as estimates.
233
+ */
234
+ function estimateFinancialsFromQuery(query) {
235
+ // Extract employee count — simple string search, no regex
236
+ let employees = 10000;
237
+ const lower = query.toLowerCase();
238
+ const empIdx = lower.indexOf('employees');
239
+ const staffIdx = lower.indexOf('staff');
240
+ const targetIdx = empIdx !== -1 ? empIdx : staffIdx;
241
+ if (targetIdx > 5) {
242
+ // Walk backward from "employees" to find the number
243
+ const before = query.substring(Math.max(0, targetIdx - 30), targetIdx).trim();
244
+ const words = before.split(/\s+/);
245
+ for (let i = words.length - 1; i >= 0; i--) {
246
+ const cleaned = (words[i] ?? '').replace(/,/g, '').replace(/approximately/i, '').trim();
247
+ const num = parseInt(cleaned, 10);
248
+ if (num > 100 && num < 10_000_000) {
249
+ employees = num;
250
+ break;
251
+ }
252
+ }
253
+ }
254
+ // Industry affects per-employee costs
255
+ const isPharma = lower.includes('pharma') || lower.includes('drug') || lower.includes('gxp') || lower.includes('clinical');
256
+ const isFinancial = lower.includes('financial') || lower.includes('banking') || lower.includes('insurance');
257
+ const isRetail = lower.includes('retail') || lower.includes('grocery') || lower.includes('store');
258
+ const costPerEmployee = isPharma ? 35 : isFinancial ? 30 : isRetail ? 15 : 25;
259
+ const savingsMultiplier = isPharma ? 80 : isFinancial ? 60 : isRetail ? 40 : 50;
260
+ const pilotCost = Math.round(employees * costPerEmployee / 1000) * 1000;
261
+ const annualSavings = Math.round(employees * savingsMultiplier / 1000) * 1000;
262
+ const paybackMonths = Math.max(6, Math.min(24, Math.round(pilotCost / (annualSavings / 12))));
263
+ const npv5yr = Math.round(annualSavings * 3.5); // ~10% discount rate, 5 years
264
+ const roiPct = Math.round((annualSavings / pilotCost) * 100);
265
+ const fmt = (n) => n >= 1_000_000 ? `$${(n / 1_000_000).toFixed(1)}M` : `$${(n / 1000).toFixed(0)}K`;
266
+ return {
267
+ budget: `${fmt(pilotCost)} - ${fmt(pilotCost * 1.5)} (estimated based on ${employees.toLocaleString()} employees)`,
268
+ roi: `${roiPct}% projected (estimated)`,
269
+ npv: `${fmt(npv5yr)} 5-year NPV (estimated, 10% discount rate)`,
270
+ payback: `${paybackMonths}-${paybackMonths + 6} months (estimated)`,
271
+ revenue: `${fmt(annualSavings)} annual efficiency gains (estimated)`,
272
+ costSavings: '',
273
+ hasData: true,
274
+ isEstimated: true,
275
+ };
276
+ }
277
+ // extractFinancials is now synthesizeFinancials — kept as export for external callers
278
+ export { synthesizeFinancials as extractFinancials };
279
+ /**
280
+ * ADR-PIPELINE-025 §5c: Extract system name from query text using string matching.
281
+ * Mirrors tech-stack-detector's extractSystemNameFromText but for renderer context.
282
+ */
283
+ function extractSystemFromQuery(query) {
284
+ const lower = query.toLowerCase();
285
+ const triggers = [
286
+ 'managed through ', 'managed by ', 'managed via ', 'managed with ',
287
+ 'managed using ', 'managed in ', 'are managed through ',
288
+ 'inside ', 'records inside ', 'plans inside ', 'actions inside ',
289
+ ];
290
+ const stopWords = new Set(['the', 'our', 'this', 'that', 'a', 'an', 'it', 'we', 'they', 'and', 'or', 'but', 'if', 'because', 'however']);
291
+ for (const trigger of triggers) {
292
+ const idx = lower.indexOf(trigger);
293
+ if (idx === -1)
294
+ continue;
295
+ const after = query.substring(idx + trigger.length).trim();
296
+ const words = [];
297
+ for (const raw of after.split(/\s+/)) {
298
+ const word = raw.replace(/[.,;:!?)\]]+$/, '');
299
+ if (!word)
300
+ break;
301
+ if (['erp', 'system', 'platform', 'software', 'crm', 'wms', 'tms'].includes(word.toLowerCase())) {
302
+ words.push(word);
303
+ break;
304
+ }
305
+ if (stopWords.has(word.toLowerCase()) && words.length > 0)
306
+ break;
307
+ if (word[0] === word[0]?.toLowerCase() && word[0] !== word[0]?.toUpperCase() && words.length > 0)
308
+ break;
309
+ words.push(word);
310
+ if (words.length >= 5)
311
+ break;
312
+ }
313
+ const name = words.join(' ').trim();
314
+ if (name.length >= 2 && name.length <= 50 && !stopWords.has(name.toLowerCase()))
315
+ return name;
316
+ }
317
+ return null;
147
318
  }
148
319
  /**
149
320
  * ADR-PIPELINE-024: Generate domain-appropriate risks from query context.
@@ -221,9 +392,9 @@ export function renderExecutiveSummary(query, simulationResult, platformResults)
221
392
  const timeline = safeString(simData, 'timeline_estimate') || safeString(simData, 'timeline') || '12-18 weeks';
222
393
  const problemStatement = distillProblemStatement(query);
223
394
  const extracted = extractScenarioFromQuery(query);
224
- const fin = extractFinancials(simData, platformResults);
395
+ const fin = synthesizeFinancials(simData, platformResults, query);
225
396
  const risks = generateDomainRisks(query, extracted);
226
- const primarySystem = extracted.systems[0] ?? 'the target platform';
397
+ const primarySystem = extracted.systems[0] ?? extractSystemFromQuery(query) ?? 'the enterprise platform';
227
398
  let recommendation = 'DEFER';
228
399
  let recDetail = 'Further analysis recommended before committing resources.';
229
400
  if (successProb >= 0.9) {
@@ -258,7 +429,12 @@ export function renderExecutiveSummary(query, simulationResult, platformResults)
258
429
  lines.push(`Projected impact: ${impactParts.join(', ')}.`);
259
430
  }
260
431
  }
261
- lines.push(`Success probability: ${(successProb * 100).toFixed(0)}% | Timeline: ${timeline}`, '');
432
+ // ADR-PIPELINE-025 §4: Success probability with decomposition
433
+ const techFeasibility = Math.min(0.95, successProb + 0.04);
434
+ const orgReadiness = Math.max(0.70, successProb - 0.03);
435
+ const dataAvailability = successProb;
436
+ const integrationFeasibility = Math.max(0.75, successProb - 0.01);
437
+ lines.push(`Success probability: **${(successProb * 100).toFixed(0)}%** (Technical ${(techFeasibility * 100).toFixed(0)}%, Organizational ${(orgReadiness * 100).toFixed(0)}%, Data ${(dataAvailability * 100).toFixed(0)}%, Integration ${(integrationFeasibility * 100).toFixed(0)}%) | Timeline: ${timeline}`, '');
262
438
  // The Opportunity — cost of inaction
263
439
  lines.push('## The Opportunity', '');
264
440
  lines.push(`${problemStatement}. Current processes rely on manual review and periodic reporting, ` +
@@ -341,9 +517,9 @@ export function renderDecisionMemo(query, simulationResult, platformResults) {
341
517
  const successProb = extractSuccessProbability(simData);
342
518
  const extracted = extractScenarioFromQuery(query);
343
519
  const problemStatement = distillProblemStatement(query);
344
- const fin = extractFinancials(simData, platformResults);
520
+ const fin = synthesizeFinancials(simData, platformResults, query);
345
521
  const risks = generateDomainRisks(query, extracted);
346
- const primarySystem = extracted.systems[0] ?? 'the target platform';
522
+ const primarySystem = extracted.systems[0] ?? extractSystemFromQuery(query) ?? 'the enterprise platform';
347
523
  const plannerData = extractAgentData(platformResults, 'copilot', 'planner');
348
524
  let recommendation = 'DEFER';
349
525
  let rationale = 'Further analysis recommended before committing resources.';
@@ -932,9 +1108,9 @@ export function buildRoadmapArtifact(query, simulationResult, platformResults) {
932
1108
  // Extract phases from timeline or generate domain-specific structure
933
1109
  const phases = safeArray(simData, 'timeline', 'phases');
934
1110
  const extracted = extractScenarioFromQuery(query);
935
- const systemList = extracted.systems.length > 0 ? extracted.systems.join(', ') : 'target platform';
1111
+ const systemList = extracted.systems.length > 0 ? extracted.systems.join(', ') : (extractSystemFromQuery(query) ?? 'target platform');
936
1112
  const entityList = extracted.domain_entities.slice(0, 3).join(', ') || 'core domain models';
937
- const primarySystem = extracted.systems[0] ?? 'enterprise platform';
1113
+ const primarySystem = extracted.systems[0] ?? extractSystemFromQuery(query) ?? 'enterprise platform';
938
1114
  const defaultPhases = phases.length > 0 ? phases : [
939
1115
  {
940
1116
  id: 'phase-1',
@@ -1111,7 +1287,7 @@ export function renderFinancialAnalysis(query, simulationResult, platformResults
1111
1287
  const problemStatement = distillProblemStatement(query);
1112
1288
  const simPayload = extractSignalPayload(simulationResult);
1113
1289
  const simData = simPayload.data ?? {};
1114
- const fin = extractFinancials(simData, platformResults);
1290
+ const fin = synthesizeFinancials(simData, platformResults, query);
1115
1291
  const successProb = extractSuccessProbability(simData);
1116
1292
  const extracted = extractScenarioFromQuery(query);
1117
1293
  const roiData = extractAgentData(platformResults, 'costops', 'roi');
@@ -1128,7 +1304,7 @@ export function renderFinancialAnalysis(query, simulationResult, platformResults
1128
1304
  '',
1129
1305
  '---',
1130
1306
  '',
1131
- '## Investment Summary', '',
1307
+ `## Investment Summary${fin.isEstimated ? ' (Estimated from Organization Profile)' : ''}`, '',
1132
1308
  '| Metric | Value |',
1133
1309
  '|--------|-------|',
1134
1310
  `| Total Investment | ${fin.budget || 'To be quantified during discovery'} |`,
@@ -1218,16 +1394,32 @@ export function renderSecurityAssessment(query, _simulationResult, platformResul
1218
1394
  const problemStatement = distillProblemStatement(query);
1219
1395
  const shieldAgents = extractAgentsByDomain(platformResults, 'shield');
1220
1396
  const govAgents = extractAgentsByDomain(platformResults, 'governance-dashboard');
1397
+ // ADR-PIPELINE-025 §7c: Detect applicable regulations from query context
1398
+ const regulations = detectApplicableRegulations(query);
1399
+ const extracted = extractScenarioFromQuery(query);
1400
+ const primarySystem = extracted.systems[0] ?? extractSystemFromQuery(query) ?? 'the enterprise platform';
1221
1401
  const lines = [
1222
1402
  '# Security & Compliance Assessment',
1223
1403
  '',
1224
1404
  `**Date:** ${now}`,
1225
1405
  `**Subject:** ${problemStatement}`,
1406
+ `**Target System:** ${primarySystem}`,
1226
1407
  '',
1227
1408
  '---',
1228
1409
  '',
1229
- '## Security Posture', '',
1230
1410
  ];
1411
+ // Applicable Regulations — always populated from query context
1412
+ lines.push('## Applicable Regulatory Frameworks', '');
1413
+ if (regulations.length > 0) {
1414
+ lines.push('| Framework | Applicability | Key Requirements | Impact on Solution |');
1415
+ lines.push('|-----------|--------------|------------------|-------------------|');
1416
+ for (const reg of regulations) {
1417
+ lines.push(`| ${reg.framework} | ${reg.applicability} | ${reg.requirements} | ${reg.impact} |`);
1418
+ }
1419
+ lines.push('');
1420
+ }
1421
+ // Security Posture
1422
+ lines.push('## Security Posture', '');
1231
1423
  if (shieldAgents.length > 0) {
1232
1424
  for (const agent of shieldAgents) {
1233
1425
  const data = extractSignalPayload(agent.response).data;
@@ -1239,8 +1431,20 @@ export function renderSecurityAssessment(query, _simulationResult, platformResul
1239
1431
  }
1240
1432
  }
1241
1433
  else {
1242
- lines.push('Security assessment to be conducted during discovery phase. Key areas: data protection (PII handling), authentication/authorization model, secret management, and compliance with applicable regulatory frameworks.', '');
1434
+ lines.push('### Data Classification', '');
1435
+ lines.push(`- **Internal data**: Operational telemetry, performance metrics, efficiency analysis`);
1436
+ lines.push(`- **Confidential data**: ${primarySystem} transaction records, financial data, employee information`);
1437
+ lines.push(`- **Regulated data**: ${regulations.length > 0 ? regulations.map(r => r.dataType).join(', ') : 'Subject to applicable industry regulations'}`);
1438
+ lines.push('');
1439
+ lines.push('### Recommended Security Controls', '');
1440
+ lines.push('- Role-based access control (RBAC) with separation of duties');
1441
+ lines.push('- Encryption at rest and in transit (TLS 1.3)');
1442
+ lines.push('- Audit trail for all data access and decision actions');
1443
+ lines.push('- Secret management via cloud provider vault (no hardcoded credentials)');
1444
+ lines.push(`- PII redaction in ${primarySystem} data pipeline`);
1445
+ lines.push('');
1243
1446
  }
1447
+ // Governance & Compliance
1244
1448
  lines.push('## Governance & Compliance', '');
1245
1449
  if (govAgents.length > 0) {
1246
1450
  for (const agent of govAgents) {
@@ -1253,14 +1457,105 @@ export function renderSecurityAssessment(query, _simulationResult, platformResul
1253
1457
  }
1254
1458
  }
1255
1459
  else {
1256
- lines.push('Governance framework to be defined during discovery phase. Recommended controls: role-based access, audit trail for all decisions, approval workflows for operational changes, and separation of duties between analysis and execution.', '');
1460
+ lines.push('### Decision Governance Model', '');
1461
+ lines.push('- Human-in-the-loop: No automated execution — all recommendations require human approval');
1462
+ lines.push('- Approval workflow: Submit → Review → Approve/Reject → Execute (governance-gated)');
1463
+ lines.push(`- ${primarySystem} writeback: Only approved actions propagate to the ERP system`);
1464
+ lines.push('- Audit trail: Append-only log with actor, timestamp, action, correlation ID');
1465
+ lines.push('');
1257
1466
  }
1258
1467
  const sources = [...shieldAgents.map(a => `shield/${a.agent}`), ...govAgents.map(a => `governance-dashboard/${a.agent}`)];
1259
1468
  lines.push('---', '');
1260
- lines.push(`*Sources: ${sources.length > 0 ? sources.join(', ') : 'none'}*`);
1469
+ lines.push(`*Sources: ${sources.length > 0 ? sources.join(', ') : 'domain context analysis + regulatory framework detection'}*`);
1261
1470
  lines.push(`*Generated: ${now}*`);
1262
1471
  return lines.join('\n');
1263
1472
  }
1473
+ function detectApplicableRegulations(query) {
1474
+ const regs = [];
1475
+ const lower = query.toLowerCase();
1476
+ // Pharmaceutical / Life Sciences
1477
+ if (lower.includes('pharma') || lower.includes('drug') || lower.includes('gxp') || lower.includes('clinical') || lower.includes('temperature-sensitive') || lower.includes('cold chain')) {
1478
+ regs.push({
1479
+ framework: 'FDA 21 CFR Part 11',
1480
+ applicability: 'Required — pharmaceutical electronic records',
1481
+ requirements: 'Audit trails, electronic signatures, data integrity, system validation',
1482
+ impact: 'All decision records must be tamper-evident with full audit trail',
1483
+ dataType: 'pharmaceutical production and distribution records',
1484
+ });
1485
+ regs.push({
1486
+ framework: 'EU GxP Annex 11',
1487
+ applicability: 'Required for EU operations — computerized systems',
1488
+ requirements: 'System validation, data integrity, access controls, change management',
1489
+ impact: 'System must be validated before production use; all changes documented',
1490
+ dataType: 'GxP-regulated process data',
1491
+ });
1492
+ regs.push({
1493
+ framework: 'GDP (Good Distribution Practice)',
1494
+ applicability: 'Required — pharmaceutical distribution',
1495
+ requirements: 'Temperature monitoring, transportation qualification, deviation management',
1496
+ impact: 'Cold chain monitoring and deviation alerts are compliance-critical',
1497
+ dataType: 'temperature and logistics records',
1498
+ });
1499
+ }
1500
+ // Healthcare
1501
+ if (lower.includes('health') || lower.includes('patient') || lower.includes('clinical') || lower.includes('hipaa') || lower.includes('medical')) {
1502
+ regs.push({
1503
+ framework: 'HIPAA',
1504
+ applicability: 'Required — protected health information',
1505
+ requirements: 'PHI encryption, access controls, breach notification, BAAs',
1506
+ impact: 'All patient data must be encrypted; access logged; minimum necessary principle',
1507
+ dataType: 'protected health information (PHI)',
1508
+ });
1509
+ }
1510
+ // Financial
1511
+ if (lower.includes('financial') || lower.includes('banking') || lower.includes('insurance') || lower.includes('sox') || lower.includes('accounting')) {
1512
+ regs.push({
1513
+ framework: 'SOX (Sarbanes-Oxley)',
1514
+ applicability: 'Required — financial reporting controls',
1515
+ requirements: 'Internal controls, audit trail, segregation of duties, change management',
1516
+ impact: 'Financial data transformations must have full audit trail and dual approval',
1517
+ dataType: 'financial records and reporting data',
1518
+ });
1519
+ regs.push({
1520
+ framework: 'PCI-DSS',
1521
+ applicability: 'If payment data is processed',
1522
+ requirements: 'Encryption, access controls, vulnerability management, monitoring',
1523
+ impact: 'Payment data must never be stored unencrypted; quarterly vulnerability scans',
1524
+ dataType: 'payment card and transaction data',
1525
+ });
1526
+ }
1527
+ // EU operations → GDPR
1528
+ if (lower.includes('europe') || lower.includes('eu ') || lower.includes('gdpr') || lower.includes('european')) {
1529
+ regs.push({
1530
+ framework: 'GDPR',
1531
+ applicability: 'Required — EU personal data processing',
1532
+ requirements: 'Data minimization, consent management, right to erasure, DPIAs, data residency',
1533
+ impact: 'EU employee/customer data must stay in EU region; privacy by design required',
1534
+ dataType: 'personal data of EU data subjects',
1535
+ });
1536
+ }
1537
+ // Multi-region → data residency
1538
+ if ((lower.includes('north america') && lower.includes('europe')) || (lower.includes('asia') && lower.includes('europe')) || lower.includes('global') || lower.includes('multi-region')) {
1539
+ regs.push({
1540
+ framework: 'Data Residency Requirements',
1541
+ applicability: 'Required — multi-jurisdictional operations',
1542
+ requirements: 'Data must be stored in the jurisdiction where it was generated unless transfer mechanisms exist',
1543
+ impact: 'Regional deployment or data partitioning may be required; cross-border transfer assessments',
1544
+ dataType: 'regionally-generated operational and personal data',
1545
+ });
1546
+ }
1547
+ // Sustainability / ESG
1548
+ if (lower.includes('sustainability') || lower.includes('emission') || lower.includes('carbon') || lower.includes('esg') || lower.includes('environmental')) {
1549
+ regs.push({
1550
+ framework: 'CSRD / EU Taxonomy',
1551
+ applicability: 'Required for EU-operating large enterprises — sustainability reporting',
1552
+ requirements: 'Auditable emissions data, double materiality assessment, ESRS compliance',
1553
+ impact: 'Sustainability metrics must be auditable with documented methodology',
1554
+ dataType: 'emissions, energy, waste, and sustainability performance data',
1555
+ });
1556
+ }
1557
+ return regs;
1558
+ }
1264
1559
  // ============================================================================
1265
1560
  // ADR-PIPELINE-020: Integration Assessment Renderer (connector-hub agents)
1266
1561
  // ============================================================================
@@ -1275,7 +1570,7 @@ export function renderIntegrationAssessment(query, _simulationResult, platformRe
1275
1570
  '',
1276
1571
  `**Date:** ${now}`,
1277
1572
  `**Subject:** ${problemStatement}`,
1278
- `**Target Systems:** ${extracted.systems.join(', ') || 'To be identified'}`,
1573
+ `**Target Systems:** ${extracted.systems.join(', ') || extractSystemFromQuery(query) || 'See scenario description'}`,
1279
1574
  '',
1280
1575
  '---',
1281
1576
  '',
@@ -1292,7 +1587,8 @@ export function renderIntegrationAssessment(query, _simulationResult, platformRe
1292
1587
  }
1293
1588
  }
1294
1589
  else {
1295
- lines.push(`Integration assessment to be completed during technical spike. Key considerations: API authentication, rate limits, data format compatibility, error handling, and retry strategy for ${extracted.systems[0] || 'the target platform'}.`, '');
1590
+ const intSystem = extracted.systems[0] || extractSystemFromQuery(query) || 'the target platform';
1591
+ lines.push(`Integration with ${intSystem} requires API authentication, rate limits assessment, data format compatibility validation, error handling strategy, and retry logic. A 1-2 week technical spike is recommended to validate these integration points before full development.`, '');
1296
1592
  }
1297
1593
  lines.push('## Edge & Resilience Patterns', '');
1298
1594
  if (edgeAgents.length > 0) {