ai-spec-dev 0.42.0 → 0.46.0

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.
Files changed (49) hide show
  1. package/README.md +33 -17
  2. package/cli/commands/create.ts +232 -11
  3. package/cli/commands/init.ts +310 -107
  4. package/cli/commands/model.ts +7 -11
  5. package/cli/index.ts +1 -1
  6. package/cli/utils.ts +72 -4
  7. package/core/config-defaults.ts +44 -0
  8. package/core/constitution-generator.ts +2 -1
  9. package/core/dsl-extractor.ts +2 -1
  10. package/core/error-feedback.ts +3 -2
  11. package/core/openapi-exporter.ts +3 -2
  12. package/core/repo-store.ts +95 -0
  13. package/core/reviewer.ts +14 -13
  14. package/core/run-logger.ts +3 -4
  15. package/core/run-snapshot.ts +2 -3
  16. package/core/run-trend.ts +3 -4
  17. package/core/spec-generator.ts +27 -42
  18. package/core/token-budget.ts +3 -8
  19. package/core/vcr.ts +3 -1
  20. package/dist/cli/index.js +919 -519
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/index.mjs +912 -512
  23. package/dist/cli/index.mjs.map +1 -1
  24. package/dist/index.d.mts +3 -2
  25. package/dist/index.d.ts +3 -2
  26. package/dist/index.js +43 -53
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +43 -53
  29. package/dist/index.mjs.map +1 -1
  30. package/package.json +1 -1
  31. package/demo-backend/.ai-spec-constitution.md +0 -65
  32. package/demo-backend/package.json +0 -21
  33. package/demo-backend/prisma/schema.prisma +0 -22
  34. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +0 -186
  35. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +0 -211
  36. package/demo-backend/src/controllers/bookmark.controller.test.ts +0 -255
  37. package/demo-backend/src/controllers/bookmark.controller.ts +0 -187
  38. package/demo-backend/src/index.ts +0 -17
  39. package/demo-backend/src/routes/bookmark.routes.test.ts +0 -264
  40. package/demo-backend/src/routes/bookmark.routes.ts +0 -11
  41. package/demo-backend/src/routes/index.ts +0 -8
  42. package/demo-backend/src/services/bookmark.service.test.ts +0 -433
  43. package/demo-backend/src/services/bookmark.service.ts +0 -261
  44. package/demo-backend/tsconfig.json +0 -12
  45. package/demo-frontend/.ai-spec-constitution.md +0 -95
  46. package/demo-frontend/package.json +0 -23
  47. package/demo-frontend/src/App.tsx +0 -12
  48. package/demo-frontend/src/main.tsx +0 -9
  49. package/demo-frontend/tsconfig.json +0 -13
package/dist/index.mjs CHANGED
@@ -2,7 +2,6 @@
2
2
  import { GoogleGenerativeAI } from "@google/generative-ai";
3
3
  import Anthropic from "@anthropic-ai/sdk";
4
4
  import OpenAI from "openai";
5
- import axios from "axios";
6
5
  import { ProxyAgent } from "undici";
7
6
 
8
7
  // prompts/spec.prompt.ts
@@ -295,7 +294,9 @@ var PROVIDER_CATALOG = {
295
294
  displayName: "MiMo (Xiaomi)",
296
295
  description: "\u5C0F\u7C73 MiMo \u2014 mimo-v2-pro (Anthropic-compatible API)",
297
296
  models: ["mimo-v2-pro"],
298
- envKey: "MIMO_API_KEY"
297
+ envKey: "MIMO_API_KEY",
298
+ // Fallback env var — MiMo's token plan uses ANTHROPIC_AUTH_TOKEN
299
+ fallbackEnvKeys: ["ANTHROPIC_AUTH_TOKEN"]
299
300
  // baseURL not used — MiMo has a dedicated provider class
300
301
  },
301
302
  gemini: {
@@ -464,8 +465,8 @@ var ClaudeProvider = class {
464
465
  ...systemInstruction ? { system: systemInstruction } : {},
465
466
  messages: [{ role: "user", content: prompt }]
466
467
  });
467
- const block = message.content[0];
468
- if (block.type === "text") return block.text;
468
+ const textBlock = message.content.find((b) => b.type === "text");
469
+ if (textBlock) return textBlock.text;
469
470
  throw new Error("Unexpected response type from Claude API");
