@vpxa/aikit 0.1.214 → 0.1.215
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/package.json +1 -1
- package/scaffold/dist/adapters/copilot.mjs +4 -4
- package/scaffold/dist/definitions/agents.mjs +2 -2
- package/scaffold/dist/definitions/bodies.mjs +409 -506
- package/scaffold/dist/definitions/flows.mjs +303 -237
- package/scaffold/dist/definitions/protocols.mjs +235 -343
- package/scaffold/dist/definitions/skills/adr-skill.mjs +470 -1044
- package/scaffold/dist/definitions/skills/multi-agents-development.mjs +102 -214
- package/scaffold/dist/definitions/skills/session-handoff.mjs +541 -1314
|
@@ -137,39 +137,33 @@ decision-makers: '{list everyone who owns the decision}'
|
|
|
137
137
|
|
|
138
138
|
## Directory
|
|
139
139
|
|
|
140
|
-
If
|
|
140
|
+
If repo already has ADR dir, keep it.
|
|
141
141
|
|
|
142
|
-
If
|
|
142
|
+
If not:
|
|
143
143
|
|
|
144
|
-
- **\`docs/decisions/\`** — MADR default
|
|
145
|
-
- **\`adr/\`** — simpler
|
|
144
|
+
- **\`docs/decisions/\`** — MADR default when repo already uses \`docs/\`.
|
|
145
|
+
- **\`adr/\`** — simpler small-repo default.
|
|
146
146
|
|
|
147
|
-
Detection order
|
|
147
|
+
Detection order: \`contributing/decisions/\`, \`docs/decisions/\`, \`adr/\`, \`docs/adr/\`, \`docs/adrs/\`, \`decisions/\`.
|
|
148
148
|
|
|
149
149
|
## Filename Conventions
|
|
150
150
|
|
|
151
151
|
Pattern: \`YYYY-MM-DD-title-with-dashes.md\`
|
|
152
152
|
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
If a repo already uses slug-only filenames (no date prefix), follow that convention.
|
|
153
|
+
- Date matches \`date\` frontmatter.
|
|
154
|
+
- Slug: lowercase, dash-separated, imperative verb phrase.
|
|
155
|
+
- Same-day ADRs are fine; slug disambiguates.
|
|
156
|
+
- If repo already uses slug-only filenames, keep that convention.
|
|
159
157
|
|
|
160
158
|
## Minimal Sections
|
|
161
159
|
|
|
162
|
-
|
|
160
|
+
Every ADR needs:
|
|
163
161
|
|
|
164
|
-
1. **Context**: why
|
|
162
|
+
1. **Context**: why now; which constraints/drivers apply.
|
|
165
163
|
2. **Decision**: what is chosen.
|
|
166
|
-
3. **Consequences**: what
|
|
167
|
-
|
|
168
|
-
For agent-first ADRs, also ensure:
|
|
164
|
+
3. **Consequences**: what gets easier/harder, plus risks, costs, follow-ups.
|
|
169
165
|
|
|
170
|
-
-
|
|
171
|
-
- Non-goals are stated
|
|
172
|
-
- Follow-up tasks are identified
|
|
166
|
+
For agent-first ADRs also state explicit constraints, non-goals, and follow-up tasks.
|
|
173
167
|
|
|
174
168
|
## Status Values
|
|
175
169
|
|
|
@@ -187,11 +181,11 @@ Common statuses:
|
|
|
187
181
|
|
|
188
182
|
| Status | Meaning |
|
|
189
183
|
| ----------------------------- | ------------------------------------------------------------- |
|
|
190
|
-
| \`proposed\` | Under discussion
|
|
191
|
-
| \`accepted\` |
|
|
192
|
-
| \`rejected\` | Considered
|
|
193
|
-
| \`deprecated\` |
|
|
194
|
-
| \`superseded by [title](link)\` | Replaced by
|
|
184
|
+
| \`proposed\` | Under discussion |
|
|
185
|
+
| \`accepted\` | Active decision |
|
|
186
|
+
| \`rejected\` | Considered, not adopted |
|
|
187
|
+
| \`deprecated\` | No longer applies |
|
|
188
|
+
| \`superseded by [title](link)\` | Replaced by newer ADR |
|
|
195
189
|
|
|
196
190
|
## YAML Front Matter Fields
|
|
197
191
|
|
|
@@ -200,96 +194,31 @@ Common statuses:
|
|
|
200
194
|
| \`status\` | Yes | Current lifecycle state |
|
|
201
195
|
| \`date\` | Yes | Date of last status change (YYYY-MM-DD) |
|
|
202
196
|
| \`decision-makers\` | Yes | People who own the decision |
|
|
203
|
-
| \`consulted\` | No |
|
|
204
|
-
| \`informed\` | No | Stakeholders
|
|
197
|
+
| \`consulted\` | No | Experts consulted |
|
|
198
|
+
| \`informed\` | No | Stakeholders updated |
|
|
205
199
|
|
|
206
|
-
|
|
200
|
+
\`consulted\` and \`informed\` follow RACI.
|
|
207
201
|
|
|
208
202
|
## Mutability
|
|
209
203
|
|
|
210
|
-
-
|
|
211
|
-
- If
|
|
212
|
-
- Status changes and after-action notes
|
|
204
|
+
- Append dated notes instead of rewriting prior reasoning.
|
|
205
|
+
- If decision changes, create new ADR and supersede old one.
|
|
206
|
+
- Status changes and after-action notes can be edited in place.
|
|
213
207
|
|
|
214
208
|
## Categories (Large Projects)
|
|
215
209
|
|
|
216
|
-
For
|
|
217
|
-
|
|
218
|
-
\`\`\`
|
|
219
|
-
contributing/decisions/ # or docs/decisions/
|
|
220
|
-
backend/
|
|
221
|
-
2025-06-15-use-postgres.md
|
|
222
|
-
frontend/
|
|
223
|
-
2025-06-20-use-react.md
|
|
224
|
-
infrastructure/
|
|
225
|
-
2025-07-01-use-terraform.md
|
|
226
|
-
\`\`\`
|
|
227
|
-
|
|
228
|
-
Date prefixes are local to each category. Choose a categorization scheme early (by architectural layer, by domain, by team) and document it in the index.
|
|
229
|
-
|
|
230
|
-
Alternative: use tags or a flat structure with a searchable index. Subdirectories are simpler and work with all tools.
|
|
210
|
+
For many ADRs, subdirectories are fine. Date prefixes stay local to category; pick one scheme early and document it in index.
|
|
231
211
|
`},{file:`references/examples.md`,content:`# ADR Examples
|
|
232
212
|
|
|
233
|
-
|
|
213
|
+
Filled examples of same decision at two detail levels. Reference only; never ship placeholder text.
|
|
234
214
|
|
|
235
215
|
## Short Version (Simple Template)
|
|
236
216
|
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
status: accepted
|
|
240
|
-
date: 2025-06-15
|
|
241
|
-
decision-makers: Sarah Chen, Joel
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
# Use SQLite for local development database
|
|
245
|
-
|
|
246
|
-
## Context and Problem Statement
|
|
247
|
-
|
|
248
|
-
Our integration tests require a database but currently hit a shared PostgreSQL instance, causing flaky tests from concurrent writes and slow CI (3+ minute setup per run). We need a fast, isolated database for local dev and CI that doesn't require infrastructure provisioning.
|
|
249
|
-
|
|
250
|
-
## Decision
|
|
251
|
-
|
|
252
|
-
Use SQLite (via sql.js) for local development and CI test runs. Production remains on PostgreSQL. We'll use a thin data-access layer that abstracts the database engine, tested against both SQLite and PostgreSQL in CI.
|
|
253
|
-
|
|
254
|
-
Non-goals: we are NOT migrating production to SQLite or building a full ORM abstraction.
|
|
255
|
-
|
|
256
|
-
## Consequences
|
|
257
|
-
|
|
258
|
-
- Good, because CI setup drops from 3+ minutes to ~2 seconds (no DB provisioning)
|
|
259
|
-
- Good, because tests are fully isolated — no shared state between runs
|
|
260
|
-
- Good, because developers can run the full test suite offline
|
|
261
|
-
- Bad, because we must maintain compatibility between SQLite and PostgreSQL SQL dialects
|
|
262
|
-
- Bad, because some PostgreSQL-specific features (JSONB operators, array columns) can't be tested locally
|
|
263
|
-
|
|
264
|
-
## Implementation Plan
|
|
265
|
-
|
|
266
|
-
- **Affected paths**: \`src/db/client.ts\` (new abstraction layer), \`src/db/sqlite-client.ts\` (new), \`src/db/pg-client.ts\` (refactored from current inline usage), \`tests/setup.ts\`, \`package.json\`
|
|
267
|
-
- **Dependencies**: add \`sql.js@1.x\` and \`@types/sql.js@1.x\` as devDependencies; no production dependency changes
|
|
268
|
-
- **Patterns to follow**: existing repository pattern in \`src/db/repositories/\` — all queries go through repository methods, never raw SQL in business logic
|
|
269
|
-
- **Patterns to avoid**: do not import \`sql.js\` or \`pg\` directly outside \`src/db/\`; do not use PostgreSQL-specific SQL (JSONB operators, \`ANY()\`, array literals) in shared queries
|
|
270
|
-
|
|
271
|
-
### Verification
|
|
272
|
-
|
|
273
|
-
- [ ] \`npm test\` passes with \`DB_ENGINE=sqlite\` (default for test env)
|
|
274
|
-
- [ ] \`npm test\` passes with \`DB_ENGINE=postgres\` against a real PostgreSQL instance
|
|
275
|
-
- [ ] No imports of \`sql.js\` or \`pg\` outside \`src/db/\`
|
|
276
|
-
- [ ] CI pipeline total time under 90 seconds (was 5+ minutes)
|
|
277
|
-
- [ ] \`src/db/client.ts\` exports a unified interface used by all repositories
|
|
278
|
-
|
|
279
|
-
## Alternatives Considered
|
|
280
|
-
|
|
281
|
-
- Docker PostgreSQL per CI run: Reliable parity, but adds 90s+ startup and requires Docker-in-Docker on CI.
|
|
282
|
-
- In-memory PostgreSQL (pg-mem): Good API compatibility, but incomplete support for our schema (triggers, CTEs) and unmaintained.
|
|
283
|
-
|
|
284
|
-
## More Information
|
|
285
|
-
|
|
286
|
-
- Follow-up: create weekly CI job running full suite against real PostgreSQL (#348)
|
|
287
|
-
- Revisit trigger: if dialect-drift bugs exceed 2 per quarter, reconsider Docker PostgreSQL approach
|
|
288
|
-
\`\`\`
|
|
217
|
+
Use \`assets/templates/adr-simple.md\` for short-form drafting. The filled example below covers the same decision space in more detail.
|
|
289
218
|
|
|
290
219
|
## Long Version (MADR Template)
|
|
291
220
|
|
|
292
|
-
|
|
221
|
+
Same decision with full options analysis:
|
|
293
222
|
|
|
294
223
|
\`\`\`markdown
|
|
295
224
|
---
|
|
@@ -423,81 +352,73 @@ Spin up a fresh PostgreSQL container for each CI job.
|
|
|
423
352
|
\`\`\`
|
|
424
353
|
`},{file:`references/review-checklist.md`,content:`# ADR Review Checklist
|
|
425
354
|
|
|
426
|
-
Use
|
|
355
|
+
Use in Phase 3. Goal: **could a coding agent read this ADR and implement without follow-up questions?**
|
|
427
356
|
|
|
428
357
|
## Agent-Readiness Checks
|
|
429
358
|
|
|
430
359
|
### Context & Problem
|
|
431
360
|
|
|
432
|
-
- [ ]
|
|
433
|
-
- [ ]
|
|
434
|
-
- [ ] No tribal knowledge
|
|
435
|
-
- [ ]
|
|
361
|
+
- [ ] Reader with no context can understand why decision exists
|
|
362
|
+
- [ ] Trigger is clear
|
|
363
|
+
- [ ] No tribal knowledge assumed; acronyms and systems are explicit
|
|
364
|
+
- [ ] Relevant issues, PRs, or ADRs are linked
|
|
436
365
|
|
|
437
366
|
### Decision
|
|
438
367
|
|
|
439
|
-
- [ ]
|
|
440
|
-
- [ ] Scope is bounded
|
|
441
|
-
- [ ] Constraints are explicit and measurable where possible
|
|
368
|
+
- [ ] Decision is specific enough to act on
|
|
369
|
+
- [ ] Scope is bounded; non-goals are stated
|
|
370
|
+
- [ ] Constraints are explicit and measurable where possible
|
|
442
371
|
|
|
443
372
|
### Consequences
|
|
444
373
|
|
|
445
|
-
- [ ]
|
|
446
|
-
- [ ] Follow-up tasks are identified
|
|
447
|
-
- [ ] Risks
|
|
448
|
-
- [ ] No consequence is a disguised restatement of the decision
|
|
374
|
+
- [ ] Consequences are concrete and actionable
|
|
375
|
+
- [ ] Follow-up tasks are identified
|
|
376
|
+
- [ ] Risks include mitigation or acceptance rationale
|
|
449
377
|
|
|
450
378
|
### Implementation Plan
|
|
451
379
|
|
|
452
|
-
- [ ] Affected files/directories are
|
|
453
|
-
- [ ]
|
|
454
|
-
- [ ] Patterns to follow reference existing code
|
|
455
|
-
- [ ] Patterns to avoid are
|
|
456
|
-
- [ ]
|
|
457
|
-
- [ ] If replacing something, migration steps are described
|
|
380
|
+
- [ ] Affected files/directories are explicit
|
|
381
|
+
- [ ] Dependency changes include version constraints
|
|
382
|
+
- [ ] Patterns to follow reference existing code
|
|
383
|
+
- [ ] Patterns to avoid are explicit
|
|
384
|
+
- [ ] Config and migration steps are listed when needed
|
|
458
385
|
|
|
459
386
|
### Verification
|
|
460
387
|
|
|
461
|
-
- [ ] Criteria are checkboxes
|
|
462
|
-
- [ ] Each criterion is testable
|
|
463
|
-
- [ ] Criteria cover
|
|
464
|
-
- [ ] No criterion is vague ("it performs well" → "p95 latency < 200ms under 100 concurrent requests")
|
|
388
|
+
- [ ] Criteria are checkboxes
|
|
389
|
+
- [ ] Each criterion is testable
|
|
390
|
+
- [ ] Criteria cover functional and structural outcomes
|
|
465
391
|
|
|
466
392
|
### Options (MADR template)
|
|
467
393
|
|
|
468
|
-
- [ ] At least two options were
|
|
469
|
-
- [ ] Each option has real pros
|
|
470
|
-
- [ ]
|
|
471
|
-
- [ ] Rejected options explain WHY they were rejected, not just what they are
|
|
394
|
+
- [ ] At least two genuine options were considered
|
|
395
|
+
- [ ] Each option has real pros and cons
|
|
396
|
+
- [ ] Chosen option cites specific drivers or tradeoffs
|
|
472
397
|
|
|
473
398
|
### Meta
|
|
474
399
|
|
|
475
|
-
- [ ] Status is
|
|
400
|
+
- [ ] Status is correct
|
|
476
401
|
- [ ] Date is set
|
|
477
402
|
- [ ] Decision-makers are listed
|
|
478
|
-
- [ ] Title is a verb phrase
|
|
403
|
+
- [ ] Title is a decision verb phrase
|
|
479
404
|
- [ ] Filename follows repo conventions
|
|
480
405
|
|
|
481
406
|
## Quick Scoring
|
|
482
407
|
|
|
483
|
-
Count
|
|
408
|
+
Count checked items.
|
|
484
409
|
|
|
485
410
|
- **All checked**: Ship it.
|
|
486
|
-
- **1–3 unchecked**: Discuss
|
|
487
|
-
- **4+ unchecked**:
|
|
411
|
+
- **1–3 unchecked**: Discuss gaps.
|
|
412
|
+
- **4+ unchecked**: Return to Phase 1.
|
|
488
413
|
|
|
489
414
|
## Common Failure Modes
|
|
490
415
|
|
|
491
|
-
| Symptom
|
|
492
|
-
|
|
|
493
|
-
| "Improve performance" as a consequence
|
|
494
|
-
| Only one option listed
|
|
495
|
-
|
|
|
496
|
-
|
|
|
497
|
-
| "We decided to use X" with no why | Missing justification | Ask: "why X over Y?" — the 'over Y' forces comparison |
|
|
498
|
-
| Implementation Plan says "update the code" | Too abstract | Ask: "which files, which functions, what pattern?" |
|
|
499
|
-
| Verification says "it works" | Not testable | Ask: "what command would you run to prove it works?" |
|
|
500
|
-
| No affected paths listed | Implementation Plan is hand-wavy | Agent should scan the codebase and propose specific paths |
|
|
416
|
+
| Symptom | Root Cause | Fix |
|
|
417
|
+
| --- | --- | --- |
|
|
418
|
+
| "Improve performance" as a consequence | Vague intent | Ask: "which metric, by how much, measured how?" |
|
|
419
|
+
| Only one option listed | Decision already made | Ask: "what did you reject and why?" |
|
|
420
|
+
| Implementation Plan says "update the code" | Too abstract | Ask: "which files, which functions, what pattern?" |
|
|
421
|
+
| Verification says "it works" | Not testable | Ask: "what command proves it works?" |
|
|
501
422
|
`},{file:`references/template-variants.md`,content:`# Template Variants
|
|
502
423
|
|
|
503
424
|
This skill ships two templates in \`assets/templates/\`.
|
|
@@ -506,11 +427,11 @@ This skill ships two templates in \`assets/templates/\`.
|
|
|
506
427
|
|
|
507
428
|
File: \`assets/templates/adr-simple.md\`
|
|
508
429
|
|
|
509
|
-
Use
|
|
430
|
+
Use when:
|
|
510
431
|
|
|
511
|
-
-
|
|
512
|
-
- You
|
|
513
|
-
- Alternatives are few and
|
|
432
|
+
- Decision is straightforward
|
|
433
|
+
- You need "why, what, consequences, impl"
|
|
434
|
+
- Alternatives are few and brief
|
|
514
435
|
- Speed matters more than exhaustive comparison
|
|
515
436
|
|
|
516
437
|
Sections: Context and Problem Statement → Decision → Consequences → Implementation Plan → Verification → Alternatives Considered (optional) → More Information (optional).
|
|
@@ -519,25 +440,24 @@ Sections: Context and Problem Statement → Decision → Consequences → Implem
|
|
|
519
440
|
|
|
520
441
|
File: \`assets/templates/adr-madr.md\`
|
|
521
442
|
|
|
522
|
-
Use
|
|
443
|
+
Use when:
|
|
523
444
|
|
|
524
|
-
-
|
|
525
|
-
-
|
|
526
|
-
-
|
|
527
|
-
- Stakeholders need
|
|
445
|
+
- Multiple real options need structured tradeoffs
|
|
446
|
+
- Decision drivers must be explicit
|
|
447
|
+
- Decision may be revisited later
|
|
448
|
+
- Stakeholders need reasoning, not only outcome
|
|
528
449
|
|
|
529
450
|
Sections: Context and Problem Statement → Decision Drivers (optional) → Considered Options → Decision Outcome → Consequences → Implementation Plan → Verification → Pros and Cons of the Options (optional) → More Information (optional).
|
|
530
451
|
|
|
531
|
-
|
|
452
|
+
Aligns with [MADR 4.0](https://adr.github.io/madr/) plus agent-first sections.
|
|
532
453
|
|
|
533
454
|
## Both Templates Share
|
|
534
455
|
|
|
535
|
-
- **YAML front matter** for
|
|
536
|
-
- **Implementation Plan** — affected paths,
|
|
537
|
-
- **Verification as checkboxes** — testable criteria
|
|
538
|
-
- **Agent-first framing
|
|
539
|
-
- **"More Information"
|
|
540
|
-
- **"Neutral, because..."** as a third argument category alongside Good and Bad
|
|
456
|
+
- **YAML front matter** for status, date, decision-makers, consulted, informed
|
|
457
|
+
- **Implementation Plan** — affected paths, deps, patterns to follow/avoid, config, migration steps
|
|
458
|
+
- **Verification as checkboxes** — testable criteria after impl
|
|
459
|
+
- **Agent-first framing** — placeholders push specificity and measurable constraints
|
|
460
|
+
- **"More Information"** for cross-links, follow-ups, revisit triggers
|
|
541
461
|
|
|
542
462
|
## Choosing Between Them
|
|
543
463
|
|
|
@@ -549,829 +469,393 @@ This template aligns with [MADR 4.0](https://adr.github.io/madr/) and extends it
|
|
|
549
469
|
| Expected lifetime | Months | Years |
|
|
550
470
|
| Needs stakeholder review | No | Yes |
|
|
551
471
|
|
|
552
|
-
When in doubt, start with Simple.
|
|
472
|
+
When in doubt, start with Simple. Expand to MADR if needed.
|
|
553
473
|
`},{file:`scripts/bootstrap_adr.js`,content:`#!/usr/bin/env node
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const path = require('node:path');
|
|
563
|
-
|
|
564
|
-
function die(msg) {
|
|
565
|
-
process.stderr.write(\`\${msg}\\n\`);
|
|
566
|
-
process.exit(1);
|
|
474
|
+
const fs=require('node:fs');
|
|
475
|
+
const path=require('node:path');
|
|
476
|
+
|
|
477
|
+
function die(msg){process.stderr.write(\`\${msg}\\n\`);process.exit(1);}
|
|
478
|
+
function toPosix(value){return value.split(path.sep).join('/');}
|
|
479
|
+
function slugify(text){
|
|
480
|
+
const value=String(text||'').trim().toLowerCase();
|
|
481
|
+
return value.replace(/['"\`]/g,'').replace(/[^a-z0-9]+/g,'-').replace(/-{2,}/g,'-').replace(/^-+|-+$/g,'')||'decision';
|
|
567
482
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
else if (a === '--strategy') out.strategy = next();
|
|
599
|
-
else if (a === '--json') out.json = true;
|
|
600
|
-
else if (a === '--help' || a === '-h') {
|
|
601
|
-
process.stdout.write(
|
|
602
|
-
[
|
|
603
|
-
'Usage: node bootstrap_adr.js [options]',
|
|
604
|
-
'',
|
|
605
|
-
'Options:',
|
|
606
|
-
' --repo-root <path> Repo root (default: .)',
|
|
607
|
-
' --dir <path> ADR directory (default: adr)',
|
|
608
|
-
' --index-file <path> Override index file path (relative to repo root unless absolute)',
|
|
609
|
-
' --force-index Overwrite index file if it exists',
|
|
610
|
-
' --first-title <text> Title for initial ADR',
|
|
611
|
-
' --first-status <text> Status for initial ADR (default: accepted)',
|
|
612
|
-
' --strategy date|slug|auto Filename strategy for initial ADR (default: date)',
|
|
613
|
-
' --json Output machine-readable JSON (default: off)',
|
|
614
|
-
'',
|
|
615
|
-
].join('\\n'),
|
|
616
|
-
);
|
|
483
|
+
function parseArgs(argv){
|
|
484
|
+
const out={repoRoot:'.',dir:'adr',forceIndex:false,indexFile:null,firstTitle:'Adopt architecture decision records',firstStatus:'accepted',deciders:'',technicalStory:'',strategy:'date',json:false};
|
|
485
|
+
for(let index=2;index<argv.length;index++){
|
|
486
|
+
const arg=argv[index];
|
|
487
|
+
const next=()=>{if(index+1>=argv.length) die(\`Missing value for \${arg}\`);return argv[++index];};
|
|
488
|
+
if(arg==='--repo-root') out.repoRoot=next();
|
|
489
|
+
else if(arg==='--dir') out.dir=next();
|
|
490
|
+
else if(arg==='--force-index') out.forceIndex=true;
|
|
491
|
+
else if(arg==='--index-file') out.indexFile=next();
|
|
492
|
+
else if(arg==='--first-title') out.firstTitle=next();
|
|
493
|
+
else if(arg==='--first-status') out.firstStatus=next();
|
|
494
|
+
else if(arg==='--deciders') out.deciders=next();
|
|
495
|
+
else if(arg==='--technical-story') out.technicalStory=next();
|
|
496
|
+
else if(arg==='--strategy') out.strategy=next();
|
|
497
|
+
else if(arg==='--json') out.json=true;
|
|
498
|
+
else if(arg==='--help'||arg==='-h'){
|
|
499
|
+
process.stdout.write([
|
|
500
|
+
'Usage: node bootstrap_adr.js [options]',
|
|
501
|
+
'',
|
|
502
|
+
'Options:',
|
|
503
|
+
' --repo-root <path> Repo root (default: .)',
|
|
504
|
+
' --dir <path> ADR dir (default: adr)',
|
|
505
|
+
' --index-file <path> Index path override',
|
|
506
|
+
' --force-index Overwrite existing index',
|
|
507
|
+
' --first-title <text> Initial ADR title',
|
|
508
|
+
' --first-status <text> Initial ADR status (default: accepted)',
|
|
509
|
+
' --strategy date|slug|auto Filename strategy (default: date)',
|
|
510
|
+
' --json Emit JSON',
|
|
511
|
+
'',
|
|
512
|
+
].join('\\n'));
|
|
617
513
|
process.exit(0);
|
|
618
|
-
}
|
|
619
|
-
die(\`Unknown arg: \${a}\`);
|
|
620
|
-
}
|
|
514
|
+
}else die(\`Unknown arg: \${arg}\`);
|
|
621
515
|
}
|
|
622
|
-
|
|
623
|
-
if (!['auto', 'date', 'slug'].includes(out.strategy)) die(\`Invalid --strategy: \${out.strategy}\`);
|
|
516
|
+
if(!['auto','date','slug'].includes(out.strategy)) die(\`Invalid --strategy: \${out.strategy}\`);
|
|
624
517
|
return out;
|
|
625
518
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if (!fs.existsSync(templatePath)) die(\`README template not found: \${templatePath}\`);
|
|
631
|
-
return fs.readFileSync(templatePath, 'utf8');
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function writeIndex(indexFile, adrDirName, { force }) {
|
|
635
|
-
if (fs.existsSync(indexFile) && !force) return;
|
|
636
|
-
const content = loadReadmeTemplate().replaceAll('{ADR_DIR}', adrDirName);
|
|
637
|
-
fs.mkdirSync(path.dirname(indexFile), { recursive: true });
|
|
638
|
-
fs.writeFileSync(indexFile, \`\${content.trimEnd()}\\n\`, 'utf8');
|
|
519
|
+
function loadReadmeTemplate(){
|
|
520
|
+
const templatePath=path.join(path.resolve(__dirname,'..'),'assets','templates','adr-readme.md');
|
|
521
|
+
if(!fs.existsSync(templatePath)) die(\`README template not found: \${templatePath}\`);
|
|
522
|
+
return fs.readFileSync(templatePath,'utf8');
|
|
639
523
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const noQuotes = t.replace(/['"\`]/g, '');
|
|
646
|
-
const dashed = noQuotes.replace(/[^a-z0-9]+/g, '-').replace(/-{2,}/g, '-');
|
|
647
|
-
const trimmed = dashed.replace(/^-+/, '').replace(/-+$/, '');
|
|
648
|
-
return trimmed || 'decision';
|
|
524
|
+
function writeIndex(indexFile,adrDirName,{force}){
|
|
525
|
+
if(fs.existsSync(indexFile)&&!force) return;
|
|
526
|
+
const content=loadReadmeTemplate().replaceAll('{ADR_DIR}',adrDirName);
|
|
527
|
+
fs.mkdirSync(path.dirname(indexFile),{recursive:true});
|
|
528
|
+
fs.writeFileSync(indexFile,\`\${content.trimEnd()}\\n\`,'utf8');
|
|
649
529
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
return p.split(path.sep).join('/');
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
function generateFirstAdr({ title, status, date, deciders, adrDir }) {
|
|
656
|
-
const deciderLine = deciders
|
|
657
|
-
? String(deciders)
|
|
658
|
-
.split(',')
|
|
659
|
-
.map((s) => s.trim())
|
|
660
|
-
.filter(Boolean)
|
|
661
|
-
.join(', ')
|
|
662
|
-
: '';
|
|
663
|
-
|
|
530
|
+
function generateFirstAdr({title,status,date,deciders,adrDir}){
|
|
531
|
+
const decisionMakers=deciders?String(deciders).split(',').map((name)=>name.trim()).filter(Boolean).join(', '):'';
|
|
664
532
|
return \`---
|
|
665
533
|
status: \${status}
|
|
666
534
|
date: \${date}
|
|
667
|
-
decision-makers: \${
|
|
535
|
+
decision-makers: \${decisionMakers}
|
|
668
536
|
---
|
|
669
537
|
|
|
670
538
|
# \${title}
|
|
671
539
|
|
|
672
540
|
## Context and Problem Statement
|
|
673
541
|
|
|
674
|
-
Architecture decisions
|
|
675
|
-
|
|
676
|
-
- Understand whether a pattern is intentional or accidental
|
|
677
|
-
- Know if a past decision still applies or has been superseded
|
|
678
|
-
- Avoid relitigating decisions that were already carefully considered
|
|
679
|
-
|
|
680
|
-
We need a lightweight, version-controlled way to capture decisions where the code lives.
|
|
542
|
+
Architecture decisions are mostly implicit: code, chat, tribal knowledge. New contributors cannot quickly recover why patterns exist or whether old decisions still apply. We need version-controlled decision records near code.
|
|
681
543
|
|
|
682
544
|
## Decision
|
|
683
545
|
|
|
684
|
-
Adopt Architecture Decision Records (ADRs) using
|
|
546
|
+
Adopt Architecture Decision Records (ADRs) using MADR 4.0, stored in \\\`\${adrDir}/\\\`.
|
|
685
547
|
|
|
686
548
|
Conventions:
|
|
687
549
|
- One ADR per file, named \\\`YYYY-MM-DD-title-with-dashes.md\\\`
|
|
688
|
-
- New ADRs start as \\\`proposed\\\`, move to \\\`accepted\\\` or \\\`rejected\\\`
|
|
689
|
-
- Superseded ADRs link to
|
|
690
|
-
- ADRs are
|
|
550
|
+
- New ADRs start as \\\`proposed\\\`, then move to \\\`accepted\\\` or \\\`rejected\\\`
|
|
551
|
+
- Superseded ADRs link to replacements
|
|
552
|
+
- ADRs are self-contained enough for coding agents to implement from them
|
|
691
553
|
|
|
692
554
|
## Consequences
|
|
693
555
|
|
|
694
|
-
* Good, because decisions
|
|
695
|
-
* Good, because
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
* Neutral, because ADRs require periodic review to mark outdated decisions as deprecated or superseded
|
|
556
|
+
* Good, because decisions stay discoverable and version-controlled with code
|
|
557
|
+
* Good, because contributors can recover why architecture choices were made
|
|
558
|
+
* Bad, because writing ADRs costs time
|
|
559
|
+
* Neutral, because ADRs need periodic review for deprecation or supersession
|
|
699
560
|
|
|
700
561
|
## Alternatives Considered
|
|
701
562
|
|
|
702
|
-
* No formal records:
|
|
703
|
-
* Wiki or Notion pages:
|
|
704
|
-
* Lightweight RFCs:
|
|
563
|
+
* No formal records: Rejected because context is lost and decisions get relitigated.
|
|
564
|
+
* Wiki or Notion pages: Rejected because they drift from code and lack repo history.
|
|
565
|
+
* Lightweight RFCs: Rejected as too heavy for most decisions; ADRs can still scale up when needed.
|
|
705
566
|
\`;
|
|
706
567
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if (headingIdx !== -1) {
|
|
721
|
-
// Insert after the heading (and any blank line after it)
|
|
722
|
-
let insertAt = headingIdx + 1;
|
|
723
|
-
while (insertAt < lines.length && lines[insertAt].trim() === '') insertAt++;
|
|
724
|
-
lines.splice(insertAt, 0, entryLine);
|
|
725
|
-
} else {
|
|
726
|
-
lines.push(entryLine);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
fs.writeFileSync(indexFile, lines.join('\\n'), 'utf8');
|
|
568
|
+
function updateIndexFile(indexFile,{relLink,title,status,date}){
|
|
569
|
+
if(!fs.existsSync(indexFile)) return;
|
|
570
|
+
const content=fs.readFileSync(indexFile,'utf8');
|
|
571
|
+
if(content.includes(relLink)) return;
|
|
572
|
+
const entryLine=\`- [\${title}](\${relLink}) (\${status}, \${date})\`;
|
|
573
|
+
const lines=content.replace(/\\r\\n/g,'\\n').split('\\n');
|
|
574
|
+
const headingIndex=lines.findIndex((line)=>/^##\\s+ADRs\\s*$/i.test(line));
|
|
575
|
+
if(headingIndex!==-1){
|
|
576
|
+
let insertAt=headingIndex+1;
|
|
577
|
+
while(insertAt<lines.length&&lines[insertAt].trim()==='') insertAt++;
|
|
578
|
+
lines.splice(insertAt,0,entryLine);
|
|
579
|
+
}else lines.push(entryLine);
|
|
580
|
+
fs.writeFileSync(indexFile,lines.join('\\n'),'utf8');
|
|
730
581
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
// Create the first ADR as a filled-out decision (not a blank template).
|
|
752
|
-
const relIndex = path.isAbsolute(indexFile) ? path.relative(repoRoot, indexFile) : indexFile;
|
|
753
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
754
|
-
|
|
755
|
-
const firstAdrContent = generateFirstAdr({
|
|
756
|
-
title: args.firstTitle,
|
|
757
|
-
status: args.firstStatus,
|
|
758
|
-
date: today,
|
|
759
|
-
deciders: args.deciders,
|
|
760
|
-
adrDir: args.dir,
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
// Determine filename using same logic as new_adr.js
|
|
764
|
-
const strategy = args.strategy === 'auto' ? 'date' : args.strategy;
|
|
765
|
-
let firstAdrFilename;
|
|
766
|
-
if (strategy === 'date') {
|
|
767
|
-
firstAdrFilename = \`\${today}-\${slugify(args.firstTitle)}.md\`;
|
|
768
|
-
} else {
|
|
769
|
-
firstAdrFilename = \`\${slugify(args.firstTitle)}.md\`;
|
|
770
|
-
}
|
|
771
|
-
const firstAdrPath = path.join(adrDir, firstAdrFilename);
|
|
772
|
-
fs.writeFileSync(firstAdrPath, \`\${firstAdrContent.trimEnd()}\\n\`, 'utf8');
|
|
773
|
-
|
|
774
|
-
// Update index
|
|
775
|
-
const relLink = toPosix(path.relative(path.dirname(indexFile), firstAdrPath));
|
|
776
|
-
updateIndexFile(indexFile, {
|
|
777
|
-
relLink,
|
|
778
|
-
title: args.firstTitle,
|
|
779
|
-
status: args.firstStatus,
|
|
780
|
-
date: today,
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
if (args.json) {
|
|
784
|
-
const payload = {
|
|
785
|
-
repoRoot,
|
|
786
|
-
adrDir,
|
|
787
|
-
adrDirRelPath: toPosix(path.relative(repoRoot, adrDir)),
|
|
788
|
-
indexPath: indexFile,
|
|
789
|
-
indexRelPath: toPosix(relIndex),
|
|
790
|
-
indexExistedBefore,
|
|
791
|
-
indexWritten,
|
|
792
|
-
firstAdr: {
|
|
793
|
-
createdAdrPath: firstAdrPath,
|
|
794
|
-
createdAdrRelPath: toPosix(path.relative(repoRoot, firstAdrPath)),
|
|
795
|
-
title: args.firstTitle,
|
|
796
|
-
status: args.firstStatus,
|
|
797
|
-
strategy,
|
|
798
|
-
date: today,
|
|
799
|
-
},
|
|
800
|
-
date: today,
|
|
801
|
-
};
|
|
802
|
-
process.stdout.write(\`\${JSON.stringify(payload)}\\n\`);
|
|
582
|
+
function main(){
|
|
583
|
+
const args=parseArgs(process.argv);
|
|
584
|
+
const repoRoot=path.resolve(process.cwd(),args.repoRoot);
|
|
585
|
+
if(!fs.existsSync(repoRoot)) die(\`Repo root does not exist: \${repoRoot}\`);
|
|
586
|
+
const adrDir=path.resolve(repoRoot,args.dir);
|
|
587
|
+
fs.mkdirSync(adrDir,{recursive:true});
|
|
588
|
+
const indexFile=args.indexFile?(path.isAbsolute(args.indexFile)?args.indexFile:path.resolve(repoRoot,args.indexFile)):path.join(adrDir,'README.md');
|
|
589
|
+
const indexExistedBefore=fs.existsSync(indexFile);
|
|
590
|
+
writeIndex(indexFile,args.dir,{force:args.forceIndex});
|
|
591
|
+
const indexWritten=fs.existsSync(indexFile)&&(!indexExistedBefore||args.forceIndex);
|
|
592
|
+
const relIndex=path.isAbsolute(indexFile)?path.relative(repoRoot,indexFile):indexFile;
|
|
593
|
+
const date=new Date().toISOString().slice(0,10);
|
|
594
|
+
const strategy=args.strategy==='auto'?'date':args.strategy;
|
|
595
|
+
const fileName=strategy==='date'?\`\${date}-\${slugify(args.firstTitle)}.md\`:\`\${slugify(args.firstTitle)}.md\`;
|
|
596
|
+
const firstAdrPath=path.join(adrDir,fileName);
|
|
597
|
+
fs.writeFileSync(firstAdrPath,\`\${generateFirstAdr({title:args.firstTitle,status:args.firstStatus,date,deciders:args.deciders,adrDir:args.dir}).trimEnd()}\\n\`,'utf8');
|
|
598
|
+
updateIndexFile(indexFile,{relLink:toPosix(path.relative(path.dirname(indexFile),firstAdrPath)),title:args.firstTitle,status:args.firstStatus,date});
|
|
599
|
+
if(args.json){
|
|
600
|
+
process.stdout.write(\`\${JSON.stringify({repoRoot,adrDir,adrDirRelPath:toPosix(path.relative(repoRoot,adrDir)),indexPath:indexFile,indexRelPath:toPosix(relIndex),indexExistedBefore,indexWritten,firstAdr:{createdAdrPath:firstAdrPath,createdAdrRelPath:toPosix(path.relative(repoRoot,firstAdrPath)),title:args.firstTitle,status:args.firstStatus,strategy,date},date})}\\n\`);
|
|
803
601
|
return;
|
|
804
602
|
}
|
|
805
|
-
|
|
806
603
|
process.stdout.write(\`\${firstAdrPath}\\n\`);
|
|
807
|
-
process.stdout.write(\`Bootstrapped ADRs at \${adrDir} (\${
|
|
604
|
+
process.stdout.write(\`Bootstrapped ADRs at \${adrDir} (\${date})\\n\`);
|
|
808
605
|
process.stdout.write(\`Index: \${indexFile}\\n\`);
|
|
809
606
|
}
|
|
810
607
|
|
|
811
608
|
main();
|
|
812
609
|
`},{file:`scripts/new_adr.js`,content:`#!/usr/bin/env node
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
const fs = require('node:fs');
|
|
823
|
-
const path = require('node:path');
|
|
824
|
-
|
|
825
|
-
function die(msg) {
|
|
826
|
-
process.stderr.write(\`\${msg}\\n\`);
|
|
827
|
-
process.exit(1);
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
function slugify(text) {
|
|
831
|
-
const t = String(text || '')
|
|
832
|
-
.trim()
|
|
833
|
-
.toLowerCase();
|
|
834
|
-
const noQuotes = t.replace(/['"\`]/g, '');
|
|
835
|
-
const dashed = noQuotes.replace(/[^a-z0-9]+/g, '-').replace(/-{2,}/g, '-');
|
|
836
|
-
const trimmed = dashed.replace(/^-+/, '').replace(/-+$/, '');
|
|
837
|
-
return trimmed || 'decision';
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
function toPosix(p) {
|
|
841
|
-
return p.split(path.sep).join('/');
|
|
610
|
+
const fs=require('node:fs');
|
|
611
|
+
const path=require('node:path');
|
|
612
|
+
|
|
613
|
+
function die(msg){process.stderr.write(\`\${msg}\\n\`);process.exit(1);}
|
|
614
|
+
function toPosix(value){return value.split(path.sep).join('/');}
|
|
615
|
+
function slugify(text){
|
|
616
|
+
const value=String(text||'').trim().toLowerCase();
|
|
617
|
+
return value.replace(/['"\`]/g,'').replace(/[^a-z0-9]+/g,'-').replace(/-{2,}/g,'-').replace(/^-+|-+$/g,'')||'decision';
|
|
842
618
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
else if (a === '--json') out.json = true;
|
|
885
|
-
else if (a === '--help' || a === '-h') {
|
|
886
|
-
process.stdout.write(
|
|
887
|
-
[
|
|
888
|
-
'Usage: node new_adr.js --title "Choose database" [options]',
|
|
889
|
-
'',
|
|
890
|
-
'Options:',
|
|
891
|
-
' --repo-root <path> Repo root (default: .)',
|
|
892
|
-
' --dir <path> ADR directory (default: auto-detect, else adr/)',
|
|
893
|
-
' --no-create-dir Do not create ADR directory if missing',
|
|
894
|
-
' --status <value> ADR status (default: proposed)',
|
|
895
|
-
' --template simple|madr Template (default: simple)',
|
|
896
|
-
' --strategy auto|date|slug Filename strategy (default: auto)',
|
|
897
|
-
' --deciders "a,b" Deciders list',
|
|
898
|
-
' --consulted "a,b" Consulted experts (RACI)',
|
|
899
|
-
' --informed "a,b" Informed stakeholders (RACI)',
|
|
900
|
-
' --technical-story <x> Issue/ticket/PR link or short ref',
|
|
901
|
-
' --chosen-option <x> MADR template: chosen option label',
|
|
902
|
-
' --update-index Update adr/README.md (or existing index)',
|
|
903
|
-
' --index-file <path> Override index file (relative to repo root unless absolute)',
|
|
904
|
-
' --json Output machine-readable JSON (default: off)',
|
|
905
|
-
'',
|
|
906
|
-
].join('\\n'),
|
|
907
|
-
);
|
|
619
|
+
function parseArgs(argv){
|
|
620
|
+
const out={repoRoot:'.',dir:null,noCreateDir:false,title:null,status:'proposed',template:'simple',strategy:'auto',deciders:'',consulted:'',informed:'',technicalStory:'',chosenOption:'',updateIndex:false,indexFile:null,json:false};
|
|
621
|
+
for(let index=2;index<argv.length;index++){
|
|
622
|
+
const arg=argv[index];
|
|
623
|
+
const next=()=>{if(index+1>=argv.length) die(\`Missing value for \${arg}\`);return argv[++index];};
|
|
624
|
+
if(arg==='--repo-root') out.repoRoot=next();
|
|
625
|
+
else if(arg==='--dir') out.dir=next();
|
|
626
|
+
else if(arg==='--no-create-dir') out.noCreateDir=true;
|
|
627
|
+
else if(arg==='--title') out.title=next();
|
|
628
|
+
else if(arg==='--status') out.status=next();
|
|
629
|
+
else if(arg==='--template') out.template=next();
|
|
630
|
+
else if(arg==='--strategy') out.strategy=next();
|
|
631
|
+
else if(arg==='--deciders') out.deciders=next();
|
|
632
|
+
else if(arg==='--consulted') out.consulted=next();
|
|
633
|
+
else if(arg==='--informed') out.informed=next();
|
|
634
|
+
else if(arg==='--technical-story') out.technicalStory=next();
|
|
635
|
+
else if(arg==='--chosen-option') out.chosenOption=next();
|
|
636
|
+
else if(arg==='--update-index') out.updateIndex=true;
|
|
637
|
+
else if(arg==='--index-file') out.indexFile=next();
|
|
638
|
+
else if(arg==='--json') out.json=true;
|
|
639
|
+
else if(arg==='--help'||arg==='-h'){
|
|
640
|
+
process.stdout.write([
|
|
641
|
+
'Usage: node new_adr.js --title "Choose database" [options]',
|
|
642
|
+
'',
|
|
643
|
+
'Options:',
|
|
644
|
+
' --repo-root <path> Repo root (default: .)',
|
|
645
|
+
' --dir <path> ADR dir (default: auto-detect, else adr/)',
|
|
646
|
+
' --no-create-dir Do not create ADR dir',
|
|
647
|
+
' --status <value> ADR status (default: proposed)',
|
|
648
|
+
' --template simple|madr Template (default: simple)',
|
|
649
|
+
' --strategy auto|date|slug Filename strategy (default: auto)',
|
|
650
|
+
' --deciders "a,b" Deciders list',
|
|
651
|
+
' --consulted "a,b" Consulted list (RACI)',
|
|
652
|
+
' --informed "a,b" Informed list (RACI)',
|
|
653
|
+
' --technical-story <x> Issue/ticket/PR ref',
|
|
654
|
+
' --chosen-option <x> MADR chosen option label',
|
|
655
|
+
' --update-index Update adr/README.md (or existing index)',
|
|
656
|
+
' --index-file <path> Index file override',
|
|
657
|
+
' --json Emit JSON',
|
|
658
|
+
'',
|
|
659
|
+
].join('\\n'));
|
|
908
660
|
process.exit(0);
|
|
909
|
-
}
|
|
910
|
-
die(\`Unknown arg: \${a}\`);
|
|
911
|
-
}
|
|
661
|
+
}else die(\`Unknown arg: \${arg}\`);
|
|
912
662
|
}
|
|
913
|
-
|
|
914
|
-
if
|
|
915
|
-
|
|
916
|
-
if (!['simple', 'madr'].includes(out.template)) die(\`Invalid --template: \${out.template}\`);
|
|
917
|
-
if (!['auto', 'date', 'slug'].includes(out.strategy)) die(\`Invalid --strategy: \${out.strategy}\`);
|
|
918
|
-
|
|
663
|
+
if(!out.title) die('Missing required --title');
|
|
664
|
+
if(!['simple','madr'].includes(out.template)) die(\`Invalid --template: \${out.template}\`);
|
|
665
|
+
if(!['auto','date','slug'].includes(out.strategy)) die(\`Invalid --strategy: \${out.strategy}\`);
|
|
919
666
|
return out;
|
|
920
667
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
path.join(repoRoot, 'contributing', 'decisions'),
|
|
925
|
-
path.join(repoRoot, 'docs', 'decisions'),
|
|
926
|
-
path.join(repoRoot, 'adr'),
|
|
927
|
-
path.join(repoRoot, 'docs', 'adr'),
|
|
928
|
-
path.join(repoRoot, 'docs', 'adrs'),
|
|
929
|
-
path.join(repoRoot, 'decisions'),
|
|
930
|
-
];
|
|
931
|
-
for (const p of candidates) {
|
|
932
|
-
try {
|
|
933
|
-
if (fs.statSync(p).isDirectory()) return p;
|
|
934
|
-
} catch {
|
|
935
|
-
// ignore
|
|
936
|
-
}
|
|
668
|
+
function detectAdrDir(repoRoot){
|
|
669
|
+
for(const candidate of [path.join(repoRoot,'contributing','decisions'),path.join(repoRoot,'docs','decisions'),path.join(repoRoot,'adr'),path.join(repoRoot,'docs','adr'),path.join(repoRoot,'docs','adrs'),path.join(repoRoot,'decisions')]){
|
|
670
|
+
try{if(fs.statSync(candidate).isDirectory()) return candidate;}catch{}
|
|
937
671
|
}
|
|
938
672
|
return null;
|
|
939
673
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
let entries = [];
|
|
943
|
-
try {
|
|
944
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
945
|
-
} catch {
|
|
946
|
-
return [];
|
|
947
|
-
}
|
|
948
|
-
return entries
|
|
949
|
-
.filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
|
|
950
|
-
.map((e) => e.name);
|
|
674
|
+
function listMdFiles(dir){
|
|
675
|
+
try{return fs.readdirSync(dir,{withFileTypes:true}).filter((entry)=>entry.isFile()&&entry.name.toLowerCase().endsWith('.md')).map((entry)=>entry.name);}catch{return [];}
|
|
951
676
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
if (/^\\d{4}-\\d{2}-\\d{2}-/.test(name)) return 'date';
|
|
957
|
-
}
|
|
958
|
-
if (md.length > 0) return 'slug';
|
|
959
|
-
return 'date';
|
|
677
|
+
function detectStrategy(adrDir){
|
|
678
|
+
const files=listMdFiles(adrDir);
|
|
679
|
+
if(files.some((name)=>/^\\d{4}-\\d{2}-\\d{2}-/.test(name))) return 'date';
|
|
680
|
+
return files.length?'slug':'date';
|
|
960
681
|
}
|
|
961
|
-
|
|
962
|
-
function
|
|
963
|
-
|
|
682
|
+
function todayISO(){return new Date().toISOString().slice(0,10);}
|
|
683
|
+
function loadTemplate(name){
|
|
684
|
+
const templatePath=path.join(path.resolve(__dirname,'..'),'assets','templates',\`adr-\${name}.md\`);
|
|
685
|
+
if(!fs.existsSync(templatePath)) die(\`Template not found: \${templatePath}\`);
|
|
686
|
+
return fs.readFileSync(templatePath,'utf8');
|
|
964
687
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
688
|
+
function renderTemplate(raw,vars){
|
|
689
|
+
let out=raw;
|
|
690
|
+
out=out.replace(/^(status:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m,\`$1\${vars.status}\`);
|
|
691
|
+
out=out.replace(/^(date:\\s*)\\{[^}]*\\}\\s*$/m,\`$1\${vars.date}\`);
|
|
692
|
+
out=out.replace(/^(decision-makers:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m,\`$1\${vars.deciders||''}\`);
|
|
693
|
+
out=vars.consulted?out.replace(/^(consulted:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m,\`$1\${vars.consulted}\`):out.replace(/^consulted:\\s*["']?\\{[^}]*\\}["']?\\s*\\n/m,'');
|
|
694
|
+
out=vars.informed?out.replace(/^(informed:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m,\`$1\${vars.informed}\`):out.replace(/^informed:\\s*["']?\\{[^}]*\\}["']?\\s*\\n/m,'');
|
|
695
|
+
return out
|
|
696
|
+
.replace(/^(#\\s+)\\{short title[^}]*\\}\\s*$/m,\`$1\${vars.title}\`)
|
|
697
|
+
.replaceAll('{TITLE}',vars.title)
|
|
698
|
+
.replaceAll('{STATUS}',vars.status)
|
|
699
|
+
.replaceAll('{DATE}',vars.date)
|
|
700
|
+
.replaceAll('{DECIDERS}',vars.deciders)
|
|
701
|
+
.replaceAll('{TECHNICAL_STORY}',vars.technicalStory)
|
|
702
|
+
.replaceAll('{CHOSEN_OPTION}',vars.chosenOption);
|
|
971
703
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
let out = raw;
|
|
976
|
-
|
|
977
|
-
// YAML front matter fields — replace the whole placeholder pattern
|
|
978
|
-
// e.g. status: "{proposed | accepted | ...}" → status: proposed
|
|
979
|
-
out = out.replace(/^(status:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m, \`$1\${vars.status}\`);
|
|
980
|
-
out = out.replace(/^(date:\\s*)\\{[^}]*\\}\\s*$/m, \`$1\${vars.date}\`);
|
|
981
|
-
out = out.replace(/^(decision-makers:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m, \`$1\${vars.deciders || ''}\`);
|
|
982
|
-
|
|
983
|
-
// consulted / informed: replace if a value was provided, otherwise remove the
|
|
984
|
-
// entire line so we don't leak placeholder text like "{list everyone...}"
|
|
985
|
-
if (vars.consulted) {
|
|
986
|
-
out = out.replace(/^(consulted:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m, \`$1\${vars.consulted}\`);
|
|
987
|
-
} else {
|
|
988
|
-
out = out.replace(/^consulted:\\s*["']?\\{[^}]*\\}["']?\\s*\\n/m, '');
|
|
989
|
-
}
|
|
990
|
-
if (vars.informed) {
|
|
991
|
-
out = out.replace(/^(informed:\\s*)["']?\\{[^}]*\\}["']?\\s*$/m, \`$1\${vars.informed}\`);
|
|
992
|
-
} else {
|
|
993
|
-
out = out.replace(/^informed:\\s*["']?\\{[^}]*\\}["']?\\s*\\n/m, '');
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
// Replace MADR-style heading placeholder
|
|
997
|
-
out = out.replace(/^(#\\s+)\\{short title[^}]*\\}\\s*$/m, \`$1\${vars.title}\`);
|
|
998
|
-
|
|
999
|
-
// Inline placeholders (title in heading, etc.)
|
|
1000
|
-
out = out
|
|
1001
|
-
.replaceAll('{TITLE}', vars.title)
|
|
1002
|
-
.replaceAll('{STATUS}', vars.status)
|
|
1003
|
-
.replaceAll('{DATE}', vars.date)
|
|
1004
|
-
.replaceAll('{DECIDERS}', vars.deciders)
|
|
1005
|
-
.replaceAll('{TECHNICAL_STORY}', vars.technicalStory)
|
|
1006
|
-
.replaceAll('{CHOSEN_OPTION}', vars.chosenOption);
|
|
1007
|
-
|
|
1008
|
-
return out;
|
|
704
|
+
function chooseIndexFile(adrDir){
|
|
705
|
+
for(const name of ['README.md','index.md']){const candidate=path.join(adrDir,name);if(fs.existsSync(candidate)) return candidate;}
|
|
706
|
+
return path.join(adrDir,'README.md');
|
|
1009
707
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
708
|
+
function insertIndexEntryUnderHeading(lines,headingRegex,entryLine){
|
|
709
|
+
const headingIndex=lines.findIndex((line)=>headingRegex.test(line));
|
|
710
|
+
if(headingIndex===-1) return {lines,inserted:false};
|
|
711
|
+
let sectionEnd=lines.length;
|
|
712
|
+
for(let index=headingIndex+1;index<lines.length;index++){if(/^##\\s+/.test(lines[index])){sectionEnd=index;break;}}
|
|
713
|
+
let insertAt=sectionEnd;
|
|
714
|
+
for(let index=sectionEnd-1;index>headingIndex;index--){if(/^[-*]\\s+/.test(lines[index])){insertAt=index+1;break;}}
|
|
715
|
+
const out=[...lines];
|
|
716
|
+
if(insertAt===headingIndex+1&&out[insertAt]!=='') out.splice(insertAt,0,'');
|
|
717
|
+
out.splice(insertAt,0,entryLine);
|
|
718
|
+
return {lines:out,inserted:true};
|
|
1017
719
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
// Prefer inserting at end of list in this section if there is a list.
|
|
1033
|
-
let lastListItem = -1;
|
|
1034
|
-
for (let i = sectionEnd - 1; i > headingIndex; i--) {
|
|
1035
|
-
if (/^[-*]\\s+/.test(lines[i])) {
|
|
1036
|
-
lastListItem = i;
|
|
1037
|
-
break;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
const insertAt = lastListItem !== -1 ? lastListItem + 1 : sectionEnd;
|
|
1042
|
-
|
|
1043
|
-
const out = [...lines];
|
|
1044
|
-
|
|
1045
|
-
// Ensure there's a blank line after the heading if we're inserting immediately after it.
|
|
1046
|
-
if (insertAt === headingIndex + 1 && out[insertAt] !== '') {
|
|
1047
|
-
out.splice(insertAt, 0, '');
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
out.splice(insertAt, 0, entryLine);
|
|
1051
|
-
return { lines: out, inserted: true };
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
function updateIndex(indexFile, { relLink, title, status, date }) {
|
|
1055
|
-
let content = '';
|
|
1056
|
-
if (fs.existsSync(indexFile)) content = fs.readFileSync(indexFile, 'utf8');
|
|
1057
|
-
else content = '# ADR Log\\n\\n';
|
|
1058
|
-
|
|
1059
|
-
if (content.includes(relLink)) return false;
|
|
1060
|
-
|
|
1061
|
-
const normalized = content.replace(/\\r\\n/g, '\\n');
|
|
1062
|
-
const hadTrailingNewline = normalized.endsWith('\\n');
|
|
1063
|
-
let lines = normalized.split('\\n');
|
|
1064
|
-
// Normalize away the trailing empty split element so insertion math is sane.
|
|
1065
|
-
if (hadTrailingNewline && lines.length > 0 && lines[lines.length - 1] === '') {
|
|
1066
|
-
lines = lines.slice(0, -1);
|
|
1067
|
-
}
|
|
1068
|
-
const entryLine = \`- [\${title}](\${relLink}) (\${status}, \${date})\`;
|
|
1069
|
-
|
|
1070
|
-
// Prefer inserting under "## ADRs" if it exists, otherwise append at EOF.
|
|
1071
|
-
const r = insertIndexEntryUnderHeading(lines, /^##\\s+ADRs\\s*$/i, entryLine);
|
|
1072
|
-
const nextLines = r.inserted ? r.lines : [...lines, entryLine];
|
|
1073
|
-
|
|
1074
|
-
let next = nextLines.join('\\n');
|
|
1075
|
-
if (hadTrailingNewline) next += '\\n';
|
|
1076
|
-
|
|
1077
|
-
fs.mkdirSync(path.dirname(indexFile), { recursive: true });
|
|
1078
|
-
fs.writeFileSync(indexFile, next, 'utf8');
|
|
720
|
+
function updateIndex(indexFile,{relLink,title,status,date}){
|
|
721
|
+
const source=fs.existsSync(indexFile)?fs.readFileSync(indexFile,'utf8'):'# ADR Log\\n\\n';
|
|
722
|
+
if(source.includes(relLink)) return false;
|
|
723
|
+
const normalized=source.replace(/\\r\\n/g,'\\n');
|
|
724
|
+
const hadTrailingNewline=normalized.endsWith('\\n');
|
|
725
|
+
const lines=normalized.split('\\n');
|
|
726
|
+
if(hadTrailingNewline&&lines[lines.length-1]==='') lines.pop();
|
|
727
|
+
const entryLine=\`- [\${title}](\${relLink}) (\${status}, \${date})\`;
|
|
728
|
+
const result=insertIndexEntryUnderHeading(lines,/^##\\s+ADRs\\s*$/i,entryLine);
|
|
729
|
+
let next=(result.inserted?result.lines:[...lines,entryLine]).join('\\n');
|
|
730
|
+
if(hadTrailingNewline) next+='\\n';
|
|
731
|
+
fs.mkdirSync(path.dirname(indexFile),{recursive:true});
|
|
732
|
+
fs.writeFileSync(indexFile,next,'utf8');
|
|
1079
733
|
return true;
|
|
1080
734
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
const
|
|
1086
|
-
if
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if
|
|
1093
|
-
if
|
|
1094
|
-
|
|
735
|
+
function main(){
|
|
736
|
+
const args=parseArgs(process.argv);
|
|
737
|
+
const repoRoot=path.resolve(process.cwd(),args.repoRoot);
|
|
738
|
+
if(!fs.existsSync(repoRoot)) die(\`Repo root does not exist: \${repoRoot}\`);
|
|
739
|
+
const adrDir=args.dir?path.resolve(repoRoot,args.dir):detectAdrDir(repoRoot)||path.join(repoRoot,'adr');
|
|
740
|
+
if(!fs.existsSync(adrDir)){if(args.noCreateDir) die(\`ADR directory does not exist: \${adrDir}\`);fs.mkdirSync(adrDir,{recursive:true});}
|
|
741
|
+
const strategy=args.strategy==='auto'?detectStrategy(adrDir):args.strategy;
|
|
742
|
+
const title=String(args.title).trim();
|
|
743
|
+
const slug=slugify(title);
|
|
744
|
+
const date=todayISO();
|
|
745
|
+
let out=path.join(adrDir,strategy==='date'?\`\${date}-\${slug}.md\`:\`\${slug}.md\`);
|
|
746
|
+
if(fs.existsSync(out)){
|
|
747
|
+
if(strategy==='date') die(\`ADR already exists: \${out}\`);
|
|
748
|
+
let suffix=2;
|
|
749
|
+
while(true){const candidate=path.join(adrDir,\`\${slug}-\${suffix}.md\`);if(!fs.existsSync(candidate)){out=candidate;break;}suffix++;}
|
|
1095
750
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
filename = \`\${today}-\${slug}.md\`;
|
|
1108
|
-
} else {
|
|
1109
|
-
filename = \`\${slug}.md\`;
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
let out = path.join(adrDir, filename);
|
|
1113
|
-
if (fs.existsSync(out)) {
|
|
1114
|
-
if (strategy === 'date') die(\`ADR already exists: \${out}\`);
|
|
1115
|
-
let i = 2;
|
|
1116
|
-
while (true) {
|
|
1117
|
-
const candidate = path.join(adrDir, \`\${slug}-\${i}.md\`);
|
|
1118
|
-
if (!fs.existsSync(candidate)) {
|
|
1119
|
-
out = candidate;
|
|
1120
|
-
break;
|
|
1121
|
-
}
|
|
1122
|
-
i++;
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
const deciders = String(args.deciders || '')
|
|
1127
|
-
.split(',')
|
|
1128
|
-
.map((s) => s.trim())
|
|
1129
|
-
.filter(Boolean)
|
|
1130
|
-
.join(', ');
|
|
1131
|
-
|
|
1132
|
-
const consulted = String(args.consulted || '')
|
|
1133
|
-
.split(',')
|
|
1134
|
-
.map((s) => s.trim())
|
|
1135
|
-
.filter(Boolean)
|
|
1136
|
-
.join(', ');
|
|
1137
|
-
const informed = String(args.informed || '')
|
|
1138
|
-
.split(',')
|
|
1139
|
-
.map((s) => s.trim())
|
|
1140
|
-
.filter(Boolean)
|
|
1141
|
-
.join(', ');
|
|
1142
|
-
|
|
1143
|
-
const raw = loadTemplate(args.template);
|
|
1144
|
-
const rendered = renderTemplate(raw, {
|
|
1145
|
-
title,
|
|
1146
|
-
status: String(args.status).trim(),
|
|
1147
|
-
date: today,
|
|
1148
|
-
deciders,
|
|
1149
|
-
consulted,
|
|
1150
|
-
informed,
|
|
1151
|
-
technicalStory: String(args.technicalStory || '').trim(),
|
|
1152
|
-
chosenOption: String(args.chosenOption || '').trim(),
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1155
|
-
fs.writeFileSync(out, \`\${rendered.trimEnd()}\\n\`, 'utf8');
|
|
1156
|
-
|
|
1157
|
-
let updatedIndexPath = null;
|
|
1158
|
-
let indexChanged = false;
|
|
1159
|
-
|
|
1160
|
-
if (args.updateIndex) {
|
|
1161
|
-
let indexFile;
|
|
1162
|
-
if (args.indexFile) {
|
|
1163
|
-
indexFile = path.isAbsolute(args.indexFile)
|
|
1164
|
-
? args.indexFile
|
|
1165
|
-
: path.resolve(repoRoot, args.indexFile);
|
|
1166
|
-
} else {
|
|
1167
|
-
indexFile = chooseIndexFile(adrDir);
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
const relLink = toPosix(path.relative(path.dirname(indexFile), out));
|
|
1171
|
-
indexChanged = updateIndex(indexFile, {
|
|
1172
|
-
relLink,
|
|
1173
|
-
title,
|
|
1174
|
-
status: String(args.status).trim(),
|
|
1175
|
-
date: today,
|
|
1176
|
-
});
|
|
1177
|
-
updatedIndexPath = indexFile;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
if (args.json) {
|
|
1181
|
-
const payload = {
|
|
1182
|
-
repoRoot,
|
|
1183
|
-
adrDir,
|
|
1184
|
-
createdAdrPath: out,
|
|
1185
|
-
createdAdrRelPath: toPosix(path.relative(repoRoot, out)),
|
|
1186
|
-
title,
|
|
1187
|
-
status: String(args.status).trim(),
|
|
1188
|
-
template: args.template,
|
|
1189
|
-
strategy,
|
|
1190
|
-
date: today,
|
|
1191
|
-
indexUpdated: Boolean(updatedIndexPath),
|
|
1192
|
-
indexChanged,
|
|
1193
|
-
indexPath: updatedIndexPath,
|
|
1194
|
-
indexRelPath: updatedIndexPath ? toPosix(path.relative(repoRoot, updatedIndexPath)) : null,
|
|
1195
|
-
};
|
|
1196
|
-
process.stdout.write(\`\${JSON.stringify(payload)}\\n\`);
|
|
1197
|
-
} else {
|
|
1198
|
-
process.stdout.write(\`\${out}\\n\`);
|
|
751
|
+
const deciders=String(args.deciders||'').split(',').map((value)=>value.trim()).filter(Boolean).join(', ');
|
|
752
|
+
const consulted=String(args.consulted||'').split(',').map((value)=>value.trim()).filter(Boolean).join(', ');
|
|
753
|
+
const informed=String(args.informed||'').split(',').map((value)=>value.trim()).filter(Boolean).join(', ');
|
|
754
|
+
const rendered=renderTemplate(loadTemplate(args.template),{title,status:String(args.status).trim(),date,deciders,consulted,informed,technicalStory:String(args.technicalStory||'').trim(),chosenOption:String(args.chosenOption||'').trim()});
|
|
755
|
+
fs.writeFileSync(out,\`\${rendered.trimEnd()}\\n\`,'utf8');
|
|
756
|
+
let updatedIndexPath=null;
|
|
757
|
+
let indexChanged=false;
|
|
758
|
+
if(args.updateIndex){
|
|
759
|
+
const indexFile=args.indexFile?(path.isAbsolute(args.indexFile)?args.indexFile:path.resolve(repoRoot,args.indexFile)):chooseIndexFile(adrDir);
|
|
760
|
+
indexChanged=updateIndex(indexFile,{relLink:toPosix(path.relative(path.dirname(indexFile),out)),title,status:String(args.status).trim(),date});
|
|
761
|
+
updatedIndexPath=indexFile;
|
|
1199
762
|
}
|
|
763
|
+
if(args.json) process.stdout.write(\`\${JSON.stringify({repoRoot,adrDir,createdAdrPath:out,createdAdrRelPath:toPosix(path.relative(repoRoot,out)),title,status:String(args.status).trim(),template:args.template,strategy,date,indexUpdated:Boolean(updatedIndexPath),indexChanged,indexPath:updatedIndexPath,indexRelPath:updatedIndexPath?toPosix(path.relative(repoRoot,updatedIndexPath)):null})}\\n\`);
|
|
764
|
+
else process.stdout.write(\`\${out}\\n\`);
|
|
1200
765
|
}
|
|
1201
766
|
|
|
1202
767
|
main();
|
|
1203
768
|
`},{file:`scripts/set_adr_status.js`,content:`#!/usr/bin/env node
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
function toPosix(p) {
|
|
1221
|
-
return p.split(path.sep).join('/');
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
function parseArgs(argv) {
|
|
1225
|
-
if (argv.includes('--help') || argv.includes('-h')) {
|
|
1226
|
-
process.stdout.write(
|
|
1227
|
-
[
|
|
1228
|
-
'Usage: node set_adr_status.js <path> --status <value> [--json]',
|
|
1229
|
-
'',
|
|
1230
|
-
'Example:',
|
|
1231
|
-
' node set_adr_status.js adr/2025-06-15-foo.md --status accepted',
|
|
1232
|
-
'',
|
|
1233
|
-
].join('\\n'),
|
|
1234
|
-
);
|
|
769
|
+
const fs=require('node:fs');
|
|
770
|
+
const path=require('node:path');
|
|
771
|
+
|
|
772
|
+
function die(msg){process.stderr.write(\`\${msg}\\n\`);process.exit(1);}
|
|
773
|
+
function toPosix(value){return value.split(path.sep).join('/');}
|
|
774
|
+
function parseArgs(argv){
|
|
775
|
+
if(argv.includes('--help')||argv.includes('-h')){
|
|
776
|
+
process.stdout.write([
|
|
777
|
+
'Usage: node set_adr_status.js <path> --status <value> [--json]',
|
|
778
|
+
'',
|
|
779
|
+
'Example:',
|
|
780
|
+
' node set_adr_status.js adr/2025-06-15-foo.md --status accepted',
|
|
781
|
+
'',
|
|
782
|
+
].join('\\n'));
|
|
1235
783
|
process.exit(0);
|
|
1236
784
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
if
|
|
1246
|
-
|
|
1247
|
-
status = argv[++i];
|
|
1248
|
-
} else if (a === '--json') {
|
|
1249
|
-
json = true;
|
|
1250
|
-
} else {
|
|
1251
|
-
die(\`Unknown arg: \${a}\`);
|
|
1252
|
-
}
|
|
785
|
+
if(argv.length<3) die('Missing <path>');
|
|
786
|
+
let status=null;
|
|
787
|
+
let json=false;
|
|
788
|
+
for(let index=3;index<argv.length;index++){
|
|
789
|
+
const arg=argv[index];
|
|
790
|
+
if(arg==='--status'){
|
|
791
|
+
if(index+1>=argv.length) die('Missing value for --status');
|
|
792
|
+
status=argv[++index];
|
|
793
|
+
}else if(arg==='--json') json=true;
|
|
794
|
+
else die(\`Unknown arg: \${arg}\`);
|
|
1253
795
|
}
|
|
1254
|
-
if
|
|
1255
|
-
return {
|
|
796
|
+
if(!status) die('Missing required --status');
|
|
797
|
+
return {file:argv[2],status:String(status).trim(),json};
|
|
1256
798
|
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
let changed
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
const line = lines[i];
|
|
1269
|
-
|
|
1270
|
-
if (i === 0 && line.trim() === '---') {
|
|
1271
|
-
passedOpening = true;
|
|
1272
|
-
out.push(line);
|
|
1273
|
-
continue;
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
if (passedOpening && inFrontMatter && line.trim() === '---') {
|
|
1277
|
-
inFrontMatter = false;
|
|
1278
|
-
out.push(line);
|
|
1279
|
-
continue;
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
if (passedOpening && inFrontMatter && /^status\\s*:/.test(line)) {
|
|
1283
|
-
out.push(\`status: \${newStatus}\`);
|
|
1284
|
-
changed = true;
|
|
1285
|
-
continue;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
799
|
+
function setYaml(lines,status){
|
|
800
|
+
if(lines.length<2||lines[0].trim()!=='---') return {lines,changed:false};
|
|
801
|
+
const out=[];
|
|
802
|
+
let inFrontMatter=true;
|
|
803
|
+
let passedOpening=false;
|
|
804
|
+
let changed=false;
|
|
805
|
+
for(let index=0;index<lines.length;index++){
|
|
806
|
+
const line=lines[index];
|
|
807
|
+
if(index===0&&line.trim()==='---'){passedOpening=true;out.push(line);continue;}
|
|
808
|
+
if(passedOpening&&inFrontMatter&&line.trim()==='---'){inFrontMatter=false;out.push(line);continue;}
|
|
809
|
+
if(passedOpening&&inFrontMatter&&/^status\\s*:/.test(line)){out.push(\`status: \${status}\`);changed=true;continue;}
|
|
1288
810
|
out.push(line);
|
|
1289
811
|
}
|
|
1290
|
-
|
|
1291
|
-
return { lines: out, changed };
|
|
812
|
+
return {lines:out,changed};
|
|
1292
813
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
814
|
+
function setBullet(lines,status){
|
|
815
|
+
let changed=false;
|
|
816
|
+
return {
|
|
817
|
+
changed,
|
|
818
|
+
lines:lines.map((line)=>{
|
|
819
|
+
const match=line.match(/^([*-])\\s*Status:\\s*(.*)$/);
|
|
820
|
+
if(!match) return line;
|
|
821
|
+
changed=true;
|
|
822
|
+
return \`\${match[1]} Status: \${status}\`;
|
|
823
|
+
}),
|
|
824
|
+
};
|
|
1303
825
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
let changed
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
if
|
|
1313
|
-
|
|
1314
|
-
// Replace next non-empty, non-heading line. If not found, insert.
|
|
1315
|
-
let j = i + 1;
|
|
1316
|
-
while (j < lines.length && lines[j].trim() === '') {
|
|
1317
|
-
out.push(lines[j]);
|
|
1318
|
-
j++;
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
if (j < lines.length && !/^##\\s+/.test(lines[j])) {
|
|
1322
|
-
out.push(newStatus);
|
|
1323
|
-
changed = true;
|
|
1324
|
-
i = j; // skip original status line
|
|
1325
|
-
continue;
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
out.push(newStatus);
|
|
1329
|
-
changed = true;
|
|
1330
|
-
i = j - 1;
|
|
826
|
+
function setSection(lines,status){
|
|
827
|
+
const out=[];
|
|
828
|
+
let changed=false;
|
|
829
|
+
for(let index=0;index<lines.length;index++){
|
|
830
|
+
out.push(lines[index]);
|
|
831
|
+
if(!/^##\\s+Status\\s*$/.test(lines[index])) continue;
|
|
832
|
+
let next=index+1;
|
|
833
|
+
while(next<lines.length&&lines[next].trim()===''){out.push(lines[next]);next++;}
|
|
834
|
+
if(next<lines.length&&!/^##\\s+/.test(lines[next])){out.push(status);changed=true;index=next;continue;}
|
|
835
|
+
out.push(status);changed=true;index=next-1;
|
|
1331
836
|
}
|
|
1332
|
-
|
|
1333
|
-
return { lines: out, changed };
|
|
837
|
+
return {lines:out,changed};
|
|
1334
838
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const content
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if
|
|
1348
|
-
|
|
1349
|
-
die(
|
|
1350
|
-
"Could not find a status to update. Expected YAML front matter 'status:', '- Status:'/'* Status:', or a '## Status' section.",
|
|
1351
|
-
);
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
const newContent = r.lines.join('\\n') + (hadTrailingNewline ? '\\n' : '');
|
|
1355
|
-
fs.writeFileSync(filePath, newContent, 'utf8');
|
|
1356
|
-
|
|
1357
|
-
if (args.json) {
|
|
1358
|
-
process.stdout.write(
|
|
1359
|
-
\`\${JSON.stringify({
|
|
1360
|
-
filePath,
|
|
1361
|
-
fileRelPath: toPosix(path.relative(process.cwd(), filePath)),
|
|
1362
|
-
status: args.status,
|
|
1363
|
-
changed: true,
|
|
1364
|
-
})}\\n\`,
|
|
1365
|
-
);
|
|
1366
|
-
} else {
|
|
1367
|
-
process.stdout.write(\`\${filePath}\\n\`);
|
|
1368
|
-
}
|
|
839
|
+
function main(){
|
|
840
|
+
const args=parseArgs(process.argv);
|
|
841
|
+
const filePath=path.resolve(process.cwd(),args.file);
|
|
842
|
+
if(!fs.existsSync(filePath)) die(\`File not found: \${filePath}\`);
|
|
843
|
+
const content=fs.readFileSync(filePath,'utf8');
|
|
844
|
+
const hadTrailingNewline=content.endsWith('\\n');
|
|
845
|
+
const lines=content.replace(/\\r\\n/g,'\\n').split('\\n');
|
|
846
|
+
let result=setYaml(lines,args.status);
|
|
847
|
+
if(!result.changed) result=setBullet(lines,args.status);
|
|
848
|
+
if(!result.changed) result=setSection(lines,args.status);
|
|
849
|
+
if(!result.changed) die("Could not find a status to update. Expected YAML front matter 'status:', '- Status:'/'* Status:', or a '## Status' section.");
|
|
850
|
+
fs.writeFileSync(filePath,result.lines.join('\\n')+(hadTrailingNewline?'\\n':''),'utf8');
|
|
851
|
+
if(args.json) process.stdout.write(\`\${JSON.stringify({filePath,fileRelPath:toPosix(path.relative(process.cwd(),filePath)),status:args.status,changed:true})}\\n\`);
|
|
852
|
+
else process.stdout.write(\`\${filePath}\\n\`);
|
|
1369
853
|
}
|
|
1370
854
|
|
|
1371
855
|
main();
|
|
1372
856
|
`},{file:`SKILL.md`,content:`---
|
|
1373
857
|
name: adr-skill
|
|
1374
|
-
description: Create and maintain Architecture Decision Records (ADRs)
|
|
858
|
+
description: Create and maintain Architecture Decision Records (ADRs) for agentic coding workflows. Use for proposing, drafting, updating, accepting/rejecting, deprecating, superseding, bootstrapping ADRs, consulting ADRs before impl, and enforcing ADR conventions. Trigger on architecture decisions, tech choices, major tradeoffs, decisions affecting >3 files or >1 team, and hard-to-reverse choices. Uses Socratic questioning plus an agent-readiness checklist.
|
|
1375
859
|
metadata:
|
|
1376
860
|
category: cross-cutting
|
|
1377
861
|
domain: general
|
|
@@ -1386,159 +870,118 @@ metadata:
|
|
|
1386
870
|
|
|
1387
871
|
## Quick Reference
|
|
1388
872
|
|
|
1389
|
-
**Purpose:** Create
|
|
873
|
+
**Purpose:** Create ADRs agents can implement from without follow-up.
|
|
1390
874
|
|
|
1391
|
-
**Write an ADR when:**
|
|
875
|
+
**Write an ADR when:** decision changes system shape, is hard to reverse, affects other people/agents, or has real alternatives.
|
|
1392
876
|
|
|
1393
877
|
**Four-Phase Workflow:**
|
|
1394
|
-
1. **Discover** —
|
|
1395
|
-
2. **Draft** —
|
|
1396
|
-
3. **Validate** —
|
|
1397
|
-
4. **Record** —
|
|
878
|
+
1. **Discover** — surface intent, constraints, alternatives
|
|
879
|
+
2. **Draft** — fill Simple or MADR template with concrete constraints
|
|
880
|
+
3. **Validate** — review for agent-readiness (target ≥ 80%)
|
|
881
|
+
4. **Record** — save to \`docs/decisions/\` or \`adr/\`, update index
|
|
1398
882
|
|
|
1399
883
|
**Two templates:**
|
|
1400
884
|
- **Simple** (≤3 options, single-team) — Context → Decision → Consequences → Implementation Plan → Alternatives
|
|
1401
|
-
- **MADR** (complex, multi-team) —
|
|
885
|
+
- **MADR** (complex, multi-team) — adds drivers, options matrix, deeper plan
|
|
1402
886
|
|
|
1403
|
-
**Commands:**
|
|
887
|
+
**Commands:** Consult existing ADRs before impl. Create/update with scripts below.
|
|
1404
888
|
|
|
1405
|
-
**Agent-readiness gate:**
|
|
889
|
+
**Agent-readiness gate:** target ≥ 80%.
|
|
1406
890
|
|
|
1407
891
|
## Mindset
|
|
1408
892
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
The future audience is more important. They have zero context about your constraints, discussions, and tradeoffs. Write for them.
|
|
1412
|
-
|
|
1413
|
-
An ADR is NOT meeting minutes. It captures:
|
|
1414
|
-
- The FORCES that drove the decision (constraints, requirements, team skills)
|
|
1415
|
-
- The ALTERNATIVES genuinely considered (with honest evaluation)
|
|
1416
|
-
- The IRREVERSIBILITY assessment (one-way door vs two-way door)
|
|
1417
|
-
- The CONSEQUENCES acknowledged upfront (technical debt accepted, capability traded)
|
|
893
|
+
Write for future readers with zero context. ADR = decision record, not meeting notes.
|
|
1418
894
|
|
|
1419
895
|
## Philosophy
|
|
1420
896
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
- Constraints must be explicit and measurable, not vibes
|
|
1424
|
-
- Decisions must be specific enough to act on ("use PostgreSQL 16 with pgvector" not "use a database")
|
|
1425
|
-
- Consequences must map to concrete follow-up tasks
|
|
1426
|
-
- Non-goals must be stated to prevent scope creep
|
|
1427
|
-
- The ADR must be self-contained — no tribal knowledge assumptions
|
|
1428
|
-
- **The ADR must include an implementation plan** — which files to touch, which patterns to follow, which tests to write, and how to verify the decision was implemented correctly
|
|
897
|
+
ADR must let an agent implement without follow-up: explicit constraints, concrete decision, follow-up tasks, non-goals, self-contained context, and an implementation plan.
|
|
1429
898
|
|
|
1430
899
|
## ADR Quality Criteria
|
|
1431
900
|
|
|
1432
901
|
An ADR is agent-ready when it passes:
|
|
1433
|
-
- [ ] **Context is self-contained**
|
|
1434
|
-
- [ ] **Options include at least 2 genuine alternatives**
|
|
1435
|
-
- [ ] **Decision rationale cites concrete evidence**
|
|
1436
|
-
- [ ] **Consequences are bidirectional**
|
|
1437
|
-
- [ ] **Status is explicit**
|
|
1438
|
-
- [ ] **Superseded ADRs link to their replacement**
|
|
902
|
+
- [ ] **Context is self-contained**
|
|
903
|
+
- [ ] **Options include at least 2 genuine alternatives**
|
|
904
|
+
- [ ] **Decision rationale cites concrete evidence**
|
|
905
|
+
- [ ] **Consequences are bidirectional**
|
|
906
|
+
- [ ] **Status is explicit**
|
|
907
|
+
- [ ] **Superseded ADRs link to their replacement**
|
|
1439
908
|
|
|
1440
909
|
## When to Write an ADR
|
|
1441
910
|
|
|
1442
911
|
Write an ADR when a decision:
|
|
1443
912
|
|
|
1444
|
-
- **Changes how
|
|
913
|
+
- **Changes how system is built or operated** (new dep, pattern, infra choice, API design)
|
|
1445
914
|
- **Is hard to reverse** once code is written against it
|
|
1446
|
-
- **Affects other people or agents** who will work in
|
|
915
|
+
- **Affects other people or agents** who will work in codebase later
|
|
1447
916
|
- **Has real alternatives** that were considered and rejected
|
|
1448
917
|
|
|
1449
918
|
## NEVER
|
|
1450
919
|
|
|
1451
|
-
- **NEVER write
|
|
1452
|
-
- **NEVER include only one option**
|
|
1453
|
-
- **NEVER use vague consequences**
|
|
1454
|
-
- **NEVER let ADRs
|
|
1455
|
-
- **NEVER modify an accepted ADR
|
|
1456
|
-
- **NEVER write
|
|
1457
|
-
- **NEVER skip
|
|
920
|
+
- **NEVER write ADR after impl** unless documenting discovery
|
|
921
|
+
- **NEVER include only one option**
|
|
922
|
+
- **NEVER use vague consequences**
|
|
923
|
+
- **NEVER let ADRs linger in stale proposed state**
|
|
924
|
+
- **NEVER modify an accepted ADR**; supersede instead
|
|
925
|
+
- **NEVER write ADR for trivial decisions**
|
|
926
|
+
- **NEVER skip rejected alternatives**
|
|
1458
927
|
|
|
1459
|
-
When in doubt: if a future agent working in this codebase would benefit from knowing _why_ this choice was made, write the ADR.
|
|
1460
928
|
|
|
1461
|
-
### Proactive ADR Triggers (For Agents)
|
|
1462
929
|
|
|
1463
|
-
|
|
930
|
+
### Proactive ADR Triggers (For Agents)
|
|
1464
931
|
|
|
1465
|
-
|
|
1466
|
-
- You are about to create a new architectural pattern (new way of handling errors, new data access layer, new API convention) that other code will need to follow
|
|
1467
|
-
- You are about to make a choice between two or more real alternatives and the tradeoffs are non-obvious
|
|
1468
|
-
- You are about to change something that contradicts an existing accepted ADR
|
|
1469
|
-
- You realize you're writing a long code comment explaining "why" — that reasoning belongs in an ADR
|
|
932
|
+
Stop and propose an ADR when you hit a new dependency, a new shared pattern, a non-obvious tradeoff, a conflict with an accepted ADR, or a long code comment explaining "why".
|
|
1470
933
|
|
|
1471
|
-
**How to propose**:
|
|
934
|
+
**How to propose**: state decision, why it matters, ask whether to capture as ADR. If yes, run workflow. If no, note reasoning in code comment and move on.
|
|
1472
935
|
|
|
1473
936
|
## Creating an ADR: Four-Phase Workflow
|
|
1474
937
|
|
|
1475
|
-
Every ADR goes through four phases. Do not skip
|
|
938
|
+
Every ADR goes through four phases. Do not skip them.
|
|
1476
939
|
|
|
1477
940
|
### Phase 0: Scan the Codebase
|
|
1478
941
|
|
|
1479
|
-
Before
|
|
942
|
+
Before questions, gather enough repo context to avoid contradictory ADRs:
|
|
1480
943
|
|
|
1481
|
-
1. **Find existing ADRs.** Check \`contributing/decisions/\`, \`docs/decisions/\`, \`adr/\`, \`docs/adr/\`, \`decisions/\`. Note
|
|
1482
|
-
2. **Check
|
|
1483
|
-
3. **Find related code patterns.** Identify
|
|
1484
|
-
4. **Look for ADR references in code.** Comments and docs often reveal
|
|
944
|
+
1. **Find existing ADRs.** Check \`contributing/decisions/\`, \`docs/decisions/\`, \`adr/\`, \`docs/adr/\`, \`decisions/\`. Note dir, naming, status style, related ADRs, possible supersession.
|
|
945
|
+
2. **Check tech stack.** Read \`package.json\`, \`go.mod\`, \`requirements.txt\`, \`Cargo.toml\`, or equivalent for relevant deps and versions.
|
|
946
|
+
3. **Find related code patterns.** Identify files, dirs, and impl patterns affected.
|
|
947
|
+
4. **Look for ADR references in code.** Comments and docs often reveal governing decisions.
|
|
1485
948
|
|
|
1486
949
|
### Phase 1: Capture Intent (Socratic)
|
|
1487
950
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
**Core questions** (ask in roughly this order, skip what's already clear from context or Phase 0):
|
|
951
|
+
Ask questions **one at a time**.
|
|
1491
952
|
|
|
1492
|
-
|
|
1493
|
-
2. **Why now?** What changed, broke, or will break if you do nothing?
|
|
1494
|
-
3. **What constraints exist?** Tech stack, timeline, budget, team skills, compliance, and existing decisions.
|
|
1495
|
-
4. **What does success look like?** Turn "it works" into measurable outcomes.
|
|
1496
|
-
5. **What options were considered?** At least two real alternatives, each with a core tradeoff.
|
|
1497
|
-
6. **What's the current lean?** Capture the instinct and the reason.
|
|
1498
|
-
7. **Who decides?** List decision-makers, consulted experts, and informed stakeholders.
|
|
1499
|
-
8. **What would an agent need to implement this?** Affected files, patterns to follow/avoid, config changes, and verification.
|
|
953
|
+
**Core questions** (skip what Phase 0 or context already answered): title, why now, constraints, success criteria, real options, current lean, who decides, and what an agent needs to implement.
|
|
1500
954
|
|
|
1501
|
-
**Adaptive follow-ups**: probe
|
|
955
|
+
**Adaptive follow-ups**: probe fuzzy areas. Useful prompts: "What's worst case if this is wrong?", "What would make you revisit this in 6 months?", "I found [existing ADR/pattern] — does this interact with it?"
|
|
1502
956
|
|
|
1503
957
|
**When to stop**: stop only when every ADR section, especially Implementation Plan and Verification, can be filled without guessing.
|
|
1504
958
|
|
|
1505
|
-
**Intent Summary Gate**: Before
|
|
959
|
+
**Intent Summary Gate**: Before Phase 2, summarize title, trigger, constraints, options, lean, non-goals, related ADRs/code, affected files, and verification plan. Ask: **Does this capture your intent? Anything to add or correct?**
|
|
1506
960
|
|
|
1507
|
-
Do NOT proceed to Phase 2 until
|
|
961
|
+
Do NOT proceed to Phase 2 until human confirms summary.
|
|
1508
962
|
|
|
1509
963
|
### Phase 2: Draft the ADR
|
|
1510
964
|
|
|
1511
965
|
1. **Choose the ADR directory.**
|
|
1512
|
-
|
|
1513
|
-
- If
|
|
1514
|
-
- If none exists, create \`contributing/decisions/\` (if \`contributing/\` exists), \`docs/decisions/\` (MADR default), or \`adr/\` (simpler repos).
|
|
1515
|
-
|
|
966
|
+
- If one exists, use it.
|
|
967
|
+
- If none exists, create \`contributing/decisions/\` (if \`contributing/\` exists), \`docs/decisions/\`, or \`adr/\`.
|
|
1516
968
|
2. **Choose a filename strategy.**
|
|
1517
|
-
|
|
1518
969
|
- If existing ADRs use date prefixes (\`YYYY-MM-DD-...\`), continue that.
|
|
1519
970
|
- Otherwise use slug-only filenames (\`choose-database.md\`).
|
|
1520
|
-
|
|
1521
971
|
3. **Choose a template.**
|
|
1522
|
-
|
|
1523
|
-
- Use \`assets/templates/adr-
|
|
1524
|
-
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
5. **Write the Implementation Plan.** This is the most important section for agent-first ADRs. It tells the next agent exactly what to do. See the template for structure.
|
|
1530
|
-
|
|
1531
|
-
6. **Write Verification criteria as checkboxes.** These must be specific enough that an agent can programmatically or manually check each one.
|
|
1532
|
-
|
|
1533
|
-
7. **Generate the file.**
|
|
1534
|
-
- Preferred: run \`scripts/new_adr.js\` (handles directory, naming, and optional index updates).
|
|
1535
|
-
- If you can't run scripts, copy a template from \`assets/templates/\` and fill it manually.
|
|
972
|
+
- Use \`assets/templates/adr-simple.md\` for straightforward decisions.
|
|
973
|
+
- Use \`assets/templates/adr-madr.md\` for multiple options with structured tradeoffs.
|
|
974
|
+
- See \`references/template-variants.md\` if unsure.
|
|
975
|
+
4. **Fill every section from confirmed intent summary.** Remove placeholders.
|
|
976
|
+
5. **Write the Implementation Plan.** It tells next agent exactly what to do.
|
|
977
|
+
6. **Write Verification criteria as checkboxes.** They must be specific enough for manual or programmatic checking.
|
|
978
|
+
7. **Generate the file.** Preferred: run \`scripts/new_adr.js\`. Otherwise copy a template and fill it manually.
|
|
1536
979
|
|
|
1537
980
|
### Phase 3: Review Against Checklist
|
|
1538
981
|
|
|
1539
|
-
|
|
982
|
+
Review against inline quality criteria, then use \`references/review-checklist.md\` for full pass.
|
|
1540
983
|
|
|
1541
|
-
**Present the review as
|
|
984
|
+
**Present the review as summary**, not raw checklist dump. Format:
|
|
1542
985
|
|
|
1543
986
|
> **ADR Review**
|
|
1544
987
|
>
|
|
@@ -1551,102 +994,85 @@ After drafting, review the ADR against the inline quality criteria above, then u
|
|
|
1551
994
|
>
|
|
1552
995
|
> **Recommendation**: {Ship it / Fix the gaps first / Needs more Phase 1 work}
|
|
1553
996
|
|
|
1554
|
-
|
|
997
|
+
Surface failures and notable strengths only. If gaps exist, propose fixes. Do not finalize until ADR passes or human accepts gaps.
|
|
1555
998
|
|
|
1556
999
|
## Consulting ADRs (Read Workflow)
|
|
1557
1000
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
Consult ADRs:
|
|
1001
|
+
Read existing ADRs **before implementing changes** in repos that have them.
|
|
1561
1002
|
|
|
1562
|
-
1. **Before architecture work.**
|
|
1563
|
-
2. **Find
|
|
1564
|
-
3. **
|
|
1565
|
-
4. **
|
|
1566
|
-
5. **
|
|
1567
|
-
6. **
|
|
1568
|
-
7. **Reference the ADR in your work.** Link it in code comments or PR notes when the decision directly governs the change.
|
|
1003
|
+
1. **Before architecture work.**
|
|
1004
|
+
2. **Find ADR dir and index.** Start with accepted ADRs.
|
|
1005
|
+
3. **Read full ADR, not just title.**
|
|
1006
|
+
4. **Follow the Implementation Plan.**
|
|
1007
|
+
5. **Escalate conflicts or contradictions.**
|
|
1008
|
+
6. **Reference governing ADR in your work.**
|
|
1569
1009
|
|
|
1570
1010
|
## Code ↔ ADR Linking
|
|
1571
1011
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
- **ADR → Code**: the Implementation Plan should name affected paths, patterns, config changes, and verification commands.
|
|
1575
|
-
- **Code → ADR**: add one lightweight comment at the entry point that links back to the governing ADR.
|
|
1576
|
-
- **Why**: this makes the reasoning discoverable in both directions and makes superseded decisions easier to unwind.
|
|
1012
|
+
- **ADR → Code**: Implementation Plan names affected paths, patterns, config changes, verification commands.
|
|
1013
|
+
- **Code → ADR**: add one lightweight comment at entry point linking back to governing ADR.
|
|
1577
1014
|
|
|
1578
1015
|
## Other Operations
|
|
1579
1016
|
|
|
1580
1017
|
### Update an Existing ADR
|
|
1581
1018
|
|
|
1582
|
-
1. Identify
|
|
1583
|
-
|
|
1584
|
-
- **Accept / reject**: change status, add any final context.
|
|
1585
|
-
- **Deprecate**: status → \`deprecated\`, explain replacement path.
|
|
1586
|
-
- **Supersede**: create a new ADR, link both ways (old → new, new → old).
|
|
1587
|
-
- **Add learnings**: append to \`## More Information\` with a date stamp. Do not rewrite history.
|
|
1588
|
-
|
|
1589
|
-
2. Use \`scripts/set_adr_status.js\` for status changes (supports YAML front matter, bullet status, and section status).
|
|
1019
|
+
1. Identify intent: accept/reject, deprecate, supersede, or add learnings.
|
|
1020
|
+
2. Use \`scripts/set_adr_status.js\` for status changes.
|
|
1590
1021
|
|
|
1591
1022
|
### Post-Acceptance Lifecycle
|
|
1592
1023
|
|
|
1593
1024
|
After an ADR is accepted:
|
|
1594
1025
|
|
|
1595
|
-
1. **Create implementation tasks.**
|
|
1596
|
-
2. **Reference
|
|
1597
|
-
3. **Add code references.**
|
|
1598
|
-
4. **Check verification criteria.**
|
|
1599
|
-
5. **Revisit when triggers fire.**
|
|
1026
|
+
1. **Create implementation tasks.**
|
|
1027
|
+
2. **Reference ADR in PRs.**
|
|
1028
|
+
3. **Add code references.**
|
|
1029
|
+
4. **Check verification criteria.** Update ADR with results in \`## More Information\`.
|
|
1030
|
+
5. **Revisit when triggers fire.**
|
|
1600
1031
|
|
|
1601
1032
|
### Index
|
|
1602
1033
|
|
|
1603
|
-
If
|
|
1034
|
+
If repo has ADR index/log file, keep it updated.
|
|
1604
1035
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
- Add a bullet entry for the new ADR.
|
|
1608
|
-
- Keep ordering consistent (numeric if numbered; date or alpha if slugs).
|
|
1036
|
+
- Let \`scripts/new_adr.js --update-index\` handle it when possible.
|
|
1037
|
+
- Otherwise add new ADR entry and keep ordering consistent.
|
|
1609
1038
|
|
|
1610
1039
|
### Bootstrap
|
|
1611
1040
|
|
|
1612
|
-
When introducing ADRs to
|
|
1041
|
+
When introducing ADRs to repo that has none:
|
|
1613
1042
|
|
|
1614
1043
|
\`\`\`bash
|
|
1615
1044
|
node scripts/bootstrap_adr.js
|
|
1616
1045
|
\`\`\`
|
|
1617
1046
|
|
|
1618
|
-
|
|
1047
|
+
Creates dir, index, and first ADR.
|
|
1619
1048
|
|
|
1620
1049
|
## Resources
|
|
1621
1050
|
|
|
1622
1051
|
### scripts/
|
|
1623
1052
|
|
|
1624
|
-
- \`scripts/new_adr.js\`
|
|
1625
|
-
- \`scripts/set_adr_status.js\`
|
|
1626
|
-
- \`scripts/bootstrap_adr.js\`
|
|
1053
|
+
- \`scripts/new_adr.js\`
|
|
1054
|
+
- \`scripts/set_adr_status.js\`
|
|
1055
|
+
- \`scripts/bootstrap_adr.js\`
|
|
1627
1056
|
|
|
1628
1057
|
### references/
|
|
1629
1058
|
|
|
1630
|
-
- \`references/review-checklist.md\`
|
|
1631
|
-
- \`references/adr-conventions.md\`
|
|
1632
|
-
- \`references/template-variants.md\`
|
|
1633
|
-
- \`references/examples.md\`
|
|
1059
|
+
- \`references/review-checklist.md\`
|
|
1060
|
+
- \`references/adr-conventions.md\`
|
|
1061
|
+
- \`references/template-variants.md\`
|
|
1062
|
+
- \`references/examples.md\`
|
|
1634
1063
|
|
|
1635
1064
|
### assets/
|
|
1636
1065
|
|
|
1637
|
-
- \`assets/templates/adr-simple.md\`
|
|
1638
|
-
- \`assets/templates/adr-madr.md\`
|
|
1639
|
-
- \`assets/templates/adr-readme.md\`
|
|
1066
|
+
- \`assets/templates/adr-simple.md\`
|
|
1067
|
+
- \`assets/templates/adr-madr.md\`
|
|
1068
|
+
- \`assets/templates/adr-readme.md\`
|
|
1640
1069
|
|
|
1641
1070
|
### Script Usage
|
|
1642
1071
|
|
|
1643
1072
|
\`\`\`bash
|
|
1644
1073
|
node scripts/new_adr.js --title "Choose database" --status proposed
|
|
1645
|
-
|
|
1646
|
-
node scripts/new_adr.js --title "Choose database" --status proposed --update-index
|
|
1647
|
-
node scripts/new_adr.js --title "Choose database" --template madr --status proposed
|
|
1648
1074
|
node scripts/bootstrap_adr.js --dir docs/decisions
|
|
1649
1075
|
\`\`\`
|
|
1650
1076
|
|
|
1651
|
-
|
|
1077
|
+
Use \`--dir\`, \`--strategy\`, and \`--json\` as needed.
|
|
1652
1078
|
`}];export{e as default};
|