@forwardimpact/pathway 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/pathway",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Career progression web app and CLI for exploring roles and generating agents",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -40,8 +40,8 @@
40
40
  "./commands": "./src/commands/index.js"
41
41
  },
42
42
  "dependencies": {
43
- "@forwardimpact/schema": "^0.4.0",
44
- "@forwardimpact/model": "^0.5.0",
43
+ "@forwardimpact/schema": "^0.5.0",
44
+ "@forwardimpact/model": "^0.6.0",
45
45
  "mustache": "^4.2.0",
46
46
  "simple-icons": "^16.7.0",
47
47
  "yaml": "^2.3.4"
@@ -37,7 +37,7 @@ import {
37
37
  validateAgentSkill,
38
38
  deriveReferenceGrade,
39
39
  deriveAgentSkills,
40
- generateSkillMd,
40
+ generateSkillMarkdown,
41
41
  deriveToolkit,
42
42
  buildAgentIndex,
43
43
  } from "@forwardimpact/model";
@@ -502,7 +502,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
502
502
  const skillFiles = derivedSkills
503
503
  .map((derived) => skillsWithAgent.find((s) => s.id === derived.skillId))
504
504
  .filter((skill) => skill?.agent)
505
- .map((skill) => generateSkillMd(skill, data.stages));
505
+ .map((skill) => generateSkillMarkdown(skill, data.stages));
506
506
 
507
507
  // Validate all profiles
508
508
  for (const profile of profiles) {
@@ -16,7 +16,7 @@ import { skillToMarkdown } from "../formatters/skill/markdown.js";
16
16
  import { prepareSkillsList } from "../formatters/skill/shared.js";
17
17
  import { getConceptEmoji } from "@forwardimpact/schema/levels";
18
18
  import { formatTable, formatError } from "../lib/cli-output.js";
19
- import { generateSkillMd } from "@forwardimpact/model/agent";
19
+ import { generateSkillMarkdown } from "@forwardimpact/model/agent";
20
20
  import { formatAgentSkill } from "../formatters/agent/skill.js";
21
21
  import { loadSkillTemplate } from "../lib/template-loader.js";
22
22
 
@@ -80,7 +80,7 @@ async function formatAgentDetail(skill, stages, dataDir) {
80
80
  }
81
81
 
82
82
  const template = await loadSkillTemplate(dataDir);
83
- const skillMd = generateSkillMd(skill, stages);
83
+ const skillMd = generateSkillMarkdown(skill, stages);
84
84
  const output = formatAgentSkill(skillMd, template);
85
85
  console.log(output);
86
86
  }
@@ -13,7 +13,7 @@ import {
13
13
  getBehaviourMaturityIndex,
14
14
  formatLevel,
15
15
  } from "../lib/render.js";
16
- import { getCapabilityIndex } from "@forwardimpact/schema/levels";
16
+ import { compareByCapability } from "@forwardimpact/model/policies";
17
17
 
18
18
  /**
19
19
  * Create a comparison skill radar chart
@@ -24,7 +24,7 @@ import { getCapabilityIndex } from "@forwardimpact/schema/levels";
24
24
  */
25
25
  export function createComparisonSkillRadar(
26
26
  currentMatrix,
27
- targetMatrix,
27
+ targetMatrix = [],
28
28
  options = {},
29
29
  ) {
30
30
  const container = div(
@@ -80,9 +80,11 @@ export function createComparisonSkillRadar(
80
80
  }
81
81
 
82
82
  // Sort by capability order, then by skill name within capability
83
+ const capabilityComparator = options.capabilities
84
+ ? compareByCapability(options.capabilities)
85
+ : (a, b) => a.capability.localeCompare(b.capability);
83
86
  skillEntries.sort((a, b) => {
84
- const capDiff =
85
- getCapabilityIndex(a.capability) - getCapabilityIndex(b.capability);
87
+ const capDiff = capabilityComparator(a, b);
86
88
  if (capDiff !== 0) return capDiff;
87
89
  return a.skillName.localeCompare(b.skillName);
88
90
  });
@@ -141,7 +143,7 @@ export function createComparisonSkillRadar(
141
143
  */
142
144
  export function createComparisonBehaviourRadar(
143
145
  currentProfile,
144
- targetProfile,
146
+ targetProfile = [],
145
147
  options = {},
146
148
  ) {
147
149
  const container = div(
@@ -93,50 +93,30 @@
93
93
  font-weight: 500;
94
94
  }
95
95
 
96
- /* Capability badges */
96
+ /* Capability badges - each capability ID maps to its own distinct color */
97
97
  .badge-delivery {
98
- background: var(--color-cat-delivery-light);
99
- color: var(--color-cat-delivery);
98
+ background: var(--color-cap-delivery-light);
99
+ color: var(--color-cap-delivery);
100
100
  }
101
101
 
102
102
  .badge-scale {
103
- background: var(--color-cat-scale-light);
104
- color: var(--color-cat-scale);
103
+ background: var(--color-cap-scale-light);
104
+ color: var(--color-cap-scale);
105
105
  }
106
106
 
107
107
  .badge-reliability {
108
- background: var(--color-cat-reliability-light);
109
- color: var(--color-cat-reliability);
110
- }
111
-
112
- .badge-data {
113
- background: var(--color-cat-data-light);
114
- color: var(--color-cat-data);
115
- }
116
-
117
- .badge-ai {
118
- background: var(--color-cat-ai-light);
119
- color: var(--color-cat-ai);
120
- }
121
-
122
- .badge-process {
123
- background: var(--color-cat-process-light);
124
- color: var(--color-cat-process);
108
+ background: var(--color-cap-reliability-light);
109
+ color: var(--color-cap-reliability);
125
110
  }
126
111
 
127
112
  .badge-business {
128
- background: var(--color-cat-business-light);
129
- color: var(--color-cat-business);
113
+ background: var(--color-cap-business-light);
114
+ color: var(--color-cap-business);
130
115
  }
131
116
 
132
117
  .badge-people {
133
- background: var(--color-cat-people-light);
134
- color: var(--color-cat-people);
135
- }
136
-
137
- .badge-documentation {
138
- background: var(--color-cat-documentation-light);
139
- color: var(--color-cat-documentation);
118
+ background: var(--color-cap-people-light);
119
+ color: var(--color-cap-people);
140
120
  }
141
121
 
142
122
  /* Tool badge */
@@ -43,30 +43,22 @@
43
43
  --color-maturity-5: #c4b5fd;
44
44
 
45
45
  /* --------------------------------------------------------------------------
46
- Colors - Categories (text)
46
+ Colors - Capabilities (text)
47
47
  -------------------------------------------------------------------------- */
48
- --color-cat-delivery: #dc2626; /* red - rapid delivery focus */
49
- --color-cat-scale: #1d4ed8; /* blue - infrastructure/platform */
50
- --color-cat-reliability: #0369a1; /* sky - SRE/operations */
51
- --color-cat-data: #0f766e; /* teal */
52
- --color-cat-ai: #7c3aed; /* violet */
53
- --color-cat-process: #c2410c; /* orange */
54
- --color-cat-business: #a16207; /* amber */
55
- --color-cat-people: #be185d; /* pink */
56
- --color-cat-documentation: #15803d; /* green */
48
+ --color-cap-delivery: #dc2626; /* red - speed and efficiency */
49
+ --color-cap-scale: #1d4ed8; /* blue - infrastructure and systems */
50
+ --color-cap-reliability: #0891b2; /* cyan - stability and operations */
51
+ --color-cap-business: #b45309; /* amber - strategy and outcomes */
52
+ --color-cap-people: #be185d; /* pink - collaboration and leadership */
57
53
 
58
54
  /* --------------------------------------------------------------------------
59
- Colors - Categories (backgrounds)
55
+ Colors - Capabilities (backgrounds)
60
56
  -------------------------------------------------------------------------- */
61
- --color-cat-delivery-light: #fecaca; /* red */
62
- --color-cat-scale-light: #dbeafe; /* blue */
63
- --color-cat-reliability-light: #e0f2fe; /* sky */
64
- --color-cat-data-light: #ccfbf1; /* teal */
65
- --color-cat-ai-light: #ede9fe; /* violet */
66
- --color-cat-process-light: #ffedd5; /* orange */
67
- --color-cat-business-light: #fef3c7; /* amber */
68
- --color-cat-people-light: #fce7f3; /* pink */
69
- --color-cat-documentation-light: #dcfce7; /* green */
57
+ --color-cap-delivery-light: #fecaca; /* red */
58
+ --color-cap-scale-light: #dbeafe; /* blue */
59
+ --color-cap-reliability-light: #cffafe; /* cyan */
60
+ --color-cap-business-light: #fef3c7; /* amber */
61
+ --color-cap-people-light: #fce7f3; /* pink */
70
62
 
71
63
  /* --------------------------------------------------------------------------
72
64
  Colors - Neutrals
@@ -43,7 +43,7 @@ export function prepareSkillsList(
43
43
  capabilities,
44
44
  descriptionLimit = 120,
45
45
  ) {
46
- const grouped = groupSkillsByCapability(skills);
46
+ const grouped = groupSkillsByCapability(skills, capabilities);
47
47
 
48
48
  const groups = {};
49
49
  for (const [capability, capabilitySkills] of Object.entries(grouped)) {
package/src/handout.html CHANGED
@@ -23,7 +23,9 @@
23
23
  "@forwardimpact/model/checklist": "/model/lib/checklist.js",
24
24
  "@forwardimpact/model/matching": "/model/lib/matching.js",
25
25
  "@forwardimpact/model/profile": "/model/lib/profile.js",
26
- "@forwardimpact/model/progression": "/model/lib/progression.js"
26
+ "@forwardimpact/model/progression": "/model/lib/progression.js",
27
+ "@forwardimpact/model/policies": "/model/lib/policies/index.js",
28
+ "@forwardimpact/model/toolkit": "/model/lib/toolkit.js"
27
29
  }
28
30
  }
29
31
  </script>
package/src/index.html CHANGED
@@ -35,7 +35,9 @@
35
35
  "@forwardimpact/model/checklist": "/model/lib/checklist.js",
36
36
  "@forwardimpact/model/matching": "/model/lib/matching.js",
37
37
  "@forwardimpact/model/profile": "/model/lib/profile.js",
38
- "@forwardimpact/model/progression": "/model/lib/progression.js"
38
+ "@forwardimpact/model/progression": "/model/lib/progression.js",
39
+ "@forwardimpact/model/policies": "/model/lib/policies/index.js",
40
+ "@forwardimpact/model/toolkit": "/model/lib/toolkit.js"
39
41
  }
40
42
  }
41
43
  </script>
@@ -11,13 +11,13 @@ import { deriveJob } from "@forwardimpact/model/derivation";
11
11
  const cache = new Map();
12
12
 
13
13
  /**
14
- * Create a consistent cache key from job parameters
14
+ * Build a consistent cache key from job parameters
15
15
  * @param {string} disciplineId
16
16
  * @param {string} gradeId
17
17
  * @param {string} [trackId] - Optional track ID
18
18
  * @returns {string}
19
19
  */
20
- export function makeJobKey(disciplineId, gradeId, trackId = null) {
20
+ export function buildJobKey(disciplineId, gradeId, trackId = null) {
21
21
  if (trackId) {
22
22
  return `${disciplineId}_${gradeId}_${trackId}`;
23
23
  }
@@ -43,7 +43,7 @@ export function getOrCreateJob({
43
43
  behaviours,
44
44
  capabilities,
45
45
  }) {
46
- const key = makeJobKey(discipline.id, grade.id, track?.id);
46
+ const key = buildJobKey(discipline.id, grade.id, track?.id);
47
47
 
48
48
  if (!cache.has(key)) {
49
49
  const job = deriveJob({
@@ -66,7 +66,7 @@ export function getOrCreateJob({
66
66
  /**
67
67
  * Clear all cached jobs
68
68
  */
69
- export function clearJobCache() {
69
+ export function clearCache() {
70
70
  cache.clear();
71
71
  }
72
72
 
@@ -76,14 +76,14 @@ export function clearJobCache() {
76
76
  * @param {string} gradeId
77
77
  * @param {string} [trackId] - Optional track ID
78
78
  */
79
- export function invalidateJob(disciplineId, gradeId, trackId = null) {
80
- cache.delete(makeJobKey(disciplineId, gradeId, trackId));
79
+ export function invalidateCachedJob(disciplineId, gradeId, trackId = null) {
80
+ cache.delete(buildJobKey(disciplineId, gradeId, trackId));
81
81
  }
82
82
 
83
83
  /**
84
84
  * Get the number of cached jobs (for testing/debugging)
85
85
  * @returns {number}
86
86
  */
87
- export function getCacheSize() {
87
+ export function getCachedJobCount() {
88
88
  return cache.size;
89
89
  }
@@ -24,7 +24,7 @@ import { loadAgentDataBrowser } from "../lib/yaml-loader.js";
24
24
  import {
25
25
  generateStageAgentProfile,
26
26
  deriveStageAgent,
27
- generateSkillMd,
27
+ generateSkillMarkdown,
28
28
  deriveAgentSkills,
29
29
  deriveReferenceGrade,
30
30
  deriveToolkit,
@@ -502,7 +502,7 @@ function createAllStagesPreview(context) {
502
502
  const skillFiles = derivedSkills
503
503
  .map((derived) => skills.find((s) => s.id === derived.skillId))
504
504
  .filter((skill) => skill?.agent)
505
- .map((skill) => generateSkillMd(skill, stages));
505
+ .map((skill) => generateSkillMarkdown(skill, stages));
506
506
 
507
507
  // Derive toolkit from agent skills
508
508
  const toolkit = deriveToolkit({
@@ -633,7 +633,7 @@ function createSingleStagePreview(context, stage) {
633
633
  const skillFiles = derivedSkills
634
634
  .map((d) => skills.find((s) => s.id === d.skillId))
635
635
  .filter((skill) => skill?.agent)
636
- .map((skill) => generateSkillMd(skill, stages));
636
+ .map((skill) => generateSkillMarkdown(skill, stages));
637
637
 
638
638
  // Derive toolkit from agent skills
639
639
  const toolkit = deriveToolkit({
@@ -45,7 +45,10 @@ export function renderLanding() {
45
45
  const stages = data.stages || [];
46
46
 
47
47
  // Calculate stats using centralized capability ordering
48
- const skillsByCapability = groupSkillsByCapability(data.skills);
48
+ const skillsByCapability = groupSkillsByCapability(
49
+ data.skills,
50
+ data.capabilities,
51
+ );
49
52
  const capabilityCount = Object.keys(skillsByCapability).length;
50
53
  const tools = aggregateTools(data.skills);
51
54
 
@@ -344,6 +344,7 @@ function createComparisonSelectorsSection({
344
344
  currentLabel: `Current (${currentGrade.id})`,
345
345
  targetLabel: `Target (${targetGrade.id})`,
346
346
  size: 400,
347
+ capabilities: data.capabilities,
347
348
  },
348
349
  ),
349
350
  createComparisonBehaviourRadar(
@@ -22,7 +22,7 @@ import {
22
22
  SKILL_LEVEL_ORDER,
23
23
  BEHAVIOUR_MATURITY_ORDER,
24
24
  groupSkillsByCapability,
25
- CAPABILITY_ORDER,
25
+ getCapabilityOrder,
26
26
  getCapabilityEmoji,
27
27
  getConceptEmoji,
28
28
  } from "@forwardimpact/schema/levels";
@@ -66,7 +66,10 @@ export function getAssessmentState() {
66
66
  */
67
67
  function getWizardSteps(data) {
68
68
  const { framework } = data;
69
- const skillsByCapability = groupSkillsByCapability(data.skills);
69
+ const skillsByCapability = groupSkillsByCapability(
70
+ data.skills,
71
+ data.capabilities,
72
+ );
70
73
  const steps = [
71
74
  {
72
75
  id: "intro",
@@ -77,7 +80,7 @@ function getWizardSteps(data) {
77
80
  ];
78
81
 
79
82
  // Add a step for each non-empty skill capability
80
- for (const capability of CAPABILITY_ORDER) {
83
+ for (const capability of getCapabilityOrder(data.capabilities)) {
81
84
  const skills = skillsByCapability[capability];
82
85
  if (skills && skills.length > 0) {
83
86
  steps.push({
@@ -277,7 +280,7 @@ function renderIntroStep(data) {
277
280
  div(
278
281
  {},
279
282
  h4({}, `${data.skills.length} Skills`),
280
- p({}, "Across " + CAPABILITY_ORDER.length + " capabilities"),
283
+ p({}, "Across " + data.capabilities.length + " capabilities"),
281
284
  ),
282
285
  ),
283
286
  div(
package/src/slides.html CHANGED
@@ -23,7 +23,9 @@
23
23
  "@forwardimpact/model/checklist": "/model/lib/checklist.js",
24
24
  "@forwardimpact/model/matching": "/model/lib/matching.js",
25
25
  "@forwardimpact/model/profile": "/model/lib/profile.js",
26
- "@forwardimpact/model/progression": "/model/lib/progression.js"
26
+ "@forwardimpact/model/progression": "/model/lib/progression.js",
27
+ "@forwardimpact/model/policies": "/model/lib/policies/index.js",
28
+ "@forwardimpact/model/toolkit": "/model/lib/toolkit.js"
27
29
  }
28
30
  }
29
31
  </script>
@@ -30,10 +30,15 @@ description: {{{description}}}{{#hasUseWhen}} Use When: {{{useWhen}}}{{/hasUseWh
30
30
  {{/hasStages}}
31
31
  {{#hasToolReferences}}
32
32
 
33
- # Required Tools
33
+ ## Required Tools
34
34
 
35
- Use these tools when applying this skill. Alternative tools require documented
36
- justification with acknowledged trade-offs.
35
+ **MANDATORY:** You MUST use these tools when applying this skill. These are
36
+ organizational standards that override general knowledge or personal preferences.
37
+
38
+ If a blocking constraint prevents use of a required tool, document in your
39
+ output: (1) which tool requirement you cannot meet, (2) the specific constraint
40
+ preventing compliance, and (3) the alternative approach with acknowledged
41
+ trade-offs.
37
42
 
38
43
  | Tool | Use When |
39
44
  | ---- | -------- |