@event4u/agent-config 2.12.0 → 2.13.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/council/analysis.md +142 -0
- package/.agent-src/commands/council/debate.md +129 -0
- package/.agent-src/commands/council/default.md +8 -0
- package/.agent-src/commands/council/design.md +16 -12
- package/.agent-src/commands/council/optimize.md +16 -15
- package/.agent-src/commands/council/pr.md +12 -12
- package/.agent-src/commands/council.md +48 -2
- package/.agent-src/personas/advisors/contrarian.md +95 -0
- package/.agent-src/personas/advisors/executor.md +99 -0
- package/.agent-src/personas/advisors/expansionist.md +98 -0
- package/.agent-src/personas/advisors/first-principles.md +98 -0
- package/.agent-src/personas/advisors/outsider.md +102 -0
- package/.agent-src/rules/copilot-routing.md +19 -0
- package/.agent-src/rules/devcontainer-routing.md +20 -0
- package/.agent-src/rules/laravel-routing.md +20 -0
- package/.agent-src/rules/symfony-routing.md +20 -0
- package/.agent-src/skills/ai-council/SKILL.md +180 -2
- package/.agent-src/skills/copilot-config/SKILL.md +1 -1
- package/.agent-src/skills/devcontainer/SKILL.md +1 -1
- package/.agent-src/skills/laravel/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-core/SKILL.md +1 -1
- package/.agent-src/skills/project-analyzer/SKILL.md +1 -1
- package/.agent-src/skills/symfony-workflow/SKILL.md +1 -1
- package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/.claude-plugin/marketplace.json +3 -1
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +47 -0
- package/CONTRIBUTING.md +5 -0
- package/README.md +3 -3
- package/config/agent-settings.template.yml +5 -93
- package/docs/architecture/multi-tool-projection.md +53 -0
- package/docs/architecture/{compression.md → source-projection.md} +21 -3
- package/docs/architecture.md +5 -5
- package/docs/catalog.md +21 -11
- package/docs/contracts/adr-architectural-consensus-mechanism.md +67 -0
- package/docs/contracts/ai-council-config.md +186 -0
- package/docs/contracts/command-clusters.md +57 -1
- package/docs/contracts/multi-tool-projection-fidelity.md +109 -0
- package/docs/getting-started.md +2 -2
- package/package.json +1 -1
- package/scripts/_archive/README.md +59 -0
- package/scripts/ai_council/_default_prices.py +10 -1
- package/scripts/ai_council/advisors.py +148 -0
- package/scripts/ai_council/clients.py +172 -0
- package/scripts/ai_council/config.py +368 -0
- package/scripts/ai_council/consensus.py +290 -0
- package/scripts/ai_council/orchestrator.py +628 -14
- package/scripts/ai_council/prompts.py +335 -0
- package/scripts/check_compressed_paths.py +6 -1
- package/scripts/ci_time_ratio.py +168 -0
- package/scripts/council_cli.py +973 -29
- package/scripts/measure_projection_bytes.py +159 -0
- package/scripts/measure_roadmap_trajectory.py +112 -0
- package/scripts/probe_projection_fidelity.py +202 -0
- package/scripts/score_skill_selection.py +198 -0
- package/scripts/skill_collision_clusters.py +162 -0
- /package/scripts/{_backfill_skill_domains.py → _archive/_backfill_skill_domains.py} +0 -0
- /package/scripts/{_bootstrap_tier_frontmatter.py → _archive/_bootstrap_tier_frontmatter.py} +0 -0
- /package/scripts/{_p43_bodies.py → _archive/_p43_bodies.py} +0 -0
- /package/scripts/{_p43_compress.py → _archive/_p43_compress.py} +0 -0
- /package/scripts/{_p4_migrate.py → _archive/_p4_migrate.py} +0 -0
- /package/scripts/{_phase2_shim_helper.py → _archive/_phase2_shim_helper.py} +0 -0
- /package/scripts/{_pilot_council_question.py → _archive/_pilot_council_question.py} +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Skill-collision cluster analysis (Phase 2.2 of step-1-v2-feedback-followup).
|
|
3
|
+
|
|
4
|
+
Walks `.agent-src.uncompressed/skills/<id>/SKILL.md`, extracts the
|
|
5
|
+
`description` frontmatter, computes pairwise keyword overlap, and groups
|
|
6
|
+
high-overlap skill pairs into clusters. The output drives the
|
|
7
|
+
selection-accuracy fixture set defined by council file 05 (Round-3
|
|
8
|
+
protocol — ≥ 3 shared significant terms → collision cluster).
|
|
9
|
+
|
|
10
|
+
Output: `agents/reports/skill-collision-clusters.json`
|
|
11
|
+
|
|
12
|
+
Schema:
|
|
13
|
+
{
|
|
14
|
+
"skill_count": int,
|
|
15
|
+
"cluster_count": int,
|
|
16
|
+
"clusters": [
|
|
17
|
+
{
|
|
18
|
+
"cluster_id": "C01",
|
|
19
|
+
"members": ["skill-a", "skill-b", ...],
|
|
20
|
+
"shared_keywords": [...],
|
|
21
|
+
"max_overlap": float,
|
|
22
|
+
"descriptions": {"skill-a": "...", ...}
|
|
23
|
+
},
|
|
24
|
+
...
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from itertools import combinations
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
import yaml
|
|
38
|
+
|
|
39
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
40
|
+
SKILLS_DIR = REPO_ROOT / ".agent-src.uncompressed" / "skills"
|
|
41
|
+
OUT_JSON = REPO_ROOT / "agents" / "reports" / "skill-collision-clusters.json"
|
|
42
|
+
|
|
43
|
+
KEYWORD_OVERLAP_THRESHOLD = 0.40
|
|
44
|
+
MIN_SHARED_KEYWORDS = 3
|
|
45
|
+
TOP_N_CLUSTERS = 10
|
|
46
|
+
|
|
47
|
+
STOPWORDS = {
|
|
48
|
+
"the", "and", "for", "with", "when", "use", "or", "of", "to", "a",
|
|
49
|
+
"an", "is", "in", "on", "by", "be", "at", "as", "it", "if", "are",
|
|
50
|
+
"this", "that", "from", "but", "not", "can", "any", "all", "no",
|
|
51
|
+
"after", "before", "during", "user", "agent", "code", "project",
|
|
52
|
+
"via", "into", "onto", "even", "without", "naming", "uses", "used",
|
|
53
|
+
"using", "also", "etc", "across", "between", "review", "design",
|
|
54
|
+
"writing", "create", "creating", "edit", "editing", "make", "making",
|
|
55
|
+
"set", "setting", "based", "well", "right", "left", "new",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def keyword_set(text: str) -> set[str]:
|
|
60
|
+
tokens = re.findall(r"[A-Za-z][A-Za-z0-9_-]{2,}", text.lower())
|
|
61
|
+
return {t for t in tokens if t not in STOPWORDS and not t.isdigit()}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def overlap_fraction(a: set[str], b: set[str]) -> float:
|
|
65
|
+
if not a or not b:
|
|
66
|
+
return 0.0
|
|
67
|
+
return len(a & b) / min(len(a), len(b))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def load_skills() -> list[dict]:
|
|
71
|
+
skills = []
|
|
72
|
+
for skill_md in sorted(SKILLS_DIR.glob("*/SKILL.md")):
|
|
73
|
+
text = skill_md.read_text()
|
|
74
|
+
if not text.startswith("---"):
|
|
75
|
+
continue
|
|
76
|
+
parts = text.split("---", 2)
|
|
77
|
+
if len(parts) < 3:
|
|
78
|
+
continue
|
|
79
|
+
try:
|
|
80
|
+
fm = yaml.safe_load(parts[1]) or {}
|
|
81
|
+
except yaml.YAMLError:
|
|
82
|
+
continue
|
|
83
|
+
name = fm.get("name") or skill_md.parent.name
|
|
84
|
+
description = (fm.get("description") or "").strip()
|
|
85
|
+
if not description:
|
|
86
|
+
continue
|
|
87
|
+
skills.append(
|
|
88
|
+
{
|
|
89
|
+
"name": name,
|
|
90
|
+
"description": description,
|
|
91
|
+
"_keywords": keyword_set(description),
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
return skills
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def build_clusters(skills: list[dict]) -> list[dict]:
|
|
98
|
+
# Pairwise edges where overlap & shared-keyword threshold is met.
|
|
99
|
+
edges: list[tuple[str, str, set[str], float]] = []
|
|
100
|
+
by_name = {s["name"]: s for s in skills}
|
|
101
|
+
for a, b in combinations(skills, 2):
|
|
102
|
+
shared = a["_keywords"] & b["_keywords"]
|
|
103
|
+
ov = overlap_fraction(a["_keywords"], b["_keywords"])
|
|
104
|
+
if len(shared) >= MIN_SHARED_KEYWORDS and ov >= KEYWORD_OVERLAP_THRESHOLD:
|
|
105
|
+
edges.append((a["name"], b["name"], shared, ov))
|
|
106
|
+
|
|
107
|
+
# Union-find over edge set → connected-component clusters.
|
|
108
|
+
parent: dict[str, str] = {}
|
|
109
|
+
|
|
110
|
+
def find(x: str) -> str:
|
|
111
|
+
parent.setdefault(x, x)
|
|
112
|
+
while parent[x] != x:
|
|
113
|
+
parent[x] = parent[parent[x]]
|
|
114
|
+
x = parent[x]
|
|
115
|
+
return x
|
|
116
|
+
|
|
117
|
+
def union(x: str, y: str) -> None:
|
|
118
|
+
rx, ry = find(x), find(y)
|
|
119
|
+
if rx != ry:
|
|
120
|
+
parent[rx] = ry
|
|
121
|
+
|
|
122
|
+
for a, b, _, _ in edges:
|
|
123
|
+
union(a, b)
|
|
124
|
+
|
|
125
|
+
components: dict[str, list[str]] = {}
|
|
126
|
+
for name in {n for edge in edges for n in edge[:2]}:
|
|
127
|
+
components.setdefault(find(name), []).append(name)
|
|
128
|
+
|
|
129
|
+
clusters: list[dict] = []
|
|
130
|
+
for idx, (_, members) in enumerate(sorted(components.items(), key=lambda kv: -len(kv[1])), start=1):
|
|
131
|
+
member_kws = [by_name[m]["_keywords"] for m in members]
|
|
132
|
+
shared_all = set.intersection(*member_kws) if member_kws else set()
|
|
133
|
+
member_edges = [(a, b, sk, ov) for a, b, sk, ov in edges if a in members and b in members]
|
|
134
|
+
max_ov = max((ov for *_, ov in member_edges), default=0.0)
|
|
135
|
+
clusters.append({
|
|
136
|
+
"cluster_id": f"C{idx:02d}",
|
|
137
|
+
"members": sorted(members),
|
|
138
|
+
"shared_keywords": sorted(shared_all),
|
|
139
|
+
"max_overlap": round(max_ov, 3),
|
|
140
|
+
"descriptions": {m: by_name[m]["description"] for m in sorted(members)},
|
|
141
|
+
})
|
|
142
|
+
return clusters[:TOP_N_CLUSTERS]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main() -> int:
|
|
146
|
+
if not SKILLS_DIR.exists():
|
|
147
|
+
print(f"❌ Skills dir not found: {SKILLS_DIR}", file=sys.stderr)
|
|
148
|
+
return 2
|
|
149
|
+
skills = load_skills()
|
|
150
|
+
clusters = build_clusters(skills)
|
|
151
|
+
OUT_JSON.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
OUT_JSON.write_text(json.dumps({
|
|
153
|
+
"skill_count": len(skills),
|
|
154
|
+
"cluster_count": len(clusters),
|
|
155
|
+
"clusters": clusters,
|
|
156
|
+
}, indent=2) + "\n")
|
|
157
|
+
print(f"✅ Wrote {OUT_JSON.relative_to(REPO_ROOT)} — {len(clusters)} clusters from {len(skills)} skills")
|
|
158
|
+
return 0
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|