knowme-careerforge-skill 0.0.7 → 0.0.8

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 (68) hide show
  1. package/AGENT.md +7 -6
  2. package/ARCHITECTURE.md +18 -15
  3. package/README.md +23 -15
  4. package/README.zh-CN.md +23 -15
  5. package/SKILL.md +26 -33
  6. package/agents/codex/knowme-careerforge.yaml +1 -1
  7. package/agents/opencode/skill.yaml +1 -1
  8. package/dist/cli/src/index.js +22 -14
  9. package/package.json +1 -1
  10. package/references/01-evidence-mining.md +80 -3
  11. package/references/02-career-goal.md +35 -15
  12. package/references/03-jd-analysis.md +10 -0
  13. package/references/04-template-selection.md +7 -2
  14. package/references/05-html-canvas-tokens.md +4 -2
  15. package/references/06-qa-and-rendering.md +39 -5
  16. package/references/07-artifact-contract.md +21 -7
  17. package/scripts/Agent.md +3 -3
  18. package/scripts/build/build-gallery.py +2 -1
  19. package/scripts/build/release.py +3 -2
  20. package/scripts/evidence/analyze-jd.py +49 -8
  21. package/scripts/evidence/extract-evidence.py +17 -391
  22. package/scripts/pipeline/forge.py +159 -85
  23. package/scripts/rendering/browser-engine.js +5 -1
  24. package/scripts/template/instantiate-resume.py +5 -3
  25. package/skill.json +1 -1
  26. package/src/knowledge/career/seniority-signals.json +1 -0
  27. package/src/knowledge/index.json +176 -2
  28. package/src/knowledge/resume/action-verbs.json +3 -2
  29. package/src/knowledge/resume/fab-model.json +5 -4
  30. package/src/knowledge/resume-schema.json +1 -1
  31. package/src/knowledge/templates.json +174 -0
  32. package/src/references/01-evidence-mining.md +80 -3
  33. package/src/references/02-career-goal.md +35 -15
  34. package/src/references/03-jd-analysis.md +10 -0
  35. package/src/references/04-template-selection.md +7 -2
  36. package/src/references/05-html-canvas-tokens.md +4 -2
  37. package/src/references/06-qa-and-rendering.md +39 -5
  38. package/src/references/07-artifact-contract.md +21 -7
  39. package/src/templates/academic-research/canvas.html +2 -1
  40. package/src/templates/classic/canvas.html +2 -1
  41. package/src/templates/classic/style.css +90 -28
  42. package/src/templates/common/base.css +19 -3
  43. package/src/templates/common/canvas-bindings.css +247 -12
  44. package/src/templates/compact-dense/style.css +5 -1
  45. package/src/templates/creative-tech/style.css +30 -1
  46. package/src/templates/data-analyst/style.css +31 -1
  47. package/src/templates/editorial/README.md +7 -0
  48. package/src/templates/editorial/canvas.html +22 -0
  49. package/src/templates/editorial/metadata.json +36 -0
  50. package/src/templates/editorial/sample-profile.json +86 -0
  51. package/src/templates/editorial/style.css +24 -0
  52. package/src/templates/executive/style.css +52 -18
  53. package/src/templates/graduate/README.md +7 -0
  54. package/src/templates/graduate/canvas.html +22 -0
  55. package/src/templates/graduate/metadata.json +38 -0
  56. package/src/templates/graduate/sample-profile.json +60 -0
  57. package/src/templates/graduate/style.css +24 -0
  58. package/src/templates/international-flow/canvas.html +2 -1
  59. package/src/templates/minimal/canvas.html +3 -2
  60. package/src/templates/minimal/style.css +6 -0
  61. package/src/templates/modern/style.css +43 -55
  62. package/src/templates/project-focus/README.md +7 -0
  63. package/src/templates/project-focus/canvas.html +22 -0
  64. package/src/templates/project-focus/metadata.json +36 -0
  65. package/src/templates/project-focus/sample-profile.json +93 -0
  66. package/src/templates/project-focus/style.css +24 -0
  67. package/src/templates/startup-generalist/canvas.html +2 -1
  68. package/src/templates/startup-generalist/style.css +15 -8
