@forwardimpact/map 0.11.1 → 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.
Files changed (59) hide show
  1. package/README.md +1 -1
  2. package/bin/fit-map.js +91 -34
  3. package/package.json +14 -4
  4. package/schema/json/capability.schema.json +33 -0
  5. package/schema/json/discipline.schema.json +2 -6
  6. package/schema/rdf/capability.ttl +48 -0
  7. package/schema/rdf/discipline.ttl +6 -19
  8. package/src/index-generator.js +67 -38
  9. package/src/index.js +10 -25
  10. package/src/loader.js +407 -559
  11. package/src/schema-validation.js +327 -307
  12. package/src/validation.js +54 -0
  13. package/examples/behaviours/_index.yaml +0 -8
  14. package/examples/behaviours/outcome_ownership.yaml +0 -43
  15. package/examples/behaviours/polymathic_knowledge.yaml +0 -41
  16. package/examples/behaviours/precise_communication.yaml +0 -39
  17. package/examples/behaviours/relentless_curiosity.yaml +0 -37
  18. package/examples/behaviours/systems_thinking.yaml +0 -40
  19. package/examples/capabilities/_index.yaml +0 -8
  20. package/examples/capabilities/business.yaml +0 -205
  21. package/examples/capabilities/delivery.yaml +0 -1001
  22. package/examples/capabilities/people.yaml +0 -68
  23. package/examples/capabilities/reliability.yaml +0 -349
  24. package/examples/capabilities/scale.yaml +0 -1672
  25. package/examples/copilot-setup-steps.yaml +0 -25
  26. package/examples/devcontainer.yaml +0 -21
  27. package/examples/disciplines/_index.yaml +0 -6
  28. package/examples/disciplines/data_engineering.yaml +0 -68
  29. package/examples/disciplines/engineering_management.yaml +0 -61
  30. package/examples/disciplines/software_engineering.yaml +0 -68
  31. package/examples/drivers.yaml +0 -202
  32. package/examples/framework.yaml +0 -73
  33. package/examples/levels.yaml +0 -115
  34. package/examples/questions/behaviours/outcome_ownership.yaml +0 -228
  35. package/examples/questions/behaviours/polymathic_knowledge.yaml +0 -275
  36. package/examples/questions/behaviours/precise_communication.yaml +0 -248
  37. package/examples/questions/behaviours/relentless_curiosity.yaml +0 -248
  38. package/examples/questions/behaviours/systems_thinking.yaml +0 -238
  39. package/examples/questions/capabilities/business.yaml +0 -107
  40. package/examples/questions/capabilities/delivery.yaml +0 -101
  41. package/examples/questions/capabilities/people.yaml +0 -106
  42. package/examples/questions/capabilities/reliability.yaml +0 -105
  43. package/examples/questions/capabilities/scale.yaml +0 -104
  44. package/examples/questions/skills/architecture_design.yaml +0 -115
  45. package/examples/questions/skills/cloud_platforms.yaml +0 -105
  46. package/examples/questions/skills/code_quality.yaml +0 -162
  47. package/examples/questions/skills/data_modeling.yaml +0 -107
  48. package/examples/questions/skills/devops.yaml +0 -111
  49. package/examples/questions/skills/full_stack_development.yaml +0 -118
  50. package/examples/questions/skills/sre_practices.yaml +0 -113
  51. package/examples/questions/skills/stakeholder_management.yaml +0 -116
  52. package/examples/questions/skills/team_collaboration.yaml +0 -106
  53. package/examples/questions/skills/technical_writing.yaml +0 -110
  54. package/examples/self-assessments.yaml +0 -64
  55. package/examples/stages.yaml +0 -191
  56. package/examples/tracks/_index.yaml +0 -5
  57. package/examples/tracks/platform.yaml +0 -47
  58. package/examples/tracks/sre.yaml +0 -46
  59. package/examples/vscode-settings.yaml +0 -21
package/README.md CHANGED
@@ -64,4 +64,4 @@ examples/
64
64
  └── questions/ # Interview questions
