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,170 @@
1
+ /**
2
+ * Configuration Manager
3
+ * Handles loading, saving, and managing YAML-based configuration
4
+ */
5
+
6
+ import { join, dirname } from 'path';
7
+ import { existsSync } from 'fs';
8
+ import { readFile, writeFile, mkdir } from 'fs/promises';
9
+ import yaml from 'yaml';
10
+
11
+ export class ConfigManager {
12
+ constructor(projectRoot) {
13
+ this.projectRoot = projectRoot;
14
+ this.configDir = join(projectRoot, '.dhurandhar');
15
+ this.configPath = join(this.configDir, 'config.yaml');
16
+ }
17
+
18
+ /**
19
+ * Check if configuration exists
20
+ */
21
+ exists() {
22
+ return existsSync(this.configPath);
23
+ }
24
+
25
+ /**
26
+ * Initialize configuration with user values
27
+ */
28
+ async initialize(config) {
29
+ // Create config directory if it doesn't exist
30
+ if (!existsSync(this.configDir)) {
31
+ await mkdir(this.configDir, { recursive: true });
32
+ }
33
+
34
+ const defaultConfig = {
35
+ version: '0.1.0',
36
+ projectName: config.projectName || 'dhurandhar-project',
37
+ userName: config.userName || 'User',
38
+ outputFolder: config.outputFolder || '_dhurandhar-output',
39
+ modules: [],
40
+ settings: {
41
+ created: new Date().toISOString(),
42
+ lastModified: new Date().toISOString(),
43
+ },
44
+ variables: {
45
+ projectRoot: this.projectRoot,
46
+ configDir: this.configDir,
47
+ },
48
+ };
49
+
50
+ await this.save(defaultConfig);
51
+ return defaultConfig;
52
+ }
53
+
54
+ /**
55
+ * Load configuration from file
56
+ */
57
+ async load() {
58
+ if (!this.exists()) {
59
+ throw new Error('Configuration file not found. Run "dhurandhar install" first.');
60
+ }
61
+
62
+ const content = await readFile(this.configPath, 'utf-8');
63
+ const config = yaml.parse(content);
64
+
65
+ // Perform variable substitution
66
+ return this.substituteVariables(config);
67
+ }
68
+
69
+ /**
70
+ * Save configuration to file
71
+ */
72
+ async save(config) {
73
+ const content = yaml.stringify(config, {
74
+ indent: 2,
75
+ lineWidth: 0,
76
+ });
77
+
78
+ await writeFile(this.configPath, content, 'utf-8');
79
+ }
80
+
81
+ /**
82
+ * Update configuration
83
+ */
84
+ async update(updates) {
85
+ const config = await this.load();
86
+ const merged = {
87
+ ...config,
88
+ ...updates,
89
+ settings: {
90
+ ...config.settings,
91
+ lastModified: new Date().toISOString(),
92
+ },
93
+ };
94
+
95
+ await this.save(merged);
96
+ return merged;
97
+ }
98
+
99
+ /**
100
+ * Reset configuration to defaults
101
+ */
102
+ async reset() {
103
+ return this.initialize({});
104
+ }
105
+
106
+ /**
107
+ * Get a configuration value with dot notation
108
+ * Example: get('settings.created')
109
+ */
110
+ async get(path) {
111
+ const config = await this.load();
112
+ return this.getNestedValue(config, path);
113
+ }
114
+
115
+ /**
116
+ * Set a configuration value with dot notation
117
+ */
118
+ async set(path, value) {
119
+ const config = await this.load();
120
+ this.setNestedValue(config, path, value);
121
+ await this.save(config);
122
+ }
123
+
124
+ /**
125
+ * Substitute variables in configuration
126
+ * Supports {variable} syntax
127
+ */
128
+ substituteVariables(obj) {
129
+ if (typeof obj === 'string') {
130
+ return obj.replace(/\{([^}]+)\}/g, (match, key) => {
131
+ const value = this.getNestedValue(obj, key);
132
+ return value !== undefined ? value : match;
133
+ });
134
+ }
135
+
136
+ if (Array.isArray(obj)) {
137
+ return obj.map(item => this.substituteVariables(item));
138
+ }
139
+
140
+ if (obj !== null && typeof obj === 'object') {
141
+ const result = {};
142
+ for (const [key, value] of Object.entries(obj)) {
143
+ result[key] = this.substituteVariables(value);
144
+ }
145
+ return result;
146
+ }
147
+
148
+ return obj;
149
+ }
150
+
151
+ /**
152
+ * Get nested value from object using dot notation
153
+ */
154
+ getNestedValue(obj, path) {
155
+ return path.split('.').reduce((current, key) => current?.[key], obj);
156
+ }
157
+
158
+ /**
159
+ * Set nested value in object using dot notation
160
+ */
161
+ setNestedValue(obj, path, value) {
162
+ const keys = path.split('.');
163
+ const lastKey = keys.pop();
164
+ const target = keys.reduce((current, key) => {
165
+ if (!current[key]) current[key] = {};
166
+ return current[key];
167
+ }, obj);
168
+ target[lastKey] = value;
169
+ }
170
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * FileSystem Utilities
3
+ * Helper functions for file system operations
4
+ */
5
+
6
+ import { join } from 'path';
7
+ import { existsSync } from 'fs';
8
+ import { mkdir, readdir, stat, readFile, writeFile, rm } from 'fs/promises';
9
+ import { glob } from 'glob';
10
+
11
+ export class FileSystem {
12
+ /**
13
+ * Ensure required directories exist
14
+ */
15
+ static async ensureDirectories(projectRoot) {
16
+ const dirs = [
17
+ '.dhurandhar',
18
+ '.dhurandhar/modules',
19
+ '.dhurandhar/cache',
20
+ '_dhurandhar-output',
21
+ ];
22
+
23
+ for (const dir of dirs) {
24
+ const fullPath = join(projectRoot, dir);
25
+ if (!existsSync(fullPath)) {
26
+ await mkdir(fullPath, { recursive: true });
27
+ }
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Copy directory recursively
33
+ */
34
+ static async copyDir(src, dest) {
35
+ await mkdir(dest, { recursive: true });
36
+
37
+ const entries = await readdir(src, { withFileTypes: true });
38
+
39
+ for (const entry of entries) {
40
+ const srcPath = join(src, entry.name);
41
+ const destPath = join(dest, entry.name);
42
+
43
+ if (entry.isDirectory()) {
44
+ await FileSystem.copyDir(srcPath, destPath);
45
+ } else {
46
+ const content = await readFile(srcPath);
47
+ await writeFile(destPath, content);
48
+ }
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Remove directory recursively
54
+ */
55
+ static async removeDir(path) {
56
+ if (existsSync(path)) {
57
+ await rm(path, { recursive: true, force: true });
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Find files matching pattern
63
+ */
64
+ static async findFiles(pattern, options = {}) {
65
+ return glob(pattern, {
66
+ cwd: options.cwd || process.cwd(),
67
+ ignore: options.ignore || ['node_modules/**', '.git/**'],
68
+ absolute: options.absolute !== false,
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Read JSON file
74
+ */
75
+ static async readJSON(path) {
76
+ const content = await readFile(path, 'utf-8');
77
+ return JSON.parse(content);
78
+ }
79
+
80
+ /**
81
+ * Write JSON file
82
+ */
83
+ static async writeJSON(path, data, options = {}) {
84
+ const content = JSON.stringify(data, null, options.indent || 2);
85
+ await writeFile(path, content + '\n', 'utf-8');
86
+ }
87
+
88
+ /**
89
+ * Check if path is a directory
90
+ */
91
+ static async isDirectory(path) {
92
+ try {
93
+ const stats = await stat(path);
94
+ return stats.isDirectory();
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Check if path is a file
102
+ */
103
+ static async isFile(path) {
104
+ try {
105
+ const stats = await stat(path);
106
+ return stats.isFile();
107
+ } catch {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Get file size
114
+ */
115
+ static async getSize(path) {
116
+ const stats = await stat(path);
117
+ return stats.size;
118
+ }
119
+
120
+ /**
121
+ * Check if file exists
122
+ */
123
+ static exists(path) {
124
+ return existsSync(path);
125
+ }
126
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Module Installer
3
+ * Handles bulk module installation during framework setup
4
+ */
5
+
6
+ import { ModuleManager } from './module-manager.js';
7
+
8
+ export class ModuleInstaller {
9
+ constructor(projectRoot) {
10
+ this.projectRoot = projectRoot;
11
+ this.moduleManager = new ModuleManager(projectRoot);
12
+ }
13
+
14
+ /**
15
+ * Install multiple modules
16
+ */
17
+ async installModules(moduleCodes) {
18
+ const results = {
19
+ installed: [],
20
+ failed: [],
21
+ };
22
+
23
+ for (const moduleCode of moduleCodes) {
24
+ try {
25
+ await this.moduleManager.install(moduleCode);
26
+ results.installed.push(moduleCode);
27
+ } catch (error) {
28
+ results.failed.push({
29
+ module: moduleCode,
30
+ error: error.message,
31
+ });
32
+ }
33
+ }
34
+
35
+ return results;
36
+ }
37
+
38
+ /**
39
+ * Uninstall multiple modules
40
+ */
41
+ async uninstallModules(moduleCodes) {
42
+ const results = {
43
+ uninstalled: [],
44
+ failed: [],
45
+ };
46
+
47
+ for (const moduleCode of moduleCodes) {
48
+ try {
49
+ await this.moduleManager.uninstall(moduleCode);
50
+ results.uninstalled.push(moduleCode);
51
+ } catch (error) {
52
+ results.failed.push({
53
+ module: moduleCode,
54
+ error: error.message,
55
+ });
56
+ }
57
+ }
58
+
59
+ return results;
60
+ }
61
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Module Manager
3
+ * Handles module discovery, installation, and management
4
+ */
5
+
6
+ import { join, dirname } from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { existsSync } from 'fs';
9
+ import { readFile } from 'fs/promises';
10
+ import yaml from 'yaml';
11
+ import { FileSystem } from './filesystem.js';
12
+ import { ConfigManager } from './config-manager.js';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ export class ModuleManager {
18
+ constructor(projectRoot) {
19
+ this.projectRoot = projectRoot;
20
+ this.configManager = new ConfigManager(projectRoot);
21
+ this.modulesSourceDir = join(__dirname, '../../src/modules');
22
+ this.modulesInstallDir = join(projectRoot, '.dhurandhar/modules');
23
+ }
24
+
25
+ /**
26
+ * List available modules from source
27
+ */
28
+ async listAvailable() {
29
+ const moduleDirs = await FileSystem.findFiles('*/module.yaml', {
30
+ cwd: this.modulesSourceDir,
31
+ absolute: false,
32
+ });
33
+
34
+ const modules = [];
35
+
36
+ for (const modulePath of moduleDirs) {
37
+ const moduleDir = dirname(modulePath);
38
+ const fullPath = join(this.modulesSourceDir, modulePath);
39
+
40
+ try {
41
+ const content = await readFile(fullPath, 'utf-8');
42
+ const metadata = yaml.parse(content);
43
+
44
+ modules.push({
45
+ code: metadata.code || moduleDir,
46
+ name: metadata.name || moduleDir,
47
+ description: metadata.description || 'No description available',
48
+ version: metadata.version || '1.0.0',
49
+ dependencies: metadata.dependencies || [],
50
+ path: moduleDir,
51
+ });
52
+ } catch (error) {
53
+ console.warn(`Warning: Could not load module ${moduleDir}:`, error.message);
54
+ }
55
+ }
56
+
57
+ return modules;
58
+ }
59
+
60
+ /**
61
+ * List installed modules
62
+ */
63
+ async listInstalled() {
64
+ if (!existsSync(this.modulesInstallDir)) {
65
+ return [];
66
+ }
67
+
68
+ const moduleDirs = await FileSystem.findFiles('*/module.yaml', {
69
+ cwd: this.modulesInstallDir,
70
+ absolute: false,
71
+ });
72
+
73
+ return moduleDirs.map(path => dirname(path));
74
+ }
75
+
76
+ /**
77
+ * Get module information
78
+ */
79
+ async getInfo(moduleCode) {
80
+ const available = await this.listAvailable();
81
+ return available.find(m => m.code === moduleCode);
82
+ }
83
+
84
+ /**
85
+ * Install a module
86
+ */
87
+ async install(moduleCode) {
88
+ const moduleInfo = await this.getInfo(moduleCode);
89
+
90
+ if (!moduleInfo) {
91
+ throw new Error(`Module "${moduleCode}" not found`);
92
+ }
93
+
94
+ // Check and install dependencies
95
+ if (moduleInfo.dependencies && moduleInfo.dependencies.length > 0) {
96
+ for (const dep of moduleInfo.dependencies) {
97
+ const isInstalled = (await this.listInstalled()).includes(dep);
98
+ if (!isInstalled) {
99
+ await this.install(dep);
100
+ }
101
+ }
102
+ }
103
+
104
+ // Copy module files
105
+ const sourcePath = join(this.modulesSourceDir, moduleInfo.path);
106
+ const destPath = join(this.modulesInstallDir, moduleInfo.code);
107
+
108
+ await FileSystem.copyDir(sourcePath, destPath);
109
+
110
+ // Update configuration
111
+ const config = await this.configManager.load();
112
+ if (!config.modules.includes(moduleCode)) {
113
+ config.modules.push(moduleCode);
114
+ await this.configManager.save(config);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Uninstall a module
120
+ */
121
+ async uninstall(moduleCode) {
122
+ const destPath = join(this.modulesInstallDir, moduleCode);
123
+
124
+ if (!existsSync(destPath)) {
125
+ throw new Error(`Module "${moduleCode}" is not installed`);
126
+ }
127
+
128
+ // Check if other modules depend on this
129
+ const installed = await this.listInstalled();
130
+ for (const installedModule of installed) {
131
+ if (installedModule === moduleCode) continue;
132
+
133
+ const info = await this.getInfo(installedModule);
134
+ if (info && info.dependencies && info.dependencies.includes(moduleCode)) {
135
+ throw new Error(
136
+ `Cannot uninstall "${moduleCode}" because "${installedModule}" depends on it`
137
+ );
138
+ }
139
+ }
140
+
141
+ // Remove module files
142
+ await FileSystem.removeDir(destPath);
143
+
144
+ // Update configuration
145
+ const config = await this.configManager.load();
146
+ config.modules = config.modules.filter(m => m !== moduleCode);
147
+ await this.configManager.save(config);
148
+ }
149
+ }