sillyspec 3.26.7 → 3.26.9
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/.claude/skills/sillyspec-archive/SKILL.md +1 -2
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -2
- package/.claude/skills/sillyspec-doctor/SKILL.md +2 -0
- package/.claude/skills/sillyspec-execute/SKILL.md +10 -2
- package/.claude/skills/sillyspec-explore/SKILL.md +0 -1
- package/.claude/skills/sillyspec-plan/SKILL.md +1 -2
- package/.claude/skills/sillyspec-quick/SKILL.md +1 -2
- package/.claude/skills/sillyspec-scan/SKILL.md +2 -2
- package/.claude/skills/sillyspec-status/SKILL.md +0 -1
- package/.claude/skills/sillyspec-verify/SKILL.md +1 -2
- package/.husky/pre-push +9 -2
- package/README.md +5 -9
- package/bin/sillyspec.js +1 -1
- package/package.json +2 -2
- package/packages/dashboard/server/index.js +14 -8
- package/src/change-risk-profile.js +5 -207
- package/src/config-schema.js +310 -287
- package/src/constants.js +97 -106
- package/src/db-engine.js +1 -1
- package/src/docs-check.js +362 -0
- package/src/docs-debt.js +226 -0
- package/src/docs-gate.js +106 -0
- package/src/doctor-diagnostics.js +23 -2
- package/src/git-helper.js +2 -2
- package/src/hooks/worktree-guard.js +1 -1
- package/src/index.js +1963 -1787
- package/src/init.js +95 -17
- package/src/machine-interface.js +7 -4
- package/src/modules.js +490 -486
- package/src/progress/stage-machine.js +3 -0
- package/src/progress.js +37 -13
- package/src/quicklog.js +182 -1
- package/src/run/command.js +154 -26
- package/src/run/complete-handlers.js +1254 -1128
- package/src/run/complete.js +9 -33
- package/src/run/gates.js +85 -1
- package/src/run/prompt.js +82 -1
- package/src/run/quick-audit.js +23 -0
- package/src/run/shared.js +273 -5
- package/src/run/stage.js +17 -1
- package/src/scan-diff.js +388 -0
- package/src/scan-staleness.js +95 -0
- package/src/stage-contract-spec.js +16 -1
- package/src/stage-contract.js +8 -5
- package/src/stage-review.js +1 -1
- package/src/stages/brainstorm-auto.js +0 -1
- package/src/stages/brainstorm.js +3 -3
- package/src/stages/execute.js +35 -11
- package/src/stages/plan-postcheck.js +93 -36
- package/src/stages/plan.js +22 -7
- package/src/stages/quick.js +1 -1
- package/src/stages/scan.js +2 -2
- package/src/stages/verify.js +11 -12
- package/src/sync.js +39 -17
- package/src/task-review.js +14 -5
- package/src/workflow.js +8 -185
- package/src/worktree-apply.js +128 -3
- package/src/worktree.js +2 -2
- package/templates/claude-instruction.md +1 -1
- package/templates/prompts/taskcard-rules.md +1 -0
- package/templates/prompts/verify-probes.md +2 -2
- package/templates/skills/sillyspec-onboard/SKILL.md +0 -92
|
@@ -33,8 +33,7 @@ sillyspec run archive --reopen --from-step N # 重新打开已完成阶段修
|
|
|
33
33
|
| `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
|
|
34
34
|
| `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
|
|
35
35
|
| `--non-interactive` | CI/脚本下禁用交互式 prompt |
|
|
36
|
-
| `--skip-approval` |
|
|
37
|
-
| `--json` | 输出 JSON(程序化读取) |
|
|
36
|
+
| `--skip-approval` | 跳过阶段转换/审批检查(不能跳产物校验 gate——review.json/文档产物硬校验仍在) |
|
|
38
37
|
|
|
39
38
|
## archive 特有
|
|
40
39
|
|
|
@@ -41,8 +41,7 @@ sillyspec run brainstorm --done --answer "..." --output "..." # 一步完成
|
|
|
41
41
|
| `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
|
|
42
42
|
| `--non-interactive` | CI/脚本下禁用交互式 prompt |
|
|
43
43
|
| `--interactive` | 强制交互(即便 stdin 非 TTY) |
|
|
44
|
-
| `--skip-approval` |
|
|
45
|
-
| `--json` | 输出 JSON(程序化读取) |
|
|
44
|
+
| `--skip-approval` | 跳过阶段转换/审批检查(不能跳产物校验 gate——review.json/文档产物硬校验仍在) |
|
|
46
45
|
|
|
47
46
|
## brainstorm 特有:requiresWait 步骤
|
|
48
47
|
|
|
@@ -63,6 +63,8 @@ sillyspec runtime list # 枚举 .si
|
|
|
63
63
|
> `--cleanup-remnant` 清理 init/平台切换残留的 0 字节空 db(占位但无数据)。默认 dry-run 只报告,`--confirm` 才删。绝不删有内容的 db。
|
|
64
64
|
>
|
|
65
65
|
> `runtime list` 是只读枚举:列出 `.sillyspec/.runtime/` 下的 db、doctor-diagnosis.json、workflow-runs/、user-inputs.md 等产物及用途。多会话/压缩后想知道「我手上有哪些证据文件」时用。加 `--json` 程序化读取。
|
|
66
|
+
>
|
|
67
|
+
> `doctor --json` 的 pointer_health 维度含 `pointer_missing_but_managed` 信号:项目根存在平台接管声明(`.sillyspec-platform-managed`)但恢复指针缺失——此状态下裸调命令会 fail-closed 拒绝静默落本地。恢复路径见信号文案:重跑平台 scan 重建指针 / `sillyspec platform disconnect` 彻底脱离 / 显式 `--spec-dir` 临时指定。
|
|
66
68
|
|
|
67
69
|
## 铁律
|
|
68
70
|
|
|
@@ -33,8 +33,7 @@ sillyspec run execute --reopen --from-step N # 重新打开已完成阶段修
|
|
|
33
33
|
| `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
|
|
34
34
|
| `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
|
|
35
35
|
| `--non-interactive` | CI/脚本下禁用交互式 prompt |
|
|
36
|
-
| `--skip-approval` |
|
|
37
|
-
| `--json` | 输出 JSON(程序化读取) |
|
|
36
|
+
| `--skip-approval` | 跳过阶段转换/审批检查(不能跳产物校验 gate——review.json/文档产物硬校验仍在) |
|
|
38
37
|
|
|
39
38
|
## execute 特有:Worktree 隔离
|
|
40
39
|
|
|
@@ -56,6 +55,14 @@ sillyspec worktree doctor --fix --change <变更名>
|
|
|
56
55
|
|
|
57
56
|
`linked / installed / n/a` 放行;`missing / stale / failed / unknown` 阻断。Wave 内所有 task 声明 `no_deps_verify: true` 时可 opt-out。
|
|
58
57
|
|
|
58
|
+
### 符号影响面报告(「加载上下文」步硬门)
|
|
59
|
+
|
|
60
|
+
execute 前缀步「加载上下文」的 `--done` 会硬校验符号影响面报告:
|
|
61
|
+
|
|
62
|
+
- 路径:`{SPEC_ROOT}/changes/<变更名>/symbol-impact.md`(步骤 prompt 中会输出具体路径)。
|
|
63
|
+
- 要求:plan.md 中**每个 task-XX 都要有一行结论**——涉及签名级变更(构造函数参数/接口/DTO/方法签名增删改)就列变更类型 + 受影响调用点 + 是否在任务范围内;无签名级变更也要显式写「无签名级变更」。
|
|
64
|
+
- 文件缺失或未覆盖全部 task → `--done` 被阻断(进度不推进),补全后重跑即可。
|
|
65
|
+
|
|
59
66
|
### Task Review Gate
|
|
60
67
|
|
|
61
68
|
execute 完成时,每个 task 必须有 `review.json` 且 verdict 通过,否则阻断完成。**例外**:task 在 `tasks/task-XX.md` frontmatter 声明 `low_risk: true`(type-only / 机械迁移等低逻辑风险)时,缺 review.json 只发 warning 不阻断。`cannot_verify` 的 task 会写入 `verify-required-evidence.json`,由 verify 阶段消费。
|
|
@@ -109,6 +116,7 @@ execute 还有**第二道**独立的 stage 级审查:除逐 task review.json
|
|
|
109
116
|
|
|
110
117
|
- `docHash` = `sha256(主审查文档内容)`(hex)—— execute 主审查文档是 `design.md`,即 `reviewedFiles[0]`。CLI 会重算 sha256 比对,不符判伪造(fail-closed);找不到主文档也 fail。**改 design.md 后须重算 docHash 再写**,可用 `sillyspec run execute --done` 触发的 prompt 注入版契约(运行时注入的 schema 表 + JSON 示例 + docHash 算法)逐字改值。
|
|
111
118
|
- `tier=independent` 时必须由独立 QA 子代理产出该 review.json(独立上下文,不共享实现者分析);`tier=self`(变更 ≤3 文件)降级为当前 agent 自审。
|
|
119
|
+
- **审查范围分级**(tier=independent 时省重复消耗):Task Review Gate 已产出 review.json 且 specVerdict/qualityVerdict 双 pass 的 task,QA 子代理只抽查(读 1-2 个核心 diff 文件抽验 reviewerNotes 与实际改动相符);fail/cannot_verify/缺失的 task 全量重审。三项始终必查:跨 task 交界、design.md 整体对照、组装行为(全量测试/构建/启动)——task review 只看单 task diff,覆盖不到这三项。
|
|
112
120
|
- 该 acceptance review 同时覆盖"代码审查"视角,后续代码审查步骤仅需轻量复审。
|
|
113
121
|
|
|
114
122
|
> 运行时 CLI 会把精确 schema 表 + 完整 JSON 示例 + docHash 算法注入到该步 prompt。本段为常驻摘要;以你实际收到的注入版契约为权威逐字模板。
|
|
@@ -37,8 +37,7 @@ sillyspec run plan --done --answer "..." --output "..." # 一步完成 wait+d
|
|
|
37
37
|
| `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
|
|
38
38
|
| `--non-interactive` | CI/脚本下禁用交互式 prompt |
|
|
39
39
|
| `--interactive` | 强制交互(即便 stdin 非 TTY) |
|
|
40
|
-
| `--skip-approval` |
|
|
41
|
-
| `--json` | 输出 JSON(程序化读取) |
|
|
40
|
+
| `--skip-approval` | 跳过阶段转换/审批检查(不能跳产物校验 gate——review.json/文档产物硬校验仍在) |
|
|
42
41
|
|
|
43
42
|
## plan 特有
|
|
44
43
|
|
|
@@ -52,8 +52,7 @@ sillyspec run quick --done --change quick-<hash> --output "…" # 完成该会
|
|
|
52
52
|
| `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
|
|
53
53
|
| `--non-interactive` | CI/脚本下禁用交互式 prompt |
|
|
54
54
|
| `--interactive` | 强制交互(即便 stdin 非 TTY) |
|
|
55
|
-
| `--skip-approval` |
|
|
56
|
-
| `--json` | 输出 JSON(程序化读取) |
|
|
55
|
+
| `--skip-approval` | 跳过阶段转换/审批检查(不能跳产物校验 gate——review.json/文档产物硬校验仍在) |
|
|
57
56
|
|
|
58
57
|
## quick 特有参数
|
|
59
58
|
|
|
@@ -31,8 +31,7 @@ sillyspec run scan --reset # 重置阶段(从头开始)
|
|
|
31
31
|
|---|---|
|
|
32
32
|
| `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
|
|
33
33
|
| `--non-interactive` | CI/脚本下禁用交互式 prompt |
|
|
34
|
-
| `--skip-approval` |
|
|
35
|
-
| `--json` | 输出 JSON(程序化读取) |
|
|
34
|
+
| `--skip-approval` | 跳过阶段转换/审批检查(不能跳产物校验 gate——review.json/文档产物硬校验仍在) |
|
|
36
35
|
|
|
37
36
|
## scan 特有参数
|
|
38
37
|
|
|
@@ -42,6 +41,7 @@ sillyspec run scan --reset # 重置阶段(从头开始)
|
|
|
42
41
|
| `--standard` | 强制 standard profile(压缩步骤,最多 1 子代理) |
|
|
43
42
|
| `--deep` | 强制 deep profile(完整流程,不按规模裁剪) |
|
|
44
43
|
| `--force-rescan` | 覆盖已有 scan 文档的保护(默认覆盖需 source_commit/updated_at 匹配) |
|
|
44
|
+
| `--diff` | 增量漂移清单:`sillyspec scan diff`(或 `--diff` flag)算 source_commit..HEAD 的 A/D/M/R 四分类漂移,按 module-map paths 归模块,agent 据此定点补文档而非全量重扫(2026-08-16) |
|
|
45
45
|
|
|
46
46
|
### scanProfile(显式选择优先,否则按规模自动裁剪)
|
|
47
47
|
|
|
@@ -34,8 +34,7 @@ sillyspec run verify --reopen --from-step N # 重新打开已完成阶段修
|
|
|
34
34
|
| `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
|
|
35
35
|
| `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
|
|
36
36
|
| `--non-interactive` | CI/脚本下禁用交互式 prompt |
|
|
37
|
-
| `--skip-approval` |
|
|
38
|
-
| `--json` | 输出 JSON(程序化读取) |
|
|
37
|
+
| `--skip-approval` | 跳过阶段转换/审批检查(不能跳产物校验 gate——review.json/文档产物硬校验仍在) |
|
|
39
38
|
|
|
40
39
|
## verify 特有:完成门控(重要)
|
|
41
40
|
|
package/.husky/pre-push
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
echo "🔧 pre-push: running lint + test..."
|
|
2
|
+
echo "🔧 pre-push: running lint + test + docs gate..."
|
|
3
3
|
npm run lint --silent 2>&1
|
|
4
4
|
if [ $? -ne 0 ]; then
|
|
5
5
|
echo "❌ pre-push: lint failed, aborting push"
|
|
@@ -10,4 +10,11 @@ if [ $? -ne 0 ]; then
|
|
|
10
10
|
echo "❌ pre-push: test failed, aborting push"
|
|
11
11
|
exit 1
|
|
12
12
|
fi
|
|
13
|
-
|
|
13
|
+
# docs gate ratchet(doc-consistency-debt 第七节):docs check 失效数 ≤ 基线放行。
|
|
14
|
+
# 基线 .sillyspec/docs-check-baseline 缺失/损坏时 fail-closed exit 2 拦截。
|
|
15
|
+
node bin/sillyspec.js docs gate 2>&1
|
|
16
|
+
if [ $? -ne 0 ]; then
|
|
17
|
+
echo "❌ pre-push: docs gate failed, aborting push"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
echo "✅ pre-push: lint + test + docs gate passed"
|
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ SillySpec 把一个变更的完整生命周期固化为一组**强制阶段**—
|
|
|
29
29
|
|
|
30
30
|
## 快速开始
|
|
31
31
|
|
|
32
|
-
需要 Node.js >=
|
|
32
|
+
需要 Node.js >= 22.13,一条命令完成初始化:
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
npx sillyspec init
|
|
@@ -50,18 +50,16 @@ npx sillyspec init
|
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
npx sillyspec init --tool claude # 指定工具
|
|
53
|
-
npx sillyspec init --workspace # 多项目工作区模式
|
|
54
53
|
npx sillyspec init --dir /path/to/x # 指定项目目录
|
|
55
54
|
npx sillyspec init --interactive # 完整引导
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
> 💡 启动 AI 时推荐使用跳过权限模式(如 `claude --dangerously-skip-permissions`)。SillySpec 会频繁执行 git、文件读写、校验脚本等操作,逐个批准会打断节奏——这也是 GSD / OpenSpec 的预期使用方式。
|
|
59
57
|
|
|
60
58
|
## 平台支持
|
|
61
59
|
|
|
62
60
|
SillySpec 进度库使用 SQLite 持久化(基于 Node.js 内置的 `node:sqlite` 模块,纯 JS 无原生编译依赖)。
|
|
63
61
|
|
|
64
|
-
- **Node.js 版本要求 >= 22.
|
|
62
|
+
- **Node.js 版本要求 >= 22.13.0**:`node:sqlite` 自 v22.13.0 起免 `--experimental-sqlite` flag 直接可用。支持 Linux x64、macOS x64 / arm64、Windows x64 等所有 Node.js 官方支持平台。
|
|
65
63
|
- **零编译安装**:不依赖任何原生绑定 / node-gyp / C++ 工具链,npm install 即装即用,无平台编译失败风险。
|
|
66
64
|
|
|
67
65
|
## 从哪里开始
|
|
@@ -101,7 +99,7 @@ SillySpec 进度库使用 SQLite 持久化(基于 Node.js 内置的 `node:sqli
|
|
|
101
99
|
| `/sillyspec:brainstorm` | 需求探索 + 规范生成:产出 design.md + tasks.md |
|
|
102
100
|
| `/sillyspec:plan` | 实现计划:文件路径 + 任务描述 + Wave 拓扑分组 |
|
|
103
101
|
| `/sillyspec:execute` | TDD 执行:子代理并行 + worktree 隔离 |
|
|
104
|
-
| `/sillyspec:verify` | 验证:对照规范 + 测试套件 + 代码审查
|
|
102
|
+
| `/sillyspec:verify` | 验证:对照规范 + 测试套件 + 代码审查 |
|
|
105
103
|
| `/sillyspec:archive` | 归档:规范沉淀到 knowledge/ |
|
|
106
104
|
| `/sillyspec:auto` | 全自动推进全部阶段 |
|
|
107
105
|
|
|
@@ -142,7 +140,7 @@ sillyspec doctor 全量自检 + 修复进度
|
|
|
142
140
|
- **拓扑排序 Wave** — plan 阶段按蓝图依赖关系自动重排 Wave 分组
|
|
143
141
|
- **进度持久化** — SQLite(sql.js WASM)持久化,支持断点恢复
|
|
144
142
|
- **模块文档** — 模块级知识库,AI 执行时按需加载相关上下文
|
|
145
|
-
-
|
|
143
|
+
- **浏览器自动化** — 可选 MCP(Playwright / Chrome DevTools),供集成/E2E 冒烟验证
|
|
146
144
|
- **平台同步** — 可选对接 SillyHub,文档同步 + 团队审批
|
|
147
145
|
|
|
148
146
|
## 可靠性保障
|
|
@@ -152,7 +150,6 @@ SillySpec 不只是 prompt,还有硬校验兜底:
|
|
|
152
150
|
- **锚定确认** — 各阶段执行前必须逐个确认读过规范文件
|
|
153
151
|
- **Hard Gate 自检** — 关键命令生成文件后强制自检格式,不通过则修正
|
|
154
152
|
- **postcheck 识别偷懒** — 自动识别占位符、fallback、未分析等 AI 偷懒模式
|
|
155
|
-
- **校验脚本** — shell 脚本自动化验证 AI 输出(validate-proposal/plan/scan/all)
|
|
156
153
|
- **归档确认** — archive 操作前展示内容等待用户确认
|
|
157
154
|
|
|
158
155
|
## 防幻觉机制
|
|
@@ -162,14 +159,13 @@ SillySpec 不只是 prompt,还有硬校验兜底:
|
|
|
162
159
|
| scan | 强制扫描代码库结构 / 约定 / 集成 / 测试 / 债务,生成 7 份文档 |
|
|
163
160
|
| brainstorm | 必须读 ARCHITECTURE.md 数据模型,支持原型图分析 |
|
|
164
161
|
| execute | 写代码前强制读现有源码,禁止编造方法调用 |
|
|
165
|
-
| 全流程 | postcheck +
|
|
162
|
+
| 全流程 | postcheck + 硬门兜底 |
|
|
166
163
|
|
|
167
164
|
## MCP 增强
|
|
168
165
|
|
|
169
166
|
通过 `sillyspec setup` 一键安装 MCP 工具,增强 AI 能力:
|
|
170
167
|
|
|
171
168
|
- **Context7** — 查询最新库文档和 API 参考
|
|
172
|
-
- **grep.app** — 搜索开源代码实现
|
|
173
169
|
- **Chrome DevTools** — 浏览器自动化,支持 E2E 验证
|
|
174
170
|
|
|
175
171
|
## 目录结构
|
package/bin/sillyspec.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import '../src/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillyspec",
|
|
3
|
-
"version": "3.26.
|
|
3
|
+
"version": "3.26.9",
|
|
4
4
|
"description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
|
|
5
5
|
"icon": "logo.jpg",
|
|
6
6
|
"homepage": "https://sillyspec.ppdmq.top/",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"sillyspec": "bin/sillyspec.js"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=22.
|
|
16
|
+
"node": ">=22.13.0"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"sillyspec",
|
|
@@ -564,14 +564,10 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
|
|
|
564
564
|
})
|
|
565
565
|
})
|
|
566
566
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
} catch (err) {
|
|
572
|
-
console.error('Failed to start file watcher:', err)
|
|
573
|
-
}
|
|
574
|
-
|
|
567
|
+
// listen 前置:Windows 下 startWatcher 的初始全盘同步扫描(homedir/Temp/桌面 depth 2 +
|
|
568
|
+
// 每项目多次 execSync('git'))实测 150s+,若排在其后则服务永不响应(假死)。
|
|
569
|
+
// 先绑定端口(listen 回调立即可达),扫描用 setImmediate 延后到事件循环——端口立即响应,
|
|
570
|
+
// 初始 projects 数据由扫描完成后 broadcast 补发。
|
|
575
571
|
server.listen(port, '127.0.0.1', () => {
|
|
576
572
|
console.log(`Dashboard server running on http://127.0.0.1:${port}`)
|
|
577
573
|
if (openBrowser) {
|
|
@@ -579,6 +575,16 @@ function startServer({ port = 3456, open: openBrowser = true } = {}) {
|
|
|
579
575
|
}
|
|
580
576
|
})
|
|
581
577
|
|
|
578
|
+
setImmediate(() => {
|
|
579
|
+
try {
|
|
580
|
+
startWatcher((projects) => {
|
|
581
|
+
broadcast({ type: 'projects:updated', data: projects })
|
|
582
|
+
})
|
|
583
|
+
} catch (err) {
|
|
584
|
+
console.error('Failed to start file watcher:', err)
|
|
585
|
+
}
|
|
586
|
+
})
|
|
587
|
+
|
|
582
588
|
const shutdown = () => {
|
|
583
589
|
stopWatcher()
|
|
584
590
|
activeProcesses.forEach(kill => kill())
|
|
@@ -2,56 +2,13 @@
|
|
|
2
2
|
* change-risk-profile.js — 变更风险分级检测
|
|
3
3
|
*
|
|
4
4
|
* 根据变更涉及的文件类型、关键词、git diff、brainstorm 产物,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* P0 = 阻塞确认(必须用户确认)
|
|
8
|
-
* P1 = 自动推进但记录
|
|
9
|
-
* P2 = 自动通过
|
|
5
|
+
* 判定 5 级风险(doc-only / unit-sufficient / contract-required /
|
|
6
|
+
* integration-critical / deployment-critical),产出门控验证需求。
|
|
10
7
|
*/
|
|
11
8
|
|
|
12
|
-
// ============ P0 触发规则 ============
|
|
13
|
-
|
|
14
|
-
const P0_FILE_PATTERNS = [
|
|
15
|
-
{ id: 'R-002', pattern: /(?:^|\/)(?:migrations?|migration)\/(?:.+)/i, desc: '数据库 migration' },
|
|
16
|
-
{ id: 'R-002', pattern: /(?:^|\/)schema\.(?:sql|prisma|ts|js)$/i, desc: '数据库 schema' },
|
|
17
|
-
{ id: 'R-003', pattern: /(?:^|\/)(?:auth|permission|role|login|token)\b/i, desc: '鉴权/权限' },
|
|
18
|
-
{ id: 'R-004', pattern: /(?:^|\/)(?:payment|billing|stripe|paypal)\b/i, desc: '支付/资金' },
|
|
19
|
-
{ id: 'R-005', pattern: /\.(?:env|env\.\w+)$/, desc: '环境配置' },
|
|
20
|
-
{ id: 'R-005', pattern: /(?:^|\/)(?:Dockerfile|docker-compose\.\w+)$/i, desc: '部署配置' },
|
|
21
|
-
{ id: 'R-005', pattern: /(?:^|\/)config\.(?:yaml|yml|json|toml)$/i, desc: '生产配置' },
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
const P0_CONTENT_PATTERNS = [
|
|
25
|
-
{ id: 'R-001', pattern: /\b(?:DROP\s+TABLE|TRUNCATE\s+TABLE|DELETE\s+FROM\s+\w+\s+(?!WHERE\s+.+\s+LIMIT))/i, desc: '删除数据' },
|
|
26
|
-
{ id: 'R-002', pattern: /\b(?:CREATE\s+TABLE|ALTER\s+TABLE|ADD\s+COLUMN|DROP\s+COLUMN|CREATE\s+INDEX)/i, desc: '数据库 migration' },
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
// ============ P1 触发规则 ============
|
|
30
|
-
|
|
31
|
-
const P1_FILE_PATTERNS = [
|
|
32
|
-
{ id: 'R-101', pattern: /(?:^|\/)(?:routes|controllers?|handlers?)\//i, desc: 'API 层' },
|
|
33
|
-
{ id: 'R-102', pattern: /(?:^|\/)(?:models|entities)\//i, desc: '数据模型层' },
|
|
34
|
-
{ id: 'R-104', pattern: /(?:^|\/)(?:services)\//i, desc: '业务逻辑层(跨模块)' },
|
|
35
|
-
{ id: 'R-106', pattern: /(?:^|\/)index\.(?:ts|js)$/i, desc: 're-export 入口' },
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
const P1_CONTENT_PATTERNS = [
|
|
39
|
-
{ id: 'R-101', pattern: /\b(?:daemon|backend|grpc|websocket|cross.?process|ipc|message.?queue)\b/i, desc: '跨进程通信' },
|
|
40
|
-
{ id: 'R-101', pattern: /\b(?:session|lease|agent.?run|lifecycle|state.?transition|claim|heartbeat)\b/i, desc: '状态机/生命周期' },
|
|
41
|
-
{ id: 'R-102', pattern: /\b(?:api|client|contract|dto)\b/i, desc: 'API contract' },
|
|
42
|
-
{ id: 'R-105', pattern: /(?:^|\/)(?:workflow|daemon|session|lifecycle|state-machine)\b/i, desc: '核心模块' },
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
// ============ P0/P1 命中文件名关键词(用于 git diff 检测) ============
|
|
46
|
-
|
|
47
|
-
const PROTECTED_FILE_NAMES = [
|
|
48
|
-
'.env', 'package.json', 'tsconfig.json', 'vite.config.ts', 'vite.config.js',
|
|
49
|
-
'prisma/schema.prisma', 'docker-compose.yml', 'docker-compose.yaml',
|
|
50
|
-
]
|
|
51
|
-
|
|
52
9
|
// ============ 向后兼容:旧的 INTEGRATION_CRITICAL_PATTERNS ============
|
|
53
10
|
|
|
54
|
-
|
|
11
|
+
const INTEGRATION_CRITICAL_PATTERNS = [
|
|
55
12
|
/\bdaemon\b/i,
|
|
56
13
|
/\bbackend\b/i,
|
|
57
14
|
/\bclient.*api\b/i,
|
|
@@ -78,7 +35,7 @@ export const INTEGRATION_CRITICAL_PATTERNS = [
|
|
|
78
35
|
/\bdocker.?compose\b/i,
|
|
79
36
|
]
|
|
80
37
|
|
|
81
|
-
|
|
38
|
+
const INTEGRATION_FILE_PATTERNS = [
|
|
82
39
|
/daemon/i,
|
|
83
40
|
/session.?manager/i,
|
|
84
41
|
/agent.?run/i,
|
|
@@ -114,165 +71,6 @@ export function isEndToEndTaskText(text = '') {
|
|
|
114
71
|
return END_TO_END_TASK_KEYWORDS.some(re => re.test(text))
|
|
115
72
|
}
|
|
116
73
|
|
|
117
|
-
// ============ 核心检测函数 ============
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 三级风险检测:P0 / P1 / P2
|
|
121
|
-
* @param {object} opts
|
|
122
|
-
* @param {string} [opts.designContent] - design.md 内容
|
|
123
|
-
* @param {string} [opts.planContent] - plan.md 内容
|
|
124
|
-
* @param {string[]} [opts.changedFiles] - 变更文件列表
|
|
125
|
-
* @param {string} [opts.diffContent] - git diff 内容(可选,用于更精确检测)
|
|
126
|
-
* @param {object} [opts.nextAction] - next-action.json 解析后的对象(可选)
|
|
127
|
-
* @param {string[]} [opts.protectedFiles] - 项目自定义 protected files 列表
|
|
128
|
-
* @returns {RiskProfile}
|
|
129
|
-
*
|
|
130
|
-
* @typedef {object} RiskProfile
|
|
131
|
-
* @property {string} level - 'P0' | 'P1' | 'P2'
|
|
132
|
-
* @property {string[]} triggers - 触发的规则 ID 列表
|
|
133
|
-
* @property {object[]} assessedFrom - 评估依据
|
|
134
|
-
* @property {string[]} applyBlockers - apply 阻塞原因
|
|
135
|
-
* @property {boolean} canAutoApply - 是否可自动 apply
|
|
136
|
-
* @property {string} applyReason - apply 决策原因
|
|
137
|
-
*/
|
|
138
|
-
export function detectRiskProfile({ designContent = '', planContent = '', changedFiles = [], diffContent = '', nextAction = null, protectedFiles = [] } = {}) {
|
|
139
|
-
const triggers = []
|
|
140
|
-
const assessedFrom = []
|
|
141
|
-
|
|
142
|
-
const combined = [designContent, planContent].join('\n')
|
|
143
|
-
|
|
144
|
-
// ── P0 检测(最高优先级)──
|
|
145
|
-
|
|
146
|
-
// 基于文件路径
|
|
147
|
-
for (const rule of P0_FILE_PATTERNS) {
|
|
148
|
-
for (const file of changedFiles) {
|
|
149
|
-
if (rule.pattern.test(file)) {
|
|
150
|
-
rule.pattern.lastIndex = 0
|
|
151
|
-
if (!triggers.includes(rule.id)) {
|
|
152
|
-
triggers.push(rule.id)
|
|
153
|
-
assessedFrom.push({ source: 'file_path', pattern: rule.desc, matchedFiles: [file] })
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// 基于 diff 内容(P0 关键词)
|
|
160
|
-
const diffText = diffContent || combined
|
|
161
|
-
for (const rule of P0_CONTENT_PATTERNS) {
|
|
162
|
-
if (rule.pattern.test(diffText)) {
|
|
163
|
-
rule.pattern.lastIndex = 0
|
|
164
|
-
if (!triggers.includes(rule.id)) {
|
|
165
|
-
triggers.push(rule.id)
|
|
166
|
-
assessedFrom.push({ source: 'content', pattern: rule.desc })
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// protected files
|
|
172
|
-
for (const file of changedFiles) {
|
|
173
|
-
if (PROTECTED_FILE_NAMES.some(p => file === p || file.endsWith('/' + p))) {
|
|
174
|
-
const id = 'R-005'
|
|
175
|
-
if (!triggers.includes(id)) {
|
|
176
|
-
triggers.push(id)
|
|
177
|
-
assessedFrom.push({ source: 'protected_file', pattern: file })
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
// 用户自定义 protected files
|
|
181
|
-
if (protectedFiles.includes(file)) {
|
|
182
|
-
const id = 'R-005'
|
|
183
|
-
if (!triggers.includes(id)) {
|
|
184
|
-
triggers.push(id)
|
|
185
|
-
assessedFrom.push({ source: 'protected_file', pattern: file })
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// next-action.json 有 blocking questions
|
|
191
|
-
if (nextAction && nextAction.has_blocking_questions === true) {
|
|
192
|
-
const id = 'R-009'
|
|
193
|
-
if (!triggers.includes(id)) {
|
|
194
|
-
triggers.push(id)
|
|
195
|
-
assessedFrom.push({ source: 'brainstorm', pattern: 'has_blocking_questions === true' })
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// ── P1 检测(只在无 P0 时检测)──
|
|
200
|
-
if (!triggers.some(t => t.startsWith('R-00'))) {
|
|
201
|
-
// 基于文件路径
|
|
202
|
-
for (const rule of P1_FILE_PATTERNS) {
|
|
203
|
-
for (const file of changedFiles) {
|
|
204
|
-
if (rule.pattern.test(file)) {
|
|
205
|
-
rule.pattern.lastIndex = 0
|
|
206
|
-
if (!triggers.includes(rule.id)) {
|
|
207
|
-
triggers.push(rule.id)
|
|
208
|
-
assessedFrom.push({ source: 'file_path', pattern: rule.desc, matchedFiles: [file] })
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// 基于内容关键词
|
|
215
|
-
for (const rule of P1_CONTENT_PATTERNS) {
|
|
216
|
-
if (rule.pattern.test(combined)) {
|
|
217
|
-
rule.pattern.lastIndex = 0
|
|
218
|
-
if (!triggers.includes(rule.id)) {
|
|
219
|
-
triggers.push(rule.id)
|
|
220
|
-
assessedFrom.push({ source: 'content', pattern: rule.desc })
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// 跨模块检测(> 3 个文件的变更)
|
|
226
|
-
if (changedFiles.length > 10) {
|
|
227
|
-
const id = 'R-101'
|
|
228
|
-
if (!triggers.includes(id)) {
|
|
229
|
-
triggers.push(id)
|
|
230
|
-
assessedFrom.push({ source: 'file_count', pattern: `${changedFiles.length} files changed` })
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// next-action.json 有 NEEDS_REVIEW 决策
|
|
235
|
-
if (nextAction && Array.isArray(nextAction.auto_decisions)) {
|
|
236
|
-
for (const d of nextAction.auto_decisions) {
|
|
237
|
-
if (d.status === 'NEEDS_REVIEW') {
|
|
238
|
-
const id = 'R-102'
|
|
239
|
-
if (!triggers.includes(id)) {
|
|
240
|
-
triggers.push(id)
|
|
241
|
-
assessedFrom.push({ source: 'brainstorm', pattern: `NEEDS_REVIEW: ${d.decision}` })
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ── 判定等级 ──
|
|
249
|
-
let level = 'P2'
|
|
250
|
-
if (triggers.some(t => t.match(/^R-0\d+$/))) {
|
|
251
|
-
level = 'P0'
|
|
252
|
-
} else if (triggers.some(t => t.match(/^R-1\d+$/))) {
|
|
253
|
-
level = 'P1'
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// ── apply 决策 ──
|
|
257
|
-
const applyBlockers = []
|
|
258
|
-
if (level === 'P0') {
|
|
259
|
-
applyBlockers.push('风险等级 P0:需要用户确认后才能 apply')
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const canAutoApply = level !== 'P0' && applyBlockers.length === 0
|
|
263
|
-
const applyReason = canAutoApply
|
|
264
|
-
? `风险等级 ${level},无 P0 触发,无 protected files 修改`
|
|
265
|
-
: applyBlockers.join('; ')
|
|
266
|
-
|
|
267
|
-
return { level, triggers, assessedFrom, applyBlockers, canAutoApply, applyReason }
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// ============ 向后兼容 ============
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* 旧的 detectChangeRisk 接口保持向后兼容
|
|
274
|
-
* @returns {{ level: string, triggers: string[], requiredVerification: string[] }}
|
|
275
|
-
*/
|
|
276
74
|
// ============ 门控可执行化(A:报错说人话) ============
|
|
277
75
|
//
|
|
278
76
|
// 历史教训:integration-critical / deployment-critical 门控只报「缺少真实集成证据 / 需要真实
|
|
@@ -320,7 +118,7 @@ export const RISK_LEVEL_CAUSES = {
|
|
|
320
118
|
}
|
|
321
119
|
|
|
322
120
|
/** design.md frontmatter 可显式声明的合法 risk_level 值(与 detectChangeRisk 判级结果同集合) */
|
|
323
|
-
|
|
121
|
+
const RISK_LEVELS = ['doc-only', 'unit-sufficient', 'contract-required', 'integration-critical', 'deployment-critical']
|
|
324
122
|
|
|
325
123
|
/**
|
|
326
124
|
* 从 design.md 顶部 frontmatter 提取显式 risk_level 声明。
|