ai-spec-dev 0.35.0 → 0.37.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 (38) hide show
  1. package/RELEASE_LOG.md +139 -0
  2. package/cli/commands/config.ts +18 -0
  3. package/cli/commands/create.ts +16 -1
  4. package/cli/utils.ts +4 -0
  5. package/core/code-generator.ts +6 -4
  6. package/core/dsl-extractor.ts +9 -1
  7. package/core/dsl-feedback.ts +7 -1
  8. package/core/dsl-validator.ts +32 -0
  9. package/core/key-store.ts +5 -4
  10. package/core/provider-utils.ts +39 -4
  11. package/dist/cli/index.js +121 -14
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/cli/index.mjs +122 -15
  14. package/dist/cli/index.mjs.map +1 -1
  15. package/dist/index.d.mts +16 -1
  16. package/dist/index.d.ts +16 -1
  17. package/dist/index.js +77 -8
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +77 -9
  20. package/dist/index.mjs.map +1 -1
  21. package/package.json +1 -1
  22. package/tests/code-generator.test.ts +253 -0
  23. package/tests/context-loader.test.ts +207 -0
  24. package/tests/dsl-validator.test.ts +105 -0
  25. package/tests/mock-server-generator.test.ts +404 -0
  26. package/tests/openapi-exporter.test.ts +310 -0
  27. package/tests/reviewer.test.ts +214 -0
  28. package/tests/spec-generator.test.ts +228 -0
  29. package/tests/spec-versioning.test.ts +205 -0
  30. package/tests/types-generator.test.ts +347 -0
  31. package/tests/vcr.test.ts +355 -0
  32. package/.claude/commands/add-lesson.md +0 -34
  33. package/.claude/commands/check-layers.md +0 -65
  34. package/.claude/commands/installed-deps.md +0 -35
  35. package/.claude/commands/recall-lessons.md +0 -40
  36. package/.claude/commands/scan-singletons.md +0 -45
  37. package/.claude/commands/verify-imports.md +0 -48
  38. package/.claude/settings.local.json +0 -24
package/dist/index.mjs CHANGED
@@ -131,14 +131,49 @@ function classifyError(err, label) {
131
131
  const e = err;
132
132
  const status = e.status ?? e.response?.status;
133
133
  if (status === 401 || status === 403)
134
- return new ProviderError(`Auth error \u2014 check your API key (${label})`, "auth", err);
134
+ return new ProviderError(
135
+ `Auth error (${label}): API key is invalid or expired.
136
+ \u2192 Check that the correct API key is set in your environment or ~/.ai-spec-keys.json
137
+ \u2192 Run "ai-spec model" to reconfigure your provider and key`,
138
+ "auth",
139
+ err
140
+ );
135
141
  if (status === 429)
136
- return new ProviderError(`Rate limit hit (${label}) \u2014 try again later or switch provider`, "rate_limit", err);
142
+ return new ProviderError(
143
+ `Rate limit hit (${label}): too many requests.
144
+ \u2192 Wait a few minutes and retry, or switch to a different provider/model
145
+ \u2192 Check your provider's billing dashboard for quota status`,
146
+ "rate_limit",
147
+ err
148
+ );
137
149
  if (e._timeout || e.message?.toLowerCase().includes("timed out"))
138
150
  return new ProviderError(`Request timed out (${label})`, "timeout", err);
139
151
  if (e.code === "ECONNRESET" || e.code === "ENOTFOUND" || e.code === "ECONNREFUSED")
140
- return new ProviderError(`Network error \u2014 check connection/proxy (${label}): ${e.message}`, "network", err);
141
- return new ProviderError(`Provider error (${label}): ${e.message}`, "provider", err);
152
+ return new ProviderError(
153
+ `Network error (${label}): ${e.message}
154
+ \u2192 Check your internet connection and proxy settings (HTTPS_PROXY)
155
+ \u2192 If behind a firewall, ensure the provider's API endpoint is reachable`,
156
+ "network",
157
+ err
158
+ );
159
+ const msg = e.message ?? "";
160
+ if (status === 404 || msg.includes("model") && (msg.includes("not found") || msg.includes("does not exist")))
161
+ return new ProviderError(
162
+ `Model not found (${label}): ${msg}
163
+ \u2192 Run "ai-spec model" to see available models for your provider
164
+ \u2192 The model name may have changed \u2014 check your provider's documentation`,
165
+ "provider",
166
+ err
167
+ );
168
+ if (msg.includes("insufficient") || msg.includes("quota") || msg.includes("balance"))
169
+ return new ProviderError(
170
+ `Quota/balance error (${label}): ${msg}
171
+ \u2192 Check your provider's billing dashboard
172
+ \u2192 Consider switching to a different provider with "ai-spec model"`,
173
+ "provider",
174
+ err
175
+ );
176
+ return new ProviderError(`Provider error (${label}): ${msg}`, "provider", err);
142
177
  }
