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
|
@@ -35,7 +35,11 @@ import {
|
|
|
35
35
|
INTAKE_VERDICTS,
|
|
36
36
|
REFUSED_VERDICT,
|
|
37
37
|
} from './lib/orchestration/ci-gap-intake.js';
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
RERUN_ALLOWANCE_VERDICTS,
|
|
40
|
+
readCiDigest,
|
|
41
|
+
recordRerunAllowance,
|
|
42
|
+
} from './lib/orchestration/ci-rerun-guard.js';
|
|
39
43
|
import {
|
|
40
44
|
STATE_LABELS,
|
|
41
45
|
transitionTicketState,
|
|
@@ -55,7 +59,7 @@ const USAGE = {
|
|
|
55
59
|
],
|
|
56
60
|
[
|
|
57
61
|
'--verdict <verdict>',
|
|
58
|
-
`One of ${INTAKE_VERDICTS.join(' | ')}. "${REFUSED_VERDICT}" is refused: it routes to Option 1, fix at source.`,
|
|
62
|
+
`One of ${INTAKE_VERDICTS.join(' | ')}. "${REFUSED_VERDICT}" is refused: it routes to Option 1, fix at source. ${RERUN_ALLOWANCE_VERDICTS.join(' / ')} also record the one-rerun allowance on the CI digest.`,
|
|
59
63
|
],
|
|
60
64
|
[
|
|
61
65
|
'--owner <bucket>',
|
|
@@ -138,7 +142,12 @@ function liveIntakePorts({ provider, searchRepo, cwd, logger }) {
|
|
|
138
142
|
* @param {object} opts
|
|
139
143
|
* @returns {string}
|
|
140
144
|
*/
|
|
141
|
-
export function renderFrictionComment({
|
|
145
|
+
export function renderFrictionComment({
|
|
146
|
+
verdict,
|
|
147
|
+
result,
|
|
148
|
+
digest,
|
|
149
|
+
rerunAllowance = null,
|
|
150
|
+
}) {
|
|
142
151
|
const target = result.issue?.url ?? result.issue?.number ?? '(not filed)';
|
|
143
152
|
const lines = [
|
|
144
153
|
`### CI gap filed — verdict \`${verdict}\``,
|
|
@@ -158,14 +167,31 @@ export function renderFrictionComment({ verdict, result, digest }) {
|
|
|
158
167
|
`- **Routing:** deferred from \`${result.routing.deferredFrom}\` (${result.routing.deferralReason}) — filed locally instead.`,
|
|
159
168
|
);
|
|
160
169
|
}
|
|
161
|
-
lines.push(
|
|
162
|
-
'',
|
|
163
|
-
'This verdict does **not** license a re-run of the failed job. Graduate the',
|
|
164
|
-
'intake issue with `/mandrel-plan <issue number>` to turn it into a Story.',
|
|
165
|
-
);
|
|
170
|
+
lines.push('', renderRerunLine(rerunAllowance), GRADUATE_LINE);
|
|
166
171
|
return lines.join('\n');
|
|
167
172
|
}
|
|
168
173
|
|
|
174
|
+
/** The graduation instruction every filing carries, verdict-independent. */
|
|
175
|
+
const GRADUATE_LINE =
|
|
176
|
+
'Graduate the intake issue with `/mandrel-plan <issue number>` to turn it into a Story.';
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* What this filing says about rerunning the failed job (Story #5343). An
|
|
180
|
+
* allowance is stated with its head SHA and its one-shot bound, because the
|
|
181
|
+
* comment is where an operator reads whether the rerun they are about to do
|
|
182
|
+
* is the sanctioned one.
|
|
183
|
+
*
|
|
184
|
+
* @param {{ headSha: string } | null} rerunAllowance
|
|
185
|
+
* @returns {string}
|
|
186
|
+
*/
|
|
187
|
+
function renderRerunLine(rerunAllowance) {
|
|
188
|
+
return rerunAllowance
|
|
189
|
+
? 'This verdict is environmental and proven, so **one** rerun of the failed ' +
|
|
190
|
+
`job is now admitted on head \`${rerunAllowance.headSha}\` — and one only. ` +
|
|
191
|
+
'A second red after it is real and routes to Option 1.'
|
|
192
|
+
: 'This verdict does **not** license a re-run of the failed job.';
|
|
193
|
+
}
|
|
194
|
+
|
|
169
195
|
/**
|
|
170
196
|
* File the CI-gap intake issue for one Story, post the `friction` comment,
|
|
171
197
|
* and optionally flip the Story to `agent::blocked`.
|
|
@@ -198,7 +224,12 @@ export async function runFileCiGap({
|
|
|
198
224
|
throw new Error('--story <id> is required (a positive issue number).');
|
|
199
225
|
}
|
|
200
226
|
const resolved = config ?? resolveConfig();
|
|
201
|
-
|
|
227
|
+
// The digest's temp root, resolved exactly as `pr-watch-with-update.js`
|
|
228
|
+
// resolves it when it WRITES the digest — the CLI never passed one, so the
|
|
229
|
+
// default read crashed on `undefined` before it could find the file.
|
|
230
|
+
const digestRoot = tempRoot ?? resolved?.project?.paths?.tempRoot ?? 'temp';
|
|
231
|
+
const ciDigest =
|
|
232
|
+
digest ?? readCiDigest({ storyId: sid, tempRoot: digestRoot, cwd });
|
|
202
233
|
if (!ciDigest) {
|
|
203
234
|
throw new Error(
|
|
204
235
|
`no CI digest for Story #${sid}. The digest is written by \`pr-watch-with-update.js --story ${sid}\` on the first red; without it there is no run link or failure signature to file.`,
|
|
@@ -237,9 +268,26 @@ export async function runFileCiGap({
|
|
|
237
268
|
now,
|
|
238
269
|
});
|
|
239
270
|
|
|
271
|
+
// Story #5343 — a proven-environmental verdict earns the one same-SHA
|
|
272
|
+
// rerun the watcher's guard will admit. Recorded on the digest, keyed to
|
|
273
|
+
// the head SHA the red was observed on, and never on a dry run.
|
|
274
|
+
const rerunAllowance = dryRun
|
|
275
|
+
? null
|
|
276
|
+
: recordRerunAllowance({
|
|
277
|
+
storyId: sid,
|
|
278
|
+
verdict,
|
|
279
|
+
tempRoot: digestRoot,
|
|
280
|
+
cwd,
|
|
281
|
+
});
|
|
282
|
+
|
|
240
283
|
const actions = { commented: false, blocked: false };
|
|
241
284
|
if (!dryRun && ticketing) {
|
|
242
|
-
const body = renderFrictionComment({
|
|
285
|
+
const body = renderFrictionComment({
|
|
286
|
+
verdict,
|
|
287
|
+
result,
|
|
288
|
+
digest: ciDigest,
|
|
289
|
+
rerunAllowance,
|
|
290
|
+
});
|
|
243
291
|
try {
|
|
244
292
|
await upsertStructuredComment(ticketing, sid, 'friction', body);
|
|
245
293
|
actions.commented = true;
|
|
@@ -258,7 +306,7 @@ export async function runFileCiGap({
|
|
|
258
306
|
}
|
|
259
307
|
}
|
|
260
308
|
|
|
261
|
-
return { storyId: sid, verdict, ...result, actions };
|
|
309
|
+
return { storyId: sid, verdict, ...result, actions, rerunAllowance };
|
|
262
310
|
}
|
|
263
311
|
|
|
264
312
|
/**
|
|
@@ -59,17 +59,62 @@ import path from 'node:path';
|
|
|
59
59
|
* `.agents/runtime-deps.json` and provided by the consumer's install of
|
|
60
60
|
* `mandrel` (npm hoists them into node_modules), but they MUST NOT
|
|
61
61
|
* appear in the consumer's *declared* package.json dependencies. Kept in sync
|
|
62
|
-
* with `.agents/runtime-deps.json` `dependencies` keys
|
|
62
|
+
* with `.agents/runtime-deps.json` `dependencies` keys — the whole set, so
|
|
63
|
+
* this list documents the closure; {@link SHAREABLE_RUNTIME_DEPS} carries the
|
|
64
|
+
* policy about which of them a consumer may also declare.
|
|
63
65
|
*/
|
|
64
66
|
const FRAMEWORK_RUNTIME_DEPS = [
|
|
67
|
+
'@babel/parser',
|
|
65
68
|
'ajv',
|
|
66
69
|
'ajv-formats',
|
|
70
|
+
'babel-runtime',
|
|
71
|
+
'escomplex-plugin-metrics-module',
|
|
72
|
+
'escomplex-plugin-syntax-babylon',
|
|
67
73
|
'js-yaml',
|
|
68
74
|
'minimatch',
|
|
69
75
|
'picomatch',
|
|
70
|
-
'typhonjs-
|
|
76
|
+
'typhonjs-ast-walker',
|
|
77
|
+
'typhonjs-escomplex-commons',
|
|
71
78
|
];
|
|
72
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Runtime dependencies the framework declares but does **not** claim
|
|
82
|
+
* exclusively, so a consumer declaring one is not evidence of a mutated
|
|
83
|
+
* manifest.
|
|
84
|
+
*
|
|
85
|
+
* This check exists to catch a consumer's `package.json` being written into
|
|
86
|
+
* with framework-internal packages. That inference only holds for packages
|
|
87
|
+
* nobody else would plausibly declare. `@babel/parser` and `babel-runtime`
|
|
88
|
+
* fail that test completely: a repository with its own Babel pipeline, AST
|
|
89
|
+
* tooling or legacy transpile output declares them for its own reasons, and
|
|
90
|
+
* failing `manifest-clean` for that would be a false positive on an ordinary
|
|
91
|
+
* consumer rather than a caught mutation.
|
|
92
|
+
*
|
|
93
|
+
* Kept as an explicit list rather than a heuristic: adding a widely-used
|
|
94
|
+
* package to the framework's runtime closure should be a deliberate decision
|
|
95
|
+
* to stop policing it, recorded here.
|
|
96
|
+
*/
|
|
97
|
+
const SHAREABLE_RUNTIME_DEPS = ['@babel/parser', 'babel-runtime'];
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Is this framework dependency's presence in a consumer manifest evidence of a
|
|
101
|
+
* mutated manifest?
|
|
102
|
+
*
|
|
103
|
+
* Only for packages the framework claims exclusively. A shareable one is
|
|
104
|
+
* declared by ordinary repositories for their own reasons.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} dep
|
|
107
|
+
* @param {Record<string, string>} declared Consumer's merged declared deps.
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
*/
|
|
110
|
+
function isLeak(dep, declared) {
|
|
111
|
+
if (!(dep in declared)) return false;
|
|
112
|
+
return !SHAREABLE_RUNTIME_DEPS.includes(dep);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Exported for the manifest-mirror drift assertion. */
|
|
116
|
+
export { FRAMEWORK_RUNTIME_DEPS, SHAREABLE_RUNTIME_DEPS };
|
|
117
|
+
|
|
73
118
|
/** The verdict marker `mandrel doctor` prints when every check passes. */
|
|
74
119
|
const DOCTOR_READY_MARKER = '✅ Ready';
|
|
75
120
|
|
|
@@ -157,7 +202,7 @@ export function checkManifestClean({ consumer, packageName, fs = nodeFs }) {
|
|
|
157
202
|
...(manifest.peerDependencies ?? {}),
|
|
158
203
|
};
|
|
159
204
|
|
|
160
|
-
const leaked = FRAMEWORK_RUNTIME_DEPS.filter((dep) => dep
|
|
205
|
+
const leaked = FRAMEWORK_RUNTIME_DEPS.filter((dep) => isLeak(dep, declared));
|
|
161
206
|
if (leaked.length > 0) {
|
|
162
207
|
return {
|
|
163
208
|
ok: false,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Problem Statement (aggregated severity profile)
|
|
10
10
|
* - Recommended Direction (rollup of recommendations by dimension)
|
|
11
11
|
* - Key Assumptions (carries the source-report links forward)
|
|
12
|
-
* - MVP Scope (the
|
|
12
|
+
* - MVP Scope (the findings themselves, flat — Story #5332)
|
|
13
13
|
* - Key Files (explicit file paths so `/mandrel-plan` authoring has concrete
|
|
14
14
|
* anchors)
|
|
15
15
|
* - Not Doing (out-of-scope items by convention)
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
|
|
20
20
|
import { SEVERITIES } from '../findings/severity.js';
|
|
21
21
|
import { auditLabelFooterForFindings } from './audit-label-taxonomy.js';
|
|
22
|
-
import { formatEpicGrouping } from './epic-grouping-directive.js';
|
|
23
22
|
import {
|
|
24
23
|
renderFingerprintFooter,
|
|
25
24
|
renderSemanticKeyFooter,
|
|
@@ -93,35 +92,56 @@ function formatRecommendedDirection(findings) {
|
|
|
93
92
|
return lines.join('\n');
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
/**
|
|
96
|
+
* The findings, flat (Story #5332).
|
|
97
|
+
*
|
|
98
|
+
* This section used to render one numbered bullet per `groupFindings` group
|
|
99
|
+
* under a `## Grouping` directive — a partition the seed had already decided
|
|
100
|
+
* before the planner read a word of it, and at a grain (`groupFindings`'s) the
|
|
101
|
+
* planner's cohesion judgment never got to review. The measured result was a
|
|
102
|
+
* sweep of 44 findings arriving as 18 Stories. The seed now states what was
|
|
103
|
+
* found and lets N reach the planner undecided; container grouping is Gate
|
|
104
|
+
* #3's call at persist, where N is known.
|
|
105
|
+
*
|
|
106
|
+
* @param {object[]} findings
|
|
107
|
+
* @returns {string}
|
|
108
|
+
*/
|
|
109
|
+
function formatFindingsList(findings) {
|
|
110
|
+
return findings
|
|
111
|
+
.map((f) => {
|
|
112
|
+
const label = DIMENSION_LABEL[f.dimension] ?? f.dimension;
|
|
113
|
+
const file = f.files?.[0] ? ` (\`${f.files[0]}\`)` : '';
|
|
114
|
+
const severity = f.severity ? `${f.severity} · ` : '';
|
|
115
|
+
return `- **${f.title}** — ${severity}${label}${file}`;
|
|
116
|
+
})
|
|
117
|
+
.join('\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The machine-readable dedup identity, one footer set per group.
|
|
122
|
+
*
|
|
123
|
+
* Deliberately **not** folded into one footer over the whole sweep, and
|
|
124
|
+
* deliberately not attached to a visible bullet. Each group's fingerprint and
|
|
125
|
+
* location-based semantic-key footers are the identity the next sweep matches
|
|
126
|
+
* on (Story #4626), and the `audit::*` labels are the reason it ever looks at
|
|
127
|
+
* the issue at all — an indexed sweep answers exact lookups from the labelled
|
|
128
|
+
* pool without reaching the provider, so a Story missing the labels is
|
|
129
|
+
* invisible however good its fingerprints (Story #5307). They are HTML
|
|
130
|
+
* comments, so they carry no partition to the planner's eye while staying
|
|
131
|
+
* byte-identical to what the standalone-Stories path emits.
|
|
132
|
+
*
|
|
133
|
+
* @param {object[]} groups
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
function formatDedupFooters(groups) {
|
|
97
137
|
return groups
|
|
98
|
-
.map((g
|
|
99
|
-
const dims = g.dimensions.join(' / ');
|
|
100
|
-
const file = g.files[0] ? ` (\`${g.files[0]}\`)` : '';
|
|
101
|
-
// Carry each group's fingerprint (and location-based semantic-key)
|
|
102
|
-
// footer into the seed so a Story authored from it via `/mandrel-plan` inherits
|
|
103
|
-
// the dedup identity — without this the recommended `/mandrel-plan --seed-file`
|
|
104
|
-
// path is invisible to the next sweep's dedup (Story #4626). The footers
|
|
105
|
-
// are HTML comments, so they never render in the visible one-pager but
|
|
106
|
-
// stay machine-readable for the dedup probe.
|
|
138
|
+
.map((g) => {
|
|
107
139
|
const findings = Array.isArray(g.findings) ? g.findings : [];
|
|
108
|
-
|
|
140
|
+
return [
|
|
109
141
|
renderFingerprintFooter(findings),
|
|
110
142
|
renderSemanticKeyFooter(findings),
|
|
111
|
-
// ...and the `audit::*` labels the dedup corpus is listed by.
|
|
112
|
-
//
|
|
113
|
-
// Without them a Story the chained planning path files is absent from
|
|
114
|
-
// the pool an indexed sweep matches against, and with an index in play
|
|
115
|
-
// the exact lookup is answered from that pool without ever reaching
|
|
116
|
-
// the provider — so the fingerprint footer above cannot rescue it. The
|
|
117
|
-
// two footers are therefore a pair: one carries the identity, the
|
|
118
|
-
// other carries the reason the next sweep ever looks at this issue
|
|
119
|
-
// (Story #5307).
|
|
120
143
|
auditLabelFooterForFindings(findings),
|
|
121
|
-
]
|
|
122
|
-
.map((f) => ` ${f}`)
|
|
123
|
-
.join('\n');
|
|
124
|
-
return `${idx + 1}. **${g.title}** — ${dims}${file}\n${footers}`;
|
|
144
|
+
].join('\n');
|
|
125
145
|
})
|
|
126
146
|
.join('\n');
|
|
127
147
|
}
|
|
@@ -165,10 +185,10 @@ export function buildPlanSeedMarkdown({ groups, findings, sourceReports }) {
|
|
|
165
185
|
}
|
|
166
186
|
const problem = formatProblemStatement(findings);
|
|
167
187
|
const direction = formatRecommendedDirection(findings);
|
|
168
|
-
const scope =
|
|
188
|
+
const scope = formatFindingsList(findings);
|
|
169
189
|
const files = formatKeyFiles(groups);
|
|
170
190
|
const assumptions = formatKeyAssumptions(sourceReports);
|
|
171
|
-
const
|
|
191
|
+
const dedupFooters = formatDedupFooters(groups);
|
|
172
192
|
|
|
173
193
|
return [
|
|
174
194
|
'# Idea Seed: Audit Remediation',
|
|
@@ -187,16 +207,14 @@ export function buildPlanSeedMarkdown({ groups, findings, sourceReports }) {
|
|
|
187
207
|
'',
|
|
188
208
|
'## MVP Scope',
|
|
189
209
|
'',
|
|
190
|
-
scope || '_(no
|
|
210
|
+
scope || '_(no findings)_',
|
|
211
|
+
'',
|
|
212
|
+
dedupFooters,
|
|
191
213
|
'',
|
|
192
214
|
'## Key Files',
|
|
193
215
|
'',
|
|
194
216
|
files,
|
|
195
217
|
'',
|
|
196
|
-
'## Grouping',
|
|
197
|
-
'',
|
|
198
|
-
grouping,
|
|
199
|
-
'',
|
|
200
218
|
'## Not Doing',
|
|
201
219
|
'',
|
|
202
220
|
'- Findings with severity below the operator-selected threshold.',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* so the Story's opt-in wiring lands as new code, not a same-file expansion
|
|
7
7
|
* of the pre-existing preview runner.
|
|
8
8
|
*/
|
|
9
|
-
import { getChangedFiles } from '../changed-files.js';
|
|
9
|
+
import { getChangedFiles, resolveChangedFilesRef } from '../changed-files.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Resolve the `incremental` option `scanAndScore` (`crap-utils.js`) expects,
|
|
@@ -14,6 +14,10 @@ import { getChangedFiles } from '../changed-files.js';
|
|
|
14
14
|
* not be resolved — a resolution failure falls back to full-scope rather
|
|
15
15
|
* than silently relaxing the gate.
|
|
16
16
|
*
|
|
17
|
+
* The join's ref comes from `resolveChangedFilesRef` (Story #5365), the same
|
|
18
|
+
* rule capture applies: the `--changed-since` ref the preview was handed wins
|
|
19
|
+
* over a configured `baseRef`, so one hook invocation cannot resolve two.
|
|
20
|
+
*
|
|
17
21
|
* Gated by `incrementalCoverage.baselineJoin` alone (Story #5173). It MUST
|
|
18
22
|
* NOT consult `skipWhenUnchanged`: the join loosens what the gate demands,
|
|
19
23
|
* while the skip only decides whether a capture runs, so a consumer that took
|
|
@@ -36,7 +40,7 @@ export function resolveCrapPreviewIncremental({
|
|
|
36
40
|
getChangedFilesImpl = getChangedFiles,
|
|
37
41
|
}) {
|
|
38
42
|
if (crap.incrementalCoverage?.baselineJoin !== true) return null;
|
|
39
|
-
const baseRef = crap
|
|
43
|
+
const baseRef = resolveChangedFilesRef({ crap, ref: diffRef });
|
|
40
44
|
try {
|
|
41
45
|
const touchedFiles = new Set(getChangedFilesImpl({ ref: baseRef, cwd }));
|
|
42
46
|
return { touchedFiles, baselineRows };
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import { readBaselineAtRef } from '../../baseline-loader.js';
|
|
25
|
-
import { resolveEscomplexVersion } from '../../crap-utils.js';
|
|
26
25
|
import { loadBaseline } from '../../gates/baseline-store.js';
|
|
27
26
|
import {
|
|
28
27
|
loadFile as loadBaselineFile,
|
|
@@ -38,11 +37,6 @@ import {
|
|
|
38
37
|
* epic-ref callers always know their own path); without one the reader
|
|
39
38
|
* resolves the configured location for the `crap` kind itself.
|
|
40
39
|
*
|
|
41
|
-
* `escomplexVersion` is back-filled from the running scorer exactly as the
|
|
42
|
-
* deleted projection stamped it — the v2 envelope does not carry the field, and
|
|
43
|
-
* `escomplex-mismatch` is a fatal axis, so omitting it would fail every
|
|
44
|
-
* baseline closed on a value that was never on disk.
|
|
45
|
-
*
|
|
46
40
|
* Returns `null` on any read/parse/schema failure; the preview gate maps that
|
|
47
41
|
* to "no baseline" and fails open, as it always did.
|
|
48
42
|
*
|
|
@@ -69,7 +63,6 @@ function readCrapBaselineFromTree({ baselinePath, projectRoot } = {}) {
|
|
|
69
63
|
}
|
|
70
64
|
return {
|
|
71
65
|
kernelVersion: envelope.kernelVersion,
|
|
72
|
-
escomplexVersion: resolveEscomplexVersion(),
|
|
73
66
|
scoringSemantics: envelope.scoringSemantics ?? null,
|
|
74
67
|
tsTranspilerVersion:
|
|
75
68
|
typeof envelope.tsTranspilerVersion === 'string'
|
|
@@ -135,7 +128,6 @@ export function loadCrapBaseline({
|
|
|
135
128
|
// No-epicRef path delegates to readFromTree which already applies the
|
|
136
129
|
// shape-check + tsTranspilerVersion back-fill, so a tree read returns either
|
|
137
130
|
// a valid envelope or null. Epic-ref path bypasses that helper — shape-check
|
|
138
|
-
// + back-fill happens here.
|
|
139
131
|
if (!epicRef) return parsed;
|
|
140
132
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
141
133
|
return null;
|
|
@@ -53,11 +53,29 @@ export { loadCrapBaseline } from './_crap-read.js';
|
|
|
53
53
|
const __filename = fileURLToPath(import.meta.url);
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `
|
|
59
|
-
*
|
|
60
|
-
*
|
|
56
|
+
* Package whose resolved version is stamped as the CRAP scorer identity.
|
|
57
|
+
*
|
|
58
|
+
* `escomplex-plugin-metrics-module` owns the Halstead and cyclomatic math. The
|
|
59
|
+
* displaced `typhonjs-escomplex` shell contributed a parser and a plugin bus
|
|
60
|
+
* and never computed a metric, so stamping it described the shell.
|
|
61
|
+
*/
|
|
62
|
+
const SCORER_PACKAGE = 'escomplex-plugin-metrics-module';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the running scorer's version by walking up from this module's
|
|
66
|
+
* directory and reading the nearest `node_modules/<SCORER_PACKAGE>/package.json`.
|
|
67
|
+
*
|
|
68
|
+
* The package read is `escomplex-plugin-metrics-module`, which computes the
|
|
69
|
+
* metrics. It was `typhonjs-escomplex` until Story #5336 replaced that shell's
|
|
70
|
+
* parse and dispatch layers; reading a package that no longer exists would
|
|
71
|
+
* yield the sentinel below on every fresh install, and the writer would refuse
|
|
72
|
+
* to persist a baseline. (Locally it can look fine anyway — the walk-up escapes
|
|
73
|
+
* a worktree into the parent checkout, whose `node_modules` may still hold the
|
|
74
|
+
* removed package. CI's fresh clone has no such parent.)
|
|
75
|
+
*
|
|
76
|
+
* Returns `'0.0.0'` when the dependency cannot be found — callers treat that
|
|
77
|
+
* sentinel as "unknown environment" and the writer refuses to persist a
|
|
78
|
+
* baseline.
|
|
61
79
|
*
|
|
62
80
|
* @returns {string}
|
|
63
81
|
*/
|
|
@@ -68,7 +86,7 @@ export function kernelVersion() {
|
|
|
68
86
|
const pkgPath = path.join(
|
|
69
87
|
dir,
|
|
70
88
|
'node_modules',
|
|
71
|
-
|
|
89
|
+
SCORER_PACKAGE,
|
|
72
90
|
'package.json',
|
|
73
91
|
);
|
|
74
92
|
if (fs.existsSync(pkgPath)) {
|
|
@@ -695,7 +713,14 @@ export function assessComparisonBasis(compareResult, opts = {}) {
|
|
|
695
713
|
* transpiler's sourcemap. `startLine` is half the row identity key, so a
|
|
696
714
|
* transpiler change makes the rows incomparable rather than merely stale; see
|
|
697
715
|
* the `ts-transpiler-drift` axis below for the two exemptions that bound it.
|
|
698
|
-
*
|
|
716
|
+
*
|
|
717
|
+
* There is no `escomplexVersion` axis. One existed, declared `fatal`, and
|
|
718
|
+
* could not fire in either direction: the v2 envelope does not carry the
|
|
719
|
+
* field, so the loaded-envelope pass excluded it as vacuous, and the peer pass
|
|
720
|
+
* back-filled the value from the running scorer before comparing it against
|
|
721
|
+
* the running scorer. A gate that presents as fatal and cannot fail is worse
|
|
722
|
+
* than an absent one, so it was removed rather than repaired — `scoringSemantics`
|
|
723
|
+
* is the axis that actually rejects an incompatible scorer.
|
|
699
724
|
*/
|
|
700
725
|
/**
|
|
701
726
|
* The one re-seed recipe every coordinate-invalidating axis ends on. Three
|
|
@@ -713,14 +738,6 @@ export const CRAP_COMPAT_AXES = [
|
|
|
713
738
|
// missing-baseline and kernel-drift checks live in exactly one place;
|
|
714
739
|
// each per-kind table composes them in with its own kind label.
|
|
715
740
|
missingBaselineAxis('CRAP'),
|
|
716
|
-
{
|
|
717
|
-
name: 'escomplex-mismatch',
|
|
718
|
-
severity: 'fatal',
|
|
719
|
-
check: ({ baseline, runningEscomplexVersion }) =>
|
|
720
|
-
baseline && baseline.escomplexVersion !== runningEscomplexVersion
|
|
721
|
-
? `[CRAP] scorer changed from ${baseline.escomplexVersion} to ${runningEscomplexVersion} — run 'npm run crap:update'`
|
|
722
|
-
: null,
|
|
723
|
-
},
|
|
724
741
|
kernelDriftAxis('CRAP'),
|
|
725
742
|
{
|
|
726
743
|
name: 'scoring-semantics-drift',
|
|
@@ -874,9 +891,9 @@ export function evaluateBaselineCompatibility(ctx) {
|
|
|
874
891
|
* predating both questions. The fourth (Story #4969) is about the `method`
|
|
875
892
|
* half — a baseline still carrying ordinal-keyed anonymous rows.
|
|
876
893
|
*
|
|
877
|
-
* `
|
|
878
|
-
*
|
|
879
|
-
*
|
|
894
|
+
* `kernel-drift` stays out: it is a warn-level axis, and this pass turns a
|
|
895
|
+
* message into a fail-closed error. (`escomplex-mismatch` was the other
|
|
896
|
+
* exclusion until it was removed outright — see `CRAP_COMPAT_AXES`.)
|
|
880
897
|
*/
|
|
881
898
|
const LOADED_ENVELOPE_AXES = [
|
|
882
899
|
'scoring-semantics-drift',
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { createGitInterface } from './git-utils.js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the ONE git ref a step of the pre-push chain computes its
|
|
5
|
+
* changed-file set against — the whole rule, stated once (Story #5365).
|
|
6
|
+
*
|
|
7
|
+
* **A ref the caller named wins; `crap.incrementalCoverage.baseRef` is the
|
|
8
|
+
* default for a caller that named none, and `main` the default for neither.**
|
|
9
|
+
* `.husky/pre-push` captures coverage at `--ref origin/main` and then previews
|
|
10
|
+
* CRAP at `--changed-since origin/main`, and the preview only reads its own
|
|
11
|
+
* tree when the artifact under it was captured over the same change set.
|
|
12
|
+
* Letting the configured value outrank the named ref meant a consumer that set
|
|
13
|
+
* `baseRef` captured one scope while the preview scored another — precisely
|
|
14
|
+
* the stale-artifact read the capture-before-preview ordering (Story #5356)
|
|
15
|
+
* closed. This repository sets no `baseRef`, so that divergence was invisible
|
|
16
|
+
* locally and only a consumer who configured one would have paid for it.
|
|
17
|
+
*
|
|
18
|
+
* Callers that name no ref — the close-validation gate, whose argv carries no
|
|
19
|
+
* `--ref` — still get the configured value, so the key keeps the meaning it
|
|
20
|
+
* was added with.
|
|
21
|
+
*
|
|
22
|
+
* Every step that derives that change set calls this: both `coverage-capture`
|
|
23
|
+
* paths and the preview's CRAP baseline join. A new consumer routes through it
|
|
24
|
+
* rather than reading `baseRef` itself.
|
|
25
|
+
*
|
|
26
|
+
* @param {{ crap: object, ref: string | null | undefined }} opts
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
export function resolveChangedFilesRef({ crap, ref }) {
|
|
30
|
+
return ref ?? crap?.incrementalCoverage?.baseRef ?? 'main';
|
|
31
|
+
}
|
|
32
|
+
|
|
3
33
|
/**
|
|
4
34
|
* Parse the stdout from `git diff --name-only` into a normalized file list.
|
|
5
35
|
* Trims whitespace, drops blank lines, and converts backslash separators to
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* Stage 6 dropped `delivery.routing.singleDelivery` (the v1 epic
|
|
6
6
|
* single-vs-fan-out kill-switch). Story #5313 dropped
|
|
7
7
|
* `delivery.routing.freshCriticSampleRate` (the maker-checker sampling
|
|
8
|
-
* floor)
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* floor), and Story #5343 dropped the derived-level routing that replaced
|
|
9
|
+
* it: `minimal` and `standard` resolve the inline self-eval as the Story's
|
|
10
|
+
* verdict owner whatever the diff touches, and `strict` is the one profile
|
|
11
|
+
* that spawns a fresh-context critic. v2 has one Story delivery path;
|
|
12
|
+
* routing here is only about spawn boot context and the ceremony profile.
|
|
12
13
|
*
|
|
13
14
|
* `delivery.routing.roleScopedAgents` is the **kill-switch for the role-scoped
|
|
14
15
|
* boot contexts** (Epic #4478, M7-B). It defaults to `true`: a converted spawn
|
|
@@ -159,11 +159,9 @@ const KEY_MEANINGS = Object.freeze({
|
|
|
159
159
|
'delivery.routing.roleScopedAgents':
|
|
160
160
|
'Whether delivery spawns boot on role-scoped .claude/agents/<role>.md contexts.',
|
|
161
161
|
'delivery.routing.ceremonyProfile':
|
|
162
|
-
'
|
|
162
|
+
'Who authors the Story acceptance verdict: minimal and standard = the inline self-eval, strict = a fresh maker-blind critic.',
|
|
163
163
|
'delivery.routing.closeAndLand':
|
|
164
164
|
'When true, single-story-close lands through merge in one close (opt out with --no-wait-merge).',
|
|
165
|
-
'delivery.feedbackLoop.auditResultsAutoFile':
|
|
166
|
-
'When true, auto-file non-blocking audit findings as follow-up issues.',
|
|
167
165
|
'delivery.feedbackLoop.retroProposals':
|
|
168
166
|
'When true, auto-file actionable retro proposals as follow-up issues.',
|
|
169
167
|
'delivery.quality.formatAutofix.timeoutMs':
|
|
@@ -53,7 +53,7 @@ export const INCREMENTAL_COVERAGE_SCHEMA = {
|
|
|
53
53
|
type: 'string',
|
|
54
54
|
minLength: 1,
|
|
55
55
|
description:
|
|
56
|
-
'
|
|
56
|
+
'Default git ref the changed-file set is computed against, for a caller that passes no `--ref`. A `--ref` the caller named wins over this value, so a caller that anchors another gate on the same ref — `.husky/pre-push`, which passes `--ref origin/main` and then previews `--changed-since origin/main` — resolves ONE scope for both steps (Story #5365). Omitted and unpassed, the gate’s own default `main` applies.',
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
59
|
additionalProperties: false,
|
|
@@ -62,6 +62,7 @@ export { resolveListValue } from './config/shared.js';
|
|
|
62
62
|
export { validateOrchestrationConfig } from './config/validate-orchestration.js';
|
|
63
63
|
export {
|
|
64
64
|
defaultNodeModulesStrategy,
|
|
65
|
+
getWorktreeIsolation,
|
|
65
66
|
WORKTREE_ISOLATION_DEFAULTS,
|
|
66
67
|
} from './config/worktree-isolation.js';
|
|
67
68
|
export { PROJECT_ROOT } from './project-root.js';
|
|
@@ -258,12 +258,12 @@ const MERGE_WATCH_SCHEMA = {
|
|
|
258
258
|
// context; false falls back to `subagent_type: general-purpose` (the instant
|
|
259
259
|
// per-consumer revert + the escape for hosts that ignore `.claude/agents/`).
|
|
260
260
|
// Story #5313 retired `delivery.routing.freshCriticSampleRate` (the
|
|
261
|
-
// maker-checker sampling floor)
|
|
262
|
-
// the
|
|
261
|
+
// maker-checker sampling floor). Story #5343 then retired the derived-level
|
|
262
|
+
// routing it left behind: the profile alone decides the verdict owner.
|
|
263
263
|
const ROUTING_SCHEMA = {
|
|
264
264
|
type: 'object',
|
|
265
265
|
description:
|
|
266
|
-
'v2 delivery-spawn routing: role-scoped boot contexts and the ceremony profile. The v1 singleDelivery epic-route kill-switch was removed in Stage 6; the freshCriticSampleRate sampling floor was retired in Story #5313.',
|
|
266
|
+
'v2 delivery-spawn routing: role-scoped boot contexts and the ceremony profile. The v1 singleDelivery epic-route kill-switch was removed in Stage 6; the freshCriticSampleRate sampling floor was retired in Story #5313 and the derived-level ceremony routing in Story #5343.',
|
|
267
267
|
properties: {
|
|
268
268
|
roleScopedAgents: {
|
|
269
269
|
type: 'boolean',
|
|
@@ -275,7 +275,7 @@ const ROUTING_SCHEMA = {
|
|
|
275
275
|
type: 'string',
|
|
276
276
|
enum: ['minimal', 'standard', 'strict'],
|
|
277
277
|
description:
|
|
278
|
-
'Acceptance-ceremony depth. minimal =
|
|
278
|
+
'Acceptance-ceremony depth — who authors the Story acceptance verdict. minimal and standard (default) = the inline self-eval, whatever the diff touches; strict = a fresh-context maker-blind critic. Review depth is a separate decision and still derives `deep` for any sensitive path (review-depth.js).',
|
|
279
279
|
default: DELIVERY_ROUTING_DEFAULTS.ceremonyProfile,
|
|
280
280
|
},
|
|
281
281
|
closeAndLand: {
|
|
@@ -452,18 +452,31 @@ const REVIEW_SCHEMA = {
|
|
|
452
452
|
};
|
|
453
453
|
|
|
454
454
|
/**
|
|
455
|
-
* `delivery.feedbackLoop` — opt-
|
|
456
|
-
*
|
|
457
|
-
* via `graduator-core.js#makeIsAutoFileEnabled`)
|
|
458
|
-
* (auto-file on); set any to `false` to suppress auto-filing the
|
|
459
|
-
* corresponding non-blocking findings as follow-up issues.
|
|
455
|
+
* `delivery.feedbackLoop` — the **opt-in** toggle consumed by the retro
|
|
456
|
+
* auto-file graduator (`lib/feedback-loop/retro-proposals-graduator.js`, read
|
|
457
|
+
* via `graduator-core.js#makeIsAutoFileEnabled`), plus the friction window.
|
|
460
458
|
*
|
|
461
|
-
* `
|
|
462
|
-
*
|
|
459
|
+
* `auditResultsAutoFile` used to sit beside `retroProposals` here. Its
|
|
460
|
+
* graduator was deleted two releases ago, so by Story #5341 — which flipped
|
|
461
|
+
* its default from `true` to `false` on the measured record of what the
|
|
462
|
+
* channel produced — there was nothing left to switch either way. Story #5366
|
|
463
|
+
* removed the key: a toggle with no runtime reader reads as a live control,
|
|
464
|
+
* and a consumer that set it was configuring nothing. The
|
|
465
|
+
* `2.60.0-retire-audit-results-autofile` migration strips it from both config
|
|
466
|
+
* surfaces, because this block is closed to additional properties and a
|
|
467
|
+
* surviving key is a hard validation failure on upgrade.
|
|
468
|
+
*
|
|
469
|
+
* `retroProposals` (Story #4418) governs the retro auto-filer, and defaults to
|
|
470
|
+
* `false` for the same Story #5341 reasons: Story #5324's roll-up carried 116
|
|
471
|
+
* signals and filed nothing, and issues #4653, #4833, #4834 and #4836 are
|
|
472
|
+
* filings that were false or leaked from test fixtures. An auto-filer whose
|
|
473
|
+
* output is dominated by noise costs triage on every run and buys nothing, so
|
|
474
|
+
* a consumer that wants it now asks for it. When `true` the
|
|
475
|
+
* retro's actionable routed proposals are filed as
|
|
463
476
|
* `meta::<framework-gap|consumer-improvement>` + `friction::<category>`
|
|
464
477
|
* issues via the graduator pre-parsed-findings seam, and the rendered retro
|
|
465
478
|
* sections list the filed issue numbers instead of paste-ready `gh` command
|
|
466
|
-
* stanzas;
|
|
479
|
+
* stanzas; left `false` it renders the command stanzas.
|
|
467
480
|
*
|
|
468
481
|
* `frictionWindowDays` (Story #4850) bounds the run-scope friction recurrence
|
|
469
482
|
* window by row age. The window deliberately spans every surviving signal
|
|
@@ -475,19 +488,13 @@ const REVIEW_SCHEMA = {
|
|
|
475
488
|
const FEEDBACK_LOOP_SCHEMA = {
|
|
476
489
|
type: 'object',
|
|
477
490
|
description:
|
|
478
|
-
'Opt-
|
|
491
|
+
'Opt-in toggle for the close-time retro auto-file graduator, plus the friction recurrence window. Auto-filing defaults to OFF (Story #5341).',
|
|
479
492
|
properties: {
|
|
480
|
-
auditResultsAutoFile: {
|
|
481
|
-
type: 'boolean',
|
|
482
|
-
description:
|
|
483
|
-
'When true (default), the close-time audit-results graduator auto-files non-blocking audit-results findings as follow-up issues routed by source classification. Set to false to suppress auto-filing; findings remain accessible in the structured comments on the Story.',
|
|
484
|
-
default: true,
|
|
485
|
-
},
|
|
486
493
|
retroProposals: {
|
|
487
494
|
type: 'boolean',
|
|
488
495
|
description:
|
|
489
|
-
'When true
|
|
490
|
-
default:
|
|
496
|
+
'When true, the retro auto-files its actionable routed proposals as meta::<framework-gap|consumer-improvement> + friction::<category> issues via the graduator pre-parsed-findings seam, and the rendered retro sections list the filed issue numbers instead of paste-ready gh command stanzas. Defaults to false (Story #5341), which renders the command stanzas instead.',
|
|
497
|
+
default: false,
|
|
491
498
|
},
|
|
492
499
|
frictionWindowDays: {
|
|
493
500
|
type: 'integer',
|