@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
@@ -9,6 +9,23 @@ import { createBadge } from "../components/card.js";
9
9
  import { formatLevel } from "./render.js";
10
10
  import { getCapabilityEmoji } from "../model/levels.js";
11
11
 
12
+ /**
13
+ * Create an external link element styled as a badge
14
+ * @param {string} text - Link text
15
+ * @param {string} url - External URL
16
+ * @returns {HTMLElement}
17
+ */
18
+ function createExternalLink(text, url) {
19
+ const link = document.createElement("a");
20
+ link.href = url;
21
+ link.target = "_blank";
22
+ link.rel = "noopener noreferrer";
23
+ link.className = "badge badge-primary";
24
+ link.textContent = text;
25
+ link.addEventListener("click", (e) => e.stopPropagation()); // Don't trigger card click
26
+ return link;
27
+ }
28
+
12
29
  /**
13
30
  * Map discipline to card config
14
31
  * @param {Object} discipline
@@ -155,24 +172,54 @@ export function jobToCardConfig(job) {
155
172
  }
156
173
 
157
174
  /**
158
- * Format capability for display
159
- * @param {string} capability
175
+ * Map tool to card config
176
+ * @param {Object} tool - Aggregated tool with usages
177
+ * @param {Array} capabilities - Capability entities for emoji lookup
178
+ * @returns {Object}
179
+ */
180
+ export function toolToCardConfig(tool, capabilities) {
181
+ // Create skills list as card content
182
+ const skillsList = createSkillsList(tool.usages, capabilities);
183
+
184
+ return {
185
+ title: tool.name,
186
+ description: tool.description,
187
+ // Docs link in header badges (upper right)
188
+ badges: tool.url ? [createExternalLink("Docs →", tool.url)] : [],
189
+ content: skillsList,
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Create an unordered list of skill links with capability emoji
195
+ * @param {Array} usages - Tool usage objects with skillId, skillName, capabilityId
196
+ * @param {Array} capabilities - Capability entities
197
+ * @returns {HTMLElement}
198
+ */
199
+ function createSkillsList(usages, capabilities) {
200
+ const ul = document.createElement("ul");
201
+ ul.className = "tool-skills-list";
202
+
203
+ for (const usage of usages) {
204
+ const emoji = getCapabilityEmoji(capabilities, usage.capabilityId);
205
+ const li = document.createElement("li");
206
+ const link = document.createElement("a");
207
+ link.href = `#/skill/${usage.skillId}`;
208
+ link.textContent = `${emoji} ${usage.skillName}`;
209
+ li.appendChild(link);
210
+ ul.appendChild(li);
211
+ }
212
+
213
+ return ul;
214
+ }
215
+
216
+ /**
217
+ * Format capability for badge display (short, tag-like)
218
+ * @param {string} capabilityId
160
219
  * @param {Array} capabilities
161
220
  * @returns {string}
162
221
  */
163
- function formatCapability(capability, capabilities) {
164
- const capabilityLabels = {
165
- delivery: "Delivery",
166
- scale: "Scale",
167
- reliability: "Reliability",
168
- data: "Data",
169
- ai: "AI",
170
- process: "Process",
171
- business: "Business",
172
- people: "People",
173
- documentation: "Documentation",
174
- };
175
- const label = capabilityLabels[capability] || formatLevel(capability);
176
- const emoji = getCapabilityEmoji(capabilities, capability);
177
- return `${emoji} ${label}`;
222
+ function formatCapability(capabilityId, capabilities) {
223
+ const emoji = getCapabilityEmoji(capabilities, capabilityId);
224
+ return `${emoji} ${capabilityId.toUpperCase()}`;
178
225
  }
package/app/lib/render.js CHANGED
@@ -111,6 +111,10 @@ export const th = (attrs, ...children) =>
111
111
  createElement("th", attrs, ...children);
112
112
  export const td = (attrs, ...children) =>
113
113
  createElement("td", attrs, ...children);
114
+ export const pre = (attrs, ...children) =>
115
+ createElement("pre", attrs, ...children);
116
+ export const code = (attrs, ...children) =>
117
+ createElement("code", attrs, ...children);
114
118
  export const button = (attrs, ...children) =>
115
119
  createElement("button", attrs, ...children);
116
120
  export const input = (attrs) => createElement("input", attrs);
@@ -70,7 +70,15 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
70
70
 
71
71
  if (capability.skills && Array.isArray(capability.skills)) {
72
72
  for (const skill of capability.skills) {
73
- const { id, name, isHumanOnly, human, agent } = skill;
73
+ const {
74
+ id,
75
+ name,
76
+ isHumanOnly,
77
+ human,
78
+ agent,
79
+ implementationReference,
80
+ toolReferences,
81
+ } = skill;
74
82
  allSkills.push({
75
83
  id,
76
84
  name,
@@ -80,6 +88,9 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
80
88
  // Include isHumanOnly flag for agent filtering (defaults to false)
81
89
  ...(isHumanOnly && { isHumanOnly }),
82
90
  ...(agent && { agent }),
91
+ // Include implementation reference and tool references (shared by human and agent)
92
+ ...(implementationReference && { implementationReference }),
93
+ ...(toolReferences && { toolReferences }),
83
94
  });
84
95
  }
85
96
  }
package/app/main.js CHANGED
@@ -26,6 +26,7 @@ import { renderTracksList, renderTrackDetail } from "./pages/track.js";
26
26
  import { renderGradesList, renderGradeDetail } from "./pages/grade.js";
27
27
  import { renderDriversList, renderDriverDetail } from "./pages/driver.js";
28
28
  import { renderStagesList, renderStageDetail } from "./pages/stage.js";
29
+ import { renderToolsList } from "./pages/tool.js";
29
30
  import { renderJobBuilder } from "./pages/job-builder.js";
30
31
  import { renderJobDetail } from "./pages/job.js";
31
32
  import { renderInterviewPrep } from "./pages/interview-builder.js";
@@ -99,6 +100,9 @@ function setupRoutes() {
99
100
  router.on("/stage", renderStagesList);
100
101
  router.on("/stage/:id", renderStageDetail);
101
102
 
103
+ // Tool
104
+ router.on("/tool", renderToolsList);
105
+
102
106
  // Job builder
103
107
  router.on("/job-builder", renderJobBuilder);
104
108
  router.on("/job/:discipline/:grade/:track", renderJobDetail);
@@ -225,21 +225,13 @@ function buildWorkingStyleFromBehaviours(
225
225
  return sections.join("\n");
226
226
  }
227
227
 
228
- /**
229
- * Stage ID to display name and next stage mapping
230
- */
231
- const STAGE_INFO = {
232
- plan: { name: "Plan", nextStage: "Code" },
233
- code: { name: "Code", nextStage: "Review" },
234
- review: { name: "Review", nextStage: "Complete" },
235
- };
236
-
237
228
  /**
238
229
  * Generate SKILL.md content from skill data
239
230
  * @param {Object} skillData - Skill with agent section containing stages
231
+ * @param {Array} stages - All stage entities
240
232
  * @returns {Object} Skill with frontmatter, title, stages array, reference, dirname
241
233
  */
242
- export function generateSkillMd(skillData) {
234
+ export function generateSkillMd(skillData, stages) {
243
235
  const { agent, name } = skillData;
244
236
 
245
237
  if (!agent) {
@@ -250,17 +242,31 @@ export function generateSkillMd(skillData) {
250
242
  throw new Error(`Skill ${skillData.id} agent section missing stages`);
251
243
  }
252
244
 
245
+ // Build stage lookup map
246
+ const stageMap = new Map(stages.map((s) => [s.id, s]));
247
+
253
248
  // Transform stages object to array for template rendering
254
249
  const stagesArray = Object.entries(agent.stages).map(
255
250
  ([stageId, stageData]) => {
256
- const info = STAGE_INFO[stageId] || {
257
- name: stageId,
258
- nextStage: "Next",
259
- };
251
+ const stageEntity = stageMap.get(stageId);
252
+ const stageName = stageEntity?.name || stageId;
253
+
254
+ // Find next stage from handoffs
255
+ let nextStageName = "Complete";
256
+ if (stageEntity?.handoffs) {
257
+ const nextHandoff = stageEntity.handoffs.find(
258
+ (h) => h.targetStage !== stageId,
259
+ );
260
+ if (nextHandoff) {
261
+ const nextStage = stageMap.get(nextHandoff.targetStage);
262
+ nextStageName = nextStage?.name || nextHandoff.targetStage;
263
+ }
264
+ }
265
+
260
266
  return {
261
267
  stageId,
262
- stageName: info.name,
263
- nextStageName: info.nextStage,
268
+ stageName,
269
+ nextStageName,
264
270
  focus: stageData.focus,
265
271
  activities: stageData.activities || [],
266
272
  ready: stageData.ready || [],
@@ -277,11 +283,13 @@ export function generateSkillMd(skillData) {
277
283
  return {
278
284
  frontmatter: {
279
285
  name: agent.name,
280
- description: agent.description.trim(),
286
+ description: agent.description,
287
+ useWhen: agent.useWhen || "",
281
288
  },
282
289
  title: name,
283
290
  stages: stagesArray,
284
- reference: agent.reference ? agent.reference.trim() : "",
291
+ reference: skillData.implementationReference || "",
292
+ toolReferences: skillData.toolReferences || [],
285
293
  dirname: agent.name,
286
294
  };
287
295
  }
@@ -376,17 +376,17 @@ export function isValidJobCombination({
376
376
  * @returns {string} Generated job title
377
377
  */
378
378
  export function generateJobTitle(discipline, grade, track = null) {
379
- const { roleTitle, specialization, isManagement } = discipline;
379
+ const { roleTitle, isManagement } = discipline;
380
380
  const { professionalTitle, managementTitle } = grade;
381
381
 
382
382
  // Management discipline (no track needed)
383
383
  if (isManagement && !track) {
384
- return `${managementTitle}, ${specialization}`;
384
+ return `${managementTitle}, ${roleTitle}`;
385
385
  }
386
386
 
387
387
  // Management discipline with track
388
388
  if (isManagement && track) {
389
- return `${managementTitle}, ${track.name}`;
389
+ return `${managementTitle}, ${roleTitle} – ${track.name}`;
390
390
  }
391
391
 
392
392
  // IC discipline with track
@@ -90,6 +90,7 @@ export const Capability = {
90
90
  RELIABILITY: "reliability",
91
91
  DATA: "data",
92
92
  AI: "ai",
93
+ ML: "ml",
93
94
  PROCESS: "process",
94
95
  BUSINESS: "business",
95
96
  PEOPLE: "people",
@@ -111,6 +112,7 @@ export const CAPABILITY_ORDER = [
111
112
  Capability.DELIVERY,
112
113
  Capability.DATA,
113
114
  Capability.AI,
115
+ Capability.ML,
114
116
  Capability.SCALE,
115
117
  Capability.RELIABILITY,
116
118
  Capability.PEOPLE,
@@ -84,7 +84,15 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
84
84
 
85
85
  if (capability.skills && Array.isArray(capability.skills)) {
86
86
  for (const skill of capability.skills) {
87
- const { id, name, isHumanOnly, human, agent } = skill;
87
+ const {
88
+ id,
89
+ name,
90
+ isHumanOnly,
91
+ human,
92
+ agent,
93
+ implementationReference,
94
+ toolReferences,
95
+ } = skill;
88
96
  allSkills.push({
89
97
  id,
90
98
  name,
@@ -95,6 +103,9 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
95
103
  ...(isHumanOnly && { isHumanOnly }),
96
104
  // Preserve agent section for agent generation
97
105
  ...(agent && { agent }),
106
+ // Include implementation reference and tool references (shared by human and agent)
107
+ ...(implementationReference && { implementationReference }),
108
+ ...(toolReferences && { toolReferences }),
98
109
  });
99
110
  }
100
111
  }
@@ -241,17 +241,13 @@ function validateSkill(skill, index, requiredStageIds = []) {
241
241
  }
242
242
  }
243
243
 
244
- // reference is optional but should be a string if present
245
- if (
246
- skill.agent.reference !== undefined &&
247
- typeof skill.agent.reference !== "string"
248
- ) {
244
+ // Error if old 'reference' field is still present (moved to skill.implementationReference)
245
+ if (skill.agent.reference !== undefined) {
249
246
  errors.push(
250
247
  createError(
251
- "INVALID_VALUE",
252
- "Skill agent reference must be a string",
248
+ "INVALID_FIELD",
249
+ "Skill agent 'reference' field is not supported. Use skill.implementationReference instead.",
253
250
  `${agentPath}.reference`,
254
- skill.agent.reference,
255
251
  ),
256
252
  );
257
253
  }
@@ -295,6 +291,76 @@ function validateSkill(skill, index, requiredStageIds = []) {
295
291
  }
296
292
  }
297
293
 
294
+ // Validate implementationReference if present (optional string)
295
+ if (
296
+ skill.implementationReference !== undefined &&
297
+ typeof skill.implementationReference !== "string"
298
+ ) {
299
+ errors.push(
300
+ createError(
301
+ "INVALID_VALUE",
302
+ "Skill implementationReference must be a string",
303
+ `${path}.implementationReference`,
304
+ skill.implementationReference,
305
+ ),
306
+ );
307
+ }
308
+
309
+ // Validate toolReferences array if present
310
+ if (skill.toolReferences !== undefined) {
311
+ if (!Array.isArray(skill.toolReferences)) {
312
+ errors.push(
313
+ createError(
314
+ "INVALID_VALUE",
315
+ "Skill toolReferences must be an array",
316
+ `${path}.toolReferences`,
317
+ skill.toolReferences,
318
+ ),
319
+ );
320
+ } else {
321
+ skill.toolReferences.forEach((tool, i) => {
322
+ const toolPath = `${path}.toolReferences[${i}]`;
323
+ if (!tool.name) {
324
+ errors.push(
325
+ createError(
326
+ "MISSING_REQUIRED",
327
+ "Tool reference missing name",
328
+ `${toolPath}.name`,
329
+ ),
330
+ );
331
+ }
332
+ if (!tool.description) {
333
+ errors.push(
334
+ createError(
335
+ "MISSING_REQUIRED",
336
+ "Tool reference missing description",
337
+ `${toolPath}.description`,
338
+ ),
339
+ );
340
+ }
341
+ if (!tool.useWhen) {
342
+ errors.push(
343
+ createError(
344
+ "MISSING_REQUIRED",
345
+ "Tool reference missing useWhen",
346
+ `${toolPath}.useWhen`,
347
+ ),
348
+ );
349
+ }
350
+ if (tool.url !== undefined && typeof tool.url !== "string") {
351
+ errors.push(
352
+ createError(
353
+ "INVALID_VALUE",
354
+ "Tool reference url must be a string",
355
+ `${toolPath}.url`,
356
+ tool.url,
357
+ ),
358
+ );
359
+ }
360
+ });
361
+ }
362
+ }
363
+
298
364
  return { errors, warnings };
299
365
  }
300
366
 
@@ -407,7 +407,7 @@ function createAllStagesPreview(context) {
407
407
  const skillFiles = derivedSkills
408
408
  .map((derived) => skills.find((s) => s.id === derived.skillId))
409
409
  .filter((skill) => skill?.agent)
410
- .map((skill) => generateSkillMd(skill));
410
+ .map((skill) => generateSkillMd(skill, stages));
411
411
 
412
412
  return div(
413
413
  { className: "agent-deployment" },
@@ -522,7 +522,7 @@ function createSingleStagePreview(context, stage) {
522
522
  const skillFiles = derivedSkills
523
523
  .map((d) => skills.find((s) => s.id === d.skillId))
524
524
  .filter((skill) => skill?.agent)
525
- .map((skill) => generateSkillMd(skill));
525
+ .map((skill) => generateSkillMd(skill, stages));
526
526
 
527
527
  return div(
528
528
  { className: "agent-deployment" },
@@ -7,6 +7,7 @@ import { getState } from "../lib/state.js";
7
7
  import { createStatCard } from "../components/card.js";
8
8
  import { groupSkillsByCapability, getConceptEmoji } from "../model/levels.js";
9
9
  import { getStageEmoji } from "../formatters/stage/shared.js";
10
+ import { aggregateTools } from "../formatters/tool/shared.js";
10
11
 
11
12
  /**
12
13
  * Create lifecycle flow visualization for landing page
@@ -43,6 +44,7 @@ export function renderLanding() {
43
44
  // Calculate stats using centralized capability ordering
44
45
  const skillsByCapability = groupSkillsByCapability(data.skills);
45
46
  const capabilityCount = Object.keys(skillsByCapability).length;
47
+ const tools = aggregateTools(data.skills);
46
48
 
47
49
  const page = div(
48
50
  { className: "landing-page" },
@@ -83,7 +85,7 @@ export function renderLanding() {
83
85
 
84
86
  // Stats grid
85
87
  div(
86
- { className: "grid grid-6" },
88
+ { className: "grid grid-4" },
87
89
  createStatCard({
88
90
  value: data.disciplines.length,
89
91
  label: "Disciplines",
@@ -99,28 +101,41 @@ export function renderLanding() {
99
101
  label: "Tracks",
100
102
  href: "/track",
101
103
  }),
104
+ createStatCard({
105
+ value: data.behaviours.length,
106
+ label: "Behaviours",
107
+ href: "/behaviour",
108
+ }),
102
109
  createStatCard({
103
110
  value: data.skills.length,
104
111
  label: "Skills",
105
112
  href: "/skill",
106
113
  }),
107
114
  createStatCard({
108
- value: data.behaviours.length,
109
- label: "Behaviours",
110
- href: "/behaviour",
115
+ value: data.drivers.length,
116
+ label: "Drivers",
117
+ href: "/driver",
111
118
  }),
112
119
  createStatCard({
113
120
  value: stages.length,
114
121
  label: "Stages",
115
122
  href: "/stage",
116
123
  }),
124
+ createStatCard({
125
+ value: tools.length,
126
+ label: "Tools",
127
+ href: "/tool",
128
+ }),
117
129
  ),
118
130
 
119
131
  // Lifecycle flow visualization
120
132
  stages.length > 0
121
133
  ? div(
122
134
  { className: "section section-detail" },
123
- h2({ className: "section-title" }, "🔄 Engineering Lifecycle"),
135
+ h2(
136
+ { className: "section-title" },
137
+ `${getConceptEmoji(framework, "stage")} Engineering Lifecycle`,
138
+ ),
124
139
  p(
125
140
  { className: "text-muted", style: "margin-bottom: 1rem" },
126
141
  "The three stages of engineering work, from planning through review.",
@@ -134,7 +149,7 @@ export function renderLanding() {
134
149
  { className: "section section-detail" },
135
150
  h2({ className: "section-title" }, "Explore the Framework"),
136
151
  div(
137
- { className: "grid grid-3" },
152
+ { className: "grid grid-4" },
138
153
  createQuickLinkCard(
139
154
  `${getConceptEmoji(framework, "discipline")} ${framework.entityDefinitions.discipline.title}`,
140
155
  `${data.disciplines.length} ${framework.entityDefinitions.discipline.title.toLowerCase()} — ${framework.entityDefinitions.discipline.description.trim().split("\n")[0]}`,
@@ -150,26 +165,31 @@ export function renderLanding() {
150
165
  `${data.tracks.length} ${framework.entityDefinitions.track.title.toLowerCase()} — ${framework.entityDefinitions.track.description.trim().split("\n")[0]}`,
151
166
  "/track",
152
167
  ),
153
- createQuickLinkCard(
154
- `${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
155
- `${data.skills.length} ${framework.entityDefinitions.skill.title.toLowerCase()} across ${capabilityCount} capabilities — ${framework.entityDefinitions.skill.description.trim().split("\n")[0]}`,
156
- "/skill",
157
- ),
158
168
  createQuickLinkCard(
159
169
  `${getConceptEmoji(framework, "behaviour")} ${framework.entityDefinitions.behaviour.title}`,
160
170
  `${data.behaviours.length} ${framework.entityDefinitions.behaviour.title.toLowerCase()} — ${framework.entityDefinitions.behaviour.description.trim().split("\n")[0]}`,
161
171
  "/behaviour",
162
172
  ),
163
173
  createQuickLinkCard(
164
- "🔄 Stages",
165
- `${stages.length} stages The engineering lifecycle from planning through review.`,
166
- "/stage",
174
+ `${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
175
+ `${data.skills.length} ${framework.entityDefinitions.skill.title.toLowerCase()} across ${capabilityCount} capabilities ${framework.entityDefinitions.skill.description.trim().split("\n")[0]}`,
176
+ "/skill",
167
177
  ),
168
178
  createQuickLinkCard(
169
179
  `${getConceptEmoji(framework, "driver")} ${framework.entityDefinitions.driver.title}`,
170
180
  `${data.drivers.length} ${framework.entityDefinitions.driver.title.toLowerCase()} — ${framework.entityDefinitions.driver.description.trim().split("\n")[0]}`,
171
181
  "/driver",
172
182
  ),
183
+ createQuickLinkCard(
184
+ `${getConceptEmoji(framework, "stage")} ${framework.entityDefinitions.stage.title}`,
185
+ `${stages.length} ${framework.entityDefinitions.stage.title.toLowerCase()} — ${framework.entityDefinitions.stage.description.trim().split("\n")[0]}`,
186
+ "/stage",
187
+ ),
188
+ createQuickLinkCard(
189
+ `${getConceptEmoji(framework, "tool")} ${framework.entityDefinitions.tool.title}`,
190
+ `${tools.length} ${framework.entityDefinitions.tool.title.toLowerCase()} — ${framework.entityDefinitions.tool.description.trim().split("\n")[0]}`,
191
+ "/tool",
192
+ ),
173
193
  ),
174
194
  ),
175
195
 
@@ -82,7 +82,7 @@ function getWizardSteps(data) {
82
82
  if (skills && skills.length > 0) {
83
83
  steps.push({
84
84
  id: `skills-${capability}`,
85
- name: formatCapability(capability),
85
+ name: formatCapability(capability, data.capabilities),
86
86
  icon: getCapabilityEmoji(data.capabilities, capability),
87
87
  type: "skills",
88
88
  capability: capability,
@@ -113,11 +113,13 @@ function getWizardSteps(data) {
113
113
 
114
114
  /**
115
115
  * Format capability name for display
116
- * @param {string} capability
116
+ * @param {string} capabilityId
117
+ * @param {Array} capabilities
117
118
  * @returns {string}
118
119
  */
119
- function formatCapability(capability) {
120
- return capability.charAt(0).toUpperCase() + capability.slice(1);
120
+ function formatCapability(capabilityId, capabilities) {
121
+ const capability = capabilities.find((c) => c.id === capabilityId);
122
+ return capability?.name || capabilityId;
121
123
  }
122
124
 
123
125
  /**
@@ -412,7 +414,7 @@ function renderSkillsStep(step, data) {
412
414
  h2(
413
415
  {},
414
416
  span({ className: "step-header-icon" }, step.icon),
415
- ` ${formatCapability(capability)} Skills`,
417
+ ` ${formatCapability(capability, data.capabilities)} Skills`,
416
418
  ),
417
419
  span(
418
420
  { className: "step-progress" },
@@ -89,25 +89,13 @@ export function renderSkillDetail(params) {
89
89
 
90
90
  /**
91
91
  * Format capability for display
92
- * @param {string} capability
92
+ * @param {string} capabilityId
93
93
  * @param {Array} capabilities
94
94
  * @returns {string}
95
95
  */
96
- function formatCapability(capability, capabilities) {
97
- const capabilityLabels = {
98
- delivery: "Delivery",
99
- scale: "Scale",
100
- reliability: "Reliability",
101
- data: "Data",
102
- ai: "AI",
103
- process: "Process",
104
- business: "Business",
105
- people: "People",
106
- documentation: "Documentation",
107
- };
108
- const label =
109
- capabilityLabels[capability] ||
110
- capability.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
111
- const emoji = getCapabilityEmoji(capabilities, capability);
96
+ function formatCapability(capabilityId, capabilities) {
97
+ const capability = capabilities.find((c) => c.id === capabilityId);
98
+ const label = capability?.name || capabilityId;
99
+ const emoji = getCapabilityEmoji(capabilities, capabilityId);
112
100
  return `${emoji} ${label}`;
113
101
  }
@@ -7,6 +7,7 @@ import { getState } from "../lib/state.js";
7
7
  import { createCardList } from "../components/list.js";
8
8
  import { renderNotFound } from "../components/error-page.js";
9
9
  import { prepareStagesList, stageToDOM } from "../formatters/stage/index.js";
10
+ import { getConceptEmoji } from "../model/levels.js";
10
11
 
11
12
  /**
12
13
  * Map stage to card configuration
@@ -15,7 +16,7 @@ import { prepareStagesList, stageToDOM } from "../formatters/stage/index.js";
15
16
  */
16
17
  function stageToCardConfig(stage) {
17
18
  return {
18
- title: `${stage.emoji || "🔄"} ${stage.name}`,
19
+ title: `${stage.emoji} ${stage.name}`,
19
20
  description: stage.truncatedDescription,
20
21
  href: `/stage/${stage.id}`,
21
22
  };
@@ -28,14 +29,13 @@ function stageToCardConfig(stage) {
28
29
  */
29
30
  function createLifecycleFlow(stages) {
30
31
  const flowItems = stages.map((stage, index) => {
31
- const emoji = stage.emoji || "🔄";
32
32
  const isLast = index === stages.length - 1;
33
33
 
34
34
  return div(
35
35
  { className: "lifecycle-flow-item" },
36
36
  a(
37
37
  { href: `#/stage/${stage.id}`, className: "lifecycle-stage" },
38
- span({ className: "lifecycle-emoji" }, emoji),
38
+ span({ className: "lifecycle-emoji" }, stage.emoji),
39
39
  span({ className: "lifecycle-name" }, stage.name),
40
40
  ),
41
41
  !isLast ? span({ className: "lifecycle-arrow" }, "→") : null,
@@ -50,7 +50,9 @@ function createLifecycleFlow(stages) {
50
50
  */
51
51
  export function renderStagesList() {
52
52
  const { data } = getState();
53
+ const { framework } = data;
53
54
  const stages = data.stages || [];
55
+ const stageEmoji = getConceptEmoji(framework, "stage");
54
56
 
55
57
  // Transform data for list view
56
58
  const { items } = prepareStagesList(stages);
@@ -60,11 +62,13 @@ export function renderStagesList() {
60
62
  // Header
61
63
  div(
62
64
  { className: "page-header" },
63
- h1({ className: "page-title" }, "🔄 Stages"),
65
+ h1(
66
+ { className: "page-title" },
67
+ `${stageEmoji} ${framework.entityDefinitions.stage.title}`,
68
+ ),
64
69
  p(
65
70
  { className: "page-description" },
66
- "The engineering lifecycle stages. Each stage has specific tools, " +
67
- "constraints, and handoffs to guide work from planning through review.",
71
+ framework.entityDefinitions.stage.description.trim().split("\n")[0],
68
72
  ),
69
73
  ),
70
74