sillyspec 3.26.8 → 3.26.10
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-execute/SKILL.md +11 -3
- 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 +4 -5
- 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 +4 -3
- package/README.md +5 -9
- 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 +6 -1
- package/src/constants.js +97 -106
- package/src/db-engine.js +1 -1
- package/src/docs-check.js +34 -5
- package/src/git-helper.js +2 -2
- package/src/hooks/worktree-guard.js +1 -1
- package/src/index.js +96 -13
- package/src/init.js +14 -3
- package/src/machine-interface.js +7 -4
- package/src/progress/stage-machine.js +3 -0
- package/src/quicklog.js +203 -4
- package/src/run/command.js +101 -3
- package/src/run/complete-handlers.js +1399 -1254
- package/src/run/complete.js +9 -1
- package/src/run/concurrent-detect.js +22 -0
- package/src/run/gates.js +67 -1
- package/src/run/prompt.js +761 -713
- package/src/run/quick-audit.js +146 -137
- package/src/run/shared.js +1215 -1109
- package/src/run/stage.js +17 -1
- package/src/scan-diff.js +388 -0
- package/src/scan-staleness.js +18 -13
- package/src/spec-sync.js +319 -0
- package/src/stage-contract-spec.js +1 -1
- package/src/stage-review.js +1 -1
- package/src/stages/archive.js +2 -1
- package/src/stages/brainstorm.js +2 -2
- package/src/stages/execute.js +1003 -985
- package/src/stages/plan-postcheck.js +22 -1
- package/src/stages/plan.js +74 -40
- package/src/stages/quick.js +13 -5
- package/src/stages/scan.js +4 -3
- package/src/stages/verify.js +14 -12
- package/src/sync.js +22 -0
- package/src/task-review.js +12 -4
- package/src/workflow.js +8 -185
- package/src/worktree-apply.js +122 -0
- package/src/worktree.js +50 -6
- 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
|
|
|
@@ -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。本段为常驻摘要;以你实际收到的注入版契约为权威逐字模板。
|
|
@@ -139,7 +147,7 @@ execute 还有**第二道**独立的 stage 级审查:除逐 task review.json
|
|
|
139
147
|
|
|
140
148
|
- **主仓 task**:子代理 workdir = 主仓 worktree 路径(CLI 自动创建的隔离 worktree)。
|
|
141
149
|
- **跨仓 task**:子代理 workdir = 跨仓仓根目录(**直接在跨仓仓主干工作区改+commit,不经主仓 worktree、不建分支**)。commit 到跨仓仓主干即落盘。
|
|
142
|
-
- 同一个 Wave 内允许混合主仓 + 跨仓 task
|
|
150
|
+
- 同一个 Wave 内允许混合主仓 + 跨仓 task。默认每 task 独立子代理(各传各的 workdir,Wave prompt 注入 per-task workdir 表,按表选);同 Wave 内满足**文件正交 / 无契约链 / 组 ≤3** 三个条件的 task 可合并为一个 batch 子代理串行实现——batch 只合并实现,task 审查、review.json 产出与 checkbox 勾选仍归主 agent(子代理返回后逐 task 进行)。
|
|
143
151
|
|
|
144
152
|
### 跨仓 task 的双锡点(CLI 写入,子代理不改)
|
|
145
153
|
|
|
@@ -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,15 +52,14 @@ 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
|
|
|
60
59
|
| 参数 | 说明 |
|
|
61
60
|
|---|---|
|
|
62
61
|
| `--linked-changes none\|a,b` | **显式关联变更(取代 `--change`,推荐)**。none=不关联,a,b=关联列表 |
|
|
63
|
-
| `--files a.js,b.js` |
|
|
62
|
+
| `--files a.js,b.js` | 显式声明本次允许修改的文件(边界保护 + 声明即归属:多 agent 并发仓防他者窗口文件混入 QUICKLOG 文件行,未声明窗口文件进「审计:」行追溯) |
|
|
64
63
|
| `--file-notes "p::注 \|\| p::注"` | quick `--done` 用:QUICKLOG「文件:」行落盘为多行带括注 bullet(省事后手改文件行)。格式 `path::括注`,`\|\|` 分隔多条;**只随 step3 --done 同命令传**(CLI 短进程,step1/step2 传无效,不带到 step3) |
|
|
65
64
|
| `--allow-new` | 允许新增文件(默认禁止,防意外创建) |
|
|
66
65
|
| `--allow-delete` | 允许删除文件(默认 fail-closed,删除是破坏性操作;确认删除带此 flag 显式解锁) |
|
|
@@ -104,8 +103,8 @@ sillyspec run quick --files src/phone.ts,src/phone.test.ts
|
|
|
104
103
|
- 完成后立即 `--done`,不跳过
|
|
105
104
|
- QUICKLOG 记录的**骨架由 CLI 接管**:启动时 CLI 自动分配 ql-ID 并在 `.sillyspec/quicklog/QUICKLOG-<user>.md` 写「进行中」条目(含关联变更 tasks.md),`--done` 时 CLI 自动翻「已完成」+ 勾选 task + 回填文件路径。ql-ID 分配/状态/task 你无需手写,只需用注入的 `<quicklog-id>` 在模块文档变更索引引用
|
|
106
105
|
- **QUICKLOG 落盘已结构化(`--done` 后按需核对,多数无需手改)**:CLI 已落盘结构化条目——标题从 `--output` 的「需求:」自动提取、正文四字段自动分行、文件行用 `--file-notes` 时为多行带括注。`--done` 后只需核对:标题弱才改(禁留 `(quick 任务)` 占位);没用 `--file-notes` 时文件行是单行、可事后补括注(参照同文件早期丰富条目);正文 `需求:`/`根因:`/`方案:`/`结果:` 四段按需充实(禁只留一段「结果:」)。一条 quick = 一条独立 ql,不追加到旧条目
|
|
107
|
-
- **收尾顺序(模块文档在 `--done` 前,QUICKLOG 在 `--done` 后,别记混)**:① 命中模块→改模块文档→`git add`;② `sillyspec run quick --done --change <id> --output "四字段" [--file-notes "..."]`(CLI 自动翻完成 + 勾 task + 落盘 QUICKLOG
|
|
108
|
-
- **最后一步 `--done --output` 必须按结构化结果模板给全四字段**(逐项一句话):`需求:… 根因:… 方案:… 结果:…`。这是 QUICKLOG「结果:」归档的唯一来源;CLI 校验缺字段会拒绝 `--done`(exit 1
|
|
106
|
+
- **收尾顺序(模块文档在 `--done` 前,QUICKLOG 在 `--done` 后,别记混)**:① 命中模块→改模块文档→`git add`;② `sillyspec run quick --done --change <id> --output "四字段" [--file-notes "..."]`(CLI 自动翻完成 + 勾 task + 落盘 QUICKLOG 标题/文件/正文;若 `--linked-changes` 关联的真实变更其 tasks.md 已全部勾选,CLI 会自动将其归档到 `changes/archive/`,无需再跑 archive 阶段);③ 核对 QUICKLOG 标题(CLI 已从「需求:」提取并截断,写成短标题则无需改)→若改了再 `git add`
|
|
107
|
+
- **最后一步 `--done --output` 必须按结构化结果模板给全四字段**(逐项一句话):`需求:… 根因:… 方案:… 结果:…`。这是 QUICKLOG「结果:」归档的唯一来源;CLI 校验缺字段会拒绝 `--done`(exit 1),补全后重跑即可。**「需求:」写一句语义化短标题**(写「改了什么」,如「登录限流修复——INCR 计数误清」)——CLI 把它提取为 QUICKLOG 条目标题,截到首个标点、超 80 字截断;写完整需求长句会被截成语义不完整的状语前半段,需求背景放「根因:/方案:」。正文内避免嵌套全角冒号(如「方案:(说明 xxx):」这样标签后紧接嵌套冒号会被拆分判定缺字段),直接让标签接正文。前两个 step 的 `--output` 是中间摘要,不用此模板
|
|
109
108
|
- **禁止**在没有运行 CLI 的情况下自行决定流程
|
|
110
109
|
|
|
111
110
|
## 用户指令
|
|
@@ -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,12 +34,13 @@ 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
|
|
|
42
|
-
verify 是只读阶段(**禁止改代码/改 git 状态**,只检查 +
|
|
41
|
+
verify 是只读阶段(**禁止改代码/改 git 状态**,只检查 + 写报告)。
|
|
42
|
+
|
|
43
|
+
**diff 对账基点**:本变更走 worktree 时,「加载规范并锚定」步骤会注入 worktree 基线锚点(分支名 + `git merge-base` 真实基点)——对照设计做 diff 对账时以它为基点,**不要拿主仓当前 HEAD 当基点**(主仓在 execute 期间可能被并行推进,用错基点会把别人的演进误判为本变更越权改动)。task review 的 base/head 引用分支上的 commit,逐 task 核验用 `git diff <base>..<head>`。完成时有硬校验:
|
|
43
44
|
|
|
44
45
|
- **必须产出 `verify-result.md`**——不存在则阻断完成(不能跳过报告直接 `--done`)
|
|
45
46
|
- **结论为 `FAIL` 则阻断完成**——不能带着 FAIL 标记 verify 完成
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillyspec",
|
|
3
|
-
"version": "3.26.
|
|
3
|
+
"version": "3.26.10",
|
|
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 声明。
|
package/src/config-schema.js
CHANGED
|
@@ -150,9 +150,10 @@ export const LOCAL_YAML_SCHEMA = {
|
|
|
150
150
|
title: '文档引用校验',
|
|
151
151
|
note: 'sillyspec docs check 的扫描与断言配置(2026-08-15 docs-check-productize)。glob 相对源码仓根展开,仅支持 目录/**/*.扩展、目录/*.扩展、字面路径 三形态。',
|
|
152
152
|
keys: [
|
|
153
|
-
{ path: 'docs-check.paths', type: 'array', optional: true, status: 'live', readers: ['docs check (src/index.js case docs) + runDocsCheck (src/docs-check.js)'], desc: '扫描的文档 glob 列表,缺省 docs/**/*.md
|
|
153
|
+
{ path: 'docs-check.paths', type: 'array', optional: true, status: 'live', readers: ['docs check (src/index.js case docs) + runDocsCheck (src/docs-check.js)'], desc: '扫描的文档 glob 列表,缺省 docs/**/*.md 与 .sillyspec/docs/**/*.md(scan/modules 产物同纳入)。', example: 'docs/**/*.md' },
|
|
154
154
|
{ path: 'docs-check.skip', type: 'array', optional: true, status: 'live', readers: ['walkGlob (src/docs-check.js)'], desc: '排除的路径/glob 列表,缺省空。', example: 'docs/sillyspec/archive' },
|
|
155
155
|
{ path: 'docs-check.keywordAssert', type: 'boolean', optional: true, status: 'live', readers: ['runDocsCheck (src/docs-check.js)'], desc: '层2 关键词断言开关,缺省 true(关闭时 warning 提示仅做存在性校验)。', example: 'true' },
|
|
156
|
+
{ path: 'docs-check.living-docs', type: 'array', optional: true, status: 'live', readers: ['resolveLivingDocs (src/run/shared.js) — 经 readLocalYamlRaw 直读'], desc: '活文档监控点追加列表(docs check 之外,quick 审计的 livingDocDrift 提示也监控)。只追加不覆盖缺省集合(缺省:docs/sillyspec/platform-interface-map.md)。配了是加哨兵,不该把缺省监控点挤掉。', example: 'docs/sillyspec/architecture.md' },
|
|
156
157
|
],
|
|
157
158
|
},
|
|
158
159
|
],
|
|
@@ -295,11 +296,15 @@ auto_mode:
|
|
|
295
296
|
- fix typo
|
|
296
297
|
|
|
297
298
|
# ── 文档引用校验(sillyspec docs check;glob 相对源码仓根,三形态:递归/单层/字面路径)──
|
|
299
|
+
# 缺省已扫 docs/**/*.md 与 .sillyspec/docs/**/*.md;本段仅在需要收窄/排除时配置
|
|
298
300
|
docs-check:
|
|
299
301
|
paths:
|
|
300
302
|
- docs/**/*.md
|
|
303
|
+
- .sillyspec/docs/**/*.md
|
|
301
304
|
# skip:
|
|
302
305
|
# - docs/sillyspec/archive
|
|
303
306
|
keywordAssert: true
|
|
307
|
+
# living-docs: # 活文档监控点追加(缺省已含 docs/sillyspec/platform-interface-map.md,只加哨兵不覆盖)
|
|
308
|
+
# - docs/sillyspec/architecture.md
|
|
304
309
|
`;
|
|
305
310
|
}
|