Coverage for src/keel/flows.py: 100%

23 statements  

« prev     ^ index     » next       coverage.py v7.14.1, created at 2026-06-16 18:07 +0000

1"""Canonical command-flow registry: the ordered phases of every keel command. 

2 

3The ship command's shape is the fixed :data:`keel.model.BACKBONE` (s0–s12). The 

4other commands have their own shapes — a CI poll, an audit scan→report, a 

5work-block loop, a triage queue pass. This module is the single source of truth 

6for all of them, so any consumer (notably ``keel-visual``) can render or reason 

7about a command's structure without re-deriving it from the adapter prose. 

8 

9Each flow is an ordered tuple of :class:`Phase`. A phase ``kind`` is one of: 

10 

11* ``normal`` — an ordinary step 

12* ``gate`` — a pass/fail quality gate (e.g. the ship test gate) 

13* ``loop`` — a repeating/iterating phase (a fix loop, a per-item work block) 

14* ``merge`` — the literal merge 

15* ``report`` — a terminal summary/output phase 

16 

17Pure data: no I/O. ``ship`` reuses the backbone verbatim (its gate/loop kinds are 

18kept exactly as the rest of keel models them — gates at s8/s10, loop at s9) so 

19nothing about the ship flow changes here. 

20""" 

21 

22from __future__ import annotations 

23 

24from dataclasses import dataclass 

25 

26from .model import BACKBONE 

27 

28SCHEMA_VERSION = "keel.flows.v1" 

29 

30PHASE_KINDS = ("normal", "gate", "loop", "merge", "report") 

31 

32 

33@dataclass(frozen=True) 

34class Phase: 

35 """One phase in a command's flow.""" 

36 

37 id: str 

38 name: str 

39 kind: str = "normal" 

40 

41 

42# Ship's own gate/loop/merge semantics, keyed by backbone step id. Kept identical 

43# to how the rest of keel (and keel-visual) already treats the backbone. 

44_SHIP_KINDS = {"s8": "gate", "s9": "loop", "s10": "merge"} 

45 

46 

47def _ship_flow() -> tuple[Phase, ...]: 

48 return tuple( 

49 Phase(step.id, step.name, _SHIP_KINDS.get(step.id, "normal")) for step in BACKBONE 

50 ) 

51 

52 

53def _flow(*phases: tuple[str, str, str]) -> tuple[Phase, ...]: 

54 return tuple(Phase(pid, name, kind) for pid, name, kind in phases) 

55 

56 

57#: The canonical flow for each keel command, in execution order. 

