mandrel 2.63.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/ci-red-handling.js +73 -0
- package/.agents/scripts/lib/orchestration/code-review.js +11 -6
- package/.agents/scripts/lib/orchestration/deliver-recover.js +56 -11
- 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 +135 -20
- 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/pr-watch-with-update.js +13 -19
- 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 +38 -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
|
@@ -138,38 +138,29 @@ export class IssuesGateway {
|
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
140
|
* Resolve an issue's container parent in one request via `Issue.parent`.
|
|
141
|
-
*
|
|
142
|
-
* return `null`, leaving the caller's checklist fallback to run.
|
|
141
|
+
* `null` means "no parent"; a degraded lookup throws after retries.
|
|
143
142
|
*
|
|
144
143
|
* @param {number} number Issue number whose parent to resolve.
|
|
145
144
|
* @returns {Promise<object|null>} Mapped parent ticket, or null.
|
|
145
|
+
* @throws {Error} When the lookup degrades.
|
|
146
146
|
* @field-manifest GraphQL Issue.parent: number, id, title, body, state,
|
|
147
147
|
* labels.nodes.name, assignees.nodes.login
|
|
148
148
|
*/
|
|
149
149
|
async getParentIssue(number) {
|
|
150
150
|
const issueNumber = Number(number);
|
|
151
151
|
if (!Number.isInteger(issueNumber) || issueNumber <= 0) return null;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
},
|
|
165
|
-
);
|
|
166
|
-
} catch (err) {
|
|
167
|
-
Logger.warn(
|
|
168
|
-
`[GitHubProvider] parent lookup for #${issueNumber} degraded to none ` +
|
|
169
|
-
`(${err?.message ?? err}).`,
|
|
170
|
-
);
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
152
|
+
const data = await withTransientRetry(
|
|
153
|
+
() =>
|
|
154
|
+
this.ghGraphql(
|
|
155
|
+
PARENT_ISSUE_QUERY,
|
|
156
|
+
{ owner: this.owner, repo: this.repo, number: issueNumber },
|
|
157
|
+
{ headers: { 'GraphQL-Features': 'sub_issues' } },
|
|
158
|
+
),
|
|
159
|
+
{
|
|
160
|
+
label: `getParentIssue #${issueNumber}`,
|
|
161
|
+
onRetry: defaultRetryWarn,
|
|
162
|
+
},
|
|
163
|
+
);
|
|
173
164
|
return subIssueNodeToTicket(data?.repository?.issue?.parent ?? null);
|
|
174
165
|
}
|
|
175
166
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Project `.agents/agents/` and `.agents/local/agents/` into a flat
|
|
5
5
|
* `.claude/agents/` tree — the sibling of `sync-claude-commands.js`, with the
|
|
6
6
|
* same payload-wins shadowing and orphan-reap. A role agent runs on its own
|
|
7
|
-
* system prompt (no
|
|
7
|
+
* system prompt (no entry-doc `@`-import closure), which is the point of routing to it.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
// cli-opt-out: top-level-await script with no main() function — runAsCli wraps an async main, which doesn't apply here.
|
|
@@ -12,7 +12,8 @@ the Story under audit. The shared lens machinery lives in
|
|
|
12
12
|
`{{auditOutputDir}}/audit-quality-results.md`. Each finding carries a
|
|
13
13
|
**Category:** (`Flakiness | Coverage | Performance | Mocking | Test Plans`); the
|
|
14
14
|
report adds a **Test Strategy Assessment** table (Unit / Integration / E2E /
|
|
15
|
-
Test Plans: Healthy / Needs Work / Missing
|
|
15
|
+
Test Plans / Property-Based Testing: Healthy / Needs Work / Missing, or `N/A`
|
|
16
|
+
for Property-Based Testing when no module is a candidate).
|
|
16
17
|
|
|
17
18
|
## Scope
|
|
18
19
|
|
|
@@ -130,6 +131,36 @@ Evaluate the gathered context against the following test quality dimensions:
|
|
|
130
131
|
finding here. Route the *architectural* framing of the same defect to
|
|
131
132
|
[`audit-architecture`](audit-architecture.md)'s Shipped-But-Never-Wired
|
|
132
133
|
dimension; this lens owns the **missing-test** framing.
|
|
134
|
+
8. **Property-Based Coverage — Invariants Tested Only by Examples.** Flag a
|
|
135
|
+
module whose correctness rests on an invariant but whose tests are all
|
|
136
|
+
hand-picked examples, which structurally cannot reach the inputs nobody
|
|
137
|
+
thought to pick. A module is a candidate **only** on code evidence: a
|
|
138
|
+
documented invariant or "never"/"always" claim in a header comment; an
|
|
139
|
+
explicit state machine or status/label transition table; an
|
|
140
|
+
encode/decode, parse/serialize or normalise pair (round-trip); a
|
|
141
|
+
merge/dedup/sort/scheduler function; bounded-concurrency or retry
|
|
142
|
+
coordination over async I/O; or an idempotency claim. A module with no
|
|
143
|
+
stated or implied invariant is **never** a finding. Rank candidates by
|
|
144
|
+
Step 0's churn × coverage gap and cite their `baselines/` coverage/CRAP row
|
|
145
|
+
where one exists.
|
|
146
|
+
|
|
147
|
+
- **Toolchain by ecosystem, never one library.** Detect an existing
|
|
148
|
+
property-testing library from the consumer's manifests (e.g.
|
|
149
|
+
`fast-check` for JS/TS, `hypothesis` for Python, `proptest`/`quickcheck`
|
|
150
|
+
for Rust, `jqwik` for the JVM, `rapid`/`gopter` for Go); recommend the
|
|
151
|
+
ecosystem-idiomatic one only when none is present.
|
|
152
|
+
- Severity: an async/concurrency coordinator, or a guard whose
|
|
153
|
+
invariant protects an irreversible write, tested only by examples →
|
|
154
|
+
**High**; any other invariant-bearing module with example-only tests →
|
|
155
|
+
**Medium**; toolchain absent with no High/Medium candidate → **one Low**
|
|
156
|
+
roll-up finding, not one per module.
|
|
157
|
+
- Category: file under `Coverage`. A property test whose seed is
|
|
158
|
+
neither pinned nor printed on failure goes under `Flakiness`: a red that
|
|
159
|
+
cannot be reproduced breaks the reproducibility the rubric demands.
|
|
160
|
+
- **Name the property.** Each finding states the invariant as a testable
|
|
161
|
+
property (e.g. `decode(encode(x)) === x`; "no transition leaves a
|
|
162
|
+
terminal state") plus its generator shape (the input domain to draw
|
|
163
|
+
from) — never a bare "add property tests".
|
|
133
164
|
|
|
134
165
|
## Constraint (lens-specific carve-out)
|
|
135
166
|
|
|
@@ -150,10 +181,14 @@ table:
|
|
|
150
181
|
|
|
151
182
|
## Test Strategy Assessment
|
|
152
183
|
|
|
153
|
-
| Layer
|
|
154
|
-
|
|
|
155
|
-
| Unit Testing
|
|
156
|
-
| Integration Testing
|
|
157
|
-
| E2E Testing
|
|
158
|
-
| Test Plans
|
|
184
|
+
| Layer | Status | Notes |
|
|
185
|
+
| ---------------------- | -------------------------------------- | -------------- |
|
|
186
|
+
| Unit Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
|
|
187
|
+
| Integration Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
|
|
188
|
+
| E2E Testing | [Healthy / Needs Work / Missing] | [Brief reason] |
|
|
189
|
+
| Test Plans | [Healthy / Needs Work / Missing] | [Brief reason] |
|
|
190
|
+
| Property-Based Testing | [Healthy / Needs Work / Missing / N/A] | [Brief reason] |
|
|
159
191
|
```
|
|
192
|
+
|
|
193
|
+
`Property-Based Testing` reads `N/A` when the repo has no candidate module
|
|
194
|
+
(dimension 8), so a repo with no invariant-bearing code is not nagged.
|
|
@@ -69,7 +69,7 @@ per-criterion, mid-delivery, and evaluates the actual work product.
|
|
|
69
69
|
> `delivery.routing.roleScopedAgents` is enabled (the **default**), use
|
|
70
70
|
> `subagent_type: acceptance-critic`: it boots on the role-scoped
|
|
71
71
|
> [`acceptance-critic`](../../agents/acceptance-critic.md) context (its own
|
|
72
|
-
> system prompt, no
|
|
72
|
+
> system prompt, no entry-doc @-closure) carrying the maker-blind
|
|
73
73
|
> invariant and the verdict schema standalone. With the kill-switch off
|
|
74
74
|
> (`roleScopedAgents: false`), fall back to
|
|
75
75
|
> `subagent_type: general-purpose`. This loop already runs inside a Story
|
|
@@ -74,8 +74,8 @@ How each tier changes the review protocol:
|
|
|
74
74
|
adversarial pass over the diff hunting for integration regressions and
|
|
75
75
|
security-relevant edges before findings are finalized.
|
|
76
76
|
|
|
77
|
-
The LLM-backed review providers (codex, security-review,
|
|
78
|
-
the resolved `depth` into the prompt/instructions they emit so the underlying
|
|
77
|
+
The LLM-backed review providers (code-review, codex, security-review,
|
|
78
|
+
ultrareview) render the resolved `depth` into the prompt/instructions they emit so the underlying
|
|
79
79
|
model actually changes thoroughness. The native provider deliberately ignores
|
|
80
80
|
`depth` — its mechanical lint + maintainability sweep already scales with diff
|
|
81
81
|
size, and there is no "review harder" knob a deterministic scorer can turn (its
|
|
@@ -110,44 +110,21 @@ The pipeline will:
|
|
|
110
110
|
- Run a focused lint check on the change set.
|
|
111
111
|
- Post a structured summary report to the `[TICKET_ID]` issue.
|
|
112
112
|
|
|
113
|
-
###
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
`git diff --name-only`).
|
|
122
|
-
2. Selects the **local-tier** lenses that own a concern decidable from a single
|
|
123
|
-
Story's diff — `resolveLensTier(lens) === 'local'` **plus** the pure
|
|
124
|
-
`matchesAnyFilePattern` matcher against the diff (the audit-suite SDK's
|
|
125
|
-
[`selectLocalLenses`](../../scripts/lib/audit-suite/selector.js)). This is
|
|
126
|
-
deliberately **not** `selectAudits`: `selectAudits` unions in keyword and
|
|
127
|
-
gate matches and has no per-tier gate, so it would widen the roster past the
|
|
128
|
-
footprint-matched local set this tier owns.
|
|
129
|
-
3. Materializes the matched roster at **`light`** depth
|
|
130
|
-
(`STORY_SCOPE_LENS_DEPTH`) via `runAuditSuite`, surfacing the outcome on the
|
|
131
|
-
review envelope's `localLensReview` field.
|
|
132
|
-
|
|
133
|
-
A diff that matches no local lens adds **no** lens work (the roster is empty and
|
|
134
|
-
`runAuditSuite` is never invoked). The pass is advisory and best-effort: a git
|
|
135
|
-
or materialization failure degrades to a skipped envelope and never blocks the
|
|
136
|
-
close.
|
|
137
|
-
|
|
138
|
-
The live close entry point —
|
|
139
|
-
[`runStoryScopeReview`](../../scripts/lib/orchestration/single-story-close/phases/code-review.js)
|
|
140
|
-
— reaches this pass through the shared `runStoryReviewCore` spine. Because
|
|
141
|
-
the pass lives inside the close subprocess (invoked after the delivering
|
|
142
|
-
child exits), it honors the maker-blind invariant above: a maker never runs
|
|
143
|
-
its own local-lens review.
|
|
113
|
+
### Story scope runs no lens pass
|
|
114
|
+
|
|
115
|
+
Close runs **no** audit-lens pass of its own (Story #5416 retired it: it
|
|
116
|
+
materialized prompt files no workflow read, then armed auto-merge without
|
|
117
|
+
waiting). The Story-scope review is this pipeline plus CI. Local-tier lens
|
|
118
|
+
concerns are covered shift-left by the write-time authoring checklists
|
|
119
|
+
threaded into the Story prompt; the on-demand `/audit-*` workflows remain the
|
|
120
|
+
way to run a full lens over a change.
|
|
144
121
|
|
|
145
122
|
## Step 2 — Review Pillars
|
|
146
123
|
|
|
147
124
|
For each changed file, execute a strict review against four pillars. The
|
|
148
125
|
second pillar (**Integration Review**) deliberately defers the security /
|
|
149
126
|
performance / quality / coverage sweeps to the change-set-scoped lenses —
|
|
150
|
-
those
|
|
127
|
+
those are covered shift-left by the write-time lens checklists.
|
|
151
128
|
Re-walking those sweeps a second time in this pillar is duplication, not
|
|
152
129
|
defense-in-depth.
|
|
153
130
|
|
|
@@ -173,9 +150,9 @@ Does the implementation match the Story's acceptance criteria and folded Spec?
|
|
|
173
150
|
|
|
174
151
|
The diff under review is `baseRef..headRef`
|
|
175
152
|
(`main..story-<storyId>`, or the configured base branch to the Story
|
|
176
|
-
branch). The
|
|
177
|
-
|
|
178
|
-
|
|
153
|
+
branch). The write-time lens checklists have already covered the local-tier
|
|
154
|
+
concerns. Pillar findings land in the single `verification-results` comment
|
|
155
|
+
this pass posts. The
|
|
179
156
|
integration view here focuses on cross-cutting ripple within the Story and
|
|
180
157
|
contract drift against the base branch. Look for:
|
|
181
158
|
|
|
@@ -260,7 +237,7 @@ prior baseline before merging.
|
|
|
260
237
|
|
|
261
238
|
Findings are **persisted as a `verification-results` structured comment on
|
|
262
239
|
the `[TICKET_ID]` issue** by `runCodeReview` (the unified findings contract —
|
|
263
|
-
this single comment carries the Story-scope
|
|
240
|
+
this single comment carries the Story-scope review findings). The target
|
|
264
241
|
ticket is the Story. The comment
|
|
265
242
|
is idempotent — re-runs replace the prior one — and its body includes
|
|
266
243
|
severity-tier counts plus the full findings list so downstream workflows
|
|
@@ -175,7 +175,7 @@ into batches of `cap` and dispatch each batch in its own turn.
|
|
|
175
175
|
exposes agent dispatch, spawn each ready Story as its own
|
|
176
176
|
`subagent_type: story-worker` sub-agent — it boots on the role-scoped
|
|
177
177
|
[`story-worker`](../../agents/story-worker.md) context (its own system prompt, no
|
|
178
|
-
|
|
178
|
+
entry-doc @-closure) carrying the load-bearing delivery MUSTs standalone. The
|
|
179
179
|
sub-agent executes [`deliver-story.md`](deliver-story.md) Steps 0–2.5
|
|
180
180
|
(init → implement → acceptance self-eval → **push**) and stops there; **you**
|
|
181
181
|
own Step 3, serialized — see `/mandrel-deliver` § Closing what the workers hand back.
|
|
@@ -291,7 +291,9 @@ confirm instead (`captureStoryFollowUps`).
|
|
|
291
291
|
reopen the issue.
|
|
292
292
|
- The parent lookup resolves the native parent edge in **one** call
|
|
293
293
|
(`getParentIssue`), falling back to a `type::epic` scan for a child linked by
|
|
294
|
-
checklist alone.
|
|
294
|
+
checklist alone. An authoritative "no parent" narrows that scan to Epics
|
|
295
|
+
whose body checklist names the Story (no per-Epic native read); only a
|
|
296
|
+
degraded lookup reads every scanned Epic's native children. Children are the body checklist **union** the native
|
|
295
297
|
sub-issue edges — the same reader `/mandrel-deliver`'s expansion uses.
|
|
296
298
|
- A checklist row citing an id that resolves to nothing is **dropped with a
|
|
297
299
|
warning** when the native read succeeded; an unresolvable *native* edge
|
|
@@ -72,6 +72,9 @@ One branch, one PR to `main`, commits against the inline `acceptance[]` /
|
|
|
72
72
|
1. Read the Story body; its acceptance criteria are the contract. Docs are
|
|
73
73
|
digest-first; read a caller-provided `checklistPath` first, and walk any
|
|
74
74
|
`## Slicing` rows as **intra-session checkpoints** (reference § Step 1).
|
|
75
|
+
After a context summary, re-derive progress from `git log` on
|
|
76
|
+
`story-<id>` against the `## Slicing` rows (each checkpoint is a commit
|
|
77
|
+
boundary) before continuing.
|
|
75
78
|
2. Implement and commit on the Story branch, iterating with quick advisory
|
|
76
79
|
gates (`typecheck`, `lint`, scoped tests) — the full chain runs in Step 3,
|
|
77
80
|
and the **one** full-suite run at Step 2.5.
|
|
@@ -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,44 @@ 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
|
+
|
|
49
|
+
## [2.64.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.63.0...mandrel-v2.64.0) (2026-09-19)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
* close-and-land: a checks-failed stop disarms auto-merge and writes the CI digest ([#5405](https://github.com/dsj1984/mandrel/issues/5405)) ([#5406](https://github.com/dsj1984/mandrel/issues/5406)) ([5819d52](https://github.com/dsj1984/mandrel/commit/5819d52a834a6973b3e138961d9735e477f6cb48))
|
|
55
|
+
|
|
18
56
|
## [2.63.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.62.0...mandrel-v2.63.0) (2026-09-18)
|
|
19
57
|
|
|
20
58
|
|
|
@@ -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 };
|