Skip a rejected option by its chapter, so one under a ### subheading is skipped too
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

cycle.py skipped a rejected option only when "Options rejected" was its nearest heading; a record that
lists its options under ### subheadings of "## Considered Options" had them held as deferrals.
This commit is contained in:
jochen
2026-10-09 19:32:16 +02:00
parent 169200fe55
commit 189dbcef7a
2 changed files with 14 additions and 9 deletions
+2 -1
View File
@@ -66,7 +66,8 @@ off — "until renamed", "left for later", "not decided here", "postponed", "fut
change" — names the issue that tracks it, as tracking it ("tracked by issue N", or "issue N tracks"), in that
paragraph or list item, or in a dated progressive insight that speaks about it: one in the same section, or one
that quotes it. An issue cited for another reason tracks nothing, and one insight does not excuse every deferral
in a record. A rejected option is not work put off and is not held
in a record. A rejected option is not work put off and is not held, judged by its `##` section, so an option under a
`###` subheading of "Considered Options" is skipped too
([issue 354](../../04-ISSUES/354-a-decided-rename-was-never-scheduled-and-the-operator-believed-it-live/00-report.md):
ADR 0244 deferred the code renames with no issue, and two days later the operator believed them done). Held to
it: a record dated from 2026-10-09, and an older one corrected under the rule. Measured from ADR 0244's date
+12 -8
View File
@@ -78,26 +78,29 @@ INSIGHT = re.compile(r"\*\*Progressive insights?[ \t]*[—–-][ \t]*(\d{4}-\d{2
def paragraphs(path):
"""A record's body as (first line number, section heading, text) paragraphs: frontmatter and fenced
"""A record's body as (first line number, section heading, chapter, text) paragraphs: frontmatter and fenced
code left out, a block quote's markers stripped, so a quoted insight reads as the paragraph it is. The
section is the nearest heading above the paragraph; a heading is its own paragraph and its own section."""
section is the nearest heading above the paragraph; a heading is its own paragraph and its own section.
The chapter is the nearest `#` or `##` heading, which a `###` subheading inside it does not change."""
text = open(path, encoding="utf-8").read()
m = re.match(r"^---\n.*?\n---\n?", text, re.S)
body, first = (text[m.end():], text[: m.end()].count("\n") + 1) if m else (text, 1)
body = re.sub(r"(?ms)^\s*```.*?^\s*```", lambda x: re.sub(r"[^\n]", " ", x.group(0)), body)
out, line, section = [], first, ""
out, line, section, chapter = [], first, "", ""
for chunk in body.split("\n\n"):
stripped = "\n".join(re.sub(r"^\s*>\s?", "", l) for l in chunk.split("\n")).strip()
if stripped:
heading = re.match(r"^#{1,6}\s+(.*)", stripped)
if heading:
section = heading.group(1).strip()
if len(stripped) - len(stripped.lstrip("#")) <= 2:
chapter = section
# A list's top-level items are units of their own: a Consequences list is one paragraph, and an
# issue named in one item tracks nothing in the next.
items = re.split(r"\n(?=[-*] |\d+\. )", stripped)
at = line + chunk[: len(chunk) - len(chunk.lstrip())].count("\n")
for item in items:
out.append((at, section, item))
out.append((at, section, chapter, item))
at += item.count("\n") + 1
line += chunk.count("\n") + 2
return out
@@ -309,17 +312,18 @@ def main():
if status != "accepted":
continue
paras = paragraphs(path)
insights = [(d, sec, text) for line, sec, text in paras for d in INSIGHT.findall(text)]
insights = [(d, sec, text) for line, sec, _, text in paras for d in INSIGHT.findall(text)]
held = decided >= DEFERRALS_FROM or any(d >= DEFERRALS_FROM for d, _, _ in insights)
if not held:
continue
# A dated insight naming an issue covers the deferrals it speaks about -- those in its own section,
# or ones it quotes -- and no other: one insight must not excuse every deferral in the record.
tracking = [(sec, text) for d, sec, text in insights if issue_refs(text, seen)]
for line, section, text in paras:
for line, section, chapter, text in paras:
m = DEFERRAL.search(text)
# A rejected option is a decision not to do it, not work put off.
if re.match(r"(?i)(options? rejected|considered options|rejected)", section):
# A rejected option is a decision not to do it, not work put off -- judged by its chapter, so an
# option under a `###` subheading of "## Considered Options" is skipped as well.
if re.match(r"(?i)(options? rejected|considered options|rejected)", chapter):
continue
if not m or issue_refs(text, seen):
continue