sdd-cli 0.1.26 → 0.1.28

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.
@@ -15,5 +15,5 @@ export type ImproveAppResult = {
15
15
  fileCount: number;
16
16
  reason?: string;
17
17
  };
18
- export declare function bootstrapProjectCode(projectRoot: string, projectName: string, intent: string, providerRequested?: string): CodeBootstrapResult;
19
- export declare function improveGeneratedApp(appDir: string, intent: string, providerRequested?: string, qualityDiagnostics?: string[]): ImproveAppResult;
18
+ export declare function bootstrapProjectCode(projectRoot: string, projectName: string, intent: string, providerRequested?: string, domainHint?: string): CodeBootstrapResult;
19
+ export declare function improveGeneratedApp(appDir: string, intent: string, providerRequested?: string, qualityDiagnostics?: string[], domainHint?: string): ImproveAppResult;
@@ -180,8 +180,85 @@ function intentSuggestsRelationalDataDomain(intent) {
180
180
  "hospital"
181
181
  ].some((token) => lower.includes(token));
182
182
  }
183
- function extraPromptConstraints(intent) {
183
+ function detectAutopilotDomain(intent, domainHint) {
184
+ const hinted = normalizeIntentText(domainHint ?? "");
185
+ if (hinted === "software" ||
186
+ hinted === "legal" ||
187
+ hinted === "business" ||
188
+ hinted === "humanities" ||
189
+ hinted === "learning" ||
190
+ hinted === "design" ||
191
+ hinted === "data_science" ||
192
+ hinted === "generic") {
193
+ return hinted;
194
+ }
195
+ const lower = normalizeIntentText(intent);
196
+ if (/\bcourt\b|\blaw\b|\bpolicy\b|\bcompliance\b|\blawyer\b|\bregulation\b|\bcontract\b/.test(lower))
197
+ return "legal";
198
+ if (/\bpricing\b|\bmarket\b|\bforecast\b|\beconomics\b|\baccounting\b|\bfinanzas\b|\bcontador\b/.test(lower))
199
+ return "business";
200
+ if (/\bhistory\b|\bsociology\b|\banthropology\b|\bphilosophy\b|\bliterature\b|\bhumanities\b/.test(lower))
201
+ return "humanities";
202
+ if (/\blearn\b|\bteach\b|\blesson\b|\bcourse\b|\bstudent\b|\bmentor\b|\bwriter\b|\bescritor\b/.test(lower))
203
+ return "learning";
204
+ if (/\blogo\b|\bbrand\b|\blayout\b|\bvisual\b|\bdesign\b/.test(lower))
205
+ return "design";
206
+ if (/\bmodel\b|\bdataset\b|\bprediction\b|\bmachine learning\b|\bml\b/.test(lower))
207
+ return "data_science";
208
+ if (/\bfeature\b|\bapi\b|\bbackend\b|\bfrontend\b|\bimplement\b|\bdeveloper\b|\bcode\b|\bapp\b|\bweb\b|\bdesktop\b|\bmovil\b|\bmobile\b/.test(lower)) {
209
+ return "software";
210
+ }
211
+ return "generic";
212
+ }
213
+ function domainPromptConstraints(domain) {
214
+ if (domain === "legal") {
215
+ return [
216
+ "Create legal-quality docs: compliance-matrix.md, risk-register.md, and legal-citations.md.",
217
+ "Compliance docs must define jurisdiction, applicable regulations, controls, and evidence mapping.",
218
+ "Risk register must include severity, likelihood, mitigation owner, and due date."
219
+ ];
220
+ }
221
+ if (domain === "business") {
222
+ return [
223
+ "Create business-quality docs: assumptions.md, sensitivity-analysis.md, and unit-economics.md.",
224
+ "Unit economics must include numeric metrics (CAC, LTV, margin, break-even or equivalent).",
225
+ "Sensitivity analysis must include best/base/worst scenarios and trigger thresholds."
226
+ ];
227
+ }
228
+ if (domain === "humanities") {
229
+ return [
230
+ "Create humanities-quality docs: methodology.md and sources.md.",
231
+ "sources.md must include at least 3 primary or high-quality secondary references.",
232
+ "Methodology must describe scope, analytical lens, limitations, and citation criteria."
233
+ ];
234
+ }
235
+ if (domain === "learning") {
236
+ return [
237
+ "Create learning-quality docs: curriculum.md, exercises.md, and references.md.",
238
+ "Exercises must include expected outcomes and evaluation criteria.",
239
+ "Curriculum must include modules, objectives, prerequisites, and progression."
240
+ ];
241
+ }
242
+ if (domain === "design") {
243
+ return [
244
+ "Create design-quality docs: design-system.md, accessibility.md, and rationale.md.",
245
+ "accessibility.md must include WCAG-oriented checks and contrast/keyboard validation.",
246
+ "rationale.md must capture major design decisions and tradeoffs."
247
+ ];
248
+ }
249
+ if (domain === "data_science") {
250
+ return [
251
+ "Create data-science quality docs: dataset-schema.md, evaluation-metrics.md, monitoring-plan.md, reproducibility.md.",
252
+ "evaluation-metrics.md must define baseline, target metrics, and acceptance thresholds.",
253
+ "monitoring-plan.md must define drift detection and alerting rules."
254
+ ];
255
+ }
256
+ return [];
257
+ }
258
+ function extraPromptConstraints(intent, domainHint) {
184
259
  const constraints = [];
260
+ const domain = detectAutopilotDomain(intent, domainHint);
261
+ constraints.push(...domainPromptConstraints(domain));
185
262
  if (intentRequiresJavaReactFullstack(intent)) {
186
263
  constraints.push("Use split structure: backend/ (Java Spring Boot) and frontend/ (React + Vite).");
187
264
  constraints.push("Backend must expose REST APIs for users, books, loans, and inventory.");
@@ -189,8 +266,15 @@ function extraPromptConstraints(intent) {
189
266
  constraints.push("Use modern React data layer: @tanstack/react-query (not react-query).");
190
267
  constraints.push("Backend architecture must include DTO classes, service interfaces, and repository interfaces.");
191
268
  constraints.push("Use Java records for immutable request/response or transport models.");
269
+ constraints.push("Use Lombok in backend entities/DTOs where appropriate (builder/getter/setter/constructor patterns).");
270
+ constraints.push("Use Jakarta/Javax Bean Validation annotations and @Valid in request boundaries.");
271
+ constraints.push("Include @RestControllerAdvice for global exception handling.");
272
+ constraints.push("Add Spring Actuator telemetry and basic Prometheus-friendly metrics configuration.");
192
273
  constraints.push("Frontend architecture must include src/api, src/hooks (use*.ts/tsx), and src/components layers.");
274
+ constraints.push("Frontend bootstrap must use React.StrictMode.");
275
+ constraints.push("Frontend should include safe input validation and avoid direct unsafe HTML rendering.");
193
276
  constraints.push("Include frontend tests and backend tests that run in local CI.");
277
+ constraints.push("Include architecture.md and execution-guide.md with clear local run instructions.");
194
278
  }
195
279
  if (intentSuggestsRelationalDataDomain(intent)) {
196
280
  constraints.push("Use a scalable relational database default (prefer PostgreSQL).");
@@ -1115,7 +1199,7 @@ function enrichDraftWithAI(input, flow, domain, baseDraft, providerRequested) {
1115
1199
  function templateFallbackAllowed() {
1116
1200
  return process.env.SDD_ALLOW_TEMPLATE_FALLBACK === "1" || process.env.SDD_DISABLE_AI_AUTOPILOT === "1";
1117
1201
  }
1118
- function bootstrapProjectCode(projectRoot, projectName, intent, providerRequested) {
1202
+ function bootstrapProjectCode(projectRoot, projectName, intent, providerRequested, domainHint) {
1119
1203
  const outputDir = path_1.default.join(projectRoot, "generated-app");
1120
1204
  fs_1.default.mkdirSync(outputDir, { recursive: true });
1121
1205
  if (process.env.SDD_DISABLE_AI_AUTOPILOT === "1") {
@@ -1140,13 +1224,16 @@ function bootstrapProjectCode(projectRoot, projectName, intent, providerRequeste
1140
1224
  let files = [];
1141
1225
  let fallbackReason;
1142
1226
  if (resolution.ok) {
1143
- const constraints = extraPromptConstraints(intent);
1227
+ const domain = detectAutopilotDomain(intent, domainHint);
1228
+ const constraints = extraPromptConstraints(intent, domainHint);
1144
1229
  const prompt = [
1145
1230
  "Generate a production-lean starter app from user intent.",
1146
1231
  "The project must be executable fully in local development.",
1232
+ `Domain profile: ${domain}.`,
1147
1233
  "Use DummyLocal adapters for integrations (databases, external APIs, queues) so everything runs locally.",
1148
1234
  "Add a schema document named schemas.md with entities, fields, relations, and constraints.",
1149
1235
  "Add regression tests and regression notes/documentation.",
1236
+ "Quality gate is strict: if required artifacts are missing, your output will be rejected and repaired.",
1150
1237
  ...constraints,
1151
1238
  "Do not mix unrelated runtime stacks unless the intent explicitly requests a multi-tier architecture.",
1152
1239
  "Return ONLY valid JSON with this shape:",
@@ -1175,12 +1262,13 @@ function bootstrapProjectCode(projectRoot, projectName, intent, providerRequeste
1175
1262
  }
1176
1263
  }
1177
1264
  if (files.length === 0) {
1178
- const fallbackConstraints = extraPromptConstraints(intent);
1265
+ const fallbackConstraints = extraPromptConstraints(intent, domainHint);
1179
1266
  const fallbackPrompt = [
1180
1267
  "Return ONLY valid JSON. No markdown.",
1181
1268
  "Schema: {\"files\":[{\"path\":\"relative/path\",\"content\":\"...\"}]}",
1182
1269
  "Generate only essential starter files to run locally with quality-first defaults.",
1183
1270
  "Must include: README.md, schemas.md, regression notes, and DummyLocal integration docs.",
1271
+ `Domain profile: ${domain}.`,
1184
1272
  ...fallbackConstraints,
1185
1273
  `Project: ${projectName}`,
1186
1274
  `Intent: ${intent}`
@@ -1276,7 +1364,7 @@ function collectProjectFiles(appDir) {
1276
1364
  walk(appDir);
1277
1365
  return output;
1278
1366
  }
1279
- function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnostics) {
1367
+ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnostics, domainHint) {
1280
1368
  if (process.env.SDD_DISABLE_AI_AUTOPILOT === "1") {
1281
1369
  return { attempted: false, applied: false, fileCount: 0, reason: "disabled by env" };
1282
1370
  }
@@ -1293,9 +1381,11 @@ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnosti
1293
1381
  .filter((line) => line.length > 0)
1294
1382
  .slice(0, 8)
1295
1383
  .map((line) => (line.length > 280 ? `${line.slice(0, 280)}...[truncated]` : line));
1296
- const constraints = extraPromptConstraints(intent);
1384
+ const domain = detectAutopilotDomain(intent, domainHint);
1385
+ const constraints = extraPromptConstraints(intent, domainHint);
1297
1386
  const prompt = [
1298
1387
  "Improve this generated app to production-lean quality.",
1388
+ `Domain profile: ${domain}.`,
1299
1389
  "Requirements:",
1300
1390
  "- Keep app intent and behavior.",
1301
1391
  "- Ensure tests pass for the selected stack.",
@@ -1320,6 +1410,7 @@ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnosti
1320
1410
  'Schema: {"files":[{"path":"relative/path","content":"..."}]}',
1321
1411
  "Fix exactly the listed quality diagnostics with minimal file edits.",
1322
1412
  "If diagnostics mention missing docs/tests, generate them.",
1413
+ `Domain profile: ${domain}.`,
1323
1414
  `Intent: ${intent}`,
1324
1415
  `Quality diagnostics: ${JSON.stringify(compactDiagnostics)}`,
1325
1416
  `Current file names: ${JSON.stringify(fileNames)}`
@@ -1332,6 +1423,7 @@ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnosti
1332
1423
  'Schema: {"files":[{"path":"relative/path","content":"..."}]}',
1333
1424
  "Apply minimal patch set: 1 to 5 files only.",
1334
1425
  "Prioritize fixing the first quality diagnostic immediately.",
1426
+ `Domain profile: ${domain}.`,
1335
1427
  `Intent: ${intent}`,
1336
1428
  `Top quality diagnostics: ${JSON.stringify(compactDiagnostics.slice(0, 2))}`
1337
1429
  ].join("\n");
@@ -6,6 +6,8 @@ type RepoMetadata = {
6
6
  export type LifecycleContext = {
7
7
  goalText?: string;
8
8
  intentSignals?: string[];
9
+ intentDomain?: string;
10
+ intentFlow?: string;
9
11
  };
10
12
  declare function tokenizeIntent(input: string): string[];
11
13
  declare function deriveRepoMetadata(projectName: string, appDir: string, context?: LifecycleContext): RepoMetadata;
@@ -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) {
@@ -202,6 +211,138 @@ function parseGoalProfile(context) {
202
211
  relationalDataApp
203
212
  };
204
213
  }
214
+ function parseDomainProfile(context) {
215
+ const hinted = normalizeText(context?.intentDomain ?? "");
216
+ if (hinted === "software" ||
217
+ hinted === "legal" ||
218
+ hinted === "business" ||
219
+ hinted === "humanities" ||
220
+ hinted === "learning" ||
221
+ hinted === "design" ||
222
+ hinted === "data_science" ||
223
+ hinted === "generic") {
224
+ return hinted;
225
+ }
226
+ const goal = normalizeText(context?.goalText ?? "");
227
+ if (!goal) {
228
+ return "generic";
229
+ }
230
+ if (/\bcourt\b|\blaw\b|\bpolicy\b|\bcompliance\b|\blawyer\b|\bregulation\b|\bcontract\b|\bjuridic/.test(goal)) {
231
+ return "legal";
232
+ }
233
+ if (/\bpricing\b|\bmarket\b|\bforecast\b|\beconomics\b|\baccounting\b|\bfinanzas\b|\bcontador\b/.test(goal)) {
234
+ return "business";
235
+ }
236
+ if (/\bhistory\b|\bsociology\b|\banthropology\b|\bphilosophy\b|\bliterature\b|\bhumanities\b/.test(goal)) {
237
+ return "humanities";
238
+ }
239
+ if (/\blearn\b|\bteach\b|\blesson\b|\bcourse\b|\bstudent\b|\bmentor\b|\bwriter\b|\bescritor\b/.test(goal)) {
240
+ return "learning";
241
+ }
242
+ if (/\blogo\b|\bbrand\b|\blayout\b|\bvisual\b|\bdesign\b/.test(goal)) {
243
+ return "design";
244
+ }
245
+ if (/\bmodel\b|\bdataset\b|\bprediction\b|\bmachine learning\b|\bml\b|\bai\b/.test(goal)) {
246
+ return "data_science";
247
+ }
248
+ if (/\bapi\b|\bbackend\b|\bfrontend\b|\bapp\b|\bweb\b|\bdesktop\b|\bmobile\b|\breact\b|\bjava\b/.test(goal)) {
249
+ return "software";
250
+ }
251
+ return "generic";
252
+ }
253
+ function countListLikeItems(raw) {
254
+ return raw
255
+ .split(/\r?\n/)
256
+ .map((line) => line.trim())
257
+ .filter((line) => /^[-*]\s+/.test(line) || /^\d+\.\s+/.test(line)).length;
258
+ }
259
+ function checkDomainArtifacts(appDir, context) {
260
+ const profile = parseDomainProfile(context);
261
+ if (profile === "software" || profile === "generic") {
262
+ return { ok: true };
263
+ }
264
+ const findDoc = (patterns) => findFileRecursive(appDir, (rel) => rel.endsWith(".md") && patterns.some((pattern) => pattern.test(rel)), 10);
265
+ const readDoc = (rel) => {
266
+ if (!rel)
267
+ return "";
268
+ try {
269
+ return normalizeText(fs_1.default.readFileSync(path_1.default.join(appDir, rel), "utf-8"));
270
+ }
271
+ catch {
272
+ return "";
273
+ }
274
+ };
275
+ if (profile === "legal") {
276
+ const compliance = findDoc([/compliance/, /regulation/, /policy/]);
277
+ const risk = findDoc([/risk/, /risk-register/]);
278
+ const evidence = findDoc([/citation/, /reference/, /sources/, /precedent/]);
279
+ if (!compliance || !risk || !evidence) {
280
+ return { ok: false, reason: "Missing legal artifacts (need compliance, risk-register, and references/citations docs)." };
281
+ }
282
+ const complianceText = readDoc(compliance);
283
+ if (!/\bjurisdiction\b|\bregion\b|\bcountry\b|\blaw\b|\bregulation\b/.test(complianceText)) {
284
+ return { ok: false, reason: "Legal compliance doc must define jurisdiction and applicable law/regulation scope." };
285
+ }
286
+ return { ok: true };
287
+ }
288
+ if (profile === "business") {
289
+ const assumptions = findDoc([/assumption/, /supuesto/]);
290
+ const sensitivity = findDoc([/sensitivity/, /scenario/, /escenario/]);
291
+ const economics = findDoc([/unit-economics/, /economics/, /forecast/, /financial/, /cashflow/, /p&l/]);
292
+ if (!assumptions || !sensitivity || !economics) {
293
+ return { ok: false, reason: "Missing business artifacts (need assumptions, sensitivity/scenarios, and economics/forecast docs)." };
294
+ }
295
+ const economicsText = readDoc(economics);
296
+ if (!/\b\d/.test(economicsText)) {
297
+ return { ok: false, reason: "Business economics/forecast doc should include at least one numeric metric or target." };
298
+ }
299
+ return { ok: true };
300
+ }
301
+ if (profile === "humanities") {
302
+ const methodology = findDoc([/methodology/, /approach/, /metodologia/]);
303
+ const sources = findDoc([/sources/, /reference/, /bibliography/, /citations/]);
304
+ if (!methodology || !sources) {
305
+ return { ok: false, reason: "Missing humanities artifacts (need methodology and sources/bibliography docs)." };
306
+ }
307
+ const sourcesText = readDoc(sources);
308
+ if (countListLikeItems(sourcesText) < 3) {
309
+ return { ok: false, reason: "Humanities source quality too low (expected at least 3 listed sources)." };
310
+ }
311
+ return { ok: true };
312
+ }
313
+ if (profile === "learning") {
314
+ const curriculum = findDoc([/curriculum/, /outline/, /syllabus/, /plan/]);
315
+ const exercises = findDoc([/exercise/, /assessment/, /practice/, /rubric/]);
316
+ const references = findDoc([/sources/, /references/, /reading/, /bibliography/]);
317
+ if (!curriculum || !exercises || !references) {
318
+ return { ok: false, reason: "Missing learning artifacts (need curriculum/outline, exercises/assessment, and references)." };
319
+ }
320
+ return { ok: true };
321
+ }
322
+ if (profile === "design") {
323
+ const designSystem = findDoc([/design-system/, /style-guide/, /brand/, /ui-kit/]);
324
+ const accessibility = findDoc([/accessibility/, /a11y/, /wcag/]);
325
+ const rationale = findDoc([/rationale/, /decision/, /tradeoff/]);
326
+ if (!designSystem || !accessibility || !rationale) {
327
+ return { ok: false, reason: "Missing design artifacts (need design-system/style-guide, accessibility, and rationale docs)." };
328
+ }
329
+ return { ok: true };
330
+ }
331
+ if (profile === "data_science") {
332
+ const dataDict = findDoc([/dataset/, /schema/, /data-dictionary/]);
333
+ const evaluation = findDoc([/evaluation/, /metrics/, /benchmark/]);
334
+ const monitoring = findDoc([/monitoring/, /drift/, /alert/]);
335
+ const reproducibility = findDoc([/reproducibility/, /experiment/, /runbook/]);
336
+ if (!dataDict || !evaluation || !monitoring || !reproducibility) {
337
+ return {
338
+ ok: false,
339
+ reason: "Missing data science artifacts (need dataset schema, evaluation metrics, monitoring/drift plan, and reproducibility docs)."
340
+ };
341
+ }
342
+ return { ok: true };
343
+ }
344
+ return { ok: true };
345
+ }
205
346
  function basicQualityCheck(appDir) {
206
347
  const required = ["README.md"];
207
348
  const missing = required.filter((name) => !fs_1.default.existsSync(path_1.default.join(appDir, name)));
@@ -314,6 +455,15 @@ function advancedQualityCheck(appDir, context) {
314
455
  output: "Outdated frontend dependency detected: react-query. Use @tanstack/react-query."
315
456
  };
316
457
  }
458
+ const requiredFrontendDeps = ["react-router-dom", "@tanstack/react-query"];
459
+ const missingFrontendDeps = requiredFrontendDeps.filter((dep) => typeof deps[dep] !== "string");
460
+ if (missingFrontendDeps.length > 0) {
461
+ return {
462
+ ok: false,
463
+ command: "advanced-quality-check",
464
+ output: `Missing modern frontend dependencies: ${missingFrontendDeps.join(", ")}`
465
+ };
466
+ }
317
467
  }
318
468
  const backendRoot = path_1.default.join(appDir, "backend", "src", "main", "java");
319
469
  if (!fs_1.default.existsSync(backendRoot)) {
@@ -351,6 +501,59 @@ function advancedQualityCheck(appDir, context) {
351
501
  output: "Missing service/repository interfaces in Java backend architecture"
352
502
  };
353
503
  }
504
+ const hasControllerAdvice = backendFiles.some((rel) => /@RestControllerAdvice\b/.test(fs_1.default.readFileSync(path_1.default.join(backendRoot, rel), "utf-8")));
505
+ if (!hasControllerAdvice) {
506
+ return {
507
+ ok: false,
508
+ command: "advanced-quality-check",
509
+ output: "Missing global exception handling (expected @RestControllerAdvice)"
510
+ };
511
+ }
512
+ const hasValidationUsage = backendFiles.some((rel) => {
513
+ const raw = fs_1.default.readFileSync(path_1.default.join(backendRoot, rel), "utf-8");
514
+ return /\b@(Valid|NotNull|NotBlank|Size|Email)\b/.test(raw) && /(jakarta|javax)\.validation/.test(raw);
515
+ });
516
+ if (!hasValidationUsage) {
517
+ return {
518
+ ok: false,
519
+ command: "advanced-quality-check",
520
+ output: "Missing bean validation usage (expected @Valid/@NotBlank with jakarta/javax.validation imports)"
521
+ };
522
+ }
523
+ const pomPath = path_1.default.join(appDir, "backend", "pom.xml");
524
+ const pomRaw = fs_1.default.existsSync(pomPath) ? fs_1.default.readFileSync(pomPath, "utf-8").toLowerCase() : "";
525
+ const requiredBackendDeps = [
526
+ "lombok",
527
+ "spring-boot-starter-validation",
528
+ "spring-boot-starter-actuator"
529
+ ];
530
+ const missingBackendDeps = requiredBackendDeps.filter((dep) => !pomRaw.includes(dep));
531
+ if (missingBackendDeps.length > 0) {
532
+ return {
533
+ ok: false,
534
+ command: "advanced-quality-check",
535
+ output: `Missing backend dependencies for production quality: ${missingBackendDeps.join(", ")}`
536
+ };
537
+ }
538
+ const hasMetricsConfig = (() => {
539
+ const metricsFile = fileExistsAny(path_1.default.join(appDir, "backend"), [
540
+ "src/main/resources/application.yml",
541
+ "src/main/resources/application.yaml",
542
+ "src/main/resources/application.properties"
543
+ ]);
544
+ if (!metricsFile) {
545
+ return false;
546
+ }
547
+ const text = normalizeText(fs_1.default.readFileSync(metricsFile, "utf-8"));
548
+ return /management\.endpoints|prometheus|actuator/.test(text);
549
+ })();
550
+ if (!hasMetricsConfig) {
551
+ return {
552
+ ok: false,
553
+ command: "advanced-quality-check",
554
+ output: "Missing backend telemetry config (expected actuator/prometheus management settings)"
555
+ };
556
+ }
354
557
  const frontendRoot = path_1.default.join(appDir, "frontend", "src");
355
558
  if (!fs_1.default.existsSync(frontendRoot)) {
356
559
  return {
@@ -378,6 +581,39 @@ function advancedQualityCheck(appDir, context) {
378
581
  output: `Expected at least 3 frontend tests for Java+React profile, found ${frontendTestCount}`
379
582
  };
380
583
  }
584
+ const frontendUsesStrictMode = (() => {
585
+ const mainCandidate = fileExistsAny(path_1.default.join(appDir, "frontend"), ["src/main.tsx", "src/main.jsx", "src/index.tsx", "src/index.jsx"]);
586
+ if (!mainCandidate) {
587
+ return false;
588
+ }
589
+ const raw = fs_1.default.readFileSync(mainCandidate, "utf-8");
590
+ return /StrictMode/.test(raw);
591
+ })();
592
+ if (!frontendUsesStrictMode) {
593
+ return {
594
+ ok: false,
595
+ command: "advanced-quality-check",
596
+ output: "Missing React StrictMode in frontend bootstrap"
597
+ };
598
+ }
599
+ const executionGuideExists = findFileRecursive(appDir, (rel) => rel === "execution-guide.md" || rel.endsWith("/execution-guide.md"), 8) ??
600
+ findFileRecursive(appDir, (rel) => rel.includes("runbook") && rel.endsWith(".md"), 8);
601
+ if (!executionGuideExists) {
602
+ return {
603
+ ok: false,
604
+ command: "advanced-quality-check",
605
+ output: "Missing execution guide/runbook markdown (expected execution-guide.md or runbook*.md)"
606
+ };
607
+ }
608
+ const architectureDocExists = findFileRecursive(appDir, (rel) => rel === "architecture.md" || rel.endsWith("/architecture.md"), 8) ??
609
+ findFileRecursive(appDir, (rel) => rel.includes("architecture") && rel.endsWith(".md"), 8);
610
+ if (!architectureDocExists) {
611
+ return {
612
+ ok: false,
613
+ command: "advanced-quality-check",
614
+ output: "Missing architecture documentation markdown (expected architecture.md)"
615
+ };
616
+ }
381
617
  }
382
618
  const dummyLocalDoc = findFileRecursive(appDir, (rel) => rel.includes("dummylocal") && rel.endsWith(".md")) ??
383
619
  findFileRecursive(appDir, (rel) => rel.includes("dummy-local") && rel.endsWith(".md")) ??
@@ -398,6 +634,14 @@ function advancedQualityCheck(appDir, context) {
398
634
  output: "Missing regression testing evidence (regression doc or tests)"
399
635
  };
400
636
  }
637
+ const domainArtifacts = checkDomainArtifacts(appDir, context);
638
+ if (!domainArtifacts.ok) {
639
+ return {
640
+ ok: false,
641
+ command: "advanced-quality-check",
642
+ output: domainArtifacts.reason || "Domain artifact quality check failed"
643
+ };
644
+ }
401
645
  const goalText = context?.goalText?.trim();
402
646
  if (goalText) {
403
647
  const readmeRaw = fs_1.default.readFileSync(readmePath, "utf-8");
@@ -516,7 +760,7 @@ function deriveRepoMetadata(projectName, appDir, context) {
516
760
  const goalSeed = context?.goalText ? tokenizeIntent(context.goalText).slice(0, 6).join("-") : "";
517
761
  const projectSeed = slugify(rawBase);
518
762
  const intentSeed = slugify(goalSeed);
519
- const base = projectSeed || intentSeed || "sdd-project";
763
+ const base = intentSeed || projectSeed || "sdd-project";
520
764
  const cleaned = base.replace(/-app$/g, "");
521
765
  const repoName = `${cleaned}-app`.slice(0, 63).replace(/-+$/g, "");
522
766
  let description = context?.goalText?.trim()
@@ -0,0 +1,13 @@
1
+ import type { LifecycleContext } from "./app-lifecycle";
2
+ type ReviewerFinding = {
3
+ reviewer: string;
4
+ severity: "high" | "medium";
5
+ message: string;
6
+ };
7
+ export type DigitalReviewResult = {
8
+ passed: boolean;
9
+ findings: ReviewerFinding[];
10
+ diagnostics: string[];
11
+ };
12
+ export declare function runDigitalHumanReview(appDir: string, context?: LifecycleContext): DigitalReviewResult;
13
+ export {};
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runDigitalHumanReview = runDigitalHumanReview;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function normalizeText(input) {
10
+ return input
11
+ .toLowerCase()
12
+ .normalize("NFD")
13
+ .replace(/[\u0300-\u036f]/g, "");
14
+ }
15
+ function collectFilesRecursive(root, maxDepth = 8) {
16
+ const results = [];
17
+ const walk = (current, depth) => {
18
+ if (depth > maxDepth)
19
+ return;
20
+ for (const entry of fs_1.default.readdirSync(current, { withFileTypes: true })) {
21
+ const full = path_1.default.join(current, entry.name);
22
+ const rel = path_1.default.relative(root, full).replace(/\\/g, "/");
23
+ if (entry.isDirectory()) {
24
+ if ([".git", "node_modules", "dist", "build", "target", "__pycache__", ".venv", "venv"].includes(entry.name)) {
25
+ continue;
26
+ }
27
+ walk(full, depth + 1);
28
+ }
29
+ else {
30
+ results.push(rel);
31
+ }
32
+ }
33
+ };
34
+ walk(root, 0);
35
+ return results;
36
+ }
37
+ function countTests(root) {
38
+ if (!fs_1.default.existsSync(root))
39
+ return 0;
40
+ const files = collectFilesRecursive(root, 10).filter((rel) => /\.(jsx?|tsx?|py|java)$/i.test(rel));
41
+ let count = 0;
42
+ for (const rel of files) {
43
+ const full = path_1.default.join(root, rel);
44
+ const raw = fs_1.default.readFileSync(full, "utf-8");
45
+ if (/\.py$/i.test(rel))
46
+ count += (raw.match(/\bdef\s+test_/g) || []).length;
47
+ else if (/\.java$/i.test(rel))
48
+ count += (raw.match(/@Test\b/g) || []).length;
49
+ else if (/\.test\.|\.spec\.|__tests__\//i.test(rel))
50
+ count += (raw.match(/\b(test|it)\s*\(/g) || []).length;
51
+ }
52
+ return count;
53
+ }
54
+ function detectDomain(context) {
55
+ const hinted = normalizeText(context?.intentDomain ?? "");
56
+ if (hinted)
57
+ return hinted;
58
+ const goal = normalizeText(context?.goalText ?? "");
59
+ if (/\bcourt\b|\blaw\b|\bcompliance\b|\bcontract\b/.test(goal))
60
+ return "legal";
61
+ if (/\bpricing\b|\bmarket\b|\bforecast\b|\beconomics\b/.test(goal))
62
+ return "business";
63
+ if (/\bhistory\b|\bhumanities\b|\bphilosophy\b/.test(goal))
64
+ return "humanities";
65
+ if (/\blearn\b|\bcourse\b|\blesson\b|\bteach\b/.test(goal))
66
+ return "learning";
67
+ if (/\bdesign\b|\blogo\b|\bbrand\b/.test(goal))
68
+ return "design";
69
+ if (/\bmodel\b|\bdataset\b|\bprediction\b|\bmachine learning\b|\bml\b/.test(goal))
70
+ return "data_science";
71
+ return "software";
72
+ }
73
+ function findDoc(root, names) {
74
+ const files = collectFilesRecursive(root, 8).filter((rel) => rel.endsWith(".md")).map((rel) => rel.toLowerCase());
75
+ for (const name of names) {
76
+ const normalized = name.toLowerCase();
77
+ const found = files.find((rel) => rel === normalized || rel.endsWith(`/${normalized}`) || rel.includes(normalized));
78
+ if (found)
79
+ return found;
80
+ }
81
+ return null;
82
+ }
83
+ function hasRunbookLikeReadme(readme) {
84
+ return /\b(run|start|setup|install)\b/.test(readme);
85
+ }
86
+ function hasUserFlowDocs(root, readme) {
87
+ if (/\buser flow\b|\bux\b|\bexperience\b|\bjourney\b/.test(readme)) {
88
+ return true;
89
+ }
90
+ return Boolean(findDoc(root, ["user-flow.md", "ux-notes.md", "experience.md"]));
91
+ }
92
+ function hasSecretLeak(root) {
93
+ const files = collectFilesRecursive(root, 8).filter((rel) => /\.(env|txt|md|json|yml|yaml|properties|ts|js|py|java)$/i.test(rel));
94
+ const patterns = [/api[_-]?key\s*[:=]\s*[^\s]+/i, /secret\s*[:=]\s*[^\s]+/i, /password\s*[:=]\s*[^\s]+/i];
95
+ for (const rel of files) {
96
+ const raw = fs_1.default.readFileSync(path_1.default.join(root, rel), "utf-8");
97
+ if (patterns.some((pattern) => pattern.test(raw)))
98
+ return true;
99
+ }
100
+ return false;
101
+ }
102
+ function runDigitalHumanReview(appDir, context) {
103
+ const findings = [];
104
+ if (!fs_1.default.existsSync(appDir)) {
105
+ return {
106
+ passed: false,
107
+ findings: [{ reviewer: "program_manager", severity: "high", message: "Generated app directory is missing." }],
108
+ diagnostics: ["[DigitalReviewer:program_manager][high] Generated app directory is missing."]
109
+ };
110
+ }
111
+ const readmePath = path_1.default.join(appDir, "README.md");
112
+ const readme = fs_1.default.existsSync(readmePath) ? normalizeText(fs_1.default.readFileSync(readmePath, "utf-8")) : "";
113
+ const totalTests = countTests(appDir);
114
+ const domain = detectDomain(context);
115
+ if (!readme) {
116
+ findings.push({ reviewer: "program_manager", severity: "high", message: "README.md is missing; delivery is not product-ready." });
117
+ }
118
+ else {
119
+ if (!/\bfeatures?\b/.test(readme)) {
120
+ findings.push({ reviewer: "program_manager", severity: "medium", message: "README must include explicit product features section." });
121
+ }
122
+ if (!hasRunbookLikeReadme(readme)) {
123
+ findings.push({ reviewer: "program_manager", severity: "high", message: "README lacks clear execution instructions (run/start/setup/install)." });
124
+ }
125
+ }
126
+ if (totalTests < 8) {
127
+ findings.push({
128
+ reviewer: "qa_engineer",
129
+ severity: "high",
130
+ message: `Automated test depth is low (${totalTests}). Minimum expected is 8 tests for acceptance.`
131
+ });
132
+ }
133
+ if (!hasUserFlowDocs(appDir, readme)) {
134
+ findings.push({
135
+ reviewer: "ux_researcher",
136
+ severity: "medium",
137
+ message: "User experience flow is unclear. Add user-flow/UX notes and acceptance of critical journeys."
138
+ });
139
+ }
140
+ if (hasSecretLeak(appDir)) {
141
+ findings.push({
142
+ reviewer: "security_reviewer",
143
+ severity: "high",
144
+ message: "Potential secret leakage detected (api key/secret/password pattern). Remove hardcoded secrets."
145
+ });
146
+ }
147
+ if (domain === "legal") {
148
+ if (!findDoc(appDir, ["compliance-matrix.md", "compliance.md"])) {
149
+ findings.push({ reviewer: "compliance_officer", severity: "high", message: "Legal project requires compliance matrix documentation." });
150
+ }
151
+ if (!findDoc(appDir, ["risk-register.md", "legal-risks.md"])) {
152
+ findings.push({ reviewer: "compliance_officer", severity: "high", message: "Legal project requires risk register with mitigations." });
153
+ }
154
+ }
155
+ if (domain === "business") {
156
+ if (!findDoc(appDir, ["unit-economics.md", "economics.md", "financial-forecast.md"])) {
157
+ findings.push({ reviewer: "business_analyst", severity: "high", message: "Business project requires unit-economics or financial forecast documentation." });
158
+ }
159
+ if (!findDoc(appDir, ["sensitivity-analysis.md", "scenario-analysis.md"])) {
160
+ findings.push({ reviewer: "business_analyst", severity: "medium", message: "Business project should include sensitivity/scenario analysis." });
161
+ }
162
+ }
163
+ if (domain === "data_science") {
164
+ if (!findDoc(appDir, ["evaluation-metrics.md", "metrics.md"])) {
165
+ findings.push({ reviewer: "ml_reviewer", severity: "high", message: "Data-science delivery requires evaluation metrics documentation." });
166
+ }
167
+ if (!findDoc(appDir, ["monitoring-plan.md", "drift-monitoring.md"])) {
168
+ findings.push({ reviewer: "ml_reviewer", severity: "high", message: "Data-science delivery requires drift monitoring plan." });
169
+ }
170
+ }
171
+ const diagnostics = findings.map((finding) => `[DigitalReviewer:${finding.reviewer}][${finding.severity}] ${finding.message}`);
172
+ return {
173
+ passed: findings.length === 0,
174
+ findings,
175
+ diagnostics
176
+ };
177
+ }
@@ -21,6 +21,7 @@ const local_metrics_1 = require("../telemetry/local-metrics");
21
21
  const errors_1 = require("../errors");
22
22
  const ai_autopilot_1 = require("./ai-autopilot");
23
23
  const app_lifecycle_1 = require("./app-lifecycle");
24
+ const digital_reviewers_1 = require("./digital-reviewers");
24
25
  const autopilot_checkpoint_1 = require("./autopilot-checkpoint");
25
26
  function printStep(step, description) {
26
27
  console.log(`${step}: ${description}`);
@@ -398,7 +399,7 @@ async function runHello(input, runQuestions) {
398
399
  }
399
400
  (0, autopilot_checkpoint_1.clearCheckpoint)(activeProject);
400
401
  const projectRoot = path_1.default.resolve(finished.doneDir, "..", "..", "..");
401
- const codeBootstrap = (0, ai_autopilot_1.bootstrapProjectCode)(projectRoot, activeProject, text, provider);
402
+ const codeBootstrap = (0, ai_autopilot_1.bootstrapProjectCode)(projectRoot, activeProject, text, provider, intent.domain);
402
403
  if (!codeBootstrap.generated) {
403
404
  printWhy(`Code generation blocked: ${codeBootstrap.reason || "provider did not return valid files"}.`);
404
405
  printWhy("No template fallback was applied. Re-run with clearer prompt or improve provider response contract.");
@@ -411,7 +412,9 @@ async function runHello(input, runQuestions) {
411
412
  }
412
413
  let lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
413
414
  goalText: text,
414
- intentSignals: intent.signals
415
+ intentSignals: intent.signals,
416
+ intentDomain: intent.domain,
417
+ intentFlow: intent.flow
415
418
  });
416
419
  lifecycle.summary.forEach((line) => printWhy(`Lifecycle: ${line}`));
417
420
  const lifecycleDisabled = process.env.SDD_DISABLE_APP_LIFECYCLE === "1";
@@ -422,12 +425,14 @@ async function runHello(input, runQuestions) {
422
425
  printWhy("Quality gates failed. Attempting AI repair iterations.");
423
426
  lifecycle.qualityDiagnostics.forEach((issue) => printWhy(`Quality issue: ${issue}`));
424
427
  for (let attempt = 1; attempt <= maxRepairAttempts && !lifecycle.qualityPassed; attempt += 1) {
425
- const repair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, lifecycle.qualityDiagnostics);
428
+ const repair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, lifecycle.qualityDiagnostics, intent.domain);
426
429
  if (repair.attempted && repair.applied) {
427
430
  printWhy(`AI repair attempt ${attempt} applied (${repair.fileCount} files). Re-running lifecycle checks.`);
428
431
  lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
429
432
  goalText: text,
430
- intentSignals: intent.signals
433
+ intentSignals: intent.signals,
434
+ intentDomain: intent.domain,
435
+ intentFlow: intent.flow
431
436
  });
432
437
  lifecycle.summary.forEach((line) => printWhy(`Lifecycle (retry ${attempt}): ${line}`));
433
438
  }
@@ -441,6 +446,68 @@ async function runHello(input, runQuestions) {
441
446
  return;
442
447
  }
443
448
  }
449
+ const digitalReviewDisabled = lifecycleDisabled || process.env.SDD_DISABLE_AI_AUTOPILOT === "1" || process.env.SDD_DISABLE_DIGITAL_REVIEW === "1";
450
+ if (!digitalReviewDisabled) {
451
+ const appDir = path_1.default.join(projectRoot, "generated-app");
452
+ const parsedReviewAttempts = Number.parseInt(process.env.SDD_DIGITAL_REVIEW_MAX_ATTEMPTS ?? "", 10);
453
+ const maxReviewAttempts = Number.isFinite(parsedReviewAttempts) && parsedReviewAttempts > 0 ? parsedReviewAttempts : 3;
454
+ let review = (0, digital_reviewers_1.runDigitalHumanReview)(appDir, {
455
+ goalText: text,
456
+ intentSignals: intent.signals,
457
+ intentDomain: intent.domain,
458
+ intentFlow: intent.flow
459
+ });
460
+ if (!review.passed) {
461
+ printWhy("Digital human reviewers found delivery issues. Applying targeted refinements.");
462
+ review.diagnostics.forEach((issue) => printWhy(`Reviewer issue: ${issue}`));
463
+ }
464
+ for (let attempt = 1; attempt <= maxReviewAttempts && !review.passed; attempt += 1) {
465
+ const repair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, review.diagnostics, intent.domain);
466
+ if (!repair.attempted || !repair.applied) {
467
+ printWhy(`Digital-review repair attempt ${attempt} skipped: ${repair.reason || "unknown reason"}`);
468
+ break;
469
+ }
470
+ printWhy(`Digital-review repair attempt ${attempt} applied (${repair.fileCount} files).`);
471
+ lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
472
+ goalText: text,
473
+ intentSignals: intent.signals,
474
+ intentDomain: intent.domain,
475
+ intentFlow: intent.flow
476
+ });
477
+ lifecycle.summary.forEach((line) => printWhy(`Lifecycle (digital-review retry ${attempt}): ${line}`));
478
+ if (!lifecycle.qualityPassed) {
479
+ const qualityRepair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, lifecycle.qualityDiagnostics, intent.domain);
480
+ if (qualityRepair.attempted && qualityRepair.applied) {
481
+ printWhy(`Quality regression repaired after digital review (${qualityRepair.fileCount} files). Re-validating delivery.`);
482
+ lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
483
+ goalText: text,
484
+ intentSignals: intent.signals,
485
+ intentDomain: intent.domain,
486
+ intentFlow: intent.flow
487
+ });
488
+ }
489
+ }
490
+ if (!lifecycle.qualityPassed) {
491
+ printWhy("Delivery regressed below lifecycle quality gates during digital-review iteration.");
492
+ continue;
493
+ }
494
+ review = (0, digital_reviewers_1.runDigitalHumanReview)(appDir, {
495
+ goalText: text,
496
+ intentSignals: intent.signals,
497
+ intentDomain: intent.domain,
498
+ intentFlow: intent.flow
499
+ });
500
+ if (!review.passed) {
501
+ review.diagnostics.forEach((issue) => printWhy(`Reviewer issue (retry ${attempt}): ${issue}`));
502
+ }
503
+ }
504
+ if (!review.passed) {
505
+ printWhy("Digital-review quality bar not met after refinement attempts.");
506
+ printRecoveryNext(activeProject, "finish", text);
507
+ return;
508
+ }
509
+ printWhy("Digital reviewers approved delivery quality.");
510
+ }
444
511
  (0, local_metrics_1.recordActivationMetric)("completed", {
445
512
  project: activeProject,
446
513
  reqId
@@ -13,6 +13,43 @@ const render_1 = require("../templates/render");
13
13
  const list_1 = require("../utils/list");
14
14
  const validate_1 = require("../validation/validate");
15
15
  const errors_1 = require("../errors");
16
+ function domainQualityProfiles(domain) {
17
+ const normalized = (domain || "software").trim().toLowerCase();
18
+ const common = ["single-responsibility", "tests-for-critical-flows", "clear-documentation", "explicit-tradeoffs"];
19
+ if (normalized === "legal") {
20
+ return {
21
+ legal: [...common, "compliance-matrix-required", "risk-register-required", "jurisdiction-defined", "citations-required"]
22
+ };
23
+ }
24
+ if (normalized === "business") {
25
+ return {
26
+ business: [...common, "assumptions-log-required", "sensitivity-analysis-required", "numeric-kpi-thresholds-required"]
27
+ };
28
+ }
29
+ if (normalized === "humanities") {
30
+ return {
31
+ humanities: [...common, "methodology-required", "source-quality-minimum", "limitations-documented"]
32
+ };
33
+ }
34
+ if (normalized === "learning") {
35
+ return {
36
+ learning: [...common, "curriculum-required", "assessment-rubric-required", "references-required"]
37
+ };
38
+ }
39
+ if (normalized === "design") {
40
+ return {
41
+ design: [...common, "design-system-required", "accessibility-checks-required", "rationale-required"]
42
+ };
43
+ }
44
+ if (normalized === "data_science") {
45
+ return {
46
+ data_science: [...common, "dataset-schema-required", "evaluation-metrics-required", "monitoring-and-drift-plan-required", "reproducibility-required"]
47
+ };
48
+ }
49
+ return {
50
+ software: [...common, "api-contracts-defined", "telemetry-baseline", "regression-suite-required"]
51
+ };
52
+ }
16
53
  function findRequirementDir(projectRoot, reqId) {
17
54
  const backlog = path_1.default.join(projectRoot, "requirements", "backlog", reqId);
18
55
  const wip = path_1.default.join(projectRoot, "requirements", "wip", reqId);
@@ -108,10 +145,23 @@ async function runReqStart(options) {
108
145
  const qualityTemplate = (0, render_1.loadTemplate)("quality");
109
146
  fs_1.default.writeFileSync(qualityPath, qualityTemplate, "utf-8");
110
147
  }
148
+ const metadataPath = path_1.default.join(project.root, "metadata.json");
149
+ let domain = "software";
150
+ if (fs_1.default.existsSync(metadataPath)) {
151
+ try {
152
+ const metadata = JSON.parse(fs_1.default.readFileSync(metadataPath, "utf-8"));
153
+ if (typeof metadata.domain === "string" && metadata.domain.trim()) {
154
+ domain = metadata.domain.trim();
155
+ }
156
+ }
157
+ catch {
158
+ // keep default
159
+ }
160
+ }
111
161
  const qualityJson = {
112
162
  rules: ["single-responsibility", "tests-for-critical-flows"],
113
163
  thresholds: { coverage: "80%", complexity: "10" },
114
- profiles: {}
164
+ profiles: domainQualityProfiles(domain)
115
165
  };
116
166
  const validation = (0, validate_1.validateJson)("quality.schema.json", qualityJson);
117
167
  if (!validation.valid) {
@@ -60,13 +60,13 @@ const SIGNALS = [
60
60
  exports.FLOW_PROMPT_PACKS = {
61
61
  BUG_FIX: ["discovery.core", "bug_fix.core"],
62
62
  PR_REVIEW: ["pr_review.core", "review.severity"],
63
- SOFTWARE_FEATURE: ["discovery.core", "release.rollout"],
64
- DATA_SCIENCE: ["discovery.core", "data.monitoring"],
65
- DESIGN: ["discovery.core", "design.accessibility"],
66
- HUMANITIES: ["discovery.core", "humanities.sources"],
67
- BUSINESS: ["discovery.core", "business.sensitivity"],
68
- LEGAL: ["discovery.core", "legal.compliance"],
69
- LEARN: ["discovery.core", "learn.format"],
63
+ SOFTWARE_FEATURE: ["discovery.core", "release.rollout", "software.quality"],
64
+ DATA_SCIENCE: ["discovery.core", "data.monitoring", "data.quality"],
65
+ DESIGN: ["discovery.core", "design.accessibility", "design.quality"],
66
+ HUMANITIES: ["discovery.core", "humanities.sources", "humanities.quality"],
67
+ BUSINESS: ["discovery.core", "business.sensitivity", "business.quality"],
68
+ LEGAL: ["discovery.core", "legal.compliance", "legal.quality"],
69
+ LEARN: ["discovery.core", "learn.format", "learning.quality"],
70
70
  GENERIC: ["discovery.core"]
71
71
  };
72
72
  function classifyIntent(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "AI-orchestrated specification-driven delivery CLI that plans, validates, and ships production-ready software projects.",
5
5
  "keywords": [
6
6
  "cli",
@@ -122,6 +122,83 @@
122
122
  "gates": [
123
123
  "learning_format_defined"
124
124
  ]
125
+ },
126
+ {
127
+ "id": "software.quality",
128
+ "questions": [
129
+ "What architecture quality controls are mandatory (DTOs, interfaces, telemetry, security checks)?",
130
+ "What CI quality thresholds are mandatory (tests, lint, build, coverage)?"
131
+ ],
132
+ "gates": [
133
+ "software_quality_controls_defined",
134
+ "ci_thresholds_defined"
135
+ ]
136
+ },
137
+ {
138
+ "id": "legal.quality",
139
+ "questions": [
140
+ "Which jurisdiction(s) and legal frameworks apply?",
141
+ "Which mandatory legal artifacts are required (compliance matrix, risk register, citations)?"
142
+ ],
143
+ "gates": [
144
+ "jurisdiction_defined",
145
+ "legal_artifacts_defined"
146
+ ]
147
+ },
148
+ {
149
+ "id": "business.quality",
150
+ "questions": [
151
+ "Which assumptions must be explicitly tracked?",
152
+ "Which unit economics and sensitivity thresholds must be satisfied?"
153
+ ],
154
+ "gates": [
155
+ "assumptions_defined",
156
+ "financial_thresholds_defined"
157
+ ]
158
+ },
159
+ {
160
+ "id": "humanities.quality",
161
+ "questions": [
162
+ "Which methodology and source-quality standards are required?",
163
+ "How will source limitations and bias be documented?"
164
+ ],
165
+ "gates": [
166
+ "methodology_defined",
167
+ "source_quality_and_limits_defined"
168
+ ]
169
+ },
170
+ {
171
+ "id": "learning.quality",
172
+ "questions": [
173
+ "What curriculum structure and progression model are required?",
174
+ "What assessment and rubric standards must be met?"
175
+ ],
176
+ "gates": [
177
+ "curriculum_defined",
178
+ "assessment_defined"
179
+ ]
180
+ },
181
+ {
182
+ "id": "design.quality",
183
+ "questions": [
184
+ "Which design-system and accessibility standards are required?",
185
+ "How should design rationale and tradeoffs be captured?"
186
+ ],
187
+ "gates": [
188
+ "design_system_defined",
189
+ "design_rationale_defined"
190
+ ]
191
+ },
192
+ {
193
+ "id": "data.quality",
194
+ "questions": [
195
+ "What dataset schema and data-quality rules are required?",
196
+ "What model evaluation, drift monitoring, and reproducibility standards apply?"
197
+ ],
198
+ "gates": [
199
+ "data_schema_defined",
200
+ "ml_quality_controls_defined"
201
+ ]
125
202
  }
126
203
  ]
127
204
  }