sillyspec 3.20.2 → 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 (208) 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 +1 -7
  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 -688
  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 -3008
  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 -485
  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/CLAUDE.md +0 -18
  131. package/docs/brainstorm-plan-contract.md +0 -64
  132. package/docs/plan-execute-contract.md +0 -123
  133. package/docs/platform-scan-protocol.md +0 -298
  134. package/docs/revision-mode.md +0 -115
  135. package/docs/sillyspec/file-lifecycle/known-implementation-gaps.md +0 -99
  136. package/docs/sillyspec/file-lifecycle/platform-workflows-sync.md +0 -218
  137. package/docs/sillyspec/file-lifecycle/stage-artifacts.md +0 -167
  138. package/docs/sillyspec/file-lifecycle/storage-and-state.md +0 -148
  139. package/docs/sillyspec/file-lifecycle/worktree-and-guard.md +0 -193
  140. package/docs/sillyspec/file-lifecycle.md +0 -125
  141. package/docs/workflow-contract-regression.md +0 -106
  142. package/docs/worktree-isolation.md +0 -252
  143. package/packages/dashboard/dist/assets/index-O2W5RV4z.css +0 -1
  144. package/packages/dashboard/dist/prototype-dashboard.html +0 -836
  145. package/packages/dashboard/dist/prototype-overview.html +0 -256
  146. package/packages/dashboard/public/prototype-dashboard.html +0 -836
  147. package/packages/dashboard/public/prototype-overview.html +0 -256
  148. package/packages/dashboard/src/components/HResizeHandle.vue +0 -48
  149. package/packages/dashboard/src/components/ProjectCard.vue +0 -187
  150. package/packages/dashboard/src/components/VResizeHandle.vue +0 -61
  151. package/packages/dashboard/src/composables/useLayout.js +0 -131
  152. package/src/brainstorm-postcheck.js +0 -158
  153. package/src/change-list.js +0 -52
  154. package/src/change-risk-profile.js +0 -352
  155. package/src/classify-change.js +0 -73
  156. package/src/constants.js +0 -70
  157. package/src/contract-matrix.js +0 -278
  158. package/src/db.js +0 -201
  159. package/src/endpoint-extractor.js +0 -315
  160. package/src/hooks/claude-pre-tool-use.cjs +0 -125
  161. package/src/hooks/worktree-guard.js +0 -761
  162. package/src/knowledge-match.js +0 -130
  163. package/src/modules.js +0 -482
  164. package/src/scan-postcheck.js +0 -383
  165. package/src/stage-contract.js +0 -700
  166. package/src/stages/brainstorm-auto.js +0 -229
  167. package/src/stages/explore.js +0 -34
  168. package/src/stages/knowledge.js +0 -498
  169. package/src/stages/plan-postcheck.js +0 -513
  170. package/src/sync.js +0 -497
  171. package/src/task-review.js +0 -346
  172. package/src/workflow.js +0 -785
  173. package/src/worktree-apply.js +0 -549
  174. package/src/worktree.js +0 -932
  175. package/templates/workflows/archive-impact.yaml +0 -79
  176. package/templates/workflows/scan-docs.yaml +0 -132
  177. package/test/brainstorm-plan-contract.test.mjs +0 -273
  178. package/test/check-syntax.mjs +0 -26
  179. package/test/cli-top-level-aliases.test.mjs +0 -174
  180. package/test/contract-artifacts.test.mjs +0 -323
  181. package/test/decision-supersede.test.mjs +0 -277
  182. package/test/knowledge-match.test.mjs +0 -231
  183. package/test/plan-execute-contract.test.mjs +0 -330
  184. package/test/plan-optimization.test.mjs +0 -572
  185. package/test/platform-artifacts.test.mjs +0 -190
  186. package/test/platform-failure-samples.test.mjs +0 -199
  187. package/test/platform-recovery-chain.test.mjs +0 -179
  188. package/test/platform-recovery.test.mjs +0 -167
  189. package/test/platform-scan-p0.test.mjs +0 -175
  190. package/test/revision-v1.test.mjs +0 -1145
  191. package/test/run-sanitize-project-name.test.mjs +0 -51
  192. package/test/run-scan-postcheck-fail.test.mjs +0 -64
  193. package/test/run-scan-project-parse.test.mjs +0 -200
  194. package/test/run-tests.mjs +0 -48
  195. package/test/scan-docs-yaml-placeholders.test.mjs +0 -84
  196. package/test/scan-knowledge.test.mjs +0 -175
  197. package/test/scan-paths.test.mjs +0 -68
  198. package/test/scan-postcheck-project-priority.test.mjs +0 -85
  199. package/test/scan-postcheck.test.mjs +0 -197
  200. package/test/scan-workflow-anyfailed-block.test.mjs +0 -52
  201. package/test/spec-dir.test.mjs +0 -206
  202. package/test/stage-contract-failed-post-check.test.mjs +0 -102
  203. package/test/stage-contract.test.mjs +0 -299
  204. package/test/stage-definitions.test.mjs +0 -39
  205. package/test/wait-gates.test.mjs +0 -501
  206. package/test/workflow-spec-base.test.mjs +0 -142
  207. package/test/worktree-guard.test.mjs +0 -136
  208. 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
- }