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.
Files changed (127) hide show
  1. package/AGENT.md +276 -0
  2. package/ARCHITECTURE.md +216 -0
  3. package/README.md +34 -54
  4. package/README.zh-CN.md +35 -57
  5. package/SKILL.md +28 -137
  6. package/agents/claude/knowme-careerforge.md +2 -16
  7. package/agents/codex/knowme-careerforge.yaml +5 -7
  8. package/agents/cursor/knowme-careerforge.mdc +4 -9
  9. package/agents/opencode/skill.yaml +1 -1
  10. package/agents/windsurf/knowme-careerforge.rules +2 -12
  11. package/dist/cli/src/commands/init.js +9 -0
  12. package/dist/cli/src/index.js +40 -19
  13. package/dist/scripts/rendering/engine-loader.d.ts +2 -0
  14. package/dist/scripts/rendering/engine-loader.js +46 -0
  15. package/dist/scripts/rendering/render-pdf.d.ts +3 -8
  16. package/dist/scripts/rendering/render-pdf.js +7 -103
  17. package/dist/scripts/rendering/runtime.d.ts +9 -0
  18. package/dist/scripts/rendering/runtime.js +63 -0
  19. package/dist/scripts/validation/validate-ats.d.ts +2 -22
  20. package/dist/scripts/validation/validate-ats.js +6 -168
  21. package/dist/scripts/validation/validate-layout.d.ts +2 -23
  22. package/dist/scripts/validation/validate-layout.js +5 -159
  23. package/package.json +16 -4
  24. package/references/01-evidence-mining.md +5 -0
  25. package/references/02-career-goal.md +38 -0
  26. package/references/03-jd-analysis.md +44 -0
  27. package/references/04-template-selection.md +43 -0
  28. package/references/05-html-canvas-tokens.md +12 -0
  29. package/references/06-qa-and-rendering.md +14 -0
  30. package/references/07-artifact-contract.md +64 -0
  31. package/scripts/Agent.md +9 -136
  32. package/scripts/build/build-gallery.py +66 -56
  33. package/scripts/build/run-all-tests.py +1 -0
  34. package/scripts/build/sync-version.py +37 -10
  35. package/scripts/contracts/profile.py +101 -0
  36. package/scripts/evidence/extract-evidence.py +109 -62
  37. package/scripts/pipeline/forge.py +266 -136
  38. package/scripts/rendering/README.md +65 -0
  39. package/scripts/rendering/browser-engine.d.ts +15 -0
  40. package/scripts/rendering/browser-engine.js +366 -0
  41. package/scripts/rendering/browser_engine.py +39 -0
  42. package/scripts/rendering/engine-loader.ts +11 -0
  43. package/scripts/rendering/render-pdf.py +26 -151
  44. package/scripts/rendering/render-pdf.ts +7 -83
  45. package/scripts/template/instantiate-resume.py +266 -167
  46. package/scripts/template/search-template.py +367 -89
  47. package/scripts/validation/measure-dom.js +5 -0
  48. package/scripts/validation/validate-ats.ts +6 -174
  49. package/scripts/validation/validate-layout.ts +5 -159
  50. package/scripts/validation/validate-resume.py +23 -134
  51. package/skill.json +1 -1
  52. package/src/knowledge/index.json +390 -10
  53. package/src/knowledge/resume-schema.json +377 -71
  54. package/src/knowledge/templates.json +388 -8
  55. package/src/references/01-evidence-mining.md +5 -0
  56. package/src/references/02-career-goal.md +38 -0
  57. package/src/references/03-jd-analysis.md +44 -0
  58. package/src/references/04-template-selection.md +43 -0
  59. package/src/references/05-html-canvas-tokens.md +12 -0
  60. package/src/references/06-qa-and-rendering.md +14 -0
  61. package/src/references/07-artifact-contract.md +64 -0
  62. package/src/templates/Agent.md +11 -0
  63. package/src/templates/academic-research/README.md +25 -0
  64. package/src/templates/academic-research/canvas.html +21 -0
  65. package/src/templates/academic-research/metadata.json +38 -0
  66. package/src/templates/academic-research/sample-profile.json +86 -0
  67. package/src/templates/academic-research/style.css +213 -0
  68. package/src/templates/classic/README.md +9 -0
  69. package/src/templates/classic/canvas.html +21 -0
  70. package/src/templates/classic/metadata.json +3 -0
  71. package/src/templates/classic/sample-profile.json +86 -0
  72. package/src/templates/classic/style.css +35 -54
  73. package/src/templates/common/base.css +182 -0
  74. package/src/templates/common/canvas-bindings.css +41 -0
  75. package/src/templates/common/resume-contract.md +8 -0
  76. package/src/templates/compact-dense/README.md +25 -0
  77. package/src/templates/compact-dense/canvas.html +14 -0
  78. package/src/templates/compact-dense/metadata.json +38 -0
  79. package/src/templates/compact-dense/sample-profile.json +86 -0
  80. package/src/templates/compact-dense/style.css +218 -0
  81. package/src/templates/creative-tech/README.md +26 -0
  82. package/src/templates/creative-tech/canvas.html +15 -0
  83. package/src/templates/creative-tech/metadata.json +39 -0
  84. package/src/templates/creative-tech/sample-profile.json +86 -0
  85. package/src/templates/creative-tech/style.css +257 -0
  86. package/src/templates/data-analyst/README.md +26 -0
  87. package/src/templates/data-analyst/canvas.html +15 -0
  88. package/src/templates/data-analyst/metadata.json +39 -0
  89. package/src/templates/data-analyst/sample-profile.json +86 -0
  90. package/src/templates/data-analyst/style.css +285 -0
  91. package/src/templates/executive/README.md +9 -0
  92. package/src/templates/executive/canvas.html +15 -0
  93. package/src/templates/executive/sample-profile.json +86 -0
  94. package/src/templates/executive/style.css +83 -74
  95. package/src/templates/international-flow/README.md +26 -0
  96. package/src/templates/international-flow/canvas.html +21 -0
  97. package/src/templates/international-flow/metadata.json +37 -0
  98. package/src/templates/international-flow/sample-profile.json +86 -0
  99. package/src/templates/international-flow/style.css +208 -0
  100. package/src/templates/minimal/README.md +9 -0
  101. package/src/templates/minimal/canvas.html +21 -0
  102. package/src/templates/minimal/sample-profile.json +86 -0
  103. package/src/templates/minimal/style.css +54 -72
  104. package/src/templates/modern/README.md +9 -0
  105. package/src/templates/modern/canvas.html +15 -0
  106. package/src/templates/modern/sample-profile.json +86 -0
  107. package/src/templates/modern/style.css +78 -71
  108. package/src/templates/startup-generalist/README.md +25 -0
  109. package/src/templates/startup-generalist/canvas.html +21 -0
  110. package/src/templates/startup-generalist/metadata.json +37 -0
  111. package/src/templates/startup-generalist/sample-profile.json +86 -0
  112. package/src/templates/startup-generalist/style.css +237 -0
  113. package/src/references/ats.md +0 -17
  114. package/src/references/evidence-first.md +0 -22
  115. package/src/references/pdf.md +0 -40
  116. package/src/references/resume-writing.md +0 -25
  117. package/src/references/template-design.md +0 -57
  118. package/src/templates/classic/template.html +0 -133
  119. package/src/templates/executive/template.html +0 -101
  120. package/src/templates/minimal/template.html +0 -114
  121. package/src/templates/modern/template.html +0 -139
  122. package/src/workflows/analyze-job.md +0 -16
  123. package/src/workflows/define-goal.md +0 -16
  124. package/src/workflows/forge-resume.md +0 -25
  125. package/src/workflows/know-me.md +0 -21
  126. package/src/workflows/position.md +0 -19
  127. package/src/workflows/review.md +0 -31
