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.
Files changed (37) hide show
  1. package/.ai-spec-workspace.json +17 -0
  2. package/.ai-spec.json +7 -0
  3. package/cli/pipeline/single-repo.ts +19 -10
  4. package/core/cli-ui.ts +136 -0
  5. package/core/code-generator.ts +4 -2
  6. package/core/error-feedback.ts +4 -2
  7. package/core/provider-utils.ts +8 -7
  8. package/demo-backend/.ai-spec-constitution.md +65 -0
  9. package/demo-backend/package.json +21 -0
  10. package/demo-backend/prisma/schema.prisma +22 -0
  11. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +186 -0
  12. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +211 -0
  13. package/demo-backend/src/controllers/bookmark.controller.test.ts +255 -0
  14. package/demo-backend/src/controllers/bookmark.controller.ts +187 -0
  15. package/demo-backend/src/index.ts +17 -0
  16. package/demo-backend/src/routes/bookmark.routes.test.ts +264 -0
  17. package/demo-backend/src/routes/bookmark.routes.ts +11 -0
  18. package/demo-backend/src/routes/index.ts +8 -0
  19. package/demo-backend/src/services/bookmark.service.test.ts +433 -0
  20. package/demo-backend/src/services/bookmark.service.ts +261 -0
  21. package/demo-backend/tsconfig.json +12 -0
  22. package/demo-frontend/.ai-spec-constitution.md +95 -0
  23. package/demo-frontend/package.json +23 -0
  24. package/demo-frontend/src/App.tsx +12 -0
  25. package/demo-frontend/src/main.tsx +9 -0
  26. package/demo-frontend/tsconfig.json +13 -0
  27. package/dist/cli/index.js +130 -21
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cli/index.mjs +130 -21
  30. package/dist/cli/index.mjs.map +1 -1
  31. package/dist/index.js +80 -8
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +80 -8
  34. package/dist/index.mjs.map +1 -1
  35. package/package.json +1 -1
  36. package/RELEASE_LOG.md +0 -2962
  37. 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/provider-utils.ts
119
+ // core/cli-ui.ts
120
120
  import chalk from "chalk";
121
- var sleep = (ms2) => new Promise((r) => setTimeout(r, ms2));
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 sleep(waitMs);
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
- console.log(`${prefix}${existingContent ? chalk10.yellow("~") : chalk10.green("+")} ${chalk10.bold(item.file)} ${chalk10.green("\u2714")}`);
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
- console.log(`${prefix}${chalk10.red("\u2718")} ${chalk10.bold(item.file)} \u2014 ${chalk10.red(err.message)}`);
6483
+ fileSpinner.fail(`${chalk10.bold(item.file)} \u2014 ${err.message}`);
6412
6484
  }
6413
6485
  }
6414
6486
  if (!taskLabel) {