@forwardimpact/pathway 0.1.0 → 0.3.0
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/app/commands/agent.js +119 -31
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +52 -33
- package/app/commands/progress.js +14 -7
- package/app/commands/serve.js +5 -0
- package/app/commands/stage.js +0 -10
- package/app/commands/track.js +5 -8
- package/app/components/builder.js +117 -30
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +30 -115
- package/app/formatters/agent/skill.js +23 -44
- package/app/formatters/behaviour/dom.js +3 -0
- package/app/formatters/behaviour/microdata.js +106 -0
- package/app/formatters/discipline/dom.js +28 -1
- package/app/formatters/discipline/microdata.js +117 -0
- package/app/formatters/discipline/shared.js +49 -8
- package/app/formatters/driver/dom.js +3 -0
- package/app/formatters/driver/microdata.js +91 -0
- package/app/formatters/grade/dom.js +5 -4
- package/app/formatters/grade/microdata.js +151 -0
- package/app/formatters/index.js +32 -1
- package/app/formatters/interview/shared.js +13 -8
- package/app/formatters/job/description.js +70 -81
- package/app/formatters/job/dom.js +40 -113
- package/app/formatters/job/markdown.js +17 -13
- package/app/formatters/json-ld.js +242 -0
- package/app/formatters/microdata-shared.js +184 -0
- package/app/formatters/progress/shared.js +14 -11
- package/app/formatters/shared.js +7 -2
- package/app/formatters/skill/dom.js +3 -0
- package/app/formatters/skill/microdata.js +151 -0
- package/app/formatters/stage/dom.js +3 -18
- package/app/formatters/stage/microdata.js +110 -0
- package/app/formatters/stage/shared.js +0 -27
- package/app/formatters/track/dom.js +5 -30
- package/app/formatters/track/markdown.js +2 -25
- package/app/formatters/track/microdata.js +111 -0
- package/app/formatters/track/shared.js +6 -58
- package/app/handout-main.js +26 -12
- package/app/handout.html +7 -0
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/form-controls.js +64 -1
- package/app/lib/job-cache.js +12 -9
- package/app/lib/render.js +8 -1
- package/app/lib/template-loader.js +75 -0
- package/app/lib/yaml-loader.js +25 -8
- package/app/main.js +8 -4
- package/app/model/agent.js +158 -130
- package/app/model/checklist.js +57 -91
- package/app/model/derivation.js +135 -68
- package/app/model/index-generator.js +1 -7
- package/app/model/job.js +19 -13
- package/app/model/levels.js +20 -12
- package/app/model/loader.js +41 -17
- package/app/model/matching.js +33 -3
- package/app/model/profile.js +38 -45
- package/app/model/schema-validation.js +438 -0
- package/app/model/validation.js +747 -68
- package/app/pages/agent-builder.js +125 -28
- package/app/pages/assessment-results.js +10 -4
- package/app/pages/discipline.js +36 -6
- package/app/pages/driver.js +9 -47
- package/app/pages/interview-builder.js +3 -1
- package/app/pages/interview.js +15 -4
- package/app/pages/job-builder.js +4 -1
- package/app/pages/job.js +43 -8
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +78 -26
- package/app/pages/self-assessment.js +3 -3
- package/app/pages/stage.js +3 -126
- package/app/slide-main.js +45 -17
- package/app/slides/index.js +3 -1
- package/app/slides/overview.js +40 -4
- package/app/slides/progress.js +4 -2
- package/app/slides.html +7 -0
- package/bin/pathway.js +28 -75
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
- package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
- package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
- package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
- package/examples/agents/.vscode/settings.json +1 -1
- package/examples/behaviours/outcome_ownership.yaml +1 -2
- package/examples/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/behaviours/precise_communication.yaml +1 -2
- package/examples/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/behaviours/systems_thinking.yaml +1 -2
- package/examples/capabilities/business.yaml +80 -142
- package/examples/capabilities/delivery.yaml +155 -219
- package/examples/capabilities/people.yaml +2 -34
- package/examples/capabilities/reliability.yaml +161 -80
- package/examples/capabilities/scale.yaml +234 -252
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +1 -0
- package/examples/disciplines/data_engineering.yaml +14 -12
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +14 -12
- package/examples/drivers.yaml +1 -4
- package/examples/framework.yaml +1 -2
- package/examples/grades.yaml +14 -15
- package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
- package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/questions/behaviours/precise_communication.yaml +1 -2
- package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/questions/behaviours/systems_thinking.yaml +1 -2
- package/examples/questions/skills/architecture_design.yaml +1 -2
- package/examples/questions/skills/cloud_platforms.yaml +1 -2
- package/examples/questions/skills/code_quality.yaml +1 -2
- package/examples/questions/skills/data_modeling.yaml +1 -2
- package/examples/questions/skills/devops.yaml +1 -2
- package/examples/questions/skills/full_stack_development.yaml +1 -2
- package/examples/questions/skills/sre_practices.yaml +1 -2
- package/examples/questions/skills/stakeholder_management.yaml +1 -2
- package/examples/questions/skills/team_collaboration.yaml +1 -2
- package/examples/questions/skills/technical_writing.yaml +1 -2
- package/examples/self-assessments.yaml +1 -3
- package/examples/stages.yaml +101 -46
- package/examples/tracks/_index.yaml +0 -1
- package/examples/tracks/platform.yaml +8 -13
- package/examples/tracks/sre.yaml +8 -18
- package/examples/vscode-settings.yaml +2 -7
- package/package.json +9 -3
- package/templates/agent.template.md +65 -0
- package/templates/job.template.md +47 -0
- package/templates/skill.template.md +28 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
- package/examples/tracks/manager.yaml +0 -53
|
@@ -3,41 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Formats job data into markdown job description content.
|
|
5
5
|
* Parallels formatters/agent/profile.js in structure.
|
|
6
|
+
*
|
|
7
|
+
* Uses Mustache templates for flexible output formatting.
|
|
8
|
+
* Templates are loaded from data/ directory with fallback to templates/ directory.
|
|
6
9
|
*/
|
|
7
10
|
|
|
11
|
+
import Mustache from "mustache";
|
|
12
|
+
|
|
8
13
|
import {
|
|
9
14
|
SKILL_LEVEL_ORDER,
|
|
10
15
|
BEHAVIOUR_MATURITY_ORDER,
|
|
11
16
|
} from "../../model/levels.js";
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
|
-
*
|
|
19
|
+
* Prepare job data for template rendering
|
|
15
20
|
* @param {Object} params
|
|
16
21
|
* @param {Object} params.job - The job definition
|
|
17
22
|
* @param {Object} params.discipline - The discipline
|
|
18
23
|
* @param {Object} params.grade - The grade
|
|
19
|
-
* @param {Object} params.track - The track
|
|
20
|
-
* @returns {
|
|
24
|
+
* @param {Object} [params.track] - The track (optional)
|
|
25
|
+
* @returns {Object} Data object ready for Mustache template
|
|
21
26
|
*/
|
|
22
|
-
|
|
23
|
-
const lines = [];
|
|
24
|
-
|
|
25
|
-
// Title
|
|
26
|
-
lines.push(`# ${job.title}`);
|
|
27
|
-
lines.push("");
|
|
28
|
-
|
|
29
|
-
// Meta information
|
|
30
|
-
lines.push(`- **Level:** ${grade.id}`);
|
|
31
|
-
lines.push(`- **Experience:** ${grade.typicalExperienceRange}`);
|
|
32
|
-
lines.push(`- **Track:** ${track.name}`);
|
|
33
|
-
lines.push("");
|
|
34
|
-
|
|
35
|
-
// Role Summary
|
|
36
|
-
lines.push("## ROLE SUMMARY");
|
|
37
|
-
lines.push("");
|
|
38
|
-
|
|
27
|
+
function prepareJobDescriptionData({ job, discipline, grade, track }) {
|
|
39
28
|
// Build role summary from discipline - use manager version if applicable
|
|
40
|
-
const isManagement =
|
|
29
|
+
const isManagement = discipline.isManagement === true;
|
|
41
30
|
let roleSummary =
|
|
42
31
|
isManagement && discipline.managementRoleSummary
|
|
43
32
|
? discipline.managementRoleSummary
|
|
@@ -46,16 +35,9 @@ export function formatJobDescription({ job, discipline, grade, track }) {
|
|
|
46
35
|
const { roleTitle, specialization } = discipline;
|
|
47
36
|
roleSummary = roleSummary.replace(/\{roleTitle\}/g, roleTitle);
|
|
48
37
|
roleSummary = roleSummary.replace(/\{specialization\}/g, specialization);
|
|
49
|
-
lines.push(roleSummary);
|
|
50
|
-
lines.push("");
|
|
51
|
-
|
|
52
|
-
// Add track context
|
|
53
|
-
if (track.roleContext) {
|
|
54
|
-
lines.push(track.roleContext);
|
|
55
|
-
lines.push("");
|
|
56
|
-
}
|
|
57
38
|
|
|
58
|
-
//
|
|
39
|
+
// Build expectations paragraph
|
|
40
|
+
let expectationsParagraph = "";
|
|
59
41
|
if (job.expectations) {
|
|
60
42
|
const exp = job.expectations;
|
|
61
43
|
const expectationSentences = [];
|
|
@@ -87,45 +69,20 @@ export function formatJobDescription({ job, discipline, grade, track }) {
|
|
|
87
69
|
}
|
|
88
70
|
|
|
89
71
|
if (expectationSentences.length > 0) {
|
|
90
|
-
|
|
91
|
-
lines.push("");
|
|
72
|
+
expectationsParagraph = expectationSentences.join(" ");
|
|
92
73
|
}
|
|
93
74
|
}
|
|
94
75
|
|
|
95
|
-
// Key Responsibilities
|
|
96
|
-
lines.push("## ROLE RESPONSIBILITIES");
|
|
97
|
-
lines.push("");
|
|
98
|
-
|
|
99
|
-
// Use derived responsibilities (already sorted by level descending)
|
|
100
|
-
const derivedResponsibilities = job.derivedResponsibilities || [];
|
|
101
|
-
|
|
102
|
-
for (const r of derivedResponsibilities) {
|
|
103
|
-
lines.push(`- **${r.capabilityName}:** ${r.responsibility}`);
|
|
104
|
-
}
|
|
105
|
-
lines.push("");
|
|
106
|
-
|
|
107
|
-
// Key Behaviours
|
|
108
|
-
lines.push("## ROLE BEHAVIOURS");
|
|
109
|
-
lines.push("");
|
|
110
|
-
|
|
111
76
|
// Sort behaviours by maturity level (highest first)
|
|
112
77
|
const sortedBehaviours = [...job.behaviourProfile].sort((a, b) => {
|
|
113
78
|
const indexA = BEHAVIOUR_MATURITY_ORDER.indexOf(a.maturity);
|
|
114
79
|
const indexB = BEHAVIOUR_MATURITY_ORDER.indexOf(b.maturity);
|
|
115
|
-
// Sort in reverse order (exemplifying first, emerging last)
|
|
116
80
|
if (indexA === -1 && indexB === -1) return 0;
|
|
117
81
|
if (indexA === -1) return 1;
|
|
118
82
|
if (indexB === -1) return -1;
|
|
119
83
|
return indexB - indexA;
|
|
120
84
|
});
|
|
121
85
|
|
|
122
|
-
for (const behaviour of sortedBehaviours) {
|
|
123
|
-
lines.push(
|
|
124
|
-
`- **${behaviour.behaviourName}:** ${behaviour.maturityDescription || ""}`,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
lines.push("");
|
|
128
|
-
|
|
129
86
|
// Group skills by level
|
|
130
87
|
const skillsByLevel = {};
|
|
131
88
|
for (const skill of job.skillMatrix) {
|
|
@@ -136,41 +93,73 @@ export function formatJobDescription({ job, discipline, grade, track }) {
|
|
|
136
93
|
skillsByLevel[level].push(skill);
|
|
137
94
|
}
|
|
138
95
|
|
|
139
|
-
// Sort levels in
|
|
96
|
+
// Sort levels in reverse order (expert first, awareness last)
|
|
140
97
|
const sortedLevels = Object.keys(skillsByLevel).sort((a, b) => {
|
|
141
98
|
const indexA = SKILL_LEVEL_ORDER.indexOf(a.toLowerCase());
|
|
142
99
|
const indexB = SKILL_LEVEL_ORDER.indexOf(b.toLowerCase());
|
|
143
|
-
// Sort in reverse order (expert first, awareness last)
|
|
144
100
|
if (indexA === -1 && indexB === -1) return a.localeCompare(b);
|
|
145
101
|
if (indexA === -1) return 1;
|
|
146
102
|
if (indexB === -1) return -1;
|
|
147
103
|
return indexB - indexA;
|
|
148
104
|
});
|
|
149
105
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (skills.length > 0) {
|
|
153
|
-
lines.push(`## ${level.toUpperCase()}-LEVEL SKILLS`);
|
|
154
|
-
lines.push("");
|
|
155
|
-
// Sort skills alphabetically by name
|
|
156
|
-
const sortedSkills = [...skills].sort((a, b) =>
|
|
157
|
-
(a.skillName || "").localeCompare(b.skillName || ""),
|
|
158
|
-
);
|
|
159
|
-
for (const skill of sortedSkills) {
|
|
160
|
-
lines.push(`- **${skill.skillName}:** ${skill.levelDescription || ""}`);
|
|
161
|
-
}
|
|
162
|
-
lines.push("");
|
|
163
|
-
}
|
|
164
|
-
}
|
|
106
|
+
// Keep only the top 2 skill levels for job descriptions
|
|
107
|
+
const topLevels = sortedLevels.slice(0, 2);
|
|
165
108
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
109
|
+
// Build skill levels array for template
|
|
110
|
+
const skillLevels = topLevels.map((level) => {
|
|
111
|
+
const skills = skillsByLevel[level];
|
|
112
|
+
const sortedSkills = [...skills].sort((a, b) =>
|
|
113
|
+
(a.skillName || "").localeCompare(b.skillName || ""),
|
|
114
|
+
);
|
|
115
|
+
return {
|
|
116
|
+
levelHeading: `${level.toUpperCase()}-LEVEL SKILLS`,
|
|
117
|
+
skills: sortedSkills.map((s) => ({
|
|
118
|
+
skillName: s.skillName,
|
|
119
|
+
levelDescription: s.levelDescription || "",
|
|
120
|
+
})),
|
|
121
|
+
};
|
|
122
|
+
});
|
|
169
123
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
124
|
+
return {
|
|
125
|
+
title: job.title,
|
|
126
|
+
gradeId: grade.id,
|
|
127
|
+
typicalExperienceRange: grade.typicalExperienceRange,
|
|
128
|
+
trackName: track?.name || null,
|
|
129
|
+
roleSummary,
|
|
130
|
+
trackRoleContext: track?.roleContext || null,
|
|
131
|
+
expectationsParagraph: expectationsParagraph || null,
|
|
132
|
+
responsibilities: (job.derivedResponsibilities || []).map((r) => ({
|
|
133
|
+
capabilityName: r.capabilityName,
|
|
134
|
+
responsibility: r.responsibility,
|
|
135
|
+
})),
|
|
136
|
+
behaviours: sortedBehaviours.map((b) => ({
|
|
137
|
+
behaviourName: b.behaviourName,
|
|
138
|
+
maturityDescription: b.maturityDescription || "",
|
|
139
|
+
})),
|
|
140
|
+
skillLevels,
|
|
141
|
+
qualificationSummary:
|
|
142
|
+
(grade.qualificationSummary || "").replace(
|
|
143
|
+
/\{typicalExperienceRange\}/g,
|
|
144
|
+
grade.typicalExperienceRange || "",
|
|
145
|
+
) || null,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
174
148
|
|
|
175
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Format job as a markdown job description using Mustache template
|
|
151
|
+
* @param {Object} params
|
|
152
|
+
* @param {Object} params.job - The job definition
|
|
153
|
+
* @param {Object} params.discipline - The discipline
|
|
154
|
+
* @param {Object} params.grade - The grade
|
|
155
|
+
* @param {Object} [params.track] - The track (optional)
|
|
156
|
+
* @param {string} template - Mustache template string
|
|
157
|
+
* @returns {string} Markdown formatted job description
|
|
158
|
+
*/
|
|
159
|
+
export function formatJobDescription(
|
|
160
|
+
{ job, discipline, grade, track },
|
|
161
|
+
template,
|
|
162
|
+
) {
|
|
163
|
+
const data = prepareJobDescriptionData({ job, discipline, grade, track });
|
|
164
|
+
return Mustache.render(template, data);
|
|
176
165
|
}
|
|
@@ -2,18 +2,7 @@
|
|
|
2
2
|
* Job formatting for DOM/web output
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
div,
|
|
7
|
-
h1,
|
|
8
|
-
h2,
|
|
9
|
-
p,
|
|
10
|
-
a,
|
|
11
|
-
span,
|
|
12
|
-
button,
|
|
13
|
-
section,
|
|
14
|
-
details,
|
|
15
|
-
summary,
|
|
16
|
-
} from "../../lib/render.js";
|
|
5
|
+
import { div, h1, h2, p, a, span, button, section } from "../../lib/render.js";
|
|
17
6
|
import { createBackLink } from "../../components/nav.js";
|
|
18
7
|
import {
|
|
19
8
|
createDetailSection,
|
|
@@ -39,6 +28,7 @@ import { formatJobDescription } from "./description.js";
|
|
|
39
28
|
* @param {Object} [options.discipline] - Discipline entity for job description
|
|
40
29
|
* @param {Object} [options.grade] - Grade entity for job description
|
|
41
30
|
* @param {Object} [options.track] - Track entity for job description
|
|
31
|
+
* @param {string} [options.jobTemplate] - Mustache template for job description
|
|
42
32
|
* @returns {HTMLElement}
|
|
43
33
|
*/
|
|
44
34
|
export function jobToDOM(view, options = {}) {
|
|
@@ -50,9 +40,10 @@ export function jobToDOM(view, options = {}) {
|
|
|
50
40
|
discipline,
|
|
51
41
|
grade,
|
|
52
42
|
track,
|
|
43
|
+
jobTemplate,
|
|
53
44
|
} = options;
|
|
54
45
|
|
|
55
|
-
const hasEntities = discipline && grade &&
|
|
46
|
+
const hasEntities = discipline && grade && jobTemplate;
|
|
56
47
|
|
|
57
48
|
return div(
|
|
58
49
|
{ className: "job-detail-page" },
|
|
@@ -108,6 +99,7 @@ export function jobToDOM(view, options = {}) {
|
|
|
108
99
|
discipline,
|
|
109
100
|
grade,
|
|
110
101
|
track,
|
|
102
|
+
template: jobTemplate,
|
|
111
103
|
})
|
|
112
104
|
: null,
|
|
113
105
|
|
|
@@ -140,14 +132,6 @@ export function jobToDOM(view, options = {}) {
|
|
|
140
132
|
),
|
|
141
133
|
})
|
|
142
134
|
: null,
|
|
143
|
-
|
|
144
|
-
// Handoff Checklists
|
|
145
|
-
view.checklists && hasChecklistItems(view.checklists)
|
|
146
|
-
? createDetailSection({
|
|
147
|
-
title: "📋 Handoff Checklists",
|
|
148
|
-
content: createChecklistSections(view.checklists),
|
|
149
|
-
})
|
|
150
|
-
: null,
|
|
151
135
|
)
|
|
152
136
|
: null,
|
|
153
137
|
|
|
@@ -164,6 +148,7 @@ export function jobToDOM(view, options = {}) {
|
|
|
164
148
|
discipline,
|
|
165
149
|
grade,
|
|
166
150
|
track,
|
|
151
|
+
template: jobTemplate,
|
|
167
152
|
})
|
|
168
153
|
: null,
|
|
169
154
|
);
|
|
@@ -211,84 +196,6 @@ function getScoreColor(score) {
|
|
|
211
196
|
return "#ef4444"; // Red
|
|
212
197
|
}
|
|
213
198
|
|
|
214
|
-
/**
|
|
215
|
-
* Check if any checklist has items
|
|
216
|
-
* @param {Object} checklists - Checklists object keyed by handoff type
|
|
217
|
-
* @returns {boolean}
|
|
218
|
-
*/
|
|
219
|
-
function hasChecklistItems(checklists) {
|
|
220
|
-
for (const items of Object.values(checklists)) {
|
|
221
|
-
if (items && items.length > 0) {
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Create collapsible checklist sections for all handoffs
|
|
230
|
-
* @param {Object} checklists - Checklists object keyed by handoff type
|
|
231
|
-
* @returns {HTMLElement}
|
|
232
|
-
*/
|
|
233
|
-
function createChecklistSections(checklists) {
|
|
234
|
-
const handoffLabels = {
|
|
235
|
-
plan_to_code: "📋 → 💻 Plan → Code",
|
|
236
|
-
code_to_review: "💻 → ✅ Code → Review",
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const sections = Object.entries(checklists)
|
|
240
|
-
.filter(([_, items]) => items && items.length > 0)
|
|
241
|
-
.map(([handoff, groups]) => {
|
|
242
|
-
const label = handoffLabels[handoff] || handoff;
|
|
243
|
-
const totalItems = groups.reduce((sum, g) => sum + g.items.length, 0);
|
|
244
|
-
|
|
245
|
-
return details(
|
|
246
|
-
{ className: "checklist-section" },
|
|
247
|
-
summary(
|
|
248
|
-
{ className: "checklist-section-header" },
|
|
249
|
-
span({ className: "checklist-section-label" }, label),
|
|
250
|
-
span({ className: "badge badge-default" }, `${totalItems} items`),
|
|
251
|
-
),
|
|
252
|
-
div(
|
|
253
|
-
{ className: "checklist-section-content" },
|
|
254
|
-
...groups.map((group) => createChecklistGroup(group)),
|
|
255
|
-
),
|
|
256
|
-
);
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
return div({ className: "checklist-sections" }, ...sections);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Create a checklist group for a capability
|
|
264
|
-
* @param {Object} group - Group with capability, level, and items
|
|
265
|
-
* @returns {HTMLElement}
|
|
266
|
-
*/
|
|
267
|
-
function createChecklistGroup(group) {
|
|
268
|
-
const emoji = group.capability.emoji || "📌";
|
|
269
|
-
const capabilityName = group.capability.name || group.capability.id;
|
|
270
|
-
|
|
271
|
-
return div(
|
|
272
|
-
{ className: "checklist-group" },
|
|
273
|
-
div(
|
|
274
|
-
{ className: "checklist-group-header" },
|
|
275
|
-
span({ className: "checklist-emoji" }, emoji),
|
|
276
|
-
span({ className: "checklist-capability" }, capabilityName),
|
|
277
|
-
span({ className: "badge badge-secondary" }, group.level),
|
|
278
|
-
),
|
|
279
|
-
div(
|
|
280
|
-
{ className: "checklist-items" },
|
|
281
|
-
...group.items.map((item) =>
|
|
282
|
-
div(
|
|
283
|
-
{ className: "checklist-item" },
|
|
284
|
-
span({ className: "checklist-checkbox" }, "☐"),
|
|
285
|
-
span({}, item),
|
|
286
|
-
),
|
|
287
|
-
),
|
|
288
|
-
),
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
199
|
/**
|
|
293
200
|
* Create the job description section with copy button
|
|
294
201
|
* @param {Object} params
|
|
@@ -296,15 +203,25 @@ function createChecklistGroup(group) {
|
|
|
296
203
|
* @param {Object} params.discipline - The discipline
|
|
297
204
|
* @param {Object} params.grade - The grade
|
|
298
205
|
* @param {Object} params.track - The track
|
|
206
|
+
* @param {string} params.template - Mustache template for job description
|
|
299
207
|
* @returns {HTMLElement} The job description section element
|
|
300
208
|
*/
|
|
301
|
-
export function createJobDescriptionSection({
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
209
|
+
export function createJobDescriptionSection({
|
|
210
|
+
job,
|
|
211
|
+
discipline,
|
|
212
|
+
grade,
|
|
213
|
+
track,
|
|
214
|
+
template,
|
|
215
|
+
}) {
|
|
216
|
+
const markdown = formatJobDescription(
|
|
217
|
+
{
|
|
218
|
+
job,
|
|
219
|
+
discipline,
|
|
220
|
+
grade,
|
|
221
|
+
track,
|
|
222
|
+
},
|
|
223
|
+
template,
|
|
224
|
+
);
|
|
308
225
|
|
|
309
226
|
const copyButton = button(
|
|
310
227
|
{
|
|
@@ -388,15 +305,25 @@ export function createJobDescriptionSection({ job, discipline, grade, track }) {
|
|
|
388
305
|
* @param {Object} params.discipline - The discipline
|
|
389
306
|
* @param {Object} params.grade - The grade
|
|
390
307
|
* @param {Object} params.track - The track
|
|
308
|
+
* @param {string} params.template - Mustache template for job description
|
|
391
309
|
* @returns {HTMLElement} The job description HTML element (print-only)
|
|
392
310
|
*/
|
|
393
|
-
export function createJobDescriptionHtml({
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
311
|
+
export function createJobDescriptionHtml({
|
|
312
|
+
job,
|
|
313
|
+
discipline,
|
|
314
|
+
grade,
|
|
315
|
+
track,
|
|
316
|
+
template,
|
|
317
|
+
}) {
|
|
318
|
+
const markdown = formatJobDescription(
|
|
319
|
+
{
|
|
320
|
+
job,
|
|
321
|
+
discipline,
|
|
322
|
+
grade,
|
|
323
|
+
track,
|
|
324
|
+
},
|
|
325
|
+
template,
|
|
326
|
+
);
|
|
400
327
|
|
|
401
328
|
const html = markdownToHtml(markdown);
|
|
402
329
|
|
|
@@ -15,9 +15,10 @@ import { SKILL_LEVEL_ORDER } from "../../model/levels.js";
|
|
|
15
15
|
* Format job detail as markdown
|
|
16
16
|
* @param {Object} view - Job detail view from presenter
|
|
17
17
|
* @param {Object} [entities] - Original entities (for job description)
|
|
18
|
+
* @param {string} [jobTemplate] - Mustache template for job description
|
|
18
19
|
* @returns {string}
|
|
19
20
|
*/
|
|
20
|
-
export function jobToMarkdown(view, entities = {}) {
|
|
21
|
+
export function jobToMarkdown(view, entities = {}, jobTemplate) {
|
|
21
22
|
const lines = [
|
|
22
23
|
`# ${view.title}`,
|
|
23
24
|
"",
|
|
@@ -77,23 +78,26 @@ export function jobToMarkdown(view, entities = {}) {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// Job Description (copyable markdown)
|
|
80
|
-
if (entities.discipline && entities.grade &&
|
|
81
|
+
if (entities.discipline && entities.grade && jobTemplate) {
|
|
81
82
|
lines.push("---", "");
|
|
82
83
|
lines.push("## Job Description", "");
|
|
83
84
|
lines.push("```markdown");
|
|
84
85
|
lines.push(
|
|
85
|
-
formatJobDescription(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
formatJobDescription(
|
|
87
|
+
{
|
|
88
|
+
job: {
|
|
89
|
+
title: view.title,
|
|
90
|
+
skillMatrix: view.skillMatrix,
|
|
91
|
+
behaviourProfile: view.behaviourProfile,
|
|
92
|
+
expectations: view.expectations,
|
|
93
|
+
derivedResponsibilities: view.derivedResponsibilities,
|
|
94
|
+
},
|
|
95
|
+
discipline: entities.discipline,
|
|
96
|
+
grade: entities.grade,
|
|
97
|
+
track: entities.track,
|
|
92
98
|
},
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
track: entities.track,
|
|
96
|
-
}),
|
|
99
|
+
jobTemplate,
|
|
100
|
+
),
|
|
97
101
|
);
|
|
98
102
|
lines.push("```");
|
|
99
103
|
}
|