froid-loop 0.11.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. froid_loop/__init__.py +11 -0
  2. froid_loop/__main__.py +12 -0
  3. froid_loop/adapters/__init__.py +3 -0
  4. froid_loop/adapters/base.py +254 -0
  5. froid_loop/adapters/entrypoints.py +63 -0
  6. froid_loop/adapters/env_fault.py +290 -0
  7. froid_loop/adapters/generic.py +2013 -0
  8. froid_loop/adapters/mock.py +49 -0
  9. froid_loop/adapters/multiplexer.py +914 -0
  10. froid_loop/adapters/opencode_http.py +1687 -0
  11. froid_loop/adapters/profile.py +650 -0
  12. froid_loop/adapters/psmux_backend.py +1428 -0
  13. froid_loop/adapters/registry.py +322 -0
  14. froid_loop/adapters/tmux_backend.py +35 -0
  15. froid_loop/adapters/tmux_base.py +630 -0
  16. froid_loop/checks.py +187 -0
  17. froid_loop/cli.py +5041 -0
  18. froid_loop/data/__init__.py +0 -0
  19. froid_loop/data/froid_loop_hook.py +228 -0
  20. froid_loop/data/froid_loop_probe_hook.py +88 -0
  21. froid_loop/data/plugins/example/plugin.toml +21 -0
  22. froid_loop/data/plugins/tea/plugin.toml +184 -0
  23. froid_loop/data/plugins/tea/tea_plugin.py +258 -0
  24. froid_loop/data/plugins/unity/plugin.toml +140 -0
  25. froid_loop/data/plugins/unity/unity_assets/FroidLoop.Unity.Editor.asmdef +16 -0
  26. froid_loop/data/plugins/unity/unity_assets/FroidLoop.Unity.Editor.asmdef.meta +7 -0
  27. froid_loop/data/plugins/unity/unity_assets/SceneAutoSaveGuard.cs +221 -0
  28. froid_loop/data/plugins/unity/unity_assets/SceneAutoSaveGuard.cs.meta +11 -0
  29. froid_loop/data/plugins/unity/unity_assets/_folders/Editor.meta +8 -0
  30. froid_loop/data/plugins/unity/unity_assets/_folders/FroidLoop.meta +8 -0
  31. froid_loop/data/plugins/unity/unity_cleanup.py +125 -0
  32. froid_loop/data/plugins/unity/unity_dialog_probe.py +239 -0
  33. froid_loop/data/plugins/unity/unity_facts.md +17 -0
  34. froid_loop/data/plugins/unity/unity_plugin.py +415 -0
  35. froid_loop/data/plugins/unity/unity_quiesce.py +234 -0
  36. froid_loop/data/plugins/unity/unity_ready.py +230 -0
  37. froid_loop/data/plugins/unity/unity_seed_assets.py +298 -0
  38. froid_loop/data/plugins/unity/unity_setup.py +551 -0
  39. froid_loop/data/plugins/unity/unity_teardown.py +362 -0
  40. froid_loop/data/profiles/antigravity.toml +52 -0
  41. froid_loop/data/profiles/claude.toml +85 -0
  42. froid_loop/data/profiles/codex.toml +22 -0
  43. froid_loop/data/profiles/copilot.toml +52 -0
  44. froid_loop/data/profiles/gemini.toml +26 -0
  45. froid_loop/data/profiles/opencode.toml +54 -0
  46. froid_loop/data/settings/core.toml +458 -0
  47. froid_loop/data/skills/README.md +93 -0
  48. froid_loop/data/skills/froid-loop-resolve/SKILL.md +288 -0
  49. froid_loop/data/skills/froid-loop-setup/SKILL.md +161 -0
  50. froid_loop/data/skills/froid-loop-setup/assets/module-help.csv +3 -0
  51. froid_loop/data/skills/froid-loop-setup/assets/module.yaml +19 -0
  52. froid_loop/data/skills/froid-loop-sweep/SKILL.md +100 -0
  53. froid_loop/data/skills/froid-loop-sweep/automation-mode.md +127 -0
  54. froid_loop/data/skills/froid-loop-sweep/deferred-work-format.md +302 -0
  55. froid_loop/data/skills/froid-loop-sweep/migration-mode.md +86 -0
  56. froid_loop/decisions.py +202 -0
  57. froid_loop/deferredwork.py +2282 -0
  58. froid_loop/devcontract.py +892 -0
  59. froid_loop/diagnostics.py +1104 -0
  60. froid_loop/documents.py +532 -0
  61. froid_loop/engine.py +7732 -0
  62. froid_loop/envvars.py +111 -0
  63. froid_loop/escalation.py +225 -0
  64. froid_loop/events.py +266 -0
  65. froid_loop/fences.py +103 -0
  66. froid_loop/froidconfig.py +226 -0
  67. froid_loop/frontmatter.py +526 -0
  68. froid_loop/gates.py +133 -0
  69. froid_loop/install.py +2936 -0
  70. froid_loop/journal.py +178 -0
  71. froid_loop/machine.py +148 -0
  72. froid_loop/model.py +898 -0
  73. froid_loop/operatoractions.py +474 -0
  74. froid_loop/platform_util.py +1490 -0
  75. froid_loop/plugins/__init__.py +64 -0
  76. froid_loop/plugins/bus.py +259 -0
  77. froid_loop/plugins/context.py +319 -0
  78. froid_loop/plugins/loader.py +145 -0
  79. froid_loop/plugins/manifest.py +279 -0
  80. froid_loop/plugins/model.py +296 -0
  81. froid_loop/plugins/registry.py +245 -0
  82. froid_loop/plugins/trust.py +75 -0
  83. froid_loop/policy.py +1569 -0
  84. froid_loop/probe.py +1044 -0
  85. froid_loop/process_host.py +408 -0
  86. froid_loop/recovery_flow.py +1561 -0
  87. froid_loop/resolve.py +283 -0
  88. froid_loop/runs.py +4715 -0
  89. froid_loop/runsetup.py +1293 -0
  90. froid_loop/sanitize.py +593 -0
  91. froid_loop/settings_schema.py +276 -0
  92. froid_loop/signals.py +160 -0
  93. froid_loop/sprintstatus.py +609 -0
  94. froid_loop/statemachine.py +57 -0
  95. froid_loop/stories.py +615 -0
  96. froid_loop/stories_engine.py +796 -0
  97. froid_loop/sweep.py +1892 -0
  98. froid_loop/tokens.py +196 -0
  99. froid_loop/tui/__init__.py +11 -0
  100. froid_loop/tui/app.py +1584 -0
  101. froid_loop/tui/data.py +840 -0
  102. froid_loop/tui/launch.py +1003 -0
  103. froid_loop/tui/screens/__init__.py +1 -0
  104. froid_loop/tui/screens/dashboard.py +1071 -0
  105. froid_loop/tui/screens/modals.py +943 -0
  106. froid_loop/tui/screens/settings_screen.py +477 -0
  107. froid_loop/tui/settings.py +135 -0
  108. froid_loop/tui/widgets.py +981 -0
  109. froid_loop/verify.py +4545 -0
  110. froid_loop/workspace.py +320 -0
  111. froid_loop/worktree_flow.py +2301 -0
  112. froid_loop-0.11.1.dist-info/METADATA +728 -0
  113. froid_loop-0.11.1.dist-info/RECORD +116 -0
  114. froid_loop-0.11.1.dist-info/WHEEL +4 -0
  115. froid_loop-0.11.1.dist-info/entry_points.txt +2 -0
  116. froid_loop-0.11.1.dist-info/licenses/LICENSE +30 -0
