mandrel 1.72.0 → 1.74.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.
Files changed (32) hide show
  1. package/.agents/README.md +9 -5
  2. package/.agents/docs/configuration.md +13 -0
  3. package/.agents/instructions.md +14 -6
  4. package/.agents/personas/devops-engineer.md +4 -2
  5. package/.agents/scripts/agents-bootstrap-github.js +42 -43
  6. package/.agents/scripts/bootstrap.js +79 -11
  7. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +430 -0
  8. package/.agents/scripts/lib/bootstrap/manifest.js +13 -5
  9. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +43 -4
  10. package/.agents/scripts/lib/bootstrap/prompt.js +1 -1
  11. package/.agents/scripts/lib/bootstrap/summary.js +0 -6
  12. package/.agents/scripts/lib/bootstrap/workflow-audit.js +25 -12
  13. package/.agents/scripts/lib/label-taxonomy.js +0 -37
  14. package/.agents/scripts/lib/onboard/init-tail.js +9 -10
  15. package/.agents/scripts/lib/orchestration/column-sync.js +22 -41
  16. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +56 -2
  17. package/.agents/scripts/lib/orchestration/project-meta-resolver.js +129 -0
  18. package/.agents/scripts/lib/story-body/story-body.js +5 -2
  19. package/.agents/scripts/lib/story-plan.js +41 -4
  20. package/.agents/scripts/lint-issue-body.js +261 -0
  21. package/.agents/scripts/providers/github/project-board.js +5 -9
  22. package/.agents/scripts/providers/github/projects-v2-graphql.js +0 -166
  23. package/.agents/scripts/providers/github/tickets.js +10 -1
  24. package/.agents/scripts/providers/github.js +0 -1
  25. package/.agents/skills/core/documentation-and-adrs/SKILL.md +38 -2
  26. package/.agents/templates/docs/architecture.md +4 -1
  27. package/.agents/templates/docs/decisions/_template.md +35 -0
  28. package/.agents/templates/docs/decisions.index.md +49 -0
  29. package/.agents/templates/docs/decisions.md +11 -0
  30. package/.agents/workflows/helpers/plan-story.md +1 -1
  31. package/docs/CHANGELOG.md +22 -0
  32. package/package.json +1 -1
@@ -2,11 +2,11 @@
2
2
  * GitHub Provider — ProjectBoardGateway.
3
3
  *
4
4
  * Owns the Projects V2 bootstrap surface: `resolveOrCreateProject`,
5
- * `ensureStatusField`, `ensureProjectViews`, `ensureProjectFields`. The
6
- * low-level GraphQL mutations live in `./projects-v2-graphql.js`; this
7
- * class threads the parent provider's `_ctx` (which carries `projectNumber`,
8
- * `projectOwner`, `state`, and the shared cache) into each call so the
9
- * legacy shim contract is preserved.
5
+ * `ensureStatusField`, `ensureProjectFields`. The low-level GraphQL
6
+ * mutations live in `./projects-v2-graphql.js`; this class threads the
7
+ * parent provider's `_ctx` (which carries `projectNumber`, `projectOwner`,
8
+ * `state`, and the shared cache) into each call so the legacy shim contract
9
+ * is preserved.
10
10
  *
11
11
  * Extracted from `../github.js` in Story #2462 / Task #2479. Public
12
12
  * surface on `GitHubProvider` is unchanged — every project-board method
@@ -36,10 +36,6 @@ export class ProjectBoardGateway {
36
36
  return projects.ensureStatusField(this._ctx, optionNames);
37
37
  }
38
38
 
39
- async ensureProjectViews(viewDefs) {
40
- return projects.ensureProjectViews(this._ctx, viewDefs);
41
- }
42
-
43
39
  /* node:coverage ignore next */
