1919from types import SimpleNamespace
2020from unittest import mock
2121
22- from pdca_harness import brief , cli , driver , flow , leaves , queue , signoff , state
22+ from pdca_harness import act , brief , cli , driver , flow , leaves , queue , signoff , state
2323from pdca_harness .config import Config , LeafConfig
2424
2525TEMPLATES = Path (__file__ ).resolve ().parents [1 ] / "templates"
@@ -45,6 +45,7 @@ def _stub_config(root: Path) -> Config:
4545 signoff = LeafConfig (mode = "stub" , family = "claude" , interactive = True ),
4646 publisher = LeafConfig (mode = "stub" , family = "claude" , interactive = True ),
4747 act = LeafConfig (mode = "stub" , family = "claude" , interactive = True ),
48+ act_cadence = 1 , # most flow tests assert Act runs after a flow; cadence #109 tested separately
4849 )
4950
5051
@@ -1045,5 +1046,39 @@ def test_authored_brief_reads_planned(self) -> None:
10451046 self .assertEqual (state .state (d ), state .PLANNED )
10461047
10471048
1049+ class ActCadence (unittest .TestCase ):
1050+ """flow auto-runs Act only when act_cadence cycles have frozen since the last Act —
1051+ counted across flow invocations, not per-run (issue #109)."""
1052+
1053+ def setUp (self ) -> None :
1054+ self .tmp = Path (tempfile .mkdtemp ())
1055+ self .cfg = _stub_config (self .tmp )
1056+ self .cfg .act_cadence = 3
1057+
1058+ def tearDown (self ) -> None :
1059+ shutil .rmtree (self .tmp , ignore_errors = True )
1060+
1061+ def _log (self ) -> Path :
1062+ return self .cfg .process_dir / "act-log.md"
1063+
1064+ def test_act_held_below_cadence_then_fires_across_flows (self ) -> None :
1065+ # Three separate single-bundle flows: Act must NOT run until the 3rd freezes the
1066+ # third cycle (cadence 3), proving the gate persists across invocations.
1067+ for i in (1 , 2 ):
1068+ flow .flow (self .cfg , f"AC{ i } " , do_act = True , today = "2026-06-04" )
1069+ self .assertFalse (self ._log ().exists (), f"Act ran too early (after flow { i } )" )
1070+ flow .flow (self .cfg , "AC3" , do_act = True , today = "2026-06-04" )
1071+ self .assertTrue (self ._log ().exists ()) # the 3rd frozen cycle trips cadence
1072+
1073+ def test_act_resets_after_running (self ) -> None :
1074+ for i in (1 , 2 , 3 ):
1075+ flow .flow (self .cfg , f"R{ i } " , do_act = True , today = "2026-06-04" )
1076+ self .assertTrue (self ._log ().exists ())
1077+ self .assertEqual (act .cycles_since_review (self .cfg ), 0 ) # marker reset to frozen count
1078+ before = self ._log ().read_text (encoding = "utf-8" )
1079+ flow .flow (self .cfg , "R4" , do_act = True , today = "2026-06-04" ) # only 1 since → below cadence
1080+ self .assertEqual (self ._log ().read_text (encoding = "utf-8" ), before ) # no new Act entry
1081+
1082+
10481083if __name__ == "__main__" :
10491084 unittest .main ()
0 commit comments