@forwardimpact/schema 0.4.0 → 0.7.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 (48) hide show
  1. package/bin/fit-schema.js +2 -2
  2. package/examples/capabilities/business.yaml +27 -11
  3. package/examples/capabilities/delivery.yaml +65 -27
  4. package/examples/capabilities/people.yaml +1 -1
  5. package/examples/capabilities/reliability.yaml +85 -31
  6. package/examples/capabilities/scale.yaml +83 -31
  7. package/examples/framework.yaml +5 -1
  8. package/examples/questions/behaviours/outcome_ownership.yaml +226 -49
  9. package/examples/questions/behaviours/polymathic_knowledge.yaml +273 -45
  10. package/examples/questions/behaviours/precise_communication.yaml +246 -52
  11. package/examples/questions/behaviours/relentless_curiosity.yaml +246 -48
  12. package/examples/questions/behaviours/systems_thinking.yaml +236 -50
  13. package/examples/questions/capabilities/business.yaml +107 -0
  14. package/examples/questions/capabilities/delivery.yaml +104 -0
  15. package/examples/questions/capabilities/people.yaml +104 -0
  16. package/examples/questions/capabilities/reliability.yaml +103 -0
  17. package/examples/questions/capabilities/scale.yaml +103 -0
  18. package/examples/questions/skills/architecture_design.yaml +102 -51
  19. package/examples/questions/skills/cloud_platforms.yaml +90 -44
  20. package/examples/questions/skills/code_quality.yaml +86 -45
  21. package/examples/questions/skills/data_modeling.yaml +93 -43
  22. package/examples/questions/skills/devops.yaml +91 -44
  23. package/examples/questions/skills/full_stack_development.yaml +93 -45
  24. package/examples/questions/skills/sre_practices.yaml +92 -41
  25. package/examples/questions/skills/stakeholder_management.yaml +97 -46
  26. package/examples/questions/skills/team_collaboration.yaml +87 -40
  27. package/examples/questions/skills/technical_writing.yaml +89 -40
  28. package/examples/stages.yaml +52 -13
  29. package/package.json +9 -9
  30. package/schema/json/behaviour-questions.schema.json +53 -26
  31. package/schema/json/capability-questions.schema.json +95 -0
  32. package/schema/json/capability.schema.json +8 -7
  33. package/schema/json/framework.schema.json +13 -0
  34. package/schema/json/skill-questions.schema.json +34 -19
  35. package/schema/json/stages.schema.json +6 -6
  36. package/schema/rdf/behaviour-questions.ttl +39 -7
  37. package/schema/rdf/capability.ttl +15 -15
  38. package/schema/rdf/defs.ttl +3 -3
  39. package/schema/rdf/framework.ttl +38 -0
  40. package/schema/rdf/skill-questions.ttl +28 -1
  41. package/schema/rdf/stages.ttl +14 -14
  42. package/{lib → src}/levels.js +53 -101
  43. package/{lib → src}/loader.js +9 -5
  44. package/{lib → src}/modifiers.js +3 -3
  45. package/{lib → src}/validation.js +105 -79
  46. /package/{lib → src}/index-generator.js +0 -0
  47. /package/{lib → src}/index.js +0 -0
  48. /package/{lib → src}/schema-validation.js +0 -0
@@ -14,7 +14,7 @@
14
14
  "properties": {
15
15
  "id": {
16
16
  "type": "string",
17
- "enum": ["specify", "plan", "code", "review", "deploy"],
17
+ "enum": ["specify", "plan", "onboard", "code", "review", "deploy"],
18
18
  "description": "Stage identifier"
19
19
  },
20
20
  "name": {
@@ -47,16 +47,16 @@
47
47
  "type": "string"
48
48
  }
49
49
  },
