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
froid_loop/checks.py ADDED
@@ -0,0 +1,187 @@
1
+ """Findings — the structured form of what ``froid-loop validate`` reports.
2
+
3
+ ``validate`` accumulated two lists of prose strings, printed one as `` ok:`` and
4
+ the other as ``FAIL:``. That made the *outcome* addressable (the exit code) but
5
+ not the *checks*: a script wanting to branch on "hooks aren't registered" had to
6
+ match remediation sentences, which are the part most likely to be reworded. A
7
+ :class:`Finding` pairs each printed line with a stable ``check`` id, so the id is
8
+ the matchable identity and the message stays free to change.
9
+
10
+ This lives in its own module rather than in ``cli`` or ``machine``:
11
+
12
+ - ``cli`` imports ``install``, and ``install`` must *return* Findings — putting
13
+ the type in ``cli`` would make that a cycle.
14
+ - ``machine`` is deliberately narrow: transport for the ``--json`` contract, zero
15
+ domain knowledge. Severity is a concept no other ``--json`` command has.
16
+
17
+ :data:`VALIDATE_CHECKS` is the registry every id must be in, enforced by an
18
+ assert in :meth:`ValidationReport.add`. That is what keeps "one printed line =
19
+ exactly one Finding" true: a new check site cannot ship without first naming
20
+ itself here. The assert only ever fires on a literal an author just wrote, never
21
+ on runtime data, so it is a lint with a stack trace rather than a validation.
22
+ """
23
+
24
+ # Strict-checked under #245 Stage 2, with one rule relaxed for this file only:
25
+ # `ValidationReport.findings` uses the idiomatic `field(default_factory=list)`,
26
+ # which pyright can only infer as `list[Unknown]` (it does not fold the declared
27
+ # `list[Finding]` annotation back into the bare `list` factory) though the field
28
+ # is correctly typed. Every other strict rule stays on.
29
+ # pyright: reportUnknownVariableType=false
30
+
31
+ from __future__ import annotations
32
+
33
+ import sys
34
+ from collections.abc import Mapping
35
+ from dataclasses import dataclass, field
36
+ from typing import Literal
37
+
38
+ Severity = Literal["ok", "warning", "problem"]
39
+
40
+ # The id names the *gate*, not the outcome — so the same id carries the ok and
41
+ # the problem for one check ("adapter.binary" is both "codex found" and "codex
42
+ # not found on PATH"). Ids are split only where the two outcomes are genuinely
43
+ # different findings with different detail (skills.base vs base-missing vs
44
+ # base-incomplete). Scheme: <area>.<check>.
45
+ VALIDATE_CHECKS: frozenset[str] = frozenset(
46
+ {
47
+ "froid-config",
48
+ "policy",
49
+ "policy.model-qualified",
50
+ "policy.isolation-repo-root",
51
+ "adapter.profile",
52
+ "adapter.binary",
53
+ "adapter.binary-unrunnable",
54
+ "adapter.hookless",
55
+ "adapter.httpx",
56
+ "adapter.kind",
57
+ "adapter.external",
58
+ "adapter.external-profile",
59
+ "queue.sprint-status",
60
+ "queue.sprint-status-unknown-keys",
61
+ "queue.stories-manifest",
62
+ "git.worktree-clean",
63
+ "git.probe",
64
+ "git.render-tracked",
65
+ "git.version",
66
+ "hooks.config-parse",
67
+ "hooks.registered",
68
+ "hooks.relay-present",
69
+ "hooks.relay-stale",
70
+ "mux.backend",
71
+ "mux.preflight",
72
+ "mux.backends-detected",
73
+ "mux.selection",
74
+ "mux.external-backend",
75
+ "host.process",
76
+ "host.win32-on-wsl-path",
77
+ "notify.desktop-unavailable",
78
+ "skills.base",
79
+ "skills.base-missing",
80
+ "skills.base-incomplete",
81
+ "skills.base-shim",
82
+ "skills.customize-legacy",
83
+ "skills.dev-renderer",
84
+ "skills.dev-renderer-config",
85
+ "skills.dev-renderer-sources",
86
+ "skills.review-layer-missing",
87
+ "skills.review-layer-unresolved",
88
+ "skills.review-layers-empty",
89
+ "skills.customize-unreadable",
90
+ "skills.stories-dispatch",
91
+ "skills.stories-dispatch-missing",
92
+ "skills.stories-dispatch-stale",
93
+ "operator.registry-stale",
94
+ "operator.actions-malformed",
95
+ "operator.confirm-interrupted",
96
+ "operator.park-record-missing",
97
+ "deferred.closes-unknown",
98
+ "deferred.closes-malformed",
99
+ "deferred.closes-entry-unreadable",
100
+ "deferred.hard-gate",
101
+ "deferred.hard-gate-unstructured",
102
+ "deferred.ledger-unreadable",
103
+ }
104
+ )
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class Finding:
109
+ """One check's outcome: a stable ``check`` id, a severity, the human line.
110
+
111
+ ``message`` is the exact prose the text mode prints (minus the severity
112
+ prefix) and is **not** contracted — several are a bare ``str(e)`` from the
113
+ config/policy/profile/sprint-status exceptions, so the wording moves with
114
+ those modules. ``detail`` carries what the check knew before it flattened
115
+ itself into that sentence, keyed however suits the check.
116
+ """
117
+
118
+ check: str
119
+ severity: Severity
120
+ message: str
121
+ detail: Mapping[str, object] | None = None
122
+
123
+
124
+ @dataclass
125
+ class ValidationReport:
126
+ """The findings of one ``validate`` pass, in emission order.
127
+
128
+ Emission order is the order the gates ran, and it is preserved across
129
+ severities — :meth:`render` filters, it never re-sorts, so the text output is
130
+ byte-identical to the two-list form it replaced (all stdout lines in append
131
+ order, then all stderr lines in append order).
132
+ """
133
+
134
+ findings: list[Finding] = field(default_factory=list)
135
+
136
+ def add(
137
+ self,
138
+ check: str,
139
+ severity: Severity,
140
+ message: str,
141
+ detail: Mapping[str, object] | None = None,
142
+ ) -> None:
143
+ assert check in VALIDATE_CHECKS, f"unregistered check id: {check!r}"
144
+ self.findings.append(Finding(check, severity, message, detail))
145
+
146
+ def ok(self, check: str, message: str, detail: Mapping[str, object] | None = None) -> None:
147
+ self.add(check, "ok", message, detail)
148
+
149
+ def warn(self, check: str, message: str, detail: Mapping[str, object] | None = None) -> None:
150
+ self.add(check, "warning", message, detail)
151
+
152
+ def fail(self, check: str, message: str, detail: Mapping[str, object] | None = None) -> None:
153
+ self.add(check, "problem", message, detail)
154
+
155
+ def extend(self, findings: list[Finding]) -> None:
156
+ for finding in findings:
157
+ self.add(finding.check, finding.severity, finding.message, finding.detail)
158
+
159
+ @property
160
+ def passed(self) -> bool:
161
+ """True when nothing failed. Warnings do not clear it and do not set it —
162
+ this is exactly the validate exit code (0 when True, 1 when False)."""
163
+ return not any(f.severity == "problem" for f in self.findings)
164
+
165
+ def counts(self) -> dict[str, int]:
166
+ return {
167
+ severity: sum(1 for f in self.findings if f.severity == severity)
168
+ for severity in ("ok", "warning", "problem")
169
+ }
170
+
171
+ def render(self) -> None:
172
+ """Print the human-readable form: notes to stdout, then problems to stderr.
173
+
174
+ The doubled space in the warning line (`` ok: warning: ...``) is the
175
+ shipped output, not a typo to tidy: the warning sites stored
176
+ ``" warning: " + msg`` into the same list the `` ok: `` printer walked.
177
+ Tidying it would be a silent text change — the validate tests
178
+ substring-match on a lowercased stream and would not notice.
179
+ """
180
+ for finding in self.findings:
181
+ if finding.severity == "ok":
182
+ print(f" ok: {finding.message}")
183
+ elif finding.severity == "warning":
184
+ print(f" ok: warning: {finding.message}")
185
+ for finding in self.findings:
186
+ if finding.severity == "problem":
187
+ print(f"FAIL: {finding.message}", file=sys.stderr)