sillyspec 3.17.15 → 3.18.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/.npmrc.bak ADDED
File without changes
@@ -0,0 +1,298 @@
1
+ # 平台 Scan 产物协议
2
+
3
+ SillySpec 平台执行模式的核心设计:**SillySpec 写产物,SillyHub 读产物**。平台不看 stdout,只靠文件系统判断 scan 成功、失败原因和证据文件位置。
4
+
5
+ ## 状态枚举(src/constants.js)
6
+
7
+ 所有平台产物共享同一套枚举值,SillyHub 直接使用常量,不猜字符串。
8
+
9
+ ### SCAN_STATUS
10
+
11
+ | 值 | 说明 |
12
+ ---|---|
13
+ | `pending` | scan 未开始 |
14
+ | `in_progress` | scan 进行中 |
15
+ | `success` | scan 成功,所有检查通过 |
16
+ | `completed_with_warnings` | scan 成功但有警告 |
17
+ | `failed_post_check` | scan 失败,post-check 不通过 |
18
+
19
+ ### POINTER_STATUS
20
+
21
+ | 值 | 说明 |
22
+ ---|---|
23
+ | `active` | 指针活跃,任务进行中 |
24
+ | `scan_completed` | scan 已完成 |
25
+ | `stale` | 指针过时(完成超过 24h,建议清理) |
26
+ | `corrupted` | 指针损坏(缺少必要字段) |
27
+
28
+ ### CHECK_SEVERITY
29
+
30
+ | 值 | 说明 |
31
+ ---|---|
32
+ | `failed` | 严重:阻止成功 |
33
+ | `warning` | 警告:不阻止成功 |
34
+ | `passed` | 通过 |
35
+
36
+ ## 目录结构
37
+
38
+ ```
39
+ <spec_root>/
40
+ ├── manifest.json # 扫描元数据 + 产物索引
41
+ ├── docs/<project>/scan/ # 项目文档
42
+ │ ├── ARCHITECTURE.md
43
+ │ ├── CONVENTIONS.md
44
+ │ ├── PROJECT.md
45
+ │ ├── STACK.md
46
+ │ ├── STRUCTURE.md
47
+ │ └── ... (7 份必需文档)
48
+ ├── projects/*.yaml # 子项目注册
49
+ ├── changes/<change-name>/ # 变更目录
50
+ └── .runtime/
51
+ ├── postcheck-result.json # post-check 结构化结果
52
+ └── platform-scan.json # 平台参数持久化(主文件)
53
+
54
+ <runtime_root>/
55
+ └── scan-runs/<scan_run_id>/
56
+ └── workflow-runs/
57
+ └── <timestamp>-<workflow>-<project>-<status>.json # workflow 检查结果
58
+
59
+ <source_root>/
60
+ ├── .sillyspec-platform.json # 平台参数恢复指针(轻量,不在 .sillyspec 内)
61
+ └── (源码,禁止 .sillyspec/ 污染)
62
+ ```
63
+
64
+ ## manifest.json
65
+
66
+ scan 完成后写入 `<spec_root>/manifest.json`,是 SillyHub 判断 scan 结果的入口文件。
67
+
68
+ ### 结构
69
+
70
+ ```json
71
+ {
72
+ "workspace_id": "ws-xxx",
73
+ "scan_run_id": "scan-2026-06-14-test-001",
74
+ "source_root": "/path/to/source",
75
+ "spec_root": "/path/to/spec",
76
+ "runtime_root": "/path/to/runtime",
77
+ "source_commit": "abc123...",
78
+ "source_commit_error": null,
79
+ "generated_at": "2026-06-14T01:50:00.000Z",
80
+ "schema_version": 1,
81
+ "postcheck_result_path": "<spec_root>/.runtime/postcheck-result.json",
82
+ "workflow_runs_dir": "<runtime_root>/scan-runs/<scan_run_id>/workflow-runs",
83
+ "platform_pointer_path": "<source_root>/.sillyspec-platform.json",
84
+ "platform_pointer_status": "active",
85
+ "scan_post_check": {
86
+ "status": "success | completed_with_warnings | failed_post_check",
87
+ "checks": [...]
88
+ }
89
+ }
90
+ ```
91
+
92
+ ### 字段说明
93
+
94
+ | 字段 | 类型 | 说明 |
95
+ |---|---|---|
96
+ | `workspace_id` | string \| null | SillyHub workspace 标识 |
97
+ | `scan_run_id` | string \| null | 本次 scan 唯一标识 |
98
+ | `source_root` | string | 源码目录绝对路径 |
99
+ | `spec_root` | string \| null | 规范目录(specDir) |
100
+ | `runtime_root` | string \| null | 运行时产物目录 |
101
+ | `source_commit` | string \| null | 源码 HEAD commit hash |
102
+ | `source_commit_error` | string \| undefined | commit 获取失败原因 |
103
+ | `generated_at` | string (ISO 8601) | manifest 生成时间 |
104
+ | `schema_version` | number | 产物协议版本,当前为 1 |
105
+ | `postcheck_result_path` | string \| null | post-check 结构化结果路径 |
106
+ | `workflow_runs_dir` | string \| null | workflow 检查结果目录 |
107
+ | `platform_pointer_path` | string | 平台指针文件路径 |
108
+ | `platform_pointer_status` | string | 初始 `active`,由指针文件独立更新 |
109
+ | `scan_post_check` | object \| undefined | post-check 结果(写入后追加) |
110
+
111
+ ### 判断 scan 结果
112
+
113
+ SillyHub 消费 manifest 的方式:
114
+
115
+ 1. 读取 `<spec_root>/manifest.json`
116
+ 2. 检查 `scan_post_check.status`:
117
+ - `success` → scan 成功
118
+ - `completed_with_warnings` → scan 成功但有警告
119
+ - `failed_post_check` → scan 失败
120
+ 3. 如果失败,读 `scan_post_check.checks` 获取具体失败项
121
+ 4. 读 `postcheck_result_path` 获取完整结构化结果
122
+ 5. 读 `workflow_runs_dir` 获取 workflow 检查证据
123
+
124
+ ## .sillyspec-platform.json
125
+
126
+ 跨 `--done` 生命周期的轻量指针文件,存储在 `<source_root>/.sillyspec-platform.json`(不在 `.sillyspec/` 内,不污染源码结构)。
127
+
128
+ ### 生命周期
129
+
130
+ | 阶段 | 行为 |
131
+ |---|---|
132
+ | **创建** | `run scan --spec-root` 时,写入 cwd 根目录 |
133
+ | **读取** | 每次 `run`/`--done`/`--skip` 时,优先从 pointer 恢复平台参数 |
134
+ | **更新** | 每次 `run` 时刷新 `savedAt` |
135
+ | **完成标记** | scan post-check 后追加 `status=scan_completed` + `completedAt` + `scanStatus` |
136
+ | **异常检测** | pointer 存在但缺 `specRoot` 时报错退出 |
137
+ | **清理** | 无自动清理。`sillyspec platform pointer` 查看状态,`sillyspec platform pointer --cleanup` 手动清理 |
138
+
139
+ ### CLI 检查命令
140
+
141
+ ```bash
142
+ # 查看指针状态
143
+ sillyspec platform pointer
144
+
145
+ # 清理过时/损坏指针
146
+ sillyspec platform pointer --cleanup
147
+ ```
148
+
149
+ 输出示例:
150
+ ```
151
+ 📄 指针文件: /path/to/source/.sillyspec-platform.json
152
+ specRoot: /path/to/spec
153
+ runtimeRoot: /path/to/runtime
154
+ workspaceId: ws-xxx
155
+ scanRunId: scan-2026-06-14-test-001
156
+ savedAt: 2026-06-14T01:50:00.000Z
157
+ 状态: stale ⚠️
158
+ completedAt: 2026-06-12T01:00:00.000Z
159
+ scanStatus: success
160
+ ⚠️ 指针已过时(完成超过 24h),可以安全删除。
161
+ ```
162
+
163
+ 状态判定逻辑:
164
+ - 缺少 `specRoot` → `corrupted`
165
+ - `status=scan_completed` 且 `completedAt` 超过 24h → `stale`
166
+ - `status=scan_completed` 且未超时 → `scan_completed` ✅
167
+ - 无 `status` 字段 → `active` 🔄
168
+
169
+ ### 结构
170
+
171
+ ```json
172
+ {
173
+ "specRoot": "/path/to/spec",
174
+ "runtimeRoot": "/path/to/runtime",
175
+ "workspaceId": "ws-xxx",
176
+ "scanRunId": "scan-2026-06-14-test-001",
177
+ "savedAt": "2026-06-14T01:50:00.000Z"
178
+ }
179
+ ```
180
+
181
+ scan 完成后追加:
182
+
183
+ ```json
184
+ {
185
+ "status": "scan_completed",
186
+ "completedAt": "2026-06-14T01:52:00.000Z",
187
+ "scanStatus": "success"
188
+ }
189
+ ```
190
+
191
+ ## postcheck-result.json
192
+
193
+ 写入 `<spec_root>/.runtime/postcheck-result.json`(平台模式)或 `<cwd>/.sillyspec/.runtime/postcheck-result.json`(本地模式)。
194
+
195
+ ### 结构
196
+
197
+ ```json
198
+ {
199
+ "workspace_id": "ws-xxx",
200
+ "scan_run_id": "scan-2026-06-14-test-001",
201
+ "status": "success | completed_with_warnings | failed_post_check",
202
+ "source_root": "/path/to/source",
203
+ "spec_root": "/path/to/spec",
204
+ "runtime_root": "/path/to/runtime",
205
+ "checks": [
206
+ {
207
+ "name": "source_root_docs_leak",
208
+ "severity": "failed | warning",
209
+ "detail": "..."
210
+ }
211
+ ],
212
+ "source_root_leak": true,
213
+ "docs_missing": ["ARCHITECTURE.md"],
214
+ "profile": {
215
+ "mode": "quick | standard | deep",
216
+ "file_count": 10,
217
+ "source_bytes": 102400,
218
+ "project_count": 1,
219
+ "reason": "..."
220
+ }
221
+ }
222
+ ```
223
+
224
+ ### check 类型
225
+
226
+ | check name | severity | 说明 |
227
+ |---|---|---|
228
+ | `source_root_docs_leak` | failed | docs 文档泄漏到 source_root |
229
+ | `source_root_leak` | failed | projects/workflows/knowledge/manifest/local 泄漏到 source_root |
230
+ | `all_docs_missing` | failed | 7 份必需文档全部缺失 |
231
+ | `partial_docs_missing` | failed | 部分文档缺失 |
232
+ | `docs_missing_header` | warning | 文档缺少 frontmatter |
233
+ | `local_config_invalid` | warning | local.yaml 中命令不存在 |
234
+ | `tool_use_error` | warning | AI 执行工具调用错误 |
235
+ | `api_error` | warning | API 错误(529/429/超时) |
236
+
237
+ ## workflow-runs
238
+
239
+ 写入 `<runtime_root>/scan-runs/<scan_run_id>/workflow-runs/`(平台模式)或 `<cwd>/.sillyspec/.runtime/workflow-runs/`(本地模式)。
240
+
241
+ 每个文件命名:`<timestamp>-<workflow>-<project>-<status>.json`
242
+
243
+ ### 结构
244
+
245
+ ```json
246
+ {
247
+ "run_id": "20260614015000-scan-docs-test-project-pass",
248
+ "created_at": "2026-06-14T01:50:00.000Z",
249
+ "source": "run.js",
250
+ "stage": "scan",
251
+ "step": "深度扫描",
252
+ "workflow": "scan-docs",
253
+ "project": "test-project",
254
+ "status": "pass | fail",
255
+ "spec_version": 1,
256
+ "roles": [...],
257
+ "workflow_checks": [...],
258
+ "failures": [...],
259
+ "retry_prompts": [...]
260
+ }
261
+ ```
262
+
263
+ ## source_root 零污染
264
+
265
+ 平台模式的核心约束:source_root 下不产生 `.sillyspec/` 目录。
266
+
267
+ post-check 会检查以下路径是否存在泄漏:
268
+ - `<source_root>/.sillyspec/docs/` — 文档泄漏
269
+ - `<source_root>/.sillyspec/projects/` — 项目注册泄漏
270
+ - `<source_root>/.sillyspec/workflows/` — 工作流泄漏
271
+ - `<source_root>/.sillyspec/knowledge/` — 术语泄漏
272
+ - `<source_root>/.sillyspec/manifest.json` — manifest 泄漏
273
+ - `<source_root>/.sillyspec/local.yaml` — 配置泄漏
274
+
275
+ ## 产物消费优先级
276
+
277
+ SillyHub 判断 scan 结果的推荐顺序:
278
+
279
+ 1. `manifest.json` → `scan_post_check.overall_status` → 快速判断成功/失败
280
+ 2. `postcheck-result.json` → 完整检查明细 + failure_categories
281
+ 3. `workflow-runs/*.json` → workflow 检查证据
282
+ 4. `docs/<project>/scan/*.md` → 实际文档内容
283
+
284
+ ### failure_categories
285
+
286
+ `postcheck-result.json` 中的 `failure_categories` 提供分类视图:
287
+
288
+ | 类别 | 包含的 check |
289
+ ---|---|
290
+ | `path_pollution` | source_root_leak, source_root_docs_leak |
291
+ | `missing_outputs` | all_docs_missing, partial_docs_missing, missing_docs |
292
+ | `bad_references` | local_config_invalid |
293
+ | `quality_warnings` | tool_use_error, api_error_529, rate_limit_exhausted, fallback_or_skip |
294
+ | `violations` | manifest_write_failed, project_list_parse_failed + 所有 path_pollution |
295
+
296
+ SillyHub 可以按类别快速定位问题域,而不需要遍历所有 checks。
297
+
298
+ 不需要解析 stdout。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.17.15",
3
+ "version": "3.18.0",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -0,0 +1,70 @@
1
+ /**
2
+ * SillySpec 平台状态枚举
3
+ *
4
+ * 所有平台产物(manifest、pointer、postcheck、workflow-runs)共享这些枚举。
5
+ * SillyHub 侧直接使用常量值,不需要猜字符串。
6
+ */
7
+
8
+ // ── scan 阶段状态 ──
9
+ export const SCAN_STATUS = Object.freeze({
10
+ PENDING: 'pending', // scan 未开始
11
+ IN_PROGRESS: 'in_progress', // scan 进行中
12
+ SUCCESS: 'success', // scan 成功(所有检查通过)
13
+ COMPLETED_WITH_WARNINGS: 'completed_with_warnings', // scan 成功但有警告
14
+ FAILED_POST_CHECK: 'failed_post_check', // scan 失败(post-check 不通过)
15
+ })
16
+
17
+ // ── 平台指针状态 ──
18
+ export const POINTER_STATUS = Object.freeze({
19
+ ACTIVE: 'active', // 指针活跃,任务进行中
20
+ SCAN_COMPLETED: 'scan_completed', // scan 已完成
21
+ STALE: 'stale', // 指针过时(完成超过 24h,建议清理)
22
+ CORRUPTED: 'corrupted', // 指针损坏(缺少必要字段)
23
+ })
24
+
25
+ // ── workflow 检查状态 ──
26
+ export const WORKFLOW_STATUS = Object.freeze({
27
+ PASS: 'pass',
28
+ FAIL: 'fail',
29
+ SKIPPED: 'skipped',
30
+ })
31
+
32
+ // ── postcheck 检查严重级别 ──
33
+ export const CHECK_SEVERITY = Object.freeze({
34
+ FAILED: 'failed',
35
+ WARNING: 'warning',
36
+ PASSED: 'passed',
37
+ })
38
+
39
+ // ── stage 步骤状态 ──
40
+ export const STEP_STATUS = Object.freeze({
41
+ PENDING: 'pending',
42
+ IN_PROGRESS: 'in-progress',
43
+ COMPLETED: 'completed',
44
+ SKIPPED: 'skipped',
45
+ })
46
+
47
+ // ── stage 阶段状态 ──
48
+ export const STAGE_STATUS = Object.freeze({
49
+ PENDING: 'pending',
50
+ IN_PROGRESS: 'in_progress',
51
+ COMPLETED: 'completed',
52
+ FAILED_POST_CHECK: 'failed_post_check',
53
+ })
54
+
55
+ /**
56
+ * 判断指针是否过时(完成超过 24h)
57
+ */
58
+ export function isPointerStale(pointer) {
59
+ if (!pointer.completedAt) return false
60
+ const completed = new Date(pointer.completedAt)
61
+ const age = Date.now() - completed.getTime()
62
+ return age > 24 * 60 * 60 * 1000
63
+ }
64
+
65
+ /**
66
+ * 判断指针是否损坏(缺少必要字段)
67
+ */
68
+ export function isPointerCorrupted(pointer) {
69
+ return !pointer || !pointer.specRoot || !pointer.savedAt
70
+ }
package/src/index.js CHANGED
@@ -451,6 +451,7 @@ SillySpec platform — SillyHub 平台同步
451
451
  sillyspec platform sync [--change <name>]
