@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
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engineering Pathway Data Loader
|
|
3
|
+
*
|
|
4
|
+
* Utility for loading and parsing YAML data files.
|
|
5
|
+
* Uses directory structure: disciplines/, tracks/, skills/
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
import { join, basename } from "path";
|
|
11
|
+
import { validateAllData, validateQuestionBank } from "./validation.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a file exists
|
|
15
|
+
* @param {string} path - Path to check
|
|
16
|
+
* @returns {Promise<boolean>} True if file exists
|
|
17
|
+
*/
|
|
18
|
+
async function fileExists(path) {
|
|
19
|
+
try {
|
|
20
|
+
await stat(path);
|
|
21
|
+
return true;
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Load a YAML file and parse it
|
|
29
|
+
* @param {string} filePath - Path to the YAML file
|
|
30
|
+
* @returns {Promise<any>} Parsed YAML content
|
|
31
|
+
*/
|
|
32
|
+
export async function loadYamlFile(filePath) {
|
|
33
|
+
const content = await readFile(filePath, "utf-8");
|
|
34
|
+
return parseYaml(content);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Load framework configuration from a data directory
|
|
39
|
+
* @param {string} dataDir - Path to the data directory
|
|
40
|
+
* @returns {Promise<Object>} Framework configuration
|
|
41
|
+
*/
|
|
42
|
+
export async function loadFrameworkConfig(dataDir) {
|
|
43
|
+
return loadYamlFile(join(dataDir, "framework.yaml"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load all question files from a directory
|
|
48
|
+
* @param {string} dir - Directory path
|
|
49
|
+
* @returns {Promise<Object>} Map of id to question levels
|
|
50
|
+
*/
|
|
51
|
+
async function loadQuestionsFromDir(dir) {
|
|
52
|
+
const files = await readdir(dir);
|
|
53
|
+
const yamlFiles = files.filter((f) => f.endsWith(".yaml"));
|
|
54
|
+
|
|
55
|
+
const entries = await Promise.all(
|
|
56
|
+
yamlFiles.map(async (file) => {
|
|
57
|
+
const id = basename(file, ".yaml");
|
|
58
|
+
const content = await loadYamlFile(join(dir, file));
|
|
59
|
+
return [id, content];
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return Object.fromEntries(entries);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Load skills from capability files
|
|
68
|
+
* Skills are embedded in capability YAML files under the 'skills' array.
|
|
69
|
+
* This function extracts all skills and adds the capability ID back to each.
|
|
70
|
+
* @param {string} capabilitiesDir - Path to capabilities directory
|
|
71
|
+
* @returns {Promise<Array>} Array of skill objects in flat format
|
|
72
|
+
*/
|
|
73
|
+
async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
74
|
+
const files = await readdir(capabilitiesDir);
|
|
75
|
+
const yamlFiles = files.filter(
|
|
76
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const allSkills = [];
|
|
80
|
+
|
|
81
|
+
for (const file of yamlFiles) {
|
|
82
|
+
const capabilityId = basename(file, ".yaml"); // Derive ID from filename
|
|
83
|
+
const capability = await loadYamlFile(join(capabilitiesDir, file));
|
|
84
|
+
|
|
85
|
+
if (capability.skills && Array.isArray(capability.skills)) {
|
|
86
|
+
for (const skill of capability.skills) {
|
|
87
|
+
const { id, name, isHumanOnly, human, agent } = skill;
|
|
88
|
+
allSkills.push({
|
|
89
|
+
id,
|
|
90
|
+
name,
|
|
91
|
+
capability: capabilityId, // Add capability from parent
|
|
92
|
+
description: human.description,
|
|
93
|
+
levelDescriptions: human.levelDescriptions,
|
|
94
|
+
// Include isHumanOnly flag for agent filtering (defaults to false)
|
|
95
|
+
...(isHumanOnly && { isHumanOnly }),
|
|
96
|
+
// Preserve agent section for agent generation
|
|
97
|
+
...(agent && { agent }),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return allSkills;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Load disciplines from directory (individual files: disciplines/{id}.yaml)
|
|
108
|
+
* @param {string} disciplinesDir - Path to disciplines directory
|
|
109
|
+
* @returns {Promise<Array>} Array of discipline objects
|
|
110
|
+
*/
|
|
111
|
+
async function loadDisciplinesFromDir(disciplinesDir) {
|
|
112
|
+
const files = await readdir(disciplinesDir);
|
|
113
|
+
const yamlFiles = files.filter(
|
|
114
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const disciplines = await Promise.all(
|
|
118
|
+
yamlFiles.map(async (file) => {
|
|
119
|
+
const id = basename(file, ".yaml"); // Derive ID from filename
|
|
120
|
+
const content = await loadYamlFile(join(disciplinesDir, file));
|
|
121
|
+
const {
|
|
122
|
+
specialization,
|
|
123
|
+
roleTitle,
|
|
124
|
+
// Shared content - now at root level
|
|
125
|
+
description,
|
|
126
|
+
// Structural properties (derivation inputs) - at top level
|
|
127
|
+
coreSkills,
|
|
128
|
+
supportingSkills,
|
|
129
|
+
broadSkills,
|
|
130
|
+
behaviourModifiers,
|
|
131
|
+
// Presentation sections
|
|
132
|
+
human,
|
|
133
|
+
agent,
|
|
134
|
+
} = content;
|
|
135
|
+
return {
|
|
136
|
+
id,
|
|
137
|
+
specialization,
|
|
138
|
+
roleTitle,
|
|
139
|
+
// Shared content at top level
|
|
140
|
+
description,
|
|
141
|
+
// Structural properties at top level
|
|
142
|
+
coreSkills,
|
|
143
|
+
supportingSkills,
|
|
144
|
+
broadSkills,
|
|
145
|
+
behaviourModifiers,
|
|
146
|
+
// Human presentation content (role summaries only)
|
|
147
|
+
...human,
|
|
148
|
+
// Preserve agent section for agent generation
|
|
149
|
+
...(agent && { agent }),
|
|
150
|
+
};
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return disciplines;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Load tracks from directory (individual files: tracks/{id}.yaml)
|
|
159
|
+
* @param {string} tracksDir - Path to tracks directory
|
|
160
|
+
* @returns {Promise<Array>} Array of track objects
|
|
161
|
+
*/
|
|
162
|
+
async function loadTracksFromDir(tracksDir) {
|
|
163
|
+
const files = await readdir(tracksDir);
|
|
164
|
+
const yamlFiles = files.filter(
|
|
165
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const tracks = await Promise.all(
|
|
169
|
+
yamlFiles.map(async (file) => {
|
|
170
|
+
const id = basename(file, ".yaml"); // Derive ID from filename
|
|
171
|
+
const content = await loadYamlFile(join(tracksDir, file));
|
|
172
|
+
const {
|
|
173
|
+
name,
|
|
174
|
+
// Shared content - now at root level
|
|
175
|
+
description,
|
|
176
|
+
roleContext,
|
|
177
|
+
// Structural properties (derivation inputs) - at top level
|
|
178
|
+
isProfessional,
|
|
179
|
+
isManagement,
|
|
180
|
+
skillModifiers,
|
|
181
|
+
behaviourModifiers,
|
|
182
|
+
assessmentWeights,
|
|
183
|
+
validDisciplines,
|
|
184
|
+
// Agent section (no human section anymore for tracks)
|
|
185
|
+
agent,
|
|
186
|
+
} = content;
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
name,
|
|
190
|
+
// Shared content at top level
|
|
191
|
+
description,
|
|
192
|
+
roleContext,
|
|
193
|
+
// Structural properties at top level
|
|
194
|
+
isProfessional,
|
|
195
|
+
isManagement,
|
|
196
|
+
skillModifiers,
|
|
197
|
+
behaviourModifiers,
|
|
198
|
+
assessmentWeights,
|
|
199
|
+
validDisciplines,
|
|
200
|
+
// Preserve agent section for agent generation
|
|
201
|
+
...(agent && { agent }),
|
|
202
|
+
};
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
return tracks;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Load behaviours from directory (individual files: behaviours/{id}.yaml)
|
|
211
|
+
* @param {string} behavioursDir - Path to behaviours directory
|
|
212
|
+
* @returns {Promise<Array>} Array of behaviour objects
|
|
213
|
+
*/
|
|
214
|
+
async function loadBehavioursFromDir(behavioursDir) {
|
|
215
|
+
const files = await readdir(behavioursDir);
|
|
216
|
+
const yamlFiles = files.filter(
|
|
217
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const behaviours = await Promise.all(
|
|
221
|
+
yamlFiles.map(async (file) => {
|
|
222
|
+
const id = basename(file, ".yaml"); // Derive ID from filename
|
|
223
|
+
const content = await loadYamlFile(join(behavioursDir, file));
|
|
224
|
+
// Flatten human properties to top level (behaviours use human: section in YAML)
|
|
225
|
+
const { name, human, agent } = content;
|
|
226
|
+
return {
|
|
227
|
+
id,
|
|
228
|
+
name,
|
|
229
|
+
...human,
|
|
230
|
+
// Preserve agent section for agent generation
|
|
231
|
+
...(agent && { agent }),
|
|
232
|
+
};
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return behaviours;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Load capabilities from directory
|
|
241
|
+
* @param {string} capabilitiesDir - Path to capabilities directory
|
|
242
|
+
* @returns {Promise<Array>} Array of capability objects
|
|
243
|
+
*/
|
|
244
|
+
async function loadCapabilitiesFromDir(capabilitiesDir) {
|
|
245
|
+
const files = await readdir(capabilitiesDir);
|
|
246
|
+
const yamlFiles = files.filter(
|
|
247
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const capabilities = await Promise.all(
|
|
251
|
+
yamlFiles.map(async (file) => {
|
|
252
|
+
const id = basename(file, ".yaml"); // Derive ID from filename
|
|
253
|
+
const content = await loadYamlFile(join(capabilitiesDir, file));
|
|
254
|
+
return { id, ...content }; // Add derived ID
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
return capabilities;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Load questions from folder structure
|
|
263
|
+
* @param {string} questionsDir - Path to questions directory
|
|
264
|
+
* @returns {Promise<import('./levels.js').QuestionBank>}
|
|
265
|
+
*/
|
|
266
|
+
export async function loadQuestionFolder(questionsDir) {
|
|
267
|
+
const [skillLevels, behaviourMaturities] = await Promise.all([
|
|
268
|
+
loadQuestionsFromDir(join(questionsDir, "skills")),
|
|
269
|
+
loadQuestionsFromDir(join(questionsDir, "behaviours")),
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
return { skillLevels, behaviourMaturities };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Load all data from a directory
|
|
277
|
+
* @param {string} dataDir - Path to the data directory
|
|
278
|
+
* @param {Object} [options] - Loading options
|
|
279
|
+
* @param {boolean} [options.validate=true] - Whether to validate data after loading
|
|
280
|
+
* @param {boolean} [options.throwOnError=true] - Whether to throw on validation errors
|
|
281
|
+
* @returns {Promise<Object>} All loaded data
|
|
282
|
+
*/
|
|
283
|
+
export async function loadAllData(dataDir, options = {}) {
|
|
284
|
+
const { validate = true, throwOnError = true } = options;
|
|
285
|
+
|
|
286
|
+
// Load capabilities first (skills are embedded in capabilities)
|
|
287
|
+
const capabilities = await loadCapabilitiesFromDir(
|
|
288
|
+
join(dataDir, "capabilities"),
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Extract skills from capabilities
|
|
292
|
+
const skills = await loadSkillsFromCapabilities(
|
|
293
|
+
join(dataDir, "capabilities"),
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Load remaining data files in parallel
|
|
297
|
+
const [
|
|
298
|
+
drivers,
|
|
299
|
+
behaviours,
|
|
300
|
+
disciplines,
|
|
301
|
+
tracks,
|
|
302
|
+
grades,
|
|
303
|
+
stages,
|
|
304
|
+
questions,
|
|
305
|
+
framework,
|
|
306
|
+
] = await Promise.all([
|
|
307
|
+
loadYamlFile(join(dataDir, "drivers.yaml")),
|
|
308
|
+
loadBehavioursFromDir(join(dataDir, "behaviours")),
|
|
309
|
+
loadDisciplinesFromDir(join(dataDir, "disciplines")),
|
|
310
|
+
loadTracksFromDir(join(dataDir, "tracks")),
|
|
311
|
+
loadYamlFile(join(dataDir, "grades.yaml")),
|
|
312
|
+
loadYamlFile(join(dataDir, "stages.yaml")),
|
|
313
|
+
loadQuestionFolder(join(dataDir, "questions")),
|
|
314
|
+
loadYamlFile(join(dataDir, "framework.yaml")),
|
|
315
|
+
]);
|
|
316
|
+
|
|
317
|
+
const data = {
|
|
318
|
+
drivers,
|
|
319
|
+
behaviours,
|
|
320
|
+
skills,
|
|
321
|
+
disciplines,
|
|
322
|
+
tracks,
|
|
323
|
+
grades,
|
|
324
|
+
capabilities,
|
|
325
|
+
stages,
|
|
326
|
+
questions,
|
|
327
|
+
framework,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Validate if requested
|
|
331
|
+
if (validate) {
|
|
332
|
+
const result = validateAllData(data);
|
|
333
|
+
|
|
334
|
+
if (!result.valid && throwOnError) {
|
|
335
|
+
const errorMessages = result.errors
|
|
336
|
+
.map((e) => `${e.type}: ${e.message}`)
|
|
337
|
+
.join("\n");
|
|
338
|
+
throw new Error(`Data validation failed:\n${errorMessages}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
data.validation = result;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return data;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Load question bank from a folder
|
|
349
|
+
* @param {string} questionsDir - Path to the questions folder
|
|
350
|
+
* @param {import('./levels.js').Skill[]} [skills] - Skills for validation
|
|
351
|
+
* @param {import('./levels.js').Behaviour[]} [behaviours] - Behaviours for validation
|
|
352
|
+
* @param {Object} [options] - Loading options
|
|
353
|
+
* @param {boolean} [options.validate=true] - Whether to validate
|
|
354
|
+
* @param {boolean} [options.throwOnError=true] - Whether to throw on errors
|
|
355
|
+
* @returns {Promise<import('./levels.js').QuestionBank>} Loaded question bank
|
|
356
|
+
*/
|
|
357
|
+
export async function loadQuestionBankFromFolder(
|
|
358
|
+
questionsDir,
|
|
359
|
+
skills,
|
|
360
|
+
behaviours,
|
|
361
|
+
options = {},
|
|
362
|
+
) {
|
|
363
|
+
const { validate = true, throwOnError = true } = options;
|
|
364
|
+
|
|
365
|
+
const questionBank = await loadQuestionFolder(questionsDir);
|
|
366
|
+
|
|
367
|
+
if (validate && skills && behaviours) {
|
|
368
|
+
const result = validateQuestionBank(questionBank, skills, behaviours);
|
|
369
|
+
|
|
370
|
+
if (!result.valid && throwOnError) {
|
|
371
|
+
const errorMessages = result.errors
|
|
372
|
+
.map((e) => `${e.type}: ${e.message}`)
|
|
373
|
+
.join("\n");
|
|
374
|
+
throw new Error(`Question bank validation failed:\n${errorMessages}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
questionBank.validation = result;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return questionBank;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Load self-assessments from a file
|
|
385
|
+
* @param {string} filePath - Path to the self-assessments YAML file
|
|
386
|
+
* @returns {Promise<import('./levels.js').SelfAssessment[]>} Array of self-assessments
|
|
387
|
+
*/
|
|
388
|
+
export async function loadSelfAssessments(filePath) {
|
|
389
|
+
return loadYamlFile(filePath);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Create a data loader for a specific directory
|
|
394
|
+
* @param {string} dataDir - Path to the data directory
|
|
395
|
+
* @returns {Object} Data loader with bound methods
|
|
396
|
+
*/
|
|
397
|
+
export function createDataLoader(dataDir) {
|
|
398
|
+
return {
|
|
399
|
+
/**
|
|
400
|
+
* Load all core data
|
|
401
|
+
* @param {Object} [options] - Loading options
|
|
402
|
+
* @returns {Promise<Object>} All data
|
|
403
|
+
*/
|
|
404
|
+
loadAll: (options) => loadAllData(dataDir, options),
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Load question bank
|
|
408
|
+
* @param {import('./levels.js').Skill[]} skills - Skills for validation
|
|
409
|
+
* @param {import('./levels.js').Behaviour[]} behaviours - Behaviours for validation
|
|
410
|
+
* @param {Object} [options] - Loading options
|
|
411
|
+
* @returns {Promise<import('./levels.js').QuestionBank>} Question bank
|
|
412
|
+
*/
|
|
413
|
+
loadQuestions: (skills, behaviours, options) =>
|
|
414
|
+
loadQuestionBankFromFolder(
|
|
415
|
+
join(dataDir, "questions"),
|
|
416
|
+
skills,
|
|
417
|
+
behaviours,
|
|
418
|
+
options,
|
|
419
|
+
),
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Load self-assessments
|
|
423
|
+
* @returns {Promise<import('./levels.js').SelfAssessment[]>} Self-assessments
|
|
424
|
+
*/
|
|
425
|
+
loadSelfAssessments: () =>
|
|
426
|
+
loadSelfAssessments(join(dataDir, "self-assessments.yaml")),
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Load a specific file
|
|
430
|
+
* @param {string} filename - File name to load
|
|
431
|
+
* @returns {Promise<any>} Parsed content
|
|
432
|
+
*/
|
|
433
|
+
loadFile: (filename) => loadYamlFile(join(dataDir, filename)),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Load example data from the examples directory
|
|
439
|
+
* @param {string} rootDir - Root directory of the project
|
|
440
|
+
* @param {Object} [options] - Loading options
|
|
441
|
+
* @returns {Promise<Object>} Example data
|
|
442
|
+
*/
|
|
443
|
+
export async function loadExampleData(rootDir, options = {}) {
|
|
444
|
+
const examplesDir = join(rootDir, "examples");
|
|
445
|
+
return loadAllData(examplesDir, options);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Validate data and optionally throw on errors
|
|
450
|
+
*
|
|
451
|
+
* This is a synchronous validation function for when you already have
|
|
452
|
+
* the data loaded and just need to validate it.
|
|
453
|
+
*
|
|
454
|
+
* @param {Object} data - All competency data
|
|
455
|
+
* @param {import('./levels.js').Driver[]} data.drivers - Drivers
|
|
456
|
+
* @param {import('./levels.js').Behaviour[]} data.behaviours - Behaviours
|
|
457
|
+
* @param {import('./levels.js').Skill[]} data.skills - Skills
|
|
458
|
+
* @param {import('./levels.js').Discipline[]} data.disciplines - Disciplines
|
|
459
|
+
* @param {import('./levels.js').Track[]} data.tracks - Tracks
|
|
460
|
+
* @param {import('./levels.js').Grade[]} data.grades - Grades
|
|
461
|
+
* @param {Object} [options] - Options
|
|
462
|
+
* @param {boolean} [options.throwOnError=true] - Whether to throw on validation errors
|
|
463
|
+
* @returns {{valid: boolean, data: Object, errors: Array, warnings: Array}}
|
|
464
|
+
*/
|
|
465
|
+
export function loadAndValidate(data, options = {}) {
|
|
466
|
+
const { throwOnError = true } = options;
|
|
467
|
+
|
|
468
|
+
const result = validateAllData(data);
|
|
469
|
+
|
|
470
|
+
if (!result.valid && throwOnError) {
|
|
471
|
+
const errorMessages = result.errors
|
|
472
|
+
.map((e) => `${e.type}: ${e.message}`)
|
|
473
|
+
.join("\n");
|
|
474
|
+
throw new Error(`Data validation failed:\n${errorMessages}`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
valid: result.valid,
|
|
479
|
+
data,
|
|
480
|
+
errors: result.errors,
|
|
481
|
+
warnings: result.warnings,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Load agent-specific data for agent profile generation
|
|
487
|
+
* Uses co-located files: each entity file contains both human and agent sections
|
|
488
|
+
* @param {string} dataDir - Path to the data directory
|
|
489
|
+
* @returns {Promise<Object>} Agent data including disciplines, tracks, behaviours, vscodeSettings
|
|
490
|
+
*/
|
|
491
|
+
export async function loadAgentData(dataDir) {
|
|
492
|
+
const disciplinesDir = join(dataDir, "disciplines");
|
|
493
|
+
const tracksDir = join(dataDir, "tracks");
|
|
494
|
+
const behavioursDir = join(dataDir, "behaviours");
|
|
495
|
+
|
|
496
|
+
// Load from co-located files
|
|
497
|
+
const [disciplineFiles, trackFiles, behaviourFiles, vscodeSettings] =
|
|
498
|
+
await Promise.all([
|
|
499
|
+
loadDisciplinesFromDir(disciplinesDir),
|
|
500
|
+
loadTracksFromDir(tracksDir),
|
|
501
|
+
loadBehavioursFromDir(behavioursDir),
|
|
502
|
+
fileExists(join(dataDir, "vscode-settings.yaml"))
|
|
503
|
+
? loadYamlFile(join(dataDir, "vscode-settings.yaml"))
|
|
504
|
+
: {},
|
|
505
|
+
]);
|
|
506
|
+
|
|
507
|
+
// Extract agent sections from co-located files
|
|
508
|
+
const disciplines = disciplineFiles
|
|
509
|
+
.filter((d) => d.agent)
|
|
510
|
+
.map((d) => ({
|
|
511
|
+
id: d.id,
|
|
512
|
+
...d.agent,
|
|
513
|
+
}));
|
|
514
|
+
|
|
515
|
+
const tracks = trackFiles
|
|
516
|
+
.filter((t) => t.agent)
|
|
517
|
+
.map((t) => ({
|
|
518
|
+
id: t.id,
|
|
519
|
+
...t.agent,
|
|
520
|
+
}));
|
|
521
|
+
|
|
522
|
+
const behaviours = behaviourFiles
|
|
523
|
+
.filter((b) => b.agent)
|
|
524
|
+
.map((b) => ({
|
|
525
|
+
id: b.id,
|
|
526
|
+
...b.agent,
|
|
527
|
+
}));
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
disciplines,
|
|
531
|
+
tracks,
|
|
532
|
+
behaviours,
|
|
533
|
+
vscodeSettings,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Load skills with agent sections from capability files
|
|
539
|
+
* Skills are embedded in capability YAML files under the 'skills' array.
|
|
540
|
+
* @param {string} dataDir - Path to the data directory
|
|
541
|
+
* @returns {Promise<Array>} Skills with agent sections preserved
|
|
542
|
+
*/
|
|
543
|
+
export async function loadSkillsWithAgentData(dataDir) {
|
|
544
|
+
const capabilitiesDir = join(dataDir, "capabilities");
|
|
545
|
+
|
|
546
|
+
const files = await readdir(capabilitiesDir);
|
|
547
|
+
const yamlFiles = files.filter(
|
|
548
|
+
(f) => f.endsWith(".yaml") && !f.startsWith("_"),
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
const allSkills = [];
|
|
552
|
+
|
|
553
|
+
for (const file of yamlFiles) {
|
|
554
|
+
const capability = await loadYamlFile(join(capabilitiesDir, file));
|
|
555
|
+
|
|
556
|
+
if (capability.skills && Array.isArray(capability.skills)) {
|
|
557
|
+
for (const skill of capability.skills) {
|
|
558
|
+
allSkills.push(skill);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return allSkills;
|
|
564
|
+
}
|