sillyspec 3.15.2 → 3.16.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.
@@ -0,0 +1,13 @@
1
+ #!/bin/sh
2
+ echo "🔧 pre-push: running lint + test..."
3
+ npm run lint --silent 2>&1
4
+ if [ $? -ne 0 ]; then
5
+ echo "❌ pre-push: lint failed, aborting push"
6
+ exit 1
7
+ fi
8
+ npm test --silent 2>&1
9
+ if [ $? -ne 0 ]; then
10
+ echo "❌ pre-push: test failed, aborting push"
11
+ exit 1
12
+ fi
13
+ echo "✅ pre-push: lint + test passed"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  author: qinyi
3
3
  created_at: 2026-05-31 11:00:00
4
- updated_at: 2026-06-03 16:00:00
4
+ updated_at: 2026-06-04 13:20:00
5
5
  ---
6
6
 
7
7
  # SillySpec 文件生命周期描述
@@ -75,6 +75,63 @@ updated_at: 2026-06-03 16:00:00
75
75
  └── codebase/SCAN-RAW.md ← 原始扫描数据(gitignore)
76
76
  ```
77
77
 
78
+ ## 1.1 平台模式目录结构(SillyHub 等)
79
+
80
+ > 当通过 `--spec-root` 和 `--runtime-root` 参数调用时,SillySpec 进入平台模式。
81
+ > `--spec-root` 的语义是 **SillySpec Storage Root**(替代 `.sillyspec/` 的位置),不是 scan docs root。
82
+
83
+ **调用方式:**
84
+ ```shell
85
+ sillyspec run scan --spec-root <storage-root> --runtime-root <runtime-root> --workspace-id <id> --scan-run-id <id>
86
+ ```
87
+
88
+ **平台模式输出结构:**
89
+ ```
90
+ <spec-root>/
91
+ └── .sillyspec/ ← SillySpec storage root(由 --spec-root 指定)
92
+ ├── docs/ ← 扫描文档(scan/modules/flows/glossary)
93
+ │ └── <project>/
94
+ │ ├── scan/
95
+ │ ├── modules/
96
+ │ ├── flows/
97
+ │ └── glossary.md
98
+ ├── projects/ ← 项目注册表
99
+ ├── workflows/ ← workflow 定义
100
+ ├── knowledge/ ← 知识库
101
+ ├── local.yaml ← 本地配置
102
+ └── manifest.json ← 平台模式元数据
103
+
104
+ <runtime-root>/
105
+ └── scan-runs/
106
+ └── <scan-run-id>/ ← 单次 scan 的运行时产物
107
+ ```
108
+
109
+ **路径映射:**
110
+ | 本地模式路径 | 平台模式路径 |
111
+ |-------------|-------------|
112
+ | `.sillyspec/docs/` | `<spec-root>/.sillyspec/docs/` |
113
+ | `.sillyspec/projects/` | `<spec-root>/.sillyspec/projects/` |
114
+ | `.sillyspec/workflows/` | `<spec-root>/.sillyspec/workflows/` |
115
+ | `.sillyspec/knowledge/` | `<spec-root>/.sillyspec/knowledge/` |
116
+ | `.sillyspec/.runtime/` | `<runtime-root>/` |
117
+ | `.sillyspec/.runtime/artifacts/` | `<runtime-root>/scan-runs/<scan-run-id>/` |
118
+ | `.sillyspec/.runtime/workflow-runs/` | `<runtime-root>/scan-runs/<scan-run-id>/workflow-runs/` |
119
+
120
+ **不传参数时,行为与本地模式完全一致。**
121
+
122
+ **路径占位符机制:**
123
+ scan.js 模板中使用 `{DOCS_ROOT}` 和 `{PROJECTS_ROOT}` 占位符,由 `run.js` 的 `outputStep` 在输出 prompt 前替换:
124
+
125
+ | 占位符 | 本地模式 | 平台模式 |
126
+ |--------|---------|---------|
127
+ | `{DOCS_ROOT}` | `cwd/.sillyspec/docs/<project>` | `<spec-root>/.sillyspec/docs/<project>` |
128
+ | `{PROJECTS_ROOT}` | `cwd/.sillyspec/projects` | `<spec-root>/.sillyspec/projects` |
129
+
130
+ > 这确保 agent 看到的 prompt 中路径已替换为真实路径,不会因为模板硬编码而写错位置。
131
+
132
+ ---
133
+
134
+
78
135
  ## 2. 全局状态文件
79
136
 
80
137
  ### `sillyspec.db` — SQLite 数据库(全局状态与变更进度)
@@ -96,7 +153,7 @@ updated_at: 2026-06-03 16:00:00
96
153
  | 表 | 用途 | 对应旧版 |
97
154
  |------|------|------|
98
155
  | `project` | 项目名、schema 版本 | `global.json` 的 `_version` + `project` |
99
- | `changes` | 变更注册表(名称、当前阶段、状态) | `global.json` 的 `activeChanges` + `progress.json` 的顶层字段 |
156
+ | `changes` | 变更注册表(名称、当前阶段、状态、isolation) | `global.json` 的 `activeChanges` + `progress.json` 的顶层字段 |
100
157
  | `stages` | 阶段状态(status / startedAt / completedAt) | `progress.json` 的 `stages.*` |
101
158
  | `steps` | 步骤状态(name / status / output) | `progress.json` 的 `stages.*.steps[]` |
102
159
  | `batch_progress` | 批量进度统计 | `progress.json` 的 `batchProgress` |
@@ -165,6 +222,9 @@ updated_at: 2026-06-03 16:00:00
165
222
  | `currentChange` | `changes.name` | 当前变更名 |
166
223
  | `lastActive` | `changes.last_active` | 最后活跃时间 |
167
224
  | `noWorktree` | `changes.no_worktree` | 跳过 worktree 标记 |
225
+ | `isolation_status` | `changes.isolation_status` | 隔离状态:pending/verified/degraded/blocked |
226
+ | `isolation_mode` | `changes.isolation_mode` | 隔离模式:worktree/native-worktree/in-place-fallback |
227
+ | `isolation_reason` | `changes.isolation_reason` | blocked/degraded 时的原因说明 |
168
228
  | `stages.<name>.status` | `stages.status` | 阶段状态 |
169
229
  | `stages.<name>.startedAt` | `stages.started_at` | 阶段开始时间 |
170
230
  | `stages.<name>.completedAt` | `stages.completed_at` | 阶段完成时间 |
@@ -517,7 +577,11 @@ PASS / PASS WITH NOTES / FAIL
517
577
 
518
578
  **创建时机:** scan 阶段的深度扫描步骤
519
579
 
520
- **存储位置:** `.sillyspec/docs/<project>/scan/`
580
+ **存储位置:**
581
+ - 本地模式:`.sillyspec/docs/<project>/scan/`
582
+ - 平台模式:`<spec-root>/.sillyspec/docs/<project>/scan/`
583
+
584
+ > scan.js 模板中使用 `{DOCS_ROOT}` 占位符,由 `outputStep` 在运行时替换为实际路径。平台模式通过 `--spec-root` 参数指定 storage root。
521
585
 
522
586
  **七份文档:**
523
587
  | 文件 | 生成步骤 | 内容 |
@@ -661,7 +725,11 @@ modules:
661
725
  "baseHash": "abc123...",
662
726
  "actualBaseHash": "def456...",
663
727
  "createdAt": "2026-05-28T14:00:00.000Z",
664
- "worktreePath": "/path/to/.sillyspec/.runtime/worktrees/..."
728
+ "worktreePath": "/path/to/.sillyspec/.runtime/worktrees/...",
729
+ "mode": "worktree",
730
+ "baselineFiles": [],
731
+ "baselineCommit": null,
732
+ "baselineHash": null
665
733
  }
666
734
  ```