143
178
  function isRetryable(err) {
144
179
  const e = err;
@@ -4073,7 +4108,7 @@ ${currentSpec}`,
4073
4108
 
4074
4109
  // core/code-generator.ts
4075
4110
  import chalk8 from "chalk";
4076
- import { execSync } from "child_process";
4111
+ import { execSync, spawnSync } from "child_process";
4077
4112
  import * as path6 from "path";
4078
4113
  import * as fs10 from "fs-extra";
4079
4114
 
@@ -4700,6 +4735,21 @@ function validateDsl(raw) {
4700
4735
  for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
4701
4736
  validateEndpoint(eps[i], `endpoints[${i}]`, errors);
4702
4737
  }
4738
+ const seenEpIds = /* @__PURE__ */ new Set();
4739
+ for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
4740
+ const ep = eps[i];
4741
+ if (ep && typeof ep === "object" && typeof ep["id"] === "string") {
4742
+ const id = ep["id"];
4743
+ if (seenEpIds.has(id)) {
4744
+ errors.push({
4745
+ path: `endpoints[${i}].id`,
4746
+ message: `Duplicate endpoint id "${id}" \u2014 each endpoint must have a unique id`
4747
+ });
4748
+ } else {
4749
+ seenEpIds.add(id);
4750
+ }
4751
+ }
4752
+ }
4703
4753
  }
4704
4754
  if (obj["behaviors"] !== void 0) {
4705
4755
  if (!Array.isArray(obj["behaviors"])) {
@@ -4756,6 +4806,21 @@ function validateModel(raw, path10, errors) {
4756
4806
  for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
4757
4807
  validateModelField(fields[j2], `${path10}.fields[${j2}]`, errors);
4758
4808
  }
4809
+ const seenFieldNames = /* @__PURE__ */ new Set();
4810
+ for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
4811
+ const f = fields[j2];
4812
+ if (f && typeof f === "object" && typeof f["name"] === "string") {
4813
+ const name = f["name"];
4814
+ if (seenFieldNames.has(name)) {
4815
+ errors.push({
4816
+ path: `${path10}.fields[${j2}].name`,
4817
+ message: `Duplicate field name "${name}" \u2014 each field within a model must have a unique name`
4818
+ });
4819
+ } else {
4820
+ seenFieldNames.add(name);
4821
+ }
4822
+ }
4823
+ }
4759
4824
  }
4760
4825
  if (m["relations"] !== void 0) {
4761
4826
  if (!Array.isArray(m["relations"])) {
@@ -5786,9 +5851,10 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
5786
5851
  console.log(chalk8.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
5787
5852
  console.log(chalk8.gray(` Spec: ${specFilePath}`));
5788
5853
  try {
5789
- execSync(`${claudeCmd} -p "${promptContent.replace(/"/g, '\\"')}"`, {
5854
+ spawnSync(claudeCmd, ["-p", promptContent], {
5790
5855
  cwd: workingDir,
5791
- stdio: "inherit"
5856
+ stdio: "inherit",
5857
+ shell: false
5792
5858
  });
5793
5859
  console.log(chalk8.green("\n \u2714 Claude Code completed."));
5794
5860
  } catch {
@@ -5840,9 +5906,10 @@ Full spec is at: ${specFilePath}
5840
5906
  Implement ONLY this task. Do not implement other tasks.`;
5841
5907
  let taskStatus = "done";
5842
5908
  try {
5843
- execSync(`${claudeCmd} -p "${taskPrompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`, {
5909
+ spawnSync(claudeCmd, ["-p", taskPrompt], {
5844
5910
  cwd: workingDir,
5845
- stdio: "inherit"
5911
+ stdio: "inherit",
5912
+ shell: false
5846
5913
  });
5847
5914
  completed++;
5848
5915
  } catch {
@@ -6843,6 +6910,7 @@ export {
6843
6910
  TaskGenerator,
6844
6911
  buildTaskPrompt,
6845
6912
  createProvider,
6913
+ extractBehavioralContract,
6846
6914
  extractComplianceScore,
6847
6915
  extractMissingCount,
6848
6916
  generateSpecWithTasks,