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
@@ -169,7 +169,7 @@ svharness auto [--work-dir <path>] [options]
169
169
  | `--arch <arch>` | 架构模板 | `android-compose` |
170
170
  | `--harness-name <name>` | harness 名称(资料包模式必填) | — |
171
171
  | `--target-score <score>` | 目标综合评分 | `9.5` |
172
- | `--max-optimize-rounds <n>` | 最大优化轮数 | `5` |
172
+ | `--max-optimize-rounds <n>` | 最大优化轮数 | `10` |
173
173
  | `--auto-approve` | 自动通过表单门禁 | `false` |
174
174
  | `-y, --yes` | 跳过所有交互确认 | `false` |
175
175
  | `--max-total-calls <n>` | ACP 调用次数上限 | `30` |
@@ -193,6 +193,49 @@ svharness auto --config auto.config.yaml --auto-approve
193
193
 
194
194
  **首次运行自动注入**:首次执行 `svharness auto` 时,CLI 会自动将 `svharness` skill 注入到 `~/.claude/skills/svharness/`,使 Claude Code 能识别 `/svharness` 命令。之后可在任意项目中对 Agent 说"自动构建 harness"触发。
195
195
 
196
+ ### `svharness auto_pack_req` — 需求包全自动条目化(S40 专项)
197
+
198
+ 独立于 `auto` 命令,专门用于需求文档条目化 + 三维度质量闭环,不污染完整 harness 构建流程。
199
+
200
+ ```bash
201
+ # 推荐:在 requirements 资料目录直接运行(目录名任意)
202
+ cd ./requirements && svharness auto_pack_req --yes
203
+
204
+ svharness auto_pack_req [workdir] [options]
205
+ ```
206
+
207
+ **工作目录**:无参数时默认当前目录。若当前目录本身是 requirements 资料夹(含 doc/md/pdf 等),自动视为输入并在上级目录创建 harness。
208
+
209
+ **内置默认**:`targetScore=9.5`(须**严格大于**)、`maxOptimizeRounds=10`、`sampleCount=7`、`autoApprove=true`、`yes=true`
210
+
211
+ **评分模型**:
212
+ ```
213
+ 综合分 = 机械门禁 × 0.40 + 对抗验证 × 0.30 + 抽样验证 × 0.30
214
+ ```
215
+
216
+ | 维度 | 说明 |
217
+ |------|------|
218
+ | 机械门禁 | `verify --strict` + `doctor --mode requirements --strict` 全通过 |
219
+ | 对抗验证 | 独立 Reviewer Agent 逐 block 挑刺(不读 Builder 自评) |
220
+ | 抽样验证 | 从 `requirements/md` 随机抽 N 片段反向追溯 yaml 产物 |
221
+
222
+ **两种入口**:
223
+ - **资料包新建**:目录下无 `harness.yaml` → 创建 harness → S30+S40
224
+ - **已有 harness 重做**:检测到 `harness.yaml` → 重置 S40=PENDING → 只跑 S40
225
+
226
+ **典型用法**:
227
+
228
+ ```bash
229
+ cd ./requirements && svharness auto_pack_req --yes
230
+ svharness auto_pack_req ./req-pack --yes
231
+ svharness auto_pack_req ./existing-harness --requirements ./docs --yes
232
+ svharness auto_pack_req --dry-run
233
+ ```
234
+
235
+ 配置模板:`auto/templates/auto-pack-req.config.example.yaml`(`auto_pack_req:` 节)
236
+
237
+ **产物**:`requirements/yaml/`、`auto-review/pack-req-complete.json`(不执行 S90 封存)
238
+
196
239
  ### `svharness shell` — Windows 右键菜单和别名管理
197
240
 
