@forwardimpact/pathway 0.3.0 → 0.4.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.
Files changed (51) hide show
  1. package/app/commands/agent.js +1 -1
  2. package/app/commands/index.js +4 -3
  3. package/app/commands/skill.js +56 -2
  4. package/app/commands/tool.js +112 -0
  5. package/app/components/checklist.js +6 -4
  6. package/app/components/markdown-textarea.js +132 -0
  7. package/app/css/components/forms.css +45 -0
  8. package/app/css/components/layout.css +12 -0
  9. package/app/css/components/surfaces.css +22 -0
  10. package/app/css/pages/detail.css +50 -0
  11. package/app/css/pages/job-builder.css +0 -42
  12. package/app/formatters/agent/profile.js +61 -9
  13. package/app/formatters/agent/skill.js +48 -6
  14. package/app/formatters/job/description.js +21 -16
  15. package/app/formatters/job/dom.js +9 -70
  16. package/app/formatters/shared.js +58 -0
  17. package/app/formatters/skill/dom.js +57 -2
  18. package/app/formatters/skill/markdown.js +18 -0
  19. package/app/formatters/skill/shared.js +12 -4
  20. package/app/formatters/stage/microdata.js +1 -1
  21. package/app/formatters/stage/shared.js +1 -1
  22. package/app/formatters/tool/shared.js +72 -0
  23. package/app/handout-main.js +7 -7
  24. package/app/index.html +10 -3
  25. package/app/lib/card-mappers.js +64 -17
  26. package/app/lib/render.js +4 -0
  27. package/app/lib/yaml-loader.js +12 -1
  28. package/app/main.js +4 -0
  29. package/app/model/agent.js +26 -18
  30. package/app/model/derivation.js +3 -3
  31. package/app/model/levels.js +2 -0
  32. package/app/model/loader.js +12 -1
  33. package/app/model/validation.js +74 -8
  34. package/app/pages/agent-builder.js +2 -2
  35. package/app/pages/landing.js +34 -14
  36. package/app/pages/self-assessment.js +7 -5
  37. package/app/pages/skill.js +5 -17
  38. package/app/pages/stage.js +10 -6
  39. package/app/pages/tool.js +50 -0
  40. package/app/slides/index.js +25 -25
  41. package/bin/pathway.js +31 -16
  42. package/examples/capabilities/business.yaml +17 -17
  43. package/examples/capabilities/delivery.yaml +51 -36
  44. package/examples/capabilities/reliability.yaml +127 -114
  45. package/examples/capabilities/scale.yaml +38 -36
  46. package/examples/disciplines/engineering_management.yaml +1 -1
  47. package/examples/framework.yaml +12 -0
  48. package/examples/grades.yaml +5 -7
  49. package/examples/self-assessments.yaml +1 -1
  50. package/package.json +1 -1
  51. package/templates/skill.template.md +31 -12
@@ -10,6 +10,47 @@
10
10
 
11
11
  import Mustache from "mustache";
12
12
 
13
+ import { trimValue, splitLines, trimFields } from "../shared.js";
14
+
15
+ /**
16
+ * Prepare agent skill data for template rendering
17
+ * Normalizes string values by trimming trailing newlines for consistent template output.
18
+ * @param {Object} params
19
+ * @param {Object} params.frontmatter - YAML frontmatter data
20
+ * @param {string} params.frontmatter.name - Skill name (required)
21
+ * @param {string} params.frontmatter.description - Skill description (required)
22
+ * @param {string} [params.frontmatter.useWhen] - When to use this skill
23
+ * @param {string} params.title - Human-readable skill title for heading
24
+ * @param {Array} params.stages - Array of stage objects with stageName, focus, activities, ready
25
+ * @param {string} params.reference - Reference content (markdown)
26
+ * @param {Array} [params.toolReferences] - Array of tool reference objects
27
+ * @returns {Object} Data object ready for Mustache template
28
+ */
29
+ function prepareAgentSkillData({
30
+ frontmatter,
31
+ title,
32
+ stages,
33
+ reference,
34
+ toolReferences,
35
+ }) {
36
+ // Process stages - trim focus and array values
37
+ const processedStages = trimFields(stages, {
38
+ focus: "required",
39
+ activities: "array",
40
+ ready: "array",
41
+ });
42
+
43
+ return {
44
+ name: frontmatter.name,
45
+ descriptionLines: splitLines(frontmatter.description),
46
+ useWhenLines: splitLines(frontmatter.useWhen),
47
+ title,
48
+ stages: processedStages,
49
+ reference: trimValue(reference) || "",
50
+ toolReferences: toolReferences || [],
51
+ };
52
+ }
53
+
13
54
  /**
14
55
  * Format agent skill as SKILL.md file content using Mustache template
15
56
  * @param {Object} skill - Skill with frontmatter, title, stages, reference
@@ -19,19 +60,20 @@ import Mustache from "mustache";
19
60
  * @param {string} skill.title - Human-readable skill title for heading
20
61
  * @param {Array} skill.stages - Array of stage objects with stageName, focus, activities, ready
21
62
  * @param {string} skill.reference - Reference content (markdown)
63
+ * @param {Array} [skill.toolReferences] - Array of tool reference objects
22
64
  * @param {string} template - Mustache template string
23
65
  * @returns {string} Complete SKILL.md file content
24
66
  */
