knowme-careerforge-skill 0.0.2 → 0.0.4

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.
Files changed (37) hide show
  1. package/SKILL.md +11 -1
  2. package/agents/claude/knowme-careerforge.md +10 -9
  3. package/agents/codex/knowme-careerforge.yaml +3 -3
  4. package/agents/cursor/knowme-careerforge.mdc +12 -8
  5. package/agents/opencode/skill.yaml +1 -1
  6. package/agents/windsurf/knowme-careerforge.rules +9 -9
  7. package/dist/cli/src/commands/init.js +9 -0
  8. package/dist/cli/src/index.js +17 -0
  9. package/package.json +2 -1
  10. package/scripts/build/sync-version.py +11 -2
  11. package/scripts/template/instantiate-resume.py +8 -2
  12. package/scripts/template/search-template.py +290 -89
  13. package/skill.json +1 -1
  14. package/src/knowledge/index.json +2 -10
  15. package/src/knowledge/templates.json +1 -9
  16. package/src/references/01-evidence-mining.md +41 -0
  17. package/src/references/02-career-goal.md +38 -0
  18. package/src/references/03-jd-analysis.md +44 -0
  19. package/src/references/04-template-selection.md +43 -0
  20. package/src/references/05-html-canvas-tokens.md +51 -0
  21. package/src/references/06-qa-and-rendering.md +54 -0
  22. package/src/templates/classic/style.css +22 -51
  23. package/src/templates/common/base.css +150 -0
  24. package/src/templates/executive/style.css +30 -73
  25. package/src/templates/minimal/style.css +30 -72
  26. package/src/templates/modern/style.css +32 -72
  27. package/src/references/ats.md +0 -17
  28. package/src/references/evidence-first.md +0 -22
  29. package/src/references/pdf.md +0 -40
  30. package/src/references/resume-writing.md +0 -25
  31. package/src/references/template-design.md +0 -57
  32. package/src/workflows/analyze-job.md +0 -16
  33. package/src/workflows/define-goal.md +0 -16
  34. package/src/workflows/forge-resume.md +0 -25
  35. package/src/workflows/know-me.md +0 -21
  36. package/src/workflows/position.md +0 -19
  37. package/src/workflows/review.md +0 -31
