@seanyao/roll 3.624.2 → 3.624.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## v3.624.3 — 2026-06-24
6
+
7
+ - **验收报告内容判定与视觉证据闸分离**:`attest:gate` 不再把“报告有 AC/ac-map/E2E 证据但声明的截图面未真实捕获”误报成 empty-shell;这类情况现在按视觉证据缺口给出可操作 reason,避免 correction loop 反复走错修复方向。(FIX-400 follow-up) `[acceptance-evidence]`
8
+
5
9
  ## v3.624.2 — 2026-06-24
6
10
 
7
11
  ### 稳定性
package/dist/roll.mjs CHANGED
@@ -249476,6 +249476,22 @@ function hasRealTerminalCapture(worktreeCwd, storyId) {
249476
249476
  return false;
249477
249477
  return takenCaptureCount(worktreeCwd, storyId, "terminal") >= need;
249478
249478
  }
249479
+ function declaredSurfaceCaptureFloor(worktreeCwd, storyId) {
249480
+ const owesWeb = owesRealWebCapture(worktreeCwd, storyId);
249481
+ const owesTerm = owesTerminalCapture(worktreeCwd, storyId);
249482
+ if (!owesWeb && !owesTerm)
249483
+ return { ok: true };
249484
+ const gaps = [];
249485
+ if (owesWeb && !hasRealWebCapture(worktreeCwd, storyId)) {
249486
+ gaps.push(`declared deliverable_url(s) not all really captured (need ${webCaptureNeed(worktreeCwd, storyId)} taken web shots)`);
249487
+ }
249488
+ if (owesTerm && !hasRealTerminalCapture(worktreeCwd, storyId)) {
249489
+ gaps.push(`declared deliverable_cmd(s) not all really captured (need ${deliverableCmdsForStory(worktreeCwd, storyId).length} taken terminal shots)`);
249490
+ }
249491
+ if (gaps.length === 0)
249492
+ return { ok: true, reason: "all declared surfaces really captured" };
249493
+ return { ok: false, reason: `declared surface capture missing: ${gaps.join("; ")}` };
249494
+ }
249479
249495
  function passAcVisualFloor(worktreeCwd, storyId) {
249480
249496
  if (violatesMustDeclareSurface(worktreeCwd, storyId)) {
249481
249497
  return { ok: false, reason: MUST_DECLARE_FAIL_REASON };
@@ -249491,26 +249507,35 @@ function passAcVisualFloor(worktreeCwd, storyId) {
249491
249507
  const missing = pass.filter((e) => !(e.evidence ?? []).some((ev) => ev.kind === "screenshot" && typeof ev.href === "string" && ev.href !== ""));
249492
249508
  if (missing.length === 0)
249493
249509
  return { ok: true };
249494
- const owesWeb = owesRealWebCapture(worktreeCwd, storyId);
249495
- const owesTerm = owesTerminalCapture(worktreeCwd, storyId);
249496
- if (owesWeb || owesTerm) {
249497
- const gaps = [];
249498
- if (owesWeb && !hasRealWebCapture(worktreeCwd, storyId)) {
249499
- gaps.push(`declared deliverable_url(s) not all really captured (need ${webCaptureNeed(worktreeCwd, storyId)} taken web shots)`);
249500
- }
249501
- if (owesTerm && !hasRealTerminalCapture(worktreeCwd, storyId)) {
249502
- gaps.push(`declared deliverable_cmd(s) not all really captured (need ${deliverableCmdsForStory(worktreeCwd, storyId).length} taken terminal shots)`);
249503
- }
249504
- if (gaps.length === 0)
249505
- return { ok: true, reason: "all declared surfaces really captured" };
249510
+ const declared = declaredSurfaceCaptureFloor(worktreeCwd, storyId);
249511
+ if (declared.reason !== void 0) {
249512
+ if (declared.ok)
249513
+ return declared;
249506
249514
  const ids2 = missing.map((e) => e.ac ?? "?").join(", ");
249507
- return { ok: false, reason: `pass AC(s) lack screenshot evidence and a declared surface was never really captured (honest-skip does not satisfy a declared surface): ${gaps.join("; ")} [${ids2}]` };
249515
+ return { ok: false, reason: `pass AC(s) lack screenshot evidence and a declared surface was never really captured (honest-skip does not satisfy a declared surface): ${declared.reason} [${ids2}]` };
249508
249516
  }
249509
249517
  if (hasMachineCaptureSkip(worktreeCwd, storyId))
249510
249518
  return { ok: true, reason: "machine capture skip present" };
249511
249519
  const ids = missing.map((e) => e.ac ?? "?").join(", ");
249512
249520
  return { ok: false, reason: `pass AC(s) lack screenshot evidence or machine capture skip: ${ids}` };
249513
249521
  }
249522
+ function visualEvidenceFloor(worktreeCwd, storyId, html) {
249523
+ const passAc = passAcVisualFloor(worktreeCwd, storyId);
249524
+ if (!passAc.ok)
249525
+ return passAc;
249526
+ if (!storyRequiresScreenshot(worktreeCwd, storyId))
249527
+ return passAc;
249528
+ const declared = declaredSurfaceCaptureFloor(worktreeCwd, storyId);
249529
+ if (!declared.ok)
249530
+ return declared;
249531
+ if (declared.reason !== void 0)
249532
+ return declared;
249533
+ if (/<figure class="shot\b|href="screenshots\/|src="screenshots\//i.test(html))
249534
+ return { ok: true };
249535
+ if (hasMachineCaptureSkip(worktreeCwd, storyId))
249536
+ return { ok: true, reason: "machine capture skip present" };
249537
+ return { ok: false, reason: "visual evidence missing: no screenshot reference or machine capture skip" };
249538
+ }
249514
249539
  function redAcFailures(worktreeCwd, storyId) {
249515
249540
  const entries = readAcMapEntries(worktreeCwd, storyId);
249516
249541
  if (entries === null)
@@ -249549,6 +249574,19 @@ function verificationReportFresh(worktreeCwd, storyId, sinceSec) {
249549
249574
  }
249550
249575
  }
249551
249576
  function verificationReportHasContent(worktreeCwd, storyId) {
249577
+ if (storyId === "")
249578
+ return false;
249579
+ const p = existingReport(worktreeCwd, storyId);
249580
+ if (p === null)
249581
+ return false;
249582
+ try {
249583
+ const html = readFileSync61(p, "utf8");
249584
+ return verificationReportHasAcceptanceContent(worktreeCwd, storyId) && visualEvidenceFloor(worktreeCwd, storyId, html).ok;
249585
+ } catch {
249586
+ return false;
249587
+ }
249588
+ }
249589
+ function verificationReportHasAcceptanceContent(worktreeCwd, storyId) {
249552
249590
  if (storyId === "")
249553
249591
  return false;
249554
249592
  const p = existingReport(worktreeCwd, storyId);
@@ -249572,23 +249610,7 @@ function verificationReportHasContent(worktreeCwd, storyId) {
249572
249610
  return false;
249573
249611
  positiveWithEvidence += 1;
249574
249612
  }
249575
- if (positiveWithEvidence === 0)
249576
- return false;
249577
- if (!passAcVisualFloor(worktreeCwd, storyId).ok)
249578
- return false;
249579
- if (storyRequiresScreenshot(worktreeCwd, storyId)) {
249580
- const owesWeb = owesRealWebCapture(worktreeCwd, storyId);
249581
- const owesTerm = owesTerminalCapture(worktreeCwd, storyId);
249582
- if (owesWeb || owesTerm) {
249583
- if (owesWeb && !hasRealWebCapture(worktreeCwd, storyId))
249584
- return false;
249585
- if (owesTerm && !hasRealTerminalCapture(worktreeCwd, storyId))
249586
- return false;
249587
- return true;
249588
- }
249589
- return /<figure class="shot\b|href="screenshots\/|src="screenshots\//i.test(html) || hasMachineCaptureSkip(worktreeCwd, storyId);
249590
- }
249591
- return true;
249613
+ return positiveWithEvidence > 0;
249592
249614
  } catch {
249593
249615
  return false;
249594
249616
  }
@@ -249638,10 +249660,19 @@ function runAttestGate(worktreeCwd, storyId, cycleId, mode, sinceSec, sinks, sco
249638
249660
  return { verdict: "skipped", mode, reasons: reasons2, blocked: blocked2 };
249639
249661
  }
249640
249662
  const fresh = verificationReportFresh(worktreeCwd, storyId, sinceSec);
249641
- if (fresh && verificationReportHasContent(worktreeCwd, storyId)) {
249663
+ if (fresh && verificationReportHasAcceptanceContent(worktreeCwd, storyId)) {
249642
249664
  const score = evaluateReviewScoreGate(scoreRepoCwd, storyId, builderSessionId, cycleId);
249643
249665
  if (score.status === "pass") {
249644
- const visual = passAcVisualFloor(worktreeCwd, storyId);
249666
+ const report = existingReport(worktreeCwd, storyId);
249667
+ const html = report === null ? "" : readFileSync61(report, "utf8");
249668
+ const visual = visualEvidenceFloor(worktreeCwd, storyId, html);
249669
+ if (!visual.ok) {
249670
+ const reasons4 = [visual.reason ?? "visual evidence gate failed"];
249671
+ const blocked3 = mode === "hard";
249672
+ sinks.alert(`attest gate (${mode}): visual evidence gate failed (${storyId}) \u2014 ${reasons4[0]} \u2014 cycle ${cycleId}` + (blocked3 ? " \u2014 BLOCKED (hard mode); story not marked Done" : ""));
249673
+ sinks.event({ cycleId, verdict: "skipped", reasons: reasons4 });
249674
+ return { verdict: "skipped", mode, reasons: reasons4, blocked: blocked3 };
249675
+ }
249645
249676
  const reasons3 = ["fresh acceptance report present", score.reason, ...visual.reason !== void 0 ? [visual.reason] : []];
249646
249677
  sinks.event({ cycleId, verdict: "produced", reasons: reasons3 });
249647
249678
  return { verdict: "produced", mode, reasons: reasons3, blocked: false };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanyao/roll",
3
- "version": "3.624.2",
3
+ "version": "3.624.3",
4
4
  "description": "Roll — Roll out features with AI agents",
5
5
  "packageManager": "pnpm@11.1.3",
6
6
  "scripts": {