mandrel 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/.agents/agents/.markdownlint.json +4 -0
  2. package/.agents/agents/acceptance-critic.md +30 -5
  3. package/.agents/agents/auditor.md +36 -19
  4. package/.agents/agents/plan-critic.md +31 -5
  5. package/.agents/agents/story-worker.md +91 -100
  6. package/.agents/docs/configuration.md +39 -25
  7. package/.agents/docs/execution-reference.md +13 -0
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +131 -265
  10. package/.agents/rules/git-conventions.md +47 -83
  11. package/.agents/rules/orchestration-error-handling.md +28 -0
  12. package/.agents/schemas/agentrc.schema.json +36 -9
  13. package/.agents/schemas/validation-evidence.schema.json +3 -1
  14. package/.agents/scripts/acceptance-eval.js +10 -6
  15. package/.agents/scripts/apply-quality-bootstrap.js +1 -1
  16. package/.agents/scripts/check-test-temp-hygiene.js +438 -0
  17. package/.agents/scripts/deliver-recover.js +23 -6
  18. package/.agents/scripts/lib/audit-suite/index.js +5 -0
  19. package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +179 -0
  20. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  21. package/.agents/scripts/lib/baselines/env-overrides.js +33 -0
  22. package/.agents/scripts/lib/baselines/git-base.js +0 -0
  23. package/.agents/scripts/lib/baselines/preview-gates.js +5 -0
  24. package/.agents/scripts/lib/config/gates/maintainability.schema.js +10 -1
  25. package/.agents/scripts/lib/config/quality.js +13 -0
  26. package/.agents/scripts/lib/config/temp-paths.js +121 -1
  27. package/.agents/scripts/lib/config-settings-schema-delivery.js +30 -0
  28. package/.agents/scripts/lib/config-settings-schema.js +12 -16
  29. package/.agents/scripts/lib/observability/metrics-ledger.js +217 -0
  30. package/.agents/scripts/lib/observability/runtime-friction.js +7 -0
  31. package/.agents/scripts/lib/orchestration/ceremony-routing.js +45 -0
  32. package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +97 -4
  33. package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +7 -0
  34. package/.agents/scripts/lib/orchestration/complexity-gate.js +533 -93
  35. package/.agents/scripts/lib/orchestration/deliver-recover.js +137 -10
  36. package/.agents/scripts/lib/orchestration/merge-block-class.js +36 -15
  37. package/.agents/scripts/lib/orchestration/merge-poll.js +213 -0
  38. package/.agents/scripts/lib/orchestration/plan-context.js +119 -3
  39. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +182 -9
  40. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +29 -2
  41. package/.agents/scripts/lib/orchestration/plan-metrics.js +31 -82
  42. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +153 -2
  43. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +221 -14
  44. package/.agents/scripts/lib/orchestration/resolve-stories.js +12 -1
  45. package/.agents/scripts/lib/orchestration/review-depth.js +9 -4
  46. package/.agents/scripts/lib/orchestration/review-providers/native.js +34 -16
  47. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -3
  48. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +230 -79
  49. package/.agents/scripts/lib/orchestration/spec-budget.js +78 -0
  50. package/.agents/scripts/lib/orchestration/story-body-gate.js +72 -0
  51. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +89 -1
  52. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +73 -0
  53. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +6 -0
  54. package/.agents/scripts/lib/orchestration/ticket-validator.js +18 -62
  55. package/.agents/scripts/lib/templates/decomposer-prompts.js +13 -6
  56. package/.agents/scripts/lib/test-env.js +65 -0
  57. package/.agents/scripts/plan-context.js +84 -9
  58. package/.agents/scripts/plan-critics.js +115 -3
  59. package/.agents/scripts/plan-persist.js +11 -1
  60. package/.agents/scripts/plan-run-epilogue.js +1 -1
  61. package/.agents/scripts/resolve-stories.js +2 -0
  62. package/.agents/scripts/single-story-confirm-merge.js +65 -5
  63. package/.agents/scripts/stories-wave-tick.js +1 -1
  64. package/.agents/workflows/deliver.md +88 -230
  65. package/.agents/workflows/helpers/acceptance-self-eval.md +16 -5
  66. package/.agents/workflows/helpers/deliver-reference.md +171 -0
  67. package/.agents/workflows/helpers/deliver-story-reference.md +223 -0
  68. package/.agents/workflows/helpers/deliver-story.md +115 -432
  69. package/.agents/workflows/helpers/plan-reference.md +246 -0
  70. package/.agents/workflows/plan.md +108 -304
  71. package/docs/CHANGELOG.md +46 -0
  72. package/lib/cli/registry.js +31 -14
  73. package/lib/migrations/index.js +2 -0
  74. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +92 -0
  75. package/package.json +1 -1
