BVP value leg scores narrative mentions as deliverables (fix + tests)
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 38/100
調査の方向性
Start with agents/termlink/bvp-estimator/estimator.py, especially commitment_text() and the estimate_task() scoring flow; note that commitment sections must be sliced before template scaffolding is stripped. Read and run tests/test_bvp_commitment_scoping.py in the consumer repo, then check the estimator’s existing tests. Done means narrative-only mentions no longer raise scores, real commitments still score, ordering is covered, and legacy bodies remain unchanged.
索引モデルが issue の本文から書いたものです。
説明
Context
- Framework version: 1.6.768
- Project: 055-agentic-fleet-cockpit (shared-tooling mode)
- Reported: 2026-09-21T23:03:35Z
Description
Summary
The BVP value leg scores what a task's prose mentions, not what the task
commits to deliver. A task that merely talks about a gate scores the same as
one that builds it — so the score tracks how much prose an author wrote.
This continues T-112 (which stripped template scaffolding). T-113 verified its
own premise, found the boilerplate already gone, and named the remaining defect
without filing it: "the score tracks how much prose someone wrote rather than
how much value the task carries." This is that finding, measured and fixed.
Root cause
Every value scorer decides by trigger-phrase presence over the whole body:
has_gate = _has_any(body, [r"PreToolUse hook", r"completion gate", ...])
if has_gate:
return 4
Run over the entire body, that cannot distinguish BUILDS-a-gate from
MENTIONS-a-gate.
Evidence (measured, not asserted)
1. Correlational — 155 tasks in a live corpus. Longest body quartile scores a
median summed D1–D4 of 3.0; shortest quartile 0.0. Pearson
r(body length, value) = +0.27. Confounded on its own: valuable tasks may
simply get written about more.
2. Causal — controlled mutation, the decisive one. Take a real task; append a
Context paragraph that mentions a completion gate, a PreToolUse hook, a
regression test, an RCA, and L-/G- references, while committing to none of
them:
CONTROL (real task) D1-D4 = (3, 4, 0, 0) sum = 7
MUTANT (+narrative waffle) D1-D4 = (4, 4, 0, 0) sum = 8
Prose that promises nothing bought a point.
Fix
commitment_text() narrows the value leg's input to ## Acceptance Criteria
and ## Verification — the sections where a task states what it will do and how
that will be checked. Narrative sections describe; they do not promise.
Order is load-bearing. strip_template_scaffolding() deletes every column-0
# line — including the ## Acceptance Criteria heading the slice depends on.
Slicing after stripping finds no section, falls through the
backward-compatibility path, and the scoping becomes a silent no-op that still
passes a naive test. My first version had exactly this bug; a dedicated test
now pins the ordering.
Bodies with no commitment sections (legacy tasks) are returned unchanged, so an
older corpus does not silently collapse to zero. That preserves the defect for
those tasks by design — the lesser of two harms, stated rather than hidden.
Results
| before | after | |
|---|---|---|
| mutation delta (waffle) | +1 | 0 |
| genuine gate commitment (D1) | 4 | 4 (unchanged — no false negative) |
| r(body length, value) | +0.271 | +0.068 |
Consumer's full suite: 516 passed, including 4 pre-existing estimator suites
(39 tests) as the regression net.
Limit — please read before merging
Distinct value totals went 10 → 8 and mean 2.30 → 1.46. This repairs the
signal's validity, not its spread. It does not by itself populate Q1,
and it should not be described as "BVP repaired". The remaining degeneracy has a
separate cause still to be characterised.
It is also a behavioural change to scoring semantics for every consumer:
tasks with thin acceptance criteria will score lower. That is arguably correct —
a task that does not say what it will deliver arguably should not score high
value, and G-020 already requires real ACs — but it is a judgement call for the
maintainer, not one a downstream consumer should make unilaterally.
Patch
diff --git a/agents/termlink/bvp-estimator/estimator.py b/agents/termlink/bvp-estimator/estimator.py
index d366afd..7839bf3 100644
--- a/agents/termlink/bvp-estimator/estimator.py
+++ b/agents/termlink/bvp-estimator/estimator.py
@@ -288,6 +288,57 @@ def strip_template_scaffolding(body: str) -> str:
return "\n".join(kept)
+# ── Commitment scoping (value leg) ────────────────────────────────────────────
+_COMMITMENT_HEADINGS = ("acceptance criteria", "verification")
+
+
+def commitment_text(body: str) -> str:
+ """Keep only the sections in which a task COMMITS to deliver something.
+
+ The value scorers (score_d1 … score_d4) decide by trigger-phrase presence:
+ `_has_any(body, [r"PreToolUse hook", r"completion gate", ...])` -> 4. Run
+ over the whole body, that cannot distinguish a task that BUILDS a gate from
+ one that merely MENTIONS gates while narrating background — so the score
+ tracks how much prose an author wrote rather than how much value the task
+ carries.
+
+ Measured on this corpus before the change (155 tasks): the longest quartile
+ of task bodies scored a median summed D1-D4 of 3.0 against 0.0 for the
+ shortest, and a controlled mutation — appending a Context paragraph that
+ mentions mechanisms but promises none of them — raised a real task's D1 from
+ 3 to 4. Prose that commits to nothing bought a point.
+
+ `## Acceptance Criteria` and `## Verification` are where a task states what
+ it will actually do and how that will be checked. Narrative sections
+ (`## Context`, `## Findings`, `## Decisions`, `## Updates`, `## Evolution`,
+ `## Recommendation`) describe, they do not promise.
+
+ Backward compatibility: a body with no commitment section at all (legacy
+ tasks predating the template) returns unchanged, so an older corpus does not
+ silently collapse to zero. That fallback preserves the defect for those
+ tasks by design — it is the lesser of two harms, and it is visible here
+ rather than hidden.
+ """
+ lines = body.splitlines()
+ kept: list[str] = []
+ in_commitment = False
+ found_any = False
+ for ln in lines:
+ m = re.match(r"^(#{2,3})\s+(.*)$", ln)
+ if m:
+ heading = m.group(2).strip().lower()
+ if m.group(1) == "##":
+ in_commitment = any(heading.startswith(h)
+ for h in _COMMITMENT_HEADINGS)
+ found_any = found_any or in_commitment
+ # A `###` under a commitment heading (### Agent / ### Human) stays.
+ if in_commitment:
+ kept.append(ln)
+ if not found_any:
+ return body
+ return "\n".join(kept)
+
+
def score_d1_antifragility(fm: dict, body: str, tags: list[str]) -> tuple[int, list[str]]:
"""D1 — Antifragility: failure-class detection/prevention mechanisms."""
ev: list[str] = []
@@ -2316,6 +2367,13 @@ def estimate_task(task_path: Path, drivers: dict[str, int]) -> dict:
# resolving components from body text: the cost leg then read the very
# boilerplate this strips. estimate_cost now strips too — keep the two in
# step.
+ # Value leg scores COMMITMENTS, not narrative: a task that merely mentions
+ # a gate must not score as one that builds it.
+ # ORDER IS LOAD-BEARING: commitment_text() slices on `##` headings, and
+ # strip_template_scaffolding() deletes every column-0 `#` line — headings
+ # included. Slicing after stripping finds no sections, silently falls back
+ # to the whole body, and the scoping becomes a no-op. Slice first, then strip.
+ value_body = strip_template_scaffolding(commitment_text(body))
body = strip_template_scaffolding(body)
tags = list(fm.get("tags") or [])
is_inception = (fm.get("workflow_type") or "").lower() == "inception"
@@ -2392,9 +2450,9 @@ def estimate_task(task_path: Path, drivers: dict[str, int]) -> dict:
if is_inception:
sc, ev = _score_inception_voi(fm, body, tags)
elif driver_id in handlers:
- sc, ev = handlers[driver_id](fm, body, tags)
+ sc, ev = handlers[driver_id](fm, value_body, tags)
elif name_aliases.get(driver_id) in handlers:
- sc, ev = handlers[name_aliases[driver_id]](fm, body, tags)
+ sc, ev = handlers[name_aliases[driver_id]](fm, value_body, tags)
else:
sc, ev = score_free_driver(driver_id, fm, body, tags)
scores[driver_id] = sc
Regression test
Five tests, all of which fail against the pre-fix estimator. Full file at
tests/test_bvp_commitment_scoping.py in the consumer repo:
test_narrative_that_promises_nothing_does_not_raise_the_score— the mutation, as a standing checktest_a_real_commitment_still_scores— guard against a fix that zeroes everything and passes the above triviallytest_trigger_phrases_in_acceptance_criteria_outscore_the_same_words_in_prose— identical vocabulary, different commitment, different scoretest_commitment_slice_must_be_taken_before_scaffolding_strip— pins the silent-no-op ordering bugtest_a_body_with_no_commitment_sections_is_left_alone— backward compatibility asserted, not assumed
Reported via fw upstream report from field installation
- 主要言語
- Shell
- スター
- 16
- フォーク
- 6
- 平均マージ
- 6時間 24分
- マージ済み PR(30日)
- 5
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
DimitriGeelen/agentic-engineering-framework のほかの issue
-
area:cli bug severity:low status:fixed-on-bleeding-edge triaged
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
DimitriGeelen/agentic-engineering-framework#100 · コメント 5 件 ·
-
area:cli bug severity:medium status:fixed-on-bleeding-edge triaged
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
DimitriGeelen/agentic-engineering-framework#97 · コメント 5 件 ·
-
area:context bug severity:low status:fixed-on-bleeding-edge triaged
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
DimitriGeelen/agentic-engineering-framework#96 · コメント 5 件 ·
-
area:docs bug severity:medium status:in-progress triaged
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
DimitriGeelen/agentic-engineering-framework#95 · コメント 4 件 ·
-
area:watchtower bug severity:low status:released triaged
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
DimitriGeelen/agentic-engineering-framework#94 · コメント 4 件 ·
DimitriGeelen/agentic-engineering-framework の issue をすべて見る
似ている issue
-
roadmap-100
難易度 2/5 1〜3時間 初心者へのやさしさ 80/100
メンテナーはふだん 1 日以内に返信
-
priority: medium status: needs-decision work-class: scoped
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
melodic-software/claude-code-plugins#9339 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
数据界面的横向滚动条缺失オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
zilliztech/attu#1037 ·
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
community-scripts/ProxmoxVE#18073 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Uptime "Test Monitor" always fails with an empty HTTP 400 (uptime-checker API endpoint not configured)対応中かも @ayoubjemai が今日担当しました。 オープンWaiting for: Product Owner
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
getsentry/self-hosted#4582 ·
メンテナーはふだん 1 日以内に返信