@forwardimpact/pathway 0.4.0 → 0.6.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 (245) hide show
  1. package/bin/{pathway.js → fit-pathway.js} +65 -153
  2. package/package.json +18 -41
  3. package/{app → src}/commands/agent.js +5 -2
  4. package/{app → src}/commands/behaviour.js +1 -1
  5. package/{app → src}/commands/command-factory.js +2 -2
  6. package/{app → src}/commands/discipline.js +1 -1
  7. package/{app → src}/commands/driver.js +2 -2
  8. package/{app → src}/commands/grade.js +2 -2
  9. package/{app → src}/commands/job.js +3 -3
  10. package/{app → src}/commands/serve.js +26 -4
  11. package/{app → src}/commands/site.js +24 -4
  12. package/{app → src}/commands/skill.js +3 -3
  13. package/{app → src}/commands/stage.js +1 -1
  14. package/{app → src}/commands/track.js +2 -2
  15. package/{app → src}/components/card.js +11 -1
  16. package/{app → src}/components/checklist.js +1 -1
  17. package/src/components/code-display.js +153 -0
  18. package/{app → src}/components/comparison-radar.js +1 -1
  19. package/{app → src}/components/detail.js +1 -1
  20. package/src/components/markdown-textarea.js +153 -0
  21. package/{app → src}/components/skill-matrix.js +1 -1
  22. package/{app → src}/css/bundles/app.css +14 -0
  23. package/{app → src}/css/components/badges.css +15 -8
  24. package/{app → src}/css/components/forms.css +23 -13
  25. package/{app → src}/css/components/surfaces.css +49 -3
  26. package/{app → src}/css/components/typography.css +1 -2
  27. package/{app → src}/css/pages/agent-builder.css +11 -102
  28. package/{app → src}/css/pages/detail.css +11 -1
  29. package/{app → src}/css/tokens.css +3 -0
  30. package/{app → src}/formatters/agent/dom.js +26 -71
  31. package/{app → src}/formatters/agent/profile.js +11 -6
  32. package/{app → src}/formatters/behaviour/dom.js +1 -1
  33. package/{app → src}/formatters/discipline/dom.js +1 -1
  34. package/{app → src}/formatters/driver/dom.js +1 -1
  35. package/{app → src}/formatters/grade/dom.js +7 -7
  36. package/{app → src}/formatters/grade/markdown.js +1 -1
  37. package/{app → src}/formatters/interview/dom.js +1 -1
  38. package/{app → src}/formatters/interview/markdown.js +1 -1
  39. package/{app → src}/formatters/interview/shared.js +3 -3
  40. package/{app → src}/formatters/job/description.js +1 -1
  41. package/{app → src}/formatters/job/dom.js +3 -3
  42. package/{app → src}/formatters/job/markdown.js +1 -1
  43. package/{app → src}/formatters/json-ld.js +1 -1
  44. package/{app → src}/formatters/progress/shared.js +3 -3
  45. package/{app → src}/formatters/skill/dom.js +69 -57
  46. package/{app → src}/formatters/skill/markdown.js +1 -1
  47. package/{app → src}/formatters/skill/shared.js +5 -3
  48. package/{app → src}/formatters/stage/microdata.js +2 -2
  49. package/{app → src}/formatters/stage/shared.js +3 -3
  50. package/{app → src}/formatters/tool/shared.js +6 -0
  51. package/{app → src}/formatters/track/dom.js +1 -1
  52. package/{app → src}/formatters/track/markdown.js +1 -1
  53. package/{app → src}/formatters/track/shared.js +4 -1
  54. package/{app → src}/handout-main.js +16 -12
  55. package/src/handout.html +43 -0
  56. package/{app → src}/index.html +23 -2
  57. package/{app → src}/lib/card-mappers.js +28 -1
  58. package/{app → src}/lib/job-cache.js +1 -1
  59. package/{app → src}/lib/render.js +1 -1
  60. package/{app → src}/pages/agent-builder.js +120 -76
  61. package/{app → src}/pages/assessment-results.js +1 -1
  62. package/{app → src}/pages/interview.js +1 -1
  63. package/{app → src}/pages/job-builder.js +1 -1
  64. package/{app → src}/pages/job.js +1 -1
  65. package/{app → src}/pages/landing.js +5 -2
  66. package/{app → src}/pages/self-assessment.js +1 -1
  67. package/{app → src}/pages/skill.js +1 -1
  68. package/{app → src}/pages/stage.js +5 -5
  69. package/{app → src}/pages/tool.js +1 -1
  70. package/{app → src}/slide-main.js +2 -2
  71. package/{app → src}/slides/chapter.js +8 -8
  72. package/{app → src}/slides/index.js +3 -3
  73. package/{app → src}/slides/job.js +1 -1
  74. package/{app → src}/slides/overview.js +9 -9
  75. package/{app → src}/slides/skill.js +1 -0
  76. package/{app → src}/slides.html +16 -1
  77. package/templates/agent.template.md +44 -13
  78. package/templates/job.template.md +14 -20
  79. package/templates/skill.template.md +20 -23
  80. package/LICENSE +0 -201
  81. package/README.md +0 -104
  82. package/app/components/markdown-textarea.js +0 -132
  83. package/app/handout.html +0 -28
  84. package/app/model/agent.js +0 -738
  85. package/app/model/checklist.js +0 -103
  86. package/app/model/derivation.js +0 -766
  87. package/app/model/index-generator.js +0 -65
  88. package/app/model/interview.js +0 -539
  89. package/app/model/job.js +0 -228
  90. package/app/model/levels.js +0 -601
  91. package/app/model/loader.js +0 -599
  92. package/app/model/matching.js +0 -888
  93. package/app/model/modifiers.js +0 -158
  94. package/app/model/profile.js +0 -259
  95. package/app/model/progression.js +0 -507
  96. package/app/model/schema-validation.js +0 -438
  97. package/app/model/validation.js +0 -2130
  98. package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
  99. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
  100. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
  101. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
  102. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
  103. package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
  104. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
  105. package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
  106. package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
  107. package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
  108. package/examples/agents/.vscode/settings.json +0 -8
  109. package/examples/behaviours/_index.yaml +0 -8
  110. package/examples/behaviours/outcome_ownership.yaml +0 -43
  111. package/examples/behaviours/polymathic_knowledge.yaml +0 -41
  112. package/examples/behaviours/precise_communication.yaml +0 -39
  113. package/examples/behaviours/relentless_curiosity.yaml +0 -37
  114. package/examples/behaviours/systems_thinking.yaml +0 -40
  115. package/examples/capabilities/_index.yaml +0 -8
  116. package/examples/capabilities/business.yaml +0 -189
  117. package/examples/capabilities/delivery.yaml +0 -303
  118. package/examples/capabilities/people.yaml +0 -68
  119. package/examples/capabilities/reliability.yaml +0 -412
  120. package/examples/capabilities/scale.yaml +0 -378
  121. package/examples/copilot-setup-steps.yaml +0 -25
  122. package/examples/devcontainer.yaml +0 -21
  123. package/examples/disciplines/_index.yaml +0 -6
  124. package/examples/disciplines/data_engineering.yaml +0 -78
  125. package/examples/disciplines/engineering_management.yaml +0 -63
  126. package/examples/disciplines/software_engineering.yaml +0 -78
  127. package/examples/drivers.yaml +0 -202
  128. package/examples/framework.yaml +0 -69
  129. package/examples/grades.yaml +0 -115
  130. package/examples/questions/behaviours/outcome_ownership.yaml +0 -51
  131. package/examples/questions/behaviours/polymathic_knowledge.yaml +0 -47
  132. package/examples/questions/behaviours/precise_communication.yaml +0 -54
  133. package/examples/questions/behaviours/relentless_curiosity.yaml +0 -50
  134. package/examples/questions/behaviours/systems_thinking.yaml +0 -52
  135. package/examples/questions/skills/architecture_design.yaml +0 -53
  136. package/examples/questions/skills/cloud_platforms.yaml +0 -47
  137. package/examples/questions/skills/code_quality.yaml +0 -48
  138. package/examples/questions/skills/data_modeling.yaml +0 -45
  139. package/examples/questions/skills/devops.yaml +0 -46
  140. package/examples/questions/skills/full_stack_development.yaml +0 -47
  141. package/examples/questions/skills/sre_practices.yaml +0 -43
  142. package/examples/questions/skills/stakeholder_management.yaml +0 -48
  143. package/examples/questions/skills/team_collaboration.yaml +0 -42
  144. package/examples/questions/skills/technical_writing.yaml +0 -42
  145. package/examples/self-assessments.yaml +0 -64
  146. package/examples/stages.yaml +0 -131
  147. package/examples/tracks/_index.yaml +0 -5
  148. package/examples/tracks/platform.yaml +0 -49
  149. package/examples/tracks/sre.yaml +0 -48
  150. package/examples/vscode-settings.yaml +0 -17
  151. /package/{app → src}/commands/index.js +0 -0
  152. /package/{app → src}/commands/init.js +0 -0
  153. /package/{app → src}/commands/interview.js +0 -0
  154. /package/{app → src}/commands/progress.js +0 -0
  155. /package/{app → src}/commands/questions.js +0 -0
  156. /package/{app → src}/commands/tool.js +0 -0
  157. /package/{app → src}/components/action-buttons.js +0 -0
  158. /package/{app → src}/components/behaviour-profile.js +0 -0
  159. /package/{app → src}/components/builder.js +0 -0
  160. /package/{app → src}/components/error-page.js +0 -0
  161. /package/{app → src}/components/grid.js +0 -0
  162. /package/{app → src}/components/list.js +0 -0
  163. /package/{app → src}/components/modifier-table.js +0 -0
  164. /package/{app → src}/components/nav.js +0 -0
  165. /package/{app → src}/components/progression-table.js +0 -0
  166. /package/{app → src}/components/radar-chart.js +0 -0
  167. /package/{app → src}/css/base.css +0 -0
  168. /package/{app → src}/css/bundles/handout.css +0 -0
  169. /package/{app → src}/css/bundles/slides.css +0 -0
  170. /package/{app → src}/css/components/buttons.css +0 -0
  171. /package/{app → src}/css/components/layout.css +0 -0
  172. /package/{app → src}/css/components/nav.css +0 -0
  173. /package/{app → src}/css/components/progress.css +0 -0
  174. /package/{app → src}/css/components/states.css +0 -0
  175. /package/{app → src}/css/components/tables.css +0 -0
  176. /package/{app → src}/css/components/utilities.css +0 -0
  177. /package/{app → src}/css/pages/assessment-results.css +0 -0
  178. /package/{app → src}/css/pages/interview-builder.css +0 -0
  179. /package/{app → src}/css/pages/job-builder.css +0 -0
  180. /package/{app → src}/css/pages/landing.css +0 -0
  181. /package/{app → src}/css/pages/lifecycle.css +0 -0
  182. /package/{app → src}/css/pages/progress-builder.css +0 -0
  183. /package/{app → src}/css/pages/self-assessment.css +0 -0
  184. /package/{app → src}/css/reset.css +0 -0
  185. /package/{app → src}/css/views/handout.css +0 -0
  186. /package/{app → src}/css/views/print.css +0 -0
  187. /package/{app → src}/css/views/slide-animations.css +0 -0
  188. /package/{app → src}/css/views/slide-base.css +0 -0
  189. /package/{app → src}/css/views/slide-sections.css +0 -0
  190. /package/{app → src}/css/views/slide-tables.css +0 -0
  191. /package/{app → src}/formatters/agent/skill.js +0 -0
  192. /package/{app → src}/formatters/behaviour/markdown.js +0 -0
  193. /package/{app → src}/formatters/behaviour/microdata.js +0 -0
  194. /package/{app → src}/formatters/behaviour/shared.js +0 -0
  195. /package/{app → src}/formatters/discipline/markdown.js +0 -0
  196. /package/{app → src}/formatters/discipline/microdata.js +0 -0
  197. /package/{app → src}/formatters/discipline/shared.js +0 -0
  198. /package/{app → src}/formatters/driver/microdata.js +0 -0
  199. /package/{app → src}/formatters/driver/shared.js +0 -0
  200. /package/{app → src}/formatters/grade/microdata.js +0 -0
  201. /package/{app → src}/formatters/grade/shared.js +0 -0
  202. /package/{app → src}/formatters/index.js +0 -0
  203. /package/{app → src}/formatters/microdata-shared.js +0 -0
  204. /package/{app → src}/formatters/progress/dom.js +0 -0
  205. /package/{app → src}/formatters/progress/markdown.js +0 -0
  206. /package/{app → src}/formatters/questions/json.js +0 -0
  207. /package/{app → src}/formatters/questions/markdown.js +0 -0
  208. /package/{app → src}/formatters/questions/shared.js +0 -0
  209. /package/{app → src}/formatters/questions/yaml.js +0 -0
  210. /package/{app → src}/formatters/shared.js +0 -0
  211. /package/{app → src}/formatters/skill/microdata.js +0 -0
  212. /package/{app → src}/formatters/stage/dom.js +0 -0
  213. /package/{app → src}/formatters/stage/index.js +0 -0
  214. /package/{app → src}/formatters/track/microdata.js +0 -0
  215. /package/{app → src}/lib/cli-output.js +0 -0
  216. /package/{app → src}/lib/error-boundary.js +0 -0
  217. /package/{app → src}/lib/errors.js +0 -0
  218. /package/{app → src}/lib/form-controls.js +0 -0
  219. /package/{app → src}/lib/markdown.js +0 -0
  220. /package/{app → src}/lib/radar.js +0 -0
  221. /package/{app → src}/lib/reactive.js +0 -0
  222. /package/{app → src}/lib/router-core.js +0 -0
  223. /package/{app → src}/lib/router-pages.js +0 -0
  224. /package/{app → src}/lib/router-slides.js +0 -0
  225. /package/{app → src}/lib/state.js +0 -0
  226. /package/{app → src}/lib/template-loader.js +0 -0
  227. /package/{app → src}/lib/utils.js +0 -0
  228. /package/{app → src}/lib/yaml-loader.js +0 -0
  229. /package/{app → src}/main.js +0 -0
  230. /package/{app → src}/pages/behaviour.js +0 -0
  231. /package/{app → src}/pages/discipline.js +0 -0
  232. /package/{app → src}/pages/driver.js +0 -0
  233. /package/{app → src}/pages/grade.js +0 -0
  234. /package/{app → src}/pages/interview-builder.js +0 -0
  235. /package/{app → src}/pages/progress-builder.js +0 -0
  236. /package/{app → src}/pages/progress.js +0 -0
  237. /package/{app → src}/pages/track.js +0 -0
  238. /package/{app → src}/slides/behaviour.js +0 -0
  239. /package/{app → src}/slides/discipline.js +0 -0
  240. /package/{app → src}/slides/driver.js +0 -0
  241. /package/{app → src}/slides/grade.js +0 -0
  242. /package/{app → src}/slides/interview.js +0 -0
  243. /package/{app → src}/slides/progress.js +0 -0
  244. /package/{app → src}/slides/track.js +0 -0
  245. /package/{app → src}/types.js +0 -0
