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.d.mts CHANGED
@@ -81,6 +81,8 @@ interface ProviderMeta {
81
81
  models: string[];
82
82
  /** Environment variable name for the API key */
83
83
  envKey: string;
84
+ /** Fallback env var names checked if envKey is not set */
85
+ fallbackEnvKeys?: string[];
84
86
  /**
85
87
  * Base URL for OpenAI-compatible providers.
86
88
  * Undefined means the provider has its own SDK (Gemini / Claude).
@@ -126,10 +128,9 @@ declare class OpenAICompatibleProvider implements AIProvider {
126
128
  generate(prompt: string, systemInstruction?: string): Promise<string>;
127
129
  }
128
130
  declare class MiMoProvider implements AIProvider {
131
+ private client;
129
132
  readonly providerName = "mimo";
130
133
  readonly modelName: string;
131
- private apiKey;
132
- private readonly baseUrl;
133
134
  constructor(apiKey: string, modelName?: string);
134
135
  generate(prompt: string, systemInstruction?: string): Promise<string>;
135
136
  }
package/dist/index.d.ts CHANGED
@@ -81,6 +81,8 @@ interface ProviderMeta {
81
81
  models: string[];
82
82
  /** Environment variable name for the API key */
83
83
  envKey: string;
84
+ /** Fallback env var names checked if envKey is not set */
85
+ fallbackEnvKeys?: string[];
84
86
  /**
85
87
  * Base URL for OpenAI-compatible providers.
86
88
  * Undefined means the provider has its own SDK (Gemini / Claude).
@@ -126,10 +128,9 @@ declare class OpenAICompatibleProvider implements AIProvider {
126
128
  generate(prompt: string, systemInstruction?: string): Promise<string>;
127
129
  }
128
130
  declare class MiMoProvider implements AIProvider {
131
+ private client;
129
132
  readonly providerName = "mimo";
130
133
  readonly modelName: string;
131
- private apiKey;
132
- private readonly baseUrl;
133
134
  constructor(apiKey: string, modelName?: string);
134
135
  generate(prompt: string, systemInstruction?: string): Promise<string>;
135
136
  }
package/dist/index.js CHANGED
@@ -68,7 +68,6 @@ module.exports = __toCommonJS(index_exports);
68
68
  var import_generative_ai = require("@google/generative-ai");
69
69
  var import_sdk = __toESM(require("@anthropic-ai/sdk"));
70
70
  var import_openai = __toESM(require("openai"));
71
- var import_axios = __toESM(require("axios"));
72
71
  var import_undici = require("undici");
73
72
 
74
73
  // prompts/spec.prompt.ts
@@ -361,7 +360,9 @@ var PROVIDER_CATALOG = {
361
360
  displayName: "MiMo (Xiaomi)",
362
361
  description: "\u5C0F\u7C73 MiMo \u2014 mimo-v2-pro (Anthropic-compatible API)",
363
362
  models: ["mimo-v2-pro"],
364
- envKey: "MIMO_API_KEY"
363
+ envKey: "MIMO_API_KEY",
364
+ // Fallback env var — MiMo's token plan uses ANTHROPIC_AUTH_TOKEN
365
+ fallbackEnvKeys: ["ANTHROPIC_AUTH_TOKEN"]
365
366
  // baseURL not used — MiMo has a dedicated provider class
366
367
  },
367
368
  gemini: {
@@ -530,8 +531,8 @@ var ClaudeProvider = class {
530
531
  ...systemInstruction ? { system: systemInstruction } : {},
531
532
  messages: [{ role: "user", content: prompt }]
532
533
  });
533
- const block = message.content[0];
534
- if (block.type === "text") return block.text;
534
+ const textBlock = message.content.find((b) => b.type === "text");
535
+ if (textBlock) return textBlock.text;
535
536
  throw new Error("Unexpected response type from Claude API");
536
537
  },
537
538
  { label: `${this.providerName}/${this.modelName}` }
@@ -576,43 +577,29 @@ var OpenAICompatibleProvider = class {
576
577
  }
577
578
  };
578
579
  var MiMoProvider = class {
580
+ client;
579
581
  providerName = "mimo";
580
582
  modelName;
581
- apiKey;
582
- baseUrl = "https://api.xiaomimimo.com/anthropic/v1/messages";
583
583
  constructor(apiKey, modelName = PROVIDER_CATALOG.mimo.models[0]) {
584
- this.apiKey = apiKey;
584
+ const baseURL = process.env["MIMO_BASE_URL"] || process.env["ANTHROPIC_BASE_URL"] || "https://token-plan-cn.xiaomimimo.com/anthropic";
585
+ this.client = new import_sdk.default({ apiKey, baseURL });
585
586
  this.modelName = modelName;
586
587
  }
587
588
  async generate(prompt, systemInstruction) {
588
589
  return withReliability(
589
590
  async () => {
590
- const body = {
591
+ const stream = this.client.messages.stream({
591
592
  model: this.modelName,
592
- max_tokens: 16384,
593
- messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
594
- top_p: 0.95,
595
- stream: false,
596
- temperature: 1,
597
- stop_sequences: null
598
- };
599
- if (systemInstruction) {
600
- body.system = systemInstruction;
601
- }
602
- const response = await import_axios.default.post(this.baseUrl, body, {
603
- headers: {
604
- "api-key": this.apiKey,
605
- "Content-Type": "application/json"
606
- }
593
+ max_tokens: 65536,
594
+ ...systemInstruction ? { system: systemInstruction } : {},
595
+ messages: [{ role: "user", content: prompt }]
607
596
  });
608
- const data = response.data;
609
- const blocks = data?.content ?? [];
610
- const textBlock = blocks.find((b) => b.type === "text");
611
- if (textBlock?.text) return textBlock.text;
612
- if (data?.stop_reason === "max_tokens") {
613
- 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.`);
614
- }
615
- throw new Error(`Unexpected MiMo response: ${JSON.stringify(response.data).slice(0, 200)}`);
597
+ const message = await stream.finalMessage();
598
+ const textBlock = message.content.find((b) => b.type === "text");
599
+ if (textBlock) return textBlock.text;
600
+ const thinkBlock = message.content.find((b) => b.type === "thinking");
601
+ if (thinkBlock) return thinkBlock.thinking;
602
+ return message.content.map((b) => b.text ?? "").join("");
616
603
  },
617
604
  { label: `${this.providerName}/${this.modelName}` }
618
605
  );
@@ -5217,13 +5204,11 @@ function typeLabel(v2) {
5217
5204
 
5218
5205
  // core/token-budget.ts
5219
5206
  var import_chalk6 = __toESM(require("chalk"));
5220
- var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
5221
- function estimateTokens(text) {
5222
- if (!text) return 0;
5223
- const cjkCount = (text.match(CJK_RANGE) ?? []).length;
5224
- const nonCjkLength = text.length - cjkCount;
5225
- return Math.ceil(cjkCount + nonCjkLength / 4);
5226
- }
5207
+
5208
+ // core/config-defaults.ts
5209
+ var DEFAULT_REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
5210
+ var DEFAULT_MAX_CONSTITUTION_CHARS = 4e3;
5211
+ var DEFAULT_MAX_REVIEW_FILE_CHARS = 3e3;
5227
5212
  var DEFAULT_TOKEN_BUDGETS = {
5228
5213
  gemini: 9e5,
5229
5214
  claude: 18e4,
@@ -5231,8 +5216,18 @@ var DEFAULT_TOKEN_BUDGETS = {
5231
5216
  deepseek: 6e4,
5232
5217
  default: 1e5
5233
5218
  };
5219
+
5220
+ // core/token-budget.ts
5221
+ var CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
5222
+ function estimateTokens(text) {
5223
+ if (!text) return 0;
5224
+ const cjkCount = (text.match(CJK_RANGE) ?? []).length;
5225
+ const nonCjkLength = text.length - cjkCount;
5226
+ return Math.ceil(cjkCount + nonCjkLength / 4);
5227
+ }
5228
+ var DEFAULT_TOKEN_BUDGETS2 = DEFAULT_TOKEN_BUDGETS;
5234
5229
  function getDefaultBudget(providerName) {
5235
- return DEFAULT_TOKEN_BUDGETS[providerName] ?? DEFAULT_TOKEN_BUDGETS.default;
5230
+ return DEFAULT_TOKEN_BUDGETS2[providerName] ?? DEFAULT_TOKEN_BUDGETS2.default;
5236
5231
  }
5237
5232
 
5238
5233
  // core/dsl-extractor.ts
@@ -6693,7 +6688,7 @@ ${context.routeSummary}
6693
6688
  }
6694
6689
  if (context.schema) {
6695
6690
  parts.push(`=== Prisma Schema ===
6696
- ${context.schema.slice(0, 4e3)}
6691
+ ${context.schema.slice(0, DEFAULT_MAX_CONSTITUTION_CHARS)}
6697
6692
  `);
6698
6693
  }
6699
6694
  if (context.errorPatterns) {
@@ -6757,7 +6752,7 @@ async function loadAccumulatedLessons(projectRoot) {
6757
6752
  const nextSection = section.slice(marker.length).match(/\n## \d/);
6758
6753
  return nextSection ? section.slice(0, marker.length + nextSection.index) : section;
6759
6754
  }
6760
- var REVIEW_HISTORY_FILE = ".ai-spec-reviews.json";
6755
+ var REVIEW_HISTORY_FILE = DEFAULT_REVIEW_HISTORY_FILE;
6761
6756
  async function loadReviewHistory(projectRoot) {
6762
6757
  const historyPath = path8.join(projectRoot, REVIEW_HISTORY_FILE);
6763
6758
  try {
@@ -6888,15 +6883,13 @@ ${specContent || "(No spec \u2014 review for general code quality)"}
6888
6883
  ${codeContext}`;
6889
6884
  const archReview = await this.provider.generate(archPrompt, reviewArchitectureSystemPrompt);
6890
6885
  console.log(import_chalk12.default.gray(" Pass 2/3: Implementation review..."));
6886
+ const specDigest = specContent && specContent.length > 600 ? specContent.slice(0, 600) + "\n... [spec truncated \u2014 see Pass 0/1 for full text]" : specContent || "(No spec)";
6891
6887
  const history = await loadReviewHistory(this.projectRoot);
6892
6888
  const historyContext = buildHistoryContext(history);
6893
6889
  const implPrompt = `Review the implementation details of this change.
6894
6890
 
6895
- === Feature Spec ===
6896
- ${specContent || "(No spec \u2014 review for general code quality)"}
6897
-
6898
- === Code ===
6899
- ${codeContext}
6891
+ === Feature Spec (digest \u2014 full spec was provided in Pass 0/1) ===
6892
+ ${specDigest}
6900
6893
 
6901
6894
  === Architecture Review (Pass 1 \u2014 do NOT repeat these findings) ===
6902
6895
  ${archReview}
@@ -6905,11 +6898,8 @@ ${historyContext}`;
6905
6898
  console.log(import_chalk12.default.gray(" Pass 3/3: Impact & complexity assessment..."));
6906
6899
  const impactPrompt = `Assess the impact and complexity of this change.
6907
6900
 
6908
- === Feature Spec ===
6909
- ${specContent || "(No spec \u2014 review for general code quality)"}
6910
-
6911
- === Code ===
6912
- ${codeContext}
6901
+ === Feature Spec (digest) ===
6902
+ ${specDigest}
6913
6903
 
6914
6904
  === Architecture Review (Pass 1 \u2014 do NOT repeat) ===
6915
6905
  ${archReview}
@@ -6983,8 +6973,8 @@ ${sep}
6983
6973
  filesSection += `
6984
6974
 
6985
6975
  === ${filePath} ===
6986
- ${content.slice(0, 3e3)}`;
6987
- if (content.length > 3e3) filesSection += `
6976
+ ${content.slice(0, DEFAULT_MAX_REVIEW_FILE_CHARS)}`;
6977
+ if (content.length > DEFAULT_MAX_REVIEW_FILE_CHARS) filesSection += `
6988
6978
  ... (truncated, ${content.length} chars total)`;
6989
6979
  } catch {
6990
6980
  filesSection += `