470
471
  },
471
472
  { label: `${this.providerName}/${this.modelName}` }
@@ -510,43 +511,29 @@ var OpenAICompatibleProvider = class {
510
511
  }
511
512
  };
512
513
  var MiMoProvider = class {
514
+ client;
513
515
  providerName = "mimo";
514
516
  modelName;
515
- apiKey;
516
- baseUrl = "https://api.xiaomimimo.com/anthropic/v1/messages";
517
517
  constructor(apiKey, modelName = PROVIDER_CATALOG.mimo.models[0]) {
518
- this.apiKey = apiKey;
518
+ const baseURL = process.env["MIMO_BASE_URL"] || process.env["ANTHROPIC_BASE_URL"] || "https://token-plan-cn.xiaomimimo.com/anthropic";
519
+ this.client = new Anthropic({ apiKey, baseURL });
519
520
  this.modelName = modelName;
520
521
  }
521
522
  async generate(prompt, systemInstruction) {
522
523
  return withReliability(
523
524
  async () => {
524
- const body = {
525
+ const stream = this.client.messages.stream({
525
526
  model: this.modelName,
526
- max_tokens: 16384,
527
- messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
528
- top_p: 0.95,
529
- stream: false,
530
- temperature: 1,
531
- stop_sequences: null
532
- };
533
- if (systemInstruction) {
534
- body.system = systemInstruction;
535
- }
536
- const response = await axios.post(this.baseUrl, body, {
537
- headers: {
538
- "api-key": this.apiKey,
539
- "Content-Type": "application/json"
540
- }
527
+ max_tokens: 65536,
528
+ ...systemInstruction ? { system: systemInstruction } : {},
529
+ messages: [{ role: "user", content: prompt }]
541
530
  });
542
- const data = response.data;
543
- const blocks = data?.content ?? [];
544
- const textBlock = blocks.find((b) => b.type === "text");
545
- if (textBlock?.text) return textBlock.text;
546
- if (data?.stop_reason === "max_tokens") {
547
- throw new Error(`MiMo response truncated (max_tokens reached). The prompt may be too long. Try a shorter spec or switch to a model with larger context.`);
548
- }
549
- throw new Error(`Unexpected MiMo response: ${JSON.stringify(response.data).slice(0, 200)}`);
531
+ const message = await stream.finalMessage();
532
+ const textBlock = message.content.find((b) => b.type === "text");
533
+ if (textBlock) return textBlock.text;
534
+ const thinkBlock = message.content.find((b) => b.type === "thinking");
535
+ if (thinkBlock) return thinkBlock.thinking;
536
+ return message.content.map((b) => b.text ?? "").join("");
550
537
  },
551
538
  { label: `${this.providerName}/${this.modelName}` }
552
539
  );
@@ -5151,13 +5138,11 @@ function typeLabel(v2) {
5151
5138
 
5152
5139
  // core/token-budget.ts
5153
5140
  import chalk6 from "chalk";
5154
- var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
5155
- function estimateTokens(text) {
5156
- if (!text) return 0;
5157
- const cjkCount = (text.match(CJK_RANGE) ?? []).length;
5158
- const nonCjkLength = text.length - cjkCount;
5159
- return Math.ceil(cjkCount + nonCjkLength / 4);
5160
- }
5141
+
5142
+ // core/config-defaults.ts
5143
+ var DEFAULT_REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
5144
+ var DEFAULT_MAX_CONSTITUTION_CHARS = 4e3;
5145
+ var DEFAULT_MAX_REVIEW_FILE_CHARS = 3e3;
5161
5146
  var DEFAULT_TOKEN_BUDGETS = {
5162
5147
  gemini: 9e5,
5163
5148
  claude: 18e4,
@@ -5165,8 +5150,18 @@ var DEFAULT_TOKEN_BUDGETS = {
5165
5150
  deepseek: 6e4,
5166
5151
  default: 1e5
5167
5152
  };
5153
+
5154
+ // core/token-budget.ts
5155
+ var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
5156
+ function estimateTokens(text) {
5157
+ if (!text) return 0;
5158
+ const cjkCount = (text.match(CJK_RANGE) ?? []).length;
5159
+ const nonCjkLength = text.length - cjkCount;
5160
+ return Math.ceil(cjkCount + nonCjkLength / 4);
5161
+ }
5162
+ var DEFAULT_TOKEN_BUDGETS2 = DEFAULT_TOKEN_BUDGETS;
5168
5163
  function getDefaultBudget(providerName) {
5169
- return DEFAULT_TOKEN_BUDGETS[providerName] ?? DEFAULT_TOKEN_BUDGETS.default;
5164
+ return DEFAULT_TOKEN_BUDGETS2[providerName] ?? DEFAULT_TOKEN_BUDGETS2.default;
5170
5165
  }
5171
5166
 
5172
5167
  // core/dsl-extractor.ts
@@ -6627,7 +6622,7 @@ ${context.routeSummary}
6627
6622
  }
6628
6623
  if (context.schema) {
6629
6624
  parts.push(`=== Prisma Schema ===
6630
- ${context.schema.slice(0, 4e3)}
6625
+ ${context.schema.slice(0, DEFAULT_MAX_CONSTITUTION_CHARS)}
6631
6626
  `);
6632
6627
  }
6633
6628
  if (context.errorPatterns) {
@@ -6691,7 +6686,7 @@ async function loadAccumulatedLessons(projectRoot) {
6691
6686
  const nextSection = section.slice(marker.length).match(/\n## \d/);
6692
6687
  return nextSection ? section.slice(0, marker.length + nextSection.index) : section;
6693
6688
  }
6694
- var REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
6689
+ var REVIEW_HISTORY_FILE = DEFAULT_REVIEW_HISTORY_FILE;
6695
6690
  async function loadReviewHistory(projectRoot) {
6696
6691
  const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6697
6692
  try {
@@ -6822,15 +6817,13 @@ ${specContent || "(No spec \u2014 review for general code quality)"}
6822
6817
  ${codeContext}`;
6823
6818
  const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
6824
6819
  console.log(chalk12.gray(" Pass 2/3: Implementation review..."));
6820
+ const specDigest = specContent && specContent.length > 600 ? specContent.slice(0, 600) + "\n... [spec truncated \u2014 see Pass 0/1 for full text]" : specContent || "(No spec)";
6825
6821
  const history = await loadReviewHistory(this.projectRoot);
6826
6822
  const historyContext = buildHistoryContext(history);
6827
6823
  const implPrompt = `Review the implementation details of this change.
6828
6824
 
6829
- === Feature Spec ===
6830
- ${specContent || "(No spec \u2014 review for general code quality)"}
6831
-
6832
- === Code ===
6833
- ${codeContext}
6825
+ === Feature Spec (digest \u2014 full spec was provided in Pass 0/1) ===
6826
+ ${specDigest}
6834
6827
 
6835
6828
  === Architecture Review (Pass 1 \u2014 do NOT repeat these findings) ===
6836
6829
  ${archReview}
@@ -6839,11 +6832,8 @@ ${historyContext}`;
6839
6832
  console.log(chalk12.gray(" Pass 3/3: Impact & complexity assessment..."));
6840
6833
  const impactPrompt = `Assess the impact and complexity of this change.
6841
6834
 
6842
- === Feature Spec ===
6843
- ${specContent || "(No spec \u2014 review for general code quality)"}
6844
-
6845
- === Code ===
6846
- ${codeContext}
6835
+ === Feature Spec (digest) ===
6836
+ ${specDigest}
6847
6837
 
6848
6838
  === Architecture Review (Pass 1 \u2014 do NOT repeat) ===
6849
6839
  ${archReview}
@@ -6917,8 +6907,8 @@ ${sep}
6917
6907
  filesSection += `
6918
6908
 
6919
6909
  === ${filePath} ===
6920
- ${content.slice(0, 3e3)}`;
6921
- if (content.length > 3e3) filesSection += `
6910
+ ${content.slice(0, DEFAULT_MAX_REVIEW_FILE_CHARS)}`;
6911
+ if (content.length > DEFAULT_MAX_REVIEW_FILE_CHARS) filesSection += `
6922
6912
  ... (truncated, ${content.length} chars total)`;
6923
6913
  } catch {
6924
6914
  filesSection += `