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.
@@ -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, reviewSystemPrompt, reviewArchitectureSystemPrompt, reviewImplementationSystemPrompt, reviewImpactComplexitySystemPrompt;
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
- console.log(` ${entry.date} [${color(bar)}] ${color(entry.score + "/10")} ${path10.basename(entry.specFile)}`);
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 if (!opts.auto) {
10132
- console.log(chalk19.gray(" (Assessment skipped \u2014 AI call failed or timed out)"));
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:"));