@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.
- package/dist/pipeline/phase2/phases/adr-generator.d.ts +6 -1
- package/dist/pipeline/phase2/phases/adr-generator.d.ts.map +1 -1
- package/dist/pipeline/phase2/phases/adr-generator.js +423 -100
- package/dist/pipeline/phase2/phases/adr-generator.js.map +1 -1
- package/dist/synthesis/ask-artifact-writer.d.ts.map +1 -1
- package/dist/synthesis/ask-artifact-writer.js +183 -99
- package/dist/synthesis/ask-artifact-writer.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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: `
|
|
184
|
+
title: `Domain Model: ${domain.primary} entity graph with ${entityNames.length} bounded contexts`,
|
|
179
185
|
status: 'proposed',
|
|
180
186
|
date: today,
|
|
181
|
-
context: `The
|
|
182
|
-
|
|
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(
|
|
185
|
-
makeAlt('
|
|
186
|
-
makeAlt('
|
|
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(
|
|
190
|
-
makeCons(
|
|
191
|
-
makeCons('Requires
|
|
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
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
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:
|
|
369
|
+
title: `${domain.erp} Integration: Anti-corruption layer with governance-gated writeback`,
|
|
203
370
|
status: 'proposed',
|
|
204
371
|
date: today,
|
|
205
|
-
context:
|
|
206
|
-
|
|
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(
|
|
209
|
-
makeAlt(
|
|
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('
|
|
213
|
-
makeCons(
|
|
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: ['
|
|
216
|
-
researchDossierItemRefs: findDossierRefs(dossier, [
|
|
389
|
+
sparcSectionRefs: ['architecture.services'],
|
|
390
|
+
researchDossierItemRefs: findDossierRefs(dossier, [domain.erp.toLowerCase(), 'erp', 'integration']),
|
|
217
391
|
});
|
|
218
392
|
}
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
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: `
|
|
401
|
+
title: `Data Persistence: ${[...databases, cloudProvider].filter(Boolean).join(' + ')} for ${domain.primary}`,
|
|
225
402
|
status: 'proposed',
|
|
226
403
|
date: today,
|
|
227
|
-
context:
|
|
228
|
-
|
|
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('
|
|
231
|
-
makeAlt('
|
|
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('
|
|
235
|
-
makeCons('
|
|
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: ['
|
|
238
|
-
researchDossierItemRefs: findDossierRefs(dossier, ['
|
|
429
|
+
sparcSectionRefs: ['architecture.services'],
|
|
430
|
+
researchDossierItemRefs: findDossierRefs(dossier, ['data', 'database', 'storage', 'persistence', ...databases.map(d => d.toLowerCase())]),
|
|
239
431
|
});
|
|
240
432
|
}
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
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: `
|
|
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:
|
|
250
|
-
|
|
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('
|
|
253
|
-
makeAlt('
|
|
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(`
|
|
257
|
-
makeCons('
|
|
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: [
|
|
466
|
+
sparcSectionRefs: ['refinement.securityConsiderations'],
|
|
467
|
+
researchDossierItemRefs: findDossierRefs(dossier, ['security', 'auth', 'access', 'compliance', 'audit', 'governance']),
|
|
261
468
|
});
|
|
262
469
|
}
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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: `
|
|
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: `${
|
|
275
|
-
|
|
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('
|
|
278
|
-
makeAlt('
|
|
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('
|
|
282
|
-
makeCons('
|
|
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:
|
|
494
|
+
sparcSectionRefs: ['refinement.performanceTargets'],
|
|
495
|
+
researchDossierItemRefs: findDossierRefs(dossier, ['performance', 'latency', 'throughput', 'rust', 'compute']),
|
|
286
496
|
});
|
|
287
497
|
}
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
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:
|
|
505
|
+
title: `Risk Mitigation: ${risk.title.slice(0, 70)}`,
|
|
294
506
|
status: 'proposed',
|
|
295
507
|
date: today,
|
|
296
|
-
context:
|
|
297
|
-
decision:
|
|
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('
|
|
300
|
-
makeAlt('
|
|
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(
|
|
304
|
-
makeCons('
|
|
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: [
|
|
307
|
-
researchDossierItemRefs:
|
|
521
|
+
sparcSectionRefs: [],
|
|
522
|
+
researchDossierItemRefs: [risk.id],
|
|
308
523
|
});
|
|
309
524
|
}
|
|
310
|
-
//
|
|
311
|
-
|
|
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: '
|
|
532
|
+
title: `Deployment: ${deployTech.join(' + ') || cloudProvider || 'Cloud'} with environment-based configuration`,
|
|
315
533
|
status: 'proposed',
|
|
316
534
|
date: today,
|
|
317
|
-
context: `${
|
|
318
|
-
|
|
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(
|
|
321
|
-
makeAlt('
|
|
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('
|
|
326
|
-
makeCons('
|
|
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: ['
|
|
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:
|
|
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:
|
|
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
|
|
343
|
-
makeAlt('Monolithic', 'Simpler but
|
|
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
|
-
|
|
353
|
-
|
|
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
|