Compare commits

...
Author SHA1 Message Date
conductor 8989073cad Merge pull request 'chore(release): auto-bump version to 0.54.1 (post-merge automation)' (#219) from chore/auto-bump-0.54.1 into master
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 11s
CI Pipeline / canary (push) Successful in 40s
CI Pipeline / unit-tests (push) Successful in 55s
CI Pipeline / lint (push) Successful in 49s
CI Pipeline / gitea-offline (push) Successful in 25s
CI Pipeline / type-check (push) Successful in 39s
CI Pipeline / required-checks (push) Skipped
2026-09-08 00:13:42 +01:00
Nick 7c265e393f merge master (required-checks dispatch fix travels with the bump branch)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / canary (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 38s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 33s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 6s
CI Pipeline / documentation (pull_request) Successful in 9s
CI Pipeline / lint (pull_request) Successful in 52s
CI Pipeline / unit-tests (pull_request) Successful in 49s
CI Pipeline / canary (pull_request) Successful in 37s
Spec gate (headless) / spec-gate (pull_request) Skipped
CI Pipeline / gitea-offline (pull_request) Successful in 34s
CI Pipeline / type-check (pull_request) Successful in 55s
CI Pipeline / required-checks (pull_request) Successful in 0s
Auto-version and Release / release (pull_request) Successful in 15s
2026-09-08 00:10:37 +01:00
conductor 53e6b70a22 Merge pull request 'fix(release): pyproject.toml joins the bump surfaces (wheel version lag)' (#220) from research-structuralization into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / lint (push) Successful in 31s
CI Pipeline / canary (push) Successful in 25s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / unit-tests (push) Successful in 33s
CI Pipeline / required-checks (push) Skipped
2026-09-08 00:09:21 +01:00
Nick 3eb7ecd7cb merge master (behind-base)
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 26s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 35s
CI Pipeline / type-check (push) Successful in 22s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / canary (pull_request) Successful in 28s
CI Pipeline / unit-tests (pull_request) Successful in 34s
CI Pipeline / lint (pull_request) Successful in 26s
CI Pipeline / type-check (pull_request) Successful in 23s
Spec gate (headless) / spec-gate (pull_request) Successful in 5s
CI Pipeline / gitea-offline (pull_request) Successful in 20s
CI Pipeline / required-checks (pull_request) Successful in 0s
Auto-version and Release / release (pull_request) Failing after 13s
2026-09-08 00:07:22 +01:00
Nick 64e7380013 fix(pipeline): required-checks also runs on workflow_dispatch — auto-bump PRs created via API get no pull_request events; without this the required context never publishes and the bump PR cannot merge (#219 observed live)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / unit-tests (push) Successful in 35s
CI Pipeline / type-check (push) Successful in 22s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / canary (pull_request) Successful in 24s
CI Pipeline / lint (pull_request) Successful in 27s
Spec gate (headless) / spec-gate (pull_request) Successful in 6s
CI Pipeline / unit-tests (pull_request) Successful in 35s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / type-check (pull_request) Successful in 23s
CI Pipeline / required-checks (pull_request) Successful in 0s
2026-09-07 23:54:54 +01:00
Nick c337681f77 fix(release): auto-bump dispatches CI itself — API-created bump PRs get no pull_request events on this Gitea (#219 stuck at zero statuses, observed live)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / unit-tests (push) Successful in 35s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 3s
CI Pipeline / lint (pull_request) Successful in 27s
CI Pipeline / canary (pull_request) Successful in 24s
Spec gate (headless) / spec-gate (pull_request) Successful in 6s
CI Pipeline / unit-tests (pull_request) Successful in 35s
CI Pipeline / type-check (pull_request) Successful in 22s
CI Pipeline / gitea-offline (pull_request) Successful in 14s
CI Pipeline / required-checks (pull_request) Successful in 1s
2026-09-07 23:42:03 +01:00
Nick 0856bee01a fix(release): pyproject.toml joins the bump surfaces — wheel version was lagging __init__ forever (0.49.0 vs 0.54.0 divergence)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / type-check (push) Successful in 24s
CI Pipeline / unit-tests (push) Successful in 36s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 12s
CI Pipeline / canary (pull_request) Successful in 24s
CI Pipeline / unit-tests (pull_request) Successful in 33s
CI Pipeline / lint (pull_request) Successful in 25s
CI Pipeline / documentation (pull_request) Successful in 7s
Spec gate (headless) / spec-gate (pull_request) Successful in 6s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / type-check (pull_request) Successful in 23s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
2026-09-07 23:31:45 +01:00
Gitea Actions 9e2653f64d chore(release): auto-bump version to 0.54.1 (post-merge automation) 2026-09-07 23:26:18 +01:00
Gitea Actions 746c302473 chore(release): auto-bump version to 0.54.1 (post-merge automation) 2026-09-07 23:26:17 +01:00
Gitea Actions 51882ef08c chore(release): auto-bump version to 0.54.1 (post-merge automation) 2026-09-07 23:26:15 +01:00
conductor cbbb4f3ee2 Merge pull request 'fix(release): auto-version resurrected — semver logic extracted, YAML-parse-death discharged (red-first)' (#218) from research-structuralization into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 27s
CI Pipeline / lint (push) Successful in 30s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / unit-tests (push) Successful in 36s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Skipped
2026-09-07 23:26:01 +01:00
Nick d4283cc3d2 style: ruff format on new files (lint gate)
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 2s
CI Pipeline / lint (push) Successful in 29s
CI Pipeline / canary (push) Successful in 29s
CI Pipeline / type-check (push) Successful in 25s
CI Pipeline / unit-tests (push) Successful in 36s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Skipped
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / lint (pull_request) Successful in 27s
CI Pipeline / canary (pull_request) Successful in 31s
CI Pipeline / unit-tests (pull_request) Successful in 35s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
CI Pipeline / type-check (pull_request) Successful in 22s
Spec gate (headless) / spec-gate (pull_request) Successful in 7s
CI Pipeline / required-checks (pull_request) Successful in 0s
Auto-version and Release / release (pull_request) Failing after 15m29s
2026-09-07 23:24:06 +01:00
Nick 2a9d6c8bc6 test: FakeForge serves /pulls for open-PR filter (fixture completeness) 2026-09-07 23:23:53 +01:00
Nick 3065d11384 fix(release): auto-version resurrected — semver logic extracted to scripts/semver_bump.py; YAML-parse-death from #190 discharge (red-first)
Root cause of missing version bumps since 2026-09-06 15:01: #190 pasted
inline python into a double-quoted YAML scalar (unescaped quotes, column-0
lines) making auto-version.yml UNPARSEABLE. Gitea rejects the workflow
wholesale — dispatch 500, pull_request:closed triggers never fire. Six
merges tonight + all merges since #190: zero bumps.

- scripts/semver_bump.py: testable next_version() (0.x semver conventions)
- workflow repaired, parses clean
- version-behind-tag now a LOUD ::warning anomaly (#192), never silent skip
- NEW GATE: tests/test_semver_bump.py validates ALL workflow files parse —
  the missing lint that let this rot silently (7 workflows covered)
2026-09-07 23:23:24 +01:00
conductor 52b9773b6d Merge pull request 'Research structuralization: dispatched headless research stage + idempotent backlog sweep (#204 second slice)' (#217) from research-structuralization into master
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 28s
CI Pipeline / canary (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 34s
CI Pipeline / type-check (push) Successful in 23s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Skipped
2026-09-07 23:10:49 +01:00
9 changed files with 194 additions and 30 deletions

No files matched your search

+19 -23
View File
@@ -43,30 +43,16 @@ jobs:
exit 0
fi
if [ "$LATEST" != "$RELEASE_VERSION" ]; then
echo "New version $RELEASE_VERSION (last tag v$LATEST) — bump rode the PR"
exit 0
if [ "$(printf '%s\n%s\n' "$LATEST" "$RELEASE_VERSION" | sort -V | head -1)" = "$RELEASE_VERSION" ]; then
echo "::warning::VERSION-BEHIND-TAG ANOMALY (#192): master=$RELEASE_VERSION < latest tag v$LATEST — bumping from master version; reconcile the tag lineage"
else
echo "Version $RELEASE_VERSION > last tag v$LATEST — bump rode the PR"
exit 0
fi
fi
NEW=$(python3 -c "
import os, re
version = os.environ['RELEASE_VERSION']
title = os.environ.get('PR_TITLE', '')
# Conventional-commits semver: feat! / BREAKING CHANGE -> major, feat -> minor, else patch
if re.match(r'^\s*\w+(\([^)]*\))?!:', title) or 'BREAKING CHANGE' in title:
kind = 'major'
elif re.match(r'^\s*feat(\([^)]*\))?:', title):
kind = 'minor'
else:
kind = 'patch'
major, minor, patch = version.split('.')
# 0.x semver convention: breaking changes bump the MINOR until 1.0.0
result = (
f'{major}.{int(minor)+1}.0' if kind == 'major' and int(major) == 0
else f'{int(major)+1}.0.0' if kind == 'major'
else f'{major}.{int(minor)+1}.0' if kind == 'minor'
else f'{major}.{minor}.{int(patch)+1}'
)
print(result)
")
# semver from PR title — logic lives in scripts/semver_bump.py
# (#190 lesson: inline python in YAML scalars rots unlinted)
NEW=$(python3 scripts/semver_bump.py "$RELEASE_VERSION" "$PR_TITLE")
export NEW_VERSION="$NEW"
echo "Auto-bump: $RELEASE_VERSION -> $NEW (via protected-branch-safe bump PR)"
python3 - <<'PYBUMP'
@@ -116,6 +102,9 @@ print(result)
"README.md": [
(f"v{VERSION}", f"v{NEW}"),
],
"pyproject.toml": [
(f'version = "{VERSION}"', f'version = "{NEW}"'),
],
}
for path, pairs in surfaces.items():
status, cur = api(f"/contents/{path}?ref={BRANCH}")
@@ -149,6 +138,13 @@ print(result)
head_sha = pr["head"]["sha"]
print(f" bump PR #{pr_number} head {head_sha[:12]}")
# 3b. DISPATCH CI on the bump branch — API-created PRs do not receive
# pull_request events on this Gitea (observed live: #219 sat with zero
# statuses). workflow_dispatch runs ci.yml on the branch ref; the
# required-checks statuses attach to the head sha the loop polls.
disp = api("/actions/workflows/ci.yml/dispatches", "POST", {"ref": BRANCH})
print(f" CI dispatched on {BRANCH}: {disp[0]}")
# 4. wait for the protected check on the bump PR (same gate as everything else)
deadline = time.time() + 900 # 15 min ceiling
state = None
+5 -1
View File
@@ -220,7 +220,11 @@ jobs:
bash -n scripts/gitea-backup.sh scripts/gitea-restore-verify.sh
required-checks:
if: gitea.event_name == 'pull_request'
# pull_request AND workflow_dispatch: the auto-bump path creates its PR via
# API (no pull_request events on this Gitea) and dispatches ci.yml on the
# bump branch — without dispatch, required-checks never publishes and the
# bump PR can never merge (#219, observed live).
if: gitea.event_name == 'pull_request' || gitea.event_name == 'workflow_dispatch'
runs-on: rune-ci-desktop
# #148: type-check MUST gate the protected status — a red type-check job
# previously left the protected merge gate green (dependency-chain hole).
+23 -1
View File
@@ -87,13 +87,33 @@ def has_research_round(comments: list[dict]) -> bool:
return False
def open_pr_issue_refs(api) -> set[int]:
"""Issue numbers referenced by OPEN PRs (closes/fixes/resolves #N in the
body) — those issues are in-flight; research is premature."""
refs: set[int] = set()
page = 1
ref_re = re.compile(r"(?:closes|fixes|resolves)\s+#(\d+)", re.IGNORECASE)
while True:
status, batch = api.get(f"/pulls?state=open&limit=50&page={page}")
if not isinstance(batch, list):
break
for pr in batch:
for m in ref_re.finditer(pr.get("body") or ""):
refs.add(int(m.group(1)))
if len(batch) < 50:
return refs
page += 1
def compute_targets(issues: list[dict], api) -> list[dict]:
"""Eligible issues for research-pending labeling.
`api` is a ForgeAdapter-like object exposing .get(path) -> (status, body).
Gate semantics: open, not blocked, not already labeled, no existing
research round, all deps server-verified closed (missing = unsatisfied).
research round, all deps server-verified closed (missing = unsatisfied),
and not referenced by an open PR (in-flight work doesn't need research).
"""
in_flight = open_pr_issue_refs(api)
targets = []
for i in issues:
if i.get("pull_request") or i.get("state") != "open":
@@ -101,6 +121,8 @@ def compute_targets(issues: list[dict], api) -> list[dict]:
labels = {l["name"] for l in i.get("labels", [])}
if "blocked" in labels or "research-pending" in labels:
continue
if i["number"] in in_flight:
continue
deps = [int(m) for m in DEP_RE.findall(i.get("body") or "")]
unsatisfied = False
for d in deps:
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""semver_bump.py — compute the next version from a merged PR title.
Extracted from auto-version.yml after the inline python (pasted into a YAML
double-quoted scalar with unescaped quotes and column-0 lines) made the whole
workflow unparseable — silently disabling every post-merge version bump since
#190 (2026-09-06 15:01). Bug-fix contract: logic in a testable script, not a
heredoc nobody can lint.
Conventions (0.x semver until 1.0.0):
- breaking (feat!:/fix!:/BREAKING CHANGE) -> minor bump (major+1 . 0 . 0 only at 1.x)
- feat:/feat(scope): -> minor bump (x . minor+1 . 0)
- anything else -> patch bump
"""
from __future__ import annotations
import re
import sys
BREAKING_RE = re.compile(r"^\s*\w+(\([^)]*\))?!:")
FEAT_RE = re.compile(r"^\s*feat(\([^)]*\))?")
def next_version(version: str, title: str) -> str:
if BREAKING_RE.match(title) or "BREAKING CHANGE" in title:
kind = "major"
elif FEAT_RE.match(title):
kind = "minor"
else:
kind = "patch"
major, minor, patch = (int(x) for x in version.split("."))
if kind == "major":
return f"{major}.{minor + 1}.0" if major == 0 else f"{major + 1}.0.0"
if kind == "minor":
return f"{major}.{minor + 1}.0"
return f"{major}.{minor}.{patch + 1}"
def main_with(argv: list[str]) -> int:
if len(argv) != 3:
print("usage: semver_bump.py <current-version> <pr-title>", file=sys.stderr)
return 2
print(next_version(argv[1], argv[2]))
return 0
if __name__ == "__main__":
sys.exit(main_with(sys.argv))
+1 -1
View File
@@ -3,4 +3,4 @@
This is the main entry point for the Rune program.
"""
__version__ = "0.54.0"
__version__ = "0.54.1"
+1 -1
View File
@@ -252,7 +252,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.54.0"' in rune_version,
'__version__ = "0.54.1"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+2 -2
View File
@@ -7,7 +7,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.54.0
Version: v0.54.1
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1923,7 +1923,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.54.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.54.1 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+13 -1
View File
@@ -46,14 +46,17 @@ RESEARCH_COMMENT = "## INWARD\n... ## OUTWARD\n... ## INNOVATION\n... ## SYNTHES
class FakeForge:
"""Simulates the SERVER contract: issues list + per-issue fetch + comments."""
def __init__(self, issues, comments=None):
def __init__(self, issues, comments=None, open_prs=None):
self.issues = issues
self.open_prs = open_prs or []
self.comments = comments or {} # issue_number -> [comment bodies]
self.added_labels: list[tuple[int, str]] = []
def get(self, path):
if path == "/issues?state=open&type=issues&limit=50&page=1":
return 200, [i for i in self.issues if not i.get("pull_request")]
if path.startswith("/pulls?state=open"):
return 200, self.open_prs
if path.startswith("/issues/") and path.endswith("/comments"):
n = int(path.split("/")[2])
return 200, self.comments.get(n, [])
@@ -162,3 +165,12 @@ def test_main_apply_writes(forge, monkeypatch, capsys):
)
assert rc == 0
assert forge.added_labels == [(23, "research-pending")]
def test_skips_issues_with_open_pr(forge, monkeypatch):
"""In-flight issues (open PR references them) are not research targets —
research is premature while implementation is already under review."""
forge.issues = [_issue(30), _issue(31)]
monkeypatch.setattr(rs, "open_pr_issue_refs", lambda api: {30})
targets = rs.compute_targets(forge.issues, forge)
assert [t["number"] for t in targets] == [31]
+81
View File
@@ -0,0 +1,81 @@
"""Red-first tests for scripts/semver_bump.py — discharge of the auto-version
outage (#190 made the workflow YAML unparseable; no bump ran since 2026-09-06).
Also freezes the #192 designed anomaly: version-behind-tag must be LOUD,
never a silent skip (the workflow's old `exit 0` on LATEST != RELEASE_VERSION
silently skipped every bump while tags ran ahead of master during testing).
"""
from __future__ import annotations
import importlib.util
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
SPEC = importlib.util.spec_from_file_location("semver_bump", ROOT / "scripts" / "semver_bump.py")
sb = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(sb)
def test_fix_bumps_patch():
assert sb.next_version("0.54.0", "fix(pipeline): merge-robot auth") == "0.54.1"
def test_feat_bumps_minor():
assert sb.next_version("0.54.0", "feat(pipeline): research stage") == "0.55.0"
def test_breaking_bumps_minor_at_0x():
assert sb.next_version("0.54.0", "feat(pipeline)!: redesign gates") == "0.55.0"
def test_breaking_bumps_major_at_1x():
assert sb.next_version("1.2.3", "feat!: redesign gates") == "2.0.0"
def test_breaking_change_keyword():
assert sb.next_version("0.54.0", "chore: stuff\n\nBREAKING CHANGE: api") == "0.55.0"
def test_chore_bumps_patch():
assert sb.next_version("0.54.0", "chore(release): auto-bump") == "0.54.1"
def test_scoped_feat_with_parenthetical():
assert sb.next_version("0.54.0", "feat(memory): hippocampal index") == "0.55.0"
def test_version_behind_tag_is_anomaly_not_skip():
"""#192: master version behind latest tag = loud anomaly. The workflow
must pass the version through to the bump logic (bump from CURRENT master
version), never silently exit because tags diverged."""
# 0.49.0 on master, tags at 0.54.0: bump STILL computes from 0.49.0 ->
# the anomaly is surfaced by the loud log line + the bump PR itself.
assert sb.next_version("0.49.0", "fix: thing") == "0.49.1"
@pytest.mark.parametrize(
"workflow",
[
".gitea/workflows/auto-version.yml",
".gitea/workflows/merge-robot.yml",
".gitea/workflows/ci.yml",
".gitea/workflows/pipeline-gate.yml",
".gitea/workflows/research-stage-headless.yml",
".gitea/workflows/research-backlog-sweep.yml",
".gitea/workflows/spec-gate-headless.yml",
],
)
def test_all_workflows_are_valid_yaml(workflow):
"""The missing gate that let #190 rot silently: every workflow file on
master MUST parse. This test IS the lint gate."""
import yaml
src = (ROOT / workflow).read_text()
# Gitea parses with its own loader; 'on' must survive — use BaseLoader-free
# safe_load and accept the YAML-1.1 on→bool quirk (Gitea normalizes too).
data = yaml.safe_load(src)
assert isinstance(data, dict), f"{workflow} must parse to a mapping"
assert any(k in data for k in ("on", True)), f"{workflow} missing trigger block"