knowme-careerforge-skill 0.0.4 → 0.0.7

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 (117) hide show
  1. package/AGENT.md +276 -0
  2. package/ARCHITECTURE.md +220 -0
  3. package/README.md +46 -63
  4. package/README.zh-CN.md +47 -66
  5. package/SKILL.md +38 -135
  6. package/agents/claude/knowme-careerforge.md +2 -17
  7. package/agents/codex/knowme-careerforge.yaml +2 -20
  8. package/agents/cursor/knowme-careerforge.mdc +4 -13
  9. package/agents/gemini/knowme-careerforge.json +1 -1
  10. package/agents/opencode/skill.yaml +1 -1
  11. package/agents/windsurf/knowme-careerforge.rules +2 -12
  12. package/dist/cli/src/commands/init.js +51 -32
  13. package/dist/cli/src/index.js +25 -21
  14. package/dist/scripts/rendering/engine-loader.d.ts +2 -0
  15. package/dist/scripts/rendering/engine-loader.js +46 -0
  16. package/dist/scripts/rendering/render-pdf.d.ts +3 -8
  17. package/dist/scripts/rendering/render-pdf.js +7 -103
  18. package/dist/scripts/rendering/runtime.d.ts +9 -0
  19. package/dist/scripts/rendering/runtime.js +63 -0
  20. package/dist/scripts/validation/validate-ats.d.ts +2 -22
  21. package/dist/scripts/validation/validate-ats.js +6 -168
  22. package/dist/scripts/validation/validate-layout.d.ts +2 -23
  23. package/dist/scripts/validation/validate-layout.js +5 -159
  24. package/package.json +15 -4
  25. package/references/01-evidence-mining.md +35 -0
  26. package/references/02-career-goal.md +40 -0
  27. package/references/03-jd-analysis.md +32 -0
  28. package/references/04-template-selection.md +30 -0
  29. package/references/05-html-canvas-tokens.md +12 -0
  30. package/references/06-qa-and-rendering.md +27 -0
  31. package/references/07-artifact-contract.md +83 -0
  32. package/scripts/Agent.md +9 -136
  33. package/scripts/build/build-gallery.py +66 -56
  34. package/scripts/build/run-all-tests.py +1 -0
  35. package/scripts/build/sync-version.py +27 -9
  36. package/scripts/contracts/profile.py +101 -0
  37. package/scripts/evidence/extract-evidence.py +109 -62
  38. package/scripts/pipeline/forge.py +276 -136
  39. package/scripts/rendering/README.md +65 -0
  40. package/scripts/rendering/browser-engine.d.ts +15 -0
  41. package/scripts/rendering/browser-engine.js +408 -0
  42. package/scripts/rendering/browser_engine.py +39 -0
  43. package/scripts/rendering/engine-loader.ts +11 -0
  44. package/scripts/rendering/render-pdf.py +26 -151
  45. package/scripts/rendering/render-pdf.ts +7 -83
  46. package/scripts/template/instantiate-resume.py +266 -173
  47. package/scripts/template/search-template.py +89 -3
  48. package/scripts/validation/measure-dom.js +5 -0
  49. package/scripts/validation/validate-ats.ts +6 -174
  50. package/scripts/validation/validate-layout.ts +5 -159
  51. package/scripts/validation/validate-resume.py +23 -134
  52. package/skill.json +7 -6
  53. package/src/knowledge/index.json +391 -3
  54. package/src/knowledge/resume-schema.json +377 -71
  55. package/src/knowledge/templates.json +389 -1
  56. package/src/references/01-evidence-mining.md +24 -30
  57. package/src/references/02-career-goal.md +4 -2
  58. package/src/references/03-jd-analysis.md +5 -17
  59. package/src/references/04-template-selection.md +20 -33
  60. package/src/references/05-html-canvas-tokens.md +6 -45
  61. package/src/references/06-qa-and-rendering.md +16 -43
  62. package/src/references/07-artifact-contract.md +83 -0
  63. package/src/templates/Agent.md +11 -0
  64. package/src/templates/academic-research/README.md +25 -0
  65. package/src/templates/academic-research/canvas.html +21 -0
  66. package/src/templates/academic-research/metadata.json +38 -0
  67. package/src/templates/academic-research/sample-profile.json +86 -0
  68. package/src/templates/academic-research/style.css +213 -0
  69. package/src/templates/classic/README.md +9 -0
  70. package/src/templates/classic/canvas.html +21 -0
  71. package/src/templates/classic/metadata.json +3 -0
  72. package/src/templates/classic/sample-profile.json +86 -0
  73. package/src/templates/classic/style.css +14 -4
  74. package/src/templates/common/base.css +32 -0
  75. package/src/templates/common/canvas-bindings.css +41 -0
  76. package/src/templates/common/resume-contract.md +8 -0
  77. package/src/templates/compact-dense/README.md +25 -0
  78. package/src/templates/compact-dense/canvas.html +14 -0
  79. package/src/templates/compact-dense/metadata.json +38 -0
  80. package/src/templates/compact-dense/sample-profile.json +86 -0
  81. package/src/templates/compact-dense/style.css +218 -0
  82. package/src/templates/creative-tech/README.md +26 -0
  83. package/src/templates/creative-tech/canvas.html +15 -0
  84. package/src/templates/creative-tech/metadata.json +39 -0
  85. package/src/templates/creative-tech/sample-profile.json +86 -0
  86. package/src/templates/creative-tech/style.css +257 -0
  87. package/src/templates/data-analyst/README.md +26 -0
  88. package/src/templates/data-analyst/canvas.html +15 -0
  89. package/src/templates/data-analyst/metadata.json +39 -0
  90. package/src/templates/data-analyst/sample-profile.json +86 -0
  91. package/src/templates/data-analyst/style.css +285 -0
  92. package/src/templates/executive/README.md +9 -0
  93. package/src/templates/executive/canvas.html +15 -0
  94. package/src/templates/executive/sample-profile.json +86 -0
  95. package/src/templates/executive/style.css +58 -6
  96. package/src/templates/international-flow/README.md +26 -0
  97. package/src/templates/international-flow/canvas.html +21 -0
  98. package/src/templates/international-flow/metadata.json +37 -0
  99. package/src/templates/international-flow/sample-profile.json +86 -0
  100. package/src/templates/international-flow/style.css +208 -0
  101. package/src/templates/minimal/README.md +12 -1
  102. package/src/templates/minimal/canvas.html +21 -0
  103. package/src/templates/minimal/sample-profile.json +86 -0
  104. package/src/templates/minimal/style.css +24 -0
  105. package/src/templates/modern/README.md +9 -0
  106. package/src/templates/modern/canvas.html +15 -0
  107. package/src/templates/modern/sample-profile.json +86 -0
  108. package/src/templates/modern/style.css +48 -1
  109. package/src/templates/startup-generalist/README.md +25 -0
  110. package/src/templates/startup-generalist/canvas.html +21 -0
  111. package/src/templates/startup-generalist/metadata.json +37 -0
  112. package/src/templates/startup-generalist/sample-profile.json +86 -0
  113. package/src/templates/startup-generalist/style.css +237 -0
  114. package/src/templates/classic/template.html +0 -133
  115. package/src/templates/executive/template.html +0 -101
  116. package/src/templates/minimal/template.html +0 -114
  117. package/src/templates/modern/template.html +0 -139