25
67
  export function formatAgentSkill(
26
- { frontmatter, title, stages, reference },
68
+ { frontmatter, title, stages, reference, toolReferences },
27
69
  template,
28
70
  ) {
29
- const data = {
30
- name: frontmatter.name,
31
- descriptionLines: frontmatter.description.trim().split("\n"),
71
+ const data = prepareAgentSkillData({
72
+ frontmatter,
32
73
  title,
33
74
  stages,
34
- reference: reference ? reference.trim() : "",
35
- };
75
+ reference,
76
+ toolReferences,
77
+ });
36
78
  return Mustache.render(template, data);
37
79
  }
@@ -14,6 +14,7 @@ import {
14
14
  SKILL_LEVEL_ORDER,
15
15
  BEHAVIOUR_MATURITY_ORDER,
16
16
  } from "../../model/levels.js";
17
+ import { trimValue, trimFields } from "../shared.js";
17
18
 
18
19
  /**
19
20
  * Prepare job data for template rendering
@@ -121,28 +122,32 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
121
122
  };
122
123
  });
123
124
 
125
+ // Build qualification summary with placeholder replacement
126
+ const qualificationSummary =
127
+ (grade.qualificationSummary || "").replace(
128
+ /\{typicalExperienceRange\}/g,
129
+ grade.typicalExperienceRange || "",
130
+ ) || null;
131
+
124
132
  return {
125
133
  title: job.title,
126
134
  gradeId: grade.id,
127
135
  typicalExperienceRange: grade.typicalExperienceRange,
128
136
  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 || "",
137
+ roleSummary: trimValue(roleSummary),
138
+ trackRoleContext: trimValue(track?.roleContext),
139
+ expectationsParagraph: trimValue(expectationsParagraph),
140
+ responsibilities: trimFields(job.derivedResponsibilities, {
141
+ responsibility: "required",
142
+ }),
143
+ behaviours: trimFields(sortedBehaviours, {
144
+ maturityDescription: "optional",
145
+ }),
146
+ skillLevels: skillLevels.map((level) => ({
147
+ ...level,
148
+ skills: trimFields(level.skills, { levelDescription: "optional" }),
139
149
  })),
140
- skillLevels,
141
- qualificationSummary:
142
- (grade.qualificationSummary || "").replace(
143
- /\{typicalExperienceRange\}/g,
144
- grade.typicalExperienceRange || "",
145
- ) || null,
150
+ qualificationSummary: trimValue(qualificationSummary),
146
151
  };
147
152
  }
148
153
 
@@ -2,7 +2,7 @@
2
2
  * Job formatting for DOM/web output
3
3
  */
4
4
 
5
- import { div, h1, h2, p, a, span, button, section } from "../../lib/render.js";
5
+ import { div, h1, h2, p, a, span, section } from "../../lib/render.js";
6
6
  import { createBackLink } from "../../components/nav.js";
