sdd-cli 0.1.30 → 0.1.31
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/README.md +1 -1
- package/dist/cli.js +4 -2
- package/dist/commands/digital-reviewers.d.ts +1 -0
- package/dist/commands/digital-reviewers.js +25 -0
- package/dist/commands/hello.js +46 -32
- package/dist/context/flags.d.ts +1 -0
- package/dist/context/flags.js +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ sdd-cli hello "create a calculator app"
|
|
|
55
55
|
## Global Flags
|
|
56
56
|
|
|
57
57
|
- `--approve`, `--improve`, `--parallel`
|
|
58
|
-
- `--non-interactive`, `--dry-run`, `--beginner`, `--from-step`
|
|
58
|
+
- `--non-interactive`, `--dry-run`, `--beginner`, `--from-step`, `--iterations`
|
|
59
59
|
- `--project`, `--output`, `--scope`, `--metrics-local`
|
|
60
60
|
- `--provider`, `--gemini`, `--model`
|
|
61
61
|
|
package/dist/cli.js
CHANGED
|
@@ -85,6 +85,7 @@ program
|
|
|
85
85
|
.option("--metrics-local", "Enable local opt-in telemetry snapshots in workspace/metrics")
|
|
86
86
|
.option("--provider <name>", "AI provider: gemini|codex|auto", (0, providers_1.defaultProviderPreference)())
|
|
87
87
|
.option("--model <name>", "AI model id (for providers that support model override)")
|
|
88
|
+
.option("--iterations <n>", "Autopilot improvement iterations (1-10)", "1")
|
|
88
89
|
.option("--gemini", "Shortcut for --provider gemini");
|
|
89
90
|
program.hook("preAction", (thisCommand, actionCommand) => {
|
|
90
91
|
const config = (0, config_1.ensureConfig)();
|
|
@@ -109,7 +110,8 @@ program.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
109
110
|
: typeof opts.provider === "string"
|
|
110
111
|
? opts.provider
|
|
111
112
|
: config.ai.preferred_cli,
|
|
112
|
-
model: typeof opts.model === "string" ? opts.model : config.ai.model
|
|
113
|
+
model: typeof opts.model === "string" ? opts.model : config.ai.model,
|
|
114
|
+
iterations: Number.parseInt(typeof opts.iterations === "string" ? opts.iterations : "1", 10)
|
|
113
115
|
});
|
|
114
116
|
process.env.SDD_GEMINI_MODEL = typeof opts.model === "string" ? opts.model : config.ai.model;
|
|
115
117
|
const commandPath = typeof actionCommand.name === "function"
|
|
@@ -492,7 +494,7 @@ function normalizeArgv(argv) {
|
|
|
492
494
|
if (args.length === 0) {
|
|
493
495
|
return argv;
|
|
494
496
|
}
|
|
495
|
-
const valueFlags = new Set(["--from-step", "--project", "--output", "--scope", "--provider", "--model"]);
|
|
497
|
+
const valueFlags = new Set(["--from-step", "--project", "--output", "--scope", "--provider", "--model", "--iterations"]);
|
|
496
498
|
let positionalIndex = -1;
|
|
497
499
|
for (let i = 0; i < args.length; i += 1) {
|
|
498
500
|
const token = args[i];
|
|
@@ -24,5 +24,6 @@ export declare function runDigitalHumanReview(appDir: string, context?: Lifecycl
|
|
|
24
24
|
export declare function convertFindingsToUserStories(findings: ReviewerFinding[]): UserStory[];
|
|
25
25
|
export declare function storiesToDiagnostics(stories: UserStory[]): string[];
|
|
26
26
|
export declare function writeUserStoriesBacklog(appDir: string, stories: UserStory[]): string | null;
|
|
27
|
+
export declare function appendDigitalReviewRound(appDir: string, round: number, review: DigitalReviewResult, stories: UserStory[]): string | null;
|
|
27
28
|
export declare function writeDigitalReviewReport(appDir: string, review: DigitalReviewResult): string | null;
|
|
28
29
|
export {};
|
|
@@ -7,6 +7,7 @@ exports.runDigitalHumanReview = runDigitalHumanReview;
|
|
|
7
7
|
exports.convertFindingsToUserStories = convertFindingsToUserStories;
|
|
8
8
|
exports.storiesToDiagnostics = storiesToDiagnostics;
|
|
9
9
|
exports.writeUserStoriesBacklog = writeUserStoriesBacklog;
|
|
10
|
+
exports.appendDigitalReviewRound = appendDigitalReviewRound;
|
|
10
11
|
exports.writeDigitalReviewReport = writeDigitalReviewReport;
|
|
11
12
|
const fs_1 = __importDefault(require("fs"));
|
|
12
13
|
const path_1 = __importDefault(require("path"));
|
|
@@ -355,6 +356,30 @@ function writeUserStoriesBacklog(appDir, stories) {
|
|
|
355
356
|
fs_1.default.writeFileSync(mdPath, `${lines.join("\n")}\n`, "utf-8");
|
|
356
357
|
return jsonPath;
|
|
357
358
|
}
|
|
359
|
+
function appendDigitalReviewRound(appDir, round, review, stories) {
|
|
360
|
+
if (!fs_1.default.existsSync(appDir)) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const deployDir = path_1.default.join(appDir, "deploy");
|
|
364
|
+
fs_1.default.mkdirSync(deployDir, { recursive: true });
|
|
365
|
+
const reportPath = path_1.default.join(deployDir, "digital-review-rounds.json");
|
|
366
|
+
const existing = fs_1.default.existsSync(reportPath)
|
|
367
|
+
? JSON.parse(fs_1.default.readFileSync(reportPath, "utf-8"))
|
|
368
|
+
: { rounds: [] };
|
|
369
|
+
const rounds = Array.isArray(existing.rounds) ? existing.rounds : [];
|
|
370
|
+
rounds.push({
|
|
371
|
+
round,
|
|
372
|
+
generatedAt: new Date().toISOString(),
|
|
373
|
+
summary: review.summary,
|
|
374
|
+
passed: review.passed,
|
|
375
|
+
score: review.score,
|
|
376
|
+
threshold: review.threshold,
|
|
377
|
+
findings: review.findings,
|
|
378
|
+
stories
|
|
379
|
+
});
|
|
380
|
+
fs_1.default.writeFileSync(reportPath, JSON.stringify({ rounds }, null, 2), "utf-8");
|
|
381
|
+
return reportPath;
|
|
382
|
+
}
|
|
358
383
|
function writeDigitalReviewReport(appDir, review) {
|
|
359
384
|
if (!fs_1.default.existsSync(appDir)) {
|
|
360
385
|
return null;
|
package/dist/commands/hello.js
CHANGED
|
@@ -128,6 +128,11 @@ async function runHello(input, runQuestions) {
|
|
|
128
128
|
const dryRun = runtimeFlags.dryRun;
|
|
129
129
|
const beginnerMode = runtimeFlags.beginner;
|
|
130
130
|
const provider = runtimeFlags.provider;
|
|
131
|
+
const iterations = runtimeFlags.iterations;
|
|
132
|
+
if (!Number.isInteger(iterations) || iterations < 1 || iterations > 10) {
|
|
133
|
+
(0, errors_1.printError)("SDD-1005", "Invalid --iterations value. Use an integer between 1 and 10.");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
131
136
|
console.log("Hello from sdd-cli.");
|
|
132
137
|
console.log(`Workspace: ${workspace.root}`);
|
|
133
138
|
if (beginnerMode) {
|
|
@@ -136,6 +141,7 @@ async function runHello(input, runQuestions) {
|
|
|
136
141
|
if (autoGuidedMode) {
|
|
137
142
|
printWhy("Auto-guided mode active: using current workspace defaults.");
|
|
138
143
|
printWhy(`AI provider preference: ${provider ?? "gemini"}`);
|
|
144
|
+
printWhy(`Iterations configured: ${iterations}`);
|
|
139
145
|
}
|
|
140
146
|
else {
|
|
141
147
|
const useWorkspace = await (0, prompt_1.confirm)("Use this workspace path? (y/n) ");
|
|
@@ -449,46 +455,50 @@ async function runHello(input, runQuestions) {
|
|
|
449
455
|
const digitalReviewDisabled = lifecycleDisabled || process.env.SDD_DISABLE_AI_AUTOPILOT === "1" || process.env.SDD_DISABLE_DIGITAL_REVIEW === "1";
|
|
450
456
|
if (!digitalReviewDisabled) {
|
|
451
457
|
const appDir = path_1.default.join(projectRoot, "generated-app");
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
458
|
+
let deliveryApproved = false;
|
|
459
|
+
for (let round = 1; round <= iterations; round += 1) {
|
|
460
|
+
printWhy(`Iteration ${round}/${iterations}: running multi-persona digital review.`);
|
|
461
|
+
let review = (0, digital_reviewers_1.runDigitalHumanReview)(appDir, {
|
|
462
|
+
goalText: text,
|
|
463
|
+
intentSignals: intent.signals,
|
|
464
|
+
intentDomain: intent.domain,
|
|
465
|
+
intentFlow: intent.flow
|
|
466
|
+
});
|
|
467
|
+
let stories = (0, digital_reviewers_1.convertFindingsToUserStories)(review.findings);
|
|
468
|
+
const reviewPath = (0, digital_reviewers_1.writeDigitalReviewReport)(appDir, review);
|
|
469
|
+
const storiesPath = (0, digital_reviewers_1.writeUserStoriesBacklog)(appDir, stories);
|
|
470
|
+
(0, digital_reviewers_1.appendDigitalReviewRound)(appDir, round, review, stories);
|
|
471
|
+
if (reviewPath) {
|
|
472
|
+
printWhy(`Digital-review report: ${reviewPath}`);
|
|
473
|
+
}
|
|
474
|
+
if (storiesPath) {
|
|
475
|
+
printWhy(`Digital-review user stories: ${storiesPath} (${stories.length} stories)`);
|
|
476
|
+
}
|
|
477
|
+
if (review.passed) {
|
|
478
|
+
printWhy(`Iteration ${round}: digital reviewers approved (${review.summary}).`);
|
|
479
|
+
deliveryApproved = true;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
printWhy(`Iteration ${round}: reviewers requested improvements (${review.summary}).`);
|
|
471
483
|
review.diagnostics.forEach((issue) => printWhy(`Reviewer issue: ${issue}`));
|
|
472
|
-
}
|
|
473
|
-
for (let attempt = 1; attempt <= maxReviewAttempts && !review.passed; attempt += 1) {
|
|
474
484
|
const storyDiagnostics = (0, digital_reviewers_1.storiesToDiagnostics)(stories);
|
|
475
|
-
const repair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, [...review.diagnostics, ...storyDiagnostics, "Implement all user stories
|
|
485
|
+
const repair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, [...review.diagnostics, ...storyDiagnostics, "Implement all prioritized user stories before next review."], intent.domain);
|
|
476
486
|
if (!repair.attempted || !repair.applied) {
|
|
477
|
-
printWhy(`
|
|
487
|
+
printWhy(`Iteration ${round}: repair skipped (${repair.reason || "unknown reason"}).`);
|
|
478
488
|
break;
|
|
479
489
|
}
|
|
480
|
-
printWhy(`
|
|
490
|
+
printWhy(`Iteration ${round}: repair applied (${repair.fileCount} files). Re-validating lifecycle.`);
|
|
481
491
|
lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
|
|
482
492
|
goalText: text,
|
|
483
493
|
intentSignals: intent.signals,
|
|
484
494
|
intentDomain: intent.domain,
|
|
485
495
|
intentFlow: intent.flow
|
|
486
496
|
});
|
|
487
|
-
lifecycle.summary.forEach((line) => printWhy(`Lifecycle (
|
|
497
|
+
lifecycle.summary.forEach((line) => printWhy(`Lifecycle (iteration ${round}): ${line}`));
|
|
488
498
|
if (!lifecycle.qualityPassed) {
|
|
499
|
+
printWhy("Quality gates failed after story implementation. Applying one quality-repair pass.");
|
|
489
500
|
const qualityRepair = (0, ai_autopilot_1.improveGeneratedApp)(appDir, text, provider, lifecycle.qualityDiagnostics, intent.domain);
|
|
490
501
|
if (qualityRepair.attempted && qualityRepair.applied) {
|
|
491
|
-
printWhy(`Quality regression repaired after digital review (${qualityRepair.fileCount} files). Re-validating delivery.`);
|
|
492
502
|
lifecycle = (0, app_lifecycle_1.runAppLifecycle)(projectRoot, activeProject, {
|
|
493
503
|
goalText: text,
|
|
494
504
|
intentSignals: intent.signals,
|
|
@@ -498,7 +508,7 @@ async function runHello(input, runQuestions) {
|
|
|
498
508
|
}
|
|
499
509
|
}
|
|
500
510
|
if (!lifecycle.qualityPassed) {
|
|
501
|
-
printWhy(
|
|
511
|
+
printWhy(`Iteration ${round}: lifecycle quality still failing.`);
|
|
502
512
|
continue;
|
|
503
513
|
}
|
|
504
514
|
review = (0, digital_reviewers_1.runDigitalHumanReview)(appDir, {
|
|
@@ -510,16 +520,20 @@ async function runHello(input, runQuestions) {
|
|
|
510
520
|
stories = (0, digital_reviewers_1.convertFindingsToUserStories)(review.findings);
|
|
511
521
|
(0, digital_reviewers_1.writeDigitalReviewReport)(appDir, review);
|
|
512
522
|
(0, digital_reviewers_1.writeUserStoriesBacklog)(appDir, stories);
|
|
513
|
-
|
|
514
|
-
|
|
523
|
+
(0, digital_reviewers_1.appendDigitalReviewRound)(appDir, round, review, stories);
|
|
524
|
+
if (review.passed) {
|
|
525
|
+
printWhy(`Iteration ${round}: delivery improved and approved (${review.summary}).`);
|
|
526
|
+
deliveryApproved = true;
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
printWhy(`Iteration ${round}: additional improvements still required (${review.summary}).`);
|
|
515
530
|
}
|
|
516
531
|
}
|
|
517
|
-
if (!
|
|
518
|
-
printWhy("Digital-review quality bar not met after
|
|
532
|
+
if (!deliveryApproved) {
|
|
533
|
+
printWhy("Digital-review quality bar not met after configured iterations.");
|
|
519
534
|
printRecoveryNext(activeProject, "finish", text);
|
|
520
535
|
return;
|
|
521
536
|
}
|
|
522
|
-
printWhy(`Digital reviewers approved delivery quality (${review.summary}).`);
|
|
523
537
|
}
|
|
524
538
|
(0, local_metrics_1.recordActivationMetric)("completed", {
|
|
525
539
|
project: activeProject,
|
package/dist/context/flags.d.ts
CHANGED
package/dist/context/flags.js
CHANGED
|
@@ -15,7 +15,8 @@ const flags = {
|
|
|
15
15
|
scope: undefined,
|
|
16
16
|
metricsLocal: false,
|
|
17
17
|
provider: process.env.SDD_AI_PROVIDER_DEFAULT ?? "gemini",
|
|
18
|
-
model: process.env.SDD_AI_MODEL_DEFAULT
|
|
18
|
+
model: process.env.SDD_AI_MODEL_DEFAULT,
|
|
19
|
+
iterations: 1
|
|
19
20
|
};
|
|
20
21
|
function setFlags(next) {
|
|
21
22
|
if ("approve" in next) {
|
|
@@ -57,6 +58,10 @@ function setFlags(next) {
|
|
|
57
58
|
if ("model" in next) {
|
|
58
59
|
flags.model = typeof next.model === "string" ? next.model : flags.model;
|
|
59
60
|
}
|
|
61
|
+
if ("iterations" in next) {
|
|
62
|
+
const raw = Number(next.iterations);
|
|
63
|
+
flags.iterations = Number.isFinite(raw) ? Math.trunc(raw) : flags.iterations;
|
|
64
|
+
}
|
|
60
65
|
}
|
|
61
66
|
function getFlags() {
|
|
62
67
|
return { ...flags };
|