knowme-careerforge-skill 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/LICENSE +21 -0
- package/README.md +227 -0
- package/README.zh-CN.md +231 -0
- package/SKILL.md +179 -0
- package/agents/claude/knowme-careerforge.md +16 -0
- package/agents/codex/knowme-careerforge.yaml +15 -0
- package/agents/cursor/knowme-careerforge.mdc +12 -0
- package/agents/gemini/knowme-careerforge.json +6 -0
- package/agents/opencode/skill.yaml +11 -0
- package/agents/windsurf/knowme-careerforge.rules +10 -0
- package/bin/knowme.js +32 -0
- package/dist/cli/src/commands/init.d.ts +10 -0
- package/dist/cli/src/commands/init.js +130 -0
- package/dist/cli/src/commands/list.d.ts +5 -0
- package/dist/cli/src/commands/list.js +92 -0
- package/dist/cli/src/index.d.ts +6 -0
- package/dist/cli/src/index.js +149 -0
- package/dist/scripts/render-pdf.d.ts +11 -0
- package/dist/scripts/render-pdf.js +105 -0
- package/dist/scripts/validate-ats.d.ts +23 -0
- package/dist/scripts/validate-ats.js +169 -0
- package/dist/scripts/validate-layout.d.ts +24 -0
- package/dist/scripts/validate-layout.js +161 -0
- package/package.json +58 -0
- package/scripts/analyze-jd.py +108 -0
- package/scripts/build-gallery.py +233 -0
- package/scripts/build-knowledge.py +81 -0
- package/scripts/instantiate-resume.py +83 -0
- package/scripts/release.py +101 -0
- package/scripts/render-pdf.ts +86 -0
- package/scripts/run-all-tests.py +41 -0
- package/scripts/search-template.py +147 -0
- package/scripts/sync-version.py +57 -0
- package/scripts/validate-ats.ts +173 -0
- package/scripts/validate-layout.ts +161 -0
- package/scripts/validate-resume.py +139 -0
- package/skill.json +47 -0
- package/src/knowledge/ats-rules.json +37 -0
- package/src/knowledge/career/seniority-signals.json +24 -0
- package/src/knowledge/index.json +734 -0
- package/src/knowledge/industries/enterprise-saas.json +21 -0
- package/src/knowledge/industries/fintech.json +21 -0
- package/src/knowledge/industries/tech-ai.json +21 -0
- package/src/knowledge/layouts.json +59 -0
- package/src/knowledge/resume/action-verbs.json +20 -0
- package/src/knowledge/resume/fab-model.json +22 -0
- package/src/knowledge/roles/ai-agent-engineer.json +52 -0
- package/src/knowledge/roles/android-engineer.json +45 -0
- package/src/knowledge/roles/architect.json +48 -0
- package/src/knowledge/roles/cpp-systems.json +47 -0
- package/src/knowledge/roles/frontend.json +49 -0
- package/src/knowledge/roles/ios-engineer.json +45 -0
- package/src/knowledge/roles/java-backend.json +49 -0
- package/src/knowledge/roles/node-fullstack.json +49 -0
- package/src/knowledge/roles/product-manager.json +48 -0
- package/src/knowledge/skills/skill-matrix.json +29 -0
- package/src/knowledge/styles.json +73 -0
- package/src/knowledge/templates.json +255 -0
- package/src/references/ats.md +17 -0
- package/src/references/evidence-first.md +22 -0
- package/src/references/pdf.md +40 -0
- package/src/references/resume-writing.md +25 -0
- package/src/references/template-design.md +57 -0
- package/src/templates/classic/README.md +12 -0
- package/src/templates/classic/metadata.json +34 -0
- package/src/templates/classic/style.css +164 -0
- package/src/templates/classic/template.html +133 -0
- package/src/templates/common/resume-contract.md +69 -0
- package/src/templates/executive/README.md +12 -0
- package/src/templates/executive/metadata.json +37 -0
- package/src/templates/executive/style.css +235 -0
- package/src/templates/executive/template.html +101 -0
- package/src/templates/minimal/README.md +12 -0
- package/src/templates/minimal/metadata.json +37 -0
- package/src/templates/minimal/style.css +219 -0
- package/src/templates/minimal/template.html +114 -0
- package/src/templates/modern/README.md +12 -0
- package/src/templates/modern/metadata.json +38 -0
- package/src/templates/modern/style.css +277 -0
- package/src/templates/modern/template.html +139 -0
- package/src/workflows/analyze-job.md +16 -0
- package/src/workflows/define-goal.md +16 -0
- package/src/workflows/forge-resume.md +25 -0
- package/src/workflows/know-me.md +21 -0
- package/src/workflows/position.md +19 -0
- package/src/workflows/review.md +31 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* KnowMe CareerForge — ATS Text Flow & Heading Hierarchy Inspector
|
|
5
|
+
* 验证 HTML 简历中的关键文本可提取性、标头层级规范与 ATS 机器解析友好度。
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.validateAts = validateAts;
|
|
42
|
+
const playwright_1 = require("playwright");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const STANDARD_SECTION_HEADERS = [
|
|
46
|
+
'工作经历', '工作经验', 'work experience', 'experience',
|
|
47
|
+
'项目经历', '核心项目', 'projects', 'key projects',
|
|
48
|
+
'专业技能', '核心技能', 'skills', 'technical skills',
|
|
49
|
+
'教育背景', '教育经历', 'education',
|
|
50
|
+
'核心领导力', '重大经历', '资质与证书', '求职意向'
|
|
51
|
+
];
|
|
52
|
+
async function validateAts(htmlPath) {
|
|
53
|
+
const absoluteHtml = path.resolve(htmlPath);
|
|
54
|
+
if (!fs.existsSync(absoluteHtml)) {
|
|
55
|
+
throw new Error(`HTML file not found: ${absoluteHtml}`);
|
|
56
|
+
}
|
|
57
|
+
const browser = await playwright_1.chromium.launch({ headless: true });
|
|
58
|
+
const page = await browser.newPage();
|
|
59
|
+
await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
|
|
60
|
+
const rawData = await page.evaluate(() => {
|
|
61
|
+
const nameEl = document.querySelector('.candidate-name, h1');
|
|
62
|
+
const name = nameEl ? (nameEl.textContent || '').trim() : null;
|
|
63
|
+
const fullText = (document.body.innerText || '').trim();
|
|
64
|
+
// 提取电话与邮箱
|
|
65
|
+
const phoneMatch = fullText.match(/(?:(?:\+|00)86)?1[3-9]\d{9}|(?:1[3-9]\d-\d{4}-\d{4})/);
|
|
66
|
+
const emailMatch = fullText.match(/[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/);
|
|
67
|
+
const headings = Array.from(document.querySelectorAll('h1, h2, h3, .section-title, .sub-title')).map(h => ({
|
|
68
|
+
level: h.tagName.toLowerCase(),
|
|
69
|
+
text: (h.textContent || '').trim()
|
|
70
|
+
}));
|
|
71
|
+
return {
|
|
72
|
+
candidateName: name,
|
|
73
|
+
phone: phoneMatch ? phoneMatch[0] : null,
|
|
74
|
+
email: emailMatch ? emailMatch[0] : null,
|
|
75
|
+
headings,
|
|
76
|
+
fullTextLength: fullText.length
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
await browser.close();
|
|
80
|
+
const warnings = [];
|
|
81
|
+
const errors = [];
|
|
82
|
+
if (!rawData.candidateName) {
|
|
83
|
+
errors.push('Candidate name (.candidate-name or h1) could not be extracted');
|
|
84
|
+
}
|
|
85
|
+
if (!rawData.email) {
|
|
86
|
+
warnings.push('No valid email address pattern detected in text flow');
|
|
87
|
+
}
|
|
88
|
+
if (!rawData.phone) {
|
|
89
|
+
warnings.push('No standard mobile phone pattern detected in text flow');
|
|
90
|
+
}
|
|
91
|
+
const validatedHeadings = rawData.headings.map(h => {
|
|
92
|
+
const textClean = h.text.toLowerCase().replace(/[^\w\u4e00-\u9fa5]/g, '');
|
|
93
|
+
const isStd = STANDARD_SECTION_HEADERS.some(s => textClean.includes(s.toLowerCase().replace(/\s+/g, '')));
|
|
94
|
+
return {
|
|
95
|
+
level: h.level,
|
|
96
|
+
text: h.text,
|
|
97
|
+
isStandard: isStd
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
const nonStdHeadings = validatedHeadings.filter(h => !h.isStandard && h.level === 'h2');
|
|
101
|
+
if (nonStdHeadings.length > 0) {
|
|
102
|
+
warnings.push(`Non-standard section headings detected: ${nonStdHeadings.map(h => `"${h.text}"`).join(', ')}`);
|
|
103
|
+
}
|
|
104
|
+
const status = errors.length > 0 ? 'FAIL' : warnings.length > 0 ? 'WARN' : 'PASS';
|
|
105
|
+
return {
|
|
106
|
+
status,
|
|
107
|
+
candidateName: rawData.candidateName,
|
|
108
|
+
detectedContacts: {
|
|
109
|
+
phone: rawData.phone,
|
|
110
|
+
email: rawData.email
|
|
111
|
+
},
|
|
112
|
+
headingsFound: validatedHeadings,
|
|
113
|
+
totalTextLength: rawData.fullTextLength,
|
|
114
|
+
warnings,
|
|
115
|
+
errors
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function main() {
|
|
119
|
+
const args = process.argv.slice(2);
|
|
120
|
+
let htmlPath = 'workspace/resume.html';
|
|
121
|
+
let jsonOutput = false;
|
|
122
|
+
for (let i = 0; i < args.length; i++) {
|
|
123
|
+
if (args[i] === '--html' || args[i] === '-i') {
|
|
124
|
+
htmlPath = args[i + 1];
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
else if (args[i] === '--json') {
|
|
128
|
+
jsonOutput = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const result = await validateAts(htmlPath);
|
|
133
|
+
if (jsonOutput) {
|
|
134
|
+
console.log(JSON.stringify(result, null, 2));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
console.log('======================================================');
|
|
138
|
+
console.log(` KnowMe CareerForge — ATS Compliance QA: [${result.status}]`);
|
|
139
|
+
console.log(` Candidate Name : ${result.candidateName || 'N/A'}`);
|
|
140
|
+
console.log(` Phone Extracted: ${result.detectedContacts.phone || 'None'}`);
|
|
141
|
+
console.log(` Email Extracted: ${result.detectedContacts.email || 'None'}`);
|
|
142
|
+
console.log(` Text Extracted : ${result.totalTextLength} characters`);
|
|
143
|
+
console.log('------------------------------------------------------');
|
|
144
|
+
console.log(' Headings Structure:');
|
|
145
|
+
for (const h of result.headingsFound) {
|
|
146
|
+
const tag = h.isStandard ? '✓' : '?';
|
|
147
|
+
console.log(` [${tag}] <${h.level}> ${h.text}`);
|
|
148
|
+
}
|
|
149
|
+
if (result.errors.length > 0) {
|
|
150
|
+
console.log('\n[!] Errors:');
|
|
151
|
+
for (const e of result.errors)
|
|
152
|
+
console.log(` - ✗ ${e}`);
|
|
153
|
+
}
|
|
154
|
+
if (result.warnings.length > 0) {
|
|
155
|
+
console.log('\n[?] Warnings:');
|
|
156
|
+
for (const w of result.warnings)
|
|
157
|
+
console.log(` - ⚠️ ${w}`);
|
|
158
|
+
}
|
|
159
|
+
console.log('======================================================');
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
163
|
+
console.error(`[✗] ATS Validation Failed: ${msg}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (require.main === module) {
|
|
168
|
+
main();
|
|
169
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
/**
|
|
3
|
+
* KnowMe CareerForge — Playwright DOM Layout & Overflow Inspector
|
|
4
|
+
* 基于无头浏览器真实渲染计算 DOM 盒模型高度,检测单页/双页物理 A4 溢出与孤行。
|
|
5
|
+
*/
|
|
6
|
+
interface LayoutValidationResult {
|
|
7
|
+
status: 'PASS' | 'OVERFLOW';
|
|
8
|
+
expectedPages: number;
|
|
9
|
+
totalPagesFound: number;
|
|
10
|
+
pageDetails: Array<{
|
|
11
|
+
pageIndex: number;
|
|
12
|
+
measuredHeightPx: number;
|
|
13
|
+
allowedHeightPx: number;
|
|
14
|
+
overflowPx: number;
|
|
15
|
+
isOverflow: boolean;
|
|
16
|
+
}>;
|
|
17
|
+
overflowSections: Array<{
|
|
18
|
+
selector: string;
|
|
19
|
+
heightPx: number;
|
|
20
|
+
textPreview: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export declare function validateLayout(htmlPath: string, expectedPages?: number): Promise<LayoutValidationResult>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* KnowMe CareerForge — Playwright DOM Layout & Overflow Inspector
|
|
5
|
+
* 基于无头浏览器真实渲染计算 DOM 盒模型高度,检测单页/双页物理 A4 溢出与孤行。
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.validateLayout = validateLayout;
|
|
42
|
+
const playwright_1 = require("playwright");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
async function validateLayout(htmlPath, expectedPages = 1) {
|
|
46
|
+
const absoluteHtml = path.resolve(htmlPath);
|
|
47
|
+
if (!fs.existsSync(absoluteHtml)) {
|
|
48
|
+
throw new Error(`HTML file not found: ${absoluteHtml}`);
|
|
49
|
+
}
|
|
50
|
+
const browser = await playwright_1.chromium.launch({ headless: true });
|
|
51
|
+
const context = await browser.newContext({
|
|
52
|
+
viewport: { width: 794, height: 1123 }, // A4 at 96 DPI
|
|
53
|
+
deviceScaleFactor: 2
|
|
54
|
+
});
|
|
55
|
+
const page = await context.newPage();
|
|
56
|
+
await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
|
|
57
|
+
await page.evaluateHandle('document.fonts.ready');
|
|
58
|
+
// A4 标准高度 (297mm in pixels at 96 DPI ~= 1122.5px)
|
|
59
|
+
const A4_HEIGHT_PX = 1122.5;
|
|
60
|
+
const evaluation = await page.evaluate((allowedHeight) => {
|
|
61
|
+
const pages = Array.from(document.querySelectorAll('.resume-page'));
|
|
62
|
+
const details = pages.map((el, index) => {
|
|
63
|
+
const rect = el.getBoundingClientRect();
|
|
64
|
+
const scrollH = el.scrollHeight;
|
|
65
|
+
const actualH = Math.max(rect.height, scrollH);
|
|
66
|
+
const diff = actualH - allowedHeight;
|
|
67
|
+
return {
|
|
68
|
+
pageIndex: index + 1,
|
|
69
|
+
measuredHeightPx: Math.round(actualH * 10) / 10,
|
|
70
|
+
allowedHeightPx: allowedHeight,
|
|
71
|
+
overflowPx: diff > 2 ? Math.round(diff * 10) / 10 : 0,
|
|
72
|
+
isOverflow: diff > 2
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
const overflows = [];
|
|
76
|
+
if (details.some(d => d.isOverflow)) {
|
|
77
|
+
const sections = Array.from(document.querySelectorAll('.resume-section, .timeline-item, .milestone-item, .experience-item, tr'));
|
|
78
|
+
for (const s of sections) {
|
|
79
|
+
const r = s.getBoundingClientRect();
|
|
80
|
+
if (r.bottom > allowedHeight) {
|
|
81
|
+
overflows.push({
|
|
82
|
+
selector: s.className || s.tagName.toLowerCase(),
|
|
83
|
+
heightPx: Math.round(r.height),
|
|
84
|
+
textPreview: (s.textContent || '').trim().slice(0, 60)
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
totalPagesFound: pages.length,
|
|
91
|
+
pageDetails: details,
|
|
92
|
+
overflowSections: overflows
|
|
93
|
+
};
|
|
94
|
+
}, A4_HEIGHT_PX);
|
|
95
|
+
await browser.close();
|
|
96
|
+
const hasOverflow = evaluation.pageDetails.some(p => p.isOverflow) || evaluation.totalPagesFound > expectedPages;
|
|
97
|
+
return {
|
|
98
|
+
status: hasOverflow ? 'OVERFLOW' : 'PASS',
|
|
99
|
+
expectedPages,
|
|
100
|
+
totalPagesFound: evaluation.totalPagesFound,
|
|
101
|
+
pageDetails: evaluation.pageDetails,
|
|
102
|
+
overflowSections: evaluation.overflowSections
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function main() {
|
|
106
|
+
const args = process.argv.slice(2);
|
|
107
|
+
let htmlPath = 'workspace/resume.html';
|
|
108
|
+
let expectedPages = 1;
|
|
109
|
+
let jsonOutput = false;
|
|
110
|
+
for (let i = 0; i < args.length; i++) {
|
|
111
|
+
if (args[i] === '--html' || args[i] === '-i') {
|
|
112
|
+
htmlPath = args[i + 1];
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
else if (args[i] === '--expected-pages' || args[i] === '-p') {
|
|
116
|
+
expectedPages = parseInt(args[i + 1], 10) || 1;
|
|
117
|
+
i++;
|
|
118
|
+
}
|
|
119
|
+
else if (args[i] === '--json') {
|
|
120
|
+
jsonOutput = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const result = await validateLayout(htmlPath, expectedPages);
|
|
125
|
+
if (jsonOutput) {
|
|
126
|
+
console.log(JSON.stringify(result, null, 2));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log('======================================================');
|
|
130
|
+
console.log(` KnowMe CareerForge — Layout QA: ${result.status}`);
|
|
131
|
+
console.log(` HTML Path : ${htmlPath}`);
|
|
132
|
+
console.log(` Expected Pages : ${result.expectedPages} | Pages Found: ${result.totalPagesFound}`);
|
|
133
|
+
console.log('------------------------------------------------------');
|
|
134
|
+
for (const p of result.pageDetails) {
|
|
135
|
+
const statusSymbol = p.isOverflow ? '✗ OVERFLOW' : '✓ FIT';
|
|
136
|
+
console.log(` Page ${p.pageIndex}: ${p.measuredHeightPx}px / ${p.allowedHeightPx}px [${statusSymbol}] (Overflow: ${p.overflowPx}px)`);
|
|
137
|
+
}
|
|
138
|
+
if (result.overflowSections.length > 0) {
|
|
139
|
+
console.log('\n[!] Nodes exceeding A4 physical boundary:');
|
|
140
|
+
for (const o of result.overflowSections) {
|
|
141
|
+
console.log(` - <${o.selector}> (${o.heightPx}px): "${o.textPreview}..."`);
|
|
142
|
+
}
|
|
143
|
+
console.log('\n[Self-Healing Guidance]:');
|
|
144
|
+
console.log(' 1. Reduce --resume-space-section by 1~2pt in workspace/resume.html <style>');
|
|
145
|
+
console.log(' 2. Reduce --resume-font-size-body by 0.2pt');
|
|
146
|
+
console.log(' 3. Condense verbose bullet points');
|
|
147
|
+
}
|
|
148
|
+
console.log('======================================================');
|
|
149
|
+
if (result.status === 'OVERFLOW') {
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
155
|
+
console.error(`[✗] Layout Validation Failed: ${msg}`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (require.main === module) {
|
|
160
|
+
main();
|
|
161
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "knowme-careerforge-skill",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "An agent-native skill for self-discovery, career positioning, and tailored resume engineering.",
|
|
5
|
+
"main": "dist/cli/src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"knowme": "bin/knowme.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"dist",
|
|
12
|
+
"src",
|
|
13
|
+
"scripts",
|
|
14
|
+
"agents",
|
|
15
|
+
"SKILL.md",
|
|
16
|
+
"skill.json"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"test": "python3 scripts/run-all-tests.py",
|
|
21
|
+
"search": "python3 scripts/search-template.py",
|
|
22
|
+
"analyze:jd": "python3 scripts/analyze-jd.py",
|
|
23
|
+
"instantiate": "python3 scripts/instantiate-resume.py",
|
|
24
|
+
"validate:layout": "ts-node scripts/validate-layout.ts",
|
|
25
|
+
"validate:ats": "ts-node scripts/validate-ats.ts",
|
|
26
|
+
"render:pdf": "ts-node scripts/render-pdf.ts",
|
|
27
|
+
"build:knowledge": "python3 scripts/build-knowledge.py",
|
|
28
|
+
"build:gallery": "python3 scripts/build-gallery.py",
|
|
29
|
+
"release": "python3 scripts/release.py",
|
|
30
|
+
"prepublishOnly": "npm run build && npm test"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"career-forge",
|
|
34
|
+
"know-me",
|
|
35
|
+
"resume-engineering",
|
|
36
|
+
"agent-skill",
|
|
37
|
+
"ai-resume",
|
|
38
|
+
"design-tokens",
|
|
39
|
+
"playwright-pdf",
|
|
40
|
+
"ats-friendly"
|
|
41
|
+
],
|
|
42
|
+
"author": "Miles <https://github.com/Max-Samson>",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"homepage": "https://github.com/Max-Samson/knowme-careerforge-skill",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/Max-Samson/knowme-careerforge-skill.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/Max-Samson/knowme-careerforge-skill/issues"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^22.0.0",
|
|
54
|
+
"playwright": "^1.49.0",
|
|
55
|
+
"ts-node": "^10.9.2",
|
|
56
|
+
"typescript": "^5.9.3"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KnowMe CareerForge — JD Analyzer & Hiring Signal Extractor
|
|
4
|
+
结构化解析招聘 JD 文本,提取岗位分类、职级、必备技能、加分项与核心业务关键词。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys, os, json, argparse, re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
COMMON_TECH_STACKS = [
|
|
11
|
+
"Python", "TypeScript", "JavaScript", "Java", "Go", "Golang", "C++", "Rust",
|
|
12
|
+
"React", "Vue", "Next.js", "Node.js", "FastAPI", "Spring Boot", "NestJS",
|
|
13
|
+
"LLM", "RAG", "LangGraph", "LangChain", "LlamaIndex", "VectorDB", "Qdrant",
|
|
14
|
+
"Milvus", "Pinecone", "Prompt Engineering", "Prompt", "Fine-tuning", "Evaluation",
|
|
15
|
+
"PostgreSQL", "MySQL", "Redis", "Kafka", "RocketMQ", "Elasticsearch",
|
|
16
|
+
"Docker", "Kubernetes", "K8s", "CI/CD", "AWS", "Aliyun", "Linux"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
def analyze_jd_text(jd_text: str) -> dict:
|
|
20
|
+
text_lower = jd_text.lower()
|
|
21
|
+
|
|
22
|
+
# 1. 识别岗位与职级
|
|
23
|
+
seniority = "mid"
|
|
24
|
+
if any(k in jd_text for k in ["资深", "专家", "架构师", "总监", "Lead", "Senior", "Principal"]):
|
|
25
|
+
seniority = "senior/lead"
|
|
26
|
+
elif any(k in jd_text for k in ["初级", "实习", "应届", "Junior"]):
|
|
27
|
+
seniority = "junior"
|
|
28
|
+
|
|
29
|
+
category = "engineering-ai"
|
|
30
|
+
if any(k in jd_text for k in ["产品经理", "Product Manager", "总监", "Director", "业务负责人"]):
|
|
31
|
+
category = "management-product"
|
|
32
|
+
|
|
33
|
+
# 2. 匹配技术栈与关键词
|
|
34
|
+
detected_skills = []
|
|
35
|
+
for skill in COMMON_TECH_STACKS:
|
|
36
|
+
pattern = re.compile(rf'(?:^|[^\w]){re.escape(skill)}(?:[^\w]|$)', re.IGNORECASE)
|
|
37
|
+
if pattern.search(jd_text):
|
|
38
|
+
detected_skills.append(skill)
|
|
39
|
+
|
|
40
|
+
# 3. 区分必备与加分
|
|
41
|
+
must_haves = detected_skills[:6] if len(detected_skills) >= 6 else detected_skills
|
|
42
|
+
nice_to_haves = detected_skills[6:] if len(detected_skills) > 6 else []
|
|
43
|
+
|
|
44
|
+
# 4. 业务痛点与职责关键词提取
|
|
45
|
+
responsibilities = []
|
|
46
|
+
for line in jd_text.splitlines():
|
|
47
|
+
line_clean = line.strip()
|
|
48
|
+
if len(line_clean) > 6 and any(verb in line_clean for verb in ["负责", "主导", "推进", "设计", "参与", "优化", "统筹", "熟练", "精通"]):
|
|
49
|
+
responsibilities.append(line_clean)
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
"status": "success",
|
|
53
|
+
"category": category,
|
|
54
|
+
"estimatedSeniority": seniority,
|
|
55
|
+
"mustHaveSkills": must_haves,
|
|
56
|
+
"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
|
+
]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def main():
|
|
66
|
+
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")
|
|
70
|
+
|
|
71
|
+
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")
|
|
76
|
+
elif args.text:
|
|
77
|
+
content = args.text
|
|
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)
|
|
93
|
+
|
|
94
|
+
if args.json:
|
|
95
|
+
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)
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
main()
|