667
735
 
@@ -675,6 +743,10 @@ modules:
675
743
  | `actualBaseHash` | string | fetch+merge 后的实际 HEAD(可能与 baseHash 不同) |
676
744
  | `createdAt` | string | ISO 时间戳 |
677
745
  | `worktreePath` | string | worktree 目录绝对路径 |
746
+ | `mode` | string | 隔离模式:`worktree` / `native-worktree` / `in-place-fallback` |
747
+ | `baselineFiles` | string[] | dirty baseline overlay 的文件列表 |
748
+ | `baselineCommit` | string? | baseline checkpoint commit hash |
749
+ | `baselineHash` | string? | baseline snapshot hash |
678
750
 
679
751
  **特殊行为:** 创建后自动 `fetch origin` + `merge origin/main --ff-only` 同步远程最新代码,`actualBaseHash` 记录 merge 后的实际 HEAD。如果本地和远程没有共同祖先则跳过 merge。
680
752
 
@@ -807,7 +879,45 @@ test_strategy: module
807
879
 
808
880
  ---
809
881
 
810
- ## 8. 文件销毁与归档
882
+ ## 8. manifest.json — 平台模式元数据
883
+
884
+ **创建时机:** 平台模式 scan 阶段全部步骤完成后,由 `runStage()` 自动生成
885
+
886
+ **存储位置:** `<spec-root>/.sillyspec/manifest.json`
887
+
888
+ **触发条件:** `sillyspec run scan --spec-root <path>` 传入时
889
+
890
+ **数据结构:**
891
+ ```json
892
+ {
893
+ "workspace_id": "ws-001",
894
+ "scan_run_id": "scan-20260604-103000",
895
+ "source_commit": "abc123def456...",
896
+ "generated_at": "2026-06-04T10:30:00.000+08:00",
897
+ "schema_version": 1
898
+ }
899
+ ```
900
+
901
+ **字段说明:**
902
+ | 字段 | 类型 | 说明 |
903
+ |------|------|------|
904
+ | `workspace_id` | string? | SillyHub workspace ID(来自 `--workspace-id`) |
905
+ | `scan_run_id` | string? | 本次 scan run ID(来自 `--scan-run-id`) |
906
+ | `source_commit` | string? | 被扫描源码的 git HEAD(非 git 目录时为 null,summary 会提示) |
907
+ | `generated_at` | string | ISO 时间戳 |
908
+ | `schema_version` | integer | manifest 格式版本,当前为 1 |
909
+
910
+ **写入方:** `runStage()` 中 scan 阶段完成回调(使用 `platformOpts.specRoot` 解析路径)
911
+
912
+ **后置校验:** scan 完成后检查 source_root(cwd)的 `.sillyspec/docs/` 是否被意外污染,发现时输出 warning。
913
+
914
+ **读取方:** SillyHub 后端读取 workspace spec 信息
915
+
916
+ **生命周期:** 每次 scan run 完成时覆盖写入(同 workspace 下只保留最新一次)
917
+
918
+ ---
919
+
920
+ ## 9. 文件销毁与归档
811
921
 
