@event4u/agent-config 5.4.1 → 5.6.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/.agent-src/commands/image/analyse.md +51 -0
- package/.agent-src/commands/image/create.md +53 -0
- package/.agent-src/commands/image/verify.md +48 -0
- package/.agent-src/commands/image.md +69 -0
- package/.agent-src/commands/knowledge/cross-repo.md +71 -0
- package/.agent-src/commands/knowledge.md +2 -0
- package/.agent-src/commands/skill/preview.md +67 -0
- package/.agent-src/commands/skill.md +48 -0
- package/.agent-src/commands/skills/discover.md +76 -0
- package/.agent-src/commands/skills.md +56 -0
- package/.agent-src/commands/video/from-song.md +351 -0
- package/.agent-src/commands/video.md +19 -9
- package/.agent-src/contexts/authority/commit-mechanics.md +8 -0
- package/.agent-src/rules/commit-policy.md +3 -8
- package/.agent-src/rules/linked-projects-onboarding-gate.md +1 -1
- package/.agent-src/rules/media-sync-ground-truth.md +58 -0
- package/.agent-src/skills/image-analyser/SKILL.md +121 -0
- package/.agent-src/skills/image-analyser/canon-spec.md +109 -0
- package/.agent-src/skills/image-analyser/evals/triggers.json +16 -0
- package/.agent-src/skills/image-creator/SKILL.md +117 -0
- package/.agent-src/skills/image-creator/evals/triggers.json +16 -0
- package/.agent-src/skills/song-to-script/SKILL.md +216 -0
- package/.claude-plugin/marketplace.json +15 -2
- package/CHANGELOG.md +84 -0
- package/CONTRIBUTING.md +6 -0
- package/README.md +3 -3
- package/config/agent-settings.template.yml +18 -0
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +327 -20
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +24 -10
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +20 -6
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/architecture.md +3 -3
- package/docs/catalog.md +20 -6
- package/docs/contracts/benchmark-report-schema.md +12 -10
- package/docs/contracts/command-clusters.md +5 -1
- package/docs/contracts/cross-repo-retrieval.md +64 -0
- package/docs/contracts/rule-router.md +39 -0
- package/docs/contracts/skill-discovery.md +80 -0
- package/docs/contracts/skill-dry-run.md +47 -0
- package/docs/contracts/value-dashboard-spec.md +7 -3
- package/docs/contracts/value-report-schema.md +6 -1
- package/docs/decisions/ADR-032-linked-projects-scope.md +7 -3
- package/docs/getting-started.md +2 -2
- package/docs/guides/cross-repo-linked-projects.md +7 -0
- package/docs/guides/cross-repo-retrieval.md +61 -0
- package/docs/guides/skill-discovery.md +71 -0
- package/docs/guides/skill-preview.md +71 -0
- package/docs/value.md +17 -17
- package/package.json +1 -1
- package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
- package/scripts/_dispatch.bash +10 -0
- package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
- package/scripts/_lib/bench_report.py +13 -14
- package/scripts/_lib/bench_telegraph_report.py +1 -2
- package/scripts/_lib/token_count.py +95 -0
- package/scripts/_lib/value_report.py +3 -3
- package/scripts/ai-video/adapters/higgsfield.sh +163 -6
- package/scripts/ai-video/adapters/openai-images.sh +92 -6
- package/scripts/ai-video/lib/probe-audio.sh +181 -0
- package/scripts/audit_auto_rules.py +22 -6
- package/scripts/audit_command_surface.py +6 -1
- package/scripts/audit_initial_context.py +210 -0
- package/scripts/bench_ab_diff.py +4 -11
- package/scripts/bench_run.py +2 -3
- package/scripts/bench_runner.py +2 -2
- package/scripts/condense.py +44 -3
- package/scripts/cross_repo_retrieve.py +172 -0
- package/scripts/inventory_meta_layers.py +288 -0
- package/scripts/iron_law_sha.py +14 -5
- package/scripts/linked_projects_list.py +91 -0
- package/scripts/measure_rule_budget.py +15 -0
- package/scripts/memory_lookup.py +53 -2
- package/scripts/project_thin_rules.py +168 -0
- package/scripts/render_value_md.py +14 -23
- package/scripts/schemas/command.schema.json +1 -1
- package/scripts/schemas/rule.schema.json +1 -1
- package/scripts/schemas/skill.schema.json +2 -2
- package/scripts/skill_discovery.py +254 -0
- package/scripts/skill_linter.py +8 -4
- package/scripts/skill_preview.py +179 -0
- package/scripts/trigger_coverage.py +129 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Skill preview — non-destructive "what will this skill do?" summary.
|
|
2
|
+
|
|
3
|
+
Phase 5 of `road-to-leaner-core-and-discovery`. Reads a skill's declared intent
|
|
4
|
+
(frontmatter + `## Steps` body) and renders a plain-language summary BEFORE the
|
|
5
|
+
skill runs. Read-only, no network, no execution.
|
|
6
|
+
|
|
7
|
+
NOT a sandbox: it surfaces declared intent, it does not run the skill or prove
|
|
8
|
+
side-effect-freeness (contract: docs/contracts/skill-dry-run.md). For
|
|
9
|
+
`execution: manual` skills (the default) it states "instructional only".
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
python3 scripts/skill_preview.py <name> [--technical] [--format text|json]
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
import yaml
|
|
23
|
+
|
|
24
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
25
|
+
SKILLS_DIR = REPO_ROOT / ".agent-src" / "skills"
|
|
26
|
+
|
|
27
|
+
_CMD_RE = re.compile(r"`(python3?|bash|node|php|npm|task|pytest)\s+[^`]+`")
|
|
28
|
+
_PATH_RE = re.compile(r"`([\w./-]+\.(?:py|sh|md|json|yml|yaml|ts|js|php))`")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PreviewError(Exception):
|
|
32
|
+
"""Raised for a missing or malformed SKILL.md — rendered, never crashed on."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _split_frontmatter(text: str) -> tuple[dict, str]:
|
|
36
|
+
if not text.startswith("---"):
|
|
37
|
+
raise PreviewError("SKILL.md has no YAML frontmatter (missing leading `---`).")
|
|
38
|
+
end = text.find("\n---", 3)
|
|
39
|
+
if end == -1:
|
|
40
|
+
raise PreviewError("SKILL.md frontmatter is not closed (missing terminating `---`).")
|
|
41
|
+
try:
|
|
42
|
+
fm = yaml.safe_load(text[3:end]) or {}
|
|
43
|
+
except yaml.YAMLError as exc:
|
|
44
|
+
raise PreviewError(f"SKILL.md frontmatter is not valid YAML: {exc}")
|
|
45
|
+
if not isinstance(fm, dict):
|
|
46
|
+
raise PreviewError("SKILL.md frontmatter did not parse to a mapping.")
|
|
47
|
+
return fm, text[end + 4:]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _steps(body: str) -> list[str]:
|
|
51
|
+
out: list[str] = []
|
|
52
|
+
in_steps = False
|
|
53
|
+
for line in body.splitlines():
|
|
54
|
+
if re.match(r"^##\s+Steps\b", line, re.IGNORECASE):
|
|
55
|
+
in_steps = True
|
|
56
|
+
continue
|
|
57
|
+
if in_steps and re.match(r"^##\s+\S", line): # next top-level section
|
|
58
|
+
break
|
|
59
|
+
if in_steps:
|
|
60
|
+
m = re.match(r"^###\s+(.*)", line)
|
|
61
|
+
if m:
|
|
62
|
+
out.append(m.group(1).strip())
|
|
63
|
+
return out
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _targets(body: str) -> tuple[list[str], list[str]]:
|
|
67
|
+
cmds = sorted({m.group(0).strip("`") for m in _CMD_RE.finditer(body)})
|
|
68
|
+
paths = sorted({m.group(1) for m in _PATH_RE.finditer(body)})
|
|
69
|
+
return cmds, paths
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def load_preview(name: str) -> dict:
|
|
73
|
+
skill_dir = SKILLS_DIR / name
|
|
74
|
+
sk = skill_dir / "SKILL.md"
|
|
75
|
+
if not sk.is_file():
|
|
76
|
+
try:
|
|
77
|
+
shown = sk.relative_to(REPO_ROOT)
|
|
78
|
+
except ValueError:
|
|
79
|
+
shown = sk
|
|
80
|
+
raise PreviewError(f"no skill named {name!r} (looked for {shown}).")
|
|
81
|
+
fm, body = _split_frontmatter(sk.read_text(encoding="utf-8", errors="replace"))
|
|
82
|
+
execution = fm.get("execution") or {}
|
|
83
|
+
if not isinstance(execution, dict):
|
|
84
|
+
execution = {}
|
|
85
|
+
cmds, paths = _targets(body)
|
|
86
|
+
return {
|
|
87
|
+
"name": fm.get("name") or name,
|
|
88
|
+
"description": (fm.get("description") or "").strip(),
|
|
89
|
+
"domain": fm.get("domain") or "",
|
|
90
|
+
"execution_type": (execution.get("type") or "manual"),
|
|
91
|
+
"handler": (execution.get("handler") or "none"),
|
|
92
|
+
"allowed_tools": execution.get("allowed_tools") or [],
|
|
93
|
+
"command": execution.get("command") or [],
|
|
94
|
+
"steps": _steps(body),
|
|
95
|
+
"commands_named": cmds,
|
|
96
|
+
"paths_named": paths,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def render_plain(p: dict) -> str:
|
|
101
|
+
lines = [f"# Preview — `{p['name']}`", ""]
|
|
102
|
+
if p["description"]:
|
|
103
|
+
lines += [p["description"], ""]
|
|
104
|
+
etype = p["execution_type"]
|
|
105
|
+
if etype == "manual":
|
|
106
|
+
lines.append("**Execution: instructional only.** This skill does not run anything "
|
|
107
|
+
"automatically — it guides the agent step by step.")
|
|
108
|
+
elif etype == "assisted":
|
|
109
|
+
lines.append(f"**Execution: assisted** (handler `{p['handler']}`). It will *propose* actions "
|
|
110
|
+
"for you to approve — it never executes silently.")
|
|
111
|
+
else:
|
|
112
|
+
lines.append(f"**Execution: {etype}** (handler `{p['handler']}`). It can run actions; review the "
|
|
113
|
+
"declared tools and commands below before allowing it.")
|
|
114
|
+
lines.append("")
|
|
115
|
+
if p["steps"]:
|
|
116
|
+
lines.append("This skill will walk these steps:")
|
|
117
|
+
lines += [f"- {s}" for s in p["steps"]]
|
|
118
|
+
lines.append("")
|
|
119
|
+
if p["allowed_tools"]:
|
|
120
|
+
lines.append(f"Declared tools: {', '.join(p['allowed_tools'])}")
|
|
121
|
+
if p["command"]:
|
|
122
|
+
lines.append(f"Declared command: `{' '.join(str(c) for c in p['command'])}`")
|
|
123
|
+
if p["commands_named"]:
|
|
124
|
+
lines.append("Commands it may run:")
|
|
125
|
+
lines += [f"- `{c}`" for c in p["commands_named"]]
|
|
126
|
+
if p["paths_named"]:
|
|
127
|
+
lines.append("Files / scripts it references:")
|
|
128
|
+
lines += [f"- `{f}`" for f in p["paths_named"]]
|
|
129
|
+
if not (p["allowed_tools"] or p["command"] or p["commands_named"] or p["paths_named"]):
|
|
130
|
+
lines.append("_No tools, commands, or file targets declared — pure guidance._")
|
|
131
|
+
lines.append("")
|
|
132
|
+
lines.append("> Preview shows declared intent only — it does not run the skill or guarantee "
|
|
133
|
+
"side-effect-freeness. Contract: docs/contracts/skill-dry-run.md")
|
|
134
|
+
return "\n".join(lines)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def render_technical(p: dict) -> str:
|
|
138
|
+
lines = [f"# Preview (technical) — {p['name']}", "", "## Frontmatter (execution)", "```yaml"]
|
|
139
|
+
lines.append(f"execution_type: {p['execution_type']}")
|
|
140
|
+
lines.append(f"handler: {p['handler']}")
|
|
141
|
+
lines.append(f"allowed_tools: {p['allowed_tools']}")
|
|
142
|
+
if p["command"]:
|
|
143
|
+
lines.append(f"command: {p['command']}")
|
|
144
|
+
lines += ["```", "", "## Declared steps"]
|
|
145
|
+
lines += [f"{i+1}. {s}" for i, s in enumerate(p["steps"])] or ["(none)"]
|
|
146
|
+
if p["commands_named"]:
|
|
147
|
+
lines += ["", "## Commands named in body"] + [f"- `{c}`" for c in p["commands_named"]]
|
|
148
|
+
if p["paths_named"]:
|
|
149
|
+
lines += ["", "## Paths named in body"] + [f"- `{f}`" for f in p["paths_named"]]
|
|
150
|
+
return "\n".join(lines)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main(argv: list[str] | None = None) -> int:
|
|
154
|
+
ap = argparse.ArgumentParser(description="Preview a skill's declared intent (read-only, no execution).")
|
|
155
|
+
ap.add_argument("name", help="Skill name (directory under .agent-src/skills/).")
|
|
156
|
+
ap.add_argument("--technical", action="store_true", help="Show raw frontmatter + step list.")
|
|
157
|
+
ap.add_argument("--format", choices=("text", "json"), default="text")
|
|
158
|
+
args = ap.parse_args(argv)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
preview = load_preview(args.name)
|
|
162
|
+
except PreviewError as exc:
|
|
163
|
+
if args.format == "json":
|
|
164
|
+
print(json.dumps({"error": str(exc), "name": args.name}, indent=2))
|
|
165
|
+
else:
|
|
166
|
+
print(f"❌ Cannot preview {args.name!r}: {exc}", file=sys.stderr)
|
|
167
|
+
return 2
|
|
168
|
+
|
|
169
|
+
if args.format == "json":
|
|
170
|
+
print(json.dumps(preview, indent=2))
|
|
171
|
+
elif args.technical:
|
|
172
|
+
print(render_technical(preview))
|
|
173
|
+
else:
|
|
174
|
+
print(render_plain(preview))
|
|
175
|
+
return 0
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Trigger-coverage suite (roadmap Phase 2.1 / 2.2).
|
|
3
|
+
|
|
4
|
+
The deterministic *must-load* floor for the lean-initial-context migration.
|
|
5
|
+
Before any auto-tier rule body is demoted to a router-resolved pointer
|
|
6
|
+
(Phase 3), this suite proves the router still fires that rule on
|
|
7
|
+
representative task phrasings — so a needed rule can never silently fail
|
|
8
|
+
to surface.
|
|
9
|
+
|
|
10
|
+
Cases live in `tests/eval/trigger-coverage.yaml` and have the shape:
|
|
11
|
+
|
|
12
|
+
- id: secrets-edit
|
|
13
|
+
prompt: "add a webhook secret to the billing service auth flow"
|
|
14
|
+
expect: [security-sensitive-stop] # MUST be in the fired set
|
|
15
|
+
|
|
16
|
+
Matching is deterministic against `dist/router.json` (NOT the semantic
|
|
17
|
+
production router — this is a reproducible floor that catches a removed
|
|
18
|
+
trigger in CI):
|
|
19
|
+
|
|
20
|
+
- kernel rules always fire (always-on layer).
|
|
21
|
+
- a tier rule fires iff any of its triggers matches the prompt:
|
|
22
|
+
- `keyword` → case-insensitive substring.
|
|
23
|
+
- `intent` → every alpha word (len>2) of the intent phrase appears as a
|
|
24
|
+
token in the prompt (so "structural decision" fires on a prompt that
|
|
25
|
+
contains both "structural" and "decision").
|
|
26
|
+
|
|
27
|
+
A case fails when an expected rule is NOT in the fired set. Exit 1 on any
|
|
28
|
+
miss → the merge that would have shrunk the rule is blocked (2.2).
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
python3 scripts/trigger_coverage.py # run, human report
|
|
32
|
+
python3 scripts/trigger_coverage.py --json
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import json
|
|
39
|
+
import re
|
|
40
|
+
import sys
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
44
|
+
ROUTER = REPO_ROOT / "dist" / "router.json"
|
|
45
|
+
CORPUS = REPO_ROOT / "tests" / "eval" / "trigger-coverage.yaml"
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
import yaml
|
|
49
|
+
except ImportError: # pragma: no cover
|
|
50
|
+
sys.stderr.write("error: PyYAML required (pip install pyyaml)\n")
|
|
51
|
+
sys.exit(2)
|
|
52
|
+
|
|
53
|
+
_WORD = re.compile(r"[a-z][a-z0-9_]+")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _tokens(text: str) -> set[str]:
|
|
57
|
+
return {w for w in _WORD.findall(text.lower()) if len(w) > 2}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def load_router() -> dict:
|
|
61
|
+
return json.loads(ROUTER.read_text(encoding="utf-8"))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def fired_rules(prompt: str, router: dict) -> set[str]:
|
|
65
|
+
"""Return every rule id the router would surface for `prompt`."""
|
|
66
|
+
low = prompt.lower()
|
|
67
|
+
toks = _tokens(prompt)
|
|
68
|
+
fired: set[str] = set(router.get("kernel", []))
|
|
69
|
+
for tier in ("tier_1", "tier_2"):
|
|
70
|
+
for entry in router.get(tier, []):
|
|
71
|
+
for trig in entry.get("triggers", []):
|
|
72
|
+
if "keyword" in trig:
|
|
73
|
+
if trig["keyword"].lower() in low:
|
|
74
|
+
fired.add(entry["id"])
|
|
75
|
+
break
|
|
76
|
+
elif "intent" in trig:
|
|
77
|
+
words = _tokens(trig["intent"])
|
|
78
|
+
if words and words <= toks:
|
|
79
|
+
fired.add(entry["id"])
|
|
80
|
+
break
|
|
81
|
+
return fired
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def run(corpus: list[dict], router: dict) -> tuple[list[dict], int]:
|
|
85
|
+
results = []
|
|
86
|
+
misses = 0
|
|
87
|
+
for case in corpus:
|
|
88
|
+
fired = fired_rules(case["prompt"], router)
|
|
89
|
+
expected = case.get("expect", [])
|
|
90
|
+
missing = [r for r in expected if r not in fired]
|
|
91
|
+
ok = not missing
|
|
92
|
+
if not ok:
|
|
93
|
+
misses += 1
|
|
94
|
+
results.append({"id": case["id"], "ok": ok, "missing": missing,
|
|
95
|
+
"expect": expected})
|
|
96
|
+
return results, misses
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main(argv: list[str] | None = None) -> int:
|
|
100
|
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
101
|
+
ap.add_argument("--json", action="store_true")
|
|
102
|
+
args = ap.parse_args(argv)
|
|
103
|
+
|
|
104
|
+
if not ROUTER.is_file():
|
|
105
|
+
sys.stderr.write(f"error: {ROUTER} missing — run compile_router first\n")
|
|
106
|
+
return 2
|
|
107
|
+
corpus = yaml.safe_load(CORPUS.read_text(encoding="utf-8")) or []
|
|
108
|
+
router = load_router()
|
|
109
|
+
results, misses = run(corpus, router)
|
|
110
|
+
|
|
111
|
+
if args.json:
|
|
112
|
+
print(json.dumps({"cases": len(results), "misses": misses,
|
|
113
|
+
"results": results}, indent=2, sort_keys=True))
|
|
114
|
+
else:
|
|
115
|
+
for r in results:
|
|
116
|
+
mark = "✅" if r["ok"] else "❌"
|
|
117
|
+
detail = "" if r["ok"] else f" MISSING: {', '.join(r['missing'])}"
|
|
118
|
+
print(f" {mark} {r['id']}{detail}")
|
|
119
|
+
print()
|
|
120
|
+
if misses:
|
|
121
|
+
print(f"❌ trigger-coverage: {misses}/{len(results)} case(s) failed — "
|
|
122
|
+
"a required rule does not fire. Blocking.")
|
|
123
|
+
else:
|
|
124
|
+
print(f"✅ trigger-coverage: {len(results)}/{len(results)} pass")
|
|
125
|
+
return 1 if misses else 0
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
sys.exit(main())
|