65
65
  ```
66
66
 
67
- See the [documentation](../../docs/map/index.md) for details.
67
+ See the [documentation](../../website/docs/map/index.md) for details.
package/bin/fit-map.js CHANGED
@@ -3,11 +3,12 @@
3
3
  /**
4
4
  * fit-map CLI
5
5
  *
6
- * Map validation and index generation for Engineering Pathway data.
6
+ * Map validation, index generation, and activity management for Engineering Pathway data.
7
7
  *
8
8
  * Commands:
9
9
  * validate [--json|--shacl] Run validation (default: --json)
10
10
  * generate-index [--data=PATH] Generate _index.yaml files
11
+ * people import <file> Import people from CSV/YAML
11
12
  * --help Show help
12
13
  */
13
14
 
@@ -22,22 +23,29 @@ const __dirname = dirname(__filename);
22
23
  * Parse CLI arguments
23
24
  */
24
25
  function parseArgs(args) {
25
- const command = args[0];
26
26
  const options = {};
27
-
28
- for (let i = 1; i < args.length; i++) {
29
- const arg = args[i];
30
- if (arg.startsWith("--data=")) {
31
- options.dataDir = arg.slice(7);
32
- } else if (arg === "--help" || arg === "-h") {
27
+ const positional = [];
28
+
29
+ for (const arg of args) {
30
+ if (arg.startsWith("--")) {
31
+ if (arg === "--help") {
32
+ options.help = true;
33
+ } else {
34
+ const [key, value] = arg.slice(2).split("=");
35
+ options[key] = value ?? true;
36
+ }
37
+ } else if (arg === "-h") {
33
38
  options.help = true;
34
- } else if (arg.startsWith("--")) {
35
- const [key, value] = arg.slice(2).split("=");
36
- options[key] = value ?? true;
39
+ } else {
40
+ positional.push(arg);
37
41
  }
38
42
  }
39
43
 
40
- return { command, options };
44
+ const command = positional[0] || null;
45
+ const subcommand = positional[1] || null;
46
+ const rest = positional.slice(2);
47
+
48
+ return { command, subcommand, options, positional: rest };
41
49
  }
42
50
 
43
51
  /**
@@ -64,11 +72,11 @@ async function findDataDir(providedPath) {
64
72
  throw new Error(`Data directory not found: ${providedPath}`);
65
73
  }
66
74
 
67
- // Check common locations
68
75
  const candidates = [
76
+ join(process.cwd(), "data/pathway"),
77
+ join(process.cwd(), "examples/pathway"),
69
78
  join(process.cwd(), "data"),
70
79
  join(process.cwd(), "examples"),
71
- join(__dirname, "../examples"),
72
80
  ];
73
81
 
74
82
  for (const candidate of candidates) {
@@ -116,17 +124,17 @@ function formatValidationResults(result) {
116
124
  async function runValidate(dataDir) {
117
125
  console.log(`🔍 Validating data in: ${dataDir}\n`);
118
126
 
119
- const { runSchemaValidation, loadAllData } = await import("../src/index.js");
127
+ const { createDataLoader, createSchemaValidator } =
128
+ await import("../src/index.js");
120
129
 
121
- // Load data first
122
- const data = await loadAllData(dataDir, { validate: false });
130
+ const loader = createDataLoader();
131
+ const validator = createSchemaValidator();
123
132
 
124
- // Run full validation
125
- const result = await runSchemaValidation(dataDir, data);
133
+ const data = await loader.loadAllData(dataDir);
134
+ const result = await validator.runFullValidation(dataDir, data);
126
135
 
127
136
  console.log(formatValidationResults(result));
128
137
 
129
- // Print summary
130
138
  console.log("\n📊 Data Summary:");
131
139
  console.log(` Skills: ${data.skills?.length || 0}`);
132
140
  console.log(` Behaviours: ${data.behaviours?.length || 0}`);
@@ -144,9 +152,10 @@ async function runValidate(dataDir) {
144
152
  async function runGenerateIndex(dataDir) {
145
153
  console.log(`📁 Generating index files in: ${dataDir}\n`);
146
154
 
147
- const { generateAllIndexes } = await import("../src/index.js");
155
+ const { createIndexGenerator } = await import("../src/index.js");
148
156
 
149
- const results = await generateAllIndexes(dataDir);
157
+ const generator = createIndexGenerator();
158
+ const results = await generator.generateAllIndexes(dataDir);
150
159
 
151
160
  for (const [dir, files] of Object.entries(results)) {
152
161
  if (files.error) {
@@ -171,7 +180,6 @@ async function runValidateShacl() {
171
180
  const { default: N3 } = await import("n3");
172
181
  const { readFile, readdir } = await import("fs/promises");
173
182
 
174
- // Find all .ttl files in the RDF directory
175
183
  const files = await readdir(rdfDir);
176
184
  const ttlFiles = files.filter((f) => f.endsWith(".ttl")).sort();
177
185
 
@@ -214,31 +222,62 @@ async function runValidateShacl() {
214
222
  }
215
223
  }
216
224
 
225
+ /**
226
+ * People import command
227
+ */
228
+ async function runPeopleImport(filePath, dataDir) {
229
+ console.log(`👤 Importing people from: ${filePath}\n`);
230
+
231
+ const { loadPeopleFile, validatePeople } =
232
+ await import("../activity/transform/people.js");
233
+
234
+ const people = await loadPeopleFile(filePath);
235
+ console.log(` Loaded ${people.length} people from file`);
236
+
237
+ const { valid, errors } = await validatePeople(people, dataDir);
238
+
239
+ if (errors.length > 0) {
240
+ console.log(`\n❌ Validation errors:`);
241
+ for (const err of errors) {
242
+ console.log(` • Row ${err.row}: ${err.message}`);
243
+ }
244
+ }
245
+
246
+ console.log(`\n✅ ${valid.length} people validated`);
247
+ if (errors.length > 0) {
248
+ console.log(`❌ ${errors.length} rows with errors\n`);
249
+ }
250
+
251
+ return errors.length > 0 ? 1 : 0;
252
+ }
253
+
217
254
  /**
218
255
  * Show help
219
256
  */
220
257
  function showHelp() {
221
258
  console.log(`