198
241
  ```bash
@@ -0,0 +1,29 @@
1
+ version: 1
2
+
3
+ defaults:
4
+ yes: true
5
+ verbose: false
6
+
7
+ auto_pack_req:
8
+ workDir: .
9
+ harnessName: my-req-pack
10
+ goal: "描述此需求包的条目化目的"
11
+ arch: documents
12
+ agent: claude-code
13
+
14
+ requirements: ./requirements
15
+
16
+ targetScore: 9.5
17
+ maxOptimizeRounds: 10
18
+ sampleCount: 7
19
+ autoApprove: true
20
+
21
+ acpMode: auto
22
+ acpTimeoutMs: 600000
23
+ maxRetriesPerPhase: 2
24
+ maxTotalCalls: 100
25
+ budgetUsd: null
26
+ costAlertThreshold: 5
27
+
28
+ convertEndpoint: "http://markitdown.desaysz.site"
29
+ dryRun: false
@@ -23,7 +23,7 @@ auto:
23
23
 
24
24
  # 质量目标与流程控制。
25
25
  targetScore: 9.5
26
- maxOptimizeRounds: 5
26
+ maxOptimizeRounds: 10
27
27
  maxPhase: null
28
28
  checkpoint: null
29
29
  autoApprove: true
@@ -33,7 +33,7 @@ auto:
33
33
  acpMode: auto
34
34
  acpTimeoutMs: 600000
35
35
  maxRetriesPerPhase: 2
36
- maxTotalCalls: 30
36
+ maxTotalCalls: 100
37
37
  budgetUsd: null
38
38
  costAlertThreshold: 5
39
39
 
@@ -0,0 +1,31 @@
1
+ 你是**独立**的需求审计 Agent(未参与 S40 条目化构建)。当前任务:对 {{HARNESS_ROOT}} 执行第 {{ROUND}} 轮对抗性验证。
2
+
3
+ ## 硬约束(违反则任务失败)
4
+ - **禁止读取** `auto-review/第{{ROUND}}轮需求自评报告.md` 及任何 Builder 自评内容。
5
+ - 仅基于源文档与产物独立审查。
6
+
7
+ ## 项目上下文
8
+ - 构建目的:{{AUTO_GOAL}}
9
+ - 机械门禁:doctor exit={{DOCTOR_EXIT}},verify exit={{VERIFY_EXIT}}
10
+
11
+ ## 审查方法
12
+ 1. 读取 `requirements/_work/structure-index.json`,逐 block 对照 `requirements/yaml/*.yaml`。
13
+ 2. 挑刺重点:
14
+ - 遗漏条目(block 无对应 requirement 且无合理 waived)
15
+ - `source_excerpt` 缩略或占位语(「见上文」「同前」)
16
+ - `description` 信息缩减(条件/数值/枚举/时序丢失)
17
+ - waived 条目缺少 `aggregation_reason`
18
+ 3. 读取 `{{HARNESS_ROOT}}/auto/requirements-review-rubric.yaml` 中 adversarial 维度。
19
+
20
+ ## 产出要求
21
+ 1. 写入 `{{HARNESS_ROOT}}/auto-review/第{{ROUND}}轮对抗评审.md`。
22
+ 2. frontmatter 必须包含:
23
+ - `round: {{ROUND}}`
24
+ - `score.adversarial.coverage`(0-10)
25
+ - `score.adversarial.fidelity`(0-10)
26
+ - `score.adversarial.completeness`(0-10)
27
+ - `score.adversarial.average`(三项平均,0-10)
28
+ - `issues`(数组,每项含 severity/category/description/unit_id 或 block_id)
29
+ - `critical_count`(issues 中 severity=critical 的数量)
30
+ 3. 正文列出发现的问题清单。
31
+ 4. 输出末尾:`<AUTO_SIGNAL>ADV_REVIEW_DONE</AUTO_SIGNAL>`。
@@ -0,0 +1,22 @@
1
+ 你是 harness 构建 Agent。当前任务:对 {{HARNESS_ROOT}} 执行第 {{ROUND}} 轮 S40 需求自评。
2
+
3
+ ## 项目上下文
4
+ - 构建目的:{{AUTO_GOAL}}
5
+ - 机械门禁:doctor exit={{DOCTOR_EXIT}},verify exit={{VERIFY_EXIT}},文件齐全度={{FILE_COMPLETENESS_PCT}}%
6
+
7
+ ## 评审契约
8
+ 1. 读取 `{{HARNESS_ROOT}}/auto/requirements-review-rubric.yaml`。
9
+ 2. 仅评审 **requirements** 维度(不评 wiki/specs/references 等)。
10
+ 3. 检查 `requirements/yaml/*.yaml`、`requirements/coverage-report.yaml`、`requirements/_work/verify-gaps.json`。
11
+
12
+ ## 产出要求
13
+ 1. 写入 `{{HARNESS_ROOT}}/auto-review/第{{ROUND}}轮需求自评报告.md`。
14
+ 2. frontmatter 必须包含:
15
+ - `round: {{ROUND}}`
16
+ - `score.dimensions.requirements`(0-10)
17
+ - `gates.doctor_exit` / `gates.verify_exit` / `gates.file_completeness_pct`
18
+ - `gate_pass`(布尔)
19
+ - `critical_count`
20
+ - `modifications_required`
21
+ 3. 正文必须包含「修改建议汇总」表(优先级 | 阶段 | 问题 | 建议动作),供优化 Agent 读取。
22
+ 4. 输出末尾:`<AUTO_SIGNAL>REQ_REVIEW_DONE</AUTO_SIGNAL>`。
@@ -0,0 +1,26 @@
1
+ 你是需求抽样验证 Agent。当前任务:对 {{HARNESS_ROOT}} 执行第 {{ROUND}} 轮反向追溯抽样验证。
2
+
3
+ ## 项目上下文
4
+ - 构建目的:{{AUTO_GOAL}}
5
+ - 抽样片段文件:{{SAMPLE_FRAGMENTS_PATH}}
6
+
7
+ ## 验证方法(Sample-based Reverse Trace)
8
+ 1. 读取 `{{SAMPLE_FRAGMENTS_PATH}}` 中的 `fragments[]`。
9
+ 2. 对每个片段,在 `requirements/yaml/*.yaml` 中**反向查找**对应条目。
10
+ 3. 对每个片段评三个子维度(0-10):
11
+ - **traceability**:能否找到对应 `unit_id`
12
+ - **fidelity**:`source_excerpt` 是否覆盖片段关键信息(条件/数值/枚举/时序)
13
+ - **usability**:`description` + `acceptance` 是否足以支撑开发者直接实现功能
14
+ 4. 读取 `{{HARNESS_ROOT}}/auto/requirements-review-rubric.yaml` 中 sample_trace 维度。
15
+
16
+ ## 产出要求
17
+ 1. 写入 `{{HARNESS_ROOT}}/auto-review/第{{ROUND}}轮抽样验证报告.md`。
18
+ 2. frontmatter 必须包含:
19
+ - `round: {{ROUND}}`
20
+ - `score.sample_trace.traceability`(所有片段平均,0-10)
21
+ - `score.sample_trace.fidelity`(0-10)
22
+ - `score.sample_trace.usability`(0-10)
23
+ - `score.sample_trace.average`(三项平均,0-10)
24
+ - `score.sample_trace.items`(数组,每项含 fragment_id、found_unit_id、traceability、fidelity、usability、note)
25
+ 3. 正文含逐片段验证明细表。
26
+ 4. 输出末尾:`<AUTO_SIGNAL>SAMPLE_TRACE_DONE</AUTO_SIGNAL>`。
@@ -0,0 +1,79 @@
1
+ # requirements-review-rubric.yaml — auto_pack_req 三维度评审契约
2
+ version: "1.0"
3
+
4
+ scope: requirements
5
+
6
+ dimensions:
7
+ - id: requirements
8
+ weight_composite: 0.0
9
+ title: Builder 自评(requirements 维度)
10
+ role: gap_generation
11
+ questions:
12
+ - 需求文档是否充分解析?覆盖率是否 100%?
13
+ - source_excerpt 是否保真?有无压缩导致细节丢失?
14
+ - unit_id / source_anchor 是否可索引?
15
+ evidence_hints:
16
+ - requirements/coverage-report.yaml
17
+ - requirements/yaml/*.yaml
18
+ - requirements/_work/verify-gaps.json
19
+
20
+ - id: adversarial
21
+ weight_composite: 0.30
22
+ title: 对抗性验证
23
+ role: scoring
24
+ questions:
25
+ - 逐 block 对照 structure-index,是否有遗漏条目?
26
+ - source_excerpt 是否缩略或使用占位语?
27
+ - description 是否缩减了条件/数值/枚举/时序?
28
+ - waived 条目是否有 aggregation_reason?
29
+ sub_scores:
30
+ - coverage
31
+ - fidelity
32
+ - completeness
33
+ evidence_hints:
34
+ - requirements/_work/structure-index.json
35
+ - requirements/yaml/*.yaml
36
+
37
+ - id: sample_trace
38
+ weight_composite: 0.30
39
+ title: 抽样反向追溯验证
40
+ role: scoring
41
+ questions:
42
+ - 随机源文档片段能否在 yaml 中找到 unit_id?
43
+ - source_excerpt 是否覆盖片段关键信息?
44
+ - description + acceptance 是否足以支撑功能开发?
45
+ sub_scores:
46
+ - traceability
47
+ - fidelity
48
+ - usability
49
+ evidence_hints:
50
+ - auto-review/sample-fragments.json
51
+ - requirements/md/*.md
52
+ - requirements/yaml/*.yaml
53
+
54
+ mechanical_gate:
55
+ weight_composite: 0.40
56
+ checks:
57
+ - svharness requirements verify --strict exit 0
58
+ - svharness doctor --mode requirements --strict exit 0
59
+ - coverage_ratio == 1
60
+ - critical_count == 0
61
+
62
+ pass_criteria:
63
+ composite_score_gt: 9.5
64
+ gate_pass: true
65
+ critical_count: 0
66
+
67
+ modification_summary_columns:
68
+ - priority
69
+ - phase
70
+ - problem
71
+ - action
72
+ - status
73
+
74
+ outputs:
75
+ self_review: "auto-review/第{n}轮需求自评报告.md"
76
+ adversarial: "auto-review/第{n}轮对抗评审.md"
77
+ sample_trace: "auto-review/第{n}轮抽样验证报告.md"
78
+ score_snapshot: auto-review/auto-score.json
79
+ complete_marker: auto-review/pack-req-complete.json
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runAutoPackReq = runAutoPackReq;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const config_1 = require("../config");
10
+ const logger_1 = require("../utils/logger");
11
+ const auto_state_1 = require("../lib/auto-state");
12
+ const auto_pack_req_context_1 = require("../lib/auto-pack-req-context");
13
+ const auto_1 = require("./auto");
14
+ const PACK_REQ_DEFAULTS = {
15
+ scope: 'requirements',
16
+ targetScore: 9.5,
17
+ maxOptimizeRounds: 10,
18
+ autoApprove: true,
19
+ yes: true,
20
+ sampleCount: 7,
21
+ };
22
+ async function runAutoPackReq(positionalWorkdir, opts, cmd) {
23
+ const loaded = (0, config_1.loadConfig)({ configPath: opts.config });
24
+ const autoSection = loaded?.config.auto_pack_req ?? loaded?.config.auto;
25
+ const merged = {
26
+ ...(0, config_1.mergeAutoOptions)(opts, autoSection, loaded?.config.defaults, cmd),
27
+ ...PACK_REQ_DEFAULTS,
28
+ ...opts,
29
+ scope: 'requirements',
30
+ targetScore: opts.targetScore ?? autoSection?.targetScore ?? PACK_REQ_DEFAULTS.targetScore,
31
+ maxOptimizeRounds: opts.maxOptimizeRounds ?? autoSection?.maxOptimizeRounds ?? PACK_REQ_DEFAULTS.maxOptimizeRounds,
32
+ sampleCount: opts.sampleCount ?? autoSection?.sampleCount ?? 7,
33
+ autoApprove: opts.autoApprove ?? true,
34
+ yes: opts.yes ?? true,
35
+ };
36
+ const workCtx = await (0, auto_pack_req_context_1.resolveAutoPackReqWorkContext)(positionalWorkdir, merged.workDir, merged.requirements);
37
+ const runOpts = {
38
+ ...merged,
39
+ ...(workCtx.requirementsPath ? { requirements: workCtx.requirementsPath } : {}),
40
+ };
41
+ if (workCtx.harnessRoot) {
42
+ logger_1.logger.info(`[auto_pack_req] 检测到已有 harness:${workCtx.harnessRoot},将重做 S40`);
43
+ await resetS40ForRerun(workCtx.harnessRoot);
44
+ }
45
+ else if (workCtx.mode === 'requirements-cwd') {
46
+ logger_1.logger.info(`[auto_pack_req] 当前目录识别为 requirements 输入:${workCtx.requirementsPath}`);
47
+ logger_1.logger.info(`[auto_pack_req] 将在上级目录创建 harness:${workCtx.workDir}`);
48
+ }
49
+ else {
50
+ logger_1.logger.info('[auto_pack_req] 资料包模式:将创建 harness 并执行 S30+S40');
51
+ }
52
+ await (0, auto_1.runAuto)(workCtx.workDir, runOpts, cmd);
53
+ }
54
+ async function resetS40ForRerun(harnessRoot) {
55
+ const statePath = node_path_1.default.join(harnessRoot, '.harness-build-state.yaml');
56
+ if (!(await fs_extra_1.default.pathExists(statePath)))
57
+ return;
58
+ const state = await (0, auto_state_1.readBuildState)(harnessRoot);
59
+ if (!state.phases.S40_extract_requirements)
60
+ return;
61
+ state.phases.S40_extract_requirements = {
62
+ ...state.phases.S40_extract_requirements,
63
+ status: 'PENDING',
64
+ };
65
+ delete state.phases.S40_extract_requirements.outdated_reason;
66
+ state.current_phase = 'S40_extract_requirements';
67
+ state.next_action = 'auto_pack_req 已重置 S40,等待重新条目化';
68
+ await (0, auto_state_1.writeBuildState)(harnessRoot, state);
69
+ logger_1.logger.success('已重置 S40_extract_requirements 为 PENDING');
70
+ }
@@ -12,6 +12,7 @@ const logger_1 = require("../utils/logger");
12
12
  const harness_resolver_1 = require("../lib/harness-resolver");
13
13
  const auto_orchestrator_1 = require("../lib/auto-orchestrator");
14
14
  const dashboard_1 = require("../dashboard");
15
+ const acp_agent_cli_resolver_1 = require("../lib/acp-agent-cli-resolver");
15
16
  /**
16
17
  * Prevent system sleep on Windows during long-running auto execution.
17
18
  * Registers process-level handlers so sleep is restored on any exit path.
@@ -46,6 +47,13 @@ async function runAuto(positionalWorkdir, opts, cmd) {
46
47
  const merged = (0, config_1.mergeAutoOptions)(opts, loaded?.config.auto, loaded?.config.defaults, cmd);
47
48
  const ctx = await resolveAutoContext(positionalWorkdir, merged);
48
49
  (0, logger_1.setVerbose)(ctx.verbose);
50
+ if (!ctx.dryRun) {
51
+ logger_1.logger.section('[auto] 启动前检查:Agent CLI');
52
+ if (!ctx.acpAgentCli) {
53
+ throw new Error('内部错误:ACP Agent CLI 未解析');
54
+ }
55
+ logger_1.logger.success(`已找到 Agent CLI → ${(0, acp_agent_cli_resolver_1.formatAcpAgentCliLog)(ctx.acpAgentCli)}`);
56
+ }
49
57
  // Start dashboard server (non-blocking, best-effort)
50
58
  (0, dashboard_1.startDashboard)(7879).catch(() => { });
51
59
  // Prevent system sleep during execution; restore on any exit path
@@ -63,28 +71,33 @@ async function runAuto(positionalWorkdir, opts, cmd) {
63
71
  async function resolveAutoContext(positionalWorkdir, opts) {
64
72
  const workDir = node_path_1.default.resolve(positionalWorkdir ?? opts.workDir ?? process.cwd());
65
73
  const harnessRoot = await (0, harness_resolver_1.resolveHarnessRoot)(workDir);
74
+ const acpAgentCli = opts.dryRun ? null : (0, acp_agent_cli_resolver_1.ensureAcpAgentCliAvailable)();
66
75
  return {
67
76
  workDir,
68
77
  harnessRoot,
69
78
  harnessExists: !!harnessRoot,
79
+ acpAgentCli,
70
80
  targetScore: normalizeNumber(opts.targetScore, 9.5, '--target-score'),
71
- maxOptimizeRounds: normalizeInteger(opts.maxOptimizeRounds, 5, '--max-optimize-rounds'),
81
+ maxOptimizeRounds: normalizeInteger(opts.maxOptimizeRounds, 10, '--max-optimize-rounds'),
72
82
  dryRun: !!opts.dryRun,
73
83
  autoApprove: !!opts.autoApprove,
74
84
  yes: !!opts.yes,
75
85
  verbose: !!opts.verbose,
76
- maxTotalCalls: normalizeInteger(opts.maxTotalCalls, 30, '--max-total-calls'),
86
+ maxTotalCalls: normalizeInteger(opts.maxTotalCalls, 100, '--max-total-calls'),
77
87
  budgetUsd: opts.budgetUsd ?? null,
78
88
  costAlertThreshold: normalizeNumber(opts.costAlertThreshold, 5, '--cost-alert'),
79
89
  acpMode: opts.acpMode === 'sdk' || opts.acpMode === 'cli-print' ? opts.acpMode : 'auto',
80
90
  acpTimeoutMs: normalizeInteger(opts.acpTimeoutMs, 1_800_000, '--acp-timeout-ms'),
81
91
  maxRetriesPerPhase: normalizeInteger(opts.maxRetriesPerPhase, 2, '--max-retries-per-phase'),
82
92
  baselineAutoExtract: normalizeBaselineExtract(opts.baselineAutoExtract),
93
+ scope: normalizeScope(opts.scope),
94
+ sampleCount: normalizeInteger(opts.sampleCount, 7, '--sample-count'),
83
95
  promptDir: opts.promptDir,
84
96
  checkpoint: opts.checkpoint,
85
97
  maxPhase: opts.maxPhase,
86
98
  goal: opts.goal,
87
99
  buildOverrides: opts,
100
+ autoRepair: opts.autoRepair ?? true,
88
101
  };
89
102
  }
90
103
  function normalizeNumber(value, fallback, name) {
@@ -106,3 +119,6 @@ function normalizeBaselineExtract(value) {
106
119
  }
107
120
  return 'conservative';
108
121
  }
122
+ function normalizeScope(value) {
123
+ return value === 'requirements' ? 'requirements' : 'full';
124
+ }
@@ -182,6 +182,8 @@ async function runConvert(opts) {
182
182
  catch (err) {
183
183
  const e = err;
184
184
  logger_1.logger.error(`markitdown_serve unreachable at ${v.endpoint}: ${e.message}`);
185
+ logger_1.logger.plain(' - if another user is converting a large PDF, the service may still be busy; retry in a minute');
186
+ logger_1.logger.plain(' - production should run uvicorn with --workers 4+ and nginx proxy_read_timeout ≥ 900s');
185
187
  logger_1.logger.plain('');
186
188
  logger_1.logger.plain(' - confirm the service is deployed and reachable from this machine');
187
189
  logger_1.logger.plain(' - pass --endpoint <url> or comma-separated URLs for failover');
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.resolveLaunchScriptPath = void 0;
7
6
  exports.resolveShellMenuIconSourcePath = resolveShellMenuIconSourcePath;
8
7
  exports.installShellMenuIcon = installShellMenuIcon;
9
8
  exports.resolveWindowsTerminalPath = resolveWindowsTerminalPath;
@@ -19,11 +18,8 @@ const child_process_1 = require("child_process");
19
18
  const logger_1 = require("../utils/logger");
20
19
  const win_registry_1 = require("../lib/win-registry");
21
20
  const ps_codechat_alias_1 = require("../lib/ps-codechat-alias");
22
- const launch_script_path_1 = require("../lib/launch-script-path");
23
- Object.defineProperty(exports, "resolveLaunchScriptPath", { enumerable: true, get: function () { return launch_script_path_1.resolveLaunchScriptPath; } });
24
21
  const STUB_DIR = path_1.default.join(process.env.LOCALAPPDATA ?? path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'svharness', 'bin');
25
- const STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.ps1');
26
- const LEGACY_STUB_PATH = path_1.default.join(STUB_DIR, 'launch_codechat_cli.cmd');
22
+ const PS1_INSTALL_PATH = path_1.default.join(STUB_DIR, 'Start-CodeChat.ps1');
27
23
  const ICON_FILENAME = 'codechat-agent.ico';
28
24
  const ICON_STUB_PATH = path_1.default.join(STUB_DIR, ICON_FILENAME);
29
25
  const STANDALONE_MENU_KEY_BG = 'HKCU\\Software\\Classes\\Directory\\Background\\shell\\CodeChatStandaloneAgent';
@@ -102,31 +98,24 @@ function resolveWindowsTerminalPath() {
102
98
  }
103
99
  return null;
104
100
  }
105
- function buildStubContent(launchScriptPath) {
106
- const escapedNodeScript = launchScriptPath.replace(/'/g, "''");
107
- // ASCII-only + UTF-8 BOM. Legacy conhost fallback sets UTF-8 before CodeChat TUI.
108
- return `param(
109
- [string]$WorkDir = (Get-Location).Path
110
- )
111
-
112
- $ErrorActionPreference = 'Stop'
113
-
114
- try { chcp 65001 | Out-Null } catch {}
115
- [Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
116
- [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
117
- $OutputEncoding = [System.Text.UTF8Encoding]::new($false)
118
-
119
- Set-Location -LiteralPath $WorkDir
120
- & node '${escapedNodeScript}' "$WorkDir"
121
- exit $LASTEXITCODE
122
- `;
123
- }
124
- function writeStubFile(launchScriptPath) {
125
- const content = buildStubContent(launchScriptPath);
126
- return fs_extra_1.default.writeFile(STUB_PATH, `\uFEFF${content}`, 'utf8');
127
- }
128
101
  function buildPsInvokeArgs(ps1Path, dirPlaceholder) {
129
- return `-NoExit -NoProfile -ExecutionPolicy Bypass -File "${ps1Path}" ${dirPlaceholder}`;
102
+ return `-NoExit -NoProfile -ExecutionPolicy Bypass -File "${ps1Path}" -Action Start -WorkDir ${dirPlaceholder}`;
103
+ }
104
+ async function installStartCodeChatPs1() {
105
+ const fromDist = path_1.default.resolve(__dirname, '..', '..', 'templates', 'codechat', 'Start-CodeChat.ps1');
106
+ const fromCwd = path_1.default.resolve(__dirname, '..', '..', '..', 'templates', 'codechat', 'Start-CodeChat.ps1');
107
+ let source = '';
108
+ if (fs_extra_1.default.existsSync(fromDist)) {
109
+ source = fromDist;
110
+ }
111
+ else if (fs_extra_1.default.existsSync(fromCwd)) {
112
+ source = fromCwd;
113
+ }
114
+ else {
115
+ throw new Error(`\u672A\u627E\u5230 Start-CodeChat.ps1\uFF08\u9884\u671F\uFF1A${fromDist}\uFF09`);
116
+ }
117
+ await fs_extra_1.default.copy(source, PS1_INSTALL_PATH, { overwrite: true });
118
+ return PS1_INSTALL_PATH;
130
119
  }
131
120
  function buildMenuCommand(ps1Path, dirPlaceholder, wtPath) {
132
121
  const psArgs = buildPsInvokeArgs(ps1Path, dirPlaceholder);
@@ -150,23 +139,18 @@ async function runShellInstall(opts = {}) {
150
139
  // Avoid duplicate menu entries: overwrite standalone menu if present.
151
140
  (0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_BG);
152
141
  (0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_DIR);
153
- const launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
154
142
  await fs_extra_1.default.ensureDir(STUB_DIR);
155
- await writeStubFile(launchScriptPath);
156
- if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
157
- await fs_extra_1.default.remove(LEGACY_STUB_PATH);
158
- }
143
+ const ps1Path = await installStartCodeChatPs1();
159
144
  const iconValue = await installShellMenuIcon();
160
145
  const wtPath = resolveWindowsTerminalPath();
161
- const bgCommand = buildMenuCommand(STUB_PATH, '"%V"', wtPath);
162
- const dirCommand = buildMenuCommand(STUB_PATH, '"%1"', wtPath);
146
+ const bgCommand = buildMenuCommand(PS1_INSTALL_PATH, '"%V"', wtPath);
147
+ const dirCommand = buildMenuCommand(PS1_INSTALL_PATH, '"%1"', wtPath);
163
148
  registerMenuKey(win_registry_1.SHELL_MENU_KEY_BG, bgCommand, iconValue);
164
149
  registerMenuKey(win_registry_1.SHELL_MENU_KEY_DIR, dirCommand, iconValue);
165
150
  const via = wtPath ? 'Windows Terminal' : 'PowerShell(UTF-8)';
166
151
  logSuccess(`Windows 右键菜单已注册:「在此启动 CodeChat Agent」(${via})`, opts.silent);
167
- logInfo(` stub: ${STUB_PATH}`, opts.silent);
152
+ logInfo(` 脚本: ${ps1Path}`, opts.silent);
168
153
  logInfo(` icon: ${ICON_STUB_PATH}`, opts.silent);
169
- logInfo(` 脚本: ${launchScriptPath}`, opts.silent);
170
154
  if (wtPath) {
171
155
  logInfo(` wt: ${wtPath}`, opts.silent);
172
156
  }
@@ -183,15 +167,12 @@ async function runShellUninstall(opts = {}) {
183
167
  // Also remove standalone script menu (Start-CodeChat.ps1 independent install)
184
168
  (0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_BG);
185
169
  (0, win_registry_1.regDeleteKey)(STANDALONE_MENU_KEY_DIR);
186
- if (await fs_extra_1.default.pathExists(STUB_PATH)) {
187
- await fs_extra_1.default.remove(STUB_PATH);
170
+ if (await fs_extra_1.default.pathExists(PS1_INSTALL_PATH)) {
171
+ await fs_extra_1.default.remove(PS1_INSTALL_PATH);
188
172
  }
189
173
  if (await fs_extra_1.default.pathExists(ICON_STUB_PATH)) {
190
174
  await fs_extra_1.default.remove(ICON_STUB_PATH);
191
175
  }
192
- if (await fs_extra_1.default.pathExists(LEGACY_STUB_PATH)) {
193
- await fs_extra_1.default.remove(LEGACY_STUB_PATH);
194
- }
195
176
  // Clean up standalone script directory (Start-CodeChat.ps1 independent install)
196
177
  const standaloneDir = path_1.default.join(process.env.LOCALAPPDATA ?? path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'), 'codechat-standalone');
197
178
  if (await fs_extra_1.default.pathExists(standaloneDir)) {
@@ -205,24 +186,17 @@ async function runShellUninstall(opts = {}) {
205
186
  */