@@ -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
- 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') || 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
- * 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
- 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>;
@@ -1,164 +1,10 @@
1
1
  #!/usr/bin/env ts-node
2
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
3
  Object.defineProperty(exports, "__esModule", { value: true });
41
4
  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: 1
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((p, idx) => {
63
- const rect = p.getBoundingClientRect();
64
- const actualHeight = Math.ceil(rect.height);
65
- const isOverflow = actualHeight > allowedHeight + 2; // 2px 浮点容差
66
- return {
67
- pageIndex: idx + 1,
68
- actualHeightPx: actualHeight,
69
- allowedHeightPx: allowedHeight,
70
- isOverflow,
71
- overflowDeltaPx: isOverflow ? Math.ceil(actualHeight - allowedHeight) : 0
72
- };
73
- });
74
- const overflowSections = [];
75
- if (details.some(d => d.isOverflow)) {
76
- const items = Array.from(document.querySelectorAll('.experience-item, .project-item, .skills-content, .resume-section'));
77
- for (const item of items) {
78
- const rect = item.getBoundingClientRect();
79
- if (rect.bottom > allowedHeight) {
80
- overflowSections.push({
81
- selector: item.className ? `.${item.className.split(' ').join('.')}` : item.tagName.toLowerCase(),
82
- bottomPx: Math.ceil(rect.bottom),
83
- overflowByPx: Math.ceil(rect.bottom - allowedHeight)
84
- });
85
- }
86
- }
87
- }
88
- return {
89
- totalPagesFound: pages.length || 1,
90
- pageDetails: details.length ? details : [{
91
- pageIndex: 1,
92
- actualHeightPx: Math.ceil(document.body.getBoundingClientRect().height),
93
- allowedHeightPx: allowedHeight,
94
- isOverflow: document.body.getBoundingClientRect().height > allowedHeight + 2,
95
- overflowDeltaPx: Math.max(0, Math.ceil(document.body.getBoundingClientRect().height - allowedHeight))
96
- }],
97
- overflowSections
98
- };
99
- }, A4_HEIGHT_PX);
100
- await browser.close();
101
- const hasOverflow = evaluation.pageDetails.some(p => p.isOverflow) || evaluation.totalPagesFound > expectedPages;
102
- return {
103
- status: hasOverflow ? 'OVERFLOW' : 'PASS',
104
- expectedPages,
105
- totalPagesFound: evaluation.totalPagesFound,
106
- pageDetails: evaluation.pageDetails,
107
- overflowSections: evaluation.overflowSections
108
- };
109
- }
110
- async function main() {
111
- const args = process.argv.slice(2);
112
- let htmlPath = 'workspace/resume.html';
113
- let expectedPages = 1;
114
- let jsonOutput = false;
115
- for (let i = 0; i < args.length; i++) {
116
- if (args[i] === '--html' || args[i] === '-i') {
117
- htmlPath = args[i + 1];
118
- i++;
119
- }
120
- else if (args[i] === '--expected-pages' || args[i] === '-p') {
121
- expectedPages = parseInt(args[i + 1], 10) || 1;
122
- i++;
123
- }
124
- else if (args[i] === '--json') {
125
- jsonOutput = true;
126
- }
127
- else if (!args[i].startsWith('-')) {
128
- htmlPath = args[i];
129
- }
130
- }
131
- try {
132
- const result = await validateLayout(htmlPath, expectedPages);
133
- if (jsonOutput) {
134
- console.log(JSON.stringify(result, null, 2));
135
- process.exit(result.status === 'PASS' ? 0 : 1);
136
- }
137
- console.log('======================================================');
138
- console.log(` KnowMe CareerForge — Layout QA: ${result.status}`);
139
- console.log(` HTML Path : ${htmlPath}`);
140
- console.log(` Expected Pages : ${result.expectedPages} | Pages Found: ${result.totalPagesFound}`);
141
- console.log('------------------------------------------------------');
142
- for (const p of result.pageDetails) {
143
- const fitText = p.isOverflow ? `[✗ OVERFLOW by ${p.overflowDeltaPx}px]` : `[✓ FIT] (Overflow: ${p.overflowDeltaPx}px)`;
144
- console.log(` Page ${p.pageIndex}: ${p.actualHeightPx}px / ${p.allowedHeightPx}px ${fitText}`);
145
- }
146
- if (result.overflowSections.length > 0) {
147
- console.log('\n[!] Offending Overflow Elements:');
148
- for (const el of result.overflowSections.slice(0, 3)) {
149
- console.log(` - Element "${el.selector}" exceeds A4 baseline by ${el.overflowByPx}px (Bottom: ${el.bottomPx}px)`);
150
- }
151
- console.log('\n💡 Recommendation: Reduce --resume-space-section by 1~2pt or reduce --resume-font-size-body by 0.2pt in <style>.');
152
- }
153
- console.log('======================================================');
154
- process.exit(result.status === 'PASS' ? 0 : 1);
155
- }
156
- catch (err) {
157
- const errorMsg = err instanceof Error ? err.message : String(err);
158
- console.error(`[✗] Layout Validation Failed: ${errorMsg}`);
159
- process.exit(1);
160
- }
161
- }
162
- if (require.main === module) {
163
- main();
5
+ const engine = require("../rendering/engine-loader");
6
+ function validateLayout(htmlPath, expectedPages = 1, options = {}) {
7
+ return engine.run(htmlPath, { ...options, expectedPages });
164
8
  }
9
+ if (require.main === module)
10
+ engine.cli();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knowme-careerforge-skill",
3
- "version": "0.0.4",
3
+ "version": "0.0.7",
4
4
  "description": "An agent-native skill for self-discovery, career positioning, and tailored resume engineering.",
5
5
  "main": "dist/cli/src/index.js",
6
6
  "bin": {
@@ -13,7 +13,10 @@
13
13
  "scripts",
14
14
  "agents",
15
15
  "SKILL.md",
16
- "skill.json"
16
+ "skill.json",
17
+ "references",
18
+ "AGENT.md",
19
+ "ARCHITECTURE.md"
17
20
  ],
18
21
  "scripts": {
19
22
  "build": "tsc",
@@ -31,7 +34,8 @@
31
34
  "build:gallery": "python3 scripts/build/build-gallery.py",
32
35
  "release": "python3 scripts/build/release.py",
33
36
  "version:sync": "python3 scripts/build/sync-version.py",
34
- "prepublishOnly": "npm run build && npm test"
37
+ "prepublishOnly": "npm test",
38
+ "pretest": "npm run build"
35
39
  },
36
40
  "keywords": [
37
41
  "career-forge",
@@ -55,8 +59,15 @@
55
59
  },
56
60
  "devDependencies": {
57
61
  "@types/node": "^22.0.0",
58
- "playwright": "^1.49.0",
59
62
  "ts-node": "^10.9.2",
60
63
  "typescript": "^5.9.3"
64
+ },
65
+ "dependencies": {
66
+ "pdf-lib": "^1.17.1",
67
+ "pdfjs-dist": "^5.4.624",
68
+ "playwright": "^1.62.1"
69
+ },
70
+ "engines": {
71
+ "node": ">=22.13.0"
61
72
  }
62
73
  }
