forgedock 1.1.6 → 1.1.8
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/README.md +3 -1
- package/bin/engine/invariants.mjs +361 -0
- package/bin/hooks/pre-tool-use.mjs +182 -0
- package/bin/journey.mjs +77 -7
- package/bin/labels.json +10 -0
- package/bin/recall.mjs +540 -0
- package/commands/autopilot.md +959 -78
- package/commands/cleanup.md +52 -3
- package/commands/optimize.md +585 -0
- package/commands/orchestrate/phase-1-resolve.md +35 -25
- package/commands/orchestrate/phase-3-dependency.md +465 -34
- package/commands/orchestrate/phase-4-execution.md +448 -29
- package/commands/orchestrate/phase-6-report.md +198 -9
- package/commands/pipeline-health/phase-2-metrics.md +305 -1
- package/commands/pipeline-health/phase-4-proposals.md +127 -0
- package/commands/quality-gate.md +417 -0
- package/commands/review-pr.md +391 -13
- package/commands/spec-doctor.md +429 -0
- package/commands/validate.md +52 -0
- package/commands/work-on/build/architect.md +77 -0
- package/commands/work-on/build/context.md +357 -22
- package/commands/work-on/build/implement.md +2 -0
- package/commands/work-on/build/validate.md +28 -0
- package/commands/work-on/build.md +44 -0
- package/commands/work-on/close.md +621 -8
- package/commands/work-on/investigate.md +221 -0
- package/commands/work-on.md +15 -14
- package/docs/FORGE-PROTOCOL.md +101 -0
- package/docs/design/awesome-list-submissions.md +105 -0
- package/docs/design/f5bot-monitoring-setup.md +43 -0
- package/docs/design/growth-strategy-2026.md +579 -0
- package/docs/spec/forge-protocol-v1.md +97 -5
- package/docs/spec/label-state-machine.md +6 -1
- package/package.json +2 -2
- package/packages/protocol/src/cli.js +374 -84
- package/packages/protocol/src/types.js +75 -1
- package/scripts/build-knowledge-index.mjs +1761 -0
- package/scripts/calibration.mjs +996 -0
- package/scripts/danger-zones.mjs +782 -0
- package/scripts/doctor-pipeline-state.sh +88 -2
- package/scripts/refresh-bot-token.sh +97 -0
- package/scripts/select-fix-targets.sh +234 -0
- package/scripts/transition-label.sh +157 -6
|
@@ -21,20 +21,122 @@ done
|
|
|
21
21
|
|
|
22
22
|
Aggregate into the batch-level analytics for Step 6B.
|
|
23
23
|
|
|
24
|
-
**Also collect the machine-readable summary cards** (
|
|
25
|
-
|
|
26
|
-
batch cards in Step 6C
|
|
24
|
+
**Also collect the machine-readable summary cards** (embedded in each issue's `FORGE:TRAJECTORY`
|
|
25
|
+
comment by `/work-on` close phase as a `<!-- FORGE:CARD: v1 sha:... b64:... -->` line). These
|
|
26
|
+
power the per-issue and batch cards in Step 6C.
|
|
27
|
+
|
|
28
|
+
**CODEC PATH (forge#1727)**: Cards are now Base64url-encoded. Use the protocol CLI's `parse`
|
|
29
|
+
subcommand to decode them — do NOT use the old `sed -n 's/.*<!-- FORGE:CARD \(.*\) -->.*/\1/p'`
|
|
30
|
+
regex extraction, which only worked for the deprecated inline-JSON form:
|
|
27
31
|
|
|
28
32
|
```bash
|
|
29
33
|
CARDS=""
|
|
30
34
|
for NUM in {all_completed_issue_numbers}; do
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
[ -n "$
|
|
35
|
+
# Fetch the TRAJECTORY comment body for this issue
|
|
36
|
+
TRAJ_BODY=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
|
|
37
|
+
--jq '[.[] | select(.body | contains("FORGE:CARD:"))] | last | .body // ""' 2>/dev/null || true)
|
|
38
|
+
if [ -n "$TRAJ_BODY" ]; then
|
|
39
|
+
# Decode via protocol CLI — handles both Base64url form (forge#1727) and gracefully
|
|
40
|
+
# exits 1 (skips) for pre-migration inline-JSON entries.
|
|
41
|
+
CARD=$(echo "$TRAJ_BODY" | node packages/protocol/src/cli.js parse --type CARD 2>/dev/null || true)
|
|
42
|
+
[ -n "$CARD" ] && CARDS="${CARDS}${CARD}"$'\n'
|
|
43
|
+
fi
|
|
44
|
+
done
|
|
45
|
+
# CARDS is now a newline-delimited list of per-issue JSON objects decoded from FORGE:CARD payloads.
|
|
46
|
+
# Skip any issue whose card is absent — pre-card runs or non-merged terminal states degrade gracefully.
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Step 6A.5: Collect merge-ready PRs (`workflow:awaiting-merge`) <!-- Added: forge#1811 -->
|
|
50
|
+
|
|
51
|
+
**Why a separate collection pass**: `workflow:awaiting-merge` issues are open (their PR hasn't
|
|
52
|
+
merged yet), so they are excluded from `{all_completed_issue_numbers}` above — that set only
|
|
53
|
+
contains issues that reached a closed/merged terminal state. Iterate the full batch issue set
|
|
54
|
+
(`{all_batch_issue_numbers}` — same prose-variable used by the batch-wide loops in
|
|
55
|
+
`phase-4-execution.md`) instead, and filter to the awaiting-merge label.
|
|
56
|
+
|
|
57
|
+
For each awaiting-merge issue, resolve its open PR using the `"Closes #N"`-anchored search
|
|
58
|
+
established by forge#1634/#1646 — **do not** use a bare-number `search "${NUM} in:body"`, which
|
|
59
|
+
can false-positive on changelogs or cross-references and resolve the wrong PR:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
MERGE_READY_PRS=() # each entry: "NUM|PR_NUMBER|TITLE"
|
|
63
|
+
|
|
64
|
+
for NUM in {all_batch_issue_numbers}; do
|
|
65
|
+
IS_AWAITING=$(gh issue view "$NUM" -R {GH_REPO} --json labels \
|
|
66
|
+
--jq '[.labels[].name | select(. == "workflow:awaiting-merge")] | length' 2>/dev/null || echo "0")
|
|
67
|
+
[ "$IS_AWAITING" -gt 0 ] || continue
|
|
68
|
+
|
|
69
|
+
TITLE=$(gh issue view "$NUM" -R {GH_REPO} --json title --jq '.title' 2>/dev/null || echo "")
|
|
70
|
+
|
|
71
|
+
# Anchor on "Closes #N" first (forge#1634/#1646 precedent).
|
|
72
|
+
PR_NUM=$(gh pr list -R {GH_REPO} --state open --search "\"Closes #${NUM}\" in:body" \
|
|
73
|
+
--json number,updatedAt --jq 'sort_by(.updatedAt) | last | .number' 2>/dev/null || echo "")
|
|
74
|
+
if [ -z "$PR_NUM" ]; then
|
|
75
|
+
# Fallback: a PR may close the issue via "Fixes #N"/"Resolves #N" rather than
|
|
76
|
+
# "Closes #N". Use the bare number only to generate candidates, then RE-VALIDATE
|
|
77
|
+
# each candidate's body against an anchored (Closes|Fixes|Resolves) #N word-boundary
|
|
78
|
+
# regex before accepting it — never accept a bare mention (changelog / cross-reference),
|
|
79
|
+
# which could resolve the wrong PR into the one-shot batch-merge command. This mirrors
|
|
80
|
+
# the recover-orphans.md re-validation precedent. <!-- forge#1822 -->
|
|
81
|
+
PR_NUM=$(gh pr list -R {GH_REPO} --state open --search "${NUM} in:body" \
|
|
82
|
+
--json number,updatedAt,body \
|
|
83
|
+
--jq --arg n "${NUM}" '[ .[] | select(.body | test("(?i)(close[sd]?|fix(e[sd])?|resolve[sd]?)\\s+#" + $n + "\\b")) ] | sort_by(.updatedAt) | last | .number // empty' 2>/dev/null || echo "")
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
[ -n "$PR_NUM" ] && MERGE_READY_PRS+=("${NUM}|${PR_NUM}|${TITLE}")
|
|
35
87
|
done
|
|
36
|
-
#
|
|
37
|
-
#
|
|
88
|
+
# MERGE_READY_PRS now holds one entry per merge-ready PR in the batch (empty array if none).
|
|
89
|
+
# Feeds the "### Merge-Ready" report block and the Implementation Results table row (Step 6B).
|
|
90
|
+
|
|
91
|
+
# Pre-build the batch-merge command (rendered verbatim as {MERGE_READY_CMD} in Step 6B —
|
|
92
|
+
# computed here, not inside the report template, so Step 6B stays plain template text with no
|
|
93
|
+
# nested code fences of its own). `gh pr merge` accepts only ONE PR selector per invocation,
|
|
94
|
+
# so chain one `gh pr merge` per PR with && rather than passing a space-separated list (which
|
|
95
|
+
# `gh` rejects). <!-- forge#1838 review: BUG-3 -->
|
|
96
|
+
MERGE_READY_CMD=""
|
|
97
|
+
if [ "${#MERGE_READY_PRS[@]}" -gt 0 ]; then
|
|
98
|
+
for _entry in "${MERGE_READY_PRS[@]}"; do
|
|
99
|
+
_pr=$(printf '%s' "$_entry" | cut -d'|' -f2)
|
|
100
|
+
[ -z "$_pr" ] && continue
|
|
101
|
+
[ -n "$MERGE_READY_CMD" ] && MERGE_READY_CMD="${MERGE_READY_CMD} && "
|
|
102
|
+
MERGE_READY_CMD="${MERGE_READY_CMD}gh pr merge ${_pr} --merge -R {GH_REPO}"
|
|
103
|
+
done
|
|
104
|
+
fi
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Step 6A.6: Collect blocked-on-human-merge dependents (`blocked-on-human-merge`) <!-- Added: forge#1812 -->
|
|
108
|
+
|
|
109
|
+
**Why a separate collection pass**: A `blocked-on-human-merge` issue is a *dependent* of a `GATED`
|
|
110
|
+
predecessor (`needs-human` or `workflow:awaiting-merge` — see `phase-4-execution.md` Step 4B's
|
|
111
|
+
Predecessor Classification), not itself blocked or failed. It has no PR of its own yet — it hasn't
|
|
112
|
+
even been dispatched. It is excluded from both `{all_completed_issue_numbers}` (never ran) and
|
|
113
|
+
`MERGE_READY_PRS` (Step 6A.5, which only covers issues that themselves reached `workflow:awaiting-merge`).
|
|
114
|
+
Reporting it as `⏭ Skipped (dep)` would be wrong — that label means the predecessor `FAILED` and the
|
|
115
|
+
work is abandoned; this state means the work is queued and will auto-dispatch on merge, no manual
|
|
116
|
+
re-run required.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
BLOCKED_ON_MERGE=() # each entry: "NUM|GATING_PRED|GATING_PR|TITLE"
|
|
120
|
+
|
|
121
|
+
for NUM in {all_batch_issue_numbers}; do
|
|
122
|
+
IS_BLOCKED=$(gh issue view "$NUM" -R {GH_REPO} --json labels \
|
|
123
|
+
--jq '[.labels[].name | select(. == "blocked-on-human-merge")] | length' 2>/dev/null || echo "0")
|
|
124
|
+
[ "$IS_BLOCKED" -gt 0 ] || continue
|
|
125
|
+
|
|
126
|
+
TITLE=$(gh issue view "$NUM" -R {GH_REPO} --json title --jq '.title' 2>/dev/null || echo "")
|
|
127
|
+
GATING_PRED=$(gh api repos/{GH_REPO}/issues/${NUM}/comments \
|
|
128
|
+
--jq '[.[] | select(.body | contains("FORGE:BLOCKED_ON_HUMAN_MERGE"))] | last | (.body | capture("Gating predecessor\\*\\*: #(?<p>[0-9]+)").p) // ""' 2>/dev/null || echo "")
|
|
129
|
+
GATING_PR=""
|
|
130
|
+
if [ -n "$GATING_PRED" ]; then
|
|
131
|
+
GATING_PR=$(gh pr list -R {GH_REPO} --state open --search "\"Closes #${GATING_PRED}\" in:body" \
|
|
132
|
+
--json number --jq '.[0].number // empty' 2>/dev/null || echo "")
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
BLOCKED_ON_MERGE+=("${NUM}|${GATING_PRED}|${GATING_PR}|${TITLE}")
|
|
136
|
+
done
|
|
137
|
+
# BLOCKED_ON_MERGE now holds one entry per dependent queued behind a human-gated predecessor
|
|
138
|
+
# (empty array if none). Feeds the "Blocked-on-Merge" report block and the Implementation
|
|
139
|
+
# Results table row (Step 6B).
|
|
38
140
|
```
|
|
39
141
|
|
|
40
142
|
### Step 6B: Present consolidated report
|
|
@@ -61,7 +163,62 @@ done
|
|
|
61
163
|
| #{N1} | {title} | from #{INV1} | ✓ Merged | #{PR} | staging |
|
|
62
164
|
| #{C} | {title} | original | ✗ Invalid | — | — |
|
|
63
165
|
| #{D} | {title} | original | ⚠ Blocked | — | — |
|
|
166
|
+
| #{F} | {title} | original | ⏸ Awaiting Merge | #{PR} | staging |
|
|
64
167
|
| #{E} | {title} | original | ⏭ Skipped (dep) | — | — |
|
|
168
|
+
| #{G} | {title} | original | 🔗 Blocked-on-Merge (gated by #{PRED}) | — | — |
|
|
169
|
+
|
|
170
|
+
`⏸ Awaiting Merge` (`workflow:awaiting-merge`) is structurally distinct from `⚠ Blocked`
|
|
171
|
+
(`needs-human`): a Blocked row means the pipeline hit something it cannot resolve on its own
|
|
172
|
+
and a human must diagnose it; an Awaiting Merge row means the PR was already remediated and
|
|
173
|
+
re-reviewed to a clean APPROVED verdict — a human only needs to click merge. Never render an
|
|
174
|
+
awaiting-merge PR as `⚠ Blocked`.
|
|
175
|
+
|
|
176
|
+
`🔗 Blocked-on-Merge` (`blocked-on-human-merge`, forge#1812) is distinct from all three of the
|
|
177
|
+
above: it is a *dependent* of a GATED predecessor (one currently `⚠ Blocked` or `⏸ Awaiting
|
|
178
|
+
Merge`) — the dependent issue itself was never dispatched and has no problem of its own. It is
|
|
179
|
+
also distinct from `⏭ Skipped (dep)`, which means the predecessor `FAILED` outright and the work
|
|
180
|
+
was abandoned. A Blocked-on-Merge row means the work is queued and will auto-dispatch the instant
|
|
181
|
+
the gating predecessor's PR merges — no manual `/orchestrate` re-run required. Never render a
|
|
182
|
+
blocked-on-human-merge dependent as `⏭ Skipped (dep)`.
|
|
183
|
+
|
|
184
|
+
{IF `MERGE_READY_PRS` (Step 6A.5) is non-empty:}
|
|
185
|
+
|
|
186
|
+
### Merge-Ready — {N} PR(s) awaiting only a human merge
|
|
187
|
+
|
|
188
|
+
These PRs were remediated and re-reviewed to a clean `APPROVED` verdict after an earlier
|
|
189
|
+
`needs-human` escalation. They do not need further diagnosis — merge them to land the batch.
|
|
190
|
+
|
|
191
|
+
| # | Issue | PR | Title |
|
|
192
|
+
|---|-------|----|----|
|
|
193
|
+
{one row per `MERGE_READY_PRS` entry: | {n} | #{NUM} | #{PR} | {title} |}
|
|
194
|
+
|
|
195
|
+
**One action to land all {N}:** `{MERGE_READY_CMD}`
|
|
196
|
+
|
|
197
|
+
(`{MERGE_READY_CMD}` is pre-built in Step 6A.5 by chaining one `gh pr merge <pr> --merge` per PR
|
|
198
|
+
in `MERGE_READY_PRS` with `&&` — `gh pr merge` takes a single PR selector, so a space-separated
|
|
199
|
+
list would be rejected. Render it verbatim here as plain text, no code fence.)
|
|
200
|
+
|
|
201
|
+
{ELSE: omit this section entirely — do not print an empty "Merge-Ready" heading.}
|
|
202
|
+
|
|
203
|
+
{IF `BLOCKED_ON_MERGE` (Step 6A.6) is non-empty:}
|
|
204
|
+
|
|
205
|
+
### Blocked-on-Merge — {N} issue(s) queued behind a human-gated predecessor
|
|
206
|
+
|
|
207
|
+
These issues are dependents of a predecessor that is currently `⚠ Blocked` or `⏸ Awaiting Merge`.
|
|
208
|
+
They were never dispatched — no work has started on them — and they need no action right now.
|
|
209
|
+
Each will auto-dispatch the instant its gating predecessor's PR merges (live-session wake via
|
|
210
|
+
`phase-4-execution.md` Step 4B item 6.6, or next-`/orchestrate`-invocation wake via
|
|
211
|
+
`phase-3-dependency.md`'s wake/compaction reconstruction). This batch is a **paused drain**, not
|
|
212
|
+
a complete one — re-running `/orchestrate` after merging the gating PR(s) below will pick these up.
|
|
213
|
+
|
|
214
|
+
| # | Issue | Gated By | Gating PR | Title |
|
|
215
|
+
|---|-------|----------|-----------|-------|
|
|
216
|
+
{one row per `BLOCKED_ON_MERGE` entry: | {n} | #{NUM} | #{GATING_PRED} | #{GATING_PR} | {title} |}
|
|
217
|
+
|
|
218
|
+
**To unblock**: merge the gating PR(s) referenced above, then re-run `/orchestrate` (or let the
|
|
219
|
+
current session's live wake pick them up automatically if it is still running).
|
|
220
|
+
|
|
221
|
+
{ELSE: omit this section entirely — do not print an empty "Blocked-on-Merge" heading.}
|
|
65
222
|
|
|
66
223
|
### Review-Spawned Issues
|
|
67
224
|
|
|
@@ -91,6 +248,8 @@ done
|
|
|
91
248
|
- **Review findings**: {N} spawned, {M} resolved in-batch, {K} swept, {J} deferred (cosmetic), {L} deferred (gen2)
|
|
92
249
|
- **Succeeded**: {N} issues resolved (implementation + sweep)
|
|
93
250
|
- **Failed**: {N} issues need attention
|
|
251
|
+
- **Merge-ready**: {#MERGE_READY_PRS[@]:-0} PRs awaiting only a human merge (see "Merge-Ready" section above)
|
|
252
|
+
- **Blocked-on-merge**: {#BLOCKED_ON_MERGE[@]:-0} issues queued behind a human-gated predecessor, will auto-dispatch on merge (see "Blocked-on-Merge" section above)
|
|
94
253
|
- **Skipped**: {N} issues (dependency failures)
|
|
95
254
|
|
|
96
255
|
### Post-Batch Cleanup
|
|
@@ -124,6 +283,35 @@ done
|
|
|
124
283
|
| Findings validated | {N} |
|
|
125
284
|
| False positives | {N} ({%}) |
|
|
126
285
|
| Anomalies flagged | {N} |
|
|
286
|
+
| Budget limit | ${BUDGET_LIMIT:-uncapped} |
|
|
287
|
+
| Projected spend (dispatched issues) | ${PROJECTED_SPEND:-—} |
|
|
288
|
+
| Actual spend (best-effort telemetry) | ${ACTUAL_SPEND:-—} |
|
|
289
|
+
| Issues deferred (budget ceiling) | {#DEFERRED_BUDGET_ISSUES[@]:-0} |
|
|
290
|
+
| ε-reserve issued (no-prior dispatch) | {EPSILON_DISPATCHED:-no} |
|
|
291
|
+
| **Value-weighted throughput** | **{VALUE_THROUGHPUT:-—} value-pts/USD** |
|
|
292
|
+
|
|
293
|
+
`value-weighted throughput` = Σ(priority_weight × danger_zone_weight) for **merged** issues ÷ actual spend (USD). A higher value means more high-priority issues were resolved per dollar. Trend this across runs via `/pipeline-health` to measure the economic scheduling ROI. <!-- Added: forge#1743 -->
|
|
294
|
+
|
|
295
|
+
**Compute value-weighted throughput** (populate before rendering the table):
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# Collect value scores for merged issues (ISSUE_VALUE[] populated in Step 3E.5)
|
|
299
|
+
MERGED_VALUE_SUM="0"
|
|
300
|
+
for NUM in {all_completed_issue_numbers}; do
|
|
301
|
+
IS_MERGED=$(gh issue view "$NUM" -R {GH_REPO} --json labels \
|
|
302
|
+
--jq '[.labels[].name | select(. == "workflow:merged")] | length' 2>/dev/null || echo "0")
|
|
303
|
+
if [ "$IS_MERGED" -gt 0 ] && [ -n "${ISSUE_VALUE[$NUM]:-}" ]; then
|
|
304
|
+
MERGED_VALUE_SUM=$(echo "scale=4; $MERGED_VALUE_SUM + ${ISSUE_VALUE[$NUM]}" | bc 2>/dev/null \
|
|
305
|
+
|| echo "$MERGED_VALUE_SUM")
|
|
306
|
+
fi
|
|
307
|
+
done
|
|
308
|
+
|
|
309
|
+
if [ "${ACTUAL_SPEND:-0}" != "0" ] && echo "${ACTUAL_SPEND:-0}" | grep -qP '^\d+(\.\d+)?$'; then
|
|
310
|
+
VALUE_THROUGHPUT=$(echo "scale=2; $MERGED_VALUE_SUM / $ACTUAL_SPEND" | bc 2>/dev/null || echo "—")
|
|
311
|
+
else
|
|
312
|
+
VALUE_THROUGHPUT="—" # no spend telemetry available
|
|
313
|
+
fi
|
|
314
|
+
```
|
|
127
315
|
|
|
128
316
|
**Domain breakdown**: {N} scraping, {N} frontend, {N} billing, ...
|
|
129
317
|
**Routing**: {N} fast-lane, {N} feature-lane
|
|
@@ -137,6 +325,7 @@ done
|
|
|
137
325
|
- Contract divergences > 20% → investigation quality may need improvement
|
|
138
326
|
- **Idle% > 50%** → agents stalling at phase boundaries; check work-on routing loop
|
|
139
327
|
- **Avg resumes > 1** → orchestrator having to compensate for agent stops
|
|
328
|
+
- **Value-weighted throughput trending down** → high-value issues deferred or blocked; check Step 3E.5 score distribution
|
|
140
329
|
|
|
141
330
|
### Next Steps
|
|
142
331
|
{If milestone and all issues done: "Milestone ready to ship. Run `/milestone ship {slug}` when ready. The ship command includes a pre-merge hunk-loss audit (Step 2.5) that detects staging-only hunks in milestone-modified files and rebases the milestone branch to absorb them before creating the PR — protecting against squash-merge regressions."}
|
|
@@ -45,7 +45,70 @@ gh issue list -R $REPO --state closed --label "review-finding" --json number,cre
|
|
|
45
45
|
--jq "[.[] | select(.createdAt > \"$SINCE\")] | length"
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
**False positive rate** = invalid / total closed. Target: < 10%.
|
|
48
|
+
**False positive rate (proxy)** = invalid / total closed. Target: < 10%.
|
|
49
|
+
|
|
50
|
+
**Note**: This proxy uses `workflow:invalid` as a stand-in for false-positive. Phase 2C.5 reports the actual false-positive rate from the `false-positive` label. Both metrics should be reported; they should converge as validation coverage increases.
|
|
51
|
+
|
|
52
|
+
### 2C.5: Validation coverage (finding lifecycle health) <!-- Added: forge#1730 -->
|
|
53
|
+
|
|
54
|
+
Measures whether the `needs-validation → validated/false-positive` lifecycle is operating. This is the primary health signal for the review system's calibration corpus.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# All finding-issue types: review-finding, staging-review, audit-finding
|
|
58
|
+
FINDING_LABELS="review-finding staging-review audit-finding"
|
|
59
|
+
|
|
60
|
+
TOTAL_FINDINGS=0
|
|
61
|
+
TOTAL_VALIDATED=0
|
|
62
|
+
TOTAL_FALSE_POSITIVE=0
|
|
63
|
+
TOTAL_NEEDS_VALIDATION=0
|
|
64
|
+
|
|
65
|
+
for LABEL in $FINDING_LABELS; do
|
|
66
|
+
COUNT=$(gh issue list -R $REPO --state all --label "$LABEL" --limit 1000 --json number \
|
|
67
|
+
--jq 'length' 2>/dev/null || echo 0)
|
|
68
|
+
TOTAL_FINDINGS=$((TOTAL_FINDINGS + COUNT))
|
|
69
|
+
|
|
70
|
+
VAL=$(gh issue list -R $REPO --state all --label "$LABEL,validated" --limit 1000 --json number \
|
|
71
|
+
--jq 'length' 2>/dev/null || echo 0)
|
|
72
|
+
TOTAL_VALIDATED=$((TOTAL_VALIDATED + VAL))
|
|
73
|
+
|
|
74
|
+
FP=$(gh issue list -R $REPO --state all --label "$LABEL,false-positive" --limit 1000 --json number \
|
|
75
|
+
--jq 'length' 2>/dev/null || echo 0)
|
|
76
|
+
TOTAL_FALSE_POSITIVE=$((TOTAL_FALSE_POSITIVE + FP))
|
|
77
|
+
|
|
78
|
+
NV=$(gh issue list -R $REPO --state all --label "$LABEL,needs-validation" --limit 1000 --json number \
|
|
79
|
+
--jq 'length' 2>/dev/null || echo 0)
|
|
80
|
+
TOTAL_NEEDS_VALIDATION=$((TOTAL_NEEDS_VALIDATION + NV))
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
TOTAL_RESOLVED=$((TOTAL_VALIDATED + TOTAL_FALSE_POSITIVE))
|
|
84
|
+
if [ "$TOTAL_FINDINGS" -gt 0 ]; then
|
|
85
|
+
VALIDATION_COVERAGE_PCT=$(echo "scale=1; $TOTAL_RESOLVED * 100 / $TOTAL_FINDINGS" | bc 2>/dev/null || echo "N/A")
|
|
86
|
+
if [ "$TOTAL_RESOLVED" -gt 0 ]; then
|
|
87
|
+
FALSE_POSITIVE_RATE_PCT=$(echo "scale=1; $TOTAL_FALSE_POSITIVE * 100 / $TOTAL_RESOLVED" | bc 2>/dev/null || echo "N/A")
|
|
88
|
+
else
|
|
89
|
+
FALSE_POSITIVE_RATE_PCT="N/A (no resolved findings)"
|
|
90
|
+
fi
|
|
91
|
+
else
|
|
92
|
+
VALIDATION_COVERAGE_PCT="N/A"
|
|
93
|
+
FALSE_POSITIVE_RATE_PCT="N/A"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
echo "=== Validation Coverage (2C.5) ==="
|
|
97
|
+
echo "Total finding-issues: $TOTAL_FINDINGS (review-finding + staging-review + audit-finding)"
|
|
98
|
+
echo " validated: $TOTAL_VALIDATED"
|
|
99
|
+
echo " false-positive: $TOTAL_FALSE_POSITIVE"
|
|
100
|
+
echo " resolved (total): $TOTAL_RESOLVED"
|
|
101
|
+
echo " needs-validation (open): $TOTAL_NEEDS_VALIDATION"
|
|
102
|
+
echo "Validation coverage: ${VALIDATION_COVERAGE_PCT}% (target: ≥ 95% for newly closed findings)"
|
|
103
|
+
echo "False-positive rate: ${FALSE_POSITIVE_RATE_PCT}% of resolved findings (actual label-based rate)"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Metrics to compute**:
|
|
107
|
+
- **Validation coverage**: (validated + false-positive) / total finding-issues. Target: ≥ 95% for newly closed findings.
|
|
108
|
+
- **False-positive rate (actual)**: false-positive / (validated + false-positive). Reports true negative rate, not the `workflow:invalid` proxy from 2C.
|
|
109
|
+
- **Unresolved finding count**: Issues still holding `needs-validation` — actionable target for `/cleanup labels` backfill.
|
|
110
|
+
|
|
111
|
+
**Why this matters**: The `needs-validation` label was previously a dead end — applied at creation, never resolved. With the lifecycle wired (#1730), these metrics become the primary signal for review system calibration quality. Validation coverage < 95% indicates pipeline runs are not completing the verdict transition; false-positive rate is now a reportable number instead of undefined.
|
|
49
112
|
|
|
50
113
|
### 2D: Build failure rate
|
|
51
114
|
|
|
@@ -494,6 +557,136 @@ gh issue list -R $FORGE_REPO --state all --label "audit-finding" \
|
|
|
494
557
|
- **Failure category distribution**: breakdown by failure point (INVESTIGATION, IMPLEMENTATION, REVIEW, REVIEW_FALSE_NEG, DEPLOY_GATE, etc.). No target — use for diagnosis. High IMPLEMENTATION counts indicate builder gaps; high REVIEW_FALSE_NEG counts indicate review agent gaps.
|
|
495
558
|
- **Mean time to detect (MTD)**: average days from PR merge to audit-finding creation. Target: < 7 days. Long MTD means defects linger undetected in production.
|
|
496
559
|
|
|
560
|
+
### 2M.5: Review intensity distribution and escaped-defect rate by tier <!-- Added: forge#1745 -->
|
|
561
|
+
|
|
562
|
+
This metric is only meaningful once `scripts/calibration.mjs --provenance` has been publishing to the `forge-knowledge` branch for at least one 30-day shadow-mode window. Before that point, all PRs have `INTENSITY_TIER=SHADOW` in their FORGE:REVIEW annotations and both tier buckets will be empty — this is expected and correct.
|
|
563
|
+
|
|
564
|
+
**Why this matters**: Review spend should track risk. This metric verifies that the trust escalation system is working as intended: proven cells should have lower escaped-defect rates than novel cells, and overall review token cost per PR should trend down while escaped-defect rate stays flat or falls.
|
|
565
|
+
|
|
566
|
+
**Success metric**: Review token cost per PR trending down (fewer agents on proven PRs) while escaped-defect rate (14-day post-merge review-findings) stays flat or falls; zero escapes attributable to a dropped agent that the full panel would have run.
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
# Collect FORGE:REVIEW annotation INTENSITY_TIER fields from merged PRs in the analysis window.
|
|
570
|
+
# Source: the APPROVED:/CHANGES REQUESTED: PR comment from review-pr.md Phase 7B.
|
|
571
|
+
# Field format (set since forge#1745): "**Intensity tier**: PROVEN | **Cell**: `...` | **Shadow mode**: false"
|
|
572
|
+
#
|
|
573
|
+
# This relies on MERGED_PRS (list of PR numbers) being available from Phase 2E.
|
|
574
|
+
# If not available, fall back to gh pr list.
|
|
575
|
+
if [ -z "${MERGED_PRS:-}" ]; then
|
|
576
|
+
MERGED_PRS=$(gh pr list -R "$FORGE_REPO" --state merged --limit 200 \
|
|
577
|
+
--json number,mergedAt \
|
|
578
|
+
--jq "[.[] | select(.mergedAt > \"$SINCE\")] | .[].number" 2>/dev/null || echo "")
|
|
579
|
+
fi
|
|
580
|
+
|
|
581
|
+
# Accumulators per intensity tier
|
|
582
|
+
TIER_NOVEL_COUNT=0
|
|
583
|
+
TIER_PROVEN_COUNT=0
|
|
584
|
+
TIER_SHADOW_COUNT=0
|
|
585
|
+
TIER_NOVEL_NEEDS_HUMAN_COUNT=0
|
|
586
|
+
TIER_NOVEL_ESCAPES=0
|
|
587
|
+
TIER_PROVEN_ESCAPES=0
|
|
588
|
+
|
|
589
|
+
# Also track escaped defect counts per tier using the audit-finding ledger from 2L.
|
|
590
|
+
# AUDIT_FINDINGS must be set from Phase 2L (contains audit-finding issues in window).
|
|
591
|
+
AUDIT_FINDINGS_AVAILABLE="${AUDIT_FINDINGS:+true}"
|
|
592
|
+
|
|
593
|
+
while IFS= read -r PR_NUM; do
|
|
594
|
+
[ -z "$PR_NUM" ] && continue
|
|
595
|
+
|
|
596
|
+
# Read the FORGE:REVIEW comment from this PR to extract INTENSITY_TIER
|
|
597
|
+
REVIEW_COMMENT=$(gh api "repos/${FORGE_REPO}/issues/${PR_NUM}/comments" \
|
|
598
|
+
--jq '[.[] | select(
|
|
599
|
+
(.body | test("APPROVED:|CHANGES REQUESTED:"; "i")) and
|
|
600
|
+
(.body | contains("Intensity tier"))
|
|
601
|
+
)] | last | .body // ""' 2>/dev/null || echo "")
|
|
602
|
+
|
|
603
|
+
if [ -z "$REVIEW_COMMENT" ]; then
|
|
604
|
+
TIER_SHADOW_COUNT=$((TIER_SHADOW_COUNT + 1))
|
|
605
|
+
continue
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
PR_TIER=$(echo "$REVIEW_COMMENT" | grep -oP '(?<=\*\*Intensity tier\*\*: )[A-Z_]+' | head -1 || echo "SHADOW")
|
|
609
|
+
case "$PR_TIER" in
|
|
610
|
+
PROVEN) TIER_PROVEN_COUNT=$((TIER_PROVEN_COUNT + 1)) ;;
|
|
611
|
+
NOVEL_NEEDS_HUMAN) TIER_NOVEL_NEEDS_HUMAN_COUNT=$((TIER_NOVEL_NEEDS_HUMAN_COUNT + 1))
|
|
612
|
+
TIER_NOVEL_COUNT=$((TIER_NOVEL_COUNT + 1)) ;;
|
|
613
|
+
NOVEL) TIER_NOVEL_COUNT=$((TIER_NOVEL_COUNT + 1)) ;;
|
|
614
|
+
*) TIER_SHADOW_COUNT=$((TIER_SHADOW_COUNT + 1)) ;;
|
|
615
|
+
esac
|
|
616
|
+
|
|
617
|
+
# Cross-reference escaped defects for this PR's files (from 2L AUDIT_FINDINGS)
|
|
618
|
+
# A REVIEW_FALSE_NEG audit-finding referencing PR #{PR_NUM} counts as an escape for this tier.
|
|
619
|
+
if [ "$AUDIT_FINDINGS_AVAILABLE" = "true" ]; then
|
|
620
|
+
ESCAPE_COUNT=$(echo "$AUDIT_FINDINGS" | \
|
|
621
|
+
jq --argjson pr "$PR_NUM" '[.[] | select(
|
|
622
|
+
(.body | test("\\*\\*Failure point\\*\\*:.*REVIEW_FALSE_NEG")) and
|
|
623
|
+
(.body | test("#" + ($pr | tostring)))
|
|
624
|
+
)] | length' 2>/dev/null || echo "0")
|
|
625
|
+
|
|
626
|
+
if [ "${ESCAPE_COUNT:-0}" -gt 0 ]; then
|
|
627
|
+
case "$PR_TIER" in
|
|
628
|
+
PROVEN) TIER_PROVEN_ESCAPES=$((TIER_PROVEN_ESCAPES + ESCAPE_COUNT)) ;;
|
|
629
|
+
NOVEL|NOVEL_NEEDS_HUMAN) TIER_NOVEL_ESCAPES=$((TIER_NOVEL_ESCAPES + ESCAPE_COUNT)) ;;
|
|
630
|
+
esac
|
|
631
|
+
fi
|
|
632
|
+
fi
|
|
633
|
+
done <<< "$MERGED_PRS"
|
|
634
|
+
|
|
635
|
+
TIER_TOTAL=$((TIER_NOVEL_COUNT + TIER_PROVEN_COUNT + TIER_SHADOW_COUNT + TIER_NOVEL_NEEDS_HUMAN_COUNT))
|
|
636
|
+
|
|
637
|
+
echo ""
|
|
638
|
+
echo "=== Review Intensity Distribution (forge#1745 — provenance trust) ==="
|
|
639
|
+
echo "Window: ${SINCE} → now | Total PRs: ${TIER_TOTAL}"
|
|
640
|
+
echo ""
|
|
641
|
+
echo "Tier distribution:"
|
|
642
|
+
printf " %-22s %4d (%s%%)\n" "PROVEN (de-escalated):" "$TIER_PROVEN_COUNT" \
|
|
643
|
+
"$([ "$TIER_TOTAL" -gt 0 ] && echo "scale=0; $TIER_PROVEN_COUNT * 100 / $TIER_TOTAL" | bc || echo "0")"
|
|
644
|
+
printf " %-22s %4d (%s%%)\n" "NOVEL (full panel):" "$TIER_NOVEL_COUNT" \
|
|
645
|
+
"$([ "$TIER_TOTAL" -gt 0 ] && echo "scale=0; $TIER_NOVEL_COUNT * 100 / $TIER_TOTAL" | bc || echo "0")"
|
|
646
|
+
printf " %-22s %4d (%s%%)\n" "SHADOW (no action yet):" "$TIER_SHADOW_COUNT" \
|
|
647
|
+
"$([ "$TIER_TOTAL" -gt 0 ] && echo "scale=0; $TIER_SHADOW_COUNT * 100 / $TIER_TOTAL" | bc || echo "0")"
|
|
648
|
+
|
|
649
|
+
echo ""
|
|
650
|
+
echo "Escaped-defect rate by tier (REVIEW_FALSE_NEG audit-findings):"
|
|
651
|
+
if [ "$TIER_PROVEN_COUNT" -gt 0 ]; then
|
|
652
|
+
PROVEN_ESCAPE_RATE=$(echo "scale=1; $TIER_PROVEN_ESCAPES * 100 / $TIER_PROVEN_COUNT" | bc 2>/dev/null || echo "N/A")
|
|
653
|
+
echo " PROVEN: ${TIER_PROVEN_ESCAPES} escapes / ${TIER_PROVEN_COUNT} PRs = ${PROVEN_ESCAPE_RATE}% (target: ≤ review-wide escape rate)"
|
|
654
|
+
else
|
|
655
|
+
echo " PROVEN: no PRs in this tier yet"
|
|
656
|
+
fi
|
|
657
|
+
if [ "$TIER_NOVEL_COUNT" -gt 0 ]; then
|
|
658
|
+
NOVEL_ESCAPE_RATE=$(echo "scale=1; $TIER_NOVEL_ESCAPES * 100 / $TIER_NOVEL_COUNT" | bc 2>/dev/null || echo "N/A")
|
|
659
|
+
echo " NOVEL: ${TIER_NOVEL_ESCAPES} escapes / ${TIER_NOVEL_COUNT} PRs = ${NOVEL_ESCAPE_RATE}%"
|
|
660
|
+
else
|
|
661
|
+
echo " NOVEL: no PRs in this tier yet"
|
|
662
|
+
fi
|
|
663
|
+
|
|
664
|
+
# Health signal: if PROVEN escape rate > NOVEL escape rate, the de-escalation is leaking.
|
|
665
|
+
# This should never happen in steady state — flag it if it does.
|
|
666
|
+
if [ "$TIER_PROVEN_COUNT" -gt 0 ] && [ "$TIER_NOVEL_COUNT" -gt 0 ]; then
|
|
667
|
+
PROVEN_ESC_CMP=$(echo "scale=3; $TIER_PROVEN_ESCAPES / $TIER_PROVEN_COUNT" | bc 2>/dev/null || echo "0")
|
|
668
|
+
NOVEL_ESC_CMP=$(echo "scale=3; $TIER_NOVEL_ESCAPES / $TIER_NOVEL_COUNT" | bc 2>/dev/null || echo "0")
|
|
669
|
+
if awk "BEGIN{exit !($PROVEN_ESC_CMP > $NOVEL_ESC_CMP)}" 2>/dev/null; then
|
|
670
|
+
echo ""
|
|
671
|
+
echo "⚠ WARNING: PROVEN tier escape rate (${PROVEN_ESC_CMP}) exceeds NOVEL tier (${NOVEL_ESC_CMP})."
|
|
672
|
+
echo " This indicates that de-escalation is suppressing agents that would have caught defects."
|
|
673
|
+
echo " Investigate: run /pipeline-health --detail and check which cells are PROVEN with recent escapes."
|
|
674
|
+
echo " Consider resetting affected provenance cells or raising the PROVEN threshold."
|
|
675
|
+
fi
|
|
676
|
+
fi
|
|
677
|
+
|
|
678
|
+
# Export for downstream phases (phase-3-analyze.md health score)
|
|
679
|
+
export TIER_NOVEL_COUNT TIER_PROVEN_COUNT TIER_SHADOW_COUNT
|
|
680
|
+
export TIER_NOVEL_ESCAPES TIER_PROVEN_ESCAPES
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**Metrics to report in Phase 5 health summary**:
|
|
684
|
+
- **Intensity distribution**: PROVEN / NOVEL / SHADOW counts and percentages over the analysis window
|
|
685
|
+
- **Escaped-defect rate by tier**: REVIEW_FALSE_NEG audit-finding rate per tier (target: PROVEN ≤ overall rate, NOVEL ≥ overall rate before de-escalation takes effect, PROVEN never exceeds NOVEL)
|
|
686
|
+
- **Shadow mode PRs**: count of PRs where intensity tier was computed but not acted on (should decrease after 30-day window)
|
|
687
|
+
|
|
688
|
+
**Note**: If `TRUST_SHADOW_MODE=true` is still set in forge.yaml, all PRs will appear as SHADOW tier in this metric. This is correct — shadow mode has not yet been enforced. After setting `trust_shadow_mode: false`, tiers will populate.
|
|
689
|
+
|
|
497
690
|
### 2M: Session discovery and date-gated filtering (conversation transcript analytics)
|
|
498
691
|
|
|
499
692
|
This phase locates conversation JSONL files for pipeline command sessions (work-on, orchestrate, review-pr, quality-gate, review-pr-staging) that ran within the analysis window. It produces `SESSION_JSONL_FILES` — consumed by downstream Conversation Transcript Analytics phases to extract flow metrics (phase durations, tool patterns, subagent efficiency, stalls, rate-limits).
|
|
@@ -1727,3 +1920,114 @@ fi
|
|
|
1727
1920
|
|
|
1728
1921
|
---
|
|
1729
1922
|
|
|
1923
|
+
### 2R: Confidence Calibration Table (from run outcomes)
|
|
1924
|
+
|
|
1925
|
+
**Purpose**: Surfaces whether the pipeline's stated confidence levels predict actual outcomes. A HIGH-confidence issue that frequently triggers review-findings or reverts within 14 days indicates systematic overconfidence — the investigator's confidence is not tracking reality. Conversely, needs-human routing on issues that almost always survive indicates overcaution. This section reads the calibration table produced by `scripts/calibration.mjs` and published to the `forge-knowledge` branch. <!-- Added: forge#1741 -->
|
|
1926
|
+
|
|
1927
|
+
**Skip if**: forge-knowledge branch is absent or `calibration/table.json` does not exist on it — emit the unavailability note below and continue.
|
|
1928
|
+
|
|
1929
|
+
**Read calibration table**:
|
|
1930
|
+
```bash
|
|
1931
|
+
# Read calibration table from forge-knowledge branch (fail-safe: any error → skip)
|
|
1932
|
+
CALIBRATION_TABLE=""
|
|
1933
|
+
CALIBRATION_AVAILABLE=false
|
|
1934
|
+
|
|
1935
|
+
if git show-ref --quiet "refs/remotes/origin/${FORGE_KNOWLEDGE_BRANCH:-forge-knowledge}" 2>/dev/null || \
|
|
1936
|
+
git ls-remote --exit-code origin "${FORGE_KNOWLEDGE_BRANCH:-forge-knowledge}" >/dev/null 2>&1; then
|
|
1937
|
+
RAW_TABLE=$(git show "origin/${FORGE_KNOWLEDGE_BRANCH:-forge-knowledge}:calibration/table.json" 2>/dev/null || echo '')
|
|
1938
|
+
if [ -n "$RAW_TABLE" ]; then
|
|
1939
|
+
CALIBRATION_TABLE="$RAW_TABLE"
|
|
1940
|
+
CALIBRATION_AVAILABLE=true
|
|
1941
|
+
CALIB_COMPUTED_AT=$(echo "$CALIBRATION_TABLE" | jq -r '.computedAt // "unknown"' 2>/dev/null || echo 'unknown')
|
|
1942
|
+
CALIB_TOTAL_RUNS=$(echo "$CALIBRATION_TABLE" | jq -r '.totalRuns // 0' 2>/dev/null || echo '0')
|
|
1943
|
+
CALIB_WINDOW_DAYS=$(echo "$CALIBRATION_TABLE" | jq -r '.windowDays // 14' 2>/dev/null || echo '14')
|
|
1944
|
+
CALIB_MIN_SAMPLES=$(echo "$CALIBRATION_TABLE" | jq -r '.minSamples // 10' 2>/dev/null || echo '10')
|
|
1945
|
+
echo "Calibration table loaded: ${CALIB_TOTAL_RUNS} runs, computed ${CALIB_COMPUTED_AT}"
|
|
1946
|
+
fi
|
|
1947
|
+
fi
|
|
1948
|
+
|
|
1949
|
+
if [ "$CALIBRATION_AVAILABLE" = false ]; then
|
|
1950
|
+
echo "Phase 2R: Calibration table unavailable (forge-knowledge:calibration/table.json not found)"
|
|
1951
|
+
echo " To compute: node scripts/calibration.mjs --repo ${REPO} --publish"
|
|
1952
|
+
# Store unavailability for Phase 5A report
|
|
1953
|
+
CALIBRATION_AVAILABILITY_NOTE="⚠ Calibration table unavailable — run: node scripts/calibration.mjs --repo ${REPO} --publish"
|
|
1954
|
+
fi
|
|
1955
|
+
```
|
|
1956
|
+
|
|
1957
|
+
**Render calibration table** (when available):
|
|
1958
|
+
|
|
1959
|
+
```bash
|
|
1960
|
+
if [ "$CALIBRATION_AVAILABLE" = true ]; then
|
|
1961
|
+
echo "## Confidence Calibration Table"
|
|
1962
|
+
echo "**Source**: forge-knowledge:calibration/table.json"
|
|
1963
|
+
echo "**Computed**: ${CALIB_COMPUTED_AT}"
|
|
1964
|
+
echo "**Outcome window**: ${CALIB_WINDOW_DAYS} days | **Min samples for trust**: ${CALIB_MIN_SAMPLES}"
|
|
1965
|
+
echo "**Total runs in corpus**: ${CALIB_TOTAL_RUNS}"
|
|
1966
|
+
echo ""
|
|
1967
|
+
|
|
1968
|
+
# Extract and render per-cell rows; flag overconfidence and overcaution cells
|
|
1969
|
+
OVERCONFIDENCE_CELLS=""
|
|
1970
|
+
OVERCAUTION_CELLS=""
|
|
1971
|
+
|
|
1972
|
+
echo "$CALIBRATION_TABLE" | jq -r '
|
|
1973
|
+
.rows[] |
|
|
1974
|
+
[
|
|
1975
|
+
.taskType,
|
|
1976
|
+
.confidence,
|
|
1977
|
+
(if .trusted then (.survivalRate * 100 | floor | tostring) + "%" else "—(N<" + (.sampleCount | tostring) + ")" end),
|
|
1978
|
+
(.sampleCount | tostring),
|
|
1979
|
+
(if .trusted then
|
|
1980
|
+
(if .flag == "overconfidence" then "⚠ OVERCONFIDENCE" elif .flag == "overcaution-candidate" then "ℹ OVERCAUTION?" else "✅ OK" end)
|
|
1981
|
+
else "ℹ untrusted" end)
|
|
1982
|
+
] | @tsv
|
|
1983
|
+
' 2>/dev/null | while IFS=$'\t' read -r taskType confidence survivalRate sampleCount status; do
|
|
1984
|
+
printf "| %-20s | %-8s | %-18s | %-7s | %s\n" \
|
|
1985
|
+
"$taskType" "$confidence" "$survivalRate" "$sampleCount" "$status"
|
|
1986
|
+
|
|
1987
|
+
# Collect flagged cells for summary
|
|
1988
|
+
if [[ "$status" == *"OVERCONFIDENCE"* ]]; then
|
|
1989
|
+
OVERCONFIDENCE_CELLS="${OVERCONFIDENCE_CELLS}\n - ${taskType} × ${confidence}: ${survivalRate} survival (${sampleCount} runs)"
|
|
1990
|
+
elif [[ "$status" == *"OVERCAUTION"* ]]; then
|
|
1991
|
+
OVERCAUTION_CELLS="${OVERCAUTION_CELLS}\n - ${taskType} × ${confidence}: ${survivalRate} survival (${sampleCount} runs)"
|
|
1992
|
+
fi
|
|
1993
|
+
done
|
|
1994
|
+
|
|
1995
|
+
# Emit flag summaries
|
|
1996
|
+
if [ -n "$OVERCONFIDENCE_CELLS" ]; then
|
|
1997
|
+
echo ""
|
|
1998
|
+
echo "### ⚠ Overconfidence Cells (HIGH confidence, survival < 80%)"
|
|
1999
|
+
echo "These task-type × confidence combinations claim HIGH confidence but have poor real-world outcomes."
|
|
2000
|
+
echo "The review threshold for these cells should be tightened (consider needs-human routing)."
|
|
2001
|
+
echo -e "$OVERCONFIDENCE_CELLS"
|
|
2002
|
+
fi
|
|
2003
|
+
|
|
2004
|
+
if [ -n "$OVERCAUTION_CELLS" ]; then
|
|
2005
|
+
echo ""
|
|
2006
|
+
echo "### ℹ Overcaution Candidates (survival > 95% — needs-human may be unnecessary)"
|
|
2007
|
+
echo "These cells have very high survival rates. Needs-human routing on them may be unnecessary,"
|
|
2008
|
+
echo "but any loosening of behavior requires the eval-gate to pass on the affected task-type corpus."
|
|
2009
|
+
echo "Do NOT loosen automatically — flag for human review."
|
|
2010
|
+
echo -e "$OVERCAUTION_CELLS"
|
|
2011
|
+
fi
|
|
2012
|
+
|
|
2013
|
+
# 30-day trend (compare against prior report's PRIOR_CALIBRATION_COMPUTED_AT if available)
|
|
2014
|
+
if [ -n "${PRIOR_CALIBRATION_COMPUTED_AT:-}" ]; then
|
|
2015
|
+
echo ""
|
|
2016
|
+
echo "### Calibration Trend"
|
|
2017
|
+
echo "Prior table computed: ${PRIOR_CALIBRATION_COMPUTED_AT}"
|
|
2018
|
+
echo "Current table computed: ${CALIB_COMPUTED_AT}"
|
|
2019
|
+
echo "(Trend comparison requires prior report data — compare manually if prior available)"
|
|
2020
|
+
fi
|
|
2021
|
+
fi
|
|
2022
|
+
```
|
|
2023
|
+
|
|
2024
|
+
**Metrics to surface in Phase 5A report**:
|
|
2025
|
+
- **Calibration data coverage**: `${CALIB_TOTAL_RUNS}` runs in corpus (target: > 20 for meaningful cells)
|
|
2026
|
+
- **Overconfidence cells**: count of cells where HIGH-confidence survival < 80%
|
|
2027
|
+
- **Overcaution candidates**: count of cells where survival > 95% (informational — loosening requires eval-gate)
|
|
2028
|
+
- **Table freshness**: `computedAt` timestamp — flag as stale if > 7 days old
|
|
2029
|
+
|
|
2030
|
+
**Why this matters**: Static confidence thresholds in review-pr.md (Phase 7B blocking criteria) are prose rules with no feedback from outcomes. The calibration table is the mechanism by which the pipeline learns whether its stated confidence is tracking reality. A task-type with persistent overconfidence should have its review intensity increased regardless of the agent's stated confidence. <!-- Added: forge#1741 -->
|
|
2031
|
+
|
|
2032
|
+
---
|
|
2033
|
+
|