knowme-careerforge-skill 0.0.2 → 0.0.6
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/AGENT.md +276 -0
- package/ARCHITECTURE.md +216 -0
- package/README.md +34 -54
- package/README.zh-CN.md +35 -57
- package/SKILL.md +28 -137
- package/agents/claude/knowme-careerforge.md +2 -16
- package/agents/codex/knowme-careerforge.yaml +5 -7
- package/agents/cursor/knowme-careerforge.mdc +4 -9
- package/agents/opencode/skill.yaml +1 -1
- package/agents/windsurf/knowme-careerforge.rules +2 -12
- package/dist/cli/src/commands/init.js +9 -0
- package/dist/cli/src/index.js +40 -19
- package/dist/scripts/rendering/engine-loader.d.ts +2 -0
- package/dist/scripts/rendering/engine-loader.js +46 -0
- package/dist/scripts/rendering/render-pdf.d.ts +3 -8
- package/dist/scripts/rendering/render-pdf.js +7 -103
- package/dist/scripts/rendering/runtime.d.ts +9 -0
- package/dist/scripts/rendering/runtime.js +63 -0
- package/dist/scripts/validation/validate-ats.d.ts +2 -22
- package/dist/scripts/validation/validate-ats.js +6 -168
- package/dist/scripts/validation/validate-layout.d.ts +2 -23
- package/dist/scripts/validation/validate-layout.js +5 -159
- package/package.json +16 -4
- package/references/01-evidence-mining.md +5 -0
- package/references/02-career-goal.md +38 -0
- package/references/03-jd-analysis.md +44 -0
- package/references/04-template-selection.md +43 -0
- package/references/05-html-canvas-tokens.md +12 -0
- package/references/06-qa-and-rendering.md +14 -0
- package/references/07-artifact-contract.md +64 -0
- package/scripts/Agent.md +9 -136
- package/scripts/build/build-gallery.py +66 -56
- package/scripts/build/run-all-tests.py +1 -0
- package/scripts/build/sync-version.py +37 -10
- package/scripts/contracts/profile.py +101 -0
- package/scripts/evidence/extract-evidence.py +109 -62
- package/scripts/pipeline/forge.py +266 -136
- package/scripts/rendering/README.md +65 -0
- package/scripts/rendering/browser-engine.d.ts +15 -0
- package/scripts/rendering/browser-engine.js +366 -0
- package/scripts/rendering/browser_engine.py +39 -0
- package/scripts/rendering/engine-loader.ts +11 -0
- package/scripts/rendering/render-pdf.py +26 -151
- package/scripts/rendering/render-pdf.ts +7 -83
- package/scripts/template/instantiate-resume.py +266 -167
- package/scripts/template/search-template.py +367 -89
- package/scripts/validation/measure-dom.js +5 -0
- package/scripts/validation/validate-ats.ts +6 -174
- package/scripts/validation/validate-layout.ts +5 -159
- package/scripts/validation/validate-resume.py +23 -134
- package/skill.json +1 -1
- package/src/knowledge/index.json +390 -10
- package/src/knowledge/resume-schema.json +377 -71
- package/src/knowledge/templates.json +388 -8
- package/src/references/01-evidence-mining.md +5 -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 +12 -0
- package/src/references/06-qa-and-rendering.md +14 -0
- package/src/references/07-artifact-contract.md +64 -0
- package/src/templates/Agent.md +11 -0
- package/src/templates/academic-research/README.md +25 -0
- package/src/templates/academic-research/canvas.html +21 -0
- package/src/templates/academic-research/metadata.json +38 -0
- package/src/templates/academic-research/sample-profile.json +86 -0
- package/src/templates/academic-research/style.css +213 -0
- package/src/templates/classic/README.md +9 -0
- package/src/templates/classic/canvas.html +21 -0
- package/src/templates/classic/metadata.json +3 -0
- package/src/templates/classic/sample-profile.json +86 -0
- package/src/templates/classic/style.css +35 -54
- package/src/templates/common/base.css +182 -0
- package/src/templates/common/canvas-bindings.css +41 -0
- package/src/templates/common/resume-contract.md +8 -0
- package/src/templates/compact-dense/README.md +25 -0
- package/src/templates/compact-dense/canvas.html +14 -0
- package/src/templates/compact-dense/metadata.json +38 -0
- package/src/templates/compact-dense/sample-profile.json +86 -0
- package/src/templates/compact-dense/style.css +218 -0
- package/src/templates/creative-tech/README.md +26 -0
- package/src/templates/creative-tech/canvas.html +15 -0
- package/src/templates/creative-tech/metadata.json +39 -0
- package/src/templates/creative-tech/sample-profile.json +86 -0
- package/src/templates/creative-tech/style.css +257 -0
- package/src/templates/data-analyst/README.md +26 -0
- package/src/templates/data-analyst/canvas.html +15 -0
- package/src/templates/data-analyst/metadata.json +39 -0
- package/src/templates/data-analyst/sample-profile.json +86 -0
- package/src/templates/data-analyst/style.css +285 -0
- package/src/templates/executive/README.md +9 -0
- package/src/templates/executive/canvas.html +15 -0
- package/src/templates/executive/sample-profile.json +86 -0
- package/src/templates/executive/style.css +83 -74
- package/src/templates/international-flow/README.md +26 -0
- package/src/templates/international-flow/canvas.html +21 -0
- package/src/templates/international-flow/metadata.json +37 -0
- package/src/templates/international-flow/sample-profile.json +86 -0
- package/src/templates/international-flow/style.css +208 -0
- package/src/templates/minimal/README.md +9 -0
- package/src/templates/minimal/canvas.html +21 -0
- package/src/templates/minimal/sample-profile.json +86 -0
- package/src/templates/minimal/style.css +54 -72
- package/src/templates/modern/README.md +9 -0
- package/src/templates/modern/canvas.html +15 -0
- package/src/templates/modern/sample-profile.json +86 -0
- package/src/templates/modern/style.css +78 -71
- package/src/templates/startup-generalist/README.md +25 -0
- package/src/templates/startup-generalist/canvas.html +21 -0
- package/src/templates/startup-generalist/metadata.json +37 -0
- package/src/templates/startup-generalist/sample-profile.json +86 -0
- package/src/templates/startup-generalist/style.css +237 -0
- 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/templates/classic/template.html +0 -133
- package/src/templates/executive/template.html +0 -101
- package/src/templates/minimal/template.html +0 -114
- package/src/templates/modern/template.html +0 -139
- 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.6';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
return '0.0.6';
|
|
66
|
+
}
|
|
56
67
|
function showHelp() {
|
|
57
68
|
console.log(`
|
|
58
69
|
======================================================================
|
|
@@ -63,8 +74,7 @@ Usage:
|
|
|
63
74
|
knowme <command> [options]
|
|
64
75
|
|
|
65
76
|
Commands:
|
|
66
|
-
forge
|
|
67
|
-
extract Extract candidate evidence & facts from codebase/Git repo
|
|
77
|
+
forge Isolated user facts -> variant -> canvas -> verified PDF
|
|
68
78
|
render Multi-strategy deterministic A4 PDF export
|
|
69
79
|
init Install and configure Skill for AI Agent platforms (cursor, claude, codex, windsurf, gemini, opencode)
|
|
70
80
|
list Display all available role profiles and HTML templates
|
|
@@ -75,17 +85,19 @@ Commands:
|
|
|
75
85
|
help Show this help message
|
|
76
86
|
|
|
77
87
|
Options (for forge):
|
|
78
|
-
--
|
|
88
|
+
--profile-json <file> User/Agent prepared facts or Master document
|
|
89
|
+
--workspace <dir> Parent for isolated run directories (default: workspace/runs)
|
|
90
|
+
--draft Create a draft canvas without PDF delivery
|
|
79
91
|
--role <title> Target role title
|
|
80
92
|
--jd <file|text> Target job description
|
|
81
|
-
--template, -t <id> Template ID (minimal, modern, executive, classic)
|
|
93
|
+
--template, -t <id> Template ID (minimal, modern, executive, classic, academic-research, international-flow, creative-tech, compact-dense, startup-generalist, data-analyst)
|
|
82
94
|
--name <name> Candidate name override
|
|
83
|
-
--output, -o <path>
|
|
95
|
+
--output, -o <path> Optional verified PDF copy (default: isolated run)
|
|
96
|
+
--font-preset <name> system or arial-unicode (requires local Arial Unicode MS)
|
|
97
|
+
--auto-heal Enable heuristic token auto-healing to eliminate A4 height overflow
|
|
84
98
|
--quiet, -q Quiet execution (compact JSON output)
|
|
85
|
-
|
|
86
99
|
Examples:
|
|
87
|
-
knowme forge --
|
|
88
|
-
knowme extract --repo . --output workspace/evidence-master.json
|
|
100
|
+
knowme forge --profile-json candidate.json --role "AI Agent Engineer" --template modern
|
|
89
101
|
knowme render workspace/resume.html workspace/resume.pdf
|
|
90
102
|
knowme init --ai codex
|
|
91
103
|
knowme init --all
|
|
@@ -107,13 +119,8 @@ function main() {
|
|
|
107
119
|
break;
|
|
108
120
|
}
|
|
109
121
|
case 'extract': {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
scriptPath = path.join(rootDir, 'scripts', 'extract-evidence.py');
|
|
113
|
-
const passArgs = args.slice(1);
|
|
114
|
-
const proc = child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
115
|
-
if (proc.status !== 0)
|
|
116
|
-
process.exit(proc.status || 1);
|
|
122
|
+
console.error('Repository-to-resume extraction is not supported. Use user-provided facts with forge --profile-json.');
|
|
123
|
+
process.exitCode = 1;
|
|
117
124
|
break;
|
|
118
125
|
}
|
|
119
126
|
case 'render': {
|
|
@@ -154,7 +161,9 @@ function main() {
|
|
|
154
161
|
if (!require('fs').existsSync(scriptPath))
|
|
155
162
|
scriptPath = path.join(rootDir, 'scripts', 'search-template.py');
|
|
156
163
|
const passArgs = args.slice(1);
|
|
157
|
-
child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
164
|
+
const proc = child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
165
|
+
if (proc.status !== 0)
|
|
166
|
+
process.exit(proc.status || 1);
|
|
158
167
|
break;
|
|
159
168
|
}
|
|
160
169
|
case 'validate': {
|
|
@@ -162,21 +171,33 @@ function main() {
|
|
|
162
171
|
if (!require('fs').existsSync(scriptPath))
|
|
163
172
|
scriptPath = path.join(rootDir, 'scripts', 'validate-resume.py');
|
|
164
173
|
const passArgs = args.slice(1);
|
|
165
|
-
child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
174
|
+
const proc = child_process.spawnSync('python3', [scriptPath, ...passArgs], { stdio: 'inherit' });
|
|
175
|
+
if (proc.status !== 0)
|
|
176
|
+
process.exit(proc.status || 1);
|
|
166
177
|
break;
|
|
167
178
|
}
|
|
168
179
|
case 'gallery': {
|
|
169
180
|
let scriptPath = path.join(rootDir, 'scripts', 'build', 'build-gallery.py');
|
|
170
181
|
if (!require('fs').existsSync(scriptPath))
|
|
171
182
|
scriptPath = path.join(rootDir, 'scripts', 'build-gallery.py');
|
|
172
|
-
child_process.spawnSync('python3', [scriptPath], { stdio: 'inherit' });
|
|
183
|
+
const proc = child_process.spawnSync('python3', [scriptPath], { stdio: 'inherit' });
|
|
184
|
+
if (proc.status !== 0)
|
|
185
|
+
process.exit(proc.status || 1);
|
|
173
186
|
break;
|
|
174
187
|
}
|
|
175
188
|
case 'test': {
|
|
176
189
|
let scriptPath = path.join(rootDir, 'scripts', 'build', 'run-all-tests.py');
|
|
177
190
|
if (!require('fs').existsSync(scriptPath))
|
|
178
191
|
scriptPath = path.join(rootDir, 'scripts', 'run-all-tests.py');
|
|
179
|
-
child_process.spawnSync('python3', [scriptPath], { stdio: 'inherit' });
|
|
192
|
+
const proc = child_process.spawnSync('python3', [scriptPath], { stdio: 'inherit' });
|
|
193
|
+
if (proc.status !== 0)
|
|
194
|
+
process.exit(proc.status || 1);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case '--version':
|
|
198
|
+
case '-v':
|
|
199
|
+
case 'version': {
|
|
200
|
+
console.log(getVersion(rootDir));
|
|
180
201
|
break;
|
|
181
202
|
}
|
|
182
203
|
case 'help':
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
const fs = __importStar(require("fs"));
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
// tsc does not copy JS. Resolve the shipped source engine from source or dist.
|
|
38
|
+
let root = __dirname;
|
|
39
|
+
while (!fs.existsSync(path.join(root, 'package.json'))) {
|
|
40
|
+
const parent = path.dirname(root);
|
|
41
|
+
if (parent === root)
|
|
42
|
+
throw new Error('Cannot locate browser engine project root');
|
|
43
|
+
root = parent;
|
|
44
|
+
}
|
|
45
|
+
const engine = require(path.join(root, 'scripts/rendering/browser-engine.js'));
|
|
46
|
+
module.exports = engine;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 加载自包含 HTML 中间工作区,等待字体及样式准备就绪,输出标准像素级 A4 PDF。
|
|
5
|
-
*/
|
|
6
|
-
interface RenderOptions {
|
|
2
|
+
import type { EngineOptions, EngineResult } from './browser-engine';
|
|
3
|
+
export declare function renderResumePdf(options: EngineOptions & {
|
|
7
4
|
inputHtml: string;
|
|
8
5
|
outputPdf: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function renderResumePdf(options: RenderOptions): Promise<void>;
|
|
11
|
-
export {};
|
|
6
|
+
}): Promise<EngineResult>;
|
|
@@ -1,109 +1,13 @@
|
|
|
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
|
-
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
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
4
|
exports.renderResumePdf = renderResumePdf;
|
|
42
|
-
const
|
|
43
|
-
const path = __importStar(require("path"));
|
|
44
|
-
const fs = __importStar(require("fs"));
|
|
5
|
+
const engine = require("./engine-loader");
|
|
45
6
|
async function renderResumePdf(options) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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();
|
|
7
|
+
const result = await engine.run(options.inputHtml, options);
|
|
8
|
+
if (result.status !== 'PASS')
|
|
9
|
+
throw new Error(JSON.stringify(result));
|
|
10
|
+
return result;
|
|
109
11
|
}
|
|
12
|
+
if (require.main === module)
|
|
13
|
+
engine.cli(process.argv.slice(2), { outputPdf: 'workspace/resume.pdf' });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface PrintInspection {
|
|
2
|
+
status: 'PASS' | 'FAIL' | 'UNVERIFIED';
|
|
3
|
+
errors: string[];
|
|
4
|
+
warnings?: string[];
|
|
5
|
+
pdfPages?: number;
|
|
6
|
+
actualHeightPx?: number;
|
|
7
|
+
measurementSource?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function inspectPrint(htmlPath: string, expectedPages?: number, outputPdf?: string): PrintInspection;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.inspectPrint = inspectPrint;
|
|
37
|
+
/** Typed bridge used by legacy TypeScript entry points and their compiled output. */
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
function inspectPrint(htmlPath, expectedPages = 1, outputPdf) {
|
|
42
|
+
let root = __dirname;
|
|
43
|
+
while (!fs.existsSync(path.join(root, 'SKILL.md'))) {
|
|
44
|
+
const parent = path.dirname(root);
|
|
45
|
+
if (parent === root)
|
|
46
|
+
throw new Error('Skill root not found');
|
|
47
|
+
root = parent;
|
|
48
|
+
}
|
|
49
|
+
const args = [path.join(root, 'scripts/rendering/browser-engine.js'), path.resolve(htmlPath), '--expected-pages', String(expectedPages)];
|
|
50
|
+
if (outputPdf)
|
|
51
|
+
args.push('--output', path.resolve(outputPdf));
|
|
52
|
+
const result = (0, child_process_1.spawnSync)(process.execPath, args, { encoding: 'utf8', timeout: 120000 });
|
|
53
|
+
if (result.error)
|
|
54
|
+
throw result.error;
|
|
55
|
+
const parsed = JSON.parse(result.stdout);
|
|
56
|
+
if (!parsed || typeof parsed !== 'object' || !('status' in parsed) || !('errors' in parsed) || !Array.isArray(parsed.errors))
|
|
57
|
+
throw new Error('Invalid print inspection result');
|
|
58
|
+
if (parsed.status !== 'PASS' && parsed.status !== 'FAIL' && parsed.status !== 'UNVERIFIED')
|
|
59
|
+
throw new Error('Invalid print status');
|
|
60
|
+
if (result.status !== 0 && parsed.status === 'PASS')
|
|
61
|
+
throw new Error('Print engine exited unsuccessfully');
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
@@ -1,23 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 验证 HTML 简历中的关键文本可提取性、标头层级规范与 ATS 机器解析友好度。
|
|
5
|
-
*/
|
|
6
|
-
interface AtsValidationResult {
|
|
7
|
-
status: 'PASS' | 'WARN' | 'FAIL';
|
|
8
|
-
candidateName: string | null;
|
|
9
|
-
detectedContacts: {
|
|
10
|
-
email: string | null;
|
|
11
|
-
phone: 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
|
-
export {};
|
|
2
|
+
import type { EngineOptions, EngineResult } from '../rendering/browser-engine';
|
|
3
|
+
export declare function validateAts(htmlPath: string, expectedPages?: number, options?: EngineOptions): Promise<EngineResult>;
|
|
@@ -1,173 +1,11 @@
|
|
|
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
|
-
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
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
4
|
exports.validateAts = validateAts;
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
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') || document.querySelector('h1');
|
|
62
|
-
const candidateName = nameEl ? nameEl.textContent?.trim() || null : null;
|
|
63
|
-
const bodyText = document.body.innerText || '';
|
|
64
|
-
const emailMatch = bodyText.match(/[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/);
|
|
65
|
-
const phoneMatch = bodyText.match(/1[3-9]\d{9}|\+?\d{1,4}[-\s]?\d{7,11}/);
|
|
66
|
-
const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4')).map(h => ({
|
|
67
|
-
level: h.tagName.toLowerCase(),
|
|
68
|
-
text: h.textContent?.replace(/[\s\n]+/g, ' ').trim() || ''
|
|
69
|
-
}));
|
|
70
|
-
return {
|
|
71
|
-
candidateName,
|
|
72
|
-
email: emailMatch ? emailMatch[0] : null,
|
|
73
|
-
phone: phoneMatch ? phoneMatch[0] : null,
|
|
74
|
-
headings,
|
|
75
|
-
totalTextLength: bodyText.trim().length
|
|
76
|
-
};
|
|
77
|
-
});
|
|
78
|
-
await browser.close();
|
|
79
|
-
const warnings = [];
|
|
80
|
-
const errors = [];
|
|
81
|
-
if (!rawData.candidateName) {
|
|
82
|
-
errors.push('Candidate name (.candidate-name or h1) could not be extracted');
|
|
83
|
-
}
|
|
84
|
-
if (!rawData.email) {
|
|
85
|
-
warnings.push('No valid email address pattern detected in text flow');
|
|
86
|
-
}
|
|
87
|
-
if (!rawData.phone) {
|
|
88
|
-
warnings.push('No standard mobile phone pattern detected in text flow');
|
|
89
|
-
}
|
|
90
|
-
const validatedHeadings = rawData.headings.map(h => {
|
|
91
|
-
const textClean = h.text.toLowerCase();
|
|
92
|
-
const isStandard = STANDARD_SECTION_HEADERS.some(sh => textClean.includes(sh.toLowerCase()));
|
|
93
|
-
return {
|
|
94
|
-
...h,
|
|
95
|
-
isStandard
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
const nonStdHeadings = validatedHeadings.filter(h => !h.isStandard && h.level === 'h2');
|
|
99
|
-
if (nonStdHeadings.length > 0) {
|
|
100
|
-
warnings.push(`Non-standard section headings detected: ${nonStdHeadings.map(h => `"${h.text}"`).join(', ')}`);
|
|
101
|
-
}
|
|
102
|
-
const status = errors.length > 0 ? 'FAIL' : warnings.length > 0 ? 'WARN' : 'PASS';
|
|
103
|
-
return {
|
|
104
|
-
status,
|
|
105
|
-
candidateName: rawData.candidateName,
|
|
106
|
-
detectedContacts: {
|
|
107
|
-
email: rawData.email,
|
|
108
|
-
phone: rawData.phone
|
|
109
|
-
},
|
|
110
|
-
headingsFound: validatedHeadings,
|
|
111
|
-
totalTextLength: rawData.totalTextLength,
|
|
112
|
-
warnings,
|
|
113
|
-
errors
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
async function main() {
|
|
117
|
-
const args = process.argv.slice(2);
|
|
118
|
-
let htmlPath = 'workspace/resume.html';
|
|
119
|
-
let jsonOutput = false;
|
|
120
|
-
for (let i = 0; i < args.length; i++) {
|
|
121
|
-
if (args[i] === '--html' || args[i] === '-i') {
|
|
122
|
-
htmlPath = args[i + 1];
|
|
123
|
-
i++;
|
|
124
|
-
}
|
|
125
|
-
else if (args[i] === '--json') {
|
|
126
|
-
jsonOutput = true;
|
|
127
|
-
}
|
|
128
|
-
else if (!args[i].startsWith('-')) {
|
|
129
|
-
htmlPath = args[i];
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
try {
|
|
133
|
-
const result = await validateAts(htmlPath);
|
|
134
|
-
if (jsonOutput) {
|
|
135
|
-
console.log(JSON.stringify(result, null, 2));
|
|
136
|
-
process.exit(result.status === 'FAIL' ? 1 : 0);
|
|
137
|
-
}
|
|
138
|
-
console.log('======================================================');
|
|
139
|
-
console.log(` KnowMe CareerForge — ATS Compliance QA: [${result.status}]`);
|
|
140
|
-
console.log(` Candidate Name : ${result.candidateName || 'NOT FOUND'}`);
|
|
141
|
-
console.log(` Phone Extracted: ${result.detectedContacts.phone || 'NOT DETECTED'}`);
|
|
142
|
-
console.log(` Email Extracted: ${result.detectedContacts.email || 'NOT DETECTED'}`);
|
|
143
|
-
console.log(` Text Extracted : ${result.totalTextLength} characters`);
|
|
144
|
-
console.log('------------------------------------------------------');
|
|
145
|
-
console.log(' Headings Structure:');
|
|
146
|
-
for (const h of result.headingsFound) {
|
|
147
|
-
const mark = h.isStandard ? '[✓]' : '[?]';
|
|
148
|
-
console.log(` ${mark} <${h.level}> ${h.text}`);
|
|
149
|
-
}
|
|
150
|
-
if (result.warnings.length > 0) {
|
|
151
|
-
console.log('\n[!] Warnings:');
|
|
152
|
-
for (const w of result.warnings) {
|
|
153
|
-
console.log(` - ⚠️ ${w}`);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (result.errors.length > 0) {
|
|
157
|
-
console.log('\n[✗] Errors:');
|
|
158
|
-
for (const e of result.errors) {
|
|
159
|
-
console.log(` - ✗ ${e}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
console.log('======================================================');
|
|
163
|
-
process.exit(result.status === 'FAIL' ? 1 : 0);
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
167
|
-
console.error(`[✗] ATS Validation Failed: ${errorMsg}`);
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
if (require.main === module) {
|
|
172
|
-
main();
|
|
5
|
+
/** Basic text availability is part of the same final-PDF acceptance gate. */
|
|
6
|
+
const engine = require("../rendering/engine-loader");
|
|
7
|
+
function validateAts(htmlPath, expectedPages = 1, options = {}) {
|
|
8
|
+
return engine.run(htmlPath, { ...options, expectedPages });
|
|
173
9
|
}
|
|
10
|
+
if (require.main === module)
|
|
11
|
+
engine.cli();
|
|
@@ -1,24 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 基于无头浏览器真实渲染计算 DOM 盒模型高度,检测单页/双页物理 A4 溢出与孤行。
|
|
5
|
-
*/
|
|
6
|
-
interface LayoutValidationResult {
|
|
7
|
-
status: 'PASS' | 'OVERFLOW';
|
|
8
|
-
expectedPages: number;
|
|
9
|
-
totalPagesFound: number;
|
|
10
|
-
pageDetails: Array<{
|
|
11
|
-
pageIndex: number;
|
|
12
|
-
actualHeightPx: number;
|
|
13
|
-
allowedHeightPx: number;
|
|
14
|
-
isOverflow: boolean;
|
|
15
|
-
overflowDeltaPx: number;
|
|
16
|
-
}>;
|
|
17
|
-
overflowSections: Array<{
|
|
18
|
-
selector: string;
|
|
19
|
-
bottomPx: number;
|
|
20
|
-
overflowByPx: number;
|
|
21
|
-
}>;
|
|
22
|
-
}
|
|
23
|
-
export declare function validateLayout(htmlPath: string, expectedPages?: number): Promise<LayoutValidationResult>;
|
|
24
|
-
export {};
|
|
2
|
+
import type { EngineOptions, EngineResult } from '../rendering/browser-engine';
|
|
3
|
+
export declare function validateLayout(htmlPath: string, expectedPages?: number, options?: EngineOptions): Promise<EngineResult>;
|