mandrel 2.56.0 → 2.57.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.
Files changed (106) hide show
  1. package/.agents/agents/plan-critic.md +13 -18
  2. package/.agents/agents/story-worker.md +25 -34
  3. package/.agents/docs/agentrc-reference.json +0 -30
  4. package/.agents/docs/configuration.md +8 -28
  5. package/.agents/docs/execution-reference.md +5 -5
  6. package/.agents/docs/quality-gates.md +8 -7
  7. package/.agents/instructions.md +9 -10
  8. package/.agents/schemas/agentrc.schema.json +9 -185
  9. package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
  10. package/.agents/scripts/acceptance-eval.js +107 -17
  11. package/.agents/scripts/ceremony-derive.js +191 -0
  12. package/.agents/scripts/check-context-budget.js +28 -33
  13. package/.agents/scripts/check-cyclomatic.js +4 -3
  14. package/.agents/scripts/deliver-light.js +31 -94
  15. package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
  16. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
  17. package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
  18. package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
  19. package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
  20. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
  21. package/.agents/scripts/lib/close-validation/gates.js +52 -1
  22. package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
  23. package/.agents/scripts/lib/config/delivery-routing.js +7 -33
  24. package/.agents/scripts/lib/config/explain.js +0 -19
  25. package/.agents/scripts/lib/config/limits.js +18 -78
  26. package/.agents/scripts/lib/config/quality.js +6 -3
  27. package/.agents/scripts/lib/config/runners.js +3 -2
  28. package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
  29. package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
  30. package/.agents/scripts/lib/config-settings-schema.js +16 -143
  31. package/.agents/scripts/lib/crap-engine.js +35 -4
  32. package/.agents/scripts/lib/crap-utils.js +17 -1
  33. package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
  34. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  35. package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
  36. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  37. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
  38. package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
  39. package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
  40. package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
  41. package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
  42. package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
  43. package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
  44. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
  45. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
  46. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
  47. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
  48. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +118 -297
  49. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
  51. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
  52. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
  53. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
  54. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
  55. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
  56. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
  57. package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
  58. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
  59. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
  60. package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
  61. package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
  62. package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
  63. package/.agents/scripts/lib/story-body/story-body.js +17 -237
  64. package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
  65. package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
  66. package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
  67. package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
  68. package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
  69. package/.agents/scripts/lib/test-run-credit.js +266 -0
  70. package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
  71. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
  72. package/.agents/scripts/lib/workers/crap-worker.js +32 -41
  73. package/.agents/scripts/plan-context.js +7 -9
  74. package/.agents/scripts/plan-critics.js +28 -54
  75. package/.agents/scripts/plan-persist.js +25 -68
  76. package/.agents/scripts/quality-preview.js +51 -0
  77. package/.agents/scripts/run-tests.js +12 -0
  78. package/.agents/scripts/stories-wave-tick.js +23 -45
  79. package/.agents/scripts/test-isolate.js +13 -180
  80. package/.agents/scripts/update-coverage-baseline.js +25 -70
  81. package/.agents/scripts/update-crap-baseline.js +19 -123
  82. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  83. package/.agents/workflows/audit-clean-code.md +4 -3
  84. package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
  85. package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
  86. package/.agents/workflows/helpers/code-review.md +2 -3
  87. package/.agents/workflows/helpers/deliver-digest.md +41 -57
  88. package/.agents/workflows/helpers/deliver-light.md +40 -105
  89. package/.agents/workflows/helpers/deliver-reference.md +1 -1
  90. package/.agents/workflows/helpers/deliver-story-reference.md +37 -58
  91. package/.agents/workflows/helpers/deliver-story.md +9 -13
  92. package/.agents/workflows/helpers/plan-reference.md +132 -219
  93. package/.agents/workflows/mandrel-plan.md +27 -40
  94. package/.agents/workflows/memory-consolidate.md +9 -13
  95. package/docs/CHANGELOG.md +23 -0
  96. package/lib/migrations/index.js +4 -0
  97. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
  98. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
  99. package/package.json +1 -1
  100. package/.agents/scripts/lib/framework-version.js +0 -39
  101. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
  102. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
  103. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
  104. package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
  105. package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
  106. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
@@ -3,224 +3,187 @@
3
3
  *
4
4
  * Exports:
5
5
  * - `resolveBaseBranchRef(config)` — the one place the persist gates learn
