svharness 0.15.10 → 0.15.13

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 (49) hide show
  1. package/README.md +106 -2
  2. package/auto/auto-harness-detail-design.md +342 -36
  3. package/auto/skills/svharness/SKILL.md +44 -1
  4. package/auto/templates/auto-pack-req.config.example.yaml +29 -0
  5. package/auto/templates/auto.config.example.yaml +2 -2
  6. package/auto/templates/phase-prompts/S40-adversarial-review.md +31 -0
  7. package/auto/templates/phase-prompts/S40-review.md +22 -0
  8. package/auto/templates/phase-prompts/S40-sample-trace.md +26 -0
  9. package/auto/templates/requirements-review-rubric.yaml +79 -0
  10. package/dist/commands/auto-pack-req.js +70 -0
  11. package/dist/commands/auto.js +18 -2
  12. package/dist/commands/convert.js +2 -0
  13. package/dist/commands/shell-integration.js +29 -56
  14. package/dist/config/merge-options.js +2 -0
  15. package/dist/config/normalize.js +7 -0
  16. package/dist/constants/convert.js +5 -0
  17. package/dist/core/markitdown-client.js +7 -2
  18. package/dist/dashboard/event-bus.js +3 -0
  19. package/dist/dashboard/index.js +1 -1
  20. package/dist/dashboard/sse-handler.js +3 -0
  21. package/dist/dashboard/static/js/app.js +5 -0
  22. package/dist/index.js +79 -6
  23. package/dist/lib/acp-agent-cli-resolver.js +193 -0
  24. package/dist/lib/acp-client.js +4 -2
  25. package/dist/lib/agent-launcher.js +9 -4
  26. package/dist/lib/auto-assets.js +1 -0
  27. package/dist/lib/auto-optimize.js +23 -3
  28. package/dist/lib/auto-orchestrator.js +79 -20
  29. package/dist/lib/auto-pack-req-context.js +88 -0
  30. package/dist/lib/auto-pack-req-finalize.js +28 -0
  31. package/dist/lib/auto-req-pack-review.js +161 -0
  32. package/dist/lib/auto-sample-trace.js +144 -0
  33. package/dist/lib/auto-state.js +130 -14
  34. package/dist/lib/cost-tracker.js +30 -2
  35. package/dist/lib/gate-checker.js +20 -10
  36. package/dist/lib/phase-prompt-loader.js +4 -0
  37. package/dist/lib/phase-runner.js +234 -71
  38. package/dist/lib/ps-codechat-alias.js +29 -16
  39. package/dist/lib/review-parser.js +14 -0
  40. package/dist/lib/score-aggregator.js +21 -0
  41. package/dist/lib/win-registry.js +1 -1
  42. package/dist/utils/validate-args.js +2 -1
  43. package/package.json +3 -2
  44. package/templates/codechat/Start-CodeChat.ps1 +176 -135
  45. package/templates/codechat/bash.exe.stackdump +29 -0
  46. package/templates/codechat/docs/project-structure.md +59 -0
  47. package/templates/svharness.config.example.yaml +2 -0
  48. package/dist/lib/launch-script-path.js +0 -24
  49. package/templates/codechat/.claude/.env +0 -59
package/README.md CHANGED
@@ -38,6 +38,10 @@ svharness build --config svharness.config.yaml --yes
38
38
  # 全自动模式 —— 资料整理 → 构建 → 审查 → 优化,支持断点续跑
39
39
  svharness auto --work-dir . --auto-approve -y
40
40
 
41
+ # 需求包专项 —— 在 requirements 资料目录直接运行(推荐)
42
+ cd ./requirements && svharness auto_pack_req --yes
43
+ # 或指定资料包根:svharness auto_pack_req ./req-pack --yes
44
+
41
45
  # 一行式 —— 跳过交互,直接生成
42
46
  svharness build --harness-name my-app --arch android-compose --agent qoder --yes
43
47
 
@@ -65,6 +69,8 @@ svharness start-agent --work-dir .
65
69
 
66
70
  **`svharness auto`**:无人值守构建入口。工作目录可以是已有 `<name>-harness/`(从 `.harness-build-state.yaml` 断点续跑),也可以是资料包根(自动检测 `baseline/`、`requirements/`、`references/`、`extra-skills/` 后先执行 build)。配置示例见 `auto/templates/auto.config.example.yaml`。
67
71
 
