mandrel 1.75.0 → 1.76.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.
|
@@ -341,11 +341,20 @@ export async function assertNoOpenPlanChildren({
|
|
|
341
341
|
const openChildren = (children ?? []).filter((t) => {
|
|
342
342
|
const labels = Array.isArray(t.labels) ? t.labels : [];
|
|
343
343
|
const isOpen = t.state === undefined || t.state === 'open';
|
|
344
|
-
//
|
|
345
|
-
// `
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
344
|
+
// Context spec tickets (PRD / Tech Spec / Acceptance Spec) carry a
|
|
345
|
+
// `context::*` label and — since the `createTicket` factory injects
|
|
346
|
+
// `type::story` by default — also `type::story`. They are reference
|
|
347
|
+
// artifacts that stay open across delivery, NOT plan children, so they
|
|
348
|
+
// MUST be excluded here: counting them would make every first decompose
|
|
349
|
+
// (where the three context tickets are the only open children) refuse.
|
|
350
|
+
const isContext = labels.some(
|
|
351
|
+
(l) => typeof l === 'string' && l.startsWith('context::'),
|
|
352
|
+
);
|
|
353
|
+
if (isContext) return false;
|
|
354
|
+
// Any remaining open typed plan ticket counts — `type::story` plus pre-v4
|
|
355
|
+
// `type::feature` leftovers. The prefix check is legacy-data detection,
|
|
356
|
+
// not compat support: the guard only refuses, it never processes the
|
|
357
|
+
// legacy tier.
|
|
349
358
|
return (
|
|
350
359
|
isOpen &&
|
|
351
360
|
labels.some((l) => typeof l === 'string' && l.startsWith('type::'))
|
|
@@ -35,6 +35,17 @@ export async function discoverOpenStories({ epicId, provider }) {
|
|
|
35
35
|
for (const t of descendants) {
|
|
36
36
|
const labels = t.labels ?? [];
|
|
37
37
|
if (!labels.includes(TYPE_LABELS.STORY)) continue;
|
|
38
|
+
// Defense-in-depth: never enumerate a context spec ticket (PRD / Tech
|
|
39
|
+
// Spec / Acceptance Spec) as a deliverable Story even if it carries
|
|
40
|
+
// `type::story`. The `createTicket` factory no longer stamps context
|
|
41
|
+
// tickets that way, but a context ticket mislabelled out-of-band must
|
|
42
|
+
// still never reach a delivery wave (it has no `story-<id>` branch or
|
|
43
|
+
// acceptance contract to deliver against).
|
|
44
|
+
if (
|
|
45
|
+
labels.some((l) => typeof l === 'string' && l.startsWith('context::'))
|
|
46
|
+
) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
38
49
|
const rawState = t.state ?? 'open';
|
|
39
50
|
const norm = typeof rawState === 'string' ? rawState.toLowerCase() : 'open';
|
|
40
51
|
if (norm !== 'open') continue;
|
|
@@ -320,13 +320,26 @@ export class TicketGateway {
|
|
|
320
320
|
});
|
|
321
321
|
|
|
322
322
|
// Mirror the Epic create path (issues.js:160 → `labels: TYPE_LABELS.EPIC`):
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
323
|
+
// inject TYPE_LABELS.STORY so a spec that omits the labels array cannot
|
|
324
|
+
// produce an unlabeled, undispatchable Story. Dedupe to avoid duplicates
|
|
325
|
+
// when the caller already carries the label.
|
|
326
|
+
//
|
|
327
|
+
// Context spec tickets (PRD / Tech Spec / Acceptance Spec) are a distinct
|
|
328
|
+
// ticket class created through this same factory carrying a `context::*`
|
|
329
|
+
// label (and no `type::story`). They MUST NOT be stamped `type::story`:
|
|
330
|
+
// doing so makes every `type::story`-counting consumer — the decompose
|
|
331
|
+
// open-children guard (`assertNoOpenPlanChildren`), the delivery wave
|
|
332
|
+
// builder (`discoverOpenStories`), the plan healthcheck — mis-classify
|
|
333
|
+
// them as deliverable Stories. Skip the injection when the caller's labels
|
|
334
|
+
// already classify the ticket as context.
|
|
326
335
|
const callerLabels = ticketData.labels ?? [];
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
336
|
+
const isContextTicket = callerLabels.some(
|
|
337
|
+
(l) => typeof l === 'string' && l.startsWith('context::'),
|
|
338
|
+
);
|
|
339
|
+
const labels =
|
|
340
|
+
isContextTicket || callerLabels.includes(TYPE_LABELS.STORY)
|
|
341
|
+
? callerLabels
|
|
342
|
+
: [TYPE_LABELS.STORY, ...callerLabels];
|
|
330
343
|
const result = await this._gh.api({
|
|
331
344
|
method: 'POST',
|
|
332
345
|
endpoint: `/repos/${this.owner}/${this.repo}/issues`,
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.76.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.75.0...mandrel-v1.76.0) (2026-06-20)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
* **plan:** stop stamping context spec tickets as type::story so first decompose isn't blocked ([#4246](https://github.com/dsj1984/mandrel/issues/4246)) ([#4247](https://github.com/dsj1984/mandrel/issues/4247)) ([564cfb8](https://github.com/dsj1984/mandrel/commit/564cfb81876f87ad5f61f3c670efb7cfc2c95540))
|
|
11
|
+
|
|
5
12
|
## [1.75.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.74.0...mandrel-v1.75.0) (2026-06-19)
|
|
6
13
|
|
|
7
14
|
|
package/package.json
CHANGED