@@ -35,6 +35,19 @@
35
35
  * sub-agent. This module takes the cluster index as an INPUT and returns a
36
36
  * decision for that one cluster; it has no way to add or remove clusters.
37
37
  *
38
+ * ## One verdict-owner per cluster (Story #4723)
39
+ *
40
+ * The resolved decision names the cluster's **single verdict owner** via
41
+ * `verdictOwner`: `'fresh-critic'` when the mode is `fresh`,
42
+ * `'inline-self-eval'` when the mode is `inline`. Exactly one pass authors
43
+ * the cluster's verdict — the fresh maker-blind critic OR the
44
+ * contract-identical inline self-eval, never both, and never an additional
45
+ * pre-pass self-assessment before the owner runs. `acceptance-eval.js` is
46
+ * the deterministic SCORER of that one authored verdict (schema validation,
47
+ * round cap, proceed/redraft/block) — it is not a third pass over the
48
+ * criteria. This removes a redundant pass only; it never removes a
49
+ * cluster's verdict (the M4-B floor above holds).
50
+ *
38
51
  * ## Tier rules (per cluster, `standard` profile)
39
52
  *
40
53
  * - `high` level → `fresh` (a sensitive path was touched — a
@@ -63,10 +76,23 @@
63
76
  * `undefined` / malformed inputs degrade to `fresh` + `full` ceremony.
64
77
  *
65
78
  * @typedef {'fresh'|'inline'} CeremonyMode
79
+ * @typedef {'fresh-critic'|'inline-self-eval'} VerdictOwner
66
80
  * @typedef {import('./review-depth.js').ChangeLevel} ChangeLevel
67
81
  * @typedef {'minimal'|'standard'|'strict'} CeremonyProfile
68
82
  */
69
83
 
84
+ /**
85
+ * Map a resolved ceremony mode to the cluster's single verdict owner
86
+ * (Story #4723). Total: any non-`fresh` value maps to the inline
87
+ * self-eval owner, mirroring how the mode itself degrades.
88
+ *
89
+ * @param {CeremonyMode} mode
90
+ * @returns {VerdictOwner}
91
+ */
92
+ export function verdictOwnerForMode(mode) {
93
+ return mode === 'fresh' ? 'fresh-critic' : 'inline-self-eval';
94
+ }
95
+
70
96
  /** @type {readonly CeremonyProfile[]} */
71
97
  export const CEREMONY_PROFILES = Object.freeze([
72
98
  'minimal',
@@ -133,9 +159,28 @@ export function sampledFresh(clusterIndex, rate) {
133
159
  * reason: string,
134
160
  * sampled: boolean,
135
161
  * profile: CeremonyProfile,
162
+ * verdictOwner: VerdictOwner,
136
163
  * }}
137
164
  */
138
165
  export function resolveCeremonyForRisk(input = {}) {
166
+ const decision = resolveCeremonyDecision(input);
167
+ return { ...decision, verdictOwner: verdictOwnerForMode(decision.mode) };
168
+ }
169
+
170
+ /**
171
+ * Internal mode/reason resolution — the tier rules and sampling floor.
172
+ * `resolveCeremonyForRisk` decorates the result with the single
173
+ * `verdictOwner` derived from the mode (Story #4723).
174
+ *
175
+ * @param {Parameters<typeof resolveCeremonyForRisk>[0]} [input]
176
+ * @returns {{
177
+ * mode: CeremonyMode,
178
+ * reason: string,
179
+ * sampled: boolean,
180
+ * profile: CeremonyProfile,
181
+ * }}
182
+ */
183
+ function resolveCeremonyDecision(input = {}) {
139
184
  const derivedLevel =
140
185
  input && typeof input === 'object' ? input.derivedLevel : undefined;
141
186
  const clusterIndex =
@@ -7,7 +7,11 @@
7
7
  * @module lib/orchestration/check-baselines/phases/evaluate
8
8
  */
9
9
 
10
- import { resolveBundleSizeEnvOverrides } from '../../../baselines/env-overrides.js';
10
+ import {
11
+ resolveBundleSizeEnvOverrides,
12
+ resolveMaintainabilityRefreshOverrides,
13
+ } from '../../../baselines/env-overrides.js';
14
+ import { readRangeSubjectsTouchingFile } from '../../../baselines/git-base.js';
11
15
  import {
12
16
  checkKernelVersion,
13
17
  getKindModule,
@@ -17,6 +21,10 @@ import { Logger } from '../../../Logger.js';
17
21
  import { isIgnoredByGlobs } from '../../../maintainability-utils.js';
18
22
  import { applyTolerance, evaluateCompare, runCompareStage } from './compare.js';
19
23
  import { applyFloors, flattenBreaches } from './floors.js';
24
+ import { DEFAULT_BASELINE_PATHS } from './parse-args.js';
25
+
26
+ /** Default refresh-tag substring when the gate omits `refreshTag`. */
27
+ const DEFAULT_REFRESH_TAG = 'baseline-refresh:';
20
28
 
21
29
  /**
22
30
  * Defense-in-depth against an `ignoreGlobs`-poisoned baseline (Epic #4326
@@ -113,6 +121,88 @@ function applyBundleSizeAcknowledgment(kind, compareOutput, env) {
113
121
  };
114
122
  }
115
123
 
124
+ /**
125
+ * Resolve the maintainability refresh trigger (Story #4731). Two paths, either
126
+ * of which acknowledges — mirroring the bundle-size acknowledge but adding the
127
+ * commit-tagged trigger the breach message already documents:
128
+ *
129
+ * 1. Env parity: `MAINTAINABILITY_REFRESH=1` (the manual override).
130
+ * 2. Commit tag: a commit in the compared range `<baseRef>..HEAD` whose
131
+ * subject contains the configured `refreshTag` AND whose diff touches the
132
+ * maintainability baseline file. One-shot by construction — once merged,
133
+ * the refreshed baseline becomes the base and the tag leaves the range.
134
+ *
135
+ * The tag is matched as a plain substring of a conventional commit subject, so
136
+ * commitlint stays satisfied (e.g. `chore(baselines): baseline-refresh: …`).
137
+ *
138
+ * @returns {{ triggered: boolean, reasons: string[] }}
139
+ */
140
+ function resolveMaintainabilityRefreshTrigger({ gateBlock, cmp, cwd, env }) {
141
+ const reasons = [];
142
+ const { acknowledged: envAck, overrides } =
143
+ resolveMaintainabilityRefreshOverrides(env);
144
+ if (envAck) reasons.push(...overrides);
145
+
146
+ const baseRef = cmp?.baseRef ?? null;
147
+ if (baseRef) {
148
+ const refreshTag =
149
+ typeof gateBlock?.refreshTag === 'string' && gateBlock.refreshTag.length
150
+ ? gateBlock.refreshTag
151
+ : DEFAULT_REFRESH_TAG;
152
+ const baselinePath =
153
+ typeof gateBlock?.baselinePath === 'string' &&
154
+ gateBlock.baselinePath.length
155
+ ? gateBlock.baselinePath
156
+ : DEFAULT_BASELINE_PATHS.maintainability;
157
+ const subjects = readRangeSubjectsTouchingFile(baseRef, baselinePath, {
158
+ cwd,
159
+ });
160
+ const match = subjects.find((s) => s.includes(refreshTag));
161
+ if (match) {
162
+ reasons.push(
163
+ `refresh commit "${match}" (subject contains ${JSON.stringify(refreshTag)}, touches ${baselinePath})`,
164
+ );
165
+ }
166
+ }
167
+
168
+ return { triggered: reasons.length > 0, reasons };
169
+ }
170
+
171
+ /**
172
+ * One-shot maintainability refresh/acknowledge (Story #4731). When triggered
173
+ * (env flag OR a `baseline-refresh:`-tagged range commit touching the baseline),
174
+ * demote every maintainability head-vs-base regression to `unchanged` for this
175
+ * run only — floors still apply, so a row below its `min` floor still breaches.
176
+ * The trigger is read fresh every run and never persisted: post-merge the
177
+ * refreshed baseline is the new base and the tag leaves the range, so the
178
+ * ratchet returns to full strength automatically.
179
+ *
180
+ * No-op for every other kind.
181
+ */
182
+ function applyMaintainabilityAcknowledgment(kind, compareOutput, ctx) {
183
+ if (kind !== 'maintainability') {
184
+ return { compareOutput, acknowledged: false };
185
+ }
186
+ const { triggered, reasons } = resolveMaintainabilityRefreshTrigger(ctx);
187
+ if (!triggered || compareOutput.regressions.length === 0) {
188
+ return { compareOutput, acknowledged: false };
189
+ }
190
+ Logger.warn(
191
+ `[maintainability] ⚠ ${reasons.join('; ')} — ` +
192
+ `${compareOutput.regressions.length} regression(s) acknowledged for this run only; ` +
193
+ 'floors still enforced. This does not persist: once the refresh is the ' +
194
+ 'new base the ratchet re-enforces at full strength.',
195
+ );
196
+ return {
197
+ acknowledged: true,
198
+ compareOutput: {
199
+ ...compareOutput,
200
+ regressions: [],
201
+ unchanged: [...compareOutput.unchanged, ...compareOutput.regressions],
202
+ },
203
+ };
204
+ }
205
+
116
206
  function buildGateReport({
117
207
  kind,
118
208
  gateBlock,
@@ -171,11 +261,14 @@ export async function evaluateKind({
171
261
  rawCompare,
172
262
  gateBlock.tolerance ?? null,
173
263
  );
174
- const { compareOutput, acknowledged } = applyBundleSizeAcknowledgment(
264
+ const bundleAck = applyBundleSizeAcknowledgment(kind, toleratedCompare, env);
265
+ const miAck = applyMaintainabilityAcknowledgment(
175
266
  kind,
176
- toleratedCompare,
177
- env,
267
+ bundleAck.compareOutput,
268
+ { gateBlock, cmp, cwd, env },
178
269
  );
270
+ const compareOutput = miAck.compareOutput;
271
+ const acknowledged = bundleAck.acknowledged || miAck.acknowledged;
179
272
  return buildGateReport({
180
273
  kind,
181
274
  gateBlock,
@@ -49,6 +49,13 @@ Env vars:
49
49
  "unchanged" for this run only (floors still
50
50
  enforced). Never persisted — the next run without
51
51
  this flag re-enforces the ratchet.
52
+ MAINTAINABILITY_REFRESH=1
53
+ One-shot acknowledge for a deliberate maintainability
54
+ baseline refresh: demotes maintainability head-vs-base
55
+ regressions to "unchanged" for this run only (floors
56
+ still enforced). Env-parity override for the
57
+ 'baseline-refresh:'-tagged range commit that touches
58
+ baselines/maintainability.json. Never persisted.
52
59
 
53
60
  Exit codes:
54
61
  0 every enabled gate passes