maestro-skills 0.1.1
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/.github/workflows/ci.yml +26 -0
- package/.github/workflows/publish-npm.yml +30 -0
- package/CONTRIBUTING.md +31 -0
- package/LICENSE +21 -0
- package/README.md +300 -0
- package/SECURITY.md +33 -0
- package/docs/github-workflow.md +96 -0
- package/docs/maestro-skills-cli.md +113 -0
- package/package.json +35 -0
- package/packages/maestro-skills/README.md +37 -0
- package/packages/maestro-skills/agents.json +36 -0
- package/packages/maestro-skills/bin/cli.js +37 -0
- package/packages/maestro-skills/lib/detect-agents.js +28 -0
- package/packages/maestro-skills/lib/install.js +58 -0
- package/packages/maestro-skills/lib/paths.js +42 -0
- package/packages/maestro-skills/lib/remove.js +71 -0
- package/packages/maestro-skills/lib/run-manifest.js +92 -0
- package/packages/maestro-skills/lib/setup.js +115 -0
- package/packages/maestro-skills/package.json +47 -0
- package/packages/maestro-skills/test/agents.test.js +17 -0
- package/packages/rodovalhofs-maestro/agents.json +36 -0
- package/packages/rodovalhofs-maestro/bin/cli.js +10 -0
- package/packages/rodovalhofs-maestro/lib/detect-agents.js +28 -0
- package/packages/rodovalhofs-maestro/lib/install.js +58 -0
- package/packages/rodovalhofs-maestro/lib/paths.js +42 -0
- package/packages/rodovalhofs-maestro/lib/remove.js +71 -0
- package/packages/rodovalhofs-maestro/lib/run-manifest.js +92 -0
- package/packages/rodovalhofs-maestro/lib/setup.js +115 -0
- package/packages/rodovalhofs-maestro/package.json +33 -0
- package/scripts/sync-skill-to-cli.mjs +75 -0
- package/scripts/sync-templates.ps1 +22 -0
- package/skills/maestro/SKILL.md +272 -0
- package/skills/maestro/maestro-exclude.example.txt +6 -0
- package/skills/maestro/scripts/bm25.py +70 -0
- package/skills/maestro/scripts/build_manifest.py +183 -0
- package/skills/maestro/scripts/concept_gaps.py +196 -0
- package/skills/maestro/scripts/domains.py +148 -0
- package/skills/maestro/scripts/intents.py +167 -0
- package/skills/maestro/scripts/maestro_paths.py +41 -0
- package/skills/maestro/scripts/route_tasks.py +101 -0
- package/skills/maestro/scripts/routing.py +106 -0
- package/skills/maestro/scripts/search_skills.py +287 -0
- package/skills/maestro/scripts/synonyms.py +47 -0
- package/templates/.github/ISSUE_TEMPLATE/bug_report.yml +34 -0
- package/templates/.github/ISSUE_TEMPLATE/chore.yml +17 -0
- package/templates/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/templates/.github/ISSUE_TEMPLATE/feature_request.yml +27 -0
- package/templates/.github/workflows/ci-failure-to-issue.yml +47 -0
- package/templates/.github/workflows/ci.yml +27 -0
- package/templates/CONTRIBUTING.md +22 -0
- package/templates/labels.json +12 -0
- package/templates/pull_request_template.md +18 -0
- package/tests/fixtures/sample-manifest.json +76 -0
- package/tests/test_concept_gaps.py +63 -0
- package/tests/test_maestro_paths.py +29 -0
- package/tests/test_search_routing.py +161 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Detect prompt concepts that lack local skill coverage (discover via find-skills)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
MAX_DISCOVER_GAPS = 2
|
|
10
|
+
|
|
11
|
+
STOPWORDS: frozenset[str] = frozenset(
|
|
12
|
+
{
|
|
13
|
+
"ui",
|
|
14
|
+
"ux",
|
|
15
|
+
"app",
|
|
16
|
+
"web",
|
|
17
|
+
"api",
|
|
18
|
+
"codigo",
|
|
19
|
+
"código",
|
|
20
|
+
"alteracao",
|
|
21
|
+
"alteração",
|
|
22
|
+
"fazer",
|
|
23
|
+
"vamos",
|
|
24
|
+
"projeto",
|
|
25
|
+
"sistema",
|
|
26
|
+
"pagina",
|
|
27
|
+
"página",
|
|
28
|
+
"tela",
|
|
29
|
+
"componente",
|
|
30
|
+
"frontend",
|
|
31
|
+
"backend",
|
|
32
|
+
"nova",
|
|
33
|
+
"novo",
|
|
34
|
+
"uma",
|
|
35
|
+
"uns",
|
|
36
|
+
"the",
|
|
37
|
+
"and",
|
|
38
|
+
"for",
|
|
39
|
+
"with",
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
IMPLEMENTATION_VERB_PATTERN = re.compile(
|
|
44
|
+
r"\b(?:colocar|usar|implementar|adicionar|integrar|instalar|"
|
|
45
|
+
r"add|use|implement|integrate|install)\s+"
|
|
46
|
+
r"([a-z][a-z0-9._/-]*)",
|
|
47
|
+
re.IGNORECASE,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
HYPHENATED_PATTERN = re.compile(r"\b([a-z][a-z0-9]*(?:-[a-z0-9]+)+)\b", re.IGNORECASE)
|
|
51
|
+
|
|
52
|
+
CAMEL_CASE_PATTERN = re.compile(r"\b([a-z]+(?:[A-Z][a-z0-9]+)+)\b")
|
|
53
|
+
|
|
54
|
+
CONCEPT_GAP_SCORE_THRESHOLD = 1.5
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _normalize_term(term: str) -> str:
|
|
58
|
+
return term.strip().lower().replace("_", "-")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_stopword(term: str) -> bool:
|
|
62
|
+
normalized = _normalize_term(term)
|
|
63
|
+
if len(normalized) < 3:
|
|
64
|
+
return True
|
|
65
|
+
if normalized in STOPWORDS:
|
|
66
|
+
return True
|
|
67
|
+
parts = normalized.split("-")
|
|
68
|
+
return len(parts) == 1 and normalized in STOPWORDS
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _concept_specificity(term: str) -> int:
|
|
72
|
+
normalized = _normalize_term(term)
|
|
73
|
+
score = 0
|
|
74
|
+
if "-" in normalized:
|
|
75
|
+
score += 3
|
|
76
|
+
if any(ch.isupper() for ch in term):
|
|
77
|
+
score += 2
|
|
78
|
+
if "." in normalized:
|
|
79
|
+
score += 2
|
|
80
|
+
if len(normalized) >= 8:
|
|
81
|
+
score += 1
|
|
82
|
+
return score
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extract_concept_candidates(query: str) -> list[str]:
|
|
86
|
+
"""Extract salient implementable concepts from the user prompt."""
|
|
87
|
+
seen: set[str] = set()
|
|
88
|
+
ordered: list[str] = []
|
|
89
|
+
|
|
90
|
+
def add(raw: str) -> None:
|
|
91
|
+
term = _normalize_term(raw)
|
|
92
|
+
if not term or is_stopword(term):
|
|
93
|
+
return
|
|
94
|
+
if term in seen:
|
|
95
|
+
return
|
|
96
|
+
seen.add(term)
|
|
97
|
+
ordered.append(term)
|
|
98
|
+
|
|
99
|
+
for match in IMPLEMENTATION_VERB_PATTERN.finditer(query):
|
|
100
|
+
add(match.group(1))
|
|
101
|
+
|
|
102
|
+
for pattern in (HYPHENATED_PATTERN, CAMEL_CASE_PATTERN):
|
|
103
|
+
for match in pattern.finditer(query):
|
|
104
|
+
add(match.group(1))
|
|
105
|
+
|
|
106
|
+
ordered.sort(key=_concept_specificity, reverse=True)
|
|
107
|
+
return ordered
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _result_covers_term(term: str, results: list[dict[str, Any]]) -> bool:
|
|
111
|
+
normalized = _normalize_term(term)
|
|
112
|
+
compact = normalized.replace("-", "")
|
|
113
|
+
for skill in results:
|
|
114
|
+
blob = " ".join(
|
|
115
|
+
[
|
|
116
|
+
str(skill.get("name", "")),
|
|
117
|
+
str(skill.get("folder", "")),
|
|
118
|
+
str(skill.get("description", "")),
|
|
119
|
+
" ".join(str(t) for t in (skill.get("tags") or [])),
|
|
120
|
+
]
|
|
121
|
+
).lower()
|
|
122
|
+
blob_compact = blob.replace("-", "").replace("_", "")
|
|
123
|
+
if normalized in blob or compact in blob_compact:
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _top_score_for_term(
|
|
129
|
+
term: str,
|
|
130
|
+
pool: list[dict[str, Any]],
|
|
131
|
+
skill_document: Callable[[dict[str, Any]], str],
|
|
132
|
+
expand_query: Callable[[str], str],
|
|
133
|
+
) -> float:
|
|
134
|
+
from bm25 import BM25
|
|
135
|
+
|
|
136
|
+
if not pool:
|
|
137
|
+
return 0.0
|
|
138
|
+
|
|
139
|
+
documents = [skill_document(skill) for skill in pool]
|
|
140
|
+
bm25 = BM25()
|
|
141
|
+
bm25.fit(documents)
|
|
142
|
+
ranked = bm25.score(expand_query(term))
|
|
143
|
+
positive = [score for _, score in ranked if score > 0]
|
|
144
|
+
return max(positive) if positive else 0.0
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def find_concept_gaps(
|
|
148
|
+
query: str,
|
|
149
|
+
results: list[dict[str, Any]],
|
|
150
|
+
pool: list[dict[str, Any]],
|
|
151
|
+
skill_document: Callable[[dict[str, Any]], str],
|
|
152
|
+
expand_query: Callable[[str], str],
|
|
153
|
+
) -> tuple[list[str], list[str]]:
|
|
154
|
+
"""
|
|
155
|
+
Return (gaps_for_discover, gap_notes).
|
|
156
|
+
|
|
157
|
+
gaps_for_discover: up to MAX_DISCOVER_GAPS concepts needing find-skills.
|
|
158
|
+
gap_notes: additional concepts beyond the limit (for graph annotations).
|
|
159
|
+
"""
|
|
160
|
+
candidates = extract_concept_candidates(query)
|
|
161
|
+
if not candidates:
|
|
162
|
+
return [], []
|
|
163
|
+
|
|
164
|
+
confirmed: list[str] = []
|
|
165
|
+
for term in candidates:
|
|
166
|
+
if _result_covers_term(term, results):
|
|
167
|
+
continue
|
|
168
|
+
term_score = _top_score_for_term(term, pool, skill_document, expand_query)
|
|
169
|
+
if term_score >= CONCEPT_GAP_SCORE_THRESHOLD:
|
|
170
|
+
continue
|
|
171
|
+
confirmed.append(term)
|
|
172
|
+
|
|
173
|
+
gaps = confirmed[:MAX_DISCOVER_GAPS]
|
|
174
|
+
notes = confirmed[MAX_DISCOVER_GAPS:]
|
|
175
|
+
return gaps, notes
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def build_discover_queries(
|
|
179
|
+
gaps: list[str],
|
|
180
|
+
full_query: str,
|
|
181
|
+
domain: str,
|
|
182
|
+
) -> list[str]:
|
|
183
|
+
"""Build npx skills find queries for each concept gap."""
|
|
184
|
+
context_tokens: list[str] = []
|
|
185
|
+
if domain and domain not in {"general", "meta"}:
|
|
186
|
+
context_tokens.append(domain.replace("-", " "))
|
|
187
|
+
for token in re.findall(r"[a-z]{4,}", full_query.lower()):
|
|
188
|
+
if token not in STOPWORDS and token not in context_tokens:
|
|
189
|
+
context_tokens.append(token)
|
|
190
|
+
|
|
191
|
+
context = " ".join(context_tokens[:4])
|
|
192
|
+
queries: list[str] = []
|
|
193
|
+
for gap in gaps:
|
|
194
|
+
query = f"{gap} {context}".strip()
|
|
195
|
+
queries.append(query)
|
|
196
|
+
return queries
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Domain taxonomy and classification for maestro."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
DOMAINS: list[str] = [
|
|
7
|
+
"web",
|
|
8
|
+
"data-viz",
|
|
9
|
+
"analytics",
|
|
10
|
+
"design",
|
|
11
|
+
"creative",
|
|
12
|
+
"devops-git",
|
|
13
|
+
"video-media",
|
|
14
|
+
"integrations",
|
|
15
|
+
"security",
|
|
16
|
+
"meta",
|
|
17
|
+
"general",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
DOMAIN_KEYWORDS: dict[str, list[str]] = {
|
|
21
|
+
"web": [
|
|
22
|
+
"react", "nextjs", "next.js", "frontend", "backend", "api", "typescript",
|
|
23
|
+
"javascript", "shadcn", "stripe", "supabase", "postgres", "tailwind",
|
|
24
|
+
"component", "browser", "testing", "debug", "remix", "vue", "svelte",
|
|
25
|
+
"html", "css", "web app", "full-stack", "auth", "payment",
|
|
26
|
+
],
|
|
27
|
+
"data-viz": [
|
|
28
|
+
"chart", "graph", "visualization", "dashboard", "d3", "canvas", "threejs",
|
|
29
|
+
"geospatial", "map", "gantt", "diagram", "scrollytelling", "plot",
|
|
30
|
+
"accessibility", "svg", "webgl", "painel",
|
|
31
|
+
],
|
|
32
|
+
"analytics": [
|
|
33
|
+
"data quality", "kpi", "jupyter", "notebook", "metric", "report",
|
|
34
|
+
"analytics", "business context", "market sizing", "validate data",
|
|
35
|
+
"pandas", "sql", "spreadsheet", "excel",
|
|
36
|
+
],
|
|
37
|
+
"design": [
|
|
38
|
+
"prototype", "ideate", "audit", "design qa", "ux research", "figma",
|
|
39
|
+
"mockup", "wireframe", "ui", "ux", "product design", "url-to-code",
|
|
40
|
+
"image-to-code", "user flow", "onboarding", "superdesign", "design system",
|
|
41
|
+
"interface", "layout",
|
|
42
|
+
],
|
|
43
|
+
"security": [
|
|
44
|
+
"security", "cybersecurity", "forensics", "malware", "pentest",
|
|
45
|
+
"penetration", "threat", "incident response", "mitre", "attack",
|
|
46
|
+
"vulnerability", "siem", "dfir", "red team", "blue team", "seguranca",
|
|
47
|
+
"forense", "volatility",
|
|
48
|
+
],
|
|
49
|
+
"creative": [
|
|
50
|
+
"moodboard", "logo", "ads", "brand", "creative", "shot", "scene",
|
|
51
|
+
"positioning", "offer", "generative polish", "explorer",
|
|
52
|
+
],
|
|
53
|
+
"devops-git": [
|
|
54
|
+
"github", "git", "pull request", "ci", "cd", "commit", "merge",
|
|
55
|
+
"workflow", "actions", "yeet", "fix ci", "address comments",
|
|
56
|
+
"failing check", "actions check", "quebrado", "falhando", "pipeline",
|
|
57
|
+
],
|
|
58
|
+
"video-media": [
|
|
59
|
+
"remotion", "video", "animation", "render", "composition", "ffmpeg",
|
|
60
|
+
"media", "audio",
|
|
61
|
+
],
|
|
62
|
+
"integrations": [
|
|
63
|
+
"twilio", "zoom", "slack", "notion", "airtable", "jira", "linear",
|
|
64
|
+
"stripe api", "webhook", "oauth", "sdk", "mcp", "salesforce",
|
|
65
|
+
"hubspot", "intercom",
|
|
66
|
+
],
|
|
67
|
+
"meta": [
|
|
68
|
+
"skill creator", "skill installer", "plugin creator", "create skill",
|
|
69
|
+
"openai docs", "imagegen", "context7", "documentation library",
|
|
70
|
+
],
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
NAME_PREFIX_DOMAIN: list[tuple[str, str]] = [
|
|
74
|
+
("build-web-data-visualization-", "data-viz"),
|
|
75
|
+
("data-analytics-", "analytics"),
|
|
76
|
+
("product-design-", "design"),
|
|
77
|
+
("creative-production-", "creative"),
|
|
78
|
+
("gh-", "devops-git"),
|
|
79
|
+
("netlify-", "web"),
|
|
80
|
+
("twilio-", "integrations"),
|
|
81
|
+
("zoom-", "integrations"),
|
|
82
|
+
("figma-", "design"),
|
|
83
|
+
("codex-", "meta"),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
HUB_SKILLS: set[str] = {
|
|
87
|
+
"index",
|
|
88
|
+
"product-design-index",
|
|
89
|
+
"data-visualization",
|
|
90
|
+
"build-web-data-visualization-data-visualization",
|
|
91
|
+
"explore",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _text_blob(name: str, description: str) -> str:
|
|
96
|
+
return f"{name} {description}".lower()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def classify_skill(name: str, description: str) -> str:
|
|
100
|
+
if name.lower() == "superdesign":
|
|
101
|
+
return "design"
|
|
102
|
+
|
|
103
|
+
blob = _text_blob(name, description)
|
|
104
|
+
for prefix, domain in NAME_PREFIX_DOMAIN:
|
|
105
|
+
if name.lower().startswith(prefix):
|
|
106
|
+
return domain
|
|
107
|
+
|
|
108
|
+
scores = {domain: 0 for domain in DOMAINS}
|
|
109
|
+
for domain, keywords in DOMAIN_KEYWORDS.items():
|
|
110
|
+
for kw in keywords:
|
|
111
|
+
if kw in blob:
|
|
112
|
+
scores[domain] += 1
|
|
113
|
+
|
|
114
|
+
best = max(scores, key=scores.get)
|
|
115
|
+
if scores[best] > 0:
|
|
116
|
+
return best
|
|
117
|
+
return "general"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def classify_query(query: str) -> tuple[str, dict[str, int]]:
|
|
121
|
+
query_lower = query.lower()
|
|
122
|
+
scores = {domain: 0 for domain in DOMAINS}
|
|
123
|
+
for domain, keywords in DOMAIN_KEYWORDS.items():
|
|
124
|
+
for kw in keywords:
|
|
125
|
+
if kw in query_lower:
|
|
126
|
+
scores[domain] += 1
|
|
127
|
+
|
|
128
|
+
best = max(scores, key=scores.get)
|
|
129
|
+
if scores[best] == 0:
|
|
130
|
+
return "general", scores
|
|
131
|
+
return best, scores
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def domain_label(domain: str) -> str:
|
|
135
|
+
labels = {
|
|
136
|
+
"web": "Web / apps",
|
|
137
|
+
"data-viz": "Data visualization",
|
|
138
|
+
"analytics": "Data analytics",
|
|
139
|
+
"design": "Product design",
|
|
140
|
+
"creative": "Creative production",
|
|
141
|
+
"devops-git": "Git / CI / DevOps",
|
|
142
|
+
"video-media": "Video / media",
|
|
143
|
+
"integrations": "Integrations / SDKs",
|
|
144
|
+
"security": "Cybersecurity",
|
|
145
|
+
"meta": "Meta / tooling",
|
|
146
|
+
"general": "General",
|
|
147
|
+
}
|
|
148
|
+
return labels.get(domain, domain)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Workflow intent profiles that boost BM25 matches beyond raw text similarity."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
INTENT_PROFILES: list[dict[str, Any]] = [
|
|
10
|
+
{
|
|
11
|
+
"name": "root-cause-debugging",
|
|
12
|
+
"task_patterns": [
|
|
13
|
+
r"\b(root[- ]?cause|debug|bug|bugs?|crash|exception|traceback)\b",
|
|
14
|
+
r"\b(failing tests?|test failures?)\b",
|
|
15
|
+
r"\b(depurar|depuracao|raiz|falhando|erro|bug)\b",
|
|
16
|
+
],
|
|
17
|
+
"skill_patterns": [
|
|
18
|
+
r"systematic-debugging",
|
|
19
|
+
r"root[- ]?cause",
|
|
20
|
+
r"troubleshooting",
|
|
21
|
+
r"problem-solving",
|
|
22
|
+
],
|
|
23
|
+
"score_multiplier": 1.45,
|
|
24
|
+
"min_boost": 1.5,
|
|
25
|
+
"suggested_mode": "auto-load",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "frontend-design",
|
|
29
|
+
"task_patterns": [
|
|
30
|
+
r"\b(landing page|redesign|ui|frontend|visual design|superdesign)\b",
|
|
31
|
+
r"\b(dashboard|mockup|wireframe|figma)\b",
|
|
32
|
+
r"\b(interface|layout|design system)\b",
|
|
33
|
+
],
|
|
34
|
+
"skill_patterns": [
|
|
35
|
+
r"superdesign",
|
|
36
|
+
r"shadcn",
|
|
37
|
+
r"frontend",
|
|
38
|
+
r"ui-ux",
|
|
39
|
+
r"design",
|
|
40
|
+
r"mockup",
|
|
41
|
+
],
|
|
42
|
+
"score_multiplier": 1.4,
|
|
43
|
+
"min_boost": 1.2,
|
|
44
|
+
"suggested_mode": "auto-load",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "devops-ci",
|
|
48
|
+
"task_patterns": [
|
|
49
|
+
r"\b(ci|github actions|pull request|pr|pipeline)\b",
|
|
50
|
+
r"\b(falhando|quebrado|fix ci)\b",
|
|
51
|
+
],
|
|
52
|
+
"skill_patterns": [
|
|
53
|
+
r"gh-fix-ci",
|
|
54
|
+
r"github",
|
|
55
|
+
r"yeet",
|
|
56
|
+
r"ci",
|
|
57
|
+
],
|
|
58
|
+
"score_multiplier": 1.35,
|
|
59
|
+
"min_boost": 1.0,
|
|
60
|
+
"suggested_mode": "auto-load",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "security-forensics",
|
|
64
|
+
"task_patterns": [
|
|
65
|
+
r"\b(forensics|volatility|memory dump|credential|mitre|dfir)\b",
|
|
66
|
+
r"\b(seguranca|forense|malware|incident)\b",
|
|
67
|
+
],
|
|
68
|
+
"skill_patterns": [
|
|
69
|
+
r"forensics",
|
|
70
|
+
r"volatility",
|
|
71
|
+
r"credential",
|
|
72
|
+
r"malware",
|
|
73
|
+
r"incident-response",
|
|
74
|
+
],
|
|
75
|
+
"score_multiplier": 1.5,
|
|
76
|
+
"min_boost": 2.0,
|
|
77
|
+
"suggested_mode": "auto-load",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "skill-discovery",
|
|
81
|
+
"task_patterns": [
|
|
82
|
+
r"\bfind (a )?skill\b",
|
|
83
|
+
r"\bnpx skills\b",
|
|
84
|
+
r"\bskills\.sh\b",
|
|
85
|
+
r"\btem skill (para|de)\b",
|
|
86
|
+
r"\binstalar skill\b",
|
|
87
|
+
r"\bis there a skill\b",
|
|
88
|
+
r"\bdiscover (agent )?skills\b",
|
|
89
|
+
],
|
|
90
|
+
"skill_patterns": [
|
|
91
|
+
r"find-skills",
|
|
92
|
+
r"create-skill",
|
|
93
|
+
r"skill-creator",
|
|
94
|
+
r"skill-installer",
|
|
95
|
+
],
|
|
96
|
+
"score_multiplier": 2.0,
|
|
97
|
+
"min_boost": 3.0,
|
|
98
|
+
"suggested_mode": "auto-load",
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
FORCE_DISCOVER_PATTERNS: list[str] = [
|
|
103
|
+
r"\bfind (a )?skill\b",
|
|
104
|
+
r"\bnpx skills\b",
|
|
105
|
+
r"\bskills\.sh\b",
|
|
106
|
+
r"\btem skill (para|de)\b",
|
|
107
|
+
r"\binstalar skill\b",
|
|
108
|
+
r"\bis there a skill\b",
|
|
109
|
+
r"\bdiscover (agent )?skills\b",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
BYPASS_PATTERNS = [
|
|
113
|
+
r"^\s*(hi|hello|hey|oi|ola|olá)\b",
|
|
114
|
+
r"^\s*(what time|que horas)\b",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def matches_any_pattern(text: str, patterns: list[str]) -> bool:
|
|
119
|
+
return any(re.search(pattern, text, re.IGNORECASE) for pattern in patterns)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def is_bypass_task(task: str) -> bool:
|
|
123
|
+
task_lower = task.strip().lower()
|
|
124
|
+
if not task_lower:
|
|
125
|
+
return False
|
|
126
|
+
return matches_any_pattern(task_lower, BYPASS_PATTERNS)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def is_force_discover(task: str) -> bool:
|
|
130
|
+
if is_bypass_task(task):
|
|
131
|
+
return False
|
|
132
|
+
return matches_any_pattern(task, FORCE_DISCOVER_PATTERNS)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def task_intents(task: str) -> list[dict[str, Any]]:
|
|
136
|
+
if is_bypass_task(task):
|
|
137
|
+
return []
|
|
138
|
+
return [
|
|
139
|
+
profile
|
|
140
|
+
for profile in INTENT_PROFILES
|
|
141
|
+
if matches_any_pattern(task, list(profile.get("task_patterns", [])))
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def apply_intent_boost(
|
|
146
|
+
score: float,
|
|
147
|
+
skill_name: str,
|
|
148
|
+
skill_text: str,
|
|
149
|
+
intents: list[dict[str, Any]],
|
|
150
|
+
) -> tuple[float, list[str], str]:
|
|
151
|
+
adjusted = score
|
|
152
|
+
matched: list[str] = []
|
|
153
|
+
suggested_mode = ""
|
|
154
|
+
searchable = f"{skill_name} {skill_text}".lower()
|
|
155
|
+
|
|
156
|
+
for profile in intents:
|
|
157
|
+
if not matches_any_pattern(searchable, list(profile.get("skill_patterns", []))):
|
|
158
|
+
continue
|
|
159
|
+
matched.append(str(profile.get("name", "")))
|
|
160
|
+
multiplier = float(profile.get("score_multiplier", 1.0))
|
|
161
|
+
min_boost = float(profile.get("min_boost", 0.0))
|
|
162
|
+
adjusted = max(adjusted * multiplier, min_boost)
|
|
163
|
+
mode = str(profile.get("suggested_mode", "") or "")
|
|
164
|
+
if mode:
|
|
165
|
+
suggested_mode = mode
|
|
166
|
+
|
|
167
|
+
return adjusted, [name for name in matched if name], suggested_mode
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared paths for Maestro (multi-agent, agent-agnostic home)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
MAESTRO_HOME = Path(os.environ.get("MAESTRO_HOME", Path.home() / ".maestro"))
|
|
10
|
+
MANIFEST_PATH = MAESTRO_HOME / "skills-manifest.json"
|
|
11
|
+
EXCLUDE_PATH = MAESTRO_HOME / "maestro-exclude.txt"
|
|
12
|
+
CONFIG_PATH = MAESTRO_HOME / "config.json"
|
|
13
|
+
|
|
14
|
+
LEGACY_CURSOR_HOME = Path(os.environ.get("CURSOR_HOME", Path.home() / ".cursor"))
|
|
15
|
+
LEGACY_MANIFEST_PATH = LEGACY_CURSOR_HOME / "skills-manifest.json"
|
|
16
|
+
LEGACY_EXCLUDE_PATH = LEGACY_CURSOR_HOME / "maestro-exclude.txt"
|
|
17
|
+
|
|
18
|
+
# (skills directory, scope label)
|
|
19
|
+
GLOBAL_SKILL_ROOTS: list[tuple[Path, str]] = [
|
|
20
|
+
(Path.home() / ".cursor" / "skills", "cursor"),
|
|
21
|
+
(Path.home() / ".claude" / "skills", "claude"),
|
|
22
|
+
(Path.home() / ".codex" / "skills", "codex"),
|
|
23
|
+
(Path.home() / ".agents" / "skills", "agents"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def project_skill_roots(project_root: Path) -> list[tuple[Path, str]]:
|
|
28
|
+
root = project_root.resolve()
|
|
29
|
+
return [
|
|
30
|
+
(root / ".cursor" / "skills", "project-cursor"),
|
|
31
|
+
(root / ".claude" / "skills", "project-claude"),
|
|
32
|
+
(root / ".codex" / "skills", "project-codex"),
|
|
33
|
+
(root / ".agents" / "skills", "project-agents"),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def all_skill_roots(project_root: Path | None = None) -> list[tuple[Path, str]]:
|
|
38
|
+
roots = list(GLOBAL_SKILL_ROOTS)
|
|
39
|
+
if project_root:
|
|
40
|
+
roots.extend(project_skill_roots(project_root))
|
|
41
|
+
return roots
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Batch route decomposed tasks using the fast manifest-based search engine."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
12
|
+
sys.path.insert(0, str(SCRIPT_DIR))
|
|
13
|
+
|
|
14
|
+
from search_skills import DEFAULT_MANIFEST, load_manifest, search_skills # noqa: E402
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def route_batch(
|
|
18
|
+
tasks: list[str],
|
|
19
|
+
manifest_path: Path,
|
|
20
|
+
) -> dict:
|
|
21
|
+
manifest = load_manifest(manifest_path)
|
|
22
|
+
results = [
|
|
23
|
+
search_skills(task.strip(), manifest)
|
|
24
|
+
for task in tasks
|
|
25
|
+
if task.strip()
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
all_gaps: list[str] = []
|
|
29
|
+
all_gap_notes: list[str] = []
|
|
30
|
+
discover_triggered = False
|
|
31
|
+
for result in results:
|
|
32
|
+
discover = result.get("discover", {})
|
|
33
|
+
if discover.get("triggered"):
|
|
34
|
+
discover_triggered = True
|
|
35
|
+
for gap in discover.get("gaps", []):
|
|
36
|
+
if gap not in all_gaps:
|
|
37
|
+
all_gaps.append(gap)
|
|
38
|
+
for note in discover.get("gap_notes", []):
|
|
39
|
+
if note not in all_gap_notes:
|
|
40
|
+
all_gap_notes.append(note)
|
|
41
|
+
|
|
42
|
+
priorities = [r.get("routing", {}).get("priority", "P3") for r in results]
|
|
43
|
+
if "P0" in priorities:
|
|
44
|
+
batch_priority, batch_decision = "P0", "recommend"
|
|
45
|
+
elif "P1" in priorities:
|
|
46
|
+
batch_priority, batch_decision = "P1", "auto-load"
|
|
47
|
+
elif "P2" in priorities:
|
|
48
|
+
batch_priority, batch_decision = "P2", "optional-load"
|
|
49
|
+
else:
|
|
50
|
+
batch_priority, batch_decision = "P3", "bypass"
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
"batch": True,
|
|
54
|
+
"task_count": len(results),
|
|
55
|
+
"routing": {
|
|
56
|
+
"priority": batch_priority,
|
|
57
|
+
"decision": batch_decision,
|
|
58
|
+
"report_policy": "report" if batch_priority == "P0" else "silent",
|
|
59
|
+
},
|
|
60
|
+
"results": results,
|
|
61
|
+
"discover": {
|
|
62
|
+
"triggered": discover_triggered,
|
|
63
|
+
"gaps": all_gaps,
|
|
64
|
+
"gap_notes": all_gap_notes,
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main() -> int:
|
|
70
|
+
parser = argparse.ArgumentParser(description="Route decomposed tasks to skills")
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"tasks",
|
|
73
|
+
nargs="*",
|
|
74
|
+
help="Task strings; omit and use stdin for batch mode",
|
|
75
|
+
)
|
|
76
|
+
parser.add_argument("--manifest", default=str(DEFAULT_MANIFEST))
|
|
77
|
+
parser.add_argument("--json", action="store_true")
|
|
78
|
+
args = parser.parse_args()
|
|
79
|
+
|
|
80
|
+
if args.tasks:
|
|
81
|
+
tasks = args.tasks
|
|
82
|
+
else:
|
|
83
|
+
tasks = [line.strip() for line in sys.stdin if line.strip()]
|
|
84
|
+
|
|
85
|
+
payload = route_batch(tasks, Path(args.manifest))
|
|
86
|
+
|
|
87
|
+
if args.json or not sys.stdout.isatty():
|
|
88
|
+
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
89
|
+
else:
|
|
90
|
+
for result in payload["results"]:
|
|
91
|
+
top = result["results"][0]["name"] if result.get("results") else "(none)"
|
|
92
|
+
routing = result.get("routing", {})
|
|
93
|
+
print(
|
|
94
|
+
f"- {result['query']}: {top} "
|
|
95
|
+
f"[{routing.get('priority')} / {routing.get('decision')}]"
|
|
96
|
+
)
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
raise SystemExit(main())
|