sillyspec 3.20.2 → 3.20.4

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.
Files changed (133) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +21 -21
  2. package/.claude/skills/sillyspec-auto/SKILL.md +83 -83
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +44 -44
  4. package/.claude/skills/sillyspec-commit/SKILL.md +106 -106
  5. package/.claude/skills/sillyspec-continue/SKILL.md +45 -45
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +31 -31
  7. package/.claude/skills/sillyspec-execute/SKILL.md +30 -30
  8. package/.claude/skills/sillyspec-explore/SKILL.md +109 -109
  9. package/.claude/skills/sillyspec-knowledge/SKILL.md +269 -269
  10. package/.claude/skills/sillyspec-plan/SKILL.md +21 -21
  11. package/.claude/skills/sillyspec-propose/SKILL.md +21 -21
  12. package/.claude/skills/sillyspec-quick/SKILL.md +21 -21
  13. package/.claude/skills/sillyspec-resume/SKILL.md +68 -68
  14. package/.claude/skills/sillyspec-scan/SKILL.md +21 -21
  15. package/.claude/skills/sillyspec-state/SKILL.md +54 -54
  16. package/.claude/skills/sillyspec-status/SKILL.md +21 -21
  17. package/.claude/skills/sillyspec-verify/SKILL.md +21 -21
  18. package/.claude/skills/sillyspec-workspace/SKILL.md +157 -157
  19. package/.husky/pre-push +13 -13
  20. package/CLAUDE.md +18 -18
  21. package/README.md +198 -188
  22. package/SKILL.md +90 -91
  23. package/bin/sillyspec.js +2 -2
  24. package/docs/brainstorm-plan-contract.md +64 -64
  25. package/docs/plan-execute-contract.md +123 -123
  26. package/docs/platform-scan-protocol.md +298 -298
  27. package/docs/revision-mode.md +115 -115
  28. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +99 -99
  29. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +218 -218
  30. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +167 -167
  31. package/docs/sillyspec/file-lifecycle/storage-and-state.md +148 -148
  32. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +211 -193
  33. package/docs/sillyspec/file-lifecycle.md +125 -125
  34. package/docs/workflow-contract-regression.md +106 -106
  35. package/docs/worktree-isolation.md +252 -252
  36. package/package.json +40 -40
  37. package/packages/dashboard/dist/assets/index-Bq_Z2hne.js +7446 -7446
  38. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +1 -1
  39. package/packages/dashboard/dist/index.html +16 -16
  40. package/packages/dashboard/index.html +15 -15
  41. package/packages/dashboard/package-lock.json +2384 -2384
  42. package/packages/dashboard/package.json +25 -25
  43. package/packages/dashboard/server/executor.js +86 -86
  44. package/packages/dashboard/server/index.js +588 -588
  45. package/packages/dashboard/server/parser.js +526 -526
  46. package/packages/dashboard/server/watcher.js +344 -344
  47. package/packages/dashboard/src/App.vue +558 -558
  48. package/packages/dashboard/src/components/ActionBar.vue +93 -93
  49. package/packages/dashboard/src/components/CommandPalette.vue +96 -96
  50. package/packages/dashboard/src/components/DetailPanel.vue +137 -137
  51. package/packages/dashboard/src/components/LogStream.vue +65 -65
  52. package/packages/dashboard/src/components/PipelineStage.vue +95 -95
  53. package/packages/dashboard/src/components/PipelineView.vue +156 -156
  54. package/packages/dashboard/src/components/ProjectList.vue +210 -210
  55. package/packages/dashboard/src/components/StageBadge.vue +67 -67
  56. package/packages/dashboard/src/components/StepCard.vue +94 -94
  57. package/packages/dashboard/src/components/detail/DocsDetail.vue +48 -48
  58. package/packages/dashboard/src/components/detail/GitDetail.vue +61 -61
  59. package/packages/dashboard/src/components/detail/TechDetail.vue +43 -43
  60. package/packages/dashboard/src/composables/useDashboard.js +170 -170
  61. package/packages/dashboard/src/composables/useKeyboard.js +119 -119
  62. package/packages/dashboard/src/composables/useWebSocket.js +129 -129
  63. package/packages/dashboard/src/main.js +8 -8
  64. package/packages/dashboard/src/style.css +132 -132
  65. package/packages/dashboard/vite.config.js +18 -18
  66. package/src/brainstorm-postcheck.js +158 -158
  67. package/src/change-list.js +52 -52
  68. package/src/change-risk-profile.js +352 -352
  69. package/src/classify-change.js +73 -73
  70. package/src/constants.js +70 -70
  71. package/src/contract-matrix.js +278 -278
  72. package/src/db.js +201 -201
  73. package/src/endpoint-extractor.js +315 -315
  74. package/src/hooks/claude-pre-tool-use.cjs +125 -125
  75. package/src/hooks/worktree-guard.js +653 -653
  76. package/src/index.js +922 -900
  77. package/src/init.js +431 -431
  78. package/src/knowledge-match.js +130 -130
  79. package/src/migrate.js +117 -117
  80. package/src/modules.js +482 -482
  81. package/src/progress.js +1734 -1734
  82. package/src/run.js +3465 -3358
  83. package/src/scan-postcheck.js +387 -383
  84. package/src/setup.js +398 -398
  85. package/src/stage-contract.js +700 -700
  86. package/src/stages/archive.js +160 -160
  87. package/src/stages/brainstorm-auto.js +229 -229
  88. package/src/stages/brainstorm.js +645 -645
  89. package/src/stages/doctor.js +365 -365
  90. package/src/stages/execute.js +625 -625
  91. package/src/stages/explore.js +34 -34
  92. package/src/stages/index.js +29 -29
  93. package/src/stages/knowledge.js +498 -498
  94. package/src/stages/plan-postcheck.js +511 -513
  95. package/src/stages/plan.js +582 -582
  96. package/src/stages/propose.js +174 -174
  97. package/src/stages/quick.js +82 -82
  98. package/src/stages/scan.js +558 -558
  99. package/src/stages/status.js +65 -65
  100. package/src/stages/verify.js +322 -322
  101. package/src/sync.js +497 -497
  102. package/src/task-review.js +346 -346
  103. package/src/workflow.js +785 -785
  104. package/src/worktree-apply.js +549 -549
  105. package/src/worktree-deps.js +185 -0
  106. package/src/worktree.js +982 -932
  107. package/templates/workflows/archive-impact.yaml +79 -79
  108. package/templates/workflows/scan-docs.yaml +132 -132
  109. package/test/brainstorm-plan-contract.test.mjs +273 -273
  110. package/test/check-syntax.mjs +26 -26
  111. package/test/contract-artifacts.test.mjs +323 -323
  112. package/test/decision-supersede.test.mjs +277 -277
  113. package/test/knowledge-match.test.mjs +231 -231
  114. package/test/plan-execute-contract.test.mjs +330 -330
  115. package/test/plan-optimization.test.mjs +572 -572
  116. package/test/platform-artifacts.test.mjs +166 -166
  117. package/test/platform-failure-samples.test.mjs +199 -199
  118. package/test/platform-recovery-chain.test.mjs +167 -167
  119. package/test/platform-recovery.test.mjs +136 -136
  120. package/test/platform-scan-p0.test.mjs +168 -168
  121. package/test/revision-v1.test.mjs +1145 -1145
  122. package/test/run-scan-project-parse.test.mjs +200 -200
  123. package/test/run-tests.mjs +48 -48
  124. package/test/scan-knowledge.test.mjs +175 -175
  125. package/test/scan-paths.test.mjs +68 -68
  126. package/test/scan-postcheck.test.mjs +197 -197
  127. package/test/spec-dir.test.mjs +206 -206
  128. package/test/stage-contract.test.mjs +299 -299
  129. package/test/stage-definitions.test.mjs +39 -39
  130. package/test/wait-gates.test.mjs +496 -496
  131. package/test/worktree-deps-provision.test.mjs +148 -0
  132. package/test/worktree-guard.test.mjs +71 -71
  133. package/test/worktree-native-overlay.test.mjs +188 -188
