sillyspec 3.20.1 → 3.20.3

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 (209) hide show
  1. package/.claude/skills/sillyspec-archive/SKILL.md +1 -5
  2. package/.claude/skills/sillyspec-auto/SKILL.md +2 -8
  3. package/.claude/skills/sillyspec-brainstorm/SKILL.md +1 -28
  4. package/.claude/skills/sillyspec-commit/SKILL.md +3 -4
  5. package/.claude/skills/sillyspec-continue/SKILL.md +4 -5
  6. package/.claude/skills/sillyspec-doctor/SKILL.md +3 -12
  7. package/.claude/skills/sillyspec-execute/SKILL.md +2 -15
  8. package/.claude/skills/sillyspec-explore/SKILL.md +1 -14
  9. package/.claude/skills/sillyspec-plan/SKILL.md +36 -5
  10. package/.claude/skills/sillyspec-propose/SKILL.md +0 -4
  11. package/.claude/skills/sillyspec-quick/SKILL.md +1 -5
  12. package/.claude/skills/sillyspec-resume/SKILL.md +66 -23
  13. package/.claude/skills/sillyspec-scan/SKILL.md +1 -5
  14. package/.claude/skills/sillyspec-state/SKILL.md +8 -8
  15. package/.claude/skills/sillyspec-status/SKILL.md +1 -5
  16. package/.claude/skills/sillyspec-verify/SKILL.md +1 -5
  17. package/.claude/skills/sillyspec-workspace/SKILL.md +3 -11
  18. package/.sillyspec/changes/archive/2026-04-08-derive-state/design.md +97 -0
  19. package/.sillyspec/changes/archive/2026-04-08-derive-state/plan.md +51 -0
  20. package/.sillyspec/changes/archive/2026-04-08-derive-state/proposal.md +29 -0
  21. package/.sillyspec/changes/archive/2026-04-08-derive-state/requirements.md +34 -0
  22. package/.sillyspec/changes/archive/2026-04-08-derive-state/tasks.md +13 -0
  23. package/.sillyspec/changes/archive/2026-04-08-derive-state/verify-result.md +43 -0
  24. package/.sillyspec/changes/auto-mode/design.md +50 -0
  25. package/.sillyspec/changes/auto-mode/proposal.md +19 -0
  26. package/.sillyspec/changes/auto-mode/requirements.md +21 -0
  27. package/.sillyspec/changes/auto-mode/tasks.md +7 -0
  28. package/.sillyspec/changes/brainstorm-archive/2026-04-05-dashboard-design.md +206 -0
  29. package/.sillyspec/changes/brainstorm-archive/2026-04-05-unified-docs-design.md +199 -0
  30. package/.sillyspec/changes/dashboard/design.md +219 -0
  31. package/.sillyspec/changes/dashboard/design.md.braindraft +206 -0
  32. package/.sillyspec/changes/run-command-design/design.md +1230 -0
  33. package/.sillyspec/changes/unified-docs-design/design.md +199 -0
  34. package/.sillyspec/docs/sillyspec/scan/.gitkeep +0 -0
  35. package/.sillyspec/knowledge/INDEX.md +8 -0
  36. package/.sillyspec/knowledge/uncategorized.md +3 -0
  37. package/.sillyspec/plans/2026-04-05-dashboard.md +737 -0
  38. package/.sillyspec/projects/sillyspec.yaml +3 -0
  39. package/README.md +11 -13
  40. package/SKILL.md +40 -44
  41. package/dist/steps/brainstorm/01-load-context.md +30 -0
  42. package/dist/steps/brainstorm/02-reuse-check.md +6 -0
  43. package/dist/steps/brainstorm/03-prototype-analysis.md +11 -0
  44. package/dist/steps/brainstorm/04-module-split.md +23 -0
  45. package/dist/steps/brainstorm/05-dialog-explore.md +8 -0
  46. package/dist/steps/brainstorm/06-propose-approaches.md +3 -0
  47. package/dist/steps/brainstorm/07-present-design.md +3 -0
  48. package/dist/steps/brainstorm/08-write-design.md +21 -0
  49. package/dist/steps/brainstorm/09-self-review.md +15 -0
  50. package/dist/steps/brainstorm/10-user-confirm.md +3 -0
  51. package/dist/steps/brainstorm/11-output-spec.md +7 -0
  52. package/dist/steps/brainstorm/manifest.yaml +26 -0
  53. package/dist/steps/execute/01-load-context.md +41 -0
  54. package/dist/steps/execute/02-scan-conventions.md +47 -0
  55. package/dist/steps/execute/03-skill-mcp.md +19 -0
  56. package/dist/steps/execute/04-assign-task.md +22 -0
  57. package/dist/steps/execute/04b-prompt-template.md +54 -0
  58. package/dist/steps/execute/05-write-test.md +7 -0
  59. package/dist/steps/execute/06-write-code.md +8 -0
  60. package/dist/steps/execute/07-run-test.md +26 -0
  61. package/dist/steps/execute/08-fix-issues.md +28 -0
  62. package/dist/steps/execute/09-next-task.md +33 -0
  63. package/dist/steps/execute/manifest.yaml +28 -0
  64. package/dist/steps/plan/01-load-context.md +22 -0
  65. package/dist/steps/plan/02-anchor-confirm.md +1 -0
  66. package/dist/steps/plan/03-expand-tasks.md +33 -0
  67. package/dist/steps/plan/04-mark-order.md +15 -0
  68. package/dist/steps/plan/05-e2e-planning.md +17 -0
  69. package/dist/steps/plan/06-self-check.md +16 -0
  70. package/dist/steps/plan/07-save.md +1 -0
  71. package/dist/steps/plan/manifest.yaml +18 -0
  72. package/dist/steps/scan/01-env-detect.md +51 -0
  73. package/dist/steps/scan/02-tech-stack.md +16 -0
  74. package/dist/steps/scan/03-conventions.md +16 -0
  75. package/dist/steps/scan/04-structure.md +19 -0
  76. package/dist/steps/scan/05-quality.md +18 -0
  77. package/dist/steps/scan/06-complete.md +49 -0
  78. package/dist/steps/scan/manifest.yaml +16 -0
  79. package/dist/steps/verify/01-load-specs.md +28 -0
  80. package/dist/steps/verify/02-check-tasks.md +1 -0
  81. package/dist/steps/verify/03-check-design.md +6 -0
  82. package/dist/steps/verify/04-run-tests.md +7 -0
  83. package/dist/steps/verify/05-e2e-tests.md +27 -0
  84. package/dist/steps/verify/05b-e2e-fix.md +33 -0
  85. package/dist/steps/verify/06-code-quality.md +25 -0
  86. package/dist/steps/verify/07-lint-check.md +27 -0
  87. package/dist/steps/verify/08-output-report.md +14 -0
  88. package/dist/steps/verify/manifest.yaml +22 -0
  89. package/package.json +2 -8
  90. package/packages/dashboard/dist/assets/{index-Bq_Z2hne.js → index-D1EVTLmc.js} +1264 -1264
  91. package/packages/dashboard/dist/assets/index-DGe8CqeP.css +1 -0
  92. package/packages/dashboard/dist/index.html +2 -2
  93. package/packages/dashboard/package-lock.json +6 -6
  94. package/packages/dashboard/server/executor.js +1 -1
  95. package/packages/dashboard/server/index.js +18 -98
  96. package/packages/dashboard/server/parser.js +71 -139
  97. package/packages/dashboard/server/watcher.js +6 -14
  98. package/packages/dashboard/src/App.vue +185 -418
  99. package/packages/dashboard/src/components/ActionBar.vue +1 -10
  100. package/packages/dashboard/src/components/CommandPalette.vue +1 -5
  101. package/packages/dashboard/src/components/DocPreview.vue +8 -105
  102. package/packages/dashboard/src/components/DocTree.vue +19 -75
  103. package/packages/dashboard/src/components/PipelineStage.vue +2 -22
  104. package/packages/dashboard/src/components/PipelineView.vue +5 -32
  105. package/packages/dashboard/src/components/ProjectOverview.vue +139 -113
  106. package/packages/dashboard/src/components/StageBadge.vue +3 -17
  107. package/packages/dashboard/src/components/StepCard.vue +2 -7
  108. package/packages/dashboard/src/composables/useDashboard.js +0 -28
  109. package/src/derive.js +147 -0
  110. package/src/index.js +40 -685
  111. package/src/init.js +63 -119
  112. package/src/migrate.js +7 -7
  113. package/src/progress.js +248 -1474
  114. package/src/run.js +302 -2920
  115. package/src/setup.js +64 -2
  116. package/src/stages/archive.js +17 -123
  117. package/src/stages/brainstorm.js +48 -454
  118. package/src/stages/doctor.js +46 -99
  119. package/src/stages/execute.js +59 -420
  120. package/src/stages/index.js +18 -12
  121. package/src/stages/plan.js +189 -492
  122. package/src/stages/propose.js +11 -70
  123. package/src/stages/quick.js +13 -52
  124. package/src/stages/scan.js +68 -468
  125. package/src/stages/status.js +1 -1
  126. package/src/stages/verify.js +16 -203
  127. package/src/step.js +543 -0
  128. package/.claude/skills/sillyspec-knowledge/SKILL.md +0 -270
  129. package/.husky/pre-push +0 -13
  130. package/.npmrc-tmp +0 -1
  131. package/CLAUDE.md +0 -18
  132. package/docs/brainstorm-plan-contract.md +0 -64
  133. package/docs/plan-execute-contract.md +0 -123
  134. package/docs/platform-scan-protocol.md +0 -298
  135. package/docs/revision-mode.md +0 -115
  136. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  137. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  138. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  139. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  140. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  141. package/docs/sillyspec/file-lifecycle.md +0 -125
  142. package/docs/workflow-contract-regression.md +0 -106
  143. package/docs/worktree-isolation.md +0 -252
  144. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  145. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  146. package/packages/dashboard/dist/prototype-overview.html +0 -256
  147. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  148. package/packages/dashboard/public/prototype-overview.html +0 -256
  149. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  150. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  151. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  152. package/packages/dashboard/src/composables/useLayout.js +0 -131
  153. package/src/brainstorm-postcheck.js +0 -158
  154. package/src/change-list.js +0 -52
  155. package/src/change-risk-profile.js +0 -352
  156. package/src/classify-change.js +0 -73
  157. package/src/constants.js +0 -70
  158. package/src/contract-matrix.js +0 -278
  159. package/src/db.js +0 -201
  160. package/src/endpoint-extractor.js +0 -315
  161. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  162. package/src/hooks/worktree-guard.js +0 -761
  163. package/src/knowledge-match.js +0 -130
  164. package/src/modules.js +0 -482
  165. package/src/scan-postcheck.js +0 -380
  166. package/src/stage-contract.js +0 -700
  167. package/src/stages/brainstorm-auto.js +0 -229
  168. package/src/stages/explore.js +0 -34
  169. package/src/stages/knowledge.js +0 -498
  170. package/src/stages/plan-postcheck.js +0 -513
  171. package/src/sync.js +0 -497
  172. package/src/task-review.js +0 -346
  173. package/src/workflow.js +0 -785
  174. package/src/worktree-apply.js +0 -549
  175. package/src/worktree.js +0 -932
  176. package/templates/workflows/archive-impact.yaml +0 -79
  177. package/templates/workflows/scan-docs.yaml +0 -132
  178. package/test/brainstorm-plan-contract.test.mjs +0 -273
  179. package/test/check-syntax.mjs +0 -26
  180. package/test/cli-top-level-aliases.test.mjs +0 -174
  181. package/test/contract-artifacts.test.mjs +0 -323
  182. package/test/decision-supersede.test.mjs +0 -277
  183. package/test/knowledge-match.test.mjs +0 -231
  184. package/test/plan-execute-contract.test.mjs +0 -330
  185. package/test/plan-optimization.test.mjs +0 -572
  186. package/test/platform-artifacts.test.mjs +0 -190
  187. package/test/platform-failure-samples.test.mjs +0 -199
  188. package/test/platform-recovery-chain.test.mjs +0 -179
  189. package/test/platform-recovery.test.mjs +0 -167
  190. package/test/platform-scan-p0.test.mjs +0 -175
  191. package/test/revision-v1.test.mjs +0 -1145
  192. package/test/run-sanitize-project-name.test.mjs +0 -51
  193. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  194. package/test/run-scan-project-parse.test.mjs +0 -77
  195. package/test/run-tests.mjs +0 -48
  196. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  197. package/test/scan-knowledge.test.mjs +0 -175
  198. package/test/scan-paths.test.mjs +0 -68
  199. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  200. package/test/scan-postcheck.test.mjs +0 -183
  201. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  202. package/test/spec-dir.test.mjs +0 -206
  203. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  204. package/test/stage-contract.test.mjs +0 -299
  205. package/test/stage-definitions.test.mjs +0 -39
  206. package/test/wait-gates.test.mjs +0 -501
  207. package/test/workflow-spec-base.test.mjs +0 -142
  208. package/test/worktree-guard.test.mjs +0 -136
  209. package/test/worktree-native-overlay.test.mjs +0 -188
