@ia-ccun/code-agent-cli 0.0.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/README.md +211 -0
- package/bin/cli.js +83 -0
- package/config/agent/APPEND_SYSTEM.md +48 -0
- package/config/agent/SYSTEM.md +33 -0
- package/config/agent/bin/fd +0 -0
- package/config/agent/extensions/context.ts +578 -0
- package/config/agent/extensions/custom-footer.ts +170 -0
- package/config/agent/extensions/custom.ts +289 -0
- package/config/agent/extensions/review.ts +1281 -0
- package/config/agent/extensions/working-msg.ts +96 -0
- package/config/agent/help.md +364 -0
- package/config/agent/models.json +56 -0
- package/config/agent/prompts/feat.md +106 -0
- package/config/agent/prompts/git-commit.md +159 -0
- package/config/agent/prompts/git-rollback.md +91 -0
- package/config/agent/prompts/git-worktree.md +277 -0
- package/config/agent/prompts/help.md +10 -0
- package/config/agent/prompts/init-project.md +53 -0
- package/config/agent/prompts/workflow.md +194 -0
- package/config/agent/settings.json +7 -0
- package/config/agent/skills/code-review/SKILL.md +50 -0
- package/config/agent/skills/commit/SKILL.md +51 -0
- package/config/agent/skills/csv-data-summarizer/SKILL.md +149 -0
- package/config/agent/skills/csv-data-summarizer/analyze.py +182 -0
- package/config/agent/skills/csv-data-summarizer/examples/showcase_financial_pl_data.csv +46 -0
- package/config/agent/skills/csv-data-summarizer/requirements.txt +4 -0
- package/config/agent/skills/csv-data-summarizer/resources/sample.csv +22 -0
- package/config/agent/skills/find-skills/SKILL.md +133 -0
- package/config/agent/skills/frontend-design/LICENSE.txt +177 -0
- package/config/agent/skills/frontend-design/SKILL.md +42 -0
- package/config/agent/skills/github/SKILL.md +47 -0
- package/config/agent/skills/hello/SKILL.md +23 -0
- package/config/agent/skills/librarian/SKILL.md +195 -0
- package/config/agent/skills/markdown-to-html/SKILL.md +62 -0
- package/config/agent/skills/pr/SKILL.md +56 -0
- package/config/agent/skills/refactor/SKILL.md +37 -0
- package/config/agent/skills/skill-creator/LICENSE.txt +202 -0
- package/config/agent/skills/skill-creator/SKILL.md +356 -0
- package/config/agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/config/agent/skills/skill-creator/references/workflows.md +28 -0
- package/config/agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/config/agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/config/agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/config/agent/skills/ui-ux-pro-max/SKILL.md +264 -0
- package/config/agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/config/agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/config/agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/config/agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/config/agent/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/config/agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/config/agent/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/config/agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/config/agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/config/agent/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/config/agent/skills/ui-ux-pro-max/scripts/analyze.py +434 -0
- package/config/agent/skills/ui-ux-pro-max/scripts/core.py +238 -0
- package/config/agent/skills/ui-ux-pro-max/scripts/search.py +61 -0
- package/config/agent/skills/unit-test/SKILL.md +115 -0
- package/config/agent/themes/catppuccin-mocha.json +99 -0
- package/config.json +6 -0
- package/dist/banner.d.ts +10 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/banner.js +32 -0
- package/dist/banner.js.map +1 -0
- package/dist/config-loader.d.ts +17 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +60 -0
- package/dist/config-loader.js.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/scripts/postinstall.js +197 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UI/UX Pro Max Core - BM25 search engine for UI/UX style guides
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from math import log
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
|
|
13
|
+
# ============ CONFIGURATION ============
|
|
14
|
+
DATA_DIR = Path(__file__).parent.parent / "data"
|
|
15
|
+
MAX_RESULTS = 3
|
|
16
|
+
|
|
17
|
+
CSV_CONFIG = {
|
|
18
|
+
"style": {
|
|
19
|
+
"file": "styles.csv",
|
|
20
|
+
"search_cols": ["Style Category", "Keywords", "Best For", "Type"],
|
|
21
|
+
"output_cols": ["Style Category", "Type", "Keywords", "Primary Colors", "Effects & Animation", "Best For", "Performance", "Accessibility", "Framework Compatibility", "Complexity"]
|
|
22
|
+
},
|
|
23
|
+
"prompt": {
|
|
24
|
+
"file": "prompts.csv",
|
|
25
|
+
"search_cols": ["Style Category", "AI Prompt Keywords (Copy-Paste Ready)", "CSS/Technical Keywords"],
|
|
26
|
+
"output_cols": ["Style Category", "AI Prompt Keywords (Copy-Paste Ready)", "CSS/Technical Keywords", "Implementation Checklist"]
|
|
27
|
+
},
|
|
28
|
+
"color": {
|
|
29
|
+
"file": "colors.csv",
|
|
30
|
+
"search_cols": ["Product Type", "Keywords", "Notes"],
|
|
31
|
+
"output_cols": ["Product Type", "Keywords", "Primary (Hex)", "Secondary (Hex)", "CTA (Hex)", "Background (Hex)", "Text (Hex)", "Border (Hex)", "Notes"]
|
|
32
|
+
},
|
|
33
|
+
"chart": {
|
|
34
|
+
"file": "charts.csv",
|
|
35
|
+
"search_cols": ["Data Type", "Keywords", "Best Chart Type", "Accessibility Notes"],
|
|
36
|
+
"output_cols": ["Data Type", "Keywords", "Best Chart Type", "Secondary Options", "Color Guidance", "Accessibility Notes", "Library Recommendation", "Interactive Level"]
|
|
37
|
+
},
|
|
38
|
+
"landing": {
|
|
39
|
+
"file": "landing.csv",
|
|
40
|
+
"search_cols": ["Pattern Name", "Keywords", "Conversion Optimization", "Section Order"],
|
|
41
|
+
"output_cols": ["Pattern Name", "Keywords", "Section Order", "Primary CTA Placement", "Color Strategy", "Conversion Optimization"]
|
|
42
|
+
},
|
|
43
|
+
"product": {
|
|
44
|
+
"file": "products.csv",
|
|
45
|
+
"search_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Key Considerations"],
|
|
46
|
+
"output_cols": ["Product Type", "Keywords", "Primary Style Recommendation", "Secondary Styles", "Landing Page Pattern", "Dashboard Style (if applicable)", "Color Palette Focus"]
|
|
47
|
+
},
|
|
48
|
+
"ux": {
|
|
49
|
+
"file": "ux-guidelines.csv",
|
|
50
|
+
"search_cols": ["Category", "Issue", "Description", "Platform"],
|
|
51
|
+
"output_cols": ["Category", "Issue", "Platform", "Description", "Do", "Don't", "Code Example Good", "Code Example Bad", "Severity"]
|
|
52
|
+
},
|
|
53
|
+
"typography": {
|
|
54
|
+
"file": "typography.csv",
|
|
55
|
+
"search_cols": ["Font Pairing Name", "Category", "Mood/Style Keywords", "Best For", "Heading Font", "Body Font"],
|
|
56
|
+
"output_cols": ["Font Pairing Name", "Category", "Heading Font", "Body Font", "Mood/Style Keywords", "Best For", "Google Fonts URL", "CSS Import", "Tailwind Config", "Notes"]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
STACK_CONFIG = {
|
|
61
|
+
"html-tailwind": {"file": "stacks/html-tailwind.csv"},
|
|
62
|
+
"react": {"file": "stacks/react.csv"},
|
|
63
|
+
"nextjs": {"file": "stacks/nextjs.csv"},
|
|
64
|
+
"vue": {"file": "stacks/vue.csv"},
|
|
65
|
+
"nuxtjs": {"file": "stacks/nuxtjs.csv"},
|
|
66
|
+
"nuxt-ui": {"file": "stacks/nuxt-ui.csv"},
|
|
67
|
+
"svelte": {"file": "stacks/svelte.csv"},
|
|
68
|
+
"swiftui": {"file": "stacks/swiftui.csv"},
|
|
69
|
+
"react-native": {"file": "stacks/react-native.csv"},
|
|
70
|
+
"flutter": {"file": "stacks/flutter.csv"}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Common columns for all stacks
|
|
74
|
+
_STACK_COLS = {
|
|
75
|
+
"search_cols": ["Category", "Guideline", "Description", "Do", "Don't"],
|
|
76
|
+
"output_cols": ["Category", "Guideline", "Description", "Do", "Don't", "Code Good", "Code Bad", "Severity", "Docs URL"]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
AVAILABLE_STACKS = list(STACK_CONFIG.keys())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ============ BM25 IMPLEMENTATION ============
|
|
83
|
+
class BM25:
|
|
84
|
+
"""BM25 ranking algorithm for text search"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, k1=1.5, b=0.75):
|
|
87
|
+
self.k1 = k1
|
|
88
|
+
self.b = b
|
|
89
|
+
self.corpus = []
|
|
90
|
+
self.doc_lengths = []
|
|
91
|
+
self.avgdl = 0
|
|
92
|
+
self.idf = {}
|
|
93
|
+
self.doc_freqs = defaultdict(int)
|
|
94
|
+
self.N = 0
|
|
95
|
+
|
|
96
|
+
def tokenize(self, text):
|
|
97
|
+
"""Lowercase, split, remove punctuation, filter short words"""
|
|
98
|
+
text = re.sub(r'[^\w\s]', ' ', str(text).lower())
|
|
99
|
+
return [w for w in text.split() if len(w) > 2]
|
|
100
|
+
|
|
101
|
+
def fit(self, documents):
|
|
102
|
+
"""Build BM25 index from documents"""
|
|
103
|
+
self.corpus = [self.tokenize(doc) for doc in documents]
|
|
104
|
+
self.N = len(self.corpus)
|
|
105
|
+
if self.N == 0:
|
|
106
|
+
return
|
|
107
|
+
self.doc_lengths = [len(doc) for doc in self.corpus]
|
|
108
|
+
self.avgdl = sum(self.doc_lengths) / self.N
|
|
109
|
+
|
|
110
|
+
for doc in self.corpus:
|
|
111
|
+
seen = set()
|
|
112
|
+
for word in doc:
|
|
113
|
+
if word not in seen:
|
|
114
|
+
self.doc_freqs[word] += 1
|
|
115
|
+
seen.add(word)
|
|
116
|
+
|
|
117
|
+
for word, freq in self.doc_freqs.items():
|
|
118
|
+
self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1)
|
|
119
|
+
|
|
120
|
+
def score(self, query):
|
|
121
|
+
"""Score all documents against query"""
|
|
122
|
+
query_tokens = self.tokenize(query)
|
|
123
|
+
scores = []
|
|
124
|
+
|
|
125
|
+
for idx, doc in enumerate(self.corpus):
|
|
126
|
+
score = 0
|
|
127
|
+
doc_len = self.doc_lengths[idx]
|
|
128
|
+
term_freqs = defaultdict(int)
|
|
129
|
+
for word in doc:
|
|
130
|
+
term_freqs[word] += 1
|
|
131
|
+
|
|
132
|
+
for token in query_tokens:
|
|
133
|
+
if token in self.idf:
|
|
134
|
+
tf = term_freqs[token]
|
|
135
|
+
idf = self.idf[token]
|
|
136
|
+
numerator = tf * (self.k1 + 1)
|
|
137
|
+
denominator = tf + self.k1 * (1 - self.b + self.b * doc_len / self.avgdl)
|
|
138
|
+
score += idf * numerator / denominator
|
|
139
|
+
|
|
140
|
+
scores.append((idx, score))
|
|
141
|
+
|
|
142
|
+
return sorted(scores, key=lambda x: x[1], reverse=True)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ============ SEARCH FUNCTIONS ============
|
|
146
|
+
def _load_csv(filepath):
|
|
147
|
+
"""Load CSV and return list of dicts"""
|
|
148
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
149
|
+
return list(csv.DictReader(f))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _search_csv(filepath, search_cols, output_cols, query, max_results):
|
|
153
|
+
"""Core search function using BM25"""
|
|
154
|
+
if not filepath.exists():
|
|
155
|
+
return []
|
|
156
|
+
|
|
157
|
+
data = _load_csv(filepath)
|
|
158
|
+
|
|
159
|
+
# Build documents from search columns
|
|
160
|
+
documents = [" ".join(str(row.get(col, "")) for col in search_cols) for row in data]
|
|
161
|
+
|
|
162
|
+
# BM25 search
|
|
163
|
+
bm25 = BM25()
|
|
164
|
+
bm25.fit(documents)
|
|
165
|
+
ranked = bm25.score(query)
|
|
166
|
+
|
|
167
|
+
# Get top results with score > 0
|
|
168
|
+
results = []
|
|
169
|
+
for idx, score in ranked[:max_results]:
|
|
170
|
+
if score > 0:
|
|
171
|
+
row = data[idx]
|
|
172
|
+
results.append({col: row.get(col, "") for col in output_cols if col in row})
|
|
173
|
+
|
|
174
|
+
return results
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def detect_domain(query):
|
|
178
|
+
"""Auto-detect the most relevant domain from query"""
|
|
179
|
+
query_lower = query.lower()
|
|
180
|
+
|
|
181
|
+
domain_keywords = {
|
|
182
|
+
"color": ["color", "palette", "hex", "#", "rgb"],
|
|
183
|
+
"chart": ["chart", "graph", "visualization", "trend", "bar", "pie", "scatter", "heatmap", "funnel"],
|
|
184
|
+
"landing": ["landing", "page", "cta", "conversion", "hero", "testimonial", "pricing", "section"],
|
|
185
|
+
"product": ["saas", "ecommerce", "e-commerce", "fintech", "healthcare", "gaming", "portfolio", "crypto", "dashboard"],
|
|
186
|
+
"prompt": ["prompt", "css", "implementation", "variable", "checklist", "tailwind"],
|
|
187
|
+
"style": ["style", "design", "ui", "minimalism", "glassmorphism", "neumorphism", "brutalism", "dark mode", "flat", "aurora"],
|
|
188
|
+
"ux": ["ux", "usability", "accessibility", "wcag", "touch", "scroll", "animation", "keyboard", "navigation", "mobile"],
|
|
189
|
+
"typography": ["font", "typography", "heading", "serif", "sans"]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
scores = {domain: sum(1 for kw in keywords if kw in query_lower) for domain, keywords in domain_keywords.items()}
|
|
193
|
+
best = max(scores, key=scores.get)
|
|
194
|
+
return best if scores[best] > 0 else "style"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def search(query, domain=None, max_results=MAX_RESULTS):
|
|
198
|
+
"""Main search function with auto-domain detection"""
|
|
199
|
+
if domain is None:
|
|
200
|
+
domain = detect_domain(query)
|
|
201
|
+
|
|
202
|
+
config = CSV_CONFIG.get(domain, CSV_CONFIG["style"])
|
|
203
|
+
filepath = DATA_DIR / config["file"]
|
|
204
|
+
|
|
205
|
+
if not filepath.exists():
|
|
206
|
+
return {"error": f"File not found: {filepath}", "domain": domain}
|
|
207
|
+
|
|
208
|
+
results = _search_csv(filepath, config["search_cols"], config["output_cols"], query, max_results)
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
"domain": domain,
|
|
212
|
+
"query": query,
|
|
213
|
+
"file": config["file"],
|
|
214
|
+
"count": len(results),
|
|
215
|
+
"results": results
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def search_stack(query, stack, max_results=MAX_RESULTS):
|
|
220
|
+
"""Search stack-specific guidelines"""
|
|
221
|
+
if stack not in STACK_CONFIG:
|
|
222
|
+
return {"error": f"Unknown stack: {stack}. Available: {', '.join(AVAILABLE_STACKS)}"}
|
|
223
|
+
|
|
224
|
+
filepath = DATA_DIR / STACK_CONFIG[stack]["file"]
|
|
225
|
+
|
|
226
|
+
if not filepath.exists():
|
|
227
|
+
return {"error": f"Stack file not found: {filepath}", "stack": stack}
|
|
228
|
+
|
|
229
|
+
results = _search_csv(filepath, _STACK_COLS["search_cols"], _STACK_COLS["output_cols"], query, max_results)
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
"domain": "stack",
|
|
233
|
+
"stack": stack,
|
|
234
|
+
"query": query,
|
|
235
|
+
"file": STACK_CONFIG[stack]["file"],
|
|
236
|
+
"count": len(results),
|
|
237
|
+
"results": results
|
|
238
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UI/UX Pro Max Search - BM25 search engine for UI/UX style guides
|
|
5
|
+
Usage: python search.py "<query>" [--domain <domain>] [--stack <stack>] [--max-results 3]
|
|
6
|
+
|
|
7
|
+
Domains: style, prompt, color, chart, landing, product, ux, typography
|
|
8
|
+
Stacks: html-tailwind, react, nextjs
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
from core import CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, search, search_stack
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def format_output(result):
|
|
16
|
+
"""Format results for Claude consumption (token-optimized)"""
|
|
17
|
+
if "error" in result:
|
|
18
|
+
return f"Error: {result['error']}"
|
|
19
|
+
|
|
20
|
+
output = []
|
|
21
|
+
if result.get("stack"):
|
|
22
|
+
output.append(f"## UI Pro Max Stack Guidelines")
|
|
23
|
+
output.append(f"**Stack:** {result['stack']} | **Query:** {result['query']}")
|
|
24
|
+
else:
|
|
25
|
+
output.append(f"## UI Pro Max Search Results")
|
|
26
|
+
output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}")
|
|
27
|
+
output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n")
|
|
28
|
+
|
|
29
|
+
for i, row in enumerate(result['results'], 1):
|
|
30
|
+
output.append(f"### Result {i}")
|
|
31
|
+
for key, value in row.items():
|
|
32
|
+
value_str = str(value)
|
|
33
|
+
if len(value_str) > 300:
|
|
34
|
+
value_str = value_str[:300] + "..."
|
|
35
|
+
output.append(f"- **{key}:** {value_str}")
|
|
36
|
+
output.append("")
|
|
37
|
+
|
|
38
|
+
return "\n".join(output)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
parser = argparse.ArgumentParser(description="UI Pro Max Search")
|
|
43
|
+
parser.add_argument("query", help="Search query")
|
|
44
|
+
parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
|
|
45
|
+
parser.add_argument("--stack", "-s", choices=AVAILABLE_STACKS, help="Stack-specific search (html-tailwind, react, nextjs)")
|
|
46
|
+
parser.add_argument("--max-results", "-n", type=int, default=MAX_RESULTS, help="Max results (default: 3)")
|
|
47
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
48
|
+
|
|
49
|
+
args = parser.parse_args()
|
|
50
|
+
|
|
51
|
+
# Stack search takes priority
|
|
52
|
+
if args.stack:
|
|
53
|
+
result = search_stack(args.query, args.stack, args.max_results)
|
|
54
|
+
else:
|
|
55
|
+
result = search(args.query, args.domain, args.max_results)
|
|
56
|
+
|
|
57
|
+
if args.json:
|
|
58
|
+
import json
|
|
59
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
60
|
+
else:
|
|
61
|
+
print(format_output(result))
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unit-test
|
|
3
|
+
description: "为选定的代码文件生成单元测试。"
|
|
4
|
+
author: xujianjiang
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 生成单元测试 Skill
|
|
8
|
+
|
|
9
|
+
## 描述
|
|
10
|
+
为选定的代码文件生成单元测试。如果单测文件已存在,就补充单测。
|
|
11
|
+
|
|
12
|
+
## 触发方式
|
|
13
|
+
`/skill:unit-test` xxxxxFacadeImpl.java
|
|
14
|
+
|
|
15
|
+
## 执行步骤
|
|
16
|
+
|
|
17
|
+
1. **读取源文件**
|
|
18
|
+
- 使用 `read` 工具读取需要测试的代码文件
|
|
19
|
+
|
|
20
|
+
2. **分析代码结构**
|
|
21
|
+
- 识别函数、类、方法
|
|
22
|
+
- 确定输入输出
|
|
23
|
+
- 识别边界条件
|
|
24
|
+
- 分析依赖关系
|
|
25
|
+
|
|
26
|
+
3. **检查现有测试文件**
|
|
27
|
+
- 查找对应的测试文件
|
|
28
|
+
- 如果存在,读取现有测试内容
|
|
29
|
+
- 确定需要补充的测试用例
|
|
30
|
+
|
|
31
|
+
4. **生成测试用例**
|
|
32
|
+
- 正常情况测试
|
|
33
|
+
- 边界条件测试
|
|
34
|
+
- 异常情况测试
|
|
35
|
+
- 模拟依赖
|
|
36
|
+
- 覆盖所有分支和条件
|
|
37
|
+
|
|
38
|
+
5. **创建或更新测试文件**
|
|
39
|
+
- 放在 `test/` 或 `__tests__/` 目录
|
|
40
|
+
- 使用合适的测试框架 (JUnit4/Jest/vitest/unittest/pytest)
|
|
41
|
+
- 保持现有测试结构和风格
|
|
42
|
+
|
|
43
|
+
## 输出
|
|
44
|
+
生成完整的测试文件,包含测试类、测试方法和断言。
|
|
45
|
+
|
|
46
|
+
## 单元测试规范
|
|
47
|
+
|
|
48
|
+
### Java项目 (JUnit4)
|
|
49
|
+
```java
|
|
50
|
+
public class XXXTest {
|
|
51
|
+
// 测试方法命名:test + 方法名 + 场景描述
|
|
52
|
+
@Test
|
|
53
|
+
public void testMethodName_WhenCondition_ThenExpectation() {
|
|
54
|
+
// Given - 准备测试数据
|
|
55
|
+
// When - 执行被测方法
|
|
56
|
+
// Then - 验证结果
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 使用断言验证结果
|
|
60
|
+
assertEquals(expected, actual);
|
|
61
|
+
assertNotNull(object);
|
|
62
|
+
assertTrue(condition);
|
|
63
|
+
|
|
64
|
+
// 使用Mockito模拟依赖
|
|
65
|
+
@Mock
|
|
66
|
+
private Dependency dependency;
|
|
67
|
+
|
|
68
|
+
@InjectMocks
|
|
69
|
+
private Service service;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### JavaScript/TypeScript项目 (Jest/Vitest)
|
|
74
|
+
```javascript
|
|
75
|
+
describe('ModuleName', () => {
|
|
76
|
+
// 测试块命名:方法名或功能描述
|
|
77
|
+
describe('methodName', () => {
|
|
78
|
+
// 测试用例命名:should + 期望行为 + when + 条件
|
|
79
|
+
it('should return expected result when input is valid', () => {
|
|
80
|
+
// Given - 准备测试数据
|
|
81
|
+
// When - 执行被测方法
|
|
82
|
+
// Then - 验证结果
|
|
83
|
+
expect(result).toEqual(expected);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Python项目 (unittest/pytest)
|
|
90
|
+
```python
|
|
91
|
+
# unittest
|
|
92
|
+
class TestModule(unittest.TestCase):
|
|
93
|
+
def test_method_name_with_condition(self):
|
|
94
|
+
# Given
|
|
95
|
+
# When
|
|
96
|
+
# Then
|
|
97
|
+
self.assertEqual(result, expected)
|
|
98
|
+
|
|
99
|
+
# pytest
|
|
100
|
+
def test_method_name_with_condition():
|
|
101
|
+
# Given
|
|
102
|
+
# When
|
|
103
|
+
# Then
|
|
104
|
+
assert result == expected
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 注意事项
|
|
108
|
+
- Java项目默认使用JUnit4框架
|
|
109
|
+
- JavaScript/TypeScript项目默认使用Jest或Vitest
|
|
110
|
+
- Python项目默认使用unittest或pytest
|
|
111
|
+
- 根据项目类型自动选择合适的测试框架
|
|
112
|
+
- 遵循项目现有的测试风格和规范
|
|
113
|
+
- 确保测试覆盖率,特别是边界条件和异常情况
|
|
114
|
+
- 使用合适的断言方法验证结果
|
|
115
|
+
- 合理使用模拟对象处理依赖
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "catppuccin-mocha",
|
|
4
|
+
"vars": {
|
|
5
|
+
"base": "#17131F",
|
|
6
|
+
"mantle": "#13101A",
|
|
7
|
+
"crust": "#100D16",
|
|
8
|
+
"surface0": "#241F2E",
|
|
9
|
+
"surface1": "#332D3E",
|
|
10
|
+
"surface2": "#433D4E",
|
|
11
|
+
"overlay0": "#5C5568",
|
|
12
|
+
"overlay1": "#716A82",
|
|
13
|
+
"overlay2": "#867F96",
|
|
14
|
+
"subtext0": "#A6A0B0",
|
|
15
|
+
"subtext1": "#BAB4C4",
|
|
16
|
+
"text": "#CDD6F4",
|
|
17
|
+
"rosewater": "#F5E0DC",
|
|
18
|
+
"flamingo": "#F2CDCD",
|
|
19
|
+
"pink": "#F5C2E7",
|
|
20
|
+
"mauve": "#CBA6F7",
|
|
21
|
+
"red": "#F38BA8",
|
|
22
|
+
"maroon": "#EBA0AC",
|
|
23
|
+
"peach": "#FAB387",
|
|
24
|
+
"yellow": "#F9E2AF",
|
|
25
|
+
"green": "#A6E3A1",
|
|
26
|
+
"teal": "#94E2D5",
|
|
27
|
+
"sky": "#89DCEB",
|
|
28
|
+
"sapphire": "#74C7EC",
|
|
29
|
+
"blue": "#89B4FA",
|
|
30
|
+
"lavender": "#B4BEFE",
|
|
31
|
+
"toolPendingBg": "#1A1524",
|
|
32
|
+
"toolSuccessBg": "#1B1828",
|
|
33
|
+
"toolErrorBg": "#261727"
|
|
34
|
+
},
|
|
35
|
+
"colors": {
|
|
36
|
+
"accent": "mauve",
|
|
37
|
+
"border": "surface2",
|
|
38
|
+
"borderAccent": "mauve",
|
|
39
|
+
"borderMuted": "surface1",
|
|
40
|
+
"success": "green",
|
|
41
|
+
"error": "red",
|
|
42
|
+
"warning": "teal",
|
|
43
|
+
"muted": "overlay1",
|
|
44
|
+
"dim": "overlay0",
|
|
45
|
+
"text": "",
|
|
46
|
+
"thinkingText": "overlay1",
|
|
47
|
+
|
|
48
|
+
"selectedBg": "#241F2E",
|
|
49
|
+
"userMessageBg": "#241F2E",
|
|
50
|
+
"userMessageText": "",
|
|
51
|
+
"customMessageBg": "surface0",
|
|
52
|
+
"customMessageText": "",
|
|
53
|
+
"customMessageLabel": "mauve",
|
|
54
|
+
"toolPendingBg": "toolPendingBg",
|
|
55
|
+
"toolSuccessBg": "toolSuccessBg",
|
|
56
|
+
"toolErrorBg": "toolErrorBg",
|
|
57
|
+
"toolTitle": "overlay2",
|
|
58
|
+
"toolOutput": "overlay1",
|
|
59
|
+
|
|
60
|
+
"mdHeading": "blue",
|
|
61
|
+
"mdLink": "sapphire",
|
|
62
|
+
"mdLinkUrl": "overlay0",
|
|
63
|
+
"mdCode": "teal",
|
|
64
|
+
"mdCodeBlock": "",
|
|
65
|
+
"mdCodeBlockBorder": "surface2",
|
|
66
|
+
"mdQuote": "overlay1",
|
|
67
|
+
"mdQuoteBorder": "surface2",
|
|
68
|
+
"mdHr": "surface2",
|
|
69
|
+
"mdListBullet": "teal",
|
|
70
|
+
|
|
71
|
+
"toolDiffAdded": "green",
|
|
72
|
+
"toolDiffRemoved": "red",
|
|
73
|
+
"toolDiffContext": "overlay1",
|
|
74
|
+
|
|
75
|
+
"syntaxComment": "overlay0",
|
|
76
|
+
"syntaxKeyword": "mauve",
|
|
77
|
+
"syntaxFunction": "blue",
|
|
78
|
+
"syntaxVariable": "flamingo",
|
|
79
|
+
"syntaxString": "green",
|
|
80
|
+
"syntaxNumber": "pink",
|
|
81
|
+
"syntaxType": "sky",
|
|
82
|
+
"syntaxOperator": "sky",
|
|
83
|
+
"syntaxPunctuation": "overlay2",
|
|
84
|
+
|
|
85
|
+
"thinkingOff": "overlay0",
|
|
86
|
+
"thinkingMinimal": "overlay0",
|
|
87
|
+
"thinkingLow": "overlay0",
|
|
88
|
+
"thinkingMedium": "overlay0",
|
|
89
|
+
"thinkingHigh": "overlay0",
|
|
90
|
+
"thinkingXhigh": "overlay0",
|
|
91
|
+
|
|
92
|
+
"bashMode": "lavender"
|
|
93
|
+
},
|
|
94
|
+
"export": {
|
|
95
|
+
"pageBg": "#17131F",
|
|
96
|
+
"cardBg": "#1A1524",
|
|
97
|
+
"infoBg": "#241F2E"
|
|
98
|
+
}
|
|
99
|
+
}
|
package/config.json
ADDED
package/dist/banner.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Banner 生成器 - AI Code Agent
|
|
3
|
+
*/
|
|
4
|
+
export declare const defaultBanner = " _ _ _ _ _____ _____ _____ _____ ____ _____ ______\n | \\ | | | | | | / ____| / ____| /\\ |_ _| / ____| / __ \\ | __ \\ | ____|\n | \\| | | | | | | | | | / \\ | | | | | | | | | | | | | |__\n | . ` | | | | | | | | | / /\\ \\ | | | | | | | | | | | | | __|\n | |\\ | | |__| | | |____ | |____ / ____ \\ _| |_ | |____ | |__| | | |__| | | |____\n |_| \\_| \\____/ \\_____| \\_____| /_/ \\_\\ |_____| \\_____| \\____/ |_____/ |______|\n\n AICode\u5B9E\u8DF5\u843D\u5730 @2026";
|
|
5
|
+
/**
|
|
6
|
+
* 生成带颜色的 Banner
|
|
7
|
+
* @param colorTheme 主题色 (hex 格式,如 #49bccf)
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateBanner(colorTheme?: string): string;
|
|
10
|
+
//# sourceMappingURL=banner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"banner.d.ts","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,aAAa,gpBAO6B,CAAC;AAExD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,UAAU,GAAE,MAAkB,GAAG,MAAM,CAGrE"}
|
package/dist/banner.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Banner 生成器 - AI Code Agent
|
|
3
|
+
*/
|
|
4
|
+
export const defaultBanner = ` _ _ _ _ _____ _____ _____ _____ ____ _____ ______
|
|
5
|
+
| \\ | | | | | | / ____| / ____| /\\ |_ _| / ____| / __ \\ | __ \\ | ____|
|
|
6
|
+
| \\| | | | | | | | | | / \\ | | | | | | | | | | | | | |__
|
|
7
|
+
| . \` | | | | | | | | | / /\\ \\ | | | | | | | | | | | | | __|
|
|
8
|
+
| |\\ | | |__| | | |____ | |____ / ____ \\ _| |_ | |____ | |__| | | |__| | | |____
|
|
9
|
+
|_| \\_| \\____/ \\_____| \\_____| /_/ \\_\\ |_____| \\_____| \\____/ |_____/ |______|
|
|
10
|
+
|
|
11
|
+
AICode实践落地 @2026`;
|
|
12
|
+
/**
|
|
13
|
+
* 生成带颜色的 Banner
|
|
14
|
+
* @param colorTheme 主题色 (hex 格式,如 #49bccf)
|
|
15
|
+
*/
|
|
16
|
+
export function generateBanner(colorTheme = '#49bccf') {
|
|
17
|
+
const rgb = hexToRgb(colorTheme);
|
|
18
|
+
return `\x1b[38;2;${rgb.r};${rgb.g};${rgb.b}m${defaultBanner}\x1b[0m`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 将 Hex 颜色转换为 RGB 对象
|
|
22
|
+
*/
|
|
23
|
+
function hexToRgb(hex) {
|
|
24
|
+
// 去掉 # 前缀
|
|
25
|
+
hex = hex.replace('#', '');
|
|
26
|
+
return {
|
|
27
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
28
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
29
|
+
b: parseInt(hex.slice(4, 6), 16)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"banner.js","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;uDAO0B,CAAC;AAExD;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,aAAqB,SAAS;IAC3D,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;IACjC,OAAO,aAAa,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,aAAa,SAAS,CAAC;AACxE,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CAAC,GAAW;IAC3B,UAAU;IACV,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAE3B,OAAO;QACL,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;QAChC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;QAChC,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置加载器
|
|
3
|
+
*/
|
|
4
|
+
import type { AppConfig } from './config.js';
|
|
5
|
+
/**
|
|
6
|
+
* 获取配置目录
|
|
7
|
+
*/
|
|
8
|
+
export declare function getConfigDir(): string;
|
|
9
|
+
/**
|
|
10
|
+
* 加载配置文件
|
|
11
|
+
*/
|
|
12
|
+
export declare function loadConfig(): AppConfig;
|
|
13
|
+
/**
|
|
14
|
+
* 获取当前配置 (懒加载)
|
|
15
|
+
*/
|
|
16
|
+
export declare function getConfig(): AppConfig;
|
|
17
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAgBrC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,SAAS,CAyBtC;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAErC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置加载器
|
|
3
|
+
*/
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { existsSync, readFileSync } from 'fs';
|
|
7
|
+
import { defaultConfig } from './config.js';
|
|
8
|
+
let cachedConfig = null;
|
|
9
|
+
/**
|
|
10
|
+
* 获取配置目录
|
|
11
|
+
*/
|
|
12
|
+
export function getConfigDir() {
|
|
13
|
+
// 优先级: 环境变量 > 命令行参数(后续实现) > 默认值
|
|
14
|
+
const configDir = process.env.AICODE_CLI_CONFIG_DIR;
|
|
15
|
+
if (configDir) {
|
|
16
|
+
// 展开环境变量中的路径
|
|
17
|
+
if (configDir === "~") {
|
|
18
|
+
return homedir();
|
|
19
|
+
}
|
|
20
|
+
if (configDir.startsWith("~/")) {
|
|
21
|
+
return join(homedir(), configDir.slice(2));
|
|
22
|
+
}
|
|
23
|
+
return configDir;
|
|
24
|
+
}
|
|
25
|
+
return join(homedir(), defaultConfig.configDir);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 加载配置文件
|
|
29
|
+
*/
|
|
30
|
+
export function loadConfig() {
|
|
31
|
+
if (cachedConfig) {
|
|
32
|
+
return cachedConfig;
|
|
33
|
+
}
|
|
34
|
+
const configPath = join(getConfigDir(), 'config.json');
|
|
35
|
+
if (existsSync(configPath)) {
|
|
36
|
+
try {
|
|
37
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
38
|
+
const userConfig = JSON.parse(content);
|
|
39
|
+
cachedConfig = { ...defaultConfig, ...userConfig };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
cachedConfig = { ...defaultConfig };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
cachedConfig = { ...defaultConfig };
|
|
47
|
+
}
|
|
48
|
+
// 设置环境变量,让 pi-coding-agent 使用我们的配置目录
|
|
49
|
+
const fullPath = getConfigDir();
|
|
50
|
+
const agentDir = join(fullPath, 'agent');
|
|
51
|
+
process.env.AICODE_CLI_CODING_AGENT_DIR = agentDir;
|
|
52
|
+
return cachedConfig;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 获取当前配置 (懒加载)
|
|
56
|
+
*/
|
|
57
|
+
export function getConfig() {
|
|
58
|
+
return loadConfig();
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../src/config-loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAE9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,IAAI,YAAY,GAAqB,IAAI,CAAC;AAE1C;;GAEG;AACH,MAAM,UAAU,YAAY;IAC1B,gCAAgC;IAChC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAEpD,IAAI,SAAS,EAAE,CAAC;QACd,aAAa;QACb,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,OAAO,OAAO,EAAE,CAAC;QACnB,CAAC;QACD,IAAI,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa,CAAC,CAAC;IAEvD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAClD,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACvC,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,UAAU,EAAE,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;QACtC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,YAAY,GAAG,EAAE,GAAG,aAAa,EAAE,CAAC;IACtC,CAAC;IAED,qCAAqC;IACrC,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,2BAA2B,GAAG,QAAQ,CAAC;IAEnD,OAAO,YAAa,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,EAAE,CAAC;AACtB,CAAC"}
|