222
- fit-map - Data validation for Engineering Pathway
259
+ fit-map - Data validation and management for Engineering Pathway
223
260
 
224
261
  Usage:
225
262
  fit-map <command> [options]
226
263
 
227
264
  Commands:
228
- validate Run validation (default: JSON schema validation)
229
- generate-index Generate _index.yaml files for directories
265
+ validate Run validation (default: JSON schema validation)
266
+ generate-index Generate _index.yaml files for directories
267
+ people import <file> Import people from CSV/YAML (validates against framework)
230
268
 
231
269
  Options:
232
- --json JSON schema + referential validation (default)
233
- --shacl SHACL schema syntax validation
234
- --data=PATH Path to data directory (default: ./data or ./examples)
235
- --help, -h Show this help message
270
+ --json JSON schema + referential validation (default)
271
+ --shacl SHACL schema syntax validation
272
+ --data=PATH Path to data directory (default: ./data or ./examples)
273
+ --help, -h Show this help message
236
274
 
237
275
  Examples:
238
276
  fit-map validate
239
277
  fit-map validate --shacl
240
278
  fit-map validate --data=./my-data
241
279
  fit-map generate-index
280
+ fit-map people import ./org/people.yaml
242
281
  `);
243
282
  }
244
283
 
@@ -246,11 +285,13 @@ Examples:
246
285
  * Main entry point
247
286
  */
248
287
  async function main() {
249
- const { command, options } = parseArgs(process.argv.slice(2));
288
+ const { command, subcommand, options, positional } = parseArgs(
289
+ process.argv.slice(2),
290
+ );
250
291
 
251
292
  if (options.help || !command) {
252
293
  showHelp();
253
- process.exit(command ? 0 : 1);
294
+ process.exit(options.help ? 0 : 1);
254
295
  }
255
296
 
256
297
  try {
@@ -261,16 +302,32 @@ async function main() {
261
302
  if (options.shacl) {
262
303
  exitCode = await runValidateShacl();
263
304
  } else {
264
- const dataDir = await findDataDir(options.dataDir);
305
+ const dataDir = await findDataDir(options.data);
265
306
  exitCode = await runValidate(dataDir);
266
307
  }
267
308
  break;
268
309
  }
269
310
  case "generate-index": {
270
- const dataDir = await findDataDir(options.dataDir);
311
+ const dataDir = await findDataDir(options.data);
271
312
  exitCode = await runGenerateIndex(dataDir);
272
313
  break;
273
314
  }
315
+ case "people": {
316
+ if (subcommand === "import") {
317
+ const filePath = positional[0];
318
+ if (!filePath) {
319
+ console.error("Error: people import requires a file path");
320
+ process.exit(1);
321
+ }
322
+ const dataDir = await findDataDir(options.data);
323
+ exitCode = await runPeopleImport(filePath, dataDir);
324
+ } else {
325
+ console.error(`Unknown people subcommand: ${subcommand || "(none)"}`);
326
+ showHelp();
327
+ exitCode = 1;
328
+ }
329
+ break;
330
+ }
274
331
  default:
275
332
  console.error(`Unknown command: ${command}`);
276
333
  showHelp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/map",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "Public data model for career frameworks, consumed by AI agents and engineers",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -20,7 +20,6 @@
20
20
  },
21
21
  "files": [
22
22
  "bin/",
23
- "examples/",
24
23
  "src/",
25
24
  "schema/"
26
25
  ],
@@ -31,9 +30,20 @@
31
30
  "./schema-validation": "./src/schema-validation.js",
32
31
  "./index-generator": "./src/index-generator.js",
33
32
  "./levels": "./src/levels.js",
34
- "./examples/*": "./examples/*",
35
33
  "./schema/json/*": "./schema/json/*",
36
- "./schema/rdf/*": "./schema/rdf/*"
34
+ "./schema/rdf/*": "./schema/rdf/*",
35
+ "./activity/queries/org": "./activity/queries/org.js",
36
+ "./activity/queries/snapshots": "./activity/queries/snapshots.js",
37
+ "./activity/queries/evidence": "./activity/queries/evidence.js",
38
+ "./activity/queries/artifacts": "./activity/queries/artifacts.js",
39
+ "./activity/storage": "./activity/storage.js",
40
+ "./activity/extract/github": "./activity/extract/github.js",
41
+ "./activity/extract/getdx": "./activity/extract/getdx.js",
42
+ "./activity/extract/people": "./activity/extract/people.js",
43
+ "./activity/transform/github": "./activity/transform/github.js",
44
+ "./activity/transform/getdx": "./activity/transform/getdx.js",
45
+ "./activity/transform/people": "./activity/transform/people.js",
46
+ "./activity/transform": "./activity/transform/index.js"
37
47
  },
38
48
  "dependencies": {
39
49
  "ajv": "^8.17.1",
@@ -105,10 +105,43 @@
105
105
  "implementationReference": {
106
106
  "type": "string",
107
107
  "description": "Code examples and detailed reference material, exported to references/REFERENCE.md"
108
+ },
109
+ "markers": {
110
+ "$ref": "#/$defs/skillMarkers",
111
+ "description": "Observable indicators of skill proficiency, keyed by level"
108
112
  }
109
113
  },
110
114
  "additionalProperties": false
111
115
  },
116
+ "skillMarkers": {
117
+ "type": "object",
118
+ "description": "Observable indicators of skill proficiency, keyed by level",
119
+ "propertyNames": {
120
+ "enum": [
121
+ "awareness",
122
+ "foundational",
123
+ "working",
124
+ "practitioner",
125
+ "expert"
126
+ ]
127
+ },
128
+ "additionalProperties": {
129
+ "type": "object",
130
+ "properties": {
131
+ "human": {
132
+ "type": "array",
133
+ "items": { "type": "string" },
134
+ "description": "Observable markers for human engineers"
135
+ },
136
+ "agent": {
137
+ "type": "array",
138
+ "items": { "type": "string" },
139
+ "description": "Observable markers for AI agents"
140
+ }
141
+ },
142
+ "additionalProperties": false
143
+ }
144
+ },
112
145
  "toolReference": {
113
146
  "type": "object",
114
147
  "required": ["name", "description", "useWhen"],
@@ -88,13 +88,9 @@
88
88
  "disciplineHumanSection": {
89
89
  "type": "object",
90
90
  "properties": {
91
- "professionalRoleSummary": {
91
+ "roleSummary": {
92
92
  "type": "string",
93
- "description": "Role summary for professional/IC track. May use {roleTitle} placeholder."
94
- },
95
- "managementRoleSummary": {
96
- "type": "string",
97
- "description": "Role summary for management track. May use {specialization} placeholder."
93
+ "description": "Role summary for this discipline. May use {roleTitle} or {specialization} placeholder."
98
94
  }
99
95
  },
100
96
  "additionalProperties": false
@@ -38,6 +38,10 @@ fit:ToolReference a rdfs:Class ;
38
38
  rdfs:label "Tool Reference"@en ;
39
39
  rdfs:comment "Reference to an external tool with usage guidance"@en .
40
40
 
41
+ fit:SkillMarkers a rdfs:Class ;
42
+ rdfs:label "Skill Markers"@en ;
43
+ rdfs:comment "Observable indicators of skill proficiency at a level"@en .
44
+
41
45
  # -----------------------------------------------------------------------------
42
46
  # Properties
43
47
  # -----------------------------------------------------------------------------
@@ -95,6 +99,24 @@ fit:implementationReference a rdf:Property ;
95
99
  rdfs:domain fit:Skill ;
96
100
  rdfs:range xsd:string .
97
101
 
102
+ fit:markers a rdf:Property ;
103
+ rdfs:label "markers"@en ;
104
+ rdfs:comment "Observable indicators keyed by proficiency level"@en ;
105
+ rdfs:domain fit:Skill ;
106
+ rdfs:range fit:SkillMarkers .
107
+
108
+ fit:humanMarkers a rdf:Property ;
109
+ rdfs:label "humanMarkers"@en ;
110
+ rdfs:comment "Observable markers for human engineers"@en ;
111
+ rdfs:domain fit:SkillMarkers ;
112
+ rdfs:range xsd:string .
113
+
114
+ fit:agentMarkers a rdf:Property ;
115
+ rdfs:label "agentMarkers"@en ;
116
+ rdfs:comment "Observable markers for AI agents"@en ;
117
+ rdfs:domain fit:SkillMarkers ;
118
+ rdfs:range xsd:string .
119
+
98
120
  fit:toolReferences a rdf:Property ;
99
121
  rdfs:label "toolReferences"@en ;
100
122
  rdfs:comment "Required tools for this skill"@en ;
@@ -315,6 +337,32 @@ fit:SkillShape a sh:NodeShape ;
315
337
  sh:maxCount 1 ;
316
338
  sh:name "implementationReference" ;
317
339
  sh:description "Code examples and detailed reference material, exported to references/REFERENCE.md" ;
340
+ ] ;
341
+ sh:property [
342
+ sh:path fit:markers ;
343
+ sh:node fit:SkillMarkersShape ;
344
+ sh:maxCount 1 ;
345
+ sh:name "markers" ;
346
+ sh:description "Observable indicators of skill proficiency, keyed by level" ;
347
+ ] .
348
+
349
+ # -----------------------------------------------------------------------------
350
+ # Skill Markers Shape
351
+ # -----------------------------------------------------------------------------
352
+
353
+ fit:SkillMarkersShape a sh:NodeShape ;
354
+ sh:targetClass fit:SkillMarkers ;
355
+ sh:property [
356
+ sh:path fit:humanMarkers ;
357
+ sh:datatype xsd:string ;
358
+ sh:name "human" ;
359
+ sh:description "Observable markers for human engineers" ;
360
+ ] ;
361
+ sh:property [
362
+ sh:path fit:agentMarkers ;
363
+ sh:datatype xsd:string ;
364
+ sh:name "agent" ;
365
+ sh:description "Observable markers for AI agents" ;
318
366
  ] .
319
367
 
320
368
  # -----------------------------------------------------------------------------
@@ -86,15 +86,9 @@ fit:behaviourModifiers a rdf:Property ;
86
86
  rdfs:label "behaviourModifiers"@en ;
87
87
  rdfs:comment "Modifiers to behaviour expectations"@en .
88
88
 
89
- fit:professionalRoleSummary a rdf:Property ;
90
- rdfs:label "professionalRoleSummary"@en ;
91
- rdfs:comment "Role summary for professional/IC track. May use {roleTitle} placeholder."@en ;
92
- rdfs:domain fit:DisciplineHumanSection ;
93
- rdfs:range xsd:string .
94
-
95
- fit:managementRoleSummary a rdf:Property ;
96
- rdfs:label "managementRoleSummary"@en ;
97
- rdfs:comment "Role summary for management track. May use {specialization} placeholder."@en ;
89
+ fit:roleSummary a rdf:Property ;
90
+ rdfs:label "roleSummary"@en ;
91
+ rdfs:comment "Role summary for this discipline. May use {roleTitle} or {specialization} placeholder."@en ;
98
92
  rdfs:domain fit:DisciplineHumanSection ;
99
93
  rdfs:range xsd:string .
100
94
 
@@ -244,18 +238,11 @@ fit:DisciplineShape a sh:NodeShape ;
244
238
  fit:DisciplineHumanSectionShape a sh:NodeShape ;
245
239
  sh:targetClass fit:DisciplineHumanSection ;
246
240
  sh:property [
247
- sh:path fit:professionalRoleSummary ;
248
- sh:datatype xsd:string ;
249
- sh:maxCount 1 ;
250
- sh:name "professionalRoleSummary" ;
251
- sh:description "Role summary for professional/IC track" ;
252
- ] ;
253
- sh:property [
254
- sh:path fit:managementRoleSummary ;
241
+ sh:path fit:roleSummary ;
255
242
  sh:datatype xsd:string ;
256
243
  sh:maxCount 1 ;
257
- sh:name "managementRoleSummary" ;
258
- sh:description "Role summary for management track" ;
244
+ sh:name "roleSummary" ;
245
+ sh:description "Role summary for this discipline" ;
259
246
  ] .
260
247
 
261
248
  # -----------------------------------------------------------------------------
@@ -10,56 +10,85 @@ import { join, basename } from "path";
10
10
  import { stringify as stringifyYaml } from "yaml";
11
11
 
12
12
  /**
13
- * Generate _index.yaml for a directory
14
- * @param {string} dir - Directory path
15
- * @returns {Promise<string[]>} List of file IDs included
13
+ * Index generator class with injectable filesystem and serializer dependencies.
16
14
  */
17
- export async function generateDirIndex(dir) {
18
- const files = await readdir(dir);
19
- const yamlFiles = files.filter(
20
- (f) => f.endsWith(".yaml") && !f.startsWith("_"),
21
- );
15
+ export class IndexGenerator {
16
+ #fs;
17
+ #yaml;
18
+
19
+ /**
20
+ * @param {{ readdir: Function, writeFile: Function }} fs
21
+ * @param {{ stringify: Function }} yamlSerializer
22
+ */
23
+ constructor(fs, yamlSerializer) {
24
+ if (!fs) throw new Error("fs is required");
25
+ if (!yamlSerializer) throw new Error("yamlSerializer is required");
26
+ this.#fs = fs;
27
+ this.#yaml = yamlSerializer;
28
+ }
22
29
 
23
- const fileIds = yamlFiles.map((f) => basename(f, ".yaml")).sort();
30
+ /**
31
+ * Generate _index.yaml for a directory
32
+ * @param {string} dir - Directory path
33
+ * @returns {Promise<string[]>} List of file IDs included
34
+ */
35
+ async generateDirIndex(dir) {
36
+ const files = await this.#fs.readdir(dir);
37
+ const yamlFiles = files.filter(
38
+ (f) => f.endsWith(".yaml") && !f.startsWith("_"),
39
+ );
24
40
 
25
- const content = stringifyYaml(
26
- {
27
- // Auto-generated index for browser loading
28
- // Do not edit manually - regenerate with: npx pathway --generate-index
29
- files: fileIds,
30
- },
31
- { lineWidth: 0 },
32
- );
41
+ const fileIds = yamlFiles.map((f) => basename(f, ".yaml")).sort();
33
42
 
34
- // Add header comment
35
- const output = `# Auto-generated index for browser loading
43
+ const content = this.#yaml.stringify(
44
+ {
45
+ // Auto-generated index for browser loading
46
+ // Do not edit manually - regenerate with: npx pathway --generate-index
47
+ files: fileIds,
48
+ },
49
+ { lineWidth: 0 },
50
+ );
51
+
52
+ const output = `# Auto-generated index for browser loading
36
53
  # Do not edit manually - regenerate with: npx pathway --generate-index
37
54
  ${content}`;
