ai-spec-dev 0.28.1 → 0.29.1
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 +27 -1
- package/RELEASE_LOG.md +58 -0
- package/cli/index.ts +58 -2
- package/core/reviewer.ts +7 -2
- package/dist/cli/index.js +56 -23
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +56 -23
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/prompts/codegen.prompt.ts +1 -19
- package/purpose.md +26 -14
package/dist/cli/index.mjs
CHANGED
|
@@ -21,8 +21,7 @@ __export(codegen_prompt_exports, {
|
|
|
21
21
|
getCodeGenSystemPrompt: () => getCodeGenSystemPrompt,
|
|
22
22
|
reviewArchitectureSystemPrompt: () => reviewArchitectureSystemPrompt,
|
|
23
23
|
reviewImpactComplexitySystemPrompt: () => reviewImpactComplexitySystemPrompt,
|
|
24
|
-
reviewImplementationSystemPrompt: () => reviewImplementationSystemPrompt
|
|
25
|
-
reviewSystemPrompt: () => reviewSystemPrompt
|
|
24
|
+
reviewImplementationSystemPrompt: () => reviewImplementationSystemPrompt
|
|
26
25
|
});
|
|
27
26
|
function getCodeGenSystemPrompt(repoType) {
|
|
28
27
|
switch (repoType) {
|
|
@@ -40,7 +39,7 @@ function getCodeGenSystemPrompt(repoType) {
|
|
|
40
39
|
return codeGenSystemPrompt;
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
|
-
var codeGenSystemPrompt, codeGenGoSystemPrompt, codeGenPythonSystemPrompt, codeGenJavaSystemPrompt, codeGenRustSystemPrompt, codeGenPhpSystemPrompt,
|
|
42
|
+
var codeGenSystemPrompt, codeGenGoSystemPrompt, codeGenPythonSystemPrompt, codeGenJavaSystemPrompt, codeGenRustSystemPrompt, codeGenPhpSystemPrompt, reviewArchitectureSystemPrompt, reviewImplementationSystemPrompt, reviewImpactComplexitySystemPrompt;
|
|
44
43
|
var init_codegen_prompt = __esm({
|
|
45
44
|
"prompts/codegen.prompt.ts"() {
|
|
46
45
|
"use strict";
|
|
@@ -190,23 +189,6 @@ CRITICAL \u2014 File Reuse Rules:
|
|
|
190
189
|
10. Register new routes in the EXISTING routes/api.php (or routes/web.php) \u2014 never create a parallel routes file.
|
|
191
190
|
11. Add new Eloquent model methods to the EXISTING Model class \u2014 never create a parallel model file.
|
|
192
191
|
12. Add new service methods to the EXISTING service class if one already covers the same resource.`;
|
|
193
|
-
reviewSystemPrompt = `You are a Senior Software Architect conducting a thorough code review. Your review should be structured, constructive, and specific.
|
|
194
|
-
|
|
195
|
-
Always format your review with these exact sections:
|
|
196
|
-
|
|
197
|
-
## \u2705 \u4F18\u70B9 (What's Good)
|
|
198
|
-
List specific things done well.
|
|
199
|
-
|
|
200
|
-
## \u26A0\uFE0F \u95EE\u9898 (Issues Found)
|
|
201
|
-
List bugs, security issues, or spec deviations with line references.
|
|
202
|
-
|
|
203
|
-
## \u{1F4A1} \u6539\u8FDB\u5EFA\u8BAE (Suggestions)
|
|
204
|
-
Actionable improvements that would elevate code quality.
|
|
205
|
-
|
|
206
|
-
## \u{1F4CA} \u603B\u4F53\u8BC4\u4EF7 (Overall Assessment)
|
|
207
|
-
Score: X/10 \u2014 One-paragraph summary.
|
|
208
|
-
|
|
209
|
-
Be specific. Reference actual code, not vague principles.`;
|
|
210
192
|
reviewArchitectureSystemPrompt = `You are a Senior Software Architect reviewing the HIGH-LEVEL design of a code change.
|
|
211
193
|
|
|
212
194
|
Focus ONLY on:
|
|
@@ -7026,7 +7008,9 @@ ${content.slice(0, 3e3)}`;
|
|
|
7026
7008
|
for (const entry of recent) {
|
|
7027
7009
|
const bar = "\u2588".repeat(entry.score) + "\u2591".repeat(10 - entry.score);
|
|
7028
7010
|
const color = entry.score >= 8 ? chalk9.green : entry.score >= 6 ? chalk9.yellow : chalk9.red;
|
|
7029
|
-
|
|
7011
|
+
const impactTag = entry.impactLevel ? chalk9.gray(` \u5F71\u54CD:${entry.impactLevel === "\u9AD8" ? chalk9.red(entry.impactLevel) : entry.impactLevel === "\u4E2D" ? chalk9.yellow(entry.impactLevel) : chalk9.green(entry.impactLevel)}`) : "";
|
|
7012
|
+
const complexityTag = entry.complexityLevel ? chalk9.gray(` \u590D\u6742\u5EA6:${entry.complexityLevel === "\u9AD8" ? chalk9.red(entry.complexityLevel) : entry.complexityLevel === "\u4E2D" ? chalk9.yellow(entry.complexityLevel) : chalk9.green(entry.complexityLevel)}`) : "";
|
|
7013
|
+
console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")}${impactTag}${complexityTag} ${path10.basename(entry.specFile)}`);
|
|
7030
7014
|
}
|
|
7031
7015
|
console.log(chalk9.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7032
7016
|
}
|
|
@@ -10040,9 +10024,11 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10040
10024
|
});
|
|
10041
10025
|
setActiveLogger(runLogger);
|
|
10042
10026
|
console.log(chalk19.blue("[1/6] Loading project context..."));
|
|
10027
|
+
runLogger.stageStart("context_load");
|
|
10043
10028
|
const loader = new ContextLoader(currentDir);
|
|
10044
10029
|
const context = await loader.loadProjectContext();
|
|
10045
10030
|
const { type: detectedRepoType } = await detectRepoType(currentDir);
|
|
10031
|
+
runLogger.stageEnd("context_load", { techStack: context.techStack, repoType: detectedRepoType });
|
|
10046
10032
|
console.log(chalk19.gray(` Tech stack : ${context.techStack.join(", ") || "unknown"} [${detectedRepoType}]`));
|
|
10047
10033
|
console.log(chalk19.gray(` Dependencies: ${context.dependencies.length} packages`));
|
|
10048
10034
|
console.log(chalk19.gray(` API files : ${context.apiStructure.length} files`));
|
|
@@ -10073,6 +10059,7 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10073
10059
|
const specProvider = createProvider(specProviderName, specApiKey, specModelName);
|
|
10074
10060
|
let initialSpec;
|
|
10075
10061
|
let initialTasks = [];
|
|
10062
|
+
runLogger.stageStart("spec_gen", { provider: specProviderName, model: specModelName });
|
|
10076
10063
|
try {
|
|
10077
10064
|
if (opts.skipTasks) {
|
|
10078
10065
|
const generator = new SpecGenerator(specProvider);
|
|
@@ -10089,7 +10076,9 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10089
10076
|
console.log(chalk19.yellow(" \u26A0 Tasks not parsed from response \u2014 will retry separately after refinement."));
|
|
10090
10077
|
}
|
|
10091
10078
|
}
|
|
10079
|
+
runLogger.stageEnd("spec_gen", { taskCount: initialTasks.length });
|
|
10092
10080
|
} catch (err) {
|
|
10081
|
+
runLogger.stageFail("spec_gen", err.message);
|
|
10093
10082
|
console.error(chalk19.red(" \u2718 Spec generation failed:"), err);
|
|
10094
10083
|
process.exit(1);
|
|
10095
10084
|
}
|
|
@@ -10099,8 +10088,10 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10099
10088
|
finalSpec = initialSpec;
|
|
10100
10089
|
} else {
|
|
10101
10090
|
console.log(chalk19.blue("\n[3/6] Interactive spec refinement..."));
|
|
10091
|
+
runLogger.stageStart("spec_refine");
|
|
10102
10092
|
const refiner = new SpecRefiner(specProvider);
|
|
10103
10093
|
finalSpec = await refiner.refineLoop(initialSpec);
|
|
10094
|
+
runLogger.stageEnd("spec_refine");
|
|
10104
10095
|
}
|
|
10105
10096
|
const featureSlug = slugify(idea);
|
|
10106
10097
|
const minScore = config2.minSpecScore ?? 0;
|
|
@@ -10109,14 +10100,17 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10109
10100
|
if (!opts.auto) {
|
|
10110
10101
|
console.log(chalk19.blue("\n[3.4/6] Spec quality assessment..."));
|
|
10111
10102
|
}
|
|
10103
|
+
runLogger.stageStart("spec_assess");
|
|
10112
10104
|
const assessment = await assessSpec(specProvider, finalSpec, context.constitution ?? void 0);
|
|
10113
10105
|
if (assessment) {
|
|
10106
|
+
runLogger.stageEnd("spec_assess", { overallScore: assessment.overallScore });
|
|
10114
10107
|
if (!opts.auto) printSpecAssessment(assessment);
|
|
10115
10108
|
if (minScore > 0 && assessment.overallScore < minScore) {
|
|
10116
10109
|
if (opts.force) {
|
|
10117
10110
|
console.log(chalk19.yellow(`
|
|
10118
10111
|
\u26A0 Score gate: ${assessment.overallScore}/10 < minimum ${minScore}/10 \u2014 bypassed with --force.`));
|
|
10119
10112
|
} else {
|
|
10113
|
+
runLogger.stageFail("spec_assess", `Score gate: ${assessment.overallScore} < ${minScore}`);
|
|
10120
10114
|
console.log(chalk19.red(`
|
|
10121
10115
|
\u2718 Spec quality gate failed: overallScore ${assessment.overallScore}/10 < minimum ${minScore}/10`));
|
|
10122
10116
|
if (!opts.auto) {
|
|
@@ -10128,8 +10122,11 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10128
10122
|
process.exit(1);
|
|
10129
10123
|
}
|
|
10130
10124
|
}
|
|
10131
|
-
} else
|
|
10132
|
-
|
|
10125
|
+
} else {
|
|
10126
|
+
runLogger.stageEnd("spec_assess", { skipped: true });
|
|
10127
|
+
if (!opts.auto) {
|
|
10128
|
+
console.log(chalk19.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
|
|
10129
|
+
}
|
|
10133
10130
|
}
|
|
10134
10131
|
}
|
|
10135
10132
|
if (!opts.auto) {
|
|
@@ -10187,17 +10184,21 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10187
10184
|
} else {
|
|
10188
10185
|
console.log(chalk19.blue("\n[DSL] Extracting structured DSL from spec..."));
|
|
10189
10186
|
console.log(chalk19.gray(` Provider: ${specProviderName}/${specModelName}`));
|
|
10187
|
+
runLogger.stageStart("dsl_extract");
|
|
10190
10188
|
try {
|
|
10191
10189
|
const isFrontend = isFrontendDeps(context.dependencies);
|
|
10192
10190
|
if (isFrontend) console.log(chalk19.gray(" Frontend project detected \u2014 using ComponentSpec extractor"));
|
|
10193
10191
|
const dslExtractor = new DslExtractor(specProvider);
|
|
10194
10192
|
extractedDsl = await dslExtractor.extract(finalSpec, { auto: opts.auto, isFrontend });
|
|
10195
10193
|
if (extractedDsl) {
|
|
10194
|
+
runLogger.stageEnd("dsl_extract", { endpoints: extractedDsl.endpoints?.length ?? 0, models: extractedDsl.models?.length ?? 0 });
|
|
10196
10195
|
console.log(chalk19.green(" \u2714 DSL extracted and validated."));
|
|
10197
10196
|
} else {
|
|
10197
|
+
runLogger.stageEnd("dsl_extract", { skipped: true });
|
|
10198
10198
|
console.log(chalk19.yellow(" \u26A0 DSL skipped \u2014 codegen will use Spec + Tasks only."));
|
|
10199
10199
|
}
|
|
10200
10200
|
} catch (err) {
|
|
10201
|
+
runLogger.stageFail("dsl_extract", err.message);
|
|
10201
10202
|
console.log(chalk19.yellow(` \u26A0 DSL extraction error: ${err.message} \u2014 continuing without DSL.`));
|
|
10202
10203
|
}
|
|
10203
10204
|
}
|
|
@@ -10255,6 +10256,7 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10255
10256
|
const testGen = new TestGenerator(codegenProvider);
|
|
10256
10257
|
generatedTestFiles = await testGen.generateTdd(extractedDsl, workingDir);
|
|
10257
10258
|
}
|
|
10259
|
+
runLogger.stageStart("codegen", { mode: codegenMode, provider: codegenProviderName, model: codegenModelName });
|
|
10258
10260
|
const codegen = new CodeGenerator(codegenProvider, codegenMode);
|
|
10259
10261
|
const generatedFiles = await codegen.generateCode(specFile, workingDir, context, {
|
|
10260
10262
|
auto: opts.auto,
|
|
@@ -10262,6 +10264,7 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10262
10264
|
dslFilePath: savedDslFile ?? void 0,
|
|
10263
10265
|
repoType: detectedRepoType
|
|
10264
10266
|
});
|
|
10267
|
+
runLogger.stageEnd("codegen", { filesGenerated: generatedFiles.length });
|
|
10265
10268
|
if (opts.tdd) {
|
|
10266
10269
|
console.log(chalk19.gray("\n[7/9] TDD mode \u2014 test files already written pre-implementation."));
|
|
10267
10270
|
} else if (opts.skipTests) {
|
|
@@ -10271,8 +10274,10 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10271
10274
|
} else {
|
|
10272
10275
|
console.log(chalk19.blue(`
|
|
10273
10276
|
[7/9] Test skeleton generation...`));
|
|
10277
|
+
runLogger.stageStart("test_gen");
|
|
10274
10278
|
const testGen = new TestGenerator(codegenProvider);
|
|
10275
10279
|
generatedTestFiles = await testGen.generate(extractedDsl, workingDir);
|
|
10280
|
+
runLogger.stageEnd("test_gen", { filesGenerated: generatedTestFiles.length });
|
|
10276
10281
|
}
|
|
10277
10282
|
if (opts.skipErrorFeedback) {
|
|
10278
10283
|
console.log(chalk19.gray("[8/9] Skipping error feedback (--skip-error-feedback)."));
|
|
@@ -10280,14 +10285,17 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10280
10285
|
if (opts.tdd) {
|
|
10281
10286
|
console.log(chalk19.cyan("[8/9] TDD mode \u2014 error feedback loop driving implementation to pass tests..."));
|
|
10282
10287
|
}
|
|
10288
|
+
runLogger.stageStart("error_feedback");
|
|
10283
10289
|
await runErrorFeedback(codegenProvider, workingDir, extractedDsl, {
|
|
10284
10290
|
maxCycles: opts.tdd ? 3 : 2
|
|
10285
10291
|
// TDD gets one extra cycle
|
|
10286
10292
|
});
|
|
10293
|
+
runLogger.stageEnd("error_feedback");
|
|
10287
10294
|
}
|
|
10288
10295
|
let reviewResult = "";
|
|
10289
10296
|
if (!opts.skipReview) {
|
|
10290
10297
|
console.log(chalk19.blue("\n[9/9] Automated code review (3-pass: architecture + implementation + impact/complexity)..."));
|
|
10298
|
+
runLogger.stageStart("review");
|
|
10291
10299
|
const reviewer = new CodeReviewer(specProvider, currentDir);
|
|
10292
10300
|
const savedSpec = await fs24.readFile(specFile, "utf-8");
|
|
10293
10301
|
if (codegenMode === "api" && generatedFiles.length > 0) {
|
|
@@ -10301,6 +10309,7 @@ program.command("create").description("Generate a feature spec and kick off code
|
|
|
10301
10309
|
process.chdir(originalDir);
|
|
10302
10310
|
}
|
|
10303
10311
|
}
|
|
10312
|
+
runLogger.stageEnd("review");
|
|
10304
10313
|
await accumulateReviewKnowledge(specProvider, currentDir, reviewResult);
|
|
10305
10314
|
}
|
|
10306
10315
|
runLogger.finish();
|
|
@@ -11149,6 +11158,12 @@ program.command("update").description("Update an existing spec with a change req
|
|
|
11149
11158
|
const provider = createProvider(providerName, apiKey, modelName);
|
|
11150
11159
|
console.log(chalk19.blue("\n\u2500\u2500\u2500 ai-spec update \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11151
11160
|
console.log(chalk19.gray(` Provider: ${providerName}/${modelName}`));
|
|
11161
|
+
const updateRunId = generateRunId();
|
|
11162
|
+
const updateSnapshot = new RunSnapshot(currentDir, updateRunId);
|
|
11163
|
+
setActiveSnapshot(updateSnapshot);
|
|
11164
|
+
const updateLogger = new RunLogger(currentDir, updateRunId, { provider: providerName, model: modelName });
|
|
11165
|
+
setActiveLogger(updateLogger);
|
|
11166
|
+
console.log(chalk19.gray(` Run ID: ${updateRunId}`));
|
|
11152
11167
|
let specPath = opts.spec ?? null;
|
|
11153
11168
|
if (!specPath) {
|
|
11154
11169
|
const specsDir = path23.join(currentDir, "specs");
|
|
@@ -11207,6 +11222,7 @@ ${context.constitution}
|
|
|
11207
11222
|
=== DSL Context ===
|
|
11208
11223
|
${JSON.stringify(result.updatedDsl, null, 2).slice(0, 3e3)}
|
|
11209
11224
|
` : "";
|
|
11225
|
+
updateLogger.stageStart("update_codegen");
|
|
11210
11226
|
for (const affected of result.affectedFiles) {
|
|
11211
11227
|
const fullPath = path23.join(currentDir, affected.file);
|
|
11212
11228
|
let existing = "";
|
|
@@ -11231,12 +11247,29 @@ ${existing || "Create from scratch."}`;
|
|
|
11231
11247
|
const raw = await codegenProvider.generate(codePrompt, _getPrompt(repoType));
|
|
11232
11248
|
const content = raw.replace(/^```\w*\n?/gm, "").replace(/\n?```$/gm, "").trim();
|
|
11233
11249
|
await fs24.ensureDir(path23.dirname(fullPath));
|
|
11250
|
+
await updateSnapshot.snapshotFile(fullPath);
|
|
11234
11251
|
await fs24.writeFile(fullPath, content, "utf-8");
|
|
11252
|
+
updateLogger.fileWritten(affected.file);
|
|
11235
11253
|
console.log(chalk19.green("\u2714"));
|
|
11236
11254
|
} catch (err) {
|
|
11255
|
+
updateLogger.stageFail("update_codegen", `${affected.file}: ${err.message}`);
|
|
11237
11256
|
console.log(chalk19.red(`\u2718 ${err.message}`));
|
|
11238
11257
|
}
|
|
11239
11258
|
}
|
|
11259
|
+
updateLogger.stageEnd("update_codegen", { filesUpdated: result.affectedFiles.length });
|
|
11260
|
+
const updatedSpecContent = await fs24.readFile(result.newSpecPath, "utf-8").catch(() => "");
|
|
11261
|
+
if (updatedSpecContent) {
|
|
11262
|
+
const updateReviewer = new CodeReviewer(provider, currentDir);
|
|
11263
|
+
const reviewResult = await updateReviewer.reviewCode(updatedSpecContent, result.newSpecPath).catch(() => "");
|
|
11264
|
+
if (reviewResult && reviewResult !== "No changes") {
|
|
11265
|
+
await accumulateReviewKnowledge(provider, currentDir, reviewResult);
|
|
11266
|
+
}
|
|
11267
|
+
}
|
|
11268
|
+
}
|
|
11269
|
+
updateLogger.finish();
|
|
11270
|
+
updateLogger.printSummary();
|
|
11271
|
+
if (updateSnapshot.fileCount > 0) {
|
|
11272
|
+
console.log(chalk19.gray(` To undo changes: ai-spec restore ${updateRunId}`));
|
|
11240
11273
|
}
|
|
11241
11274
|
if (!opts.codegen && result.affectedFiles.length > 0) {
|
|
11242
11275
|
console.log(chalk19.blue("\n Next steps:"));
|