sdd-cli 0.1.26 → 0.1.27
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.
|
@@ -189,8 +189,15 @@ function extraPromptConstraints(intent) {
|
|
|
189
189
|
constraints.push("Use modern React data layer: @tanstack/react-query (not react-query).");
|
|
190
190
|
constraints.push("Backend architecture must include DTO classes, service interfaces, and repository interfaces.");
|
|
191
191
|
constraints.push("Use Java records for immutable request/response or transport models.");
|
|
192
|
+
constraints.push("Use Lombok in backend entities/DTOs where appropriate (builder/getter/setter/constructor patterns).");
|
|
193
|
+
constraints.push("Use Jakarta/Javax Bean Validation annotations and @Valid in request boundaries.");
|
|
194
|
+
constraints.push("Include @RestControllerAdvice for global exception handling.");
|
|
195
|
+
constraints.push("Add Spring Actuator telemetry and basic Prometheus-friendly metrics configuration.");
|
|
192
196
|
constraints.push("Frontend architecture must include src/api, src/hooks (use*.ts/tsx), and src/components layers.");
|
|
197
|
+
constraints.push("Frontend bootstrap must use React.StrictMode.");
|
|
198
|
+
constraints.push("Frontend should include safe input validation and avoid direct unsafe HTML rendering.");
|
|
193
199
|
constraints.push("Include frontend tests and backend tests that run in local CI.");
|
|
200
|
+
constraints.push("Include architecture.md and execution-guide.md with clear local run instructions.");
|
|
194
201
|
}
|
|
195
202
|
if (intentSuggestsRelationalDataDomain(intent)) {
|
|
196
203
|
constraints.push("Use a scalable relational database default (prefer PostgreSQL).");
|
|
@@ -44,6 +44,15 @@ function countJsTsTests(root, maxDepth = 8) {
|
|
|
44
44
|
}
|
|
45
45
|
return count;
|
|
46
46
|
}
|
|
47
|
+
function fileExistsAny(root, candidates) {
|
|
48
|
+
for (const candidate of candidates) {
|
|
49
|
+
const full = path_1.default.join(root, candidate);
|
|
50
|
+
if (fs_1.default.existsSync(full)) {
|
|
51
|
+
return full;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
47
56
|
function findFileRecursive(root, predicate, maxDepth = 4) {
|
|
48
57
|
const walk = (current, depth) => {
|
|
49
58
|
if (depth > maxDepth) {
|
|
@@ -314,6 +323,15 @@ function advancedQualityCheck(appDir, context) {
|
|
|
314
323
|
output: "Outdated frontend dependency detected: react-query. Use @tanstack/react-query."
|
|
315
324
|
};
|
|
316
325
|
}
|
|
326
|
+
const requiredFrontendDeps = ["react-router-dom", "@tanstack/react-query"];
|
|
327
|
+
const missingFrontendDeps = requiredFrontendDeps.filter((dep) => typeof deps[dep] !== "string");
|
|
328
|
+
if (missingFrontendDeps.length > 0) {
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
command: "advanced-quality-check",
|
|
332
|
+
output: `Missing modern frontend dependencies: ${missingFrontendDeps.join(", ")}`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
317
335
|
}
|
|
318
336
|
const backendRoot = path_1.default.join(appDir, "backend", "src", "main", "java");
|
|
319
337
|
if (!fs_1.default.existsSync(backendRoot)) {
|
|
@@ -351,6 +369,59 @@ function advancedQualityCheck(appDir, context) {
|
|
|
351
369
|
output: "Missing service/repository interfaces in Java backend architecture"
|
|
352
370
|
};
|
|
353
371
|
}
|
|
372
|
+
const hasControllerAdvice = backendFiles.some((rel) => /@RestControllerAdvice\b/.test(fs_1.default.readFileSync(path_1.default.join(backendRoot, rel), "utf-8")));
|
|
373
|
+
if (!hasControllerAdvice) {
|
|
374
|
+
return {
|
|
375
|
+
ok: false,
|
|
376
|
+
command: "advanced-quality-check",
|
|
377
|
+
output: "Missing global exception handling (expected @RestControllerAdvice)"
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const hasValidationUsage = backendFiles.some((rel) => {
|
|
381
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(backendRoot, rel), "utf-8");
|
|
382
|
+
return /\b@(Valid|NotNull|NotBlank|Size|Email)\b/.test(raw) && /(jakarta|javax)\.validation/.test(raw);
|
|
383
|
+
});
|
|
384
|
+
if (!hasValidationUsage) {
|
|
385
|
+
return {
|
|
386
|
+
ok: false,
|
|
387
|
+
command: "advanced-quality-check",
|
|
388
|
+
output: "Missing bean validation usage (expected @Valid/@NotBlank with jakarta/javax.validation imports)"
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const pomPath = path_1.default.join(appDir, "backend", "pom.xml");
|
|
392
|
+
const pomRaw = fs_1.default.existsSync(pomPath) ? fs_1.default.readFileSync(pomPath, "utf-8").toLowerCase() : "";
|
|
393
|
+
const requiredBackendDeps = [
|
|
394
|
+
"lombok",
|
|
395
|
+
"spring-boot-starter-validation",
|
|
396
|
+
"spring-boot-starter-actuator"
|
|
397
|
+
];
|
|
398
|
+
const missingBackendDeps = requiredBackendDeps.filter((dep) => !pomRaw.includes(dep));
|
|
399
|
+
if (missingBackendDeps.length > 0) {
|
|
400
|
+
return {
|
|
401
|
+
ok: false,
|
|
402
|
+
command: "advanced-quality-check",
|
|
403
|
+
output: `Missing backend dependencies for production quality: ${missingBackendDeps.join(", ")}`
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const hasMetricsConfig = (() => {
|
|
407
|
+
const metricsFile = fileExistsAny(path_1.default.join(appDir, "backend"), [
|
|
408
|
+
"src/main/resources/application.yml",
|
|
409
|
+
"src/main/resources/application.yaml",
|
|
410
|
+
"src/main/resources/application.properties"
|
|
411
|
+
]);
|
|
412
|
+
if (!metricsFile) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
const text = normalizeText(fs_1.default.readFileSync(metricsFile, "utf-8"));
|
|
416
|
+
return /management\.endpoints|prometheus|actuator/.test(text);
|
|
417
|
+
})();
|
|
418
|
+
if (!hasMetricsConfig) {
|
|
419
|
+
return {
|
|
420
|
+
ok: false,
|
|
421
|
+
command: "advanced-quality-check",
|
|
422
|
+
output: "Missing backend telemetry config (expected actuator/prometheus management settings)"
|
|
423
|
+
};
|
|
424
|
+
}
|
|
354
425
|
const frontendRoot = path_1.default.join(appDir, "frontend", "src");
|
|
355
426
|
if (!fs_1.default.existsSync(frontendRoot)) {
|
|
356
427
|
return {
|
|
@@ -378,6 +449,39 @@ function advancedQualityCheck(appDir, context) {
|
|
|
378
449
|
output: `Expected at least 3 frontend tests for Java+React profile, found ${frontendTestCount}`
|
|
379
450
|
};
|
|
380
451
|
}
|
|
452
|
+
const frontendUsesStrictMode = (() => {
|
|
453
|
+
const mainCandidate = fileExistsAny(path_1.default.join(appDir, "frontend"), ["src/main.tsx", "src/main.jsx", "src/index.tsx", "src/index.jsx"]);
|
|
454
|
+
if (!mainCandidate) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
const raw = fs_1.default.readFileSync(mainCandidate, "utf-8");
|
|
458
|
+
return /StrictMode/.test(raw);
|
|
459
|
+
})();
|
|
460
|
+
if (!frontendUsesStrictMode) {
|
|
461
|
+
return {
|
|
462
|
+
ok: false,
|
|
463
|
+
command: "advanced-quality-check",
|
|
464
|
+
output: "Missing React StrictMode in frontend bootstrap"
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const executionGuideExists = findFileRecursive(appDir, (rel) => rel === "execution-guide.md" || rel.endsWith("/execution-guide.md"), 8) ??
|
|
468
|
+
findFileRecursive(appDir, (rel) => rel.includes("runbook") && rel.endsWith(".md"), 8);
|
|
469
|
+
if (!executionGuideExists) {
|
|
470
|
+
return {
|
|
471
|
+
ok: false,
|
|
472
|
+
command: "advanced-quality-check",
|
|
473
|
+
output: "Missing execution guide/runbook markdown (expected execution-guide.md or runbook*.md)"
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
const architectureDocExists = findFileRecursive(appDir, (rel) => rel === "architecture.md" || rel.endsWith("/architecture.md"), 8) ??
|
|
477
|
+
findFileRecursive(appDir, (rel) => rel.includes("architecture") && rel.endsWith(".md"), 8);
|
|
478
|
+
if (!architectureDocExists) {
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
command: "advanced-quality-check",
|
|
482
|
+
output: "Missing architecture documentation markdown (expected architecture.md)"
|
|
483
|
+
};
|
|
484
|
+
}
|
|
381
485
|
}
|
|
382
486
|
const dummyLocalDoc = findFileRecursive(appDir, (rel) => rel.includes("dummylocal") && rel.endsWith(".md")) ??
|
|
383
487
|
findFileRecursive(appDir, (rel) => rel.includes("dummy-local") && rel.endsWith(".md")) ??
|
|
@@ -516,7 +620,7 @@ function deriveRepoMetadata(projectName, appDir, context) {
|
|
|
516
620
|
const goalSeed = context?.goalText ? tokenizeIntent(context.goalText).slice(0, 6).join("-") : "";
|
|
517
621
|
const projectSeed = slugify(rawBase);
|
|
518
622
|
const intentSeed = slugify(goalSeed);
|
|
519
|
-
const base =
|
|
623
|
+
const base = intentSeed || projectSeed || "sdd-project";
|
|
520
624
|
const cleaned = base.replace(/-app$/g, "");
|
|
521
625
|
const repoName = `${cleaned}-app`.slice(0, 63).replace(/-+$/g, "");
|
|
522
626
|
let description = context?.goalText?.trim()
|