ai-spec-dev 0.41.0 → 0.42.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.
- package/.ai-spec-workspace.json +17 -0
- package/.ai-spec.json +7 -0
- package/cli/pipeline/single-repo.ts +19 -10
- package/core/cli-ui.ts +136 -0
- package/core/code-generator.ts +4 -2
- package/core/error-feedback.ts +4 -2
- package/core/provider-utils.ts +8 -7
- package/demo-backend/.ai-spec-constitution.md +65 -0
- package/demo-backend/package.json +21 -0
- package/demo-backend/prisma/schema.prisma +22 -0
- package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +186 -0
- package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +211 -0
- package/demo-backend/src/controllers/bookmark.controller.test.ts +255 -0
- package/demo-backend/src/controllers/bookmark.controller.ts +187 -0
- package/demo-backend/src/index.ts +17 -0
- package/demo-backend/src/routes/bookmark.routes.test.ts +264 -0
- package/demo-backend/src/routes/bookmark.routes.ts +11 -0
- package/demo-backend/src/routes/index.ts +8 -0
- package/demo-backend/src/services/bookmark.service.test.ts +433 -0
- package/demo-backend/src/services/bookmark.service.ts +261 -0
- package/demo-backend/tsconfig.json +12 -0
- package/demo-frontend/.ai-spec-constitution.md +95 -0
- package/demo-frontend/package.json +23 -0
- package/demo-frontend/src/App.tsx +12 -0
- package/demo-frontend/src/main.tsx +9 -0
- package/demo-frontend/tsconfig.json +13 -0
- package/dist/cli/index.js +130 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +130 -21
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.js +80 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +80 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/RELEASE_LOG.md +0 -2962
- package/purpose.md +0 -1434
package/dist/index.mjs
CHANGED
|
@@ -116,9 +116,77 @@ CRITICAL \u2014 \u5386\u53F2\u6559\u8BAD\u5E94\u7528\uFF08Accumulated Lessons\uF
|
|
|
116
116
|
3. \u5BF9\u4E8E\u6BCF\u6761\u76F4\u63A5\u76F8\u5173\u7684\u6559\u8BAD\uFF0C\u5728 \xA78 \u5B9E\u65BD\u8981\u70B9\u672B\u5C3E\u8FFD\u52A0\u4E00\u884C\uFF1A\u300C\u26A0 \u57FA\u4E8E\u5386\u53F2\u6559\u8BAD\uFF1A[\u7B80\u8FF0\u672C\u6B21 spec \u5982\u4F55\u89C4\u907F\u8BE5\u95EE\u9898]\u300D
|
|
117
117
|
4. \u5982\u65E0\u76F8\u5173\u6559\u8BAD\uFF0C\xA78 \u4E0D\u5FC5\u8FFD\u52A0\u4EFB\u4F55\u5185\u5BB9`;
|
|
118
118
|
|
|
119
|
-
// core/
|
|
119
|
+
// core/cli-ui.ts
|
|
120
120
|
import chalk from "chalk";
|
|
121
|
-
var
|
|
121
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
122
|
+
function startSpinner(text) {
|
|
123
|
+
const isTTY = process.stderr.isTTY;
|
|
124
|
+
let frame = 0;
|
|
125
|
+
let currentText = text;
|
|
126
|
+
let stopped = false;
|
|
127
|
+
function render() {
|
|
128
|
+
if (stopped) return;
|
|
129
|
+
const symbol = chalk.cyan(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
|
|
130
|
+
if (isTTY) {
|
|
131
|
+
process.stderr.write(`\r ${symbol} ${currentText}${" ".repeat(10)}`);
|
|
132
|
+
}
|
|
133
|
+
frame++;
|
|
134
|
+
}
|
|
135
|
+
if (!isTTY) {
|
|
136
|
+
process.stderr.write(` \u2026 ${currentText}
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
const timer = setInterval(render, 80);
|
|
140
|
+
render();
|
|
141
|
+
return {
|
|
142
|
+
update(newText) {
|
|
143
|
+
currentText = newText;
|
|
144
|
+
},
|
|
145
|
+
stop(finalText) {
|
|
146
|
+
if (stopped) return;
|
|
147
|
+
stopped = true;
|
|
148
|
+
clearInterval(timer);
|
|
149
|
+
if (isTTY) {
|
|
150
|
+
process.stderr.write(`\r${" ".repeat(currentText.length + 20)}\r`);
|
|
151
|
+
}
|
|
152
|
+
if (finalText) {
|
|
153
|
+
process.stderr.write(` ${finalText}
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
succeed(successText) {
|
|
158
|
+
this.stop(chalk.green(`\u2714 ${successText}`));
|
|
159
|
+
},
|
|
160
|
+
fail(failText) {
|
|
161
|
+
this.stop(chalk.red(`\u2718 ${failText}`));
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function retryCountdown(opts) {
|
|
166
|
+
const { attempt, maxAttempts, waitMs, errorMessage, label } = opts;
|
|
167
|
+
const isTTY = process.stderr.isTTY;
|
|
168
|
+
const shortErr = errorMessage.length > 120 ? errorMessage.slice(0, 117) + "..." : errorMessage;
|
|
169
|
+
process.stderr.write("\n");
|
|
170
|
+
process.stderr.write(chalk.yellow(` \u250C\u2500 Retry ${attempt}/${maxAttempts} `) + chalk.gray(`[${label}]`) + chalk.yellow(` ${"\u2500".repeat(Math.max(1, 40 - label.length))}
|
|
171
|
+
`));
|
|
172
|
+
process.stderr.write(chalk.yellow(` \u2502 `) + chalk.white(shortErr) + "\n");
|
|
173
|
+
process.stderr.write(chalk.yellow(` \u2502 `) + chalk.gray(`Waiting before retry...`) + "\n");
|
|
174
|
+
const totalSeconds = Math.ceil(waitMs / 1e3);
|
|
175
|
+
for (let s = totalSeconds; s > 0; s--) {
|
|
176
|
+
const bar = chalk.green("\u2588".repeat(totalSeconds - s)) + chalk.gray("\u2591".repeat(s));
|
|
177
|
+
const line = chalk.yellow(` \u2502 `) + `${bar} ${chalk.bold.white(`${s}s`)}`;
|
|
178
|
+
if (isTTY) {
|
|
179
|
+
process.stderr.write(`\r${line}${" ".repeat(10)}`);
|
|
180
|
+
}
|
|
181
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
182
|
+
}
|
|
183
|
+
if (isTTY) {
|
|
184
|
+
process.stderr.write(`\r${" ".repeat(70)}\r`);
|
|
185
|
+
}
|
|
186
|
+
process.stderr.write(chalk.yellow(` \u2514\u2500 `) + chalk.cyan(`Retrying now...`) + "\n\n");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// core/provider-utils.ts
|
|
122
190
|
var ProviderError = class extends Error {
|
|
123
191
|
constructor(message, kind, originalError) {
|
|
124
192
|
super(message);
|
|
@@ -202,11 +270,14 @@ async function withReliability(fn, opts) {
|
|
|
202
270
|
throw classifyError(err, label);
|
|
203
271
|
}
|
|
204
272
|
const waitMs = attempt === 0 ? 2e3 : 6e3;
|
|
205
|
-
console.warn(
|
|
206
|
-
chalk.yellow(` \u26A0 ${label} failed (attempt ${attempt + 1}/${retries + 1}), retrying in ${waitMs / 1e3}s`) + chalk.gray(` \u2014 ${err.message}`)
|
|
207
|
-
);
|
|
208
273
|
onRetry?.(attempt + 1, err);
|
|
209
|
-
await
|
|
274
|
+
await retryCountdown({
|
|
275
|
+
attempt: attempt + 1,
|
|
276
|
+
maxAttempts: retries + 1,
|
|
277
|
+
waitMs,
|
|
278
|
+
errorMessage: err.message ?? String(err),
|
|
279
|
+
label
|
|
280
|
+
});
|
|
210
281
|
}
|
|
211
282
|
}
|
|
212
283
|
throw new Error("unreachable");
|
|
@@ -6397,6 +6468,7 @@ ${spec}
|
|
|
6397
6468
|
${constitutionSection}
|
|
6398
6469
|
=== ${existingContent ? "Existing content (modify and return the complete file)" : "Create this file from scratch"} ===
|
|
6399
6470
|
${existingContent || "Output only the complete file content."}`;
|
|
6471
|
+
const fileSpinner = startSpinner(`${prefix}Generating ${chalk10.bold(item.file)}...`);
|
|
6400
6472
|
try {
|
|
6401
6473
|
const raw = await this.provider.generate(codePrompt, systemPrompt);
|
|
6402
6474
|
const fileContent = stripCodeFences(raw);
|
|
@@ -6404,11 +6476,11 @@ ${existingContent || "Output only the complete file content."}`;
|
|
|
6404
6476
|
await fs10.ensureDir(path6.dirname(fullPath));
|
|
6405
6477
|
await fs10.writeFile(fullPath, fileContent, "utf-8");
|
|
6406
6478
|
getActiveLogger()?.fileWritten(item.file);
|
|
6407
|
-
|
|
6479
|
+
fileSpinner.succeed(`${existingContent ? chalk10.yellow("~") : chalk10.green("+")} ${chalk10.bold(item.file)}`);
|
|
6408
6480
|
successCount++;
|
|
6409
6481
|
writtenFiles.push(item.file);
|
|
6410
6482
|
} catch (err) {
|
|
6411
|
-
|
|
6483
|
+
fileSpinner.fail(`${chalk10.bold(item.file)} \u2014 ${err.message}`);
|
|
6412
6484
|
}
|
|
6413
6485
|
}
|
|
6414
6486
|
if (!taskLabel) {
|