@@ -1,12 +1,22 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- KnowMe CareerForge — Template Search & Ranking Engine (BM25 + Multi-criteria scoring)
4
- 根据目标岗位、期望版式风格、目标页数与信息密度,加权检索最匹配的 HTML 简历模板。
3
+ KnowMe CareerForge — Template Search & Ranking Engine
4
+ Architecture: Deep Module with Pluggable Scorer Strategies (Weighted Rules + BM25 Text Search)
5
+
6
+ Design Contract:
7
+ - BaseTemplateScorer: Abstract strategy interface
8
+ - WeightedRuleScorer: Multi-criteria rule-based weighted scorer (Role 35%, Style 25%, ATS 20%, Page 10%, Density 10%)
9
+ - BM25TextScorer: Pure-Python BM25 keyword retrieval across template text documents
10
+ - HybridTemplateScorer: Weighted blend (70% Rule Score + 30% BM25 Text Score)
11
+ - search_templates(): Public facade interface
5
12
  """
6
13
 
7
- import sys, os, json, argparse
14
+ import sys, os, json, argparse, math, re
8
15
  from pathlib import Path
16
+ from dataclasses import dataclass, field
9
17
  from typing import List, Dict, Any, Optional
18
+ from abc import ABC, abstractmethod
19
+
10
20
 
11
21
  def get_project_root() -> Path:
12
22
  curr = Path(__file__).resolve().parent
@@ -16,6 +26,17 @@ def get_project_root() -> Path:
16
26
  curr = curr.parent
17
27
  return Path.cwd()
18
28
 
29
+
30
+ @dataclass
31
+ class SearchQuery:
32
+ role: str
33
+ style: Optional[str] = None
34
+ target_pages: int = 1
35
+ density: str = "balanced"
36
+ keywords: Optional[str] = None
37
+ engine: str = "hybrid" # "weighted" | "bm25" | "hybrid"
38
+
39
+
19
40
  def load_templates() -> List[Dict[str, Any]]:
20
41
  root_dir = get_project_root()
21
42
  base_dir = root_dir / "src" / "templates"
@@ -30,120 +51,300 @@ def load_templates() -> List[Dict[str, Any]]:
30
51
  if meta_file.exists():
31
52
  try:
32
53
  data = json.loads(meta_file.read_text(encoding="utf-8"))
33
- data["directory"] = f"src/templates/{t_dir.name}"
54
+ data["_directory"] = str(t_dir.relative_to(root_dir))
34
55
  templates.append(data)
35
56
  except Exception:
36
57
  pass
37
58
  return templates
38
59
 
39
- def calculate_match_score(template: Dict[str, Any], query_role: str, query_style: Optional[str] = None, target_pages: int = 1, density: str = "balanced") -> float:
40
- score = 0.0
60
+
61
+ # ==============================================================================
62
+ # 1. SCORER STRATEGY INTERFACE & IMPLEMENTATIONS
63
+ # ==============================================================================
64
+
65
+ class BaseTemplateScorer(ABC):
66
+ """Abstract interface for template scoring algorithms."""
41
67
 
42
- # 1. 岗位匹配度 (Role Match - 35%)
43
- role_score = 0.0
44
- query_lower = query_role.lower()
45
- supported = [r.lower() for r in template.get("supportedRoles", [])]
68
+ @abstractmethod
69
+ def score(self, template: Dict[str, Any], query: SearchQuery) -> float:
70
+ """Return match score normalized between 0.0 and 100.0."""
71
+ pass
72
+
73
+
74
+ class WeightedRuleScorer(BaseTemplateScorer):
75
+ """
76
+ Rule-based weighted scoring engine:
77
+ - Role Match: 35%
78
+ - Style Match: 25%
79
+ - ATS Tier: 20%
80
+ - Page Fit: 10%
81
+ - Density Fit: 10%
82
+ """
46
83
 
47
- for r in supported:
48
- if r in query_lower or query_lower in r:
49
- role_score = 1.0
50
- break
51
-
52
- if role_score < 0.5:
53
- cat = template.get("roleCategory", "")
54
- if "ai" in query_lower or "algorithm" in query_lower or "agent" in query_lower:
55
- role_score = 0.95 if cat == "engineering-ai" else 0.40
56
- elif "product" in query_lower or "manager" in query_lower or "director" in query_lower:
57
- role_score = 0.95 if cat == "management-product" else 0.40
58
- elif "engineer" in query_lower or "architect" in query_lower:
59
- role_score = 0.85
60
- else:
61
- role_score = 0.60
62
-
63
- score += role_score * 0.35
64
-
65
- # 2. 风格匹配度 (Style Match - 25%)
66
- style_score = 0.70
67
- if query_style:
68
- q_style_lower = query_style.lower()
69
- t_style = template.get("style", "").lower()
70
- t_tone = template.get("visualStyle", {}).get("tone", "").lower()
84
+ def __init__(self, weights: Optional[Dict[str, float]] = None):
85
+ self.weights = weights or {
86
+ "role": 0.35,
87
+ "style": 0.25,
88
+ "ats": 0.20,
89
+ "page": 0.10,
90
+ "density": 0.10
91
+ }
92
+
93
+ def score(self, template: Dict[str, Any], query: SearchQuery) -> float:
94
+ score = 0.0
71
95
 
72
- if q_style_lower in t_style or q_style_lower in t_tone or q_style_lower == template.get("id"):
73
- style_score = 1.0
74
- elif "minimal" in q_style_lower and "minimal" in t_style:
75
- style_score = 0.95
76
- elif "split" in q_style_lower and "split" in t_style:
77
- style_score = 0.95
78
- elif "modern" in q_style_lower and "modern" in t_tone:
79
- style_score = 0.95
96
+ # 1. Role Match
97
+ role_score = 0.0
98
+ query_lower = query.role.lower()
99
+ supported = [r.lower() for r in template.get("supportedRoles", [])]
100
+
101
+ for r in supported:
102
+ if r in query_lower or query_lower in r:
103
+ role_score = 1.0
104
+ break
105
+
106
+ if role_score < 0.5:
107
+ cat = template.get("roleCategory", "")
108
+ if any(k in query_lower for k in ["ai", "agent", "llm", "frontend", "backend", "fullstack", "devops", "engineer"]) and "engineering" in cat:
109
+ role_score = 0.85
110
+ elif any(k in query_lower for k in ["manager", "product", "lead", "director", "architect", "cto"]) and "management" in cat:
111
+ role_score = 0.85
112
+ elif any(k in query_lower for k in ["bank", "fintech", "state", "corp", "government"]) and "corporate" in cat:
113
+ role_score = 0.85
114
+ else:
115
+ role_score = 0.60
116
+
117
+ score += role_score * self.weights["role"]
118
+
119
+ # 2. Style Match
120
+ style_score = 0.70
121
+ if query.style:
122
+ q_style_lower = query.style.lower()
123
+ t_style = template.get("style", "").lower()
124
+ t_tone = template.get("visualStyle", {}).get("tone", "").lower()
125
+
126
+ if q_style_lower in t_style or q_style_lower in t_tone:
127
+ style_score = 1.0
128
+ elif any(k in t_style for k in q_style_lower.split("-")):
129
+ style_score = 0.85
130
+ else:
131
+ style_score = 0.50
132
+ score += style_score * self.weights["style"]
133
+
134
+ # 3. ATS Tier
135
+ ats_tier = template.get("atsScoreTier", "tier-1-optimal")
136
+ ats_score = 1.0 if "optimal" in ats_tier or "tier-1" in ats_tier else 0.85
137
+ score += ats_score * self.weights["ats"]
138
+
139
+ # 4. Page Fit
140
+ target_p = template.get("layout", {}).get("targetPages", 1)
141
+ max_p = template.get("layout", {}).get("maxPages", 2)
142
+ if query.target_pages == target_p:
143
+ page_score = 1.0
144
+ elif query.target_pages <= max_p:
145
+ page_score = 0.80
80
146
  else:
81
- style_score = 0.50
82
- score += style_score * 0.25
83
-
84
- # 3. ATS 兼容评级 (ATS Tier - 20%)
85
- ats_tier = template.get("atsScoreTier", "tier-1-optimal")
86
- ats_score = 1.0 if "optimal" in ats_tier or "tier-1" in ats_tier else 0.85
87
- score += ats_score * 0.20
88
-
89
- # 4. 页数适配度 (Page Fit - 10%)
90
- target_p = template.get("layout", {}).get("targetPages", 1)
91
- max_p = template.get("layout", {}).get("maxPages", 2)
92
- if target_pages == target_p:
93
- page_score = 1.0
94
- elif target_pages <= max_p:
95
- page_score = 0.80
96
- else:
97
- page_score = 0.50
98
- score += page_score * 0.10
147
+ page_score = 0.50
148
+ score += page_score * self.weights["page"]
149
+
150
+ # 5. Density Preference
151
+ t_density = template.get("layout", {}).get("density", "balanced")
152
+ density_score = 1.0 if query.density == t_density else 0.80
153
+ score += density_score * self.weights["density"]
154
+
155
+ return round(score * 100, 1)
156
+
157
+
158
+ class BM25TextScorer(BaseTemplateScorer):
159
+ """
160
+ Pure Python BM25 Text Scorer across template metadata documents.
161
+ Constructs a virtual document per template: (name, category, style, supportedRoles, tone).
162
+ """
163
+
164
+ def __init__(self, templates: Optional[List[Dict[str, Any]]] = None, k1: float = 1.5, b: float = 0.75):
165
+ self.k1 = k1
166
+ self.b = b
167
+ self.templates = templates or load_templates()
168
+ self.docs = [self._extract_doc(t) for t in self.templates]
169
+ self.avg_dl = sum(len(d) for d in self.docs) / max(len(self.docs), 1)
170
+ self.doc_freqs = self._calc_doc_freqs()
171
+
172
+ def _extract_doc(self, template: Dict[str, Any]) -> List[str]:
173
+ text_parts = [
174
+ template.get("name", ""),
175
+ template.get("style", ""),
176
+ template.get("roleCategory", ""),
177
+ template.get("visualStyle", {}).get("tone", ""),
178
+ " ".join(template.get("supportedRoles", []))
179
+ ]
180
+ combined = " ".join(text_parts).lower()
181
+ # Tokenize words & Chinese characters
182
+ tokens = re.findall(r'[a-zA-Z0-9_\-]+|[\u4e00-\u9fa5]', combined)
183
+ return tokens
99
184
 
100
- # 5. 信息密度偏好 (Density - 10%)
101
- t_density = template.get("layout", {}).get("density", "balanced")
102
- density_score = 1.0 if density == t_density else 0.80
103
- score += density_score * 0.10
185
+ def _calc_doc_freqs(self) -> Dict[str, int]:
186
+ freqs: Dict[str, int] = {}
187
+ for doc in self.docs:
188
+ for word in set(doc):
189
+ freqs[word] = freqs.get(word, 0) + 1
190
+ return freqs
104
191
 
105
- return round(score * 100, 1)
192
+ def score(self, template: Dict[str, Any], query: SearchQuery) -> float:
193
+ doc = self._extract_doc(template)
194
+ doc_len = len(doc)
195
+ if doc_len == 0:
196
+ return 50.0
106
197
 
107
- def search_templates(role: str, style: Optional[str] = None, target_pages: int = 1, density: str = "balanced") -> List[Dict[str, Any]]:
198
+ query_text = f"{query.role} {query.style or ''} {query.keywords or ''}".lower()
199
+ q_tokens = re.findall(r'[a-zA-Z0-9_\-]+|[\u4e00-\u9fa5]', query_text)
200
+ if not q_tokens:
201
+ return 70.0
202
+
203
+ n_docs = len(self.docs)
204
+ score = 0.0
205
+
206
+ for term in q_tokens:
207
+ df = self.doc_freqs.get(term, 0)
208
+ if df == 0:
209
+ continue
210
+ # Standard BM25 IDF with smoothing
211
+ idf = math.log(1 + (n_docs - df + 0.5) / (df + 0.5))
212
+ tf = doc.count(term)
213
+ numerator = tf * (self.k1 + 1)
214
+ denominator = tf + self.k1 * (1 - self.b + self.b * (doc_len / max(self.avg_dl, 1)))
215
+ score += idf * (numerator / max(denominator, 0.001))
216
+
217
+ # Normalize score into roughly 50.0 ~ 100.0 range
218
+ normalized = 50.0 + min(score * 15.0, 50.0)
219
+ return round(normalized, 1)
220
+
221
+
222
+ class HybridTemplateScorer(BaseTemplateScorer):
223
+ """
224
+ Weighted combination of rule-based constraints and BM25 text relevance.
225
+ Default: 70% Weighted Rules + 30% BM25 Relevance.
226
+ """
227
+
228
+ def __init__(self, rule_weight: float = 0.70, bm25_weight: float = 0.30):
229
+ self.rule_scorer = WeightedRuleScorer()
230
+ self.bm25_scorer = BM25TextScorer()
231
+ self.rule_weight = rule_weight
232
+ self.bm25_weight = bm25_weight
233
+
234
+ def score(self, template: Dict[str, Any], query: SearchQuery) -> float:
235
+ rule_score = self.rule_scorer.score(template, query)
236
+ bm25_score = self.bm25_scorer.score(template, query)
237
+ blended = (rule_score * self.rule_weight) + (bm25_score * self.bm25_weight)
238
+ return round(blended, 1)
239
+
240
+
241
+ # ==============================================================================
242
+ # 2. PUBLIC FACADE INTERFACE (DEEP MODULE)
243
+ # ==============================================================================
244
+
245
+ def search_templates(
246
+ role: str,
247
+ style: Optional[str] = None,
248
+ target_pages: int = 1,
249
+ density: str = "balanced",
250
+ keywords: Optional[str] = None,
251
+ engine: str = "hybrid"
252
+ ) -> List[Dict[str, Any]]:
253
+ """
254
+ Search and rank HTML resume templates.
255
+
256
+ Args:
257
+ role: Target job title or direction (e.g., 'AI Agent Engineer', 'Tech Lead')
258
+ style: Preferred visual/layout style (e.g., 'single-column-minimal', 'two-column-split')
259
+ target_pages: Expected page count (1 or 2)
260
+ density: Information density preference ('high', 'balanced', 'spacious')
261
+ keywords: Optional JD keywords for text matching
262
+ engine: Scoring engine ('hybrid' | 'weighted' | 'bm25')
263
+
264
+ Returns:
265
+ List of matching templates sorted by matchScore descending.
266
+ """
267
+ query = SearchQuery(
268
+ role=role,
269
+ style=style,
270
+ target_pages=target_pages,
271
+ density=density,
272
+ keywords=keywords,
273
+ engine=engine
274
+ )
275
+
108
276
  templates = load_templates()
277
+ if not templates:
278
+ return []
279
+
280
+ # Select Scorer Strategy
281
+ scorer: BaseTemplateScorer
282
+ if engine == "bm25":
283
+ scorer = BM25TextScorer(templates)
284
+ elif engine == "weighted":
285
+ scorer = WeightedRuleScorer()
286
+ else:
287
+ scorer = HybridTemplateScorer()
288
+
109
289
  results = []
110
-
111
290
  for t in templates:
112
- s = calculate_match_score(t, role, style, target_pages, density)
113
- results.append({
114
- "score": s,
115
- "template": t
116
- })
291
+ t_copy = dict(t)
292
+ score_val = scorer.score(t, query)
293
+ t_copy["matchScore"] = score_val
294
+ t_copy["score"] = score_val
295
+ t_copy["template"] = t
296
+ results.append(t_copy)
117
297
 
118
- results.sort(key=lambda x: x["score"], reverse=True)
298
+ results.sort(key=lambda x: x.get("matchScore", 0.0), reverse=True)
119
299
  return results
120
-
121
300
  def main():
122
301
  parser = argparse.ArgumentParser(description="KnowMe CareerForge — Template Search Engine")
123
- parser.add_argument("role", nargs="?", default="AI Agent Engineer", help="Target role title")
124
- parser.add_argument("--style", "-s", default=None, help="Desired style (minimal, modern, executive, classic)")
125
- parser.add_argument("--target-pages", "-p", type=int, default=1, help="Expected target pages (1 or 2)")
126
- parser.add_argument("--density", "-d", default="balanced", help="Information density preference (high, balanced, normal)")
127
- parser.add_argument("--json", action="store_true", help="Output results in raw JSON")
128
-
302
+ parser.add_argument("role", nargs="?", default=None, help="Target role or job title (e.g. 'AI Agent Engineer')")
303
+ parser.add_argument("--role", "-r", dest="named_role", default=None, help="Target role (named option)")
304
+ parser.add_argument("--style", "-s", default=None, help="Style preference (minimal, modern, executive, classic)")
305
+ parser.add_argument("--target-pages", "-p", type=int, default=1, help="Target pages (1 or 2)")
306
+ parser.add_argument("--density", "-d", default="balanced", choices=["high", "balanced", "spacious"], help="Density")
307
+ parser.add_argument("--keywords", "-k", default=None, help="Additional JD keywords")
308
+ parser.add_argument("--engine", "-e", default="hybrid", choices=["hybrid", "weighted", "bm25"], help="Scoring engine")
309
+ parser.add_argument("--json", action="store_true", help="Output raw JSON array")
310
+
129
311
  args = parser.parse_args()
130
- results = search_templates(args.role, style=args.style, target_pages=args.target_pages, density=args.density)
312
+ target_role = args.named_role or args.role
313
+ if not target_role:
314
+ parser.error("the following arguments are required: role (or --role / -r)")
131
315
 
316
+ results = search_templates(
317
+ role=target_role,
318
+ style=args.style,
319
+ target_pages=args.target_pages,
320
+ density=args.density,
321
+ keywords=args.keywords,
322
+ engine=args.engine
323
+ )
324
+
132
325
  if args.json:
133
326
  print(json.dumps(results, ensure_ascii=False, indent=2))
134
327
  return
135
-
328
+
136
329
  print("=" * 80)
137
- print(f" KnowMe CareerForge — Template Recommendations for '{args.role}'")
330
+ print(f" KnowMe CareerForge — Template Search Engine Results")
331
+ print(f" Query Role : {target_role}")
332
+ print(f" Style Filter : {args.style or 'Any'}")
333
+ print(f" Engine : {args.engine.upper()}")
334
+ print(f" Matches : {len(results)} template(s) found")
138
335
  print("=" * 80)
336
+
139
337
  for idx, r in enumerate(results, 1):
140
- t = r["template"]
141
- print(f"{idx}. [Score: {r['score']}] {t['name']} (ID: {t['id']})")
142
- print(f" Category: {t['roleCategory']} | Style: {t['style']} | ATS Tier: {t['atsScoreTier']}")
143
- print(f" Target Pages: {t['layout']['targetPages']} (Max: {t['layout']['maxPages']}) | Density: {t['layout']['density']}")
144
- print(f" Tone: {t['visualStyle']['tone']} | Accent: {t['visualStyle']['accentColor']}")
145
- print(f" Directory: {t['directory']}")
338
+ custom_tokens = r.get("customizableTokens", [])
339
+ tokens_preview = ", ".join(custom_tokens[:4]) + ("..." if len(custom_tokens) > 4 else "")
340
+ print(f"\n{idx}. [{r.get('matchScore')} pts] {r.get('name')} (ID: {r.get('id')})")
341
+ print(f" Category: {r.get('roleCategory')} | Style: {r.get('style')} | ATS Tier: {r.get('atsScoreTier')}")
342
+ print(f" Target Pages: {r.get('layout', {}).get('targetPages', 1)} | Density: {r.get('layout', {}).get('density')}")
343
+ print(f" Directory: {r.get('_directory')}")
344
+ if tokens_preview:
345
+ print(f" Tokens : {tokens_preview}")
146
346
  print("-" * 80)
147
347
 
348
+
148
349
  if __name__ == "__main__":
149
350
  main()
package/skill.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/skill.json",
3
3
  "name": "knowme-careerforge",
4
4
  "displayName": "KnowMe CareerForge",
5
- "version": "0.0.2",
5
+ "version": "0.0.4",
6
6
  "description": "An agent-native skill for self-discovery, career positioning, and tailored resume engineering. Know yourself. Define your direction. Forge your opportunity.",
7
7
  "author": "Miles <https://github.com/Max-Samson>",
8
8
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.2",
2
+ "version": "0.0.4",
3
3
  "totalRoles": 9,
4
4
  "totalTemplates": 4,
5
5
  "roles": [
@@ -530,9 +530,7 @@
530
530
  "--resume-font-size-meta",
531
531
  "--resume-font-size-title",
532
532
  "--resume-line-height-body",
533
- "--resume-page-min-height",
534
- "--resume-page-padding",
535
- "--resume-page-width"
533
+ "--resume-page-padding"
536
534
  ]
537
535
  },
538
536
  {
@@ -592,8 +590,6 @@
592
590
  "--resume-font-size-title",
593
591
  "--resume-left-col-width",
594
592
  "--resume-line-height-body",
595
- "--resume-page-min-height",
596
- "--resume-page-width",
597
593
  "--resume-space-bullet",
598
594
  "--resume-space-item",
599
595
  "--resume-space-section"
@@ -654,9 +650,7 @@
654
650
  "--resume-font-size-name",
655
651
  "--resume-font-size-target",
656
652
  "--resume-line-height-body",
657
- "--resume-page-min-height",
658
653
  "--resume-page-padding",
659
- "--resume-page-width",
660
654
  "--resume-space-bullet",
661
655
  "--resume-space-header-bottom",
662
656
  "--resume-space-item",
@@ -722,8 +716,6 @@
722
716
  "--resume-font-size-name",
723
717
  "--resume-font-size-target",
724
718
  "--resume-line-height-body",
725
- "--resume-page-min-height",
726
- "--resume-page-width",
727
719
  "--resume-sidebar-width",
728
720
  "--resume-space-bullet",
729
721
  "--resume-space-item",
@@ -51,9 +51,7 @@
51
51
  "--resume-font-size-meta",
52
52
  "--resume-font-size-title",
53
53
  "--resume-line-height-body",
54
- "--resume-page-min-height",
55
- "--resume-page-padding",
56
- "--resume-page-width"
54
+ "--resume-page-padding"
57
55
  ]
58
56
  },
59
57
  {
@@ -113,8 +111,6 @@
113
111
  "--resume-font-size-title",
114
112
  "--resume-left-col-width",
115
113
  "--resume-line-height-body",
116
- "--resume-page-min-height",
117
- "--resume-page-width",
118
114
  "--resume-space-bullet",
119
115
  "--resume-space-item",
120
116
  "--resume-space-section"
@@ -175,9 +171,7 @@
175
171
  "--resume-font-size-name",
176
172
  "--resume-font-size-target",
177
173
  "--resume-line-height-body",
178
- "--resume-page-min-height",
179
174
  "--resume-page-padding",
180
- "--resume-page-width",
181
175
  "--resume-space-bullet",
182
176
  "--resume-space-header-bottom",
183
177
  "--resume-space-item",
@@ -243,8 +237,6 @@
243
237
  "--resume-font-size-name",
244
238
  "--resume-font-size-target",
245
239
  "--resume-line-height-body",
246
- "--resume-page-min-height",
247
- "--resume-page-width",
248
240
  "--resume-sidebar-width",
249
241
  "--resume-space-bullet",
250
242
  "--resume-space-item",
@@ -0,0 +1,41 @@
1
+ # Reference Manual 01: Evidence Mining & Anti-Hallucination Gate
2
+
3
+ > **"The goal is not to make the candidate look better than they are. The goal is to make their real strengths visible to the right opportunity."**
4
+
5
+ ---
6
+
7
+ ## 1. L1~L3 Evidence Classification Standard
8
+
9
+ Every bullet point and technical claim on the resume MUST be grounded in traceable candidate evidence:
10
+
11
+ | Level | Evidence Source | Confidence | Usage & Phrasing Standard | Prohibited Anti-Patterns |
12
+ |:---|:---|:---|:---|:---|
13
+ | **Level 1 (Direct Strong Evidence)** | Code implementation (`src/`), config files (`package.json`, `go.mod`, `Cargo.toml`, `Dockerfile`), API contracts, CI/CD pipelines, Git commit history | 95%+ | Direct, confident assertion with quantified metrics: *"Designed dual-path retrieval pipeline using FastAPI and Qdrant, achieving 92% Top-3 recall rate."* | Fabricating business revenue or claiming ownership beyond repository boundaries. |
14
+ | **Level 2 (Structural Medium Evidence)** | Directory structure, module dependencies, database schemas, framework integrations | 80%~95% | Engineering integration verbs: *"Engineered distributed microservice endpoints for order and inventory management using gRPC."* | Exaggerating simple library imports as custom from-scratch underlying framework development. |
15
+ | **Level 3 (Contextual Weak Inference)** | Agile practices, code reviews, deployment workflows, cross-team collaboration | 60%~80% | Conservative contextual phrasing: *"Participated in system architecture reviews and collaborated with product teams on sprint delivery."* | Claiming solo leadership of large engineering teams without organizational backing. |
16
+ | **Unsupported (No Evidence)** | Facts not found in candidate records or codebases | <50% | **STRICTLY PROHIBITED FROM RESUME** | Inventing unverified degrees, false company tenures, or imaginary revenue numbers. |
17
+
18
+ ---
19
+
20
+ ## 2. Automated Codebase & Git Mining Protocol
21
+
22
+ Run the evidence extraction engine against local repository or workspace:
23
+ ```bash
24
+ python3 scripts/evidence/extract-evidence.py --repo . --name "<Candidate Name>" --output workspace/evidence-master.json --quiet
25
+ ```
26
+
27
+ ### Extracted Evidence Dimensions:
28
+ 1. **Git Provenance**: Author names, email, commit count, active development timeframes;
29
+ 2. **Tech Stack Signatures**: Dependency parsing across 25+ language/framework ecosystems;
30
+ 3. **Architecture Signals**: Monorepo setups, containerization (Docker/K8s), automated testing, CI/CD workflows;
31
+ 4. **Code Volume & Distribution**: Breakdown by language and directory hierarchy.
32
+
33
+ ---
34
+
35
+ ## 3. Anti-Hallucination Execution Checklist
36
+
37
+ Before writing any line into `workspace/resume.html`:
38
+ - [ ] Is the primary technology verified in candidate code, config, or explicit profile?
39
+ - [ ] Is the action verb accurate to actual candidate contribution level (Led vs Implemented vs Participated)?
40
+ - [ ] Are all metrics (percentages, QPS, latency) grounded in reality rather than fabricated?
41
+ - [ ] If JD requires a skill absent from candidate evidence, mark as gap rather than hallucinating.
@@ -0,0 +1,38 @@
1
+ # Reference Manual 02: Career Goal Definition & Role Archetypes
2
+
3
+ > **"Know Yourself. Define Your Direction. Forge Your Opportunity."**
4
+
5
+ ---
6
+
7
+ ## 1. Professional Seniority Signals
8
+
9
+ Identify candidate level and calibrate narrative depth accordingly:
10
+
11
+ | Seniority Tier | Core Value Proposition | Key Narrative Signals | Section Priority |
12
+ |:---|:---|:---|:---|
13
+ | **Junior (1~3 Years)** | Rapid learning, solid coding standards, reliable task delivery | Clean code, unit test coverage, framework fluency, bug resolution | Skills → Projects → Experience → Education |
14
+ | **Mid-Level (3~5 Years)** | Independent ownership, feature architecture, performance optimization | End-to-end module ownership, database tuning, API design, CI/CD | Experience → Projects → Skills → Education |
15
+ | **Senior / Lead (5~8+ Years)** | System architecture, technical leadership, cross-system trade-offs | Distributed systems, high availability, mentoring, business impact | Experience → Projects → Architecture → Skills |
16
+ | **Principal / Architect** | Technical vision, organizational strategy, governance, ROI | Architecture governance, multi-team alignment, technology selection | Experience → Leadership → Architecture → Patents |
17
+
18
+ ---
19
+
20
+ ## 2. Role Archetypes Knowledge Base (`src/knowledge/roles/*.json`)
21
+
22
+ The skill ships with 9+ structured role profiles:
23
+ - `ai-agent-engineer.json`: LLM Orchestration, RAG, Tool Calling, VectorDB, Prompt Engineering
24
+ - `frontend.json`: React/Vue/Next.js, Web Vitals, Design Systems, State Management
25
+ - `java-backend.json`: Spring Boot, JVM tuning, Distributed Caching, Message Queues
26
+ - `node-fullstack.json`: TypeScript, Node.js runtime, GraphQL, Cloud Native
27
+ - `architect.json`: Distributed Systems, Domain-Driven Design, High Concurrency, Cloud Architecture
28
+ - `product-manager.json`: PRD, User Research, Data Analysis, Roadmapping, Growth
29
+
30
+ ---
31
+
32
+ ## 3. Core Value Proposition Formulation
33
+
34
+ Synthesize the 15~25 word Value Proposition headline following this formula:
35
+ $$\text{Value Proposition} = [\text{Target Role}] + [\text{Years/Domain Focus}] + [\text{Top 2 Signature Strengths}] + [\text{Concrete Impact Deliverable}]$$
36
+
37
+ *Example*:
38
+ > **Senior AI Agent Engineer** with 5+ years building scalable LLM orchestration & enterprise RAG pipelines, delivering 40% latency reduction across multi-agent production workflows.
@@ -0,0 +1,44 @@
1
+ # Reference Manual 03: Job Description (JD) Analysis & Keyword Extraction
2
+
3
+ > **"Understand the hiring manager's unstated pain point to align your verified strengths."**
4
+
5
+ ---
6
+
7
+ ## 1. Automated JD Signal Parsing Protocol
8
+
9
+ Execute the JD analyzer to extract structured requirements:
10
+ ```bash
11
+ python3 scripts/evidence/analyze-jd.py --jd "path/to/jd.md" --json
12
+ ```
13
+
14
+ ### Signal Extraction Dimensions:
15
+ 1. **Target Role & Seniority Tier**: Extracted from title and years of experience requirements;
16
+ 2. **Must-Have Skills (Hard Constraints)**: Top technical stacks and non-negotiable architectural competencies;
17
+ 3. **Nice-to-Have Skills (Differentiators)**: Emerging tools, cloud certifications, domain bonuses;
18
+ 4. **Hiring Pain Points**: High-frequency verbs indicating immediate business challenges (e.g., "reduce latency", "overhaul legacy monolithic service", "establish CI/CD").
19
+
20
+ ---
21
+
22
+ ## 2. Keyword Density & Highlighting Rules
23
+
24
+ In `workspace/resume.html`, inject JD keywords to pass ATS automated screening while remaining natural for human reviewers:
25
+
26
+ - **Primary Stack (Top 3~5 Keywords)**: Wrap with `<strong>` tags inside bullet points where evidence exists;
27
+ - **Technology Badges**: Place in `.tech-tags` or `.tech-badge` spans;
28
+ - **Density Control**: Maintain keyword density around 2.5%~4.0% of total words to prevent ATS keyword stuffing penalties.
29
+
30
+ ---
31
+
32
+ ## 3. FAB (Feature-Advantage-Benefit) Bullet Engineering
33
+
34
+ Structure every project bullet point using the FAB formula:
35
+
36
+ ```
37
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
38
+ │ Feature (技术动作)│ ────> │ Advantage (技术优势)│ ────> │ Benefit (业务价值) │
39
+ │ Exact tech & tool │ │ Architectural gain│ │ Quantified impact│
40
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
41
+ ```
42
+
43
+ *Before (Weak)*: Worked on database and improved query performance.
44
+ *After (FAB Standard)*: Refactored PostgreSQL indexing and introduced Redis multi-tier caching (**Feature**), cutting P99 query latency from 850ms to 45ms (**Advantage**), supporting 10x traffic spike during peak sales (**Benefit**).