ai-spec-dev 0.29.1 → 0.30.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-spec-dev",
3
- "version": "0.29.1",
3
+ "version": "0.30.1",
4
4
  "description": "AI-driven Development Orchestrator SDK & CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/purpose.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 痛点 · 架构创新 · 边界处理 · DSL 的意义 · 当前局限 · 未来方向
4
4
  >
5
- > 当前版本:v0.29.0 · 最后更新:2026-03-27
5
+ > 当前版本:v0.30.0 · 最后更新:2026-03-29
6
6
 
7
7
  ***
8
8
 
@@ -19,7 +19,7 @@
19
19
  7. [当前局限](#7-当前局限)
20
20
  8. [未来优化方向](#8-未来优化方向)
21
21
 
22
- > **版本记录速览**:v0.17.0 宪法截断修复 · v0.18.0 `learn` + `minSpecScore` + 行为契约提取 · v0.19.0 错误解析重写 + Auto Gate 修复 · v0.20.0 `mock --serve` 一键联调 · v0.21.0 store 公开 API 提取修复 · v0.22.0 service/api 层分离 · v0.23.0 view/route 双层 + 文件名幻觉修复 · v0.24.0 四项质量修复(export default、impliesRegistration、依赖拓扑排序、lesson 计数)· v0.25.0 HTTP import 正则、分页提取、isToolCrash 三项修复 · v0.26.0 多仓库 review 目录、batch 容错、tasks JSON 健壮性 · **v0.27.0 可靠性三件套**(Provider retry/timeout/分类、文件快照 + `restore`、RunId 结构化日志)· **v0.28.0 3-pass review**(Pass 3 影响面评估 + 代码复杂度评估)· **v0.29.0 全量审查修复**(RunLogger 完整插桩、update 快照/日志/knowledge、Score Trend 显示影响/复杂度等级、死代码清理)
22
+ > **版本记录速览**:v0.17.0 宪法截断修复 · v0.18.0 `learn` + `minSpecScore` + 行为契约提取 · v0.19.0 错误解析重写 + Auto Gate 修复 · v0.20.0 `mock --serve` 一键联调 · v0.21.0 store 公开 API 提取修复 · v0.22.0 service/api 层分离 · v0.23.0 view/route 双层 + 文件名幻觉修复 · v0.24.0 四项质量修复(export default、impliesRegistration、依赖拓扑排序、lesson 计数)· v0.25.0 HTTP import 正则、分页提取、isToolCrash 三项修复 · v0.26.0 多仓库 review 目录、batch 容错、tasks JSON 健壮性 · **v0.27.0 可靠性三件套**(Provider retry/timeout/分类、文件快照 + `restore`、RunId 结构化日志)· **v0.28.0 3-pass review**(Pass 3 影响面评估 + 代码复杂度评估)· **v0.29.0 全量审查修复**(RunLogger 完整插桩、update 快照/日志/knowledge、Score Trend 显示影响/复杂度等级、死代码清理)· **v0.30.0 错误修复依赖图排序 + 前端 Import 多行感知解析**
23
23
 
24
24
  ***
25
25
 
@@ -191,6 +191,7 @@ data → infra → service → api → view → route → test
191
191
 
192
192
  - 修复 prompt 中携带了 DSL 上下文,AI 修复时知道这个文件应该满足什么接口契约,避免了「把错误藏起来」式的修复
193
193
  - v0.19.0 之前的实现取输出的「最后 80 行」,TypeScript / Jest 错误结构是「具体 file:line 在前,摘要在后」,导致取到的全是摘要,AI 修复的是摘要描述而不是实际错误位置。v0.19.0 重写为全文扫描 + file:line 过滤,彻底解决了这个问题。
194
+ - **v0.30.0 — 依赖图排序修复**:旧版修复顺序由错误首次出现的顺序决定,与文件 import 关系无关。当 `serviceA.ts` 导出的类型被 `controllerB.ts`、`storeC.ts` 同时引用且三者均出错时,若 controller/store 先修复,修复 prompt 中的 service 仍是破损版本,cascade 错误只能等 cycle 2 处理。v0.30.0 新增 `buildRepairOrder()`:解析每个出错文件的相对 import 声明(跳过 `import type`),在出错文件子图上做 Kahn 拓扑排序,**被依赖文件优先修复**,有环依赖追加末尾。效果:cycle 1 可一次消除更多 cascade 错误,2 轮上限的利用率提升。
194
195
 
195
196
  ***
196
197
 
@@ -216,6 +217,27 @@ data → infra → service → api → view → route → test
216
217
 
217
218
  - **`paginationExample` 逐行提取重写**:旧版正则用 `[^}]*` 匹配接口体,遇到嵌套对象字段(`filter: { status?: string }`)立即截断;函数匹配用 `\n\}` 要求闭合括号紧接换行,缩进的 ` }` 永远不匹配;且只支持 `export function`,遗漏了现代代码中更常见的 `export const = () =>` 写法。新版全部改为逐行扫描 + 大括号深度计数器,支持嵌套对象、任意缩进的闭合括号、arrow function,提取可靠性从「经常为空」提升到「能找到就找到」。
218
219
 
220
+ **v0.30.0 — 多行感知 Import 解析:**
221
+
222
+ v0.25.0 的正则扩展解决了模块路径识别范围的问题,但遗留了另一个结构性缺陷:**所有提取逻辑均假设 import 语句在单行内完成**。现代 TypeScript 项目中多行 named import 极为常见:
223
+
224
+ ```typescript
225
+ // 这种写法在 v0.30.0 之前静默匹配失败
226
+ import {
227
+ request,
228
+ type RequestConfig,
229
+ } from '@/utils/http'
230
+ ```
231
+
232
+ 旧版正则 `[^}]+` 不跨换行符,遇到上述格式直接失败,`httpClientImport` 结果为 `undefined`,AI 回退到自由发挥。
233
+
234
+ **修复方案(不引入任何新依赖)**:
235
+
236
+ - `parseImportStatements(content)` — 先将文件中所有 `import { ... }` 块折叠为单行(正则 `/import\s*\{[^}]*\}/gs` + 替换换行),再逐行匹配并返回 `{ line, modulePath, specifiers }` 结构化对象,跳过 `import type`
237
+ - `HTTP_MODULE_PATTERNS` 数组 — 三类识别规则与 import 解析完全解耦,独立维护
238
+ - `findHttpClientImport()` — 组合两者,替换旧版一行式 `content.match(httpImportRegex)`
239
+ - `extractRouteModuleContext()` 中 `layoutImport` 的静态形式改走 `parseImportStatements()`;动态形式(`const Layout = defineAsyncComponent(...)`)增加多行折叠预处理,消除换行导致的漏匹配
240
+
219
241
  ***
220
242
 
221
243
  ### 2.7 跨 Task 一致性(Generated File Cache)
@@ -631,9 +653,12 @@ DSL 设计主要针对 REST API 场景(HTTP 端点 + 数据模型)。对于
631
653
 
632
654
  ### 7.5 错误修复能力有上限
633
655
 
634
- 错误反馈循环最多运行 2 次,每次修复以文件为粒度。对于复杂的跨文件依赖错误(A 文件的 export 类型变了,B、C、D 都引用了它),当前的修复逻辑可能只修复了第一个文件,后续文件的错误留到下一 cycle,效率较低。
656
+ 错误反馈循环最多运行 2 次,每次修复以文件为粒度。仍存在的限制:
657
+ - 修复上限固定为 2 cycle,无法动态调整
658
+ - 同一文件内多个相互依赖的逻辑错误需要多轮才能全部消除
659
+ - Python、Java 等语言的 import 语法暂不参与依赖排序(当前仅解析 TS/JS 相对 import)
635
660
 
636
- > v0.19.0 改进了错误 **解析** 质量(全文扫描 + file:line 过滤),确保 AI 看到的是真实错误位置而不是汇总摘要。但 2 cycle 上限和「按文件粒度串行修复」的策略本身未变。
661
+ > v0.19.0 改进了错误 **解析** 质量(全文扫描 + file:line 过滤)。v0.30.0 新增 `buildRepairOrder()` 对出错文件按 import 依赖拓扑排序,被依赖文件优先修复,cascade 错误在 cycle 1 的消除率提升。2 cycle 上限和「按文件粒度串行修复」的基本策略不变。
637
662
 
638
663
  ***
639
664
 
package/cli/welcome.ts DELETED
@@ -1,151 +0,0 @@
1
- import chalk from "chalk";
2
- import * as os from "os";
3
- import * as path from "path";
4
- import * as fs from "fs-extra";
5
-
6
- // ─── Layout constants ─────────────────────────────────────────────────────────
7
-
8
- const VERSION = "0.14.1";
9
- /** Total visible width of the title / bottom bar */
10
- const TOTAL_W = 76;
11
- /** Visible width of the left content column */
12
- const L_WIDTH = 44;
13
- /** Visible width of the right content column (TOTAL_W - L_WIDTH - 4 for " │ " + leading space) */
14
- const R_WIDTH = TOTAL_W - L_WIDTH - 4;
15
-
16
- // ─── ASCII robot ──────────────────────────────────────────────────────────────
17
-
18
- const ROBOT_COLOR = chalk.hex("#E8885A");
19
-
20
- const ROBOT: string[] = [
21
- ROBOT_COLOR(" ┌───────────┐"),
22
- ROBOT_COLOR(" │") + chalk.bold.white(" ◉ ") + ROBOT_COLOR(" ") + chalk.bold.white("◉ ") + ROBOT_COLOR("│"),
23
- ROBOT_COLOR(" │") + chalk.dim(" ╰─╯ ") + ROBOT_COLOR("│"),
24
- ROBOT_COLOR(" └─────┬─────┘"),
25
- ROBOT_COLOR(" │"),
26
- ROBOT_COLOR(" ────┴────"),
27
- ];
28
-
29
- // ─── Helpers ──────────────────────────────────────────────────────────────────
30
-
31
- /** Visible (non-ANSI) length of a string */
32
- function visLen(s: string): number {
33
- // eslint-disable-next-line no-control-regex
34
- return s.replace(/\x1b\[[0-9;]*m/g, "").length;
35
- }
36
-
37
- /** Pad a string on the right to reach `width` visible chars */
38
- function padR(s: string, width: number): string {
39
- const vl = visLen(s);
40
- return vl >= width ? s : s + " ".repeat(width - vl);
41
- }
42
-
43
- /** Render one full-width row: left column + separator + right column */
44
- function row(left: string, right: string): string {
45
- return padR(left, L_WIDTH) + " " + chalk.gray("│") + " " + padR(right, R_WIDTH);
46
- }
47
-
48
- /** Center a string (with chalk codes) within `width` visible chars */
49
- function center(s: string, width: number): string {
50
- const vl = visLen(s);
51
- const pad = Math.max(0, Math.floor((width - vl) / 2));
52
- return " ".repeat(pad) + s;
53
- }
54
-
55
- // ─── Recent specs ─────────────────────────────────────────────────────────────
56
-
57
- async function getRecentSpecs(dir: string): Promise<string[]> {
58
- const specsDir = path.join(dir, "specs");
59
- if (!(await fs.pathExists(specsDir))) return [];
60
- try {
61
- const files = await fs.readdir(specsDir);
62
- const mdFiles = files.filter((f) => f.endsWith(".md"));
63
- const withStats = await Promise.all(
64
- mdFiles.map(async (f) => {
65
- const stat = await fs.stat(path.join(specsDir, f));
66
- return { name: f, mtime: stat.mtime.getTime() };
67
- })
68
- );
69
- withStats.sort((a, b) => b.mtime - a.mtime);
70
- return withStats.slice(0, 3).map(({ name, mtime }) => {
71
- const ms = Date.now() - mtime;
72
- const hours = Math.floor(ms / 3_600_000);
73
- const days = Math.floor(hours / 24);
74
- const age = days > 0 ? `${days}d ago` : hours > 0 ? `${hours}h ago` : "just now";
75
- // trim to fit right column
76
- const slug = name.replace(/\.md$/, "").slice(0, R_WIDTH - age.length - 2);
77
- return chalk.white(slug) + chalk.dim(" " + age);
78
- });
79
- } catch {
80
- return [];
81
- }
82
- }
83
-
84
- // ─── Public API ───────────────────────────────────────────────────────────────
85
-
86
- export async function printWelcome(
87
- currentDir: string,
88
- config?: { provider?: string; model?: string }
89
- ): Promise<void> {
90
- const username = os.userInfo().username;
91
- const homeDir = os.homedir();
92
- const shortDir = currentDir.startsWith(homeDir)
93
- ? "~" + currentDir.slice(homeDir.length)
94
- : currentDir;
95
-
96
- const recentSpecs = await getRecentSpecs(currentDir);
97
-
98
- const providerBit = config?.provider
99
- ? config.provider + (config.model ? " · " + config.model : "")
100
- : "";
101
- // Build bottom info and truncate to fit left column (leave 2-char indent)
102
- const bottomRaw = [providerBit, shortDir].filter(Boolean).join(" · ");
103
- const maxInfoLen = L_WIDTH - 2;
104
- const bottomTruncated = bottomRaw.length > maxInfoLen
105
- ? bottomRaw.slice(0, maxInfoLen - 1) + "…"
106
- : bottomRaw;
107
- const bottomLine = " " + chalk.dim(bottomTruncated);
108
-
109
- // ── Title bar ───────────────────────────────────────────────────────────────
110
- const titleInner = `ai-spec v${VERSION} `;
111
- const titleDashes = "─".repeat(Math.max(0, TOTAL_W - titleInner.length - 4));
112
- console.log(
113
- "\n" +
114
- chalk.hex("#FF6B35")("─── " + titleInner + titleDashes)
115
- );
116
-
117
- // ── Build left-column lines ──────────────────────────────────────────────────
118
- const welcomeText = "Welcome back, " + chalk.bold.white(username) + "!";
119
- const leftLines: string[] = [
120
- "",
121
- center(welcomeText, L_WIDTH),
122
- "",
123
- ...ROBOT,
124
- "",
125
- bottomLine,
126
- "",
127
- ];
128
-
129
- // ── Build right-column lines ─────────────────────────────────────────────────
130
- const rightLines: string[] = [
131
- chalk.hex("#FF8C00").bold("Tips for getting started"),
132
- chalk.gray("─".repeat(R_WIDTH)),
133
- chalk.white('ai-spec create "feature"'),
134
- chalk.gray("ai-spec workspace run"),
135
- chalk.gray("ai-spec update \"change\""),
136
- "",
137
- chalk.hex("#FF8C00").bold("Recent activity"),
138
- chalk.gray("─".repeat(R_WIDTH)),
139
- ...(recentSpecs.length > 0 ? recentSpecs : [chalk.dim("No recent activity")]),
140
- ];
141
-
142
- // ── Render rows ─────────────────────────────────────────────────────────────
143
- const maxLines = Math.max(leftLines.length, rightLines.length);
144
- for (let i = 0; i < maxLines; i++) {
145
- console.log(row(leftLines[i] ?? "", rightLines[i] ?? ""));
146
- }
147
-
148
- // ── Bottom bar ──────────────────────────────────────────────────────────────
149
- console.log(chalk.gray("─".repeat(TOTAL_W)));
150
- console.log();
151
- }