206
187
  function getShellStatus() {
207
188
  assertWin32();
208
- let launchScriptPath = '';
209
- try {
210
- launchScriptPath = (0, launch_script_path_1.resolveLaunchScriptPath)();
211
- }
212
- catch {
213
- launchScriptPath = '(未找到)';
214
- }
189
+ const ps1Installed = fs_extra_1.default.existsSync(PS1_INSTALL_PATH);
215
190
  const backgroundMenu = (0, win_registry_1.regKeyExists)(win_registry_1.SHELL_MENU_KEY_BG);
216
191
  const directoryMenu = (0, win_registry_1.regKeyExists)(win_registry_1.SHELL_MENU_KEY_DIR);
217
- const stubExists = fs_extra_1.default.existsSync(STUB_PATH);
218
192
  const wtPath = resolveWindowsTerminalPath();
219
193
  const aliasStatus = (0, ps_codechat_alias_1.getCodechatAliasStatus)();
220
194
  return {
221
- installed: backgroundMenu && directoryMenu && stubExists,
222
- stubPath: STUB_PATH,
195
+ installed: backgroundMenu && directoryMenu && ps1Installed,
196
+ stubPath: PS1_INSTALL_PATH,
223
197
  backgroundMenu,
224
198
  directoryMenu,
225
- launchScriptPath,
199
+ launchScriptPath: PS1_INSTALL_PATH,
226
200
  viaWindowsTerminal: wtPath !== null,
227
201
  aliasInstalled: aliasStatus.installed,
228
202
  profilePath: aliasStatus.profilePath,
@@ -232,10 +206,9 @@ function printShellStatus() {
232
206
  const status = getShellStatus();
233
207
  logger_1.logger.section('Windows shell 集成状态');
234
208
  logger_1.logger.plain(` 右键菜单完整: ${status.installed ? '是' : '否'}`);
235
- logger_1.logger.plain(` stub 路径: ${status.stubPath}`);
209
+ logger_1.logger.plain(` Start-CodeChat.ps1: ${status.stubPath}`);
236
210
  logger_1.logger.plain(` 空白处菜单: ${status.backgroundMenu ? '已注册' : '未注册'}`);
237
211
  logger_1.logger.plain(` 文件夹菜单: ${status.directoryMenu ? '已注册' : '未注册'}`);
238
- logger_1.logger.plain(` 启动脚本: ${status.launchScriptPath}`);
239
212
  logger_1.logger.plain(` 经 WT 启动: ${status.viaWindowsTerminal ? '是(install 时检测到 wt.exe)' : '否'}`);
240
213
  logger_1.logger.plain(` codechat 别名: ${status.aliasInstalled ? '已安装' : '未安装'}`);
241
214
  logger_1.logger.plain(` profile 路径: ${status.profilePath}`);
@@ -85,6 +85,8 @@ function mergeAutoOptions(cli, configSection, defaults, cmd) {
85
85
  promptDir: pickString('promptDir', cli.promptDir, cfg.promptDir, cmd),
86
86
  baselineAutoExtract: pickString('baselineAutoExtract', cli.baselineAutoExtract, cfg.baselineAutoExtract, cmd),
87
87
  convertEndpoint: pickString('convertEndpoint', cli.convertEndpoint, cfg.convertEndpoint, cmd),
88
+ scope: pickString('scope', cli.scope, cfg.scope, cmd),
89
+ sampleCount: pickNumber('sampleCount', cli.sampleCount, cfg.sampleCount, cmd),
88
90
  };
89
91
  }
90
92
  function mergeBuildOptions(cli, configSection, defaults, cmd) {
@@ -50,6 +50,7 @@ function pickAutoSection(raw) {
50
50
  'costAlertThreshold',
51
51
  'acpTimeoutMs',
52
52
  'maxRetriesPerPhase',
53
+ 'sampleCount',
53
54
  ]) {
54
55
  if (raw[k] !== undefined && raw[k] !== null) {
55
56
  s[k] = Number(raw[k]);
@@ -235,5 +236,11 @@ function normalizeConfig(raw, configPath) {
235
236
  }
236
237
  cfg.auto = pickAutoSection(raw.auto);
237
238
  }
239
+ if (raw.auto_pack_req !== undefined) {
240
+ if (!isPlainObject(raw.auto_pack_req)) {
241
+ throw new Error(`配置 auto_pack_req 必须是对象:${configPath}`);
242
+ }
243
+ cfg.auto_pack_req = pickAutoSection(raw.auto_pack_req);
244
+ }
238
245
  return cfg;
239
246
  }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONVERT_TIMEOUT_SEC = void 0;
4
+ /** Default per-file convert HTTP timeout: 15 minutes. */
5
+ exports.DEFAULT_CONVERT_TIMEOUT_SEC = 900;
@@ -57,7 +57,7 @@ function parseEndpointList(raw) {
57
57
  }
58
58
  return out;
59
59
  }
60
- async function ping(endpoint, timeoutMs = 10_000) {
60
+ async function ping(endpoint, timeoutMs = 30_000) {
61
61
  const controller = new AbortController();
62
62
  const timer = setTimeout(() => controller.abort(), timeoutMs);
63
63
  try {
@@ -80,7 +80,7 @@ async function ping(endpoint, timeoutMs = 10_000) {
80
80
  }
81
81
  }
82
82
  /** Probe endpoints in order; return those that respond to /healthz. */
83
- async function filterHealthyEndpoints(endpoints, timeoutMs = 10_000) {
83
+ async function filterHealthyEndpoints(endpoints, timeoutMs = 30_000) {
84
84
  const healthy = [];
85
85
  for (const endpoint of endpoints) {
86
86
  try {
@@ -186,6 +186,11 @@ function extractTraceId(body) {
186
186
  }
187
187
  function describeHttpError(status, body, endpoint) {
188
188
  const prefix = endpoint ? `[${endpoint}] ` : '';
189
+ if (status === 504) {
190
+ return (`${prefix}gateway timeout (HTTP 504): reverse proxy gave up waiting for markitdown_serve ` +
191
+ `(worker may still be running VLM/OCR). Increase nginx proxy_read_timeout to match ` +
192
+ `--timeout-sec, or reduce MARKITDOWN_OCR_MAX_RENDER_PX / use a shorter PDF export.`);
193
+ }
189
194
  if (body && typeof body === 'object') {
190
195
  const b = body;
191
196
  if (status === 415 && b.error === 'unsupported_format') {
@@ -9,6 +9,9 @@ class DashboardEventBus extends node_events_1.EventEmitter {
9
9
  emitBuildCompleted(finalScore) {
10
10
  this.emitEvent('build_completed', { finalScore });
11
11
  }
12
+ emitBuildFailed(finalScore) {
13
+ this.emitEvent('build_failed', { finalScore });
14
+ }
12
15
  emitPhaseStarted(key, short, description, attempt) {
13
16
  this.emitEvent('phase_started', { key, short, description, attempt });
14
17
  }
@@ -24,7 +24,7 @@ function startDashboard(port = 7879) {
24
24
  server = (0, server_1.createServer)(event_bus_1.dashboardEventBus, sseManager);
25
25
  // Subscribe to all events from the bus and forward to SSE clients
26
26
  const eventTypes = [
27
- 'build_started', 'build_completed',
27
+ 'build_started', 'build_completed', 'build_failed',
28
28
  'phase_started', 'phase_completed', 'phase_failed',
29
29
  'acp_output',
30
30
  'cost_updated', 'score_updated',