dhurandhar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.dhurandhar-session-start.md +242 -0
  2. package/LICENSE +21 -0
  3. package/README.md +416 -0
  4. package/docs/ARCHITECTURE_V2.md +249 -0
  5. package/docs/DECISION_REGISTRY.md +357 -0
  6. package/docs/IMPLEMENTATION_PERSONAS.md +406 -0
  7. package/docs/PLUGGABLE_STRATEGIES.md +439 -0
  8. package/docs/SYSTEM_OBSERVER.md +433 -0
  9. package/docs/TEST_FIRST_AGILE.md +359 -0
  10. package/docs/architecture.md +279 -0
  11. package/docs/engineering-first-philosophy.md +263 -0
  12. package/docs/getting-started.md +218 -0
  13. package/docs/module-development.md +323 -0
  14. package/docs/strategy-example.md +299 -0
  15. package/docs/test-first-example.md +392 -0
  16. package/package.json +79 -0
  17. package/src/core/README.md +92 -0
  18. package/src/core/agent-instructions/backend-developer.md +412 -0
  19. package/src/core/agent-instructions/devops-engineer.md +372 -0
  20. package/src/core/agent-instructions/dhurandhar-council.md +547 -0
  21. package/src/core/agent-instructions/edge-case-hunter.md +322 -0
  22. package/src/core/agent-instructions/frontend-developer.md +494 -0
  23. package/src/core/agent-instructions/lead-system-architect.md +631 -0
  24. package/src/core/agent-instructions/system-observer.md +319 -0
  25. package/src/core/agent-instructions/test-architect.md +284 -0
  26. package/src/core/module.yaml +54 -0
  27. package/src/core/schemas/design-module-schema.yaml +995 -0
  28. package/src/core/schemas/system-design-map-schema.yaml +324 -0
  29. package/src/modules/example/README.md +130 -0
  30. package/src/modules/example/module.yaml +252 -0
  31. package/tools/cli/commands/audit.js +267 -0
  32. package/tools/cli/commands/config.js +113 -0
  33. package/tools/cli/commands/context.js +170 -0
  34. package/tools/cli/commands/decisions.js +398 -0
  35. package/tools/cli/commands/entity.js +218 -0
  36. package/tools/cli/commands/epic.js +125 -0
  37. package/tools/cli/commands/install.js +172 -0
  38. package/tools/cli/commands/module.js +109 -0
  39. package/tools/cli/commands/service.js +167 -0
  40. package/tools/cli/commands/story.js +225 -0
  41. package/tools/cli/commands/strategy.js +294 -0
  42. package/tools/cli/commands/test.js +277 -0
  43. package/tools/cli/commands/validate.js +107 -0
  44. package/tools/cli/dhurandhar.js +212 -0
  45. package/tools/lib/config-manager.js +170 -0
  46. package/tools/lib/filesystem.js +126 -0
  47. package/tools/lib/module-installer.js +61 -0
  48. package/tools/lib/module-manager.js +149 -0
  49. package/tools/lib/sdm-manager.js +982 -0
  50. package/tools/lib/test-engine.js +255 -0
  51. package/tools/lib/test-templates/api-client.template.js +100 -0
  52. package/tools/lib/test-templates/vitest.config.template.js +37 -0
  53. package/tools/lib/validators/config-validator.js +113 -0
  54. package/tools/lib/validators/module-validator.js +137 -0