@@ -0,0 +1,458 @@
1
+ # Core settings schema — presentation + widget hints for .froid-loop/policy.toml.
2
+ #
3
+ # This file describes HOW each policy key is rendered in the settings UI (widget
4
+ # kind, label, help text, select options, numeric bounds). It is NOT the source
5
+ # of truth for defaults or validation: policy.py's frozen dataclasses are. So
6
+ # defaults and select options are *referenced* from policy, never literal-ed:
7
+ #
8
+ # default_ref = "ScmPolicy.merge_strategy" -> getattr(policy.ScmPolicy, "merge_strategy")
9
+ # options_ref = "MERGE_STRATEGIES" -> tuple(sorted(policy.MERGE_STRATEGIES))
10
+ #
11
+ # A sync test asserts every resolved default/option matches the dataclass, so
12
+ # this file can never silently drift from the runtime model. Section and field
13
+ # order here IS the render order in the settings screen.
14
+ #
15
+ # field.kind is the widget hint: select | int | float | str | switch | lines | args
16
+ # (lines = newline-separated list; args = shlex token list with an override toggle).
17
+
18
+ [[section]]
19
+ name = "gates"
20
+ description = "approval gates, escalation & retrospective behavior"
21
+ [[section.field]]
22
+ key = "mode"
23
+ kind = "select"
24
+ options_ref = "GATE_MODES"
25
+ default_ref = "GatesPolicy.mode"
26
+ [[section.field]]
27
+ key = "retrospective"
28
+ kind = "select"
29
+ options_ref = "RETRO_MODES"
30
+ default_ref = "GatesPolicy.retrospective"
31
+
32
+ [[section]]
33
+ name = "review"
34
+ description = "separate adversarial review session toggle"
35
+ [[section.field]]
36
+ key = "enabled"
37
+ kind = "switch"
38
+ default_ref = "ReviewPolicy.enabled"
39
+ label = "separate review session"
40
+ description = "ON: adversarial review runs in a dedicated 2nd session · OFF: the dev pass runs its own review layers inline, story straight to done"
41
+ [[section.field]]
42
+ key = "trigger"
43
+ kind = "select"
44
+ options_ref = "REVIEW_TRIGGER_MODES"
45
+ default_ref = "ReviewPolicy.trigger"
46
+ label = "review trigger"
47
+ description = "recommended: run the 2nd-opinion review only when the dev pass flags it · always: run it every story (both bounded by limits.max_review_cycles)"
48
+ [[section.field]]
49
+ key = "on_timeout"
50
+ kind = "select"
51
+ options_ref = "REVIEW_ON_TIMEOUT_MODES"
52
+ default_ref = "ReviewPolicy.on_timeout"
53
+ label = "review timeout policy"
54
+ description = "retry: burn a review cycle per timeout (default) · salvage-if-done: commit the verified dev product and refile the follow-up · defer: give up on the first timeout"
55
+ [[section.field]]
56
+ key = "on_status_contradiction"
57
+ kind = "select"
58
+ options_ref = "REVIEW_ON_STATUS_CONTRADICTION_MODES"
59
+ default_ref = "ReviewPolicy.on_status_contradiction"
60
+ label = "review revokes sprint sign-off"
61
+ description = "escalate: pause naming both sides when a review writes sprint-status back off done (default) · retry: legacy — burn review cycles, then defer + roll back"
62
+
63
+ [[section]]
64
+ name = "stories"
65
+ description = "story-queue source: classic sprint board vs. folder+id dispatch"
66
+ [[section.field]]
67
+ key = "source"
68
+ kind = "select"
69
+ options_ref = "STORIES_SOURCES"
70
+ default_ref = "StoriesPolicy.source"
71
+ label = "story source"
72
+ description = "sprint-status: walk sprint-status.yaml · stories: folder+id dispatch off a typed stories.yaml (needs spec_folder)"
73
+ [[section.field]]
74
+ key = "spec_folder"
75
+ kind = "str"
76
+ default_ref = "StoriesPolicy.spec_folder"
77
+ label = "spec folder"
78
+ description = "stories mode only: project-relative path to the epic spec folder (holds stories.yaml + SPEC.md)"
79
+
80
+ [[section]]
81
+ name = "limits"
82
+ description = "cycle/attempt caps, timeout & token budget"
83
+ [[section.field]]
84
+ key = "max_review_cycles"
85
+ kind = "int"
86
+ minimum = 1
87
+ default_ref = "LimitsPolicy.max_review_cycles"
88
+ [[section.field]]
89
+ key = "max_dev_attempts"
90
+ kind = "int"
91
+ minimum = 1
92
+ default_ref = "LimitsPolicy.max_dev_attempts"
93
+ [[section.field]]
94
+ key = "max_followup_reviews"
95
+ kind = "int"
96
+ minimum = 0
97
+ default_ref = "LimitsPolicy.max_followup_reviews"
98
+ description = "additional review rounds granted solely because a finalized (status: done) round still recommended a follow-up; once spent, such a round converges + refiles the recommendation instead of burning another cycle · 0 = never honor a pass's own recommendation"
99
+ [[section.field]]
100
+ key = "session_timeout_min"
101
+ kind = "int"
102
+ minimum = 1
103
+ default_ref = "LimitsPolicy.session_timeout_min"
104
+ [[section.field]]
105
+ key = "git_timeout_s"
106
+ kind = "int"
107
+ minimum = 1
108
+ default_ref = "LimitsPolicy.git_timeout_s"
109
+ description = "bound on any single git subprocess the orchestrator spawns; exceeding it degrades/pauses (never crashes the run) — raise on a loaded host or a very large worktree"
110
+ [[section.field]]
111
+ key = "teardown_grace_s"
112
+ kind = "int"
113
+ minimum = 0
114
+ default_ref = "LimitsPolicy.teardown_grace_s"
115
+ description = "verified session teardown: poll the killed window up to this long, then force-kill its pane pids and re-kill the window · 0 = single unverified best-effort kill"
116
+ [[section.field]]
117
+ key = "stop_without_result_nudges"
118
+ kind = "int"
119
+ minimum = 0
120
+ default_ref = "LimitsPolicy.stop_without_result_nudges"
121
+ [[section.field]]
122
+ key = "dev_stall_grace_s"
123
+ kind = "int"
124
+ minimum = 0
125
+ default_ref = "LimitsPolicy.dev_stall_grace_s"
126
+ description = "silence grace for a dev/review session, armed at launch and re-armed by transport activity or fresh Stop/idle evidence, before bounded nudges/stalled classification · 0 = no launch timer, but a result-less turn end still fails fast"
127
+ [[section.field]]
128
+ key = "dev_stall_nudges"
129
+ kind = "int"
130
+ minimum = 0
131
+ default_ref = "LimitsPolicy.dev_stall_nudges"
132
+ description = "best-effort wake nudges for a silent dev/review session on grace expiry before it is called stalled; transport activity re-arms the grace and fresh Stop/idle evidence restores this budget · 0 = stall on grace expiry"
133
+ [[section.field]]
134
+ key = "dev_stall_nudges_cap"
135
+ kind = "int"
136
+ minimum = 0
137
+ default_ref = "LimitsPolicy.dev_stall_nudges_cap"
138
+ description = "total (never-restored) best-effort stall nudges for a dev/review session; bounds launch-time recovery and Stop/idle budget refills because an accepted nudge does not guarantee a wake · 0 = stall on first grace expiry"
139
+ [[section.field]]
140
+ key = "workflow_stall_nudges_cap"
141
+ kind = "int"
142
+ minimum = 0
143
+ default_ref = "LimitsPolicy.workflow_stall_nudges_cap"
144
+ description = "total (never-restored) stall nudges for an injected plugin-workflow session before it is called stalled; bounds a session that finished its work but never wrote its completion marker · 0 = stall on first grace expiry"
145
+ [[section.field]]
146
+ key = "dev_contract_nudge"
147
+ kind = "switch"
148
+ default_ref = "LimitsPolicy.dev_contract_nudge"
149
+ label = "dev contract nudge"
150
+ description = "ON: one targeted nudge per session when a Stop finds a spec finalized to a terminal frontmatter status but missing its `## Auto Run Result` marker, asking the skill to append it and end its turn (sent exactly once, never refilled, touches no stall counters) · OFF: rely only on harness-side frontmatter synthesis"
151
+ [[section.field]]
152
+ key = "max_tokens_per_story"
153
+ kind = "int"
154
+ minimum = 1
155
+ default_ref = "LimitsPolicy.max_tokens_per_story"
156
+ label = "story budget (weighted tokens)"
157
+ description = "advisory cap on one story's cumulative weighted spend across all its sessions, re-checked at every session boundary · crossing it raises one ATTENTION + desktop notice per story and nothing else — the story runs on · for a cap that can END a session see session budget mode below"
158
+ [[section.field]]
159
+ key = "cache_read_weight"
160
+ kind = "float"
161
+ minimum = 0.0
162
+ maximum = 1.0
163
+ default_ref = "LimitsPolicy.cache_read_weight"
164
+ description = "weight of cache-read tokens in every token total — budgets and displays alike · cache reads bill at ~0.1x base input on all supported vendors and are 80-95% of the raw count on agentic work, so the default keeps totals proportional to spend rather than to context re-reads · 1.0 = count cache reads at full price (raw)"
165
+ [[section.field]]
166
+ key = "session_budget_mode"
167
+ kind = "select"
168
+ options_ref = "SESSION_BUDGET_MODES"
169
+ default_ref = "LimitsPolicy.session_budget_mode"
170
+ label = "session budget mode"
171
+ description = "mid-session guard on the weighted per-session spend, sampled every ~30s · off: no sampling · warn: one ATTENTION + breadcrumb · enforce: wrap-up nudge then over_budget termination (retry→defer)"
172
+ [[section.field]]
173
+ key = "max_tokens_per_session"
174
+ kind = "int"
175
+ minimum = 1
176
+ default_ref = "LimitsPolicy.max_tokens_per_session"
177
+ description = "weighted (cache-discounted) token cap a single session may spend before the budget guard trips; healthy sessions run ~1-2.5M weighted, so the default trips only true runaways"
178
+ [[section.field]]
179
+ key = "session_budget_grace_s"
180
+ kind = "int"
181
+ minimum = 0
182
+ default_ref = "LimitsPolicy.session_budget_grace_s"
183
+ description = "enforce mode: seconds a tripped session gets to wrap up after the nudge before it is terminated over_budget · 0 = terminate at trip, no nudge"
184
+
185
+ [[section]]
186
+ name = "verify"
187
+ description = "post-implementation verification commands"
188
+ [[section.field]]
189
+ key = "commands"
190
+ kind = "lines"
191
+ [[section.field]]
192
+ key = "stream_capture_kb"
193
+ kind = "int"
194
+ minimum = 0
195
+ default_ref = "VerifyPolicy.stream_capture_kb"
196
+ label = "verifier stream capture (KiB)"
197
+ description = "per-stream cap on the verifier stdout/stderr retained under the run's verify/ directory (the tail is kept; the journal records the full size and a truncation flag) · 0 = capture nothing"
198
+
199
+ [[section]]
200
+ name = "notify"
201
+ description = "desktop & file notifications"
202
+ [[section.field]]
203
+ key = "desktop"
204
+ kind = "switch"
205
+ default_ref = "NotifyPolicy.desktop"
206
+ [[section.field]]
207
+ key = "file"
208
+ kind = "switch"
209
+ default_ref = "NotifyPolicy.file"
210
+
211
+ [[section]]
212
+ name = "adapter"
213
+ description = "CLI client, model & bypass flags (base for all stages)"
214
+ [[section.field]]
215
+ key = "name"
216
+ kind = "str"
217
+ placeholder = "CLI profile — default: claude"
218
+ [[section.field]]
219
+ key = "model"
220
+ kind = "str"
221
+ placeholder = "CLI default model"
222
+ [[section.field]]
223
+ key = "extra_args"
224
+ kind = "args"
225
+ [[section.field]]
226
+ key = "cleanup_session_on_finish"
227
+ kind = "switch"
228
+ default_ref = "AdapterPolicy.cleanup_session_on_finish"
229
+ [[section.field]]
230
+ key = "usage_grace_s"
231
+ kind = "float"
232
+ minimum = 0
233
+ default_ref = "AdapterPolicy.usage_grace_s"
234
+ placeholder = "inherit from CLI profile (copilot ships 8s)"
235
+ description = "seconds to poll the transcript for token usage after a session ends; CLIs that flush totals on shutdown (copilot) need a grace"
236
+ [[section.field]]
237
+ key = "stop_without_result_nudges"
238
+ kind = "int"
239
+ minimum = 0
240
+ default_ref = "AdapterPolicy.stop_without_result_nudges"
241
+ placeholder = "inherit (copilot profile ships 5)"
242
+ description = "result-less Stop signals tolerated before a session is called stalled; overrides the global limits value for this CLI"
243
+
244
+ # Per-stage adapter overrides. One section per [adapter.dev|review|triage] table,
245
+ # expanded from this single template by the STAGES loop so they never drift.
246
+ [[section]]
247
+ name = "adapter.{stage}"
248
+ expand_stages = true
249
+ description = "{stage}-stage adapter overrides"
250
+ [[section.field]]
251
+ key = "name"
252
+ kind = "str"
253
+ placeholder = "inherit from [adapter]"
254
+ [[section.field]]
255
+ key = "model"
256
+ kind = "str"
257
+ placeholder = "inherit / client default"
258
+ [[section.field]]
259
+ key = "extra_args"
260
+ kind = "args"
261
+ [[section.field]]
262
+ key = "usage_grace_s"
263
+ kind = "float"
264
+ minimum = 0
265
+ default_ref = "StageAdapterPolicy.usage_grace_s"
266
+ placeholder = "inherit from [adapter]"
267
+ [[section.field]]
268
+ key = "stop_without_result_nudges"
269
+ kind = "int"
270
+ minimum = 0
271
+ default_ref = "StageAdapterPolicy.stop_without_result_nudges"
272
+ placeholder = "inherit from [adapter]"
273
+
274
+ [[section]]
275
+ name = "sweep"
276
+ description = "deferred-work sweep automation"
277
+ [[section.field]]
278
+ key = "auto"
279
+ kind = "select"
280
+ options_ref = "SWEEP_AUTO_MODES"
281
+ default_ref = "SweepPolicy.auto"
282
+ [[section.field]]
283
+ key = "max_bundles"
284
+ kind = "int"
285
+ minimum = 1
286
+ default_ref = "SweepPolicy.max_bundles"
287
+ [[section.field]]
288
+ key = "max_triage_attempts"
289
+ kind = "int"
290
+ minimum = 1
291
+ default_ref = "SweepPolicy.max_triage_attempts"
292
+ [[section.field]]
293
+ key = "repeat"
294
+ kind = "switch"
295
+ default_ref = "SweepPolicy.repeat"
296
+ [[section.field]]
297
+ key = "max_cycles"
298
+ kind = "int"
299
+ minimum = 1
300
+ default_ref = "SweepPolicy.max_cycles"
301
+
302
+ [[section]]
303
+ name = "scm"
304
+ description = "git isolation, branching & merge-back"
305
+ [[section.field]]
306
+ key = "isolation"
307
+ kind = "select"
308
+ options_ref = "ISOLATION_MODES"
309
+ default_ref = "ScmPolicy.isolation"
310
+ description = "none: work in place on the checked-out branch (default) · worktree: run each story in its own git worktree, merge back to the target branch"
311
+ [[section.field]]
312
+ key = "branch_per"
313
+ kind = "select"
314
+ options_ref = "BRANCH_PER_MODES"
315
+ default_ref = "ScmPolicy.branch_per"
316
+ description = "worktree mode: one branch per story, or one shared branch per run (run forces delete-branch off so the shared branch survives)"
317
+ [[section.field]]
318
+ key = "target_branch"
319
+ kind = "str"
320
+ placeholder = "default: the branch checked out at run start"
321
+ description = "worktree mode: branch all units merge back into (created if missing)"
322
+ [[section.field]]
323
+ key = "merge_strategy"
324
+ kind = "select"
325
+ options_ref = "MERGE_STRATEGIES"
326
+ default_ref = "ScmPolicy.merge_strategy"
327
+ description = "worktree mode: how a unit branch lands on the target — ff, merge, or squash"
328
+ [[section.field]]
329
+ key = "delete_branch"
330
+ kind = "switch"
331
+ default_ref = "ScmPolicy.delete_branch"
332
+ description = "worktree mode: delete the unit branch after a successful merge"
333
+ [[section.field]]
334
+ key = "keep_failed"
335
+ kind = "switch"
336
+ default_ref = "ScmPolicy.keep_failed"
337
+ description = "worktree mode: keep a failed unit's worktree + branch mounted for inspection"
338
+ [[section.field]]
339
+ key = "rollback_on_failure"
340
+ kind = "switch"
341
+ default_ref = "ScmPolicy.rollback_on_failure"
342
+ label = "auto-rollback failed attempts"
343
+ description = "⚠ in-place mode (isolation=none): when ON, a failed attempt's tracked changes are auto-reverted and the untracked files this run created are deleted (its uncommitted work is lost). When OFF (default), the orchestrator never touches your tree — it pauses with manual recovery steps. Governs unattended/stopped attempts only: a resolved escalation's re-drive always auto-recovers regardless (reverts the failed source, keeps the corrected spec). Prefer isolation=worktree to keep failures off your main checkout."
344
+ [[section.field]]
345
+ key = "preserve_keep"
346
+ kind = "int"
347
+ minimum = 0
348
+ default_ref = "ScmPolicy.preserve_keep"
349
+ label = "preserve-ref retention"
350
+ description = "attempt-preserve/* recovery branches and attempt-preserve-dirty/* worktree snapshots kept at each run start (per family) · the newest by committer date survive and the tail is deleted (0 = never prune)"
351
+ [[section.field]]
352
+ key = "seed_adapter_defaults"
353
+ kind = "switch"
354
+ default_ref = "ScmPolicy.seed_adapter_defaults"
355
+ label = "seed adapter configs"
356
+ description = "worktree mode: copy each loaded adapter's gitignored MCP/CLI configs (.mcp.json, .claude/settings.json, .codex/config.toml …) into the worktree so isolated sessions can reach their MCP server"
357
+ [[section.field]]
358
+ key = "worktree_seed"
359
+ kind = "lines"
360
+ label = "extra worktree seed files"
361
+ description = "worktree mode: additional project-relative gitignored files to copy into each worktree, one per line — on top of the adapter defaults above"
362
+ [[section.field]]
363
+ key = "commit_message_template"
364
+ kind = "str"
365
+ placeholder = "blank = built-in default; {story_key} / {run_id} / {story_title} substituted"
366
+ label = "commit message template"
367
+ description = "commit message dev sessions use for a story/bundle commit when set"
368
+ [[section.field]]
369
+ key = "failed_diff_max_mb"
370
+ kind = "int"
371
+ minimum = 1
372
+ default_ref = "ScmPolicy.failed_diff_max_mb"
373
+ label = "failed-diff size cap (MB)"
374
+ description = "per-file size limit for untracked files captured into a kept-failed unit's changes.patch · oversized files are skipped with a marker"
375
+ [[section.field]]
376
+ key = "failed_diff_unlimited"
377
+ kind = "switch"
378
+ default_ref = "ScmPolicy.failed_diff_unlimited"
379
+ label = "uncap failed-diff size"
380
+ description = "⚠ ON: capture failed-unit diffs with NO size limit (overrides the cap above) — may produce very large patches; a warning is logged when active"
381
+
382
+ [[section]]
383
+ name = "cleanup"
384
+ description = "disk reclamation for .froid-loop/runs, plus each run's out-of-tree control-plane dir (terminal runs only)"
385
+ [[section.field]]
386
+ key = "run_retention"
387
+ kind = "int"
388
+ minimum = 0
389
+ default_ref = "CleanupPolicy.run_retention"
390
+ label = "run retention (count)"
391
+ description = "newest concluded runs kept whole · older ones are trimmed or archived by `froid-loop clean` (0 = keep none by count)"
392
+ [[section.field]]
393
+ key = "retention_days"
394
+ kind = "int"
395
+ minimum = 0
396
+ default_ref = "CleanupPolicy.retention_days"
397
+ label = "run retention (days)"
398
+ description = "0 = disabled · else also keep runs newer than N days regardless of the count above"
399
+ [[section.field]]
400
+ key = "trim_artifacts"
401
+ kind = "switch"
402
+ default_ref = "CleanupPolicy.trim_artifacts"
403
+ label = "trim heavy artifacts"
404
+ description = "drop the heavy worktrees/ tree from concluded runs · the run still lists in the dashboard with full status/journal/logs"
405
+ [[section.field]]
406
+ key = "archive_old"
407
+ kind = "switch"
408
+ default_ref = "CleanupPolicy.archive_old"
409
+ label = "archive past the window"
410
+ description = "archive (.froid-loop/archive/<id>.tar.gz) rather than permanently delete runs beyond the retention window"
411
+ [[section.field]]
412
+ key = "auto_clean_on_finish"
413
+ kind = "switch"
414
+ default_ref = "CleanupPolicy.auto_clean_on_finish"
415
+ label = "auto-clean on finish"
416
+ description = "reconcile worktrees leaked by a mid-flight stop + apply retention each time a run finishes cleanly"
417
+ [[section.field]]
418
+ key = "clean_tmp"
419
+ kind = "switch"
420
+ default_ref = "CleanupPolicy.clean_tmp"
421
+ label = "clean plugin /tmp scratch"
422
+ description = "let engine plugins clean their /tmp scratch on finish (e.g. the Unity MCP server's downloaded zips)"
423
+
424
+ # The game-engine layer (Unity) is no longer a core section — it is a plugin.
425
+ # Its settings render from the plugin's own schema (data/plugins/unity/plugin.toml)
426
+ # under [plugins.unity] when "unity" is in [plugins] enabled.
427
+
428
+ [[section]]
429
+ name = "tui"
430
+ description = "dashboard rendering (slow-link / SSH tuning)"
431
+ [[section.field]]
432
+ key = "low_frame_rate"
433
+ kind = "switch"
434
+ default_ref = "TuiPolicy.low_frame_rate"
435
+ label = "low frame rate"
436
+ description = "cap to 15fps + disable animations — fixes repaint tearing/garbage over slow or SSH links · takes effect next time the TUI launches"
437
+
438
+ [[section]]
439
+ name = "operator"
440
+ description = "stories that owe external, human-only actions"
441
+ [[section.field]]
442
+ key = "enabled"
443
+ kind = "switch"
444
+ default_ref = "OperatorPolicy.enabled"
445
+ label = "allow awaiting-operator parks"
446
+ description = "let a session finish + commit a story whose acceptance criteria need a human action (buy a domain, publish a DNS record) and park it as awaiting-operator instead of forcing done or blocked · what is owed is recorded in the story spec's operator_actions: frontmatter, and `froid-loop confirm <story-key>` completes the story once you have done it"
447
+
448
+ [[section]]
449
+ name = "mux"
450
+ description = "terminal-multiplexer backend (machine-specific — policy.toml is gitignored)"
451
+ # options are injected at build-registry time from detect_multiplexers() (plus any
452
+ # already-forced value), since registered backends are a runtime fact, not an enum.
453
+ [[section.field]]
454
+ key = "backend"
455
+ kind = "select"
456
+ default_ref = "MuxPolicy.backend"
457
+ label = "backend"
458
+ description = "force a registered transport backend by name (see `froid-loop mux`); leave blank to auto-select — FROID_LOOP_MUX_BACKEND outranks it · takes effect on the next froid-loop invocation"
@@ -0,0 +1,93 @@
1
+ # FROID Loop module (`froid-loop`)
2
+
3
+ A FROID module pairing the automation skills with the
4
+ [froid-loop orchestrator tool](https://github.com/cordya-ai/froid-loop) (the
5
+ Python program that drives the loop). The skills can be installed by the FROID
6
+ installer, or laid down by `froid-loop init` (the orchestrator's wheel **bundles**
7
+ them); either way `froid-loop-setup` installs the `froid-loop` package from its
8
+ Git repository, so installing this module gives you a working system — skills
9
+ plus the orchestrator that invokes them. Standard FROID installs are never
10
+ modified; the skills are froid-loop-owned, standalone or froid-loop-native (see
11
+ the table below).
12
+
13
+ | Component | Forked from | Role |
14
+ | ------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | `froid-loop` | — (this repo, Git) | the orchestrator: ralph-loop, hooks, tmux adapters, TUI. CLI `froid-loop`. Installed by `froid-loop-setup` from Git. |
16
+ | `froid-loop-resolve` | — (froid-loop-native) | interactive CRITICAL-escalation resolution: a human disambiguates a frozen spec so a paused story can be re-driven (`/froid-loop-resolve <story>`) |
17
+ | `froid-loop-sweep` | — (froid-loop-native) | read-only deferred-work ledger triage; owns the canonical `deferred-work-format.md` |
18
+ | `froid-loop-setup` | — (scaffolded) | **installs the orchestrator tool from Git**, runs `froid-loop init` + `validate`, refreshes `_froid/froid-loop/module-help.csv` |
19
+
20
+ The **inner dev primitive is the upstream `froid-build-auto` skill** (Froid Plane's
21
+ generic unattended dev session; `froid-dev-auto` on pre-rename releases, resolved
22
+ from disk and invoked under whichever name is there). It is **not** owned or
23
+ bundled here — the orchestrator drives it as an external skill that must already be installed
24
+ (by the Froid installer / bmm-core). The froid-loop orchestrator synthesizes its `result.json`
25
+ from the spec the session leaves on disk (see `froid_loop.devcontract`). The skill
26
+ self-reviews inline (its step-04 review layers) and commits its own
27
+ work each iteration; the orchestrator's **follow-up review is just a re-invocation
28
+ of the primitive on the done spec** (Froid Plane #2508 routes a `done` spec to a
29
+ fresh review pass), so there is no separate review skill.
30
+
31
+ ## Install into a project
32
+
33
+ The orchestrator tool now bundles these skills, so `froid-loop init` lays them
34
+ down for you:
35
+
36
+ ```bash
37
+ uv tool install "froid-loop[tui] @ git+https://github.com/cordya-ai/froid-loop.git"
38
+ froid-loop init --project /path/to/project --cli claude # add --cli codex/gemini as needed
39
+ claude "/froid-loop-setup accept all defaults" # installs the tool + wires the project
40
+ ```
41
+
42
+ `froid-loop init` installs the `froid-loop-*` skills into `.claude/skills/`
43
+ (claude) and/or `.agents/skills/` (codex/gemini), registers hooks, writes
44
+ `.froid-loop/policy.toml`, and gitignores the runs dir. Existing skill dirs are
45
+ left untouched (`--force-skills` to overwrite, `--no-skills` to skip).
46
+ `froid-loop-setup` is one-shot for the bootstrap the FROID installer cannot do: it
47
+ ensures the orchestrator tool is installed, then runs `froid-loop init` and
48
+ `froid-loop validate` (preflight). Module registration — `_froid/froid-loop/`, the
49
+ central `config.toml`, the `/froid-help` catalog — belongs to the FROID installer,
50
+ which regenerates it on every run; the only file the skill writes there is
51
+ `_froid/froid-loop/module-help.csv`.
52
+
53
+ The skills must be installed **together**: `froid-loop-sweep` owns the canonical
54
+ `deferred-work-format.md` that the ledger normalizes to, and the upstream dev
55
+ primitive must also be present (since Froid Plane 6.10.1-next.33 it records
56
+ deferred findings in its spec's frontmatter for the orchestrator to harvest into
57
+ the ledger; pre-rename copies append flat entries the sweep normalizes instead).
58
+ Requires the Froid Plane (bmm) module (`_froid/bmm/config.yaml`) and a `sprint-status.yaml` from
59
+ `froid-sprint-planning`.
60
+
61
+ `_froid/custom/<skill-name>.toml` customization overrides are keyed by skill
62
+ directory name.
63
+
64
+ ## Maintaining the skills
65
+
66
+ - This directory (`src/froid_loop/data/skills/`) is **canonical** for the skills
67
+ and is bundled into the wheel as package data, so `froid-loop init` can install
68
+ them. The repo's `.claude/skills/` and `.agents/skills/` hold dev-workspace
69
+ copies; `tests/test_module_skills_sync.py` fails if they drift. After editing
70
+ here, re-copy the skill dirs into both trees.
71
+ - The orchestrator tool is **not** bundled in the skill dirs — the FROID installer
72
+ copies only the skill directories, so a sibling `tool/` would never reach an
73
+ installed project. `froid-loop-setup` installs the `froid-loop` package from
74
+ <https://github.com/cordya-ai/froid-loop> (`src/froid_loop`, `pyproject.toml`
75
+ are canonical at the repo root). (The skills, by contrast, ride along inside
76
+ the package wheel.)
77
+ - The inner dev primitive (`froid-build-auto`, or `froid-dev-auto` pre-rename) is
78
+ **not** maintained here — it is the upstream bmm-core skill, driven unmodified.
79
+ Nothing in this directory mirrors it; the orchestrator adapts to it via
80
+ `froid_loop.devcontract`.
81
+ - Do **not** rename the result.json `workflow` values — they are machine
82
+ contracts the orchestrator validates, not skill names:
83
+ - dev → `"auto-dev"` (checked by `verify.DEV_WORKFLOW` in
84
+ `verify_dev` / `verify_dev_bundle`; the orchestrator forges this value in
85
+ `devcontract` when synthesizing the dev result from the spec).
86
+ - sweep triage / migrate → `"deferred-sweep-triage"` / `"deferred-sweep-migrate"`
87
+ (checked in `sweep.py`).
88
+
89
+ Validate after changes (from the repo root):
90
+
91
+ ```bash
92
+ python3 .claude/skills/froid-module-builder/scripts/validate-module.py src/froid_loop/data/skills
93
+ ```