6
- * which ref to probe.
7
- * - `validateTickets(tickets, config)` — runs the cross-link, model-capacity,
8
- * freshness, and task-body validators in one pass. Capacity settings are
9
- * explicit inputs so the validator and decomposer share one live delivery
10
- * envelope instead of silently falling back to framework defaults.
11
- * - `makeDefaultFanOutCounter({ baseBranchRef, cwd, git })` — production
12
- * fan-out probe used by the conflict policy.
6
+ * which branch name the operator configured.
7
+ *
8
+ * The ref the probes read is resolved per checkout (`resolveProbeRef`): the
9
+ * local branch when it exists, else its `origin/` tracking ref (a PR
10
+ * checkout on CI has no local `main`), else nothing — a shallow checkout
11
+ * with no base at all skips the probes instead of reading every path as
12
+ * absent.
13
+ * - `validateTickets(tickets, config, opts)` — repairs the mechanical
14
+ * `changes[]` formalities against the base branch, then runs the
15
+ * cross-link, freshness, and task-body validators in one pass.
16
+ *
17
+ * Story #5312 deleted the fan-out probe that lived here: the delete
18
+ * blast-radius count never refused a real plan, and the `git grep` it paid
19
+ * for on every persist bought a warning nobody acted on.
13
20
  *
14
21
  * @module lib/orchestration/plan-persist/persist-helpers
15
22
  */
16
23
 
17
- import posix from 'node:path/posix';
18
24
  import { gitSpawn } from '../../git-utils.js';
19
25
  import { validateTaskBodies } from '../task-body-validator.js';
20
26
  import { validateAndNormalizeTickets } from '../ticket-validator.js';
21
- import { resolveConflictPolicy } from '../ticket-validator-conflicts.js';
22
- import { normalizeVerifyTiers } from '../verify-tier-repair.js';
27
+ import { repairChangeEntries } from './changes-repair.js';
23
28
 
24
29
  /**
25
- * Extensions an import specifier may elide. Probed longest-path-first when
26
- * resolving an extensionless specifier back onto a concrete repo path.
30
+ * Resolve the ref the persist gates probe against.
31
+ *
32
+ * The canonical resolved config carries the base branch at
33
+ * `project.baseBranch` (`lib/config-resolver.js` defaults it to `main`).
34
+ * This helper used to read `config.baseBranch` — a key the resolver never
35
+ * produces — so every freshness / file-assumption probe silently targeted
36
+ * the literal `main` regardless of configuration. Benign in a repo whose
37
+ * base branch *is* `main`; wrong for any consumer that configured something
38
+ * else (Story #4541).
39
+ *
40
+ * The flat `config.baseBranch` fallback is retained for the legacy
41
+ * `settings`-bag callers that pass `{ baseBranch, paths, planning }`.
42
+ *
43
+ * @param {object} [config] Resolved config, or a legacy settings bag.
44
+ * @returns {string}
27
45
  */