50
- "entryCriteria": {
50
+ "readChecklist": {
51
51
  "type": "array",
52
- "description": "Conditions that must be met before entering this stage",
52
+ "description": "Read-Then-Do Checklist: steps to follow in order during this stage",
53
53
  "items": {
54
54
  "type": "string"
55
55
  }
56
56
  },
57
- "exitCriteria": {
57
+ "confirmChecklist": {
58
58
  "type": "array",
59
- "description": "Conditions that must be met before leaving this stage",
59
+ "description": "Do-Then-Confirm Checklist: items to verify before handing off to next stage",
60
60
  "items": {
61
61
  "type": "string"
62
62
  }
@@ -70,7 +70,7 @@
70
70
  "properties": {
71
71
  "targetStage": {
72
72
  "type": "string",
73
- "enum": ["specify", "plan", "code", "review", "deploy"],
73
+ "enum": ["specify", "plan", "onboard", "code", "review", "deploy"],
74
74
  "description": "The stage to transition to"
75
75
  },
76
76
  "label": {
@@ -7,7 +7,10 @@
7
7
  # =============================================================================
8
8
  # Behaviour Questions Schema
9
9
  # =============================================================================
10
- # Interview questions for assessing behaviours.
10
+ # Stakeholder simulation interview questions for assessing behaviours,
11
+ # organized by role type (professional/management) and maturity level.
12
+ # Each question presents a realistic stakeholder scenario with simulation
13
+ # prompts to guide the interview panel.
11
14
  # =============================================================================
12
15
 
13
16
  # -----------------------------------------------------------------------------
@@ -26,6 +29,12 @@ fit:atMaturity a rdf:Property ;
26
29
  rdfs:domain fit:Question ;
27
30
  rdfs:range fit:BehaviourMaturity .
28
31
 
32
+ fit:simulationPrompts a rdf:Property ;
33
+ rdfs:label "simulationPrompts"@en ;
34
+ rdfs:comment "Guiding prompts to steer the stakeholder simulation and probe the candidate's behaviour"@en ;
35
+ rdfs:domain fit:Question ;
36
+ rdfs:range xsd:string .
37
+
29
38
  # =============================================================================
30
39
  # SHACL SHAPES
31
40
  # =============================================================================
@@ -48,27 +57,42 @@ fit:BehaviourQuestionShape a sh:NodeShape ;
48
57
  sh:property [
49
58
  sh:path fit:text ;
50
59
  sh:datatype xsd:string ;
51
- sh:maxLength 150 ;
60
+ sh:maxLength 300 ;
52
61
  sh:minCount 1 ;
53
62
  sh:maxCount 1 ;
54
63
  sh:name "text" ;
55
- sh:description "The question text (under 150 characters)" ;
64
+ sh:description "The stakeholder scenario (under 300 characters)" ;
65
+ ] ;
66
+ sh:property [
67
+ sh:path fit:context ;
68
+ sh:datatype xsd:string ;
69
+ sh:maxCount 1 ;
70
+ sh:name "context" ;
71
+ sh:description "Additional context to set up the simulation scenario" ;
72
+ ] ;
73
+ sh:property [
74
+ sh:path fit:simulationPrompts ;
75
+ sh:datatype xsd:string ;
76
+ sh:minCount 2 ;
77
+ sh:maxCount 5 ;
78
+ sh:name "simulationPrompts" ;
79
+ sh:description "3-5 guiding prompts to steer the simulation" ;
56
80
  ] ;
57
81
  sh:property [
58
82
  sh:path fit:lookingFor ;
59
83
  sh:datatype xsd:string ;
60
- sh:minCount 1 ;
84
+ sh:minCount 2 ;
61
85
  sh:maxCount 4 ;
62
86
  sh:name "lookingFor" ;
63
- sh:description "2-4 bullet points of good answer indicators" ;
87
+ sh:description "2-4 indicators of strong behavioural response" ;
64
88
  ] ;
65
89
  sh:property [
66
90
  sh:path fit:expectedDurationMinutes ;
67
91
  sh:datatype xsd:integer ;
68
- sh:minInclusive 1 ;
92
+ sh:minInclusive 10 ;
69
93
  sh:maxCount 1 ;
70
94
  sh:name "expectedDurationMinutes" ;
71
- sh:description "Expected duration in minutes" ;
95
+ sh:description "Expected duration in minutes (default: 20)" ;
72
96
  ] ;
73
97
  sh:property [
74
98
  sh:path fit:followUps ;
@@ -93,4 +117,12 @@ fit:BehaviourQuestionShape a sh:NodeShape ;
93
117
  sh:maxCount 1 ;
94
118
  sh:name "atMaturity" ;
95
119
  sh:description "The behaviour maturity this question is for" ;
120
+ ] ;
121
+ sh:property [
122
+ sh:path fit:forRoleType ;
123
+ sh:in ( fit:professional fit:management ) ;
124
+ sh:minCount 1 ;
125
+ sh:maxCount 1 ;
126
+ sh:name "forRoleType" ;
127
+ sh:description "The role type this question is for" ;
96
128
  ] .
@@ -153,14 +153,14 @@ fit:focus a rdf:Property ;
153
153
  rdfs:comment "Primary focus for this stage"@en ;
154
154
  rdfs:range xsd:string .
155
155
 
156
- fit:activities a rdf:Property ;
157
- rdfs:label "activities"@en ;
158
- rdfs:comment "Key activities to perform in this stage"@en ;
156
+ fit:readChecklist a rdf:Property ;
157
+ rdfs:label "readChecklist"@en ;
158
+ rdfs:comment "Read-Then-Do Checklist: steps to follow in order during this stage"@en ;
159
159
  rdfs:range xsd:string .
160
160
 
161
- fit:ready a rdf:Property ;
162
- rdfs:label "ready"@en ;
163
- rdfs:comment "Criteria that indicate readiness to move to next stage"@en ;
161
+ fit:confirmChecklist a rdf:Property ;
162
+ rdfs:label "confirmChecklist"@en ;
163
+ rdfs:comment "Do-Then-Confirm Checklist: items to verify before moving to next stage"@en ;
164
164
  rdfs:range xsd:string .
165
165
 
166
166
  # =============================================================================
@@ -197,12 +197,12 @@ fit:CapabilityShape a sh:NodeShape ;
197
197
  sh:description "Emoji for visual representation" ;
198
198
  ] ;
199
199
  sh:property [
200
- sh:path fit:displayOrder ;
200
+ sh:path fit:ordinalRank ;
201
201
  sh:datatype xsd:integer ;
202
202
  sh:minInclusive 1 ;
203
203
  sh:maxCount 1 ;
204
- sh:name "displayOrder" ;
205
- sh:description "Order for display in UI" ;
204
+ sh:name "ordinalRank" ;
205
+ sh:description "Numeric rank for ordering capabilities (1 = first)" ;
206
206
  ] ;
207
207
  sh:property [
208
208
  sh:path fit:description ;
@@ -425,16 +425,16 @@ fit:SkillStageShape a sh:NodeShape ;
425
425
  sh:description "Primary focus for this stage" ;
426
426
  ] ;
427
427
  sh:property [
428
- sh:path fit:activities ;
428
+ sh:path fit:readChecklist ;
429
429
  sh:datatype xsd:string ;
430
430
  sh:minCount 1 ;
431
- sh:name "activities" ;
432
- sh:description "Key activities to perform in this stage" ;
431
+ sh:name "readChecklist" ;
432
+ sh:description "Read-Then-Do Checklist: steps to follow in order during this stage" ;
433
433
  ] ;
434
434
  sh:property [
435
- sh:path fit:ready ;
435
+ sh:path fit:confirmChecklist ;
436
436
  sh:datatype xsd:string ;
437
437
  sh:minCount 1 ;
438
- sh:name "ready" ;
439
- sh:description "Criteria that indicate readiness to move to next stage" ;
438
+ sh:name "confirmChecklist" ;
439
+ sh:description "Do-Then-Confirm Checklist: items to verify before moving to next stage" ;
440
440
  ] .
@@ -134,9 +134,9 @@ fit:emojiIcon a rdf:Property ;
134
134
  rdfs:comment "Emoji for visual representation"@en ;
135
135
  rdfs:range xsd:string .
136
136
 
137
- fit:displayOrder a rdf:Property ;
138
- rdfs:label "displayOrder"@en ;
139
- rdfs:comment "Order for display in UI"@en ;
137
+ fit:ordinalRank a rdf:Property ;
138
+ rdfs:label "ordinalRank"@en ;
139
+ rdfs:comment "Numeric rank for ordering (1 = first)"@en ;
140
140
  rdfs:range xsd:integer .
141
141
 
142
142
  fit:ordinal a rdf:Property ;
@@ -22,6 +22,10 @@ fit:EntityDefinition a rdfs:Class ;
22
22
  rdfs:label "Entity Definition"@en ;
23
23
  rdfs:comment "Definition for an entity type used in pages and chapters"@en .
24
24
 
25
+ fit:Distribution a rdfs:Class ;
26
+ rdfs:label "Distribution"@en ;
27
+ rdfs:comment "Distribution configuration for publishing and installing the framework"@en .
28
+
25
29
  # -----------------------------------------------------------------------------
26
30
  # Properties
27
31
  # -----------------------------------------------------------------------------
@@ -38,6 +42,18 @@ fit:entityDefinitions a rdf:Property ;
38
42
  rdfs:domain fit:Framework ;
39
43
  rdfs:range fit:EntityDefinition .
40
44
 
45
+ fit:distribution a rdf:Property ;
46
+ rdfs:label "distribution"@en ;
47
+ rdfs:comment "Distribution configuration for the framework"@en ;
48
+ rdfs:domain fit:Framework ;
49
+ rdfs:range fit:Distribution .
50
+
51
+ fit:siteUrl a rdf:Property ;
52
+ rdfs:label "siteUrl"@en ;
53
+ rdfs:comment "Base URL for the published static site"@en ;
54
+ rdfs:domain fit:Distribution ;
55
+ rdfs:range xsd:anyURI .
56
+
41
57
  fit:entityType a rdf:Property ;
42
58
  rdfs:label "entityType"@en ;
43
59
  rdfs:comment "The type of entity this definition describes"@en ;
@@ -88,6 +104,28 @@ fit:FrameworkShape a sh:NodeShape ;
88
104
  sh:node fit:EntityDefinitionShape ;
89
105
  sh:name "entityDefinitions" ;
90
106
  sh:description "Definitions for each entity type" ;
107
+ ] ;
108
+ sh:property [
109
+ sh:path fit:distribution ;
110
+ sh:node fit:DistributionShape ;
111
+ sh:maxCount 1 ;
112
+ sh:name "distribution" ;
113
+ sh:description "Distribution configuration for the framework" ;
114
+ ] .
115
+
116
+ # -----------------------------------------------------------------------------
117
+ # Distribution Shape
118
+ # -----------------------------------------------------------------------------
119
+
120
+ fit:DistributionShape a sh:NodeShape ;
121
+ sh:targetClass fit:Distribution ;
122
+ sh:property [
123
+ sh:path fit:siteUrl ;
124
+ sh:datatype xsd:anyURI ;
125
+ sh:minCount 1 ;
126
+ sh:maxCount 1 ;
127
+ sh:name "siteUrl" ;
128
+ sh:description "Base URL for the published static site" ;
91
129
  ] .
92
130
 
93
131
  # -----------------------------------------------------------------------------
@@ -7,7 +7,8 @@
7
7
  # =============================================================================
8
8
  # Skill Questions Schema
9
9
  # =============================================================================
10
- # Interview questions for assessing skills.
10
+ # Interview questions for assessing skills, organized by role type
11
+ # (professional/management) and level.
11
12
  # =============================================================================
12
13
 
13
14
  # -----------------------------------------------------------------------------
@@ -18,6 +19,18 @@ fit:Question a rdfs:Class ;
18
19
  rdfs:label "Question"@en ;
19
20
  rdfs:comment "Interview question for assessing skills or behaviours"@en .
20
21
 
22
+ fit:RoleType a rdfs:Class ;
23
+ rdfs:label "RoleType"@en ;
24
+ rdfs:comment "Type of role: professional (IC) or management"@en .
25
+
26
+ fit:professional a fit:RoleType ;
27
+ rdfs:label "professional"@en ;
28
+ rdfs:comment "Professional/individual contributor roles"@en .
29
+
30
+ fit:management a fit:RoleType ;
31
+ rdfs:label "management"@en ;
32
+ rdfs:comment "Management roles"@en .
33
+
21
34
  # -----------------------------------------------------------------------------
22
35
  # Properties
23
36
  # -----------------------------------------------------------------------------
@@ -58,6 +71,12 @@ fit:atLevel a rdf:Property ;
58
71
  rdfs:domain fit:Question ;
59
72
  rdfs:range fit:SkillLevel .
60
73
 
74
+ fit:forRoleType a rdf:Property ;
75
+ rdfs:label "forRoleType"@en ;
76
+ rdfs:comment "The role type this question is intended for (professional or management)"@en ;
77
+ rdfs:domain fit:Question ;
78
+ rdfs:range fit:RoleType .
79
+
61
80
  # =============================================================================
62
81
  # SHACL SHAPES
63
82
  # =============================================================================
@@ -125,4 +144,12 @@ fit:SkillQuestionShape a sh:NodeShape ;
125
144
  sh:maxCount 1 ;
126
145
  sh:name "atLevel" ;
127
146
  sh:description "The skill level this question is for" ;
147
+ ] ;
148
+ sh:property [
149
+ sh:path fit:forRoleType ;
150
+ sh:in ( fit:professional fit:management ) ;
151
+ sh:minCount 1 ;
152
+ sh:maxCount 1 ;
153
+ sh:name "forRoleType" ;
154
+ sh:description "The role type this question is for" ;
128
155
  ] .
@@ -28,14 +28,14 @@ fit:handoffs a rdf:Property ;
28
28
  rdfs:domain fit:Stage ;
29
29
  rdfs:range fit:Handoff .
30
30
 
31
- fit:entryCriteria a rdf:Property ;
32
- rdfs:label "entryCriteria"@en ;
33
- rdfs:comment "Conditions that must be met before entering this stage"@en ;
31
+ fit:readChecklist a rdf:Property ;
32
+ rdfs:label "readChecklist"@en ;
33
+ rdfs:comment "Read-Then-Do Checklist: steps to follow in order during this stage"@en ;
34
34
  rdfs:range xsd:string .
35
35
 
36
- fit:exitCriteria a rdf:Property ;
37
- rdfs:label "exitCriteria"@en ;
38
- rdfs:comment "Conditions that must be met before leaving this stage"@en ;
36
+ fit:confirmChecklist a rdf:Property ;
37
+ rdfs:label "confirmChecklist"@en ;
38
+ rdfs:comment "Do-Then-Confirm Checklist: items to verify before handing off to next stage"@en ;
39
39
  rdfs:range xsd:string .
40
40
 
41
41
  fit:summary a rdf:Property ;
@@ -74,7 +74,7 @@ fit:StageShape a sh:NodeShape ;
74
74
  sh:targetClass fit:Stage ;
75
75
  sh:property [
76
76
  sh:path fit:id ;
77
- sh:in ( "specify" "plan" "code" "review" "deploy" ) ;
77
+ sh:in ( "specify" "plan" "onboard" "code" "review" "deploy" ) ;
78
78
  sh:minCount 1 ;
79
79
  sh:maxCount 1 ;
80
80
  sh:name "id" ;
@@ -122,16 +122,16 @@ fit:StageShape a sh:NodeShape ;
122
122
  sh:description "Restrictions on behaviour in this stage" ;
123
123
  ] ;
124
124
  sh:property [
125
- sh:path fit:entryCriteria ;
125
+ sh:path fit:readChecklist ;
126
126
  sh:datatype xsd:string ;
127
- sh:name "entryCriteria" ;
128
- sh:description "Conditions that must be met before entering this stage" ;
127
+ sh:name "readChecklist" ;
128
+ sh:description "Read-Then-Do Checklist: steps to follow in order during this stage" ;
129
129
  ] ;
130
130
  sh:property [
131
- sh:path fit:exitCriteria ;
131
+ sh:path fit:confirmChecklist ;
132
132
  sh:datatype xsd:string ;
133
- sh:name "exitCriteria" ;
134
- sh:description "Conditions that must be met before leaving this stage" ;
133
+ sh:name "confirmChecklist" ;
134
+ sh:description "Do-Then-Confirm Checklist: items to verify before handing off to next stage" ;
135
135
  ] .
136
136
 
137
137
  # -----------------------------------------------------------------------------
@@ -142,7 +142,7 @@ fit:HandoffShape a sh:NodeShape ;
142
142
  sh:targetClass fit:Handoff ;
143
143
  sh:property [
144
144
  sh:path fit:targetStage ;
145
- sh:in ( "specify" "plan" "code" "review" "deploy" ) ;
145
+ sh:in ( "specify" "plan" "onboard" "code" "review" "deploy" ) ;
146
146
  sh:minCount 1 ;
147
147
  sh:maxCount 1 ;
148
148
  sh:name "targetStage" ;
@@ -54,30 +54,25 @@ export const BEHAVIOUR_MATURITY_ORDER = [
54
54
  BehaviourMaturity.EXEMPLIFYING,
55
55
  ];
56
56
 
57
- /**
58
- * Lifecycle stages for development workflow
59
- * @readonly
60
- * @enum {string}
61
- */
62
- export const Stage = {
63
- SPECIFY: "specify",
64
- PLAN: "plan",
65
- CODE: "code",
66
- REVIEW: "review",
67
- DEPLOY: "deploy",
68
- };
57
+ // ============================================================================
58
+ // Data-driven Stage Functions
59
+ // ============================================================================
60
+ // Stage ordering is derived from loaded stage data, not hardcoded.
61
+ // Use getStageOrder(stages) to get stage IDs in lifecycle order.
69
62
 
70
63
  /**
71
- * Ordered array of stages for lifecycle progression
72
- * @type {string[]}
64
+ * Get ordered stage IDs from loaded stage data
65
+ *
66
+ * Stages are defined in stages.yaml and their array order IS the
67
+ * canonical lifecycle order. This function extracts IDs preserving
68
+ * that order, similar to getCapabilityOrder for capabilities.
69
+ *
70
+ * @param {Object[]} stages - Loaded stages array from stages.yaml
71
+ * @returns {string[]} Stage IDs in lifecycle order
73
72
  */
74
- export const STAGE_ORDER = [
75
- Stage.SPECIFY,
76
- Stage.PLAN,
77
- Stage.CODE,
78
- Stage.REVIEW,
79
- Stage.DEPLOY,
80
- ];
73
+ export function getStageOrder(stages) {
74
+ return stages.map((s) => s.id);
75
+ }
81
76
 
82
77
  /**
83
78
  * Skill capabilities (what capability area)
@@ -98,72 +93,44 @@ export const Capability = {
98
93
  PRODUCT: "product",
99
94
  };
100
95
 
101
- /**
102
- * Ordered array of capabilities for consistent display
103
- * Groups related capabilities logically:
104
- * 1. Core delivery
105
- * 2. Data & AI capabilities
106
- * 3. Scale & reliability
107
- * 4. People & process
108
- * 5. Business, documentation & product
109
- * @type {string[]}
110
- */
111
- export const CAPABILITY_ORDER = [
112
- Capability.DELIVERY,
113
- Capability.DATA,
114
- Capability.AI,
115
- Capability.ML,
116
- Capability.SCALE,
117
- Capability.RELIABILITY,
118
- Capability.PEOPLE,
119
- Capability.PROCESS,
120
- Capability.BUSINESS,
121
- Capability.DOCUMENTATION,
122
- Capability.PRODUCT,
123
- ];
124
-
125
- /**
126
- * Get the index of a capability in the ordered list
127
- * @param {string} capability - The capability to look up
128
- * @returns {number} The index (0-based), or -1 if not found
129
- */
130
- export function getCapabilityIndex(capability) {
131
- return CAPABILITY_ORDER.indexOf(capability);
132
- }
96
+ // ============================================================================
97
+ // Data-driven Capability Functions
98
+ // ============================================================================
99
+ // These functions work with loaded capability data for responsibility derivation
133
100
 
134
101
  /**
135
- * Compare two capabilities for sorting
136
- * @param {string} a - First capability
137
- * @param {string} b - Second capability
138
- * @returns {number} Comparison result for sorting
102
+ * Get capability metadata from loaded capability data
103
+ * @param {Object[]} capabilities - Loaded capabilities array
104
+ * @param {string} capabilityId - The capability ID to look up
105
+ * @returns {Object|undefined} The capability object or undefined
139
106
  */
140
- export function compareCapabilities(a, b) {
141
- return getCapabilityIndex(a) - getCapabilityIndex(b);
107
+ export function getCapabilityById(capabilities, capabilityId) {
108
+ return capabilities.find((c) => c.id === capabilityId);
142
109
  }
143
110
 
144
111
  /**
145
- * Sort an array of skills by capability order, then by name
146
- * @param {import('./levels.js').Skill[]} skills - Array of skills to sort
147
- * @returns {import('./levels.js').Skill[]} Sorted array (new array, does not mutate input)
112
+ * Get ordered capability IDs from loaded capability data
113
+ * @param {Object[]} capabilities - Loaded capabilities array
114
+ * @returns {string[]} Capability IDs in display order
148
115
  */
149
- export function sortSkillsByCapability(skills) {
150
- return [...skills].sort((a, b) => {
151
- const capabilityCompare = compareCapabilities(a.capability, b.capability);
152
- if (capabilityCompare !== 0) return capabilityCompare;
153
- return a.name.localeCompare(b.name);
154
- });
116
+ export function getCapabilityOrder(capabilities) {
117
+ return [...capabilities]
118
+ .sort((a, b) => (a.ordinalRank || 0) - (b.ordinalRank || 0))
119
+ .map((c) => c.id);
155
120
  }
156
121
 
157
122
  /**
158
- * Group skills by capability in the defined order
123
+ * Group skills by capability in display order
159
124
  * @param {import('./levels.js').Skill[]} skills - Array of skills to group
160
- * @returns {Object<string, import('./levels.js').Skill[]>} Object with capabilities as keys (in order)
125
+ * @param {Object[]} capabilities - Loaded capabilities array for ordering
126
+ * @returns {Object<string, import('./levels.js').Skill[]>} Object with capabilities as keys (in display order)
161
127
  */
162
- export function groupSkillsByCapability(skills) {
128
+ export function groupSkillsByCapability(skills, capabilities) {
129
+ const capabilityOrder = getCapabilityOrder(capabilities);
163
130
  const result = {};
164
131
 
165
- // Initialize all capabilities in order (ensures consistent key order)
166
- for (const capability of CAPABILITY_ORDER) {
132
+ // Initialize all capabilities in display order (ensures consistent key order)
133
+ for (const capability of capabilityOrder) {
167
134
  result[capability] = [];
168
135
  }
169
136
 
@@ -186,32 +153,6 @@ export function groupSkillsByCapability(skills) {
186
153
  return result;
187
154
  }
188
155
 
189
- // ============================================================================
190
- // Data-driven Capability Functions
191
- // ============================================================================
192
- // These functions work with loaded capability data for responsibility derivation
193
-
194
- /**
195
- * Get capability metadata from loaded capability data
196
- * @param {Object[]} capabilities - Loaded capabilities array
197
- * @param {string} capabilityId - The capability ID to look up
198
- * @returns {Object|undefined} The capability object or undefined
199
- */
200
- export function getCapabilityById(capabilities, capabilityId) {
201
- return capabilities.find((c) => c.id === capabilityId);
202
- }
203
-
204
- /**
205
- * Get ordered capability IDs from loaded capability data
206
- * @param {Object[]} capabilities - Loaded capabilities array
207
- * @returns {string[]} Capability IDs in display order
208
- */
209
- export function getCapabilityOrder(capabilities) {
210
- return [...capabilities]
211
- .sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0))
212
- .map((c) => c.id);
213
- }
214
-
215
156
  /**
216
157
  * Get emoji for a capability from loaded capability data
217
158
  * @param {Object[]} capabilities - Loaded capabilities array
@@ -400,10 +341,21 @@ export const SkillType = {
400
341
  * @property {number} [expectedDurationMinutes] - Estimated time to ask and answer
401
342
  */
402
343
 
344
+ /**
345
+ * @typedef {Object<string, Question[]>} LevelQuestions - Questions organized by level
346
+ */
347
+
348
+ /**
349
+ * @typedef {Object} RoleTypeQuestions
350
+ * @property {LevelQuestions} [professionalQuestions] - Questions for professional/IC roles
351
+ * @property {LevelQuestions} [managementQuestions] - Questions for management roles
352
+ */
353
+
403
354
  /**
404
355
  * @typedef {Object} QuestionBank
405
- * @property {Object<string, Object<string, Question[]>>} skillLevels - Questions by skill ID, then by level
406
- * @property {Object<string, Object<string, Question[]>>} behaviourMaturities - Questions by behaviour ID, then by maturity
356
+ * @property {Object<string, RoleTypeQuestions>} skillLevels - Questions by skill ID, then by role type (professional/management), then by level
357
+ * @property {Object<string, RoleTypeQuestions>} behaviourMaturities - Questions by behaviour ID, then by role type, then by maturity
358
+ * @property {Object<string, RoleTypeQuestions>} [capabilityLevels] - Questions by capability ID, then by role type, then by level
407
359
  */
408
360
 
409
361
  /**
@@ -281,12 +281,16 @@ async function loadCapabilitiesFromDir(capabilitiesDir) {
281
281
  * @returns {Promise<import('./levels.js').QuestionBank>}
282
282
  */
283
283
  export async function loadQuestionFolder(questionsDir) {
284
- const [skillLevels, behaviourMaturities] = await Promise.all([
285
- loadQuestionsFromDir(join(questionsDir, "skills")),
286
- loadQuestionsFromDir(join(questionsDir, "behaviours")),
287
- ]);
284
+ const [skillLevels, behaviourMaturities, capabilityLevels] =
285
+ await Promise.all([
286
+ loadQuestionsFromDir(join(questionsDir, "skills")),
287
+ loadQuestionsFromDir(join(questionsDir, "behaviours")),
288
+ loadQuestionsFromDir(join(questionsDir, "capabilities")).catch(
289
+ () => ({}),
290
+ ),
291
+ ]);
288
292
 
289
- return { skillLevels, behaviourMaturities };
293
+ return { skillLevels, behaviourMaturities, capabilityLevels };
290
294
  }
291
295
 
292
296
  /**
@@ -5,13 +5,13 @@
5
5
  * Full modifier logic is in @forwardimpact/model.
6
6
  */
7
7
 
8
- import { CAPABILITY_ORDER } from "./levels.js";
8
+ import { Capability } from "./levels.js";
9
9
 
10
10
  /**
11
- * Valid skill capability names
11
+ * Valid skill capability names (derived from Capability enum)
12
12
  * @type {Set<string>}
13
13
  */
14
- const VALID_CAPABILITIES = new Set(CAPABILITY_ORDER);
14
+ const VALID_CAPABILITIES = new Set(Object.values(Capability));
15
15
 
16
16
  /**
17
17
  * Check if a key is a skill capability