knowme-careerforge-skill 0.0.1 → 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.
- package/README.md +6 -9
- package/README.zh-CN.md +6 -9
- package/SKILL.md +105 -116
- package/agents/claude/knowme-careerforge.md +11 -9
- package/agents/codex/knowme-careerforge.yaml +15 -6
- package/agents/cursor/knowme-careerforge.mdc +12 -7
- package/agents/opencode/skill.yaml +1 -1
- package/agents/windsurf/knowme-careerforge.rules +11 -8
- package/dist/cli/src/commands/init.d.ts +1 -1
- package/dist/cli/src/commands/init.js +46 -10
- package/dist/cli/src/index.js +82 -22
- package/dist/scripts/render-pdf.d.ts +0 -9
- package/dist/scripts/render-pdf.js +5 -68
- package/dist/scripts/rendering/render-pdf.d.ts +11 -0
- package/dist/scripts/rendering/render-pdf.js +109 -0
- package/dist/scripts/validate-ats.d.ts +0 -21
- package/dist/scripts/validate-ats.js +5 -132
- package/dist/scripts/validate-layout.d.ts +0 -22
- package/dist/scripts/validate-layout.js +5 -124
- package/dist/scripts/validation/validate-ats.d.ts +23 -0
- package/dist/scripts/validation/validate-ats.js +173 -0
- package/dist/scripts/validation/validate-layout.d.ts +24 -0
- package/dist/scripts/validation/validate-layout.js +164 -0
- package/package.json +16 -12
- package/scripts/Agent.md +138 -0
- package/scripts/{build-gallery.py → build/build-gallery.py} +128 -99
- package/scripts/{build-knowledge.py → build/build-knowledge.py} +9 -1
- package/scripts/{release.py → build/release.py} +20 -16
- package/scripts/{run-all-tests.py → build/run-all-tests.py} +9 -1
- package/scripts/build/sync-version.py +116 -0
- package/scripts/{analyze-jd.py → evidence/analyze-jd.py} +34 -45
- package/scripts/evidence/extract-evidence.py +348 -0
- package/scripts/pipeline/forge.py +142 -0
- package/scripts/rendering/render-pdf.py +162 -0
- package/scripts/{render-pdf.ts → rendering/render-pdf.ts} +3 -3
- package/scripts/template/instantiate-resume.py +184 -0
- package/scripts/template/search-template.py +350 -0
- package/scripts/{validate-ats.ts → validation/validate-ats.ts} +39 -35
- package/scripts/{validate-layout.ts → validation/validate-layout.ts} +43 -42
- package/scripts/{validate-resume.py → validation/validate-resume.py} +31 -22
- package/skill.json +1 -1
- package/src/knowledge/index.json +2 -10
- package/src/knowledge/resume-schema.json +112 -0
- package/src/knowledge/templates.json +1 -9
- package/src/references/01-evidence-mining.md +41 -0
- package/src/references/02-career-goal.md +38 -0
- package/src/references/03-jd-analysis.md +44 -0
- package/src/references/04-template-selection.md +43 -0
- package/src/references/05-html-canvas-tokens.md +51 -0
- package/src/references/06-qa-and-rendering.md +54 -0
- package/src/templates/classic/style.css +22 -51
- package/src/templates/common/base.css +150 -0
- package/src/templates/executive/style.css +30 -73
- package/src/templates/minimal/style.css +30 -72
- package/src/templates/modern/style.css +32 -72
- package/scripts/instantiate-resume.py +0 -83
- package/scripts/search-template.py +0 -147
- package/scripts/sync-version.py +0 -57
- package/src/references/ats.md +0 -17
- package/src/references/evidence-first.md +0 -22
- package/src/references/pdf.md +0 -40
- package/src/references/resume-writing.md +0 -25
- package/src/references/template-design.md +0 -57
- package/src/workflows/analyze-job.md +0 -16
- package/src/workflows/define-goal.md +0 -16
- package/src/workflows/forge-resume.md +0 -25
- package/src/workflows/know-me.md +0 -21
- package/src/workflows/position.md +0 -19
- package/src/workflows/review.md +0 -31
package/dist/cli/src/index.js
CHANGED
|
@@ -53,6 +53,17 @@ function findPackageRoot(startDir) {
|
|
|
53
53
|
}
|
|
54
54
|
return path.resolve(startDir, '../../..');
|
|
55
55
|
}
|
|
56
|
+
function getVersion(rootDir) {
|
|
57
|
+
try {
|
|
58
|
+
const pkgJson = path.join(rootDir, 'package.json');
|
|
59
|
+
if (require('fs').existsSync(pkgJson)) {
|
|
60
|
+
const data = JSON.parse(require('fs').readFileSync(pkgJson, 'utf-8'));
|
|
61
|
+
return data.version || '0.0.4';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
return '0.0.4';
|
|
66
|
+
}
|
|
56
67
|
function showHelp() {
|
|
57
68
|
console.log(`
|
|
58
69
|
======================================================================
|
|
@@ -63,26 +74,32 @@ Usage:
|
|
|
63
74
|
knowme <command> [options]
|
|
64
75
|
|
|
65
76
|
Commands:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
forge One-shot resume engineering pipeline (repo mining -> canvas -> QA -> PDF)
|
|
78
|
+
extract Extract candidate evidence & facts from codebase/Git repo
|
|
79
|
+
render Multi-strategy deterministic A4 PDF export
|
|
80
|
+
init Install and configure Skill for AI Agent platforms (cursor, claude, codex, windsurf, gemini, opencode)
|
|
81
|
+
list Display all available role profiles and HTML templates
|
|
82
|
+
search <role> Search and rank templates for a specific role (e.g. knowme search "AI Agent Engineer")
|
|
83
|
+
validate Validate working canvas HTML layout & ATS compliance
|
|
84
|
+
gallery Build & refresh static HTML template gallery
|
|
85
|
+
test Run full automated test suite
|
|
86
|
+
help Show this help message
|
|
73
87
|
|
|
74
|
-
Options (for
|
|
75
|
-
--
|
|
76
|
-
--
|
|
77
|
-
--
|
|
88
|
+
Options (for forge):
|
|
89
|
+
--repo, -r <dir> Target repository path (default: .)
|
|
90
|
+
--role <title> Target role title
|
|
91
|
+
--jd <file|text> Target job description
|
|
92
|
+
--template, -t <id> Template ID (minimal, modern, executive, classic)
|
|
93
|
+
--name <name> Candidate name override
|
|
94
|
+
--output, -o <path> Output PDF path (default: workspace/resume.pdf)
|
|
95
|
+
--quiet, -q Quiet execution (compact JSON output)
|
|
78
96
|
|
|
79
97
|
Examples:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
npx knowme-careerforge validate
|
|
98
|
+
knowme forge --repo . --role "AI Agent Engineer" --template modern
|
|
99
|
+
knowme extract --repo . --output workspace/evidence-master.json
|
|
100
|
+
knowme render workspace/resume.html workspace/resume.pdf
|
|
101
|
+
knowme init --ai codex
|
|
102
|
+
knowme init --all
|
|
86
103
|
`);
|
|
87
104
|
}
|
|
88
105
|
function main() {
|
|
@@ -90,6 +107,36 @@ function main() {
|
|
|
90
107
|
const command = args[0] || 'help';
|
|
91
108
|
const rootDir = findPackageRoot(__dirname);
|
|
92
109
|
switch (command) {
|
|
110
|
+
case 'forge': {
|
|
111
|
+
let scriptPath = path.join(rootDir, 'scripts', 'pipeline', 'forge.py');
|
|
112
|
+
if (!require('fs').existsSync(scriptPath))
|
|
113
|
+
scriptPath = path.join(rootDir, 'scripts', 'forge.py');
|
|
114
|
+
const passArgs = args.slice(1);
|
|
115
|
+
const proc = child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
116
|
+
if (proc.status !== 0)
|
|
117
|
+
process.exit(proc.status || 1);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case 'extract': {
|
|
121
|
+
let scriptPath = path.join(rootDir, 'scripts', 'evidence', 'extract-evidence.py');
|
|
122
|
+
if (!require('fs').existsSync(scriptPath))
|
|
123
|
+
scriptPath = path.join(rootDir, 'scripts', 'extract-evidence.py');
|
|
124
|
+
const passArgs = args.slice(1);
|
|
125
|
+
const proc = child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
126
|
+
if (proc.status !== 0)
|
|
127
|
+
process.exit(proc.status || 1);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case 'render': {
|
|
131
|
+
let scriptPath = path.join(rootDir, 'scripts', 'rendering', 'render-pdf.py');
|
|
132
|
+
if (!require('fs').existsSync(scriptPath))
|
|
133
|
+
scriptPath = path.join(rootDir, 'scripts', 'render-pdf.py');
|
|
134
|
+
const passArgs = args.slice(1);
|
|
135
|
+
const proc = child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
136
|
+
if (proc.status !== 0)
|
|
137
|
+
process.exit(proc.status || 1);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
93
140
|
case 'init': {
|
|
94
141
|
let platform = 'cursor';
|
|
95
142
|
let all = false;
|
|
@@ -114,28 +161,41 @@ function main() {
|
|
|
114
161
|
(0, list_1.runList)();
|
|
115
162
|
break;
|
|
116
163
|
case 'search': {
|
|
117
|
-
|
|
118
|
-
|
|
164
|
+
let scriptPath = path.join(rootDir, 'scripts', 'template', 'search-template.py');
|
|
165
|
+
if (!require('fs').existsSync(scriptPath))
|
|
166
|
+
scriptPath = path.join(rootDir, 'scripts', 'search-template.py');
|
|
119
167
|
const passArgs = args.slice(1);
|
|
120
168
|
child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
121
169
|
break;
|
|
122
170
|
}
|
|
123
171
|
case 'validate': {
|
|
124
|
-
|
|
172
|
+
let scriptPath = path.join(rootDir, 'scripts', 'validation', 'validate-resume.py');
|
|
173
|
+
if (!require('fs').existsSync(scriptPath))
|
|
174
|
+
scriptPath = path.join(rootDir, 'scripts', 'validate-resume.py');
|
|
125
175
|
const passArgs = args.slice(1);
|
|
126
176
|
child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
127
177
|
break;
|
|
128
178
|
}
|
|
129
179
|
case 'gallery': {
|
|
130
|
-
|
|
180
|
+
let scriptPath = path.join(rootDir, 'scripts', 'build', 'build-gallery.py');
|
|
181
|
+
if (!require('fs').existsSync(scriptPath))
|
|
182
|
+
scriptPath = path.join(rootDir, 'scripts', 'build-gallery.py');
|
|
131
183
|
child_process.spawnSync('python3', [scriptPath], { stdio: 'inherit' });
|
|
132
184
|
break;
|
|
133
185
|
}
|
|
134
186
|
case 'test': {
|
|
135
|
-
|
|
187
|
+
let scriptPath = path.join(rootDir, 'scripts', 'build', 'run-all-tests.py');
|
|
188
|
+
if (!require('fs').existsSync(scriptPath))
|
|
189
|
+
scriptPath = path.join(rootDir, 'scripts', 'run-all-tests.py');
|
|
136
190
|
child_process.spawnSync('python3', [scriptPath], { stdio: 'inherit' });
|
|
137
191
|
break;
|
|
138
192
|
}
|
|
193
|
+
case '--version':
|
|
194
|
+
case '-v':
|
|
195
|
+
case 'version': {
|
|
196
|
+
console.log(getVersion(rootDir));
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
139
199
|
case 'help':
|
|
140
200
|
case '--help':
|
|
141
201
|
case '-h':
|
|
@@ -1,11 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
/**
|
|
3
|
-
* KnowMe CareerForge — Playwright Deterministic PDF Renderer
|
|
4
|
-
* 加载自包含 HTML 中间工作区,等待字体及样式准备就绪,输出标准像素级 A4 PDF。
|
|
5
|
-
*/
|
|
6
|
-
interface RenderOptions {
|
|
7
|
-
inputHtml: string;
|
|
8
|
-
outputPdf: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function renderResumePdf(options: RenderOptions): Promise<void>;
|
|
11
2
|
export {};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
2
|
"use strict";
|
|
3
|
-
/**
|
|
4
|
-
* KnowMe CareerForge — Playwright Deterministic PDF Renderer
|
|
5
|
-
* 加载自包含 HTML 中间工作区,等待字体及样式准备就绪,输出标准像素级 A4 PDF。
|
|
6
|
-
*/
|
|
7
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
4
|
if (k2 === undefined) k2 = k;
|
|
9
5
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -38,68 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
34
|
};
|
|
39
35
|
})();
|
|
40
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
|
|
42
|
-
const playwright_1 = require("playwright");
|
|
37
|
+
/** Forwarding shim for scripts/rendering/render-pdf.ts */
|
|
43
38
|
const path = __importStar(require("path"));
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!fs.existsSync(absoluteHtml)) {
|
|
49
|
-
throw new Error(`Input HTML file does not exist: ${absoluteHtml}`);
|
|
50
|
-
}
|
|
51
|
-
// 确保输出目录存在
|
|
52
|
-
const outputDir = path.dirname(absolutePdf);
|
|
53
|
-
if (!fs.existsSync(outputDir)) {
|
|
54
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
55
|
-
}
|
|
56
|
-
console.log('======================================================');
|
|
57
|
-
console.log(' KnowMe CareerForge — Playwright PDF Renderer');
|
|
58
|
-
console.log(` Source Canvas : ${absoluteHtml}`);
|
|
59
|
-
console.log(` Target PDF : ${absolutePdf}`);
|
|
60
|
-
console.log('======================================================');
|
|
61
|
-
const browser = await playwright_1.chromium.launch({ headless: true });
|
|
62
|
-
const context = await browser.newContext({
|
|
63
|
-
viewport: { width: 794, height: 1123 }, // A4 at 96 DPI
|
|
64
|
-
deviceScaleFactor: 2
|
|
65
|
-
});
|
|
66
|
-
const page = await context.newPage();
|
|
67
|
-
await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
|
|
68
|
-
// 确保所有 Web 字体准备就绪
|
|
69
|
-
await page.evaluateHandle('document.fonts.ready');
|
|
70
|
-
await page.pdf({
|
|
71
|
-
path: absolutePdf,
|
|
72
|
-
format: 'A4',
|
|
73
|
-
printBackground: true,
|
|
74
|
-
preferCSSPageSize: true,
|
|
75
|
-
margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' }
|
|
76
|
-
});
|
|
77
|
-
await browser.close();
|
|
78
|
-
console.log(`[✓] Deterministic PDF Rendered Successfully -> ${absolutePdf}`);
|
|
79
|
-
}
|
|
80
|
-
async function main() {
|
|
81
|
-
const args = process.argv.slice(2);
|
|
82
|
-
let inputHtml = 'workspace/resume.html';
|
|
83
|
-
let outputPdf = 'workspace/resume.pdf';
|
|
84
|
-
for (let i = 0; i < args.length; i++) {
|
|
85
|
-
if (args[i] === '--input' || args[i] === '-i') {
|
|
86
|
-
inputHtml = args[i + 1];
|
|
87
|
-
i++;
|
|
88
|
-
}
|
|
89
|
-
else if (args[i] === '--output' || args[i] === '-o') {
|
|
90
|
-
outputPdf = args[i + 1];
|
|
91
|
-
i++;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
await renderResumePdf({ inputHtml, outputPdf });
|
|
96
|
-
}
|
|
97
|
-
catch (err) {
|
|
98
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
99
|
-
console.error(`[✗] PDF Rendering Failed: ${errorMsg}`);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (require.main === module) {
|
|
104
|
-
main();
|
|
105
|
-
}
|
|
39
|
+
const child_process = __importStar(require("child_process"));
|
|
40
|
+
const target = path.join(__dirname, 'rendering', 'render-pdf.ts');
|
|
41
|
+
const proc = child_process.spawnSync('ts-node', [target, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
42
|
+
process.exit(proc.status || 0);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
/**
|
|
3
|
+
* KnowMe CareerForge — Playwright Deterministic PDF Renderer
|
|
4
|
+
* 加载自包含 HTML 中间工作区,等待字体及样式准备就绪,输出标准像素级 A4 PDF。
|
|
5
|
+
*/
|
|
6
|
+
interface RenderOptions {
|
|
7
|
+
inputHtml: string;
|
|
8
|
+
outputPdf: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function renderResumePdf(options: RenderOptions): Promise<void>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* KnowMe CareerForge — Playwright Deterministic PDF Renderer
|
|
5
|
+
* 加载自包含 HTML 中间工作区,等待字体及样式准备就绪,输出标准像素级 A4 PDF。
|
|
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.renderResumePdf = renderResumePdf;
|
|
42
|
+
const playwright_1 = require("playwright");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
async function renderResumePdf(options) {
|
|
46
|
+
const absoluteHtml = path.resolve(options.inputHtml);
|
|
47
|
+
const absolutePdf = path.resolve(options.outputPdf);
|
|
48
|
+
if (!fs.existsSync(absoluteHtml)) {
|
|
49
|
+
throw new Error(`Input HTML file does not exist: ${absoluteHtml}`);
|
|
50
|
+
}
|
|
51
|
+
const outputDir = path.dirname(absolutePdf);
|
|
52
|
+
if (!fs.existsSync(outputDir)) {
|
|
53
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
console.log('======================================================');
|
|
56
|
+
console.log(' KnowMe CareerForge — Playwright PDF Renderer');
|
|
57
|
+
console.log(` Source Canvas : ${absoluteHtml}`);
|
|
58
|
+
console.log(` Target PDF : ${absolutePdf}`);
|
|
59
|
+
console.log('======================================================');
|
|
60
|
+
const browser = await playwright_1.chromium.launch({ headless: true });
|
|
61
|
+
const context = await browser.newContext({
|
|
62
|
+
viewport: { width: 794, height: 1123 }, // A4 at 96 DPI
|
|
63
|
+
deviceScaleFactor: 2
|
|
64
|
+
});
|
|
65
|
+
const page = await context.newPage();
|
|
66
|
+
await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
|
|
67
|
+
await page.evaluateHandle('document.fonts.ready');
|
|
68
|
+
await page.pdf({
|
|
69
|
+
path: absolutePdf,
|
|
70
|
+
format: 'A4',
|
|
71
|
+
printBackground: true,
|
|
72
|
+
preferCSSPageSize: true,
|
|
73
|
+
margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' }
|
|
74
|
+
});
|
|
75
|
+
await browser.close();
|
|
76
|
+
console.log(`[✓] Deterministic PDF Rendered Successfully -> ${absolutePdf}`);
|
|
77
|
+
}
|
|
78
|
+
async function main() {
|
|
79
|
+
const args = process.argv.slice(2);
|
|
80
|
+
let inputHtml = 'workspace/resume.html';
|
|
81
|
+
let outputPdf = 'workspace/resume.pdf';
|
|
82
|
+
for (let i = 0; i < args.length; i++) {
|
|
83
|
+
if (args[i] === '--input' || args[i] === '-i') {
|
|
84
|
+
inputHtml = args[i + 1];
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
else if (args[i] === '--output' || args[i] === '-o') {
|
|
88
|
+
outputPdf = args[i + 1];
|
|
89
|
+
i++;
|
|
90
|
+
}
|
|
91
|
+
else if (!args[i].startsWith('-')) {
|
|
92
|
+
if (i === 0)
|
|
93
|
+
inputHtml = args[i];
|
|
94
|
+
else if (i === 1)
|
|
95
|
+
outputPdf = args[i];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await renderResumePdf({ inputHtml, outputPdf });
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
103
|
+
console.error(`[✗] PDF Rendering Failed: ${errorMsg}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (require.main === module) {
|
|
108
|
+
main();
|
|
109
|
+
}
|
|
@@ -1,23 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
/**
|
|
3
|
-
* KnowMe CareerForge — ATS Text Flow & Heading Hierarchy Inspector
|
|
4
|
-
* 验证 HTML 简历中的关键文本可提取性、标头层级规范与 ATS 机器解析友好度。
|
|
5
|
-
*/
|
|
6
|
-
interface AtsValidationResult {
|
|
7
|
-
status: 'PASS' | 'WARN' | 'FAIL';
|
|
8
|
-
candidateName: string | null;
|
|
9
|
-
detectedContacts: {
|
|
10
|
-
phone: string | null;
|
|
11
|
-
email: string | null;
|
|
12
|
-
};
|
|
13
|
-
headingsFound: Array<{
|
|
14
|
-
level: string;
|
|
15
|
-
text: string;
|
|
16
|
-
isStandard: boolean;
|
|
17
|
-
}>;
|
|
18
|
-
totalTextLength: number;
|
|
19
|
-
warnings: string[];
|
|
20
|
-
errors: string[];
|
|
21
|
-
}
|
|
22
|
-
export declare function validateAts(htmlPath: string): Promise<AtsValidationResult>;
|
|
23
2
|
export {};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
2
|
"use strict";
|
|
3
|
-
/**
|
|
4
|
-
* KnowMe CareerForge — ATS Text Flow & Heading Hierarchy Inspector
|
|
5
|
-
* 验证 HTML 简历中的关键文本可提取性、标头层级规范与 ATS 机器解析友好度。
|
|
6
|
-
*/
|
|
7
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
4
|
if (k2 === undefined) k2 = k;
|
|
9
5
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -38,132 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
34
|
};
|
|
39
35
|
})();
|
|
40
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
-
|
|
42
|
-
const playwright_1 = require("playwright");
|
|
37
|
+
/** Forwarding shim for scripts/validation/validate-ats.ts */
|
|
43
38
|
const path = __importStar(require("path"));
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
}
|
|
39
|
+
const child_process = __importStar(require("child_process"));
|
|
40
|
+
const target = path.join(__dirname, 'validation', 'validate-ats.ts');
|
|
41
|
+
const proc = child_process.spawnSync('ts-node', [target, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
42
|
+
process.exit(proc.status || 0);
|
|
@@ -1,24 +1,2 @@
|
|
|
1
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
2
|
export {};
|