28
- const RESOLVABLE_EXTENSIONS = ['', '.js', '.mjs', '.cjs', '.jsx', '.json'];
29
-
30
- /** Every quoted string on a candidate line — the specifier lives in one. */
31
- const QUOTED_RE = /['"]([^'"\n]+)['"]/g;
32
-
33
- function escapeRegExp(value) {
34
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
46
+ export function resolveBaseBranchRef(config) {
47
+ return config?.project?.baseBranch ?? config?.baseBranch ?? 'main';
35
48
  }
36
49
 
37
50
  /**
38
- * The specifier tails an importer of `path` could plausibly write as the
39
- * final segment of its quoted specifier: the basename, the basename minus
40
- * its extension, and — for a directory-index module — the directory name
41
- * (`./foo` resolving to `foo/index.js`).
42
- *
43
- * This is a *candidate* net only. It is deliberately generous because the
44
- * resolution pass below re-checks every hit against the real path; a tail
45
- * that over-matches costs one extra resolve, a tail that under-matches
46
- * loses a genuine importer.
51
+ * Does `ref` name a commit in the repo at `cwd`?
52
+ *
53
+ * @param {string} ref
54
+ * @param {string} cwd
55
+ * @returns {boolean}
47
56
  */
48
- function specifierTails(path) {
49
- const base = posix.basename(path);
50
- const ext = posix.extname(base);
51
- const stem = ext ? base.slice(0, -ext.length) : base;
52
- const tails = new Set([base, stem]);
53
- if (stem === 'index') {
54
- const dir = posix.basename(posix.dirname(path));
55
- if (dir && dir !== '.') tails.add(dir);
56
- }
57
- return [...tails].filter((t) => t.length > 0);
57
+ function refResolves(ref, cwd) {
58
+ return (
59
+ gitSpawn(cwd, 'rev-parse', '--verify', '--quiet', `${ref}^{commit}`)
60
+ .status === 0
61
+ );
58
62
  }
59
63
 
60
64
  /**
61
- * ERE matching a line that carries an `import` / `export … from` /
62
- * `require(` / dynamic `import(` whose quoted specifier *ends* at one of
63
- * `tails`. The `(^|/)`-equivalent guard (`([^'"]*\/)?`) is what keeps
64
- * `notification.js` from matching `push-notification.js`.
65
+ * Resolve the configured base branch to a ref the footprint probes can read
66
+ * in this checkout, or `null` when none exists.
67
+ *
68
+ * A developer checkout carries a local `main`; a CI pull-request checkout
69
+ * (`actions/checkout` at the merge ref, detached) carries only
70
+ * `origin/main`; a shallow smoke checkout carries neither. Probing the
71
+ * bare branch name in the last two answers "absent" for every path, which
72
+ * turns each bare-path repair into a `creates` and every declared path into
73
+ * a stale reference. So: the local branch when it resolves, else its
74
+ * `origin/` tracking ref when that does, else `null` — the caller then
75
+ * skips the probes and says so, rather than reporting the tree as missing.
76
+ *
77
+ * @param {{ baseBranch: string, cwd?: string }} opts
78
+ * @returns {string|null}
65
79
  */
66
- function buildProbePattern(tails) {
67
- const alt = tails.map(escapeRegExp).join('|');
68
- return `(from|require|import)[[:space:]]*\\(?[[:space:]]*['"]([^'"]*/)?(${alt})['"]`;
80
+ function resolveProbeRef({ baseBranch, cwd }) {
81
+ const repo = cwd ?? process.cwd();
82
+ const candidates = [baseBranch, `origin/${baseBranch}`];
83
+ return candidates.find((ref) => refResolves(ref, repo)) ?? null;
69
84
  }
70
85
 
71
86
  /**
72
- * Resolve a relative specifier written in `importerPath` back onto a repo
73
- * path and report whether it names `deletedPath`.
74
- *
75
- * **Known boundary:** only relative (`./`, `../`) specifiers resolve. A
76
- * consumer repo that imports its own modules through bare specifiers or a
77
- * path alias (`#lib/x`, `@app/x`, a `tsconfig` `paths` entry) would
78
- * under-count, because resolving those needs the resolver config this probe
79
- * deliberately does not read. Mandrel's own internal imports are all
80
- * relative. Under-counting is the *quiet* failure direction — it argues for
81
- * a deletion rather than against one — so if alias-importing consumers
82
- * appear, this is the place to teach the probe their resolver.
87
+ * Default git probe: returns true when `path` exists at `ref` in the cwd repo.
88
+ * `git cat-file -e <ref>:<path>` is the standard low-cost existence check —
89
+ * the same probe the validator's own gates run, so the repair pass and the
90
+ * gates cannot disagree about what is at base.
91
+ *
92
+ * @param {{ baseBranchRef: string, path: string, cwd?: string }} opts
93
+ * @returns {boolean}
83
94
  */
84
- function specifierResolvesTo(importerPath, specifier, deletedPath) {
85
- if (!specifier.startsWith('./') && !specifier.startsWith('../')) return false;
86
- const resolved = posix.normalize(
87
- posix.join(posix.dirname(importerPath), specifier),
95
+ function defaultGitRunner({ baseBranchRef, path, cwd }) {
96
+ const result = gitSpawn(
97
+ cwd ?? process.cwd(),
98
+ 'cat-file',
99
+ '-e',
100
+ `${baseBranchRef}:${path}`,
88
101
  );
89
- for (const ext of RESOLVABLE_EXTENSIONS) {
90
- if (`${resolved}${ext}` === deletedPath) return true;
91
- if (ext && `${resolved}/index${ext}` === deletedPath) return true;
92
- }
93
- return false;
102
+ return result.status === 0;
94
103
  }
95
104
 
96
105
  /**
97
- * Quote one argv entry so the reported probe is **runnable as emitted**.
98
- *
99
- * The probe is the operator's route to checking the number, so it has to
100
- * survive a paste into a shell. Unquoted, the ERE's `(`, `|`, `[[:space:]]`
101
- * and `?` are glob/grouping metacharacters: zsh fails the paste with
102
- * `no matches found` *and exits 0*, which reads as "zero importers" — the
103
- * gate's own audit trail would then argue for the deletion it is meant to
104
- * question (Story #4547).
106
+ * The `existsAtBase` predicate the `changes[]` repair pass probes with. With
107
+ * no base to read, a bare bullet is taken as the in-place edit it almost
108
+ * always is.
109
+ *
110
+ * @param {{ baseBranchRef: string|null, cwd?: string, gitRunner?: Function }} opts
111
+ * @returns {(path: string) => boolean}
105
112
  */
106
- function shellQuote(value) {
107
- if (/^[A-Za-z0-9_./-]+$/.test(value)) return value;
108
- return `'${value.replaceAll("'", "'\\''")}'`;
113
+ function makeExistsAtBase({ baseBranchRef, cwd, gitRunner }) {
114
+ if (baseBranchRef === null) return () => true;
115
+ const runner = gitRunner ?? defaultGitRunner;
116
+ return (path) => Boolean(runner({ baseBranchRef, path, cwd }));
109
117
  }
110
118
 
111
119
  /**
112
- * Parse one `git grep -n` output line of the form `<ref>:<path>:<lineno>:<text>`.
120
+ * The one warning a checkout with no readable base leaves on the dry-run.
121
+ *
122
+ * @param {string} baseBranch
123
+ * @param {string|null} baseBranchRef
124
+ * @returns {string[]}
113
125
  */
114
- function parseGrepLine(line, baseBranchRef) {
115
- const prefix = `${baseBranchRef}:`;
116
- if (!line.startsWith(prefix)) return null;
117
- const rest = line.slice(prefix.length);
118
- const pathEnd = rest.indexOf(':');
119
- if (pathEnd === -1) return null;
120
- const path = rest.slice(0, pathEnd);
121
- const afterPath = rest.slice(pathEnd + 1);
122
- const lineEnd = afterPath.indexOf(':');
123
- if (lineEnd === -1) return null;
124
- return { path, text: afterPath.slice(lineEnd + 1) };
126
+ function probeSkipWarnings(baseBranch, baseBranchRef) {
127
+ if (baseBranchRef !== null) return [];
128
+ return [
129
+ `base branch ${baseBranch} does not resolve in this checkout (tried ` +
130
+ `${baseBranch} and origin/${baseBranch}) — footprint probes skipped; ` +
131
+ 'the plan summary reports its references as ambiguous, not stale.',
132
+ ];
125
133
  }
126
134
 
127
135
  /**
128
- * Default fan-out probe — resolves the *importers* of the deleted module at
129
- * `baseBranchRef`, and reports the referencing files alongside the exact
130
- * probe that produced them.
131
- *
132
- * Two-stage, because accuracy and cost pull in opposite directions:
133
- *
134
- * 1. `git grep -n -E` narrows the tree to lines whose quoted import /
135
- * require specifier could name the module (final-segment match).
136
- * 2. Each candidate specifier is resolved against its own importer's
137
- * directory and compared to the deleted path. Only a real resolution
138
- * counts.
139
- *
140
- * The predecessor (Story #2962) grepped the basename stem as a bare word
141
- * across the whole tree, so a module named `notification` or `options`
142
- * reported dozens of call sites drawn from prose, schemas, and unrelated
143
- * modules — and the gate that fired on that number told the operator to
144
- * split a migration that did not exist. It also returned 0 without probing
145
- * for any stem under three characters, under-reporting in silence. Both are
146
- * gone: coupling is measured by resolution, not vocabulary (Story #4547).
147
- *
148
- * @returns {(arg: { path: string }) => { count: number, files: string[], probe: string }}
136
+ * Attach non-enumerable bookkeeping to the validated array.
137
+ *
138
+ * @param {object[]} validated
139
+ * @param {Record<string, unknown>} extras
149
140
  */
150
- export function makeDefaultFanOutCounter({ baseBranchRef, cwd, git } = {}) {
151
- const spawn = git?.gitSpawn ?? gitSpawn;
152
- return ({ path }) => {
153
- const tails = specifierTails(path);
154
- const pattern = buildProbePattern(tails);
155
- const args = ['grep', '-n', '-E', '--full-name', pattern, baseBranchRef];
156
- const probe = `git ${args.map(shellQuote).join(' ')}`;
157
- const result = spawn(cwd ?? process.cwd(), ...args);
158
- // git grep exits 1 on "no matches" — an empty result, not a failure.
159
- if (result.status !== 0) return { count: 0, files: [], probe };
160
- const files = new Set();
161
- for (const line of result.stdout.split('\n')) {
162
- if (line.trim().length === 0) continue;
163
- const hit = parseGrepLine(line, baseBranchRef);
164
- // The deleted module's own self-references are not call sites.
165
- if (!hit || hit.path === path) continue;
166
- for (const match of hit.text.matchAll(QUOTED_RE)) {
167
- if (specifierResolvesTo(hit.path, match[1], path)) {
168
- files.add(hit.path);
169
- break;
170
- }
171
- }
172
- }
173
- const sorted = [...files].sort();
174
- return { count: sorted.length, files: sorted, probe };
175
- };
141
+ function defineHidden(validated, extras) {
142
+ for (const [key, value] of Object.entries(extras)) {
143
+ Object.defineProperty(validated, key, {
144
+ value,
145
+ enumerable: false,
146
+ configurable: true,
147
+ writable: true,
148
+ });
149
+ }
176
150
  }
177
151
 
178
152
  /**
179
- * Resolve the ref the persist gates probe against.
180
- *
181
- * The canonical resolved config carries the base branch at
182
- * `project.baseBranch` (`lib/config-resolver.js` defaults it to `main`).
183
- * This helper used to read `config.baseBranch` — a key the resolver never
184
- * produces — so every freshness / file-assumption / fan-out probe silently
185
- * targeted the literal `main` regardless of configuration. Benign in a repo
186
- * whose base branch *is* `main`; wrong for any consumer that configured
187
- * something else (Story #4541).
153
+ * Repair the mechanical `changes[]` formalities, then validate.
188
154
  *
189
- * The flat `config.baseBranch` fallback is retained for the legacy
190
- * `settings`-bag callers that pass `{ baseBranch, paths, planning }`.
155
+ * Repair before judging (Story #5312, the shape Story #5005 set for the
156
+ * verify tier): a plain-string bullet or a trailing parenthetical is
157
+ * rewritten into `{ path, assumption }` by probing the base branch, and
158
+ * each rewrite is reported on the returned array's `repairs` so the dry-run
159
+ * can print it. An entry nothing path-shaped can be salvaged from survives
160
+ * untouched and still hard-errors in `validateTaskBodies`.
191
161
  *
192
- * @param {object} [config] Resolved config, or a legacy settings bag.
193
- * @returns {string}
162
+ * @param {object[]} tickets Mutated in place.
163
+ * @param {object} config Resolved config.
164
+ * @param {{ cwd?: string, gitRunner?: Function }} [opts]
165
+ * @returns {object[] & { findings: object[], errors: string[], warnings: string[], normalizations: object[], repairs: object[], probeRef: string|null }}
194
166
  */
195
- export function resolveBaseBranchRef(config) {
196
- return config?.project?.baseBranch ?? config?.baseBranch ?? 'main';
197
- }
198
-
199
167
  export function validateTickets(tickets, config, opts = {}) {
200
- const baseBranchRef = resolveBaseBranchRef(config);
201
- const conflictPolicy = resolveConflictPolicy(config);
202
- if (typeof opts.fanOutCounter === 'function') {
203
- conflictPolicy.fanOutCounter = opts.fanOutCounter;
204
- } else {
205
- conflictPolicy.fanOutCounter = makeDefaultFanOutCounter({
168
+ const baseBranch = resolveBaseBranchRef(config);
169
+ const baseBranchRef = resolveProbeRef({ baseBranch, cwd: opts.cwd });
170
+ const repairs = repairChangeEntries(tickets, {
171
+ existsAtBase: makeExistsAtBase({
206
172
  baseBranchRef,
207
173
  cwd: opts.cwd,
208
- });
209
- }
174
+ gitRunner: opts.gitRunner,
175
+ }),
176
+ });
210
177
  const validated = validateAndNormalizeTickets(tickets, {
211
- baseBranchRef,
212
- conflictPolicy,
213
- modelCapacity: opts.modelCapacity,
178
+ baseBranchRef: baseBranchRef ?? undefined,
179
+ gitRunner: opts.gitRunner,
214
180
  // Thread the repo cwd into the AC-freshness / file-assumption git
215
181
  // probes (#4474 PR7) — without it they silently ran against
216
182
  // process.cwd(), which is only the repo root by coincidence.
217
183
  cwd: opts.cwd,
218
184
  });
219
- // Repair before judging (Story #5005): a `verify[]` entry whose tier the
220
- // validator can already infer is auto-corrected rather than rejected, so a
221
- // mechanically-fixable formality no longer costs a re-authoring round. An
222
- // uninferable entry survives untouched and still hard-errors below.
223
- normalizeVerifyTiers(validated);
185
+ validated.warnings.push(...probeSkipWarnings(baseBranch, baseBranchRef));
224
186
  validateTaskBodies(validated);
187
+ defineHidden(validated, { repairs, probeRef: baseBranchRef });
225
188
  return validated;
226
189
  }