@forwardimpact/map 0.11.0 → 0.12.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/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
  /**
@@ -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/ingestion/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.0",
3
+ "version": "0.12.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": {
@@ -33,7 +33,14 @@
33
33
  "./levels": "./src/levels.js",
34
34
  "./examples/*": "./examples/*",
35
35
  "./schema/json/*": "./schema/json/*",
36
- "./schema/rdf/*": "./schema/rdf/*"
36
+ "./schema/rdf/*": "./schema/rdf/*",
37
+ "./activity/queries/org": "./activity/queries/org.js",
38
+ "./activity/queries/snapshots": "./activity/queries/snapshots.js",
39
+ "./activity/queries/evidence": "./activity/queries/evidence.js",
40
+ "./activity/queries/artifacts": "./activity/queries/artifacts.js",
41
+ "./activity/ingestion/people": "./activity/ingestion/people.js",
42
+ "./activity/ingestion/getdx": "./activity/ingestion/getdx.js",
43
+ "./activity/ingestion/github": "./activity/ingestion/github.js"
37
44
  },
38
45
  "dependencies": {
39
46
  "ajv": "^8.17.1",
@@ -45,5 +52,8 @@
45
52
  },
46
53
  "engines": {
47
54
  "node": ">=18.0.0"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
48
58
  }
49
59
  }
@@ -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"],
@@ -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
  # -----------------------------------------------------------------------------
package/src/loader.js CHANGED
@@ -94,6 +94,7 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
94
94
  installScript,
95
95
  implementationReference,
96
96
  toolReferences,
97
+ markers,
97
98
  } = skill;
98
99
  allSkills.push({
99
100
  id,
@@ -111,6 +112,8 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
111
112
  // Include implementation reference and tool references (shared by human and agent)
112
113
  ...(implementationReference && { implementationReference }),
113
114
  ...(toolReferences && { toolReferences }),
115
+ // Include markers for evidence evaluation
116
+ ...(markers && { markers }),
114
117
  });
115
118
  }
116
119
  }
package/src/modifiers.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Skill Modifier Helpers for Validation
3
3
  *
4
4
  * Contains only the isCapability function needed for schema validation.
5
- * Full modifier logic is in @forwardimpact/libpathway.
5
+ * Full modifier logic is in @forwardimpact/libskill.
6
6
  */
7
7
 
8
8
  import { Capability } from "./levels.js";
package/src/validation.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import {
8
8
  Capability,
9
+ SKILL_PROFICIENCY_ORDER,
9
10
  getSkillProficiencyIndex,
10
11
  getBehaviourMaturityIndex,
11
12
  } from "./levels.js";
@@ -406,6 +407,59 @@ function validateSkill(skill, index, requiredStageIds = []) {
406
407
  }
407
408
  }
408
409
 
410
+ // Validate markers if present
411
+ if (skill.markers !== undefined) {
412
+ if (typeof skill.markers !== "object" || Array.isArray(skill.markers)) {
413
+ errors.push(
414
+ createError(
415
+ "INVALID_VALUE",
416
+ "Skill markers must be an object keyed by proficiency level",
417
+ `${path}.markers`,
418
+ skill.markers,
419
+ ),
420
+ );
421
+ } else {
422
+ for (const [level, levelMarkers] of Object.entries(skill.markers)) {
423
+ if (!SKILL_PROFICIENCY_ORDER.includes(level)) {
424
+ errors.push(
425
+ createError(
426
+ "INVALID_VALUE",
427
+ `Invalid marker level: ${level}. Must be one of: ${SKILL_PROFICIENCY_ORDER.join(", ")}`,
428
+ `${path}.markers.${level}`,
429
+ level,
430
+ ),
431
+ );
432
+ continue;
433
+ }
434
+ if (typeof levelMarkers !== "object" || Array.isArray(levelMarkers)) {
435
+ errors.push(
436
+ createError(
437
+ "INVALID_VALUE",
438
+ `Markers at level ${level} must be an object with human/agent arrays`,
439
+ `${path}.markers.${level}`,
440
+ levelMarkers,
441
+ ),
442
+ );
443
+ continue;
444
+ }
445
+ for (const section of ["human", "agent"]) {
446
+ if (levelMarkers[section] !== undefined) {
447
+ if (!Array.isArray(levelMarkers[section])) {
448
+ errors.push(
449
+ createError(
450
+ "INVALID_VALUE",
451
+ `Markers ${section} at level ${level} must be an array of strings`,
452
+ `${path}.markers.${level}.${section}`,
453
+ levelMarkers[section],
454
+ ),
455
+ );
456
+ }
457
+ }
458
+ }
459
+ }
460
+ }
461
+ }
462
+
409
463
  return { errors, warnings };
410
464
  }
411
465