sdd-cli 0.1.27 → 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.");
@@ -1122,7 +1199,7 @@ function enrichDraftWithAI(input, flow, domain, baseDraft, providerRequested) {
1122
1199
  function templateFallbackAllowed() {
1123
1200
  return process.env.SDD_ALLOW_TEMPLATE_FALLBACK === "1" || process.env.SDD_DISABLE_AI_AUTOPILOT === "1";
1124
1201
  }
1125
- function bootstrapProjectCode(projectRoot, projectName, intent, providerRequested) {
1202
+ function bootstrapProjectCode(projectRoot, projectName, intent, providerRequested, domainHint) {
1126
1203
  const outputDir = path_1.default.join(projectRoot, "generated-app");
1127
1204
  fs_1.default.mkdirSync(outputDir, { recursive: true });
1128
1205
  if (process.env.SDD_DISABLE_AI_AUTOPILOT === "1") {
@@ -1147,13 +1224,16 @@ function bootstrapProjectCode(projectRoot, projectName, intent, providerRequeste
1147
1224
  let files = [];
1148
1225
  let fallbackReason;
1149
1226
  if (resolution.ok) {
1150
- const constraints = extraPromptConstraints(intent);
1227
+ const domain = detectAutopilotDomain(intent, domainHint);
1228
+ const constraints = extraPromptConstraints(intent, domainHint);
1151
1229
  const prompt = [
1152
1230
  "Generate a production-lean starter app from user intent.",
1153
1231
  "The project must be executable fully in local development.",
1232
+ `Domain profile: ${domain}.`,
1154
1233
  "Use DummyLocal adapters for integrations (databases, external APIs, queues) so everything runs locally.",
1155
1234
  "Add a schema document named schemas.md with entities, fields, relations, and constraints.",
1156
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.",
1157
1237
  ...constraints,
1158
1238
  "Do not mix unrelated runtime stacks unless the intent explicitly requests a multi-tier architecture.",
1159
1239
  "Return ONLY valid JSON with this shape:",
@@ -1182,12 +1262,13 @@ function bootstrapProjectCode(projectRoot, projectName, intent, providerRequeste
1182
1262
  }
1183
1263
  }
1184
1264
  if (files.length === 0) {
1185
- const fallbackConstraints = extraPromptConstraints(intent);
1265
+ const fallbackConstraints = extraPromptConstraints(intent, domainHint);
1186
1266
  const fallbackPrompt = [
1187
1267
  "Return ONLY valid JSON. No markdown.",
1188
1268
  "Schema: {\"files\":[{\"path\":\"relative/path\",\"content\":\"...\"}]}",
1189
1269
  "Generate only essential starter files to run locally with quality-first defaults.",
1190
1270
  "Must include: README.md, schemas.md, regression notes, and DummyLocal integration docs.",
1271
+ `Domain profile: ${domain}.`,
1191
1272
  ...fallbackConstraints,
1192
1273
  `Project: ${projectName}`,
1193
1274
  `Intent: ${intent}`
@@ -1283,7 +1364,7 @@ function collectProjectFiles(appDir) {
1283
1364
  walk(appDir);
1284
1365
  return output;
1285
1366
  }
1286
- function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnostics) {
1367
+ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnostics, domainHint) {
1287
1368
  if (process.env.SDD_DISABLE_AI_AUTOPILOT === "1") {
1288
1369
  return { attempted: false, applied: false, fileCount: 0, reason: "disabled by env" };
1289
1370
  }
@@ -1300,9 +1381,11 @@ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnosti
1300
1381
  .filter((line) => line.length > 0)
1301
1382
  .slice(0, 8)
1302
1383
  .map((line) => (line.length > 280 ? `${line.slice(0, 280)}...[truncated]` : line));
1303
- const constraints = extraPromptConstraints(intent);
1384
+ const domain = detectAutopilotDomain(intent, domainHint);
1385
+ const constraints = extraPromptConstraints(intent, domainHint);
1304
1386
  const prompt = [
1305
1387
  "Improve this generated app to production-lean quality.",
1388
+ `Domain profile: ${domain}.`,
1306
1389
  "Requirements:",
1307
1390
  "- Keep app intent and behavior.",
1308
1391
  "- Ensure tests pass for the selected stack.",
@@ -1327,6 +1410,7 @@ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnosti
1327
1410
  'Schema: {"files":[{"path":"relative/path","content":"..."}]}',
1328
1411
  "Fix exactly the listed quality diagnostics with minimal file edits.",
1329
1412
  "If diagnostics mention missing docs/tests, generate them.",
1413
+ `Domain profile: ${domain}.`,
1330
1414
  `Intent: ${intent}`,
1331
1415
  `Quality diagnostics: ${JSON.stringify(compactDiagnostics)}`,
1332
1416
  `Current file names: ${JSON.stringify(fileNames)}`
@@ -1339,6 +1423,7 @@ function improveGeneratedApp(appDir, intent, providerRequested, qualityDiagnosti
1339
1423
  'Schema: {"files":[{"path":"relative/path","content":"..."}]}',
1340
1424
  "Apply minimal patch set: 1 to 5 files only.",
1341
1425
  "Prioritize fixing the first quality diagnostic immediately.",
1426
+ `Domain profile: ${domain}.`,
1342
1427
  `Intent: ${intent}`,
1343
1428
  `Top quality diagnostics: ${JSON.stringify(compactDiagnostics.slice(0, 2))}`
1344
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;
@@ -211,6 +211,138 @@ function parseGoalProfile(context) {
211
211
  relationalDataApp
212
212
  };
213
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
+ }
214
346
  function basicQualityCheck(appDir) {
215
347
  const required = ["README.md"];
216
348
  const missing = required.filter((name) => !fs_1.default.existsSync(path_1.default.join(appDir, name)));
@@ -502,6 +634,14 @@ function advancedQualityCheck(appDir, context) {
502
634
  output: "Missing regression testing evidence (regression doc or tests)"
503
635
  };
504
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
+ }
505
645
  const goalText = context?.goalText?.trim();
506
646
  if (goalText) {
507
647
  const readmeRaw = fs_1.default.readFileSync(readmePath, "utf-8");
@@ -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.27",
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
  }