@forwardimpact/pathway 0.1.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 (227) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/app/commands/agent.js +430 -0
  4. package/app/commands/behaviour.js +61 -0
  5. package/app/commands/command-factory.js +211 -0
  6. package/app/commands/discipline.js +58 -0
  7. package/app/commands/driver.js +94 -0
  8. package/app/commands/grade.js +60 -0
  9. package/app/commands/index.js +20 -0
  10. package/app/commands/init.js +67 -0
  11. package/app/commands/interview.js +68 -0
  12. package/app/commands/job.js +157 -0
  13. package/app/commands/progress.js +77 -0
  14. package/app/commands/questions.js +179 -0
  15. package/app/commands/serve.js +143 -0
  16. package/app/commands/site.js +121 -0
  17. package/app/commands/skill.js +76 -0
  18. package/app/commands/stage.js +129 -0
  19. package/app/commands/track.js +70 -0
  20. package/app/components/action-buttons.js +66 -0
  21. package/app/components/behaviour-profile.js +53 -0
  22. package/app/components/builder.js +341 -0
  23. package/app/components/card.js +98 -0
  24. package/app/components/checklist.js +145 -0
  25. package/app/components/comparison-radar.js +237 -0
  26. package/app/components/detail.js +230 -0
  27. package/app/components/error-page.js +72 -0
  28. package/app/components/grid.js +109 -0
  29. package/app/components/list.js +120 -0
  30. package/app/components/modifier-table.js +142 -0
  31. package/app/components/nav.js +64 -0
  32. package/app/components/progression-table.js +320 -0
  33. package/app/components/radar-chart.js +102 -0
  34. package/app/components/skill-matrix.js +97 -0
  35. package/app/css/base.css +56 -0
  36. package/app/css/bundles/app.css +40 -0
  37. package/app/css/bundles/handout.css +43 -0
  38. package/app/css/bundles/slides.css +40 -0
  39. package/app/css/components/badges.css +215 -0
  40. package/app/css/components/buttons.css +101 -0
  41. package/app/css/components/forms.css +105 -0
  42. package/app/css/components/layout.css +209 -0
  43. package/app/css/components/nav.css +166 -0
  44. package/app/css/components/progress.css +166 -0
  45. package/app/css/components/states.css +82 -0
  46. package/app/css/components/surfaces.css +243 -0
  47. package/app/css/components/tables.css +362 -0
  48. package/app/css/components/typography.css +122 -0
  49. package/app/css/components/utilities.css +41 -0
  50. package/app/css/pages/agent-builder.css +391 -0
  51. package/app/css/pages/assessment-results.css +453 -0
  52. package/app/css/pages/detail.css +59 -0
  53. package/app/css/pages/interview-builder.css +148 -0
  54. package/app/css/pages/job-builder.css +134 -0
  55. package/app/css/pages/landing.css +92 -0
  56. package/app/css/pages/lifecycle.css +118 -0
  57. package/app/css/pages/progress-builder.css +274 -0
  58. package/app/css/pages/self-assessment.css +502 -0
  59. package/app/css/reset.css +50 -0
  60. package/app/css/tokens.css +153 -0
  61. package/app/css/views/handout.css +30 -0
  62. package/app/css/views/print.css +608 -0
  63. package/app/css/views/slide-animations.css +113 -0
  64. package/app/css/views/slide-base.css +330 -0
  65. package/app/css/views/slide-sections.css +597 -0
  66. package/app/css/views/slide-tables.css +275 -0
  67. package/app/formatters/agent/dom.js +540 -0
  68. package/app/formatters/agent/profile.js +133 -0
  69. package/app/formatters/agent/skill.js +58 -0
  70. package/app/formatters/behaviour/dom.js +91 -0
  71. package/app/formatters/behaviour/markdown.js +54 -0
  72. package/app/formatters/behaviour/shared.js +64 -0
  73. package/app/formatters/discipline/dom.js +187 -0
  74. package/app/formatters/discipline/markdown.js +87 -0
  75. package/app/formatters/discipline/shared.js +131 -0
  76. package/app/formatters/driver/dom.js +103 -0
  77. package/app/formatters/driver/shared.js +92 -0
  78. package/app/formatters/grade/dom.js +208 -0
  79. package/app/formatters/grade/markdown.js +94 -0
  80. package/app/formatters/grade/shared.js +86 -0
  81. package/app/formatters/index.js +50 -0
  82. package/app/formatters/interview/dom.js +97 -0
  83. package/app/formatters/interview/markdown.js +66 -0
  84. package/app/formatters/interview/shared.js +332 -0
  85. package/app/formatters/job/description.js +176 -0
  86. package/app/formatters/job/dom.js +411 -0
  87. package/app/formatters/job/markdown.js +102 -0
  88. package/app/formatters/progress/dom.js +135 -0
  89. package/app/formatters/progress/markdown.js +86 -0
  90. package/app/formatters/progress/shared.js +339 -0
  91. package/app/formatters/questions/json.js +43 -0
  92. package/app/formatters/questions/markdown.js +303 -0
  93. package/app/formatters/questions/shared.js +274 -0
  94. package/app/formatters/questions/yaml.js +76 -0
  95. package/app/formatters/shared.js +71 -0
  96. package/app/formatters/skill/dom.js +168 -0
  97. package/app/formatters/skill/markdown.js +109 -0
  98. package/app/formatters/skill/shared.js +125 -0
  99. package/app/formatters/stage/dom.js +135 -0
  100. package/app/formatters/stage/index.js +12 -0
  101. package/app/formatters/stage/shared.js +111 -0
  102. package/app/formatters/track/dom.js +128 -0
  103. package/app/formatters/track/markdown.js +105 -0
  104. package/app/formatters/track/shared.js +181 -0
  105. package/app/handout-main.js +421 -0
  106. package/app/handout.html +21 -0
  107. package/app/index.html +59 -0
  108. package/app/lib/card-mappers.js +173 -0
  109. package/app/lib/cli-output.js +270 -0
  110. package/app/lib/error-boundary.js +70 -0
  111. package/app/lib/errors.js +49 -0
  112. package/app/lib/form-controls.js +47 -0
  113. package/app/lib/job-cache.js +86 -0
  114. package/app/lib/markdown.js +114 -0
  115. package/app/lib/radar.js +866 -0
  116. package/app/lib/reactive.js +77 -0
  117. package/app/lib/render.js +212 -0
  118. package/app/lib/router-core.js +160 -0
  119. package/app/lib/router-pages.js +16 -0
  120. package/app/lib/router-slides.js +202 -0
  121. package/app/lib/state.js +148 -0
  122. package/app/lib/utils.js +14 -0
  123. package/app/lib/yaml-loader.js +327 -0
  124. package/app/main.js +213 -0
  125. package/app/model/agent.js +702 -0
  126. package/app/model/checklist.js +137 -0
  127. package/app/model/derivation.js +699 -0
  128. package/app/model/index-generator.js +71 -0
  129. package/app/model/interview.js +539 -0
  130. package/app/model/job.js +222 -0
  131. package/app/model/levels.js +591 -0
  132. package/app/model/loader.js +564 -0
  133. package/app/model/matching.js +858 -0
  134. package/app/model/modifiers.js +158 -0
  135. package/app/model/profile.js +266 -0
  136. package/app/model/progression.js +507 -0
  137. package/app/model/validation.js +1385 -0
  138. package/app/pages/agent-builder.js +823 -0
  139. package/app/pages/assessment-results.js +507 -0
  140. package/app/pages/behaviour.js +70 -0
  141. package/app/pages/discipline.js +71 -0
  142. package/app/pages/driver.js +106 -0
  143. package/app/pages/grade.js +117 -0
  144. package/app/pages/interview-builder.js +50 -0
  145. package/app/pages/interview.js +304 -0
  146. package/app/pages/job-builder.js +50 -0
  147. package/app/pages/job.js +58 -0
  148. package/app/pages/landing.js +305 -0
  149. package/app/pages/progress-builder.js +58 -0
  150. package/app/pages/progress.js +495 -0
  151. package/app/pages/self-assessment.js +729 -0
  152. package/app/pages/skill.js +113 -0
  153. package/app/pages/stage.js +231 -0
  154. package/app/pages/track.js +69 -0
  155. package/app/slide-main.js +360 -0
  156. package/app/slides/behaviour.js +38 -0
  157. package/app/slides/chapter.js +82 -0
  158. package/app/slides/discipline.js +40 -0
  159. package/app/slides/driver.js +39 -0
  160. package/app/slides/grade.js +32 -0
  161. package/app/slides/index.js +198 -0
  162. package/app/slides/interview.js +58 -0
  163. package/app/slides/job.js +55 -0
  164. package/app/slides/overview.js +126 -0
  165. package/app/slides/progress.js +83 -0
  166. package/app/slides/skill.js +40 -0
  167. package/app/slides/track.js +39 -0
  168. package/app/slides.html +56 -0
  169. package/app/types.js +147 -0
  170. package/bin/pathway.js +489 -0
  171. package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
  172. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
  173. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
  174. package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
  175. package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
  176. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
  177. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
  178. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
  179. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
  180. package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
  181. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
  182. package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
  183. package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
  184. package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
  185. package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
  186. package/examples/agents/.vscode/settings.json +8 -0
  187. package/examples/behaviours/_index.yaml +8 -0
  188. package/examples/behaviours/outcome_ownership.yaml +44 -0
  189. package/examples/behaviours/polymathic_knowledge.yaml +42 -0
  190. package/examples/behaviours/precise_communication.yaml +40 -0
  191. package/examples/behaviours/relentless_curiosity.yaml +38 -0
  192. package/examples/behaviours/systems_thinking.yaml +41 -0
  193. package/examples/capabilities/_index.yaml +8 -0
  194. package/examples/capabilities/business.yaml +251 -0
  195. package/examples/capabilities/delivery.yaml +352 -0
  196. package/examples/capabilities/people.yaml +100 -0
  197. package/examples/capabilities/reliability.yaml +318 -0
  198. package/examples/capabilities/scale.yaml +394 -0
  199. package/examples/disciplines/_index.yaml +5 -0
  200. package/examples/disciplines/data_engineering.yaml +76 -0
  201. package/examples/disciplines/software_engineering.yaml +76 -0
  202. package/examples/drivers.yaml +205 -0
  203. package/examples/framework.yaml +58 -0
  204. package/examples/grades.yaml +118 -0
  205. package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
  206. package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
  207. package/examples/questions/behaviours/precise_communication.yaml +55 -0
  208. package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
  209. package/examples/questions/behaviours/systems_thinking.yaml +53 -0
  210. package/examples/questions/skills/architecture_design.yaml +54 -0
  211. package/examples/questions/skills/cloud_platforms.yaml +48 -0
  212. package/examples/questions/skills/code_quality.yaml +49 -0
  213. package/examples/questions/skills/data_modeling.yaml +46 -0
  214. package/examples/questions/skills/devops.yaml +47 -0
  215. package/examples/questions/skills/full_stack_development.yaml +48 -0
  216. package/examples/questions/skills/sre_practices.yaml +44 -0
  217. package/examples/questions/skills/stakeholder_management.yaml +49 -0
  218. package/examples/questions/skills/team_collaboration.yaml +43 -0
  219. package/examples/questions/skills/technical_writing.yaml +43 -0
  220. package/examples/self-assessments.yaml +66 -0
  221. package/examples/stages.yaml +76 -0
  222. package/examples/tracks/_index.yaml +6 -0
  223. package/examples/tracks/manager.yaml +53 -0
  224. package/examples/tracks/platform.yaml +54 -0
  225. package/examples/tracks/sre.yaml +58 -0
  226. package/examples/vscode-settings.yaml +22 -0
  227. package/package.json +68 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Application state management