package/src/sync.js DELETED
@@ -1,497 +0,0 @@
1
- /**
2
- * SillySpec SyncManager — SillyHub 平台同步模块
3
- *
4
- * 独立于 ProgressManager,由 run.js 和 index.js 调用。
5
- * Best effort:所有网络失败 console.warn,不抛错,不阻塞主流程。
6
- *
7
- * 配置来源:.sillyspec/local.yaml 中的 platform 段
8
- * HTTP 请求:Node.js 原生 fetch(Node 22+)
9
- */
10
-
11
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
12
- import { join } from 'path';
13
-
14
- const LOCAL_YAML = '.sillyspec/local.yaml';
15
- const CHANGES_DIR = '.sillyspec/changes';
16
- const REQUEST_TIMEOUT_MS = 10_000;
17
-
18
- /** 四件套文档文件名 */
19
- const DOCUMENT_FILES = ['proposal.md', 'design.md', 'requirements.md', 'tasks.md'];
20
-
21
- // ── YAML 辅助 ──
22
-
23
- /**
24
- * 简易 YAML 读写,只处理 project 段的扁平结构。
25
- * 与 worktree-guard.js 的 parseSimpleYaml 保持一致的轻量风格。
26
- */
27
- function readLocalYaml(cwd) {
28
- const p = join(cwd, LOCAL_YAML);
29
- if (!existsSync(p)) return {};
30
- try {
31
- return parseSimpleYaml(readFileSync(p, 'utf8'));
32
- } catch {
33
- return {};
34
- }
35
- }
36
-
37
- function writeLocalYaml(cwd, obj) {
38
- const dir = join(cwd, '.sillyspec');
39
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
40
- const lines = [];
41
- const rootKeys = Object.keys(obj);
42
- for (const key of rootKeys) {
43
- const val = obj[key];
44
- if (val === null || val === undefined) continue;
45
- if (typeof val === 'object' && !Array.isArray(val)) {
46
- lines.push(`${key}:`);
47
- for (const [k, v] of Object.entries(val)) {
48
- if (typeof v === 'string') {
49
- lines.push(` ${k}: ${v}`);
50
- } else {
51
- lines.push(` ${k}: ${JSON.stringify(v)}`);
52
- }
53
- }
54
- } else {
55
- lines.push(`${key}: ${typeof val === 'string' ? val : JSON.stringify(val)}`);
56
- }
57
- }
58
- writeFileSync(join(cwd, LOCAL_YAML), lines.join('\n') + '\n', 'utf8');
59
- }
60
-
61
- function parseSimpleYaml(content) {
62
- const result = {};
63
- let currentSection = null;
64
- for (const line of content.split('\n')) {
65
- const trimmed = line.trim();
66
- if (!trimmed || trimmed.startsWith('#')) continue;
67
- if (!trimmed.startsWith(' ')) {
68
- const m = trimmed.match(/^(\S+)\s*:\s*(.*)$/);
69
- if (m) {
70
- const key = m[1];
71
- const val = m[2].trim();
72
- if (val) {
73
- result[key] = val;
74
- currentSection = null;
75
- } else {
76
- result[key] = {};
77
- currentSection = key;
78
- }
79
- }
80
- } else if (currentSection) {
81
- const m = trimmed.match(/^(\S+)\s*:\s*(.*)$/);
82
- if (m && result[currentSection] && typeof result[currentSection] === 'object') {
83
- result[currentSection][m[1]] = m[2].trim();
84
- }
85
- }
86
- }
87
- return result;
88
- }
89
-
90
- // ── HTTP 辅助 ──
91
-
92
- async function fetchJson(url, options = {}) {
93
- const controller = new AbortController();
94
- const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
95
- try {
96
- const res = await fetch(url, { ...options, signal: controller.signal });
97
- if (!res.ok) {
98
- const text = await res.text().catch(() => '');
99
- console.warn(`[sync] ${options.method || 'GET'} ${url} → ${res.status} ${text.slice(0, 200)}`);
100
- return null;
101
- }
102
- const ct = res.headers.get('content-type') || '';
103
- if (ct.includes('application/json')) {
104
- return res.json();
105
- }
106
- return null;
107
- } catch (err) {
108
- if (err.name === 'AbortError') {
109
- console.warn(`[sync] ${url} 请求超时 (${REQUEST_TIMEOUT_MS}ms)`);
110
- } else {
111
- console.warn(`[sync] ${url} 请求失败: ${err.message}`);
112
- }
113
- return null;
114
- } finally {
115
- clearTimeout(timer);
116
- }
117
- }
118
-
119
- // ── SyncManager ──
120
-
121
- export class SyncManager {
122
- constructor(cwd) {
123
- this.cwd = cwd;
124
- }
125
-
126
- /**
127
- * 连接 SillyHub 平台。
128
- * 保存配置到 .sillyspec/local.yaml,发送 ping 验证连接。
129
- */
130
- async connect(url, token) {
131
- // 验证连接
132
- const healthUrl = `${url.replace(/\/+$/, '')}/api/health`;
133
- const result = await fetchJson(healthUrl);
134
- if (result === null) {
135
- console.warn(`[sync] 平台连接验证失败: ${url}`);
136
- return;
137
- }
138
- console.log(`[sync] 平台连接成功: ${url}`);
139
-
140
- // 写入 local.yaml
141
- const config = readLocalYaml(this.cwd);
142
- config.platform = {
143
- url: url.replace(/\/+$/, ''),
144
- token,
145
- last_connected: new Date().toISOString(),
146
- };
147
- writeLocalYaml(this.cwd, config);
148
- }
149
-
150
- /**
151
- * 断开平台连接。
152
- * 从 local.yaml 删除 platform 配置段。
153
- */
154
- disconnect() {
155
- const p = join(this.cwd, LOCAL_YAML);
156
- if (!existsSync(p)) {
157
- console.log('[sync] 已断开连接(无配置文件)');
158
- return;
159
- }
160
- const config = readLocalYaml(this.cwd);
161
- if (!config.platform) {
162
- console.log('[sync] 已断开连接(未连接)');
163
- return;
164
- }
165
- delete config.platform;
166
- if (Object.keys(config).length === 0) {
167
- // 配置为空,删除整个文件
168
- try { unlinkSync(p); } catch { /* best effort */ }
169
- } else {
170
- writeLocalYaml(this.cwd, config);
171
- }
172
- console.log('[sync] 已断开连接');
173
- }
174
-
175
- /**
176
- * 增量同步变更的 progress 状态到平台。
177
- * 读取 ProgressManager.read() 的数据,POST 到平台。
178
- * 同步完成后更新 changes 表的 platform_last_sync 字段。
179
- */
180
- async sync(changeName) {
181
- const platform = this._getPlatform();
182
- if (!platform) {
183
- console.warn('[sync] 未连接平台,请先 sillyspec platform connect');
184
- return { synced: 0, errors: ['未连接平台'] };
185
- }
186
-
187
- if (!changeName) {
188
- console.warn('[sync] sync 需要指定变更名称 (changeName)');
189
- return { synced: 0, errors: ['未指定变更名称'] };
190
- }
191
-
192
- // 检查变更是否存在
193
- const changeDir = join(this.cwd, CHANGES_DIR, changeName);
194
- if (!existsSync(changeDir)) {
195
- console.warn(`[sync] 变更不存在: ${changeName}`);
196
- return { synced: 0, errors: [`变更不存在: ${changeName}`] };
197
- }
198
-
199
- // 读取 progress 数据(通过导入 ProgressManager 动态调用)
200
- let progressData;
201
- try {
202
- const { ProgressManager } = await import('./progress.js');
203
- const pm = new ProgressManager();
204
- progressData = await pm.read(this.cwd, changeName);
205
- } catch (err) {
206
- console.warn(`[sync] 读取 progress 失败 (${changeName}): ${err.message}`);
207
- return { synced: 0, errors: [`读取 progress 失败: ${err.message}`] };
208
- }
209
-
210
- // POST 到平台
211
- const syncUrl = `${platform.url}/api/changes/${changeName}/progress`;
212
- const result = await fetchJson(syncUrl, {
213
- method: 'POST',
214
- headers: {
215
- 'Content-Type': 'application/json',
216
- Authorization: `Bearer ${platform.token}`,
217
- },
218
- body: JSON.stringify(progressData),
219
- });
220
-
221
- if (!result) {
222
- return { synced: 0, errors: [`同步请求失败: ${changeName}`] };
223
- }
224
-
225
- // 更新 platform_last_sync
226
- try {
227
- const { ProgressManager } = await import('./progress.js');
228
- const pm = new ProgressManager();
229
- await pm._updatePlatformLastSync(this.cwd, changeName);
230
- } catch (err) {
231
- console.warn(`[sync] 更新 platform_last_sync 失败: ${err.message}`);
232
- }
233
-
234
- console.log(`[sync] 已同步变更: ${changeName}`);
235
- return { synced: 1, errors: [] };
236
- }
237
-
238
- /**
239
- * 同步四件套文档到平台(全量同步)。
240
- * POST {url}/api/changes/{changeName}/documents
241
- */
242
- async syncDocuments(changeName) {
243
- const platform = this._getPlatform();
244
- if (!platform) {
245
- console.warn('[sync] 未连接平台,请先 sillyspec platform connect');
246
- return { synced: 0, errors: ['未连接平台'] };
247
- }
248
-
249
- if (!changeName) {
250
- console.warn('[sync] syncDocuments 需要指定变更名称 (changeName)');
251
- return { synced: 0, errors: ['未指定变更名称'] };
252
- }
253
-
254
- const changeDir = join(this.cwd, CHANGES_DIR, changeName);
255
- if (!existsSync(changeDir)) {
256
- console.warn(`[sync] 变更不存在: ${changeName}`);
257
- return { synced: 0, errors: [`变更不存在: ${changeName}`] };
258
- }
259
-
260
- const documents = {};
261
- let syncedCount = 0;
262
- const errors = [];
263
-
264
- for (const docFile of DOCUMENT_FILES) {
265
- const docPath = join(changeDir, docFile);
266
- if (existsSync(docPath)) {
267
- try {
268
- documents[docFile] = readFileSync(docPath, 'utf8');
269
- syncedCount++;
270
- } catch (err) {
271
- errors.push(`读取 ${docFile} 失败: ${err.message}`);
272
- }
273
- }
274
- }
275
-
276
- if (syncedCount === 0) {
277
- console.warn(`[sync] 未找到可同步的文档: ${changeName}`);
278
- return { synced: 0, errors: [...errors, '无可用文档'] };
279
- }
280
-
281
- const docUrl = `${platform.url}/api/changes/${changeName}/documents`;
282
- const result = await fetchJson(docUrl, {
283
- method: 'POST',
284
- headers: {
285
- 'Content-Type': 'application/json',
286
- Authorization: `Bearer ${platform.token}`,
287
- },
288
- body: JSON.stringify(documents),
289
- });
290
-
291
- if (!result) {
292
- return { synced: 0, errors: [...errors, '文档同步请求失败'] };
293
- }
294
-
295
- console.log(`[sync] 已同步 ${syncedCount} 个文档: ${changeName}`);
296
- return { synced: syncedCount, errors };
297
- }
298
-
299
- /**
300
- * 检查变更的审批状态。
301
- * GET {url}/api/changes/{changeName}/approval
302
- * 返回 { status: 'pending'|'approved'|'rejected', reason?: string }
303
- */
304
- async checkApproval(changeName) {
305
- const platform = this._getPlatform();
306
- if (!platform) {
307
- console.warn('[sync] 未连接平台,请先 sillyspec platform connect');
308
- return { status: 'pending', reason: '未连接平台' };
309
- }
310
-
311
- if (!changeName) {
312
- console.warn('[sync] checkApproval 需要指定变更名称 (changeName)');
313
- return { status: 'pending', reason: '未指定变更名称' };
314
- }
315
-
316
- const approvalUrl = `${platform.url}/api/changes/${changeName}/approval`;
317
- const result = await fetchJson(approvalUrl, {
318
- headers: { Authorization: `Bearer ${platform.token}` },
319
- });
320
-
321
- if (!result) {
322
- console.warn(`[sync] 检查审批状态失败: ${changeName}`);
323
- return { status: 'pending', reason: '请求失败' };
324
- }
325
-
326
- // 更新本地 approvals 表
327
- try {
328
- const { ProgressManager } = await import('./progress.js');
329
- const pm = new ProgressManager();
330
- await pm._updateApprovalStatus(this.cwd, changeName, result.status, result.reason);
331
- } catch (err) {
332
- console.warn(`[sync] 更新本地审批状态失败: ${err.message}`);
333
- }
334
-
335
- if (result.status === 'rejected') {
336
- console.warn(`[sync] 审批被拒绝 (${changeName}): ${result.reason || '无原因'}`);
337
- }
338
-
339
- return result;
340
- }
341
-
342
- /**
343
- * 查看同步状态。
344
- * 读取 local.yaml 中的 platform 配置,返回连接信息。
345
- */
346
- status() {
347
- const config = readLocalYaml(this.cwd);
348
- const platform = config.platform;
349
- if (!platform) {
350
- return { connected: false };
351
- }
352
- return {
353
- connected: true,
354
- url: platform.url,
355
- lastSync: platform.last_connected || null,
356
- };
357
- }
358
-
359
- /** 获取当前平台配置,未连接返回 null */
360
- _getPlatform() {
361
- const config = readLocalYaml(this.cwd);
362
- return config.platform || null;
363
- }
364
- }
365
-
366
- // ── CLI 入口函数 ──
367
-
368
- /**
369
- * syncModule — sillyspec platform 子命令入口
370
- *
371
- * 用法:
372
- * sillyspec platform connect <url> <token>
373
- * sillyspec platform disconnect
374
- * sillyspec platform sync [changeName]
375
- * sillyspec platform sync-docs [changeName]
376
- * sillyspec platform approval <changeName>
377
- * sillyspec platform status
378
- *
379
- * @param {string[]} args — 子命令及参数
380
- * @param {string} cwd — 工作目录
381
- */
382
- /**
383
- * 便捷函数导出 — 供 index.js 和 run.js 直接调用
384
- */
385
- export async function connect(url, token, cwd) {
386
- return new SyncManager(cwd).connect(url, token);
387
- }
388
-
389
- export async function disconnect(cwd) {
390
- return new SyncManager(cwd).disconnect();
391
- }
392
-
393
- export async function sync(changeName, cwd) {
394
- return new SyncManager(cwd).sync(changeName);
395
- }
396
-
397
- export async function syncDocuments(changeName, cwd) {
398
- return new SyncManager(cwd).syncDocuments(changeName);
399
- }
400
-
401
- export async function checkApproval(changeName, cwd) {
402
- return new SyncManager(cwd).checkApproval(changeName);
403
- }
404
-
405
- export async function approve(changeName, cwd) {
406
- // TODO: SillyHub 平台侧实现后启用
407
- console.warn(`[sync] approve 尚未实现 (${changeName})`);
408
- }
409
-
410
- export async function reject(changeName, reason, cwd) {
411
- // TODO: SillyHub 平台侧实现后启用
412
- console.warn(`[sync] reject 尚未实现 (${changeName})`);
413
- }
414
-
415
- export async function status(cwd) {
416
- const sm = new SyncManager(cwd);
417
- const st = sm.status();
418
- if (!st.connected) {
419
- console.log('平台: 未连接');
420
- } else {
421
- console.log(`平台: ${st.url}`);
422
- console.log(`上次连接: ${st.lastSync || '未知'}`);
423
- }
424
- }
425
-
426
- /**
427
- * syncModule — sillyspec platform 子命令入口
428
- */
429
- export async function syncModule(args, cwd) {
430
- const sm = new SyncManager(cwd);
431
-
432
- const sub = args[0];
433
-
434
- switch (sub) {
435
- case 'connect': {
436
- const url = args[1];
437
- const token = args[2];
438
- if (!url || !token) {
439
- console.error('用法: sillyspec platform connect <url> <token>');
440
- process.exit(1);
441
- }
442
- await sm.connect(url, token);
443
- break;
444
- }
445
-
446
- case 'disconnect':
447
- sm.disconnect();
448
- break;
449
-
450
- case 'sync': {
451
- const changeName = args[1];
452
- const result = await sm.sync(changeName);
453
- if (result.errors.length > 0) {
454
- console.log(`同步完成,${result.errors.length} 个错误`);
455
- }
456
- break;
457
- }
458
-
459
- case 'sync-docs':
460
- case 'sync-documents': {
461
- const changeName = args[1];
462
- const result = await sm.syncDocuments(changeName);
463
- if (result.errors.length > 0) {
464
- console.log(`文档同步完成,${result.errors.length} 个错误`);
465
- }
466
- break;
467
- }
468
-
469
- case 'approval':
470
- case 'check-approval': {
471
- const changeName = args[1];
472
- if (!changeName) {
473
- console.error('用法: sillyspec platform approval <changeName>');
474
- process.exit(1);
475
- }
476
- const approval = await sm.checkApproval(changeName);
477
- console.log(`审批状态: ${approval.status}${approval.reason ? ` (${approval.reason})` : ''}`);
478
- break;
479
- }
480
-
481
- case 'status': {
482
- const st = sm.status();
483
- if (!st.connected) {
484
- console.log('平台: 未连接');
485
- } else {
486
- console.log(`平台: ${st.url}`);
487
- console.log(`上次连接: ${st.lastSync || '未知'}`);
488
- }
489
- break;
490
- }
491
-
492
- default:
493
- console.error(`未知子命令: ${sub || '(无)'}`);
494
- console.error('可用命令: connect, disconnect, sync, sync-docs, approval, status');
495
- process.exit(1);
496
- }
497
- }