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.
- package/dist/commands/ai-autopilot.d.ts +2 -2
- package/dist/commands/ai-autopilot.js +98 -6
- package/dist/commands/app-lifecycle.d.ts +2 -0
- package/dist/commands/app-lifecycle.js +245 -1
- package/dist/commands/digital-reviewers.d.ts +13 -0
- package/dist/commands/digital-reviewers.js +177 -0
- package/dist/commands/hello.js +71 -4
- package/dist/commands/req-start.js +51 -1
- package/dist/router/intent.js +7 -7
- package/package.json +1 -1
- package/templates/prompt-pack-index.json +77 -0
|
@@ -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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
+
}
|
package/dist/commands/hello.js
CHANGED
|
@@ -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) {
|
package/dist/router/intent.js
CHANGED
|
@@ -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
|
@@ -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
|
}
|