@llm-dev-ops/agentics-cli 1.5.45 → 1.5.47

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.
@@ -166,191 +166,514 @@ function buildADRsFromSPARC(sparc, dossier, scenarioQuery) {
166
166
  const adrs = [];
167
167
  let idx = 1;
168
168
  const today = new Date().toISOString().slice(0, 10);
169
- const { services, securityItems, perfItems } = normalizeSPARCForADR(sparc);
170
- // 1. Service implementation ADRs — describe WHAT each service builds, not just "use adapter pattern"
171
- const svcForADRs = services.slice(0, 6);
172
- for (const svc of svcForADRs) {
173
- const depsText = svc.dependencies.length > 0
174
- ? ` It integrates with: ${svc.dependencies.join(', ')}.`
175
- : '';
169
+ const q = scenarioQuery ?? '';
170
+ const { services, securityItems, perfItems, integrationPlan } = normalizeSPARCForADR(sparc);
171
+ // ── Extract domain context from the query ──────────────────────────────
172
+ const domain = extractDomainContext(q);
173
+ const techStack = extractTechFromQuery(q);
174
+ // ======================================================================
175
+ // SPARC §S — Specification: Domain Model ADR
176
+ // What entities exist, how they relate, what invariants constrain them
177
+ // ======================================================================
178
+ if (services.length > 0) {
179
+ const entityNames = services.slice(0, 6).map(s => s.name);
180
+ const depsAll = services.flatMap(s => s.dependencies).filter(Boolean);
181
+ const uniqueDeps = [...new Set(depsAll)];
176
182
  adrs.push({
177
183
  id: `ADR-P2-${pad3(idx++)}`,
178
- title: `Implement ${svc.name}${svc.responsibility ? ': ' + svc.responsibility.slice(0, 60) : ''}`,
184
+ title: `Domain Model: ${domain.primary} entity graph with ${entityNames.length} bounded contexts`,
179
185
  status: 'proposed',
180
186
  date: today,
181
- context: `The platform requires a ${svc.name} component that handles ${svc.responsibility || 'core domain logic'}.${depsText} This is a key part of the solution described in the project requirements.`,
182
- decision: `Build ${svc.name} as a modular service with clear interfaces. ${svc.responsibility ? 'Implement: ' + svc.responsibility + '.' : ''} ${svc.dependencies.length > 0 ? 'Use adapter interfaces for each dependency (' + svc.dependencies.join(', ') + ') to enable testing and future replacement.' : 'Expose a clean API surface for other services to consume.'}`,
187
+ context: `The ${domain.primary} domain decomposes into ${entityNames.length} bounded contexts: ${entityNames.join(', ')}. ` +
188
+ `${uniqueDeps.length > 0 ? 'Cross-context dependencies: ' + uniqueDeps.slice(0, 5).join(', ') + '. ' : ''}` +
189
+ `${domain.stakeholders ? 'Stakeholders: ' + domain.stakeholders + '. ' : ''}` +
190
+ `Each context must own its data and enforce its own invariants.`,
191
+ decision: `Model ${domain.primary} as ${entityNames.length} bounded contexts with explicit aggregate roots. ` +
192
+ `${entityNames.slice(0, 3).map(n => `"${n}" owns its entities and exposes a domain API`).join('; ')}. ` +
193
+ `Cross-context communication via domain events (not shared database). ` +
194
+ `${domain.erp ? 'Anti-corruption layer translates between ' + domain.erp + ' wire format and domain types.' : ''}`,
183
195
  alternatives: [
184
- makeAlt(`Dedicated ${svc.name} module`, `Clear ownership, testable in isolation`, false),
185
- makeAlt('Inline in monolith', 'Simpler initially but harder to test and scale', true),
186
- makeAlt('Third-party service', 'Faster to deploy but less control over domain logic', true),
196
+ makeAlt(`${entityNames.length} bounded contexts with domain events`, 'Each context evolves independently; matches ' + domain.primary + ' organizational structure', false),
197
+ makeAlt('Shared domain model', 'Faster initial development but all contexts coupled any schema change ripples everywhere', true),
198
+ makeAlt('CRUD-only without domain model', 'Simpler but business rules scattered across layers, untestable', true),
187
199
  ],
188
200
  consequences: [
189
- makeCons(`${svc.name} logic is encapsulated with clear boundaries`, 'positive'),
190
- makeCons(`Enables independent testing of ${svc.name} business rules`, 'positive'),
191
- makeCons('Requires well-defined interfaces between services', 'negative'),
201
+ makeCons(`Each ${domain.primary} context can be developed, tested, and deployed independently`, 'positive'),
202
+ makeCons('Domain events provide audit trail of all cross-context interactions', 'positive'),
203
+ makeCons('Requires upfront domain modeling investment before implementation', 'negative'),
204
+ makeCons(`Cross-context queries (e.g. ${entityNames.length > 1 ? entityNames[0] + ' → ' + entityNames[1] : 'aggregation'}) require API composition, not JOINs`, 'negative'),
205
+ ],
206
+ sparcSectionRefs: ['specification.requirements', 'architecture.services'],
207
+ researchDossierItemRefs: findDossierRefs(dossier, entityNames),
208
+ });
209
+ }
210
+ // ======================================================================
211
+ // SPARC §P — Pseudocode: Simulation / Optimization / Core Algorithm ADR
212
+ // HOW the core computation works — not generic, specific to the domain
213
+ // ======================================================================
214
+ const hasSimulation = /simulat|scenario|what.?if|forecast|predict|model(?:ing|ed|s)/i.test(q);
215
+ const hasOptimization = /optimiz|minimize|maximize|tradeoff|trade.?off|pareto|quantif/i.test(q);
216
+ const hasGraphModeling = /graph|network|dependenc|flow|topolog|adjacen/i.test(q);
217
+ if (hasSimulation || hasOptimization || hasGraphModeling) {
218
+ const algoType = hasOptimization ? 'multi-objective optimization'
219
+ : hasGraphModeling ? 'graph-based dependency modeling'
220
+ : 'scenario simulation';
221
+ const rustNote = techStack.includes('Rust')
222
+ ? `Implement the compute-intensive ${algoType} engine in Rust for performance (graph traversal, constraint solving, Monte Carlo). TypeScript orchestrates scenario configuration, result aggregation, and API exposure.`
223
+ : `Implement ${algoType} engine with clear separation between scenario configuration (inputs), computation (engine), and result presentation (outputs).`;
224
+ const simOutputs = hasOptimization
225
+ ? 'Pareto frontier across competing objectives (cost, risk, emissions, complexity). Each point on the frontier is a decision-grade scenario with full traceoff breakdown.'
226
+ : 'Ranked scenario comparison with metrics per dimension. Each scenario includes confidence intervals, sensitivity analysis, and assumption documentation.';
227
+ adrs.push({
228
+ id: `ADR-P2-${pad3(idx++)}`,
229
+ title: `${domain.primary} ${hasOptimization ? 'Optimization' : 'Simulation'} Engine: ${algoType} with immutable inputs/outputs`,
230
+ status: 'proposed',
231
+ date: today,
232
+ context: `The system must ${hasOptimization ? 'optimize across competing objectives' : 'simulate multiple strategies'} for ${domain.primary}. ` +
233
+ `Decision-makers need structured, comparative outputs — not automated execution. ` +
234
+ `${hasGraphModeling ? 'The domain involves network/graph structures with dependencies that affect propagation of changes. ' : ''}` +
235
+ `All simulation inputs must be immutable; results must be reproducible and auditable.`,
236
+ decision: `${rustNote} ` +
237
+ `Simulation accepts immutable scenario parameters, runs computation, and produces: ${simOutputs} ` +
238
+ `Results carry a cryptographic hash of inputs for reproducibility verification. ` +
239
+ `No execution occurs from simulation — results are decision-grade outputs for human review.`,
240
+ alternatives: [
241
+ makeAlt(`Dedicated ${algoType} engine with immutable I/O`, 'Auditable, reproducible, supports regulatory review', false),
242
+ makeAlt('Spreadsheet-based modeling', 'Insufficient for multi-dimensional analysis at enterprise scale', true),
243
+ makeAlt('Auto-executing optimizer', 'Violates human-in-the-loop governance requirement', true),
244
+ ],
245
+ consequences: [
246
+ makeCons('Every simulation result is reproducible — same inputs always produce same outputs', 'positive'),
247
+ makeCons('Stakeholders compare scenarios side-by-side before committing resources', 'positive'),
248
+ makeCons(techStack.includes('Rust') ? 'Requires Rust/TypeScript FFI boundary and dual-language build pipeline' : 'Compute-intensive scenarios may require dedicated infrastructure', 'negative'),
249
+ makeCons('Scenario parameter space must be bounded to prevent combinatorial explosion', 'negative'),
250
+ ],
251
+ sparcSectionRefs: ['pseudocode.modules', 'architecture.dataFlow'],
252
+ researchDossierItemRefs: findDossierRefs(dossier, ['simulation', 'optimization', 'scenario', 'model', 'graph']),
253
+ });
254
+ }
255
+ // ======================================================================
256
+ // SPARC §A — Architecture: Per-service implementation decisions
257
+ // Each service gets a SPECIFIC decision, not "build as modular service"
258
+ // ======================================================================
259
+ for (const svc of services.slice(0, 4)) {
260
+ // Skip if the service name would duplicate the domain model or simulation ADR
261
+ if (idx > 2 && /domain|model|simul|optim|engine/i.test(svc.name))
262
+ continue;
263
+ const depsText = svc.dependencies.length > 0
264
+ ? svc.dependencies.join(', ')
265
+ : '';
266
+ const responsibility = svc.responsibility || svc.name;
267
+ // Determine what KIND of service this is and generate a specific decision
268
+ const isDataIngestion = /ingest|import|extract|collect|read|fetch|sync/i.test(responsibility);
269
+ const isAnalysis = /analy|score|classif|evaluat|assess|rank|compar/i.test(responsibility);
270
+ const isOrchestration = /orchestrat|workflow|pipeline|coordinat|schedul/i.test(responsibility);
271
+ const isPresentation = /report|dashboard|visual|output|present|display|generat.*output/i.test(responsibility);
272
+ const isGovernance = /approv|govern|audit|compliance|lineage|review/i.test(responsibility);
273
+ let specificDecision;
274
+ let specificTitle;
275
+ let specificAlts;
276
+ if (isGovernance) {
277
+ specificTitle = `${svc.name}: Human-in-the-loop approval with immutable audit trail`;
278
+ specificDecision = `Implement ${svc.name} as a state machine (draft → submitted → approved/rejected → executed). ` +
279
+ `Every state transition is logged immutably with actor identity, timestamp, and decision rationale. ` +
280
+ `Approval requires authenticated user with appropriate role — no self-approval, no programmatic bypass. ` +
281
+ `${domain.erp ? 'Only APPROVED decisions can trigger ' + domain.erp + ' writeback.' : ''}`;
282
+ specificAlts = [
283
+ makeAlt('State machine with RBAC and immutable log', 'Meets audit and compliance requirements', false),
284
+ makeAlt('Simple boolean approved flag', 'No state history, no role enforcement, no audit trail', true),
285
+ makeAlt('Manual email-based approval', 'No programmatic enforcement, approval can be forged', true),
286
+ ];
287
+ }
288
+ else if (isDataIngestion) {
289
+ specificTitle = `${svc.name}: ${domain.erp ? domain.erp + ' data ingestion' : 'External data ingestion'} with validation`;
290
+ specificDecision = `${svc.name} ingests data from ${depsText || (domain.erp ?? 'external sources')} through an anti-corruption layer. ` +
291
+ `All incoming records are validated against domain schemas before entering the system. ` +
292
+ `Invalid records are quarantined with error details, not silently dropped. ` +
293
+ `Ingestion is idempotent — re-running the same import produces identical state.`;
294
+ specificAlts = [
295
+ makeAlt('ACL with schema validation and quarantine', 'Protects domain model from external data quality issues', false),
296
+ makeAlt('Direct database import', 'Couples domain model to external schemas — any upstream change breaks the system', true),
297
+ ];
298
+ }
299
+ else if (isAnalysis) {
300
+ specificTitle = `${svc.name}: Domain-specific ${/score|rank/i.test(responsibility) ? 'scoring' : 'analysis'} with configurable weights`;
301
+ specificDecision = `${svc.name} implements ${responsibility.slice(0, 100)}. ` +
302
+ `Scoring weights and thresholds are externalized as configuration (not hardcoded constants) so domain experts can tune them without code changes. ` +
303
+ `All analysis outputs include the weights used, enabling reproducibility and audit. ` +
304
+ `${depsText ? 'Consumes data from: ' + depsText + '.' : ''}`;
305
+ specificAlts = [
306
+ makeAlt('Configurable analysis with weight transparency', 'Domain experts can tune; results are auditable', false),
307
+ makeAlt('Hardcoded scoring rules', 'Faster initially but requires developer for every threshold change', true),
308
+ ];
309
+ }
310
+ else if (isOrchestration) {
311
+ specificTitle = `${svc.name}: Workflow orchestration with checkpoint and resume`;
312
+ specificDecision = `${svc.name} coordinates: ${responsibility.slice(0, 120)}. ` +
313
+ `Each workflow step is checkpointed so failures resume from last successful step, not from scratch. ` +
314
+ `Workflow state is persisted (not in-memory) to survive process restarts. ` +
315
+ `${depsText ? 'Orchestrates: ' + depsText + '.' : ''}`;
316
+ specificAlts = [
317
+ makeAlt('Persistent workflow with checkpoints', 'Resilient to failures, supports long-running processes', false),
318
+ makeAlt('In-memory orchestration', 'Process restart loses all workflow state', true),
319
+ ];
320
+ }
321
+ else if (isPresentation) {
322
+ specificTitle = `${svc.name}: Decision-grade output generation with lineage`;
323
+ specificDecision = `${svc.name} generates structured outputs for ${domain.stakeholders || 'leadership review'}. ` +
324
+ `Every output traces back to the simulation/analysis that produced it (lineage hash). ` +
325
+ `Outputs include: comparative metrics, tradeoff visualization data, confidence indicators, and assumption documentation. ` +
326
+ `${domain.erp ? 'Optionally formats approved outputs for ' + domain.erp + ' writeback.' : ''}`;
327
+ specificAlts = [
328
+ makeAlt('Structured outputs with lineage tracing', 'Decision-makers can verify what analysis produced each recommendation', false),
329
+ makeAlt('Free-form text reports', 'No traceability, no structured comparison', true),
330
+ ];
331
+ }
332
+ else {
333
+ // Generic but still domain-specific — use the actual responsibility text
334
+ specificTitle = `${svc.name}: ${responsibility.slice(0, 70)}`;
335
+ specificDecision = `Implement ${svc.name} to handle: ${responsibility}. ` +
336
+ `${depsText ? 'Integrates with ' + depsText + ' via typed interfaces (not direct coupling). ' : ''}` +
337
+ `Expose domain operations as a service API. All state mutations produce domain events for downstream consumers. ` +
338
+ `${techStack.includes('Rust') && /comput|optim|graph|simul/i.test(responsibility) ? 'Implement compute-intensive logic in Rust.' : ''}`;
339
+ specificAlts = [
340
+ makeAlt(`Dedicated ${svc.name} service with domain API`, `Encapsulates ${domain.primary} business rules for ${svc.name}`, false),
341
+ makeAlt('Inline in monolith', 'Couples ' + svc.name + ' logic to unrelated concerns', true),
342
+ ];
343
+ }
344
+ adrs.push({
345
+ id: `ADR-P2-${pad3(idx++)}`,
346
+ title: specificTitle,
347
+ status: 'proposed',
348
+ date: today,
349
+ context: `${domain.primary} requires: ${responsibility.slice(0, 200)}. ` +
350
+ `${depsText ? 'Dependencies: ' + depsText + '. ' : ''}` +
351
+ `This service is part of the ${domain.primary} solution.`,
352
+ decision: specificDecision,
353
+ alternatives: specificAlts,
354
+ consequences: [
355
+ makeCons(`${svc.name} business rules are testable in isolation`, 'positive'),
356
+ makeCons(`Clear API contract for ${svc.name} enables parallel team development`, 'positive'),
357
+ makeCons(`Requires explicit interface definition between ${svc.name} and its consumers`, 'negative'),
192
358
  ],
193
359
  sparcSectionRefs: [`architecture.services.${svc.name}`],
194
360
  researchDossierItemRefs: findDossierRefs(dossier, [svc.name, ...svc.dependencies]),
195
361
  });
196
362
  }
197
- // 2. Security ADR — domain-specific, not "defense-in-depth" boilerplate
198
- if (securityItems.length > 0) {
199
- const securityContext = securityItems.slice(0, 3).map(s => typeof s === 'string' ? s : (s && typeof s === 'object' ? (s['text'] ?? s['description'] ?? JSON.stringify(s)) : String(s))).join('; ');
363
+ // ======================================================================
364
+ // SPARC §A — Architecture: ERP Integration ADR (if ERP detected)
365
+ // ======================================================================
366
+ if (domain.erp) {
200
367
  adrs.push({
201
368
  id: `ADR-P2-${pad3(idx++)}`,
202
- title: `Security: ${securityContext.slice(0, 80) || 'Access Control and Data Protection'}`,
369
+ title: `${domain.erp} Integration: Anti-corruption layer with governance-gated writeback`,
203
370
  status: 'proposed',
204
371
  date: today,
205
- context: `The project has specific security requirements: ${securityContext}. ${scenarioQuery ? 'The system handles sensitive ' + (scenarioQuery.match(/financial|compliance|regulatory|audit|trading|clinical|patient|confidential/i)?.[0] ?? 'business') + ' data.' : ''}`,
206
- decision: `Implement role-based access control with domain-specific permissions. ${securityItems.length > 1 ? 'Apply ' + securityItems.slice(0, 2).map(s => typeof s === 'string' ? s : s?.['text'] ?? JSON.stringify(s)).join(' and ') + ' at the service layer.' : ''} All sensitive operations must produce audit trail entries.`,
372
+ context: `${domain.primary} data originates in ${domain.erp}. The AI system reads operational data for analysis/simulation ` +
373
+ `and optionally writes approved decisions back. Direct coupling to ${domain.erp} internals would make the domain model fragile.`,
374
+ decision: `Build an anti-corruption layer (ACL) between ${domain.erp} and the ${domain.primary} domain. ` +
375
+ `The ACL translates ${domain.erp} wire formats into domain types and vice versa. ` +
376
+ `Read mode is the default; writeback is gated behind APPROVED governance status. ` +
377
+ `Circuit breaker + sync queue handle ${domain.erp} outages. ` +
378
+ `All ${domain.erp} operations carry idempotency keys to prevent duplicate writes on retry.`,
207
379
  alternatives: [
208
- makeAlt('Role-based with audit trail', 'Meets domain compliance needs', false),
209
- makeAlt('Simple API key', 'Insufficient for multi-user sensitive data', true),
380
+ makeAlt(`ACL with governance-gated writeback`, `Protects ${domain.erp} integrity; enables AI-assisted decisions without risk`, false),
381
+ makeAlt(`Direct ${domain.erp} API calls from services`, `Tight coupling; ${domain.erp} schema changes break domain model`, true),
382
+ makeAlt('Full data replication to local DB', `Stale data risk; doubles storage cost; compliance concerns with data residency`, true),
210
383
  ],
211
384
  consequences: [
212
- makeCons('Meets domain-specific compliance and audit requirements', 'positive'),
213
- makeCons('Requires role definition and permission matrix per service', 'negative'),
385
+ makeCons(`${domain.erp} schema changes don't cascade to domain services`, 'positive'),
386
+ makeCons(`Writeback requires human approval no accidental ${domain.erp} mutations`, 'positive'),
387
+ makeCons(`ACL translation layer must track ${domain.erp} API version changes`, 'negative'),
214
388
  ],
215
- sparcSectionRefs: ['refinement.securityConsiderations'],
216
- researchDossierItemRefs: findDossierRefs(dossier, ['security', 'auth', 'access', 'encryption']),
389
+ sparcSectionRefs: ['architecture.services'],
390
+ researchDossierItemRefs: findDossierRefs(dossier, [domain.erp.toLowerCase(), 'erp', 'integration']),
217
391
  });
218
392
  }
219
- // 3. Performance ADR — tied to specific domain operations
220
- if (perfItems.length > 0) {
221
- const perfContext = perfItems.slice(0, 3).map(s => typeof s === 'string' ? s : (s && typeof s === 'object' ? (s['text'] ?? s['description'] ?? JSON.stringify(s)) : String(s))).join('; ');
393
+ // ======================================================================
394
+ // SPARC §A — Architecture: Data Persistence ADR (technology-specific)
395
+ // ======================================================================
396
+ const databases = techStack.filter(t => /postgres|mysql|bigquery|mongodb|redis|dynamodb|cloud sql/i.test(t));
397
+ const cloudProvider = techStack.find(t => /google cloud|aws|azure|gcp/i.test(t));
398
+ if (databases.length > 0 || cloudProvider) {
222
399
  adrs.push({
223
400
  id: `ADR-P2-${pad3(idx++)}`,
224
- title: `Performance: ${perfContext.slice(0, 80) || 'Optimize Critical Path Operations'}`,
401
+ title: `Data Persistence: ${[...databases, cloudProvider].filter(Boolean).join(' + ')} for ${domain.primary}`,
225
402
  status: 'proposed',
226
403
  date: today,
227
- context: `Performance targets: ${perfContext}. ${scenarioQuery?.includes('simulation') || scenarioQuery?.includes('real-time') ? 'The system includes computationally intensive operations that need optimization.' : ''}`,
228
- decision: `Optimize the critical data paths identified in the SPARC specification. Use caching for read-heavy queries, async processing for non-blocking operations, and connection pooling for database access. ${scenarioQuery?.includes('Rust') ? 'Use Rust for compute-intensive modules as specified in requirements.' : ''}`,
404
+ context: `${domain.primary} data includes: domain entities, ${hasSimulation ? 'simulation results, ' : ''}audit trail, and operational records. ` +
405
+ `User specified: ${techStack.join(', ')}. ` +
406
+ `Audit data must be immutable. Simulation results must be reproducible.`,
407
+ decision: databases.map(db => {
408
+ if (/bigquery/i.test(db))
409
+ return `**BigQuery** for analytical queries, historical trend analysis, and reporting aggregations.`;
410
+ if (/postgres/i.test(db))
411
+ return `**Cloud SQL (PostgreSQL)** for transactional domain data, audit logs, and governance state. Use row-level security for multi-tenant isolation.`;
412
+ if (/redis/i.test(db))
413
+ return `**Redis** for caching frequently-read reference data and session state.`;
414
+ if (/mongodb/i.test(db))
415
+ return `**MongoDB** for flexible document storage of simulation configurations and results.`;
416
+ return `**${db}** for persistent domain data.`;
417
+ }).join(' ') +
418
+ (cloudProvider ? ` Deploy on ${cloudProvider} managed services to reduce operational burden.` : ''),
229
419
  alternatives: [
230
- makeAlt('Targeted optimization', 'Focuses effort on actual bottlenecks', false),
231
- makeAlt('Premature optimization everywhere', 'Wastes effort on non-critical paths', true),
420
+ makeAlt(`${databases.join(' + ')} on ${cloudProvider ?? 'managed cloud'}`, 'Matches specified tech stack; managed services reduce ops burden', false),
421
+ makeAlt('Single-database architecture', 'Simpler but cannot optimize for both transactional and analytical workloads', true),
422
+ makeAlt('In-memory only', 'Process restart destroys all data — compliance disqualifier', true),
232
423
  ],
233
424
  consequences: [
234
- makeCons('Critical operations meet performance targets', 'positive'),
235
- makeCons('Requires profiling to validate improvements', 'negative'),
425
+ makeCons('Polyglot persistence optimizes each data access pattern', 'positive'),
426
+ makeCons(`${cloudProvider ?? 'Cloud'} managed services handle backups, scaling, and failover`, 'positive'),
427
+ makeCons('Multiple data stores increase operational complexity and migration effort', 'negative'),
236
428
  ],
237
- sparcSectionRefs: ['refinement.performanceTargets'],
238
- researchDossierItemRefs: findDossierRefs(dossier, ['performance', 'latency', 'throughput']),
429
+ sparcSectionRefs: ['architecture.services'],
430
+ researchDossierItemRefs: findDossierRefs(dossier, ['data', 'database', 'storage', 'persistence', ...databases.map(d => d.toLowerCase())]),
239
431
  });
240
432
  }
241
- // 4. High-severity risk ADRs — use actual risk content, not generic "mitigate through redundancy"
242
- const highRisks = dossier.items.filter(i => i.category === 'risk' && i.severity && /high|critical/i.test(i.severity));
243
- for (const risk of highRisks.slice(0, 3)) {
433
+ // ======================================================================
434
+ // SPARC §R Refinement: Security & Compliance ADR
435
+ // ======================================================================
436
+ const secText = itemsToText(securityItems);
437
+ const hasCompliance = /audit|compliance|regulatory|governance|lineage|human.?in.?the.?loop|esg|esg/i.test(q);
438
+ if (secText || hasCompliance) {
439
+ const complianceTerms = (q.match(/audit(?:ability)?|compliance|regulatory|governance|ESG|lineage|human.?in.?the.?loop|GDPR|HIPAA|SOX/gi) ?? []);
440
+ const uniqueTerms = [...new Set(complianceTerms.map(t => t.toLowerCase()))];
244
441
  adrs.push({
245
442
  id: `ADR-P2-${pad3(idx++)}`,
246
- title: `Address Risk: ${risk.title.slice(0, 70)}`,
443
+ title: `Security & Compliance: ${uniqueTerms.slice(0, 3).join(', ') || 'RBAC and audit trail'} for ${domain.primary}`,
247
444
  status: 'proposed',
248
445
  date: today,
249
- context: risk.content,
250
- decision: `Address this risk by: implementing validation at the boundary where this risk originates, adding monitoring/alerting for early detection, and designing a fallback path for degraded operation. Specific controls should be documented in the implementation.`,
446
+ context: `${domain.primary} decisions affect ${domain.stakeholders || 'operational teams and leadership'}. ` +
447
+ `${secText ? 'Security requirements: ' + secText + '. ' : ''}` +
448
+ `${uniqueTerms.length > 0 ? 'Compliance domains: ' + uniqueTerms.join(', ') + '. ' : ''}` +
449
+ `The system must maintain strict auditability: who decided what, when, and based on which analysis.`,
450
+ decision: `Implement RBAC with domain-specific roles (not just admin/user). ` +
451
+ `Define roles mapped to ${domain.primary} operations: who can run simulations, who can approve decisions, who can trigger ${domain.erp ?? 'ERP'} writeback. ` +
452
+ `All state-changing operations produce immutable audit entries with actor identity, timestamp, and operation details. ` +
453
+ `${hasSimulation ? 'Simulation results are write-once with content hash for tamper detection. ' : ''}` +
454
+ `Human-in-the-loop is mandatory before any changes propagate to operational systems.`,
251
455
  alternatives: [
252
- makeAlt('Active controls at boundary', 'Prevents impact before it occurs', false),
253
- makeAlt('Accept and monitor', 'Lower effort but reactive', true),
456
+ makeAlt(`RBAC + immutable audit + ${hasSimulation ? 'simulation lineage' : 'operation lineage'}`, 'Meets regulatory requirements; provides full traceability', false),
457
+ makeAlt('Simple API key authentication', 'No role differentiation; anyone with the key can approve decisions', true),
458
+ makeAlt('Audit logging without RBAC', 'Records who did what but cannot prevent unauthorized actions', true),
254
459
  ],
255
460
  consequences: [
256
- makeCons(`Risk "${risk.title.slice(0, 40)}" mitigated with domain-specific controls`, 'positive'),
257
- makeCons('Requires validation logic in the affected service', 'negative'),
461
+ makeCons(`Full decision traceability for ${uniqueTerms[0] ?? 'regulatory'} compliance`, 'positive'),
462
+ makeCons('No automated execution without human approval', 'positive'),
463
+ makeCons('Role definition and permission matrix must be maintained as organization evolves', 'negative'),
464
+ makeCons('Immutable audit log storage grows continuously — requires archival strategy', 'negative'),
258
465
  ],
259
- sparcSectionRefs: [],
260
- researchDossierItemRefs: [risk.id],
466
+ sparcSectionRefs: ['refinement.securityConsiderations'],
467
+ researchDossierItemRefs: findDossierRefs(dossier, ['security', 'auth', 'access', 'compliance', 'audit', 'governance']),
261
468
  });
262
469
  }
263
- // 5. Compliance ADR — reference the specific compliance requirements
264
- const complianceRefs = findDossierRefs(dossier, ['compliance', 'regulatory', 'gdpr', 'hipaa', 'pci', 'privacy', 'audit', 'governance']);
265
- if (complianceRefs.length > 0) {
266
- const complianceContext = scenarioQuery
267
- ? (scenarioQuery.match(/audit|compliance|regulatory|governance|disclosure|reporting/gi) ?? []).join(', ')
268
- : '';
470
+ // ======================================================================
471
+ // SPARC §R Refinement: Performance ADR (if Rust or compute-intensive)
472
+ // ======================================================================
473
+ const perfText = itemsToText(perfItems);
474
+ if (perfText || techStack.includes('Rust')) {
269
475
  adrs.push({
270
476
  id: `ADR-P2-${pad3(idx++)}`,
271
- title: `Compliance: ${complianceContext.slice(0, 60) || 'Audit Trail and Regulatory Requirements'}`,
477
+ title: `Performance: ${techStack.includes('Rust') ? 'Rust compute engine + TypeScript orchestration' : 'Optimization strategy for ' + domain.primary}`,
272
478
  status: 'proposed',
273
479
  date: today,
274
- context: `${complianceRefs.length} compliance-related items identified. ${complianceContext ? 'The project specifically requires: ' + complianceContext + '.' : ''}`,
275
- decision: `Implement immutable audit logging for all state-changing operations. Each service must record who, what, when, and why for every domain action. ${complianceContext.includes('governance') ? 'Include human-in-the-loop approval workflows for high-impact decisions.' : ''}`,
480
+ context: `${perfText ? 'Performance requirements: ' + perfText + '. ' : ''}` +
481
+ `${techStack.includes('Rust') ? 'The user specified Rust for compute-intensive components and TypeScript for orchestration. ' : ''}` +
482
+ `${hasSimulation ? 'Simulation/optimization workloads require bounded execution time for interactive use.' : ''}`,
483
+ decision: `${techStack.includes('Rust') ? 'Split computation: Rust handles graph traversal, constraint solving, and numerical optimization. TypeScript handles API routing, workflow orchestration, and result formatting. Communication via typed JSON over stdin/stdout (subprocess) or FFI (napi-rs). ' : ''}` +
484
+ `All compute operations have configurable timeouts. Results include execution time metadata. ` +
485
+ `${hasSimulation ? 'Simulation parameter space is bounded (max iterations, max graph depth) to prevent runaway computation.' : ''}`,
276
486
  alternatives: [
277
- makeAlt('Immutable audit log per service', 'Complete traceability', false),
278
- makeAlt('Centralized logging only', 'Gaps in coverage', true),
487
+ makeAlt(techStack.includes('Rust') ? 'Rust engine + TypeScript orchestration' : 'Targeted optimization of critical paths', 'Best performance where it matters; orchestration stays in TypeScript for productivity', false),
488
+ makeAlt('Pure TypeScript', techStack.includes('Rust') ? 'Simpler build but 10-100x slower for graph/numeric workloads' : 'Simpler but may not meet latency targets', true),
279
489
  ],
280
490
  consequences: [
281
- makeCons('Full audit trail for compliance and regulatory review', 'positive'),
282
- makeCons('Storage and query overhead for audit data', 'negative'),
491
+ makeCons(techStack.includes('Rust') ? 'Compute-intensive operations run at native speed' : 'Critical paths are optimized based on profiling data', 'positive'),
492
+ makeCons(techStack.includes('Rust') ? 'Dual-language build pipeline increases CI/CD complexity' : 'Optimization effort must be validated with benchmarks', 'negative'),
283
493
  ],
284
- sparcSectionRefs: [],
285
- researchDossierItemRefs: complianceRefs,
494
+ sparcSectionRefs: ['refinement.performanceTargets'],
495
+ researchDossierItemRefs: findDossierRefs(dossier, ['performance', 'latency', 'throughput', 'rust', 'compute']),
286
496
  });
287
497
  }
288
- // 6. Data persistence ADR
289
- const dataRefs = findDossierRefs(dossier, ['data', 'database', 'storage', 'persistence']);
290
- if (dataRefs.length > 0) {
498
+ // ======================================================================
499
+ // SPARC §R Refinement: High-severity risk ADRs from dossier
500
+ // ======================================================================
501
+ const highRisks = dossier.items.filter(i => i.category === 'risk' && i.severity && /high|critical/i.test(i.severity));
502
+ for (const risk of highRisks.slice(0, 2)) {
291
503
  adrs.push({
292
504
  id: `ADR-P2-${pad3(idx++)}`,
293
- title: 'Data Persistence Strategy',
505
+ title: `Risk Mitigation: ${risk.title.slice(0, 70)}`,
294
506
  status: 'proposed',
295
507
  date: today,
296
- context: `${dataRefs.length} data-related items. Services need isolated data stores.`,
297
- decision: 'Each bounded context owns its data store. Event sourcing for audit-critical domains.',
508
+ context: risk.content,
509
+ decision: `Mitigate "${risk.title}" with: (1) input validation at the service boundary where this risk originates, ` +
510
+ `(2) monitoring and alerting for early detection of the failure mode, ` +
511
+ `(3) graceful degradation path that preserves data integrity when the risk materializes. ` +
512
+ `Document specific controls in the affected service's test suite.`,
298
513
  alternatives: [
299
- makeAlt('Context-owned stores', 'Strong isolation', false),
300
- makeAlt('Shared database', 'Simpler but coupling risk', true),
514
+ makeAlt('Active boundary controls + monitoring + degradation', 'Prevents impact; detects early; degrades gracefully', false),
515
+ makeAlt('Accept risk and monitor', 'Lower effort but reactive — damage occurs before detection', true),
301
516
  ],
302
517
  consequences: [
303
- makeCons('Data isolation per bounded context', 'positive'),
304
- makeCons('Cross-context queries need API/events', 'negative'),
518
+ makeCons(`"${risk.title.slice(0, 40)}" mitigated with layered controls`, 'positive'),
519
+ makeCons('Mitigation logic adds complexity to the affected service boundary', 'negative'),
305
520
  ],
306
- sparcSectionRefs: ['architecture.services'],
307
- researchDossierItemRefs: dataRefs,
521
+ sparcSectionRefs: [],
522
+ researchDossierItemRefs: [risk.id],
308
523
  });
309
524
  }
310
- // 7. Inter-service communication
311
- if (services.length > 1) {
525
+ // ======================================================================
526
+ // SPARC §C Completion: Deployment & Integration ADR
527
+ // ======================================================================
528
+ const deployTech = techStack.filter(t => /cloud run|cloud function|lambda|fargate|ecs|kubernetes|k8s|docker/i.test(t));
529
+ if (deployTech.length > 0 || cloudProvider) {
312
530
  adrs.push({
313
531
  id: `ADR-P2-${pad3(idx++)}`,
314
- title: 'Inter-Service Communication Pattern',
532
+ title: `Deployment: ${deployTech.join(' + ') || cloudProvider || 'Cloud'} with environment-based configuration`,
315
533
  status: 'proposed',
316
534
  date: today,
317
- context: `${services.length} services need communication pattern.`,
318
- decision: 'Sync REST/gRPC for queries, async events for commands and domain events.',
535
+ context: `${domain.primary} services deploy to ${cloudProvider ?? 'cloud infrastructure'}. ` +
536
+ `${deployTech.length > 0 ? 'Specified: ' + deployTech.join(', ') + '. ' : ''}` +
537
+ `Services must be stateless (state in databases), independently deployable, and environment-configurable.`,
538
+ decision: `Deploy each service as a stateless container on ${deployTech[0] || cloudProvider || 'managed cloud compute'}. ` +
539
+ `All configuration via environment variables (database URLs, API keys, feature flags) — zero hardcoded secrets. ` +
540
+ `Health checks on every service endpoint. Structured JSON logging to cloud logging service. ` +
541
+ `${integrationPlan ? 'Integration plan: ' + integrationPlan.slice(0, 150) + '.' : ''}`,
319
542
  alternatives: [
320
- makeAlt('Hybrid sync+async', 'Balanced approach', false),
321
- makeAlt('All synchronous', 'Simple but cascading failures', true),
322
- makeAlt('All async', 'Loose coupling but complex debugging', true),
543
+ makeAlt(`${deployTech[0] || 'Serverless'} with env-based config`, 'Matches specified infrastructure; scales automatically', false),
544
+ makeAlt('VM-based deployment', 'More control but higher operational overhead', true),
323
545
  ],
324
546
  consequences: [
325
- makeCons('Sync for reads, async for writes — balanced', 'positive'),
326
- makeCons('Need both REST and messaging infrastructure', 'negative'),
547
+ makeCons('Zero-downtime deployments with rolling updates', 'positive'),
548
+ makeCons('Environment parity (dev/staging/prod) via configuration only', 'positive'),
549
+ makeCons('Cold start latency for serverless (mitigated with min instances)', 'negative'),
327
550
  ],
328
- sparcSectionRefs: ['architecture.dataFlow'],
329
- researchDossierItemRefs: [],
551
+ sparcSectionRefs: ['completion.integrationPlan'],
552
+ researchDossierItemRefs: findDossierRefs(dossier, ['deploy', 'cloud', 'infrastructure', 'container']),
330
553
  });
331
554
  }
332
- // Fallback: at least one ADR
555
+ // Fallback: at least one ADR (should never fire with a real query)
333
556
  if (adrs.length === 0) {
334
557
  adrs.push({
335
558
  id: 'ADR-P2-001',
336
- title: 'System Architecture Approach',
559
+ title: `${domain.primary} System Architecture`,
337
560
  status: 'proposed',
338
561
  date: today,
339
- context: 'Initial architecture decision based on SPARC specification.',
340
- decision: 'Build modular system with clear service boundaries, TDD, and iterative refinement.',
562
+ context: q.slice(0, 500) || 'Initial architecture decision based on SPARC specification.',
563
+ decision: `Build modular ${domain.primary} system with bounded contexts, domain events, and iterative delivery. Start with working prototype.`,
341
564
  alternatives: [
342
- makeAlt('Modular architecture', 'Follows SPARC methodology', false),
343
- makeAlt('Monolithic', 'Simpler but harder to scale', true),
565
+ makeAlt('Modular bounded contexts', 'Follows SPARC methodology; enables incremental delivery', false),
566
+ makeAlt('Monolithic', 'Simpler initially but limits independent evolution', true),
344
567
  ],
345
- consequences: [makeCons('Clean architecture', 'positive')],
568
+ consequences: [makeCons('Clean architecture enables iterative refinement', 'positive')],
346
569
  sparcSectionRefs: [],
347
570
  researchDossierItemRefs: [],
348
571
  });
349
572
  }
350
573
  return adrs;
351
574
  }
352
- export function buildPhase2ADRs(sparc, dossier) {
353
- return buildADRsFromSPARC(sparc, dossier);
575
+ function extractDomainContext(query) {
576
+ // Extract primary domain — look for noun phrases that describe the system's purpose
577
+ let primary = 'System';
578
+ const domainPatterns = [
579
+ /(?:global|enterprise)\s+([\w\s-]{5,60}?)(?:\s+(?:strategy|program|platform|system|initiative))/i,
580
+ /(?:help\s+(?:model|optimize|manage|simulate|build))\s+(.{10,60}?)(?:\.|,|$)/i,
581
+ /(?:challenge.*?is|goal.*?is)\s+(.{10,60}?)(?:\.|,|$)/i,
582
+ /\b((?:supplier|waste|emission|carbon|inventory|financial|clinical|manufacturing|procurement|supply chain)\s+[\w\s-]{3,40}?(?:optimization|management|simulation|tracking|analysis|modeling|reduction|transition))/i,
583
+ ];
584
+ for (const pat of domainPatterns) {
585
+ const m = query.match(pat);
586
+ if (m) {
587
+ primary = (m[1] ?? m[0]).trim().replace(/^(a|an|the)\s+/i, '');
588
+ primary = primary.charAt(0).toUpperCase() + primary.slice(1);
589
+ if (primary.length > 60)
590
+ primary = primary.slice(0, 57) + '...';
591
+ break;
592
+ }
593
+ }
594
+ if (primary === 'System') {
595
+ const fallback = query.match(/\b((?:global|enterprise|manufacturing|waste|carbon|emission|clinical|financial|supply|supplier|inventory)\s+\w+(?:\s+\w+){0,2})/i);
596
+ if (fallback?.[1])
597
+ primary = fallback[1].charAt(0).toUpperCase() + fallback[1].slice(1);
598
+ }
599
+ // ERP detection
600
+ let erp = null;
601
+ const erpPatterns = [
602
+ [/\bworkday\b/i, 'Workday'],
603
+ [/\boracle\s+fusion\s+cloud\b/i, 'Oracle Fusion Cloud'],
604
+ [/\boracle\s+(?:erp|cloud)\b/i, 'Oracle ERP Cloud'],
605
+ [/\bsap\s+s\/4\s*hana\b/i, 'SAP S/4HANA'],
606
+ [/\bsap\b/i, 'SAP'],
607
+ [/\bdynamics\s*365\b/i, 'Dynamics 365'],
608
+ [/\bnetsuite\b/i, 'NetSuite'],
609
+ ];
610
+ for (const [pat, name] of erpPatterns) {
611
+ if (pat.test(query)) {
612
+ erp = name;
613
+ break;
614
+ }
615
+ }
616
+ // Stakeholders
617
+ const stakeholderMatch = query.match(/(?:affect|impact|involve)\s+(.{10,120}?)(?:\.|$)/i);
618
+ const matchedStakeholders = (query.match(/(?:procurement|sustainability|operations|executive|leadership|compliance|regulatory)\s+(?:team|leadership|management)/gi) ?? []).join(', ');
619
+ const stakeholders = stakeholderMatch?.[1]?.trim()
620
+ ?? (matchedStakeholders || 'operational teams and leadership');
621
+ return { primary, erp, stakeholders };
622
+ }
623
+ function extractTechFromQuery(query) {
624
+ const techs = [];
625
+ const patterns = [
626
+ [/\btypescript\b/i, 'TypeScript'], [/\brust\b/i, 'Rust'], [/\bpython\b/i, 'Python'],
627
+ [/\bgoogle cloud\b/i, 'Google Cloud'], [/\bcloud run\b/i, 'Cloud Run'], [/\bcloud functions?\b/i, 'Cloud Functions'],
628
+ [/\baws\b/i, 'AWS'], [/\bazure\b/i, 'Azure'],
629
+ [/\bpostgres(?:ql)?\b/i, 'PostgreSQL'], [/\bcloud sql\b/i, 'Cloud SQL (PostgreSQL)'],
630
+ [/\bbigquery\b/i, 'BigQuery'], [/\bmongodb\b/i, 'MongoDB'], [/\bredis\b/i, 'Redis'],
631
+ [/\bkafka\b/i, 'Kafka'], [/\brabbitmq\b/i, 'RabbitMQ'],
632
+ [/\bkubernetes\b|\bk8s\b/i, 'Kubernetes'], [/\bdocker\b/i, 'Docker'],
633
+ ];
634
+ for (const [pat, name] of patterns) {
635
+ if (pat.test(query) && !techs.includes(name))
636
+ techs.push(name);
637
+ }
638
+ return techs;
639
+ }
640
+ function itemsToText(items) {
641
+ return items.slice(0, 3).map(s => {
642
+ if (typeof s === 'string')
643
+ return s;
644
+ if (s && typeof s === 'object') {
645
+ const obj = s;
646
+ return String(obj['text'] ?? obj['description'] ?? JSON.stringify(s));
647
+ }
648
+ return String(s);
649
+ }).join('; ');
650
+ }
651
+ export function buildPhase2ADRs(sparc, dossier, query) {
652
+ // Try LLM-generated domain-specific ADRs first, fall back to SPARC-derived templates
653
+ const adrs = (query ? tryGenerateLLMADRs(sparc, dossier, query) : null)
654
+ ?? buildADRsFromSPARC(sparc, dossier, query);
655
+ return adrs;
656
+ }
657
+ /**
658
+ * Render a list of ADR records as a single markdown document.
659
+ * Used by ask-artifact-writer to produce engineering/architecture-decisions.md
660
+ */
661
+ export function renderADRsAsDocument(adrs, query) {
662
+ const lines = [];
663
+ const now = new Date().toISOString();
664
+ lines.push('# Architecture Decision Records');
665
+ lines.push('');
666
+ lines.push(`> Generated: ${now}`);
667
+ lines.push(`> Project: ${query.slice(0, 200)}${query.length > 200 ? '...' : ''}`);
668
+ lines.push(`> Derived from: SPARC specification analysis`);
669
+ lines.push('');
670
+ for (const adr of adrs) {
671
+ lines.push(renderADRMarkdown(adr));
672
+ lines.push('---');
673
+ lines.push('');
674
+ }
675
+ lines.push(`*${adrs.length} ADRs derived from SPARC analysis. Review with the team before implementation.*`);
676
+ return lines.join('\n');
354
677
  }
355
678
  // ============================================================================
356
679
  // ADR-027: LLM-Generated Architecture Decision Records