452
452
  sillyspec platform sync-docs [--change <name>]
453
453
  sillyspec platform status
454
+ sillyspec platform pointer [--cleanup]
454
455
  sillyspec platform approve <change-name>
455
456
  sillyspec platform reject <change-name> [--reason <reason>]
456
457
  `);
@@ -466,6 +467,60 @@ SillySpec platform — SillyHub 平台同步
466
467
  }
467
468
 
468
469
  switch (platformSub) {
470
+ case 'pointer': {
471
+ // 指针状态检查(不依赖 sync 模块)
472
+ const { readFileSync, existsSync } = await import('fs')
473
+ const { join } = await import('path')
474
+ const { POINTER_STATUS, isPointerStale, isPointerCorrupted } = await import('./constants.js')
475
+ const pointerPath = join(dir, '.sillyspec-platform.json')
476
+
477
+ if (!existsSync(pointerPath)) {
478
+ console.log('ℹ️ 无平台指针文件。当前不在平台模式或未进行过平台 scan。')
479
+ break
480
+ }
481
+
482
+ try {
483
+ const pointer = JSON.parse(readFileSync(pointerPath, 'utf8'))
484
+ console.log(`📄 指针文件: ${pointerPath}`)
485
+ console.log(` specRoot: ${pointer.specRoot || '(缺失 ❌)'}`)
486
+ console.log(` runtimeRoot: ${pointer.runtimeRoot || '(未设置)'}`)
487
+ console.log(` workspaceId: ${pointer.workspaceId || '(未设置)'}`)
488
+ console.log(` scanRunId: ${pointer.scanRunId || '(未设置)'}`)
489
+ console.log(` savedAt: ${pointer.savedAt || '(未知)'}`)
490
+
491
+ if (isPointerCorrupted(pointer)) {
492
+ console.log(` 状态: ${POINTER_STATUS.CORRUPTED} ❌`)
493
+ console.log(` ⚠️ 指针损坏(缺少 specRoot),建议删除后重新运行平台 scan。`)
494
+ if (platformArgs.includes('--cleanup')) {
495
+ const { unlinkSync } = await import('fs')
496
+ unlinkSync(pointerPath)
497
+ console.log(` 🗑️ 已清理损坏指针。`)
498
+ }
499
+ } else if (pointer.status === POINTER_STATUS.SCAN_COMPLETED) {
500
+ if (isPointerStale(pointer)) {
501
+ console.log(` 状态: ${POINTER_STATUS.STALE} ⚠️`)
502
+ console.log(` completedAt: ${pointer.completedAt}`)
503
+ console.log(` scanStatus: ${pointer.scanStatus || '(未知)'}`)
504
+ console.log(` ⚠️ 指针已过时(完成超过 24h),可以安全删除。`)
505
+ if (platformArgs.includes('--cleanup')) {
506
+ const { unlinkSync } = await import('fs')
507
+ unlinkSync(pointerPath)
508
+ console.log(` 🗑️ 已清理过时指针。`)
509
+ }
510
+ } else {
511
+ console.log(` 状态: ${pointer.status} ✅`)
512
+ console.log(` completedAt: ${pointer.completedAt}`)
513
+ console.log(` scanStatus: ${pointer.scanStatus || '(未知)'}`)
514
+ }
515
+ } else {
516
+ console.log(` 状态: ${POINTER_STATUS.ACTIVE} 🔄`)
517
+ }
518
+ } catch (e) {
519
+ console.log(` 状态: ${POINTER_STATUS.CORRUPTED} ❌`)
520
+ console.log(` ⚠️ 指针文件损坏: ${e.message}`)
521
+ }
522
+ break;
523
+ }
469
524
  case 'connect': {
470
525
  const url = platformArgs[0];
471
526
  if (!url) {
package/src/run.js CHANGED
@@ -9,6 +9,7 @@ import { existsSync, readdirSync, mkdirSync, writeFileSync, appendFileSync, read
9
9
  import { createRequire } from 'module'
10
10
  const require = createRequire(import.meta.url)
11
11
  import { ProgressManager } from './progress.js'
12
+ import { SCAN_STATUS, POINTER_STATUS, isPointerCorrupted } from './constants.js'
12
13
 
13
14
  /**
14
15
  * 在容器/Docker 环境下,git 可能因目录所有权不匹配报 dubious ownership。
@@ -613,21 +614,29 @@ async function outputStep(stageName, stepIndex, steps, cwd, changeName, dbProjec
613
614
  }
614
615
  profileDirectives.push(`--output 只需要列出文件名,不要写长篇总结。`)
615
616
  promptText = profileDirectives.join('\n') + '\n\n' + promptText
617
+
618
+ // scanProfile 分支也要替换占位符(非 platform 模式也会走到这里)
619
+ const _pName = dbProjectName || basename(cwd)
620
+ const _specSS = platformOpts?.specRoot || join(cwd, '.sillyspec')
621
+ const _docsRoot = join(_specSS, 'docs', _pName)
622
+ promptText = promptText.replace(/\{DOCS_ROOT\}/g, _docsRoot)
623
+ promptText = promptText.replace(/\{PROJECTS_ROOT\}/g, join(_specSS, 'projects'))
624
+ promptText = promptText.replace(/\{WORKFLOWS_ROOT\}/g, join(_specSS, 'workflows'))
625
+ promptText = promptText.replace(/\{KNOWLEDGE_ROOT\}/g, join(_specSS, 'knowledge'))
626
+ promptText = promptText.replace(/\{SPEC_ROOT\}/g, _specSS)
616
627
  } else {
617
628
  // 非 platform 模式也要替换占位符
618
- if (stageName === 'scan') {
619
- const projectName = dbProjectName || basename(cwd)
620
- const specSillyspec = join(cwd, '.sillyspec')
621
- const docsRoot = join(specSillyspec, 'docs', projectName)
622
- const projectsRoot = join(specSillyspec, 'projects')
623
- const workflowsRoot = join(specSillyspec, 'workflows')
624
- const knowledgeRoot = join(specSillyspec, 'knowledge')
625
- promptText = promptText.replace(/\{DOCS_ROOT\}/g, docsRoot)
626
- promptText = promptText.replace(/\{PROJECTS_ROOT\}/g, projectsRoot)
627
- promptText = promptText.replace(/\{WORKFLOWS_ROOT\}/g, workflowsRoot)
628
- promptText = promptText.replace(/\{KNOWLEDGE_ROOT\}/g, knowledgeRoot)
629
- promptText = promptText.replace(/\{SPEC_ROOT\}/g, specSillyspec)
630
- }
629
+ const projectName = dbProjectName || basename(cwd)
630
+ const specSillyspec = join(cwd, '.sillyspec')
631
+ const docsRoot = join(specSillyspec, 'docs', projectName)
632
+ const projectsRoot = join(specSillyspec, 'projects')
633
+ const workflowsRoot = join(specSillyspec, 'workflows')
634
+ const knowledgeRoot = join(specSillyspec, 'knowledge')
635
+ promptText = promptText.replace(/\{DOCS_ROOT\}/g, docsRoot)
636
+ promptText = promptText.replace(/\{PROJECTS_ROOT\}/g, projectsRoot)
637
+ promptText = promptText.replace(/\{WORKFLOWS_ROOT\}/g, workflowsRoot)
638
+ promptText = promptText.replace(/\{KNOWLEDGE_ROOT\}/g, knowledgeRoot)
639
+ promptText = promptText.replace(/\{SPEC_ROOT\}/g, specSillyspec)
631
640
  }
632
641
 
633
642
  // 注入模块上下文(brainstorm/plan/execute 阶段,基于 Module Context Index)
@@ -1891,7 +1900,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1891
1900
  if (stageName === 'scan' && (platformOpts.specRoot || platformOpts.runtimeRoot)) {
1892
1901
  try {
1893
1902
  stageData.scanMeta = stageData.scanMeta || {}; stageData.scanMeta.manifestWritten = false; // 默认失败
1894
- const { mkdirSync, writeFileSync } = await import('fs')
1903
+ const { mkdirSync, writeFileSync, readFileSync: _readFileSync } = await import('fs')
1895
1904
  const { join } = await import('path')
1896
1905
  const { execSync } = await import('child_process')
1897
1906
  const manifestDir = platformOpts.specRoot
@@ -1903,10 +1912,19 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1903
1912
  const manifest = {
1904
1913
  workspace_id: platformOpts.workspaceId || null,
1905
1914
  scan_run_id: platformOpts.scanRunId || null,
1915
+ source_root: cwd,
1916
+ spec_root: platformOpts.specRoot || null,
1917
+ runtime_root: platformOpts.runtimeRoot || null,
1906
1918
  source_commit: sourceCommit,
1907
1919
  source_commit_error: sourceCommit === null ? (scErr || 'unknown') : undefined,
1908
1920
  generated_at: new Date().toISOString(),
1909
1921
  schema_version: 1,
1922
+ postcheck_result_path: null,
1923
+ workflow_runs_dir: platformOpts.runtimeRoot
1924
+ ? join(platformOpts.runtimeRoot, 'scan-runs', platformOpts.scanRunId || 'unknown', 'workflow-runs')
1925
+ : null,
1926
+ platform_pointer_path: join(cwd, '.sillyspec-platform.json'),
1927
+ platform_pointer_status: POINTER_STATUS.ACTIVE,
1910
1928
  }
1911
1929
  const manifestPath = join(manifestDir, 'manifest.json')
1912
1930
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n')
@@ -1947,6 +1965,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1947
1965
  })
1948
1966
  if (postcheckJsonPath) {
1949
1967
  console.log(`📄 postcheck-result.json 已写入: ${postcheckJsonPath}`)
1968
+ manifest.postcheck_result_path = postcheckJsonPath
1950
1969
  }
1951
1970
 
1952
1971
  // 将 post-check 结果写入 manifest
@@ -1958,9 +1977,19 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
1958
1977
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n')
1959
1978
  console.log(`📄 manifest.json 已更新(含 post-check 结果)`)
1960
1979
 
1980
+ // 更新平台指针状态为 scan_completed
1981
+ const pointerPath = join(cwd, '.sillyspec-platform.json')
1982
+ try {
1983
+ const pointer = JSON.parse(_readFileSync(pointerPath, 'utf8'))
1984
+ pointer.status = POINTER_STATUS.SCAN_COMPLETED
1985
+ pointer.completedAt = new Date().toISOString()
1986
+ pointer.scanStatus = postResult.status
1987
+ writeFileSync(pointerPath, JSON.stringify(pointer, null, 2) + '\n')
1988
+ } catch {}
1989
+
1961
1990
  // failed_post_check 时强制阻止 clean success
1962
1991
  if (postResult.status === 'failed_post_check') {
1963
- stageData.status = 'failed_post_check'
1992
+ stageData.status = SCAN_STATUS.FAILED_POST_CHECK
1964
1993
  stageData.completedAt = new Date().toLocaleString('zh-CN',{hour12:false})
1965
1994
  await pm._write(cwd, progress, changeName)
1966
1995
  console.error(`\n❌ scan post-check 失败,状态设为 failed_post_check。不允许 clean success。`)
@@ -2135,7 +2164,14 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
2135
2164
  console.log(rp.prompt)
2136
2165
  }
2137
2166
  }
2138
- const saved = saveWorkflowRun(result, { cwd, source: 'run.js', stage: 'verify', step: steps[currentIdx]?.name })
2167
+ const saved = saveWorkflowRun(result, {
2168
+ cwd,
2169
+ source: 'run.js',
2170
+ stage: 'scan',
2171
+ step: steps[currentIdx]?.name,
2172
+ ...(platformOpts.runtimeRoot ? { runtimeRoot: platformOpts.runtimeRoot } : {}),
2173
+ ...(platformOpts.scanRunId ? { scanRunId: platformOpts.scanRunId } : {})
2174
+ })
2139
2175
  if (saved) console.log(`📁 结果已归档:${saved}`)
2140
2176
  }
2141
2177
  if (anyFailed) {
@@ -2165,7 +2201,14 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
2165
2201
  console.log(` └─ ${f}`)
2166
2202
  }
2167
2203
  }
2168
- const saved = saveWorkflowRun(result, { cwd, source: 'run.js', stage: 'archive', step: steps[currentIdx]?.name })
2204
+ const saved = saveWorkflowRun(result, {
2205
+ cwd,
2206
+ source: 'run.js',
2207
+ stage: 'archive',
2208
+ step: steps[currentIdx]?.name,
2209
+ ...(platformOpts.runtimeRoot ? { runtimeRoot: platformOpts.runtimeRoot } : {}),
2210
+ ...(platformOpts.scanRunId ? { scanRunId: platformOpts.scanRunId } : {})
2211
+ })
2169
2212
  if (saved) console.log(`📁 结果已归档:${saved}`)
2170
2213
  }
2171
2214
  } catch (e) {