@@ -0,0 +1,982 @@
1
+ /**
2
+ * System Design Map (SDM) Manager
3
+ * Handles persistent architectural state for cross-session context rehydration
4
+ *
5
+ * Purpose: Eliminate cognitive fatigue by maintaining architectural context
6
+ * between sessions without requiring rediscovery
7
+ */
8
+
9
+ import { join } from 'path';
10
+ import { existsSync } from 'fs';
11
+ import { readFile, writeFile } from 'fs/promises';
12
+ import yaml from 'yaml';
13
+
14
+ export class SDMManager {
15
+ constructor(projectRoot) {
16
+ this.projectRoot = projectRoot;
17
+ this.sdmPath = join(projectRoot, 'SYSTEM_DESIGN_MAP.yaml');
18
+ }
19
+
20
+ /**
21
+ * Check if SDM exists
22
+ */
23
+ exists() {
24
+ return existsSync(this.sdmPath);
25
+ }
26
+
27
+ /**
28
+ * Initialize new SDM
29
+ */
30
+ async initialize(projectName = 'new-project') {
31
+ const sdm = {
32
+ metadata: {
33
+ project_name: projectName,
34
+ version: '1.0.0',
35
+ last_updated: new Date().toISOString(),
36
+ architecture_type: 'microservices', // or monolith, serverless
37
+ },
38
+ tech_stack: {
39
+ languages: [],
40
+ frameworks: [],
41
+ databases: [],
42
+ infrastructure: [],
43
+ },
44
+ user_types: [],
45
+ entities: [],
46
+ services: [],
47
+ communication: [],
48
+ infrastructure: {
49
+ deployment_target: null,
50
+ components: [],
51
+ },
52
+ build_config: [],
53
+ // NEW: Agile Blueprint for Test-First
54
+ agile_blueprint: {
55
+ epics: [],
56
+ test_suite_status: {
57
+ total_stories: 0,
58
+ tested_stories: 0,
59
+ coverage_percentage: 0,
60
+ last_test_run: null,
61
+ },
62
+ },
63
+ // NEW: Technical Strategies - Pluggable Architectural Patterns
64
+ technical_strategies: {
65
+ persistence: {
66
+ model: null,
67
+ constraints: [],
68
+ database_assignments: {},
69
+ },
70
+ state_management: {
71
+ caching_layer: null,
72
+ session_storage: null,
73
+ cache_invalidation: null,
74
+ },
75
+ communication: {
76
+ primary_pattern: null,
77
+ event_bus: null,
78
+ api_gateway: null,
79
+ },
80
+ deployment: {
81
+ orchestration: null,
82
+ scaling_strategy: null,
83
+ service_mesh: { enabled: false, technology: 'none' },
84
+ },
85
+ observability: {
86
+ logging: null,
87
+ metrics: null,
88
+ tracing: null,
89
+ distributed_tracing: false,
90
+ },
91
+ security: {
92
+ authentication: null,
93
+ authorization: null,
94
+ secrets_management: null,
95
+ },
96
+ resilience: {
97
+ circuit_breaker: false,
98
+ retry_strategy: null,
99
+ timeout_policy: null,
100
+ bulkhead_isolation: false,
101
+ },
102
+ },
103
+ };
104
+
105
+ await this.save(sdm);
106
+ return sdm;
107
+ }
108
+
109
+ /**
110
+ * Load SDM for rehydration
111
+ */
112
+ async load() {
113
+ if (!this.exists()) {
114
+ throw new Error('System Design Map not found. Run "dhurandhar install" first.');
115
+ }
116
+
117
+ const content = await readFile(this.sdmPath, 'utf-8');
118
+ return yaml.parse(content);
119
+ }
120
+
121
+ /**
122
+ * Save SDM
123
+ */
124
+ async save(sdm) {
125
+ // Update timestamp
126
+ sdm.metadata.last_updated = new Date().toISOString();
127
+
128
+ const content = yaml.stringify(sdm, {
129
+ indent: 2,
130
+ lineWidth: 0,
131
+ });
132
+
133
+ await writeFile(this.sdmPath, content, 'utf-8');
134
+ }
135
+
136
+ /**
137
+ * Set architectural strategy
138
+ */
139
+ async setStrategy(category, strategy) {
140
+ const sdm = await this.load();
141
+
142
+ if (!sdm.technical_strategies) {
143
+ sdm.technical_strategies = {};
144
+ }
145
+
146
+ if (!sdm.technical_strategies[category]) {
147
+ sdm.technical_strategies[category] = {};
148
+ }
149
+
150
+ // Merge strategy into category
151
+ Object.assign(sdm.technical_strategies[category], strategy);
152
+
153
+ await this.save(sdm);
154
+ return sdm;
155
+ }
156
+
157
+ /**
158
+ * Get active strategies
159
+ */
160
+ async getStrategies() {
161
+ const sdm = await this.load();
162
+ return sdm.technical_strategies || {};
163
+ }
164
+
165
+ /**
166
+ * Get decision registry
167
+ */
168
+ async getDecisions() {
169
+ const sdm = await this.load();
170
+ return sdm.decision_registry || {};
171
+ }
172
+
173
+ /**
174
+ * Set decision category
175
+ */
176
+ async setDecision(category, decisions) {
177
+ const sdm = await this.load();
178
+
179
+ if (!sdm.decision_registry) {
180
+ sdm.decision_registry = {};
181
+ }
182
+
183
+ sdm.decision_registry[category] = decisions;
184
+
185
+ await this.save(sdm);
186
+ return sdm;
187
+ }
188
+
189
+ /**
190
+ * Get rehydration context (including decisions)
191
+ */
192
+ async getRehydrationContext() {
193
+ const sdm = await this.load();
194
+
195
+ return {
196
+ metadata: sdm.metadata || {},
197
+ tech_stack: sdm.tech_stack || {},
198
+ decision_registry: sdm.decision_registry || {}, // NEW
199
+ technical_strategies: sdm.technical_strategies || {},
200
+ services: sdm.services || [],
201
+ entities: sdm.entities || [],
202
+ agile_blueprint: sdm.agile_blueprint || {},
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Apply strategy constraints to service
208
+ */
209
+ applyStrategyConstraints(service, strategies) {
210
+ // Clone service to avoid mutation
211
+ const enhancedService = { ...service };
212
+
213
+ // Persistence Strategy: Database-per-Service
214
+ if (strategies.persistence?.model === 'database_per_service') {
215
+ if (!enhancedService.dedicated_database) {
216
+ enhancedService.dedicated_database = {
217
+ name: `${service.name.replace('-service', '')}_db`,
218
+ type: service.tech_stack?.database || 'PostgreSQL',
219
+ isolation: 'full',
220
+ };
221
+ }
222
+
223
+ // Add constraint: No cross-service queries
224
+ if (!enhancedService.constraints) enhancedService.constraints = [];
225
+ if (!enhancedService.constraints.includes('no_cross_service_queries')) {
226
+ enhancedService.constraints.push('no_cross_service_queries');
227
+ }
228
+ }
229
+
230
+ // Communication Strategy: Event-Driven
231
+ if (strategies.communication?.primary_pattern === 'asynchronous_events') {
232
+ if (!enhancedService.event_boundaries) {
233
+ enhancedService.event_boundaries = {
234
+ produces: [],
235
+ consumes: [],
236
+ event_bus: strategies.communication.event_bus?.technology || 'kafka',
237
+ };
238
+ }
239
+ }
240
+
241
+ // State Management: Distributed Cache
242
+ if (strategies.state_management?.caching_layer === 'distributed_redis') {
243
+ if (!enhancedService.dependencies) enhancedService.dependencies = [];
244
+ if (!enhancedService.dependencies.includes('redis')) {
245
+ enhancedService.dependencies.push('redis');
246
+ }
247
+
248
+ enhancedService.cache_config = {
249
+ type: 'redis',
250
+ strategy: strategies.state_management.cache_invalidation || 'ttl_based',
251
+ };
252
+ }
253
+
254
+ // Resilience: Circuit Breaker
255
+ if (strategies.resilience?.circuit_breaker) {
256
+ enhancedService.resilience = {
257
+ circuit_breaker: {
258
+ enabled: true,
259
+ failure_threshold: 5,
260
+ timeout: 30000,
261
+ },
262
+ retry: {
263
+ strategy: strategies.resilience.retry_strategy || 'exponential_backoff',
264
+ max_attempts: 3,
265
+ },
266
+ timeout: strategies.resilience.timeout_policy || 'moderate_30s',
267
+ };
268
+ }
269
+
270
+ // Security: Authentication
271
+ if (strategies.security?.authentication === 'jwt_centralized') {
272
+ if (!enhancedService.dependencies) enhancedService.dependencies = [];
273
+ if (!enhancedService.dependencies.includes('auth-service')) {
274
+ enhancedService.dependencies.push('auth-service');
275
+ }
276
+
277
+ enhancedService.authentication = {
278
+ type: 'jwt',
279
+ validation: 'centralized',
280
+ token_endpoint: '/api/v1/auth/validate',
281
+ };
282
+ }
283
+
284
+ return enhancedService;
285
+ }
286
+
287
+ /**
288
+ * Apply decision registry constraints to service
289
+ */
290
+ applyDecisionConstraints(service, decisions) {
291
+ if (!decisions) return service;
292
+
293
+ const enhancedService = { ...service };
294
+
295
+ // Add decision constraints as metadata
296
+ enhancedService._decision_constraints = {};
297
+
298
+ // Naming conventions
299
+ if (decisions.naming_conventions) {
300
+ enhancedService._decision_constraints.naming = {
301
+ case_styles: decisions.naming_conventions.case_styles || {},
302
+ file_patterns: decisions.naming_conventions.file_patterns || {},
303
+ };
304
+ }
305
+
306
+ // Design patterns
307
+ if (decisions.design_patterns) {
308
+ enhancedService._decision_constraints.patterns = {
309
+ data_access: decisions.design_patterns.data_access,
310
+ dependency_injection: decisions.design_patterns.dependency_injection,
311
+ validation: decisions.design_patterns.validation,
312
+ };
313
+
314
+ // Check for service-specific patterns
315
+ const servicePattern = decisions.design_patterns.service_specific_patterns?.find(
316
+ p => p.service === service.name
317
+ );
318
+ if (servicePattern) {
319
+ enhancedService._decision_constraints.patterns.service_specific = servicePattern.pattern;
320
+ }
321
+ }
322
+
323
+ // Error standards
324
+ if (decisions.error_response_standards) {
325
+ enhancedService._decision_constraints.errors = {
326
+ code_mapping: decisions.error_response_standards.error_code_mapping || {},
327
+ response_envelope: decisions.error_response_standards.response_envelope || {},
328
+ };
329
+ }
330
+
331
+ // Observability standards
332
+ if (decisions.observability_standards) {
333
+ enhancedService._decision_constraints.observability = {
334
+ logging: decisions.observability_standards.logging || {},
335
+ tracing: decisions.observability_standards.tracing || {},
336
+ metrics: decisions.observability_standards.metrics || {},
337
+ };
338
+ }
339
+
340
+ return enhancedService;
341
+ }
342
+
343
+ /**
344
+ * Add a service to SDM (with strategy and decision injection)
345
+ */
346
+ async addService(service) {
347
+ const sdm = await this.load();
348
+
349
+ // Apply global strategy constraints
350
+ const strategies = sdm.technical_strategies || {};
351
+ let enhancedService = this.applyStrategyConstraints(service, strategies);
352
+
353
+ // Apply decision registry constraints
354
+ const decisions = sdm.decision_registry || {};
355
+ enhancedService = this.applyDecisionConstraints(enhancedService, decisions);
356
+
357
+ // Check if service already exists
358
+ const existingIndex = sdm.services.findIndex(s => s.name === service.name);
359
+
360
+ if (existingIndex >= 0) {
361
+ // Update existing
362
+ sdm.services[existingIndex] = { ...sdm.services[existingIndex], ...enhancedService };
363
+ } else {
364
+ // Add new
365
+ sdm.services.push(enhancedService);
366
+ }
367
+
368
+ // Update tech stack summary
369
+ this.updateTechStackSummary(sdm, enhancedService);
370
+
371
+ await this.save(sdm);
372
+ return sdm;
373
+ }
374
+
375
+ /**
376
+ * Add an entity to SDM
377
+ */
378
+ async addEntity(entity) {
379
+ const sdm = await this.load();
380
+
381
+ const existingIndex = sdm.entities.findIndex(e => e.name === entity.name);
382
+
383
+ if (existingIndex >= 0) {
384
+ sdm.entities[existingIndex] = { ...sdm.entities[existingIndex], ...entity };
385
+ } else {
386
+ sdm.entities.push(entity);
387
+ }
388
+
389
+ await this.save(sdm);
390
+ return sdm;
391
+ }
392
+
393
+ /**
394
+ * Add service communication pattern
395
+ */
396
+ async addCommunication(from, to, protocol, pattern = 'sync') {
397
+ const sdm = await this.load();
398
+
399
+ const comm = { from, to, protocol, pattern };
400
+
401
+ // Remove duplicate if exists
402
+ sdm.communication = sdm.communication.filter(
403
+ c => !(c.from === from && c.to === to)
404
+ );
405
+
406
+ sdm.communication.push(comm);
407
+
408
+ await this.save(sdm);
409
+ return sdm;
410
+ }
411
+
412
+ /**
413
+ * Add Epic to Agile Blueprint
414
+ */
415
+ async addEpic(epic) {
416
+ const sdm = await this.load();
417
+
418
+ if (!sdm.agile_blueprint) {
419
+ sdm.agile_blueprint = { epics: [], test_suite_status: {} };
420
+ }
421
+ if (!sdm.agile_blueprint.epics) {
422
+ sdm.agile_blueprint.epics = [];
423
+ }
424
+
425
+ sdm.agile_blueprint.epics.push(epic);
426
+ await this.save(sdm);
427
+ return sdm;
428
+ }
429
+
430
+ /**
431
+ * Add Story to Epic
432
+ */
433
+ async addStory(epicId, story) {
434
+ const sdm = await this.load();
435
+
436
+ const epic = sdm.agile_blueprint?.epics?.find(e => e.id === epicId);
437
+ if (!epic) {
438
+ throw new Error(`Epic ${epicId} not found`);
439
+ }
440
+
441
+ if (!epic.stories) epic.stories = [];
442
+ epic.stories.push(story);
443
+
444
+ await this.save(sdm);
445
+ return sdm;
446
+ }
447
+
448
+ /**
449
+ * Update task status
450
+ */
451
+ async updateTaskStatus(storyId, taskId, status) {
452
+ const sdm = await this.load();
453
+
454
+ for (const epic of sdm.agile_blueprint?.epics || []) {
455
+ const story = epic.stories?.find(s => s.id === storyId);
456
+ if (story) {
457
+ const task = story.tasks?.find(t => t.id === taskId);
458
+ if (task) {
459
+ task.status = status;
460
+ await this.save(sdm);
461
+ return sdm;
462
+ }
463
+ }
464
+ }
465
+
466
+ throw new Error(`Task ${taskId} not found in Story ${storyId}`);
467
+ }
468
+
469
+ /**
470
+ * Get rehydration context (formatted for agent consumption)
471
+ */
472
+ async getRehydrationContext() {
473
+ const sdm = await this.load();
474
+
475
+ return {
476
+ summary: this.generateSummary(sdm),
477
+ services: sdm.services,
478
+ entities: sdm.entities,
479
+ communication: sdm.communication,
480
+ agile_blueprint: sdm.agile_blueprint,
481
+ fullMap: sdm,
482
+ };
483
+ }
484
+
485
+ /**
486
+ * Generate human-readable summary
487
+ */
488
+ generateSummary(sdm) {
489
+ const lines = [];
490
+
491
+ lines.push(`# ${sdm.metadata.project_name}`);
492
+ lines.push(`Architecture: ${sdm.metadata.architecture_type}`);
493
+ lines.push(`Last Updated: ${sdm.metadata.last_updated}`);
494
+ lines.push('');
495
+ lines.push('## Tech Stack');
496
+ lines.push(`Languages: ${sdm.tech_stack.languages.join(', ') || 'None'}`);
497
+ lines.push(`Frameworks: ${sdm.tech_stack.frameworks.join(', ') || 'None'}`);
498
+ lines.push(`Databases: ${sdm.tech_stack.databases.join(', ') || 'None'}`);
499
+ lines.push('');
500
+ lines.push(`## Services (${sdm.services.length})`);
501
+ sdm.services.forEach(s => {
502
+ lines.push(`- ${s.name}: ${s.scope}`);
503
+ lines.push(` Stack: ${s.tech_stack.language}/${s.tech_stack.framework}`);
504
+ });
505
+ lines.push('');
506
+
507
+ // Technical Strategies Summary
508
+ if (sdm.technical_strategies) {
509
+ const strategies = [];
510
+ if (sdm.technical_strategies.persistence?.model) {
511
+ strategies.push(`Persistence: ${sdm.technical_strategies.persistence.model}`);
512
+ }
513
+ if (sdm.technical_strategies.communication?.primary_pattern) {
514
+ strategies.push(`Communication: ${sdm.technical_strategies.communication.primary_pattern}`);
515
+ }
516
+ if (sdm.technical_strategies.state_management?.caching_layer) {
517
+ strategies.push(`Caching: ${sdm.technical_strategies.state_management.caching_layer}`);
518
+ }
519
+
520
+ if (strategies.length > 0) {
521
+ lines.push(`## Active Strategies`);
522
+ strategies.forEach(s => lines.push(`- ${s}`));
523
+ lines.push('');
524
+ }
525
+ }
526
+
527
+ // Agile Blueprint Summary
528
+ if (sdm.agile_blueprint && sdm.agile_blueprint.epics && sdm.agile_blueprint.epics.length > 0) {
529
+ const totalStories = sdm.agile_blueprint.epics.reduce((acc, e) => acc + (e.stories?.length || 0), 0);
530
+ lines.push(`## Agile Blueprint`);
531
+ lines.push(`Epics: ${sdm.agile_blueprint.epics.length}`);
532
+ lines.push(`Stories: ${totalStories}`);
533
+ if (sdm.agile_blueprint.test_suite_status) {
534
+ lines.push(`Test Coverage: ${sdm.agile_blueprint.test_suite_status.coverage_percentage || 0}%`);
535
+ }
536
+ lines.push('');
537
+ }
538
+
539
+ return lines.join('\n');
540
+ }
541
+
542
+ /**
543
+ * Check architectural alignment for existing services
544
+ * Returns services that need updates to match new strategy
545
+ */
546
+ async checkStrategyAlignment(newStrategy, category) {
547
+ const sdm = await this.load();
548
+ const misalignedServices = [];
549
+
550
+ for (const service of sdm.services) {
551
+ const alignmentIssues = [];
552
+
553
+ // Check persistence alignment
554
+ if (category === 'persistence' && newStrategy.model === 'database_per_service') {
555
+ if (!service.dedicated_database) {
556
+ alignmentIssues.push('Missing dedicated database');
557
+ }
558
+ }
559
+
560
+ // Check communication alignment
561
+ if (category === 'communication' && newStrategy.primary_pattern === 'asynchronous_events') {
562
+ if (!service.event_boundaries) {
563
+ alignmentIssues.push('Missing event boundaries (produces/consumes)');
564
+ }
565
+ }
566
+
567
+ // Check state management alignment
568
+ if (category === 'state_management' && newStrategy.caching_layer === 'distributed_redis') {
569
+ if (!service.dependencies?.includes('redis')) {
570
+ alignmentIssues.push('Missing Redis dependency for distributed cache');
571
+ }
572
+ }
573
+
574
+ if (alignmentIssues.length > 0) {
575
+ misalignedServices.push({
576
+ service: service.name,
577
+ issues: alignmentIssues,
578
+ });
579
+ }
580
+ }
581
+
582
+ return misalignedServices;
583
+ }
584
+
585
+ /**
586
+ * Apply strategy to all existing services
587
+ */
588
+ async realignServicesToStrategy() {
589
+ const sdm = await this.load();
590
+ const strategies = sdm.technical_strategies;
591
+
592
+ for (let i = 0; i < sdm.services.length; i++) {
593
+ sdm.services[i] = this.applyStrategyConstraints(sdm.services[i], strategies);
594
+ }
595
+
596
+ await this.save(sdm);
597
+ return sdm;
598
+ }
599
+
600
+ /**
601
+ * Perform architectural audit - detect drift between SDM and codebase
602
+ */
603
+ async performAudit() {
604
+ const sdm = await this.load();
605
+ const codebase = await this.scanCodebase();
606
+
607
+ const audit = {
608
+ timestamp: new Date().toISOString(),
609
+ summary: {
610
+ total_services: sdm.services.length,
611
+ implemented_services: 0,
612
+ total_entities: sdm.entities.length,
613
+ implemented_entities: 0,
614
+ total_stories: 0,
615
+ tested_stories: 0,
616
+ strategy_compliance_percentage: 100,
617
+ },
618
+ unimplemented: {
619
+ services: [],
620
+ entities: [],
621
+ stories: [],
622
+ },
623
+ unmanaged: {
624
+ services: [],
625
+ entities: [],
626
+ routes: [],
627
+ },
628
+ strategy_violations: [],
629
+ drift_percentage: 0,
630
+ };
631
+
632
+ // Check services
633
+ for (const service of sdm.services) {
634
+ const implemented = codebase.services.some(s =>
635
+ s.name === service.name || s.name === service.name.replace('-service', '')
636
+ );
637
+
638
+ if (implemented) {
639
+ audit.summary.implemented_services++;
640
+
641
+ // Check strategy compliance
642
+ const violations = this.checkServiceStrategyCompliance(service, sdm.technical_strategies, codebase);
643
+ audit.strategy_violations.push(...violations);
644
+ } else {
645
+ audit.unimplemented.services.push({
646
+ name: service.name,
647
+ scope: service.scope,
648
+ });
649
+ }
650
+ }
651
+
652
+ // Check entities
653
+ for (const entity of sdm.entities) {
654
+ const implemented = codebase.entities.some(e =>
655
+ e.name === entity.name || e.table_name === entity.table_name
656
+ );
657
+
658
+ if (implemented) {
659
+ audit.summary.implemented_entities++;
660
+ } else {
661
+ audit.unimplemented.entities.push({
662
+ name: entity.name,
663
+ table_name: entity.table_name,
664
+ });
665
+ }
666
+ }
667
+
668
+ // Check stories and tests
669
+ if (sdm.agile_blueprint && sdm.agile_blueprint.epics) {
670
+ for (const epic of sdm.agile_blueprint.epics) {
671
+ for (const story of epic.stories || []) {
672
+ audit.summary.total_stories++;
673
+
674
+ const testFile = `tests/contracts/${story.id.toLowerCase()}-standard.test.js`;
675
+ const testExists = codebase.tests.includes(testFile);
676
+
677
+ if (testExists) {
678
+ audit.summary.tested_stories++;
679
+ } else {
680
+ audit.unimplemented.stories.push({
681
+ id: story.id,
682
+ name: story.name,
683
+ epic: epic.id,
684
+ });
685
+ }
686
+ }
687
+ }
688
+ }
689
+
690
+ // Check unmanaged (code not in SDM)
691
+ for (const codeService of codebase.services) {
692
+ const inSDM = sdm.services.some(s =>
693
+ s.name === codeService.name || `${codeService.name}-service` === s.name
694
+ );
695
+
696
+ if (!inSDM) {
697
+ audit.unmanaged.services.push(codeService);
698
+ }
699
+ }
700
+
701
+ for (const codeEntity of codebase.entities) {
702
+ const inSDM = sdm.entities.some(e =>
703
+ e.name === codeEntity.name || e.table_name === codeEntity.table_name
704
+ );
705
+
706
+ if (!inSDM) {
707
+ audit.unmanaged.entities.push(codeEntity);
708
+ }
709
+ }
710
+
711
+ // Calculate drift percentage
712
+ const totalComponents =
713
+ audit.summary.total_services +
714
+ audit.summary.total_entities +
715
+ audit.summary.total_stories;
716
+
717
+ const driftComponents =
718
+ audit.unimplemented.services.length +
719
+ audit.unimplemented.entities.length +
720
+ audit.unimplemented.stories.length +
721
+ audit.unmanaged.services.length +
722
+ audit.unmanaged.entities.length +
723
+ audit.strategy_violations.length;
724
+
725
+ audit.drift_percentage = totalComponents > 0
726
+ ? Math.round((driftComponents / totalComponents) * 100)
727
+ : 0;
728
+
729
+ // Calculate strategy compliance
730
+ const totalViolations = audit.strategy_violations.length;
731
+ const totalServices = sdm.services.length;
732
+ audit.summary.strategy_compliance_percentage = totalServices > 0
733
+ ? Math.round(((totalServices - totalViolations) / totalServices) * 100)
734
+ : 100;
735
+
736
+ return audit;
737
+ }
738
+
739
+ /**
740
+ * Scan codebase for actual implementation
741
+ */
742
+ async scanCodebase() {
743
+ const fs = await import('fs');
744
+ const path = await import('path');
745
+
746
+ const codebase = {
747
+ services: [],
748
+ entities: [],
749
+ routes: [],
750
+ tests: [],
751
+ };
752
+
753
+ // Scan for services
754
+ const serviceDirs = ['services', 'cmd', 'src/services', 'apps'];
755
+ for (const dir of serviceDirs) {
756
+ const fullPath = path.join(this.projectRoot, dir);
757
+ if (fs.existsSync(fullPath)) {
758
+ const items = fs.readdirSync(fullPath, { withFileTypes: true });
759
+ for (const item of items) {
760
+ if (item.isDirectory()) {
761
+ codebase.services.push({
762
+ name: item.name,
763
+ path: path.join(dir, item.name),
764
+ });
765
+ }
766
+ }
767
+ }
768
+ }
769
+
770
+ // Scan for entities/models
771
+ const entityDirs = ['models', 'entities', 'src/models', 'src/entities'];
772
+ for (const dir of entityDirs) {
773
+ const fullPath = path.join(this.projectRoot, dir);
774
+ if (fs.existsSync(fullPath)) {
775
+ const items = fs.readdirSync(fullPath);
776
+ for (const item of items) {
777
+ if (item.match(/\.(ts|js|go|py)$/)) {
778
+ const name = item.replace(/\.(ts|js|go|py)$/, '');
779
+ codebase.entities.push({
780
+ name,
781
+ file: path.join(dir, item),
782
+ });
783
+ }
784
+ }
785
+ }
786
+ }
787
+
788
+ // Scan for tests
789
+ const testDirs = ['tests', '__tests__', 'test'];
790
+ for (const dir of testDirs) {
791
+ const fullPath = path.join(this.projectRoot, dir);
792
+ if (fs.existsSync(fullPath)) {
793
+ this.scanTestsRecursive(fullPath, dir, codebase.tests);
794
+ }
795
+ }
796
+
797
+ return codebase;
798
+ }
799
+
800
+ /**
801
+ * Recursively scan test directories
802
+ */
803
+ scanTestsRecursive(dir, relativePath, tests) {
804
+ const fs = require('fs');
805
+ const path = require('path');
806
+
807
+ const items = fs.readdirSync(dir, { withFileTypes: true });
808
+ for (const item of items) {
809
+ const fullPath = path.join(dir, item.name);
810
+ const relPath = path.join(relativePath, item.name);
811
+
812
+ if (item.isDirectory()) {
813
+ this.scanTestsRecursive(fullPath, relPath, tests);
814
+ } else if (item.name.match(/\.(test|spec)\.(ts|js)$/) || item.name.match(/_test\.go$/)) {
815
+ tests.push(relPath);
816
+ }
817
+ }
818
+ }
819
+
820
+ /**
821
+ * Check service compliance with decision registry
822
+ */
823
+ checkServiceDecisionCompliance(service, decisions, codebase) {
824
+ const violations = [];
825
+
826
+ if (!decisions) return violations;
827
+
828
+ // Check naming conventions (would need code analysis - placeholder)
829
+ // In real implementation, would analyze actual code files
830
+
831
+ // Check design patterns compliance
832
+ if (decisions.design_patterns?.data_access && service._decision_constraints) {
833
+ const expectedPattern = decisions.design_patterns.data_access;
834
+ // Placeholder: Would analyze code to verify Repository pattern is used
835
+ }
836
+
837
+ return violations;
838
+ }
839
+
840
+ /**
841
+ * Check service compliance with active strategies
842
+ */
843
+ checkServiceStrategyCompliance(service, strategies, codebase) {
844
+ const violations = [];
845
+
846
+ if (!strategies) return violations;
847
+
848
+ // Check persistence strategy
849
+ if (strategies.persistence?.model === 'database_per_service') {
850
+ if (!service.dedicated_database) {
851
+ violations.push({
852
+ service: service.name,
853
+ strategy: 'persistence',
854
+ violation: 'Missing dedicated database',
855
+ expected: 'database_per_service',
856
+ actual: 'no dedicated_database config',
857
+ });
858
+ }
859
+ }
860
+
861
+ // Check communication strategy
862
+ if (strategies.communication?.primary_pattern === 'asynchronous_events') {
863
+ if (!service.event_boundaries) {
864
+ violations.push({
865
+ service: service.name,
866
+ strategy: 'communication',
867
+ violation: 'Missing event boundaries',
868
+ expected: 'asynchronous_events',
869
+ actual: 'no event_boundaries config',
870
+ });
871
+ }
872
+ }
873
+
874
+ // Check security strategy
875
+ if (strategies.security?.authentication === 'jwt_centralized') {
876
+ if (service.name !== 'auth-service' && !service.dependencies?.includes('auth-service')) {
877
+ violations.push({
878
+ service: service.name,
879
+ strategy: 'security',
880
+ violation: 'Missing auth-service dependency',
881
+ expected: 'jwt_centralized',
882
+ actual: 'no auth-service dependency',
883
+ });
884
+ }
885
+ }
886
+
887
+ // Check resilience strategy
888
+ if (strategies.resilience?.circuit_breaker === true) {
889
+ if (!service.resilience?.circuit_breaker) {
890
+ violations.push({
891
+ service: service.name,
892
+ strategy: 'resilience',
893
+ violation: 'Missing circuit breaker config',
894
+ expected: 'circuit_breaker: true',
895
+ actual: 'no circuit_breaker config',
896
+ });
897
+ }
898
+ }
899
+
900
+ return violations;
901
+ }
902
+
903
+ /**
904
+ * Sync SDM to match codebase state
905
+ */
906
+ async syncToCodebase() {
907
+ const sdm = await this.load();
908
+ const codebase = await this.scanCodebase();
909
+ const changes = {
910
+ added: { services: 0, entities: 0 },
911
+ removed: { services: 0, entities: 0 },
912
+ };
913
+
914
+ // Add unmanaged services
915
+ for (const codeService of codebase.services) {
916
+ const inSDM = sdm.services.some(s =>
917
+ s.name === codeService.name || `${codeService.name}-service` === s.name
918
+ );
919
+
920
+ if (!inSDM) {
921
+ sdm.services.push({
922
+ name: `${codeService.name}-service`,
923
+ scope: `Auto-discovered from ${codeService.path}`,
924
+ tech_stack: {
925
+ language: sdm.tech_stack.languages[0] || 'Unknown',
926
+ framework: sdm.tech_stack.frameworks[0] || 'Unknown',
927
+ },
928
+ _discovered: true,
929
+ });
930
+ changes.added.services++;
931
+ }
932
+ }
933
+
934
+ // Add unmanaged entities
935
+ for (const codeEntity of codebase.entities) {
936
+ const inSDM = sdm.entities.some(e =>
937
+ e.name === codeEntity.name
938
+ );
939
+
940
+ if (!inSDM) {
941
+ sdm.entities.push({
942
+ name: codeEntity.name,
943
+ table_name: codeEntity.name.toLowerCase() + 's',
944
+ attributes: [],
945
+ _discovered: true,
946
+ });
947
+ changes.added.entities++;
948
+ }
949
+ }
950
+
951
+ // Mark unimplemented as "planned" instead of removing
952
+ for (const service of sdm.services) {
953
+ const implemented = codebase.services.some(s =>
954
+ s.name === service.name || s.name === service.name.replace('-service', '')
955
+ );
956
+
957
+ if (!implemented && !service._status) {
958
+ service._status = 'planned';
959
+ }
960
+ }
961
+
962
+ await this.save(sdm);
963
+ return changes;
964
+ }
965
+
966
+ /**
967
+ * Update tech stack summary from service
968
+ */
969
+ updateTechStackSummary(sdm, service) {
970
+ const { language, framework, database } = service.tech_stack;
971
+
972
+ if (language && !sdm.tech_stack.languages.includes(language)) {
973
+ sdm.tech_stack.languages.push(language);
974
+ }
975
+ if (framework && !sdm.tech_stack.frameworks.includes(framework)) {
976
+ sdm.tech_stack.frameworks.push(framework);
977
+ }
978
+ if (database && !sdm.tech_stack.databases.includes(database)) {
979
+ sdm.tech_stack.databases.push(database);
980
+ }
981
+ }
982
+ }