@vfarcic/dot-ai 0.5.0 → 0.5.1

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 (145) hide show
  1. package/.claude/commands/context-load.md +11 -0
  2. package/.claude/commands/context-save.md +16 -0
  3. package/.claude/commands/prd-done.md +115 -0
  4. package/.claude/commands/prd-get.md +25 -0
  5. package/.claude/commands/prd-start.md +87 -0
  6. package/.claude/commands/task-done.md +77 -0
  7. package/.claude/commands/tests-reminder.md +32 -0
  8. package/.claude/settings.local.json +20 -0
  9. package/.eslintrc.json +25 -0
  10. package/.github/workflows/ci.yml +170 -0
  11. package/.prettierrc.json +10 -0
  12. package/.teller.yml +8 -0
  13. package/CLAUDE.md +162 -0
  14. package/assets/images/logo.png +0 -0
  15. package/bin/dot-ai.ts +47 -0
  16. package/destroy.sh +45 -0
  17. package/devbox.json +13 -0
  18. package/devbox.lock +225 -0
  19. package/docs/API.md +449 -0
  20. package/docs/CONTEXT.md +49 -0
  21. package/docs/DEVELOPMENT.md +203 -0
  22. package/docs/NEXT_STEPS.md +97 -0
  23. package/docs/STAGE_BASED_API.md +97 -0
  24. package/docs/cli-guide.md +798 -0
  25. package/docs/design.md +750 -0
  26. package/docs/discovery-engine.md +515 -0
  27. package/docs/error-handling.md +429 -0
  28. package/docs/function-registration.md +157 -0
  29. package/docs/mcp-guide.md +416 -0
  30. package/package.json +2 -121
  31. package/renovate.json +51 -0
  32. package/setup.sh +111 -0
  33. package/{dist/cli.js → src/cli.ts} +26 -19
  34. package/src/core/claude.ts +280 -0
  35. package/src/core/deploy-operation.ts +127 -0
  36. package/src/core/discovery.ts +900 -0
  37. package/src/core/error-handling.ts +562 -0
  38. package/src/core/index.ts +143 -0
  39. package/src/core/kubernetes-utils.ts +218 -0
  40. package/src/core/memory.ts +148 -0
  41. package/src/core/schema.ts +830 -0
  42. package/src/core/session-utils.ts +97 -0
  43. package/src/core/workflow.ts +234 -0
  44. package/src/index.ts +18 -0
  45. package/src/interfaces/cli.ts +872 -0
  46. package/src/interfaces/mcp.ts +183 -0
  47. package/src/mcp/server.ts +131 -0
  48. package/src/tools/answer-question.ts +807 -0
  49. package/src/tools/choose-solution.ts +169 -0
  50. package/src/tools/deploy-manifests.ts +94 -0
  51. package/src/tools/generate-manifests.ts +502 -0
  52. package/src/tools/index.ts +41 -0
  53. package/src/tools/recommend.ts +370 -0
  54. package/tests/__mocks__/@kubernetes/client-node.ts +106 -0
  55. package/tests/build-system.test.ts +345 -0
  56. package/tests/configuration.test.ts +226 -0
  57. package/tests/core/deploy-operation.test.ts +38 -0
  58. package/tests/core/discovery.test.ts +1648 -0
  59. package/tests/core/error-handling.test.ts +632 -0
  60. package/tests/core/schema.test.ts +1658 -0
  61. package/tests/core/session-utils.test.ts +245 -0
  62. package/tests/core.test.ts +439 -0
  63. package/tests/fixtures/configmap-no-labels.yaml +8 -0
  64. package/tests/fixtures/crossplane-app-configuration.yaml +6 -0
  65. package/tests/fixtures/crossplane-providers.yaml +45 -0
  66. package/tests/fixtures/crossplane-rbac.yaml +48 -0
  67. package/tests/fixtures/invalid-configmap.yaml +8 -0
  68. package/tests/fixtures/invalid-deployment.yaml +17 -0
  69. package/tests/fixtures/test-deployment.yaml +28 -0
  70. package/tests/fixtures/valid-configmap.yaml +15 -0
  71. package/tests/infrastructure.test.ts +426 -0
  72. package/tests/interfaces/cli.test.ts +1036 -0
  73. package/tests/interfaces/mcp.test.ts +139 -0
  74. package/tests/kubernetes-utils.test.ts +200 -0
  75. package/tests/mcp/server.test.ts +126 -0
  76. package/tests/setup.ts +31 -0
  77. package/tests/tools/answer-question.test.ts +367 -0
  78. package/tests/tools/choose-solution.test.ts +481 -0
  79. package/tests/tools/deploy-manifests.test.ts +185 -0
  80. package/tests/tools/generate-manifests.test.ts +441 -0
  81. package/tests/tools/index.test.ts +111 -0
  82. package/tests/tools/recommend.test.ts +180 -0
  83. package/tsconfig.json +34 -0
  84. package/dist/cli.d.ts +0 -3
  85. package/dist/cli.d.ts.map +0 -1
  86. package/dist/core/claude.d.ts +0 -42
  87. package/dist/core/claude.d.ts.map +0 -1
  88. package/dist/core/claude.js +0 -229
  89. package/dist/core/deploy-operation.d.ts +0 -38
  90. package/dist/core/deploy-operation.d.ts.map +0 -1
  91. package/dist/core/deploy-operation.js +0 -101
  92. package/dist/core/discovery.d.ts +0 -162
  93. package/dist/core/discovery.d.ts.map +0 -1
  94. package/dist/core/discovery.js +0 -758
  95. package/dist/core/error-handling.d.ts +0 -167
  96. package/dist/core/error-handling.d.ts.map +0 -1
  97. package/dist/core/error-handling.js +0 -399
  98. package/dist/core/index.d.ts +0 -42
  99. package/dist/core/index.d.ts.map +0 -1
  100. package/dist/core/index.js +0 -123
  101. package/dist/core/kubernetes-utils.d.ts +0 -38
  102. package/dist/core/kubernetes-utils.d.ts.map +0 -1
  103. package/dist/core/kubernetes-utils.js +0 -177
  104. package/dist/core/memory.d.ts +0 -45
  105. package/dist/core/memory.d.ts.map +0 -1
  106. package/dist/core/memory.js +0 -113
  107. package/dist/core/schema.d.ts +0 -187
  108. package/dist/core/schema.d.ts.map +0 -1
  109. package/dist/core/schema.js +0 -655
  110. package/dist/core/session-utils.d.ts +0 -29
  111. package/dist/core/session-utils.d.ts.map +0 -1
  112. package/dist/core/session-utils.js +0 -121
  113. package/dist/core/workflow.d.ts +0 -70
  114. package/dist/core/workflow.d.ts.map +0 -1
  115. package/dist/core/workflow.js +0 -161
  116. package/dist/index.d.ts +0 -15
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/index.js +0 -32
  119. package/dist/interfaces/cli.d.ts +0 -74
  120. package/dist/interfaces/cli.d.ts.map +0 -1
  121. package/dist/interfaces/cli.js +0 -769
  122. package/dist/interfaces/mcp.d.ts +0 -30
  123. package/dist/interfaces/mcp.d.ts.map +0 -1
  124. package/dist/interfaces/mcp.js +0 -105
  125. package/dist/mcp/server.d.ts +0 -9
  126. package/dist/mcp/server.d.ts.map +0 -1
  127. package/dist/mcp/server.js +0 -151
  128. package/dist/tools/answer-question.d.ts +0 -27
  129. package/dist/tools/answer-question.d.ts.map +0 -1
  130. package/dist/tools/answer-question.js +0 -696
  131. package/dist/tools/choose-solution.d.ts +0 -23
  132. package/dist/tools/choose-solution.d.ts.map +0 -1
  133. package/dist/tools/choose-solution.js +0 -171
  134. package/dist/tools/deploy-manifests.d.ts +0 -25
  135. package/dist/tools/deploy-manifests.d.ts.map +0 -1
  136. package/dist/tools/deploy-manifests.js +0 -74
  137. package/dist/tools/generate-manifests.d.ts +0 -23
  138. package/dist/tools/generate-manifests.d.ts.map +0 -1
  139. package/dist/tools/generate-manifests.js +0 -424
  140. package/dist/tools/index.d.ts +0 -11
  141. package/dist/tools/index.d.ts.map +0 -1
  142. package/dist/tools/index.js +0 -34
  143. package/dist/tools/recommend.d.ts +0 -23
  144. package/dist/tools/recommend.d.ts.map +0 -1
  145. package/dist/tools/recommend.js +0 -332