58FLOWS: dict[str, tuple[Phase, ...]] = { 

59 "ship": _ship_flow(), 

60 "ci-check": _flow( 

61 ("poll", "poll", "normal"), 

62 ("report", "report", "report"), 

63 ), 

64 "coverage": _flow( 

65 ("orient", "orient", "normal"), 

66 ("areas", "areas", "normal"), 

67 ("baseline", "baseline", "normal"), 

68 ("head", "head", "normal"), 

69 ("delta", "delta", "normal"), 

70 ("hotspots", "hotspots", "normal"), 

71 ("post", "post", "report"), 

72 ), 

73 "deps-audit": _flow( 

74 ("orient", "orient", "normal"), 

75 ("tracking", "tracking", "normal"), 

76 ("scan", "scan", "normal"), 

77 ("drift", "drift", "normal"), 

78 ("report", "report", "normal"), 

79 ("post", "post", "report"), 

80 ), 

81 "flake-audit": _flow( 

82 ("orient", "orient", "normal"), 

83 ("evidence", "evidence", "normal"), 

84 ("aggregate", "aggregate", "normal"), 

85 ("classify", "classify", "normal"), 

86 ("dedupe", "dedupe", "normal"), 

87 ("report", "report", "normal"), 

88 ("open", "open", "report"), 

89 ), 

90 "implement": _flow( 

91 ("config", "config", "normal"), 

92 ("fetch", "fetch", "normal"), 

93 ("branch", "branch", "normal"), 

94 ("resolve", "resolve", "normal"), 

95 ("codename", "codename", "normal"), 

96 ("delegate", "delegate", "normal"), 

97 ("report", "report", "report"), 

98 ), 

99 "morning": _flow( 

100 ("config", "config", "normal"), 

101 ("deferrals", "deferrals", "normal"), 

102 ("shipped", "shipped", "normal"), 

103 ("health", "health", "normal"), 

104 ("enrichment", "enrichment", "normal"), 

105 ("window", "window", "normal"), 

106 ("output", "output", "report"), 

107 ), 

108 "overnight": _flow( 

109 ("config", "config", "normal"), 

110 ("preflight", "preflight", "normal"), 

111 ("queue", "queue", "normal"), 

112 ("loop", "work block", "loop"), 

113 ("report", "report", "report"), 

114 ), 

115 "pr-loop": _flow( 

116 ("config", "config", "normal"), 

117 ("find", "find", "normal"), 

118 ("open", "open", "normal"), 

119 ("read", "read", "normal"), 

120 ("categorize", "categorize", "normal"), 

121 ("fix", "fix", "loop"), 

122 ("review", "review", "normal"), 

123 ("post", "post", "normal"), 

124 ("recheck", "recheck", "normal"), 

125 ("collect", "collect", "normal"), 

126 ("handoff", "handoff", "report"), 

127 ), 

128 "regression": _flow( 

129 ("orient", "orient", "normal"), 

130 ("preflight", "preflight", "normal"), 

131 ("fanout", "fan out", "loop"), 

132 ("aggregate", "aggregate", "normal"), 

133 ("dedupe", "dedupe", "normal"), 

134 ("open", "open", "normal"), 

135 ("report", "report", "report"), 

136 ), 

137 "review-all-day": _flow( 

138 ("config", "config", "normal"), 

139 ("parse", "parse", "normal"), 

140 ("commits", "commits", "normal"), 

141 ("decide", "decide", "normal"), 

142 ("classify", "classify", "loop"), 

143 ("open", "open", "normal"), 

144 ("report", "report", "report"), 

145 ), 

146 "review-cycle": _flow( 

147 ("config", "config", "normal"), 

148 ("validate", "validate", "normal"), 

149 ("loop", "loop", "loop"), 

150 ("reviewers", "reviewers", "normal"), 

151 ("post", "post", "normal"), 

152 ("fixloop", "fix loop", "loop"), 

153 ("report", "report", "report"), 

154 ), 

155 "stale-prs": _flow( 

156 ("orient", "orient", "normal"), 

157 ("list", "list", "normal"), 

158 ("classify", "classify", "normal"), 

159 ("triage", "triage", "normal"), 

160 ("post", "post", "normal"), 

161 ("rebase", "rebase", "normal"), 

162 ("summary", "summary", "report"), 

163 ), 

164 "triage": _flow( 

165 ("config", "config", "normal"), 

166 ("find", "find", "normal"), 

167 ("tier", "tier", "normal"), 

168 ("classify", "classify", "loop"), 

169 ("rank", "rank", "normal"), 

170 ("apply", "apply", "normal"), 

171 ("summary", "summary", "report"), 

172 ), 

173 "work-block": _flow( 

174 ("config", "config", "normal"), 

175 ("snapshot", "snapshot", "normal"), 

176 ("loop", "work block", "loop"), 

177 ("report", "report", "report"), 

178 ), 

179 "wrap": _flow( 

180 ("config", "config", "normal"), 

181 ("sanity", "sanity", "normal"), 

182 ("gates", "gates", "gate"), 

183 ("commit", "commit", "normal"), 

184 ("push", "push", "normal"), 

185 ("recap", "recap", "report"), 

186 ), 

187} 

188 

189#: The default flow when a command is unknown — the ship backbone. 

190DEFAULT_COMMAND = "ship" 

191 

192 

193def command_names() -> tuple[str, ...]: 

194 """Return every known command name, in a stable sorted order.""" 

195 return tuple(sorted(FLOWS)) 

196 

197 

198def flow_for(command: str) -> tuple[Phase, ...]: 

199 """Return the ordered phases for ``command`` (the ship backbone if unknown).""" 

200 return FLOWS.get(command, FLOWS[DEFAULT_COMMAND]) 

201 

202 

203def is_known(command: str) -> bool: 

204 """Return whether ``command`` has a dedicated flow (vs falling back to ship).""" 

205 return command in FLOWS