mandrel 2.31.0 → 2.33.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 +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +12 -9
- package/.agents/docs/agentrc-reference.json +63 -58
- package/.agents/docs/configuration.md +275 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +2 -2
- package/.agents/schemas/agentrc.schema.json +1955 -1410
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +16 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/audit-to-stories.js +160 -41
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +159 -0
- package/.agents/scripts/check-lifecycle-lint.js +108 -124
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +81 -35
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/runners.js +38 -16
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/dependency-parser.js +20 -7
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/findings/provenance-field.js +135 -0
- package/.agents/scripts/lib/findings/route-finding.js +57 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-config-resolver.js +181 -0
- package/.agents/scripts/lib/knip-entry-sync.js +508 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +7 -26
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/footer-block.js +97 -0
- package/.agents/scripts/lib/story-body/story-body.js +18 -81
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/wave-runner/footprint.js +306 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/blocked-by-add.js +25 -10
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +21 -7
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/stories-wave-tick.js +192 -9
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +28 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +30 -2
- package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
- package/.agents/workflows/helpers/plan-reference.md +76 -0
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +58 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +14 -5
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -109,6 +109,59 @@ function tolerantMergeWatchMode(value) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Shortest override reason that can plausibly name a rejected finding. Below
|
|
114
|
+
* this, the flag is being used to silence the gate rather than to record a
|
|
115
|
+
* judgement, which is the failure mode the reason requirement exists to stop.
|
|
116
|
+
*/
|
|
117
|
+
const MIN_OVERRIDE_REASON_LENGTH = 12;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Parse `--override-review-block <reason>` — the sanctioned,
|
|
121
|
+
* logged override of a Story-scope code-review critical blocker.
|
|
122
|
+
*
|
|
123
|
+
* The reason is **mandatory and validated**, because the alternative it
|
|
124
|
+
* replaces is not "no override" but `gh pr merge` run by hand: before this
|
|
125
|
+
* flag, a review blocker the operator had reviewed and rejected left bypassing
|
|
126
|
+
* the gate entirely as the only way to land, and that bypass wrote nothing
|
|
127
|
+
* down anywhere. An override that records why is strictly more auditable than
|
|
128
|
+
* the hand-merge it displaces; an override that records nothing is not, so a
|
|
129
|
+
* bare or blank `--override-review-block` fails closed here — before any phase
|
|
130
|
+
* runs, at no mutation cost — rather than arming a silent one.
|
|
131
|
+
*
|
|
132
|
+
* @param {unknown} value
|
|
133
|
+
* @returns {string|undefined} the trimmed reason, or `undefined` when absent.
|
|
134
|
+
*/
|
|
135
|
+
export function parseOverrideReviewBlock(value) {
|
|
136
|
+
if (value == null) return undefined;
|
|
137
|
+
// `parseArgs` with `strict: false` yields `true` for a bare string flag.
|
|
138
|
+
const reason = typeof value === 'string' ? value.trim() : '';
|
|
139
|
+
if (reason.length >= MIN_OVERRIDE_REASON_LENGTH) return reason;
|
|
140
|
+
throw new Error(
|
|
141
|
+
'--override-review-block requires a reason of at least ' +
|
|
142
|
+
`${MIN_OVERRIDE_REASON_LENGTH} characters naming the finding you reviewed ` +
|
|
143
|
+
`and rejected (got ${JSON.stringify(value)}). The reason is posted to the ` +
|
|
144
|
+
'PR and the Story and recorded as friction telemetry.',
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* {@link parseOverrideReviewBlock} degraded to absent instead of throwing, for
|
|
150
|
+
* the tolerant reporting parse. Same contract as
|
|
151
|
+
* {@link tolerantMergeWatchMode}: safe only because a tolerant parse runs no
|
|
152
|
+
* phase.
|
|
153
|
+
*
|
|
154
|
+
* @param {unknown} value
|
|
155
|
+
* @returns {string|undefined}
|
|
156
|
+
*/
|
|
157
|
+
function tolerantOverrideReviewBlock(value) {
|
|
158
|
+
try {
|
|
159
|
+
return parseOverrideReviewBlock(value);
|
|
160
|
+
} catch {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
112
165
|
/**
|
|
113
166
|
* Standardized CLI argument parser for sprint scripts.
|
|
114
167
|
* Supports options like --epic, --story, --dry-run, --skip-dashboard.
|
|
@@ -147,6 +200,11 @@ export function parseSprintArgs(
|
|
|
147
200
|
// Absent means "use the config"; see `parseMergeWatchMode` for why an
|
|
148
201
|
// unrecognized value fails closed instead of degrading to absent.
|
|
149
202
|
'merge-watch-mode': { type: 'string' },
|
|
203
|
+
// Sanctioned override of a code-review critical blocker.
|
|
204
|
+
// Absent means "the blocker blocks"; see `parseOverrideReviewBlock` for
|
|
205
|
+
// why a bare or too-short reason fails closed instead of arming a silent
|
|
206
|
+
// override.
|
|
207
|
+
'override-review-block': { type: 'string' },
|
|
150
208
|
executor: { type: 'string' },
|
|
151
209
|
cwd: { type: 'string' },
|
|
152
210
|
'recut-of': { type: 'string' },
|
|
@@ -183,6 +241,12 @@ export function parseSprintArgs(
|
|
|
183
241
|
mergeWatchMode: tolerant
|
|
184
242
|
? tolerantMergeWatchMode(values['merge-watch-mode'])
|
|
185
243
|
: parseMergeWatchMode(values['merge-watch-mode']),
|
|
244
|
+
// The operator's recorded reason for overriding a review
|
|
245
|
+
// blocker. `undefined` when the flag is absent, which is what keeps the
|
|
246
|
+
// blocker blocking by default.
|
|
247
|
+
overrideReviewBlock: tolerant
|
|
248
|
+
? tolerantOverrideReviewBlock(values['override-review-block'])
|
|
249
|
+
: parseOverrideReviewBlock(values['override-review-block']),
|
|
186
250
|
executor: values.executor ?? null,
|
|
187
251
|
// Resolve worktree cwd from flag or env. Empty string/whitespace → null.
|
|
188
252
|
cwd:
|
|
@@ -327,8 +327,8 @@ export function buildDefaultGates({
|
|
|
327
327
|
...buildTestGateEntry(coverageCaptureActive),
|
|
328
328
|
{
|
|
329
329
|
// Gate name kept generic ("format") so the close-orchestrator log line
|
|
330
|
-
//
|
|
331
|
-
//
|
|
330
|
+
// doesn't shift when a repo swaps biome for Prettier / dprint via
|
|
331
|
+
// `project.commands.formatCheck`. The
|
|
332
332
|
// actual command and the remediation hint resolve from config.
|
|
333
333
|
name: 'format',
|
|
334
334
|
cmd: formatCmd,
|
|
@@ -87,9 +87,9 @@ function applyChangedFileScope({ gate, spawnCwd, log }) {
|
|
|
87
87
|
* `<tempRoot>/standalone/stories/story-<id>/validation-evidence.json`
|
|
88
88
|
* keyspace. v2.0.0 removed the Epic tier and its Epic-keyed keyspace.
|
|
89
89
|
*
|
|
90
|
-
* `onGateStart` is invoked immediately before each gate's runner spawn
|
|
91
|
-
*
|
|
92
|
-
*
|
|
90
|
+
* `onGateStart` is invoked immediately before each gate's runner spawn, so a
|
|
91
|
+
* caller can record per-gate wall-clock telemetry. Errors thrown from the
|
|
92
|
+
* hook propagate.
|
|
93
93
|
*
|
|
94
94
|
* Projection advisories (Story #4776): when `baseBranch` and `storyBranch`
|
|
95
95
|
* are both supplied and every gate passed, the maintainability and CRAP
|
|
@@ -38,7 +38,6 @@
|
|
|
38
38
|
*/
|
|
39
39
|
export const ACCEPTANCE_EVAL_DEFAULTS = Object.freeze({
|
|
40
40
|
maxRounds: 2,
|
|
41
|
-
clusterCeiling: 4,
|
|
42
41
|
});
|
|
43
42
|
|
|
44
43
|
/**
|
|
@@ -51,22 +50,6 @@ export const ACCEPTANCE_EVAL_DEFAULTS = Object.freeze({
|
|
|
51
50
|
*/
|
|
52
51
|
export const ACCEPTANCE_EVAL_MAX_ROUNDS_CEILING = 5;
|
|
53
52
|
|
|
54
|
-
/**
|
|
55
|
-
* Hard, undisableable ceiling on `clusterCeiling` — the max ACs one
|
|
56
|
-
* single-delivery acceptance critic scores in a single fresh-context pass
|
|
57
|
-
* (Epic #4475, M4-B, design §S2a). It is the **acceptance-dilution guard**:
|
|
58
|
-
* single delivery collapses the whole Epic into one session, so the only
|
|
59
|
-
* acceptance coverage left is the per-AC-cluster critic fan-out
|
|
60
|
-
* (`ceil(totalACs / clusterCeiling)` independent maker-blind passes). A
|
|
61
|
-
* pathologically large `clusterCeiling` would collapse that fan-out to a
|
|
62
|
-
* single critic scoring every AC at once — exactly the dilution the design
|
|
63
|
-
* forecloses — so a configured value above this ceiling is clamped down to
|
|
64
|
-
* it. Kept small on purpose.
|
|
65
|
-
*
|
|
66
|
-
* @type {number}
|
|
67
|
-
*/
|
|
68
|
-
export const ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX = 8;
|
|
69
|
-
|
|
70
53
|
/**
|
|
71
54
|
* Clamp a candidate round count into the inviolable `[1, ceiling]` range.
|
|
72
55
|
* Non-integer / non-finite inputs fall back to the documented default.
|
|
@@ -85,43 +68,19 @@ function clampRounds(value, fallback) {
|
|
|
85
68
|
return candidate;
|
|
86
69
|
}
|
|
87
70
|
|
|
88
|
-
/**
|
|
89
|
-
* Clamp a candidate cluster ceiling into the inviolable
|
|
90
|
-
* `[1, ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX]` range. Non-integer / non-finite
|
|
91
|
-
* inputs fall back to the documented default. Mirrors `clampRounds` — the
|
|
92
|
-
* anti-dilution guard cannot be disabled (`clusterCeiling: 0` or a negative
|
|
93
|
-
* clamps up to 1; an over-max value clamps down to the hard cap).
|
|
94
|
-
*
|
|
95
|
-
* @param {unknown} value
|
|
96
|
-
* @param {number} fallback
|
|
97
|
-
* @returns {number}
|
|
98
|
-
*/
|
|
99
|
-
function clampClusterCeiling(value, fallback) {
|
|
100
|
-
const candidate =
|
|
101
|
-
typeof value === 'number' && Number.isInteger(value) ? value : fallback;
|
|
102
|
-
if (candidate < 1) return 1;
|
|
103
|
-
if (candidate > ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX) {
|
|
104
|
-
return ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX;
|
|
105
|
-
}
|
|
106
|
-
return candidate;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
71
|
/**
|
|
110
72
|
* Read the merged acceptance-eval block. Returns the canonical shape:
|
|
111
73
|
*
|
|
112
74
|
* {
|
|
113
|
-
* maxRounds: number,
|
|
114
|
-
* ceiling: number
|
|
115
|
-
* clusterCeiling: number, // clamped into [1, clusterCeilingMax]
|
|
116
|
-
* clusterCeilingMax: number // the undisableable hard cap on cluster size
|
|
75
|
+
* maxRounds: number, // clamped into [1, ceiling]
|
|
76
|
+
* ceiling: number // the undisableable hard cap on rounds
|
|
117
77
|
* }
|
|
118
78
|
*
|
|
119
|
-
* `maxRounds` is always a positive integer no greater than `ceiling
|
|
120
|
-
*
|
|
121
|
-
* `clusterCeilingMax`, regardless of what the resolved config carried.
|
|
79
|
+
* `maxRounds` is always a positive integer no greater than `ceiling`,
|
|
80
|
+
* regardless of what the resolved config carried.
|
|
122
81
|
*
|
|
123
82
|
* @param {object | null | undefined} config
|
|
124
|
-
* @returns {{ maxRounds: number, ceiling: number
|
|
83
|
+
* @returns {{ maxRounds: number, ceiling: number }}
|
|
125
84
|
*/
|
|
126
85
|
export function getAcceptanceEval(config) {
|
|
127
86
|
const user = config?.delivery?.acceptanceEval ?? {};
|
|
@@ -129,14 +88,8 @@ export function getAcceptanceEval(config) {
|
|
|
129
88
|
user.maxRounds,
|
|
130
89
|
ACCEPTANCE_EVAL_DEFAULTS.maxRounds,
|
|
131
90
|
);
|
|
132
|
-
const clusterCeiling = clampClusterCeiling(
|
|
133
|
-
user.clusterCeiling,
|
|
134
|
-
ACCEPTANCE_EVAL_DEFAULTS.clusterCeiling,
|
|
135
|
-
);
|
|
136
91
|
return {
|
|
137
92
|
maxRounds,
|
|
138
93
|
ceiling: ACCEPTANCE_EVAL_MAX_ROUNDS_CEILING,
|
|
139
|
-
clusterCeiling,
|
|
140
|
-
clusterCeilingMax: ACCEPTANCE_EVAL_CLUSTER_CEILING_MAX,
|
|
141
94
|
};
|
|
142
95
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `project.commands` accessor (Epic #1720 Story #1739 — top-level reshape).
|
|
3
3
|
*
|
|
4
|
-
* The surviving
|
|
5
|
-
* `
|
|
4
|
+
* The surviving four command keys are `test`, `typecheck`,
|
|
5
|
+
* `formatCheck`, `formatWrite`.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export const COMMANDS_DEFAULTS = Object.freeze({
|
|
9
|
-
lintBaseline: 'npx eslint . --format json',
|
|
10
9
|
test: 'npm test',
|
|
11
10
|
typecheck: null,
|
|
12
11
|
formatCheck: 'npx biome format .',
|
|
@@ -19,12 +18,11 @@ export const COMMANDS_DEFAULTS = Object.freeze({
|
|
|
19
18
|
* a bare `{ project }` bag.
|
|
20
19
|
*
|
|
21
20
|
* @param {object | null | undefined} config
|
|
22
|
-
* @returns {{
|
|
21
|
+
* @returns {{ test: string, typecheck: string|null, formatCheck: string, formatWrite: string }}
|
|
23
22
|
*/
|
|
24
23
|
export function getCommands(config) {
|
|
25
24
|
const commands = config?.project?.commands ?? {};
|
|
26
25
|
return {
|
|
27
|
-
lintBaseline: commands.lintBaseline ?? COMMANDS_DEFAULTS.lintBaseline,
|
|
28
26
|
test: commands.test ?? COMMANDS_DEFAULTS.test,
|
|
29
27
|
typecheck:
|
|
30
28
|
commands.typecheck === undefined
|
|
@@ -72,8 +72,6 @@ const KEY_MEANINGS = Object.freeze({
|
|
|
72
72
|
'Root for gitignored scratch output (per-run state, mirrors, logs).',
|
|
73
73
|
'project.docsContextFiles':
|
|
74
74
|
'Authoritative files an agent must read before starting any task.',
|
|
75
|
-
'project.commands.lintBaseline':
|
|
76
|
-
'Lint command the close-validation chain runs.',
|
|
77
75
|
'project.commands.test': 'Test command the close-validation chain runs.',
|
|
78
76
|
'project.commands.typecheck':
|
|
79
77
|
'Typecheck command (null disables the typecheck gate).',
|
|
@@ -136,8 +134,6 @@ const KEY_MEANINGS = Object.freeze({
|
|
|
136
134
|
// delivery.*
|
|
137
135
|
'delivery.execution.timeoutMs':
|
|
138
136
|
'Per-execution timeout for orchestrated delivery steps.',
|
|
139
|
-
'delivery.lease.ttlMs':
|
|
140
|
-
'Time-to-live for the delivery lease before a stale claim is reclaimable.',
|
|
141
137
|
'delivery.ci.watch.pollIntervalMs':
|
|
142
138
|
'Poll cadence (ms) for the merge/CI watch loop.',
|
|
143
139
|
'delivery.ci.watch.maxPolls':
|
|
@@ -147,7 +143,7 @@ const KEY_MEANINGS = Object.freeze({
|
|
|
147
143
|
'delivery.ci.autoMerge':
|
|
148
144
|
'Merge posture: trust-ci merges on green checks; strict also requires a clean review gate.',
|
|
149
145
|
'delivery.docsFreshness.paths':
|
|
150
|
-
'Docs
|
|
146
|
+
'Docs the audit-documentation lens folds into its target set.',
|
|
151
147
|
'delivery.deliverRunner.concurrencyCap':
|
|
152
148
|
'Maximum Stories dispatched in parallel within one wave. Default 3 — conservative by design to keep host-quota consumption predictable. Operators running wide waves with adequate parallel-agent quota should raise this to reduce wall-clock time proportionally.',
|
|
153
149
|
'delivery.worktreeIsolation.enabled':
|
|
@@ -202,8 +198,6 @@ const KEY_MEANINGS = Object.freeze({
|
|
|
202
198
|
'Whether a dedicated refactor stage runs during delivery.',
|
|
203
199
|
'delivery.acceptanceEval.maxRounds':
|
|
204
200
|
'Redraft rounds the per-Story acceptance self-eval loop runs before escalating to agent::blocked (default 2; clamped to a hard ceiling that cannot be disabled).',
|
|
205
|
-
'delivery.acceptanceEval.clusterCeiling':
|
|
206
|
-
'Max acceptance criteria one single-delivery acceptance critic scores per fresh-context pass; the Story ACs are split into ceil(totalACs / clusterCeiling) maker-blind critic clusters (default 4; clamped to [1, 8]).',
|
|
207
201
|
|
|
208
202
|
// qa.*
|
|
209
203
|
'qa.featureRoot': 'Root directory holding the QA harness .feature files.',
|
|
@@ -257,6 +251,10 @@ const PREFIX_MEANINGS = Object.freeze([
|
|
|
257
251
|
'QA harness deployment target (baseUrl, per-environment sign-in seam, allowWrites gate).',
|
|
258
252
|
],
|
|
259
253
|
['qa.personas', 'QA harness persona / credential mapping.'],
|
|
254
|
+
[
|
|
255
|
+
'qa.gherkinLint',
|
|
256
|
+
'Static Gherkin corpus gate: per-scope feature/step roots plus the exemption-tag and step-waiver escapes.',
|
|
257
|
+
],
|
|
260
258
|
]);
|
|
261
259
|
|
|
262
260
|
/**
|
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
/* node:coverage ignore file -- AJV schema declaration (data-as-code) */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { gateBase } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const BUNDLE_DECLARATION = {
|
|
6
6
|
type: 'object',
|
|
7
|
+
description: 'One built artifact the bundle-size gate weighs.',
|
|
7
8
|
required: ['name', 'path', 'limit'],
|
|
8
9
|
properties: {
|
|
9
|
-
name: {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
name: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
minLength: 1,
|
|
13
|
+
description: 'Label the rollup row is keyed by.',
|
|
14
|
+
},
|
|
15
|
+
path: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
minLength: 1,
|
|
18
|
+
description: 'Glob or path of the built artifact to measure.',
|
|
19
|
+
},
|
|
20
|
+
limit: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
minLength: 1,
|
|
23
|
+
description: 'Human size ceiling (e.g. `"180 kB"`).',
|
|
24
|
+
},
|
|
12
25
|
},
|
|
13
26
|
additionalProperties: false,
|
|
14
27
|
};
|
|
15
28
|
|
|
16
29
|
export const BUNDLE_SIZE_GATE = {
|
|
17
30
|
type: 'object',
|
|
31
|
+
description:
|
|
32
|
+
'Built-artifact size ratchet for web targets. Off by default — it needs declared bundles.',
|
|
18
33
|
properties: {
|
|
19
|
-
...
|
|
20
|
-
|
|
34
|
+
...gateBase({
|
|
35
|
+
enabled: false,
|
|
36
|
+
baselinePath: 'baselines/bundle-size.json',
|
|
37
|
+
tolerance: { kind: 'percent', value: 0 },
|
|
38
|
+
floors: { '*': {} },
|
|
39
|
+
}),
|
|
40
|
+
bundles: {
|
|
41
|
+
type: 'array',
|
|
42
|
+
items: BUNDLE_DECLARATION,
|
|
43
|
+
description:
|
|
44
|
+
'The artifacts to weigh. Empty means the gate has nothing to do.',
|
|
45
|
+
default: [],
|
|
46
|
+
},
|
|
21
47
|
},
|
|
22
48
|
additionalProperties: false,
|
|
23
49
|
};
|
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
/* node:coverage ignore file -- AJV schema declaration (data-as-code) */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { COVERAGE_GATE_DEFAULTS } from '../quality.js';
|
|
4
|
+
import { gateBase, SAFE_STRING } from './shared.js';
|
|
4
5
|
|
|
5
6
|
export const COVERAGE_GATE = {
|
|
6
7
|
type: 'object',
|
|
8
|
+
description:
|
|
9
|
+
'Line/branch/function coverage ratchet, read from the Istanbul JSON summary the project test run emits.',
|
|
7
10
|
properties: {
|
|
8
|
-
...
|
|
9
|
-
|
|
11
|
+
...gateBase({
|
|
12
|
+
enabled: COVERAGE_GATE_DEFAULTS.enabled,
|
|
13
|
+
baselinePath: COVERAGE_GATE_DEFAULTS.baselinePath,
|
|
14
|
+
tolerance: COVERAGE_GATE_DEFAULTS.tolerance,
|
|
15
|
+
floors: COVERAGE_GATE_DEFAULTS.floors,
|
|
16
|
+
}),
|
|
17
|
+
coveragePath: {
|
|
18
|
+
...SAFE_STRING,
|
|
19
|
+
minLength: 1,
|
|
20
|
+
description:
|
|
21
|
+
'Repo-relative path to the Istanbul `coverage-final.json` the capture step writes and the gate reads.',
|
|
22
|
+
default: COVERAGE_GATE_DEFAULTS.coveragePath,
|
|
23
|
+
},
|
|
10
24
|
// Story #2136 / Task #2142 — bounded timeout for `npm run test:coverage`.
|
|
11
25
|
// Wired into `runCapture` via `spawnSync({ timeout, killSignal })`. A
|
|
12
26
|
// SIGKILL fired by the timeout is translated to exit code 124 (the GNU
|
|
13
27
|
// `timeout` convention) so close-validation can branch on "hang" vs.
|
|
14
|
-
// "test failed".
|
|
15
|
-
timeoutMs: {
|
|
28
|
+
// "test failed".
|
|
29
|
+
timeoutMs: {
|
|
30
|
+
type: 'integer',
|
|
31
|
+
minimum: 1,
|
|
32
|
+
description:
|
|
33
|
+
'Bounded timeout (ms) for the `npm run test:coverage` capture spawn. A SIGKILL at the budget boundary maps to exit 124 so close-validation can tell a hang from a test failure.',
|
|
34
|
+
default: COVERAGE_GATE_DEFAULTS.timeoutMs,
|
|
35
|
+
},
|
|
16
36
|
},
|
|
17
37
|
additionalProperties: false,
|
|
18
38
|
};
|
|
@@ -18,9 +18,19 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export const INCREMENTAL_COVERAGE_SCHEMA = {
|
|
20
20
|
type: 'object',
|
|
21
|
+
description:
|
|
22
|
+
'Story #4981 — opt-in incremental coverage-capture + CRAP-join scoping. Default (key absent) preserves today’s full-repo behaviour byte-for-byte. When `enabled: true`, `coverage-capture.js` scopes `npm run test:coverage` to the files changed against `baseRef` (default: the gate’s own `--ref` / `main`), and the CRAP join treats a method in a file the diff did not touch as resolved by its committed baseline row instead of requiring fresh coverage for it.',
|
|
21
23
|
properties: {
|
|
22
|
-
enabled: {
|
|
23
|
-
|
|
24
|
+
enabled: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
description: 'Master switch for incremental capture + join scoping.',
|
|
27
|
+
},
|
|
28
|
+
baseRef: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
minLength: 1,
|
|
31
|
+
description:
|
|
32
|
+
'Git ref the changed-file set is computed against. Omitted falls back to the gate’s own `--ref` (`main`).',
|
|
33
|
+
},
|
|
24
34
|
},
|
|
25
35
|
additionalProperties: false,
|
|
26
36
|
};
|
|
@@ -1,41 +1,86 @@
|
|
|
1
1
|
/* node:coverage ignore file -- AJV schema declaration (data-as-code) */
|
|
2
2
|
|
|
3
|
+
import { CRAP_GATE_DEFAULTS } from '../quality.js';
|
|
3
4
|
import { INCREMENTAL_COVERAGE_SCHEMA } from './crap-incremental-coverage.schema.js';
|
|
4
5
|
import {
|
|
5
|
-
|
|
6
|
+
gateBase,
|
|
6
7
|
LIST_OR_EXTENDER_OF_STRINGS,
|
|
7
8
|
SAFE_STRING,
|
|
8
9
|
} from './shared.js';
|
|
9
10
|
|
|
10
11
|
export const CRAP_GATE = {
|
|
11
12
|
type: 'object',
|
|
13
|
+
description:
|
|
14
|
+
'CRAP (Change Risk Anti-Pattern) ratchet — per-method cyclomatic complexity joined against per-method coverage.',
|
|
12
15
|
properties: {
|
|
13
|
-
...
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
...gateBase({
|
|
17
|
+
enabled: CRAP_GATE_DEFAULTS.enabled,
|
|
18
|
+
baselinePath: CRAP_GATE_DEFAULTS.baselinePath,
|
|
19
|
+
tolerance: CRAP_GATE_DEFAULTS.tolerance,
|
|
20
|
+
floors: CRAP_GATE_DEFAULTS.floors,
|
|
21
|
+
}),
|
|
22
|
+
targetDirs: {
|
|
23
|
+
...LIST_OR_EXTENDER_OF_STRINGS,
|
|
24
|
+
description:
|
|
25
|
+
"Directories whose JS sources the CRAP gate scores. Mandrel ships a `src/`-centric default; projects whose executable code lives elsewhere (e.g. this repo's `.agents/scripts/`) override here. The framework default is intentionally not auto-discovered, so an override is the explicit, auditable signal.",
|
|
26
|
+
default: [...CRAP_GATE_DEFAULTS.targetDirs],
|
|
27
|
+
},
|
|
28
|
+
newMethodCeiling: {
|
|
29
|
+
type: 'integer',
|
|
30
|
+
minimum: 1,
|
|
31
|
+
description:
|
|
32
|
+
'Hard CRAP ceiling applied to a method the diff introduces. A new method above it fails the gate regardless of the baseline.',
|
|
33
|
+
default: CRAP_GATE_DEFAULTS.newMethodCeiling,
|
|
34
|
+
},
|
|
35
|
+
requireCoverage: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
description:
|
|
38
|
+
'When true, the gate refuses to score without a coverage artifact rather than silently reporting complexity-only rows.',
|
|
39
|
+
default: CRAP_GATE_DEFAULTS.requireCoverage,
|
|
40
|
+
},
|
|
41
|
+
// Story #4775 — fail-closed floor on the per-method coverage JOIN.
|
|
42
|
+
minMethodResolutionRate: {
|
|
43
|
+
type: 'number',
|
|
44
|
+
minimum: 0,
|
|
45
|
+
maximum: 1,
|
|
46
|
+
description:
|
|
47
|
+
'Fail-closed floor on the per-method coverage JOIN (Story #4775): the fraction of methods that must resolve a coverage entry, counted only over files that HAVE one, before `update-crap-baseline.js` will persist. A broken join is silent by construction — unresolved methods are simply absent — so the updater refuses rather than writing a thin baseline and logging it as success. Not enforced below 25 joinable methods, where a diff-scoped run’s rate is noise. Default 0.75; a healthy repo resolves ~98%.',
|
|
48
|
+
},
|
|
24
49
|
friction: {
|
|
25
50
|
type: 'object',
|
|
26
|
-
|
|
51
|
+
description:
|
|
52
|
+
'Friction-signal wiring for a CRAP baseline regression, so a recurring one can reach the actionable threshold.',
|
|
53
|
+
properties: {
|
|
54
|
+
markerKey: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
minLength: 1,
|
|
57
|
+
description: 'Signal marker key the regression is recorded under.',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
27
60
|
additionalProperties: false,
|
|
61
|
+
default: { ...CRAP_GATE_DEFAULTS.friction },
|
|
62
|
+
},
|
|
63
|
+
refreshTag: {
|
|
64
|
+
...SAFE_STRING,
|
|
65
|
+
minLength: 1,
|
|
66
|
+
description:
|
|
67
|
+
'Commit-subject substring that acknowledges a deliberate CRAP baseline refresh in the compared range. A range commit carrying it that also touches the baseline file demotes head-vs-base regressions; floors stay enforced.',
|
|
68
|
+
default: CRAP_GATE_DEFAULTS.refreshTag,
|
|
69
|
+
},
|
|
70
|
+
refreshTimeoutMs: {
|
|
71
|
+
type: 'integer',
|
|
72
|
+
minimum: 1,
|
|
73
|
+
description:
|
|
74
|
+
'Bounded timeout (ms) for `npm run crap:update` spawned by the baseline-attribution refresh path. Mirrors `coverage.timeoutMs`: a SIGKILL fired at the budget boundary maps to exit 124 so the close orchestrator can flip the Story to `agent::blocked`. Default 60000 (Story #2165).',
|
|
75
|
+
default: CRAP_GATE_DEFAULTS.refreshTimeoutMs,
|
|
76
|
+
},
|
|
77
|
+
ignoreGlobs: {
|
|
78
|
+
type: 'array',
|
|
79
|
+
items: { type: 'string', minLength: 1 },
|
|
80
|
+
description:
|
|
81
|
+
'Minimatch glob patterns matched against the canonicalised repo-relative path of each discovered file. Files matching any pattern are excluded from CRAP discovery before scoring. Orthogonal to `components` (grouping) — a file excluded here never appears in any component bucket. Absent or empty preserves the existing IGNORED_DIRS-only behaviour (Story #3217).',
|
|
82
|
+
default: [...CRAP_GATE_DEFAULTS.ignoreGlobs],
|
|
28
83
|
},
|
|
29
|
-
refreshTag: { ...SAFE_STRING, minLength: 1 },
|
|
30
|
-
// Story #2165 — bounded timeout for `npm run crap:update` spawned by the
|
|
31
|
-
// baseline-attribution refresh path. Mirrors `coverage.timeoutMs` (Story
|
|
32
|
-
// #2142): a SIGKILL fired at the budget boundary maps to exit code 124
|
|
33
|
-
// so the close orchestrator can flip the Story to `agent::blocked`.
|
|
34
|
-
refreshTimeoutMs: { type: 'integer', minimum: 1 },
|
|
35
|
-
// Story #3217 — glob patterns matched against canonicalised repo-relative
|
|
36
|
-
// paths to exclude files from CRAP discovery before scoring. Orthogonal
|
|
37
|
-
// to `components` (grouping). Absent/empty preserves existing behaviour.
|
|
38
|
-
ignoreGlobs: { type: 'array', items: { type: 'string', minLength: 1 } },
|
|
39
84
|
incrementalCoverage: INCREMENTAL_COVERAGE_SCHEMA,
|
|
40
85
|
},
|
|
41
86
|
additionalProperties: false,
|
|
@@ -1,26 +1,38 @@
|
|
|
1
1
|
/* node:coverage ignore file -- AJV schema declaration (data-as-code) */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { gateBase, LIST_OR_EXTENDER_OF_STRINGS } from './shared.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* `delivery.quality.gates.duplication` — code-duplication (DRY) gate
|
|
7
|
-
* (Story #3664). Shares the four-field `GATE_BASE` (enabled, baselinePath,
|
|
8
|
-
* tolerance, floors, components) and adds the scan-scope extras: the
|
|
9
|
-
* `targetDirs` the duplication scanner walks, a bounded refresh timeout
|
|
10
|
-
* mirroring crap/MI, and `ignoreGlobs` to exclude files from the scan.
|
|
11
|
-
*/
|
|
12
5
|
export const DUPLICATION_GATE = {
|
|
13
6
|
type: 'object',
|
|
7
|
+
description:
|
|
8
|
+
'Code-duplication (DRY) gate (Story #3664). Shares the gate base and adds the scan-scope extras: the `targetDirs` the duplication scanner walks, a bounded refresh timeout mirroring crap/MI, and `ignoreGlobs` to exclude files from the scan.',
|
|
14
9
|
properties: {
|
|
15
|
-
...
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
...gateBase({
|
|
11
|
+
enabled: false,
|
|
12
|
+
baselinePath: 'baselines/duplication.json',
|
|
13
|
+
tolerance: { kind: 'absolute', value: 1 },
|
|
14
|
+
floors: { '*': { percentage: 25 } },
|
|
15
|
+
}),
|
|
16
|
+
targetDirs: {
|
|
17
|
+
...LIST_OR_EXTENDER_OF_STRINGS,
|
|
18
|
+
description:
|
|
19
|
+
"Directories whose JS sources the duplication (DRY) gate scans for copy-paste clones. Mandrel ships a `src/`-centric default; projects whose executable code lives elsewhere (e.g. this repo's `.agents/scripts/`) override here. The framework default is intentionally not auto-discovered, so an override is the explicit, auditable signal (Story #3664).",
|
|
20
|
+
default: ['src'],
|
|
21
|
+
},
|
|
22
|
+
refreshTimeoutMs: {
|
|
23
|
+
type: 'integer',
|
|
24
|
+
minimum: 1,
|
|
25
|
+
description:
|
|
26
|
+
'Bounded timeout (ms) for `npm run duplication:update` spawned by the baseline-attribution refresh path. Mirrors `crap.refreshTimeoutMs` / `coverage.timeoutMs`: a SIGKILL fired at the budget boundary maps to exit 124. Default 60000 (Story #3664).',
|
|
27
|
+
default: 60000,
|
|
28
|
+
},
|
|
29
|
+
ignoreGlobs: {
|
|
30
|
+
type: 'array',
|
|
31
|
+
items: { type: 'string', minLength: 1 },
|
|
32
|
+
description:
|
|
33
|
+
'Minimatch glob patterns matched against the canonicalised repo-relative path of each discovered file. Files matching any pattern are excluded from duplication discovery before scanning. Orthogonal to `components` (grouping). Absent or empty preserves the existing behaviour (Story #3664).',
|
|
34
|
+
default: [],
|
|
35
|
+
},
|
|
24
36
|
},
|
|
25
37
|
additionalProperties: false,
|
|
26
38
|
};
|
|
@@ -14,14 +14,17 @@ import { MUTATION_GATE } from './mutation.schema.js';
|
|
|
14
14
|
*
|
|
15
15
|
* Split from the former `lib/config-gates-schema.js` aggregate
|
|
16
16
|
* (Story #2987) into per-gate sub-schema files under
|
|
17
|
-
* `lib/config/gates/`. Shared fragments (`
|
|
17
|
+
* `lib/config/gates/`. Shared fragments (`gateBase()`, `SAFE_STRING`,
|
|
18
18
|
* `TOLERANCE_SCHEMA`, `FLOORS_SCHEMA`, `COMPONENTS_SCHEMA`,
|
|
19
19
|
* `LIST_OR_EXTENDER_OF_STRINGS`, `NULLABLE_NONEMPTY_SAFE_STRING`) live
|
|
20
20
|
* in `./shared.js`. Each per-gate file exports one sub-schema literal
|
|
21
|
-
* with the gate-specific extras layered on top of `
|
|
21
|
+
* with the gate-specific extras layered on top of `gateBase()`, passing
|
|
22
|
+
* that gate's own base-field defaults (Story #5007).
|
|
22
23
|
*/
|
|
23
24
|
export const GATES_SCHEMA = {
|
|
24
25
|
type: 'object',
|
|
26
|
+
description:
|
|
27
|
+
'The eight quality gates, each sharing the `{ enabled, baselinePath, tolerance, floors, components }` base.',
|
|
25
28
|
properties: {
|
|
26
29
|
lint: LINT_GATE,
|
|
27
30
|
coverage: COVERAGE_GATE,
|
|
@@ -1,23 +1,51 @@
|
|
|
1
1
|
/* node:coverage ignore file -- AJV schema declaration (data-as-code) */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { gateBase, NULLABLE_NONEMPTY_SAFE_STRING } from './shared.js';
|
|
4
4
|
|
|
5
5
|
const LIGHTHOUSE_ROUTE = {
|
|
6
6
|
type: 'object',
|
|
7
|
+
description: 'One route the Lighthouse run scores.',
|
|
7
8
|
required: ['path'],
|
|
8
9
|
properties: {
|
|
9
|
-
path: {
|
|
10
|
-
|
|
10
|
+
path: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
minLength: 1,
|
|
13
|
+
description: 'Route path appended to `baseUrl`.',
|
|
14
|
+
},
|
|
15
|
+
formFactor: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
enum: ['mobile', 'desktop'],
|
|
18
|
+
description: 'Emulated form factor for this route.',
|
|
19
|
+
},
|
|
11
20
|
},
|
|
12
21
|
additionalProperties: false,
|
|
13
22
|
};
|
|
14
23
|
|
|
15
24
|
export const LIGHTHOUSE_GATE = {
|
|
16
25
|
type: 'object',
|
|
26
|
+
description:
|
|
27
|
+
'Lighthouse category-score ratchet for web targets. Off by default — it needs a running deployment.',
|
|
17
28
|
properties: {
|
|
18
|
-
...
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
...gateBase({
|
|
30
|
+
enabled: false,
|
|
31
|
+
baselinePath: 'baselines/lighthouse.json',
|
|
32
|
+
tolerance: { kind: 'absolute', value: 0 },
|
|
33
|
+
floors: {
|
|
34
|
+
'*': { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 },
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
baseUrl: {
|
|
38
|
+
...NULLABLE_NONEMPTY_SAFE_STRING,
|
|
39
|
+
description:
|
|
40
|
+
'Origin the routes are resolved against. `null` leaves the gate unusable, which is why it ships disabled.',
|
|
41
|
+
default: null,
|
|
42
|
+
},
|
|
43
|
+
routes: {
|
|
44
|
+
type: 'array',
|
|
45
|
+
items: LIGHTHOUSE_ROUTE,
|
|
46
|
+
description: 'Routes to score on each run.',
|
|
47
|
+
default: [],
|
|
48
|
+
},
|
|
21
49
|
},
|
|
22
50
|
additionalProperties: false,
|
|
23
51
|
};
|