leos-agent 7.0.0 → 10.2.0
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.
- package/README.md +544 -28
- package/commands/handoff.md +11 -0
- package/commands/handon.md +10 -0
- package/commands/review-pr.md +9 -0
- package/commands-claude/watch-review.md +9 -0
- package/index.js +12 -0
- package/package.json +29 -14
- package/payload/codex-agents/leo-executor.toml +36 -0
- package/payload/codex-agents/leo-runner.toml +28 -0
- package/rules/preferences.md +97 -0
- package/scripts/check.py +244 -0
- package/scripts/ghreview.py +17 -3
- package/scripts/handoff.py +183 -0
- package/scripts/leo-install.py +509 -0
- package/scripts/measure_context.py +113 -0
- package/scripts/publish-npm.py +138 -0
- package/scripts/watch_review.py +169 -0
- package/skills/doctor/SKILL.md +73 -115
- package/skills/doctor/agents/openai.yaml +5 -0
- package/skills/handoff/SKILL.md +99 -0
- package/skills/handoff/agents/openai.yaml +5 -0
- package/skills/handon/SKILL.md +61 -0
- package/skills/install/SKILL.md +79 -0
- package/skills/install/agents/openai.yaml +5 -0
- package/skills/review-pr/SKILL.md +59 -318
- package/skills/review-pr/reference/lenses.md +67 -0
- package/skills/review-pr/reference/procedure.md +348 -0
- package/skills-claude/attach-pr/SKILL.md +178 -0
- package/skills-claude/watch-review/SKILL.md +91 -0
- package/adapters/cursor/agents/executor.md +0 -17
- package/adapters/cursor/agents/expert.md +0 -70
- package/adapters/cursor/agents/explore.md +0 -16
- package/adapters/cursor/agents/implementer.md +0 -18
- package/adapters/cursor/agents/investigator.md +0 -18
- package/adapters/cursor/agents/planner.md +0 -28
- package/adapters/cursor/agents/review-lens.md +0 -22
- package/adapters/cursor/agents/reviewer.md +0 -34
- package/adapters/opencode/agents.json +0 -105
- package/adapters/opencode/plugin.js +0 -576
- package/config/MCP_PINS.md +0 -17
- package/config/models.json +0 -676
- package/hooks/bash-guard.py +0 -583
- package/hooks/cursor-guard.py +0 -84
- package/hooks/hooks-cursor.json +0 -11
- package/hooks/hooks.json +0 -20
- package/hooks/session-start.py +0 -148
- package/roles/executor.md +0 -15
- package/roles/expert.md +0 -67
- package/roles/explore.md +0 -13
- package/roles/implementer.md +0 -16
- package/roles/investigator.md +0 -15
- package/roles/planner.md +0 -25
- package/roles/review-lens.md +0 -20
- package/roles/reviewer.md +0 -31
- package/scripts/doctor.py +0 -520
- package/scripts/jsonc_bridge.cjs +0 -23
- package/scripts/memory.py +0 -744
- package/scripts/render_adapters.py +0 -508
- package/scripts/setup.py +0 -1753
- package/settings.json +0 -7
- package/skills/.gitkeep +0 -0
- package/skills/brainstorming/SKILL.md +0 -111
- package/skills/debugging/SKILL.md +0 -100
- package/skills/delegation/SKILL.md +0 -143
- package/skills/executing-plans/SKILL.md +0 -117
- package/skills/finishing-a-branch/SKILL.md +0 -125
- package/skills/freshness/SKILL.md +0 -131
- package/skills/memory/SKILL.md +0 -154
- package/skills/resolve-ticket/SKILL.md +0 -275
- package/skills/setup/SKILL.md +0 -199
- package/skills/setup/agents/openai.yaml +0 -5
- package/skills/test-first/SKILL.md +0 -92
- package/skills/using-leo/SKILL.md +0 -101
- package/skills/using-leo/references/claude-mapping.md +0 -33
- package/skills/using-leo/references/codex-mapping.md +0 -33
- package/skills/using-leo/references/cursor-mapping.md +0 -35
- package/skills/using-leo/references/hermes-mapping.md +0 -37
- package/skills/using-leo/references/opencode-mapping.md +0 -39
- package/skills/verification/SKILL.md +0 -110
- package/skills/visual-verification/SKILL.md +0 -115
- package/skills/watch-review/SKILL.md +0 -128
- package/skills/watch-review/agents/openai.yaml +0 -5
- package/skills/worktrees/SKILL.md +0 -131
- package/skills/writing-plans/SKILL.md +0 -97
- package/skills/writing-skills/SKILL.md +0 -141
- package/vendor/jsonc-parser-3.3.1/LICENSE.md +0 -21
- package/vendor/jsonc-parser-3.3.1/README.md +0 -26
- package/vendor/jsonc-parser-3.3.1/lib/umd/impl/edit.js +0 -201
- package/vendor/jsonc-parser-3.3.1/lib/umd/impl/format.js +0 -275
- package/vendor/jsonc-parser-3.3.1/lib/umd/impl/parser.js +0 -682
- package/vendor/jsonc-parser-3.3.1/lib/umd/impl/scanner.js +0 -456
- package/vendor/jsonc-parser-3.3.1/lib/umd/impl/string-intern.js +0 -42
- package/vendor/jsonc-parser-3.3.1/lib/umd/main.d.ts +0 -351
- package/vendor/jsonc-parser-3.3.1/lib/umd/main.js +0 -194
- package/vendor/jsonc-parser-3.3.1/package.json +0 -37
- package/workflows/cost-tiered-fix.js +0 -287
package/scripts/doctor.py
DELETED
|
@@ -1,520 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""doctor: report how Leo's Agent is wired on this machine.
|
|
3
|
-
|
|
4
|
-
This script answers only what disk and environment can prove. It deliberately
|
|
5
|
-
does NOT claim the policy reached the model: a hook can be present, executable,
|
|
6
|
-
and correctly listed, and still have failed open this session. Only the running
|
|
7
|
-
agent can see its own context, so leo:doctor pairs this output with three
|
|
8
|
-
questions the model answers itself.
|
|
9
|
-
|
|
10
|
-
Most breadcrumb logs are reported as history with unknown provenance, never as
|
|
11
|
-
a verdict about this session. Some legacy entries carry no timestamps and the
|
|
12
|
-
test suite drives failure paths deliberately, so "the log has errors" usually
|
|
13
|
-
proves nothing on its own. `opencode-skills.log` is the capability exception:
|
|
14
|
-
its presence records a namespaced-skill registration failure and is reported
|
|
15
|
-
as degraded until it is intentionally cleared after repair.
|
|
16
|
-
|
|
17
|
-
doctor.py human-readable report
|
|
18
|
-
doctor.py --json the same facts as JSON
|
|
19
|
-
doctor.py --harness <name> state the harness instead of detecting it
|
|
20
|
-
|
|
21
|
-
Exit code is nonzero when required payload/bootstrap artifacts are missing or
|
|
22
|
-
invalid, or the running Python is unsupported. Degraded historical warnings
|
|
23
|
-
remain report-only.
|
|
24
|
-
"""
|
|
25
|
-
import importlib.util
|
|
26
|
-
import json
|
|
27
|
-
import os
|
|
28
|
-
import re
|
|
29
|
-
import sys
|
|
30
|
-
|
|
31
|
-
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
32
|
-
PAYLOAD = os.path.dirname(_HERE)
|
|
33
|
-
TIERS = ("fable", "opus", "sonnet", "haiku")
|
|
34
|
-
MIN_PYTHON = (3, 9)
|
|
35
|
-
EXPECTED_HARNESSES = {"claude", "codex", "cursor", "hermes", "opencode"}
|
|
36
|
-
SEMVER_RE = re.compile(
|
|
37
|
-
r"^(0|[1-9]\d*)\."
|
|
38
|
-
r"(0|[1-9]\d*)\."
|
|
39
|
-
r"(0|[1-9]\d*)"
|
|
40
|
-
r"(?:-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\."
|
|
41
|
-
r"(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*)?"
|
|
42
|
-
r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$"
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
HARNESS_ENV = ("CURSOR_PLUGIN_ROOT", "CURSOR_VERSION", "PLUGIN_ROOT", "CLAUDE_PLUGIN_ROOT")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _known_harnesses():
|
|
50
|
-
models = _read_json(os.path.join(PAYLOAD, "config", "models.json")) or {}
|
|
51
|
-
harnesses = models.get("harnesses") if isinstance(models, dict) else None
|
|
52
|
-
return set(harnesses) if isinstance(harnesses, dict) else set()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _detect_harness(argv=()):
|
|
56
|
-
"""Three signals, most explicit first; never a guess.
|
|
57
|
-
|
|
58
|
-
The env-var rules still live in hooks/session-start.py and are reused
|
|
59
|
-
rather than re-derived, because their ordering carries two subtleties a
|
|
60
|
-
second implementation gets wrong: Cursor must be tested first because it
|
|
61
|
-
sets more than one variable, and the absence of CLAUDE_PLUGIN_ROOT is not
|
|
62
|
-
a Codex signal. The filename is hyphenated and therefore not importable by
|
|
63
|
-
name, so it loads by path — the same technique hooks/cursor-guard.py uses.
|
|
64
|
-
|
|
65
|
-
What is new is that the delegation is *gated*. That function's final branch
|
|
66
|
-
returns "claude" as a default, which is right for a hook that only ever
|
|
67
|
-
runs on the three hook harnesses. doctor ships to five. Hermes and OpenCode
|
|
68
|
-
run no hook and export no plugin-root variable, so doctor inherited the
|
|
69
|
-
default and reported `claude` on both — printing Claude's tier table and
|
|
70
|
-
listing four Claude-only skills as available on harnesses that have none of
|
|
71
|
-
them. Absence of every marker means unknown, and unknown is reported.
|
|
72
|
-
"""
|
|
73
|
-
known = _known_harnesses()
|
|
74
|
-
|
|
75
|
-
# 1. Stated outright. leo:doctor tells the agent to pass the harness it can
|
|
76
|
-
# read off its own mapping heading. Validated against the config, so a
|
|
77
|
-
# typo degrades to detection rather than inventing a harness.
|
|
78
|
-
argv = list(argv)
|
|
79
|
-
for index, arg in enumerate(argv):
|
|
80
|
-
name = None
|
|
81
|
-
if arg == "--harness" and index + 1 < len(argv):
|
|
82
|
-
name = argv[index + 1]
|
|
83
|
-
elif arg.startswith("--harness="):
|
|
84
|
-
name = arg.split("=", 1)[1]
|
|
85
|
-
if name and name in known:
|
|
86
|
-
return name, "--harness"
|
|
87
|
-
|
|
88
|
-
# 2. A positive signal the two hookless harnesses set for themselves at
|
|
89
|
-
# registration (__init__.py and adapters/opencode/plugin.js).
|
|
90
|
-
declared = os.environ.get("LEOS_AGENT_HARNESS")
|
|
91
|
-
if declared and declared in known:
|
|
92
|
-
return declared, "env (LEOS_AGENT_HARNESS)"
|
|
93
|
-
|
|
94
|
-
# 3. The env-var rules, unchanged — but only when a marker actually exists.
|
|
95
|
-
if not any(os.environ.get(var) for var in HARNESS_ENV):
|
|
96
|
-
return "unknown", "no signal"
|
|
97
|
-
|
|
98
|
-
path = os.path.join(PAYLOAD, "hooks", "session-start.py")
|
|
99
|
-
try:
|
|
100
|
-
spec = importlib.util.spec_from_file_location("leo_session_start", path)
|
|
101
|
-
module = importlib.util.module_from_spec(spec)
|
|
102
|
-
spec.loader.exec_module(module)
|
|
103
|
-
return module._detect_harness(), "hooks/session-start.py"
|
|
104
|
-
except Exception:
|
|
105
|
-
if os.environ.get("CURSOR_PLUGIN_ROOT") or os.environ.get("CURSOR_VERSION"):
|
|
106
|
-
return "cursor", "env"
|
|
107
|
-
if os.environ.get("PLUGIN_ROOT"):
|
|
108
|
-
return "codex", "env"
|
|
109
|
-
return "claude", "env"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def _read_json(path):
|
|
113
|
-
try:
|
|
114
|
-
with open(path, encoding="utf-8") as fh:
|
|
115
|
-
return json.load(fh)
|
|
116
|
-
except Exception:
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def _manifest_valid(manifest):
|
|
121
|
-
"""Validate the required portable manifest fields doctor reports."""
|
|
122
|
-
if not isinstance(manifest, dict):
|
|
123
|
-
return False
|
|
124
|
-
for key in ("name", "description", "homepage", "repository", "license"):
|
|
125
|
-
if not isinstance(manifest.get(key), str) or not manifest[key].strip():
|
|
126
|
-
return False
|
|
127
|
-
version = manifest.get("version")
|
|
128
|
-
if not isinstance(version, str) or SEMVER_RE.fullmatch(version) is None:
|
|
129
|
-
return False
|
|
130
|
-
author = manifest.get("author")
|
|
131
|
-
if not isinstance(author, dict) or not isinstance(author.get("name"), str) \
|
|
132
|
-
or not author["name"].strip():
|
|
133
|
-
return False
|
|
134
|
-
skills = manifest.get("skills")
|
|
135
|
-
return (isinstance(skills, list) and bool(skills) and
|
|
136
|
-
all(isinstance(item, str) and item.strip() for item in skills))
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def _models_valid(models):
|
|
140
|
-
"""Apply structural checks and the canonical renderer validation.
|
|
141
|
-
|
|
142
|
-
The renderer owns cross-field coherence, while these checks reject
|
|
143
|
-
skeletal documents that happen not to exercise one of its relationships.
|
|
144
|
-
Doctor must never call either shape healthy merely because it contains a
|
|
145
|
-
``harnesses`` object.
|
|
146
|
-
"""
|
|
147
|
-
if not isinstance(models, dict) or models.get("schemaVersion") != 4:
|
|
148
|
-
return False
|
|
149
|
-
for key in ("roles", "harnesses", "skills", "mcp", "memoryTarget", "visual"):
|
|
150
|
-
if not isinstance(models.get(key), dict) or not models[key]:
|
|
151
|
-
return False
|
|
152
|
-
capabilities = models.get("capabilities")
|
|
153
|
-
if not isinstance(capabilities, list) or not capabilities:
|
|
154
|
-
return False
|
|
155
|
-
harnesses = models["harnesses"]
|
|
156
|
-
if set(harnesses) != EXPECTED_HARNESSES:
|
|
157
|
-
return False
|
|
158
|
-
for rows in harnesses.values():
|
|
159
|
-
if not isinstance(rows, dict):
|
|
160
|
-
return False
|
|
161
|
-
for tier in TIERS:
|
|
162
|
-
mapping = rows.get(tier)
|
|
163
|
-
if (not isinstance(mapping, dict) or
|
|
164
|
-
not isinstance(mapping.get("model"), str) or
|
|
165
|
-
not mapping["model"].strip()):
|
|
166
|
-
return False
|
|
167
|
-
if "effort" in mapping and (not isinstance(mapping["effort"], str) or
|
|
168
|
-
not mapping["effort"].strip()):
|
|
169
|
-
return False
|
|
170
|
-
for spec in models["roles"].values():
|
|
171
|
-
if (not isinstance(spec, dict) or spec.get("tier") not in TIERS or
|
|
172
|
-
spec.get("access") not in ("read-only", "write")):
|
|
173
|
-
return False
|
|
174
|
-
for row in capabilities:
|
|
175
|
-
if (not isinstance(row, dict) or not isinstance(row.get("key"), str) or
|
|
176
|
-
not isinstance(row.get("modes"), list) or not row["modes"] or
|
|
177
|
-
not isinstance(row.get("values"), dict)):
|
|
178
|
-
return False
|
|
179
|
-
|
|
180
|
-
validator_path = os.path.join(PAYLOAD, "scripts", "render_adapters.py")
|
|
181
|
-
try:
|
|
182
|
-
spec = importlib.util.spec_from_file_location("leo_doctor_models_validator",
|
|
183
|
-
validator_path)
|
|
184
|
-
validator = importlib.util.module_from_spec(spec)
|
|
185
|
-
spec.loader.exec_module(validator)
|
|
186
|
-
validator._validate(models)
|
|
187
|
-
except Exception:
|
|
188
|
-
return False
|
|
189
|
-
return True
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def _local_root():
|
|
193
|
-
return os.environ.get("LEOS_AGENT_LOCAL_PATH") or os.path.join(
|
|
194
|
-
os.path.expanduser("~"), ".leos-agent-local"
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _memory_report():
|
|
199
|
-
try:
|
|
200
|
-
sys.path.insert(0, _HERE)
|
|
201
|
-
import memory
|
|
202
|
-
|
|
203
|
-
root = memory.memory_root()
|
|
204
|
-
hermes_enabled = memory.hermes_enabled()
|
|
205
|
-
hermes_target = os.path.join(memory.hermes_home(), "SOUL.md")
|
|
206
|
-
hermes_projection = {
|
|
207
|
-
"enabled": hermes_enabled,
|
|
208
|
-
"path": hermes_target,
|
|
209
|
-
"status": (
|
|
210
|
-
"disabled" if not hermes_enabled else
|
|
211
|
-
"projected" if (os.path.isfile(hermes_target) and
|
|
212
|
-
memory.BEGIN in _slurp(hermes_target)) else
|
|
213
|
-
"not projected"
|
|
214
|
-
),
|
|
215
|
-
}
|
|
216
|
-
if not os.path.isdir(root):
|
|
217
|
-
return {"store": root, "facts": 0, "present": False, "targets": [],
|
|
218
|
-
"hermes_projection": hermes_projection}
|
|
219
|
-
index = memory._load_index() or {"facts": []}
|
|
220
|
-
targets = [
|
|
221
|
-
{"harness": h, "path": f, "present": os.path.exists(f),
|
|
222
|
-
"projected": os.path.exists(f) and memory.BEGIN in _slurp(f)}
|
|
223
|
-
for h, gate, f, _, _ in memory.projection_targets()
|
|
224
|
-
if os.path.isdir(gate)
|
|
225
|
-
]
|
|
226
|
-
return {"store": root, "facts": len(index["facts"]), "present": True,
|
|
227
|
-
"targets": targets, "hermes_projection": hermes_projection}
|
|
228
|
-
except Exception as exc:
|
|
229
|
-
return {"store": _local_root(), "error": f"{type(exc).__name__}: {exc}"}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def _slurp(path):
|
|
233
|
-
try:
|
|
234
|
-
with open(path, encoding="utf-8", errors="replace") as fh:
|
|
235
|
-
return fh.read()
|
|
236
|
-
except OSError:
|
|
237
|
-
return ""
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def _breadcrumbs():
|
|
241
|
-
out = {}
|
|
242
|
-
for name in ("session-start.log", "hermes-policy.log", "opencode-guard.log",
|
|
243
|
-
"opencode-skills.log"):
|
|
244
|
-
path = os.path.join(_local_root(), name)
|
|
245
|
-
if not os.path.exists(path):
|
|
246
|
-
continue
|
|
247
|
-
lines = [l for l in _slurp(path).splitlines() if l.strip()]
|
|
248
|
-
if lines:
|
|
249
|
-
out[name] = {"entries": len(lines), "newest": lines[-1][:120]}
|
|
250
|
-
return out
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
def _contains(path, needle):
|
|
254
|
-
return needle in _slurp(path)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _hook_manifest_valid(data, harness):
|
|
258
|
-
"""Validate the session-start registration, not a stray filename mention."""
|
|
259
|
-
if not isinstance(data, dict) or not isinstance(data.get("hooks"), dict):
|
|
260
|
-
return False
|
|
261
|
-
event = "sessionStart" if harness == "cursor" else "SessionStart"
|
|
262
|
-
groups = data["hooks"].get(event)
|
|
263
|
-
if not isinstance(groups, list):
|
|
264
|
-
return False
|
|
265
|
-
hooks = groups if harness == "cursor" else [
|
|
266
|
-
hook
|
|
267
|
-
for group in groups if isinstance(group, dict)
|
|
268
|
-
for hook in (group.get("hooks") or []) if isinstance(hook, dict)
|
|
269
|
-
]
|
|
270
|
-
return any(
|
|
271
|
-
isinstance(hook, dict)
|
|
272
|
-
and isinstance(hook.get("command"), str)
|
|
273
|
-
and "hooks/session-start.py" in hook["command"]
|
|
274
|
-
for hook in hooks
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def _bootstrap(harness):
|
|
279
|
-
"""Describe the harness's actual policy delivery mechanism.
|
|
280
|
-
|
|
281
|
-
Presence is intentionally a payload check, not a claim that the host ran
|
|
282
|
-
it. Codex hook trust is visible only in its `/hooks` UI, and neither
|
|
283
|
-
Hermes nor OpenCode exposes a host-side registration ledger to inspect.
|
|
284
|
-
"""
|
|
285
|
-
hook = os.path.join(PAYLOAD, "hooks", "session-start.py")
|
|
286
|
-
if harness in ("claude", "codex", "cursor"):
|
|
287
|
-
manifest = os.path.join(
|
|
288
|
-
PAYLOAD, "hooks", "hooks-cursor.json" if harness == "cursor" else "hooks.json"
|
|
289
|
-
)
|
|
290
|
-
manifest_data = _read_json(manifest)
|
|
291
|
-
data = {"kind": "session hook", "hook": hook,
|
|
292
|
-
"present": os.path.isfile(hook),
|
|
293
|
-
"executable": os.access(hook, os.X_OK),
|
|
294
|
-
"manifest": manifest,
|
|
295
|
-
"manifest_valid": _hook_manifest_valid(manifest_data, harness),
|
|
296
|
-
"required": ("present", "executable", "manifest_valid")}
|
|
297
|
-
if harness == "codex":
|
|
298
|
-
data["trust_instruction"] = (
|
|
299
|
-
"Codex cannot be verified from disk; run /hooks and confirm this plugin is trusted."
|
|
300
|
-
)
|
|
301
|
-
return data
|
|
302
|
-
if harness == "opencode":
|
|
303
|
-
plugin = os.path.join(PAYLOAD, "adapters", "opencode", "plugin.js")
|
|
304
|
-
return {"kind": "config.instructions", "plugin": plugin,
|
|
305
|
-
"present": os.path.isfile(plugin),
|
|
306
|
-
"instructions": _contains(plugin, "config.instructions"),
|
|
307
|
-
"required": ("present", "instructions")}
|
|
308
|
-
if harness == "hermes":
|
|
309
|
-
entry = os.path.join(os.path.dirname(PAYLOAD), "..", "__init__.py")
|
|
310
|
-
entry = os.path.abspath(entry)
|
|
311
|
-
return {"kind": "registration + first-tool-result fallback", "entrypoint": entry,
|
|
312
|
-
"present": os.path.isfile(entry),
|
|
313
|
-
"registration": _contains(entry, "def register(ctx)"),
|
|
314
|
-
"fallback": _contains(
|
|
315
|
-
entry, 'register_hook("transform_tool_result", _on_transform_tool_result)'
|
|
316
|
-
),
|
|
317
|
-
"required": ("present", "registration", "fallback")}
|
|
318
|
-
return {"kind": "unknown", "present": True, "required": ()}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def _bootstrap_valid(bootstrap):
|
|
322
|
-
return all(bootstrap.get(key) for key in bootstrap.get("required", ("present",)))
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def _skills():
|
|
326
|
-
shipped = {}
|
|
327
|
-
for root in ("skills", "skills-claude"):
|
|
328
|
-
directory = os.path.join(PAYLOAD, root)
|
|
329
|
-
shipped[root] = sorted(
|
|
330
|
-
name for name in os.listdir(directory)
|
|
331
|
-
if os.path.isfile(os.path.join(directory, name, "SKILL.md"))
|
|
332
|
-
) if os.path.isdir(directory) else []
|
|
333
|
-
return shipped
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def collect(argv=()):
|
|
337
|
-
harness, source = _detect_harness(argv)
|
|
338
|
-
manifest_path = os.path.join(PAYLOAD, ".claude-plugin", "plugin.json")
|
|
339
|
-
models_path = os.path.join(PAYLOAD, "config", "models.json")
|
|
340
|
-
manifest_raw = _read_json(manifest_path)
|
|
341
|
-
models_raw = _read_json(models_path)
|
|
342
|
-
manifest = manifest_raw if isinstance(manifest_raw, dict) else {}
|
|
343
|
-
models = models_raw if isinstance(models_raw, dict) else {}
|
|
344
|
-
harnesses = models.get("harnesses")
|
|
345
|
-
harnesses = harnesses if isinstance(harnesses, dict) else {}
|
|
346
|
-
config = harnesses.get(harness)
|
|
347
|
-
config = config if isinstance(config, dict) else {}
|
|
348
|
-
bootstrap = _bootstrap(harness)
|
|
349
|
-
local = _local_root()
|
|
350
|
-
skills = _skills()
|
|
351
|
-
skill_config = models.get("skills")
|
|
352
|
-
skill_config = skill_config if isinstance(skill_config, dict) else {}
|
|
353
|
-
claude_only_raw = skill_config.get("claudeOnly")
|
|
354
|
-
claude_only = ({item for item in claude_only_raw if isinstance(item, str)}
|
|
355
|
-
if isinstance(claude_only_raw, list) else set())
|
|
356
|
-
exclusions = skill_config.get("exclude")
|
|
357
|
-
exclusions = exclusions if isinstance(exclusions, dict) else {}
|
|
358
|
-
excluded_raw = exclusions.get(harness)
|
|
359
|
-
excluded = ({item for item in excluded_raw if isinstance(item, str)}
|
|
360
|
-
if isinstance(excluded_raw, list) else set())
|
|
361
|
-
|
|
362
|
-
registered = [n for n in skills["skills"] if n not in excluded]
|
|
363
|
-
if harness == "claude":
|
|
364
|
-
registered += skills["skills-claude"]
|
|
365
|
-
tiers = {}
|
|
366
|
-
for tier in TIERS:
|
|
367
|
-
mapping = config.get(tier)
|
|
368
|
-
mapping = mapping if isinstance(mapping, dict) else {}
|
|
369
|
-
tiers[tier] = {"model": mapping.get("model"), "effort": mapping.get("effort")}
|
|
370
|
-
manifest_valid = _manifest_valid(manifest)
|
|
371
|
-
models_valid = _models_valid(models)
|
|
372
|
-
tier_mapping_valid = harness == "unknown" or all(
|
|
373
|
-
isinstance(value["model"], str) and bool(value["model"])
|
|
374
|
-
for value in tiers.values()
|
|
375
|
-
)
|
|
376
|
-
python_report = {
|
|
377
|
-
"running": ".".join(str(v) for v in sys.version_info[:3]),
|
|
378
|
-
"minimum": ".".join(str(v) for v in MIN_PYTHON),
|
|
379
|
-
"supported": sys.version_info[:2] >= MIN_PYTHON,
|
|
380
|
-
}
|
|
381
|
-
breadcrumbs = _breadcrumbs()
|
|
382
|
-
degraded_reasons = []
|
|
383
|
-
if harness == "unknown":
|
|
384
|
-
degraded_reasons.append("unknown harness")
|
|
385
|
-
if "opencode-skills.log" in breadcrumbs:
|
|
386
|
-
degraded_reasons.append("OpenCode namespaced skills previously failed to register")
|
|
387
|
-
if not python_report["supported"]:
|
|
388
|
-
degraded_reasons.append("unsupported Python")
|
|
389
|
-
if not (manifest_valid and models_valid and tier_mapping_valid):
|
|
390
|
-
degraded_reasons.append("invalid required payload")
|
|
391
|
-
if harness != "unknown" and not _bootstrap_valid(bootstrap):
|
|
392
|
-
degraded_reasons.append("missing or invalid required bootstrap")
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
"harness": {"value": harness, "source": source},
|
|
396
|
-
"payload": {
|
|
397
|
-
"path": PAYLOAD,
|
|
398
|
-
"version": manifest.get("version"),
|
|
399
|
-
"manifest": manifest_path,
|
|
400
|
-
"models": models_path,
|
|
401
|
-
"valid": manifest_valid and models_valid and tier_mapping_valid,
|
|
402
|
-
},
|
|
403
|
-
"bootstrap": bootstrap,
|
|
404
|
-
"python": python_report,
|
|
405
|
-
"tiers": tiers,
|
|
406
|
-
"local_state": {
|
|
407
|
-
"path": local,
|
|
408
|
-
"present": os.path.isdir(local),
|
|
409
|
-
"writable": os.access(local, os.W_OK) if os.path.isdir(local) else None,
|
|
410
|
-
},
|
|
411
|
-
"memory": _memory_report(),
|
|
412
|
-
"skills": {
|
|
413
|
-
"shipped_portable": skills["skills"],
|
|
414
|
-
"shipped_claude_only": skills["skills-claude"],
|
|
415
|
-
"expected_here": sorted(registered),
|
|
416
|
-
"excluded_here": sorted(excluded | (set() if harness == "claude" else claude_only)),
|
|
417
|
-
},
|
|
418
|
-
"breadcrumbs": breadcrumbs,
|
|
419
|
-
# Most breadcrumbs are deliberately untrusted history. The OpenCode
|
|
420
|
-
# shadow-skills log is different: it means this payload could not
|
|
421
|
-
# register the namespaced skill tree, so its presence is a durable
|
|
422
|
-
# degraded capability signal even though it cannot timestamp a turn.
|
|
423
|
-
"status": "degraded" if degraded_reasons else "healthy",
|
|
424
|
-
"degraded_reasons": degraded_reasons,
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
def _render(data):
|
|
429
|
-
lines = ["leo doctor", ""]
|
|
430
|
-
|
|
431
|
-
def row(label, value, source):
|
|
432
|
-
# A long path must not shove the source column off the line: overflow
|
|
433
|
-
# drops the source onto its own indented continuation instead.
|
|
434
|
-
value = str(value)
|
|
435
|
-
if len(value) > 44:
|
|
436
|
-
lines.append(f" {label:<16}{value}")
|
|
437
|
-
lines.append(f" {'':<16}{'':<44}{source}")
|
|
438
|
-
else:
|
|
439
|
-
lines.append(f" {label:<16}{value:<44}{source}")
|
|
440
|
-
|
|
441
|
-
harness = data["harness"]
|
|
442
|
-
row("harness", harness["value"], f"detected via {harness['source']}")
|
|
443
|
-
row("payload", f"{data['payload']['path']}", "disk")
|
|
444
|
-
row("version", data["payload"]["version"] or "unknown", "disk")
|
|
445
|
-
|
|
446
|
-
boot = data["bootstrap"]
|
|
447
|
-
state = "present" if _bootstrap_valid(boot) else "MISSING or invalid"
|
|
448
|
-
row("bootstrap", f"{boot['kind']}: {state}", "disk")
|
|
449
|
-
if boot.get("trust_instruction"):
|
|
450
|
-
row("Codex hooks", boot["trust_instruction"], "manual check")
|
|
451
|
-
python = data["python"]
|
|
452
|
-
row("python", f"{python['running']} (minimum {python['minimum']})",
|
|
453
|
-
"supported" if python["supported"] else "UNSUPPORTED")
|
|
454
|
-
|
|
455
|
-
tiers = " · ".join(
|
|
456
|
-
f"{t.capitalize()} {v['model']}" + (f"/{v['effort']}" if v["effort"] else "")
|
|
457
|
-
for t, v in data["tiers"].items() if v["model"]
|
|
458
|
-
)
|
|
459
|
-
row("tiers", tiers or "no mapping for this harness", "config/models.json")
|
|
460
|
-
|
|
461
|
-
local = data["local_state"]
|
|
462
|
-
row("local state", local["path"],
|
|
463
|
-
"disk: " + ("writable" if local["writable"] else
|
|
464
|
-
"present" if local["present"] else "not created yet"))
|
|
465
|
-
|
|
466
|
-
mem = data["memory"]
|
|
467
|
-
if mem.get("error"):
|
|
468
|
-
row("memory", mem["error"], "disk")
|
|
469
|
-
elif not mem["present"]:
|
|
470
|
-
row("memory", "no store yet", "disk")
|
|
471
|
-
else:
|
|
472
|
-
row("memory", f"{mem['facts']} facts", "disk")
|
|
473
|
-
for target in mem["targets"]:
|
|
474
|
-
mark = "projected" if target["projected"] else "not projected"
|
|
475
|
-
row("", f" {target['harness']}: {mark}", target["path"])
|
|
476
|
-
hermes = mem.get("hermes_projection")
|
|
477
|
-
if hermes:
|
|
478
|
-
row("Hermes memory", hermes["status"], hermes["path"])
|
|
479
|
-
|
|
480
|
-
skills = data["skills"]
|
|
481
|
-
row("skills shipped",
|
|
482
|
-
f"{len(skills['shipped_portable'])} portable · "
|
|
483
|
-
f"{len(skills['shipped_claude_only'])} claude-only", "disk")
|
|
484
|
-
row("expected here", f"{len(skills['expected_here'])} skills", "disk + config")
|
|
485
|
-
if skills["excluded_here"]:
|
|
486
|
-
row("not available", ", ".join(skills["excluded_here"]), "config/models.json")
|
|
487
|
-
|
|
488
|
-
if data["breadcrumbs"]:
|
|
489
|
-
lines.append("")
|
|
490
|
-
lines.append(" breadcrumbs (history, provenance unknown — not this session):")
|
|
491
|
-
for name, info in data["breadcrumbs"].items():
|
|
492
|
-
lines.append(f" {name}: {info['entries']} entries, newest: {info['newest']}")
|
|
493
|
-
|
|
494
|
-
lines += [
|
|
495
|
-
"",
|
|
496
|
-
" This script proves what shipped to disk. It cannot prove the policy",
|
|
497
|
-
" reached the model — answer the three context questions in leo:doctor.",
|
|
498
|
-
]
|
|
499
|
-
return "\n".join(lines)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
def _exit_code(data):
|
|
503
|
-
if not data["payload"].get("valid") or not data["python"].get("supported"):
|
|
504
|
-
return 1
|
|
505
|
-
if data["harness"]["value"] != "unknown" and not _bootstrap_valid(data["bootstrap"]):
|
|
506
|
-
return 1
|
|
507
|
-
return 0
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
def main(argv):
|
|
511
|
-
data = collect(argv)
|
|
512
|
-
if "--json" in argv:
|
|
513
|
-
print(json.dumps(data, indent=1, sort_keys=True))
|
|
514
|
-
else:
|
|
515
|
-
print(_render(data))
|
|
516
|
-
return _exit_code(data)
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if __name__ == "__main__":
|
|
520
|
-
sys.exit(main(sys.argv[1:]))
|
package/scripts/jsonc_bridge.cjs
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
const parser = require(process.argv[2]);
|
|
4
|
-
try {
|
|
5
|
-
const request = JSON.parse(require("fs").readFileSync(0, "utf8"));
|
|
6
|
-
const errors = [];
|
|
7
|
-
const value = parser.parse(request.text, errors, { allowTrailingComma: true, disallowComments: false });
|
|
8
|
-
if (errors.length) throw new Error("invalid JSONC (parse error " + errors[0].error + ")");
|
|
9
|
-
if (request.mode === "parse") process.stdout.write(JSON.stringify({ value }));
|
|
10
|
-
else {
|
|
11
|
-
let text = request.text;
|
|
12
|
-
const eol = text.includes("\r\n") ? "\r\n" : "\n";
|
|
13
|
-
const indentation = text.match(/(?:\r?\n)([ \t]+)"/);
|
|
14
|
-
const indent = indentation ? indentation[1] : " ";
|
|
15
|
-
for (const edit of request.edits || []) {
|
|
16
|
-
text = parser.applyEdits(text, parser.modify(text, edit.path, edit.value, {
|
|
17
|
-
isArrayInsertion: false,
|
|
18
|
-
formattingOptions: { insertSpaces: !indent.includes("\t"), tabSize: indent.includes("\t") ? 1 : Math.max(1, indent.length), eol, keepLines: true },
|
|
19
|
-
}));
|
|
20
|
-
}
|
|
21
|
-
process.stdout.write(JSON.stringify({ text }));
|
|
22
|
-
}
|
|
23
|
-
} catch (error) { process.stderr.write(String(error && error.message || error)); process.exit(2); }
|