812
922
  ### 变更归档流程
813
923
 
@@ -821,12 +931,32 @@ test_strategy: module
821
931
 
822
932
  ### Worktree 清理流程
823
933
 
824
- **触发:** `WorktreeManager.cleanup()`
934
+ **触发:** `WorktreeManager.cleanup(changeName)`
825
935
 
826
- **操作:**
936
+ **安全检查(mode-based):**
937
+
938
+ | mode | 行为 | 结果 |
939
+ |------|------|------|
940
+ | `worktree`(SillySpec 创建) | 执行删除 | `cleaned` |
941
+ | `native-worktree`(外部隔离) | **拒绝删除**(除非 `--force`) | 抛错 / `kept` |
942
+ | `in-place-fallback`(降级) | 跳过,无目录可删 | `skipped` / `none` |
943
+ | unknown / missing | 不删除 | `kept` |
944
+
945
+ **操作(mode=worktree 时):**
827
946
  1. `git worktree remove <path> --force`
828
- 2. `git branch -D <branch>`(忽略分支不存在错误)
829
- 3. 确保目录已删除
947
+ 2. 确保目录已删除(fallback rmSync)
948
+ 3. `git branch -D <branch>`(忽略分支不存在错误)
949
+ 4. 清除 meta 目录
950
+ 5. 返回 `{ result: 'cleaned', mode }`
951
+
952
+ **执行摘要 Worktree 状态:**
953
+
954
+ | 场景 | 摘要显示 |
955
+ |------|----------|
956
+ | worktree 目录已被删除 | `Worktree: cleaned` |
957
+ | native-worktree 保留 | `Worktree: kept (external worktree)` |
958
+ | in-place-fallback | `Worktree: none (in-place)` |
959
+ | worktree 仍存在 | `Worktree: exists` |
830
960
 
831
961
  ### 数据存储迁移
832
962
 
@@ -859,7 +989,7 @@ test_strategy: module
859
989
 
