@forwardimpact/map 0.11.1 ā 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 +80 -23
- package/package.json +9 -2
- package/schema/json/capability.schema.json +33 -0
- package/schema/rdf/capability.ttl +48 -0
- package/src/loader.js +3 -0
- package/src/validation.js +54 -0
package/bin/fit-map.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* fit-map CLI
|
|
5
5
|
*
|
|
6
|
-
* Map validation
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
if (arg.startsWith("--
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
options[key] = value ?? true;
|
|
39
|
+
} else {
|
|
40
|
+
positional.push(arg);
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
|
|
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
|
|
229
|
-
generate-index
|
|
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
|
|
233
|
-
--shacl
|
|
234
|
-
--data=PATH
|
|
235
|
-
--help, -h
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
|
@@ -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/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
|
|