opsward.decisions#
Decision capture: numbered ADRs, the one-line decision log, and their drift.
Two tiers, per ADR 0010 and misc/docs/research/adr_and_home_finding.md: a
trivial or medium decision is one Y-statement line appended to decisions/log.md
(“In the context of …, facing …, we decided for …, to achieve …, accepting …,
because …”); a load-bearing, expensive-to-reverse one is a numbered MADR file
decisions/NNNN-slug.md that is never edited once accepted, only superseded.
Pure functions here; the CLI (opsward decision new|note) is the only writer, and
it never overwrites. maintain uses check_decisions() to report an ADR that
cites a path which no longer exists, or that has no Status, which is how rot shows up.
Functions
|
The numbered ADR files in decisions_dir, in number order (the template is not one). |
|
|
|
Paths an ADR cites as current, outside fenced code: bare path tokens and Markdown link targets (relative ones only; URLs and host names are not paths). |
|
One line, single-spaced; empty is an error the CLI reports. |
|
The ADRs a scan found ( |
|
The log file and the full content it should hold after appending text. |
|
One Y-statement line for |
|
The next numbered ADR for title, rendered from the |
|
One more than the highest numbered ADR, so numbers never collide (1 for an empty dir). |
|
A file-name slug: lowercase, hyphens, nothing else, at most 60 characters. |
|
The ADR's status text, from a |
- opsward.decisions.adr_files(decisions_dir)[source]#
The numbered ADR files in decisions_dir, in number order (the template is not one).
- opsward.decisions.check_decisions(sr)[source]#
maintaincheck: ADRs citing paths that no longer exist, or carrying no status.Rot in a decision record is a path that moved: the rationale still reads well and points at nothing. Superseded, deprecated and rejected ADRs are history and are not checked for paths: the superseding record is where the current paths live. This reports; it never rewrites an accepted ADR (ADR 0010).
- Return type:
- opsward.decisions.cited_paths(content)[source]#
Paths an ADR cites as current, outside fenced code: bare path tokens and Markdown link targets (relative ones only; URLs and host names are not paths).
A path the record cites as history is not checked: wrap it in
~~strikethrough~~, or say so on the line (formerly,previously,moved,migrated,renamed,replaced,removed,retired,deleted), as in “we movedpkg/old.pytopkg/new.py” — there onlypkg/new.pymust exist.>>> cited_paths("See `pkg/a.py`, [0002](0002-x.md), [doc](../research/r.md), https://x.io/a/b.py, docs.github.com/en/x.md\n```\nsrc/in/fence.py\n```") ['../research/r.md', '0002-x.md', 'pkg/a.py'] >>> cited_paths("We moved `pkg/old.py` to `pkg/new.py`; ~~`pkg/gone.py`~~ is history.") ['pkg/new.py'] >>> cited_paths("It was read from `pkg/live.py`.\nRenamed [old](../old.md) to [new](../new.md).") ['../new.md', 'pkg/live.py']
- opsward.decisions.clean_title(title)[source]#
One line, single-spaced; empty is an error the CLI reports.
>>> clean_title(" two\n lines ") 'two lines'
- Return type:
- opsward.decisions.decision_docs(sr)[source]#
The ADRs a scan found (
ScanResult.decisions), numbered files only.
- opsward.decisions.log_append(decisions_dir, text, *, date=None)[source]#
The log file and the full content it should hold after appending text.
A missing log starts from the
log.mdtemplate. The existing log is read strictly: an unreadable or non-UTF-8 log raises (OSError/UnicodeDecodeError) rather than being replaced by the new line alone. The caller writes; this does not.
- opsward.decisions.log_entry(text, *, date=None)[source]#
One Y-statement line for
decisions/log.md.>>> log_entry("In the context of goldens, facing drift, we decided for pinning.", date=_dt.date(2026, 10, 19)) '- 2026-10-19 — In the context of goldens, facing drift, we decided for pinning.'
- Return type:
- opsward.decisions.new_adr(decisions_dir, title, *, status='Proposed', date=None, evidence='')[source]#
The next numbered ADR for title, rendered from the
adr.mdtemplate.evidence is the issue, discussion or PR the decision rests on (a URL or
#N); it lands in the Evidence section so the three surfaces stay linked.>>> import tempfile >>> gf = new_adr(Path(tempfile.mkdtemp()), "Pin the CLI surface", date=_dt.date(2026, 10, 19), evidence="#8") >>> gf.target_path.name, gf.content.splitlines()[0] ('0001-pin-the-cli-surface.md', '# 0001 — Pin the CLI surface')
- Return type:
- opsward.decisions.next_number(decisions_dir)[source]#
One more than the highest numbered ADR, so numbers never collide (1 for an empty dir).
>>> import tempfile >>> next_number(Path(tempfile.mkdtemp())) 1
- Return type:
- opsward.decisions.slugify(title)[source]#
A file-name slug: lowercase, hyphens, nothing else, at most 60 characters.
>>> slugify("Pin the CLI surface by goldens!") 'pin-the-cli-surface-by-goldens'
- Return type:
- opsward.decisions.status_of(content)[source]#
The ADR’s status text, from a
## Statusheading, a**Status:**line or MADR front matter;Nonewhen there is none.>>> status_of("# x\n\n## Status\n\nAccepted (2026-01-01)\n") 'Accepted (2026-01-01)' >>> status_of("---\nstatus: accepted\n---\n# x\n"), status_of("**Status:** Superseded by 0003"), status_of("# x\nno status") ('accepted', 'Superseded by 0003', None)