svharness 0.15.11 → 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 (43) hide show
  1. package/README.md +106 -2
  2. package/auto/auto-harness-detail-design.md +136 -4
  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 +1 -1
  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 +16 -1
  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/index.js +77 -5
  19. package/dist/lib/acp-agent-cli-resolver.js +193 -0
  20. package/dist/lib/acp-client.js +4 -2
  21. package/dist/lib/agent-launcher.js +9 -4
  22. package/dist/lib/auto-assets.js +1 -0
  23. package/dist/lib/auto-optimize.js +23 -3
  24. package/dist/lib/auto-orchestrator.js +53 -20
  25. package/dist/lib/auto-pack-req-context.js +88 -0
  26. package/dist/lib/auto-pack-req-finalize.js +28 -0
  27. package/dist/lib/auto-req-pack-review.js +161 -0
  28. package/dist/lib/auto-sample-trace.js +144 -0
  29. package/dist/lib/auto-state.js +124 -15
  30. package/dist/lib/gate-checker.js +20 -10
  31. package/dist/lib/phase-runner.js +13 -2
  32. package/dist/lib/ps-codechat-alias.js +29 -16
  33. package/dist/lib/review-parser.js +14 -0
  34. package/dist/lib/score-aggregator.js +21 -0
  35. package/dist/lib/win-registry.js +1 -1
  36. package/dist/utils/validate-args.js +2 -1
  37. package/package.json +3 -2
  38. package/templates/codechat/Start-CodeChat.ps1 +176 -135
  39. package/templates/codechat/bash.exe.stackdump +29 -0
  40. package/templates/codechat/docs/project-structure.md +59 -0
  41. package/templates/svharness.config.example.yaml +2 -0
  42. package/dist/lib/launch-script-path.js +0 -24
  43. package/templates/codechat/.claude/.env +0 -59
@@ -14,6 +14,7 @@ function resolveAutoAssetPaths(promptDirOverride) {
14
14
  autoRoot,
15
15
  promptDir,
16
16
  reviewRubric: node_path_1.default.join(autoRoot, 'templates', 'review-rubric.yaml'),
17
+ requirementsReviewRubric: node_path_1.default.join(autoRoot, 'templates', 'requirements-review-rubric.yaml'),
17
18
  skillDir: node_path_1.default.join(autoRoot, 'skills', 'svharness'),
18
19
  };
19
20
  }
@@ -11,6 +11,18 @@ const logger_1 = require("../utils/logger");
11
11
  const phase_prompt_loader_1 = require("./phase-prompt-loader");
12
12
  const phase_deps_1 = require("./phase-deps");
13
13
  const auto_state_1 = require("./auto-state");
14
+ const SCOPE_ALLOWED_PHASES = {
15
+ full: new Set([
16
+ 'S10_wiki',
17
+ 'S40_extract_requirements',
18
+ 'S50_generate_specs',
19
+ 'S60_process_references',
20
+ 'S65_customize_agent_env',
21
+ 'S70_runtime_assets',
22
+ 'S80_seed_memory',
23
+ ]),
24
+ requirements: new Set(['S40_extract_requirements']),
25
+ };
14
26
  async function runOptimizePlan(ctx, acp, cost, round, review) {
15
27
  if (!ctx.harnessRoot)
16
28
  throw new Error('缺少 harnessRoot,无法生成优化计划');
@@ -62,22 +74,27 @@ async function runOptimizeImplement(ctx, acp, cost, round, review) {
62
74
  logger_1.logger.warn(`优化实施 ACP 调用失败:${result.error ?? 'unknown error'},继续状态重置`);
63
75
  }
64
76
  const state = await (0, auto_state_1.readBuildState)(ctx.harnessRoot);
65
- const phases = await collectPlanPhases(ctx.harnessRoot, round, review);
77
+ const allowed = SCOPE_ALLOWED_PHASES[ctx.scope];
78
+ const phases = (await collectPlanPhases(ctx.harnessRoot, round, review)).filter((phase) => allowed.has(phase));
66
79
  for (const phase of phases) {
67
80
  if (!state.phases[phase])
68
81
  continue;
69
82
  state.phases[phase].status = 'PENDING';
70
83
  delete state.phases[phase].outdated_reason;
71
- (0, phase_deps_1.invalidateDownstream)(phase, state);
84
+ if (ctx.scope === 'full') {
85
+ (0, phase_deps_1.invalidateDownstream)(phase, state);
86
+ }
72
87
  }
73
88
  for (const phase of (0, phase_deps_1.collectOutdatedKeys)(state)) {
89
+ if (!allowed.has(phase))
90
+ continue;
74
91
  state.phases[phase].status = 'PENDING';
75
92
  delete state.phases[phase].outdated_reason;
76
93
  }
77
94
  await (0, auto_state_1.writeBuildState)(ctx.harnessRoot, state);
78
95
  }
