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.
- package/.agents/README.md +9 -5
- package/.agents/docs/configuration.md +13 -0
- package/.agents/instructions.md +14 -6
- package/.agents/personas/devops-engineer.md +4 -2
- package/.agents/scripts/agents-bootstrap-github.js +42 -43
- package/.agents/scripts/bootstrap.js +79 -11
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +430 -0
- package/.agents/scripts/lib/bootstrap/manifest.js +13 -5
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +43 -4
- package/.agents/scripts/lib/bootstrap/prompt.js +1 -1
- package/.agents/scripts/lib/bootstrap/summary.js +0 -6
- package/.agents/scripts/lib/bootstrap/workflow-audit.js +25 -12
- package/.agents/scripts/lib/label-taxonomy.js +0 -37
- package/.agents/scripts/lib/onboard/init-tail.js +9 -10
- package/.agents/scripts/lib/orchestration/column-sync.js +22 -41
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +56 -2
- package/.agents/scripts/lib/orchestration/project-meta-resolver.js +129 -0
- package/.agents/scripts/lib/story-body/story-body.js +5 -2
- package/.agents/scripts/lib/story-plan.js +41 -4
- package/.agents/scripts/lint-issue-body.js +261 -0
- package/.agents/scripts/providers/github/project-board.js +5 -9
- package/.agents/scripts/providers/github/projects-v2-graphql.js +0 -166
- package/.agents/scripts/providers/github/tickets.js +10 -1
- package/.agents/scripts/providers/github.js +0 -1
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +38 -2
- package/.agents/templates/docs/architecture.md +4 -1
- package/.agents/templates/docs/decisions/_template.md +35 -0
- package/.agents/templates/docs/decisions.index.md +49 -0
- package/.agents/templates/docs/decisions.md +11 -0
- package/.agents/workflows/helpers/plan-story.md +1 -1
- package/docs/CHANGELOG.md +22 -0
- 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`, `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
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