860
990
  ---
861
991
 
862
- ## 9. 可选 / 条件生成文件
992
+ ## 10. 可选 / 条件生成文件
863
993
 
864
994
  以下文件不一定存在,在特定条件下才会生成。
865
995
 
@@ -117,9 +117,11 @@ allowWrite = stageGate && locationGate && fileGate
117
117
  | **`--no-worktree` 标志** | 跳过隔离创建,但 hook 仍然拦截源码写入 |
118
118
  | **`SILLYSPEC_DISABLE_HOOKS=1`** | 紧急禁用所有 hook,全部放行 |
119
119
  | **无 gate-status.json** | stageGate=false,默认禁止源码写入 |
120
- | **worktree 创建失败** | 报错停止,不进入无隔离状态 |
120
+ | **worktree 创建失败** | 自动降级为 in-place + baseline protection(mode: in-place-fallback) |
121
+ | **已在 linked worktree** | 复用当前目录(mode: native-worktree) |
122
+ | **worktree 目录未 ignore** | 阻断创建,提示修复 |
121
123
 
122
- > ⚠️ 不存在"降级到放行"的路径。只有"降级到更严格"或"紧急逃生开关"。设计原则是默认安全。
124
+ > ⚠️ in-place 降级模式仍会记录 baseline(baselineFiles + baselineHash + baselineCommit),hook 拦截继续生效,不会无保护地直接写源码。
123
125
 
124
126
  ## 多 Agent 并行使用
125
127
 
@@ -146,6 +148,59 @@ sillyspec worktree create feature-ui
146
148
  | `SILLYSPEC_DISABLE_HOOKS` | 设为 `1` 时禁用所有 hook(紧急逃生) |
147
149
  | `SILLYSPEC_WORKTREE_DIR` | 自定义 worktree 存储目录(默认 `.sillyspec/.runtime/worktrees/`) |
148
150
 
151
+ ## 环境隔离检测
152
+
153
+ SillySpec 在创建 worktree 前会自动检测当前环境的隔离状态:
154
+
155
+ ### submodule 防护
156
+
157
+ 使用 `git rev-parse --git-dir` 和 `--git-common-dir` 判断是否在 linked worktree 中。
158
+ 同时用 `--show-superproject-working-tree` 排除 git submodule 的误判:
159
+
160
+ ```
161
+ if GIT_DIR != GIT_COMMON && 无 superproject:
162
+ → 已在 linked worktree,复用当前隔离环境
163
+ if 无 superproject 为空:
164
+ → 在 git submodule 内,阻断创建并提示
165
+ else:
166
+ → 在主仓库中,正常创建 worktree
167
+ ```
168
+
169
+ ### .gitignore 强制校验
170
+
171
+ worktree 存储目录 `.sillyspec/.runtime/worktrees/` 必须被 `.gitignore` 忽略:
172
+
173
+ - **init / doctor 阶段:** 预检查并提示修复
174
+ - **execute 阶段:** 未 ignore 则直接阻断 worktree 创建,抛出明确错误
175
+ - **不会自动修改 .gitignore:** 避免污染 baseline
176
+
177
+ 修复方式:在项目 `.gitignore` 中添加:
178
+ ```
179
+ .sillyspec/.runtime/worktrees/
180
+ ```
181
+
182
+ ### isolation 状态
183
+
184
+ worktree create 后,isolation 状态写入 `sillyspec.db` 的 `changes` 表(权威状态源):
185
+
186
+ | 字段 | 说明 |
187
+ |------|------|
188
+ | `isolation_status` | pending / verified / degraded / blocked |
189
+ | `isolation_mode` | worktree / native-worktree / in-place-fallback / null |
190
+ | `isolation_reason` | blocked 或 degraded 时的原因说明 |
191
+
192
+ 状态映射:
193
+
194
+ | status | mode | 触发条件 |
195
+ |--------|------|----------|
196
+ | verified | worktree | `git worktree add` 成功 |
197
+ | verified | native-worktree | 已在 linked worktree,复用 |
198
+ | degraded | in-place-fallback | `git worktree add` 失败,降级 in-place |
199
+ | blocked | null | submodule 内 / gitignore 未配置 |
200
+ | null | null | 尚未执行隔离检查 |
201
+
202
+ > ⚠️ isolation 的权威来源是 sillyspec.db。meta.json 中的 mode 仅作为 worktree 目录的运行时元信息。gate-status.json 不存储 isolation。
203
+
149
204
  ## 常见问题和故障排除
