tlc-claude-code 1.4.8 → 1.4.9
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.
- package/package.json +1 -1
- package/server/index.js +229 -14
- package/server/lib/compliance/control-mapper.js +401 -0
- package/server/lib/compliance/control-mapper.test.js +117 -0
- package/server/lib/compliance/evidence-linker.js +296 -0
- package/server/lib/compliance/evidence-linker.test.js +121 -0
- package/server/lib/compliance/gdpr-checklist.js +416 -0
- package/server/lib/compliance/gdpr-checklist.test.js +131 -0
- package/server/lib/compliance/hipaa-checklist.js +277 -0
- package/server/lib/compliance/hipaa-checklist.test.js +101 -0
- package/server/lib/compliance/iso27001-checklist.js +287 -0
- package/server/lib/compliance/iso27001-checklist.test.js +99 -0
- package/server/lib/compliance/multi-framework-reporter.js +284 -0
- package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
- package/server/lib/compliance/pci-dss-checklist.js +214 -0
- package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
- package/server/lib/compliance/trust-centre.js +187 -0
- package/server/lib/compliance/trust-centre.test.js +93 -0
- package/server/lib/dashboard/api-server.js +155 -0
- package/server/lib/dashboard/api-server.test.js +155 -0
- package/server/lib/dashboard/health-api.js +199 -0
- package/server/lib/dashboard/health-api.test.js +122 -0
- package/server/lib/dashboard/notes-api.js +234 -0
- package/server/lib/dashboard/notes-api.test.js +134 -0
- package/server/lib/dashboard/router-api.js +176 -0
- package/server/lib/dashboard/router-api.test.js +132 -0
- package/server/lib/dashboard/tasks-api.js +289 -0
- package/server/lib/dashboard/tasks-api.test.js +161 -0
- package/server/lib/dashboard/tlc-introspection.js +197 -0
- package/server/lib/dashboard/tlc-introspection.test.js +138 -0
- package/server/lib/dashboard/version-api.js +222 -0
- package/server/lib/dashboard/version-api.test.js +112 -0
- package/server/lib/dashboard/websocket-server.js +104 -0
- package/server/lib/dashboard/websocket-server.test.js +118 -0
- package/server/lib/deploy/branch-classifier.js +163 -0
- package/server/lib/deploy/branch-classifier.test.js +164 -0
- package/server/lib/deploy/deployment-approval.js +299 -0
- package/server/lib/deploy/deployment-approval.test.js +296 -0
- package/server/lib/deploy/deployment-audit.js +374 -0
- package/server/lib/deploy/deployment-audit.test.js +307 -0
- package/server/lib/deploy/deployment-executor.js +335 -0
- package/server/lib/deploy/deployment-executor.test.js +329 -0
- package/server/lib/deploy/deployment-rules.js +163 -0
- package/server/lib/deploy/deployment-rules.test.js +188 -0
- package/server/lib/deploy/rollback-manager.js +379 -0
- package/server/lib/deploy/rollback-manager.test.js +321 -0
- package/server/lib/deploy/security-gates.js +236 -0
- package/server/lib/deploy/security-gates.test.js +222 -0
- package/server/lib/k8s/gitops-config.js +188 -0
- package/server/lib/k8s/gitops-config.test.js +59 -0
- package/server/lib/k8s/helm-generator.js +196 -0
- package/server/lib/k8s/helm-generator.test.js +59 -0
- package/server/lib/k8s/kustomize-generator.js +176 -0
- package/server/lib/k8s/kustomize-generator.test.js +58 -0
- package/server/lib/k8s/network-policy.js +114 -0
- package/server/lib/k8s/network-policy.test.js +53 -0
- package/server/lib/k8s/pod-security.js +114 -0
- package/server/lib/k8s/pod-security.test.js +55 -0
- package/server/lib/k8s/rbac-generator.js +132 -0
- package/server/lib/k8s/rbac-generator.test.js +57 -0
- package/server/lib/k8s/resource-manager.js +172 -0
- package/server/lib/k8s/resource-manager.test.js +60 -0
- package/server/lib/k8s/secrets-encryption.js +168 -0
- package/server/lib/k8s/secrets-encryption.test.js +49 -0
- package/server/lib/monitoring/alert-manager.js +238 -0
- package/server/lib/monitoring/alert-manager.test.js +106 -0
- package/server/lib/monitoring/health-check.js +226 -0
- package/server/lib/monitoring/health-check.test.js +176 -0
- package/server/lib/monitoring/incident-manager.js +230 -0
- package/server/lib/monitoring/incident-manager.test.js +98 -0
- package/server/lib/monitoring/log-aggregator.js +147 -0
- package/server/lib/monitoring/log-aggregator.test.js +89 -0
- package/server/lib/monitoring/metrics-collector.js +337 -0
- package/server/lib/monitoring/metrics-collector.test.js +172 -0
- package/server/lib/monitoring/status-page.js +214 -0
- package/server/lib/monitoring/status-page.test.js +105 -0
- package/server/lib/monitoring/uptime-monitor.js +194 -0
- package/server/lib/monitoring/uptime-monitor.test.js +109 -0
- package/server/lib/network/fail2ban-config.js +294 -0
- package/server/lib/network/fail2ban-config.test.js +275 -0
- package/server/lib/network/firewall-manager.js +252 -0
- package/server/lib/network/firewall-manager.test.js +254 -0
- package/server/lib/network/geoip-filter.js +282 -0
- package/server/lib/network/geoip-filter.test.js +264 -0
- package/server/lib/network/rate-limiter.js +229 -0
- package/server/lib/network/rate-limiter.test.js +293 -0
- package/server/lib/network/request-validator.js +351 -0
- package/server/lib/network/request-validator.test.js +345 -0
- package/server/lib/network/security-headers.js +251 -0
- package/server/lib/network/security-headers.test.js +283 -0
- package/server/lib/network/tls-config.js +210 -0
- package/server/lib/network/tls-config.test.js +248 -0
- package/server/lib/security/auth-security.js +369 -0
- package/server/lib/security/auth-security.test.js +448 -0
- package/server/lib/security/cis-benchmark.js +152 -0
- package/server/lib/security/cis-benchmark.test.js +137 -0
- package/server/lib/security/compose-templates.js +312 -0
- package/server/lib/security/compose-templates.test.js +229 -0
- package/server/lib/security/container-runtime.js +456 -0
- package/server/lib/security/container-runtime.test.js +503 -0
- package/server/lib/security/cors-validator.js +278 -0
- package/server/lib/security/cors-validator.test.js +310 -0
- package/server/lib/security/crypto-utils.js +253 -0
- package/server/lib/security/crypto-utils.test.js +409 -0
- package/server/lib/security/dockerfile-linter.js +459 -0
- package/server/lib/security/dockerfile-linter.test.js +483 -0
- package/server/lib/security/dockerfile-templates.js +278 -0
- package/server/lib/security/dockerfile-templates.test.js +164 -0
- package/server/lib/security/error-sanitizer.js +426 -0
- package/server/lib/security/error-sanitizer.test.js +331 -0
- package/server/lib/security/headers-generator.js +368 -0
- package/server/lib/security/headers-generator.test.js +398 -0
- package/server/lib/security/image-scanner.js +83 -0
- package/server/lib/security/image-scanner.test.js +106 -0
- package/server/lib/security/input-validator.js +352 -0
- package/server/lib/security/input-validator.test.js +330 -0
- package/server/lib/security/network-policy.js +174 -0
- package/server/lib/security/network-policy.test.js +164 -0
- package/server/lib/security/output-encoder.js +237 -0
- package/server/lib/security/output-encoder.test.js +276 -0
- package/server/lib/security/path-validator.js +359 -0
- package/server/lib/security/path-validator.test.js +293 -0
- package/server/lib/security/query-builder.js +421 -0
- package/server/lib/security/query-builder.test.js +318 -0
- package/server/lib/security/secret-detector.js +290 -0
- package/server/lib/security/secret-detector.test.js +354 -0
- package/server/lib/security/secrets-validator.js +137 -0
- package/server/lib/security/secrets-validator.test.js +120 -0
- package/server/lib/security-testing/dast-runner.js +154 -0
- package/server/lib/security-testing/dast-runner.test.js +62 -0
- package/server/lib/security-testing/dependency-scanner.js +172 -0
- package/server/lib/security-testing/dependency-scanner.test.js +64 -0
- package/server/lib/security-testing/pentest-runner.js +230 -0
- package/server/lib/security-testing/pentest-runner.test.js +60 -0
- package/server/lib/security-testing/sast-runner.js +136 -0
- package/server/lib/security-testing/sast-runner.test.js +62 -0
- package/server/lib/security-testing/secret-scanner.js +153 -0
- package/server/lib/security-testing/secret-scanner.test.js +66 -0
- package/server/lib/security-testing/security-gate.js +216 -0
- package/server/lib/security-testing/security-gate.test.js +115 -0
- package/server/lib/security-testing/security-reporter.js +303 -0
- package/server/lib/security-testing/security-reporter.test.js +114 -0
- package/server/lib/standards/audit-checker.js +546 -0
- package/server/lib/standards/audit-checker.test.js +415 -0
- package/server/lib/standards/cleanup-executor.js +452 -0
- package/server/lib/standards/cleanup-executor.test.js +293 -0
- package/server/lib/standards/refactor-stepper.js +425 -0
- package/server/lib/standards/refactor-stepper.test.js +298 -0
- package/server/lib/standards/standards-injector.js +167 -0
- package/server/lib/standards/standards-injector.test.js +232 -0
- package/server/lib/user-management.test.js +284 -0
- package/server/lib/vps/backup-manager.js +157 -0
- package/server/lib/vps/backup-manager.test.js +59 -0
- package/server/lib/vps/caddy-config.js +159 -0
- package/server/lib/vps/caddy-config.test.js +48 -0
- package/server/lib/vps/compose-orchestrator.js +219 -0
- package/server/lib/vps/compose-orchestrator.test.js +50 -0
- package/server/lib/vps/database-config.js +208 -0
- package/server/lib/vps/database-config.test.js +47 -0
- package/server/lib/vps/deploy-script.js +211 -0
- package/server/lib/vps/deploy-script.test.js +53 -0
- package/server/lib/vps/secrets-manager.js +148 -0
- package/server/lib/vps/secrets-manager.test.js +58 -0
- package/server/lib/vps/server-hardening.js +174 -0
- package/server/lib/vps/server-hardening.test.js +70 -0
- package/server/package-lock.json +19 -0
- package/server/package.json +1 -0
- package/server/templates/CLAUDE.md +37 -0
- package/server/templates/CODING-STANDARDS.md +408 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit Checker Module
|
|
3
|
+
*
|
|
4
|
+
* Checks project structure and code for TLC standards compliance.
|
|
5
|
+
* @module audit-checker
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs').promises;
|
|
10
|
+
|
|
11
|
+
// Try to load glob, but make it optional (tests use mocks)
|
|
12
|
+
let defaultGlob;
|
|
13
|
+
try {
|
|
14
|
+
defaultGlob = require('glob').glob;
|
|
15
|
+
} catch {
|
|
16
|
+
defaultGlob = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check that CLAUDE.md and CODING-STANDARDS.md exist
|
|
21
|
+
* @param {string} projectPath - Path to project root
|
|
22
|
+
* @param {Object} options - Options with injectable dependencies
|
|
23
|
+
* @param {Object} options.fs - File system module (for testing)
|
|
24
|
+
* @returns {Promise<{passed: boolean, issues: Array<{file: string, type: string}>}>}
|
|
25
|
+
*/
|
|
26
|
+
async function checkStandardsFiles(projectPath, options = {}) {
|
|
27
|
+
const fsModule = options.fs || fs;
|
|
28
|
+
const issues = [];
|
|
29
|
+
const requiredFiles = ['CLAUDE.md', 'CODING-STANDARDS.md'];
|
|
30
|
+
|
|
31
|
+
for (const file of requiredFiles) {
|
|
32
|
+
const filePath = path.join(projectPath, file);
|
|
33
|
+
try {
|
|
34
|
+
await fsModule.access(filePath);
|
|
35
|
+
} catch {
|
|
36
|
+
issues.push({ file, type: 'missing' });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
passed: issues.length === 0,
|
|
42
|
+
issues
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Detect flat services/, interfaces/, controllers/ folders
|
|
48
|
+
* @param {string} projectPath - Path to project root
|
|
49
|
+
* @param {Object} options - Options with injectable dependencies
|
|
50
|
+
* @param {Function} options.glob - Glob function (for testing)
|
|
51
|
+
* @returns {Promise<{passed: boolean, issues: Array<{type: string, folder: string}>}>}
|
|
52
|
+
*/
|
|
53
|
+
async function checkFlatFolders(projectPath, options = {}) {
|
|
54
|
+
const globFn = options.glob || defaultGlob;
|
|
55
|
+
const issues = [];
|
|
56
|
+
const flatFolders = ['services', 'interfaces', 'controllers'];
|
|
57
|
+
|
|
58
|
+
for (const folder of flatFolders) {
|
|
59
|
+
const pattern = `src/${folder}/*.*`;
|
|
60
|
+
const files = await globFn(pattern, { cwd: projectPath });
|
|
61
|
+
if (files.length > 0) {
|
|
62
|
+
issues.push({ type: 'flat-folder', folder });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
passed: issues.length === 0,
|
|
68
|
+
issues
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Detect inline interfaces in service files
|
|
74
|
+
* @param {string} projectPath - Path to project root
|
|
75
|
+
* @param {Object} options - Options with injectable dependencies
|
|
76
|
+
* @param {Function} options.glob - Glob function (for testing)
|
|
77
|
+
* @param {Function} options.readFile - File read function (for testing)
|
|
78
|
+
* @returns {Promise<{passed: boolean, issues: Array<{type: string, file: string}>}>}
|
|
79
|
+
*/
|
|
80
|
+
async function checkInlineInterfaces(projectPath, options = {}) {
|
|
81
|
+
const globFn = options.glob || defaultGlob;
|
|
82
|
+
const readFileFn = options.readFile || (async (p) => fs.readFile(p, 'utf-8'));
|
|
83
|
+
const issues = [];
|
|
84
|
+
|
|
85
|
+
const pattern = '**/*.service.ts';
|
|
86
|
+
const files = await globFn(pattern, { cwd: projectPath });
|
|
87
|
+
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const filePath = path.join(projectPath, file);
|
|
90
|
+
const content = await readFileFn(filePath);
|
|
91
|
+
|
|
92
|
+
// Look for interface declarations (interface X {)
|
|
93
|
+
const interfacePattern = /\binterface\s+\w+\s*\{/;
|
|
94
|
+
if (interfacePattern.test(content)) {
|
|
95
|
+
issues.push({ type: 'inline-interface', file });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
passed: issues.length === 0,
|
|
101
|
+
issues
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Detect hardcoded URLs and ports in code
|
|
107
|
+
* @param {string} projectPath - Path to project root
|
|
108
|
+
* @param {Object} options - Options with injectable dependencies
|
|
109
|
+
* @param {Function} options.glob - Glob function (for testing)
|
|
110
|
+
* @param {Function} options.readFile - File read function (for testing)
|
|
111
|
+
* @returns {Promise<{passed: boolean, issues: Array<{type: string, file: string, value?: string}>}>}
|
|
112
|
+
*/
|
|
113
|
+
async function checkHardcodedUrls(projectPath, options = {}) {
|
|
114
|
+
const globFn = options.glob || defaultGlob;
|
|
115
|
+
const readFileFn = options.readFile || (async (p) => fs.readFile(p, 'utf-8'));
|
|
116
|
+
const issues = [];
|
|
117
|
+
|
|
118
|
+
const pattern = 'src/**/*.{js,ts,jsx,tsx}';
|
|
119
|
+
const files = await globFn(pattern, { cwd: projectPath });
|
|
120
|
+
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
const filePath = path.join(projectPath, file);
|
|
123
|
+
const content = await readFileFn(filePath);
|
|
124
|
+
|
|
125
|
+
// Check for hardcoded URLs (http:// or https://)
|
|
126
|
+
const urlPattern = /['"`](https?:\/\/[^'"`]+)['"`]/g;
|
|
127
|
+
let urlMatch;
|
|
128
|
+
while ((urlMatch = urlPattern.exec(content)) !== null) {
|
|
129
|
+
issues.push({
|
|
130
|
+
type: 'hardcoded-url',
|
|
131
|
+
file,
|
|
132
|
+
value: urlMatch[1]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check for hardcoded port assignments (const port = 3000)
|
|
137
|
+
const portPattern = /\b(const|let|var)\s+port\s*=\s*(\d+)/g;
|
|
138
|
+
let portMatch;
|
|
139
|
+
while ((portMatch = portPattern.exec(content)) !== null) {
|
|
140
|
+
issues.push({
|
|
141
|
+
type: 'hardcoded-port',
|
|
142
|
+
file,
|
|
143
|
+
value: portMatch[2]
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
passed: issues.length === 0,
|
|
150
|
+
issues
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Detect magic strings (string comparisons like === 'active')
|
|
156
|
+
* @param {string} projectPath - Path to project root
|
|
157
|
+
* @param {Object} options - Options with injectable dependencies
|
|
158
|
+
* @param {Function} options.glob - Glob function (for testing)
|
|
159
|
+
* @param {Function} options.readFile - File read function (for testing)
|
|
160
|
+
* @returns {Promise<{passed: boolean, issues: Array<{type: string, file: string, value: string}>}>}
|
|
161
|
+
*/
|
|
162
|
+
async function checkMagicStrings(projectPath, options = {}) {
|
|
163
|
+
const globFn = options.glob || defaultGlob;
|
|
164
|
+
const readFileFn = options.readFile || (async (p) => fs.readFile(p, 'utf-8'));
|
|
165
|
+
const issues = [];
|
|
166
|
+
|
|
167
|
+
// Common exceptions that are acceptable
|
|
168
|
+
const exceptions = [
|
|
169
|
+
'utf-8', 'utf8', 'utf-16', 'ascii', 'base64', 'hex', 'binary',
|
|
170
|
+
'application/json', 'text/html', 'text/plain',
|
|
171
|
+
'GET', 'POST', 'PUT', 'DELETE', 'PATCH',
|
|
172
|
+
'true', 'false', 'null', 'undefined',
|
|
173
|
+
'development', 'production', 'test'
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
const pattern = 'src/**/*.{js,ts,jsx,tsx}';
|
|
177
|
+
const files = await globFn(pattern, { cwd: projectPath });
|
|
178
|
+
|
|
179
|
+
for (const file of files) {
|
|
180
|
+
// Skip test files
|
|
181
|
+
if (file.includes('.test.') || file.includes('.spec.') || file.includes('__tests__')) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const filePath = path.join(projectPath, file);
|
|
186
|
+
const content = await readFileFn(filePath);
|
|
187
|
+
|
|
188
|
+
// Skip import/require statements
|
|
189
|
+
const lines = content.split('\n');
|
|
190
|
+
for (const line of lines) {
|
|
191
|
+
// Skip import/require lines
|
|
192
|
+
if (line.trim().startsWith('import') || line.includes('require(')) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Look for string comparisons: === 'value' or == 'value'
|
|
197
|
+
const magicPattern = /===?\s*['"]([^'"]+)['"]/g;
|
|
198
|
+
let match;
|
|
199
|
+
while ((match = magicPattern.exec(line)) !== null) {
|
|
200
|
+
const value = match[1];
|
|
201
|
+
// Skip exceptions
|
|
202
|
+
if (!exceptions.includes(value.toLowerCase())) {
|
|
203
|
+
issues.push({
|
|
204
|
+
type: 'magic-string',
|
|
205
|
+
file,
|
|
206
|
+
value
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
passed: issues.length === 0,
|
|
215
|
+
issues
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check seed files are organized per-entity (not in flat src/seeds/)
|
|
221
|
+
* @param {string} projectPath - Path to project root
|
|
222
|
+
* @param {Object} options - Options with injectable dependencies
|
|
223
|
+
* @param {Function} options.glob - Glob function (for testing)
|
|
224
|
+
* @returns {Promise<{passed: boolean, issues: Array<{type: string}>}>}
|
|
225
|
+
*/
|
|
226
|
+
async function checkSeedOrganization(projectPath, options = {}) {
|
|
227
|
+
const globFn = options.glob || defaultGlob;
|
|
228
|
+
const issues = [];
|
|
229
|
+
|
|
230
|
+
// Check for flat seeds/ folder at src/seeds/
|
|
231
|
+
// Pattern matches direct files in src/seeds/ (not nested in entity folders)
|
|
232
|
+
const flatSeedsPattern = 'src/seeds/*.*';
|
|
233
|
+
const files = await globFn(flatSeedsPattern, { cwd: projectPath });
|
|
234
|
+
|
|
235
|
+
// Filter to only files actually in src/seeds/ (not entity/seeds/)
|
|
236
|
+
const flatSeeds = files.filter(file => {
|
|
237
|
+
// Normalize path separators
|
|
238
|
+
const normalized = file.replace(/\\/g, '/');
|
|
239
|
+
// Check if file is directly in src/seeds/ (e.g., src/seeds/userSeed.ts)
|
|
240
|
+
// Not in src/{entity}/seeds/ (e.g., src/user/seeds/user.seed.ts)
|
|
241
|
+
return normalized.match(/^src\/seeds\/[^/]+$/);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (flatSeeds.length > 0) {
|
|
245
|
+
issues.push({ type: 'flat-seeds' });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
passed: issues.length === 0,
|
|
250
|
+
issues
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check exported functions have JSDoc comments
|
|
256
|
+
* @param {string} projectPath - Path to project root
|
|
257
|
+
* @param {Object} options - Options with injectable dependencies
|
|
258
|
+
* @param {Function} options.glob - Glob function (for testing)
|
|
259
|
+
* @param {Function} options.readFile - File read function (for testing)
|
|
260
|
+
* @returns {Promise<{passed: boolean, issues: Array<{type: string, file: string, function: string}>}>}
|
|
261
|
+
*/
|
|
262
|
+
async function checkJsDocCoverage(projectPath, options = {}) {
|
|
263
|
+
const globFn = options.glob || defaultGlob;
|
|
264
|
+
const readFileFn = options.readFile || (async (p) => fs.readFile(p, 'utf-8'));
|
|
265
|
+
const issues = [];
|
|
266
|
+
|
|
267
|
+
const pattern = 'src/**/*.{js,ts}';
|
|
268
|
+
const files = await globFn(pattern, { cwd: projectPath });
|
|
269
|
+
|
|
270
|
+
for (const file of files) {
|
|
271
|
+
const filePath = path.join(projectPath, file);
|
|
272
|
+
const content = await readFileFn(filePath);
|
|
273
|
+
const lines = content.split('\n');
|
|
274
|
+
|
|
275
|
+
for (let i = 0; i < lines.length; i++) {
|
|
276
|
+
const line = lines[i];
|
|
277
|
+
|
|
278
|
+
// Look for export function declarations
|
|
279
|
+
const exportFuncMatch = line.match(/export\s+function\s+(\w+)/);
|
|
280
|
+
if (exportFuncMatch) {
|
|
281
|
+
const funcName = exportFuncMatch[1];
|
|
282
|
+
|
|
283
|
+
// Check if previous non-empty line ends a JSDoc comment
|
|
284
|
+
let hasJsDoc = false;
|
|
285
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
286
|
+
const prevLine = lines[j].trim();
|
|
287
|
+
if (prevLine === '') continue;
|
|
288
|
+
if (prevLine.endsWith('*/')) {
|
|
289
|
+
// Check if this is a JSDoc (starts with /**)
|
|
290
|
+
for (let k = j; k >= 0; k--) {
|
|
291
|
+
if (lines[k].includes('/**')) {
|
|
292
|
+
hasJsDoc = true;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
if (lines[k].includes('/*') && !lines[k].includes('/**')) {
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!hasJsDoc) {
|
|
304
|
+
issues.push({
|
|
305
|
+
type: 'missing-jsdoc',
|
|
306
|
+
file,
|
|
307
|
+
function: funcName
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
passed: issues.length === 0,
|
|
316
|
+
issues
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Check for deep relative imports (3+ levels: ../../../)
|
|
322
|
+
* @param {string} projectPath - Path to project root
|
|
323
|
+
* @param {Object} options - Options with injectable dependencies
|
|
324
|
+
* @param {Function} options.glob - Glob function (for testing)
|
|
325
|
+
* @param {Function} options.readFile - File read function (for testing)
|
|
326
|
+
* @returns {Promise<{passed: boolean, issues: Array<{type: string, file: string}>}>}
|
|
327
|
+
*/
|
|
328
|
+
async function checkImportStyle(projectPath, options = {}) {
|
|
329
|
+
const globFn = options.glob || defaultGlob;
|
|
330
|
+
const readFileFn = options.readFile || (async (p) => fs.readFile(p, 'utf-8'));
|
|
331
|
+
const issues = [];
|
|
332
|
+
|
|
333
|
+
const pattern = 'src/**/*.{js,ts,jsx,tsx}';
|
|
334
|
+
const files = await globFn(pattern, { cwd: projectPath });
|
|
335
|
+
|
|
336
|
+
for (const file of files) {
|
|
337
|
+
const filePath = path.join(projectPath, file);
|
|
338
|
+
const content = await readFileFn(filePath);
|
|
339
|
+
|
|
340
|
+
// Look for deep relative imports (3+ levels)
|
|
341
|
+
// Matches: from '../../../' or from "../../../" or require('../../../')
|
|
342
|
+
const deepImportPattern = /(?:from\s+['"]|require\s*\(\s*['"])(?:\.\.\/){3,}/;
|
|
343
|
+
if (deepImportPattern.test(content)) {
|
|
344
|
+
issues.push({
|
|
345
|
+
type: 'deep-import',
|
|
346
|
+
file
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
passed: issues.length === 0,
|
|
353
|
+
issues
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Run all audit checks on a project
|
|
359
|
+
* @param {string} projectPath - Path to project root
|
|
360
|
+
* @param {Object} options - Options with injectable dependencies
|
|
361
|
+
* @returns {Promise<Object>} Audit results with summary
|
|
362
|
+
*/
|
|
363
|
+
async function auditProject(projectPath, options = {}) {
|
|
364
|
+
const [
|
|
365
|
+
standardsFiles,
|
|
366
|
+
flatFolders,
|
|
367
|
+
inlineInterfaces,
|
|
368
|
+
hardcodedUrls,
|
|
369
|
+
magicStrings,
|
|
370
|
+
seedOrganization,
|
|
371
|
+
jsDocCoverage,
|
|
372
|
+
importStyle
|
|
373
|
+
] = await Promise.all([
|
|
374
|
+
checkStandardsFiles(projectPath, options),
|
|
375
|
+
checkFlatFolders(projectPath, options),
|
|
376
|
+
checkInlineInterfaces(projectPath, options),
|
|
377
|
+
checkHardcodedUrls(projectPath, options),
|
|
378
|
+
checkMagicStrings(projectPath, options),
|
|
379
|
+
checkSeedOrganization(projectPath, options),
|
|
380
|
+
checkJsDocCoverage(projectPath, options),
|
|
381
|
+
checkImportStyle(projectPath, options)
|
|
382
|
+
]);
|
|
383
|
+
|
|
384
|
+
const allResults = [
|
|
385
|
+
standardsFiles,
|
|
386
|
+
flatFolders,
|
|
387
|
+
inlineInterfaces,
|
|
388
|
+
hardcodedUrls,
|
|
389
|
+
magicStrings,
|
|
390
|
+
seedOrganization,
|
|
391
|
+
jsDocCoverage,
|
|
392
|
+
importStyle
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
const totalIssues = allResults.reduce((sum, r) => sum + r.issues.length, 0);
|
|
396
|
+
const passed = allResults.every(r => r.passed);
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
standardsFiles,
|
|
400
|
+
flatFolders,
|
|
401
|
+
inlineInterfaces,
|
|
402
|
+
hardcodedUrls,
|
|
403
|
+
magicStrings,
|
|
404
|
+
seedOrganization,
|
|
405
|
+
jsDocCoverage,
|
|
406
|
+
importStyle,
|
|
407
|
+
summary: {
|
|
408
|
+
totalIssues,
|
|
409
|
+
passed
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Generate markdown audit report
|
|
416
|
+
* @param {Object} auditResults - Results from auditProject
|
|
417
|
+
* @returns {string} Markdown report content
|
|
418
|
+
*/
|
|
419
|
+
function generateReport(auditResults) {
|
|
420
|
+
const { summary } = auditResults;
|
|
421
|
+
const status = summary.passed ? 'PASSED' : 'FAILED';
|
|
422
|
+
const statusIcon = summary.passed ? '[+]' : '[-]';
|
|
423
|
+
|
|
424
|
+
let report = `# Audit Report\n\n`;
|
|
425
|
+
report += `**Status:** ${statusIcon} ${status}\n`;
|
|
426
|
+
report += `**Total Issues:** ${summary.totalIssues}\n\n`;
|
|
427
|
+
|
|
428
|
+
// Standards Files
|
|
429
|
+
if (auditResults.standardsFiles) {
|
|
430
|
+
report += `## Standards Files\n\n`;
|
|
431
|
+
if (auditResults.standardsFiles.passed) {
|
|
432
|
+
report += `[+] All required files present\n\n`;
|
|
433
|
+
} else {
|
|
434
|
+
for (const issue of auditResults.standardsFiles.issues) {
|
|
435
|
+
report += `- ${issue.type}: ${issue.file}\n`;
|
|
436
|
+
}
|
|
437
|
+
report += '\n';
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Flat Folders
|
|
442
|
+
if (auditResults.flatFolders) {
|
|
443
|
+
report += `## Folder Organization\n\n`;
|
|
444
|
+
if (auditResults.flatFolders.passed) {
|
|
445
|
+
report += `[+] No flat folders detected\n\n`;
|
|
446
|
+
} else {
|
|
447
|
+
for (const issue of auditResults.flatFolders.issues) {
|
|
448
|
+
report += `- ${issue.type}: ${issue.folder}\n`;
|
|
449
|
+
}
|
|
450
|
+
report += '\n';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Inline Interfaces
|
|
455
|
+
if (auditResults.inlineInterfaces) {
|
|
456
|
+
report += `## Inline Interfaces\n\n`;
|
|
457
|
+
if (auditResults.inlineInterfaces.passed) {
|
|
458
|
+
report += `[+] No inline interfaces in services\n\n`;
|
|
459
|
+
} else {
|
|
460
|
+
for (const issue of auditResults.inlineInterfaces.issues) {
|
|
461
|
+
report += `- ${issue.type}: ${issue.file}\n`;
|
|
462
|
+
}
|
|
463
|
+
report += '\n';
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Hardcoded URLs
|
|
468
|
+
if (auditResults.hardcodedUrls) {
|
|
469
|
+
report += `## Hardcoded URLs/Ports\n\n`;
|
|
470
|
+
if (auditResults.hardcodedUrls.passed) {
|
|
471
|
+
report += `[+] No hardcoded URLs or ports\n\n`;
|
|
472
|
+
} else {
|
|
473
|
+
for (const issue of auditResults.hardcodedUrls.issues) {
|
|
474
|
+
report += `- ${issue.type}: ${issue.file}${issue.value ? ` (${issue.value})` : ''}\n`;
|
|
475
|
+
}
|
|
476
|
+
report += '\n';
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Magic Strings
|
|
481
|
+
if (auditResults.magicStrings) {
|
|
482
|
+
report += `## Magic Strings\n\n`;
|
|
483
|
+
if (auditResults.magicStrings.passed) {
|
|
484
|
+
report += `[+] No magic strings detected\n\n`;
|
|
485
|
+
} else {
|
|
486
|
+
for (const issue of auditResults.magicStrings.issues) {
|
|
487
|
+
report += `- ${issue.type}: ${issue.file} (${issue.value})\n`;
|
|
488
|
+
}
|
|
489
|
+
report += '\n';
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Seed Organization
|
|
494
|
+
if (auditResults.seedOrganization) {
|
|
495
|
+
report += `## Seed Organization\n\n`;
|
|
496
|
+
if (auditResults.seedOrganization.passed) {
|
|
497
|
+
report += `[+] Seeds properly organized per-entity\n\n`;
|
|
498
|
+
} else {
|
|
499
|
+
for (const issue of auditResults.seedOrganization.issues) {
|
|
500
|
+
report += `- ${issue.type}: Use per-entity seed folders instead of flat src/seeds/\n`;
|
|
501
|
+
}
|
|
502
|
+
report += '\n';
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// JSDoc Coverage
|
|
507
|
+
if (auditResults.jsDocCoverage) {
|
|
508
|
+
report += `## JSDoc Coverage\n\n`;
|
|
509
|
+
if (auditResults.jsDocCoverage.passed) {
|
|
510
|
+
report += `[+] All exported functions have JSDoc\n\n`;
|
|
511
|
+
} else {
|
|
512
|
+
for (const issue of auditResults.jsDocCoverage.issues) {
|
|
513
|
+
report += `- ${issue.type}: ${issue.file} (${issue.function})\n`;
|
|
514
|
+
}
|
|
515
|
+
report += '\n';
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Import Style
|
|
520
|
+
if (auditResults.importStyle) {
|
|
521
|
+
report += `## Import Style\n\n`;
|
|
522
|
+
if (auditResults.importStyle.passed) {
|
|
523
|
+
report += `[+] No deep relative imports\n\n`;
|
|
524
|
+
} else {
|
|
525
|
+
for (const issue of auditResults.importStyle.issues) {
|
|
526
|
+
report += `- ${issue.type}: ${issue.file}\n`;
|
|
527
|
+
}
|
|
528
|
+
report += '\n';
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return report;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
module.exports = {
|
|
536
|
+
checkStandardsFiles,
|
|
537
|
+
checkFlatFolders,
|
|
538
|
+
checkInlineInterfaces,
|
|
539
|
+
checkHardcodedUrls,
|
|
540
|
+
checkMagicStrings,
|
|
541
|
+
checkSeedOrganization,
|
|
542
|
+
checkJsDocCoverage,
|
|
543
|
+
checkImportStyle,
|
|
544
|
+
auditProject,
|
|
545
|
+
generateReport
|
|
546
|
+
};
|