44
40
  async ensureProjectFields(fieldDefs) {
45
41
  return projects.ensureProjectFields(this._ctx, fieldDefs);
@@ -21,14 +21,8 @@ const Q_PROJ = (scope, fields) =>
21
21
  const M_PROJ = `mutation($ownerId:ID!,$title:String!){createProjectV2(input:{ownerId:$ownerId,title:$title}){projectV2{id number}}}`;
22
22
  const M_FIELD = `mutation($projectId:ID!,$name:String!,$options:[ProjectV2SingleSelectFieldOptionInput!]!){createProjectV2Field(input:{projectId:$projectId,dataType:SINGLE_SELECT,name:$name,singleSelectOptions:$options}){projectV2Field{... on ProjectV2SingleSelectField{id name}}}}`;
23
23
  const M_UPDATE = `mutation($fieldId:ID!,$name:String!,$options:[ProjectV2SingleSelectFieldOptionInput!]!){updateProjectV2Field(input:{fieldId:$fieldId,name:$name,singleSelectOptions:$options}){projectV2Field{... on ProjectV2SingleSelectField{id name}}}}`;
24
- // Projects V2 view creation uses the REST API — the GraphQL
25
- // `createProjectV2View` mutation is not generally available. Endpoints:
26
- // org-owned: POST /orgs/{org}/projectsV2/{number}/views ({org} login)
27
- // user-owned: POST /users/{user_id}/projectsV2/{number}/views (numeric id)
28
- const REST_API_VERSION = '2026-03-10';
29
24
  const M_ITEM = `mutation($projectId:ID!,$contentId:ID!){addProjectV2ItemById(input:{projectId:$projectId,contentId:$contentId}){item{id}}}`;
30
25
  const F_STATUS = `id fields(first:50){nodes{... on ProjectV2SingleSelectField{id name options{id name}}}}`;
31
- const F_VIEWS = `id views(first:50){nodes{name}}`;
32
26
  const F_FIELDS = `id fields(first:50){nodes{... on ProjectV2Field{name} ... on ProjectV2IterationField{name} ... on ProjectV2SingleSelectField{name}}}`;
33
27
  const SCOPES_RE =
34
28
  /INSUFFICIENT_SCOPES|Resource not accessible by personal access token|your token has not been granted the required scopes/i;
@@ -106,97 +100,6 @@ async function gql(ctx, query, variables, { retry = false } = {}) {
106
100
  return retry ? withTransientRetry(run) : run();
107
101
  }
108
102
 
109
- /**
110
- * Issue a REST request against api.github.com, reusing the same token and
111
- * fetch seam as `gql`. Throws on non-2xx with the response body for context.
112
- * Pass `{ retry: true }` to retry transient network blips (idempotent calls).
113
- */
114
- async function rest(ctx, method, apiPath, body, { retry = false } = {}) {
115
- const run = async () => {
116
- const fetchImpl = ctx.fetchImpl ?? globalThis.fetch;
117
- const response = await fetchImpl(`https://api.github.com${apiPath}`, {
118
- method,
119
- headers: {
120
- Accept: 'application/vnd.github+json',
121
- Authorization: `Bearer ${ctx.token ?? resolveToken()}`,
122
- 'Content-Type': 'application/json',
123
- 'User-Agent': 'node.js',
124
- 'X-GitHub-Api-Version': REST_API_VERSION,
125
- },
126
- ...(body ? { body: JSON.stringify(body) } : {}),
127
- });
128
- if (!response.ok) {
129
- const text = await response.text().catch(() => '');
130
- const err = new Error(
131
- `[GitHubProvider] REST ${method} ${apiPath} → ${response.status}: ${text}`,
132
- );
133
- err.status = response.status;
134
- throw err;
135
- }
136
- return response.json().catch(() => ({}));
137
- };
138
- return retry ? withTransientRetry(run) : run();
139
- }
140
-
141
- /**
142
- * Resolve an owner login to its account type and numeric id via
143
- * `GET /users/{login}` (which serves both users and orgs). The REST views
144
- * endpoint keys orgs by login but users by numeric id, so we need both.
145
- */
146
- async function resolveOwnerAccount(ctx, owner) {
147
- const data = await rest(
148
- ctx,
149
- 'GET',
150
- `/users/${encodeURIComponent(owner)}`,
151
- undefined,
152
- { retry: true },
153
- );
154
- return { id: data?.id ?? null, type: data?.type ?? null };
155
- }
156
-
157
- /**
158
- * Build the candidate REST views endpoints to try, in order. Orgs key by
159
- * login. For user-owned projects the docs label the path param `{user_id}`
160
- * but it's ambiguous (numeric id vs login) and the numeric form was observed
161
- * to 404 — so we try the login first (mirroring the org endpoint) then fall
162
- * back to the numeric id, treating a 404 as "wrong param, try the next".
163
- */
164
- function viewsEndpoints(account, owner, projectNumber) {
165
- if (account.type === 'Organization') {
166
- return [
167
- `/orgs/${encodeURIComponent(owner)}/projectsV2/${projectNumber}/views`,
168
- ];
169
- }
170
- const candidates = [];
171
- if (owner) {
172
- candidates.push(
173
- `/users/${encodeURIComponent(owner)}/projectsV2/${projectNumber}/views`,
174
- );
175
- }
176
- if (account.id != null) {
177
- candidates.push(`/users/${account.id}/projectsV2/${projectNumber}/views`);
178
- }
179
- return candidates;
180
- }
181
-
182
- /**
183
- * POST a view to the first candidate endpoint that does not 404. A 404 means
184
- * the path param shape was wrong (login vs numeric id) — try the next. Any
185
- * other status is a real failure and propagates.
186
- */
187
- async function createView(ctx, endpoints, body) {
188
- let lastError = null;
189
- for (const endpoint of endpoints) {
190
- try {
191
- return await rest(ctx, 'POST', endpoint, body, { retry: true });
192
- } catch (err) {
193
- lastError = err;
194
- if (err.status !== 404) throw err;
195
- }
196
- }
197
- throw lastError ?? new Error('[GitHubProvider] No views endpoint available.');
198
- }
199
-
200
103
  async function lookupProject(ctx, fragment, strict = false) {
201
104
  if (!ctx.projectNumber) return null;
202
105
  let lastError = null;
@@ -357,75 +260,6 @@ export async function ensureStatusField(ctx, optionNames) {
357
260
  }
358
261
  }
359
262
 
360
- export async function ensureProjectViews(ctx, viewDefs) {
361
- if (!ctx.projectNumber)
362
- throw new Error(
363
- '[GitHubProvider] ensureProjectViews requires projectNumber.',
364
- );
365
- const created = [],
366
- skipped = [];
367
- let project;
368
- try {
369
- project = await lookupProject(ctx, F_VIEWS, true);
370
- } catch {
371
- return {
372
- created,
373
- skipped: viewDefs.map((view) => view.name),
374
- unavailable: true,
375
- };
376
- }
377
- if (!project)
378
- throw new Error(
379
- `[GitHubProvider] Project #${ctx.projectNumber} not found for ${ctx.projectOwner}.`,
380
- );
381
- const existingViewNames = new Set(
382
- (project.views?.nodes ?? []).map((view) => view?.name).filter(Boolean),
383
- );
384
-
385
- // Resolve the owner account once to pick the right REST endpoint shape.
386
- let account;
387
- try {
388
- account = await resolveOwnerAccount(ctx, ctx.projectOwner);
389
- } catch (err) {
390
- return {
391
- created,
392
- skipped: viewDefs.map((view) => view.name),
393
- unavailable: true,
394
- error: err.message,
395
- };
396
- }
397
-
398
- const endpoints = viewsEndpoints(
399
- account,
400
- ctx.projectOwner,
401
- ctx.projectNumber,
402
- );
403
- let unavailable = false;
404
- let error;
405
- for (const def of viewDefs) {
406
- if (existingViewNames.has(def.name) || unavailable) {
407
- skipped.push(def.name);
408
- continue;
409
- }
410
- try {
411
- await createView(ctx, endpoints, {
412
- name: def.name,
413
- // PROJECT_VIEW_DEFS predate REST layouts; the GraphQL path always
414
- // created board views, so default to 'board' (override via
415
- // `def.layout` = 'table' | 'board' | 'roadmap').
416
- layout: def.layout ?? 'board',
417
- ...(def.filter ? { filter: def.filter } : {}),
418
- });
419
- created.push(def.name);
420
- } catch (err) {
421
- unavailable = true;
422
- error = err.message;
423
- skipped.push(def.name);
424
- }
425
- }
426
- return { created, skipped, unavailable, ...(error ? { error } : {}) };
427
- }
428
-
429
263
  export async function ensureProjectFields(ctx, fieldDefs) {
430
264
  if (!ctx.projectNumber) return { created: [], skipped: [] };
431
265
  const project = await lookupProject(ctx, F_FIELDS);
@@ -23,6 +23,7 @@
23
23
 
24
24
  import { parseBlockedBy, parseBlocks } from '../../lib/dependency-parser.js';
25
25
  import { Logger } from '../../lib/Logger.js';
26
+ import { TYPE_LABELS } from '../../lib/label-constants.js';
26
27
  import { addIssueToBoard } from './board-add.js';
27
28
  import { createInlineTicketCache } from './cache.js';
28
29
  import { withTransientRetry } from './errors.js';
@@ -318,13 +319,21 @@ export class TicketGateway {
318
319
  dependencies: ticketData.dependencies ?? [],
319
320
  });
320
321
 
322
+ // Mirror the Epic create path (issues.js:160 → `labels: TYPE_LABELS.EPIC`):
323
+ // always inject TYPE_LABELS.STORY so a spec that omits the labels array
324
+ // cannot produce an unlabeled, undispatchable Story. Dedupe to avoid
325
+ // duplicates when the caller already carries the label.
326
+ const callerLabels = ticketData.labels ?? [];
327
+ const labels = callerLabels.includes(TYPE_LABELS.STORY)
328
+ ? callerLabels
329
+ : [TYPE_LABELS.STORY, ...callerLabels];
321
330
  const result = await this._gh.api({
322
331
  method: 'POST',
323
332
  endpoint: `/repos/${this.owner}/${this.repo}/issues`,
324
333
  body: {
325
334
  title: ticketData.title,
326
335
  body: renderedBody,
327
- labels: ticketData.labels ?? [],
336
+ labels,
328
337
  },
329
338
  });
330
339
  const issue = parseApiJson(result);
@@ -130,7 +130,6 @@ const DELEGATIONS = [
130
130
  ['setMergeMethods', 'mergeMethods.setMergeMethods'],
131
131
  ['resolveOrCreateProject', 'projectBoard.resolveOrCreateProject'],
132
132
  ['ensureStatusField', 'projectBoard.ensureStatusField'],
133
- ['ensureProjectViews', 'projectBoard.ensureProjectViews'],
134
133
  ['ensureProjectFields', 'projectBoard.ensureProjectFields'],
135
134
  ];
136
135
  for (const [name, target] of DELEGATIONS) {
@@ -12,7 +12,7 @@ description:
12
12
 
13
13
  - Document the **why**, not the what. Capture context, constraints, alternatives considered, and trade-offs — code already shows what was built.
14
14
  - Write an ADR for any decision that would be expensive to reverse (framework choice, data model, auth strategy, API architecture, hosting platform).
15
- - Store ADRs at `docs/decisions/` with sequential numbering (`ADR-001`, `ADR-002`, ) and the canonical sections: **Status, Date, Context, Decision, Alternatives Considered, Consequences**.
15
+ - Mandrel ships **two first-class decisions-log layouts** — pick one at onboarding (see [Decisions-log layouts](#decisions-log-layouts)): the **single-file dated-entry** `docs/decisions.md` (default; best for small projects) or the **index + `docs/decisions/` directory** (MADR-style, one file per ADR; best once the log outgrows a single file). Either way, the canonical ADR sections are **Status, Date, Deciders, Context, Decision, (Alternatives Considered), Consequences**.
16
16
  - Mark an ADR's status as `Accepted`, `Superseded by ADR-XXX`, or `Deprecated`. Never silently delete an ADR — supersede it.
17
17
  - Do **not** document obvious code; do **not** restate what the code already says. Stale or redundant docs are worse than no docs.
18
18
  - Comments explain **non-obvious intent** (the why). If a comment describes what the code does, refactor the code instead.
@@ -54,9 +54,41 @@ highest-value documentation you can write.
54
54
  - Choosing between build tools, hosting platforms, or infrastructure
55
55
  - Any decision that would be expensive to reverse
56
56
 
57
+ ### Decisions-log layouts
58
+
59
+ Mandrel ships **two supported layouts** for the decisions log. Both keep the
60
+ mandatory-read file named `docs/decisions.md` (the `project.docsContextFiles`
61
+ default), so `config-resolver.js` and every `.agents/` reference resolve the
62
+ same regardless of which you pick — only the **shape** differs. Choose one at
63
+ onboarding:
64
+
65
+ | Layout | Shape | Template(s) | When to use |
66
+ | ----------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
67
+ | **Single-file dated entries** (default) | One `decisions.md` of append-only `## YYYY-MM-DD — title` entries | [`templates/docs/decisions.md`](../../../templates/docs/decisions.md) | Small projects; a handful of decisions; you want everything in one scannable file. |
68
+ | **Index + `decisions/` directory** | `decisions.md` is a one-row-per-ADR **index**; each ADR is `decisions/NNNN-*.md` | [`templates/docs/decisions.index.md`](../../../templates/docs/decisions.index.md) + [`templates/docs/decisions/_template.md`](../../../templates/docs/decisions/_template.md) | The log has outgrown a single file (dozens of ADRs); you want per-decision history and `git blame` per ADR. |
69
+
70
+ To adopt the directory layout, replace `decisions.md` with the index variant,
71
+ create a `decisions/` directory beside it, and scaffold each ADR from
72
+ `decisions/_template.md` using zero-padded sequential numbering
73
+ (`0001-*.md`, `0002-*.md`, …).
74
+
75
+ > **Loading model (resolved design question).** The decisions **index** is the
76
+ > only artifact loaded into mandatory task context — individual ADR bodies
77
+ > under `decisions/` are **lazy / link-followed**, not auto-loaded. This is
78
+ > **index-only by default**: auto-loading every ADR body into each task's
79
+ > context would reintroduce exactly the bloat the split exists to remove.
80
+ > `project.docsContextFiles` entries are plain filenames resolved against the
81
+ > docs root (no glob expansion in the loader), so the index ships as a normal
82
+ > mandatory-read with no loader change. A project that genuinely wants the full
83
+ > ADR set in mandatory context can add explicit per-file entries (or a
84
+ > `decisions/*.md`-style entry if it maintains its own globbing) as a
85
+ > deliberate opt-in, but that is the exception, not the default.
86
+
57
87
  ### ADR Template
58
88
 
59
- Store ADRs in `docs/decisions/` with sequential numbering:
89
+ In the **single-file** layout, append a short dated entry per the
90
+ `templates/docs/decisions.md` format. In the **directory** layout, store ADRs
91
+ in `docs/decisions/` with sequential numbering:
60
92
 
61
93
  ```markdown
62
94
  # ADR-001: Use PostgreSQL for primary database
@@ -69,6 +101,10 @@ Accepted | Superseded by ADR-XXX | Deprecated
69
101
 
70
102
  2025-01-15
71
103
 
104
+ ## Deciders
105
+
106
+ The platform team (architect + two senior engineers).
107
+
72
108
  ## Context
73
109
 
74
110
  We need a primary database for the task management application. Key
@@ -27,4 +27,7 @@ Describe the top-level directories and their responsibilities.
27
27
 
28
28
  ## Key Decisions
29
29
 
30
- Link to `decisions.md` for the architectural decision log.
30
+ Link to `decisions.md` for the architectural decision log. Mandrel supports two
31
+ first-class layouts for it: a single-file dated-entry `decisions.md` (default)
32
+ or an index + `decisions/` ADR directory — see
33
+ [`.agents/skills/core/documentation-and-adrs/SKILL.md`](../../skills/core/documentation-and-adrs/SKILL.md).
@@ -0,0 +1,35 @@
1
+ # ADR-NNNN: <short decision title>
2
+
3
+ > Copy this file to `decisions/NNNN-<kebab-title>.md` (zero-padded, sequential
4
+ > — `0001`, `0002`, …) and add a matching row to the `decisions.md` index.
5
+ > ADRs are **append-only**: never rewrite or delete an accepted ADR — supersede
6
+ > it with a new one and flip this one's **Status** to `Superseded by ADR-NNNN`.
7
+
8
+ ## Status
9
+
10
+ Accepted
11
+
12
+ <!-- One of: Proposed | Accepted | Superseded by ADR-NNNN | Deprecated -->
13
+
14
+ ## Date
15
+
16
+ YYYY-MM-DD
17
+
18
+ ## Deciders
19
+
20
+ <!-- Who made the call (names / roles / "the team"). -->
21
+
22
+ ## Context
23
+
24
+ <!-- What forced the decision: the constraint, problem, or trade-off. What
25
+ were the requirements and the forces in tension? -->
26
+
27
+ ## Decision
28
+
29
+ <!-- What was chosen, stated plainly. -->
30
+
31
+ ## Consequences
32
+
33
+ <!-- What this enables and what it costs going forward — positive and negative.
34
+ Include follow-on work, new constraints, and anything a future reader must
35
+ know before reversing this. -->
@@ -0,0 +1,49 @@
1
+ # Architectural Decisions Log (Index)
2
+
3
+ > **Directory-layout variant.** This is the MADR-style **index + `decisions/`
4
+ > directory** alternative to the default single-file dated-entry
5
+ > [`decisions.md`](decisions.md). To adopt it, replace your `decisions.md` with
6
+ > this index, create a `decisions/` directory next to it, and scaffold ADRs
7
+ > from [`decisions/_template.md`](decisions/_template.md). This file stays named
8
+ > `decisions.md` so it remains the `project.docsContextFiles` mandatory-read
9
+ > every `.agents/` reference and `config-resolver.js` already point at — only
10
+ > its **shape** changes from dated entries to an index.
11
+ >
12
+ > Prefer this layout once the single-file log grows past a few dozen entries
13
+ > (athportal hit ~1060 lines / 32 ADRs before splitting). For small projects,
14
+ > keep the default single-file template instead.
15
+
16
+ ## How this layout works
17
+
18
+ - **This file is the index** — one row per ADR, newest at the top. It is the
19
+ mandatory-read; agents scan the index and follow the link into a specific
20
+ ADR only when the detail is relevant (index-only by default — see
21
+ [Loading model](#loading-model)).
22
+ - **Each ADR is its own file** under `decisions/`, named
23
+ `NNNN-<kebab-title>.md` with a zero-padded sequential number.
24
+ - **ADRs are append-only.** Never rewrite or delete an accepted ADR — write a
25
+ new one and flip the old one's status to `Superseded by ADR-NNNN`.
26
+ - **Scaffold new ADRs** from [`decisions/_template.md`](decisions/_template.md)
27
+ (Status / Date / Deciders / Context / Decision / Consequences).
28
+
29
+ ## Loading model
30
+
31
+ This index is the only decisions artifact loaded into mandatory task context
32
+ (`project.docsContextFiles`). Individual ADR bodies under `decisions/` are
33
+ **lazy / link-followed**, not auto-loaded — that is the whole point of the
34
+ split: keep the per-task context lean while preserving the full decision
35
+ history on disk. If a project genuinely wants the entire ADR set in mandatory
36
+ context, it can add an explicit `decisions/*.md`-style entry to
37
+ `project.docsContextFiles` as an opt-in (see the configuration reference), but
38
+ index-only is the intended default.
39
+
40
+ ## Index
41
+
42
+ | ADR | Title | Status | Date |
43
+ | -------- | ---------------------------------------- | -------- | ---------- |
44
+ | ADR-0001 | _Example — replace with your first ADR_ | Proposed | YYYY-MM-DD |
45
+
46
+ _Add new rows above this line, newest first. Once you scaffold a real ADR from
47
+ [`decisions/_template.md`](decisions/_template.md) into
48
+ `decisions/0001-<title>.md`, link the ADR id to that file (e.g.
49
+ `[ADR-0001](decisions/0001-<title>.md)`) and delete this example row._
@@ -4,6 +4,17 @@
4
4
  > decisions here as dated entries. This file is one of the
5
5
  > `project.docsContextFiles` mandatory-reads — agents consult it before every
6
6
  > task to avoid re-litigating settled choices.
7
+ >
8
+ > **Two supported layouts — pick one at onboarding.** This single-file
9
+ > dated-entry layout is the **default**, ideal for small projects. Once the
10
+ > log grows past a few dozen entries it becomes a context-bloat liability;
11
+ > at that point switch to the first-class **index + `decisions/` directory**
12
+ > variant ([`decisions.index.md`](decisions.index.md) + the ADR scaffold at
13
+ > [`decisions/_template.md`](decisions/_template.md)). Both layouts keep the
14
+ > file named `decisions.md` so the `project.docsContextFiles` mandatory-read
15
+ > resolves the same; only the shape differs. See
16
+ > [`.agents/skills/core/documentation-and-adrs/SKILL.md`](../../skills/core/documentation-and-adrs/SKILL.md)
17
+ > for when to choose which.
7
18
 
8
19
  ## Format
9
20
 
@@ -97,7 +97,7 @@ Envelope fields (`kind: "story-plan-context"`, `version: 1`):
97
97
  | `bodyTemplate` | Contents of `.agents/templates/single-story-body.md`. |
98
98
  | `requiredSections` | `["Context", "Acceptance Criteria", "Out of Scope", "Notes"]`. |
99
99
  | `duplicateCandidates` | Ranked open Stories whose titles fuzzy-match the seed. |
100
- | `techStack` | The `## Tech Stack` section of `docs/architecture.md`. |
100
+ | `techStack` | The project's Tech Stack inventory, resolved in order: `docs/tech-stack.md` (full body) when present, else the `## Tech Stack` section of `docs/architecture.md` (numbered/decorated and final-section headings tolerated). |
101
101
  | `deliverContract` | Workflow path + required/forbidden labels and references. |
102
102
 
103
103
  ### Refine heuristic
package/docs/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.74.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.73.0...mandrel-v1.74.0) (2026-06-19)
6
+
7
+
8
+ ### Added
9
+
10
+ * minimize install footprint: drop Project views, make the board + custom-fields + issue-forms decoration opt-in (default off) ([#4234](https://github.com/dsj1984/mandrel/issues/4234)) ([#4235](https://github.com/dsj1984/mandrel/issues/4235)) ([9c8acf6](https://github.com/dsj1984/mandrel/commit/9c8acf6f8b324d3d2c221031f636680c823d5c2a))
11
+
12
+
13
+ ### Fixed
14
+
15
+ * **bootstrap:** make quality gates and docs-stub scaffold opt-in and default-off ([#4240](https://github.com/dsj1984/mandrel/issues/4240)) ([e41da73](https://github.com/dsj1984/mandrel/commit/e41da7343e7fc1b55d1e1749f2e532b231426766))
16
+ * **decompose:** enforce/default the mandatory type::story label on Story create → no more unlabeled, undispatchable Stories ([#4241](https://github.com/dsj1984/mandrel/issues/4241)) ([#4242](https://github.com/dsj1984/mandrel/issues/4242)) ([14d3752](https://github.com/dsj1984/mandrel/commit/14d3752f0bf8ed1133508e5c1955e60afb8d8a24))
17
+ * **projects:** resolve organization-owned Projects v2 boards (refs [#4237](https://github.com/dsj1984/mandrel/issues/4237)) ([#4238](https://github.com/dsj1984/mandrel/issues/4238)) ([3c033c3](https://github.com/dsj1984/mandrel/commit/3c033c36a03ca68c94be0cfbee991ef33e7fa14d))
18
+
19
+ ## [1.73.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.72.0...mandrel-v1.73.0) (2026-06-17)
20
+
21
+
22
+ ### Added
23
+
24
+ * generate GitHub issue forms from the Story/Epic body SSOT (human↔agent ticket consistency) ([#4227](https://github.com/dsj1984/mandrel/issues/4227)) ([#4233](https://github.com/dsj1984/mandrel/issues/4233)) ([d42b0cb](https://github.com/dsj1984/mandrel/commit/d42b0cb6122f46d8528d4bbb3793f27be2854bc1))
25
+ * **plan:** robust tech-stack hydrator resolution (refs [#4228](https://github.com/dsj1984/mandrel/issues/4228)) ([#4230](https://github.com/dsj1984/mandrel/issues/4230)) ([4f1ad3c](https://github.com/dsj1984/mandrel/commit/4f1ad3c13b9a50f4e436d63282c7f6c2f461ab0e))
26
+
5
27
  ## [1.72.0](https://github.com/dsj1984/mandrel/compare/mandrel-v1.71.0...mandrel-v1.72.0) (2026-06-17)
6
28
 
7
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "1.72.0",
3
+ "version": "1.74.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/",