150
205
 
151
206
  ### worktree 残留无法清理
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.15.2",
3
+ "version": "3.16.0",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -23,6 +23,10 @@
23
23
  "spec-driven"
24
24
  ],
25
25
  "license": "MIT",
26
+ "scripts": {
27
+ "test": "node test/*.test.mjs",
28
+ "lint": "node --check src/index.js && node --check src/run.js && node --check src/progress.js && node --check src/db.js && node --check src/worktree.js && node --check src/workflow.js && node --check src/worktree-apply.js"
29
+ },
26
30
  "dependencies": {
27
31
  "@inquirer/prompts": "^7.10.1",
28
32
  "chalk": "^5.6.2",
package/src/db.js CHANGED
@@ -164,5 +164,22 @@ export class DB {
164
164
  this.db.run('CREATE INDEX IF NOT EXISTS idx_changes_status ON changes(status)');
165
165
  this.db.run('CREATE INDEX IF NOT EXISTS idx_stages_change ON stages(change_id)');
166
166
  this.db.run('CREATE INDEX IF NOT EXISTS idx_steps_stage ON steps(stage_id)');
167
+
168
+ // Migration: add isolation columns to changes table (idempotent)
169
+ this._migrateAddColumn('changes', 'isolation_status', 'TEXT');
170
+ this._migrateAddColumn('changes', 'isolation_mode', 'TEXT');
171
+ this._migrateAddColumn('changes', 'isolation_reason', 'TEXT');
172
+ }
173
+
174
+ /**
175
+ * 幂等地给表添加列(列已存在则跳过)
176
+ * @private
177
+ */
178
+ _migrateAddColumn(table, column, type) {
179
+ try {
180
+ this.db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
181
+ } catch {
182
+ // 列已存在,静默跳过
183
+ }
167
184
  }
168
185
  }
package/src/index.js CHANGED
@@ -32,6 +32,10 @@ SillySpec CLI — 规范驱动开发工具包
32
32
  --status 查看阶段进度
33
33
  --reset 重置阶段
34
34
  --change <name> 设置当前变更名
35
+ --spec-root <path> 平台模式:SillySpec storage root 路径
36
+ --runtime-root <path> 平台模式:运行时产物根路径
37
+ --workspace-id <id> 平台模式:workspace ID
38
+ --scan-run-id <id> 平台模式:scan run ID
35
39
  auto 连续推进 brainstorm→plan→execute→verify
36
40
 
37
41
  可选阶段:
@@ -270,10 +274,29 @@ async function main() {
270
274
  }