72
+ **`svharness auto_pack_req`**:需求包专项入口(独立于 `auto`)。**无参数时默认把当前目录当作工作目录**;若当前目录本身是 requirements 资料夹(含 doc/md/pdf 等,目录名任意),则自动视为输入并在**上级目录**创建 harness。也支持传入资料包根(含 `requirements/` 子目录)或已有 harness 根。仅执行 S30 + S40,三维度质量闭环直到综合分 **严格大于** 9.5。配置示例见 `auto/templates/auto-pack-req.config.example.yaml`(`auto_pack_req:` 节)。详细设计见 [`auto/auto-harness-detail-design.md` §2.2.1](./auto/auto-harness-detail-design.md#221-svharness-auto_pack_req-需求包专项)。
73
+
68
74
  生成完成后,在你的 Agent IDE 中说:
69
75
  - "运行 harness-build-skills-main"或"harness-build-skill-orchestrator"即可开始逐步填充内容;
70
76
  - 构建完成后在目标项目中说"应用 harness-apply-skills-main 完成 xxx 功能开发"即可调用 harness。
@@ -234,6 +240,8 @@ svharness wizard
234
240
  | `convert` | 对应 `svharness convert` |
235
241
  | `doctor` | 对应 `svharness doctor` |
236
242
  | `wiki` | 对应 `svharness wiki` |
243
+ | `auto` | 对应 `svharness auto` |
244
+ | `auto_pack_req` | 对应 `svharness auto_pack_req`(需求包 S40 专项) |
237
245
 
238
246
  `build` 节除路径外,可写人类可读说明(仅写入配置文件,供团队阅读):
239
247
 
@@ -289,7 +297,7 @@ svharness build \
289
297
  | `--convert-endpoint <url>` | 可选 | build 自动 convert 使用的 markitdown 服务基址(支持逗号分隔 failover);省略时读 `SVHARNESS_MARKITDOWN_ENDPOINT` | `http://markitdown.desaysz.site` |
290
298
  | `--convert-concurrency <n>` | 可选 | build 自动 convert 并发上传数 | `3` |
291
299
  | `--convert-max-file-mb <n>` | 可选 | build 自动 convert 单文件大小上限(MB) | `50` |
292
- | `--convert-timeout-sec <n>` | 可选 | build 自动 convert 请求超时秒数 | `120` |
300
+ | `--convert-timeout-sec <n>` | 可选 | build 自动 convert 请求超时秒数 | `900`(15 分钟) |
293
301
  | `--convert-force` | 可选 flag | build 自动 convert 覆盖同名 `.md`(否则按 `-1/-2` 追加) | `false` |
294
302
  | `--force` | 可选 flag | 覆写已存在的 harness 目录;同时允许覆盖已注入的 build skills/rules 与项目根 `AGENTS.md` / `CLAUDE.md`(默认遇到已存在文件会跳过) | `false` |
295
303
  | `-y, --yes` | 可选 flag | 跳过所有提示,采用默认值 | `false` |
@@ -540,7 +548,7 @@ svharness convert --verbose --yes
540
548
  | `--endpoint <url>` | 云端 `markitdown_serve` 基址;**逗号分隔**可配置 failover;省略时读 `SVHARNESS_MARKITDOWN_ENDPOINT` | `http://markitdown.desaysz.site` |
541
549
  | `--concurrency <n>` | 并发上传数 | `3` |
542
550
  | `--max-file-mb <n>` | 单文件大小上限(MB),本地 + 服务端双重限流 | `50` |
543
- | `--timeout-sec <n>` | 单请求超时秒数 | `120`(2 分钟) |
551
+ | `--timeout-sec <n>` | 单请求超时秒数 | `900`(15 分钟) |
544
552
  | `--type <type>` | 目标子目录:`requirements`(正式需求)\| `references`(参考资料) | `requirements` |
545
553
  | `--force` | flag,覆盖已存在同名 `.md`(默认自动追加 `-1`、`-2` 后缀) | `false` |
546
554
  | `--split-sheets-suffix <suffix>` | xlsx/xls 按 sheet 拆分的子目录后缀 | `_split` |
@@ -586,6 +594,102 @@ convert-failures.json
586
594
 
587
595
  完整契约、fallback chain、Docker 部署见 [markitdown_serve/README.md](./markitdown_serve/README.md);模块设计见 [tmp/svharnessbuild-detail-design.md §4.10](../tmp/svharnessbuild-detail-design.md#410-文档转换convert-命令--markitdown_serve-远端服务)。
588
596
 
597
+ ### `auto` —— 全自动构建 harness(无人值守)
598
+
599
+ 一条命令完成资料整理 → S10~S80 构建 → S85 七维评审 → 优化循环,直到综合分 ≥ 目标分后 S90 封存。
600
+
601
+ ```bash
602
+ svharness auto [workdir] [options]
603
+ ```
604
+
605
+ 工作目录可以是已有 `<name>-harness/`(断点续跑),也可以是资料包根(自动检测后先 build)。
606
+
607
+ | 参数 | 说明 | 默认值 |
608
+ |------|------|--------|
609
+ | `--work-dir <path>` | 工作目录 | 当前目录 |
610
+ | `--config <path>` | 配置文件(`auto:` 节) | `svharness.config.yaml` |
611
+ | `--target-score <score>` | 目标综合评分 | `9.5` |
612
+ | `--max-optimize-rounds <n>` | 最大优化轮数 | `10` |
613
+ | `--max-phase <phase>` | 只运行到此阶段即停止(如 `S40` 会跳过评审) | — |
614
+ | `--auto-approve` / `-y, --yes` | 无人值守 | `autoApprove` 默认 `true` |
615
+ | `--dry-run` | 打印 prompt,不调用 Agent | `false` |
616
+
617
+ 配置示例:`auto/templates/auto.config.example.yaml`
618
+
619
+ ### `auto_pack_req` —— 需求包全自动条目化(S40 专项)
620
+
621
+ 独立于 `auto`,专门用于需求文档条目化 + 质量闭环,**不污染**完整 harness 构建流程。
622
+
623
+ ```bash
624
+ # 推荐:在 requirements 资料目录直接运行(目录名任意)
625
+ cd ./requirements
626
+ svharness auto_pack_req --yes
627
+
628
+ # 或指定资料包根 / harness 根
629
+ svharness auto_pack_req [workdir] [options]
630
+ ```
631
+
632
+ **工作目录解析**(`[workdir]` 可省略,默认当前目录):
633
+
634
+ | 当前/指定目录 | 行为 |
635
+ |---------------|------|
636
+ | 本身是 requirements 资料夹(含文档文件,目录名任意) | 视为 `--requirements` 输入,在**上级目录**创建 `<name>-harness/` |
637
+ | 资料包根(含 `requirements/` 等子目录) | 在目录内自动检测并创建 harness |
638
+ | 已有 harness 根(或恰好一个 `*-harness/`) | 重置 S40,重做条目化 |
639
+ | 多个 `*-harness/` | 报错,须显式指定路径 |
640
+
641
+ **内置默认**:`targetScore=9.5`(须**严格大于**)、`maxOptimizeRounds=10`、`sampleCount=7`、`autoApprove=true`、`yes=true`
642
+
643
+ **评分模型**:
644
+
645
+ ```
646
+ 综合分 = 机械门禁 × 0.40 + 对抗验证 × 0.30 + 抽样验证 × 0.30
647
+ ```
648
+
649
+ | 维度 | 权重 | 说明 |
650
+ |------|------|------|
651
+ | 机械门禁 | 40% | `verify --strict` + `doctor --mode requirements --strict` 全通过;任一失败机械分=0 |
652
+ | 对抗验证 | 30% | 独立 Reviewer Agent 逐 block 挑刺(禁止读 Builder 自评) |
653
+ | 抽样验证 | 30% | 从 `requirements/md` 随机抽 N 片段,反向追溯 yaml 产物是否可支撑开发 |
654
+
655
+ **通过条件**(AND):`综合分 > targetScore` + 硬门禁全通过 + `critical_count=0`
656
+
657
+ **两种入口**:
658
+
659
+ | 模式 | 检测条件 | 行为 |
660
+ |------|----------|------|
661
+ | 资料包新建 | 无 `harness.yaml` | `runMaterialsPrepAndBuild` → S30+S40 |
662
+ | 已有 harness 重做 | 存在 `harness.yaml` | 重置 `S40=PENDING` → 只跑 S40 |
663
+
664
+ **主要参数**:
665
+
666
+ | 参数 | 说明 | 默认值 |
667
+ |------|------|--------|
668
+ | `--requirements <path>` | 需求文档路径 | 自动检测 |
669
+ | `--target-score <score>` | 目标综合评分(须严格大于) | `9.5` |
670
+ | `--max-optimize-rounds <n>` | 最大优化轮数 | `10` |
671
+ | `--sample-count <n>` | 抽样验证片段数 | `7` |
672
+ | `--config <path>` | 配置文件(`auto_pack_req:` 节) | — |
673
+ | `--dry-run` | 打印 S40 + 三维度评审 prompt | `false` |
674
+
675
+ **典型用法**:
676
+
677
+ ```bash
678
+ cd ./requirements && svharness auto_pack_req --yes
679
+ svharness auto_pack_req ./req-pack --yes
680
+ svharness auto_pack_req ./my-app-harness --requirements ./docs --yes
681
+ svharness auto_pack_req --config auto-pack-req.config.yaml --dry-run
682
+ ```
683
+
684
+ **产物**(不执行 S90):
685
+
686
+ - `requirements/yaml/*.yaml`、`requirements/coverage-report.yaml`
687
+ - `auto-review/第N轮需求自评报告.md`、`第N轮对抗评审.md`、`第N轮抽样验证报告.md`
688
+ - `auto-review/auto-score.json`、`auto-review/pack-req-complete.json`
689
+
690
+ 配置示例:`auto/templates/auto-pack-req.config.example.yaml`
691
+ 详细设计:[`auto/auto-harness-detail-design.md` §2.2.1](./auto/auto-harness-detail-design.md#221-svharness-auto_pack_req-需求包专项)
692
+
589
693
  ### `wiki` —— 对任意代码工程生成 baseline wiki(v0.15+)
590
694
 
591
695
  独立子命令,**不依赖 harness 生命周期**,直接对任意代码工程目录就地扫描生成 wiki。与 `build` 内置 wiki 共用同一 repowiki 核心,但不拷贝 code、不创建 harness 骨架。
@@ -1,10 +1,10 @@
1
1
  # auto-harness 详细设计方案
2
2
 
3
- > **文档定位**:`svharness auto` 模式的完整设计书,面向本系统的开发者与维护者。
3
+ > **文档定位**:`svharness auto` 与 `svharness auto_pack_req` 模式的完整设计书,面向本系统的开发者与维护者。
4
4
  > **上级文档**:`svharnessbuild/README.md`(CLI 总体规范)、`svharnessbuild/tmp/svharnessbuild-detail-design.md`(SDD)。
5
5
  > **参照工程**:[snarktank/ralph](https://github.com/snarktank/ralph)(外循环思路)。
6
- > **版本**:v2.5(2026-06-16,新增 Web 仪表盘、默认标志优化、优化容错)
7
- > **最后更新**:2026-06-16
6
+ > **版本**:v2.6(2026-06-26,任意错误不中断 + CostTracker 超限兜底优化)
7
+ > **最后更新**:2026-06-26
8
8
 
9
9
  ---
10
10
 
@@ -309,6 +309,105 @@ async function resolveHarnessRoot(workDir: string): Promise<string | null> {
309
309
 
310
310
  > `harness.yaml` 通常位于 `<work-dir>/<name>-harness/harness.yaml`,而非项目根直接子级。`--work-dir` 可指向资料包根或 harness 根;Orchestrator 先执行上述解析再进入主循环。
311
311
 
312
+ ### 2.2.1 `svharness auto_pack_req` — 需求包专项
313
+
314
+ > **定位**:独立于 `svharness auto` 的 preset 命令,内部固定 `scope=requirements`,不暴露 `--scope` CLI 参数。复用 `AutoOrchestrator` + `PhaseRunner`,不新建 orchestrator 类。
315
+
316
+ #### 命令形态
317
+
318
+ ```bash
319
+ # 推荐:在 requirements 资料目录直接运行(目录名任意)
320
+ cd ./requirements && svharness auto_pack_req --yes
321
+
322
+ # 或指定资料包根 / harness 根
323
+ svharness auto_pack_req [workdir] [options]
324
+ ```
325
+
326
+ 实现入口:`src/commands/auto-pack-req.ts` → `resolveAutoPackReqWorkContext()` → 注入 preset opts → 调用 `runAuto()`。
327
+
328
+ #### 工作目录解析(`[workdir]` 可省略)
329
+
330
+ 无参数时默认 `process.cwd()`,按以下优先级识别:
331
+
332
+ | 当前目录 | 行为 |
333
+ |----------|------|
334
+ | harness 根(或恰好一个 `*-harness/`) | 重置 S40,重做条目化 |
335
+ | 资料包根(含 `requirements/`、`baseline/` 等子目录) | 在目录内自动检测并 `build` |
336
+ | **requirements 资料夹**(含 doc/md/pdf 等,**目录名任意**) | 将当前目录固定为 `--requirements` 输入,在**上级目录**创建 `<name>-harness/` |
337
+ | 其它 | 按资料包根处理(与 `auto` 一致) |
338
+
339
+ 示例:在 `req-pack/requirements/` 内执行 `svharness auto_pack_req --yes`,等价于在 `req-pack/` 执行并指定 `--requirements ./requirements`。
340
+
341
+ #### 内置默认(可被 CLI / 配置覆盖)
342
+
343
+ | 参数 | 默认值 |
344
+ |------|--------|
345
+ | `scope` | `requirements`(内部固定) |
346
+ | `targetScore` | `9.5`(通过条件为**严格大于**) |
347
+ | `maxOptimizeRounds` | `10` |
348
+ | `sampleCount` | `7` |
349
+ | `autoApprove` / `yes` | `true` |
350
+
351
+ 配置文件节:`auto_pack_req:`(示例见 `auto/templates/auto-pack-req.config.example.yaml`)。
352
+
353
+ #### 两种入口
354
+
355
+ | 模式 | 检测 | 行为 |
356
+ |------|------|------|
357
+ | 资料包新建 | `resolveHarnessRoot(workDir)` 为 null | `runMaterialsPrepAndBuild` → 仅跑 `S30_convert_docs` + `S40_extract_requirements` |
358
+ | 已有 harness 重做 | 存在 `harness.yaml` | `resetS40ForRerun()` 将 `S40_extract_requirements.status=PENDING`,不动 S50~S90 |
359
+
360
+ 阶段白名单(`SCOPE_PHASES.requirements`):
361
+
362
+ ```typescript
363
+ ['S30_convert_docs', 'S40_extract_requirements']
364
+ ```
365
+
366
+ #### 三维度质量闭环
367
+
368
+ ```
369
+ 综合分 = 机械门禁分 × 0.40 + 对抗验证分 × 0.30 + 抽样验证分 × 0.30
370
+ ```
371
+
372
+ | 维度 | 权重 | 实现 |
373
+ |------|------|------|
374
+ | 机械门禁 | 40% | `runMechanicalGates({ scope: 'requirements' })`:`verify --strict` + `doctor --mode requirements --strict`;任一失败机械分=0 |
375
+ | 对抗验证 | 30% | ACP + `S40-adversarial-review.md`;禁止读 Builder 自评报告 |
376
+ | 抽样验证 | 30% | `auto-sample-trace.ts` 生成 `sample-fragments.json` + ACP + `S40-sample-trace.md` 反向追溯 |
377
+
378
+ 每轮评审流程(`runReqPackReview`):
379
+
380
+ 1. 机械门禁
381
+ 2. `writeSampleFragmentsFile()` — 从 `requirements/md` 随机抽 N 片段
382
+ 3. Builder 自评 — `S40-review.md`(产出 gap 表供优化,**不计入**综合分权重)
383
+ 4. 对抗验证 — `S40-adversarial-review.md`
384
+ 5. 抽样验证 — `S40-sample-trace.md`
385
+ 6. `computeReqPackScore()` 加权 → 写 `auto-review/auto-score.json`
386
+
387
+ **通过条件**(AND):
388
+
389
+ - `composite_score > targetScore`
390
+ - `gate_pass === true`(verify=0、doctor=0、critical=0)
391
+ - 不执行 `finalizeAutoHarness`(S90);改为写 `auto-review/pack-req-complete.json`
392
+
393
+ #### 优化循环 scope 隔离
394
+
395
+ - `SCOPE_ALLOWED_PHASES.requirements` 仅含 `S40_extract_requirements`
396
+ - `invalidateDownstream` 在 requirements scope 下**不调用**(避免 S50+ 标 OUTDATED)
397
+ - 优化 prompt 仍复用 `optimize-plan.md` / `optimize-implement.md`
398
+
399
+ #### 关键文件
400
+
401
+ | 类型 | 路径 |
402
+ |------|------|
403
+ | CLI 入口 | `src/commands/auto-pack-req.ts` |
404
+ | 三维度评审 | `src/lib/auto-req-pack-review.ts` |
405
+ | 抽样生成 | `src/lib/auto-sample-trace.ts` |
406
+ | 评分公式 | `src/lib/score-aggregator.ts` → `computeReqPackScore` |
407
+ | 完成标记 | `src/lib/auto-pack-req-finalize.ts` |
408
+ | Prompt | `auto/templates/phase-prompts/S40-review.md` 等 |
409
+ | Rubric | `auto/templates/requirements-review-rubric.yaml` |
410
+
312
411
  ### 2.3 典型运行输出
313
412
 
314
413
  ```
@@ -391,7 +490,7 @@ program
391
490
  .option('--no-force', '禁用自动 force(目标目录存在时不覆盖)')
392
491
  // === 质量与流程控制 ===
393
492
  .option('--target-score <score>', '目标综合评分(默认 9.5)', '9.5')
394
- .option('--max-optimize-rounds <n>','最大优化轮数(默认 5)', '5')
493
+ .option('--max-optimize-rounds <n>','最大优化轮数(默认 10)', '10')
395
494
  .option('--checkpoint <phase>', '到达此阶段前暂停等待人工确认(如 S85)')
396
495
  .option('--max-phase <phase>', '只运行到此阶段即停止(调试用)')
397
496
  .option('--dry-run', '打印各阶段 prompt 和命令,不实际执行')
@@ -399,9 +498,10 @@ program
399
498
  .option('-y, --yes', '跳过所有交互确认(推荐无人值守)')
400
499
  .option('--verbose', '详细日志')
401
500
  // === 成本控制 ===
402
- .option('--max-total-calls <n>', '总 ACP 调用次数上限(含重试和优化),超出即冻结', '30')
501
+ .option('--max-total-calls <n>', '总 ACP 调用次数上限(含重试和优化),超出即冻结', '100')
403
502
  .option('--budget-usd <amount>', '费用预算上限(USD),超出暂停等待确认')
404
503
  .option('--cost-alert <amount>', '单次调用费用告警阈值(USD)', '5')
504
+ .option('--no-auto-repair', '禁用阶段失败自动修复(默认启用)')
405
505
  .action(runAuto);
406
506
  ```
407
507
 
@@ -440,20 +540,46 @@ class AutoOrchestrator {
440
540
  this.ensurePhaseRunnerReady();
441
541
 
442
542
  // === AGENT-2: 构建 ===
443
- await this.runHarnessBuild(); // 驱动 S10→S80 全流程
543
+ try {
544
+ await this.runHarnessBuild(); // 驱动 S10→S80 全流程
545
+ } catch (err) {
546
+ // v2.6: 构建阶段失败不裸崩溃,先 emit build_failed 事件再终止
547
+ dashboardEventBus.emitBuildFailed(undefined);
548
+ throw new Error(`构建阶段失败:${err.message};auto 构建终止,请在修复后重新执行`);
549
+ }
444
550
 
445
551
  // === AGENT-3 + AGENT-4: 审查 + 优化循环 ===
446
552
  let round = 0;
447
553
  while (round < this.ctx.maxOptimizeRounds) {
448
554
  round++;
449
- const score = await this.runHarnessReview(round); // 产出 第<n>评审报告.md
450
- if (score.composite_score >= this.ctx.targetScore && score.gate_pass) {
555
+ let review;
556
+ try {
557
+ review = await this.runHarnessReview(round); // 产出 第<n>评审报告.md
558
+ } catch (err) {
559
+ // v2.6: 资源限制(预算/调用次数超限)优雅终止,其他异常继续下一轮
560
+ if (err.message.includes('预算') || err.message.includes('调用次数') || err.message.includes('ACP 调用')) {
561
+ logger.error(`评审因资源限制终止:${err.message}`);
562
+ dashboardEventBus.emitBuildFailed(undefined);
563
+ throw err;
564
+ }
565
+ logger.warn(`评审异常:${err.message},继续下一轮`);
566
+ continue;
567
+ }
568
+ if (review.composite_score >= this.ctx.targetScore && review.gate_pass) {
451
569
  await this.runFinalize();
452
570
  break;
453
571
  }
454
572
  // AGENT-4 两步:先写 第<n>轮修改-plan.md,再实施
455
- await this.runOptimizePlan(round, score);
456
- await this.runOptimizeImplement(round, score);
573
+ try {
574
+ await this.runOptimizePlan(round, score);
575
+ await this.runOptimizeImplement(round, score);
576
+ } catch (err) {
577
+ // v2.6: 资源限制 break,其他异常继续
578
+ if (err.message.includes('预算') || err.message.includes('次数')) {
579
+ break;
580
+ }
581
+ logger.warn(`第 ${round} 轮优化失败:${err.message},继续下一轮`);
582
+ }
457
583
  }
458
584
  }
459
585
  }
@@ -620,13 +746,19 @@ class CostTracker {
620
746
  estimatedCostUsd: number;
621
747
  phaseCosts: Map<string, number>;
622
748
 
749
+ // v2.6: 80%/95% 预警告警标志(单次触发,不重复)
750
+ private budget80Warned = false;
751
+ private budget95Warned = false;
752
+ private calls80Warned = false;
753
+ private calls95Warned = false;
754
+
623
755
  constructor(
624
- private maxTotalCalls: number, // 默认 30
756
+ private maxTotalCalls: number, // 默认 100(v2.6 从 30 提升)
625
757
  private budgetUsd: number | null, // null = 不限制
626
758
  private alertThreshold: number, // 默认 5 USD
627
759
  ) {}
628
760
 
629
- async recordCall(phase: string, promptTokens: number, outputTokens: number): Promise<void> {
761
+ recordCall(phase: string, promptTokens = 0, outputTokens = 0): void {
630
762
  this.totalCalls++;
631
763
  const cost = estimateCost(promptTokens, outputTokens);
632
764
  this.estimatedCostUsd += cost;
@@ -635,14 +767,40 @@ class CostTracker {
635
767
  if (cost > this.alertThreshold) {
636
768
  logger.warn(`[成本] ${phase} 本次约 $${cost.toFixed(2)},累计 $${this.estimatedCostUsd.toFixed(2)}`);
637
769
  }
770
+
771
+ // v2.6: 预算 80%/95% 预警告警
772
+ if (this.budgetUsd !== null) {
773
+ const usagePct = (this.estimatedCostUsd / this.budgetUsd) * 100;
774
+ if (usagePct >= 80 && usagePct < 95 && !this.budget80Warned) {
775
+ this.budget80Warned = true;
776
+ logger.warn(`[成本] 预算已使用 ${usagePct.toFixed(0)}%($${this.estimatedCostUsd.toFixed(2)} / $${this.budgetUsd.toFixed(2)}),接近上限`);
777
+ }
778
+ if (usagePct >= 95 && !this.budget95Warned) {
779
+ this.budget95Warned = true;
780
+ logger.warn(`[成本] 预算已使用 ${usagePct.toFixed(0)}%($${this.estimatedCostUsd.toFixed(2)} / $${this.budgetUsd.toFixed(2)}),即将达到上限`);
781
+ }
782
+ }
783
+
784
+ // v2.6: 调用次数 80%/95% 预警告警
785
+ if (this.maxTotalCalls > 0) {
786
+ const callsPct = (this.totalCalls / this.maxTotalCalls) * 100;
787
+ if (callsPct >= 80 && callsPct < 95 && !this.calls80Warned) {
788
+ this.calls80Warned = true;
789
+ logger.warn(`[成本] ACP 调用次数 ${this.totalCalls}/${this.maxTotalCalls}(${callsPct.toFixed(0)}%),接近上限`);
790
+ }
791
+ if (callsPct >= 95 && !this.calls95Warned) {
792
+ this.calls95Warned = true;
793
+ logger.warn(`[成本] ACP 调用次数 ${this.totalCalls}/${this.maxTotalCalls}(${callsPct.toFixed(0)}%),即将达到上限`);
794
+ }
795
+ }
796
+
638
797
  if (this.totalCalls >= this.maxTotalCalls) {
639
- logger.warn(`[成本] 总调用次数 ${this.totalCalls} 已达上限 ${this.maxTotalCalls},暂停等待确认`);
640
- await enterCheckpoint('max-calls-exceeded');
798
+ throw new Error(`ACP 调用次数 ${this.totalCalls} 已超过上限 ${this.maxTotalCalls},累计费用 $${this.estimatedCostUsd.toFixed(2)}`);
641
799
  }
642
800
  if (this.budgetUsd !== null && this.estimatedCostUsd >= this.budgetUsd) {
643
- logger.warn(`[成本] 费用 $${this.estimatedCostUsd.toFixed(2)} 已达预算上限,暂停等待确认`);
644
- await enterCheckpoint('budget-exceeded');
801
+ throw new Error(`ACP 估算费用 $${this.estimatedCostUsd.toFixed(2)} 已超过预算 $${this.budgetUsd.toFixed(2)}(${this.totalCalls} 次调用)`);
645
802
  }
803
+ dashboardEventBus.emitCostUpdated(this.estimatedCostUsd, this.totalCalls);
646
804
  }
647
805
 
648
806
  checkBudget(): 'ok' | 'alert' | 'exceeded';
@@ -650,10 +808,10 @@ class CostTracker {
650
808
  }
651
809
  ```
652
810
 
653
- **默认建议**(首次运行保守):
811
+ **默认建议**(v2.6 提升):
654
812
 
655
- - `--max-total-calls 30`(约 $10–$30 API 成本)
656
- - `--max-optimize-rounds 2`(大多数场景 2 轮内要么通过要么卡住)
813
+ - `--max-total-calls 100`(约 $30–$100 API 成本,覆盖完整构建+多轮优化)
814
+ - `--max-optimize-rounds 10`(默认值)
657
815
 
658
816
  ### 4.5 完成标记协议
659
817
 
@@ -1265,25 +1423,68 @@ async function rerunOutdatedPhases(
1265
1423
  }
1266
1424
  ```
1267
1425
 
1268
- ### 6.4 失败重试策略
1426
+ ### 6.4 失败重试策略(v2.6 重写)
1427
+
1428
+ 整个重试循环 + auto-repair + SKIPPED 跳过链,全部在 `PhaseRunner.runPhase()` 内实现。
1429
+
1430
+ **流程图**:
1431
+
1432
+ ```
1433
+ for attempt = 0..maxRetriesPerPhase:
1434
+ cost.recordCall()
1435
+ ┌─ try-catch 捕获 CostTracker 异常 ──────────────┐
1436
+ │ acpOk=false → tryResolveFromArtifacts(免费) │
1437
+ │ ├─ 命中 → DONE (「资源受限产物兜底」) ✅ │
1438
+ │ └─ 未命中 → break 退出循环 │
1439
+ └─────────────────────────────────────────────────┘
1440
+ ┌─ acpOk=true → ACP 调用 → 状态检查 ─────────────┐
1441
+ │ ├─ 状态 DONE/SKIPPED → return ✅ │
1442
+ │ ├─ 信号兜底 <PHASE_DONE/SKIP> → return ✅ │
1443
+ │ └─ 产物兜底(检查磁盘产物)→ return ✅ │
1444
+ └─────────────────────────────────────────────────┘
1445
+
1446
+ 重试循环结束后(尝试全部耗尽):
1447
+ 1. emitPhaseFailed
1448
+ 2. if (autoRepair !== false):
1449
+ tryAutoRepair() — 再次调 ACP 修复数据
1450
+ ├─ ACP 修复成功 → DONE ✅
1451
+ └─ 修复失败 → 日志警告
1452
+ 3. 标记 SKIPPED + writeBuildState → 继续下一阶段 ▶️
1453
+ (不再 throw Error,构建不会中断)
1454
+ ```
1455
+
1456
+ **关键差异**(对比 v2.5):
1457
+
1458
+ | 方面 | v2.5 | v2.6 |
1459
+ |------|------|------|
1460
+ | retry 耗尽后 | `throw new Error()` | `tryAutoRepair()` + 标记 `SKIPPED` |
1461
+ | CostTracker 超限 | 异常穿透 → 进程终止 | try-catch 捕获 → 免费产物兜底 + `break` |
1462
+ | 仪表盘事件 | 无 `build_failed` | 新增全链路 `build_failed` 事件 |
1463
+ | YAML 写入 | 直接 `writeFile` | `.tmp` + `rename` 原子写入 |
1464
+ | 默认调用次数 | 30 | 100 |
1465
+
1466
+ **`tryAutoRepair()` 实现**:
1269
1467
 
1270
1468
  ```typescript
1271
- async retryPhase(phase: string, lastResult: AcpResult) {
1272
- // 分析失败原因
1273
- const doctorOutput = await runDoctor(this.harnessRoot, phase);
1274
- const fixPrompt = buildFixPrompt(phase, lastResult.output, doctorOutput);
1275
-
1276
- for (let attempt = 1; attempt <= MAX_RETRIES_PER_PHASE; attempt++) {
1277
- logger.warn(`${phase} 门禁未通过,第 ${attempt} 次修复尝试...`);
1278
- const retryResult = await this.acp.call({
1279
- workDir: this.harnessRoot,
1280
- prompt: fixPrompt
1469
+ private async tryAutoRepair(def, harnessRoot, phaseStartTime): Promise<boolean> {
1470
+ try {
1471
+ this.cost.recordCall(def.key + '_repair');
1472
+ const result = await this.acp.call({
1473
+ workDir: harnessRoot,
1474
+ phase: def.key + '_repair',
1475
+ prompt: wrapAcpPrompt(repairPrompt, `<PHASE_DONE>${def.short}</PHASE_DONE>`),
1281
1476
  });
1282
- const state = await readBuildState(this.harnessRoot);
1283
- if (state.phases[phase]?.status === 'DONE') return;
1477
+ // 三级兜底:状态检查 信号兜底 → 产物兜底
1478
+ const state = await readBuildState(harnessRoot);
1479
+ if (phaseStatus(state, def.key) === 'DONE') return true;
1480
+ const resolved = await this.tryResolveFromSignal(def, harnessRoot, result);
1481
+ if (resolved) return true;
1482
+ const artifactResolved = await this.tryResolveFromArtifacts(def, harnessRoot, result, phaseStartTime);
1483
+ if (artifactResolved) return true;
1484
+ return false;
1485
+ } catch (err) {
1486
+ return false; // 任何异常不穿透,调用方标记 SKIPPED
1284
1487
  }
1285
-
1286
- throw new PhaseFailedError(phase, `超过最大重试次数 ${MAX_RETRIES_PER_PHASE}`);
1287
1488
  }
1288
1489
  ```
1289
1490
 
@@ -2157,7 +2358,7 @@ auto:
2157
2358
 
2158
2359
  # 质量目标
2159
2360
  targetScore: 9.5
2160
- maxOptimizeRounds: 5
2361
+ maxOptimizeRounds: 10
2161
2362
  mechanicalWeight: 0.30 # 机械指标占综合分比例(默认 30%)
2162
2363
  reviewWeight: 0.70 # 评审 Agent 占综合分比例(默认 70%)
2163
2364
 
@@ -2171,8 +2372,11 @@ auto:
2171
2372
  dashboard: true # v2.5 新增:启动本地 Web 仪表盘(端口 7879)
2172
2373
  dashboardPort: 7879 # 仪表盘 HTTP 端口
2173
2374
 
2375
+ # v2.6 新增:阶段失败自动修复(默认启用)
2376
+ autoRepair: true # false 则禁用 ACP 自动修复,阶段失败后直接 SKIPPED
2377
+
2174
2378
  # 成本控制
2175
- maxTotalCalls: 30
2379
+ maxTotalCalls: 100 # v2.6 从 30 提升(对应约 $30-$100 API 成本)
2176
2380
  budgetUsd: null # null = 不限制
2177
2381
  costAlertThreshold: 5 # USD,单次调用告警阈值
2178
2382
 
@@ -2180,6 +2384,7 @@ auto:
2180
2384
  acpMode: auto # auto | sdk | cli-print
2181
2385
  acpTimeoutMs: 1800000 # 30 分钟(v2.4 从 10 分钟提升)
2182
2386
  maxRetriesPerPhase: 2 # 每阶段最大重试次数
2387
+ autoRepair: true # v2.6
2183
2388
 
2184
2389
  # 构建参数(同 svharness.config.yaml build 节)
2185
2390
  generateWiki: false # build 时是否直接生成完整 wiki(false = 由 auto 驱动 ACP 生成)
@@ -2627,3 +2832,104 @@ PhaseRunner/AutoOrchestrator/CostTracker
2627
2832
  → broadcast SSE event → 浏览器三面板自动更新
2628
2833
  ```
2629
2834
 
2835
+ ---
2836
+
2837
+ ## 附录:v2.6 实现修订记录(2026-06-26)
2838
+
2839
+ 根据审查与验证反馈,新增任意错误不中断机制与 CostTracker 超限兜底优化:
2840
+
2841
+ | # | 文件 | 修订内容 | 类型 | 对应 § |
2842
+ |---|------|---------|------|--------|
2843
+ | 1 | `src/lib/auto-orchestrator.ts` | **runBuildPhases() try-catch**:初始构建失败时 `logger.error` + `emitBuildFailed` + 带上下文重新抛出 | Bug 修复 | §3.3 |
2844
+ | 2 | `src/lib/auto-orchestrator.ts` | **评审阶段 try-catch**:`runHarnessReview()` 单独 try-catch,资源限制优雅终止,其他异常继续下一轮 | 增强 | §3.3 |
2845
+ | 3 | `src/lib/auto-orchestrator.ts` | **优化循环 catch break**:CostTracker 超限时 `break` 而非 `continue`,避免下一轮再次触发崩溃 | Bug 修复 | §3.3 |
2846
+ | 4 | `src/lib/phase-runner.ts` | **重试耗尽 + auto-repair + SKIPPED**:ACP 重试(默认 3 次)全部耗尽后,先调 `tryAutoRepair()` 尝试 ACP 自动修复数据,修复失败则标记 SKIPPED 并继续下一阶段(不再 `throw Error`) | 新增 | §6.4 |
2847
+ | 5 | `src/lib/phase-runner.ts` | **CostTracker 超限兜底**:`cost.recordCall()` 包 try-catch,捕获后走 `else` 分支免费产物兜底 + `break` 退出重试循环;同时修复了 `if(acpOk)` 缺少闭合 brace 导致的代码结构 Bug | Bug 修复 | §6.4 |
2848
+ | 6 | `src/lib/phase-runner.ts` | **tryAutoRepair() 方法**:新增 `tryAutoRepair()` 私有方法,调用 ACP agent 诊断修复数据问题,叠加三级 fallback(状态/信号/产物) | 新增 | §6.4 |
2849
+ | 7 | `src/lib/auto-state.ts` | **YAML 原子写入**:`writeBuildState()` 改为先写 `.tmp` 再 `fs.rename` 原子替换;`readBuildState()` dedupe 修复路径也统一使用 `.tmp` + `rename` | 增强 | §6.5 |
2850
+ | 8 | `src/lib/cost-tracker.ts` | **80%/95% 预警告警**:新增 4 个一次性预警标志,预算/调用次数达到 80% 和 95% 时各触发一次 `logger.warn`;超限 `throw` 附带完整累计费用和调用次数信息 | 增强 | §4.4 |
2851
+ | 9 | `src/commands/auto.ts` | **默认值提升**:`maxTotalCalls` 默认 30→100;`autoRepair` 默认 `true` 传入 `AutoContext` | 配置 | §12.1 |
2852
+ | 10 | `src/index.ts` | **CLI 选项更新**:`--max-total-calls` 帮助文本更新为默认 100;新增 `--no-auto-repair` 选项 | 配置 | §3.1 |
2853
+ | 11 | `src/dashboard/event-bus.ts` | **build_failed 事件**:新增 `'build_failed'` 事件类型和 `emitBuildFailed()` 方法 | 新增 | §15.1 |
2854
+ | 12 | `src/dashboard/sse-handler.ts` | **build_failed 状态**:`build_failed` 事件将 `BuildSnapshot.status` 置为 `'failed'` | 增强 | §15.2 |
2855
+ | 13 | `src/dashboard/index.ts` | **事件路由**:`build_failed` 注册到事件订阅数组 | 增强 | §15.3 |
2856
+ | 14 | `src/dashboard/static/js/app.js` | **前端失败提示**:`build_failed` 处理器在日志面板显示 `❌ 构建失败` | 增强 | §15.5 |
2857
+ | 15 | `auto/templates/auto.config.example.yaml` | **示例值更新**:`maxTotalCalls: 100` | 配置 | §12.1 |
2858
+
2859
+ ### 架构变更
2860
+
2861
+ ```
2862
+ v2.5 → v2.6 关键变更:
2863
+
2864
+ PhaseRunner.runPhase() 重试循环
2865
+ ├── 原:cost.recordCall() 裸调用 → 异常穿透 → 进程终止
2866
+ ├── 新:try-catch 捕获 → acpOk=false → else 分支免费产物兜底 → break
2867
+
2868
+ ├── 原:重试耗尽 → throw Error → 构建崩溃
2869
+ └── 新:重试耗尽 → tryAutoRepair() → 修复成功 DONE / 修复失败 SKIPPED ▶️
2870
+
2871
+ CostTracker.recordCall()
2872
+ ├── 原:超限直接 throw
2873
+ └── 新:80% → warn | 95% → warn | 100% → throw(含完整累计信息)
2874
+
2875
+ auto-state.ts writeBuildState()
2876
+ ├── 原:直接 writeFile
2877
+ └── 新:writeFile(.tmp) → rename(.yaml) 原子写入
2878
+
2879
+ 仪表盘事件
2880
+ └── 新增 build_failed(event-bus → sse-handler → app.js 全链路)
2881
+ ```
2882
+
2883
+ ### 容器对比示意
2884
+
2885
+ ```
2886
+ v2.6 PhaseRunner 重试循环容器边界:
2887
+
2888
+ for (attempt = 0; attempt <= maxRetries; attempt++) { ← 容器:重试循环
2889
+ try { cost.recordCall(); } catch { acpOk=false; }
2890
+ if (acpOk) {
2891
+ // ACP 调用 + 状态检查 + 信号兜底 + 产物兜底
2892
+ } else {
2893
+ // 资源耗尽:免费产物兜底 + break
2894
+ }
2895
+ if (attempt < maxRetries) { ... retry warning ... }
2896
+ } ← 容器闭合
2897
+
2898
+ emitPhaseFailed ← 在循环外
2899
+ if (autoRepair) { tryAutoRepair(); } ← 在循环外
2900
+ mark SKIPPED + writeBuildState + continue ← 在循环外
2901
+ ```
2902
+
2903
+ ---
2904
+
2905
+ ## 附录:v2.7 实现修订记录(auto_pack_req)
2906
+
2907
+ 新增 `svharness auto_pack_req` 命令,作为 `auto` 的 preset 包装,专门服务 S40 需求条目化三维度质量闭环。
2908
+
2909
+ | # | 文件 | 修订内容 | 类型 |
2910
+ |---|------|---------|------|
2911
+ | 1 | `src/commands/auto-pack-req.ts` | 新增 preset 入口;已有 harness 时 `resetS40ForRerun` | 新增 |
2912
+ | 2 | `src/index.ts` | 注册 `auto_pack_req [workdir]` 子命令 | 新增 |
2913
+ | 3 | `src/types/auto-types.ts` | `AutoScope`、`SampleFragment`、`ReqPackScoreSnapshot` 等 | 新增 |
2914
+ | 4 | `src/lib/auto-orchestrator.ts` | `scope=requirements` 走 `runReqPackReview` + `finalizeReqPack` | 增强 |
2915
+ | 5 | `src/lib/phase-runner.ts` | `SCOPE_PHASES` 白名单 | 增强 |
2916
+ | 6 | `src/lib/gate-checker.ts` | requirements scope 使用 `doctor --mode requirements` | 增强 |
2917
+ | 7 | `src/lib/score-aggregator.ts` | `computeReqPackScore` 三维度公式 | 新增 |
2918
+ | 8 | `src/lib/auto-req-pack-review.ts` | 机械 + 自评 + 对抗 + 抽样评审编排 | 新增 |
2919
+ | 9 | `src/lib/auto-sample-trace.ts` | 从 md 随机抽片段写 `sample-fragments.json` | 新增 |
2920
+ | 10 | `src/lib/auto-optimize.ts` | requirements scope 仅允许重跑 S40 | 增强 |
2921
+ | 11 | `auto/templates/phase-prompts/S40-*.md` | 三维度评审 prompt | 新增 |
2922
+ | 12 | `auto/templates/requirements-review-rubric.yaml` | 评审契约 SSOT | 新增 |
2923
+ | 13 | `auto/templates/auto-pack-req.config.example.yaml` | 配置示例 | 新增 |
2924
+
2925
+ ```
2926
+ auto_pack_req 与 auto 关系:
2927
+
2928
+ svharness auto_pack_req
2929
+ └── runAutoPackReq() 注入 scope=requirements preset
2930
+ └── runAuto() → AutoOrchestrator
2931
+ ├── PhaseRunner(S30+S40 only)
2932
+ ├── runReqPackReview()(三维度)
2933
+ └── finalizeReqPack()(非 S90)
2934
+ ```
2935
+