metaensemble 0.2.0__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.
- evals/README.md +147 -0
- evals/__init__.py +0 -0
- evals/cassettes/README.md +10 -0
- evals/cassettes/bootstrap.jsonl +800 -0
- evals/configs/default.yaml +59 -0
- evals/datasets/__init__.py +0 -0
- evals/datasets/suite_a/tasks.yaml +123 -0
- evals/datasets/suite_b/items.yaml +90 -0
- evals/runners/__init__.py +12 -0
- evals/runners/api.py +518 -0
- evals/runners/metrics.py +132 -0
- metaensemble/__init__.py +13 -0
- metaensemble/cli.py +1362 -0
- metaensemble/commands/dispatch.md +39 -0
- metaensemble/commands/executors.md +12 -0
- metaensemble/commands/ledger.md +19 -0
- metaensemble/commands/limits.md +12 -0
- metaensemble/commands/perf.md +12 -0
- metaensemble/commands/relaunch.md +29 -0
- metaensemble/commands/standup.md +14 -0
- metaensemble/config/budgets.example.yaml +72 -0
- metaensemble/config/quality.example.yaml +82 -0
- metaensemble/hooks/__init__.py +1 -0
- metaensemble/hooks/_common.py +148 -0
- metaensemble/hooks/deliverable_sync.py +73 -0
- metaensemble/hooks/file_event.py +303 -0
- metaensemble/hooks/post_task.py +460 -0
- metaensemble/hooks/pre_task.py +548 -0
- metaensemble/hooks/session_start.py +212 -0
- metaensemble/hooks/session_summary.py +392 -0
- metaensemble/hooks/subagent_stop.py +94 -0
- metaensemble/lib/__init__.py +1 -0
- metaensemble/lib/config.py +414 -0
- metaensemble/lib/cost_gate.py +299 -0
- metaensemble/lib/dispatch.py +341 -0
- metaensemble/lib/doctor.py +1563 -0
- metaensemble/lib/file_events.py +395 -0
- metaensemble/lib/ids.py +91 -0
- metaensemble/lib/installer.py +5018 -0
- metaensemble/lib/ledger.py +812 -0
- metaensemble/lib/manifest.py +141 -0
- metaensemble/lib/native_state.py +463 -0
- metaensemble/lib/overlaps.py +155 -0
- metaensemble/lib/quality_gate.py +155 -0
- metaensemble/lib/quality_runners.py +446 -0
- metaensemble/lib/reconcile.py +420 -0
- metaensemble/lib/recording.py +422 -0
- metaensemble/lib/relaunch.py +174 -0
- metaensemble/lib/runtime_payload.py +42 -0
- metaensemble/lib/runtime_state.py +308 -0
- metaensemble/lib/sidecar.py +166 -0
- metaensemble/lib/topology.py +181 -0
- metaensemble/lib/transcript.py +432 -0
- metaensemble/output-styles/deliverable.md +33 -0
- metaensemble/output-styles/wire.md +38 -0
- metaensemble/roles/architect.md +52 -0
- metaensemble/roles/backend.md +43 -0
- metaensemble/roles/code-quality.md +49 -0
- metaensemble/roles/data-engineer.md +42 -0
- metaensemble/roles/devops.md +42 -0
- metaensemble/roles/docs.md +41 -0
- metaensemble/roles/frontend.md +42 -0
- metaensemble/roles/ml-engineer.md +42 -0
- metaensemble/roles/test-engineer.md +42 -0
- metaensemble/schemas/brief.schema.json +80 -0
- metaensemble/schemas/manifest.schema.json +142 -0
- metaensemble/schemas/role.schema.json +84 -0
- metaensemble/skills/metaensemble-protocol/SKILL.md +226 -0
- metaensemble/state/migrations/001_init.sql +72 -0
- metaensemble/state/migrations/002_outcome_extended.sql +86 -0
- metaensemble/state/migrations/003_run_provenance.sql +36 -0
- metaensemble/statusline/me_status.py +187 -0
- metaensemble/tools/__init__.py +7 -0
- metaensemble/tools/executors.py +62 -0
- metaensemble/tools/ledger.py +121 -0
- metaensemble/tools/limits.py +165 -0
- metaensemble/tools/perf.py +150 -0
- metaensemble/tools/standup.py +177 -0
- metaensemble/tools/stats.py +115 -0
- metaensemble-0.2.0.dist-info/METADATA +221 -0
- metaensemble-0.2.0.dist-info/RECORD +85 -0
- metaensemble-0.2.0.dist-info/WHEEL +5 -0
- metaensemble-0.2.0.dist-info/entry_points.txt +2 -0
- metaensemble-0.2.0.dist-info/licenses/LICENSE +21 -0
- metaensemble-0.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""File-tool hook — enforces project boundary and records file provenance.
|
|
3
|
+
|
|
4
|
+
This hook handles Write/Edit/MultiEdit/NotebookEdit. On PreToolUse it blocks
|
|
5
|
+
file writes outside the active MetaEnsemble project root. On PostToolUse it
|
|
6
|
+
records successful file-tool events so the enclosing Task Run can persist
|
|
7
|
+
`files_touched_json` and `tool_use_json` without relying on subagent
|
|
8
|
+
transcript discovery.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import json
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))
|
|
18
|
+
|
|
19
|
+
from metaensemble.hooks._common import emit, log_error, read_input # noqa: E402
|
|
20
|
+
from metaensemble.lib.file_events import ( # noqa: E402
|
|
21
|
+
FileToolEvent,
|
|
22
|
+
append_file_event,
|
|
23
|
+
is_within,
|
|
24
|
+
nearest_project_root,
|
|
25
|
+
read_active_dispatch,
|
|
26
|
+
read_active_dispatch_by_agent,
|
|
27
|
+
read_active_dispatch_for_project,
|
|
28
|
+
resolve_against_cwd,
|
|
29
|
+
resolve_tool_paths,
|
|
30
|
+
)
|
|
31
|
+
from metaensemble.lib.overlaps import ( # noqa: E402
|
|
32
|
+
protected_overlap_for_path,
|
|
33
|
+
report_root_for_project,
|
|
34
|
+
)
|
|
35
|
+
from metaensemble.lib.recording import coerce_to_text # noqa: E402
|
|
36
|
+
from metaensemble.lib.runtime_state import _encode_cwd_for_runtime # noqa: E402
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _payload_cwd(payload: dict) -> Path:
|
|
40
|
+
raw = payload.get("cwd")
|
|
41
|
+
if isinstance(raw, str) and raw:
|
|
42
|
+
return Path(raw)
|
|
43
|
+
return Path.cwd()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _active_context(session_id: str, cwd: Path, agent_id: str | None = None):
|
|
47
|
+
# Background path: authorize by the per-dispatch agentId first. This is the
|
|
48
|
+
# only correlation key that survives same-session fan-out.
|
|
49
|
+
if agent_id:
|
|
50
|
+
active = read_active_dispatch_by_agent(agent_id)
|
|
51
|
+
if active is not None:
|
|
52
|
+
return active, Path(active.project_root), Path(active.state_dir)
|
|
53
|
+
# Legacy session/project fallback — synchronous-runtime compatibility only.
|
|
54
|
+
active = read_active_dispatch(session_id) if session_id else None
|
|
55
|
+
if active is not None:
|
|
56
|
+
return active, Path(active.project_root), Path(active.state_dir)
|
|
57
|
+
root = nearest_project_root(cwd)
|
|
58
|
+
if root is None:
|
|
59
|
+
return None, None, None
|
|
60
|
+
active = read_active_dispatch_for_project(root)
|
|
61
|
+
if active is None:
|
|
62
|
+
return None, None, None
|
|
63
|
+
return active, Path(active.project_root), Path(active.state_dir)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _tool_failed(payload: dict) -> bool:
|
|
67
|
+
response = payload.get("tool_response") or payload.get("tool_output")
|
|
68
|
+
if isinstance(response, dict) and response.get("is_error"):
|
|
69
|
+
return True
|
|
70
|
+
text = coerce_to_text(response).lower()
|
|
71
|
+
return "error:" in text or "failed" in text
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _coerce_content_text(content) -> str:
|
|
75
|
+
if isinstance(content, str):
|
|
76
|
+
return content
|
|
77
|
+
if isinstance(content, list):
|
|
78
|
+
parts: list[str] = []
|
|
79
|
+
for item in content:
|
|
80
|
+
if isinstance(item, dict):
|
|
81
|
+
raw = item.get("text") or item.get("content") or ""
|
|
82
|
+
parts.append(str(raw))
|
|
83
|
+
else:
|
|
84
|
+
parts.append(str(item))
|
|
85
|
+
return "\n".join(parts)
|
|
86
|
+
return str(content or "")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _recent_user_texts(transcript_path: str | None) -> tuple[str, ...]:
|
|
90
|
+
if not transcript_path:
|
|
91
|
+
return ()
|
|
92
|
+
path = Path(transcript_path)
|
|
93
|
+
try:
|
|
94
|
+
lines = path.read_text().splitlines()
|
|
95
|
+
except OSError:
|
|
96
|
+
return ()
|
|
97
|
+
out: list[str] = []
|
|
98
|
+
for line in reversed(lines[-200:]):
|
|
99
|
+
if not line.strip():
|
|
100
|
+
continue
|
|
101
|
+
try:
|
|
102
|
+
event = json.loads(line)
|
|
103
|
+
except json.JSONDecodeError:
|
|
104
|
+
continue
|
|
105
|
+
if event.get("type") != "user":
|
|
106
|
+
continue
|
|
107
|
+
message = event.get("message")
|
|
108
|
+
if not isinstance(message, dict) or message.get("role") != "user":
|
|
109
|
+
continue
|
|
110
|
+
out.append(_coerce_content_text(message.get("content")))
|
|
111
|
+
if len(out) >= 20:
|
|
112
|
+
break
|
|
113
|
+
return tuple(out)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _looks_like_dispatch_command(payload: dict) -> bool:
|
|
117
|
+
for text in _recent_user_texts(payload.get("transcript_path")):
|
|
118
|
+
if "<command-name>/dispatch</command-name>" in text:
|
|
119
|
+
return True
|
|
120
|
+
if (
|
|
121
|
+
"ARGUMENTS:" in text
|
|
122
|
+
and "When the Principal invokes `/dispatch" in text
|
|
123
|
+
and "Coordinator protocol" in text
|
|
124
|
+
):
|
|
125
|
+
return True
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _is_allowed_coordinator_write(path: Path, root: Path) -> bool:
|
|
130
|
+
try:
|
|
131
|
+
rel = path.relative_to(root.resolve(strict=False))
|
|
132
|
+
except ValueError:
|
|
133
|
+
return False
|
|
134
|
+
parts = rel.parts
|
|
135
|
+
if len(parts) >= 3 and parts[:2] == (".metaensemble", "manifests"):
|
|
136
|
+
return True
|
|
137
|
+
report_root = root / report_root_for_project(root)
|
|
138
|
+
return path.suffix.lower() == ".md" and is_within(
|
|
139
|
+
path.resolve(strict=False),
|
|
140
|
+
report_root.resolve(strict=False),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _claude_project_state_dirs(cwd: Path, root: Path) -> tuple[Path, ...]:
|
|
145
|
+
"""Claude Code runtime state dirs that belong to this active project.
|
|
146
|
+
|
|
147
|
+
The boundary guard should not block Claude Code from updating its own
|
|
148
|
+
per-project transcript/memory state. Scope this carve-out to the current
|
|
149
|
+
cwd and nearest MetaEnsemble root rather than all of `~/.claude/projects`.
|
|
150
|
+
"""
|
|
151
|
+
base = Path.home() / ".claude" / "projects"
|
|
152
|
+
dirs = [
|
|
153
|
+
base / _encode_cwd_for_runtime(cwd),
|
|
154
|
+
base / _encode_cwd_for_runtime(root),
|
|
155
|
+
]
|
|
156
|
+
out: list[Path] = []
|
|
157
|
+
seen: set[str] = set()
|
|
158
|
+
for d in dirs:
|
|
159
|
+
resolved = d.resolve(strict=False)
|
|
160
|
+
key = str(resolved)
|
|
161
|
+
if key not in seen:
|
|
162
|
+
seen.add(key)
|
|
163
|
+
out.append(resolved)
|
|
164
|
+
return tuple(out)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _is_allowed_claude_project_state_write(path: Path, cwd: Path, root: Path) -> bool:
|
|
168
|
+
resolved = path.resolve(strict=False)
|
|
169
|
+
return any(is_within(resolved, d) for d in _claude_project_state_dirs(cwd, root))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _emit_boundary_block(raw: str, resolved: Path, root: Path) -> int:
|
|
173
|
+
emit({
|
|
174
|
+
"continue": False,
|
|
175
|
+
"stopReason": (
|
|
176
|
+
"MetaEnsemble project boundary guard blocked a file edit outside "
|
|
177
|
+
f"the active project root.\n\nRequested path: {raw}\n"
|
|
178
|
+
f"Resolved path: {resolved}\nProject root: {root}\n\n"
|
|
179
|
+
"Run MetaEnsemble from the parent project root, or dispatch a task "
|
|
180
|
+
"whose file paths stay inside the installed project."
|
|
181
|
+
),
|
|
182
|
+
})
|
|
183
|
+
return 2
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _emit_overlap_ownership_block(path: Path, root: Path) -> int:
|
|
187
|
+
surface = protected_overlap_for_path(root, path)
|
|
188
|
+
metaensemble_surface = surface.metaensemble_surface if surface else "MetaEnsemble Ledger"
|
|
189
|
+
emit({
|
|
190
|
+
"continue": False,
|
|
191
|
+
"stopReason": (
|
|
192
|
+
"MetaEnsemble overlap ownership guard blocked a file edit to a "
|
|
193
|
+
"project-maintained work-record surface assigned to MetaEnsemble.\n\n"
|
|
194
|
+
f"Requested path: {path}\n"
|
|
195
|
+
f"Project root: {root}\n"
|
|
196
|
+
f"Overlap category: {surface.category if surface else 'unknown'}\n"
|
|
197
|
+
f"Project surface: {surface.project_surface if surface else path}\n"
|
|
198
|
+
f"MetaEnsemble surface: {metaensemble_surface}\n\n"
|
|
199
|
+
"Change `.metaensemble/install-decisions.yaml` to "
|
|
200
|
+
"`action: project_owned` or `action: dual` for this overlap if "
|
|
201
|
+
"the manual document should still be maintained."
|
|
202
|
+
),
|
|
203
|
+
})
|
|
204
|
+
return 2
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _emit_direct_dispatch_edit_block(tool_name: str, root: Path) -> int:
|
|
208
|
+
emit({
|
|
209
|
+
"continue": False,
|
|
210
|
+
"stopReason": (
|
|
211
|
+
"MetaEnsemble dispatch protocol blocked a direct file edit. "
|
|
212
|
+
f"`{tool_name}` was invoked while handling `/dispatch`, but no "
|
|
213
|
+
"active Task/Agent Run was present.\n\n"
|
|
214
|
+
f"Project root: {root}\n\n"
|
|
215
|
+
"The Coordinator must spawn an Executor via Task/Agent so the "
|
|
216
|
+
"Run is recorded in the Ledger with files touched and tool use."
|
|
217
|
+
),
|
|
218
|
+
})
|
|
219
|
+
return 2
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def run() -> int:
|
|
223
|
+
payload = read_input()
|
|
224
|
+
tool_name = payload.get("tool_name") or ""
|
|
225
|
+
tool_input = payload.get("tool_input") or {}
|
|
226
|
+
paths = resolve_tool_paths(tool_name, tool_input)
|
|
227
|
+
if not paths:
|
|
228
|
+
emit({"continue": True})
|
|
229
|
+
return 0
|
|
230
|
+
|
|
231
|
+
session_id = payload.get("session_id") or ""
|
|
232
|
+
agent_id = payload.get("agent_id")
|
|
233
|
+
hook_event = payload.get("hook_event_name") or ""
|
|
234
|
+
cwd = _payload_cwd(payload)
|
|
235
|
+
installed_root = nearest_project_root(cwd)
|
|
236
|
+
active, project_root, state_dir = _active_context(session_id, cwd, agent_id)
|
|
237
|
+
if project_root is None or state_dir is None:
|
|
238
|
+
if (
|
|
239
|
+
hook_event == "PreToolUse"
|
|
240
|
+
and installed_root is not None
|
|
241
|
+
and _looks_like_dispatch_command(payload)
|
|
242
|
+
):
|
|
243
|
+
resolved = [
|
|
244
|
+
resolve_against_cwd(raw, cwd)
|
|
245
|
+
for raw in paths
|
|
246
|
+
]
|
|
247
|
+
if all(
|
|
248
|
+
_is_allowed_coordinator_write(p, installed_root)
|
|
249
|
+
or _is_allowed_claude_project_state_write(p, cwd, installed_root)
|
|
250
|
+
for p in resolved
|
|
251
|
+
):
|
|
252
|
+
emit({"continue": True})
|
|
253
|
+
return 0
|
|
254
|
+
return _emit_direct_dispatch_edit_block(tool_name, installed_root)
|
|
255
|
+
emit({"continue": True})
|
|
256
|
+
return 0
|
|
257
|
+
|
|
258
|
+
resolved_paths: list[tuple[str, Path]] = []
|
|
259
|
+
for raw in paths:
|
|
260
|
+
resolved = resolve_against_cwd(raw, cwd)
|
|
261
|
+
if not is_within(resolved, project_root):
|
|
262
|
+
if _is_allowed_claude_project_state_write(resolved, cwd, project_root):
|
|
263
|
+
continue
|
|
264
|
+
return _emit_boundary_block(raw, resolved, project_root)
|
|
265
|
+
if (
|
|
266
|
+
hook_event == "PreToolUse"
|
|
267
|
+
and protected_overlap_for_path(project_root, resolved) is not None
|
|
268
|
+
):
|
|
269
|
+
return _emit_overlap_ownership_block(resolved, project_root)
|
|
270
|
+
resolved_paths.append((raw, resolved))
|
|
271
|
+
|
|
272
|
+
if hook_event == "PostToolUse" and not _tool_failed(payload):
|
|
273
|
+
for _raw, resolved in resolved_paths:
|
|
274
|
+
try:
|
|
275
|
+
rel = str(resolved.relative_to(project_root.resolve(strict=False)))
|
|
276
|
+
except ValueError:
|
|
277
|
+
rel = None
|
|
278
|
+
try:
|
|
279
|
+
append_file_event(
|
|
280
|
+
state_dir,
|
|
281
|
+
FileToolEvent(
|
|
282
|
+
ts=datetime.now(timezone.utc).isoformat(),
|
|
283
|
+
session_id=session_id,
|
|
284
|
+
run_id=active.run_id if active else None,
|
|
285
|
+
tool_name=tool_name,
|
|
286
|
+
path=str(resolved),
|
|
287
|
+
rel_path=rel,
|
|
288
|
+
cwd=str(cwd),
|
|
289
|
+
),
|
|
290
|
+
)
|
|
291
|
+
except Exception as exc:
|
|
292
|
+
log_error("file-event-record-failed", str(exc), {
|
|
293
|
+
"tool_name": tool_name,
|
|
294
|
+
"path": str(resolved),
|
|
295
|
+
"session_id": session_id,
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
emit({"continue": True})
|
|
299
|
+
return 0
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if __name__ == "__main__":
|
|
303
|
+
sys.exit(run())
|