sillyspec 3.24.4 → 3.24.6

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.
@@ -1,74 +1,74 @@
1
- ---
2
- name: sillyspec:scan
3
- description: 用于扫描项目代码库,生成架构文档、代码约定、目录结构等。适合用户说"扫描项目、分析代码库、生成文档、scan"。产出 7 份扫描文档 + 模块映射。
4
- ---
5
-
6
- ## 何时使用
7
-
8
- - 用户说"扫描项目、分析代码库、生成文档、scan"
9
- - 棕地项目首次接入 sillyspec,生成架构/约定/结构等基础文档
10
- - 产出 7 份 scan 文档(PROJECT/ARCHITECTURE/CONVENTIONS/STRUCTURE/INTEGRATIONS/TESTING/CONCERNS)+ 模块映射
11
-
12
- ## 多变更说明
13
-
14
- scan 是辅助阶段,通常不需要 `--change`。但项目有多个活跃变更时,所有 `sillyspec run` 命令加 `--change <变更名>` 可指定操作目标。
15
-
16
- ## 步骤生命周期(所有阶段通用)
17
-
18
- > `sillyspec scan` 是 `sillyspec run scan` 的顶层别名,两者等价。
19
-
20
- ```bash
21
- sillyspec run scan # 输出当前步骤 prompt
22
- sillyspec run scan --done --output "摘要" # 完成当前步骤
23
- sillyspec run scan --status # 查看阶段进度
24
- sillyspec run scan --skip # 跳过可选步骤
25
- sillyspec run scan --reset # 重置阶段(从头开始)
26
- ```
27
-
28
- ## 通用参数(所有阶段适用)
29
-
30
- | 参数 | 说明 |
31
- |---|---|
32
- | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
33
- | `--non-interactive` | CI/脚本下禁用交互式 prompt |
34
- | `--skip-approval` | 跳过审批/校验门控 |
35
- | `--json` | 输出 JSON(程序化读取) |
36
-
37
- ## scan 特有参数
38
-
39
- | 参数 | 说明 |
40
- |---|---|
41
- | `--deep` | 强制 deep 扫描 profile(完整流程,不按规模裁剪) |
42
- | `--force-rescan` | 覆盖已有 scan 文档的保护(默认覆盖需 source_commit/updated_at 匹配) |
43
-
44
- ### scanProfile(按项目规模自动裁剪)
45
-
46
- CLI 根据源码规模自动选择 profile,无需手动指定:
47
-
48
- | profile | 触发条件 | 行为 |
49
- |---|---|---|
50
- | quick | ≤30 文件 且 ≤80KB 且 ≤3 项目 | 3 步,0 子代理,5 份核心文档 |
51
- | standard | ≤200 文件 且 ≤800KB | 压缩步骤,最多 1 子代理 |
52
- | deep | 大项目或 `--deep` | 完整流程 |
53
-
54
- ### post-check
55
-
56
- scan 完成时 CLI 自动校验 7 份文档齐全。缺失会设状态为 `failed_post_check`,阻断进入主流程下游(brainstorm/plan 等),需修复后重跑 scan。
57
-
58
- ## 阶段流转
59
-
60
- ```
61
- (项目起点) → scan → brainstorm
62
- ```
63
-
64
- scan 完成后,运行 `sillyspec run brainstorm "<需求>"` 开始具体变更的设计。
65
-
66
- ## 铁律
67
-
68
- - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
69
- - 只做当前步骤 prompt 描述的操作,不跳过
70
- - scan 文档写入 `{DOCS_ROOT}/scan/`(平台模式用占位符路径,不写裸 `.sillyspec/`)
71
- - 完成后立即 `--done`,不跳过
72
-
73
- ## 用户指令
74
- $ARGUMENTS
1
+ ---
2
+ name: sillyspec:scan
3
+ description: 扫描项目代码库,生成架构文档、代码约定、目录结构。产出 7 份扫描文档 + 模块映射。
4
+ ---
5
+
6
+ ## 何时使用
7
+
8
+ - 用户说"扫描项目、分析代码库、生成文档、scan"
9
+ - 棕地项目首次接入 sillyspec,生成架构/约定/结构等基础文档
10
+ - 产出 7 份 scan 文档(PROJECT/ARCHITECTURE/CONVENTIONS/STRUCTURE/INTEGRATIONS/TESTING/CONCERNS)+ 模块映射
11
+
12
+ ## 多变更说明
13
+
14
+ scan 是辅助阶段,通常不需要 `--change`。但项目有多个活跃变更时,所有 `sillyspec run` 命令加 `--change <变更名>` 可指定操作目标。
15
+
16
+ ## 步骤生命周期(所有阶段通用)
17
+
18
+ > `sillyspec scan` 是 `sillyspec run scan` 的顶层别名,两者等价。
19
+
20
+ ```bash
21
+ sillyspec run scan # 输出当前步骤 prompt
22
+ sillyspec run scan --done --output "摘要" # 完成当前步骤
23
+ sillyspec run scan --status # 查看阶段进度
24
+ sillyspec run scan --skip # 跳过可选步骤
25
+ sillyspec run scan --reset # 重置阶段(从头开始)
26
+ ```
27
+
28
+ ## 通用参数(所有阶段适用)
29
+
30
+ | 参数 | 说明 |
31
+ |---|---|
32
+ | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
33
+ | `--non-interactive` | CI/脚本下禁用交互式 prompt |
34
+ | `--skip-approval` | 跳过审批/校验门控 |
35
+ | `--json` | 输出 JSON(程序化读取) |
36
+
37
+ ## scan 特有参数
38
+
39
+ | 参数 | 说明 |
40
+ |---|---|
41
+ | `--deep` | 强制 deep 扫描 profile(完整流程,不按规模裁剪) |
42
+ | `--force-rescan` | 覆盖已有 scan 文档的保护(默认覆盖需 source_commit/updated_at 匹配) |
43
+
44
+ ### scanProfile(按项目规模自动裁剪)
45
+
46
+ CLI 根据源码规模自动选择 profile,无需手动指定:
47
+
48
+ | profile | 触发条件 | 行为 |
49
+ |---|---|---|
50
+ | quick | ≤30 文件 且 ≤80KB 且 ≤3 项目 | 3 步,0 子代理,5 份核心文档 |
51
+ | standard | ≤200 文件 且 ≤800KB | 压缩步骤,最多 1 子代理 |
52
+ | deep | 大项目或 `--deep` | 完整流程 |
53
+
54
+ ### post-check
55
+
56
+ scan 完成时 CLI 自动校验 7 份文档齐全。缺失会设状态为 `failed_post_check`,阻断进入主流程下游(brainstorm/plan 等),需修复后重跑 scan。
57
+
58
+ ## 阶段流转
59
+
60
+ ```
61
+ (项目起点) → scan → brainstorm
62
+ ```
63
+
64
+ scan 完成后,运行 `sillyspec run brainstorm "<需求>"` 开始具体变更的设计。
65
+
66
+ ## 铁律
67
+
68
+ - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
69
+ - 只做当前步骤 prompt 描述的操作,不跳过
70
+ - scan 文档写入 `{DOCS_ROOT}/scan/`(平台模式用占位符路径,不写裸 `.sillyspec/`)
71
+ - 完成后立即 `--done`,不跳过
72
+
73
+ ## 用户指令
74
+ $ARGUMENTS
@@ -1,55 +1,55 @@
1
- ---
2
- name: sillyspec:status
3
- description: 用于查看 SillySpec 当前进度和状态。适合用户说"看下状态、当前进度、status"。显示当前阶段、步骤完成度、活跃变更。
4
- ---
5
-
6
- ## 何时使用
7
-
8
- - 用户说"看下状态、当前进度、status"
9
- - 查看项目整体进度:活跃变更、各阶段状态、步骤完成度
10
-
11
- ## status vs state 分工
12
-
13
- - **`/sillyspec:status`**(本 skill)— 查看项目整体进度(change 文件级别、各阶段状态)。走 `sillyspec run status` 阶段。
14
- - **`/sillyspec:state`** — 查看当前工作状态(阶段/步骤级别、下一步建议)。走 `sillyspec progress show`。
15
-
16
- 两者互补:status 看"有什么",state 看"在做什么"。
17
-
18
- ## 多变更说明
19
-
20
- status 是辅助阶段。项目有多个活跃变更时,加 `--change <变更名>` 查看指定变更的详情;不指定时汇总显示所有变更。
21
-
22
- ## 步骤生命周期(所有阶段通用)
23
-
24
- > `sillyspec status` 是 `sillyspec run status` 的顶层别名,两者等价。status 是辅助阶段,只读。
25
-
26
- ```bash
27
- sillyspec run status # 输出当前步骤 prompt
28
- sillyspec run status --done --output "摘要" # 完成阶段
29
- sillyspec run status --status # 查看阶段状态
30
- ```
31
-
32
- ## 通用参数(所有阶段适用)
33
-
34
- | 参数 | 说明 |
35
- |---|---|
36
- | `--change <名>` | 指定变更名(多变更时查看指定变更详情) |
37
- | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
38
- | `--json` | 输出 JSON(程序化读取) |
39
-
40
- ## 配套的只读查询命令(不经 run)
41
-
42
- ```bash
43
- sillyspec progress show # 当前工作状态(阶段/步骤级)
44
- sillyspec progress show --change <名> # 指定变更详情
45
- sillyspec progress check # 状态一致性检查
46
- ```
47
-
48
- ## 铁律
49
-
50
- - status 是只读阶段,**不修改任何文件**
51
- - **必须用 exec 工具(shell)执行 CLI**
52
- - 完成后立即 `--done`,不跳过
53
-
54
- ## 用户指令
55
- $ARGUMENTS
1
+ ---
2
+ name: sillyspec:status
3
+ description: 查看 SillySpec 当前进度与状态:当前阶段、步骤完成度、活跃变更。
4
+ ---
5
+
6
+ ## 何时使用
7
+
8
+ - 用户说"看下状态、当前进度、status"
9
+ - 查看项目整体进度:活跃变更、各阶段状态、步骤完成度
10
+
11
+ ## status vs state 分工
12
+
13
+ - **`/sillyspec:status`**(本 skill)— 查看项目整体进度(change 文件级别、各阶段状态)。走 `sillyspec run status` 阶段。
14
+ - **`/sillyspec:state`** — 查看当前工作状态(阶段/步骤级别、下一步建议)。走 `sillyspec progress show`。
15
+
16
+ 两者互补:status 看"有什么",state 看"在做什么"。
17
+
18
+ ## 多变更说明
19
+
20
+ status 是辅助阶段。项目有多个活跃变更时,加 `--change <变更名>` 查看指定变更的详情;不指定时汇总显示所有变更。
21
+
22
+ ## 步骤生命周期(所有阶段通用)
23
+
24
+ > `sillyspec status` 是 `sillyspec run status` 的顶层别名,两者等价。status 是辅助阶段,只读。
25
+
26
+ ```bash
27
+ sillyspec run status # 输出当前步骤 prompt
28
+ sillyspec run status --done --output "摘要" # 完成阶段
29
+ sillyspec run status --status # 查看阶段状态
30
+ ```
31
+
32
+ ## 通用参数(所有阶段适用)
33
+
34
+ | 参数 | 说明 |
35
+ |---|---|
36
+ | `--change <名>` | 指定变更名(多变更时查看指定变更详情) |
37
+ | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
38
+ | `--json` | 输出 JSON(程序化读取) |
39
+
40
+ ## 配套的只读查询命令(不经 run)
41
+
42
+ ```bash
43
+ sillyspec progress show # 当前工作状态(阶段/步骤级)
44
+ sillyspec progress show --change <名> # 指定变更详情
45
+ sillyspec progress check # 状态一致性检查
46
+ ```
47
+
48
+ ## 铁律
49
+
50
+ - status 是只读阶段,**不修改任何文件**
51
+ - **必须用 exec 工具(shell)执行 CLI**
52
+ - 完成后立即 `--done`,不跳过
53
+
54
+ ## 用户指令
55
+ $ARGUMENTS
@@ -1,82 +1,82 @@
1
- ---
2
- name: sillyspec:verify
3
- description: 用于验证代码实现是否符合 design 和模块文档。适合用户说"验证下、检查下、跑 verify"。对照 design.md + 模块文档检查任务完成度、设计一致性、运行测试。
4
- ---
5
-
6
- ## 何时使用
7
-
8
- - 用户说"验证下、检查下、跑 verify"
9
- - 对照 design.md + 模块文档检查任务完成度
10
- - 设计一致性检查 + 运行测试套件
11
- - 产出 `verify-result.md`(PASS / PASS WITH NOTES / FAIL)
12
-
13
- ## 多变更说明
14
-
15
- 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。
16
-
17
- ## 步骤生命周期(所有阶段通用)
18
-
19
- > `sillyspec verify` 是 `sillyspec run verify` 的顶层别名,两者等价。
20
-
21
- ```bash
22
- sillyspec run verify # 输出当前步骤 prompt
23
- sillyspec run verify --done --output "摘要" # 完成当前步骤(--input "用户原话" 记录输入)
24
- sillyspec run verify --status # 查看阶段进度
25
- sillyspec run verify --skip # 跳过可选步骤
26
- sillyspec run verify --reset # 重置阶段(从头开始)
27
- sillyspec run verify --reopen --from-step N # 重新打开已完成阶段修订(N=序号或名称)
28
- ```
29
-
30
- ## 通用参数(所有阶段适用)
31
-
32
- | 参数 | 说明 |
33
- |---|---|
34
- | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
35
- | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
36
- | `--non-interactive` | CI/脚本下禁用交互式 prompt |
37
- | `--skip-approval` | 跳过审批/校验门控(需明确意图) |
38
- | `--json` | 输出 JSON(程序化读取) |
39
-
40
- ## verify 特有:完成门控(重要)
41
-
42
- verify 是只读阶段(**禁止改代码/改 git 状态**,只检查 + 写报告)。完成时有硬校验:
43
-
44
- - **必须产出 `verify-result.md`**——不存在则阻断完成(不能跳过报告直接 `--done`)
45
- - **结论为 `FAIL` 则阻断完成**——不能带着 FAIL 标记 verify 完成
46
- - **`integration-critical` / `deployment-critical` 变更**(design/plan 含 daemon/session/lease/lifecycle 等关键词):结论 PASS WITH NOTES 降级为 FAIL,必须有真实集成证据(Runtime Evidence section)
47
- - `verify-required-evidence.json`(execute 写入)中每条 missing evidence → 阻断
48
-
49
- 被阻断时 CLI 打印 ❌ 校验失败,不会提示"验证通过"。修复 `verify-result.md` 后重新 `--done`。
50
-
51
- ## verify-result.md 格式
52
-
53
- ```markdown
54
- # 验证报告
55
- ## 结论
56
- PASS / PASS WITH NOTES / FAIL ← 必须有结论章节(标题含"结论/Conclusion/Result/结果"即可,不限于确切"## 结论"),FAIL 会阻断 verify 完成
57
- ## 任务完成度
58
- ## 设计一致性
59
- ## 探针结果
60
- ## 测试结果
61
- ## 变更风险等级
62
- ## Runtime Evidence(integration/deployment-critical 必填)
63
- ```
64
-
65
- ## 阶段流转
66
-
67
- ```
68
- execute → verify → archive
69
- ```
70
-
71
- verify 通过(PASS)后,运行 `sillyspec run archive --change <变更名>` 归档。FAIL 则修复后重跑 `sillyspec run verify`。
72
-
73
- ## 铁律
74
-
75
- - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
76
- - verify 阶段**绝对禁止** git checkout/restore/reset、删除/覆盖源码文件——只检查 + 报告
77
- - 发现问题只报告,不尝试修复(修复回 execute)
78
- - `verify-result.md` 结论必须基于证据,不写"看起来没问题"
79
- - 完成后立即 `--done`,不跳过
80
-
81
- ## 用户指令
82
- $ARGUMENTS
1
+ ---
2
+ name: sillyspec:verify
3
+ description: 验证代码实现是否符合 design 与模块文档。对照 design.md + 模块文档检查任务完成度、设计一致性、运行测试。
4
+ ---
5
+
6
+ ## 何时使用
7
+
8
+ - 用户说"验证下、检查下、跑 verify"
9
+ - 对照 design.md + 模块文档检查任务完成度
10
+ - 设计一致性检查 + 运行测试套件
11
+ - 产出 `verify-result.md`(PASS / PASS WITH NOTES / FAIL)
12
+
13
+ ## 多变更说明
14
+
15
+ 项目有多个活跃变更(`.sillyspec/changes/` 下有多个目录)时,所有 `sillyspec run` 命令需加 `--change <变更名>` 指定操作目标;只有一个变更时可省略(CLI 自动检测)。
16
+
17
+ ## 步骤生命周期(所有阶段通用)
18
+
19
+ > `sillyspec verify` 是 `sillyspec run verify` 的顶层别名,两者等价。
20
+
21
+ ```bash
22
+ sillyspec run verify # 输出当前步骤 prompt
23
+ sillyspec run verify --done --output "摘要" # 完成当前步骤(--input "用户原话" 记录输入)
24
+ sillyspec run verify --status # 查看阶段进度
25
+ sillyspec run verify --skip # 跳过可选步骤
26
+ sillyspec run verify --reset # 重置阶段(从头开始)
27
+ sillyspec run verify --reopen --from-step N # 重新打开已完成阶段修订(N=序号或名称)
28
+ ```
29
+
30
+ ## 通用参数(所有阶段适用)
31
+
32
+ | 参数 | 说明 |
33
+ |---|---|
34
+ | `--change <名>` | 指定变更名(多活跃变更必填,单变更可省略自动检测) |
35
+ | `--spec-dir <path>` | 指定规范目录(默认 `<项目>/.sillyspec`) |
36
+ | `--non-interactive` | CI/脚本下禁用交互式 prompt |
37
+ | `--skip-approval` | 跳过审批/校验门控(需明确意图) |
38
+ | `--json` | 输出 JSON(程序化读取) |
39
+
40
+ ## verify 特有:完成门控(重要)
41
+
42
+ verify 是只读阶段(**禁止改代码/改 git 状态**,只检查 + 写报告)。完成时有硬校验:
43
+
44
+ - **必须产出 `verify-result.md`**——不存在则阻断完成(不能跳过报告直接 `--done`)
45
+ - **结论为 `FAIL` 则阻断完成**——不能带着 FAIL 标记 verify 完成
46
+ - **`integration-critical` / `deployment-critical` 变更**(design/plan 含 daemon/session/lease/lifecycle 等关键词):结论 PASS WITH NOTES 降级为 FAIL,必须有真实集成证据(Runtime Evidence section)
47
+ - `verify-required-evidence.json`(execute 写入)中每条 missing evidence → 阻断
48
+
49
+ 被阻断时 CLI 打印 ❌ 校验失败,不会提示"验证通过"。修复 `verify-result.md` 后重新 `--done`。
50
+
51
+ ## verify-result.md 格式
52
+
53
+ ```markdown
54
+ # 验证报告
55
+ ## 结论
56
+ PASS / PASS WITH NOTES / FAIL ← 必须有结论章节(标题含"结论/Conclusion/Result/结果"即可,不限于确切"## 结论"),FAIL 会阻断 verify 完成
57
+ ## 任务完成度
58
+ ## 设计一致性
59
+ ## 探针结果
60
+ ## 测试结果
61
+ ## 变更风险等级
62
+ ## Runtime Evidence(integration/deployment-critical 必填)
63
+ ```
64
+
65
+ ## 阶段流转
66
+
67
+ ```
68
+ execute → verify → archive
69
+ ```
70
+
71
+ verify 通过(PASS)后,运行 `sillyspec run archive --change <变更名>` 归档。FAIL 则修复后重跑 `sillyspec run verify`。
72
+
73
+ ## 铁律
74
+
75
+ - **必须用 exec 工具(shell)执行 CLI,不要自己编造流程**
76
+ - verify 阶段**绝对禁止** git checkout/restore/reset、删除/覆盖源码文件——只检查 + 报告
77
+ - 发现问题只报告,不尝试修复(修复回 execute)
78
+ - `verify-result.md` 结论必须基于证据,不写"看起来没问题"
79
+ - 完成后立即 `--done`,不跳过
80
+
81
+ ## 用户指令
82
+ $ARGUMENTS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.24.4",
3
+ "version": "3.24.6",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
package/src/db.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import initSqlJs from 'sql.js';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
- import { dirname } from 'path';
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'fs';
3
+ import { basename, dirname, join } from 'path';
4
4
 
