awc-zns-mtd 2.9.0 → 2.10.3

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 (38) hide show
  1. package/.github/workflows/ci.yml +148 -0
  2. package/.husky/pre-commit +2 -0
  3. package/.prettierignore +31 -0
  4. package/.prettierrc +13 -0
  5. package/IMPLEMENTATION_SUMMARY.md +410 -0
  6. package/PHASE_2_SUMMARY.md +289 -0
  7. package/README.md +114 -47
  8. package/SECURITY.md +58 -0
  9. package/eslint.config.js +70 -0
  10. package/jest.config.js +49 -0
  11. package/package.json +40 -14
  12. package/src/modules/custom-agents/cli/awc-agent.js +505 -372
  13. package/test/integration/cli/cli-commands.integration.test.js +105 -0
  14. package/test/setup.js +22 -0
  15. package/test/unit/commands/version.test.js +39 -0
  16. package/test/unit/config/config-manager.test.js +147 -0
  17. package/test/unit/utils/file-utils.test.js +177 -0
  18. package/test/unit/utils/validators.test.js +57 -0
  19. package/tools/cli/commands/init.js +556 -513
  20. package/tools/cli/commands/new-project.js +680 -659
  21. package/tools/cli/commands/validate.js +13 -13
  22. package/tools/cli/commands/version.js +5 -3
  23. package/tools/cli/utils/console-logger.js +39 -15
  24. package/tools/cli/utils/logger.js +176 -0
  25. package/tools/cli/utils/project-analyzer.js +33 -16
  26. package/tools/cli/utils/validators.js +144 -0
  27. package/tools/cli/utils/version.js +6 -2
  28. package/tools/config/config-manager.js +243 -0
  29. package/tools/version/changelog-manager.js +301 -288
  30. package/tools/version/update-checker.js +32 -32
  31. package/tools/version/version-bump.js +89 -90
  32. package/tools/version/version-manager.js +17 -7
  33. package/tsconfig.json +47 -0
  34. package/types/index.d.ts +206 -0
  35. package/tools/cli/commands/init-old.js +0 -147
  36. package/tools/cli/commands/new-project-broken.js +0 -1302
  37. package/tools/cli/commands/new-project-old.js +0 -1302
  38. package/tools/cli/commands/new-project.js.backup +0 -1302
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tests de integración para CLI
3
+ * Pruebas end-to-end de comandos principales
4
+ */
5
+
6
+ const { execSync } = require('child_process');
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const os = require('os');
10
+
11
+ // Leer versión actual del package.json para tests dinámicos
12
+ const packageJson = require('../../../package.json');
13
+ const currentVersion = packageJson.version;
14
+
15
+ describe('CLI Integration Tests', () => {
16
+ let testDir;
17
+ const cliPath = path.join(__dirname, '../../../tools/cli/awc-cli.js');
18
+
19
+ beforeEach(async () => {
20
+ // Crear directorio temporal para tests
21
+ testDir = path.join(os.tmpdir(), `awc-test-${Date.now()}`);
22
+ await fs.ensureDir(testDir);
23
+ });
24
+
25
+ afterEach(async () => {
26
+ // Limpiar directorio temporal
27
+ await fs.remove(testDir);
28
+ });
29
+
30
+ describe('Comando: version', () => {
31
+ test('debe mostrar la versión del método', () => {
32
+ const output = execSync(`node "${cliPath}" version`, {
33
+ encoding: 'utf-8',
34
+ cwd: testDir
35
+ });
36
+
37
+ expect(output).toContain(currentVersion);
38
+ expect(output).toContain('ZΞNAPSΞS');
39
+ });
40
+
41
+ test('debe ejecutarse sin errores', () => {
42
+ expect(() => {
43
+ execSync(`node "${cliPath}" version`, {
44
+ cwd: testDir,
45
+ stdio: 'ignore'
46
+ });
47
+ }).not.toThrow();
48
+ });
49
+ });
50
+
51
+ describe('Comando: --help', () => {
52
+ test('debe mostrar ayuda general', () => {
53
+ const output = execSync(`node "${cliPath}" --help`, {
54
+ encoding: 'utf-8',
55
+ cwd: testDir
56
+ });
57
+
58
+ expect(output).toContain('install');
59
+ expect(output).toContain('version');
60
+ });
61
+ });
62
+
63
+ describe('Comando: --version', () => {
64
+ test('debe mostrar versión corta', () => {
65
+ const output = execSync(`node "${cliPath}" --version`, {
66
+ encoding: 'utf-8',
67
+ cwd: testDir
68
+ });
69
+
70
+ expect(output).toContain(currentVersion);
71
+ });
72
+ });
73
+
74
+ describe('Validación de comandos', () => {
75
+ test('comando inválido debe fallar', () => {
76
+ expect(() => {
77
+ execSync(`node "${cliPath}" comando-inexistente`, {
78
+ cwd: testDir,
79
+ stdio: 'pipe'
80
+ });
81
+ }).toThrow();
82
+ });
83
+ });
84
+
85
+ describe('Logs generados', () => {
86
+ test('debe crear directorio de logs', async () => {
87
+ // Ejecutar un comando para generar logs
88
+ try {
89
+ execSync(`node "${cliPath}" version`, {
90
+ cwd: testDir,
91
+ stdio: 'ignore'
92
+ });
93
+ } catch {
94
+ // Ignorar errores de ejecución
95
+ }
96
+
97
+ // Verificar que se crearon logs (si el comando los genera)
98
+ const logsDir = path.join(process.cwd(), '.awc', 'logs');
99
+ const logsExist = await fs.pathExists(logsDir);
100
+
101
+ // Los logs pueden o no existir dependiendo de la implementación
102
+ expect(typeof logsExist).toBe('boolean');
103
+ });
104
+ });
105
+ });
package/test/setup.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Jest Setup
3
+ * Configuración global para tests
4
+ */
5
+
6
+ // Suprimir console.log en tests (opcional)
7
+ global.console = {
8
+ ...console,
9
+ log: jest.fn(),
10
+ debug: jest.fn(),
11
+ info: jest.fn(),
12
+ warn: jest.fn(),
13
+ error: jest.fn()
14
+ };
15
+
16
+ // Configurar timeout global
17
+ jest.setTimeout(10000);
18
+
19
+ // Limpiar mocks después de cada test
20
+ afterEach(() => {
21
+ jest.clearAllMocks();
22
+ });
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tests unitarios para el comando 'version'
3
+ */
4
+
5
+ const { versionCommand } = require('../../../tools/cli/commands/version');
6
+ const { getVersion } = require('../../../tools/cli/utils/version');
7
+
8
+ // Mock de dependencias
9
+ jest.mock('../../../tools/cli/utils/version');
10
+ jest.mock('../../../tools/cli/utils/console-logger', () => ({
11
+ displayLogo: jest.fn()
12
+ }));
13
+
14
+ describe('Command: version', () => {
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ });
18
+
19
+ test('debe mostrar la versión actual del método', async () => {
20
+ // Arrange
21
+ getVersion.mockReturnValue('2.9.0');
22
+
23
+ // Act
24
+ await versionCommand();
25
+
26
+ // Assert
27
+ expect(getVersion).toHaveBeenCalled();
28
+ });
29
+
30
+ test('debe manejar errores al obtener la versión', async () => {
31
+ // Arrange
32
+ getVersion.mockImplementation(() => {
33
+ throw new Error('No se pudo leer package.json');
34
+ });
35
+
36
+ // Act & Assert
37
+ await expect(versionCommand()).rejects.toThrow();
38
+ });
39
+ });
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Tests unitarios para ConfigManager
3
+ */
4
+
5
+ const ConfigManager = require('../../../tools/config/config-manager');
6
+
7
+ describe('ConfigManager', () => {
8
+ describe('Constantes de directorio', () => {
9
+ test('debe retornar directorios correctos', () => {
10
+ expect(ConfigManager.AWC_DIR).toBe('.awc');
11
+ expect(ConfigManager.AGENTS_DIR).toContain('agents');
12
+ expect(ConfigManager.WORKFLOWS_DIR).toContain('workflows');
13
+ expect(ConfigManager.TEMPLATES_DIR).toContain('templates');
14
+ });
15
+ });
16
+
17
+ describe('Agentes Core', () => {
18
+ test('debe tener 4 agentes core', () => {
19
+ expect(ConfigManager.CORE_AGENTS).toHaveLength(4);
20
+ expect(ConfigManager.CORE_AGENTS).toContain('zen-master');
21
+ expect(ConfigManager.CORE_AGENTS).toContain('architect-senior');
22
+ });
23
+ });
24
+
25
+ describe('Tipos de proyecto', () => {
26
+ test('debe incluir todos los tipos soportados', () => {
27
+ expect(ConfigManager.PROJECT_TYPES).toContain('greenfield');
28
+ expect(ConfigManager.PROJECT_TYPES).toContain('migration');
29
+ expect(ConfigManager.PROJECT_TYPES).toContain('code-audit');
30
+ expect(ConfigManager.PROJECT_TYPES).toContain('enterprise');
31
+ });
32
+ });
33
+
34
+ describe('Tecnologías soportadas', () => {
35
+ test('debe incluir tecnologías principales', () => {
36
+ const techs = ConfigManager.SUPPORTED_TECHNOLOGIES;
37
+ expect(techs).toContain('java');
38
+ expect(techs).toContain('dotnet');
39
+ expect(techs).toContain('python');
40
+ expect(techs).toContain('react');
41
+ expect(techs).toContain('nodejs');
42
+ });
43
+ });
44
+
45
+ describe('getProjectPath', () => {
46
+ test('debe construir path correcto', () => {
47
+ const cwd = '/home/user/project';
48
+ const path = ConfigManager.getProjectPath(cwd, 'agents');
49
+
50
+ expect(path).toContain('.awc');
51
+ expect(path).toContain('agents');
52
+ });
53
+
54
+ test('debe manejar path vacío', () => {
55
+ const cwd = '/home/user/project';
56
+ const path = ConfigManager.getProjectPath(cwd);
57
+
58
+ expect(path).toContain('.awc');
59
+ expect(path).not.toContain('undefined');
60
+ });
61
+ });
62
+
63
+ describe('getAgentsPath', () => {
64
+ test('debe retornar path a agentes', () => {
65
+ const cwd = '/project';
66
+ const path = ConfigManager.getAgentsPath(cwd);
67
+
68
+ expect(path).toContain('.awc');
69
+ expect(path).toContain('agents');
70
+ });
71
+ });
72
+
73
+ describe('validateConfig', () => {
74
+ test('debe validar configuración correcta', () => {
75
+ const config = {
76
+ projectName: 'test-project',
77
+ version: '1.0.0',
78
+ workflow: 'standard'
79
+ };
80
+
81
+ const result = ConfigManager.validateConfig(config);
82
+
83
+ expect(result.valid).toBe(true);
84
+ expect(result.errors).toHaveLength(0);
85
+ });
86
+
87
+ test('debe rechazar config sin projectName', () => {
88
+ const config = {
89
+ version: '1.0.0'
90
+ };
91
+
92
+ const result = ConfigManager.validateConfig(config);
93
+
94
+ expect(result.valid).toBe(false);
95
+ expect(result.errors).toContain('projectName es requerido');
96
+ });
97
+
98
+ test('debe rechazar config sin version', () => {
99
+ const config = {
100
+ projectName: 'test'
101
+ };
102
+
103
+ const result = ConfigManager.validateConfig(config);
104
+
105
+ expect(result.valid).toBe(false);
106
+ expect(result.errors).toContain('version es requerida');
107
+ });
108
+
109
+ test('debe rechazar workflow inválido', () => {
110
+ const config = {
111
+ projectName: 'test',
112
+ version: '1.0.0',
113
+ workflow: 'invalid-workflow'
114
+ };
115
+
116
+ const result = ConfigManager.validateConfig(config);
117
+
118
+ expect(result.valid).toBe(false);
119
+ expect(result.errors.length).toBeGreaterThan(0);
120
+ });
121
+ });
122
+
123
+ describe('Configuración de entorno', () => {
124
+ test('debe obtener info del entorno', () => {
125
+ const env = ConfigManager.getEnvironmentConfig();
126
+
127
+ expect(env).toHaveProperty('nodeVersion');
128
+ expect(env).toHaveProperty('platform');
129
+ expect(env).toHaveProperty('arch');
130
+ expect(env).toHaveProperty('cwd');
131
+ expect(env).toHaveProperty('env');
132
+ });
133
+ });
134
+
135
+ describe('Límites y validaciones', () => {
136
+ test('debe tener límites definidos', () => {
137
+ expect(ConfigManager.MAX_PROJECT_NAME_LENGTH).toBe(50);
138
+ expect(ConfigManager.MIN_PROJECT_NAME_LENGTH).toBe(3);
139
+ });
140
+
141
+ test('debe tener nombres reservados', () => {
142
+ expect(ConfigManager.RESERVED_NAMES).toContain('node_modules');
143
+ expect(ConfigManager.RESERVED_NAMES).toContain('.git');
144
+ expect(ConfigManager.RESERVED_NAMES).toContain('package.json');
145
+ });
146
+ });
147
+ });
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Tests unitarios para file-utils
3
+ */
4
+
5
+ const fs = require('fs-extra');
6
+ const path = require('path');
7
+ const yaml = require('js-yaml');
8
+ const {
9
+ loadConfig,
10
+ updateConfig,
11
+ createConfigFile,
12
+ readYamlFile,
13
+ writeYamlFile,
14
+ isDirectoryEmpty
15
+ } = require('../../../tools/cli/utils/file-utils');
16
+
17
+ // Mock de fs-extra
18
+ jest.mock('fs-extra');
19
+
20
+ describe('File Utils', () => {
21
+ const testDir = '/test/project/.awc';
22
+ const configJsonPath = path.join(testDir, 'config.json');
23
+ const configYamlPath = path.join(testDir, 'config.yaml');
24
+
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ });
28
+
29
+ describe('loadConfig', () => {
30
+ test('debe cargar configuración JSON cuando existe', async () => {
31
+ const mockConfig = {
32
+ projectName: 'test-project',
33
+ version: '1.0.0',
34
+ initialized: true
35
+ };
36
+
37
+ fs.pathExists.mockImplementation((filePath) => {
38
+ return Promise.resolve(filePath === configJsonPath);
39
+ });
40
+ fs.readJson.mockResolvedValue(mockConfig);
41
+
42
+ const config = await loadConfig(testDir);
43
+
44
+ expect(fs.pathExists).toHaveBeenCalledWith(configJsonPath);
45
+ expect(fs.readJson).toHaveBeenCalledWith(configJsonPath);
46
+ expect(config).toHaveProperty('projectName', 'test-project');
47
+ });
48
+
49
+ test('debe cargar configuración YAML cuando JSON no existe', async () => {
50
+ const mockConfig = {
51
+ projectName: 'test-project',
52
+ version: '1.0.0'
53
+ };
54
+
55
+ fs.pathExists.mockImplementation((filePath) => {
56
+ if (filePath === configJsonPath) {
57
+ return Promise.resolve(false);
58
+ }
59
+ if (filePath === configYamlPath) {
60
+ return Promise.resolve(true);
61
+ }
62
+ return Promise.resolve(false);
63
+ });
64
+ fs.readFile.mockResolvedValue(yaml.dump(mockConfig));
65
+
66
+ const config = await loadConfig(testDir);
67
+
68
+ expect(fs.pathExists).toHaveBeenCalledWith(configJsonPath);
69
+ expect(fs.pathExists).toHaveBeenCalledWith(configYamlPath);
70
+ expect(config).toHaveProperty('projectName', 'test-project');
71
+ });
72
+
73
+ test('debe retornar null si no existe archivo', async () => {
74
+ fs.pathExists.mockResolvedValue(false);
75
+
76
+ const config = await loadConfig(testDir);
77
+
78
+ expect(config).toBeNull();
79
+ });
80
+ });
81
+
82
+ describe('updateConfig', () => {
83
+ test('debe actualizar configuración existente en JSON', async () => {
84
+ const newConfig = {
85
+ projectName: 'updated-project',
86
+ version: '2.0.0'
87
+ };
88
+
89
+ fs.writeJson.mockResolvedValue();
90
+
91
+ await updateConfig(testDir, newConfig);
92
+
93
+ expect(fs.writeJson).toHaveBeenCalledWith(configJsonPath, newConfig, { spaces: 2 });
94
+ });
95
+ });
96
+
97
+ describe('createConfigFile', () => {
98
+ test('debe crear archivo YAML correctamente', async () => {
99
+ const config = {
100
+ projectName: 'new-project',
101
+ version: '1.0.0'
102
+ };
103
+
104
+ fs.writeFile.mockResolvedValue();
105
+
106
+ await createConfigFile(testDir, config);
107
+
108
+ expect(fs.writeFile).toHaveBeenCalledWith(
109
+ configYamlPath,
110
+ expect.stringContaining('projectName:'),
111
+ 'utf8'
112
+ );
113
+ });
114
+ });
115
+
116
+ describe('readYamlFile', () => {
117
+ test('debe leer archivo YAML correctamente', async () => {
118
+ const mockData = { key: 'value', nested: { prop: 123 } };
119
+ fs.readFile.mockResolvedValue(yaml.dump(mockData));
120
+
121
+ const result = await readYamlFile('/test/file.yaml');
122
+
123
+ expect(result).toEqual(mockData);
124
+ });
125
+
126
+ test('debe retornar null en caso de error', async () => {
127
+ fs.readFile.mockRejectedValue(new Error('File not found'));
128
+
129
+ const result = await readYamlFile('/test/missing.yaml');
130
+
131
+ expect(result).toBeNull();
132
+ });
133
+ });
134
+
135
+ describe('writeYamlFile', () => {
136
+ test('debe escribir archivo YAML correctamente', async () => {
137
+ const data = { name: 'test', value: 42 };
138
+ fs.writeFile.mockResolvedValue();
139
+
140
+ await writeYamlFile('/test/output.yaml', data);
141
+
142
+ expect(fs.writeFile).toHaveBeenCalledWith(
143
+ '/test/output.yaml',
144
+ expect.stringContaining('name: test'),
145
+ 'utf8'
146
+ );
147
+ });
148
+ });
149
+
150
+ describe('isDirectoryEmpty', () => {
151
+ test('debe retornar true si directorio está vacío', async () => {
152
+ fs.pathExists.mockResolvedValue(true);
153
+ fs.readdir.mockResolvedValue([]);
154
+
155
+ const result = await isDirectoryEmpty(testDir);
156
+
157
+ expect(result).toBe(true);
158
+ });
159
+
160
+ test('debe retornar false si directorio tiene archivos', async () => {
161
+ fs.pathExists.mockResolvedValue(true);
162
+ fs.readdir.mockResolvedValue(['file1.txt', 'file2.txt']);
163
+
164
+ const result = await isDirectoryEmpty(testDir);
165
+
166
+ expect(result).toBe(false);
167
+ });
168
+
169
+ test('debe retornar true si directorio no existe', async () => {
170
+ fs.pathExists.mockResolvedValue(false);
171
+
172
+ const result = await isDirectoryEmpty('/nonexistent');
173
+
174
+ expect(result).toBe(true);
175
+ });
176
+ });
177
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Tests unitarios para utilidades de validación
3
+ */
4
+
5
+ const {
6
+ validateProjectName,
7
+ validatePath,
8
+ sanitizePath
9
+ } = require('../../../tools/cli/utils/validators');
10
+
11
+ describe('Validators Utils', () => {
12
+ describe('validateProjectName', () => {
13
+ test('debe aceptar nombres válidos', () => {
14
+ expect(validateProjectName('mi-proyecto')).toBe(true);
15
+ expect(validateProjectName('proyecto_123')).toBe(true);
16
+ expect(validateProjectName('MiProyecto')).toBe(true);
17
+ });
18
+
19
+ test('debe rechazar nombres inválidos', () => {
20
+ expect(validateProjectName('')).toBe(false);
21
+ expect(validateProjectName(' ')).toBe(false);
22
+ expect(validateProjectName('proyecto con espacios')).toBe(false);
23
+ expect(validateProjectName('proyecto@especial')).toBe(false);
24
+ expect(validateProjectName('../malicious')).toBe(false);
25
+ });
26
+
27
+ test('debe rechazar nombres reservados', () => {
28
+ expect(validateProjectName('node_modules')).toBe(false);
29
+ expect(validateProjectName('package.json')).toBe(false);
30
+ });
31
+ });
32
+
33
+ describe('validatePath', () => {
34
+ test('debe validar paths seguros', () => {
35
+ expect(validatePath('./relative/path')).toBe(true);
36
+ expect(validatePath('C:\\Users\\project')).toBe(true);
37
+ });
38
+
39
+ test('debe rechazar paths maliciosos', () => {
40
+ expect(validatePath('../../../etc/passwd')).toBe(false);
41
+ expect(validatePath('C:\\Windows\\System32')).toBe(false);
42
+ });
43
+ });
44
+
45
+ describe('sanitizePath', () => {
46
+ test('debe normalizar paths', () => {
47
+ const result = sanitizePath('path//to///file');
48
+ expect(result).not.toContain('//');
49
+ expect(sanitizePath('path\\to\\file')).not.toContain('\\\\');
50
+ });
51
+
52
+ test('debe eliminar caracteres peligrosos', () => {
53
+ expect(sanitizePath('path/../other')).not.toContain('..');
54
+ expect(sanitizePath('path/./file')).not.toContain('./');
55
+ });
56
+ });
57
+ });