package/src/db.js CHANGED
@@ -1,201 +1,201 @@
1
- import initSqlJs from 'sql.js';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
- import { dirname } from 'path';
4
-
5
- export class DB {
6
- constructor(dbPath) {
7
- this.dbPath = dbPath;
8
- this.db = null;
9
- }
10
-
11
- async init() {
12
- // 1. 确保父目录存在
13
- const dir = dirname(this.dbPath);
14
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
15
-
16
- // 2. 初始化 sql.js
17
- const SQL = await initSqlJs();
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
- }
26
-
27
- // 4. 设置 PRAGMA
28
- this.db.run('PRAGMA journal_mode = WAL');
29
- this.db.run('PRAGMA busy_timeout = 5000');
30
- this.db.run('PRAGMA foreign_keys = ON');
31
- this.db.run('PRAGMA synchronous = NORMAL');
32
-
33
- // 5. 创建表结构
34
- this._createSchema();
35
-
36
- // 6. 保存到磁盘
37
- this._save();
38
- }
39
-
40
- close() {
41
- if (this.db) {
42
- this._save();
43
- this.db.close();
44
- this.db = null;
45
- }
46
- }
47
-
48
- transaction(fn) {
49
- if (!this.db) throw new Error('DB not initialized');
50
- this.db.run('BEGIN');
51
- try {
52
- const result = fn(this.db);
53
- this.db.run('COMMIT');
54
- this._save();
55
- return result;
56
- } catch (err) {
57
- this.db.run('ROLLBACK');
58
- throw err;
59
- }
60
- }
61
-
62
- /** 获取底层 db 对象(供 progress.js 直接使用) */
63
- getDb() {
64
- return this.db;
65
- }
66
-
67
- /** 将内存中的数据库持久化到磁盘 */
68
- _save() {
69
- if (!this.db) return;
70
- const data = this.db.export();
71
- const buffer = Buffer.from(data);
72
- writeFileSync(this.dbPath, buffer);
73
- // sql.js 的 export() 会重置 PRAGMA 状态,需要重新设置
74
- this.db.run('PRAGMA journal_mode = WAL');
75
- this.db.run('PRAGMA busy_timeout = 5000');
76
- this.db.run('PRAGMA foreign_keys = ON');
77
- this.db.run('PRAGMA synchronous = NORMAL');
78
- }
79
-
80
- _createSchema() {
81
- // project 表
82
- this.db.run(`
83
- CREATE TABLE IF NOT EXISTS project (
84
- id INTEGER PRIMARY KEY DEFAULT 1,
85
- name TEXT NOT NULL,
86
- schema_version INTEGER DEFAULT 4,
87
- created_at TEXT NOT NULL,
88
- updated_at TEXT NOT NULL
89
- )
90
- `);
91
-
92
- // changes 表
93
- this.db.run(`
94
- CREATE TABLE IF NOT EXISTS changes (
95
- id INTEGER PRIMARY KEY AUTOINCREMENT,
96
- name TEXT UNIQUE NOT NULL,
97
- current_stage TEXT DEFAULT 'scan',
98
- status TEXT DEFAULT 'active',
99
- no_worktree INTEGER DEFAULT 0,
100
- created_at TEXT NOT NULL,
101
- last_active TEXT NOT NULL,
102
- platform_change_id INTEGER,
103
- platform_workspace_id INTEGER,
104
- platform_last_sync TEXT,
105
- platform_sync_enabled INTEGER DEFAULT 0
106
- )
107
- `);
108
-
109
- // stages 表
110
- this.db.run(`
111
- CREATE TABLE IF NOT EXISTS stages (
112
- id INTEGER PRIMARY KEY AUTOINCREMENT,
113
- change_id INTEGER NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
114
- stage TEXT NOT NULL,
115
- status TEXT DEFAULT 'pending',
116
- started_at TEXT,
117
- completed_at TEXT,
118
- UNIQUE(change_id, stage)
119
- )
120
- `);
121
-
122
- // steps 表
123
- this.db.run(`
124
- CREATE TABLE IF NOT EXISTS steps (
125
- id INTEGER PRIMARY KEY AUTOINCREMENT,
126
- stage_id INTEGER NOT NULL REFERENCES stages(id) ON DELETE CASCADE,
127
- name TEXT NOT NULL,
128
- status TEXT DEFAULT 'pending',
129
- output TEXT,
130
- completed_at TEXT,
131
- ordering INTEGER NOT NULL DEFAULT 0
132
- )
133
- `);
134
-
135
- // batch_progress 表
136
- this.db.run(`
137
- CREATE TABLE IF NOT EXISTS batch_progress (
138
- id INTEGER PRIMARY KEY AUTOINCREMENT,
139
- change_id INTEGER NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
140
- total INTEGER DEFAULT 0,
141
- completed INTEGER DEFAULT 0,
142
- failed INTEGER DEFAULT 0,
143
- skipped INTEGER DEFAULT 0,
144
- UNIQUE(change_id)
145
- )
146
- `);
147
-
148
- // approvals 表
149
- this.db.run(`
150
- CREATE TABLE IF NOT EXISTS approvals (
151
- id INTEGER PRIMARY KEY AUTOINCREMENT,
152
- change_id INTEGER NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
153
- status TEXT DEFAULT 'not_required',
154
- requested_at TEXT,
155
- approved_by TEXT,
156
- approved_at TEXT,
157
- rejection_reason TEXT,
158
- UNIQUE(change_id)
159
- )
160
- `);
161
-
162
- // 索引
163
- this.db.run('CREATE INDEX IF NOT EXISTS idx_changes_current_stage ON changes(current_stage)');
164
- this.db.run('CREATE INDEX IF NOT EXISTS idx_changes_status ON changes(status)');
165
- this.db.run('CREATE INDEX IF NOT EXISTS idx_stages_change ON stages(change_id)');
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
- // Migration: add waiting support columns to steps table (idempotent)
174
- this._migrateAddColumn('steps', 'wait_reason', 'TEXT');
175
- this._migrateAddColumn('steps', 'wait_options', 'TEXT');
176
- this._migrateAddColumn('steps', 'wait_answer', 'TEXT');
177
- this._migrateAddColumn('steps', 'waited_at', 'TEXT');
178
- // repeatableWait support
179
- this._migrateAddColumn('steps', 'wait_answers', 'TEXT'); // JSON array
180
- this._migrateAddColumn('steps', 'wait_round', 'INTEGER');
181
- this._migrateAddColumn('steps', 'max_wait_rounds', 'INTEGER');
182
-
183
- // Revision v1 support
184
- this._migrateAddColumn('stages', 'revision', 'INTEGER DEFAULT 0');
185
- this._migrateAddColumn('stages', 'reopened_from_step', 'TEXT');
186
- this._migrateAddColumn('stages', 'reopened_at', 'TEXT');
187
- this._migrateAddColumn('stages', 'stale_reason', 'TEXT');
188
- }
189
-
190
- /**
191
- * 幂等地给表添加列(列已存在则跳过)
192
- * @private
193
- */
194
- _migrateAddColumn(table, column, type) {
195
- try {
196
- this.db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
197
- } catch {
198
- // 列已存在,静默跳过
199
- }
200
- }
201
- }
1
+ import initSqlJs from 'sql.js';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
+ import { dirname } from 'path';
4
+
5
+ export class DB {
6
+ constructor(dbPath) {
7
+ this.dbPath = dbPath;
8
+ this.db = null;
9
+ }
10
+
11
+ async init() {
12
+ // 1. 确保父目录存在
13
+ const dir = dirname(this.dbPath);
14
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
15
+
16
+ // 2. 初始化 sql.js
17
+ const SQL = await initSqlJs();
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
+ }
26
+
27
+ // 4. 设置 PRAGMA
28
+ this.db.run('PRAGMA journal_mode = WAL');
29
+ this.db.run('PRAGMA busy_timeout = 5000');
30
+ this.db.run('PRAGMA foreign_keys = ON');
31
+ this.db.run('PRAGMA synchronous = NORMAL');
32
+
33
+ // 5. 创建表结构
34
+ this._createSchema();
35
+
36
+ // 6. 保存到磁盘
37
+ this._save();
38
+ }
39
+
40
+ close() {
41
+ if (this.db) {
42
+ this._save();
43
+ this.db.close();
44
+ this.db = null;
45
+ }
46
+ }
47
+
48
+ transaction(fn) {
49
+ if (!this.db) throw new Error('DB not initialized');
50
+ this.db.run('BEGIN');
51
+ try {
52
+ const result = fn(this.db);
53
+ this.db.run('COMMIT');
54
+ this._save();
55
+ return result;
56
+ } catch (err) {
57
+ this.db.run('ROLLBACK');
58
+ throw err;
59
+ }
60
+ }
61
+
62
+ /** 获取底层 db 对象(供 progress.js 直接使用) */
63
+ getDb() {
64
+ return this.db;
65
+ }
66
+
67
+ /** 将内存中的数据库持久化到磁盘 */
68
+ _save() {
69
+ if (!this.db) return;
70
+ const data = this.db.export();
71
+ const buffer = Buffer.from(data);
72
+ writeFileSync(this.dbPath, buffer);
73
+ // sql.js 的 export() 会重置 PRAGMA 状态,需要重新设置
74
+ this.db.run('PRAGMA journal_mode = WAL');
75
+ this.db.run('PRAGMA busy_timeout = 5000');
76
+ this.db.run('PRAGMA foreign_keys = ON');
77
+ this.db.run('PRAGMA synchronous = NORMAL');
78
+ }
79
+
80
+ _createSchema() {
81
+ // project 表
82
+ this.db.run(`
83
+ CREATE TABLE IF NOT EXISTS project (
84
+ id INTEGER PRIMARY KEY DEFAULT 1,
85
+ name TEXT NOT NULL,
86
+ schema_version INTEGER DEFAULT 4,
87
+ created_at TEXT NOT NULL,
88
+ updated_at TEXT NOT NULL
89
+ )
90
+ `);
91
+
92
+ // changes 表
93
+ this.db.run(`
94
+ CREATE TABLE IF NOT EXISTS changes (
95
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
96
+ name TEXT UNIQUE NOT NULL,
97
+ current_stage TEXT DEFAULT 'scan',
98
+ status TEXT DEFAULT 'active',
99
+ no_worktree INTEGER DEFAULT 0,
100
+ created_at TEXT NOT NULL,
101
+ last_active TEXT NOT NULL,
102
+ platform_change_id INTEGER,
103
+ platform_workspace_id INTEGER,
104
+ platform_last_sync TEXT,
105
+ platform_sync_enabled INTEGER DEFAULT 0
106
+ )
107
+ `);
108
+
109
+ // stages 表
110
+ this.db.run(`
111
+ CREATE TABLE IF NOT EXISTS stages (
112
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
113
+ change_id INTEGER NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
114
+ stage TEXT NOT NULL,
115
+ status TEXT DEFAULT 'pending',
116
+ started_at TEXT,
117
+ completed_at TEXT,
118
+ UNIQUE(change_id, stage)
119
+ )
120
+ `);
121
+
122
+ // steps 表
123
+ this.db.run(`
124
+ CREATE TABLE IF NOT EXISTS steps (
125
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
126
+ stage_id INTEGER NOT NULL REFERENCES stages(id) ON DELETE CASCADE,
127
+ name TEXT NOT NULL,
128
+ status TEXT DEFAULT 'pending',
129
+ output TEXT,
130
+ completed_at TEXT,
131
+ ordering INTEGER NOT NULL DEFAULT 0
132
+ )
133
+ `);
134
+
135
+ // batch_progress 表
136
+ this.db.run(`
137
+ CREATE TABLE IF NOT EXISTS batch_progress (
138
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
139
+ change_id INTEGER NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
140
+ total INTEGER DEFAULT 0,
141
+ completed INTEGER DEFAULT 0,
142
+ failed INTEGER DEFAULT 0,
143
+ skipped INTEGER DEFAULT 0,
144
+ UNIQUE(change_id)
145
+ )
146
+ `);
147
+
148
+ // approvals 表
149
+ this.db.run(`
150
+ CREATE TABLE IF NOT EXISTS approvals (
151
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
152
+ change_id INTEGER NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
153
+ status TEXT DEFAULT 'not_required',
154
+ requested_at TEXT,
155
+ approved_by TEXT,
156
+ approved_at TEXT,
157
+ rejection_reason TEXT,
158
+ UNIQUE(change_id)
159
+ )
160
+ `);
161
+
162
+ // 索引
163
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_changes_current_stage ON changes(current_stage)');
164
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_changes_status ON changes(status)');
165
+ this.db.run('CREATE INDEX IF NOT EXISTS idx_stages_change ON stages(change_id)');
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
+ // Migration: add waiting support columns to steps table (idempotent)
174
+ this._migrateAddColumn('steps', 'wait_reason', 'TEXT');
175
+ this._migrateAddColumn('steps', 'wait_options', 'TEXT');
176
+ this._migrateAddColumn('steps', 'wait_answer', 'TEXT');
177
+ this._migrateAddColumn('steps', 'waited_at', 'TEXT');
178
+ // repeatableWait support
179
+ this._migrateAddColumn('steps', 'wait_answers', 'TEXT'); // JSON array
180
+ this._migrateAddColumn('steps', 'wait_round', 'INTEGER');
181
+ this._migrateAddColumn('steps', 'max_wait_rounds', 'INTEGER');
182
+
183
+ // Revision v1 support
184
+ this._migrateAddColumn('stages', 'revision', 'INTEGER DEFAULT 0');
185
+ this._migrateAddColumn('stages', 'reopened_from_step', 'TEXT');
186
+ this._migrateAddColumn('stages', 'reopened_at', 'TEXT');
187
+ this._migrateAddColumn('stages', 'stale_reason', 'TEXT');
188
+ }
189
+
190
+ /**
191
+ * 幂等地给表添加列(列已存在则跳过)
192
+ * @private
193
+ */
194
+ _migrateAddColumn(table, column, type) {
195
+ try {
196
+ this.db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
197
+ } catch {
198
+ // 列已存在,静默跳过
199
+ }
200
+ }
201
+ }