Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ec615a656 | ||
|
|
0fdfe195e7 | ||
|
|
71d2d63a36 | ||
|
|
0873b521e3 |
No files matched your search
@@ -0,0 +1 @@
|
||||
probe 1791291272
|
||||
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.99.5**
|
||||
**Current release: v0.99.6**
|
||||
|
||||
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.99.5"
|
||||
version = "0.99.6"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.99.5"
|
||||
__version__ = "0.99.6"
|
||||
|
||||
# One build identity (handoff 2026-09-10 §4): the semantic version above is
|
||||
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
|
||||
|
||||
@@ -233,7 +233,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.99.5"' in rune_version,
|
||||
'__version__ = "0.99.6"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
@@ -36,6 +36,7 @@ from typing import Any
|
||||
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from rune import structural_store as ss
|
||||
@@ -421,7 +422,8 @@ class ConstructionInterface:
|
||||
errors.append({"error": Errors.INSUFFICIENT_EVIDENCE, "detail": trace["rationale"][:200]})
|
||||
contract = None
|
||||
if generate and trace["resolution"] not in ("INSUFFICIENT_EVIDENCE",):
|
||||
cid = contract_id or f"BUILDER-{ss.now()}-{self.builder}"
|
||||
cid = (contract_id
|
||||
or f"BUILDER-{ss.now()}-{uuid.uuid4().hex[:6]}-{self.builder}")
|
||||
cd.generate_contract(
|
||||
self.scaffold,
|
||||
doc,
|
||||
@@ -768,21 +770,62 @@ class ConstructionInterface:
|
||||
material_closure = self._auto_material_change(
|
||||
scope_files or [],
|
||||
note=f"contract {contract_id} closed (material change)")
|
||||
continuation = self._programme_continuation(contract_id)
|
||||
state = {
|
||||
"phase": "closed",
|
||||
"material_change_closure": material_closure,
|
||||
"contract_quality": {
|
||||
k: q[k]
|
||||
for k in ("correctly_predicted", "expected_new_observed", "genuinely_unexpected_new", "scope_check")
|
||||
if k in q
|
||||
},
|
||||
}
|
||||
if continuation is not None:
|
||||
state["programme_continuation"] = continuation
|
||||
return _envelope(
|
||||
"close",
|
||||
success=True,
|
||||
ids={"contract_id": contract_id},
|
||||
state={
|
||||
"phase": "closed",
|
||||
"material_change_closure": material_closure,
|
||||
"contract_quality": {
|
||||
k: q[k]
|
||||
for k in ("correctly_predicted", "expected_new_observed", "genuinely_unexpected_new", "scope_check")
|
||||
if k in q
|
||||
},
|
||||
},
|
||||
state=state,
|
||||
)
|
||||
|
||||
def _programme_continuation(self, contract_id: str) -> dict | None:
|
||||
"""T5.15 slice 3 (the programme_continuation wire): after a legal
|
||||
close, any persisted Programme stage bound to this contract is
|
||||
marked COMPLETE and the continuation canary's verdict rides the
|
||||
close envelope — 'task done' ≠ 'programme done', and the next
|
||||
eligible stage is SURFACED, never guessed. No programme bound →
|
||||
None (no continuation block in the envelope). Failures degrade:
|
||||
a broken programme ledger never fails a real closure."""
|
||||
try:
|
||||
from pathlib import Path as _Path
|
||||
|
||||
from rune import programme_continuation as pc
|
||||
|
||||
base = _Path(self.scaffold.current).parent / pc.DEFAULT_PROGRAMME_AREA
|
||||
if not base.is_dir():
|
||||
return None
|
||||
for prog_file in sorted(base.glob("*.json")):
|
||||
prog = pc.Programme.load(base, prog_file.stem)
|
||||
if prog is None:
|
||||
continue
|
||||
stage = next((s for s in prog.stages
|
||||
if s.stage_id == contract_id
|
||||
and s.state != pc.COMPLETE), None)
|
||||
if stage is None:
|
||||
continue
|
||||
stage.state = pc.COMPLETE
|
||||
stage.result["closed_at_contract"] = contract_id
|
||||
prog.save(base)
|
||||
return {
|
||||
"programme_id": prog.programme_id,
|
||||
"stage_marked_complete": contract_id,
|
||||
"canary": prog.continuation_canary(),
|
||||
}
|
||||
except Exception as exc: # noqa: BLE001 — ledger failure never fails a closure
|
||||
return {"error": f"programme continuation degraded: {exc}"[:200]}
|
||||
return None
|
||||
|
||||
def _auto_material_change(self, scope_files: list[str], *,
|
||||
note: str) -> dict:
|
||||
"""T21.4 §21.4H (rule 19Q.2): governed construction closes → the
|
||||
|
||||
@@ -8,7 +8,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.99.5
|
||||
Version: v0.99.6
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -1948,7 +1948,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.99.5 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.99.6 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -671,3 +672,86 @@ def test_15f_transition_to_building_runs_no_guards(tmp_path):
|
||||
iface.scaffold.commit(doc, what="attach guard")
|
||||
env = iface.transition(cid, "approved") # NOT a completion edge
|
||||
assert env["success"]
|
||||
|
||||
|
||||
# ── T5.15 slice 3: programme_continuation wired into close() ────────────────
|
||||
|
||||
|
||||
def _seed_programme(iface, programme_id, stage_contract_ids, tmp_path):
|
||||
"""Persist a real Programme bound to the interface's scaffold store."""
|
||||
from rune import programme_continuation as pc
|
||||
|
||||
base = Path(iface.scaffold.current).parent / pc.DEFAULT_PROGRAMME_AREA
|
||||
prog = pc.Programme(programme_id=programme_id, objective="wave test")
|
||||
for i, cid in enumerate(stage_contract_ids):
|
||||
prog.stages.append(pc.ProgrammeStage(
|
||||
stage_id=cid, objective=f"stage {i}", depends_on=[]))
|
||||
prog.save(base)
|
||||
return prog, base
|
||||
|
||||
|
||||
def test_close_marks_programme_stage_complete_and_surfaces_continuation(tmp_path):
|
||||
"""Closing a contract that is a stage of a persisted Programme marks
|
||||
that stage COMPLETE and the envelope carries the continuation canary —
|
||||
the programme's next eligible stage is surfaced, not guessed."""
|
||||
from rune import programme_continuation as pc
|
||||
|
||||
iface = _iface(tmp_path)
|
||||
env1 = _submit(iface, objective="wave stage one")
|
||||
cid1 = env1["ids"]["contract_id"]
|
||||
env2 = _submit(iface, objective="wave stage two")
|
||||
cid2 = env2["ids"]["contract_id"]
|
||||
# drive both through to verifying so close is legal
|
||||
for cid in (cid1, cid2):
|
||||
iface.transition(cid, "approved")
|
||||
iface.transition(cid, "building")
|
||||
iface.transition(cid, "verifying")
|
||||
prog, base = _seed_programme(iface, "wave-x", [cid1, cid2], tmp_path)
|
||||
env = iface.close(cid1, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
|
||||
assert env["success"], env["errors"]
|
||||
cont = env["state"].get("programme_continuation")
|
||||
assert cont, "close envelope must carry the programme continuation"
|
||||
assert cont["programme_id"] == "wave-x"
|
||||
assert cont["stage_marked_complete"] == cid1
|
||||
reloaded = pc.Programme.load(base, "wave-x")
|
||||
stage1 = next(s for s in reloaded.stages if s.stage_id == cid1)
|
||||
assert stage1.state == pc.COMPLETE
|
||||
assert cont["canary"]["verdict"] == "PROGRAMME_OPEN"
|
||||
assert cont["canary"]["next_action"] == ("start", cid2)
|
||||
|
||||
|
||||
def test_close_without_programme_has_no_continuation_block(tmp_path):
|
||||
iface = _iface(tmp_path)
|
||||
env = _submit(iface)
|
||||
cid = env["ids"]["contract_id"]
|
||||
iface.transition(cid, "approved")
|
||||
iface.transition(cid, "building")
|
||||
iface.transition(cid, "verifying")
|
||||
env = iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
|
||||
assert env["success"]
|
||||
assert "programme_continuation" not in env["state"]
|
||||
|
||||
|
||||
def test_close_programme_completion_verdict_surfaces(tmp_path):
|
||||
"""Closing the LAST stage yields PROGRAMME_COMPLETE — the canary says
|
||||
stop, the runner never guesses."""
|
||||
iface = _iface(tmp_path)
|
||||
env = _submit(iface)
|
||||
cid = env["ids"]["contract_id"]
|
||||
iface.transition(cid, "approved")
|
||||
iface.transition(cid, "building")
|
||||
iface.transition(cid, "verifying")
|
||||
prog, base = _seed_programme(iface, "solo", [cid], tmp_path)
|
||||
env = iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
|
||||
cont = env["state"]["programme_continuation"]
|
||||
assert cont["canary"]["verdict"] == "PROGRAMME_COMPLETE"
|
||||
|
||||
|
||||
def test_two_submits_same_second_get_distinct_contract_ids(tmp_path):
|
||||
"""Second-granularity timestamps collide when two needs are filed in
|
||||
the same second (real builder-pair scenario) — ids must be distinct."""
|
||||
iface = _iface(tmp_path)
|
||||
e1 = _submit(iface, objective="wave stage one")
|
||||
e2 = _submit(iface, objective="wave stage two")
|
||||
assert e1["success"] and e2["success"], (e1["errors"], e2["errors"])
|
||||
assert e1["ids"]["contract_id"] != e2["ids"]["contract_id"]
|
||||
Reference in new issue
Block a user