3
+ */
4
+
5
+ /**
6
+ * @typedef {Object} AppState
7
+ * @property {Object} data - Loaded data from YAML files
8
+ * @property {Object} ui - UI state
9
+ */
10
+
11
+ /** @type {AppState} */
12
+ const state = {
13
+ data: {
14
+ skills: [],
15
+ behaviours: [],
16
+ disciplines: [],
17
+ tracks: [],
18
+ grades: [],
19
+ drivers: [],
20
+ questions: {},
21
+ capabilities: [],
22
+ stages: [],
23
+ framework: {},
24
+ loaded: false,
25
+ error: null,
26
+ },
27
+ ui: {
28
+ currentRoute: "/",
29
+ filters: {
30
+ skills: { capability: null, search: "" },
31
+ behaviours: { search: "" },
32
+ disciplines: { search: "" },
33
+ tracks: { search: "" },
34
+ grades: { search: "" },
35
+ drivers: { search: "" },
36
+ },
37
+ },
38
+ };
39
+
40
+ /** @type {Set<Function>} */
41
+ const listeners = new Set();
42
+
43
+ /**
44
+ * Get the current state
45
+ * @returns {AppState}
46
+ */
47
+ export function getState() {
48
+ return state;
49
+ }
50
+
51
+ /**
52
+ * Get a specific path from state
53
+ * @param {string} path - Dot-notation path (e.g., 'data.skills')
54
+ * @returns {*}
55
+ */
56
+ export function getStatePath(path) {
57
+ return path.split(".").reduce((obj, key) => obj?.[key], state);
58
+ }
59
+
60
+ /**
61
+ * Update state at a specific path
62
+ * @param {string} path - Dot-notation path
63
+ * @param {*} value - New value
64
+ */
65
+ export function updateState(path, value) {
66
+ const keys = path.split(".");
67
+ const lastKey = keys.pop();
68
+ const target = keys.reduce((obj, key) => obj[key], state);
69
+ target[lastKey] = value;
70
+ notifyListeners();
71
+ }
72
+
73
+ /**
74
+ * Merge data into state
75
+ * @param {Object} data - Data to merge
76
+ */
77
+ export function setData(data) {
78
+ Object.assign(state.data, data, { loaded: true, error: null });
79
+ notifyListeners();
80
+ }
81
+
82
+ /**
83
+ * Set an error in state
84
+ * @param {Error} error
85
+ */
86
+ export function setError(error) {
87
+ state.data.error = error.message;
88
+ notifyListeners();
89
+ }
90
+
91
+ /**
92
+ * Subscribe to state changes
93
+ * @param {Function} listener
94
+ * @returns {Function} Unsubscribe function
95
+ */
96
+ export function subscribe(listener) {
97
+ listeners.add(listener);
98
+ return () => listeners.delete(listener);
99
+ }
100
+
101
+ /**
102
+ * Notify all listeners of state change
103
+ */
104
+ function notifyListeners() {
105
+ listeners.forEach((listener) => listener(state));
106
+ }
107
+
108
+ /**
109
+ * Update a filter
110
+ * @param {string} entity - Entity type (skills, behaviours, etc.)
111
+ * @param {string} filterKey - Filter key
112
+ * @param {*} value - Filter value
113
+ */
114
+ export function setFilter(entity, filterKey, value) {
115
+ if (state.ui.filters[entity]) {
116
+ state.ui.filters[entity][filterKey] = value;
117
+ notifyListeners();
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Get filters for an entity
123
+ * @param {string} entity
124
+ * @returns {Object}
125
+ */
126
+ export function getFilters(entity) {
127
+ return state.ui.filters[entity] || {};
128
+ }
129
+
130
+ /**
131
+ * @typedef {Object} Branding
132
+ * @property {string} title - Application title
133
+ * @property {string} tag - Brand hashtag/tag
134
+ * @property {string} description - Application description
135
+ */
136
+
137
+ /**
138
+ * Get branding elements from framework data
139
+ * @returns {Branding}
140
+ */
141
+ export function getBranding() {
142
+ const { framework } = state.data;
143
+ return {
144
+ title: framework.title || "Engineering Pathway",
145
+ tag: framework.tag || "#BenchTools",
146
+ description: framework.description || "",
147
+ };
148
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * General utility functions
3
+ */
4
+
5
+ /**
6
+ * Get an array of items by their IDs
7
+ * @param {Array} items - Array of items with id property
8
+ * @param {string[]} ids - Array of IDs to find
9
+ * @returns {Array} - Found items, filtered to remove nulls
10
+ */
11
+ export function getItemsByIds(items, ids) {
12
+ if (!ids) return [];
13
+ return ids.map((id) => items.find((item) => item.id === id)).filter(Boolean);
14
+ }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Browser-compatible YAML loading
3
+ *
4
+ * Uses _index.yaml files to discover files in directory structures.
5
+ * These index files are auto-generated by the CLI (npx pathway --generate-index).
6
+ */
7
+
8
+ import { parse as parseYaml } from "https://cdn.jsdelivr.net/npm/yaml@2.3.4/+esm";
9
+
10
+ /**
11
+ * Load and parse a YAML file
12
+ * @param {string} path - Path to the YAML file
13
+ * @returns {Promise<*>}
14
+ */
15
+ export async function loadYamlFile(path) {
16
+ const response = await fetch(path);
17
+ if (!response.ok) {
18
+ throw new Error(
19
+ `Failed to load ${path}: ${response.status} ${response.statusText}`,
20
+ );
21
+ }
22
+ const text = await response.text();
23
+ return parseYaml(text);
24
+ }
25
+
26
+ /**
27
+ * Try to load a YAML file, return null if not found
28
+ * @param {string} path - Path to the YAML file
29
+ * @returns {Promise<*|null>}
30
+ */
31
+ async function tryLoadYamlFile(path) {
32
+ const response = await fetch(path);
33
+ if (response.status === 404) {
34
+ return null;
35
+ }
36
+ if (!response.ok) {
37
+ throw new Error(
38
+ `Failed to load ${path}: ${response.status} ${response.statusText}`,
39
+ );
40
+ }
41
+ const text = await response.text();
42
+ return parseYaml(text);
43
+ }
44
+
45
+ /**
46
+ * Load directory index (list of file IDs)
47
+ * @param {string} dir - Directory path
48
+ * @returns {Promise<string[]>} Array of file IDs
49
+ */
50
+ async function loadDirIndex(dir) {
51
+ const index = await loadYamlFile(`${dir}/_index.yaml`);
52
+ return index.files || [];
53
+ }
54
+
55
+ /**
56
+ * Load skills from capability files
57
+ * Skills are embedded in capability YAML files under the 'skills' array.
58
+ * This function extracts all skills and adds the capability ID back to each.
59
+ * @param {string} capabilitiesDir - Path to capabilities directory
60
+ * @returns {Promise<Array>} Array of skill objects
61
+ */
62
+ async function loadSkillsFromCapabilities(capabilitiesDir) {
63
+ const capabilityIds = await loadDirIndex(capabilitiesDir);
64
+ const allSkills = [];
65
+
66
+ for (const capabilityId of capabilityIds) {
67
+ const capability = await loadYamlFile(
68
+ `${capabilitiesDir}/${capabilityId}.yaml`,
69
+ );
70
+
71
+ if (capability.skills && Array.isArray(capability.skills)) {
72
+ for (const skill of capability.skills) {
73
+ const { id, name, isHumanOnly, human, agent } = skill;
74
+ allSkills.push({
75
+ id,
76
+ name,
77
+ capability: capabilityId, // Add capability from parent
78
+ description: human.description,
79
+ levelDescriptions: human.levelDescriptions,
80
+ // Include isHumanOnly flag for agent filtering (defaults to false)
81
+ ...(isHumanOnly && { isHumanOnly }),
82
+ ...(agent && { agent }),
83
+ });
84
+ }
85
+ }
86
+ }
87
+
88
+ return allSkills;
89
+ }
90
+
91
+ /**
92
+ * Load disciplines from directory using _index.yaml
93
+ * @param {string} disciplinesDir - Path to disciplines directory
94
+ * @returns {Promise<Array>} Array of discipline objects
95
+ */
96
+ async function loadDisciplinesFromDir(disciplinesDir) {
97
+ const disciplineIds = await loadDirIndex(disciplinesDir);
98
+
99
+ const disciplines = await Promise.all(
100
+ disciplineIds.map(async (id) => {
101
+ const content = await loadYamlFile(`${disciplinesDir}/${id}.yaml`);
102
+ // Shared content at top level, role summaries under human:
103
+ const {
104
+ specialization,
105
+ roleTitle,
106
+ // Shared content - now at root level
107
+ description,
108
+ // Structural properties (derivation inputs)
109
+ coreSkills,
110
+ supportingSkills,
111
+ broadSkills,
112
+ behaviourModifiers,
113
+ // Presentation sections
114
+ human,
115
+ agent,
116
+ } = content;
117
+ return {
118
+ id,
119
+ specialization,
120
+ roleTitle,
121
+ // Shared content at top level
122
+ description,
123
+ // Structural properties
124
+ coreSkills,
125
+ supportingSkills,
126
+ broadSkills,
127
+ behaviourModifiers,
128
+ // Human presentation content (role summaries only)
129
+ ...human,
130
+ ...(agent && { agent }),
131
+ };
132
+ }),
133
+ );
134
+ return disciplines;
135
+ }
136
+
137
+ /**
138
+ * Load tracks from directory using _index.yaml
139
+ * @param {string} tracksDir - Path to tracks directory
140
+ * @returns {Promise<Array>} Array of track objects
141
+ */
142
+ async function loadTracksFromDir(tracksDir) {
143
+ const trackIds = await loadDirIndex(tracksDir);
144
+
145
+ const tracks = await Promise.all(
146
+ trackIds.map(async (id) => {
147
+ const content = await loadYamlFile(`${tracksDir}/${id}.yaml`);
148
+ // Shared content at top level (no human section for tracks anymore)
149
+ const {
150
+ name,
151
+ // Shared content - now at root level
152
+ description,
153
+ roleContext,
154
+ // Structural properties (derivation inputs)
155
+ isProfessional,
156
+ isManagement,
157
+ skillModifiers,
158
+ behaviourModifiers,
159
+ matchingWeights,
160
+ validDisciplines,
161
+ // Agent section (no human section anymore for tracks)
162
+ agent,
163
+ } = content;
164
+ return {
165
+ id,
166
+ name,
167
+ // Shared content at top level
168
+ description,
169
+ roleContext,
170
+ // Structural properties
171
+ isProfessional,
172
+ isManagement,
173
+ skillModifiers,
174
+ behaviourModifiers,
175
+ matchingWeights,
176
+ validDisciplines,
177
+ ...(agent && { agent }),
178
+ };
179
+ }),
180
+ );
181
+ return tracks;
182
+ }
183
+
184
+ /**
185
+ * Load behaviours from directory using _index.yaml
186
+ * @param {string} behavioursDir - Path to behaviours directory
187
+ * @returns {Promise<Array>} Array of behaviour objects
188
+ */
189
+ async function loadBehavioursFromDir(behavioursDir) {
190
+ const behaviourIds = await loadDirIndex(behavioursDir);
191
+
192
+ const behaviours = await Promise.all(
193
+ behaviourIds.map(async (id) => {
194
+ const content = await loadYamlFile(`${behavioursDir}/${id}.yaml`);
195
+ // Flatten human properties to top level (behaviours use human: section in YAML)
196
+ const { name, human, agent } = content;
197
+ return {
198
+ id,
199
+ name,
200
+ ...human,
201
+ ...(agent && { agent }),
202
+ };
203
+ }),
204
+ );
205
+ return behaviours;
206
+ }
207
+
208
+ /**
209
+ * Load capabilities from directory using _index.yaml
210
+ * @param {string} capabilitiesDir - Path to capabilities directory
211
+ * @returns {Promise<Array>} Array of capability objects
212
+ */
213
+ async function loadCapabilitiesFromDir(capabilitiesDir) {
214
+ const capabilityIds = await loadDirIndex(capabilitiesDir);
215
+
216
+ const capabilities = await Promise.all(
217
+ capabilityIds.map((id) => loadYamlFile(`${capabilitiesDir}/${id}.yaml`)),
218
+ );
219
+ return capabilities;
220
+ }
221
+
222
+ /**
223
+ * Load questions from folder structure using skill/behaviour IDs
224
+ * @param {string} questionsDir - Path to questions directory
225
+ * @param {Array} skills - Skills array (with id property)
226
+ * @param {Array} behaviours - Behaviours array (with id property)
227
+ * @returns {Promise<Object>}
228
+ */
229
+ async function loadQuestionFolder(questionsDir, skills, behaviours) {
230
+ const [skillEntries, behaviourEntries] = await Promise.all([
231
+ Promise.all(
232
+ skills.map(async (skill) => {
233
+ const content = await tryLoadYamlFile(
234
+ `${questionsDir}/skills/${skill.id}.yaml`,
235
+ );
236
+ return [skill.id, content || {}];
237
+ }),
238
+ ),
239
+ Promise.all(
240
+ behaviours.map(async (behaviour) => {
241
+ const content = await tryLoadYamlFile(
242
+ `${questionsDir}/behaviours/${behaviour.id}.yaml`,
243
+ );
244
+ return [behaviour.id, content || {}];
245
+ }),
246
+ ),
247
+ ]);
248
+
249
+ return {
250
+ skillLevels: Object.fromEntries(skillEntries),
251
+ behaviourMaturities: Object.fromEntries(behaviourEntries),
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Load all data files
257
+ * @param {string} [dataDir='./data'] - Path to data directory
258
+ * @returns {Promise<Object>}
259
+ */
260
+ export async function loadAllData(dataDir = "./data") {
261
+ // Load capabilities first (skills are embedded in capabilities)
262
+ const capabilities = await loadCapabilitiesFromDir(`${dataDir}/capabilities`);
263
+
264
+ // Extract skills from capabilities
265
+ const skills = await loadSkillsFromCapabilities(`${dataDir}/capabilities`);
266
+
267
+ // Load remaining core data in parallel (using _index.yaml for discovery)
268
+ const [drivers, behaviours, disciplines, tracks, grades, stages, framework] =
269
+ await Promise.all([
270
+ loadYamlFile(`${dataDir}/drivers.yaml`),
271
+ loadBehavioursFromDir(`${dataDir}/behaviours`),
272
+ loadDisciplinesFromDir(`${dataDir}/disciplines`),
273
+ loadTracksFromDir(`${dataDir}/tracks`),
274
+ loadYamlFile(`${dataDir}/grades.yaml`),
275
+ loadYamlFile(`${dataDir}/stages.yaml`),
276
+ loadYamlFile(`${dataDir}/framework.yaml`),
277
+ ]);
278
+
279
+ // Load questions using skill/behaviour IDs
280
+ const questions = await loadQuestionFolder(
281
+ `${dataDir}/questions`,
282
+ skills,
283
+ behaviours,
284
+ );
285
+
286
+ return {
287
+ drivers,
288
+ behaviours,
289
+ skills,
290
+ disciplines,
291
+ tracks,
292
+ grades,
293
+ questions,
294
+ capabilities,
295
+ stages,
296
+ framework,
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Load agent-specific data for browser-based agent generation
302
+ * Uses co-located files where agent sections are embedded in entity files
303
+ * @param {string} [dataDir='./data'] - Path to data directory
304
+ * @returns {Promise<Object>} Agent data including disciplines, tracks, behaviours, vscodeSettings
305
+ */
306
+ export async function loadAgentDataBrowser(dataDir = "./data") {
307
+ const [disciplines, tracks, behaviours, vscodeSettings] = await Promise.all([
308
+ loadDisciplinesFromDir(`${dataDir}/disciplines`),
309
+ loadTracksFromDir(`${dataDir}/tracks`),
310
+ loadBehavioursFromDir(`${dataDir}/behaviours`),
311
+ tryLoadYamlFile(`${dataDir}/vscode-settings.yaml`),
312
+ ]);
313
+
314
+ // Extract agent sections from co-located files
315
+ return {
316
+ disciplines: disciplines
317
+ .filter((d) => d.agent)
318
+ .map((d) => ({ id: d.id, ...d.agent })),
319
+ tracks: tracks
320
+ .filter((t) => t.agent)
321
+ .map((t) => ({ id: t.id, ...t.agent })),
322
+ behaviours: behaviours
323
+ .filter((b) => b.agent)
324
+ .map((b) => ({ id: b.id, ...b.agent })),
325
+ vscodeSettings: vscodeSettings || {},
326
+ };
327
+ }
package/app/main.js ADDED
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Main application entry point
3
+ */
4
+
5
+ import { createPagesRouter } from "./lib/router-pages.js";
6
+ import { setData, setError, getBranding } from "./lib/state.js";
7
+ import { loadAllData } from "./lib/yaml-loader.js";
8
+ import { render, div, h1, p, showError } from "./lib/render.js";
9
+
10
+ const router = createPagesRouter({
11
+ onNotFound: renderNotFound,
12
+ });
13
+
14
+ // Import pages
15
+ import { renderLanding } from "./pages/landing.js";
16
+ import { renderSkillsList, renderSkillDetail } from "./pages/skill.js";
17
+ import {
18
+ renderBehavioursList,
19
+ renderBehaviourDetail,
20
+ } from "./pages/behaviour.js";
21
+ import {
22
+ renderDisciplinesList,
23
+ renderDisciplineDetail,
24
+ } from "./pages/discipline.js";
25
+ import { renderTracksList, renderTrackDetail } from "./pages/track.js";
26
+ import { renderGradesList, renderGradeDetail } from "./pages/grade.js";
27
+ import { renderDriversList, renderDriverDetail } from "./pages/driver.js";
28
+ import { renderStagesList, renderStageDetail } from "./pages/stage.js";
29
+ import { renderJobBuilder } from "./pages/job-builder.js";
30
+ import { renderJobDetail } from "./pages/job.js";
31
+ import { renderInterviewPrep } from "./pages/interview-builder.js";
32
+ import { renderInterviewDetail } from "./pages/interview.js";
33
+ import { renderCareerProgress } from "./pages/progress-builder.js";
34
+ import { renderProgressDetail } from "./pages/progress.js";
35
+ import { renderSelfAssessment } from "./pages/self-assessment.js";
36
+ import { renderAssessmentResults } from "./pages/assessment-results.js";
37
+ import { renderAgentBuilder } from "./pages/agent-builder.js";
38
+
39
+ /**
40
+ * Initialize the application
41
+ */
42
+ async function init() {
43
+ // Set up navigation toggle for mobile
44
+ setupMobileNav();
45
+
46
+ // Load data
47
+ try {
48
+ const data = await loadAllData("./data");
49
+ setData(data);
50
+
51
+ // Populate branding from framework data
52
+ populateBranding();
53
+ } catch (error) {
54
+ console.error("Failed to load data:", error);
55
+ setError(error);
56
+ showError(`Failed to load data: ${error.message}`);
57
+ return;
58
+ }
59
+
60
+ // Set up routes
61
+ setupRoutes();
62
+
63
+ // Start router
64
+ router.start();
65
+ }
66
+
67
+ /**
68
+ * Set up all application routes
69
+ */
70
+ function setupRoutes() {
71
+ // Landing page
72
+ router.on("/", renderLanding);
73
+
74
+ // Skill
75
+ router.on("/skill", renderSkillsList);
76
+ router.on("/skill/:id", renderSkillDetail);
77
+
78
+ // Behaviour
79
+ router.on("/behaviour", renderBehavioursList);
80
+ router.on("/behaviour/:id", renderBehaviourDetail);
81
+
82
+ // Discipline
83
+ router.on("/discipline", renderDisciplinesList);
84
+ router.on("/discipline/:id", renderDisciplineDetail);
85
+
86
+ // Track
87
+ router.on("/track", renderTracksList);
88
+ router.on("/track/:id", renderTrackDetail);
89
+
90
+ // Grade
91
+ router.on("/grade", renderGradesList);
92
+ router.on("/grade/:id", renderGradeDetail);
93
+
94
+ // Driver
95
+ router.on("/driver", renderDriversList);
96
+ router.on("/driver/:id", renderDriverDetail);
97
+
98
+ // Stage
99
+ router.on("/stage", renderStagesList);
100
+ router.on("/stage/:id", renderStageDetail);
101
+
102
+ // Job builder
103
+ router.on("/job-builder", renderJobBuilder);
104
+ router.on("/job/:discipline/:track/:grade", renderJobDetail);
105
+
106
+ // Interview prep
107
+ router.on("/interview-prep", renderInterviewPrep);
108
+ router.on("/interview/:discipline/:track/:grade", renderInterviewDetail);
109
+
110
+ // Career progress
111
+ router.on("/career-progress", renderCareerProgress);
112
+ router.on("/progress/:discipline/:track/:grade", renderProgressDetail);
113
+
114
+ // Self-assessment
115
+ router.on("/self-assessment", renderSelfAssessment);
116
+ router.on("/self-assessment/results", renderAssessmentResults);
117
+
118
+ // Agent builder
119
+ router.on("/agent-builder", renderAgentBuilder);
120
+ router.on("/agent/:discipline/:track", renderAgentBuilder);
121
+ router.on("/agent/:discipline/:track/:stage", renderAgentBuilder);
122
+ }
123
+
124
+ /**
125
+ * Render 404 page
126
+ */
127
+ function renderNotFound() {
128
+ render(
129
+ div(
130
+ { className: "not-found" },
131
+ h1({}, "404 - Page Not Found"),
132
+ p({}, "The page you are looking for does not exist."),
133
+ div(
134
+ { className: "not-found-actions" },
135
+ div(
136
+ {},
137
+ createElement(
138
+ "a",
139
+ { href: "#/", className: "btn btn-primary" },
140
+ "Go Home",
141
+ ),
142
+ ),
143
+ ),
144
+ ),
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Helper to create elements (used in not found)
150
+ */
151
+ function createElement(tag, attrs, text) {
152
+ const el = document.createElement(tag);
153
+ Object.entries(attrs).forEach(([key, value]) => {
154
+ if (key === "className") el.className = value;
155
+ else el.setAttribute(key, value);
156
+ });
157
+ if (text) el.textContent = text;
158
+ return el;
159
+ }
160
+
161
+ /**
162
+ * Populate branding elements from framework data
163
+ */
164
+ function populateBranding() {
165
+ const branding = getBranding();
166
+
167
+ // Update document title
168
+ document.title = branding.title;
169
+
170
+ // Update nav brand
171
+ const navBrand = document.querySelector(".nav-brand a");
172
+ if (navBrand) {
173
+ navBrand.textContent = branding.title;
174
+ }
175
+
176
+ // Update nav brand tag
177
+ const brandTag = document.querySelector(".nav-brand .brand-tag");
178
+ if (brandTag) {
179
+ brandTag.textContent = branding.tag;
180
+ }
181
+
182
+ // Update footer
183
+ const footer = document.querySelector("#app-footer p");
184
+ if (footer) {
185
+ footer.textContent = branding.title;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Set up mobile navigation toggle
191
+ */
192
+ function setupMobileNav() {
193
+ const toggle = document.getElementById("nav-toggle");
194
+ const links = document.getElementById("nav-links");
195
+
196
+ if (toggle && links) {
197
+ toggle.addEventListener("click", () => {
198
+ links.classList.toggle("nav-open");
199
+ toggle.classList.toggle("nav-toggle-active");
200
+ });
201
+
202
+ // Close menu when a link is clicked
203
+ links.addEventListener("click", (e) => {
204
+ if (e.target.tagName === "A") {
205
+ links.classList.remove("nav-open");
206
+ toggle.classList.remove("nav-toggle-active");
207
+ }
208
+ });
209
+ }
210
+ }
211
+
212
+ // Start the app
213
+ init();