@@ -1,395 +1,21 @@
1
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
- """
2
+ """Retired compatibility entry: repository contents are not candidate evidence."""
3
+ import json
4
+ import sys
6
5
 
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
- """Build structured candidate profile from codebase facts + optional user overrides.
229
-
230
- Placeholder discipline:
231
- - repo_mode=True : facts dict is populated from real repo scan; safe to synthesize
232
- experience/education skeletons because they'll be user-reviewed before use.
233
- - repo_mode=False: only user_overrides carry real data. NEVER emit fabricated placeholder
234
- strings (fake company, fake school, fake dates). Use null / empty list instead.
235
- """
236
- overrides = user_overrides or {}
237
- author = facts.get("author_info", {})
238
- repo_mode: bool = bool(facts.get("detected_tech_stacks") or facts.get("evidence_items"))
239
-
240
- # ── Basics ────────────────────────────────────────────────────────────────
241
- name = overrides.get("name") or author.get("name") or (None if not repo_mode else "候选人")
242
- title = overrides.get("title") or (None if not repo_mode else "资深工程师 / 技术专家")
243
- email = overrides.get("email") or author.get("email") or (None if not repo_mode else "candidate@example.com")
244
- phone = overrides.get("phone") or (None if not repo_mode else None)
245
- github = overrides.get("github") or author.get("git_remote") or None
246
- location = overrides.get("location") or (None if not repo_mode else None)
247
-
248
- tech_stacks = facts.get("detected_tech_stacks", [])
249
- languages = list(facts.get("detected_languages", {}).keys())
250
-
251
- # ── Skills ────────────────────────────────────────────────────────────────
252
- all_tech = tech_stacks + [l for l in languages if l not in tech_stacks]
253
- arch_signals = facts.get("architectural_signals", [])
254
- skills: list = []
255
- if all_tech:
256
- skills.append({"category": "核心技术栈", "items": all_tech, "highlighted": all_tech[:4]})
257
- if arch_signals:
258
- skills.append({"category": "工程化与架构", "items": arch_signals, "highlighted": arch_signals[:2]})
259
-
260
- # ── Experience ────────────────────────────────────────────────────────────
261
- bullets = [
262
- {
263
- "text": item["claim"],
264
- "evidenceLevel": item["evidenceLevel"],
265
- "evidenceSource": item["evidenceSource"],
266
- "keywords": item.get("keywords", [])
267
- }
268
- for item in facts.get("evidence_items", [])
269
- ]
270
-
271
- if repo_mode and bullets:
272
- # Repo mode: synthesize a single experience entry from code evidence.
273
- # Company / dates are placeholders that the user MUST review.
274
- experience = [
275
- {
276
- "company": overrides.get("company") or "[请填写:公司名称]",
277
- "role": title or "[请填写:职位名称]",
278
- "startDate": overrides.get("startDate") or "[请填写:开始时间]",
279
- "endDate": overrides.get("endDate") or "至今",
280
- "location": location or "[请填写:工作地点]",
281
- "summary": "负责核心产品研发与关键架构攻坚",
282
- "bullets": bullets
283
- }
284
- ]
285
- elif overrides.get("experience"):
286
- experience = overrides["experience"]
287
- else:
288
- experience = []
289
-
290
- # ── Projects ──────────────────────────────────────────────────────────────
291
- if repo_mode and tech_stacks:
292
- projects = [
293
- {
294
- "name": facts.get("repo_name") or "[请填写:项目名称]",
295
- "role": "核心负责人 / 主力开发者",
296
- "techStack": tech_stacks[:5],
297
- "repoUrl": github,
298
- "startDate": "[请填写:开始时间]",
299
- "endDate": "至今",
300
- "bullets": [
301
- {"text": item["claim"], "evidenceLevel": item["evidenceLevel"], "evidenceSource": item["evidenceSource"]}
302
- for item in facts.get("evidence_items", [])
303
- ]
304
- }
305
- ]
306
- elif overrides.get("projects"):
307
- projects = overrides["projects"]
308
- else:
309
- projects = []
310
-
311
- # ── Education ─────────────────────────────────────────────────────────────
312
- if overrides.get("education"):
313
- education = overrides["education"]
314
- elif overrides.get("school") or overrides.get("degree"):
315
- education = [
316
- {
317
- "institution": overrides.get("school") or "[请填写:学校名称]",
318
- "degree": overrides.get("degree") or "[请填写:学历与专业]",
319
- "startDate": overrides.get("edu_start") or "[请填写:入学时间]",
320
- "endDate": overrides.get("edu_end") or "[请填写:毕业时间]"
321
- }
322
- ]
323
- elif repo_mode:
324
- # Repo mode: emit a reminder skeleton so the user knows to fill it in.
325
- education = [
326
- {
327
- "institution": "[请填写:学校名称]",
328
- "degree": "[请填写:学历与专业]",
329
- "startDate": "[请填写:入学时间]",
330
- "endDate": "[请填写:毕业时间]"
331
- }
332
- ]
333
- else:
334
- education = []
335
-
336
- # ── Assemble ──────────────────────────────────────────────────────────────
337
- basics: Dict[str, Any] = {}
338
- if name: basics["name"] = name
339
- if title: basics["title"] = title
340
- if email: basics["email"] = email
341
- if phone: basics["phone"] = phone
342
- if location: basics["location"] = location
343
- if github: basics["github"] = github
344
- if all_tech:
345
- basics["summary"] = (
346
- f"拥有坚实的技术研发与工程化落地实战经验,"
347
- f"主导过多个基于 {', '.join(all_tech[:3])} 的核心系统设计与交付。"
348
- )
349
-
350
- profile: Dict[str, Any] = {"basics": basics}
351
- if skills: profile["skills"] = skills
352
- if experience: profile["experience"] = experience
353
- if projects: profile["projects"] = projects
354
- if education: profile["education"] = education
355
- return profile
356
6
 
357
7
  def main():
358
- parser = argparse.ArgumentParser(description="KnowMe CareerForge — Codebase Evidence Miner")
359
- parser.add_argument("--repo", "-r", default=".", help="Target repository directory (default: current directory)")
360
- parser.add_argument("--output", "-o", default="workspace/evidence-master.json", help="Output evidence JSON path")
361
- parser.add_argument("--name", help="Candidate full name override")
362
- parser.add_argument("--role", help="Candidate target role/title override")
363
- parser.add_argument("--email", help="Candidate email override")
364
- parser.add_argument("--phone", help="Candidate phone override")
365
- parser.add_argument("--quiet", "-q", action="store_true", help="Quiet execution mode")
366
-
367
- args = parser.parse_args()
368
- repo_path = Path(args.repo)
369
-
370
- facts = scan_repo(repo_path)
371
-
372
- overrides = {}
373
- if args.name: overrides["name"] = args.name
374
- if args.role: overrides["title"] = args.role
375
- if args.email: overrides["email"] = args.email
376
- if args.phone: overrides["phone"] = args.phone
377
-
378
- profile = build_master_profile(facts, overrides)
379
-
380
- out_path = Path(args.output)
381
- out_path.parent.mkdir(parents=True, exist_ok=True)
382
- out_path.write_text(json.dumps(profile, ensure_ascii=False, indent=2), encoding="utf-8")
383
-
384
- if not args.quiet:
385
- print("==============================================================")
386
- print(" KnowMe CareerForge — Codebase Evidence Mining Report")
387
- print(f" Target Repository : {facts['repo_path']}")
388
- print(f" Detected Techs : {', '.join(facts['detected_tech_stacks']) or 'None'}")
389
- print(f" Architecture : {', '.join(facts['architectural_signals']) or 'Standard'}")
390
- print(f" Evidence Generated: {len(facts['evidence_items'])} L1/L2 items")
391
- print(f" Profile Written To: {out_path}")
392
- print("==============================================================")
393
-
394
- if __name__ == "__main__":
395
- main()
8
+ # Intentionally do not parse repository/output paths or read candidate inputs.
9
+ # Old CLI/npm/direct callers must fail before creating or overwriting files.
10
+ print(json.dumps({
11
+ 'status': 'FAIL',
12
+ 'stage': 'unsupported',
13
+ 'errors': ['Repository-to-resume extraction is not supported. Use user-provided facts with forge --profile-json.'],
14
+ 'warnings': [],
15
+ 'outputs': {},
16
+ }))
17
+ return 1
18
+
19
+
20
+ if __name__ == '__main__':
21
+ sys.exit(main())