@@ -0,0 +1,35 @@
1
+ # Turn supplied facts into resume claims
2
+
3
+ Read this when composing or materially rewriting candidate content. Compare with the user's original messages or supplied resume, not just an Agent-generated profile. Tools verify that profile text survives rendering; they cannot establish that the profile was faithful in the first place.
4
+
5
+ ## Compose, then check against the source
6
+
7
+ 1. Identify supplied facts and the target role separately. Treat role/JD knowledge as requirements to compare, not personal qualifications. Do not read role profiles for a straightforward rewrite; consult them only for a specific positioning question.
8
+ 2. Write a concise draft of the content. Every skill, responsibility, mechanism, result and level of proficiency must be supported by supplied material. Professional editing can improve order and clarity without increasing ownership, scope or certainty.
9
+ 3. Before saving the profile, compare the claims with the original source. Remove unsupported additions; ask only when a consequential ambiguity prevents a useful result. Keep this comparison internal, without a separate report or approval round. If helpful for a nontrivial rewrite, the existing bullet `evidenceSource` string can record a short source quotation; it is not printed and is not an independent verification claim.
10
+
11
+ Check each changed dimension: **technology · ownership · scope · mechanism · result · certainty**. A target title can go in the job objective; it cannot change an actual employment title, create years of experience in another stack, or become an item in skills.
12
+
13
+ ## Concrete rewrite boundaries
14
+
15
+ These are isolated editing examples, not candidate material or a profile to execute.
16
+
17
+ | Supplied statement | Faithful treatment | Unsupported addition |
18
+ | --- | --- | --- |
19
+ | Worked with Node.js and NestJS; wants a Java role | Keep the actual stack; use Java only in the target title until the user supplies Java skills | Add Java/Git to skills, or describe the period as Java experience |
20
+ | Participated in Redis and message-queue work | Keep “participated” and the unnamed message queue | Led architecture; Kafka/RocketMQ; cache prewarming; payment/event pipelines |
21
+ | Optimized queries; response time decreased about 40% under high concurrency | Retain that result and its qualification in the strongest relevant work bullet | Average/P99, 20ms, QPS, indexing or SQL execution-plan changes not supplied |
22
+ | Developed order, user and payment modules | Describe the modules in the work entry | Invent a separately named high-concurrency project, new project dates or a “core owner” role |
23
+ | Used Spring Boot and MySQL | List those technologies or the supplied use case | Expert JVM/JUC knowledge, deep index internals, distributed transactions |
24
+ | Bachelor degree and a major, without school/dates | Include the two known education fields; omit unknown fields | Guess a university, dates, GPA or claim that all education data is complete |
25
+
26
+ Do not turn a suggested outcome into a required ingredient of every bullet. A duty without a metric remains valid. One supplied project can be developed into a project entry when actual context and contributions are available; do not duplicate the same work as a second achievement just to fill a section. Keep a brief summary distinct from detailed work bullets.
27
+
28
+ ## Continue safely across turns
29
+
30
+ - User acceptance such as “use this version” authorizes progressing with known facts. It does not retroactively support a skill or achievement introduced by the Agent. Correct your unsupported additions before export; explain a material correction briefly instead of silently preserving it.
31
+ - When real candidate material arrives after an explicitly requested fictional example, build the candidate profile from that material. Reusing the layout is fine; filling over a fictional profile risks leaving invented skills and achievements behind.
32
+ - On a contact-only update, change only those fields and necessary resulting layout. Do not rewrite summary/skills/projects, change template, add qualifications or re-ask the whole questionnaire. If continuing in a new session, use the prior current profile/canvas and ask only if the current revision is genuinely ambiguous.
33
+ - Supplied wording may be strengthened only by stronger supplied facts; do not weaken explicit ownership merely because the source is chat. Conflicting dates or roles require a focused clarification or omission of the disputed claim, not guessing.
34
+
35
+ Use Draft for a requested partial canvas, not simply because optional fields are missing. Master preserves this run's known facts and does not certify their external truth. Missing values remain omitted/null. See 07-artifact-contract.md for data shapes and tool states.
@@ -0,0 +1,40 @@
1
+ # Reference Manual 02: Career Goal Definition & Role Archetypes
2
+
3
+ > **"Know Yourself. Define Your Direction. Forge Your Opportunity."**
4
+
5
+ ---
6
+
7
+ ## 1. Professional Seniority Signals
8
+
9
+ Use these archetypes to guide questions, not to infer facts. Years alone do not establish seniority, ownership or proficiency. Preserve the user’s stated role and responsibility; omit unsupported claims from the following illustrative patterns.
10
+
11
+ | Seniority Tier | Core Value Proposition | Key Narrative Signals | Section Priority |
12
+ |:---|:---|:---|:---|
13
+ | **Junior (1~3 Years)** | Rapid learning, solid coding standards, reliable task delivery | Clean code, unit test coverage, framework fluency, bug resolution | Skills → Projects → Experience → Education |
14
+ | **Mid-Level (3~5 Years)** | Independent ownership, feature architecture, performance optimization | End-to-end module ownership, database tuning, API design, CI/CD | Experience → Projects → Skills → Education |
15
+ | **Senior / Lead (5~8+ Years)** | System architecture, technical leadership, cross-system trade-offs | Distributed systems, high availability, mentoring, business impact | Experience → Projects → Architecture → Skills |
16
+ | **Principal / Architect** | Technical vision, organizational strategy, governance, ROI | Architecture governance, multi-team alignment, technology selection | Experience → Leadership → Architecture → Patents |
17
+
18
+ ---
19
+
20
+ ## 2. Role Archetypes Knowledge Base (`src/knowledge/roles/*.json`)
21
+
22
+ Read a role profile only for a specific positioning question after candidate facts are available. `mustHaveSkills`, `niceToHaveSkills` and `evidenceSignals` describe hiring expectations and question topics; they are never defaults for personal skills, mechanisms or accomplishments. A generic role name may cover several seniority levels.
23
+
24
+ The skill ships with 9+ structured role profiles:
25
+ - `ai-agent-engineer.json`: LLM Orchestration, RAG, Tool Calling, VectorDB, Prompt Engineering
26
+ - `frontend.json`: React/Vue/Next.js, Web Vitals, Design Systems, State Management
27
+ - `java-backend.json`: Spring Boot, JVM tuning, Distributed Caching, Message Queues
28
+ - `node-fullstack.json`: TypeScript, Node.js runtime, GraphQL, Cloud Native
29
+ - `architect.json`: Distributed Systems, Domain-Driven Design, High Concurrency, Cloud Architecture
30
+ - `product-manager.json`: PRD, User Research, Data Analysis, Roadmapping, Growth
31
+
32
+ ---
33
+
34
+ ## 3. Core Value Proposition Formulation
35
+
36
+ If useful, write a short headline from supplied facts. The following formula is optional: omit years, strengths or impact when unknown. The example is fictional and must never fill a candidate record.
37
+ $$\text{Value Proposition} = [\text{Target Role}] + [\text{Years/Domain Focus}] + [\text{Top 2 Signature Strengths}] + [\text{Concrete Impact Deliverable}]$$
38
+
39
+ *Example*:
40
+ > **Senior AI Agent Engineer** with 5+ years building scalable LLM orchestration & enterprise RAG pipelines, delivering 40% latency reduction across multi-agent production workflows.
@@ -0,0 +1,32 @@
1
+ # Reference Manual 03: Job Description (JD) Analysis & Keyword Extraction
2
+
3
+ > **"Understand the hiring manager's unstated pain point to align your verified strengths."**
4
+
5
+ ---
6
+
7
+ ## 1. Automated JD Signal Parsing Protocol
8
+
9
+ Execute the JD analyzer to extract structured requirements:
10
+ ```bash
11
+ python3 scripts/evidence/analyze-jd.py --jd "path/to/jd.md" --json
12
+ ```
13
+
14
+ ### Signal Extraction Dimensions:
15
+ 1. **Target Role & Seniority Tier**: Extracted from title and years of experience requirements;
16
+ 2. **Must-Have Skills (Hard Constraints)**: Top technical stacks and non-negotiable architectural competencies;
17
+ 3. **Nice-to-Have Skills (Differentiators)**: Emerging tools, cloud certifications, domain bonuses;
18
+ 4. **Hiring Pain Points**: High-frequency verbs indicating immediate business challenges (e.g., "reduce latency", "overhaul legacy monolithic service", "establish CI/CD").
19
+
20
+ ---
21
+
22
+ ## 2. Keyword Density & Highlighting Rules
23
+
24
+ Use JD terms only where the candidate has supplied matching experience. Keywords select or emphasize existing facts; they do not add skills to a Master or Variant. A technology named only by the vacancy remains a gap to discuss.
25
+
26
+ Prefer a few relevant, readable terms over repetition. There is no project-validated universal ATS keyword-density threshold. Do not claim automated screening success from keyword counts.
27
+
28
+ ## 3. Writing useful bullets
29
+
30
+ Describe the supplied action and its context. Add the method and outcome only when the user provided them. FAB is an optional editing aid, not a requirement that every duty acquire an invented business impact.
31
+
32
+ For example, if the only fact is “optimized order queries”, a faithful rewrite is “Optimized order queries for the ecommerce service” only if that service context was supplied. Do not infer indexing, Redis caching, P99 latency or traffic multiples. Preserve “participated” versus “led”, and ask for consequential missing mechanisms instead of writing them as facts.
@@ -0,0 +1,30 @@
1
+ # Template selection and readable composition
2
+
3
+ Select for the candidate's actual amount of content, strongest evidence and explicit preference. A backend title alone does not determine the layout. The ten current IDs below are directory IDs accepted by the pipeline; metadata describes design intent, not visual or ATS certification.
4
+
5
+ | ID | Structure | Review before choosing |
6
+ | --- | --- | --- |
7
+ | minimal | Single column | Straight reading order; sparse content needs breathing room rather than compression |
8
+ | international-flow | Single column | Consider for linear prose; inspect language and line lengths |
9
+ | modern | Sidebar, 32:68 | Keep side content short; check narrow-column wrapping |
10
+ | creative-tech | Sidebar, 28:72 | Use only when the visual emphasis suits the user |
11
+ | data-analyst | Sidebar, 30:70 | Do not invent metrics to match the template name |
12
+ | executive | Banner and split columns | Needs suitable supplied leadership evidence; a title does not confer it |
13
+ | classic | Structured table | Check sparse fields and reading order |
14
+ | compact-dense | Dense two columns | For substantial supplied content; avoid for a short career description |
15
+ | academic-research | Dense single column | Research content must actually be supplied |
16
+ | startup-generalist | Modular cards | Check block balance with the actual sections |
17
+
18
+ Use the existing search when no template was selected:
19
+
20
+ ```bash
21
+ python3 <skill-root>/scripts/template/search-template.py "<target role>" --target-pages 1 --summary
22
+ ```
23
+
24
+ The summary returns the top three candidates with IDs and layout metadata; the existing `--json` option returns all metadata only when needed. For a simple, sparse resume, prefer a readable single-column structure from the table over a dense sidebar just because it has a higher role score. Do not load every template source or build a gallery for a single resume.
25
+
26
+ The pipeline also ranks automatically if `--template` is omitted. Ranking does not inspect the final PDF's aesthetics. Inspect the selected output and report `templateUsed`; do not claim that all ten designs were visually compared if only one was rendered.
27
+
28
+ For experienced candidates, work evidence should be easy to find. Keep skill lists compact; avoid paragraphs claiming proficiency in every role keyword. A summary and a project section are optional, and should not repeat the same achievement. For sparse profiles, preserve honest whitespace, use a simple layout and ask for useful details only when needed. Do not fill a page with generic qualities or fabricated projects. For dense profiles, edit repetition before reducing type size or spacing.
29
+
30
+ Each template maintains `canvas.html`, `style.css`, `metadata.json`, `README.md` and a fictional `sample-profile.json`. Gallery previews use the same instantiator, combining common/base.css, template style.css and common/canvas-bindings.css. Read source only for a needed layout edit; never load gallery sample content to assemble candidate facts. Token tuning is described in 05-html-canvas-tokens.md. Multiple PDF pages require explicit page containers, regardless of metadata's maximum page count.
@@ -0,0 +1,12 @@
1
+ # HTML canvas and tokens
2
+
3
+ Instantiate an empty canvas.html skeleton with explicit user facts. canvas.html is the sole maintained structure. Gallery previews bind explicit fictional sample-profile.json through the same instantiator and full CSS chain; sample data must never serve as default candidate facts. Shared normalization accepts omitted/null optional fields and rejects invalid types. Failed binding must preserve old output and return nonzero.
4
+
5
+ Tune the run's HTML with CSS variables for section/item/bullet spacing and body typography. Preserve manually edited content rather than regenerating stale profile data. Auto-healing cannot remove facts or reduce body font below 8.8pt. Validate in print mode, inspect all pages and verify the actual PDF before delivery. See 07-artifact-contract.md.
6
+
7
+
8
+ ## 本地字体与缺字诊断
9
+
10
+ `forge.py` 和 `instantiate-resume.py` 支持 `--font-preset system|arial-unicode`,默认保留系统字体。`arial-unicode` 显式使用本机 Arial Unicode MS;没有该字体时字体验收失败并返回 UNVERIFIED,不保证跨机器可用,也不自动下载字体。字体选择只改变画布样式,不改变 Master 事实,并记录在 Variant 的 source.fontPreset 和运行清单中。
11
+
12
+ 中文 PDF 若文本验收失败,检查 QA 每页的 missingCodepoints、missingFragments 与 extractedRadicals。部首字符可能来自字体字码映射差异,也可能存在真实缺字;不能仅凭这种提示放行。可选用本机可用字体、创建新运行并重新检查,不改写姓名/经历来绕过问题,也不把部首字符全部忽略。
@@ -0,0 +1,27 @@
1
+ # QA and final rendering
2
+
3
+ Validation and rendering use scripts/rendering/browser-engine.js. Inspect print media after fonts settle, every page container and visible content boundary; generate a fresh PDF and parse every page for A4 portrait dimensions, allowed page count and extractable content matching the canvas.
4
+
5
+ PASS requires the actual checks to complete. Missing browser/dependencies or timeout is UNVERIFIED (exit 2), a failed check is FAIL (exit 1). JSON mode uses the same exit rules. Do not use an estimate or an existing PDF as proof. Export verified bytes through a temporary file and atomic replacement; failed export retains the previous file.
6
+
7
+ The pipeline isolates each run and records artifact hashes in manifest.json. Use its current PASS output paths, not shared default filenames. Factual and visual review remain Agent responsibilities; text extraction does not certify every ATS. See 07-artifact-contract.md for the complete lifecycle.
8
+
9
+
10
+ ## 本地字体与缺字诊断
11
+
12
+ `forge.py` 和 `instantiate-resume.py` 支持 `--font-preset system|arial-unicode`,默认保留系统字体。`arial-unicode` 显式使用本机 Arial Unicode MS;没有该字体时字体验收失败并返回 UNVERIFIED,不保证跨机器可用,也不自动下载字体。字体选择只改变画布样式,不改变 Master 事实,并记录在 Variant 的 source.fontPreset 和运行清单中。
13
+
14
+ 中文 PDF 若文本验收失败,检查 QA 每页的 missingCodepoints、missingFragments 与 extractedRadicals。部首字符可能来自字体字码映射差异,也可能存在真实缺字;不能仅凭这种提示放行。可选用本机可用字体、创建新运行并重新检查,不改写姓名/经历来绕过问题,也不把部首字符全部忽略。
15
+
16
+ ## What each check can actually establish
17
+
18
+ | Evidence | What it supports | What it cannot establish |
19
+ | --- | --- | --- |
20
+ | Original user material compared with rewritten claims | Fidelity to supplied facts | Independent verification of the user's history |
21
+ | Master/Variant hashes and bound text | Run provenance and rendering consistency | That Agent-written profile claims came from the user |
22
+ | Current PDF page/text/layout checks | Technical PDF acceptance | Complete optional information, ideal design or universal ATS support |
23
+ | Every page of the final PDF viewed | Actual visual appearance of those pages | Authenticity of the candidate's achievements |
24
+
25
+ Inspect the PDF itself using the host's PDF viewer or an available PDF-to-image tool, not a screen-media HTML preview. If no such tool is available, say the PDF's visual review is incomplete; keep the technical PASS distinction. Do not install a second rendering system just to repeat checks the pipeline already performed.
26
+
27
+ When a font mapping warning occurs, read the reported missing fragments/codepoints and compare with the source text and page appearance. A warning is a diagnostic clue, not a diagnosis or an automatic reason to force a specific font. Use an available explicit font only as a justified retry; preserve facts and recheck the new PDF. After the current export and review pass, deliver rather than creating more copies or rerunning unchanged checks.