mandrel 1.79.0 → 1.81.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/docs/configuration.md +7 -0
- package/.agents/docs/quality-gates.md +61 -0
- package/.agents/rules/git-conventions.md +12 -0
- package/.agents/scripts/lib/baselines/env-overrides.js +35 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +47 -1
- package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +7 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/pipeline.js +1 -1
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +2 -1
- package/.agents/workflows/helpers/worktree-lifecycle.md +68 -0
- package/docs/CHANGELOG.md +14 -0
- package/package.json +1 -1
|
@@ -627,6 +627,12 @@ No tier-specific knobs beyond the common shape.
|
|
|
627
627
|
| --------- | -------- | ------- | ---------------------------------------------------------------------------------------- |
|
|
628
628
|
| `bundles` | No | `[]` | Array of `{ name, path, limit }` entries (e.g. `{ "name": "app", "path": "dist/app.js", "limit": "100kB" }`). |
|
|
629
629
|
|
|
630
|
+
Unlike `crap` / `maintainability`, this gate has no `refreshTag` config
|
|
631
|
+
field — there is no scorer to regenerate the baseline from source, so the
|
|
632
|
+
one-shot refresh/acknowledge mechanism is an env var, not a config knob.
|
|
633
|
+
See [`.agents/docs/quality-gates.md` § Bundle-size ratchet](quality-gates.md#bundle-size-ratchet--one-shot-refreshacknowledge-story-151)
|
|
634
|
+
for `BUNDLE_SIZE_REFRESH=1` usage.
|
|
635
|
+
|
|
630
636
|
#### `delivery.quality.formatAutofix`
|
|
631
637
|
|
|
632
638
|
| Field | Required | Default | Purpose |
|
|
@@ -791,6 +797,7 @@ the lint ratchet, and the CRAP/MI gates.
|
|
|
791
797
|
| `baselines/lint.json` | `lint-baseline.js` | `node .agents/scripts/lint-baseline.js capture` |
|
|
792
798
|
| `baselines/crap.json` | `update-crap-baseline.js` | `npm run crap:update` |
|
|
793
799
|
| `baselines/maintainability.json` | `update-maintainability-baseline.js` | `npm run maintainability:update` |
|
|
800
|
+
| `baselines/bundle-size.json` | consumer's own build/measure step | Commit the build's measured sizes; for an intentional growth, run the check with `BUNDLE_SIZE_REFRESH=1` (see [Bundle-size ratchet](quality-gates.md#bundle-size-ratchet--one-shot-refreshacknowledge-story-151)) |
|
|
794
801
|
|
|
795
802
|
These files are the contract. They are read by every gate (Story close, push
|
|
796
803
|
hook, CI) and are regenerated only via tagged `baseline-refresh:` commits
|
|
@@ -452,6 +452,67 @@ watch loop — an unjustified baseline ratchet is no longer caught by CI.
|
|
|
452
452
|
|
|
453
453
|
---
|
|
454
454
|
|
|
455
|
+
## Bundle-size ratchet — one-shot refresh/acknowledge (Story #151)
|
|
456
|
+
|
|
457
|
+
> Baseline envelope, axes, and component model: see the
|
|
458
|
+
> [Baseline reference](#baseline-reference) section below.
|
|
459
|
+
|
|
460
|
+
`check-baselines --gate bundle-size` is a **strict** ratchet: it diffs the
|
|
461
|
+
branch's committed `baselines/bundle-size.json` (head) against the base
|
|
462
|
+
ref's copy (`origin/main` by default) using the gate's configured
|
|
463
|
+
`tolerance`, and separately checks the head aggregate against `floors`.
|
|
464
|
+
Unlike `coverage` / `crap` / `maintainability`, bundle-size has **no
|
|
465
|
+
scorer of its own** — the measured `rawKb` / `gzippedKb` numbers come from
|
|
466
|
+
whatever build step the consumer already runs, not a source-tree rescan —
|
|
467
|
+
so there is no `refreshBaseline({ kind: 'bundle-size', ... })` path to
|
|
468
|
+
regenerate a "corrected" baseline the way `npm run crap:update` does.
|
|
469
|
+
|
|
470
|
+
This makes an **intentional** bundle-size growth (a framework major bump,
|
|
471
|
+
a new dependency, an SSR runtime swap) impossible to land cleanly with the
|
|
472
|
+
usual levers: permanently raising `tolerance` in `.agentrc.json` disables
|
|
473
|
+
the ratchet for every *future* PR too, not just the one that legitimately
|
|
474
|
+
grew.
|
|
475
|
+
|
|
476
|
+
### `BUNDLE_SIZE_REFRESH=1`
|
|
477
|
+
|
|
478
|
+
Set the environment variable for the one CI/local run that needs to land
|
|
479
|
+
the growth:
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
BUNDLE_SIZE_REFRESH=1 npm run bundle-size:check
|
|
483
|
+
# or, calling the dispatcher directly:
|
|
484
|
+
BUNDLE_SIZE_REFRESH=1 node .agents/scripts/check-baselines.js --gate bundle-size
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
When set (`1` or `true`, case-insensitive), every `bundle-size`
|
|
488
|
+
head-vs-base regression is demoted to `unchanged` **for that invocation
|
|
489
|
+
only** — the gate compares head-vs-head in effect, so it passes even
|
|
490
|
+
though the committed baseline grew. **Floors still apply**: an
|
|
491
|
+
acknowledged PR can still fail if the head aggregate breaches the
|
|
492
|
+
configured `floors` budget, so a genuinely runaway regression isn't
|
|
493
|
+
silently waved through under the guise of "intentional".
|
|
494
|
+
|
|
495
|
+
Commit the regenerated `baselines/bundle-size.json` (reflecting the real,
|
|
496
|
+
larger sizes) in the same PR so the new numbers become the base for the
|
|
497
|
+
*next* PR's diff.
|
|
498
|
+
|
|
499
|
+
### The ratchet returns to full strength automatically
|
|
500
|
+
|
|
501
|
+
`BUNDLE_SIZE_REFRESH` is read fresh on every invocation and is **never
|
|
502
|
+
persisted** — no config write, no committed tag, no lingering state. The
|
|
503
|
+
very next `check-baselines --gate bundle-size` run (i.e. the next PR),
|
|
504
|
+
without the env var set, re-enforces the ratchet at full strength against
|
|
505
|
+
the now-larger committed baseline. There is nothing to remember to reset.
|
|
506
|
+
|
|
507
|
+
This mirrors the `CRAP_TOLERANCE` env-override precedent (see
|
|
508
|
+
[CRAP gate — Consumer onboarding](#crap-gate--consumer-onboarding) above),
|
|
509
|
+
but as a true one-shot acknowledgment rather than a run-scoped tolerance
|
|
510
|
+
override: `CRAP_TOLERANCE` changes the *threshold*, `BUNDLE_SIZE_REFRESH`
|
|
511
|
+
demotes the *outcome* of an already-flagged regression, which is the
|
|
512
|
+
correct shape for a gate with no rescoring path of its own.
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
455
516
|
## HITL blocker escalation
|
|
456
517
|
|
|
457
518
|
`risk::high` is informational/planning metadata only. Runtime execution
|
|
@@ -107,6 +107,18 @@ rejected by `pre-push` hooks):
|
|
|
107
107
|
4. **Never bypass hooks**: Do not use `--no-verify`, `--no-gpg-sign`, or
|
|
108
108
|
other hook-skipping flags unless the operator explicitly authorizes it.
|
|
109
109
|
If a hook fails, investigate the underlying cause.
|
|
110
|
+
- **Known false-negative signature**: a `pre-push`/`pre-commit` failure
|
|
111
|
+
whose message is a _zero-match_ error (e.g. Biome's
|
|
112
|
+
`No files were processed in the specified paths`) rather than a
|
|
113
|
+
reported violation, combined with an agent CWD under a harness-managed
|
|
114
|
+
worktree path a consumer's lint config ignores (e.g.
|
|
115
|
+
`.claude/worktrees/<name>/` against a `files.includes` glob like
|
|
116
|
+
`"!**/.claude"`), is a **consumer-tooling gap**, not a real lint
|
|
117
|
+
failure. It does not authorize `--no-verify`. See
|
|
118
|
+
[`worktree-lifecycle.md` § Harness-worktree ⇄ consumer-lint-ignore interaction](../workflows/helpers/worktree-lifecycle.md#harness-worktree-consumer-lint-ignore-interaction-story-152)
|
|
119
|
+
for the recognition signature and the sanctioned consumer-side fix
|
|
120
|
+
(`--no-errors-on-unmatched` or equivalent) before escalating via
|
|
121
|
+
`agent::blocked`.
|
|
110
122
|
|
|
111
123
|
## Meta Labels (Retrospective Signal Routing)
|
|
112
124
|
|
|
@@ -82,6 +82,41 @@ export function resolveCrapEnvOverrides(crapConfig, env) {
|
|
|
82
82
|
return { newMethodCeiling, tolerance, refreshTag, overrides };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Pure helper: resolve the one-shot bundle-size refresh/acknowledge flag
|
|
87
|
+
* (Story #151). Unlike `coverage` / `crap` / `maintainability`, the
|
|
88
|
+
* bundle-size gate has no scorer of its own — the measured sizes come from
|
|
89
|
+
* a build step the operator already runs, not a source-tree rescan — so
|
|
90
|
+
* there is no `refreshBaseline({ kind: 'bundle-size', ... })` path to
|
|
91
|
+
* regenerate a "corrected" baseline. Instead, `BUNDLE_SIZE_REFRESH=1`
|
|
92
|
+
* (mirroring `CRAP_TOLERANCE`'s env-override precedent) tells
|
|
93
|
+
* `check-baselines --gate bundle-size` to treat this run's head
|
|
94
|
+
* measurements as the newly acknowledged baseline: head-vs-base
|
|
95
|
+
* regressions are demoted to `unchanged` for this invocation only. Floors
|
|
96
|
+
* still apply — an acknowledged PR can still fail on an absolute budget
|
|
97
|
+
* breach, only the ratchet-vs-`origin/main` comparison is suspended.
|
|
98
|
+
*
|
|
99
|
+
* The flag is **not persisted** anywhere (no config write, no committed
|
|
100
|
+
* tag): the very next `check-baselines` invocation without the env var —
|
|
101
|
+
* i.e. the next PR — reverts to full strict enforcement automatically, so
|
|
102
|
+
* there is no lingering loosened tolerance to remember to reset (AC-3).
|
|
103
|
+
*
|
|
104
|
+
* Accepted truthy values: `1`, `true` (case-insensitive). Anything else
|
|
105
|
+
* (including unset/empty) resolves to `acknowledged: false`.
|
|
106
|
+
*
|
|
107
|
+
* @param {NodeJS.ProcessEnv} env
|
|
108
|
+
* @returns {{ acknowledged: boolean, overrides: string[] }}
|
|
109
|
+
*/
|
|
110
|
+
export function resolveBundleSizeEnvOverrides(env) {
|
|
111
|
+
const raw = env?.BUNDLE_SIZE_REFRESH;
|
|
112
|
+
const acknowledged =
|
|
113
|
+
typeof raw === 'string' && /^(1|true)$/i.test(raw.trim());
|
|
114
|
+
const overrides = acknowledged
|
|
115
|
+
? [`acknowledged=true (BUNDLE_SIZE_REFRESH=${raw})`]
|
|
116
|
+
: [];
|
|
117
|
+
return { acknowledged, overrides };
|
|
118
|
+
}
|
|
119
|
+
|
|
85
120
|
/**
|
|
86
121
|
* Pure helper: resolve the effective MI tolerance by layering precedence:
|
|
87
122
|
* 1. `CRAP_TOLERANCE` env-var (CI override — the baseline-refresh-
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* @module lib/orchestration/check-baselines/phases/evaluate
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { resolveBundleSizeEnvOverrides } from '../../../baselines/env-overrides.js';
|
|
10
11
|
import { checkKernelVersion } from '../../../baselines/kernel.js';
|
|
11
12
|
import * as reader from '../../../baselines/reader.js';
|
|
13
|
+
import { Logger } from '../../../Logger.js';
|
|
12
14
|
import { applyTolerance, evaluateCompare, runCompareStage } from './compare.js';
|
|
13
15
|
import { applyFloors, flattenBreaches } from './floors.js';
|
|
14
16
|
|
|
@@ -22,6 +24,38 @@ function loadHeadBaseline(kind, cwd, configPath) {
|
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* One-shot bundle-size refresh/acknowledge (Story #151). When
|
|
29
|
+
* `BUNDLE_SIZE_REFRESH=1` is set, demote every `bundle-size` regression to
|
|
30
|
+
* `unchanged` for this run only — floors still apply, so a genuine budget
|
|
31
|
+
* breach is still caught. The flag is read fresh on every invocation and
|
|
32
|
+
* never persisted, so the ratchet returns to full strength automatically on
|
|
33
|
+
* the very next run (no lingering loosened tolerance to remember to reset).
|
|
34
|
+
*
|
|
35
|
+
* No-op for every other kind.
|
|
36
|
+
*/
|
|
37
|
+
function applyBundleSizeAcknowledgment(kind, compareOutput, env) {
|
|
38
|
+
if (kind !== 'bundle-size') return { compareOutput, acknowledged: false };
|
|
39
|
+
const { acknowledged, overrides } = resolveBundleSizeEnvOverrides(env);
|
|
40
|
+
if (!acknowledged || compareOutput.regressions.length === 0) {
|
|
41
|
+
return { compareOutput, acknowledged: false };
|
|
42
|
+
}
|
|
43
|
+
Logger.warn(
|
|
44
|
+
`[bundle-size] ⚠ ${overrides.join(', ')} — ` +
|
|
45
|
+
`${compareOutput.regressions.length} regression(s) acknowledged for this run only; ` +
|
|
46
|
+
'floors still enforced. This does not persist: the next run without ' +
|
|
47
|
+
'BUNDLE_SIZE_REFRESH re-enforces the ratchet at full strength.',
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
acknowledged: true,
|
|
51
|
+
compareOutput: {
|
|
52
|
+
...compareOutput,
|
|
53
|
+
regressions: [],
|
|
54
|
+
unchanged: [...compareOutput.unchanged, ...compareOutput.regressions],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
25
59
|
function buildGateReport({
|
|
26
60
|
kind,
|
|
27
61
|
gateBlock,
|
|
@@ -30,6 +64,7 @@ function buildGateReport({
|
|
|
30
64
|
breaches,
|
|
31
65
|
compareOutput,
|
|
32
66
|
cmp,
|
|
67
|
+
acknowledged,
|
|
33
68
|
}) {
|
|
34
69
|
const kernel = checkKernelVersion(kind, baseline.kernelVersion);
|
|
35
70
|
return {
|
|
@@ -50,6 +85,7 @@ function buildGateReport({
|
|
|
50
85
|
regressionCount: compareOutput.regressions.length,
|
|
51
86
|
baseRef: cmp.baseRef ?? null,
|
|
52
87
|
generatedAt: baseline.generatedAt,
|
|
88
|
+
acknowledged,
|
|
53
89
|
};
|
|
54
90
|
}
|
|
55
91
|
|
|
@@ -59,6 +95,7 @@ export async function evaluateKind({
|
|
|
59
95
|
scope,
|
|
60
96
|
cwd,
|
|
61
97
|
configPath,
|
|
98
|
+
env = process.env,
|
|
62
99
|
}) {
|
|
63
100
|
const headLoad = loadHeadBaseline(kind, cwd, configPath);
|
|
64
101
|
if (headLoad.schemaError) return { kind, schemaError: headLoad.schemaError };
|
|
@@ -67,7 +104,15 @@ export async function evaluateKind({
|
|
|
67
104
|
const breaches = flattenBreaches(findings);
|
|
68
105
|
const cmp = await evaluateCompare({ kind, gateBlock, scope, cwd });
|
|
69
106
|
const rawCompare = runCompareStage(baseline, cmp);
|
|
70
|
-
const
|
|
107
|
+
const toleratedCompare = applyTolerance(
|
|
108
|
+
rawCompare,
|
|
109
|
+
gateBlock.tolerance ?? null,
|
|
110
|
+
);
|
|
111
|
+
const { compareOutput, acknowledged } = applyBundleSizeAcknowledgment(
|
|
112
|
+
kind,
|
|
113
|
+
toleratedCompare,
|
|
114
|
+
env,
|
|
115
|
+
);
|
|
71
116
|
return buildGateReport({
|
|
72
117
|
kind,
|
|
73
118
|
gateBlock,
|
|
@@ -76,5 +121,6 @@ export async function evaluateKind({
|
|
|
76
121
|
breaches,
|
|
77
122
|
compareOutput,
|
|
78
123
|
cmp,
|
|
124
|
+
acknowledged,
|
|
79
125
|
});
|
|
80
126
|
}
|
|
@@ -43,6 +43,13 @@ Unified baseline dispatcher. Per-kind pipeline (schema → floor → tolerance
|
|
|
43
43
|
compare) over every configured gate, with centralised friction emission and
|
|
44
44
|
aggregated exit codes.
|
|
45
45
|
|
|
46
|
+
Env vars:
|
|
47
|
+
BUNDLE_SIZE_REFRESH=1 One-shot acknowledge for an intentional bundle-size
|
|
48
|
+
growth: demotes bundle-size regressions to
|
|
49
|
+
"unchanged" for this run only (floors still
|
|
50
|
+
enforced). Never persisted — the next run without
|
|
51
|
+
this flag re-enforces the ratchet.
|
|
52
|
+
|
|
46
53
|
Exit codes:
|
|
47
54
|
0 every enabled gate passes
|
|
48
55
|
1 any floor breach
|
|
@@ -59,7 +59,7 @@ function dispatchPerKind({ wanted, quality, env, cwd, configPath }) {
|
|
|
59
59
|
wanted.map((kind) => {
|
|
60
60
|
const gateBlock = quality.gates[kind];
|
|
61
61
|
const scope = resolveDispatchScope({ kind, quality, env });
|
|
62
|
-
return evaluateKind({ kind, gateBlock, scope, cwd, configPath });
|
|
62
|
+
return evaluateKind({ kind, gateBlock, scope, cwd, configPath, env });
|
|
63
63
|
}),
|
|
64
64
|
);
|
|
65
65
|
}
|
|
@@ -27,7 +27,8 @@ function formatGateLine(g) {
|
|
|
27
27
|
? ''
|
|
28
28
|
: ` [kernel drift ${g.kernelBaseline} → ${g.kernelCurrent}]`;
|
|
29
29
|
const baseRef = g.baseRef ? ` [baseRef=${g.baseRef}]` : '';
|
|
30
|
-
|
|
30
|
+
const ack = g.acknowledged ? ' [ACKNOWLEDGED — this run only]' : '';
|
|
31
|
+
return ` - ${g.kind}: ${status}${drift}${baseRef}${ack}`;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function formatViolationLine(component, v) {
|
|
@@ -260,6 +260,74 @@ Symlink strategy:
|
|
|
260
260
|
specific failure up to 3 times with 250/500/1000 ms backoff. Unrelated fetch
|
|
261
261
|
failures surface immediately — no retry.
|
|
262
262
|
|
|
263
|
+
## Harness-worktree ⇄ consumer-lint-ignore interaction (Story #152)
|
|
264
|
+
|
|
265
|
+
Mandrel's own worktree isolation (above) always roots story worktrees at
|
|
266
|
+
`delivery.worktreeIsolation.root` (default `.worktrees/` at the repo root).
|
|
267
|
+
That path is separate from **the host IDE/CLI harness's own worktree
|
|
268
|
+
mechanism** — for example Claude Code, when it manages an agent session as a
|
|
269
|
+
git worktree, nests it at `.claude/worktrees/<name>/`. A mandrel delivery
|
|
270
|
+
agent can be invoked from *either* location depending on how the operator's
|
|
271
|
+
harness composes with `/deliver`: mandrel's own `.worktrees/story-<id>/` when
|
|
272
|
+
`worktreeIsolation.enabled` drives the checkout, or a harness-level
|
|
273
|
+
`.claude/worktrees/<name>/` when the harness itself provides the isolated
|
|
274
|
+
working directory mandrel runs inside.
|
|
275
|
+
|
|
276
|
+
This matters because a consumer's `pre-push` (or `pre-commit`) lint step is
|
|
277
|
+
commonly configured with an ignore glob that excludes noisy agent-tooling
|
|
278
|
+
directories, e.g. a Biome `files.includes` entry like `"!**/.claude"`. When
|
|
279
|
+
the *agent's CWD itself* resolves under `.claude/worktrees/<name>/`, a
|
|
280
|
+
lint invocation scoped to `.` (`biome check .`, or equivalent) resolves
|
|
281
|
+
every candidate path as living under the ignored `.claude` prefix — the glob
|
|
282
|
+
matches zero files, and tools that treat zero-match as failure (Biome's
|
|
283
|
+
default `check` behavior without `--no-errors-on-unmatched`) exit non-zero
|
|
284
|
+
with something like `No files were processed in the specified paths`. This
|
|
285
|
+
is a **false negative**: the changed files were never actually linted
|
|
286
|
+
against, and the hook is not reporting a real defect. It is functionally
|
|
287
|
+
distinct from a `pre-push` rejection caused by a genuine lint violation, and
|
|
288
|
+
must not be treated the same way.
|
|
289
|
+
|
|
290
|
+
**Do not resolve this by bypassing the push hook.**
|
|
291
|
+
[`rules/git-conventions.md`](../../rules/git-conventions.md) § "Push
|
|
292
|
+
Validation & Reliability" prohibits skipping hooks without explicit operator
|
|
293
|
+
authorization, and that prohibition is not weakened by this interaction —
|
|
294
|
+
the zero-match failure is a **consumer-tooling gap**, not a framework
|
|
295
|
+
authorization the agent gets to grant itself.
|
|
296
|
+
|
|
297
|
+
**Sanctioned resolution path:**
|
|
298
|
+
|
|
299
|
+
1. **Recognize the signature.** A `pre-push`/`pre-commit` failure whose
|
|
300
|
+
message is a zero-match error (`No files were processed`, `0 files
|
|
301
|
+
matched`, or equivalent for the consumer's linter) — not a reported
|
|
302
|
+
violation in a specific file — combined with an agent CWD under
|
|
303
|
+
`.claude/worktrees/` (or any other harness-managed path a consumer's lint
|
|
304
|
+
config ignores) is this known interaction, not a real lint failure.
|
|
305
|
+
2. **Fix it in the consumer, not the agent invocation.** The remedy lives in
|
|
306
|
+
the consumer's own lint command, mirroring what its `lint-staged` config
|
|
307
|
+
(if present) likely already does for the same reason: make the zero-match
|
|
308
|
+
case a no-op instead of a failure. For Biome:
|
|
309
|
+
`biome check --no-errors-on-unmatched .`. Other linters have an
|
|
310
|
+
equivalent flag (e.g. ESLint's `--no-error-on-unmatched-pattern`). This is
|
|
311
|
+
a one-line consumer-side change, typically to `.husky/pre-push` or the
|
|
312
|
+
`package.json` script it invokes.
|
|
313
|
+
3. **Escalate through the normal HITL path**, per
|
|
314
|
+
[`.agents/instructions.md` § 1.J](../../instructions.md), if the agent
|
|
315
|
+
cannot edit the consumer's hook/lint config directly (e.g. it sits outside
|
|
316
|
+
the Story's scope). Transition to `agent::blocked`, name the zero-match
|
|
317
|
+
signature and the one-line remedy in the blocker summary, and let the
|
|
318
|
+
operator apply the consumer-side fix or explicitly authorize a one-time
|
|
319
|
+
hook-skip per [`rules/git-conventions.md`](../../rules/git-conventions.md)
|
|
320
|
+
§ "Push Validation & Reliability". Explicit operator authorization is the
|
|
321
|
+
*only* circumstance under which a hook may be skipped — never as an
|
|
322
|
+
agent's unilateral default when this signature is recognized.
|
|
323
|
+
4. **Do not relocate mandrel's own worktrees to work around a harness-level
|
|
324
|
+
path.** `delivery.worktreeIsolation.root` controls where *mandrel*
|
|
325
|
+
materializes `story-<id>` worktrees (default `.worktrees/`, already
|
|
326
|
+
outside `.claude/`) and is unrelated to where the host harness places its
|
|
327
|
+
own session worktree. Changing `worktreeIsolation.root` does not fix this
|
|
328
|
+
interaction when the false negative originates from the harness's path,
|
|
329
|
+
not mandrel's.
|
|
330
|
+
|
|
263
331
|
## Fallback: single-tree mode
|
|
264
332
|
|
|
265
333
|
Set `delivery.worktreeIsolation.enabled: false` (or omit the block) to
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.81.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.80.0...mandrel-v1.81.0) (2026-07-01)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
* **baselines:** add one-shot BUNDLE_SIZE_REFRESH acknowledge for the bundle-size ratchet ([#4309](https://github.com/dsj1984/mandrel/issues/4309)) ([bc466b9](https://github.com/dsj1984/mandrel/commit/bc466b98f6c7521c3a24bbb4d93ee5169c163839))
|
|
11
|
+
|
|
12
|
+
## [1.80.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.79.0...mandrel-v1.80.0) (2026-07-01)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Chores
|
|
16
|
+
|
|
17
|
+
* **release:** force a release to ship the [#4306](https://github.com/dsj1984/mandrel/issues/4306) worktree-lifecycle doc fix ([#4307](https://github.com/dsj1984/mandrel/issues/4307)) ([fa54ebb](https://github.com/dsj1984/mandrel/commit/fa54ebb52fc8fe264bab2f4c6dea180973023c28))
|
|
18
|
+
|
|
5
19
|
## [1.79.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.78.0...mandrel-v1.79.0) (2026-06-30)
|
|
6
20
|
|
|
7
21
|
|
package/package.json
CHANGED