5
5
  export class DB {
6
6
  constructor(dbPath) {
@@ -16,13 +16,8 @@ export class DB {
16
16
  // 2. 初始化 sql.js
17
17
  const SQL = await initSqlJs();
18
18
 
19
- // 3. 加载已有数据库或创建新库
20
- if (existsSync(this.dbPath)) {
21
- const buf = readFileSync(this.dbPath);
22
- this.db = new SQL.Database(buf);
23
- } else {
24
- this.db = new SQL.Database();
25
- }
19
+ // 3. 加载已有数据库(含损坏检测 + .bak 回滚)或创建新库
20
+ this.db = this._loadDatabase(SQL);
26
21
 
27
22
  // 4. 设置 PRAGMA
28
23
  this.db.run('PRAGMA journal_mode = WAL');
@@ -64,12 +59,17 @@ export class DB {
64
59
  return this.db;
65
60
  }
66
61
 
67
- /** 将内存中的数据库持久化到磁盘 */
62
+ /**
63
+ * 将内存中的数据库持久化到磁盘(原子写 + 保留上一版 .bak 备份)。
64
+ * 任何时刻磁盘上的主库要么是完整的上一版、要么是完整的新版,
65
+ * 不会出现写入中途崩溃导致的截断态。sql.js 是纯内存库,
66
+ * PRAGMA journal_mode=WAL 对它无意义——真正的持久化原子性在这里保证。
67
+ */
68
68
  _save() {
69
69
  if (!this.db) return;
70
70
  const data = this.db.export();
71
71
  const buffer = Buffer.from(data);
72
- writeFileSync(this.dbPath, buffer);
72
+ this._atomicWriteSync(this.dbPath, buffer);
73
73
  // sql.js 的 export() 会重置 PRAGMA 状态,需要重新设置
74
74
  this.db.run('PRAGMA journal_mode = WAL');
75
75
  this.db.run('PRAGMA busy_timeout = 5000');
@@ -77,6 +77,103 @@ export class DB {
77
77
  this.db.run('PRAGMA synchronous = NORMAL');
78
78
  }
79
79
 
80
+ /**
81
+ * 原子落盘:同目录 tmp 写入 → 旧主库改名 .bak → tmp 改名为主库。
82
+ * 三步保证主库永不处于半写状态;.bak 始终保留上一完整版本。
83
+ * Windows 上 rename 偶发 EPERM/EBUSY(杀毒/索引扫描占文件),带短退避重试。
84
+ */
85
+ _atomicWriteSync(filePath, buffer) {
86
+ const dir = dirname(filePath);
87
+ const tmpPath = join(dir, `${basename(filePath)}.tmp`);
88
+ const bakPath = `${filePath}.bak`;
89
+ // 1. 完整内容先落 tmp(同目录,保证 rename 不跨卷)
90
+ writeFileSync(tmpPath, buffer);
91
+ // 2. 旧主库 → .bak(保留上一版;不存在则跳过;失败不阻断主流程)
92
+ if (existsSync(filePath)) {
93
+ try { this._renameSyncRetry(filePath, bakPath); }
94
+ catch { /* 备份失败最坏只是少一个 .bak,主库仍会被新内容替换 */ }
95
+ }
96
+ // 3. tmp → 主库(成功后主库即新版完整态)
97
+ this._renameSyncRetry(tmpPath, filePath);
98
+ }
99
+
100
+ /** rename 带退避重试,覆盖 Windows 偶发的 EPERM/EBUSY/EACCES/ENOTEMPTY。 */
101
+ _renameSyncRetry(from, to, retries = 5) {
102
+ let lastErr;
103
+ for (let attempt = 0; attempt <= retries; attempt++) {
104
+ try {
105
+ renameSync(from, to);
106
+ return;
107
+ } catch (err) {
108
+ lastErr = err;
109
+ const code = err && err.code;
110
+ if (!code || !['EPERM', 'EBUSY', 'EACCES', 'ENOTEMPTY'].includes(code)) throw err;
111
+ if (attempt < retries) this._sleepSync(15 * (attempt + 1));
112
+ }
113
+ }
114
+ throw lastErr;
115
+ }
116
+
117
+ /** 同步退避:优先 Atomics.wait,不可用时退化为忙等(仅在 rename 冲突的极端情况触发)。 */
118
+ _sleepSync(ms) {
119
+ try {
120
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
121
+ } catch {
122
+ const end = Date.now() + ms;
123
+ while (Date.now() < end) { /* busy wait */ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * 加载数据库:主库 → .bak → 报错/空库,逐级回退。
129
+ * - 主库可正常打开:直接用。
130
+ * - 主库缺失/为空/损坏:尝试 .bak;成功则 warn 后用 .bak。
131
+ * - 主库与 .bak 都不可用:
132
+ * · 全新项目(两者都不存在):创建空库;
133
+ * · 主库曾存在却无法恢复(截断/损坏且无可用备份):fail-loud 抛错,
134
+ * 不静默建空库——那会无声吞掉用户所有进度。
135
+ */
136
+ _loadDatabase(SQL) {
137
+ const bakPath = `${this.dbPath}.bak`;
138
+ // readValid: null=文件不存在;'empty'=存在但 0 字节;Buffer=有内容
139
+ const readValid = (p) => {
140
+ if (!existsSync(p)) return null;
141
+ const b = readFileSync(p);
142
+ return (b && b.length > 0) ? b : 'empty';
143
+ };
144
+ const tryOpen = (raw) => {
145
+ if (!raw || raw === 'empty') return null;
146
+ let instance;
147
+ try { instance = new SQL.Database(raw); }
148
+ catch { return null; }
149
+ // 双保险:损坏 buffer 偶尔被 sql.js 静默打开为空库,用一次查询确认它真能用
150
+ try { instance.exec('SELECT count(*) FROM sqlite_master'); return instance; }
151
+ catch { try { instance.close(); } catch {} return null; }
152
+ };
153
+
154
+ const primaryRaw = readValid(this.dbPath);
155
+ const primaryDb = tryOpen(primaryRaw);
156
+ if (primaryDb) return primaryDb;
157
+
158
+ const bakDb = tryOpen(readValid(bakPath));
159
+ if (bakDb) {
160
+ const reason = primaryRaw === 'empty'
161
+ ? 'sillyspec.db 为空(可能被截断)'
162
+ : primaryRaw === null
163
+ ? 'sillyspec.db 不存在'
164
+ : 'sillyspec.db 损坏';
165
+ console.warn(`⚠️ ${reason},已从 .bak 备份恢复。`);
166
+ return bakDb;
167
+ }
168
+
169
+ if (primaryRaw === null) {
170
+ // 主库与备份都不存在 → 全新项目
171
+ return new SQL.Database();
172
+ }
173
+ // 主库曾存在(空/损坏)但无法恢复 → 数据丢失,必须 fail-loud
174
+ throw new Error('sillyspec.db 损坏且 .bak 备份不可用,无法恢复。请从版本控制或其他备份恢复 .sillyspec/.runtime/ 后重试。');
175
+ }
176
+
80
177
  _createSchema() {
81
178
  // project 表
82
179
  this.db.run(`