sillyspec 3.11.11 → 3.12.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.11.11",
3
+ "version": "3.12.1",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -29,6 +29,7 @@
29
29
  "chokidar": "^4.0",
30
30
  "open": "^10.1",
31
31
  "ora": "^9.3.0",
32
+ "sql.js": "^1.14.1",
32
33
  "ws": "^8.18"
33
34
  }
34
35
  }
package/src/db.js ADDED
@@ -0,0 +1,168 @@
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
+ }
package/src/index.js CHANGED
@@ -35,7 +35,7 @@ SillySpec CLI — 规范驱动开发工具包
35
35
  auto 连续推进 brainstorm→plan→execute→verify
36
36
 
37
37
  可选阶段:
38
- scan, brainstorm, plan, execute, verify, archive
38
+ scan, brainstorm, propose, plan, execute, verify, archive
39
39
  quick, explore, status, doctor
40
40
 
41
41
  sillyspec progress <cmd> 进度记录(轻量,不强制顺序)
@@ -50,6 +50,15 @@ SillySpec CLI — 规范驱动开发工具包
50
50
 
51
51
  sillyspec docs migrate 迁移旧文档到统一结构
52
52
 
53
+ sillyspec platform <cmd> SillyHub 平台同步
54
+ connect <url> [--token <t>] 连接平台
55
+ disconnect 断开连接
56
+ sync [--change <name>] 同步变更状态
57
+ sync-docs [--change <name>] 同步四件套文档
58
+ status 查看同步状态
59
+ approve <change-name> 审批变更
60
+ reject <change-name> [--reason <r>] 拒绝变更
61
+
53
62
  sillyspec dashboard 启动 Dashboard Web UI
54
63
  [--port <number>] 指定端口(默认 3456)
55
64
  [--no-open] 不自动打开浏览器
@@ -136,7 +145,6 @@ async function main() {
136
145
  break;
137
146
  case 'progress': {
138
147
  const pm = new ProgressManager();
139
- pm._migrateIfNeeded(dir);
140
148
  const subCommand = filteredArgs[1];
141
149
  const stageIdx = filteredArgs.indexOf('--stage');
142
150
  const stage = stageIdx >= 0 && filteredArgs[stageIdx + 1] ? filteredArgs[stageIdx + 1] : null;
@@ -372,6 +380,94 @@ SillySpec worktree — git worktree 隔离管理
372
380
  }
373
381
  break;
374
382
  }
383
+ case 'platform': {
384
+ const platformSub = filteredArgs[1];
385
+ const platformArgs = filteredArgs.slice(2);
386
+
387
+ if (!platformSub || platformSub === 'help' || platformSub === '--help' || platformSub === '-h') {
388
+ console.log(`
389
+ SillySpec platform — SillyHub 平台同步
390
+
391
+ 用法:
392
+ sillyspec platform connect <url> [--token <token>]
393
+ sillyspec platform disconnect
394
+ sillyspec platform sync [--change <name>]
395
+ sillyspec platform sync-docs [--change <name>]
396
+ sillyspec platform status
397
+ sillyspec platform approve <change-name>
398
+ sillyspec platform reject <change-name> [--reason <reason>]
399
+ `);
400
+ break;
401
+ }
402
+
403
+ let syncModule;
404
+ try {
405
+ syncModule = await import('./sync.js');
406
+ } catch {
407
+ console.error('❌ 平台同步功能不可用(sync.js 未实现)');
408
+ process.exit(1);
409
+ }
410
+
411
+ switch (platformSub) {
412
+ case 'connect': {
413
+ const url = platformArgs[0];
414
+ if (!url) {
415
+ console.error('❌ 用法: sillyspec platform connect <url> [--token <token>]');
416
+ process.exit(1);
417
+ }
418
+ const tokenIdx = args.indexOf('--token');
419
+ const token = tokenIdx >= 0 && args[tokenIdx + 1] ? args[tokenIdx + 1] : undefined;
420
+ if (!token) {
421
+ console.error('⚠️ 未提供 --token,将使用交互式输入(TODO: task-11)');
422
+ }
423
+ await syncModule.connect(url, token, dir);
424
+ break;
425
+ }
426
+ case 'disconnect':
427
+ await syncModule.disconnect(dir);
428
+ break;
429
+ case 'sync': {
430
+ const syncChangeIdx = args.indexOf('--change');
431
+ const syncChangeName = syncChangeIdx >= 0 && args[syncChangeIdx + 1] ? args[syncChangeIdx + 1] : null;
432
+ await syncModule.sync(syncChangeName, dir);
433
+ break;
434
+ }
435
+ case 'sync-docs': {
436
+ const syncDocsChangeIdx = args.indexOf('--change');
437
+ const syncDocsChangeName = syncDocsChangeIdx >= 0 && args[syncDocsChangeIdx + 1] ? args[syncDocsChangeIdx + 1] : null;
438
+ await syncModule.syncDocuments(syncDocsChangeName, dir);
439
+ break;
440
+ }
441
+ case 'status':
442
+ await syncModule.status(dir);
443
+ break;
444
+ case 'approve': {
445
+ const approveName = platformArgs[0];
446
+ if (!approveName) {
447
+ console.error('❌ 用法: sillyspec platform approve <change-name>');
448
+ process.exit(1);
449
+ }
450
+ await syncModule.approve(approveName, dir);
451
+ break;
452
+ }
453
+ case 'reject': {
454
+ const rejectName = platformArgs[0];
455
+ if (!rejectName) {
456
+ console.error('❌ 用法: sillyspec platform reject <change-name> [--reason <reason>]');
457
+ process.exit(1);
458
+ }
459
+ const reasonIdx = args.indexOf('--reason');
460
+ const reason = reasonIdx >= 0 && args[reasonIdx + 1] ? args[reasonIdx + 1] : undefined;
461
+ await syncModule.reject(rejectName, reason, dir);
462
+ break;
463
+ }
464
+ default:
465
+ console.error(`❌ 未知子命令: platform ${platformSub}`);
466
+ console.log(' 运行 sillyspec platform --help 查看帮助');
467
+ process.exit(1);
468
+ }
469
+ break;
470
+ }
375
471
  default:
376
472
  console.error(`❌ 未知命令: ${command}`);
377
473
  printUsage();
package/src/init.js CHANGED
@@ -147,15 +147,9 @@ async function doInstall(projectDir, tools, subprojects = []) {
147
147
  mkdirSync(join(runtimeDir, sub), { recursive: true });
148
148
  }
149
149
 
150
- // 创建全局状态文件
150
+ // 初始化 SQLite 数据库(创建 DB + 建表 + 写入 project 行 + user-inputs.md)
151
151
  const pm = new ProgressManager();
152
- pm.init(projectDir);
153
-
154
- // 创建初始 user-inputs.md
155
- const inputsPath = join(runtimeDir, 'user-inputs.md');
156
- if (!existsSync(inputsPath)) {
157
- writeFileSync(inputsPath, '# 用户输入记录\n\n> 每步完成时由 AI 自动追加,记录用户所有原话。\n\n');
158
- }
152
+ await pm.init(projectDir);
159
153
 
160
154
  const gitignorePath = join(projectDir, '.gitignore');
161
155
  const ignoreRules = ['.sillyspec/codebase/SCAN-RAW.md', '.sillyspec/local.yaml', '.sillyspec/.runtime/'];