-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontracts.py
More file actions
325 lines (267 loc) · 12 KB
/
Copy pathcontracts.py
File metadata and controls
325 lines (267 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# -*- coding: utf-8 -*-
"""Runtime contract validation for the DietCode plugin."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
_REQUIRED_HOOKS = (
"on_session_start",
"on_session_end",
"pre_tool_call",
"post_tool_call",
"transform_tool_result",
)
_GUIDANCE_BUILDER_ATTR = "_dietcode_guidance_builder"
@dataclass
class ContractReport:
ok: bool = True
errors: list[str] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
checks: dict[str, Any] = field(default_factory=dict)
def add_error(self, msg: str) -> None:
self.ok = False
self.errors.append(msg)
def add_warning(self, msg: str) -> None:
self.warnings.append(msg)
def _governance_enabled_in_config() -> bool:
try:
from plugins.dietcode.lib.agent.governance_exemptions import is_governance_enforcement_enabled
return bool(is_governance_enforcement_enabled())
except Exception:
return False
def _dietcode_in_default_toolsets() -> bool:
try:
from hermes_cli.config import load_config
cfg = load_config()
toolsets = cfg.get("toolsets") if isinstance(cfg, dict) else None
if not isinstance(toolsets, list):
return False
return "dietcode" in toolsets
except Exception:
return False
def _plugin_disabled_in_config() -> bool:
try:
from hermes_cli.config import load_config
cfg = load_config()
plugins = cfg.get("plugins") if isinstance(cfg, dict) else None
if not isinstance(plugins, dict):
return False
disabled = plugins.get("disabled") or []
if not isinstance(disabled, list):
return False
return "dietcode" in disabled
except Exception:
return False
def _runtime_features_snapshot() -> dict[str, bool]:
try:
from plugins.dietcode.lib.agent.features import features_snapshot
return features_snapshot()
except Exception:
return {}
def _hook_chain_summary() -> dict[str, list[str]]:
try:
from plugins.dietcode.hooks import hook_chain_summary
return hook_chain_summary()
except Exception:
return {}
def _hook_names_present() -> dict[str, bool]:
try:
from hermes_cli.plugins import get_plugin_manager
pm = get_plugin_manager()
hooks = pm._hooks
return {
name: any(
cb.__name__ == f"dietcode_{name}" or cb.__name__.startswith("dietcode_")
for cb in hooks.get(name, [])
)
for name in _REQUIRED_HOOKS
}
except Exception:
return {name: False for name in _REQUIRED_HOOKS}
def validate_runtime_contract(*, strict: bool = False) -> ContractReport:
"""Validate DietCode integration invariants for production deployments."""
from plugins.dietcode.guard import dietcode_tools_in_registry, is_dietcode_plugin_registered
from plugins.dietcode.tools_loader import get_load_report
report = ContractReport()
load = get_load_report()
registered = is_dietcode_plugin_registered()
tools_ok = dietcode_tools_in_registry()
hooks = _hook_names_present()
governance_on = _governance_enabled_in_config()
expected = _dietcode_in_default_toolsets()
disabled = _plugin_disabled_in_config()
report.checks = {
"plugin_registered": registered,
"tools_in_registry": tools_ok,
"modules_failed": load.failed,
"registry_missing": load.registry_missing,
"hooks": hooks,
"hook_chains": _hook_chain_summary(),
"runtime_features": _runtime_features_snapshot(),
"governance_config_enabled": governance_on,
"dietcode_in_toolsets": expected,
"dietcode_disabled_in_config": disabled,
}
if disabled and expected:
report.add_error(
"dietcode is in toolsets but listed in plugins.disabled — tools/hooks will not load"
)
if not registered:
if expected and not disabled:
report.add_error("DietCode plugin did not register (discover_plugins failure or import error)")
elif strict:
report.add_error("DietCode plugin not registered on PluginManager")
if load.failed:
report.add_error(f"Tool module import failures: {list(load.failed.keys())}")
if load.registry_missing:
report.add_error(f"Registry missing expected tools: {load.registry_missing}")
if not tools_ok and registered:
report.add_error("DietCode registered but expected registry tools are absent")
missing_hooks = [name for name, present in hooks.items() if not present]
if registered and missing_hooks:
report.add_error(f"DietCode hook chains missing: {missing_hooks}")
if governance_on and not hooks.get("transform_tool_result"):
report.add_error(
"joyzoning.governance.enabled is true but transform_tool_result hook is not wired "
"(enable the DietCode plugin and add dietcode to toolsets)"
)
if registered and governance_on and hooks.get("transform_tool_result"):
if not expected:
report.add_warning(
"joyzoning.governance.enabled is true and the transform hook is active, but "
"dietcode is not in toolsets — layering enforcement runs on write_file/patch "
"without JoyZoning tools in the session. Add dietcode to toolsets or set "
"joyzoning.governance.enabled: false to disable."
)
try:
from plugins.dietcode.lib.agent.joyzoning.config import get_joyzoning_config
if not get_joyzoning_config().enabled:
report.add_warning(
"joyzoning.enabled is false but governance enforcement is on — file "
"mutations are still scanned; enable joyzoning for lifecycle tools and gates."
)
except Exception:
pass
try:
from plugins.dietcode.audit import (
broccolidb_bundle_symlink_ok,
duplicate_diet_hooks,
legacy_shim_dirs_absent,
removed_habitat_modules_absent,
runtime_layout_ok,
scan_stale_joyzoning_config_keys,
)
layout_ok, layout_missing = runtime_layout_ok()
report.checks["runtime_layout_ok"] = layout_ok
report.checks["runtime_layout_missing"] = layout_missing
if not layout_ok:
report.add_error(f"DietCode runtime layout incomplete: {layout_missing}")
shims_gone, shim_dirs = legacy_shim_dirs_absent()
report.checks["legacy_shim_dirs_absent"] = shims_gone
report.checks["legacy_shim_dirs_present"] = shim_dirs
if not shims_gone:
report.add_error(f"Legacy DietCode shim plugins still on disk: {shim_dirs}")
habitat_gone, habitat_files = removed_habitat_modules_absent()
report.checks["habitat_modules_absent"] = habitat_gone
report.checks["habitat_modules_present"] = habitat_files
if not habitat_gone:
report.add_error(f"Removed Habitat modules still present: {habitat_files}")
symlink_ok, symlink_detail = broccolidb_bundle_symlink_ok()
report.checks["broccolidb_bundle_symlink_ok"] = symlink_ok
report.checks["broccolidb_bundle_symlink_detail"] = symlink_detail
if not symlink_ok:
report.add_warning(f"BroccoliDB plugin bundle layout: {symlink_detail}")
stale_cfg = scan_stale_joyzoning_config_keys()
report.checks["stale_joyzoning_config_keys"] = stale_cfg
if stale_cfg:
report.add_warning(
f"config.yaml joyzoning section contains removed keys: {stale_cfg}"
)
no_dupes, dupe_issues = duplicate_diet_hooks()
report.checks["no_duplicate_diet_hooks"] = no_dupes
if registered and not no_dupes:
report.add_error(f"Duplicate diet hook registrations: {dupe_issues}")
hook_registry_failures = []
try:
from plugins.dietcode.lib.runtime.hook_registry import validate_hook_registry
hook_registry_failures = validate_hook_registry()
except Exception as exc:
hook_registry_failures = [f"hook registry validation failed: {exc}"]
report.checks["hook_registry_valid"] = not hook_registry_failures
report.checks["hook_registry_failures"] = hook_registry_failures
if hook_registry_failures:
report.add_error(f"Hook registry invalid: {hook_registry_failures}")
command_registry_failures = []
try:
from plugins.dietcode.lib.runtime.command_registry import validate_command_registry
command_registry_failures = validate_command_registry()
except Exception as exc:
command_registry_failures = [f"command registry validation failed: {exc}"]
report.checks["command_registry_valid"] = not command_registry_failures
report.checks["command_registry_failures"] = command_registry_failures
if command_registry_failures:
report.add_error(f"Command registry invalid: {command_registry_failures}")
from plugins.dietcode.audit import scan_stale_doc_paths
stale_docs = scan_stale_doc_paths()
report.checks["stale_doc_paths"] = stale_docs
if stale_docs:
report.add_warning(
f"Fork docs reference removed integration paths ({len(stale_docs)} lines)"
)
except Exception as exc:
report.add_warning(f"Audit checks skipped: {exc}")
try:
from hermes_cli.plugins import get_plugin_manager
pm = get_plugin_manager()
if not getattr(pm, _GUIDANCE_BUILDER_ATTR, None):
report.add_warning("Prompt guidance builder not registered on PluginManager")
except Exception:
pass
try:
from plugins.dietcode.audit import dietcode_plugin_root
from plugins.dietcode.paths import is_valid_broccolidb_root
bundled_plugin = dietcode_plugin_root()
for rel in (
"lib/runtime/governance_hooks.py",
"lib/runtime/joyzoning_hooks.py",
"lib/runtime/kanban_hooks.py",
"lib/runtime/jsdp_hooks.py",
"lib/runtime/roadmap_hooks.py",
"lib/runtime/audit_hooks.py",
"lib/runtime/mutation_hooks.py",
"lib/runtime/hook_registry.py",
"lib/agent/gates/kanban_complete.py",
"lib/tools/roadmap_tools.py",
"lib/agent/roadmap/cockpit.py",
"lib/agent/roadmap/doctor.py",
"lib/agent/roadmap/operator.py",
"lib/agent/roadmap/workspace_state.py",
"lib/agent/roadmap/explain_gate.py",
"lib/agent/roadmap/gate.py",
"lib/agent/roadmap/config.py",
"optional-skills/dietcode/auto-rolling-roadmap/SKILL.md",
"slash_commands.py",
"lib/tools/broccolidb.py",
"audit.py",
"public.py",
):
if not (bundled_plugin / rel).is_file():
report.add_error(f"DietCode layout missing required file: {rel}")
root_pkg = bundled_plugin.parents[1] / "broccolidb" / "package.json"
plugin_pkg = bundled_plugin / "broccolidb" / "package.json"
if root_pkg.is_file() and plugin_pkg.is_file():
import json
try:
root_data = json.loads(root_pkg.read_text(encoding="utf-8"))
plugin_data = json.loads(plugin_pkg.read_text(encoding="utf-8"))
if root_data.get("version") != plugin_data.get("version"):
report.add_warning(
"Root broccolidb/ and plugins/dietcode/broccolidb/ version mismatch — sync trees"
)
except (json.JSONDecodeError, OSError):
pass
bundled = bundled_plugin / "broccolidb"
if not is_valid_broccolidb_root(bundled):
report.add_warning("Plugin bundle broccolidb/ missing or invalid (run npm ci in bundle)")
except Exception:
pass
return report