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 = projectSeed || intentSeed || "sdd-project";
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()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "AI-orchestrated specification-driven delivery CLI that plans, validates, and ships production-ready software projects.",
5
5
  "keywords": [
6
6
  "cli",