ai-spec-dev 0.35.0 → 0.36.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/RELEASE_LOG.md +119 -0
- package/cli/commands/config.ts +18 -0
- package/cli/commands/create.ts +16 -1
- package/cli/utils.ts +4 -0
- package/core/code-generator.ts +6 -4
- package/core/dsl-extractor.ts +9 -1
- package/core/dsl-feedback.ts +7 -1
- package/core/dsl-validator.ts +32 -0
- package/core/key-store.ts +5 -4
- package/core/provider-utils.ts +39 -4
- package/dist/cli/index.js +121 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +122 -15
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +77 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +77 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/tests/code-generator.test.ts +253 -0
- package/tests/context-loader.test.ts +207 -0
- package/tests/dsl-validator.test.ts +105 -0
- package/tests/openapi-exporter.test.ts +310 -0
- package/tests/reviewer.test.ts +214 -0
- package/tests/spec-generator.test.ts +228 -0
- package/tests/spec-versioning.test.ts +205 -0
package/dist/index.d.mts
CHANGED
|
@@ -183,6 +183,21 @@ declare function loadTasksForSpec(specFilePath: string): Promise<SpecTask[] | nu
|
|
|
183
183
|
/** Persist a single task's status to the tasks JSON file (checkpoint). */
|
|
184
184
|
declare function updateTaskStatus(specFilePath: string, taskId: string, status: TaskStatus): Promise<void>;
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Extract a behavioral contract summary from a generated file.
|
|
188
|
+
*
|
|
189
|
+
* Captures:
|
|
190
|
+
* - export interface / type / enum — full multi-line blocks (the actual TS contracts)
|
|
191
|
+
* - export function / const / class — opening signature line
|
|
192
|
+
* - Throw statements — error codes & validation constraints
|
|
193
|
+
*
|
|
194
|
+
* Multi-line blocks (interface, type alias with {}) are captured in full so
|
|
195
|
+
* downstream tasks see complete method signatures and field shapes, not just
|
|
196
|
+
* a single-line "export interface Foo {" that conveys nothing.
|
|
197
|
+
*
|
|
198
|
+
* Falls back to first 3000 chars for CommonJS files with no explicit exports.
|
|
199
|
+
*/
|
|
200
|
+
declare function extractBehavioralContract(content: string): string;
|
|
186
201
|
type CodeGenMode = "claude-code" | "api" | "plan";
|
|
187
202
|
interface CodeGenOptions {
|
|
188
203
|
/** Run claude non-interactively via -p flag (saves tokens, good for automation) */
|
|
@@ -281,4 +296,4 @@ declare class GitWorktreeManager {
|
|
|
281
296
|
createWorktree(idea: string): Promise<string | null>;
|
|
282
297
|
}
|
|
283
298
|
|
|
284
|
-
export { type AIProvider, CONSTITUTION_FILE, ClaudeProvider, type CodeGenMode, type CodeGenOptions, CodeGenerator, CodeReviewer, ConstitutionGenerator, ContextLoader, DEFAULT_MODELS, ENV_KEY_MAP, FRONTEND_FRAMEWORKS, GeminiProvider, GitWorktreeManager, MiMoProvider, OpenAICompatibleProvider, PROVIDER_CATALOG, type ProjectContext, type ProviderMeta, SUPPORTED_PROVIDERS, type SharedConfigFile, SpecGenerator, SpecRefiner, type SpecTask, TaskGenerator, type TaskLayer, type TaskPriority, type TaskStatus, buildTaskPrompt, createProvider, extractComplianceScore, extractMissingCount, generateSpecWithTasks, isFrontendDeps, loadConstitution, loadTasksForSpec, printConstitutionHint, printTaskProgress, printTasks, updateTaskStatus };
|
|
299
|
+
export { type AIProvider, CONSTITUTION_FILE, ClaudeProvider, type CodeGenMode, type CodeGenOptions, CodeGenerator, CodeReviewer, ConstitutionGenerator, ContextLoader, DEFAULT_MODELS, ENV_KEY_MAP, FRONTEND_FRAMEWORKS, GeminiProvider, GitWorktreeManager, MiMoProvider, OpenAICompatibleProvider, PROVIDER_CATALOG, type ProjectContext, type ProviderMeta, SUPPORTED_PROVIDERS, type SharedConfigFile, SpecGenerator, SpecRefiner, type SpecTask, TaskGenerator, type TaskLayer, type TaskPriority, type TaskStatus, buildTaskPrompt, createProvider, extractBehavioralContract, extractComplianceScore, extractMissingCount, generateSpecWithTasks, isFrontendDeps, loadConstitution, loadTasksForSpec, printConstitutionHint, printTaskProgress, printTasks, updateTaskStatus };
|
package/dist/index.d.ts
CHANGED
|
@@ -183,6 +183,21 @@ declare function loadTasksForSpec(specFilePath: string): Promise<SpecTask[] | nu
|
|
|
183
183
|
/** Persist a single task's status to the tasks JSON file (checkpoint). */
|
|
184
184
|
declare function updateTaskStatus(specFilePath: string, taskId: string, status: TaskStatus): Promise<void>;
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Extract a behavioral contract summary from a generated file.
|
|
188
|
+
*
|
|
189
|
+
* Captures:
|
|
190
|
+
* - export interface / type / enum — full multi-line blocks (the actual TS contracts)
|
|
191
|
+
* - export function / const / class — opening signature line
|
|
192
|
+
* - Throw statements — error codes & validation constraints
|
|
193
|
+
*
|
|
194
|
+
* Multi-line blocks (interface, type alias with {}) are captured in full so
|
|
195
|
+
* downstream tasks see complete method signatures and field shapes, not just
|
|
196
|
+
* a single-line "export interface Foo {" that conveys nothing.
|
|
197
|
+
*
|
|
198
|
+
* Falls back to first 3000 chars for CommonJS files with no explicit exports.
|
|
199
|
+
*/
|
|
200
|
+
declare function extractBehavioralContract(content: string): string;
|
|
186
201
|
type CodeGenMode = "claude-code" | "api" | "plan";
|
|
187
202
|
interface CodeGenOptions {
|
|
188
203
|
/** Run claude non-interactively via -p flag (saves tokens, good for automation) */
|
|
@@ -281,4 +296,4 @@ declare class GitWorktreeManager {
|
|
|
281
296
|
createWorktree(idea: string): Promise<string | null>;
|
|
282
297
|
}
|
|
283
298
|
|
|
284
|
-
export { type AIProvider, CONSTITUTION_FILE, ClaudeProvider, type CodeGenMode, type CodeGenOptions, CodeGenerator, CodeReviewer, ConstitutionGenerator, ContextLoader, DEFAULT_MODELS, ENV_KEY_MAP, FRONTEND_FRAMEWORKS, GeminiProvider, GitWorktreeManager, MiMoProvider, OpenAICompatibleProvider, PROVIDER_CATALOG, type ProjectContext, type ProviderMeta, SUPPORTED_PROVIDERS, type SharedConfigFile, SpecGenerator, SpecRefiner, type SpecTask, TaskGenerator, type TaskLayer, type TaskPriority, type TaskStatus, buildTaskPrompt, createProvider, extractComplianceScore, extractMissingCount, generateSpecWithTasks, isFrontendDeps, loadConstitution, loadTasksForSpec, printConstitutionHint, printTaskProgress, printTasks, updateTaskStatus };
|
|
299
|
+
export { type AIProvider, CONSTITUTION_FILE, ClaudeProvider, type CodeGenMode, type CodeGenOptions, CodeGenerator, CodeReviewer, ConstitutionGenerator, ContextLoader, DEFAULT_MODELS, ENV_KEY_MAP, FRONTEND_FRAMEWORKS, GeminiProvider, GitWorktreeManager, MiMoProvider, OpenAICompatibleProvider, PROVIDER_CATALOG, type ProjectContext, type ProviderMeta, SUPPORTED_PROVIDERS, type SharedConfigFile, SpecGenerator, SpecRefiner, type SpecTask, TaskGenerator, type TaskLayer, type TaskPriority, type TaskStatus, buildTaskPrompt, createProvider, extractBehavioralContract, extractComplianceScore, extractMissingCount, generateSpecWithTasks, isFrontendDeps, loadConstitution, loadTasksForSpec, printConstitutionHint, printTaskProgress, printTasks, updateTaskStatus };
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,7 @@ __export(index_exports, {
|
|
|
50
50
|
TaskGenerator: () => TaskGenerator,
|
|
51
51
|
buildTaskPrompt: () => buildTaskPrompt,
|
|
52
52
|
createProvider: () => createProvider,
|
|
53
|
+
extractBehavioralContract: () => extractBehavioralContract,
|
|
53
54
|
extractComplianceScore: () => extractComplianceScore,
|
|
54
55
|
extractMissingCount: () => extractMissingCount,
|
|
55
56
|
generateSpecWithTasks: () => generateSpecWithTasks,
|
|
@@ -196,14 +197,49 @@ function classifyError(err, label) {
|
|
|
196
197
|
const e = err;
|
|
197
198
|
const status = e.status ?? e.response?.status;
|
|
198
199
|
if (status === 401 || status === 403)
|
|
199
|
-
return new ProviderError(
|
|
200
|
+
return new ProviderError(
|
|
201
|
+
`Auth error (${label}): API key is invalid or expired.
|
|
202
|
+
\u2192 Check that the correct API key is set in your environment or ~/.ai-spec-keys.json
|
|
203
|
+
\u2192 Run "ai-spec model" to reconfigure your provider and key`,
|
|
204
|
+
"auth",
|
|
205
|
+
err
|
|
206
|
+
);
|
|
200
207
|
if (status === 429)
|
|
201
|
-
return new ProviderError(
|
|
208
|
+
return new ProviderError(
|
|
209
|
+
`Rate limit hit (${label}): too many requests.
|
|
210
|
+
\u2192 Wait a few minutes and retry, or switch to a different provider/model
|
|
211
|
+
\u2192 Check your provider's billing dashboard for quota status`,
|
|
212
|
+
"rate_limit",
|
|
213
|
+
err
|
|
214
|
+
);
|
|
202
215
|
if (e._timeout || e.message?.toLowerCase().includes("timed out"))
|
|
203
216
|
return new ProviderError(`Request timed out (${label})`, "timeout", err);
|
|
204
217
|
if (e.code === "ECONNRESET" || e.code === "ENOTFOUND" || e.code === "ECONNREFUSED")
|
|
205
|
-
return new ProviderError(
|
|
206
|
-
|
|
218
|
+
return new ProviderError(
|
|
219
|
+
`Network error (${label}): ${e.message}
|
|
220
|
+
\u2192 Check your internet connection and proxy settings (HTTPS_PROXY)
|
|
221
|
+
\u2192 If behind a firewall, ensure the provider's API endpoint is reachable`,
|
|
222
|
+
"network",
|
|
223
|
+
err
|
|
224
|
+
);
|
|
225
|
+
const msg = e.message ?? "";
|
|
226
|
+
if (status === 404 || msg.includes("model") && (msg.includes("not found") || msg.includes("does not exist")))
|
|
227
|
+
return new ProviderError(
|
|
228
|
+
`Model not found (${label}): ${msg}
|
|
229
|
+
\u2192 Run "ai-spec model" to see available models for your provider
|
|
230
|
+
\u2192 The model name may have changed \u2014 check your provider's documentation`,
|
|
231
|
+
"provider",
|
|
232
|
+
err
|
|
233
|
+
);
|
|
234
|
+
if (msg.includes("insufficient") || msg.includes("quota") || msg.includes("balance"))
|
|
235
|
+
return new ProviderError(
|
|
236
|
+
`Quota/balance error (${label}): ${msg}
|
|
237
|
+
\u2192 Check your provider's billing dashboard
|
|
238
|
+
\u2192 Consider switching to a different provider with "ai-spec model"`,
|
|
239
|
+
"provider",
|
|
240
|
+
err
|
|
241
|
+
);
|
|
242
|
+
return new ProviderError(`Provider error (${label}): ${msg}`, "provider", err);
|
|
207
243
|
}
|
|
208
244
|
function isRetryable(err) {
|
|
209
245
|
const e = err;
|
|
@@ -4765,6 +4801,21 @@ function validateDsl(raw) {
|
|
|
4765
4801
|
for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
|
|
4766
4802
|
validateEndpoint(eps[i], `endpoints[${i}]`, errors);
|
|
4767
4803
|
}
|
|
4804
|
+
const seenEpIds = /* @__PURE__ */ new Set();
|
|
4805
|
+
for (let i = 0; i < Math.min(eps.length, MAX_ENDPOINTS); i++) {
|
|
4806
|
+
const ep = eps[i];
|
|
4807
|
+
if (ep && typeof ep === "object" && typeof ep["id"] === "string") {
|
|
4808
|
+
const id = ep["id"];
|
|
4809
|
+
if (seenEpIds.has(id)) {
|
|
4810
|
+
errors.push({
|
|
4811
|
+
path: `endpoints[${i}].id`,
|
|
4812
|
+
message: `Duplicate endpoint id "${id}" \u2014 each endpoint must have a unique id`
|
|
4813
|
+
});
|
|
4814
|
+
} else {
|
|
4815
|
+
seenEpIds.add(id);
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4768
4819
|
}
|
|
4769
4820
|
if (obj["behaviors"] !== void 0) {
|
|
4770
4821
|
if (!Array.isArray(obj["behaviors"])) {
|
|
@@ -4821,6 +4872,21 @@ function validateModel(raw, path10, errors) {
|
|
|
4821
4872
|
for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
|
|
4822
4873
|
validateModelField(fields[j2], `${path10}.fields[${j2}]`, errors);
|
|
4823
4874
|
}
|
|
4875
|
+
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
4876
|
+
for (let j2 = 0; j2 < Math.min(fields.length, MAX_FIELDS_PER_MODEL); j2++) {
|
|
4877
|
+
const f = fields[j2];
|
|
4878
|
+
if (f && typeof f === "object" && typeof f["name"] === "string") {
|
|
4879
|
+
const name = f["name"];
|
|
4880
|
+
if (seenFieldNames.has(name)) {
|
|
4881
|
+
errors.push({
|
|
4882
|
+
path: `${path10}.fields[${j2}].name`,
|
|
4883
|
+
message: `Duplicate field name "${name}" \u2014 each field within a model must have a unique name`
|
|
4884
|
+
});
|
|
4885
|
+
} else {
|
|
4886
|
+
seenFieldNames.add(name);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4824
4890
|
}
|
|
4825
4891
|
if (m["relations"] !== void 0) {
|
|
4826
4892
|
if (!Array.isArray(m["relations"])) {
|
|
@@ -5851,9 +5917,10 @@ ${tasks.map((t) => `${t.id} [${t.layer}] ${t.title}
|
|
|
5851
5917
|
console.log(import_chalk8.default.cyan(` \u{1F916} Auto mode: running claude -p (non-interactive)...`));
|
|
5852
5918
|
console.log(import_chalk8.default.gray(` Spec: ${specFilePath}`));
|
|
5853
5919
|
try {
|
|
5854
|
-
(0, import_child_process.
|
|
5920
|
+
(0, import_child_process.spawnSync)(claudeCmd, ["-p", promptContent], {
|
|
5855
5921
|
cwd: workingDir,
|
|
5856
|
-
stdio: "inherit"
|
|
5922
|
+
stdio: "inherit",
|
|
5923
|
+
shell: false
|
|
5857
5924
|
});
|
|
5858
5925
|
console.log(import_chalk8.default.green("\n \u2714 Claude Code completed."));
|
|
5859
5926
|
} catch {
|
|
@@ -5905,9 +5972,10 @@ Full spec is at: ${specFilePath}
|
|
|
5905
5972
|
Implement ONLY this task. Do not implement other tasks.`;
|
|
5906
5973
|
let taskStatus = "done";
|
|
5907
5974
|
try {
|
|
5908
|
-
(0, import_child_process.
|
|
5975
|
+
(0, import_child_process.spawnSync)(claudeCmd, ["-p", taskPrompt], {
|
|
5909
5976
|
cwd: workingDir,
|
|
5910
|
-
stdio: "inherit"
|
|
5977
|
+
stdio: "inherit",
|
|
5978
|
+
shell: false
|
|
5911
5979
|
});
|
|
5912
5980
|
completed++;
|
|
5913
5981
|
} catch {
|
|
@@ -6909,6 +6977,7 @@ var GitWorktreeManager = class {
|
|
|
6909
6977
|
TaskGenerator,
|
|
6910
6978
|
buildTaskPrompt,
|
|
6911
6979
|
createProvider,
|
|
6980
|
+
extractBehavioralContract,
|
|
6912
6981
|
extractComplianceScore,
|
|
6913
6982
|
extractMissingCount,
|
|
6914
6983
|
generateSpecWithTasks,
|