mandrel 2.64.0 → 2.65.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/agents/acceptance-critic.md +3 -2
- package/.agents/agents/auditor.md +3 -2
- package/.agents/agents/plan-critic.md +3 -2
- package/.agents/agents/story-worker.md +2 -2
- package/.agents/audit-checklists/quality.md +3 -0
- package/.agents/docs/agentrc-reference.json +1 -9
- package/.agents/docs/configuration.md +8 -7
- package/.agents/schemas/agentrc.schema.json +6 -13
- package/.agents/schemas/audit-rules.schema.json +1 -1
- package/.agents/schemas/story-deliver-terminal.schema.json +5 -0
- package/.agents/scripts/bootstrap.js +8 -2
- package/.agents/scripts/check-context-budget.js +1 -1
- package/.agents/scripts/lib/ITicketingProvider.js +1 -3
- package/.agents/scripts/lib/audit-suite/findings.js +1 -17
- package/.agents/scripts/lib/audit-suite/frontmatter.js +0 -28
- package/.agents/scripts/lib/audit-suite/index.js +0 -6
- package/.agents/scripts/lib/audit-suite/selector.js +0 -31
- package/.agents/scripts/lib/bootstrap/agents-md-fold.js +156 -0
- package/.agents/scripts/lib/bootstrap/commit-push.js +1 -1
- package/.agents/scripts/lib/bootstrap/manifest.js +2 -2
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +13 -29
- package/.agents/scripts/lib/config/review-chain-default.js +13 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +11 -13
- package/.agents/scripts/lib/doc-tiers.js +25 -6
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/metrics-ledger.js +0 -72
- package/.agents/scripts/lib/orchestration/code-review.js +11 -6
- package/.agents/scripts/lib/orchestration/epic-rollup.js +29 -12
- package/.agents/scripts/lib/orchestration/merge-block-class.js +20 -4
- package/.agents/scripts/lib/orchestration/merge-poll.js +41 -22
- package/.agents/scripts/lib/orchestration/required-checks.js +147 -0
- package/.agents/scripts/lib/orchestration/review-providers/code-review.js +203 -0
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +6 -4
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -2
- package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +1 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +0 -12
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +27 -11
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +112 -82
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +72 -5
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +12 -87
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +3 -0
- package/.agents/scripts/lib/templates/decomposer-prompts.js +5 -24
- package/.agents/scripts/providers/github/issues.js +14 -23
- package/.agents/scripts/sync-claude-agents.js +1 -1
- package/.agents/workflows/audit-quality.md +42 -7
- package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
- package/.agents/workflows/helpers/code-review.md +15 -38
- package/.agents/workflows/helpers/deliver-reference.md +4 -2
- package/.agents/workflows/helpers/deliver-story.md +3 -0
- package/.agents/workflows/helpers/plan-reference.md +9 -8
- package/.agents/workflows/mandrel-deliver.md +2 -1
- package/.agents/workflows/mandrel-plan.md +10 -7
- package/.agents/workflows/mandrel-update.md +5 -3
- package/docs/CHANGELOG.md +31 -0
- package/lib/cli/claude-code-version.js +73 -0
- package/lib/cli/doctor.js +2 -2
- package/lib/cli/registry.js +9 -0
- package/lib/cli/uninstall.js +37 -9
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.65.0-fold-claude-md-into-agents-md.js +38 -0
- package/package.json +2 -1
- package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +0 -99
- package/.agents/scripts/lib/audit-suite/runner.js +0 -205
- package/.agents/scripts/lib/audit-suite/substitutions.js +0 -96
- package/.agents/scripts/lib/audit-suite/workflow-loader.js +0 -37
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +0 -234
|
@@ -100,19 +100,20 @@ not re-deriving which assumptions were really the agent's to make.
|
|
|
100
100
|
|
|
101
101
|
## Gate #1 → the one advisory line
|
|
102
102
|
|
|
103
|
-
Gate #1 stops
|
|
104
|
-
|
|
105
|
-
advisory line
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
Gate #1 stops only when there is at least one HITL unknown or
|
|
104
|
+
`duplicates[]` is non-empty; otherwise the run announces the sharpened plan
|
|
105
|
+
intent plus the advisory line and continues to authoring. Everything else the
|
|
106
|
+
envelope surfaced collapses to **one advisory line** beneath the gate.
|
|
107
|
+
Nothing on that line reroutes the run or is invoked by `/mandrel-plan`; each
|
|
108
|
+
item names something the operator may prefer to do instead. Under
|
|
108
109
|
`--yes` the line is recorded and planning continues — an unattended run has
|
|
109
110
|
nobody to take an offer.
|
|
110
111
|
|
|
111
112
|
The line names, in order, whichever of these the envelope carries:
|
|
112
113
|
|
|
113
114
|
- **`duplicates[]`** — open Stories the seed resembles (never Epics). Name
|
|
114
|
-
the top one or two by id and title
|
|
115
|
-
still the operator's call.
|
|
115
|
+
the top one or two by id and title. A plan that duplicates open work is
|
|
116
|
+
still the operator's call, so a non-empty list also stops Gate #1.
|
|
116
117
|
- **Open `intake` rows** (`priorFeedback`) — CI-gap intake filings written by
|
|
117
118
|
[`file-ci-gap.js`](../../scripts/file-ci-gap.js) when a delivery reached an
|
|
118
119
|
Option-2 verdict in [`ci-remediation.md`](../../rules/ci-remediation.md).
|
|
@@ -339,7 +340,7 @@ On `dispatch: true`, dispatch **one fresh-context, maker-blind sub-agent**.
|
|
|
339
340
|
When `delivery.routing.roleScopedAgents` is enabled (the **default**), use
|
|
340
341
|
`subagent_type: plan-critic` — it boots on the role-scoped
|
|
341
342
|
[`plan-critic`](../../agents/plan-critic.md) context (its own system prompt,
|
|
342
|
-
no
|
|
343
|
+
no entry-doc @-closure) that carries the maker-blind invariant, the
|
|
343
344
|
`pre-mortem` charter, and the output shape standalone. When the kill-switch
|
|
344
345
|
is off (`roleScopedAgents: false`) or the host cannot spawn at this depth,
|
|
345
346
|
fall back to a generic sub-agent and hand it the same charter. Either way the
|
|
@@ -76,7 +76,8 @@ to an attended run.
|
|
|
76
76
|
it cannot read — a missing gate would co-dispatch against an unlanded
|
|
77
77
|
blocker.
|
|
78
78
|
|
|
79
|
-
2. **
|
|
79
|
+
2. **Announce (N>1).** Present the resolved order and proceed — do not wait
|
|
80
|
+
for confirmation; the operator can interject mid-run to change it.
|
|
80
81
|
|
|
81
82
|
3. **Run the beat.** One command per beat, repeated until the envelope reports
|
|
82
83
|
the run `done`:
|
|
@@ -71,13 +71,16 @@ assumed; a **HITL** unknown goes to Gate #1. Under `--yes` do not ask free-form
|
|
|
71
71
|
operator questions — AFK unknowns are still researched; only HITL unknowns land
|
|
72
72
|
in Key Assumptions, each a decision-made-by-default.
|
|
73
73
|
|
|
74
|
-
**Gate #1** — STOP
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
**Gate #1** — STOP only when a HITL unknown the operator owns exists or
|
|
75
|
+
`duplicates[]` is non-empty (planning a duplicate of open work stays the
|
|
76
|
+
operator's call): confirm the sharpened plan intent and settle it. Otherwise
|
|
77
|
+
announce the sharpened intent and the advisory line, and continue to
|
|
78
|
+
authoring. Everything else the envelope surfaced — `duplicates[]`, open
|
|
79
|
+
`intake` rows, a truthy `memoryPoolAdvisory.recommend`, a truthy
|
|
80
|
+
`complexitySignals.uiSurface` naming [`/prototype`](prototype.md) (never
|
|
81
|
+
invoke it here) — collapses to **one advisory line** under the gate, which
|
|
82
|
+
never reroutes the run ([ref](helpers/plan-reference.md)).
|
|
83
|
+
Under `--yes`, auto-proceed.
|
|
81
84
|
|
|
82
85
|
### 2. Author
|
|
83
86
|
|
|
@@ -200,13 +200,15 @@ non-optional.
|
|
|
200
200
|
|
|
201
201
|
## Step 4 — Review the surfaced changelog and update consumer-side guidance
|
|
202
202
|
|
|
203
|
-
Framework upgrades change behaviour the consumer's own `AGENTS.md`
|
|
204
|
-
|
|
203
|
+
Framework upgrades change behaviour the consumer's own `AGENTS.md` and
|
|
204
|
+
runbooks often encode. Step 1 already printed the changelog
|
|
205
205
|
for the applied range — that output is your source of truth (re-read the
|
|
206
206
|
transcript or the GitHub Releases page if it scrolled past). For each entry
|
|
207
207
|
between the installed and target versions:
|
|
208
208
|
|
|
209
|
-
1. **Consumer `AGENTS.md
|
|
209
|
+
1. **Consumer `AGENTS.md`.** It is the entry doc — `mandrel update` folds a
|
|
210
|
+
root `CLAUDE.md` into it and deletes `CLAUDE.md`, so reconcile the folded
|
|
211
|
+
content here. Update instructions so a fresh
|
|
210
212
|
agent reading them in isolation produces output that passes the
|
|
211
213
|
framework's new validators; remove or rewrite instructions that
|
|
212
214
|
contradict a tightened rule.
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,37 @@ All notable changes to this project will be documented in this file.
|
|
|
15
15
|
-->
|
|
16
16
|
<!-- markdownlint-disable-file MD004 MD012 MD037 -->
|
|
17
17
|
|
|
18
|
+
## [2.65.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.64.0...mandrel-v2.65.0) (2026-09-24)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### ⚠ BREAKING CHANGES
|
|
22
|
+
|
|
23
|
+
* consumers must run Claude Code >= 2.1.277, which loads a root AGENTS.md; `mandrel update` folds the root CLAUDE.md into AGENTS.md and deletes CLAUDE.md.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
* add a low-effort model bug review to close and pin role-agent effort ([#5426](https://github.com/dsj1984/mandrel/issues/5426)) ([#5431](https://github.com/dsj1984/mandrel/issues/5431)) ([ec39643](https://github.com/dsj1984/mandrel/commit/ec3964390210e140ebaa324bbfe27b67fd470fa4))
|
|
28
|
+
* **audit-quality:** add a property-based coverage dimension (refs [#5425](https://github.com/dsj1984/mandrel/issues/5425)) ([#5428](https://github.com/dsj1984/mandrel/issues/5428)) ([f78fd62](https://github.com/dsj1984/mandrel/commit/f78fd62edda9060580b1cbacaec1cc82875040d1))
|
|
29
|
+
* cut consumers over to AGENTS.md: bootstrap wiring, CLAUDE.md fold migration, uninstall revert, Claude Code version doctor check ([#5410](https://github.com/dsj1984/mandrel/issues/5410)) ([#5411](https://github.com/dsj1984/mandrel/issues/5411)) ([df69d59](https://github.com/dsj1984/mandrel/commit/df69d59e7ced7342f321b8435ab50b317655f7f3))
|
|
30
|
+
* make AGENTS.md mandrel's always-loaded entry doc: host-faithful closure resolver, delete CLAUDE.md, rewrite AGENTS.md ([#5409](https://github.com/dsj1984/mandrel/issues/5409)) ([#5413](https://github.com/dsj1984/mandrel/issues/5413)) ([929e5bb](https://github.com/dsj1984/mandrel/commit/929e5bb6834e1a25152163ac34a87d327034ce05))
|
|
31
|
+
* trim legacy story-author scaffolding and drop two needless workflow stops ([#5427](https://github.com/dsj1984/mandrel/issues/5427)) ([#5429](https://github.com/dsj1984/mandrel/issues/5429)) ([7b39d36](https://github.com/dsj1984/mandrel/commit/7b39d3637b4911ad7122d6cbaef3c19c88704101))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
* merge wait: fail fast on a red required check without waiting for unrelated jobs ([#5415](https://github.com/dsj1984/mandrel/issues/5415)) ([#5420](https://github.com/dsj1984/mandrel/issues/5420)) ([381d8ff](https://github.com/dsj1984/mandrel/commit/381d8ff4145ec3210d984936904368d51cde426a))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Performance
|
|
40
|
+
|
|
41
|
+
* close: per-phase timing, faster merge observation, concurrent post-land GitHub steps ([#5417](https://github.com/dsj1984/mandrel/issues/5417)) ([#5423](https://github.com/dsj1984/mandrel/issues/5423)) ([2d3c537](https://github.com/dsj1984/mandrel/commit/2d3c5373402ad0e212951b70ca2880204da3a8f3))
|
|
42
|
+
* epic rollup: stop reading every Epic's sub-issues when a Story has no native parent ([#5414](https://github.com/dsj1984/mandrel/issues/5414)) ([#5419](https://github.com/dsj1984/mandrel/issues/5419)) ([025c650](https://github.com/dsj1984/mandrel/commit/025c650409a757b47be9847a4584e5e45fdef8e8))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
* retire the Story-scope local-lens pass from close ([#5416](https://github.com/dsj1984/mandrel/issues/5416)) ([#5422](https://github.com/dsj1984/mandrel/issues/5422)) ([ecd86da](https://github.com/dsj1984/mandrel/commit/ecd86daa1a9d1ddff7f58e7f832e491a9c58cb68))
|
|
48
|
+
|
|
18
49
|
## [2.64.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.63.0...mandrel-v2.64.0) (2026-09-19)
|
|
19
50
|
|
|
20
51
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// lib/cli/claude-code-version.js
|
|
2
|
+
/**
|
|
3
|
+
* `mandrel doctor` check `claude-code-version`: an AGENTS.md-only project
|
|
4
|
+
* needs a Claude Code host that loads AGENTS.md at all. Below the floor the
|
|
5
|
+
* framework never hydrates, so the check fails. A missing `claude` binary,
|
|
6
|
+
* unparseable output, or a project still carrying CLAUDE.md skips with
|
|
7
|
+
* `ok: true` so headless/CI hosts are never blocked. The `claude --version`
|
|
8
|
+
* spawn is injected by the registry (fixed argv, never user input); with no
|
|
9
|
+
* runner the check skips.
|
|
10
|
+
*
|
|
11
|
+
* @module cli/claude-code-version
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
|
|
17
|
+
import { compareVersions } from './version-helpers.js';
|
|
18
|
+
|
|
19
|
+
/** Oldest Claude Code release that loads a root AGENTS.md. */
|
|
20
|
+
export const CLAUDE_CODE_AGENTS_MD_FLOOR = '2.1.277';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} text
|
|
24
|
+
* @returns {string|null} the leading `x.y.z`, or null
|
|
25
|
+
*/
|
|
26
|
+
export function parseClaudeVersion(text) {
|
|
27
|
+
const match = /^\s*v?(\d+\.\d+\.\d+)/.exec(text ?? '');
|
|
28
|
+
return match ? match[1] : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {{
|
|
33
|
+
* projectRoot?: string,
|
|
34
|
+
* cwd?: () => string,
|
|
35
|
+
* existsSync?: (p: string) => boolean,
|
|
36
|
+
* runClaude?: () => { status: number|null, stdout: string, error?: NodeJS.ErrnoException },
|
|
37
|
+
* }} [opts]
|
|
38
|
+
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
39
|
+
*/
|
|
40
|
+
export function runClaudeCodeVersion({
|
|
41
|
+
projectRoot,
|
|
42
|
+
cwd = () => process.cwd(),
|
|
43
|
+
existsSync = fs.existsSync,
|
|
44
|
+
runClaude = () => ({ status: null, stdout: '', error: new Error('none') }),
|
|
45
|
+
} = {}) {
|
|
46
|
+
const root = projectRoot ?? cwd();
|
|
47
|
+
if (existsSync(path.join(root, 'CLAUDE.md'))) {
|
|
48
|
+
return { ok: true, detail: 'skipped: CLAUDE.md present' };
|
|
49
|
+
}
|
|
50
|
+
if (!existsSync(path.join(root, 'AGENTS.md'))) {
|
|
51
|
+
return { ok: true, detail: 'skipped: no AGENTS.md' };
|
|
52
|
+
}
|
|
53
|
+
const r = runClaude();
|
|
54
|
+
if (r.error || r.status !== 0) {
|
|
55
|
+
return { ok: true, detail: 'skipped: claude not found on PATH' };
|
|
56
|
+
}
|
|
57
|
+
const version = parseClaudeVersion(r.stdout);
|
|
58
|
+
if (!version) {
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
detail: 'skipped: unparseable `claude --version` output',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const detail = `Claude Code ${version} (required >=${CLAUDE_CODE_AGENTS_MD_FLOOR} to load AGENTS.md)`;
|
|
65
|
+
if (compareVersions(version, CLAUDE_CODE_AGENTS_MD_FLOOR) >= 0) {
|
|
66
|
+
return { ok: true, detail };
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
detail,
|
|
71
|
+
remedy: `Upgrade Claude Code to >=${CLAUDE_CODE_AGENTS_MD_FLOOR} (e.g. \`claude update\`) — older hosts do not load AGENTS.md, so the framework never hydrates.`,
|
|
72
|
+
};
|
|
73
|
+
}
|
package/lib/cli/doctor.js
CHANGED
|
@@ -54,7 +54,7 @@ function formatSummary(passed, total) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* Informational line for the
|
|
57
|
+
* Informational line for the host entry doc's always-loaded closure (file count,
|
|
58
58
|
* KB). Never counted toward the verdict; a resolve failure degrades to a
|
|
59
59
|
* neutral line.
|
|
60
60
|
*
|
|
@@ -73,7 +73,7 @@ export function formatClosureReport({
|
|
|
73
73
|
return `ℹ ${label} always-loaded closure unavailable\n`;
|
|
74
74
|
}
|
|
75
75
|
if (files.length === 0) {
|
|
76
|
-
return `ℹ ${label} no CLAUDE.md
|
|
76
|
+
return `ℹ ${label} no entry-doc closure found (CLAUDE.md or AGENTS.md)\n`;
|
|
77
77
|
}
|
|
78
78
|
const kb = (tierTotalBytes(files) / 1024).toFixed(1);
|
|
79
79
|
return `ℹ ${label} ${files.length} file(s), ${kb} KB always-loaded\n`;
|
package/lib/cli/registry.js
CHANGED
|
@@ -28,6 +28,7 @@ import { isCommandExcluded } from '../../.agents/scripts/lib/command-header.js';
|
|
|
28
28
|
import { getDeliveryRouting } from '../../.agents/scripts/lib/config/delivery-routing.js';
|
|
29
29
|
import { isResolvable } from '../../.agents/scripts/lib/runtime-deps/dep-resolution.js';
|
|
30
30
|
import { describeParserMajorError } from '../../.agents/scripts/lib/runtime-deps/parser-major.js';
|
|
31
|
+
import { runClaudeCodeVersion } from './claude-code-version.js';
|
|
31
32
|
import {
|
|
32
33
|
defaultResolvePackageRoot,
|
|
33
34
|
listFiles as listPayloadFiles,
|
|
@@ -958,6 +959,14 @@ export const registry = [
|
|
|
958
959
|
name: 'agents-drift',
|
|
959
960
|
run: (opts) => runAgentsDrift(opts),
|
|
960
961
|
},
|
|
962
|
+
{
|
|
963
|
+
name: 'claude-code-version',
|
|
964
|
+
run: (opts) =>
|
|
965
|
+
runClaudeCodeVersion({
|
|
966
|
+
runClaude: () => spawn('claude', ['--version'], { timeout: 10_000 }),
|
|
967
|
+
...opts,
|
|
968
|
+
}),
|
|
969
|
+
},
|
|
961
970
|
{
|
|
962
971
|
name: 'merge-driver',
|
|
963
972
|
run: (opts) => runMergeDriver(opts),
|
package/lib/cli/uninstall.js
CHANGED
|
@@ -21,8 +21,8 @@ import {
|
|
|
21
21
|
BOOTSTRAP_COMMAND,
|
|
22
22
|
GITIGNORE_BLOCKS,
|
|
23
23
|
SYNC_COMMAND,
|
|
24
|
+
SYSTEM_PROMPT_AGENTS_MD,
|
|
24
25
|
SYSTEM_PROMPT_BLOCK,
|
|
25
|
-
SYSTEM_PROMPT_CLAUDE_MD,
|
|
26
26
|
SYSTEM_PROMPT_IMPORT,
|
|
27
27
|
} from '../../.agents/scripts/lib/bootstrap/project-bootstrap.js';
|
|
28
28
|
import {
|
|
@@ -60,17 +60,17 @@ const FRAMEWORK_NPM_SCRIPTS = Object.freeze({
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Strip the system-prompt import block from
|
|
64
|
-
* only when byte-identical to the install template — a heading-only
|
|
65
|
-
* would delete an operator's all-headings file.
|
|
63
|
+
* Strip the system-prompt import block from a root entry doc. The file is
|
|
64
|
+
* deleted only when byte-identical to the install template — a heading-only
|
|
65
|
+
* heuristic would delete an operator's all-headings file.
|
|
66
66
|
*
|
|
67
|
+
* @param {string} rel - `AGENTS.md`, or a legacy un-migrated `CLAUDE.md`.
|
|
67
68
|
* @param {string} projectRoot
|
|
68
69
|
* @param {typeof fs} fsImpl
|
|
69
70
|
* @returns {ReversalOutcome}
|
|
70
71
|
*/
|
|
71
|
-
function
|
|
72
|
-
const target = path.join(projectRoot,
|
|
73
|
-
const rel = 'CLAUDE.md';
|
|
72
|
+
function revertEntryDoc(rel, projectRoot, fsImpl) {
|
|
73
|
+
const target = path.join(projectRoot, rel);
|
|
74
74
|
if (!fsImpl.existsSync(target)) {
|
|
75
75
|
return { kind: 'skipped', target: rel, detail: 'file absent' };
|
|
76
76
|
}
|
|
@@ -78,12 +78,12 @@ function revertClaudeMd(projectRoot, fsImpl) {
|
|
|
78
78
|
if (!original.includes(SYSTEM_PROMPT_IMPORT)) {
|
|
79
79
|
return { kind: 'skipped', target: rel, detail: 'import already absent' };
|
|
80
80
|
}
|
|
81
|
-
if (original.trim() ===
|
|
81
|
+
if (original.trim() === SYSTEM_PROMPT_AGENTS_MD.trim()) {
|
|
82
82
|
fsImpl.rmSync(target, { force: true });
|
|
83
83
|
return {
|
|
84
84
|
kind: 'reverted',
|
|
85
85
|
target: rel,
|
|
86
|
-
detail:
|
|
86
|
+
detail: `removed install-created ${rel}`,
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
// Full block first, then the bare import line for a hand-edited block.
|
|
@@ -109,6 +109,33 @@ function revertClaudeMd(projectRoot, fsImpl) {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} projectRoot
|
|
114
|
+
* @param {typeof fs} fsImpl
|
|
115
|
+
* @returns {ReversalOutcome}
|
|
116
|
+
*/
|
|
117
|
+
function revertAgentsMd(projectRoot, fsImpl) {
|
|
118
|
+
return revertEntryDoc('AGENTS.md', projectRoot, fsImpl);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Pre-2.65 ledgers name CLAUDE.md. An un-migrated CLAUDE.md reverts as
|
|
123
|
+
* before; once the update migration folded it away, the wiring lives in
|
|
124
|
+
* AGENTS.md, so that is reverted instead. CLAUDE.md is never recreated.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} projectRoot
|
|
127
|
+
* @param {typeof fs} fsImpl
|
|
128
|
+
* @returns {ReversalOutcome}
|
|
129
|
+
*/
|
|
130
|
+
function revertClaudeMd(projectRoot, fsImpl) {
|
|
131
|
+
const legacy = fsImpl.existsSync(path.join(projectRoot, 'CLAUDE.md'));
|
|
132
|
+
return revertEntryDoc(
|
|
133
|
+
legacy ? 'CLAUDE.md' : 'AGENTS.md',
|
|
134
|
+
projectRoot,
|
|
135
|
+
fsImpl,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
112
139
|
/**
|
|
113
140
|
* Splice the sync hook and any legacy plugin-enablement keys out of
|
|
114
141
|
* `.claude/settings.json`, keeping everything else; delete the file if empty.
|
|
@@ -405,6 +432,7 @@ function revertPreCommitHook(projectRoot, fsImpl) {
|
|
|
405
432
|
* @type {Readonly<Record<string, (root: string, fsImpl: typeof fs, executedAction?: string) => ReversalOutcome>>}
|
|
406
433
|
*/
|
|
407
434
|
const REVERSAL_BY_TARGET = Object.freeze({
|
|
435
|
+
'AGENTS.md': revertAgentsMd,
|
|
408
436
|
'CLAUDE.md': revertClaudeMd,
|
|
409
437
|
'.claude/settings.json': revertClaudeSettings,
|
|
410
438
|
'.claude/plugins/mandrel': revertClaudeCommands,
|
package/lib/migrations/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { retireDeliveryLimitKnobs } from './steps/2.57.0-retire-delivery-limit-k
|
|
|
16
16
|
import { retirePlanningLimitKnobs } from './steps/2.57.0-retire-planning-limit-knobs.js';
|
|
17
17
|
import { retireAuditResultsAutoFile } from './steps/2.60.0-retire-audit-results-autofile.js';
|
|
18
18
|
import { baselineMergeQueueShape } from './steps/2.63.0-baseline-merge-queue-shape.js';
|
|
19
|
+
import { foldClaudeMdIntoAgentsMdStep } from './steps/2.65.0-fold-claude-md-into-agents-md.js';
|
|
19
20
|
import { stripRemovedAgentrcKeys } from './steps/strip-removed-agentrc-keys.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -40,6 +41,7 @@ export const migrations = [
|
|
|
40
41
|
retireAuditResultsAutoFile,
|
|
41
42
|
stripRemovedAgentrcKeys,
|
|
42
43
|
baselineMergeQueueShape,
|
|
44
|
+
foldClaudeMdIntoAgentsMdStep,
|
|
43
45
|
];
|
|
44
46
|
|
|
45
47
|
export { compareVersions };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// lib/migrations/steps/2.65.0-fold-claude-md-into-agents-md.js
|
|
2
|
+
/**
|
|
3
|
+
* Fold a consumer's root CLAUDE.md into AGENTS.md and delete CLAUDE.md.
|
|
4
|
+
* The host reads CLAUDE.md exclusively whenever it exists, so a surviving one
|
|
5
|
+
* would shadow the AGENTS.md wiring. Shares the bootstrap fold contract.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import nodeFs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
foldClaudeMdIntoAgentsMd,
|
|
13
|
+
LEGACY_ENTRY_DOC,
|
|
14
|
+
} from '../../../.agents/scripts/lib/bootstrap/agents-md-fold.js';
|
|
15
|
+
|
|
16
|
+
export const foldClaudeMdIntoAgentsMdStep = {
|
|
17
|
+
version: '2.65.0',
|
|
18
|
+
description:
|
|
19
|
+
'fold the root CLAUDE.md into AGENTS.md and delete it — AGENTS.md is ' +
|
|
20
|
+
'now the entry doc (Story #5410)',
|
|
21
|
+
/**
|
|
22
|
+
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
23
|
+
* @param {typeof nodeFs} [fsImpl]
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
detect(ctx, fsImpl = ctx?.fs ?? nodeFs) {
|
|
27
|
+
const root = ctx?.projectRoot ?? process.cwd();
|
|
28
|
+
return fsImpl.existsSync(path.join(root, LEGACY_ENTRY_DOC));
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
32
|
+
* @param {typeof nodeFs} [fsImpl]
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
35
|
+
apply(ctx, fsImpl = ctx?.fs ?? nodeFs) {
|
|
36
|
+
foldClaudeMdIntoAgentsMd(ctx?.projectRoot ?? process.cwd(), fsImpl);
|
|
37
|
+
},
|
|
38
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mandrel",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.65.0",
|
|
4
4
|
"description": "Claude Code-first opinionated workflow framework: instructions, skills, rules, and SDLC workflows that govern AI coding assistants.",
|
|
5
5
|
"files": [
|
|
6
6
|
".agents/",
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
"@cucumber/gherkin": "^42.0.1",
|
|
103
103
|
"c8": "^11.0.0",
|
|
104
104
|
"chokidar": "^5.0.0",
|
|
105
|
+
"fast-check": "^4.10.2",
|
|
105
106
|
"husky": "^9.1.7",
|
|
106
107
|
"jscpd": "^4.2.5",
|
|
107
108
|
"knip": "^6.17.1",
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Close-scope lens diff-floor: a diff under the changed-line floor that
|
|
3
|
-
* touches no sensitive path skips lens materialization. Fail-open — every
|
|
4
|
-
* degraded input (unknown count, unreadable manifest, floor `0`) means "do
|
|
5
|
-
* not skip". Sensitive classes come from the same selector as review depth,
|
|
6
|
-
* so the two never disagree. Total; no I/O beyond the injected git spawn.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { gitSpawn } from '../git-utils.js';
|
|
10
|
-
import { readNumstatRows } from '../orchestration/diff-magnitude.js';
|
|
11
|
-
import { selectSensitivePathClasses } from './selector.js';
|
|
12
|
-
|
|
13
|
-
/** Measured: zero-yield closes clustered well under this size. */
|
|
14
|
-
export const DEFAULT_LENS_DIFF_FLOOR = 40;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Whole-diff additions + deletions (no companion exemption: the question is
|
|
18
|
-
* "is this diff small"). Shares the numstat parse with the light path's
|
|
19
|
-
* backstop. `null` on any git failure; binary rows count 0.
|
|
20
|
-
*
|
|
21
|
-
* @param {{
|
|
22
|
-
* baseRef: string,
|
|
23
|
-
* headRef: string,
|
|
24
|
-
* cwd?: string,
|
|
25
|
-
* gitSpawnFn?: typeof gitSpawn,
|
|
26
|
-
* }} args
|
|
27
|
-
* @returns {number|null}
|
|
28
|
-
*/
|
|
29
|
-
export function countChangedLines({
|
|
30
|
-
baseRef,
|
|
31
|
-
headRef,
|
|
32
|
-
cwd = process.cwd(),
|
|
33
|
-
gitSpawnFn = gitSpawn,
|
|
34
|
-
} = {}) {
|
|
35
|
-
const rows = readNumstatRows({ baseRef, headRef, cwd, gitSpawnFn });
|
|
36
|
-
if (rows === null) return null;
|
|
37
|
-
return rows.reduce((total, row) => total + row.additions + row.deletions, 0);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Skip only when the floor is enabled, the count is known and below it, and
|
|
42
|
-
* no sensitive-path class matches; every other state names its reason.
|
|
43
|
-
*
|
|
44
|
-
* @param {{
|
|
45
|
-
* changedFiles?: string[]|null,
|
|
46
|
-
* changedLineCount?: number|null,
|
|
47
|
-
* floor?: number,
|
|
48
|
-
* injectedRules?: object,
|
|
49
|
-
* selectSensitivePathClassesFn?: typeof selectSensitivePathClasses,
|
|
50
|
-
* }} [input]
|
|
51
|
-
* @returns {{
|
|
52
|
-
* skip: boolean,
|
|
53
|
-
* reason: 'floor-disabled'|'line-count-unknown'|'at-or-above-floor'|'sensitive-classes-unknown'|'sensitive-path-hit'|'below-floor',
|
|
54
|
-
* floor: number,
|
|
55
|
-
* changedLineCount: number|null,
|
|
56
|
-
* sensitiveClasses: string[],
|
|
57
|
-
* }}
|
|
58
|
-
*/
|
|
59
|
-
export function evaluateLensDiffFloor(input = {}) {
|
|
60
|
-
const floorRaw = input.floor;
|
|
61
|
-
const floor =
|
|
62
|
-
typeof floorRaw === 'number' && Number.isInteger(floorRaw) && floorRaw >= 0
|
|
63
|
-
? floorRaw
|
|
64
|
-
: DEFAULT_LENS_DIFF_FLOOR;
|
|
65
|
-
const count =
|
|
66
|
-
typeof input.changedLineCount === 'number' &&
|
|
67
|
-
Number.isFinite(input.changedLineCount) &&
|
|
68
|
-
input.changedLineCount >= 0
|
|
69
|
-
? Math.floor(input.changedLineCount)
|
|
70
|
-
: null;
|
|
71
|
-
const verdict = (skip, reason, sensitiveClasses = []) => ({
|
|
72
|
-
skip,
|
|
73
|
-
reason,
|
|
74
|
-
floor,
|
|
75
|
-
changedLineCount: count,
|
|
76
|
-
sensitiveClasses,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
if (floor <= 0) return verdict(false, 'floor-disabled');
|
|
80
|
-
if (count === null) return verdict(false, 'line-count-unknown');
|
|
81
|
-
if (count >= floor) return verdict(false, 'at-or-above-floor');
|
|
82
|
-
|
|
83
|
-
const select =
|
|
84
|
-
input.selectSensitivePathClassesFn ?? selectSensitivePathClasses;
|
|
85
|
-
let classes;
|
|
86
|
-
try {
|
|
87
|
-
classes = select({
|
|
88
|
-
changedFiles: Array.isArray(input.changedFiles) ? input.changedFiles : [],
|
|
89
|
-
injectedRules: input.injectedRules,
|
|
90
|
-
});
|
|
91
|
-
} catch {
|
|
92
|
-
return verdict(false, 'sensitive-classes-unknown');
|
|
93
|
-
}
|
|
94
|
-
const matched = Array.isArray(classes) ? classes : [];
|
|
95
|
-
if (matched.length > 0) {
|
|
96
|
-
return verdict(false, 'sensitive-path-hit', matched);
|
|
97
|
-
}
|
|
98
|
-
return verdict(true, 'below-floor');
|
|
99
|
-
}
|