knowme-careerforge-skill 0.0.1 → 0.0.2

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 (47) hide show
  1. package/README.md +6 -9
  2. package/README.zh-CN.md +6 -9
  3. package/SKILL.md +95 -116
  4. package/agents/claude/knowme-careerforge.md +9 -8
  5. package/agents/codex/knowme-careerforge.yaml +15 -6
  6. package/agents/cursor/knowme-careerforge.mdc +7 -6
  7. package/agents/opencode/skill.yaml +1 -1
  8. package/agents/windsurf/knowme-careerforge.rules +11 -8
  9. package/dist/cli/src/commands/init.d.ts +1 -1
  10. package/dist/cli/src/commands/init.js +37 -10
  11. package/dist/cli/src/index.js +65 -22
  12. package/dist/scripts/render-pdf.d.ts +0 -9
  13. package/dist/scripts/render-pdf.js +5 -68
  14. package/dist/scripts/rendering/render-pdf.d.ts +11 -0
  15. package/dist/scripts/rendering/render-pdf.js +109 -0
  16. package/dist/scripts/validate-ats.d.ts +0 -21
  17. package/dist/scripts/validate-ats.js +5 -132
  18. package/dist/scripts/validate-layout.d.ts +0 -22
  19. package/dist/scripts/validate-layout.js +5 -124
  20. package/dist/scripts/validation/validate-ats.d.ts +23 -0
  21. package/dist/scripts/validation/validate-ats.js +173 -0
  22. package/dist/scripts/validation/validate-layout.d.ts +24 -0
  23. package/dist/scripts/validation/validate-layout.js +164 -0
  24. package/package.json +15 -12
  25. package/scripts/Agent.md +138 -0
  26. package/scripts/{build-gallery.py → build/build-gallery.py} +128 -99
  27. package/scripts/{build-knowledge.py → build/build-knowledge.py} +9 -1
  28. package/scripts/{release.py → build/release.py} +20 -16
  29. package/scripts/{run-all-tests.py → build/run-all-tests.py} +9 -1
  30. package/scripts/build/sync-version.py +107 -0
  31. package/scripts/{analyze-jd.py → evidence/analyze-jd.py} +34 -45
  32. package/scripts/evidence/extract-evidence.py +348 -0
  33. package/scripts/pipeline/forge.py +142 -0
  34. package/scripts/rendering/render-pdf.py +162 -0
  35. package/scripts/{render-pdf.ts → rendering/render-pdf.ts} +3 -3
  36. package/scripts/template/instantiate-resume.py +178 -0
  37. package/scripts/{search-template.py → template/search-template.py} +44 -42
  38. package/scripts/{validate-ats.ts → validation/validate-ats.ts} +39 -35
  39. package/scripts/{validate-layout.ts → validation/validate-layout.ts} +43 -42
  40. package/scripts/{validate-resume.py → validation/validate-resume.py} +31 -22
  41. package/skill.json +1 -1
  42. package/src/knowledge/index.json +1 -1
  43. package/src/knowledge/resume-schema.json +112 -0
  44. package/src/workflows/forge-resume.md +2 -2
  45. package/src/workflows/review.md +3 -3
  46. package/scripts/instantiate-resume.py +0 -83
  47. package/scripts/sync-version.py +0 -57
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ KnowMe CareerForge — Version Synchronization Engine
4
+ 将项目根版本号统一同步至 package.json, pyproject.toml, skill.json, cli/package.json, SKILL.md, agents/*, Agent.md。
5
+ """
6
+
7
+ import sys, os, json, re, argparse
8
+ from pathlib import Path
9
+
10
+ def get_project_root() -> Path:
11
+ curr = Path(__file__).resolve().parent
12
+ for _ in range(5):
13
+ if (curr / "SKILL.md").exists() or (curr / "package.json").exists():
14
+ return curr
15
+ curr = curr.parent
16
+ return Path.cwd()
17
+
18
+ def sync_version(new_version: str):
19
+ base_dir = get_project_root()
20
+ # 去除可能传入的 'v' 前缀
21
+ semver = new_version.lstrip("v")
22
+
23
+ print("==============================================================")
24
+ print(f" KnowMe CareerForge — Synchronizing Version to v{semver}")
25
+ print("==============================================================")
26
+
27
+ # 1. Update package.json
28
+ pkg_json_path = base_dir / "package.json"
29
+ if pkg_json_path.exists():
30
+ data = json.loads(pkg_json_path.read_text(encoding="utf-8"))
31
+ data["version"] = semver
32
+ pkg_json_path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
33
+ print(f"[✓] Synced package.json -> {semver}")
34
+
35
+ # 2. Update cli/package.json
36
+ cli_pkg_path = base_dir / "cli" / "package.json"
37
+ if cli_pkg_path.exists():
38
+ data = json.loads(cli_pkg_path.read_text(encoding="utf-8"))
39
+ data["version"] = semver
40
+ cli_pkg_path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
41
+ print(f"[✓] Synced cli/package.json -> {semver}")
42
+
43
+ # 3. Update skill.json
44
+ skill_json_path = base_dir / "skill.json"
45
+ if skill_json_path.exists():
46
+ data = json.loads(skill_json_path.read_text(encoding="utf-8"))
47
+ data["version"] = semver
48
+ skill_json_path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
49
+ print(f"[✓] Synced skill.json -> {semver}")
50
+
51
+ # 4. Update pyproject.toml
52
+ pyproj_path = base_dir / "pyproject.toml"
53
+ if pyproj_path.exists():
54
+ text = pyproj_path.read_text(encoding="utf-8")
55
+ new_text = re.sub(r'version\s*=\s*"[^"]+"', f'version = "{semver}"', text)
56
+ pyproj_path.write_text(new_text, encoding="utf-8")
57
+ print(f"[✓] Synced pyproject.toml -> {semver}")
58
+
59
+ # 5. Update SKILL.md
60
+ skill_md_path = base_dir / "SKILL.md"
61
+ if skill_md_path.exists():
62
+ text = skill_md_path.read_text(encoding="utf-8")
63
+ new_text = re.sub(r'version:\s*[\d.]+', f'version: {semver}', text)
64
+ skill_md_path.write_text(new_text, encoding="utf-8")
65
+ print(f"[✓] Synced SKILL.md -> {semver}")
66
+
67
+ # 6. Update agents/codex/knowme-careerforge.yaml
68
+ codex_yaml = base_dir / "agents" / "codex" / "knowme-careerforge.yaml"
69
+ if codex_yaml.exists():
70
+ text = codex_yaml.read_text(encoding="utf-8")
71
+ new_text = re.sub(r'version:\s*[\d.]+', f'version: {semver}', text)
72
+ codex_yaml.write_text(new_text, encoding="utf-8")
73
+ print(f"[✓] Synced agents/codex/knowme-careerforge.yaml -> {semver}")
74
+
75
+ # 7. Update agents/opencode/skill.yaml
76
+ opencode_yaml = base_dir / "agents" / "opencode" / "skill.yaml"
77
+ if opencode_yaml.exists():
78
+ text = opencode_yaml.read_text(encoding="utf-8")
79
+ new_text = re.sub(r'version:\s*[\d.]+', f'version: {semver}', text)
80
+ opencode_yaml.write_text(new_text, encoding="utf-8")
81
+ print(f"[✓] Synced agents/opencode/skill.yaml -> {semver}")
82
+
83
+ # 8. Update scripts/Agent.md
84
+ agent_md = base_dir / "scripts" / "Agent.md"
85
+ if agent_md.exists():
86
+ text = agent_md.read_text(encoding="utf-8")
87
+ new_text = re.sub(r'版本[::]\s*v[\d.]+(?:-[\w.]+)?', f'版本:v{semver}', text)
88
+ agent_md.write_text(new_text, encoding="utf-8")
89
+ print(f"[✓] Synced scripts/Agent.md -> v{semver}")
90
+
91
+ # 9. Recompile knowledge index to update index.json version
92
+ kb_script = base_dir / "scripts" / "build" / "build-knowledge.py"
93
+ if kb_script.exists():
94
+ import subprocess
95
+ subprocess.run([sys.executable, str(kb_script)], stdout=subprocess.DEVNULL)
96
+ print(f"[✓] Recompiled knowledge index -> v{semver}")
97
+
98
+ print("\n[✓] All package versions successfully synchronized!")
99
+
100
+ def main():
101
+ parser = argparse.ArgumentParser(description="Synchronize project version")
102
+ parser.add_argument("version", nargs="?", default="0.0.2", help="New semantic version (e.g. 0.0.2)")
103
+ args = parser.parse_args()
104
+ sync_version(args.version)
105
+
106
+ if __name__ == "__main__":
107
+ main()
@@ -17,9 +17,6 @@ COMMON_TECH_STACKS = [
17
17
  ]
18
18
 
19
19
  def analyze_jd_text(jd_text: str) -> dict:
20
- text_lower = jd_text.lower()
21
-
22
- # 1. 识别岗位与职级
23
20
  seniority = "mid"
24
21
  if any(k in jd_text for k in ["资深", "专家", "架构师", "总监", "Lead", "Senior", "Principal"]):
25
22
  seniority = "senior/lead"
@@ -30,18 +27,15 @@ def analyze_jd_text(jd_text: str) -> dict:
30
27
  if any(k in jd_text for k in ["产品经理", "Product Manager", "总监", "Director", "业务负责人"]):
31
28
  category = "management-product"
32
29
 
33
- # 2. 匹配技术栈与关键词
34
30
  detected_skills = []
35
31
  for skill in COMMON_TECH_STACKS:
36
32
  pattern = re.compile(rf'(?:^|[^\w]){re.escape(skill)}(?:[^\w]|$)', re.IGNORECASE)
37
33
  if pattern.search(jd_text):
38
34
  detected_skills.append(skill)
39
35
 
40
- # 3. 区分必备与加分
41
36
  must_haves = detected_skills[:6] if len(detected_skills) >= 6 else detected_skills
42
37
  nice_to_haves = detected_skills[6:] if len(detected_skills) > 6 else []
43
38
 
44
- # 4. 业务痛点与职责关键词提取
45
39
  responsibilities = []
46
40
  for line in jd_text.splitlines():
47
41
  line_clean = line.strip()
@@ -50,59 +44,54 @@ def analyze_jd_text(jd_text: str) -> dict:
50
44
 
51
45
  return {
52
46
  "status": "success",
47
+ "seniority": seniority,
53
48
  "category": category,
54
- "estimatedSeniority": seniority,
49
+ "detectedSkills": detected_skills,
55
50
  "mustHaveSkills": must_haves,
56
51
  "niceToHaveSkills": nice_to_haves,
57
- "detectedSkills": detected_skills,
58
- "keyResponsibilities": responsibilities[:6],
59
- "hiringSignals": [
60
- "强调工程化与系统高并发稳定性" if "稳定" in jd_text or "高并发" in jd_text else "强调快速交付与 0-1 探索",
61
- "大模型与 AI Agent 场景深度落地" if "大模型" in jd_text or "rag" in text_lower or "agent" in text_lower else "传统业务系统演进"
62
- ]
52
+ "keyResponsibilities": responsibilities[:5]
63
53
  }
64
54
 
65
55
  def main():
66
56
  parser = argparse.ArgumentParser(description="KnowMe CareerForge — JD Analyzer")
67
- parser.add_argument("--jd", "-j", help="Path to JD text file")
68
- parser.add_argument("--text", "-t", help="Raw JD text string")
69
- parser.add_argument("--json", action="store_true", help="Output only clean JSON")
57
+ parser.add_argument("--jd", help="Path to JD markdown/text file")
58
+ parser.add_argument("--text", help="Raw JD text string")
59
+ parser.add_argument("--json", action="store_true", help="Output JSON format")
70
60
 
71
61
  args = parser.parse_args()
72
-
73
- content = ""
74
- if args.jd and Path(args.jd).exists():
75
- content = Path(args.jd).read_text(encoding="utf-8")
62
+ jd_content = ""
63
+
64
+ if args.jd:
65
+ p = Path(args.jd)
66
+ if not p.exists():
67
+ print(f"Error: JD file not found: {p}", file=sys.stderr)
68
+ sys.exit(1)
69
+ jd_content = p.read_text(encoding="utf-8")
76
70
  elif args.text:
77
- content = args.text
71
+ jd_content = args.text
72
+ else:
73
+ if not sys.stdin.isatty():
74
+ jd_content = sys.stdin.read()
75
+ else:
76
+ parser.print_help()
77
+ sys.exit(1)
78
78
 
79
- if not content.strip():
80
- # 默认样例
81
- content = """职位:资深 AI Agent 研发工程师
82
- 岗位职责:
83
- 1. 负责企业级多 Agent 智能协同工作流平台的设计与核心研发;
84
- 2. 主导基于 RAG 的专业知识库检索管线优化与模型微调;
85
- 3. 持续优化 Prompt 与上下文窗口,压榨模型推理延迟与 Token 成本。
86
- 任职要求:
87
- 1. 本科及以上学历,精通 Python、FastAPI 及多线程异步编程;
88
- 2. 熟练掌握 LangGraph、LangChain、LlamaIndex 等 Agent 开发框架;
89
- 3. 深入理解向量数据库(Qdrant / Milvus)原理,具备高并发高可用调优经验;
90
- 4. 熟悉 Docker、Kubernetes 云原生技术栈者优先。"""
91
-
92
- result = analyze_jd_text(content)
79
+ result = analyze_jd_text(jd_content)
93
80
 
94
81
  if args.json:
95
82
  print(json.dumps(result, ensure_ascii=False, indent=2))
96
- return
97
-
98
- print("=" * 70)
99
- print(" KnowMe CareerForge — JD Analysis Result")
100
- print(f" Category : {result['category']}")
101
- print(f" Seniority Level : {result['estimatedSeniority']}")
102
- print(f" Must-Have Skills : {', '.join(result['mustHaveSkills'])}")
103
- print(f" Nice-To-Have Skills : {', '.join(result['niceToHaveSkills'])}")
104
- print(f" Hiring Signals : {', '.join(result['hiringSignals'])}")
105
- print("=" * 70)
83
+ else:
84
+ print("=" * 70)
85
+ print(" KnowMe CareerForge — JD Analysis Report")
86
+ print("=" * 70)
87
+ print(f" Target Category : {result['category']}")
88
+ print(f" Seniority Level : {result['seniority']}")
89
+ print(f" Must-have Skills : {', '.join(result['mustHaveSkills']) or 'None'}")
90
+ print(f" Nice-to-have Skills: {', '.join(result['niceToHaveSkills']) or 'None'}")
91
+ print(" Key Responsibilities:")
92
+ for r in result["keyResponsibilities"]:
93
+ print(f" • {r}")
94
+ print("=" * 70)
106
95
 
107
96
  if __name__ == "__main__":
108
97
  main()
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ KnowMe CareerForge — Codebase & Git Evidence Miner (repo-to-resume-tailor engine)
4
+ 深度解析本地代码工程、依赖清单、系统架构与 Git 提交历史,提取 L1~L3 结构化证据链并输出 Candidate Master Profile。
5
+ """
6
+
7
+ import sys, os, json, argparse, re, subprocess
8
+ from pathlib import Path
9
+ from typing import Dict, List, Any, Optional
10
+
11
+ def get_project_root() -> Path:
12
+ curr = Path(__file__).resolve().parent
13
+ for _ in range(5):
14
+ if (curr / "SKILL.md").exists() or (curr / "package.json").exists():
15
+ return curr
16
+ curr = curr.parent
17
+ return Path.cwd()
18
+
19
+ TECH_SIGNATURES = {
20
+ # AI / LLM & Agents
21
+ "LangChain": ["langchain", "langchain-core", "@langchain/core"],
22
+ "LangGraph": ["langgraph", "@langchain/langgraph"],
23
+ "LlamaIndex": ["llama-index", "llamaindex"],
24
+ "Qdrant": ["qdrant-client", "@qdrant/js-client-rest"],
25
+ "Milvus": ["pymilvus", "@zilliz/milvus2-sdk-node"],
26
+ "FastAPI": ["fastapi", "uvicorn"],
27
+ "PyTorch": ["torch", "torchvision"],
28
+ "OpenAI SDK": ["openai", "@openai/openai"],
29
+ # Web & Fullstack
30
+ "TypeScript": ["typescript", "ts-node", "@types/node"],
31
+ "React": ["react", "react-dom", "next"],
32
+ "Next.js": ["next"],
33
+ "Vue.js": ["vue", "nuxt"],
34
+ "Node.js": ["express", "koa", "nestjs", "@nestjs/core", "fastify"],
35
+ "Tailwind CSS": ["tailwindcss", "@tailwindcss/postcss"],
36
+ "Playwright": ["playwright", "@playwright/test"],
37
+ "GraphQL": ["graphql", "apollo-server", "@apollo/client"],
38
+ # Backend & Systems
39
+ "Go / Golang": ["github.com/gin-gonic/gin", "google.golang.org/grpc"],
40
+ "Rust": ["tokio", "serde", "actix-web", "axum"],
41
+ "Spring Boot": ["spring-boot", "org.springframework.boot"],
42
+ # Database & Cache
43
+ "PostgreSQL": ["pg", "psycopg2", "asyncpg", "typeorm", "prisma"],
44
+ "MySQL": ["mysql", "mysql2", "pymysql"],
45
+ "Redis": ["redis", "ioredis", "aioredis"],
46
+ "Elasticsearch": ["elasticsearch", "@elastic/elasticsearch"],
47
+ # DevOps & Infrastructure
48
+ "Docker": ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"],
49
+ "Kubernetes": ["k8s", "helm", "Deployment", "StatefulSet"],
50
+ "GitHub Actions": [".github/workflows"]
51
+ }
52
+
53
+ def run_cmd(cmd: List[str], cwd: Optional[Path] = None) -> str:
54
+ try:
55
+ res = subprocess.run(cmd, cwd=str(cwd) if cwd else None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=10)
56
+ return res.stdout.strip() if res.returncode == 0 else ""
57
+ except Exception:
58
+ return ""
59
+
60
+ def scan_repo(repo_path: Path) -> Dict[str, Any]:
61
+ repo_path = repo_path.resolve()
62
+ facts: Dict[str, Any] = {
63
+ "repo_name": repo_path.name,
64
+ "repo_path": str(repo_path),
65
+ "detected_languages": {},
66
+ "detected_tech_stacks": set(),
67
+ "architectural_signals": [],
68
+ "author_info": {},
69
+ "git_stats": {},
70
+ "key_dependencies": [],
71
+ "evidence_items": []
72
+ }
73
+
74
+ if not repo_path.exists() or not repo_path.is_dir():
75
+ return facts
76
+
77
+ # 1. Git 信息提取 (Author, commits, tags)
78
+ git_dir = repo_path / ".git"
79
+ if git_dir.exists():
80
+ user_name = run_cmd(["git", "config", "user.name"], cwd=repo_path)
81
+ user_email = run_cmd(["git", "config", "user.email"], cwd=repo_path)
82
+ commit_count = run_cmd(["git", "rev-list", "--count", "HEAD"], cwd=repo_path)
83
+ remote_url = run_cmd(["git", "config", "--get", "remote.origin.url"], cwd=repo_path)
84
+ recent_commits = run_cmd(["git", "log", "-n", "8", "--pretty=format:%s"], cwd=repo_path)
85
+
86
+ facts["author_info"]["name"] = user_name or ""
87
+ facts["author_info"]["email"] = user_email or ""
88
+ facts["author_info"]["git_remote"] = remote_url or ""
89
+ facts["git_stats"]["total_commits"] = int(commit_count) if commit_count.isdigit() else 0
90
+ facts["git_stats"]["recent_commits"] = [c for c in recent_commits.splitlines() if c]
91
+
92
+ # 2. 统计文件类型分布
93
+ lang_map = {
94
+ ".ts": "TypeScript", ".tsx": "TypeScript/React", ".js": "JavaScript", ".jsx": "JavaScript/React",
95
+ ".py": "Python", ".go": "Go", ".rs": "Rust", ".java": "Java", ".cpp": "C++", ".c": "C",
96
+ ".html": "HTML", ".css": "CSS", ".vue": "Vue", ".sql": "SQL", ".sh": "Shell"
97
+ }
98
+
99
+ file_counts = {}
100
+ ignored_dirs = {".git", "node_modules", ".venv", "venv", "__pycache__", "dist", "build", ".next", ".turbo"}
101
+
102
+ for root, dirs, files in os.walk(repo_path):
103
+ dirs[:] = [d for d in dirs if d not in ignored_dirs and not d.startswith(".")]
104
+ for f in files:
105
+ p = Path(root) / f
106
+ ext = p.suffix.lower()
107
+ if ext in lang_map:
108
+ lang = lang_map[ext]
109
+ file_counts[lang] = file_counts.get(lang, 0) + 1
110
+
111
+ facts["detected_languages"] = file_counts
112
+
113
+ # 3. 依赖文件深度解析
114
+ dep_names = set()
115
+
116
+ # 3.1 Node/TS
117
+ pkg_json = repo_path / "package.json"
118
+ if pkg_json.exists():
119
+ try:
120
+ pkg_data = json.loads(pkg_json.read_text(encoding="utf-8"))
121
+ if not facts["author_info"].get("name") and pkg_data.get("author"):
122
+ author_val = pkg_data.get("author")
123
+ facts["author_info"]["name"] = author_val if isinstance(author_val, str) else author_val.get("name", "")
124
+ if not facts["author_info"].get("git_remote") and pkg_data.get("repository"):
125
+ repo_val = pkg_data.get("repository")
126
+ facts["author_info"]["git_remote"] = repo_val if isinstance(repo_val, str) else repo_val.get("url", "")
127
+
128
+ deps = {**pkg_data.get("dependencies", {}), **pkg_data.get("devDependencies", {})}
129
+ dep_names.update(deps.keys())
130
+ except Exception:
131
+ pass
132
+
133
+ # 3.2 Python
134
+ req_txt = repo_path / "requirements.txt"
135
+ if req_txt.exists():
136
+ try:
137
+ for line in req_txt.read_text(encoding="utf-8").splitlines():
138
+ line = line.strip()
139
+ if line and not line.startswith("#"):
140
+ dep_names.add(re.split(r'[=<>~]', line)[0].strip().lower())
141
+ except Exception:
142
+ pass
143
+
144
+ pyproject = repo_path / "pyproject.toml"
145
+ if pyproject.exists():
146
+ try:
147
+ content = pyproject.read_text(encoding="utf-8").lower()
148
+ for key in ["fastapi", "torch", "langchain", "qdrant", "pytest", "pydantic"]:
149
+ if key in content:
150
+ dep_names.add(key)
151
+ except Exception:
152
+ pass
153
+
154
+ # 3.3 Go
155
+ gomod = repo_path / "go.mod"
156
+ if gomod.exists():
157
+ try:
158
+ for line in gomod.read_text(encoding="utf-8").splitlines():
159
+ line = line.strip()
160
+ if line.startswith("require") or " " in line:
161
+ parts = line.split()
162
+ if len(parts) >= 2 and "/" in parts[0]:
163
+ dep_names.add(parts[0])
164
+ except Exception:
165
+ pass
166
+
167
+ # 3.4 Rust
168
+ cargo = repo_path / "Cargo.toml"
169
+ if cargo.exists():
170
+ try:
171
+ for line in cargo.read_text(encoding="utf-8").splitlines():
172
+ line = line.strip()
173
+ if "=" in line and not line.startswith("["):
174
+ dep_names.add(line.split("=")[0].strip().lower())
175
+ except Exception:
176
+ pass
177
+
178
+ # 4. 匹配技术栈标签
179
+ for tech, sigs in TECH_SIGNATURES.items():
180
+ for sig in sigs:
181
+ if sig.lower() in [d.lower() for d in dep_names] or (repo_path / sig).exists():
182
+ facts["detected_tech_stacks"].add(tech)
183
+ break
184
+
185
+ # 5. 架构与工程化信号
186
+ if (repo_path / "packages").exists() or (repo_path / "pnpm-workspace.yaml").exists():
187
+ facts["architectural_signals"].append("Monorepo 模块化架构")
188
+ if (repo_path / "Dockerfile").exists() or (repo_path / "docker-compose.yml").exists():
189
+ facts["architectural_signals"].append("Docker 容器化交付")
190
+ if (repo_path / ".github" / "workflows").exists():
191
+ facts["architectural_signals"].append("CI/CD 自动化流水线")
192
+ if (repo_path / "tests").exists() or (repo_path / "test").exists():
193
+ facts["architectural_signals"].append("自动化单元/集成测试")
194
+
195
+ facts["key_dependencies"] = sorted(list(dep_names))
196
+ facts["detected_tech_stacks"] = sorted(list(facts["detected_tech_stacks"]))
197
+
198
+ # 6. 生成 L1~L3 证据项
199
+ evidence_items = []
200
+ if facts["detected_tech_stacks"]:
201
+ evidence_items.append({
202
+ "claim": f"基于 {', '.join(facts['detected_tech_stacks'][:4])} 构建核心工程架构,实现高可靠端到端业务流",
203
+ "evidenceLevel": "L1",
204
+ "evidenceSource": f"代码库依赖配置文件 ({', '.join([f for f in ['package.json', 'go.mod', 'requirements.txt', 'Cargo.toml'] if (repo_path / f).exists()]) or '依赖树'})",
205
+ "keywords": facts["detected_tech_stacks"][:5]
206
+ })
207
+
208
+ if facts["architectural_signals"]:
209
+ evidence_items.append({
210
+ "claim": f"主导设计工程架构体系,落地 {', '.join(facts['architectural_signals'])},保障交付效率与质量",
211
+ "evidenceLevel": "L1",
212
+ "evidenceSource": "工程根目录架构配置文件与流水线定义",
213
+ "keywords": ["Architecture", "CI/CD", "Testing", "DevOps"]
214
+ })
215
+
216
+ if facts["git_stats"].get("total_commits", 0) > 10:
217
+ evidence_items.append({
218
+ "claim": f"持续推进工程重构与版本迭代,累计提交 {facts['git_stats']['total_commits']} 次代码并完成多版本发布",
219
+ "evidenceLevel": "L2",
220
+ "evidenceSource": "Git 提交历史与版本变更记录",
221
+ "keywords": ["Refactoring", "Git", "Release Engineering"]
222
+ })
223
+
224
+ facts["evidence_items"] = evidence_items
225
+ return facts
226
+
227
+ def build_master_profile(facts: Dict[str, Any], user_overrides: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
228
+ overrides = user_overrides or {}
229
+ author = facts.get("author_info", {})
230
+
231
+ name = overrides.get("name") or author.get("name") or "求职者"
232
+ title = overrides.get("title") or "资深工程师 / 技术专家"
233
+ email = overrides.get("email") or author.get("email") or "candidate@example.com"
234
+ phone = overrides.get("phone") or "138-0000-0000"
235
+ github = overrides.get("github") or author.get("git_remote") or "https://github.com"
236
+
237
+ tech_stacks = facts.get("detected_tech_stacks", [])
238
+ languages = list(facts.get("detected_languages", {}).keys())
239
+
240
+ profile = {
241
+ "basics": {
242
+ "name": name,
243
+ "title": title,
244
+ "email": email,
245
+ "phone": phone,
246
+ "location": overrides.get("location") or "北京 / 远程",
247
+ "github": github,
248
+ "summary": f"拥有坚实的技术研发与工程化落地实战经验,主导过多个基于 {', '.join((tech_stacks + languages)[:3])} 的核心系统设计与交付。"
249
+ },
250
+ "skills": [
251
+ {
252
+ "category": "核心技术栈",
253
+ "items": tech_stacks if tech_stacks else ["Python", "TypeScript", "FastAPI", "React", "Docker"],
254
+ "highlighted": tech_stacks[:4]
255
+ },
256
+ {
257
+ "category": "工程化与架构",
258
+ "items": facts.get("architectural_signals", ["CI/CD", "微服务架构", "自动化测试", "性能优化"]),
259
+ "highlighted": ["CI/CD", "性能优化"]
260
+ }
261
+ ],
262
+ "experience": [
263
+ {
264
+ "company": overrides.get("company") or "创新科技企业",
265
+ "role": title,
266
+ "startDate": "2022.06",
267
+ "endDate": "至今",
268
+ "location": "北京",
269
+ "summary": "负责核心产品研发与关键架构攻坚",
270
+ "bullets": [
271
+ {
272
+ "text": item["claim"],
273
+ "evidenceLevel": item["evidenceLevel"],
274
+ "evidenceSource": item["evidenceSource"],
275
+ "keywords": item["keywords"]
276
+ }
277
+ for item in facts.get("evidence_items", [])
278
+ ]
279
+ }
280
+ ],
281
+ "projects": [
282
+ {
283
+ "name": facts.get("repo_name", "CoreSystem"),
284
+ "role": "核心负责人 / 主力开发者",
285
+ "techStack": tech_stacks[:5],
286
+ "repoUrl": github,
287
+ "startDate": "2023.01",
288
+ "endDate": "至今",
289
+ "bullets": [
290
+ {
291
+ "text": item["claim"],
292
+ "evidenceLevel": item["evidenceLevel"],
293
+ "evidenceSource": item["evidenceSource"]
294
+ }
295
+ for item in facts.get("evidence_items", [])
296
+ ]
297
+ }
298
+ ],
299
+ "education": [
300
+ {
301
+ "institution": overrides.get("school") or "重点大学",
302
+ "degree": overrides.get("degree") or "计算机科学与技术 本科",
303
+ "startDate": "2016.09",
304
+ "endDate": "2020.06"
305
+ }
306
+ ]
307
+ }
308
+ return profile
309
+
310
+ def main():
311
+ parser = argparse.ArgumentParser(description="KnowMe CareerForge — Codebase Evidence Miner")
312
+ parser.add_argument("--repo", "-r", default=".", help="Target repository directory (default: current directory)")
313
+ parser.add_argument("--output", "-o", default="workspace/evidence-master.json", help="Output evidence JSON path")
314
+ parser.add_argument("--name", help="Candidate full name override")
315
+ parser.add_argument("--role", help="Candidate target role/title override")
316
+ parser.add_argument("--email", help="Candidate email override")
317
+ parser.add_argument("--phone", help="Candidate phone override")
318
+ parser.add_argument("--quiet", "-q", action="store_true", help="Quiet execution mode")
319
+
320
+ args = parser.parse_args()
321
+ repo_path = Path(args.repo)
322
+
323
+ facts = scan_repo(repo_path)
324
+
325
+ overrides = {}
326
+ if args.name: overrides["name"] = args.name
327
+ if args.role: overrides["title"] = args.role
328
+ if args.email: overrides["email"] = args.email
329
+ if args.phone: overrides["phone"] = args.phone
330
+
331
+ profile = build_master_profile(facts, overrides)
332
+
333
+ out_path = Path(args.output)
334
+ out_path.parent.mkdir(parents=True, exist_ok=True)
335
+ out_path.write_text(json.dumps(profile, ensure_ascii=False, indent=2), encoding="utf-8")
336
+
337
+ if not args.quiet:
338
+ print("==============================================================")
339
+ print(" KnowMe CareerForge — Codebase Evidence Mining Report")
340
+ print(f" Target Repository : {facts['repo_path']}")
341
+ print(f" Detected Techs : {', '.join(facts['detected_tech_stacks']) or 'None'}")
342
+ print(f" Architecture : {', '.join(facts['architectural_signals']) or 'Standard'}")
343
+ print(f" Evidence Generated: {len(facts['evidence_items'])} L1/L2 items")
344
+ print(f" Profile Written To: {out_path}")
345
+ print("==============================================================")
346
+
347
+ if __name__ == "__main__":
348
+ main()