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,225 @@
1
+ /**
2
+ * Story Command - User Journey Definition
3
+ * Defines Stories with API interaction boundaries (contract-first)
4
+ */
5
+
6
+ import * as clack from '@clack/prompts';
7
+ import chalk from 'chalk';
8
+ import { SDMManager } from '../../lib/sdm-manager.js';
9
+ import { ConfigManager } from '../../lib/config-manager.js';
10
+ import { TestEngine } from '../../lib/test-engine.js';
11
+
12
+ export async function storyCommand(options) {
13
+ try {
14
+ const sdmManager = new SDMManager(process.cwd());
15
+ const configManager = new ConfigManager(process.cwd());
16
+
17
+ if (!configManager.exists()) {
18
+ clack.log.error(chalk.red('Framework not installed.'));
19
+ clack.log.info('Run: dhurandhar install');
20
+ process.exit(1);
21
+ }
22
+
23
+ if (options.list) {
24
+ await listStories(sdmManager);
25
+ } else if (options.add) {
26
+ await addStory(sdmManager, options.add, options.epic);
27
+ } else {
28
+ console.log(chalk.cyan('Usage:'));
29
+ console.log(' dhurandhar story --list List all stories');
30
+ console.log(' dhurandhar story --add "Story" --epic ID Add story to epic');
31
+ }
32
+
33
+ } catch (error) {
34
+ clack.log.error(chalk.red('Story operation failed:'), error.message);
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ async function listStories(sdmManager) {
40
+ const sdm = await sdmManager.load();
41
+
42
+ if (!sdm.agile_blueprint || !sdm.agile_blueprint.epics) {
43
+ console.log(chalk.dim(' No stories defined yet.'));
44
+ return;
45
+ }
46
+
47
+ let totalStories = 0;
48
+
49
+ clack.intro(chalk.cyan.bold('Stories'));
50
+
51
+ console.log('');
52
+ sdm.agile_blueprint.epics.forEach(epic => {
53
+ if (!epic.stories || epic.stories.length === 0) return;
54
+
55
+ console.log(chalk.bold(`${epic.id}: ${epic.name}`));
56
+ epic.stories.forEach(story => {
57
+ totalStories++;
58
+ const taskCount = story.tasks?.length || 0;
59
+ const completeTasks = story.tasks?.filter(t => t.status === 'complete').length || 0;
60
+ console.log(` ${story.id}: ${story.name}`);
61
+ console.log(` Endpoint: ${chalk.cyan(story.interaction_boundary?.method)} ${story.interaction_boundary?.api_endpoint}`);
62
+ console.log(` Tasks: ${completeTasks}/${taskCount} complete`);
63
+ });
64
+ console.log('');
65
+ });
66
+
67
+ clack.outro(`Total: ${totalStories} stories`);
68
+ }
69
+
70
+ async function addStory(sdmManager, storyName, epicId) {
71
+ clack.intro(chalk.cyan.bold('Add Story - Test Architect Mode'));
72
+
73
+ const sdm = await sdmManager.load();
74
+
75
+ if (!sdm.agile_blueprint || !sdm.agile_blueprint.epics || sdm.agile_blueprint.epics.length === 0) {
76
+ clack.log.error('No epics found. Add an epic first:');
77
+ clack.log.info('dhurandhar epic add "Epic Name"');
78
+ process.exit(1);
79
+ }
80
+
81
+ // Select Epic
82
+ let selectedEpic;
83
+ if (epicId) {
84
+ selectedEpic = sdm.agile_blueprint.epics.find(e => e.id === epicId);
85
+ if (!selectedEpic) {
86
+ clack.log.error(`Epic ${epicId} not found`);
87
+ process.exit(1);
88
+ }
89
+ } else {
90
+ const epicChoice = await clack.select({
91
+ message: 'Which epic?',
92
+ options: sdm.agile_blueprint.epics.map(e => ({
93
+ value: e.id,
94
+ label: `${e.id}: ${e.name}`,
95
+ })),
96
+ });
97
+
98
+ if (clack.isCancel(epicChoice)) {
99
+ clack.cancel('Cancelled.');
100
+ process.exit(0);
101
+ }
102
+
103
+ selectedEpic = sdm.agile_blueprint.epics.find(e => e.id === epicChoice);
104
+ }
105
+
106
+ // Story name
107
+ let name = typeof storyName === 'string' ? storyName : null;
108
+
109
+ if (!name) {
110
+ name = await clack.text({
111
+ message: 'Story name (user journey):',
112
+ placeholder: 'OAuth2 Social Login Flow',
113
+ validate: (v) => !v ? 'Required' : undefined,
114
+ });
115
+
116
+ if (clack.isCancel(name)) {
117
+ clack.cancel('Cancelled.');
118
+ process.exit(0);
119
+ }
120
+ }
121
+
122
+ // Technical questions (max 3) - Test Architect persona
123
+ console.log('');
124
+ console.log(chalk.dim('Technical specification (max 3 questions):'));
125
+
126
+ // Q1: Which service?
127
+ const service = await clack.select({
128
+ message: '1. Target service:',
129
+ options: sdm.services?.map(s => ({ value: s.name, label: s.name })) ||
130
+ [{ value: 'new-service', label: '(Create new service)' }],
131
+ });
132
+
133
+ if (clack.isCancel(service)) {
134
+ clack.cancel('Cancelled.');
135
+ process.exit(0);
136
+ }
137
+
138
+ // Q2: API endpoint
139
+ const endpoint = await clack.text({
140
+ message: '2. API endpoint:',
141
+ placeholder: '/api/v1/auth/oauth/callback',
142
+ validate: (v) => !v || !v.startsWith('/') ? 'Must start with /' : undefined,
143
+ });
144
+
145
+ if (clack.isCancel(endpoint)) {
146
+ clack.cancel('Cancelled.');
147
+ process.exit(0);
148
+ }
149
+
150
+ // Q3: HTTP method
151
+ const method = await clack.select({
152
+ message: '3. HTTP method:',
153
+ options: [
154
+ { value: 'GET', label: 'GET' },
155
+ { value: 'POST', label: 'POST' },
156
+ { value: 'PUT', label: 'PUT' },
157
+ { value: 'DELETE', label: 'DELETE' },
158
+ ],
159
+ });
160
+
161
+ if (clack.isCancel(method)) {
162
+ clack.cancel('Cancelled.');
163
+ process.exit(0);
164
+ }
165
+
166
+ // Generate Story ID
167
+ const storyId = `STORY-${String(
168
+ sdm.agile_blueprint.epics.reduce((acc, e) => acc + (e.stories?.length || 0), 0) + 1
169
+ ).padStart(3, '0')}`;
170
+
171
+ // Create Story with interaction boundary
172
+ const story = {
173
+ id: storyId,
174
+ name,
175
+ interaction_boundary: {
176
+ service,
177
+ api_endpoint: endpoint,
178
+ method,
179
+ request_contract: {}, // To be filled by developer
180
+ response_contract: {}, // To be filled by developer
181
+ error_states: [400, 401, 404, 500], // Default errors
182
+ },
183
+ technical_acceptance: [
184
+ `Returns 200 on successful ${method} request`,
185
+ `Returns appropriate error codes (400, 401, 404, 500)`,
186
+ ],
187
+ tasks: [
188
+ {
189
+ id: `TASK-${storyId.split('-')[1]}-001`,
190
+ description: `Write contract tests for ${name}`,
191
+ type: 'test',
192
+ status: 'not_started',
193
+ test_coverage: {
194
+ test_file: `tests/contracts/${storyId.toLowerCase()}-standard.test.js`,
195
+ standard_flows: false,
196
+ error_states: false,
197
+ edge_cases: false,
198
+ },
199
+ },
200
+ {
201
+ id: `TASK-${storyId.split('-')[1]}-002`,
202
+ description: `Implement ${method} ${endpoint}`,
203
+ type: 'implementation',
204
+ linked_to: {
205
+ type: 'service',
206
+ name: service,
207
+ },
208
+ status: 'not_started',
209
+ },
210
+ ],
211
+ };
212
+
213
+ // Add Story to Epic
214
+ if (!selectedEpic.stories) selectedEpic.stories = [];
215
+ selectedEpic.stories.push(story);
216
+
217
+ await sdmManager.save(sdm);
218
+
219
+ clack.outro(chalk.green(`✓ Story ${storyId} added with 2 tasks`));
220
+
221
+ console.log('');
222
+ console.log(chalk.cyan('Next: Generate contract tests'));
223
+ console.log(' dhurandhar test --generate');
224
+ console.log('');
225
+ }
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Strategy Command - Direct-Action Architectural Strategy Management
3
+ * Pluggable patterns: Persistence, State, Communication, Security, Resilience
4
+ */
5
+
6
+ import * as clack from '@clack/prompts';
7
+ import chalk from 'chalk';
8
+ import { SDMManager } from '../../lib/sdm-manager.js';
9
+ import { ConfigManager } from '../../lib/config-manager.js';
10
+
11
+ export async function strategyCommand(options) {
12
+ try {
13
+ const sdmManager = new SDMManager(process.cwd());
14
+ const configManager = new ConfigManager(process.cwd());
15
+
16
+ if (!configManager.exists()) {
17
+ clack.log.error(chalk.red('Framework not installed.'));
18
+ clack.log.info('Run: dhurandhar install');
19
+ process.exit(1);
20
+ }
21
+
22
+ if (options.show) {
23
+ await showStrategies(sdmManager);
24
+ } else if (options.set) {
25
+ await setStrategy(sdmManager, options.set);
26
+ } else if (options.pivot) {
27
+ await pivotStrategy(sdmManager, options.pivot);
28
+ } else if (options.align) {
29
+ await alignServices(sdmManager);
30
+ } else {
31
+ console.log(chalk.cyan('Usage:'));
32
+ console.log(' dhurandhar strategy --show Show active strategies');
33
+ console.log(' dhurandhar strategy --set <category> Set architectural strategy');
34
+ console.log(' dhurandhar strategy --pivot <category> Pivot existing strategy');
35
+ console.log(' dhurandhar strategy --align Align all services to strategies');
36
+ }
37
+
38
+ } catch (error) {
39
+ clack.log.error(chalk.red('Strategy operation failed:'), error.message);
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ async function showStrategies(sdmManager) {
45
+ const strategies = await sdmManager.getStrategies();
46
+
47
+ clack.intro(chalk.cyan.bold('📐 Active Architectural Strategies'));
48
+
49
+ console.log('');
50
+
51
+ // Persistence
52
+ if (strategies.persistence?.model) {
53
+ console.log(chalk.bold('Persistence Strategy:'));
54
+ console.log(` Model: ${chalk.cyan(strategies.persistence.model)}`);
55
+ if (strategies.persistence.constraints?.length > 0) {
56
+ console.log(` Constraints: ${strategies.persistence.constraints.join(', ')}`);
57
+ }
58
+ console.log('');
59
+ }
60
+
61
+ // State Management
62
+ if (strategies.state_management?.caching_layer) {
63
+ console.log(chalk.bold('State Management:'));
64
+ console.log(` Caching: ${chalk.cyan(strategies.state_management.caching_layer)}`);
65
+ console.log(` Sessions: ${chalk.cyan(strategies.state_management.session_storage || 'not set')}`);
66
+ console.log('');
67
+ }
68
+
69
+ // Communication
70
+ if (strategies.communication?.primary_pattern) {
71
+ console.log(chalk.bold('Communication Pattern:'));
72
+ console.log(` Primary: ${chalk.cyan(strategies.communication.primary_pattern)}`);
73
+ if (strategies.communication.event_bus) {
74
+ console.log(` Event Bus: ${chalk.cyan(strategies.communication.event_bus.technology)}`);
75
+ }
76
+ console.log('');
77
+ }
78
+
79
+ // Security
80
+ if (strategies.security?.authentication) {
81
+ console.log(chalk.bold('Security:'));
82
+ console.log(` Authentication: ${chalk.cyan(strategies.security.authentication)}`);
83
+ console.log(` Authorization: ${chalk.cyan(strategies.security.authorization || 'not set')}`);
84
+ console.log('');
85
+ }
86
+
87
+ // Resilience
88
+ if (strategies.resilience?.circuit_breaker !== undefined) {
89
+ console.log(chalk.bold('Resilience:'));
90
+ console.log(` Circuit Breaker: ${strategies.resilience.circuit_breaker ? chalk.green('enabled') : chalk.dim('disabled')}`);
91
+ if (strategies.resilience.retry_strategy) {
92
+ console.log(` Retry: ${chalk.cyan(strategies.resilience.retry_strategy)}`);
93
+ }
94
+ console.log('');
95
+ }
96
+
97
+ clack.outro('');
98
+ }
99
+
100
+ async function setStrategy(sdmManager, category) {
101
+ clack.intro(chalk.cyan.bold(`Set ${category} Strategy`));
102
+
103
+ let strategy = {};
104
+
105
+ switch (category) {
106
+ case 'persistence':
107
+ strategy = await definePersistenceStrategy();
108
+ break;
109
+ case 'communication':
110
+ strategy = await defineCommunicationStrategy();
111
+ break;
112
+ case 'state':
113
+ strategy = await defineStateStrategy();
114
+ break;
115
+ case 'security':
116
+ strategy = await defineSecurityStrategy();
117
+ break;
118
+ case 'resilience':
119
+ strategy = await defineResilienceStrategy();
120
+ break;
121
+ default:
122
+ clack.log.error(`Unknown category: ${category}`);
123
+ clack.log.info('Valid: persistence, communication, state, security, resilience');
124
+ process.exit(1);
125
+ }
126
+
127
+ // Save strategy
128
+ const categoryKey = category === 'state' ? 'state_management' : category;
129
+ await sdmManager.setStrategy(categoryKey, strategy);
130
+
131
+ // Check alignment
132
+ const misaligned = await sdmManager.checkStrategyAlignment(strategy, categoryKey);
133
+
134
+ clack.outro(chalk.green(`✓ ${category} strategy set`));
135
+
136
+ if (misaligned.length > 0) {
137
+ console.log('');
138
+ console.log(chalk.yellow(`⚠ ${misaligned.length} services need alignment:`));
139
+ misaligned.forEach(m => {
140
+ console.log(` ${chalk.bold(m.service)}: ${m.issues.join(', ')}`);
141
+ });
142
+ console.log('');
143
+ console.log(chalk.cyan('Run to auto-align:'));
144
+ console.log(' dhurandhar strategy --align');
145
+ console.log('');
146
+ }
147
+ }
148
+
149
+ async function pivotStrategy(sdmManager, category) {
150
+ clack.intro(chalk.cyan.bold(`⚡ Pivot ${category} Strategy`));
151
+
152
+ const currentStrategies = await sdmManager.getStrategies();
153
+ const categoryKey = category === 'state' ? 'state_management' : category;
154
+ const current = currentStrategies[categoryKey];
155
+
156
+ console.log('');
157
+ console.log(chalk.dim('Current strategy:'));
158
+ console.log(JSON.stringify(current, null, 2));
159
+ console.log('');
160
+
161
+ const confirm = await clack.confirm({
162
+ message: 'This will update all services. Continue?',
163
+ initialValue: false,
164
+ });
165
+
166
+ if (clack.isCancel(confirm) || !confirm) {
167
+ clack.cancel('Cancelled.');
168
+ process.exit(0);
169
+ }
170
+
171
+ // Set new strategy
172
+ await setStrategy(sdmManager, category);
173
+ }
174
+
175
+ async function alignServices(sdmManager) {
176
+ clack.intro(chalk.cyan.bold('🔄 Align Services to Strategies'));
177
+
178
+ const spinner = clack.spinner();
179
+ spinner.start('Realigning services...');
180
+
181
+ await sdmManager.realignServicesToStrategy();
182
+
183
+ spinner.stop('Services aligned');
184
+
185
+ clack.outro(chalk.green('✓ All services now match active strategies'));
186
+
187
+ console.log('');
188
+ console.log(chalk.cyan('Check results:'));
189
+ console.log(' dhurandhar service --list');
190
+ console.log('');
191
+ }
192
+
193
+ // Strategy Definition Helpers
194
+
195
+ async function definePersistenceStrategy() {
196
+ const model = await clack.select({
197
+ message: 'Persistence model:',
198
+ options: [
199
+ { value: 'database_per_service', label: 'Database-per-Service', hint: 'Full isolation, eventual consistency' },
200
+ { value: 'shared_database', label: 'Shared Database', hint: 'Monolith pattern, ACID transactions' },
201
+ { value: 'hybrid', label: 'Hybrid', hint: 'Mix of both' },
202
+ { value: 'event_sourcing', label: 'Event Sourcing', hint: 'Event log as source of truth' },
203
+ ],
204
+ });
205
+
206
+ if (clack.isCancel(model)) process.exit(0);
207
+
208
+ const constraints = model === 'database_per_service'
209
+ ? ['no_cross_service_queries', 'eventual_consistency']
210
+ : [];
211
+
212
+ return { model, constraints };
213
+ }
214
+
215
+ async function defineCommunicationStrategy() {
216
+ const pattern = await clack.select({
217
+ message: 'Primary communication pattern:',
218
+ options: [
219
+ { value: 'synchronous_rest', label: 'Synchronous REST', hint: 'Request/response, immediate' },
220
+ { value: 'asynchronous_events', label: 'Asynchronous Events', hint: 'Event-driven, decoupled' },
221
+ { value: 'hybrid', label: 'Hybrid', hint: 'Both REST and events' },
222
+ { value: 'grpc_streaming', label: 'gRPC Streaming', hint: 'High performance, bidirectional' },
223
+ ],
224
+ });
225
+
226
+ if (clack.isCancel(pattern)) process.exit(0);
227
+
228
+ let eventBus = null;
229
+
230
+ if (pattern === 'asynchronous_events' || pattern === 'hybrid') {
231
+ const technology = await clack.select({
232
+ message: 'Event bus technology:',
233
+ options: [
234
+ { value: 'kafka', label: 'Apache Kafka' },
235
+ { value: 'rabbitmq', label: 'RabbitMQ' },
236
+ { value: 'aws_sns_sqs', label: 'AWS SNS/SQS' },
237
+ { value: 'redis_streams', label: 'Redis Streams' },
238
+ ],
239
+ });
240
+
241
+ if (!clack.isCancel(technology)) {
242
+ eventBus = { technology, message_format: 'json' };
243
+ }
244
+ }
245
+
246
+ return { primary_pattern: pattern, event_bus: eventBus };
247
+ }
248
+
249
+ async function defineStateStrategy() {
250
+ const caching = await clack.select({
251
+ message: 'Caching layer:',
252
+ options: [
253
+ { value: 'distributed_redis', label: 'Distributed Redis', hint: 'Shared cache across services' },
254
+ { value: 'local_in_memory', label: 'Local In-Memory', hint: 'Per-service cache' },
255
+ { value: 'sidecar_cache', label: 'Sidecar Cache', hint: 'Service mesh pattern' },
256
+ { value: 'none', label: 'None', hint: 'No caching' },
257
+ ],
258
+ });
259
+
260
+ if (clack.isCancel(caching)) process.exit(0);
261
+
262
+ return { caching_layer: caching, cache_invalidation: 'ttl_based' };
263
+ }
264
+
265
+ async function defineSecurityStrategy() {
266
+ const auth = await clack.select({
267
+ message: 'Authentication strategy:',
268
+ options: [
269
+ { value: 'jwt_centralized', label: 'JWT Centralized', hint: 'Auth service validates all tokens' },
270
+ { value: 'oauth2_delegated', label: 'OAuth2 Delegated', hint: 'External OAuth provider' },
271
+ { value: 'mtls', label: 'mTLS', hint: 'Certificate-based, service-to-service' },
272
+ { value: 'api_keys', label: 'API Keys', hint: 'Simple key-based auth' },
273
+ ],
274
+ });
275
+
276
+ if (clack.isCancel(auth)) process.exit(0);
277
+
278
+ return { authentication: auth, authorization: 'rbac' };
279
+ }
280
+
281
+ async function defineResilienceStrategy() {
282
+ const circuitBreaker = await clack.confirm({
283
+ message: 'Enable circuit breaker pattern?',
284
+ initialValue: true,
285
+ });
286
+
287
+ if (clack.isCancel(circuitBreaker)) process.exit(0);
288
+
289
+ return {
290
+ circuit_breaker: circuitBreaker,
291
+ retry_strategy: 'exponential_backoff',
292
+ timeout_policy: 'moderate_30s',
293
+ };
294
+ }