@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.
- package/LICENSE +201 -0
- package/README.md +104 -0
- package/app/commands/agent.js +430 -0
- package/app/commands/behaviour.js +61 -0
- package/app/commands/command-factory.js +211 -0
- package/app/commands/discipline.js +58 -0
- package/app/commands/driver.js +94 -0
- package/app/commands/grade.js +60 -0
- package/app/commands/index.js +20 -0
- package/app/commands/init.js +67 -0
- package/app/commands/interview.js +68 -0
- package/app/commands/job.js +157 -0
- package/app/commands/progress.js +77 -0
- package/app/commands/questions.js +179 -0
- package/app/commands/serve.js +143 -0
- package/app/commands/site.js +121 -0
- package/app/commands/skill.js +76 -0
- package/app/commands/stage.js +129 -0
- package/app/commands/track.js +70 -0
- package/app/components/action-buttons.js +66 -0
- package/app/components/behaviour-profile.js +53 -0
- package/app/components/builder.js +341 -0
- package/app/components/card.js +98 -0
- package/app/components/checklist.js +145 -0
- package/app/components/comparison-radar.js +237 -0
- package/app/components/detail.js +230 -0
- package/app/components/error-page.js +72 -0
- package/app/components/grid.js +109 -0
- package/app/components/list.js +120 -0
- package/app/components/modifier-table.js +142 -0
- package/app/components/nav.js +64 -0
- package/app/components/progression-table.js +320 -0
- package/app/components/radar-chart.js +102 -0
- package/app/components/skill-matrix.js +97 -0
- package/app/css/base.css +56 -0
- package/app/css/bundles/app.css +40 -0
- package/app/css/bundles/handout.css +43 -0
- package/app/css/bundles/slides.css +40 -0
- package/app/css/components/badges.css +215 -0
- package/app/css/components/buttons.css +101 -0
- package/app/css/components/forms.css +105 -0
- package/app/css/components/layout.css +209 -0
- package/app/css/components/nav.css +166 -0
- package/app/css/components/progress.css +166 -0
- package/app/css/components/states.css +82 -0
- package/app/css/components/surfaces.css +243 -0
- package/app/css/components/tables.css +362 -0
- package/app/css/components/typography.css +122 -0
- package/app/css/components/utilities.css +41 -0
- package/app/css/pages/agent-builder.css +391 -0
- package/app/css/pages/assessment-results.css +453 -0
- package/app/css/pages/detail.css +59 -0
- package/app/css/pages/interview-builder.css +148 -0
- package/app/css/pages/job-builder.css +134 -0
- package/app/css/pages/landing.css +92 -0
- package/app/css/pages/lifecycle.css +118 -0
- package/app/css/pages/progress-builder.css +274 -0
- package/app/css/pages/self-assessment.css +502 -0
- package/app/css/reset.css +50 -0
- package/app/css/tokens.css +153 -0
- package/app/css/views/handout.css +30 -0
- package/app/css/views/print.css +608 -0
- package/app/css/views/slide-animations.css +113 -0
- package/app/css/views/slide-base.css +330 -0
- package/app/css/views/slide-sections.css +597 -0
- package/app/css/views/slide-tables.css +275 -0
- package/app/formatters/agent/dom.js +540 -0
- package/app/formatters/agent/profile.js +133 -0
- package/app/formatters/agent/skill.js +58 -0
- package/app/formatters/behaviour/dom.js +91 -0
- package/app/formatters/behaviour/markdown.js +54 -0
- package/app/formatters/behaviour/shared.js +64 -0
- package/app/formatters/discipline/dom.js +187 -0
- package/app/formatters/discipline/markdown.js +87 -0
- package/app/formatters/discipline/shared.js +131 -0
- package/app/formatters/driver/dom.js +103 -0
- package/app/formatters/driver/shared.js +92 -0
- package/app/formatters/grade/dom.js +208 -0
- package/app/formatters/grade/markdown.js +94 -0
- package/app/formatters/grade/shared.js +86 -0
- package/app/formatters/index.js +50 -0
- package/app/formatters/interview/dom.js +97 -0
- package/app/formatters/interview/markdown.js +66 -0
- package/app/formatters/interview/shared.js +332 -0
- package/app/formatters/job/description.js +176 -0
- package/app/formatters/job/dom.js +411 -0
- package/app/formatters/job/markdown.js +102 -0
- package/app/formatters/progress/dom.js +135 -0
- package/app/formatters/progress/markdown.js +86 -0
- package/app/formatters/progress/shared.js +339 -0
- package/app/formatters/questions/json.js +43 -0
- package/app/formatters/questions/markdown.js +303 -0
- package/app/formatters/questions/shared.js +274 -0
- package/app/formatters/questions/yaml.js +76 -0
- package/app/formatters/shared.js +71 -0
- package/app/formatters/skill/dom.js +168 -0
- package/app/formatters/skill/markdown.js +109 -0
- package/app/formatters/skill/shared.js +125 -0
- package/app/formatters/stage/dom.js +135 -0
- package/app/formatters/stage/index.js +12 -0
- package/app/formatters/stage/shared.js +111 -0
- package/app/formatters/track/dom.js +128 -0
- package/app/formatters/track/markdown.js +105 -0
- package/app/formatters/track/shared.js +181 -0
- package/app/handout-main.js +421 -0
- package/app/handout.html +21 -0
- package/app/index.html +59 -0
- package/app/lib/card-mappers.js +173 -0
- package/app/lib/cli-output.js +270 -0
- package/app/lib/error-boundary.js +70 -0
- package/app/lib/errors.js +49 -0
- package/app/lib/form-controls.js +47 -0
- package/app/lib/job-cache.js +86 -0
- package/app/lib/markdown.js +114 -0
- package/app/lib/radar.js +866 -0
- package/app/lib/reactive.js +77 -0
- package/app/lib/render.js +212 -0
- package/app/lib/router-core.js +160 -0
- package/app/lib/router-pages.js +16 -0
- package/app/lib/router-slides.js +202 -0
- package/app/lib/state.js +148 -0
- package/app/lib/utils.js +14 -0
- package/app/lib/yaml-loader.js +327 -0
- package/app/main.js +213 -0
- package/app/model/agent.js +702 -0
- package/app/model/checklist.js +137 -0
- package/app/model/derivation.js +699 -0
- package/app/model/index-generator.js +71 -0
- package/app/model/interview.js +539 -0
- package/app/model/job.js +222 -0
- package/app/model/levels.js +591 -0
- package/app/model/loader.js +564 -0
- package/app/model/matching.js +858 -0
- package/app/model/modifiers.js +158 -0
- package/app/model/profile.js +266 -0
- package/app/model/progression.js +507 -0
- package/app/model/validation.js +1385 -0
- package/app/pages/agent-builder.js +823 -0
- package/app/pages/assessment-results.js +507 -0
- package/app/pages/behaviour.js +70 -0
- package/app/pages/discipline.js +71 -0
- package/app/pages/driver.js +106 -0
- package/app/pages/grade.js +117 -0
- package/app/pages/interview-builder.js +50 -0
- package/app/pages/interview.js +304 -0
- package/app/pages/job-builder.js +50 -0
- package/app/pages/job.js +58 -0
- package/app/pages/landing.js +305 -0
- package/app/pages/progress-builder.js +58 -0
- package/app/pages/progress.js +495 -0
- package/app/pages/self-assessment.js +729 -0
- package/app/pages/skill.js +113 -0
- package/app/pages/stage.js +231 -0
- package/app/pages/track.js +69 -0
- package/app/slide-main.js +360 -0
- package/app/slides/behaviour.js +38 -0
- package/app/slides/chapter.js +82 -0
- package/app/slides/discipline.js +40 -0
- package/app/slides/driver.js +39 -0
- package/app/slides/grade.js +32 -0
- package/app/slides/index.js +198 -0
- package/app/slides/interview.js +58 -0
- package/app/slides/job.js +55 -0
- package/app/slides/overview.js +126 -0
- package/app/slides/progress.js +83 -0
- package/app/slides/skill.js +40 -0
- package/app/slides/track.js +39 -0
- package/app/slides.html +56 -0
- package/app/types.js +147 -0
- package/bin/pathway.js +489 -0
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
- package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
- package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
- package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
- package/examples/agents/.vscode/settings.json +8 -0
- package/examples/behaviours/_index.yaml +8 -0
- package/examples/behaviours/outcome_ownership.yaml +44 -0
- package/examples/behaviours/polymathic_knowledge.yaml +42 -0
- package/examples/behaviours/precise_communication.yaml +40 -0
- package/examples/behaviours/relentless_curiosity.yaml +38 -0
- package/examples/behaviours/systems_thinking.yaml +41 -0
- package/examples/capabilities/_index.yaml +8 -0
- package/examples/capabilities/business.yaml +251 -0
- package/examples/capabilities/delivery.yaml +352 -0
- package/examples/capabilities/people.yaml +100 -0
- package/examples/capabilities/reliability.yaml +318 -0
- package/examples/capabilities/scale.yaml +394 -0
- package/examples/disciplines/_index.yaml +5 -0
- package/examples/disciplines/data_engineering.yaml +76 -0
- package/examples/disciplines/software_engineering.yaml +76 -0
- package/examples/drivers.yaml +205 -0
- package/examples/framework.yaml +58 -0
- package/examples/grades.yaml +118 -0
- package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
- package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
- package/examples/questions/behaviours/precise_communication.yaml +55 -0
- package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
- package/examples/questions/behaviours/systems_thinking.yaml +53 -0
- package/examples/questions/skills/architecture_design.yaml +54 -0
- package/examples/questions/skills/cloud_platforms.yaml +48 -0
- package/examples/questions/skills/code_quality.yaml +49 -0
- package/examples/questions/skills/data_modeling.yaml +46 -0
- package/examples/questions/skills/devops.yaml +47 -0
- package/examples/questions/skills/full_stack_development.yaml +48 -0
- package/examples/questions/skills/sre_practices.yaml +44 -0
- package/examples/questions/skills/stakeholder_management.yaml +49 -0
- package/examples/questions/skills/team_collaboration.yaml +43 -0
- package/examples/questions/skills/technical_writing.yaml +43 -0
- package/examples/self-assessments.yaml +66 -0
- package/examples/stages.yaml +76 -0
- package/examples/tracks/_index.yaml +6 -0
- package/examples/tracks/manager.yaml +53 -0
- package/examples/tracks/platform.yaml +54 -0
- package/examples/tracks/sre.yaml +58 -0
- package/examples/vscode-settings.yaml +22 -0
- package/package.json +68 -0
package/app/lib/state.js
ADDED
|
@@ -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
|
+
}
|
package/app/lib/utils.js
ADDED
|
@@ -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();
|