38
55
 
39
- await writeFile(join(dir, "_index.yaml"), output, "utf-8");
56
+ await this.#fs.writeFile(join(dir, "_index.yaml"), output, "utf-8");
40
57
 
41
- return fileIds;
42
- }
58
+ return fileIds;
59
+ }
43
60
 
44
- /**
45
- * Generate all index files for the data directory
46
- * @param {string} dataDir - Path to the data directory
47
- * @returns {Promise<Object>} Summary of generated indexes
48
- */
49
- export async function generateAllIndexes(dataDir) {
50
- const directories = ["behaviours", "disciplines", "tracks", "capabilities"];
61
+ /**
62
+ * Generate all index files for the data directory
63
+ * @param {string} dataDir - Path to the data directory
64
+ * @returns {Promise<Object>} Summary of generated indexes
65
+ */
66
+ async generateAllIndexes(dataDir) {
67
+ const directories = ["behaviours", "disciplines", "tracks", "capabilities"];
51
68
 
52
- const results = {};
69
+ const results = {};
53
70
 
54
- for (const dir of directories) {
55
- const fullPath = join(dataDir, dir);
56
- try {
57
- const files = await generateDirIndex(fullPath);
58
- results[dir] = files;
59
- } catch (err) {
60
- results[dir] = { error: err.message };
71
+ for (const dir of directories) {
72
+ const fullPath = join(dataDir, dir);
73
+ try {
74
+ const files = await this.generateDirIndex(fullPath);
75
+ results[dir] = files;
76
+ } catch (err) {
77
+ results[dir] = { error: err.message };
78
+ }
61
79
  }
80
+
81
+ return results;
62
82
  }
83
+ }
63
84
 
64
- return results;
85
+ /**
86
+ * Create an IndexGenerator with real filesystem and serializer dependencies
87
+ * @returns {IndexGenerator}
88
+ */
89
+ export function createIndexGenerator() {
90
+ return new IndexGenerator(
91
+ { readdir, writeFile },
92
+ { stringify: stringifyYaml },
93
+ );
65
94
  }
package/src/index.js CHANGED
@@ -4,22 +4,17 @@
4
4
  * Data model definitions, validation, and loading for Engineering Pathway.
5
5
  */
6
6
 
7
- // Data loading
8
- export {
9
- loadAllData,
10
- loadYamlFile,
11
- createDataLoader,
12
- loadFrameworkConfig,
13
- loadQuestionFolder,
14
- loadQuestionBankFromFolder,
15
- loadSelfAssessments,
16
- loadExampleData,
17
- loadAndValidate,
18
- loadAgentData,
19
- loadSkillsWithAgentData,
20
- } from "./loader.js";
7
+ // Classes
8
+ export { DataLoader } from "./loader.js";
9
+ export { SchemaValidator } from "./schema-validation.js";
10
+ export { IndexGenerator } from "./index-generator.js";
11
+
12
+ // Factory functions
13
+ export { createDataLoader } from "./loader.js";
14
+ export { createSchemaValidator } from "./schema-validation.js";
15
+ export { createIndexGenerator } from "./index-generator.js";
21
16
 
22
- // Referential integrity validation
17
+ // Pure validation functions (unchanged)
23
18
  export {
24
19
  validateAllData,
25
20
  validateQuestionBank,
@@ -27,16 +22,6 @@ export {
27
22
  validateAgentData,
28
23
  } from "./validation.js";
29
24
 
30
- // Schema-based validation
31
- export {
32
- validateDataDirectory,
33
- validateReferentialIntegrity,
34
- runSchemaValidation,
35
- } from "./schema-validation.js";
36
-
37
- // Index generation
38
- export { generateAllIndexes, generateDirIndex } from "./index-generator.js";
39
-
40
25
  // Type constants and helpers
41
26
  export * from "./levels.js";
42
27