@@ -16,6 +16,8 @@ import {
16
16
  span,
17
17
  label,
18
18
  section,
19
+ select,
20
+ option,
19
21
  } from "../lib/render.js";
20
22
  import { getState } from "../lib/state.js";
21
23
  import { loadAgentDataBrowser } from "../lib/yaml-loader.js";
@@ -25,7 +27,7 @@ import {
25
27
  generateSkillMd,
26
28
  deriveAgentSkills,
27
29
  deriveReferenceGrade,
28
- } from "../model/agent.js";
30
+ } from "@forwardimpact/model/agent";
29
31
  import {
30
32
  createSelectWithValue,
31
33
  createDisciplineSelect,
@@ -34,6 +36,7 @@ import { createReactive } from "../lib/reactive.js";
34
36
  import { getStageEmoji } from "../formatters/stage/shared.js";
35
37
  import { formatAgentProfile } from "../formatters/agent/profile.js";
36
38
  import { formatAgentSkill } from "../formatters/agent/skill.js";
39
+ import { createCodeDisplay } from "../components/code-display.js";
37
40
 
38
41
  /** All stages option value */
39
42
  const ALL_STAGES_VALUE = "all";
@@ -101,9 +104,71 @@ export async function renderAgentBuilder() {
101
104
  const availableDisciplines = data.disciplines.filter((d) =>
102
105
  agentDisciplineIds.has(d.id),
103
106
  );
104
- const availableTracks = data.tracks.filter((t) => agentTrackIds.has(t.id));
107
+ // All tracks with agent definitions (will be filtered per-discipline)
108
+ const allAgentTracks = data.tracks.filter((t) => agentTrackIds.has(t.id));
105
109
  const stages = data.stages || [];
106
110
 
111
+ /**
112
+ * Get tracks valid for a discipline that also have agent definitions
113
+ * @param {string} disciplineId - Discipline ID
114
+ * @returns {Array} - Valid tracks for the discipline
115
+ */
116
+ function getValidTracksForDiscipline(disciplineId) {
117
+ const discipline = data.disciplines.find((d) => d.id === disciplineId);
118
+ if (!discipline) return [];
119
+
120
+ const validTracks = discipline.validTracks ?? [];
121
+ // Filter to track IDs only (exclude null which means trackless)
122
+ const validTrackIds = validTracks.filter((t) => t !== null);
123
+
124
+ // Intersection: valid for discipline AND has agent definition
125
+ return allAgentTracks.filter((t) => validTrackIds.includes(t.id));
126
+ }
127
+
128
+ // Track select element - created once, options updated when discipline changes
129
+ const trackSelectEl = select(
130
+ { className: "form-select", id: "agent-track-select" },
131
+ option({ value: "", disabled: true, selected: true }, "Select a track..."),
132
+ );
133
+ trackSelectEl.disabled = true;
134
+
135
+ /**
136
+ * Update track select options based on selected discipline
137
+ * @param {string} disciplineId - Discipline ID
138
+ */
139
+ function updateTrackOptions(disciplineId) {
140
+ const validTracks = getValidTracksForDiscipline(disciplineId);
141
+
142
+ // Clear existing options
143
+ trackSelectEl.innerHTML = "";
144
+
145
+ if (validTracks.length === 0) {
146
+ trackSelectEl.appendChild(
147
+ option(
148
+ { value: "", disabled: true, selected: true },
149
+ "No tracks available for this discipline",
150
+ ),
151
+ );
152
+ trackSelectEl.disabled = true;
153
+ return;
154
+ }
155
+
156
+ // Add placeholder
157
+ trackSelectEl.appendChild(
158
+ option(
159
+ { value: "", disabled: true, selected: true },
160
+ "Select a track...",
161
+ ),
162
+ );
163
+
164
+ // Add available track options
165
+ validTracks.forEach((t) => {
166
+ trackSelectEl.appendChild(option({ value: t.id }, t.name));
167
+ });
168
+
169
+ trackSelectEl.disabled = false;
170
+ }
171
+
107
172
  // Build stage options with "All Stages" first
108
173
  const stageOptions = [
109
174
  { id: ALL_STAGES_VALUE, name: "All Stages" },
@@ -134,7 +199,7 @@ export async function renderAgentBuilder() {
134
199
  // Preview container - will be updated reactively
135
200
  const previewContainer = div(
136
201
  { className: "agent-preview" },
137
- createEmptyState(availableDisciplines.length, availableTracks.length),
202
+ createEmptyState(availableDisciplines.length, allAgentTracks.length),
138
203
  );
139
204
 
140
205
  /**
@@ -156,7 +221,7 @@ export async function renderAgentBuilder() {
156
221
 
157
222
  if (!discipline) {
158
223
  previewContainer.appendChild(
159
- createEmptyState(availableDisciplines.length, availableTracks.length),
224
+ createEmptyState(availableDisciplines.length, allAgentTracks.length),
160
225
  );
161
226
  return;
162
227
  }
@@ -251,7 +316,14 @@ export async function renderAgentBuilder() {
251
316
  initialValue: selection.get().discipline,
252
317
  placeholder: "Select a discipline...",
253
318
  onChange: (value) => {
254
- selection.update((prev) => ({ ...prev, discipline: value }));
319
+ // Update track options when discipline changes
320
+ updateTrackOptions(value);
321
+ // Reset track selection when discipline changes
322
+ selection.update((prev) => ({
323
+ ...prev,
324
+ discipline: value,
325
+ track: "",
326
+ }));
255
327
  },
256
328
  getDisplayName: (d) => d.specialization || d.name,
257
329
  })
@@ -260,25 +332,32 @@ export async function renderAgentBuilder() {
260
332
  "No disciplines have agent definitions.",
261
333
  ),
262
334
  ),
263
- // Track selector
335
+ // Track selector (dynamically filtered by discipline)
264
336
  div(
265
337
  { className: "form-group" },
266
338
  label({ className: "form-label" }, "Track"),
267
- availableTracks.length > 0
268
- ? createSelectWithValue({
269
- id: "agent-track-select",
270
- items: availableTracks,
271
- initialValue: selection.get().track,
272
- placeholder: "Select a track...",
273
- onChange: (value) => {
274
- selection.update((prev) => ({ ...prev, track: value }));
275
- },
276
- getDisplayName: (t) => t.name,
277
- })
278
- : p(
279
- { className: "text-muted" },
280
- "No tracks have agent definitions.",
281
- ),
339
+ (() => {
340
+ // Wire up track select change handler
341
+ trackSelectEl.addEventListener("change", (e) => {
342
+ selection.update((prev) => ({ ...prev, track: e.target.value }));
343
+ });
344
+ // Initialize track options if discipline is pre-selected
345
+ const initialDiscipline = selection.get().discipline;
346
+ if (initialDiscipline) {
347
+ updateTrackOptions(initialDiscipline);
348
+ // Set initial track value if provided and valid
349
+ const initialTrack = selection.get().track;
350
+ const validTracks =
351
+ getValidTracksForDiscipline(initialDiscipline);
352
+ if (
353
+ initialTrack &&
354
+ validTracks.some((t) => t.id === initialTrack)
355
+ ) {
356
+ trackSelectEl.value = initialTrack;
357
+ }
358
+ }
359
+ return trackSelectEl;
360
+ })(),
282
361
  ),
283
362
  // Stage selector (dropdown with All Stages option)
284
363
  div(
@@ -590,10 +669,15 @@ function createAgentCard(stage, profile, stages, agentTemplate, _derived) {
590
669
  span({ className: "agent-card-emoji" }, stageEmoji),
591
670
  h3({}, `${stage.name} Agent`),
592
671
  ),
593
- createCopyButton(content),
594
672
  ),
595
- p({ className: "agent-card-filename" }, profile.filename),
596
- div({ className: "agent-card-preview" }, createCodePreview(content)),
673
+ div(
674
+ { className: "agent-card-preview" },
675
+ createCodeDisplay({
676
+ content,
677
+ filename: profile.filename,
678
+ maxHeight: 400,
679
+ }),
680
+ ),
597
681
  );
598
682
 
599
683
  return card;
@@ -614,57 +698,18 @@ function createSkillCard(skill, skillTemplate) {
614
698
  div(
615
699
  { className: "skill-card-header" },
616
700
  span({ className: "skill-card-name" }, skill.frontmatter.name),
617
- createCopyButton(content),
618
701
  ),
619
- p({ className: "skill-card-filename" }, filename),
620
- div({ className: "skill-card-preview" }, createCodePreview(content)),
702
+ div(
703
+ { className: "skill-card-preview" },
704
+ createCodeDisplay({
705
+ content,
706
+ filename,
707
+ maxHeight: 300,
708
+ }),
709
+ ),
621
710
  );
622
711
  }
623
712
 
624
- /**
625
- * Create a code preview element
626
- * @param {string} content - Code content
627
- * @returns {HTMLElement}
628
- */
629
- function createCodePreview(content) {
630
- const pre = document.createElement("pre");
631
- pre.className = "code-block code-preview";
632
-
633
- const code = document.createElement("code");
634
- code.textContent = content;
635
-
636
- pre.appendChild(code);
637
- return pre;
638
- }
639
-
640
- /**
641
- * Create a copy button
642
- * @param {string} content - Content to copy
643
- * @returns {HTMLElement}
644
- */
645
- function createCopyButton(content) {
646
- const btn = document.createElement("button");
647
- btn.className = "btn btn-sm copy-btn";
648
- btn.textContent = "📋 Copy";
649
-
650
- btn.addEventListener("click", async () => {
651
- try {
652
- await navigator.clipboard.writeText(content);
653
- btn.textContent = "✓ Copied";
654
- setTimeout(() => {
655
- btn.textContent = "📋 Copy";
656
- }, 2000);
657
- } catch {
658
- btn.textContent = "Failed";
659
- setTimeout(() => {
660
- btn.textContent = "📋 Copy";
661
- }, 2000);
662
- }
663
- });
664
-
665
- return btn;
666
- }
667
-
668
713
  /**
669
714
  * Create download all button for all stages
670
715
  * @param {Array} stageAgents - Array of {stage, derived, profile}
@@ -860,11 +905,10 @@ function createCliHint(disciplineId, trackId, stageId) {
860
905
  { className: "agent-section cli-hint" },
861
906
  h2({}, "CLI Alternative"),
862
907
  p({}, "Generate this agent from the command line:"),
863
- div(
864
- { className: "cli-command" },
865
- createCodePreview(command),
866
- createCopyButton(command),
867
- ),
908
+ createCodeDisplay({
909
+ content: command,
910
+ language: "bash",
911
+ }),
868
912
  );
869
913
 
870
914
  return container;
@@ -19,7 +19,7 @@ import { getState } from "../lib/state.js";
19
19
  import { createBadge } from "../components/card.js";
20
20
  import { formatLevel } from "../lib/render.js";
21
21
  import { getAssessmentState, resetAssessment } from "./self-assessment.js";
22
- import { findRealisticMatches } from "../model/matching.js";
22
+ import { findRealisticMatches } from "@forwardimpact/model/matching";
23
23
 
24
24
  /**
25
25
  * Render the assessment results page
@@ -21,7 +21,7 @@ import { createBadge } from "../components/card.js";
21
21
  import { createBackLink } from "../components/nav.js";
22
22
  import { createDetailSection } from "../components/detail.js";
23
23
  import { renderError } from "../components/error-page.js";
24
- import { getConceptEmoji } from "../model/levels.js";
24
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
25
25
  import {
26
26
  prepareAllInterviews,
27
27
  INTERVIEW_TYPES,
@@ -5,7 +5,7 @@
5
5
  import { render } from "../lib/render.js";
6
6
  import { getState } from "../lib/state.js";
7
7
  import { createBuilder, createStandardPreview } from "../components/builder.js";
8
- import { prepareJobBuilderPreview } from "../model/job.js";
8
+ import { prepareJobBuilderPreview } from "@forwardimpact/model/job";
9
9
 
10
10
  /**
11
11
  * Render job builder page
@@ -5,7 +5,7 @@
5
5
  import { render, div, p } from "../lib/render.js";
6
6
  import { getState } from "../lib/state.js";
7
7
  import { renderError } from "../components/error-page.js";
8
- import { prepareJobDetail } from "../model/job.js";
8
+ import { prepareJobDetail } from "@forwardimpact/model/job";
9
9
  import { jobToDOM } from "../formatters/job/dom.js";
10
10
 
11
11
  /** @type {string|null} Cached job template */
@@ -5,7 +5,10 @@
5
5
  import { render, div, h1, h2, p, a, span } from "../lib/render.js";
6
6
  import { getState } from "../lib/state.js";
7
7
  import { createStatCard } from "../components/card.js";
8
- import { groupSkillsByCapability, getConceptEmoji } from "../model/levels.js";
8
+ import {
9
+ groupSkillsByCapability,
10
+ getConceptEmoji,
11
+ } from "@forwardimpact/schema/levels";
9
12
  import { getStageEmoji } from "../formatters/stage/shared.js";
10
13
  import { aggregateTools } from "../formatters/tool/shared.js";
11
14
 
@@ -53,7 +56,7 @@ export function renderLanding() {
53
56
  { className: "landing-hero" },
54
57
  div(
55
58
  { className: "hero-title-wrapper" },
56
- h1({}, `${framework.emoji} ${framework.title}`),
59
+ h1({}, `${framework.emojiIcon} ${framework.title}`),
57
60
  span({ className: "brand-tag brand-tag-hero" }, framework.tag),
58
61
  ),
59
62
  p({}, framework.description.trim()),
@@ -25,7 +25,7 @@ import {
25
25
  CAPABILITY_ORDER,
26
26
  getCapabilityEmoji,
27
27
  getConceptEmoji,
28
- } from "../model/levels.js";
28
+ } from "@forwardimpact/schema/levels";
29
29
  import { formatLevel } from "../lib/render.js";
30
30
 
31
31
  /**
@@ -10,7 +10,7 @@ import { renderNotFound } from "../components/error-page.js";
10
10
  import { prepareSkillsList } from "../formatters/skill/shared.js";
11
11
  import { skillToDOM } from "../formatters/skill/dom.js";
12
12
  import { skillToCardConfig } from "../lib/card-mappers.js";
13
- import { getCapabilityEmoji } from "../model/levels.js";
13
+ import { getCapabilityEmoji } from "@forwardimpact/schema/levels";
14
14
 
15
15
  /**
16
16
  * Render skills list page
@@ -7,16 +7,16 @@ 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
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
11
11
 
12
12
  /**
13
13
  * Map stage to card configuration
14
- * @param {Object} stage - Prepared stage item (includes emoji)
14
+ * @param {Object} stage - Prepared stage item (includes emojiIcon)
15
15
  * @returns {Object} Card configuration
16
16
  */
17
17
  function stageToCardConfig(stage) {
18
18
  return {
19
- title: `${stage.emoji} ${stage.name}`,
19
+ title: `${stage.emojiIcon} ${stage.name}`,
20
20
  description: stage.truncatedDescription,
21
21
  href: `/stage/${stage.id}`,
22
22
  };
@@ -24,7 +24,7 @@ function stageToCardConfig(stage) {
24
24
 
25
25
  /**
26
26
  * Create lifecycle flow visualization
27
- * @param {Array} stages - Array of stage items (each includes emoji)
27
+ * @param {Array} stages - Array of stage items (each includes emojiIcon)
28
28
  * @returns {HTMLElement}
29
29
  */
30
30
  function createLifecycleFlow(stages) {
@@ -35,7 +35,7 @@ function createLifecycleFlow(stages) {
35
35
  { className: "lifecycle-flow-item" },
36
36
  a(
37
37
  { href: `#/stage/${stage.id}`, className: "lifecycle-stage" },
38
- span({ className: "lifecycle-emoji" }, stage.emoji),
38
+ span({ className: "lifecycle-emoji" }, stage.emojiIcon),
39
39
  span({ className: "lifecycle-name" }, stage.name),
40
40
  ),
41
41
  !isLast ? span({ className: "lifecycle-arrow" }, "→") : null,
@@ -10,7 +10,7 @@ import { prepareToolsList } from "../formatters/tool/shared.js";
10
10
  import { createBadge } from "../components/card.js";
11
11
  import { createCardList } from "../components/list.js";
12
12
  import { toolToCardConfig } from "../lib/card-mappers.js";
13
- import { getConceptEmoji } from "../model/levels.js";
13
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
14
14
 
15
15
  /**
16
16
  * Render tools list page
@@ -8,7 +8,7 @@ import { createSlideRouter } from "./lib/router-slides.js";
8
8
  import { setData, getState } from "./lib/state.js";
9
9
  import { loadAllData } from "./lib/yaml-loader.js";
10
10
  import { span, a } from "./lib/render.js";
11
- import { generateAllJobs } from "./model/derivation.js";
11
+ import { generateAllJobs } from "@forwardimpact/model/derivation";
12
12
  import { sortTracksByName } from "./formatters/track/shared.js";
13
13
 
14
14
  // Import slide renderers
@@ -346,7 +346,7 @@ function populateBrandHeader(framework) {
346
346
  header.appendChild(
347
347
  a(
348
348
  { className: "brand-title", href: "#/" },
349
- `${framework.emoji} ${framework.title}`,
349
+ `${framework.emojiIcon} ${framework.title}`,
350
350
  ),
351
351
  );
352
352
  header.appendChild(span({ className: "brand-tag" }, framework.tag));
@@ -20,37 +20,37 @@ export function renderChapterSlide({ render, data, params }) {
20
20
  const chapterConfig = {
21
21
  driver: {
22
22
  title: framework.entityDefinitions.driver.title,
23
- emoji: framework.entityDefinitions.driver.emoji,
23
+ emojiIcon: framework.entityDefinitions.driver.emojiIcon,
24
24
  description: framework.entityDefinitions.driver.description,
25
25
  },
26
26
  skill: {
27
27
  title: framework.entityDefinitions.skill.title,
28
- emoji: framework.entityDefinitions.skill.emoji,
28
+ emojiIcon: framework.entityDefinitions.skill.emojiIcon,
29
29
  description: framework.entityDefinitions.skill.description,
30
30
  },
31
31
  behaviour: {
32
32
  title: framework.entityDefinitions.behaviour.title,
33
- emoji: framework.entityDefinitions.behaviour.emoji,
33
+ emojiIcon: framework.entityDefinitions.behaviour.emojiIcon,
34
34
  description: framework.entityDefinitions.behaviour.description,
35
35
  },
36
36
  discipline: {
37
37
  title: framework.entityDefinitions.discipline.title,
38
- emoji: framework.entityDefinitions.discipline.emoji,
38
+ emojiIcon: framework.entityDefinitions.discipline.emojiIcon,
39
39
  description: framework.entityDefinitions.discipline.description,
40
40
  },
41
41
  grade: {
42
42
  title: framework.entityDefinitions.grade.title,
43
- emoji: framework.entityDefinitions.grade.emoji,
43
+ emojiIcon: framework.entityDefinitions.grade.emojiIcon,
44
44
  description: framework.entityDefinitions.grade.description,
45
45
  },
46
46
  track: {
47
47
  title: framework.entityDefinitions.track.title,
48
- emoji: framework.entityDefinitions.track.emoji,
48
+ emojiIcon: framework.entityDefinitions.track.emojiIcon,
49
49
  description: framework.entityDefinitions.track.description,
50
50
  },
51
51
  job: {
52
52
  title: framework.entityDefinitions.job.title,
53
- emoji: framework.entityDefinitions.job.emoji,
53
+ emojiIcon: framework.entityDefinitions.job.emojiIcon,
54
54
  description: framework.entityDefinitions.job.description,
55
55
  },
56
56
  };
@@ -72,7 +72,7 @@ export function renderChapterSlide({ render, data, params }) {
72
72
  { className: "slide chapter-cover" },
73
73
  h1(
74
74
  { className: "chapter-title" },
75
- config.emoji ? `${config.emoji} ` : "",
75
+ config.emojiIcon ? `${config.emojiIcon} ` : "",
76
76
  span({ className: "gradient-text" }, config.title),
77
77
  ),
78
78
  p({ className: "chapter-description" }, config.description.trim()),
@@ -5,8 +5,8 @@
5
5
  */
6
6
 
7
7
  import { div, heading1, heading2, p, a, ul, li, span } from "../lib/render.js";
8
- import { getConceptEmoji } from "../model/levels.js";
9
- import { generateAllJobs } from "../model/derivation.js";
8
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
9
+ import { generateAllJobs } from "@forwardimpact/model/derivation";
10
10
 
11
11
  /**
12
12
  * Render the slide index
@@ -22,7 +22,7 @@ export function renderSlideIndex({ render, data }) {
22
22
  { className: "page-header" },
23
23
  heading1(
24
24
  { className: "page-title" },
25
- `${framework.emoji} ${framework.title}`,
25
+ `${framework.emojiIcon} ${framework.title}`,
26
26
  ),
27
27
  p(
28
28
  { className: "page-description" },
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { div, h1, p } from "../lib/render.js";
8
- import { prepareJobDetail } from "../model/job.js";
8
+ import { prepareJobDetail } from "@forwardimpact/model/job";
9
9
  import { jobToDOM } from "../formatters/index.js";
10
10
 
11
11
  /**
@@ -22,7 +22,7 @@ import { prepareBehavioursList } from "../formatters/behaviour/shared.js";
22
22
  import { prepareDriversList } from "../formatters/driver/shared.js";
23
23
  import { prepareGradesList } from "../formatters/grade/shared.js";
24
24
  import { prepareTracksList } from "../formatters/track/shared.js";
25
- import { generateAllJobs } from "../model/derivation.js";
25
+ import { generateAllJobs } from "@forwardimpact/model/derivation";
26
26
 
27
27
  /**
28
28
  * Format discipline group name for display
@@ -63,14 +63,14 @@ export function renderOverviewSlide({ render, data, params }) {
63
63
  const chapterConfig = {
64
64
  driver: {
65
65
  title: framework.entityDefinitions.driver.title,
66
- emoji: framework.entityDefinitions.driver.emoji,
66
+ emojiIcon: framework.entityDefinitions.driver.emojiIcon,
67
67
  description: framework.entityDefinitions.driver.description,
68
68
  entities: prepareDriversList(data.drivers).items,
69
69
  mapper: driverToCardConfig,
70
70
  },
71
71
  skill: {
72
72
  title: framework.entityDefinitions.skill.title,
73
- emoji: framework.entityDefinitions.skill.emoji,
73
+ emojiIcon: framework.entityDefinitions.skill.emojiIcon,
74
74
  description: framework.entityDefinitions.skill.description,
75
75
  entities: Object.values(
76
76
  prepareSkillsList(data.skills, data.capabilities).groups,
@@ -79,14 +79,14 @@ export function renderOverviewSlide({ render, data, params }) {
79
79
  },
80
80
  behaviour: {
81
81
  title: framework.entityDefinitions.behaviour.title,
82
- emoji: framework.entityDefinitions.behaviour.emoji,
82
+ emojiIcon: framework.entityDefinitions.behaviour.emojiIcon,
83
83
  description: framework.entityDefinitions.behaviour.description,
84
84
  entities: prepareBehavioursList(data.behaviours).items,
85
85
  mapper: behaviourToCardConfig,
86
86
  },
87
87
  discipline: {
88
88
  title: framework.entityDefinitions.discipline.title,
89
- emoji: framework.entityDefinitions.discipline.emoji,
89
+ emojiIcon: framework.entityDefinitions.discipline.emojiIcon,
90
90
  description: framework.entityDefinitions.discipline.description,
91
91
  groups: prepareDisciplinesList(data.disciplines).groups,
92
92
  mapper: disciplineToCardConfig,
@@ -94,21 +94,21 @@ export function renderOverviewSlide({ render, data, params }) {
94
94
  },
95
95
  grade: {
96
96
  title: framework.entityDefinitions.grade.title,
97
- emoji: framework.entityDefinitions.grade.emoji,
97
+ emojiIcon: framework.entityDefinitions.grade.emojiIcon,
98
98
  description: framework.entityDefinitions.grade.description,
99
99
  entities: prepareGradesList(data.grades).items,
100
100
  mapper: gradeToCardConfig,
101
101
  },
102
102
  track: {
103
103
  title: framework.entityDefinitions.track.title,
104
- emoji: framework.entityDefinitions.track.emoji,
104
+ emojiIcon: framework.entityDefinitions.track.emojiIcon,
105
105
  description: framework.entityDefinitions.track.description,
106
106
  entities: prepareTracksList(data.tracks).items,
107
107
  mapper: trackToCardConfig,
108
108
  },
109
109
  job: {
110
110
  title: framework.entityDefinitions.job.title,
111
- emoji: framework.entityDefinitions.job.emoji,
111
+ emojiIcon: framework.entityDefinitions.job.emojiIcon,
112
112
  description: framework.entityDefinitions.job.description,
113
113
  entities: generateAllJobs({
114
114
  disciplines: data.disciplines,
@@ -150,7 +150,7 @@ export function renderOverviewSlide({ render, data, params }) {
150
150
  { className: "overview-header" },
151
151
  h1(
152
152
  { className: "overview-title" },
153
- config.emoji ? `${config.emoji} ` : "",
153
+ config.emojiIcon ? `${config.emojiIcon} ` : "",
154
154
  span({ className: "gradient-text" }, config.title),
155
155
  ),
156
156
  p({ className: "overview-description" }, config.description.trim()),
@@ -35,6 +35,7 @@ export function renderSkillSlide({ render, data, params }) {
35
35
  drivers: data.drivers,
36
36
  capabilities: data.capabilities,
37
37
  showBackLink: false,
38
+ showToolsAndPatterns: false,
38
39
  }),
39
40
  );
40
41
  }
@@ -8,7 +8,22 @@
8
8
  <script type="importmap">
9
9
  {
10
10
  "imports": {
11
- "mustache": "https://esm.sh/mustache@4.2.0"
11
+ "mustache": "https://esm.sh/mustache@4.2.0",
12
+ "@forwardimpact/schema": "/schema/lib/index.js",
13
+ "@forwardimpact/schema/levels": "/schema/lib/levels.js",
14
+ "@forwardimpact/schema/loader": "/schema/lib/loader.js",
15
+ "@forwardimpact/schema/validation": "/schema/lib/validation.js",
16
+ "@forwardimpact/model": "/model/lib/index.js",
17
+ "@forwardimpact/model/derivation": "/model/lib/derivation.js",
18
+ "@forwardimpact/model/modifiers": "/model/lib/modifiers.js",
19
+ "@forwardimpact/model/agent": "/model/lib/agent.js",
20
+ "@forwardimpact/model/interview": "/model/lib/interview.js",
21
+ "@forwardimpact/model/job": "/model/lib/job.js",
22
+ "@forwardimpact/model/job-cache": "/model/lib/job-cache.js",
23
+ "@forwardimpact/model/checklist": "/model/lib/checklist.js",
24
+ "@forwardimpact/model/matching": "/model/lib/matching.js",
25
+ "@forwardimpact/model/profile": "/model/lib/profile.js",
26
+ "@forwardimpact/model/progression": "/model/lib/progression.js"
12
27
  }
13
28
  }
14
29
  </script>