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