mandrel 1.64.0 → 1.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.
|
@@ -26,6 +26,28 @@ export const INTEGRATION_INCLUDE = [
|
|
|
26
26
|
|
|
27
27
|
const matchesIntegration = picomatch(INTEGRATION_INCLUDE, { dot: true });
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Repo-relative roots the tier walker scans for test files (names ending in
|
|
31
|
+
* `.test.js`).
|
|
32
|
+
*
|
|
33
|
+
* `tests` holds the framework's suite tree; `lib` holds the published CLI
|
|
34
|
+
* (under `lib/cli` and `lib/migrations`) whose tests are colocated in
|
|
35
|
+
* `__tests__` directories per the unit-tier convention in
|
|
36
|
+
* `rules/testing-standards.md`. Without `lib` here, both the quick /
|
|
37
|
+
* integration walk and the full-tier glob set miss the colocated CLI tests,
|
|
38
|
+
* leaving that coverage dark in `npm test`. The matching full-tier globs
|
|
39
|
+
* live in `FULL_TIER_GLOBS`.
|
|
40
|
+
*/
|
|
41
|
+
const TEST_WALK_ROOTS = ['tests', 'lib'];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Glob targets for the `full` tier — one per walk root in `TEST_WALK_ROOTS`.
|
|
45
|
+
* The `tests` glob is a flat recursive sweep; the `lib` glob is scoped to
|
|
46
|
+
* `__tests__` subtrees so it only matches colocated tests, never the shipped
|
|
47
|
+
* source modules themselves.
|
|
48
|
+
*/
|
|
49
|
+
const FULL_TIER_GLOBS = ['tests/**/*.test.js', 'lib/**/__tests__/**/*.test.js'];
|
|
50
|
+
|
|
29
51
|
/**
|
|
30
52
|
* @param {string} dir
|
|
31
53
|
* @param {string} prefix
|
|
@@ -56,13 +78,11 @@ function walkTestFiles(dir, prefix, fsLike) {
|
|
|
56
78
|
* @returns {string[]}
|
|
57
79
|
*/
|
|
58
80
|
export function listTestFilesForTier(tier, repoRoot, fsLike = fs) {
|
|
59
|
-
const all =
|
|
60
|
-
path.join(repoRoot,
|
|
61
|
-
'tests',
|
|
62
|
-
fsLike,
|
|
81
|
+
const all = TEST_WALK_ROOTS.flatMap((root) =>
|
|
82
|
+
walkTestFiles(path.join(repoRoot, root), root, fsLike),
|
|
63
83
|
).sort();
|
|
64
84
|
if (tier === 'full') {
|
|
65
|
-
return [
|
|
85
|
+
return [...FULL_TIER_GLOBS];
|
|
66
86
|
}
|
|
67
87
|
const integration = all.filter((file) => matchesIntegration(file));
|
|
68
88
|
if (tier === 'integration') {
|
|
@@ -239,24 +239,61 @@ and optionally route or promote it — with the operator deciding each write.
|
|
|
239
239
|
`regression-of-closed`. Stamp the `fingerprintFooter(sha)` marker into any
|
|
240
240
|
Issue body so future runs dedup against it.
|
|
241
241
|
|
|
242
|
-
3. **
|
|
242
|
+
3. **Promote `file`-dispositioned findings through `/plan`** (never a raw
|
|
243
|
+
GitHub Issue) via
|
|
243
244
|
[`promote-finding.js`](../scripts/lib/findings/promote-finding.js), which
|
|
244
|
-
clusters, routes, and files through the same ports
|
|
245
|
-
promotion
|
|
245
|
+
clusters, sizes, routes, and files through the same ports `/qa-explore` and
|
|
246
|
+
`/audit-to-stories` consume — never hand-roll the promotion, the clustering,
|
|
247
|
+
or the sizing:
|
|
246
248
|
|
|
247
249
|
```js
|
|
248
250
|
import { promoteFindings } from '../scripts/lib/findings/promote-finding.js';
|
|
249
|
-
const promotions = await promoteFindings(ledgerItems, {
|
|
251
|
+
const { promotions } = await promoteFindings(ledgerItems, {
|
|
252
|
+
searchIssues, // GitHub provider, open + closed
|
|
253
|
+
createStory, // tight cluster (≤2 surfaces): render seed → /plan --from-notes
|
|
254
|
+
createEpic, // broad cluster (>2 surfaces): render seed → /plan --idea
|
|
255
|
+
});
|
|
250
256
|
```
|
|
251
257
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
- **Sizing is delegated, not decided in prose.** `promoteFindings` runs
|
|
259
|
+
`clusterLedgerItems` + `targetForCluster`: a cluster spanning **≤2**
|
|
260
|
+
distinct coverage surfaces routes to `createStory`; **>2** routes to
|
|
261
|
+
`createEpic`. Do not re-cluster, re-size, or re-dedup in the workflow —
|
|
262
|
+
[`route-finding.js`](../scripts/lib/findings/route-finding.js) /
|
|
263
|
+
[`promote-finding.js`](../scripts/lib/findings/promote-finding.js) are the
|
|
264
|
+
single implementation.
|
|
265
|
+
- **`createStory` (`/plan --from-notes`)** — render a **redacted**
|
|
266
|
+
`--from-notes` seed from the cluster (reuse the `/audit-to-stories`
|
|
267
|
+
Phase 5b notes shape; redaction already ran in Phase 2), **stamp the
|
|
268
|
+
cluster's `fingerprintFooter(sha)` verbatim into the seed body**, then
|
|
269
|
+
chain `/plan --from-notes <seed>`. The footer must survive into the issue
|
|
270
|
+
body the Story create path writes — it round-trips through
|
|
271
|
+
`story-plan.js --body <file> --dry-run` unchanged (asserted by the
|
|
272
|
+
deterministic round-trip test under `tests/`) so a later `routeFinding`
|
|
273
|
+
dedups the same finding instead of re-filing it.
|
|
274
|
+
- **`createEpic` (`/plan --idea`)** — carry the cluster's
|
|
275
|
+
`fingerprintFooter(sha)` into the `/plan --idea` seed, then chain
|
|
276
|
+
`/plan --idea <seed>`. **Known limitation (not solved here):**
|
|
277
|
+
per-child-Story fingerprint propagation through full Epic decomposition is
|
|
278
|
+
*not* guaranteed — the fingerprint is carried in the Epic seed only; the
|
|
279
|
+
child Stories `/plan` spawns from that seed are not individually
|
|
280
|
+
footer-stamped.
|
|
281
|
+
- **A `file` disposition never opens a raw GitHub Issue.** Every `file`
|
|
282
|
+
finding flows through `promoteFindings` → `/plan`; only `defer` (carry
|
|
283
|
+
forward as backlog) and `dismiss` (non-actionable) skip the `/plan`
|
|
284
|
+
handoff.
|
|
285
|
+
|
|
286
|
+
4. **Gate:** any ledger append, seed write, `/plan` invocation, ticket-filing,
|
|
287
|
+
or label mutation is a write — confirm **each one** with the operator before
|
|
288
|
+
it happens. The plan→deliver hard stop is preserved: each `/plan` chain
|
|
289
|
+
pauses at its own HITL gates and never auto-delivers. Redaction has already
|
|
290
|
+
run, so nothing unredacted reaches disk or GitHub.
|
|
255
291
|
|
|
256
292
|
After recording, summarize: the finding recorded, its coverage verdict and
|
|
257
293
|
`missingTest`, any route/promotion decision
|
|
258
|
-
(`new`/`update-existing`/`duplicate`/`regression-of-closed`)
|
|
259
|
-
|
|
294
|
+
(`new`/`update-existing`/`duplicate`/`regression-of-closed`) and whether it was
|
|
295
|
+
promoted to a Story (`/plan --from-notes`) or Epic (`/plan --idea`), and the
|
|
296
|
+
rolling backlog a resumed session will pick up.
|
|
260
297
|
|
|
261
298
|
---
|
|
262
299
|
|
|
@@ -291,3 +328,24 @@ backlog a resumed session will pick up.
|
|
|
291
328
|
promotion ([`promote-finding.js`](../scripts/lib/findings/promote-finding.js)),
|
|
292
329
|
and session resolution ([`qa-session.js`](../scripts/lib/qa/qa-session.js))
|
|
293
330
|
are deterministic — never re-derive them in prose.
|
|
331
|
+
- **Promote through `/plan`, never a raw Issue.** A `file`-dispositioned
|
|
332
|
+
finding is promoted via `promoteFindings`, which chains into
|
|
333
|
+
[`/plan`](plan.md) (`--from-notes` for a tight cluster, `--idea` for a broad
|
|
334
|
+
one) — mirroring [`/audit-to-stories`](audit-to-stories.md). `/qa-assist`
|
|
335
|
+
never opens a bare GitHub Issue for a `file` finding. The cluster's
|
|
336
|
+
`fingerprintFooter(sha)` is stamped verbatim into the seed so a future
|
|
337
|
+
`routeFinding` dedups it.
|
|
338
|
+
|
|
339
|
+
## See also
|
|
340
|
+
|
|
341
|
+
- [`/plan`](plan.md) — the planning pipeline `/qa-assist` chains into when an
|
|
342
|
+
operator dispositions a finding `file` (`--from-notes` for a Story, `--idea`
|
|
343
|
+
for an Epic). The plan→deliver hard stop is preserved across the handoff.
|
|
344
|
+
- [`/qa-explore`](qa-explore.md) — the agent-led sibling that drives a named
|
|
345
|
+
surface and triages through the same `/plan` handoff.
|
|
346
|
+
- [`/audit-to-stories`](audit-to-stories.md) — the precedent for the
|
|
347
|
+
findings → `/plan` handoff and the shared fingerprint-footer dedup contract.
|
|
348
|
+
- [`promote-finding.js`](../scripts/lib/findings/promote-finding.js) /
|
|
349
|
+
[`route-finding.js`](../scripts/lib/findings/route-finding.js) — the shared
|
|
350
|
+
cluster/size/promote and dedup/route/fingerprint-footer helpers. There is no
|
|
351
|
+
second clustering, sizing, or dedup implementation.
|
|
@@ -296,19 +296,62 @@ For each untriaged ledger item:
|
|
|
296
296
|
`searchIssues` port to the GitHub provider (querying both open and closed
|
|
297
297
|
Issues).
|
|
298
298
|
|
|
299
|
-
3. **Decide the disposition** with the operator: `file` (promote
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
3. **Decide the disposition** with the operator: `file` (promote through
|
|
300
|
+
`/plan` — never a raw GitHub Issue), `defer` (carry forward to a later
|
|
301
|
+
session as backlog), or `dismiss` (non-actionable). Record the chosen
|
|
302
|
+
`disposition` back onto the ledger item.
|
|
303
303
|
|
|
304
|
-
4. **
|
|
305
|
-
|
|
306
|
-
|
|
304
|
+
4. **Promote the `file`-dispositioned findings through `/plan`** via
|
|
305
|
+
[`promote-finding.js`](../scripts/lib/findings/promote-finding.js) — the
|
|
306
|
+
same cluster/size/route/file path `/qa-assist` and `/audit-to-stories`
|
|
307
|
+
consume. Never hand-roll the clustering, sizing, or promotion in prose:
|
|
308
|
+
|
|
309
|
+
```js
|
|
310
|
+
import { promoteFindings } from '../scripts/lib/findings/promote-finding.js';
|
|
311
|
+
const { promotions } = await promoteFindings(ledgerItems, {
|
|
312
|
+
searchIssues, // GitHub provider, open + closed
|
|
313
|
+
createStory, // tight cluster (≤2 surfaces): render seed → /plan --from-notes
|
|
314
|
+
createEpic, // broad cluster (>2 surfaces): render seed → /plan --idea
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
- **Sizing is delegated, not decided in prose.** `promoteFindings` runs
|
|
319
|
+
`clusterLedgerItems` + `targetForCluster`: a cluster spanning **≤2**
|
|
320
|
+
distinct coverage surfaces routes to `createStory`; **>2** routes to
|
|
321
|
+
`createEpic`. The workflow introduces no new sizing, clustering, or dedup
|
|
322
|
+
logic — `route-finding.js` / `promote-finding.js` remain the single
|
|
323
|
+
implementation.
|
|
324
|
+
- **`createStory` (`/plan --from-notes`)** — render a **redacted**
|
|
325
|
+
`--from-notes` seed from the cluster (reuse the `/audit-to-stories`
|
|
326
|
+
Phase 5b notes shape; redaction already ran in Capture), **stamp the
|
|
327
|
+
cluster's `fingerprintFooter(sha)` verbatim into the seed body**, then
|
|
328
|
+
chain `/plan --from-notes <seed>`. The footer must survive into the issue
|
|
329
|
+
body the Story create path writes — it round-trips through
|
|
330
|
+
`story-plan.js --body <file> --dry-run` unchanged (asserted by the
|
|
331
|
+
deterministic round-trip test under `tests/`) so a later `routeFinding`
|
|
332
|
+
dedups the same finding instead of re-filing it.
|
|
333
|
+
- **`createEpic` (`/plan --idea`)** — carry the cluster's
|
|
334
|
+
`fingerprintFooter(sha)` into the `/plan --idea` seed, then chain
|
|
335
|
+
`/plan --idea <seed>`. **Known limitation (not solved here):**
|
|
336
|
+
per-child-Story fingerprint propagation through full Epic decomposition is
|
|
337
|
+
*not* guaranteed — the fingerprint is carried in the Epic seed only; the
|
|
338
|
+
child Stories `/plan` spawns from that seed are not individually
|
|
339
|
+
footer-stamped.
|
|
340
|
+
- **A `file` disposition never opens a raw GitHub Issue.** Every `file`
|
|
341
|
+
finding flows through `promoteFindings` → `/plan`; only `defer` and
|
|
342
|
+
`dismiss` skip the `/plan` handoff.
|
|
343
|
+
|
|
344
|
+
5. **Gate:** any ticket-filing, seed write, `/plan` invocation, or label
|
|
345
|
+
mutation is a write — confirm each one with the operator before it happens.
|
|
346
|
+
Capture stayed read-only precisely so that every state change lands here,
|
|
347
|
+
deliberately and confirmed. The plan→deliver hard stop is preserved: each
|
|
348
|
+
`/plan` chain pauses at its own HITL gates and never auto-delivers.
|
|
307
349
|
|
|
308
350
|
After triage, write the updated dispositions back to the ledger (still under
|
|
309
351
|
`temp/qa/`), and summarize: items captured, the driving method used, classes,
|
|
310
|
-
routes (`new`/`update-existing`/`duplicate`/`regression-of-closed`),
|
|
311
|
-
|
|
352
|
+
routes (`new`/`update-existing`/`duplicate`/`regression-of-closed`), the
|
|
353
|
+
Stories (`/plan --from-notes`) and Epics (`/plan --idea`) promoted, and the
|
|
354
|
+
deferred rolling backlog that a resumed session will pick up.
|
|
312
355
|
|
|
313
356
|
---
|
|
314
357
|
|
|
@@ -343,8 +386,31 @@ tickets, and the deferred rolling backlog that a resumed session will pick up.
|
|
|
343
386
|
([`coverage-verdict.js`](../scripts/lib/qa/coverage-verdict.js)),
|
|
344
387
|
missing-test ([`propose-missing-test.js`](../scripts/lib/qa/propose-missing-test.js)),
|
|
345
388
|
classification ([`classify-finding.js`](../scripts/lib/findings/classify-finding.js)),
|
|
346
|
-
|
|
347
|
-
|
|
389
|
+
dedup/route ([`route-finding.js`](../scripts/lib/findings/route-finding.js)),
|
|
390
|
+
and cluster/size/promote
|
|
391
|
+
([`promote-finding.js`](../scripts/lib/findings/promote-finding.js)) are
|
|
392
|
+
deterministic — never re-derive them in prose.
|
|
393
|
+
- **Promote through `/plan`, never a raw Issue.** A `file`-dispositioned
|
|
394
|
+
finding is promoted via `promoteFindings`, which chains into
|
|
395
|
+
[`/plan`](plan.md) (`--from-notes` for a tight cluster, `--idea` for a broad
|
|
396
|
+
one) — mirroring [`/audit-to-stories`](audit-to-stories.md). `/qa-explore`
|
|
397
|
+
never opens a bare GitHub Issue for a `file` finding. The cluster's
|
|
398
|
+
`fingerprintFooter(sha)` is stamped verbatim into the seed so a future
|
|
399
|
+
`routeFinding` dedups it.
|
|
348
400
|
- **Resume safely.** A reused session appends and carries the un-triaged
|
|
349
401
|
backlog forward via [`qa-session.js`](../scripts/lib/qa/qa-session.js); it
|
|
350
402
|
never overwrites a prior ledger.
|
|
403
|
+
|
|
404
|
+
## See also
|
|
405
|
+
|
|
406
|
+
- [`/plan`](plan.md) — the planning pipeline `/qa-explore` Triage chains into
|
|
407
|
+
for a `file`-dispositioned finding (`--from-notes` for a Story, `--idea` for
|
|
408
|
+
an Epic). The plan→deliver hard stop is preserved across the handoff.
|
|
409
|
+
- [`/qa-assist`](qa-assist.md) — the human-led sibling that enriches a single
|
|
410
|
+
operator observation and triages through the same `/plan` handoff.
|
|
411
|
+
- [`/audit-to-stories`](audit-to-stories.md) — the precedent for the
|
|
412
|
+
findings → `/plan` handoff and the shared fingerprint-footer dedup contract.
|
|
413
|
+
- [`promote-finding.js`](../scripts/lib/findings/promote-finding.js) /
|
|
414
|
+
[`route-finding.js`](../scripts/lib/findings/route-finding.js) — the shared
|
|
415
|
+
cluster/size/promote and dedup/route/fingerprint-footer helpers. There is no
|
|
416
|
+
second clustering, sizing, or dedup implementation.
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.65.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.64.0...mandrel-v1.65.0) (2026-06-14)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
* Epic [#4118](https://github.com/dsj1984/mandrel/issues/4118) ([#4127](https://github.com/dsj1984/mandrel/issues/4127)) ([d24a1f7](https://github.com/dsj1984/mandrel/commit/d24a1f7d3fc36d20015890fbadd7d08caa1d506b))
|
|
11
|
+
* **qa:** route /qa-assist and /qa-explore triage into /plan (refs [#4115](https://github.com/dsj1984/mandrel/issues/4115)) ([#4116](https://github.com/dsj1984/mandrel/issues/4116)) ([de2a211](https://github.com/dsj1984/mandrel/commit/de2a211089104edd1cb76f77d668b0219a041c3e))
|
|
12
|
+
|
|
5
13
|
## [1.64.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.63.0...mandrel-v1.64.0) (2026-06-14)
|
|
6
14
|
|
|
7
15
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mandrel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.65.0",
|
|
4
4
|
"description": "Claude Code-first opinionated workflow framework: instructions, personas, skills, and SDLC workflows that govern AI coding assistants.",
|
|
5
5
|
"files": [
|
|
6
6
|
".agents/",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"lint-staged": "^17.0.4",
|
|
86
86
|
"markdownlint-cli2": "^0.18.1",
|
|
87
87
|
"memfs": "^4.57.2",
|
|
88
|
+
"node-pty": "^1.0.0",
|
|
88
89
|
"typescript": ">=5.0.0"
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|