mandrel 2.58.0 → 2.60.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/README.md +17 -12
- package/.agents/agents/acceptance-critic.md +24 -43
- package/.agents/agents/story-worker.md +18 -19
- package/.agents/docs/SDLC.md +12 -13
- package/.agents/docs/agentrc-reference.json +1 -2
- package/.agents/docs/configuration.md +29 -46
- package/.agents/docs/quality-gates.md +9 -5
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +5 -7
- package/.agents/rules/ci-remediation.md +41 -8
- package/.agents/rules/known-tooling-behavior.md +65 -15
- package/.agents/runtime-deps.json +7 -2
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +6 -11
- package/.agents/schemas/crap-baseline.schema.json +1 -1
- package/.agents/schemas/crap-report.schema.json +1 -1
- package/.agents/schemas/story-deliver-terminal.schema.json +3 -3
- package/.agents/scripts/README.md +11 -1
- package/.agents/scripts/acceptance-eval.js +25 -27
- package/.agents/scripts/ceremony-derive.js +15 -10
- package/.agents/scripts/check-context-budget.js +148 -228
- package/.agents/scripts/check-schema-references.js +5 -3
- package/.agents/scripts/check-workflow-citations.js +33 -147
- package/.agents/scripts/coverage-capture.js +7 -4
- package/.agents/scripts/deliver-light.js +41 -100
- package/.agents/scripts/deliver-run.js +631 -0
- package/.agents/scripts/file-ci-gap.js +59 -11
- package/.agents/scripts/install-matrix-assert.js +48 -3
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +51 -33
- package/.agents/scripts/lib/baselines/crap-preview-incremental.js +6 -2
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +0 -8
- package/.agents/scripts/lib/baselines/kinds/crap.js +35 -18
- package/.agents/scripts/lib/changed-files.js +30 -0
- package/.agents/scripts/lib/config/delivery-routing.js +5 -4
- package/.agents/scripts/lib/config/explain.js +1 -3
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +1 -1
- package/.agents/scripts/lib/config-resolver.js +1 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +28 -21
- package/.agents/scripts/lib/coverage-capture-fullscope.js +10 -2
- package/.agents/scripts/lib/coverage-capture-incremental.js +3 -2
- package/.agents/scripts/lib/coverage-capture-usage.js +4 -1
- package/.agents/scripts/lib/crap-engine.js +2 -2
- package/.agents/scripts/lib/crap-utils.js +21 -5
- package/.agents/scripts/lib/doc-tiers.js +4 -2
- package/.agents/scripts/lib/escomplex-ast-compat.js +39 -17
- package/.agents/scripts/lib/escomplex-kernel.js +298 -0
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +7 -6
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +7 -5
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/gh-exec.js +160 -0
- package/.agents/scripts/lib/maintainability-engine.js +3 -3
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +74 -132
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +123 -12
- package/.agents/scripts/lib/orchestration/complexity-gate.js +180 -352
- package/.agents/scripts/lib/orchestration/light-suitability.js +71 -136
- package/.agents/scripts/lib/orchestration/plan-context.js +44 -50
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +8 -6
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +104 -119
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +41 -25
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +11 -11
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +63 -29
- package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +107 -0
- package/.agents/scripts/lib/orchestration/review-depth.js +14 -11
- package/.agents/scripts/lib/orchestration/run-epilogue.js +260 -182
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +63 -99
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +3 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +137 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +105 -18
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -3
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +156 -39
- package/.agents/scripts/lib/orchestration/story-init-envelope.js +71 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -17
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +25 -209
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +8 -5
- package/.agents/scripts/lib/orchestration/ticket-validator.js +44 -183
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +14 -25
- package/.agents/scripts/lib/runtime-deps/dep-resolution.js +155 -0
- package/.agents/scripts/lib/runtime-deps/ensure-installed.js +44 -9
- package/.agents/scripts/lib/runtime-deps/parser-major.js +110 -0
- package/.agents/scripts/lib/runtime-deps/preflight.js +6 -25
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +46 -1
- package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
- package/.agents/scripts/lib/story-body/body-format-lints.js +58 -12
- package/.agents/scripts/lib/story-body/story-body.js +83 -29
- package/.agents/scripts/lib/templates/decomposer-prompts.js +28 -33
- package/.agents/scripts/lib/wave-runner/live-probe.js +31 -5
- package/.agents/scripts/merge-baseline.js +4 -5
- package/.agents/scripts/plan-context.js +117 -28
- package/.agents/scripts/plan-persist.js +79 -39
- package/.agents/scripts/plan-run-epilogue.js +11 -8
- package/.agents/scripts/pr-watch-with-update.js +9 -2
- package/.agents/scripts/run-verify.js +13 -6
- package/.agents/scripts/single-story-init.js +7 -57
- package/.agents/scripts/stories-wave-tick.js +160 -26
- package/.agents/skills/core/gates-and-baselines/reference.md +0 -1
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
- package/.agents/workflows/audit-to-stories.md +14 -11
- package/.agents/workflows/helpers/acceptance-self-eval.md +84 -157
- package/.agents/workflows/helpers/code-review.md +4 -2
- package/.agents/workflows/helpers/deliver-digest.md +31 -24
- package/.agents/workflows/helpers/deliver-light.md +92 -101
- package/.agents/workflows/helpers/deliver-reference.md +116 -100
- package/.agents/workflows/helpers/deliver-story-reference.md +58 -124
- package/.agents/workflows/helpers/deliver-story.md +17 -18
- package/.agents/workflows/helpers/plan-reference.md +82 -60
- package/.agents/workflows/mandrel-deliver.md +47 -31
- package/.agents/workflows/mandrel-plan.md +32 -30
- package/.agents/workflows/mandrel-update.md +36 -21
- package/README.md +3 -3
- package/docs/CHANGELOG.md +43 -0
- package/lib/cli/registry.js +45 -25
- package/lib/cli/update.js +376 -17
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +40 -0
- package/package.json +8 -2
- package/.agents/schemas/model-attribution.schema.json +0 -53
- package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +0 -188
- package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
- package/.agents/scripts/lib/templates/spec-author-prompts.js +0 -76
- package/.agents/skills/core/scope-triage/SKILL.md +0 -48
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Two halves, deliberately separated by the `createIssue` boundary:
|
|
17
17
|
*
|
|
18
|
-
* 1. **`
|
|
19
|
-
* runs *before* any GitHub write.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
18
|
+
* 1. **`resolveSupersedePartition`** — the plan-time completion pass that
|
|
19
|
+
* runs *before* any GitHub write. No id may be claimed by two Stories
|
|
20
|
+
* and no Story may claim an id that was not a source ticket — both fail
|
|
21
|
+
* closed. A source id nobody claimed is assigned to the primary Story
|
|
22
|
+
* with a warning (Story #5342): the plan is replacing it either way, so
|
|
23
|
+
* the only open question was bookkeeping.
|
|
24
24
|
* 2. **`closeSupersededTickets`** — the bookkeeping pass that runs *after*
|
|
25
25
|
* the Stories exist. It **never throws**: a throw here would leave the
|
|
26
26
|
* run half-done with Stories already live. An already-closed, deleted,
|
|
@@ -279,18 +279,12 @@ function describeStoryIds(entries) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
|
-
*
|
|
282
|
+
* Index which Story claims each source id.
|
|
283
283
|
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* @param {Array<{ slug: string, supersedes: Array<{ id: number }> }>} stories
|
|
288
|
-
* @param {number[]} sourceTicketIds Ids passed to `/mandrel-plan --tickets`.
|
|
284
|
+
* @param {Array<{ slug: string, supersedes?: Array<{ id: number }> }>} list
|
|
285
|
+
* @returns {Map<number, string[]>} id → claiming slugs, in draft order.
|
|
289
286
|
*/
|
|
290
|
-
|
|
291
|
-
const list = Array.isArray(stories) ? stories : [];
|
|
292
|
-
const sources = new Set(sourceTicketIds);
|
|
293
|
-
|
|
287
|
+
function indexSupersedeClaims(list) {
|
|
294
288
|
/** @type {Map<number, string[]>} */
|
|
295
289
|
const claims = new Map();
|
|
296
290
|
for (const story of list) {
|
|
@@ -300,9 +294,47 @@ export function assertSupersedePartition(stories, sourceTicketIds = []) {
|
|
|
300
294
|
claims.set(id, owners);
|
|
301
295
|
}
|
|
302
296
|
}
|
|
297
|
+
return claims;
|
|
298
|
+
}
|
|
303
299
|
|
|
304
|
-
|
|
300
|
+
/**
|
|
301
|
+
* Complete the supersede map, refusing only what the plan gets wrong.
|
|
302
|
+
*
|
|
303
|
+
* Two halves, split by who can be right (Story #5342):
|
|
304
|
+
*
|
|
305
|
+
* - **Refused.** A Story claiming an id that was never a source ticket, and
|
|
306
|
+
* two Stories claiming the same id. Both name an intent the run cannot
|
|
307
|
+
* act on — the first would comment on and close an issue nobody asked
|
|
308
|
+
* about, the second cannot say which Story replaced it — so they fail
|
|
309
|
+
* closed, **before** `createIssue`, and no Story goes live against an
|
|
310
|
+
* inconsistent tracker.
|
|
311
|
+
* - **Assigned with a warning.** A source id no Story claimed. Every id
|
|
312
|
+
* passed to `--tickets` is being replaced by this plan by construction;
|
|
313
|
+
* which Story records it is a bookkeeping detail, and the primary Story
|
|
314
|
+
* is the answer the operator would have given. Refusing cost a whole
|
|
315
|
+
* re-author round to type back a fact the run already knew.
|
|
316
|
+
*
|
|
317
|
+
* Mutates the unclaimed ids onto the primary Story's `supersedes[]`.
|
|
318
|
+
*
|
|
319
|
+
* **`stories[0]` is the primary, and it is the *only* derivation of it**
|
|
320
|
+
* (Story #5361): `assemblePlanStories` hands this list over already sorted by
|
|
321
|
+
* `orderStoriesByDependencies`, which is the same order the create loop files
|
|
322
|
+
* the Stories in — so the `superseded-by` comment this assignment produces
|
|
323
|
+
* can never name a different Story from the checkpoint and the plan summary.
|
|
324
|
+
* A non-empty list is the caller's contract (assembly refuses an empty
|
|
325
|
+
* draft before it gets here).
|
|
326
|
+
*
|
|
327
|
+
* @param {Array<{ slug: string, supersedes: Array<{ id: number, note: string|null }> }>} stories
|
|
328
|
+
* Dependency-ordered and non-empty.
|
|
329
|
+
* @param {number[]} sourceTicketIds Ids passed to `/mandrel-plan --tickets`.
|
|
330
|
+
* @returns {string[]} One warning per id assigned by default.
|
|
331
|
+
*/
|
|
332
|
+
export function resolveSupersedePartition(stories, sourceTicketIds = []) {
|
|
333
|
+
const list = Array.isArray(stories) ? stories : [];
|
|
334
|
+
const sources = new Set(sourceTicketIds);
|
|
335
|
+
const claims = indexSupersedeClaims(list);
|
|
305
336
|
|
|
337
|
+
const errors = [];
|
|
306
338
|
for (const [id, owners] of claims) {
|
|
307
339
|
if (owners.length > 1) {
|
|
308
340
|
errors.push(
|
|
@@ -317,23 +349,25 @@ export function assertSupersedePartition(stories, sourceTicketIds = []) {
|
|
|
317
349
|
);
|
|
318
350
|
}
|
|
319
351
|
}
|
|
320
|
-
|
|
321
|
-
for (const id of sources) {
|
|
322
|
-
if (!claims.has(id)) {
|
|
323
|
-
errors.push(
|
|
324
|
-
`source ticket #${id} is not claimed by any Story's supersedes[] — ` +
|
|
325
|
-
'a partial supersede map is a planning error. Claim it, or drop it ' +
|
|
326
|
-
'from --tickets.',
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
352
|
if (errors.length > 0) {
|
|
332
353
|
throw new Error(
|
|
333
354
|
`[plan-persist] supersede partition failed with ${errors.length} ` +
|
|
334
355
|
`error(s):\n${errors.map((e) => ` - ${e}`).join('\n')}`,
|
|
335
356
|
);
|
|
336
357
|
}
|
|
358
|
+
|
|
359
|
+
const primary = list[0];
|
|
360
|
+
const warnings = [];
|
|
361
|
+
for (const id of sources) {
|
|
362
|
+
if (claims.has(id)) continue;
|
|
363
|
+
primary.supersedes = [...(primary.supersedes ?? []), { id, note: null }];
|
|
364
|
+
warnings.push(
|
|
365
|
+
`source ticket #${id} was claimed by no Story's supersedes[] — ` +
|
|
366
|
+
`assigned to the primary Story "${primary.slug}". Author the claim ` +
|
|
367
|
+
'explicitly if another Story is the one that replaces it.',
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return warnings;
|
|
337
371
|
}
|
|
338
372
|
|
|
339
373
|
/**
|
|
@@ -567,7 +601,7 @@ export async function closeSupersededTickets({
|
|
|
567
601
|
});
|
|
568
602
|
},
|
|
569
603
|
// The per-source-ticket close (Story #4952) fans out across **distinct**
|
|
570
|
-
// tickets — `
|
|
604
|
+
// tickets — `resolveSupersedePartition` has already failed the run closed
|
|
571
605
|
// if two Stories claim the same id, so no two units in flight can touch
|
|
572
606
|
// the same issue. Within one unit the probe → comment → close sequence
|
|
573
607
|
// stays strictly ordered: commenting on an issue the probe reported
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wave-collision-gate.js — the split gate (Story #5332).
|
|
3
|
+
*
|
|
4
|
+
* Kept out of `wave-serialisation.js` because it answers a different
|
|
5
|
+
* question. That module *predicts* what the dispatcher will do with a draft
|
|
6
|
+
* and renders the prediction as a receipt; this one decides whether the draft
|
|
7
|
+
* may be created at all, and so owns both the one enumeration the refusal and
|
|
8
|
+
* the receipt share and the shape reconciliation that enumeration needs.
|
|
9
|
+
*
|
|
10
|
+
* @module lib/orchestration/plan-persist/wave-collision-gate
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { predictWaveSerialisation } from './wave-serialisation.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Expose an assembled Story's declared footprint where `storyFootprint`
|
|
17
|
+
* looks for it.
|
|
18
|
+
*
|
|
19
|
+
* `assemblePlanStories` returns the persisted artifact — `{ slug, title,
|
|
20
|
+
* body, bodyObject, acceptance, depends_on, … }` — and carries the parsed
|
|
21
|
+
* `changes[]` inside `bodyObject`, not at the top level. `storyFootprint`
|
|
22
|
+
* reads `files` / `changes` / `changeset` only, so from Story #5313 (which
|
|
23
|
+
* retired the body scrape that had been widening the footprint out of the
|
|
24
|
+
* markdown) until this Story the production call saw an empty footprint for
|
|
25
|
+
* every assembled Story and predicted nothing — the unit fixtures passed a
|
|
26
|
+
* top-level `changes` and so could not show it. The prediction is now the
|
|
27
|
+
* split gate, and a gate that cannot see a declaration cannot fire, so the
|
|
28
|
+
* shapes are reconciled here rather than by widening the dispatcher's own
|
|
29
|
+
* predicate: the runtime's records (`resolve-stories.js`) already carry
|
|
30
|
+
* `changes` at the top level and pass through untouched.
|
|
31
|
+
*
|
|
32
|
+
* @param {object} story
|
|
33
|
+
* @returns {object} The same record, with a top-level `changes` when one can
|
|
34
|
+
* be resolved from its `bodyObject`.
|
|
35
|
+
*/
|
|
36
|
+
function withDeclaredFootprint(story) {
|
|
37
|
+
if (!story || typeof story !== 'object') return story;
|
|
38
|
+
const declared = [story.files, story.changes, story.changeset].some(
|
|
39
|
+
(shape) => Array.isArray(shape) && shape.length > 0,
|
|
40
|
+
);
|
|
41
|
+
if (declared) return story;
|
|
42
|
+
const fromBody = story.bodyObject?.changes;
|
|
43
|
+
return Array.isArray(fromBody) ? { ...story, changes: fromBody } : story;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Render one refused pair as a report line.
|
|
48
|
+
*
|
|
49
|
+
* @param {{ wave: number, slugs: [string, string], paths: string[], source: string }} collision
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
function formatCollision({ wave, slugs, paths, source }) {
|
|
53
|
+
const declared = paths.map((p) => `\`${p}\``).join(', ');
|
|
54
|
+
return ` - wave ${wave}: "${slugs[0]}" + "${slugs[1]}" both declare ${declared} (${source})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Compute the same-wave collisions of a draft and refuse an N>1 draft that
|
|
59
|
+
* has any — the split gate.
|
|
60
|
+
*
|
|
61
|
+
* ADR `20260912-5312` deleted every numeric plan-time ceiling and left the
|
|
62
|
+
* default-single policy enforced by prose plus `assertAcceptancePartition`,
|
|
63
|
+
* which refused only byte-identical acceptance text across siblings — a shape
|
|
64
|
+
* model output does not produce. The measured result was a plan of 18 Stories
|
|
65
|
+
* whose own summary comment recorded 39 shared files across 14 same-wave
|
|
66
|
+
* Stories: the plan refuted its own parallelism claim, after persist, with
|
|
67
|
+
* nothing acting on it.
|
|
68
|
+
*
|
|
69
|
+
* So the gate is the dispatcher's own predicate rather than a proxy for it. A
|
|
70
|
+
* pair {@link predictWaveSerialisation} names is a pair
|
|
71
|
+
* `stories-wave-tick.js` will refuse to co-dispatch, so the split buys no
|
|
72
|
+
* parallelism while still paying a delivery session per Story. Two remedies,
|
|
73
|
+
* both the author's to take before anything is created: merge the pair into
|
|
74
|
+
* the one Story it already is, or order it with `depends_on` so the members
|
|
75
|
+
* land in different waves.
|
|
76
|
+
*
|
|
77
|
+
* **N=1 can never trip it.** A single-Story draft has no pair to score, so
|
|
78
|
+
* the prediction is empty by construction.
|
|
79
|
+
*
|
|
80
|
+
* The computed collisions are **returned** so the caller hands the same value
|
|
81
|
+
* to the plan-summary receipt instead of recomputing it — a recomputation is
|
|
82
|
+
* how the refusal and the receipt would come to disagree.
|
|
83
|
+
*
|
|
84
|
+
* @param {ReturnType<typeof import('./summary.js').buildWaveTable>} waveTable
|
|
85
|
+
* @param {Array<object>} stories The assembled Stories, in draft order.
|
|
86
|
+
* @param {{ tempRoot?: string }} [options] Threaded to the predicate.
|
|
87
|
+
* @returns {ReturnType<typeof predictWaveSerialisation>}
|
|
88
|
+
* @throws {Error} When an N>1 draft has at least one colliding same-wave pair.
|
|
89
|
+
*/
|
|
90
|
+
export function assertNoWaveCollisions(waveTable, stories, options = {}) {
|
|
91
|
+
const list = Array.isArray(stories) ? stories : [];
|
|
92
|
+
const collisions = predictWaveSerialisation(
|
|
93
|
+
waveTable,
|
|
94
|
+
list.map(withDeclaredFootprint),
|
|
95
|
+
options,
|
|
96
|
+
);
|
|
97
|
+
if (list.length <= 1 || collisions.length === 0) return collisions;
|
|
98
|
+
throw new Error(
|
|
99
|
+
`[plan-persist] ${collisions.length} same-wave collision(s) — the ` +
|
|
100
|
+
'dispatcher will refuse to co-dispatch these pairs, so the split buys ' +
|
|
101
|
+
'no parallelism and costs a delivery session per Story:\n' +
|
|
102
|
+
`${collisions.map(formatCollision).join('\n')}\n` +
|
|
103
|
+
'Remedy: merge each pair into the one Story it already is (its stages ' +
|
|
104
|
+
'belong in `## Slicing`), or order the pair with `depends_on` so the ' +
|
|
105
|
+
'members sit in different waves.',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -75,19 +75,22 @@ export const DEFAULT_DIFF_WIDTH = Object.freeze({
|
|
|
75
75
|
* `audit-rules.json`?
|
|
76
76
|
*
|
|
77
77
|
* This is the **single source** of the derived level — the review depth
|
|
78
|
-
* ({@link resolveDepth})
|
|
79
|
-
* (`
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
78
|
+
* ({@link resolveDepth}) and the dispatch-side complexity routing
|
|
79
|
+
* (`complexity-gate.js#deriveStoryShape`, Story #4722) both consume what this
|
|
80
|
+
* returns, so no risk decision can disagree about how risky a change is.
|
|
81
|
+
* Dispatch reads the **predicted** shape (the Story's declared `changes[]`
|
|
82
|
+
* footprint) and close reads the **actual** diff — one taxonomy, two read
|
|
83
|
+
* points, which is what keeps a lite-shaped Story whose footprint touches a
|
|
84
|
+
* sensitive path on the full route and under a deep review.
|
|
85
|
+
*
|
|
86
|
+
* The acceptance verdict owner is **not** downstream of this level: Story
|
|
87
|
+
* #5343 re-based `ceremony-routing.js#resolveCeremonyForRisk` on the ceremony
|
|
88
|
+
* profile alone, and Story #5366 removed the level from its signature.
|
|
86
89
|
*
|
|
87
90
|
* Returns `null` — the fail-safe "no derivable signal" level — when the change
|
|
88
|
-
* set is empty/unknown or the manifest cannot be read. Both
|
|
89
|
-
*
|
|
90
|
-
* `
|
|
91
|
+
* set is empty/unknown or the manifest cannot be read. Both consumers treat
|
|
92
|
+
* `null` as the more thorough posture (`standard` depth, the conservative
|
|
93
|
+
* `full` route), so a derivation failure never buys a change less checking.
|
|
91
94
|
*
|
|
92
95
|
* Total: never throws.
|
|
93
96
|
*
|