271
275
  case 'worktree': {
272
276
  const { WorktreeManager } = await import('./worktree.js');
277
+ const { ProgressManager } = await import('./progress.js');
273
278
  const wtSubCmd = filteredArgs[1];
274
- // 提取第一个非 -- 开头的位置参数作为 wtName
275
279
  const wtName = filteredArgs.slice(2).find(a => !a.startsWith('-'));
276
280
  const wm = new WorktreeManager({ cwd: dir });
281
+ const pm = new ProgressManager();
282
+
283
+ // isolation 写入 DB 的辅助函数
284
+ async function _writeIsolationToDB(cwd, changeName, info) {
285
+ if (info.blocked) {
286
+ await pm.updateChangeIsolation(cwd, changeName, {
287
+ status: 'blocked',
288
+ mode: null,
289
+ reason: info.reason,
290
+ });
291
+ } else {
292
+ const mode = info.mode || 'worktree';
293
+ const statusMap = { 'worktree': 'verified', 'native-worktree': 'verified', 'in-place-fallback': 'degraded' };
294
+ await pm.updateChangeIsolation(cwd, changeName, {
295
+ status: statusMap[mode] || 'verified',
296
+ mode,
297
+ });
298
+ }
299
+ }
277
300
 
278
301
  if (!wtSubCmd || wtSubCmd === 'help' || wtSubCmd === '--help' || wtSubCmd === '-h') {
279
302
  console.log(`
@@ -306,8 +329,15 @@ SillySpec worktree — git worktree 隔离管理
306
329
  console.log(` 分支: ${info.branch}`);
307
330
  console.log(` 路径: ${info.worktreePath}`);
308
331
  console.log(` 基准: ${info.baseHash.slice(0, 8)}`);
332
+ if (info.mode) {
333
+ console.log(` 模式: ${info.mode}`);
334
+ }
335
+ // 写入 isolation 信息到 gate-status.json
336
+ await _writeIsolationToDB(dir, wtName, info);
309
337
  } catch (e) {
310
338
  console.error(`❌ ${e.message}`);
339
+ // 写入 blocked 状态到 gate-status.json
340
+ await _writeIsolationToDB(dir, wtName, { blocked: true, reason: e.message });
311
341
  process.exit(1);
312
342
  }
313
343
  break;
@@ -373,9 +403,20 @@ SillySpec worktree — git worktree 隔离管理
373
403
  console.error('❌ 用法: sillyspec worktree cleanup <change-name>');
374
404
  process.exit(1);
375
405
  }
406
+ const forceFlag = args.includes('--force');
376
407
  try {
377
- wm.cleanup(wtName);
378
- console.log(`✅ worktree 已清理: ${wtName}`);
408
+ const result = wm.cleanup(wtName, { force: forceFlag });
409
+ if (result.result === 'cleaned') {
410
+ console.log(`✅ worktree 已清理: ${wtName} (mode: ${result.mode})`);
411
+ } else if (result.result === 'force-cleaned') {
412
+ console.log(`⚠️ worktree 已强制清理: ${wtName} (mode: ${result.mode})`);
413
+ console.log(` 原因: git worktree remove 失败,通过直接删除目录完成`);
414
+ } else if (result.result === 'skipped') {
415
+ console.log(`⏭️ worktree 跳过清理: ${wtName} (mode: ${result.mode})`);
416
+ console.log(` 原因: in-place 模式没有隔离目录需要清理`);
417
+ } else {
418
+ console.log(`ℹ️ worktree 未找到: ${wtName}`);
419
+ }
379
420
  } catch (e) {
380
421
  console.error(`❌ ${e.message}`);
381
422
  process.exit(1);
package/src/progress.js CHANGED
@@ -393,6 +393,48 @@ export class ProgressManager {
393
393
  });
394
394
  }
395
395
 
396
+ /**
397
+ * 更新变更的隔离状态
398
+ * @param {string} cwd - 项目根目录
399
+ * @param {string} changeName - 变更名
400
+ * @param {{ status: string, mode?: string, reason?: string }} isolation
401
+ */
402
+ async updateChangeIsolation(cwd, changeName, isolation) {
403
+ const db = await this._ensureDB(cwd);
404
+ const sqlDb = db.getDb();
405
+ try {
406
+ sqlDb.run(
407
+ `UPDATE changes SET isolation_status = ?, isolation_mode = ?, isolation_reason = ?, last_active = ? WHERE name = ?`,
408
+ [isolation.status, isolation.mode || null, isolation.reason || null, new Date().toISOString(), changeName]
409
+ );
410
+ db._save();
411
+ } catch (err) {
412
+ console.warn('⚠️ 更新 isolation 状态失败:', err.message);
413
+ }
414
+ }
415
+
416
+ /**
417
+ * 读取变更的隔离状态
418
+ * @param {string} cwd - 项目根目录
419
+ * @param {string} changeName - 变更名
420
+ * @returns {{ status: string|null, mode: string|null, reason: string|null }|null}
421
+ */
422
+ async readChangeIsolation(cwd, changeName) {
423
+ const db = await this._ensureDB(cwd);
424
+ const sqlDb = db.getDb();
425
+ try {
426
+ const rows = sqlDb.exec(
427
+ `SELECT isolation_status, isolation_mode, isolation_reason FROM changes WHERE name = ?`,
428
+ [changeName]
429
+ );
430
+ if (!rows || rows.length === 0 || rows[0].values.length === 0) return null;
431
+ const [status, mode, reason] = rows[0].values[0];
432
+ return { status: status || null, mode: mode || null, reason: reason || null };
433
+ } catch {
434
+ return null;
435
+ }
436
+ }
437
+
396
438
  /**
397
439
  * 重命名变更:同步更新 DB + 目录
398
440
  * @param {string} cwd - 项目根目录