@@ -1,177 +1,9 @@
1
1
  #!/usr/bin/env ts-node
2
- /**
3
- * KnowMe CareerForge — ATS Text Flow & Heading Hierarchy Inspector
4
- * 验证 HTML 简历中的关键文本可提取性、标头层级规范与 ATS 机器解析友好度。
5
- */
2
+ /** Basic text availability is part of the same final-PDF acceptance gate. */
3
+ import engine = require('../rendering/engine-loader');
4
+ import type { EngineOptions, EngineResult } from '../rendering/browser-engine';
6
5
 
7
- import { chromium } from 'playwright';
8
- import * as path from 'path';
9
- import * as fs from 'fs';
10
-
11
- interface AtsValidationResult {
12
- status: 'PASS' | 'WARN' | 'FAIL';
13
- candidateName: string | null;
14
- detectedContacts: {
15
- email: string | null;
16
- phone: string | null;
17
- };
18
- headingsFound: Array<{
19
- level: string;
20
- text: string;
21
- isStandard: boolean;
22
- }>;
23
- totalTextLength: number;
24
- warnings: string[];
25
- errors: string[];
26
- }
27
-
28
- const STANDARD_SECTION_HEADERS = [
29
- '工作经历', '工作经验', 'work experience', 'experience',
30
- '项目经历', '核心项目', 'projects', 'key projects',
31
- '专业技能', '核心技能', 'skills', 'technical skills',
32
- '教育背景', '教育经历', 'education',
33
- '核心领导力', '重大经历', '资质与证书', '求职意向'
34
- ];
35
-
36
- export async function validateAts(htmlPath: string): Promise<AtsValidationResult> {
37
- const absoluteHtml = path.resolve(htmlPath);
38
- if (!fs.existsSync(absoluteHtml)) {
39
- throw new Error(`HTML file not found: ${absoluteHtml}`);
40
- }
41
-
42
- const browser = await chromium.launch({ headless: true });
43
- const page = await browser.newPage();
44
- await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
45
-
46
- const rawData = await page.evaluate(() => {
47
- const nameEl = document.querySelector('.candidate-name') || document.querySelector('h1');
48
- const candidateName = nameEl ? nameEl.textContent?.trim() || null : null;
49
-
50
- const bodyText = document.body.innerText || '';
51
- const emailMatch = bodyText.match(/[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/);
52
- const phoneMatch = bodyText.match(/1[3-9]\d{9}|\+?\d{1,4}[-\s]?\d{7,11}/);
53
-
54
- const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4')).map(h => ({
55
- level: h.tagName.toLowerCase(),
56
- text: h.textContent?.replace(/[\s\n]+/g, ' ').trim() || ''
57
- }));
58
-
59
- return {
60
- candidateName,
61
- email: emailMatch ? emailMatch[0] : null,
62
- phone: phoneMatch ? phoneMatch[0] : null,
63
- headings,
64
- totalTextLength: bodyText.trim().length
65
- };
66
- });
67
-
68
- await browser.close();
69
-
70
- const warnings: string[] = [];
71
- const errors: string[] = [];
72
-
73
- if (!rawData.candidateName) {
74
- errors.push('Candidate name (.candidate-name or h1) could not be extracted');
75
- }
76
-
77
- if (!rawData.email) {
78
- warnings.push('No valid email address pattern detected in text flow');
79
- }
80
-
81
- if (!rawData.phone) {
82
- warnings.push('No standard mobile phone pattern detected in text flow');
83
- }
84
-
85
- const validatedHeadings = rawData.headings.map(h => {
86
- const textClean = h.text.toLowerCase();
87
- const isStandard = STANDARD_SECTION_HEADERS.some(sh => textClean.includes(sh.toLowerCase()));
88
- return {
89
- ...h,
90
- isStandard
91
- };
92
- });
93
-
94
- const nonStdHeadings = validatedHeadings.filter(h => !h.isStandard && h.level === 'h2');
95
- if (nonStdHeadings.length > 0) {
96
- warnings.push(`Non-standard section headings detected: ${nonStdHeadings.map(h => `"${h.text}"`).join(', ')}`);
97
- }
98
-
99
- const status: 'PASS' | 'WARN' | 'FAIL' = errors.length > 0 ? 'FAIL' : warnings.length > 0 ? 'WARN' : 'PASS';
100
-
101
- return {
102
- status,
103
- candidateName: rawData.candidateName,
104
- detectedContacts: {
105
- email: rawData.email,
106
- phone: rawData.phone
107
- },
108
- headingsFound: validatedHeadings,
109
- totalTextLength: rawData.totalTextLength,
110
- warnings,
111
- errors
112
- };
113
- }
114
-
115
- async function main() {
116
- const args = process.argv.slice(2);
117
- let htmlPath = 'workspace/resume.html';
118
- let jsonOutput = false;
119
-
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
- } else if (args[i] === '--json') {
125
- jsonOutput = true;
126
- } else if (!args[i].startsWith('-')) {
127
- htmlPath = args[i];
128
- }
129
- }
130
-
131
- try {
132
- const result = await validateAts(htmlPath);
133
-
134
- if (jsonOutput) {
135
- console.log(JSON.stringify(result, null, 2));
136
- process.exit(result.status === 'FAIL' ? 1 : 0);
137
- }
138
-
139
- console.log('======================================================');
140
- console.log(` KnowMe CareerForge — ATS Compliance QA: [${result.status}]`);
141
- console.log(` Candidate Name : ${result.candidateName || 'NOT FOUND'}`);
142
- console.log(` Phone Extracted: ${result.detectedContacts.phone || 'NOT DETECTED'}`);
143
- console.log(` Email Extracted: ${result.detectedContacts.email || 'NOT DETECTED'}`);
144
- console.log(` Text Extracted : ${result.totalTextLength} characters`);
145
- console.log('------------------------------------------------------');
146
- console.log(' Headings Structure:');
147
- for (const h of result.headingsFound) {
148
- const mark = h.isStandard ? '[✓]' : '[?]';
149
- console.log(` ${mark} <${h.level}> ${h.text}`);
150
- }
151
-
152
- if (result.warnings.length > 0) {
153
- console.log('\n[!] Warnings:');
154
- for (const w of result.warnings) {
155
- console.log(` - ⚠️ ${w}`);
156
- }
157
- }
158
-
159
- if (result.errors.length > 0) {
160
- console.log('\n[✗] Errors:');
161
- for (const e of result.errors) {
162
- console.log(` - ✗ ${e}`);
163
- }
164
- }
165
-
166
- console.log('======================================================');
167
- process.exit(result.status === 'FAIL' ? 1 : 0);
168
- } catch (err: unknown) {
169
- const errorMsg = err instanceof Error ? err.message : String(err);
170
- console.error(`[✗] ATS Validation Failed: ${errorMsg}`);
171
- process.exit(1);
172
- }
173
- }
174
-
175
- if (require.main === module) {
176
- main();
6
+ export function validateAts(htmlPath: string, expectedPages = 1, options: EngineOptions = {}): Promise<EngineResult> {
7
+ return engine.run(htmlPath, { ...options, expectedPages });
177
8
  }
9
+ if (require.main === module) engine.cli();
@@ -1,162 +1,8 @@
1
1
  #!/usr/bin/env ts-node
2
- /**
3
- * KnowMe CareerForge — Playwright DOM Layout & Overflow Inspector
4
- * 基于无头浏览器真实渲染计算 DOM 盒模型高度,检测单页/双页物理 A4 溢出与孤行。
5
- */
2
+ import engine = require('../rendering/engine-loader');
3
+ import type { EngineOptions, EngineResult } from '../rendering/browser-engine';
6
4
 
7
- import { chromium } from 'playwright';
8
- import * as path from 'path';
9
- import * as fs from 'fs';
10
-
11
- interface LayoutValidationResult {
12
- status: 'PASS' | 'OVERFLOW';
13
- expectedPages: number;
14
- totalPagesFound: number;
15
- pageDetails: Array<{
16
- pageIndex: number;
17
- actualHeightPx: number;
18
- allowedHeightPx: number;
19
- isOverflow: boolean;
20
- overflowDeltaPx: number;
21
- }>;
22
- overflowSections: Array<{
23
- selector: string;
24
- bottomPx: number;
25
- overflowByPx: number;
26
- }>;
27
- }
28
-
29
- export async function validateLayout(htmlPath: string, expectedPages: number = 1): Promise<LayoutValidationResult> {
30
- const absoluteHtml = path.resolve(htmlPath);
31
- if (!fs.existsSync(absoluteHtml)) {
32
- throw new Error(`HTML file not found: ${absoluteHtml}`);
33
- }
34
-
35
- const browser = await chromium.launch({ headless: true });
36
- const context = await browser.newContext({
37
- viewport: { width: 794, height: 1123 }, // A4 at 96 DPI
38
- deviceScaleFactor: 1
39
- });
40
-
41
- const page = await context.newPage();
42
- await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
43
- await page.evaluateHandle('document.fonts.ready');
44
-
45
- // A4 标准高度 (297mm in pixels at 96 DPI ~= 1122.5px)
46
- const A4_HEIGHT_PX = 1122.5;
47
-
48
- const evaluation = await page.evaluate((allowedHeight) => {
49
- const pages = Array.from(document.querySelectorAll('.resume-page'));
50
- const details = pages.map((p, idx) => {
51
- const rect = p.getBoundingClientRect();
52
- const actualHeight = Math.ceil(rect.height);
53
- const isOverflow = actualHeight > allowedHeight + 2; // 2px 浮点容差
54
- return {
55
- pageIndex: idx + 1,
56
- actualHeightPx: actualHeight,
57
- allowedHeightPx: allowedHeight,
58
- isOverflow,
59
- overflowDeltaPx: isOverflow ? Math.ceil(actualHeight - allowedHeight) : 0
60
- };
61
- });
62
-
63
- const overflowSections: Array<{ selector: string; bottomPx: number; overflowByPx: number }> = [];
64
- if (details.some(d => d.isOverflow)) {
65
- const items = Array.from(document.querySelectorAll('.experience-item, .project-item, .skills-content, .resume-section'));
66
- for (const item of items) {
67
- const rect = item.getBoundingClientRect();
68
- if (rect.bottom > allowedHeight) {
69
- overflowSections.push({
70
- selector: item.className ? `.${item.className.split(' ').join('.')}` : item.tagName.toLowerCase(),
71
- bottomPx: Math.ceil(rect.bottom),
72
- overflowByPx: Math.ceil(rect.bottom - allowedHeight)
73
- });
74
- }
75
- }
76
- }
77
-
78
- return {
79
- totalPagesFound: pages.length || 1,
80
- pageDetails: details.length ? details : [{
81
- pageIndex: 1,
82
- actualHeightPx: Math.ceil(document.body.getBoundingClientRect().height),
83
- allowedHeightPx: allowedHeight,
84
- isOverflow: document.body.getBoundingClientRect().height > allowedHeight + 2,
85
- overflowDeltaPx: Math.max(0, Math.ceil(document.body.getBoundingClientRect().height - allowedHeight))
86
- }],
87
- overflowSections
88
- };
89
- }, A4_HEIGHT_PX);
90
-
91
- await browser.close();
92
-
93
- const hasOverflow = evaluation.pageDetails.some(p => p.isOverflow) || evaluation.totalPagesFound > expectedPages;
94
-
95
- return {
96
- status: hasOverflow ? 'OVERFLOW' : 'PASS',
97
- expectedPages,
98
- totalPagesFound: evaluation.totalPagesFound,
99
- pageDetails: evaluation.pageDetails,
100
- overflowSections: evaluation.overflowSections
101
- };
102
- }
103
-
104
- async function main() {
105
- const args = process.argv.slice(2);
106
- let htmlPath = 'workspace/resume.html';
107
- let expectedPages = 1;
108
- let jsonOutput = false;
109
-
110
- for (let i = 0; i < args.length; i++) {
111
- if (args[i] === '--html' || args[i] === '-i') {
112
- htmlPath = args[i + 1];
113
- i++;
114
- } else if (args[i] === '--expected-pages' || args[i] === '-p') {
115
- expectedPages = parseInt(args[i + 1], 10) || 1;
116
- i++;
117
- } else if (args[i] === '--json') {
118
- jsonOutput = true;
119
- } else if (!args[i].startsWith('-')) {
120
- htmlPath = args[i];
121
- }
122
- }
123
-
124
- try {
125
- const result = await validateLayout(htmlPath, expectedPages);
126
-
127
- if (jsonOutput) {
128
- console.log(JSON.stringify(result, null, 2));
129
- process.exit(result.status === 'PASS' ? 0 : 1);
130
- }
131
-
132
- console.log('======================================================');
133
- console.log(` KnowMe CareerForge — Layout QA: ${result.status}`);
134
- console.log(` HTML Path : ${htmlPath}`);
135
- console.log(` Expected Pages : ${result.expectedPages} | Pages Found: ${result.totalPagesFound}`);
136
- console.log('------------------------------------------------------');
137
-
138
- for (const p of result.pageDetails) {
139
- const fitText = p.isOverflow ? `[✗ OVERFLOW by ${p.overflowDeltaPx}px]` : `[✓ FIT] (Overflow: ${p.overflowDeltaPx}px)`;
140
- console.log(` Page ${p.pageIndex}: ${p.actualHeightPx}px / ${p.allowedHeightPx}px ${fitText}`);
141
- }
142
-
143
- if (result.overflowSections.length > 0) {
144
- console.log('\n[!] Offending Overflow Elements:');
145
- for (const el of result.overflowSections.slice(0, 3)) {
146
- console.log(` - Element "${el.selector}" exceeds A4 baseline by ${el.overflowByPx}px (Bottom: ${el.bottomPx}px)`);
147
- }
148
- console.log('\n💡 Recommendation: Reduce --resume-space-section by 1~2pt or reduce --resume-font-size-body by 0.2pt in <style>.');
149
- }
150
-
151
- console.log('======================================================');
152
- process.exit(result.status === 'PASS' ? 0 : 1);
153
- } catch (err: unknown) {
154
- const errorMsg = err instanceof Error ? err.message : String(err);
155
- console.error(`[✗] Layout Validation Failed: ${errorMsg}`);
156
- process.exit(1);
157
- }
158
- }
159
-
160
- if (require.main === module) {
161
- main();
5
+ export function validateLayout(htmlPath: string, expectedPages = 1, options: EngineOptions = {}): Promise<EngineResult> {
6
+ return engine.run(htmlPath, { ...options, expectedPages });
162
7
  }
8
+ if (require.main === module) engine.cli();
@@ -1,148 +1,37 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- KnowMe CareerForge — Resume Validation Engine (Layout, Schema & ATS Compliance)
4
- 验证 HTML 工作区简历的语义结构、Design Tokens 完整度与单页/双页排版合规性。
5
- """
6
-
7
- import sys, os, json, argparse, re
2
+ """Validate print DOM and an actual temporary PDF through the shared engine."""
3
+ import argparse
4
+ import json
5
+ import sys
8
6
  from pathlib import Path
9
7
 
10
- def get_project_root() -> Path:
11
- curr = Path(__file__).resolve().parent
12
- for _ in range(5):
13
- if (curr / "SKILL.md").exists() or (curr / "package.json").exists():
14
- return curr
15
- curr = curr.parent
16
- return Path.cwd()
17
-
18
- def validate_resume_html(html_path: str, expected_pages: int = 1) -> dict:
19
- p = Path(html_path)
20
- if not p.exists():
21
- root = get_project_root()
22
- alt = root / html_path
23
- if alt.exists():
24
- p = alt
25
- else:
26
- return {
27
- "status": "FAIL",
28
- "file": str(p),
29
- "errors": [f"File not found: {html_path}"],
30
- "warnings": [],
31
- "checks": {}
32
- }
33
-
34
- content = p.read_text(encoding="utf-8")
35
- errors = []
36
- warnings = []
37
- checks = {}
38
-
39
- # 1. 检查语义化容器
40
- if '<div class="resume-page"' not in content and "<div class='resume-page'" not in content:
41
- errors.append("Missing root container <div class='resume-page'>")
42
- else:
43
- checks["root_container"] = "PASS"
44
-
45
- # 2. 检查基本信息字段
46
- if 'candidate-name' not in content:
47
- errors.append("Missing candidate name element (.candidate-name)")
48
- else:
49
- checks["candidate_name"] = "PASS"
50
-
51
- # 3. 检查 Design Tokens (:root CSS 变量)
52
- required_tokens = [
53
- "--resume-page-width",
54
- "--resume-page-min-height",
55
- "--resume-font-size-body",
56
- "--resume-space-section",
57
- "--resume-color-primary"
58
- ]
59
- missing_tokens = []
60
- for token in required_tokens:
61
- if token not in content:
62
- missing_tokens.append(token)
63
-
64
- if missing_tokens:
65
- warnings.append(f"Missing recommended CSS Design Tokens: {', '.join(missing_tokens)}")
66
- else:
67
- checks["design_tokens"] = "PASS"
8
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "rendering"))
9
+ from browser_engine import run, exit_code
68
10
 
69
- # 4. 检查打印分页规则
70
- if "@page" not in content:
71
- warnings.append("Missing @page print declaration in CSS")
72
- if "page-break-after" not in content and "break-after" not in content:
73
- warnings.append("Missing page-break-after CSS rules for multi-page/A4 constraints")
74
11
 
75
- # 5. 检查核心内容模块
76
- sections = ["experience", "projects", "skills", "education"]
77
- detected_sections = []
78
- for s in sections:
79
- if f'id="{s}"' in content or f"id='{s}'" in content or s in content.lower():
80
- detected_sections.append(s)
12
+ def validate_resume_html(html_path, expected_pages=1, auto_heal=False):
13
+ return run(html_path, expected_pages=expected_pages, auto_heal=auto_heal)
81
14
 
82
- checks["detected_sections"] = detected_sections
83
15
 
84
- # 6. 单页高度预估 (可见文字量)
85
- body_text_only = re.sub(r'<style.*?>.*?</style>', '', content, flags=re.DOTALL)
86
- visible_text = re.sub(r'<[^>]+>', '', body_text_only).strip()
87
- char_count = len(re.sub(r'\s+', ' ', visible_text))
16
+ def auto_heal_resume(html_path, max_pages=1):
17
+ result = validate_resume_html(html_path, max_pages, True)
18
+ return {**result.get("checks", {}).get("auto_heal", {}),
19
+ "healed": result["status"] == "PASS", "status": result["status"],
20
+ "errors": result["errors"]}
88
21
 
89
- density_status = "OPTIMAL"
90
- if expected_pages == 1:
91
- if char_count > 1600:
92
- warnings.append(f"High text density for 1 page: {char_count} chars (recommend trimming or reducing spacing tokens)")
93
- density_status = "OVERFLOW_RISK"
94
- elif char_count < 300:
95
- warnings.append(f"Low content volume for 1 page: {char_count} chars")
96
- density_status = "UNDERFLOW"
97
-
98
- checks["char_count"] = char_count
99
- checks["density_status"] = density_status
100
-
101
- passed = len(errors) == 0
102
-
103
- return {
104
- "status": "PASS" if passed else "FAIL",
105
- "file": str(p.resolve()),
106
- "expectedPages": expected_pages,
107
- "errors": errors,
108
- "warnings": warnings,
109
- "checks": checks
110
- }
111
22
 
112
23
  def main():
113
- parser = argparse.ArgumentParser(description="KnowMe CareerForge — Resume Validator")
114
- parser.add_argument("path", nargs="?", default="workspace/resume.html", help="Path to resume.html")
115
- parser.add_argument("--html", help="Path to resume.html (named)")
116
- parser.add_argument("--expected-pages", "-p", type=int, default=1, help="Expected target pages (1 or 2)")
117
- parser.add_argument("--json", action="store_true", help="Output JSON result")
118
-
24
+ parser = argparse.ArgumentParser(description=__doc__)
25
+ parser.add_argument("path", nargs="?", default="workspace/resume.html")
26
+ parser.add_argument("--html")
27
+ parser.add_argument("--expected-pages", "-p", type=int, default=1)
28
+ parser.add_argument("--auto-heal", action="store_true")
29
+ parser.add_argument("--json", action="store_true")
119
30
  args = parser.parse_args()
120
- target_html = args.html or args.path
121
- result = validate_resume_html(target_html, expected_pages=args.expected_pages)
122
- if args.json:
123
- print(json.dumps(result, ensure_ascii=False, indent=2))
124
- return
125
-
126
- print("=" * 70)
127
- print(f" KnowMe CareerForge — Resume Validation: {result['status']}")
128
- print(f" Target File : {result['file']}")
129
- print(f" Expected Pages : {result['expectedPages']}")
130
- print(f" Text Density : {result.get('checks', {}).get('density_status', 'N/A')} ({result.get('checks', {}).get('char_count', 0)} visible chars)")
131
- print(f" Sections Found : {', '.join(result.get('checks', {}).get('detected_sections', []))}")
132
-
133
- if result.get("errors"):
134
- print("\n[!] Errors:")
135
- for e in result["errors"]:
136
- print(f" - ✗ {e}")
137
-
138
- if result.get("warnings"):
139
- print("\n[?] Warnings:")
140
- for w in result["warnings"]:
141
- print(f" - ⚠️ {w}")
31
+ result = validate_resume_html(args.html or args.path, args.expected_pages, args.auto_heal)
32
+ print(json.dumps(result, ensure_ascii=False))
33
+ return exit_code(result)
142
34
 
143
- print("=" * 70)
144
- if result["status"] != "PASS":
145
- sys.exit(1)
146
35
 
147
36
  if __name__ == "__main__":
148
- main()
37
+ raise SystemExit(main())
package/skill.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/skill.json",
3
3
  "name": "knowme-careerforge",
4
4
  "displayName": "KnowMe CareerForge",
5
- "version": "0.0.2",
5
+ "version": "0.0.6",
6
6
  "description": "An agent-native skill for self-discovery, career positioning, and tailored resume engineering. Know yourself. Define your direction. Forge your opportunity.",
7
7
  "author": "Miles <https://github.com/Max-Samson>",
8
8
  "license": "MIT",