sillyspec 3.11.10 → 3.12.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.
- package/.claude/skills/sillyspec-brainstorm/SKILL.md +7 -5
- package/.claude/skills/sillyspec-resume/SKILL.md +21 -64
- package/.claude/skills/sillyspec-workspace/SKILL.md +10 -2
- package/docs/sillyspec/file-lifecycle.md +1110 -0
- package/package.json +2 -1
- package/src/db.js +168 -0
- package/src/hooks/worktree-guard.js +110 -26
- package/src/index.js +98 -2
- package/src/init.js +2 -8
- package/src/progress.js +652 -325
- package/src/run.js +123 -24
- package/src/stages/archive.js +7 -11
- package/src/stages/verify.js +6 -6
- package/src/sync.js +497 -0
- package/src/worktree.js +30 -0
- package/.npmrc.tmp +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillyspec",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
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
|
+
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 三重门禁:stageGate × locationGate × fileGate
|
|
5
5
|
* 纯判断模块,不做实际的 hook 注入。
|
|
6
|
+
*
|
|
7
|
+
* P0 优化:
|
|
8
|
+
* - 阶段检测 fallback:gate-status.json → progress.json currentStage
|
|
9
|
+
* - 拦截提示针对每个阶段给出具体修复建议
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
import { existsSync, readFileSync } from 'fs'
|
|
@@ -39,6 +43,41 @@ const DANGER_STASH_ACTIONS = new Set(['drop', 'clear', 'pop'])
|
|
|
39
43
|
/** 危险命令前缀 */
|
|
40
44
|
const DANGER_PREFIXES = ['sudo', 'rm -rf', 'rm -r', 'rmdir']
|
|
41
45
|
|
|
46
|
+
// ── 阶段 → 拦截提示映射 ──
|
|
47
|
+
|
|
48
|
+
const STAGE_HINTS = {
|
|
49
|
+
'(none)': [
|
|
50
|
+
'没有检测到活跃的 SillySpec 流程。',
|
|
51
|
+
'你需要先启动一个任务流程才能修改源码:',
|
|
52
|
+
'',
|
|
53
|
+
' 小改动(≤3 文件):sillyspec run quick "任务描述"',
|
|
54
|
+
' 大改动(>3 文件):sillyspec run brainstorm → plan → execute',
|
|
55
|
+
' 全自动模式: sillyspec run auto "任务描述"',
|
|
56
|
+
],
|
|
57
|
+
'brainstorm': [
|
|
58
|
+
'当前在 brainstorm(需求分析)阶段,这个阶段只写文档,不写代码。',
|
|
59
|
+
'完成 brainstorm 后,流程会自动推进到 plan → execute。',
|
|
60
|
+
'execute 阶段才允许写代码。',
|
|
61
|
+
],
|
|
62
|
+
'plan': [
|
|
63
|
+
'当前在 plan(计划制定)阶段,这个阶段只写计划文档和任务蓝图,不写代码。',
|
|
64
|
+
'完成 plan 后,运行 sillyspec run execute 进入执行阶段。',
|
|
65
|
+
],
|
|
66
|
+
'verify': [
|
|
67
|
+
'当前在 verify(验证)阶段,只做代码审查和测试验证,不修改源码。',
|
|
68
|
+
'如需修改,请先回到 execute 阶段或使用 quick 模式:',
|
|
69
|
+
' sillyspec run quick "修改描述"',
|
|
70
|
+
],
|
|
71
|
+
'archive': [
|
|
72
|
+
'当前在 archive(归档)阶段,不修改源码。',
|
|
73
|
+
'如需修改,请开启新变更:sillyspec run quick "修改描述"',
|
|
74
|
+
],
|
|
75
|
+
'explore': [
|
|
76
|
+
'当前在 explore(探索)阶段,只读不写。',
|
|
77
|
+
'确认方案后使用:sillyspec run brainstorm 或 sillyspec run quick',
|
|
78
|
+
],
|
|
79
|
+
}
|
|
80
|
+
|
|
42
81
|
// ── 辅助函数 ──
|
|
43
82
|
|
|
44
83
|
function resolveWorktreeDir(cwd) {
|
|
@@ -60,6 +99,46 @@ function readGateStatus(cwd) {
|
|
|
60
99
|
}
|
|
61
100
|
}
|
|
62
101
|
|
|
102
|
+
/**
|
|
103
|
+
* 从 progress.json fallback 读取 currentStage
|
|
104
|
+
* 优先级:gate-status.json > 全局 progress.json > 变更级 progress.json
|
|
105
|
+
* @param {string} cwd
|
|
106
|
+
* @returns {string|null} 阶段名,null 表示无法确定
|
|
107
|
+
*/
|
|
108
|
+
function readCurrentStage(cwd) {
|
|
109
|
+
// 1. gate-status.json(权威来源)
|
|
110
|
+
const gateStatus = readGateStatus(cwd)
|
|
111
|
+
if (gateStatus && gateStatus.stage) return gateStatus.stage
|
|
112
|
+
|
|
113
|
+
// 2. 全局 progress.json
|
|
114
|
+
const globalProgress = path.join(cwd, '.sillyspec', '.runtime', 'progress.json')
|
|
115
|
+
if (existsSync(globalProgress)) {
|
|
116
|
+
try {
|
|
117
|
+
const data = JSON.parse(readFileSync(globalProgress, 'utf8'))
|
|
118
|
+
if (data.currentStage) return data.currentStage
|
|
119
|
+
} catch { /* skip */ }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 3. 变更级 progress.json(从 global.json 找到活跃变更)
|
|
123
|
+
const globalFile = path.join(cwd, '.sillyspec', '.runtime', 'global.json')
|
|
124
|
+
if (existsSync(globalFile)) {
|
|
125
|
+
try {
|
|
126
|
+
const global = JSON.parse(readFileSync(globalFile, 'utf8'))
|
|
127
|
+
const changesDir = path.join(cwd, '.sillyspec', 'changes')
|
|
128
|
+
for (const cn of (global.activeChanges || [])) {
|
|
129
|
+
const pp = path.join(changesDir, cn, 'progress.json')
|
|
130
|
+
if (!existsSync(pp)) continue
|
|
131
|
+
try {
|
|
132
|
+
const data = JSON.parse(readFileSync(pp, 'utf8'))
|
|
133
|
+
if (data.currentStage) return data.currentStage
|
|
134
|
+
} catch { /* skip */ }
|
|
135
|
+
}
|
|
136
|
+
} catch { /* skip */ }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
63
142
|
/**
|
|
64
143
|
* 检查当前变更是否处于 noWorktree 模式
|
|
65
144
|
* @param {string} cwd
|
|
@@ -153,7 +232,6 @@ function loadLocalConfig(cwd) {
|
|
|
153
232
|
for (const p of candidates) {
|
|
154
233
|
if (!existsSync(p)) continue
|
|
155
234
|
try {
|
|
156
|
-
// 简单 YAML 解析(只处理顶层键值对和简单数组)
|
|
157
235
|
const content = readFileSync(p, 'utf8')
|
|
158
236
|
return parseSimpleYaml(content)
|
|
159
237
|
} catch {
|
|
@@ -327,6 +405,16 @@ function matchDangerBlacklist(command) {
|
|
|
327
405
|
return parts.some(p => isSingleCommandDangerous(p))
|
|
328
406
|
}
|
|
329
407
|
|
|
408
|
+
/**
|
|
409
|
+
* 构建阶段拦截提示
|
|
410
|
+
* @param {string} stage
|
|
411
|
+
* @returns {string}
|
|
412
|
+
*/
|
|
413
|
+
function buildStageHint(stage) {
|
|
414
|
+
const hint = STAGE_HINTS[stage] || STAGE_HINTS['(none)']
|
|
415
|
+
return hint.join('\n')
|
|
416
|
+
}
|
|
417
|
+
|
|
330
418
|
// ── 公共接口 ──
|
|
331
419
|
|
|
332
420
|
/**
|
|
@@ -336,6 +424,10 @@ function matchDangerBlacklist(command) {
|
|
|
336
424
|
* - noWorktree 模式下,execute/quick 阶段不允许源码写入(没有隔离环境)
|
|
337
425
|
* - 除非同时设置 SILLYSPEC_DISABLE_HOOKS=1
|
|
338
426
|
*
|
|
427
|
+
* P0 优化:
|
|
428
|
+
* - 使用 readCurrentStage() fallback 读取阶段(gate-status → progress)
|
|
429
|
+
* - 拦截提示按阶段给出具体修复建议
|
|
430
|
+
*
|
|
339
431
|
* @param {string} filePath - 目标文件绝对路径
|
|
340
432
|
* @param {string} cwd - 当前工作目录
|
|
341
433
|
* @returns {{ blocked: boolean, reason?: string }}
|
|
@@ -348,21 +440,14 @@ export function shouldBlockWrite(filePath, cwd) {
|
|
|
348
440
|
// 1. 文件门禁:文档类/配置类始终放行
|
|
349
441
|
if (matchFileWhitelist(absPath)) return { blocked: false }
|
|
350
442
|
|
|
351
|
-
// 2.
|
|
443
|
+
// 2. 阶段门禁(使用 fallback 读取)
|
|
352
444
|
const effectiveCwd = cwd || process.cwd()
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
445
|
+
const stage = readCurrentStage(effectiveCwd) || '(none)'
|
|
446
|
+
|
|
447
|
+
if (!ALLOWED_STAGES.includes(stage)) {
|
|
356
448
|
return {
|
|
357
449
|
blocked: true,
|
|
358
|
-
reason:
|
|
359
|
-
`当前阶段 "${stage}" 不允许修改源码。`,
|
|
360
|
-
`源码修改只能在 execute 或 quick 阶段进行。`,
|
|
361
|
-
'请先完成文档规划流程:',
|
|
362
|
-
' - 小改动:运行 sillyspec run quick',
|
|
363
|
-
' - 大改动:运行 sillyspec run brainstorm → plan → execute',
|
|
364
|
-
' - 或使用 sillyspec run auto 连续推进全流程',
|
|
365
|
-
].join('\n')
|
|
450
|
+
reason: buildStageHint(stage)
|
|
366
451
|
}
|
|
367
452
|
}
|
|
368
453
|
|
|
@@ -385,8 +470,14 @@ export function shouldBlockWrite(filePath, cwd) {
|
|
|
385
470
|
blocked: true,
|
|
386
471
|
reason: [
|
|
387
472
|
'源码修改只能在 worktree 隔离环境中进行。',
|
|
388
|
-
'
|
|
389
|
-
'
|
|
473
|
+
'',
|
|
474
|
+
'你可能需要:',
|
|
475
|
+
' 1. 确认 worktree 已创建:sillyspec worktree list',
|
|
476
|
+
' 2. 如未创建,先创建:sillyspec worktree create <变更名>',
|
|
477
|
+
' 3. 在 worktree 目录中工作(子代理的 cwd 设为 worktree 路径)',
|
|
478
|
+
'',
|
|
479
|
+
'如果你在 execute 阶段,通常会自动创建 worktree。',
|
|
480
|
+
'检查是否跳过了 "创建 worktree" 步骤。',
|
|
390
481
|
].join('\n')
|
|
391
482
|
}
|
|
392
483
|
}
|
|
@@ -405,25 +496,18 @@ export function shouldBlockBash(command, cwd) {
|
|
|
405
496
|
// cwd 在 worktree 内 → 全部放行
|
|
406
497
|
if (isInsideWorktree(effectiveCwd)) return { blocked: false }
|
|
407
498
|
|
|
408
|
-
//
|
|
409
|
-
const
|
|
410
|
-
const stageOk =
|
|
499
|
+
// 阶段门禁(使用 fallback 读取)
|
|
500
|
+
const stage = readCurrentStage(effectiveCwd) || '(none)'
|
|
501
|
+
const stageOk = ALLOWED_STAGES.includes(stage)
|
|
411
502
|
|
|
412
503
|
if (!stageOk) {
|
|
413
504
|
// 非 execute/quick 阶段,只允许只读白名单
|
|
414
505
|
const localConfig = loadLocalConfig(effectiveCwd)
|
|
415
506
|
const extraReadonly = localConfig.worktreeHook?.readonlyCommands || localConfig['worktree-hook']?.readonlyCommands || []
|
|
416
507
|
if (matchReadonlyWhitelist(command, extraReadonly)) return { blocked: false }
|
|
417
|
-
const stage = gateStatus?.stage || '(none)'
|
|
418
508
|
return {
|
|
419
509
|
blocked: true,
|
|
420
|
-
reason:
|
|
421
|
-
`当前阶段 "${stage}" 不允许执行此命令。`,
|
|
422
|
-
'源码修改只能在 execute 或 quick 阶段进行。',
|
|
423
|
-
'请先完成文档规划流程:',
|
|
424
|
-
' - 小改动:运行 sillyspec run quick',
|
|
425
|
-
' - 大改动:运行 sillyspec run brainstorm → plan → execute',
|
|
426
|
-
].join('\n')
|
|
510
|
+
reason: buildStageHint(stage)
|
|
427
511
|
}
|
|
428
512
|
}
|
|
429
513
|
|
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/'];
|