79
96
  async function collectPlanPhases(harnessRoot, round, review) {
80
- const phases = new Set(review.gaps.map((gap) => (0, phase_deps_1.normalizeGapPhase)(gap.phase)));
97
+ const phases = new Set(review.gaps.map((gap) => (0, phase_deps_1.normalizeGapPhase)(gap.phase)).filter((p) => p.includes('_')));
81
98
  const planPath = node_path_1.default.join(harnessRoot, 'auto-review', `第${round}轮修改-plan.md`);
82
99
  if (await fs_extra_1.default.pathExists(planPath)) {
83
100
  const body = await fs_extra_1.default.readFile(planPath, 'utf8');
@@ -85,5 +102,8 @@ async function collectPlanPhases(harnessRoot, round, review) {
85
102
  phases.add((0, phase_deps_1.normalizeGapPhase)(match[0]));
86
103
  }
87
104
  }
105
+ if (phases.size === 0) {
106
+ phases.add('S40_extract_requirements');
107
+ }
88
108
  return [...phases];
89
109
  }
@@ -52,9 +52,9 @@ class AutoOrchestrator {
52
52
  phaseRunner;
53
53
  constructor(ctx) {
54
54
  this.ctx = ctx;
55
- this.acp = new acp_client_1.AcpClient(ctx.acpMode);
55
+ const agentCommand = ctx.acpAgentCli?.command ?? 'claude';
56
+ this.acp = new acp_client_1.AcpClient(ctx.acpMode, agentCommand);
56
57
  this.cost = new cost_tracker_1.CostTracker(ctx.maxTotalCalls, ctx.budgetUsd, ctx.costAlertThreshold);
57
- // PhaseRunner 在 run() 中 ctx 就绪后延迟创建,避免初始 ctx.harnessRoot=null 时 PhaseRunner 持有陈旧引用
58
58
  }
59
59
  async run() {
60
60
  if (!this.ctx.harnessExists) {
@@ -63,7 +63,6 @@ class AutoOrchestrator {
63
63
  if (!this.ctx.harnessRoot) {
64
64
  throw new Error('未解析到 harnessRoot,无法继续 auto');
65
65
  }
66
- // PhaseRunner 延迟创建:此时 ctx.harnessRoot 已就绪(可能在 runMaterialsPrepAndBuild 后才赋值)
67
66
  this.phaseRunner = new phase_runner_1.PhaseRunner(this.ctx, this.acp, this.cost);
68
67
  event_bus_1.dashboardEventBus.emitBuildStarted(phase_runner_1.PHASE_DEFS.length);
69
68
  await this.ensureReviewRubric();
@@ -76,36 +75,53 @@ class AutoOrchestrator {
76
75
  event_bus_1.dashboardEventBus.emitBuildFailed(undefined);
77
76
  throw new Error(`构建阶段失败:${msg};auto 构建终止,请在修复后重新执行`);
78
77
  }
79
- if (this.ctx.maxPhase && phaseNumber(this.ctx.maxPhase) < 85) {
80
- logger_1.logger.info(`已到达 --max-phase ${this.ctx.maxPhase},跳过评审优化`);
81
- event_bus_1.dashboardEventBus.emitBuildCompleted(undefined);
82
- return;
78
+ if (this.shouldSkipReviewLoop()) {
79
+ logger_1.logger.info(this.ctx.scope === 'requirements'
80
+ ? '[auto_pack_req] 构建阶段完成,进入三维度评审'
81
+ : `已到达 --max-phase ${this.ctx.maxPhase},跳过评审优化`);
82
+ if (this.ctx.scope !== 'requirements') {
83
+ event_bus_1.dashboardEventBus.emitBuildCompleted(undefined);
84
+ return;
85
+ }
83
86
  }
87
+ await this.runReviewOptimizeLoop();
88
+ }
89
+ shouldSkipReviewLoop() {
90
+ if (this.ctx.scope === 'requirements')
91
+ return false;
92
+ return !!(this.ctx.maxPhase && phaseNumber(this.ctx.maxPhase) < 85);
93
+ }
94
+ async runReviewOptimizeLoop() {
84
95
  let previousScore = 0;
85
96
  let finalReview;
86
97
  for (let round = 1; round <= this.ctx.maxOptimizeRounds; round++) {
87
98
  event_bus_1.dashboardEventBus.emitReviewStarted(round);
88
99
  let review;
89
100
  try {
90
- review = await this.runHarnessReview(round);
101
+ review = await this.runReview(round);
91
102
  }
92
103
  catch (err) {
93
104
  const msg = err.message;
94
105
  if (msg.includes('预算') || msg.includes('调用次数') || msg.includes('ACP 调用')) {
95
106
  logger_1.logger.error(`[auto] 第 ${round} 轮评审因资源限制终止:${msg}`);
96
107
  event_bus_1.dashboardEventBus.emitBuildFailed(undefined);
97
- throw err; // 资源限制是整个流程的硬终止
108
+ throw err;
98
109
  }
99
110
  logger_1.logger.warn(`[auto] 第 ${round} 轮评审异常:${msg},继续下一轮`);
100
111
  continue;
101
112
  }
102
113
  event_bus_1.dashboardEventBus.emitReviewCompleted(round, review.composite_score, review.critical_count);
103
114
  event_bus_1.dashboardEventBus.emitScoreUpdated(review.composite_score, round);
104
- if (review.composite_score >= this.ctx.targetScore &&
105
- review.gate_pass &&
106
- review.critical_count === 0) {
115
+ const passed = this.ctx.scope === 'requirements'
116
+ ? review.composite_score > this.ctx.targetScore &&
117
+ review.gate_pass &&
118
+ review.critical_count === 0
119
+ : review.composite_score >= this.ctx.targetScore &&
120
+ review.gate_pass &&
121
+ review.critical_count === 0;
122
+ if (passed) {
107
123
  finalReview = review;
108
- await this.runFinalize(review);
124
+ await this.runCompletion(review);
109
125
  break;
110
126
  }
111
127
  if (round >= this.ctx.maxOptimizeRounds) {
@@ -127,7 +143,7 @@ class AutoOrchestrator {
127
143
  const msg = err.message;
128
144
  if (msg.includes('预算') || msg.includes('次数') || msg.includes('ACP 调用')) {
129
145
  logger_1.logger.warn(`[auto] 第 ${round} 轮优化因资源限制终止:${msg}`);
130
- break; // 资源限制触发时终止优化循环,保留当前评分
146
+ break;
131
147
  }
132
148
  logger_1.logger.warn(`第 ${round} 轮优化执行失败:${msg},状态重置已完成,下一轮可继续`);
133
149
  }
@@ -144,14 +160,26 @@ class AutoOrchestrator {
144
160
  if (!this.ctx.harnessRoot)
145
161
  return;
146
162
  const assets = (0, auto_assets_1.resolveAutoAssetPaths)();
147
- const dest = node_path_1.default.join(this.ctx.harnessRoot, 'auto', 'review-rubric.yaml');
148
- if (!(await fs_extra_1.default.pathExists(dest))) {
149
- await fs_extra_1.default.ensureDir(node_path_1.default.dirname(dest));
150
- await fs_extra_1.default.copyFile(assets.reviewRubric, dest);
163
+ const autoDir = node_path_1.default.join(this.ctx.harnessRoot, 'auto');
164
+ await fs_extra_1.default.ensureDir(autoDir);
165
+ const rubricDest = node_path_1.default.join(autoDir, 'review-rubric.yaml');
166
+ if (!(await fs_extra_1.default.pathExists(rubricDest))) {
167
+ await fs_extra_1.default.copyFile(assets.reviewRubric, rubricDest);
151
168
  logger_1.logger.success('已注入 auto/review-rubric.yaml');
152
169
  }
170
+ if (this.ctx.scope === 'requirements') {
171
+ const reqRubricDest = node_path_1.default.join(autoDir, 'requirements-review-rubric.yaml');
172
+ if (!(await fs_extra_1.default.pathExists(reqRubricDest))) {
173
+ await fs_extra_1.default.copyFile(assets.requirementsReviewRubric, reqRubricDest);
174
+ logger_1.logger.success('已注入 auto/requirements-review-rubric.yaml');
175
+ }
176
+ }
153
177
  }
154
- async runHarnessReview(round) {
178
+ async runReview(round) {
179
+ if (this.ctx.scope === 'requirements') {
180
+ const { runReqPackReview } = await Promise.resolve().then(() => __importStar(require('./auto-req-pack-review')));
181
+ return runReqPackReview(this.ctx, this.acp, this.cost, round);
182
+ }
155
183
  const { runHarnessReview } = await Promise.resolve().then(() => __importStar(require('./auto-review')));
156
184
  return runHarnessReview(this.ctx, this.acp, this.cost, round);
157
185
  }
@@ -164,7 +192,12 @@ class AutoOrchestrator {
164
192
  await runOptimizeImplement(this.ctx, this.acp, this.cost, round, review);
165
193
  await this.phaseRunner.runBuildPhases({ onlyPendingOrOutdated: true });
166
194
  }
167
- async runFinalize(review) {
195
+ async runCompletion(review) {
196
+ if (this.ctx.scope === 'requirements') {
197
+ const { finalizeReqPack } = await Promise.resolve().then(() => __importStar(require('./auto-pack-req-finalize')));
198
+ await finalizeReqPack(this.ctx, review);
199
+ return;
200
+ }
168
201
  const { finalizeAutoHarness } = await Promise.resolve().then(() => __importStar(require('./auto-finalize')));
169
202
  await finalizeAutoHarness(this.ctx, review);
170
203
  }
@@ -0,0 +1,88 @@
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.looksLikeMaterialsPackRoot = looksLikeMaterialsPackRoot;
7
+ exports.looksLikeRequirementsFolder = looksLikeRequirementsFolder;
8
+ exports.resolveAutoPackReqWorkContext = resolveAutoPackReqWorkContext;
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const harness_resolver_1 = require("./harness-resolver");
12
+ const DOC_FILE_RE = /\.(md|docx|doc|pdf|xlsx|xls|pptx|ppt|txt|csv)$/i;
13
+ const MATERIALS_PACK_SUBDIR_RE = /^(requirements?|baseline|references?|extra[-_]?skills?|需求|接口|协议|skill)/i;
14
+ /** 当前目录是否像资料包根(含 requirements/、baseline/ 等子目录) */
15
+ async function looksLikeMaterialsPackRoot(dir) {
16
+ if (!(await fs_extra_1.default.pathExists(dir)))
17
+ return false;
18
+ const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
19
+ for (const entry of entries) {
20
+ if (!entry.isDirectory())
21
+ continue;
22
+ if (MATERIALS_PACK_SUBDIR_RE.test(entry.name))
23
+ return true;
24
+ }
25
+ return false;
26
+ }
27
+ /** 当前目录是否像 requirements 资料夹(含文档文件,且不像资料包根) */
28
+ async function looksLikeRequirementsFolder(dir) {
29
+ if (!(await fs_extra_1.default.pathExists(dir)))
30
+ return false;
31
+ if (await (0, harness_resolver_1.isHarnessRoot)(dir))
32
+ return false;
33
+ if (await looksLikeMaterialsPackRoot(dir))
34
+ return false;
35
+ const entries = await fs_extra_1.default.readdir(dir, { withFileTypes: true });
36
+ const topFiles = entries.filter((e) => e.isFile());
37
+ if (topFiles.some((f) => DOC_FILE_RE.test(f.name)))
38
+ return true;
39
+ for (const entry of entries) {
40
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
41
+ continue;
42
+ const sub = node_path_1.default.join(dir, entry.name);
43
+ const subEntries = await fs_extra_1.default.readdir(sub, { withFileTypes: true });
44
+ if (subEntries.some((f) => f.isFile() && DOC_FILE_RE.test(f.name)))
45
+ return true;
46
+ }
47
+ return false;
48
+ }
49
+ /**
50
+ * 解析 auto_pack_req 工作上下文。
51
+ * 无显式 workdir 时:若当前目录本身是 requirements 资料夹,则以其父目录为 workDir 并固定 requirements 输入。
52
+ */
53
+ async function resolveAutoPackReqWorkContext(positionalWorkdir, configWorkDir, requirementsOverride) {
54
+ const explicit = positionalWorkdir ?? configWorkDir;
55
+ if (explicit) {
56
+ const workDir = node_path_1.default.resolve(explicit);
57
+ const harnessRoot = await (0, harness_resolver_1.resolveHarnessRoot)(workDir);
58
+ return {
59
+ workDir,
60
+ harnessRoot,
61
+ requirementsPath: requirementsOverride
62
+ ? node_path_1.default.isAbsolute(requirementsOverride)
63
+ ? requirementsOverride
64
+ : node_path_1.default.resolve(workDir, requirementsOverride)
65
+ : undefined,
66
+ mode: harnessRoot ? 'harness-rerun' : 'materials-pack',
67
+ };
68
+ }
69
+ const cwd = process.cwd();
70
+ const harnessFromCwd = await (0, harness_resolver_1.resolveHarnessRoot)(cwd);
71
+ if (harnessFromCwd) {
72
+ return { workDir: cwd, harnessRoot: harnessFromCwd, mode: 'harness-rerun' };
73
+ }
74
+ if (await looksLikeMaterialsPackRoot(cwd)) {
75
+ return { workDir: cwd, harnessRoot: null, mode: 'materials-pack' };
76
+ }
77
+ if (await looksLikeRequirementsFolder(cwd)) {
78
+ const parent = node_path_1.default.dirname(cwd);
79
+ const harnessFromParent = await (0, harness_resolver_1.resolveHarnessRoot)(parent);
80
+ return {
81
+ workDir: parent,
82
+ harnessRoot: harnessFromParent,
83
+ requirementsPath: node_path_1.default.resolve(cwd),
84
+ mode: harnessFromParent ? 'harness-rerun' : 'requirements-cwd',
85
+ };
86
+ }
87
+ return { workDir: cwd, harnessRoot: null, mode: 'materials-pack' };
88
+ }
@@ -0,0 +1,28 @@
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.finalizeReqPack = finalizeReqPack;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const logger_1 = require("../utils/logger");
10
+ async function finalizeReqPack(ctx, review) {
11
+ if (!ctx.harnessRoot)
12
+ throw new Error('缺少 harnessRoot,无法完成需求包流程');
13
+ const outPath = node_path_1.default.join(ctx.harnessRoot, 'auto-review', 'pack-req-complete.json');
14
+ await fs_extra_1.default.ensureDir(node_path_1.default.dirname(outPath));
15
+ await fs_extra_1.default.writeFile(outPath, JSON.stringify({
16
+ completed_at: new Date().toISOString(),
17
+ scope: 'requirements',
18
+ composite_score: review.composite_score,
19
+ target_score: ctx.targetScore,
20
+ gate_pass: review.gate_pass,
21
+ critical_count: review.critical_count,
22
+ mechanical_score: review.mechanical_score,
23
+ adversarial_score: review.review_score,
24
+ report_path: node_path_1.default.relative(ctx.harnessRoot, review.report_path).replace(/\\/g, '/'),
25
+ note: 'auto_pack_req 需求条目化闭环完成(未执行 S90 封存)',
26
+ }, null, 2), 'utf8');
27
+ logger_1.logger.success(`auto_pack_req 完成:综合分 ${review.composite_score.toFixed(1)} > ${ctx.targetScore},产物见 auto-review/`);
28
+ }
@@ -0,0 +1,161 @@
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.runReqPackReview = runReqPackReview;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const logger_1 = require("../utils/logger");
10
+ const phase_prompt_loader_1 = require("./phase-prompt-loader");
11
+ const gate_checker_1 = require("./gate-checker");
12
+ const score_aggregator_1 = require("./score-aggregator");
13
+ const auto_sample_trace_1 = require("./auto-sample-trace");
14
+ const review_parser_1 = require("./review-parser");
15
+ async function runReqPackReview(ctx, acp, cost, round) {
16
+ if (!ctx.harnessRoot)
17
+ throw new Error('缺少 harnessRoot,无法评审');
18
+ await fs_extra_1.default.ensureDir(node_path_1.default.join(ctx.harnessRoot, 'auto-review'));
19
+ const gates = await (0, gate_checker_1.runMechanicalGates)(ctx.harnessRoot, {
20
+ scope: 'requirements',
21
+ verbose: ctx.verbose,
22
+ });
23
+ await (0, auto_sample_trace_1.writeSampleFragmentsFile)(ctx.harnessRoot, ctx.sampleCount);
24
+ const selfReviewPath = node_path_1.default.join(ctx.harnessRoot, 'auto-review', `第${round}轮需求自评报告.md`);
25
+ const adversarialPath = node_path_1.default.join(ctx.harnessRoot, 'auto-review', `第${round}轮对抗评审.md`);
26
+ const samplePath = node_path_1.default.join(ctx.harnessRoot, 'auto-review', `第${round}轮抽样验证报告.md`);
27
+ if (ctx.dryRun) {
28
+ for (const template of ['S40-review.md', 'S40-adversarial-review.md', 'S40-sample-trace.md']) {
29
+ const prompt = await (0, phase_prompt_loader_1.loadPhasePrompt)(template, buildPromptVars(ctx, round, gates), ctx.promptDir);
30
+ logger_1.logger.plain(`\n--- ${template} ---\n${prompt}\n`);
31
+ }
32
+ return dryRunReqPackReview(round, gates);
33
+ }
34
+ await runAcpReviewPhase(ctx, acp, cost, round, 'S40-review.md', 'req_self_review', '<AUTO_SIGNAL>REQ_REVIEW_DONE</AUTO_SIGNAL>');
35
+ await runAcpReviewPhase(ctx, acp, cost, round, 'S40-adversarial-review.md', 'req_adversarial_review', '<AUTO_SIGNAL>ADV_REVIEW_DONE</AUTO_SIGNAL>');
36
+ await runAcpReviewPhase(ctx, acp, cost, round, 'S40-sample-trace.md', 'req_sample_trace', '<AUTO_SIGNAL>SAMPLE_TRACE_DONE</AUTO_SIGNAL>');
37
+ const selfRaw = await (0, review_parser_1.parseReportFrontmatter)(selfReviewPath);
38
+ const advRaw = await (0, review_parser_1.parseReportFrontmatter)(adversarialPath);
39
+ const sampleRaw = await (0, review_parser_1.parseReportFrontmatter)(samplePath);
40
+ if (!advRaw || !sampleRaw) {
41
+ throw new Error(`第 ${round} 轮需求评审报告缺失或 frontmatter 格式错误`);
42
+ }
43
+ const adversarial = parseAdversarialScore(advRaw);
44
+ const sampleTrace = parseSampleTraceScore(sampleRaw);
45
+ const selfContent = await fs_extra_1.default.readFile(selfReviewPath, 'utf8').catch(() => '');
46
+ const gaps = (0, review_parser_1.parseGapsFromMarkdown)(selfContent);
47
+ const criticalCount = countCriticalIssues(adversarial, gaps);
48
+ const scores = (0, score_aggregator_1.computeReqPackScore)(gates, adversarial.average, sampleTrace.average, criticalCount);
49
+ const review = {
50
+ round,
51
+ report_path: adversarialPath,
52
+ dimension_scores: {
53
+ requirements: Number(asRecord(asRecord(selfRaw?.score).dimensions).requirements ?? 0),
54
+ },
55
+ mechanical_score: scores.mechanical,
56
+ review_score: scores.adversarial,
57
+ critical_count: criticalCount,
58
+ gate_pass: scores.gate_pass,
59
+ gate_details: gates,
60
+ modifications_required: !(scores.composite > ctx.targetScore &&
61
+ scores.gate_pass &&
62
+ criticalCount === 0),
63
+ gaps,
64
+ composite_score: scores.composite,
65
+ };
66
+ await writeReqPackScoreSnapshot(ctx, review, adversarial, sampleTrace, scores);
67
+ logger_1.logger.info(`第 ${round} 轮需求包评分:综合 ${scores.composite.toFixed(1)} / 10 ` +
68
+ `(机械 ${scores.mechanical.toFixed(1)} + 对抗 ${scores.adversarial.toFixed(1)} + 抽样 ${scores.sample_trace.toFixed(1)})`);
69
+ return review;
70
+ }
71
+ async function runAcpReviewPhase(ctx, acp, cost, round, template, phaseKey, signal) {
72
+ const gates = await (0, gate_checker_1.runMechanicalGates)(ctx.harnessRoot, { scope: 'requirements', verbose: ctx.verbose });
73
+ const prompt = await (0, phase_prompt_loader_1.loadPhasePrompt)(template, buildPromptVars(ctx, round, gates), ctx.promptDir);
74
+ cost.recordCall(`${phaseKey}_round_${round}`);
75
+ const result = await acp.call({
76
+ workDir: ctx.harnessRoot,
77
+ phase: phaseKey,
78
+ prompt: (0, phase_prompt_loader_1.wrapAcpPrompt)(prompt, signal),
79
+ timeoutMs: ctx.acpTimeoutMs,
80
+ });
81
+ await fs_extra_1.default.writeFile(node_path_1.default.join(ctx.harnessRoot, 'auto-review', `${phaseKey}-round-${round}.log`), [result.output, result.error ? `\n[stderr]\n${result.error}` : ''].join(''), 'utf8');
82
+ if (!result.success) {
83
+ throw new Error(`${template} Agent 调用失败:${result.error ?? 'unknown error'}`);
84
+ }
85
+ }
86
+ function buildPromptVars(ctx, round, gates) {
87
+ return {
88
+ HARNESS_ROOT: ctx.harnessRoot,
89
+ AUTO_GOAL: ctx.goal ?? '未提供构建目的,请根据 harness 内容判断',
90
+ ROUND: round,
91
+ DOCTOR_EXIT: gates.doctor_exit,
92
+ VERIFY_EXIT: gates.verify_exit,
93
+ FILE_COMPLETENESS_PCT: gates.file_completeness_pct.toFixed(1),
94
+ SAMPLE_FRAGMENTS_PATH: node_path_1.default.join(ctx.harnessRoot, 'auto-review', 'sample-fragments.json'),
95
+ };
96
+ }
97
+ function parseAdversarialScore(raw) {
98
+ const score = asRecord(raw.score);
99
+ const adv = asRecord(score.adversarial);
100
+ const coverage = Number(adv.coverage ?? 0);
101
+ const fidelity = Number(adv.fidelity ?? 0);
102
+ const completeness = Number(adv.completeness ?? 0);
103
+ const average = Number(adv.average ?? (coverage + fidelity + completeness) / 3);
104
+ const issues = Array.isArray(raw.issues)
105
+ ? raw.issues
106
+ : [];
107
+ return { coverage, fidelity, completeness, average, issues };
108
+ }
109
+ function parseSampleTraceScore(raw) {
110
+ const score = asRecord(raw.score);
111
+ const st = asRecord(score.sample_trace);
112
+ const traceability = Number(st.traceability ?? 0);
113
+ const fidelity = Number(st.fidelity ?? 0);
114
+ const usability = Number(st.usability ?? 0);
115
+ const average = Number(st.average ?? (traceability + fidelity + usability) / 3);
116
+ const items = Array.isArray(st.items)
117
+ ? st.items
118
+ : [];
119
+ return { traceability, fidelity, usability, average, items };
120
+ }
121
+ function countCriticalIssues(adversarial, gaps) {
122
+ const fromAdv = adversarial.issues.filter((i) => i.severity === 'critical').length;
123
+ const fromGaps = gaps.filter((g) => g.severity === 'critical').length;
124
+ return fromAdv + fromGaps;
125
+ }
126
+ async function writeReqPackScoreSnapshot(ctx, review, adversarial, sampleTrace, scores) {
127
+ const snapshot = {
128
+ round: review.round,
129
+ evaluated_at: new Date().toISOString(),
130
+ scope: 'requirements',
131
+ mechanical_score: scores.mechanical,
132
+ adversarial_score: scores.adversarial,
133
+ sample_trace_score: scores.sample_trace,
134
+ composite_score: scores.composite,
135
+ target_score: ctx.targetScore,
136
+ gate_pass: scores.gate_pass,
137
+ critical_count: review.critical_count,
138
+ adversarial_details: adversarial,
139
+ sample_trace_details: sampleTrace,
140
+ gate_details: review.gate_details,
141
+ };
142
+ await fs_extra_1.default.writeFile(node_path_1.default.join(ctx.harnessRoot, 'auto-review', 'auto-score.json'), JSON.stringify(snapshot, null, 2), 'utf8');
143
+ }
144
+ function dryRunReqPackReview(round, gates) {
145
+ return {
146
+ round,
147
+ report_path: 'dry-run',
148
+ review_score: 0,
149
+ dimension_scores: {},
150
+ critical_count: 0,
151
+ gaps: [],
152
+ gate_pass: false,
153
+ gate_details: gates,
154
+ mechanical_score: 0,
155
+ composite_score: 0,
156
+ modifications_required: true,
157
+ };
158
+ }
159
+ function asRecord(value) {
160
+ return typeof value === 'object' && value !== null ? value : {};
161
+ }
@@ -0,0 +1,144 @@
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.generateSampleFragments = generateSampleFragments;
7
+ exports.writeSampleFragmentsFile = writeSampleFragmentsFile;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const MIN_EXCERPT_CHARS = 50;
11
+ const MAX_EXCERPT_CHARS = 200;
12
+ const NUMERIC_PATTERN = /\d+(\.\d+)?(%|ms|s|Hz|MHz|GB|MB|KB)?/;
13
+ const ENUM_PATTERN = /\b(ON|OFF|TRUE|FALSE|ENABLE|DISABLE|是|否|打开|关闭)\b/i;
14
+ const TIMING_PATTERN = /\b(延迟|超时|timeout|delay|ms|秒|分钟|时序|周期)\b/i;
15
+ const SIGNAL_PRIORITY = {
16
+ numeric: 5,
17
+ timing: 4,
18
+ enum: 4,
19
+ table_row: 3,
20
+ list_item: 2,
21
+ paragraph: 1,
22
+ };
23
+ function classifyLine(text) {
24
+ const trimmed = text.trim();
25
+ if (!trimmed || trimmed.startsWith('```') || trimmed === '---') {
26
+ return { signal_type: 'paragraph', priority: 0 };
27
+ }
28
+ if (trimmed.startsWith('|') && trimmed.split('|').filter(Boolean).length >= 2) {
29
+ return { signal_type: 'table_row', priority: SIGNAL_PRIORITY.table_row };
30
+ }
31
+ if (/^[-*+]\s/.test(trimmed) || /^\d+\.\s/.test(trimmed)) {
32
+ return { signal_type: 'list_item', priority: SIGNAL_PRIORITY.list_item };
33
+ }
34
+ if (NUMERIC_PATTERN.test(trimmed)) {
35
+ return { signal_type: 'numeric', priority: SIGNAL_PRIORITY.numeric };
36
+ }
37
+ if (TIMING_PATTERN.test(trimmed)) {
38
+ return { signal_type: 'timing', priority: SIGNAL_PRIORITY.timing };
39
+ }
40
+ if (ENUM_PATTERN.test(trimmed)) {
41
+ return { signal_type: 'enum', priority: SIGNAL_PRIORITY.enum };
42
+ }
43
+ if (/^#{1,6}\s/.test(trimmed)) {
44
+ return { signal_type: 'list_item', priority: SIGNAL_PRIORITY.list_item + 1 };
45
+ }
46
+ return { signal_type: 'paragraph', priority: SIGNAL_PRIORITY.paragraph };
47
+ }
48
+ function buildExcerpt(lines, startIdx) {
49
+ let excerpt = '';
50
+ let endIdx = startIdx;
51
+ for (let i = startIdx; i < lines.length && excerpt.length < MAX_EXCERPT_CHARS; i++) {
52
+ const line = lines[i]?.trim();
53
+ if (!line)
54
+ continue;
55
+ const next = excerpt ? `${excerpt}\n${line}` : line;
56
+ if (next.length > MAX_EXCERPT_CHARS && excerpt.length >= MIN_EXCERPT_CHARS)
57
+ break;
58
+ excerpt = next;
59
+ endIdx = i;
60
+ if (excerpt.length >= MIN_EXCERPT_CHARS && excerpt.length >= MAX_EXCERPT_CHARS * 0.6)
61
+ break;
62
+ }
63
+ if (excerpt.length < MIN_EXCERPT_CHARS)
64
+ return null;
65
+ return { excerpt, endIdx };
66
+ }
67
+ async function collectCandidates(mdDir) {
68
+ const candidates = [];
69
+ if (!(await fs_extra_1.default.pathExists(mdDir)))
70
+ return candidates;
71
+ const files = (await fs_extra_1.default.readdir(mdDir)).filter((f) => f.endsWith('.md'));
72
+ for (const file of files) {
73
+ const abs = node_path_1.default.join(mdDir, file);
74
+ const content = await fs_extra_1.default.readFile(abs, 'utf8');
75
+ const lines = content.split(/\r?\n/);
76
+ for (let i = 0; i < lines.length; i++) {
77
+ const { signal_type, priority } = classifyLine(lines[i] ?? '');
78
+ if (priority === 0)
79
+ continue;
80
+ candidates.push({
81
+ file: node_path_1.default.join('requirements/md', file).replace(/\\/g, '/'),
82
+ lineNo: i + 1,
83
+ text: lines[i] ?? '',
84
+ signal_type,
85
+ priority,
86
+ });
87
+ }
88
+ }
89
+ return candidates;
90
+ }
91
+ function shuffle(items) {
92
+ const copy = [...items];
93
+ for (let i = copy.length - 1; i > 0; i--) {
94
+ const j = Math.floor(Math.random() * (i + 1));
95
+ [copy[i], copy[j]] = [copy[j], copy[i]];
96
+ }
97
+ return copy;
98
+ }
99
+ async function generateSampleFragments(harnessRoot, count = 7) {
100
+ const mdDir = node_path_1.default.join(harnessRoot, 'requirements', 'md');
101
+ const candidates = await collectCandidates(mdDir);
102
+ if (candidates.length === 0)
103
+ return [];
104
+ const sorted = [...candidates].sort((a, b) => b.priority - a.priority);
105
+ const highPriority = sorted.filter((c) => c.priority >= 3);
106
+ const pool = shuffle(highPriority.length >= count ? highPriority : sorted);
107
+ const selected = pool.slice(0, Math.min(count, pool.length));
108
+ const fragments = [];
109
+ const usedKeys = new Set();
110
+ for (const cand of selected) {
111
+ const abs = node_path_1.default.join(harnessRoot, cand.file);
112
+ if (!(await fs_extra_1.default.pathExists(abs)))
113
+ continue;
114
+ const lines = (await fs_extra_1.default.readFile(abs, 'utf8')).split(/\r?\n/);
115
+ const startIdx = cand.lineNo - 1;
116
+ const built = buildExcerpt(lines, startIdx);
117
+ if (!built)
118
+ continue;
119
+ const key = `${cand.file}:${cand.lineNo}`;
120
+ if (usedKeys.has(key))
121
+ continue;
122
+ usedKeys.add(key);
123
+ fragments.push({
124
+ id: `SF-${String(fragments.length + 1).padStart(3, '0')}`,
125
+ file: cand.file,
126
+ start_line: cand.lineNo,
127
+ end_line: built.endIdx + 1,
128
+ excerpt: built.excerpt,
129
+ signal_type: cand.signal_type,
130
+ });
131
+ }
132
+ return fragments;
133
+ }
134
+ async function writeSampleFragmentsFile(harnessRoot, count = 7) {
135
+ const fragments = await generateSampleFragments(harnessRoot, count);
136
+ const outDir = node_path_1.default.join(harnessRoot, 'auto-review');
137
+ await fs_extra_1.default.ensureDir(outDir);
138
+ await fs_extra_1.default.writeJson(node_path_1.default.join(outDir, 'sample-fragments.json'), {
139
+ generated_at: new Date().toISOString(),
140
+ count: fragments.length,
141
+ fragments,
142
+ }, { spaces: 2 });
143
+ return fragments;
144
+ }