@@ -0,0 +1,45 @@
1
+ apiVersion: pkg.crossplane.io/v1beta1
2
+ kind: DeploymentRuntimeConfig
3
+ metadata:
4
+ name: crossplane-provider-helm
5
+ spec:
6
+ deploymentTemplate:
7
+ spec:
8
+ selector: {}
9
+ template:
10
+ spec:
11
+ containers:
12
+ - name: package-runtime
13
+ serviceAccountName: crossplane-provider-helm
14
+ ---
15
+ apiVersion: pkg.crossplane.io/v1
16
+ kind: Provider
17
+ metadata:
18
+ name: crossplane-provider-helm
19
+ spec:
20
+ package: xpkg.upbound.io/crossplane-contrib/provider-helm:v0.19.0
21
+ runtimeConfigRef:
22
+ name: crossplane-provider-helm
23
+ ---
24
+ apiVersion: pkg.crossplane.io/v1beta1
25
+ kind: DeploymentRuntimeConfig
26
+ metadata:
27
+ name: crossplane-provider-kubernetes
28
+ spec:
29
+ deploymentTemplate:
30
+ spec:
31
+ selector: {}
32
+ template:
33
+ spec:
34
+ containers:
35
+ - name: package-runtime
36
+ serviceAccountName: crossplane-provider-kubernetes
37
+ ---
38
+ apiVersion: pkg.crossplane.io/v1
39
+ kind: Provider
40
+ metadata:
41
+ name: crossplane-provider-kubernetes
42
+ spec:
43
+ package: xpkg.upbound.io/crossplane-contrib/provider-kubernetes:v0.15.0
44
+ runtimeConfigRef:
45
+ name: crossplane-provider-kubernetes
@@ -0,0 +1,48 @@
1
+ apiVersion: rbac.authorization.k8s.io/v1
2
+ kind: ClusterRole
3
+ metadata:
4
+ name: crossplane-all
5
+ labels:
6
+ rbac.crossplane.io/aggregate-to-crossplane: "true"
7
+ rules:
8
+ - apiGroups: ["*"]
9
+ resources: ["*"]
10
+ verbs: ["*"]
11
+ ---
12
+ apiVersion: v1
13
+ kind: ServiceAccount
14
+ metadata:
15
+ name: crossplane-provider-helm
16
+ namespace: crossplane-system
17
+ ---
18
+ apiVersion: rbac.authorization.k8s.io/v1
19
+ kind: ClusterRoleBinding
20
+ metadata:
21
+ name: crossplane-provider-helm
22
+ subjects:
23
+ - kind: ServiceAccount
24
+ name: crossplane-provider-helm
25
+ namespace: crossplane-system
26
+ roleRef:
27
+ kind: ClusterRole
28
+ name: cluster-admin
29
+ apiGroup: rbac.authorization.k8s.io
30
+ ---
31
+ apiVersion: v1
32
+ kind: ServiceAccount
33
+ metadata:
34
+ name: crossplane-provider-kubernetes
35
+ namespace: crossplane-system
36
+ ---
37
+ apiVersion: rbac.authorization.k8s.io/v1
38
+ kind: ClusterRoleBinding
39
+ metadata:
40
+ name: crossplane-provider-kubernetes
41
+ subjects:
42
+ - kind: ServiceAccount
43
+ name: crossplane-provider-kubernetes
44
+ namespace: crossplane-system
45
+ roleRef:
46
+ kind: ClusterRole
47
+ name: cluster-admin
48
+ apiGroup: rbac.authorization.k8s.io
@@ -0,0 +1,8 @@
1
+ apiVersion: v1
2
+ kind: ConfigMap
3
+ metadata:
4
+ name: invalid-configmap
5
+ # Missing namespace but that's just a warning
6
+ unknownField: this-should-cause-validation-error
7
+ data:
8
+ config.yaml: test-value
@@ -0,0 +1,17 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: invalid-app
5
+ spec:
6
+ # Missing required selector field
7
+ replicas: "not-a-number" # Should be integer
8
+ template:
9
+ metadata:
10
+ labels:
11
+ app: invalid-app
12
+ spec:
13
+ containers:
14
+ - name: app
15
+ # Missing required image field
16
+ ports:
17
+ - containerPort: "80" # Should be integer
@@ -0,0 +1,28 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: test-app
5
+ labels:
6
+ app: test-app
7
+ spec:
8
+ replicas: 3
9
+ selector:
10
+ matchLabels:
11
+ app: test-app
12
+ template:
13
+ metadata:
14
+ labels:
15
+ app: test-app
16
+ spec:
17
+ containers:
18
+ - name: app
19
+ image: nginx:1.21
20
+ ports:
21
+ - containerPort: 80
22
+ resources:
23
+ requests:
24
+ memory: "64Mi"
25
+ cpu: "250m"
26
+ limits:
27
+ memory: "128Mi"
28
+ cpu: "500m"
@@ -0,0 +1,15 @@
1
+ apiVersion: v1
2
+ kind: ConfigMap
3
+ metadata:
4
+ name: test-configmap-integration
5
+ namespace: default
6
+ labels:
7
+ app: test-integration
8
+ test-type: integration
9
+ data:
10
+ config.yaml: |
11
+ setting: value
12
+ environment: test
13
+ app.properties: |
14
+ debug=true
15
+ port=8080
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Test Infrastructure Validation
3
+ *
4
+ * These tests ensure our test infrastructure is properly configured
5
+ * and working before we implement any actual functionality.
6
+ */
7
+
8
+ import { existsSync, readFileSync, mkdirSync } from 'fs';
9
+ import * as path from 'path';
10
+ import * as yaml from 'yaml';
11
+
12
+ describe('Test Infrastructure', () => {
13
+ describe('Project Structure', () => {
14
+ test('should have package.json with correct structure', () => {
15
+ const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
16
+
17
+ expect(packageJson.name).toBe('@vfarcic/dot-ai');
18
+ expect(packageJson.main).toBe('dist/index.js');
19
+ expect(packageJson.bin).toHaveProperty('dot-ai');
20
+ expect(packageJson.exports['.']).toBeDefined();
21
+ expect(packageJson.exports['./mcp']).toBeDefined();
22
+ });
23
+
24
+ test('should have TypeScript configuration', () => {
25
+ const tsConfig = JSON.parse(readFileSync('tsconfig.json', 'utf8'));
26
+
27
+ expect(tsConfig.compilerOptions.strict).toBe(true);
28
+ expect(tsConfig.compilerOptions.outDir).toBe('./dist');
29
+ expect(tsConfig.compilerOptions.rootDir).toBe('./src');
30
+ });
31
+
32
+ test('should have Jest configuration in package.json', () => {
33
+ const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
34
+
35
+ expect(packageJson.jest).toBeDefined();
36
+ expect(packageJson.jest.preset).toBe('ts-jest');
37
+ expect(packageJson.jest.testEnvironment).toBe('node');
38
+ });
39
+ });
40
+
41
+ describe('Directory Structure Expectations', () => {
42
+ test('should be able to create src directory structure', () => {
43
+ // Test that we can create the expected directory structure
44
+ const expectedDirs = [
45
+ 'src',
46
+ 'src/core',
47
+ 'src/interfaces',
48
+ 'bin'
49
+ ];
50
+
51
+ expectedDirs.forEach(dir => {
52
+ if (!existsSync(dir)) {
53
+ mkdirSync(dir, { recursive: true });
54
+ }
55
+ expect(existsSync(dir)).toBe(true);
56
+ });
57
+ });
58
+
59
+ test('should be able to validate expected file locations', () => {
60
+ // Test paths that should exist or be creatable
61
+ const expectedFiles = [
62
+ { path: 'src/index.ts', shouldExist: false }, // Will be created
63
+ { path: 'src/core/index.ts', shouldExist: false }, // Will be created
64
+ { path: 'src/interfaces/cli.ts', shouldExist: false }, // Will be created
65
+ { path: 'src/interfaces/mcp.ts', shouldExist: false }, // Will be created
66
+ { path: 'bin/dot-ai', shouldExist: false } // Will be created
67
+ ];
68
+
69
+ expectedFiles.forEach(({ path: filePath, shouldExist }) => {
70
+ if (shouldExist) {
71
+ expect(existsSync(filePath)).toBe(true);
72
+ } else {
73
+ // Test that the directory exists for future file creation
74
+ const dir = path.dirname(filePath);
75
+ expect(existsSync(dir)).toBe(true);
76
+ }
77
+ });
78
+ });
79
+ });
80
+
81
+ describe('TypeScript Environment', () => {
82
+ test('should support TypeScript compilation', () => {
83
+ // This test validates that our TypeScript environment is working
84
+ // by using TypeScript features that would fail if not properly configured
85
+
86
+ interface TestInterface {
87
+ name: string;
88
+ value: number;
89
+ }
90
+
91
+ const testObject: TestInterface = {
92
+ name: 'test',
93
+ value: 42
94
+ };
95
+
96
+ expect(testObject.name).toBe('test');
97
+ expect(testObject.value).toBe(42);
98
+ });
99
+
100
+ test('should support ES2022 features', () => {
101
+ // Test that our target ES2022 is working
102
+ const testArray = [1, 2, 3, 4, 5];
103
+ const result = testArray.at(-1); // ES2022 feature
104
+
105
+ expect(result).toBe(5);
106
+ });
107
+
108
+ test('should support async/await', async () => {
109
+ // Test async support which we'll need for Kubernetes API calls
110
+ const asyncFunction = async (): Promise<string> => {
111
+ return Promise.resolve('async works');
112
+ };
113
+
114
+ const result = await asyncFunction();
115
+ expect(result).toBe('async works');
116
+ });
117
+ });
118
+
119
+ describe('Test Utilities', () => {
120
+ test('should be able to mock modules', () => {
121
+ // Test that Jest mocking is working
122
+ const mockFunction = jest.fn();
123
+ mockFunction.mockReturnValue('mocked value');
124
+
125
+ expect(mockFunction()).toBe('mocked value');
126
+ expect(mockFunction).toHaveBeenCalledTimes(1);
127
+ });
128
+
129
+ test('should support test environment setup', () => {
130
+ // Test that our test environment variables and setup are working
131
+ expect(process.env.NODE_ENV).toBe('test');
132
+ });
133
+ });
134
+ });
135
+
136
+ describe('Smoke Tests', () => {
137
+ test('Jest is working correctly', () => {
138
+ expect(1 + 1).toBe(2);
139
+ });
140
+
141
+ test('TypeScript compilation is working', () => {
142
+ const message: string = 'TypeScript works';
143
+ expect(typeof message).toBe('string');
144
+ expect(message).toBe('TypeScript works');
145
+ });
146
+ });
147
+
148
+ describe('CI/CD Pipeline Infrastructure', () => {
149
+ const rootDir = process.cwd();
150
+ const githubWorkflowsDir = path.join(rootDir, '.github', 'workflows');
151
+
152
+ describe('GitHub Actions Configuration', () => {
153
+ test('should have .github/workflows directory', () => {
154
+ expect(existsSync(githubWorkflowsDir)).toBe(true);
155
+ });
156
+
157
+ test('should have main CI workflow file', () => {
158
+ const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
159
+ expect(existsSync(ciWorkflowPath)).toBe(true);
160
+ });
161
+
162
+ test('should have consolidated CI & security workflow', () => {
163
+ const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
164
+ expect(existsSync(ciWorkflowPath)).toBe(true);
165
+
166
+ // Should NOT have separate security.yml (consolidated into ci.yml)
167
+ const securityWorkflowPath = path.join(githubWorkflowsDir, 'security.yml');
168
+ expect(existsSync(securityWorkflowPath)).toBe(false);
169
+ });
170
+
171
+ test('should have renovate configuration for dependency management', () => {
172
+ const renovatePath = path.join(rootDir, 'renovate.json');
173
+ expect(existsSync(renovatePath)).toBe(true);
174
+
175
+ const renovateContent = readFileSync(renovatePath, 'utf-8');
176
+ const renovateConfig = JSON.parse(renovateContent);
177
+ expect(renovateConfig.extends).toContain('config:base');
178
+ expect(renovateConfig.schedule).toBeDefined();
179
+ expect(renovateConfig.packageRules).toBeDefined();
180
+ });
181
+ });
182
+
183
+ describe('CI Workflow Validation', () => {
184
+ let ciWorkflow: any;
185
+
186
+ beforeAll(() => {
187
+ const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
188
+ if (existsSync(ciWorkflowPath)) {
189
+ const content = readFileSync(ciWorkflowPath, 'utf-8');
190
+ ciWorkflow = yaml.parse(content);
191
+ }
192
+ });
193
+
194
+ test('should trigger on push and pull request', () => {
195
+ expect(ciWorkflow?.on).toBeDefined();
196
+ expect(ciWorkflow.on.push).toBeDefined();
197
+ expect(ciWorkflow.on.pull_request).toBeDefined();
198
+ });
199
+
200
+ test('should have test job with proper Node.js setup', () => {
201
+ expect(ciWorkflow?.jobs?.test).toBeDefined();
202
+ const testJob = ciWorkflow.jobs.test;
203
+
204
+ expect(testJob['runs-on']).toBe('ubuntu-latest');
205
+
206
+ // Should use single Node.js 20.x (no matrix for performance)
207
+ expect(testJob.strategy?.matrix).toBeUndefined();
208
+
209
+ // Should use Node.js 20.x in setup step
210
+ const nodeSetupStep = testJob?.steps?.find((step: any) => step.uses?.includes('actions/setup-node'));
211
+ expect(nodeSetupStep?.with?.['node-version']).toBe('20.x');
212
+ });
213
+
214
+ test('should include all required CI steps', () => {
215
+ const testJob = ciWorkflow?.jobs?.test;
216
+ const stepNames = testJob?.steps?.map((step: any) => step.name || step.uses) || [];
217
+
218
+ expect(stepNames.some((name: string) => name.toLowerCase().includes('checkout'))).toBe(true);
219
+ expect(stepNames.some((name: string) => name.includes('Node.js'))).toBe(true);
220
+ expect(stepNames.some((name: string) => name.toLowerCase().includes('install'))).toBe(true);
221
+ expect(stepNames.some((name: string) => name.toLowerCase().includes('lint'))).toBe(true);
222
+ expect(stepNames.some((name: string) => name.toLowerCase().includes('build'))).toBe(true);
223
+ expect(stepNames.some((name: string) => name.toLowerCase().includes('test'))).toBe(true);
224
+ });
225
+
226
+ test('should focus on unit testing without cluster setup', () => {
227
+ const ciContent = readFileSync('.github/workflows/ci.yml', 'utf8');
228
+ const ciWorkflow = yaml.parse(ciContent);
229
+
230
+ const testJob = ciWorkflow.jobs?.test;
231
+ expect(testJob).toBeDefined();
232
+
233
+ const stepNames = testJob?.steps?.map((step: any) => step.name || step.uses) || [];
234
+
235
+ // Should NOT have cluster setup since we use mocked tests
236
+ expect(stepNames.some((name: string) => name.toLowerCase().includes('kind'))).toBe(false);
237
+ expect(stepNames.some((name: string) => name.toLowerCase().includes('cluster'))).toBe(false);
238
+ });
239
+
240
+ test('should use npm ci for efficient dependency installation', () => {
241
+ const testJob = ciWorkflow?.jobs?.test;
242
+ const stepCommands = testJob?.steps?.map((step: any) => step.run) || [];
243
+
244
+ // Should use npm ci (which handles caching automatically)
245
+ expect(stepCommands.some((cmd: string) => cmd?.includes('npm ci'))).toBe(true);
246
+
247
+ // Should have Node.js cache enabled in setup step
248
+ const nodeSetupStep = testJob?.steps?.find((step: any) => step.uses?.includes('actions/setup-node'));
249
+ expect(nodeSetupStep?.with?.cache).toBe('npm');
250
+ });
251
+ });
252
+
253
+ describe('Consolidated Security Features Validation', () => {
254
+ let ciWorkflow: any;
255
+
256
+ beforeAll(() => {
257
+ const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
258
+ if (existsSync(ciWorkflowPath)) {
259
+ const content = readFileSync(ciWorkflowPath, 'utf-8');
260
+ ciWorkflow = yaml.parse(content);
261
+ }
262
+ });
263
+
264
+ test('should have separate security job with dependency audit', () => {
265
+ expect(ciWorkflow?.jobs?.security).toBeDefined();
266
+ const securityJob = ciWorkflow.jobs.security;
267
+
268
+ const auditStep = securityJob?.steps?.find((step: any) =>
269
+ step.name?.toLowerCase().includes('audit') ||
270
+ step.run?.includes('npm audit')
271
+ );
272
+ expect(auditStep).toBeDefined();
273
+ });
274
+
275
+ test('should include CodeQL analysis job', () => {
276
+ expect(ciWorkflow?.jobs?.security).toBeDefined();
277
+ const securityJob = ciWorkflow.jobs.security;
278
+
279
+ const codeqlInitStep = securityJob?.steps?.find((step: any) =>
280
+ step.uses?.includes('github/codeql-action/init')
281
+ );
282
+ expect(codeqlInitStep).toBeDefined();
283
+
284
+ const codeqlAnalyzeStep = securityJob?.steps?.find((step: any) =>
285
+ step.uses?.includes('github/codeql-action/analyze')
286
+ );
287
+ expect(codeqlAnalyzeStep).toBeDefined();
288
+ });
289
+
290
+ test('should have proper permissions for security scanning and publishing', () => {
291
+ expect(ciWorkflow?.permissions).toBeDefined();
292
+ expect(ciWorkflow.permissions.actions).toBe('read');
293
+ expect(ciWorkflow.permissions.contents).toBe('write'); // Needed for publishing and tagging
294
+ expect(ciWorkflow.permissions['security-events']).toBe('write');
295
+ expect(ciWorkflow.permissions['id-token']).toBe('write'); // Needed for npm publishing
296
+ });
297
+ });
298
+
299
+ describe('Renovate Configuration Validation', () => {
300
+ let renovateConfig: any;
301
+
302
+ beforeAll(() => {
303
+ const renovatePath = path.join(rootDir, 'renovate.json');
304
+ if (existsSync(renovatePath)) {
305
+ const content = readFileSync(renovatePath, 'utf-8');
306
+ renovateConfig = JSON.parse(content);
307
+ }
308
+ });
309
+
310
+ test('should extend base configuration', () => {
311
+ expect(renovateConfig?.extends).toBeDefined();
312
+ expect(renovateConfig.extends).toContain('config:base');
313
+ });
314
+
315
+ test('should have scheduled updates', () => {
316
+ expect(renovateConfig?.schedule).toBeDefined();
317
+ expect(Array.isArray(renovateConfig.schedule)).toBe(true);
318
+ });
319
+
320
+ test('should have package rules for intelligent grouping', () => {
321
+ expect(renovateConfig?.packageRules).toBeDefined();
322
+ expect(Array.isArray(renovateConfig.packageRules)).toBe(true);
323
+ expect(renovateConfig.packageRules.length).toBeGreaterThan(0);
324
+ });
325
+
326
+ test('should configure automerge for safe updates', () => {
327
+ const automergeRule = renovateConfig?.packageRules?.find((rule: any) =>
328
+ rule.automerge === true
329
+ );
330
+ expect(automergeRule).toBeDefined();
331
+ });
332
+ });
333
+
334
+ describe('Workflow Security and Best Practices', () => {
335
+ test('workflows should use specific action versions (not @main)', () => {
336
+ const workflowFiles = ['ci.yml', 'security.yml'];
337
+
338
+ workflowFiles.forEach(file => {
339
+ const workflowPath = path.join(githubWorkflowsDir, file);
340
+ if (existsSync(workflowPath)) {
341
+ const content = readFileSync(workflowPath, 'utf-8');
342
+ const workflow = yaml.parse(content);
343
+
344
+ // Check all jobs for action versions
345
+ Object.values(workflow.jobs || {}).forEach((job: any) => {
346
+ job.steps?.forEach((step: any) => {
347
+ if (step.uses && !step.uses.includes('@v')) {
348
+ // Allow some exceptions for well-known stable actions
349
+ const allowedMainActions = ['actions/checkout@main'];
350
+ if (!allowedMainActions.includes(step.uses)) {
351
+ expect(step.uses).toMatch(/@v\d+/);
352
+ }
353
+ }
354
+ });
355
+ });
356
+ }
357
+ });
358
+ });
359
+
360
+ test('workflows should use secure action versions', () => {
361
+ const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
362
+ if (existsSync(ciWorkflowPath)) {
363
+ const content = readFileSync(ciWorkflowPath, 'utf-8');
364
+ const workflow = yaml.parse(content);
365
+
366
+ // Simple workflows don't need explicit permissions for basic read/test operations
367
+ // but should use versioned actions for security
368
+ Object.values(workflow.jobs || {}).forEach((job: any) => {
369
+ job.steps?.forEach((step: any) => {
370
+ if (step.uses) {
371
+ expect(step.uses).toMatch(/@v\d+/);
372
+ }
373
+ });
374
+ });
375
+ }
376
+ });
377
+ });
378
+
379
+ describe('Package.json CI/CD Integration', () => {
380
+ let packageJson: any;
381
+
382
+ beforeAll(() => {
383
+ const packagePath = path.join(rootDir, 'package.json');
384
+ const content = readFileSync(packagePath, 'utf-8');
385
+ packageJson = JSON.parse(content);
386
+ });
387
+
388
+ test('should have CI-friendly scripts', () => {
389
+ expect(packageJson.scripts['ci']).toBeDefined();
390
+ expect(packageJson.scripts['ci:test']).toBeDefined();
391
+ expect(packageJson.scripts['ci:build']).toBeDefined();
392
+ });
393
+
394
+ test('should have engines field for Node.js version', () => {
395
+ expect(packageJson.engines?.node).toBeDefined();
396
+ expect(packageJson.engines.node).toMatch(/>=\s*18/);
397
+ });
398
+
399
+ test('should have repository field for GitHub integration', () => {
400
+ expect(packageJson.repository).toBeDefined();
401
+ expect(typeof packageJson.repository === 'string' || packageJson.repository.type).toBe('git');
402
+ });
403
+ });
404
+
405
+ describe('Development Dependencies for CI/CD', () => {
406
+ let packageJson: any;
407
+
408
+ beforeAll(() => {
409
+ const packagePath = path.join(rootDir, 'package.json');
410
+ const content = readFileSync(packagePath, 'utf-8');
411
+ packageJson = JSON.parse(content);
412
+ });
413
+
414
+ test('should include security scanning tools', () => {
415
+ const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
416
+ // Should have audit tools or rely on npm audit
417
+ expect(packageJson.scripts).toHaveProperty('audit');
418
+ });
419
+
420
+ test('should include linting and formatting for CI', () => {
421
+ const devDeps = packageJson.devDependencies || {};
422
+ expect(devDeps.eslint).toBeDefined();
423
+ expect(devDeps.prettier).toBeDefined();
424
+ });
425
+ });
426
+ });