mandrel 1.87.0 โ 1.88.0
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/.agents/docs/SDLC.md +15 -3
- package/.agents/docs/configuration.md +2 -0
- package/.agents/schemas/agentrc.schema.json +12 -0
- package/.agents/scripts/lib/config/explain.js +4 -0
- package/.agents/scripts/lib/config/runners.js +13 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +7 -0
- package/.agents/scripts/lib/config-settings-schema-quality.js +7 -0
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +19 -5
- package/.agents/scripts/lib/feedback-loop/code-review-graduator.js +17 -0
- package/.agents/workflows/helpers/code-review.md +83 -7
- package/.agents/workflows/helpers/deliver-epic.md +24 -4
- package/.agents/workflows/helpers/epic-audit.md +93 -10
- package/docs/CHANGELOG.md +7 -0
- package/package.json +1 -1
package/.agents/docs/SDLC.md
CHANGED
|
@@ -891,11 +891,23 @@ watch / auto-merge / cleanup tail that drives the PR to merge:
|
|
|
891
891
|
fixed on a hotfix branch and re-merged into the Epic.
|
|
892
892
|
2. **Audit (Phase 4).** The change-set audit lenses run against the Epic
|
|
893
893
|
diff; findings flow through as advisory signal to inform the code
|
|
894
|
-
review that follows.
|
|
894
|
+
review that follows. Remediation routing is **threshold-aware**
|
|
895
|
+
(`delivery.epicAudit.autoFixSeverity`, default `medium`): at `medium`
|
|
896
|
+
the host LLM fixes ๐ด/๐ /๐ก findings on-branch (Mediums batched per
|
|
897
|
+
lens) while ๐ข Suggestions graduate to follow-up issues; `high`
|
|
898
|
+
reproduces the older Critical/High-only routing. Findings fixed
|
|
899
|
+
on-branch are recorded under the `audit-results` comment's
|
|
900
|
+
`## Fixed on-branch` section, which the graduator skips so they never
|
|
901
|
+
spawn duplicate follow-up issues.
|
|
895
902
|
3. **Code-review (Phase 5).** `lib/orchestration/code-review.js` (extracted
|
|
896
903
|
from the `code-review.md` helper) audits the diff and posts the
|
|
897
|
-
findings as a `code-review` structured comment on the Epic.
|
|
898
|
-
|
|
904
|
+
findings as a `code-review` structured comment on the Epic. Focused-fix
|
|
905
|
+
routing is threshold-aware in the same way
|
|
906
|
+
(`delivery.codeReview.autoFixSeverity`, default `medium` โ fixes
|
|
907
|
+
๐ด/๐ /๐ก on-branch, ๐ข stays on the comment), and fixed findings land
|
|
908
|
+
under the comment's `## Fixed on-branch` section so the graduator skips
|
|
909
|
+
them. The severity gate is unchanged: surviving ๐ด Critical findings
|
|
910
|
+
halt the run; surviving ๐ /๐ก/๐ข flow through as non-blocking.
|
|
899
911
|
4. **Retro (Phase 6).** `lib/orchestration/retro-runner.js` (extracted from the old
|
|
900
912
|
retro helper) aggregates perf signals, friction counts, hotfix counts,
|
|
901
913
|
recut counts, parked counts, and HITL count using
|
|
@@ -275,12 +275,14 @@ top-level keys are validation errors.
|
|
|
275
275
|
| `epicAudit` | No | `object` | โ | Nested configuration block. |
|
|
276
276
|
| `epicAudit.maxFixAttempts` | No | `integer` | โ | Maximum auto-fix retry attempts per finding in /deliver Phase 4 (epic-audit). 0 disables auto-fix. Default 3. |
|
|
277
277
|
| `epicAudit.maxFixScopeFiles` | No | `integer` | โ | Maximum file count a single auto-fix may modify before escalating to agent::blocked. Default 5. |
|
|
278
|
+
| `epicAudit.autoFixSeverity` | No | `"high"` \| `"medium"` | `"medium"` | Severity threshold for on-branch remediation in /deliver Phase 4 (epic-audit). `medium` (default) routes ๐ด/๐ /๐ก findings into the host-LLM remediation loop (Mediums batched per lens: one commit per lens, a single validation + overlapping-lens rescan at the end) while ๐ข suggestions still graduate to follow-up issues; `high` reproduces the pre-4399 Critical/High-only routing. Hard cutover โ no back-compat flag. |
|
|
278
279
|
| `codeReview` | No | `object` | โ | Nested configuration block. |
|
|
279
280
|
| `codeReview.provider` | No | `"native"` \| `"codex"` \| `"security-review"` | `"native"` | Legacy single-adapter selection. ReviewProvider that produces the Finding[] consumed by runCodeReview(). Story #2833 registered `native` (in-process maintainability/lint); Story #2830 added `codex` (invokes `/codex:review` plugin); Story #2871 added `security-review` (shells out to `claude --print /security-review`). When `providers` (chain shape) is set this field is ignored with a warning. Selecting an adapter whose probe fails hard-fails at factory construction unless declared `optional: true` in the chain. |
|
|
280
281
|
| `codeReview.providers[]` | No | `array<object>` | โ | Multi-provider chain (Story #2871). When set and non-empty, takes precedence over the legacy `provider` field. The orchestrator iterates inline entries in declaration order and merges their Finding[] before posting one structured comment; manual-prompt entries (e.g. ultrareview) contribute a trailing 'Manual review suggestions' section. Each item has: name, scopes, optional, manualPrompt, when. |
|
|
281
282
|
| `codeReview.providerConfig` | No | `object` | โ | Optional escape hatch for adapter-specific configuration. No documented keys in Epic #2815; reserved so future adapters can be configured without another schema migration. |
|
|
282
283
|
| `codeReview.maxFixAttempts` | No | `integer` | โ | Maximum auto-fix retry attempts per finding in /deliver Phase 5 (code-review). 0 disables auto-fix. Default 3. |
|
|
283
284
|
| `codeReview.maxFixScopeFiles` | No | `integer` | โ | Maximum file count a single auto-fix may modify before escalating to agent::blocked. Default 5. |
|
|
285
|
+
| `codeReview.autoFixSeverity` | No | `"high"` \| `"medium"` | `"medium"` | Severity threshold for on-branch remediation in /deliver Phase 5 (code-review). `medium` (default) routes ๐ด/๐ /๐ก findings into the host-LLM focused-fix routing (Mediums batched per lens: one commit per lens, a single validation + rescan at the end) while ๐ข suggestions still graduate to follow-up issues; `high` reproduces the pre-4399 Critical/High-only routing. Hard cutover โ no back-compat flag. |
|
|
284
286
|
| `retro` | No | `object` | โ | Story #3042 (Epic #3019). Operator-tunable retro behaviour. Currently exposes `perfThresholds`, the gates the retro perf-signals classifier uses to decide which signals to surface in the `## Performance Signals` / `## Recommended Follow-Ons` retro sections. |
|
|
285
287
|
| `retro.perfThresholds` | No | `object` | โ | Gates for `classifyPerfSignals` (lib/orchestration/retro-perf-heuristics.js). Defaults are 0.6 / 0.4 / 2. |
|
|
286
288
|
| `retro.perfThresholds.utilisation` | No | `number` | โ | Per-wave utilisation threshold. Waves whose `utilisation` is strictly below this value emit a `low-utilisation` signal. Default 0.6. |
|
|
@@ -542,6 +542,12 @@
|
|
|
542
542
|
"type": "integer",
|
|
543
543
|
"minimum": 1,
|
|
544
544
|
"description": "Maximum file count a single auto-fix may modify before escalating to agent::blocked. Default 5."
|
|
545
|
+
},
|
|
546
|
+
"autoFixSeverity": {
|
|
547
|
+
"type": "string",
|
|
548
|
+
"enum": ["high", "medium"],
|
|
549
|
+
"default": "medium",
|
|
550
|
+
"description": "Severity threshold for on-branch remediation in /deliver Phase 4 (epic-audit). `medium` (default) routes ๐ด/๐ /๐ก findings into the host-LLM remediation loop (Mediums batched per lens: one commit per lens, a single validation + overlapping-lens rescan at the end) while ๐ข suggestions still graduate to follow-up issues; `high` reproduces the pre-4399 Critical/High-only routing. Hard cutover โ no back-compat flag."
|
|
545
551
|
}
|
|
546
552
|
},
|
|
547
553
|
"additionalProperties": false
|
|
@@ -624,6 +630,12 @@
|
|
|
624
630
|
"type": "integer",
|
|
625
631
|
"minimum": 1,
|
|
626
632
|
"description": "Maximum file count a single auto-fix may modify before escalating to agent::blocked. Default 5."
|
|
633
|
+
},
|
|
634
|
+
"autoFixSeverity": {
|
|
635
|
+
"type": "string",
|
|
636
|
+
"enum": ["high", "medium"],
|
|
637
|
+
"default": "medium",
|
|
638
|
+
"description": "Severity threshold for on-branch remediation in /deliver Phase 5 (code-review). `medium` (default) routes ๐ด/๐ /๐ก findings into the host-LLM focused-fix routing (Mediums batched per lens: one commit per lens, a single validation + rescan at the end) while ๐ข suggestions still graduate to follow-up issues; `high` reproduces the pre-4399 Critical/High-only routing. Hard cutover โ no back-compat flag."
|
|
627
639
|
}
|
|
628
640
|
},
|
|
629
641
|
"additionalProperties": false
|
|
@@ -196,6 +196,10 @@ const KEY_MEANINGS = Object.freeze({
|
|
|
196
196
|
'Maximum auto-fix attempts the code-review phase makes.',
|
|
197
197
|
'delivery.codeReview.maxFixScopeFiles':
|
|
198
198
|
'Maximum files an auto-fix may touch in one attempt.',
|
|
199
|
+
'delivery.codeReview.autoFixSeverity':
|
|
200
|
+
'Severity threshold for on-branch code-review remediation (medium fixes ๐ด/๐ /๐ก, high fixes ๐ด/๐ only; default medium).',
|
|
201
|
+
'delivery.epicAudit.autoFixSeverity':
|
|
202
|
+
'Severity threshold for on-branch epic-audit remediation (medium fixes ๐ด/๐ /๐ก, high fixes ๐ด/๐ only; default medium).',
|
|
199
203
|
'delivery.refactorStage.enabled':
|
|
200
204
|
'Whether a dedicated refactor stage runs during delivery.',
|
|
201
205
|
'delivery.acceptanceEval.maxRounds':
|
|
@@ -48,15 +48,22 @@ const DEFAULT_DELIVER_RUNNER = Object.freeze({
|
|
|
48
48
|
* and Phase 5 (code-review). Operators override via
|
|
49
49
|
* `delivery.epicAudit.*` and `delivery.codeReview.*` in `.agentrc.json`
|
|
50
50
|
* (Story #2611, Epic #2586).
|
|
51
|
+
*
|
|
52
|
+
* `autoFixSeverity` (Story #4399) defaults to `'medium'` โ the phase
|
|
53
|
+
* remediates ๐ด/๐ /๐ก findings on-branch while ๐ข suggestions graduate to
|
|
54
|
+
* follow-up issues. `'high'` reproduces the pre-4399 Critical/High-only
|
|
55
|
+
* routing. Hard cutover per `rules/git-conventions.md` โ no back-compat flag.
|
|
51
56
|
*/
|
|
52
57
|
export const DEFAULT_EPIC_AUDIT = Object.freeze({
|
|
53
58
|
maxFixAttempts: 3,
|
|
54
59
|
maxFixScopeFiles: 5,
|
|
60
|
+
autoFixSeverity: 'medium',
|
|
55
61
|
});
|
|
56
62
|
|
|
57
63
|
export const DEFAULT_CODE_REVIEW = Object.freeze({
|
|
58
64
|
maxFixAttempts: 3,
|
|
59
65
|
maxFixScopeFiles: 5,
|
|
66
|
+
autoFixSeverity: 'medium',
|
|
60
67
|
});
|
|
61
68
|
|
|
62
69
|
/**
|
|
@@ -65,8 +72,8 @@ export const DEFAULT_CODE_REVIEW = Object.freeze({
|
|
|
65
72
|
* @param {object | null | undefined} config
|
|
66
73
|
* @returns {{
|
|
67
74
|
* deliverRunner: { concurrencyCap: number, progressReportIntervalSec: number, verifyConcurrencyCap: number },
|
|
68
|
-
* epicAudit: { maxFixAttempts: number, maxFixScopeFiles: number },
|
|
69
|
-
* codeReview: { maxFixAttempts: number, maxFixScopeFiles: number },
|
|
75
|
+
* epicAudit: { maxFixAttempts: number, maxFixScopeFiles: number, autoFixSeverity: 'high'|'medium' },
|
|
76
|
+
* codeReview: { maxFixAttempts: number, maxFixScopeFiles: number, autoFixSeverity: 'high'|'medium' },
|
|
70
77
|
* storyMergeRetry: { maxAttempts: number, backoffMs: readonly number[] },
|
|
71
78
|
* decomposer: { concurrencyCap: number },
|
|
72
79
|
* }}
|
|
@@ -92,12 +99,16 @@ export function getRunners(config) {
|
|
|
92
99
|
epicAuditUser.maxFixAttempts ?? DEFAULT_EPIC_AUDIT.maxFixAttempts,
|
|
93
100
|
maxFixScopeFiles:
|
|
94
101
|
epicAuditUser.maxFixScopeFiles ?? DEFAULT_EPIC_AUDIT.maxFixScopeFiles,
|
|
102
|
+
autoFixSeverity:
|
|
103
|
+
epicAuditUser.autoFixSeverity ?? DEFAULT_EPIC_AUDIT.autoFixSeverity,
|
|
95
104
|
},
|
|
96
105
|
codeReview: {
|
|
97
106
|
maxFixAttempts:
|
|
98
107
|
codeReviewUser.maxFixAttempts ?? DEFAULT_CODE_REVIEW.maxFixAttempts,
|
|
99
108
|
maxFixScopeFiles:
|
|
100
109
|
codeReviewUser.maxFixScopeFiles ?? DEFAULT_CODE_REVIEW.maxFixScopeFiles,
|
|
110
|
+
autoFixSeverity:
|
|
111
|
+
codeReviewUser.autoFixSeverity ?? DEFAULT_CODE_REVIEW.autoFixSeverity,
|
|
101
112
|
},
|
|
102
113
|
storyMergeRetry: DEFAULT_STORY_MERGE_RETRY,
|
|
103
114
|
decomposer: DEFAULT_DECOMPOSER,
|
|
@@ -210,12 +210,19 @@ const MERGE_WATCH_SCHEMA = {
|
|
|
210
210
|
* `agent::blocked` (default 5) โ a deliberately narrow bound for
|
|
211
211
|
* unattended auto-fixes, independent of the Story-sizing thresholds in
|
|
212
212
|
* `ticket-validator-sizing.js`.
|
|
213
|
+
*
|
|
214
|
+
* `autoFixSeverity` (Story #4399) is the threshold that governs which
|
|
215
|
+
* findings the Phase 4 host-LLM remediation loop fixes on-branch: `medium`
|
|
216
|
+
* (the default) routes ๐ด/๐ /๐ก into remediation while ๐ข still graduates;
|
|
217
|
+
* `high` reproduces the pre-4399 Critical/High-only routing. It is a hard
|
|
218
|
+
* cutover per `rules/git-conventions.md` โ there is no back-compat flag.
|
|
213
219
|
*/
|
|
214
220
|
const EPIC_AUDIT_SCHEMA = {
|
|
215
221
|
type: 'object',
|
|
216
222
|
properties: {
|
|
217
223
|
maxFixAttempts: { type: 'integer', minimum: 0 },
|
|
218
224
|
maxFixScopeFiles: { type: 'integer', minimum: 1 },
|
|
225
|
+
autoFixSeverity: { type: 'string', enum: ['high', 'medium'] },
|
|
219
226
|
},
|
|
220
227
|
additionalProperties: false,
|
|
221
228
|
};
|
|
@@ -116,6 +116,12 @@ export const QUALITY_SCHEMA = {
|
|
|
116
116
|
/**
|
|
117
117
|
* `delivery.codeReview` โ sibling to `delivery.epicAudit`. Same bounded
|
|
118
118
|
* retry + scope cap, applied to /deliver Phase 5 (code-review).
|
|
119
|
+
*
|
|
120
|
+
* `autoFixSeverity` (Story #4399) is the sibling of
|
|
121
|
+
* `delivery.epicAudit.autoFixSeverity`: the threshold that governs which
|
|
122
|
+
* Phase 5 findings the host-LLM focused-fix routing remediates on-branch โ
|
|
123
|
+
* `medium` (default) routes ๐ด/๐ /๐ก while ๐ข still graduates, `high`
|
|
124
|
+
* reproduces the pre-4399 Critical/High-only routing.
|
|
119
125
|
*/
|
|
120
126
|
export const CODE_REVIEW_SCHEMA = {
|
|
121
127
|
type: 'object',
|
|
@@ -173,6 +179,7 @@ export const CODE_REVIEW_SCHEMA = {
|
|
|
173
179
|
providerConfig: { type: 'object', additionalProperties: true },
|
|
174
180
|
maxFixAttempts: { type: 'integer', minimum: 0 },
|
|
175
181
|
maxFixScopeFiles: { type: 'integer', minimum: 1 },
|
|
182
|
+
autoFixSeverity: { type: 'string', enum: ['high', 'medium'] },
|
|
176
183
|
},
|
|
177
184
|
additionalProperties: false,
|
|
178
185
|
};
|
|
@@ -98,6 +98,13 @@ export function buildIdempotencyMarker(epicId, index) {
|
|
|
98
98
|
* emoji and embeds the cited path inside backticks. ๐ด critical findings
|
|
99
99
|
* are filtered out (they're blocking โ the Epic stops on those).
|
|
100
100
|
*
|
|
101
|
+
* Findings the Phase 4 remediation loop already fixed on-branch are
|
|
102
|
+
* rendered under a **"Fixed on-branch"** heading (Story #4399) with a โ
|
|
103
|
+
* prefix so they no longer parse as open findings. As a belt-and-suspenders
|
|
104
|
+
* guard the parser also skips every line inside a Fixed-on-branch section
|
|
105
|
+
* outright, so a remediated ๐ก Medium never spawns a ghost follow-up issue
|
|
106
|
+
* even if its line retains its original severity emoji.
|
|
107
|
+
*
|
|
101
108
|
* Pure. Exported so the parser can be unit-tested in isolation.
|
|
102
109
|
*
|
|
103
110
|
* @param {string} body
|
|
@@ -109,18 +116,25 @@ export function parseFindings(body) {
|
|
|
109
116
|
const lines = body.split(/\r?\n/);
|
|
110
117
|
let idx = 0;
|
|
111
118
|
let lens = 'unknown';
|
|
119
|
+
let inFixedSection = false;
|
|
112
120
|
for (const rawLine of lines) {
|
|
113
121
|
const trimmed = rawLine.trim();
|
|
114
122
|
if (trimmed.length === 0) continue;
|
|
115
123
|
|
|
116
|
-
//
|
|
117
|
-
// names a known audit family (`audit-*`).
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
// Any markdown heading resets the Fixed-on-branch guard and, when it
|
|
125
|
+
// names a known audit family (`audit-*`), sets the active lens. A
|
|
126
|
+
// "Fixed on-branch" heading opens a section whose entries never
|
|
127
|
+
// graduate (Story #4399).
|
|
128
|
+
const headingMatch = trimmed.match(/^#{2,6}\s+(.+)$/);
|
|
129
|
+
if (headingMatch) {
|
|
130
|
+
inFixedSection = /fixed on-branch/i.test(headingMatch[1]);
|
|
131
|
+
const lensMatch = headingMatch[1].match(/^(audit-[a-z0-9-]+)/i);
|
|
132
|
+
if (lensMatch) lens = lensMatch[1];
|
|
121
133
|
continue;
|
|
122
134
|
}
|
|
123
135
|
|
|
136
|
+
if (inFixedSection) continue;
|
|
137
|
+
|
|
124
138
|
let severity = null;
|
|
125
139
|
if (trimmed.startsWith('๐ด')) {
|
|
126
140
|
// Critical Blocker โ skip; never graduates.
|
|
@@ -104,6 +104,13 @@ export function buildIdempotencyMarker(epicId, index) {
|
|
|
104
104
|
* filter ๐ด (Critical Blocker โ blocking) out; ๐ /๐ก/๐ข are non-blocking
|
|
105
105
|
* and graduate to follow-up issues.
|
|
106
106
|
*
|
|
107
|
+
* Findings the Phase 5 focused-fix routing already fixed on-branch are
|
|
108
|
+
* rendered under a **"Fixed on-branch"** heading (Story #4399) with a โ
|
|
109
|
+
* prefix so they no longer parse as open findings. As a belt-and-suspenders
|
|
110
|
+
* guard the parser also skips every line inside a Fixed-on-branch section
|
|
111
|
+
* outright, so a remediated ๐ก Medium never spawns a ghost follow-up issue
|
|
112
|
+
* even if its line retains its original severity emoji.
|
|
113
|
+
*
|
|
107
114
|
* @param {string} body
|
|
108
115
|
* @returns {Array<{ severity: 'high'|'medium'|'low', path: string, summary: string, index: number }>}
|
|
109
116
|
*/
|
|
@@ -112,9 +119,19 @@ export function parseFindings(body) {
|
|
|
112
119
|
const findings = [];
|
|
113
120
|
const lines = body.split(/\r?\n/);
|
|
114
121
|
let idx = 0;
|
|
122
|
+
let inFixedSection = false;
|
|
115
123
|
for (const line of lines) {
|
|
116
124
|
const trimmed = line.trim();
|
|
117
125
|
if (trimmed.length === 0) continue;
|
|
126
|
+
// Any markdown heading resets the Fixed-on-branch guard; a
|
|
127
|
+
// "Fixed on-branch" heading opens a section whose entries never
|
|
128
|
+
// graduate (Story #4399).
|
|
129
|
+
const headingMatch = trimmed.match(/^#{1,6}\s+(.+)$/);
|
|
130
|
+
if (headingMatch) {
|
|
131
|
+
inFixedSection = /fixed on-branch/i.test(headingMatch[1]);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (inFixedSection) continue;
|
|
118
135
|
let severity = null;
|
|
119
136
|
if (trimmed.startsWith('๐ ')) severity = 'high';
|
|
120
137
|
else if (trimmed.startsWith('๐ก')) severity = 'medium';
|
|
@@ -293,12 +293,63 @@ For every finding, provide:
|
|
|
293
293
|
fix worked. Keep it tight (โค 5 sentences); the sub-agent will read the
|
|
294
294
|
surrounding code itself.
|
|
295
295
|
|
|
296
|
+
### The `## Fixed on-branch` section (Story #4399)
|
|
297
|
+
|
|
298
|
+
Findings that Step 4.5 remediated on `[HEAD_REF]` MUST be rendered under a
|
|
299
|
+
dedicated **`## Fixed on-branch`** heading, **not** in the severity groups
|
|
300
|
+
above. This is the contract seam that keeps remediated findings from
|
|
301
|
+
spawning ghost follow-up issues: the
|
|
302
|
+
[`code-review` graduator](../../scripts/lib/feedback-loop/code-review-graduator.js)
|
|
303
|
+
skips every entry inside this section (both because a fixed entry is
|
|
304
|
+
rendered with a **โ
prefix** โ so it carries no leading severity emoji the
|
|
305
|
+
parser would match โ and because the parser has an explicit
|
|
306
|
+
Fixed-on-branch section guard).
|
|
307
|
+
|
|
308
|
+
Render each fixed finding as a `โ
`-prefixed line naming its original
|
|
309
|
+
severity, the file path in backticks, and the remediating commit SHA, e.g.:
|
|
310
|
+
|
|
311
|
+
```markdown
|
|
312
|
+
## Fixed on-branch
|
|
313
|
+
|
|
314
|
+
- โ
๐ก Medium: `src/lib/foo.js` โ missing edge-case guard added (a1b2c3d)
|
|
315
|
+
- โ
๐ High: `src/api/users.js` โ ownership check added (d4e5f6a)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Open (escalated / unfixed) findings stay in their severity group with
|
|
319
|
+
their leading severity emoji so the graduator still files them.
|
|
320
|
+
|
|
296
321
|
## Step 4.5 โ Focused-fix Routing (host LLM, no automated loop)
|
|
297
322
|
|
|
298
323
|
There is **no runtime auto-fix function** at this phase. The host LLM is
|
|
299
|
-
the executor:
|
|
300
|
-
|
|
301
|
-
|
|
324
|
+
the executor: it decides, per finding, between a focused fix on
|
|
325
|
+
`[HEAD_REF]` and leaving the finding on the `code-review` structured
|
|
326
|
+
comment for the operator.
|
|
327
|
+
|
|
328
|
+
### Resolve the remediation threshold (Story #4399)
|
|
329
|
+
|
|
330
|
+
Read `delivery.codeReview.autoFixSeverity` from the resolved `.agentrc.json`
|
|
331
|
+
(default **`medium`**; the resolver in
|
|
332
|
+
[`config/runners.js`](../../scripts/lib/config/runners.js) supplies the
|
|
333
|
+
default when the key is absent). The threshold governs **which severities
|
|
334
|
+
route into on-branch remediation** โ it never changes the halting rule (a
|
|
335
|
+
surviving ๐ด still stops) or the escalation classes:
|
|
336
|
+
|
|
337
|
+
- **`medium`** (default) โ route ๐ด Critical, ๐ High, **and ๐ก Medium**
|
|
338
|
+
findings into remediation. ๐ข Suggestions stay on the comment (never
|
|
339
|
+
auto-fixed).
|
|
340
|
+
- **`high`** โ route only ๐ด Critical and ๐ High findings, reproducing
|
|
341
|
+
the pre-4399 behavior exactly. ๐ก Medium and ๐ข Suggestion findings stay
|
|
342
|
+
on the comment.
|
|
343
|
+
|
|
344
|
+
Hard cutover per
|
|
345
|
+
[`rules/git-conventions.md`](../../rules/git-conventions.md) ยง Contract
|
|
346
|
+
Cutovers โ no back-compat flag; `high` is opt-in to the old routing.
|
|
347
|
+
|
|
348
|
+
### ๐ด / ๐ findings โ per-finding ceremony (unchanged)
|
|
349
|
+
|
|
350
|
+
For each ๐ด / ๐ finding from Step 4, decide between two paths and keep the
|
|
351
|
+
`code-review` structured comment authoritative for anything not fixed
|
|
352
|
+
in-place.
|
|
302
353
|
|
|
303
354
|
1. **Apply a focused fix on `[HEAD_REF]`.** Permitted only when the
|
|
304
355
|
finding is unambiguously *fixable* (clean remediation, no scope
|
|
@@ -328,11 +379,36 @@ anything not fixed in-place.
|
|
|
328
379
|
attempt (the equivalent of the prior loop's
|
|
329
380
|
`validation-regression` / `thrash-detected` exits).
|
|
330
381
|
|
|
382
|
+
### ๐ก Medium findings โ batched per-lens ceremony (only when `autoFixSeverity: medium`)
|
|
383
|
+
|
|
384
|
+
When the threshold is `medium`, remediate the fixable ๐ก Medium findings in
|
|
385
|
+
a **batch keyed by owning review lens/pillar** rather than the per-finding
|
|
386
|
+
ceremony above:
|
|
387
|
+
|
|
388
|
+
1. Group the fixable Mediums by owning lens (the pillar or audit family
|
|
389
|
+
that produced them). A Medium is fixable on the same terms as a ๐ ; a
|
|
390
|
+
Medium in any escalation class stays on the comment exactly like a ๐ .
|
|
391
|
+
2. For each lens, call `assert-branch.js --expected [HEAD_REF]`, stage
|
|
392
|
+
explicit paths only, and make **one focused conventional commit per
|
|
393
|
+
lens** (`fix(<scope>): <description> (review findings batch)`).
|
|
394
|
+
3. Bounded-attempt semantics extend to the batch: each finding gets **at
|
|
395
|
+
most one** attempt, and a lens's batch commit that would exceed
|
|
396
|
+
`delivery.codeReview.maxFixScopeFiles` routes that lens's findings to
|
|
397
|
+
escalation (`scope-exceeded`) instead of committing.
|
|
398
|
+
4. After **all** lens batches are committed, run a **single** validation
|
|
399
|
+
pass (`npm run lint` plus the relevant `npm test` slice) and a
|
|
400
|
+
**single** targeted rescan over the touched files. Surviving batched
|
|
401
|
+
findings stay on the comment for Step 5.
|
|
402
|
+
|
|
403
|
+
Record every remediated finding (๐ or ๐ก) in the **"Fixed on-branch"**
|
|
404
|
+
section of the `code-review` comment (Step 4) so it does not graduate to a
|
|
405
|
+
follow-up issue.
|
|
406
|
+
|
|
331
407
|
Do not invent a programmatic retry budget. The host LLM applies *at most
|
|
332
|
-
one* focused-fix attempt per finding
|
|
333
|
-
Escalated findings remain on the `code-review`
|
|
334
|
-
their reason recorded, so Step 5 (and downstream
|
|
335
|
-
why each one was not auto-remediated.
|
|
408
|
+
one* focused-fix attempt per finding (or per batched finding) before
|
|
409
|
+
escalating to the operator. Escalated findings remain on the `code-review`
|
|
410
|
+
structured comment with their reason recorded, so Step 5 (and downstream
|
|
411
|
+
consumers) see exactly why each one was not auto-remediated.
|
|
336
412
|
|
|
337
413
|
## Step 4.6 โ Cross-phase re-check trigger
|
|
338
414
|
|
|
@@ -489,6 +489,16 @@ therefore auto-runs its mapped lenses (e.g. a `security`-axis Epic runs
|
|
|
489
489
|
low-risk Epic adds nothing. Findings are persisted as an `audit-results`
|
|
490
490
|
structured comment on the Epic.
|
|
491
491
|
|
|
492
|
+
The helper's Step 3 remediation is **threshold-aware** (Story #4399): it
|
|
493
|
+
reads `delivery.epicAudit.autoFixSeverity` (default **`medium`**) and, at
|
|
494
|
+
`medium`, routes ๐ด/๐ /**๐ก** findings into on-branch remediation (Mediums
|
|
495
|
+
batched per lens โ one commit per lens, a single validation + overlapping-
|
|
496
|
+
lens rescan at the end) while ๐ข Suggestions still graduate; `high`
|
|
497
|
+
reproduces the pre-4399 Critical/High-only routing. Remediated findings are
|
|
498
|
+
rendered under the comment's `## Fixed on-branch` section so they never
|
|
499
|
+
graduate to follow-up issues. The severity gate below is **unchanged** โ
|
|
500
|
+
it keys off the surviving (unfixed) findings.
|
|
501
|
+
|
|
492
502
|
The helper walks the selected roster **serially in-context by default**; when
|
|
493
503
|
the roster carries more than one lens it **may delegate the walk to a single
|
|
494
504
|
audit-orchestrator sub-agent** that fans the already-selected lenses out as
|
|
@@ -500,8 +510,8 @@ per-lens cost gate is preserved, and the seven sequential-only lenses are **not*
|
|
|
500
510
|
batch-converted โ the fan-out parallelizes across lenses only and never changes
|
|
501
511
|
how any single lens runs internally.
|
|
502
512
|
|
|
503
|
-
- **Any ๐ด Critical Blocker** โ STOP. Relay to the operator.
|
|
504
|
-
- **Only
|
|
513
|
+
- **Any surviving ๐ด Critical Blocker** โ STOP. Relay to the operator.
|
|
514
|
+
- **Only ๐ /๐ก/๐ข surviving** โ log as non-blocking and continue.
|
|
505
515
|
- **Selector reports `degraded: true`** โ STOP. Propagate the
|
|
506
516
|
`reason`/`detail`, post a friction comment, do not fall back to a
|
|
507
517
|
full-roster audit.
|
|
@@ -533,8 +543,18 @@ emit so a high-risk Epic gets a deeper adversarial pass and a low-risk one a
|
|
|
533
543
|
lighter one. Depth is **input-only** โ it never changes the findings envelope
|
|
534
544
|
or the posted comment shape.
|
|
535
545
|
|
|
536
|
-
|
|
537
|
-
|
|
546
|
+
The helper's Step 4.5 focused-fix routing is **threshold-aware**
|
|
547
|
+
(Story #4399): it reads `delivery.codeReview.autoFixSeverity` (default
|
|
548
|
+
**`medium`**) and, at `medium`, routes ๐ด/๐ /**๐ก** findings into on-branch
|
|
549
|
+
remediation (Mediums batched per lens โ one commit per lens, a single
|
|
550
|
+
validation + rescan at the end) while ๐ข Suggestions stay on the comment;
|
|
551
|
+
`high` reproduces the pre-4399 Critical/High-only routing. Remediated
|
|
552
|
+
findings are rendered under the comment's `## Fixed on-branch` section so
|
|
553
|
+
they never graduate to follow-up issues. The severity gate below is
|
|
554
|
+
**unchanged** โ it keys off the surviving (unfixed) findings.
|
|
555
|
+
|
|
556
|
+
- **Any surviving ๐ด Critical Blocker** โ STOP. Relay to the operator.
|
|
557
|
+
- **Only ๐ /๐ก/๐ข surviving** โ log as non-blocking and continue.
|
|
538
558
|
|
|
539
559
|
---
|
|
540
560
|
|
|
@@ -263,9 +263,32 @@ envelope" bullet. Until then, the host LLM is the gate.
|
|
|
263
263
|
## Step 3 โ Remediation Routing (host LLM, no automated loop)
|
|
264
264
|
|
|
265
265
|
There is **no runtime auto-fix function** at this phase. The host LLM is
|
|
266
|
-
the executor: it inspects the aggregated
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
the executor: it inspects the aggregated findings from Step 2 and either
|
|
267
|
+
applies a focused fix on the Epic branch or escalates the finding to the
|
|
268
|
+
operator via the `audit-results` comment in Step 4.
|
|
269
|
+
|
|
270
|
+
### Resolve the remediation threshold (Story #4399)
|
|
271
|
+
|
|
272
|
+
Read `delivery.epicAudit.autoFixSeverity` from the resolved `.agentrc.json`
|
|
273
|
+
(default **`medium`**; the resolver in
|
|
274
|
+
[`config/runners.js`](../../scripts/lib/config/runners.js) supplies the
|
|
275
|
+
default when the key is absent). The threshold governs **which severities
|
|
276
|
+
route into on-branch remediation** โ it never changes the halting rule
|
|
277
|
+
(a surviving ๐ด still stops Phase 4 in Step 4) or the escalation classes:
|
|
278
|
+
|
|
279
|
+
- **`medium`** (default) โ route ๐ด Critical, ๐ High, **and ๐ก Medium**
|
|
280
|
+
findings into remediation. ๐ข Suggestions still graduate to follow-up
|
|
281
|
+
issues (never auto-fixed).
|
|
282
|
+
- **`high`** โ route only ๐ด Critical and ๐ High findings into
|
|
283
|
+
remediation, reproducing the pre-4399 behavior exactly. ๐ก Medium and
|
|
284
|
+
๐ข Suggestion findings graduate to follow-up issues untouched.
|
|
285
|
+
|
|
286
|
+
This is a hard cutover per
|
|
287
|
+
[`rules/git-conventions.md`](../../rules/git-conventions.md) ยง Contract
|
|
288
|
+
Cutovers โ there is no back-compat flag; `high` is opt-in to the old
|
|
289
|
+
routing, `medium` is the shipped default.
|
|
290
|
+
|
|
291
|
+
### ๐ด / ๐ findings โ per-finding ceremony (unchanged)
|
|
269
292
|
|
|
270
293
|
For each ๐ด / ๐ finding, the host LLM MUST decide between two paths:
|
|
271
294
|
|
|
@@ -297,10 +320,42 @@ For each ๐ด / ๐ finding, the host LLM MUST decide between two paths:
|
|
|
297
320
|
attempt (the equivalent of the prior loop's
|
|
298
321
|
`validation-regression` / `thrash-detected` exits).
|
|
299
322
|
|
|
323
|
+
### ๐ก Medium findings โ batched per-lens ceremony (only when `autoFixSeverity: medium`)
|
|
324
|
+
|
|
325
|
+
When the threshold is `medium`, remediate the fixable ๐ก Medium findings
|
|
326
|
+
in a **batch keyed by owning lens** rather than the per-finding ceremony
|
|
327
|
+
above โ the per-finding rescan is disproportionate for the volume of
|
|
328
|
+
Mediums a wide change set surfaces:
|
|
329
|
+
|
|
330
|
+
1. Group the fixable Mediums by their owning lens. A Medium is fixable on
|
|
331
|
+
the same terms as a ๐ (clean remediation, no escalation class); a
|
|
332
|
+
Medium that falls into any escalation class (`spec-deviation`,
|
|
333
|
+
`secrets`, `test-deletion`, `scope-exceeded`) routes to Step 4
|
|
334
|
+
untouched exactly like a ๐ .
|
|
335
|
+
2. For each lens, call `assert-branch.js --expected [EPIC_BRANCH]`, stage
|
|
336
|
+
explicit paths only, and make **one focused conventional commit per
|
|
337
|
+
lens** carrying all that lens's Medium fixes
|
|
338
|
+
(`fix(<scope>): <description> (audit findings batch)`).
|
|
339
|
+
3. The bounded-attempt semantics extend to the batch: each finding in the
|
|
340
|
+
batch gets **at most one** attempt, and a lens's batch commit that
|
|
341
|
+
would exceed `delivery.epicAudit.maxFixScopeFiles` routes that lens's
|
|
342
|
+
findings to escalation (`scope-exceeded`) instead of committing.
|
|
343
|
+
4. After **all** lens batches are committed, run a **single** validation
|
|
344
|
+
pass (`npm run lint` plus the relevant `npm test` slice) and a
|
|
345
|
+
**single** rescan of the **overlapping lenses only** (re-invoke
|
|
346
|
+
`run-audit-suite.js` for the lenses whose findings were touched).
|
|
347
|
+
Confirm the batched findings are gone. If a batched finding survives
|
|
348
|
+
the rescan or validation regresses, route the surviving finding(s) to
|
|
349
|
+
escalation and record the attempt context in Step 4.
|
|
350
|
+
|
|
351
|
+
Record every remediated finding (๐ or ๐ก) in the **"Fixed on-branch"**
|
|
352
|
+
section of the `audit-results` comment (Step 4) so it does not graduate to
|
|
353
|
+
a follow-up issue.
|
|
354
|
+
|
|
300
355
|
Do not invent a programmatic retry budget. The host LLM applies *at most
|
|
301
|
-
one* focused-fix attempt per finding
|
|
302
|
-
remediation is the operator's call after reading
|
|
303
|
-
comment.
|
|
356
|
+
one* focused-fix attempt per finding (or per batched finding) before
|
|
357
|
+
escalating; any further remediation is the operator's call after reading
|
|
358
|
+
the `audit-results` comment.
|
|
304
359
|
|
|
305
360
|
Escalated findings flow through to Step 4 unchanged with their
|
|
306
361
|
escalation reason recorded โ the audit pass does not delete them, it
|
|
@@ -332,12 +387,40 @@ The body MUST include:
|
|
|
332
387
|
- a link to the per-lens artifact files under `<auditOutputDir>` so the
|
|
333
388
|
operator (and downstream retro) can re-read the full prompt body.
|
|
334
389
|
|
|
390
|
+
### The `## Fixed on-branch` section (Story #4399)
|
|
391
|
+
|
|
392
|
+
Findings that Step 3 remediated on the Epic branch MUST be rendered under a
|
|
393
|
+
dedicated **`## Fixed on-branch`** heading, **not** under their lens's
|
|
394
|
+
open-findings group. This is the contract seam that keeps remediated
|
|
395
|
+
findings from spawning ghost follow-up issues: the
|
|
396
|
+
[`audit-results` graduator](../../scripts/lib/feedback-loop/audit-results-graduator.js)
|
|
397
|
+
skips every entry inside this section (both because a fixed entry is
|
|
398
|
+
rendered with a **โ
prefix** โ so it carries no leading severity emoji the
|
|
399
|
+
parser would match โ and because the parser has an explicit
|
|
400
|
+
Fixed-on-branch section guard).
|
|
401
|
+
|
|
402
|
+
Render each fixed finding as a `โ
`-prefixed line naming its original
|
|
403
|
+
severity, the file path in backticks, and the remediating commit SHA, e.g.:
|
|
404
|
+
|
|
405
|
+
```markdown
|
|
406
|
+
## Fixed on-branch
|
|
407
|
+
|
|
408
|
+
- โ
๐ก Medium (audit-clean-code): `.agents/scripts/foo.js` โ dead branch removed (a1b2c3d)
|
|
409
|
+
- โ
๐ High (audit-security): `src/api/users.js` โ ownership check added (d4e5f6a)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Open (escalated / unfixed) findings stay under their lens heading with
|
|
413
|
+
their leading severity emoji so the graduator still files them.
|
|
414
|
+
|
|
335
415
|
### Severity gating
|
|
336
416
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
417
|
+
The gate is unchanged by the threshold โ it keys off the **surviving**
|
|
418
|
+
(unfixed) findings after Step 3:
|
|
419
|
+
|
|
420
|
+
- **Any surviving ๐ด Critical Blocker** โ STOP. Relay to the operator and
|
|
421
|
+
let `/deliver` Phase 4 record a manual intervention.
|
|
422
|
+
- **Only ๐ /๐ก/๐ข surviving** โ log as non-blocking and return to
|
|
423
|
+
`/deliver` Phase 5 (code-review).
|
|
341
424
|
|
|
342
425
|
## Constraints
|
|
343
426
|
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.88.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.87.0...mandrel-v1.88.0) (2026-07-08)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
* **delivery:** threshold-aware auto-fix remediates Medium findings on-branch (refs [#4399](https://github.com/dsj1984/mandrel/issues/4399)) ([#4400](https://github.com/dsj1984/mandrel/issues/4400)) ([34964b3](https://github.com/dsj1984/mandrel/commit/34964b3665852bf43081f6d893430ff9a0a1f630))
|
|
11
|
+
|
|
5
12
|
## [1.87.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.86.0...mandrel-v1.87.0) (2026-07-08)
|
|
6
13
|
|
|
7
14
|
|
package/package.json
CHANGED