7
7
  import {
8
8
  createDetailSection,
@@ -14,6 +14,7 @@ import {
14
14
  } from "../../components/radar-chart.js";
15
15
  import { createSkillMatrix } from "../../components/skill-matrix.js";
16
16
  import { createBehaviourProfile } from "../../components/behaviour-profile.js";
17
+ import { createMarkdownTextarea } from "../../components/markdown-textarea.js";
17
18
  import { markdownToHtml } from "../../lib/markdown.js";
18
19
  import { formatJobDescription } from "./description.js";
19
20
 
@@ -223,77 +224,15 @@ export function createJobDescriptionSection({
223
224
  template,
224
225
  );
225
226
 
226
- const copyButton = button(
227
- {
228
- className: "btn btn-primary copy-btn",
229
- onClick: async () => {
230
- try {
231
- await navigator.clipboard.writeText(markdown);
232
- copyButton.textContent = "✓ Copied!";
233
- copyButton.classList.add("copied");
234
- setTimeout(() => {
235
- copyButton.textContent = "Copy Markdown";
236
- copyButton.classList.remove("copied");
237
- }, 2000);
238
- } catch (err) {
239
- console.error("Failed to copy:", err);
240
- copyButton.textContent = "Copy failed";
241
- setTimeout(() => {
242
- copyButton.textContent = "Copy Markdown";
243
- }, 2000);
244
- }
245
- },
246
- },
247
- "Copy Markdown",
248
- );
249
-
250
- const copyHtmlButton = button(
251
- {
252
- className: "btn btn-secondary copy-btn",
253
- onClick: async () => {
254
- try {
255
- const html = markdownToHtml(markdown);
256
- // Use ClipboardItem with text/html MIME type for rich text pasting in Word
257
- const blob = new Blob([html], { type: "text/html" });
258
- const clipboardItem = new ClipboardItem({ "text/html": blob });
259
- await navigator.clipboard.write([clipboardItem]);
260
- copyHtmlButton.textContent = "✓ Copied!";
261
- copyHtmlButton.classList.add("copied");
262
- setTimeout(() => {
263
- copyHtmlButton.textContent = "Copy as HTML";
264
- copyHtmlButton.classList.remove("copied");
265
- }, 2000);
266
- } catch (err) {
267
- console.error("Failed to copy:", err);
268
- copyHtmlButton.textContent = "Copy failed";
269
- setTimeout(() => {
270
- copyHtmlButton.textContent = "Copy as HTML";
271
- }, 2000);
272
- }
273
- },
274
- },
275
- "Copy as HTML",
276
- );
277
-
278
- const textarea = document.createElement("textarea");
279
- textarea.className = "job-description-textarea";
280
- textarea.readOnly = true;
281
- textarea.value = markdown;
282
-
283
227
  return createDetailSection({
284
228
  title: "Job Description",
285
- content: div(
286
- { className: "job-description-container" },
287
- div(
288
- { className: "job-description-header" },
289
- p(
290
- { className: "text-muted" },
291
- "Copy this markdown-formatted job description for use in job postings, documentation, or sharing.",
292
- ),
293
- div({ className: "button-group" }, copyButton, copyHtmlButton),
294
- ),
295
- textarea,
296
- ),
229
+ content: createMarkdownTextarea({
230
+ markdown,
231
+ description:
232
+ "Copy this markdown-formatted job description for use in job postings, documentation, or sharing.",
233
+ toHtml: markdownToHtml,
234
+ minHeight: 450,
235
+ }),
297
236
  });
298
237
  }
299
238
 
@@ -4,6 +4,64 @@
4
4
  * Common formatting functions used across different output formats (CLI, DOM, markdown)
5
5
  */
6
6
 
7
+ /**
8
+ * Trim trailing newlines from a string value
9
+ * Used by template prepare functions for consistent output formatting.
10
+ * @param {string|null|undefined} value - Value to trim
11
+ * @returns {string|null} Trimmed value or null if empty
12
+ */
13
+ export function trimValue(value) {
14
+ if (value == null) return null;
15
+ const trimmed = value.replace(/\n+$/, "");
16
+ return trimmed || null;
17
+ }
18
+
19
+ /**
20
+ * Trim a required field, preserving original if trim would result in empty
21
+ * Use for fields that must have a value.
22
+ * @param {string|null|undefined} value - Value to trim
23
+ * @returns {string} Trimmed value or original
24
+ */
25
+ export function trimRequired(value) {
26
+ return trimValue(value) || value || "";
27
+ }
28
+
29
+ /**
30
+ * Trim and split a string into lines
31
+ * @param {string|null|undefined} value - Value to process
32
+ * @returns {string[]} Array of lines (empty array if no value)
33
+ */
34
+ export function splitLines(value) {
35
+ const trimmed = trimValue(value);
36
+ return trimmed ? trimmed.split("\n") : [];
37
+ }
38
+
39
+ /**
40
+ * Transform an array of objects by applying trimValue to specified fields
41
+ * @param {Array<Object>} array - Array of objects to transform
42
+ * @param {Object<string, 'optional'|'required'|'array'>} fieldSpec - Fields to trim and their type
43
+ * - 'optional': use trimValue (returns null if empty)
44
+ * - 'required': use trimRequired (preserves original if empty)
45
+ * - 'array': trim each element in array field
46
+ * @returns {Array<Object>} Transformed array
47
+ */
48
+ export function trimFields(array, fieldSpec) {
49
+ if (!array) return [];
50
+ return array.map((item) => {
51
+ const result = { ...item };
52
+ for (const [field, type] of Object.entries(fieldSpec)) {
53
+ if (type === "optional") {
54
+ result[field] = trimValue(item[field]);
55
+ } else if (type === "required") {
56
+ result[field] = trimRequired(item[field]);
57
+ } else if (type === "array") {
58
+ result[field] = (item[field] || []).map((v) => trimRequired(v));
59
+ }
60
+ }
61
+ return result;
62
+ });
63
+ }
64
+
7
65
  /**
8
66
  * Format level as text with dots (for CLI/markdown)
9
67
  * @param {number} level - 1-5
@@ -18,8 +18,9 @@ import {
18
18
  } from "../../lib/render.js";
19
19
  import { createBackLink } from "../../components/nav.js";
20
20
  import { createLevelCell } from "../../components/detail.js";
21
+ import { createMarkdownTextarea } from "../../components/markdown-textarea.js";
21
22
  import { SKILL_LEVEL_ORDER } from "../../model/levels.js";
22
- import { prepareSkillDetail, formatCapability } from "./shared.js";
23
+ import { prepareSkillDetail } from "./shared.js";
23
24
  import { createJsonLdScript, skillToJsonLd } from "../json-ld.js";
24
25
 
25
26
  /**
@@ -61,7 +62,7 @@ export function skillToDOM(
61
62
  { className: "page-meta" },
62
63
  span(
63
64
  { className: "badge badge-default" },
64
- formatCapability(view.capability),
65
+ `${view.capabilityEmoji} ${view.capability.toUpperCase()}`,
65
66
  ),
66
67
  view.isHumanOnly
67
68
  ? span(
@@ -97,6 +98,60 @@ export function skillToDOM(
97
98
  ),
98
99
  ),
99
100
 
101
+ // Recommended Tools
102
+ view.toolReferences.length > 0
103
+ ? div(
104
+ { className: "detail-section" },
105
+ heading2({ className: "section-title" }, "Recommended Tools"),
106
+ table(
107
+ { className: "tools-table" },
108
+ thead({}, tr({}, th({}, "Tool"), th({}, "Use When"))),
109
+ tbody(
110
+ {},
111
+ ...view.toolReferences.map((tool) =>
112
+ tr(
113
+ {},
114
+ td(
115
+ {},
116
+ tool.url
117
+ ? a(
118
+ {
119
+ href: tool.url,
120
+ target: "_blank",
121
+ rel: "noopener noreferrer",
122
+ },
123
+ tool.name,
124
+ )
125
+ : tool.name,
126
+ ),
127
+ td({}, tool.useWhen),
128
+ ),
129
+ ),
130
+ ),
131
+ ),
132
+ showBackLink
133
+ ? p(
134
+ { className: "see-all-link" },
135
+ a({ href: "#/tool" }, "See all tools →"),
136
+ )
137
+ : null,
138
+ )
139
+ : null,
140
+
141
+ // Implementation Reference
142
+ view.implementationReference
143
+ ? div(
144
+ { className: "detail-section" },
145
+ heading2({ className: "section-title" }, "Implementation Patterns"),
146
+ createMarkdownTextarea({
147
+ markdown: view.implementationReference,
148
+ description:
149
+ "Project-specific implementation guidance for this skill.",
150
+ minHeight: 450,
151
+ }),
152
+ )
153
+ : null,
154
+
100
155
  // Used in Disciplines and Linked to Drivers in two columns
101
156
  view.relatedDisciplines.length > 0 || view.relatedDrivers.length > 0
102
157
  ? div(
@@ -105,5 +105,23 @@ export function skillToMarkdown(
105
105
  lines.push("");
106
106
  }
107
107
 
108
+ // Recommended tools
109
+ if (view.toolReferences.length > 0) {
110
+ lines.push("## Recommended Tools", "");
111
+ const toolRows = view.toolReferences.map((tool) => [
112
+ tool.url ? `[${tool.name}](${tool.url})` : tool.name,
113
+ tool.useWhen,
114
+ ]);
115
+ lines.push(tableToMarkdown(["Tool", "Use When"], toolRows));
116
+ lines.push("");
117
+ }
118
+
119
+ // Implementation reference
120
+ if (view.implementationReference) {
121
+ lines.push("## Implementation Patterns", "");
122
+ lines.push(view.implementationReference);
123
+ lines.push("");
124
+ }
125
+
108
126
  return lines.join("\n");
109
127
  }
@@ -13,12 +13,12 @@ import { truncate } from "../shared.js";
13
13
 
14
14
  /**
15
15
  * Format capability name for display
16
- * @param {string} capability
16
+ * @param {string} capabilityName - The capability name to display
17
17
  * @returns {string}
18
18
  */
19
- export function formatCapability(capability) {
20
- if (!capability) return "";
21
- return capability.charAt(0).toUpperCase() + capability.slice(1);
19
+ export function formatCapability(capabilityName) {
20
+ if (!capabilityName) return "";
21
+ return capabilityName;
22
22
  }
23
23
 
24
24
  /**
@@ -66,12 +66,15 @@ export function prepareSkillsList(
66
66
  * @property {string} name
67
67
  * @property {string} description
68
68
  * @property {string} capability
69
+ * @property {string} capabilityName
69
70
  * @property {boolean} isHumanOnly
70
71
  * @property {string} capabilityEmoji
71
72
  * @property {Object<string, string>} levelDescriptions
72
73
  * @property {Array<{id: string, name: string, skillType: string}>} relatedDisciplines
73
74
  * @property {Array<{id: string, name: string, modifier: number}>} relatedTracks
74
75
  * @property {Array<{id: string, name: string}>} relatedDrivers
76
+ * @property {Array<{name: string, url?: string, description: string, useWhen: string}>} toolReferences
77
+ * @property {string|null} implementationReference
75
78
  */
76
79
 
77
80
  /**
@@ -110,16 +113,21 @@ export function prepareSkillDetail(
110
113
  .filter((d) => d.contributingSkills?.includes(skill.id))
111
114
  .map((d) => ({ id: d.id, name: d.name }));
112
115
 
116
+ const capabilityEntity = capabilities.find((c) => c.id === skill.capability);
117
+
113
118
  return {
114
119
  id: skill.id,
115
120
  name: skill.name,
116
121
  description: skill.description,
117
122
  capability: skill.capability,
123
+ capabilityName: capabilityEntity?.name || skill.capability,
118
124
  isHumanOnly: skill.isHumanOnly || false,
119
125
  capabilityEmoji: getCapabilityEmoji(capabilities, skill.capability),
120
126
  levelDescriptions: skill.levelDescriptions,
121
127
  relatedDisciplines,
122
128
  relatedTracks,
123
129
  relatedDrivers,
130
+ toolReferences: skill.toolReferences || [],
131
+ implementationReference: skill.implementationReference || null,
124
132
  };
125
133
  }
@@ -32,7 +32,7 @@ export function stageListToMicrodata(stages) {
32
32
  ? `→ ${stage.handoffs.map((h) => h.target).join(", ")}`
33
33
  : "";
34
34
  return `${openTag("article", { itemtype: "Stage", itemid: `#${stage.id}` })}
35
- ${prop("h2", "name", `${stage.emoji || ""} ${stage.name}`)}
35
+ ${prop("h2", "name", `${stage.emoji} ${stage.name}`)}
36
36
  ${prop("p", "description", stage.truncatedDescription)}
37
37
  ${handoffText ? `<p>Handoffs: ${handoffText}</p>` : ""}
38
38
  </article>`;
@@ -80,5 +80,5 @@ export function prepareStageDetail(stage) {
80
80
  */
81
81
  export function getStageEmoji(stages, stageId) {
82
82
  const stage = stages.find((s) => s.id === stageId);
83
- return stage?.emoji || "🔄";
83
+ return stage?.emoji;
84
84
  }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Tool presentation helpers
3
+ *
4
+ * Shared utilities for formatting tool data across DOM and CLI outputs.
5
+ */
6
+
7
+ /**
8
+ * @typedef {Object} ToolUsage
9
+ * @property {string} skillId
10
+ * @property {string} skillName
11
+ * @property {string} capabilityId
12
+ * @property {string} useWhen
13
+ */
14
+
15
+ /**
16
+ * @typedef {Object} AggregatedTool
17
+ * @property {string} name
18
+ * @property {string} [url]
19
+ * @property {string} description
20
+ * @property {ToolUsage[]} usages
21
+ */
22
+
23
+ /**
24
+ * Aggregate tools from all skills, deduplicating by name
25
+ * @param {Array} skills - All skills with toolReferences
26
+ * @returns {AggregatedTool[]}
27
+ */
28
+ export function aggregateTools(skills) {
29
+ const toolMap = new Map();
30
+
31
+ for (const skill of skills) {
32
+ if (!skill.toolReferences) continue;
33
+
34
+ for (const tool of skill.toolReferences) {
35
+ const usage = {
36
+ skillId: skill.id,
37
+ skillName: skill.name,
38
+ capabilityId: skill.capability,
39
+ useWhen: tool.useWhen,
40
+ };
41
+
42
+ const existing = toolMap.get(tool.name);
43
+ if (existing) {
44
+ existing.usages.push(usage);
45
+ } else {
46
+ toolMap.set(tool.name, {
47
+ name: tool.name,
48
+ url: tool.url,
49
+ description: tool.description,
50
+ usages: [usage],
51
+ });
52
+ }
53
+ }
54
+ }
55
+
56
+ return Array.from(toolMap.values()).sort((a, b) =>
57
+ a.name.localeCompare(b.name),
58
+ );
59
+ }
60
+
61
+ /**
62
+ * Prepare tools list view data
63
+ * @param {Array} skills - All skills
64
+ * @returns {{ tools: AggregatedTool[], totalCount: number }}
65
+ */
66
+ export function prepareToolsList(skills) {
67
+ const tools = aggregateTools(skills);
68
+ return {
69
+ tools,
70
+ totalCount: tools.length,
71
+ };
72
+ }
@@ -135,25 +135,25 @@ function renderIndex(data) {
135
135
  `${getConceptEmoji(framework, "job")} ${framework.entityDefinitions.job.title}`,
136
136
  ),
137
137
  " - ",
138
- `${data.disciplines.length} disciplines, ${data.tracks.length} tracks, ${data.grades.length} grades`,
138
+ `${data.disciplines.length} disciplines, ${data.grades.length} grades, ${data.tracks.length} tracks`,
139
139
  ),
140
140
  li(
141
141
  {},
142
142
  a(
143
- { href: "#/skill" },
144
- `${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
143
+ { href: "#/behaviour" },
144
+ `${getConceptEmoji(framework, "behaviour")} ${framework.entityDefinitions.behaviour.title}`,
145
145
  ),
146
146
  " - ",
147
- `${data.skills.length} skill definitions`,
147
+ `${data.behaviours.length} behaviour definitions`,
148
148
  ),
149
149
  li(
150
150
  {},
151
151
  a(
152
- { href: "#/behaviour" },
153
- `${getConceptEmoji(framework, "behaviour")} ${framework.entityDefinitions.behaviour.title}`,
152
+ { href: "#/skill" },
153
+ `${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
154
154
  ),
155
155
  " - ",
156
- `${data.behaviours.length} behaviour definitions`,
156
+ `${data.skills.length} skill definitions`,
157
157
  ),
158
158
  li(
159
159
  {},
package/app/index.html CHANGED
@@ -5,6 +5,12 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Engineering Pathway</title>
7
7
  <link rel="stylesheet" href="css/bundles/app.css" />
8
+ <link
9
+ rel="stylesheet"
10
+ href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css"
11
+ />
12
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-markdown.min.js"></script>
8
14
  <script type="importmap">
9
15
  {
10
16
  "imports": {
@@ -31,12 +37,13 @@
31
37
  </button>
32
38
  <ul class="nav-links" id="nav-links">
33
39
  <li><a href="#/discipline">Disciplines</a></li>
34
- <li><a href="#/track">Tracks</a></li>
35
40
  <li><a href="#/grade">Grades</a></li>
36
- <li><a href="#/skill">Skills</a></li>
41
+ <li><a href="#/track">Tracks</a></li>
37
42
  <li><a href="#/behaviour">Behaviours</a></li>
38
- <li><a href="#/stage">Stages</a></li>
43
+ <li><a href="#/skill">Skills</a></li>
39
44
  <li><a href="#/driver">Drivers</a></li>
45
+ <li><a href="#/stage">Stages</a></li>
46
+ <li><a href="#/tool">Tools</a></li>
40
47
  <li><a href="#/job-builder" class="nav-cta">Build a Job</a></li>
41
48
  <li><a href="#/agent-builder" class="nav-cta">Build an Agent</a></li>
42
49
  <li><a href="#/interview-prep" class="nav-cta">Interview Prep</a></li>