@forwardimpact/pathway 0.1.0 → 0.2.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/app/commands/agent.js +109 -21
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +43 -29
- package/app/commands/progress.js +14 -7
- package/app/commands/serve.js +5 -0
- package/app/commands/stage.js +0 -10
- package/app/commands/track.js +5 -8
- package/app/components/builder.js +111 -27
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +113 -87
- package/app/formatters/agent/skill.js +64 -31
- package/app/formatters/behaviour/dom.js +3 -0
- package/app/formatters/behaviour/microdata.js +106 -0
- package/app/formatters/discipline/dom.js +28 -1
- package/app/formatters/discipline/microdata.js +117 -0
- package/app/formatters/discipline/shared.js +49 -8
- package/app/formatters/driver/dom.js +3 -0
- package/app/formatters/driver/microdata.js +91 -0
- package/app/formatters/grade/dom.js +3 -0
- package/app/formatters/grade/microdata.js +151 -0
- package/app/formatters/index.js +32 -1
- package/app/formatters/interview/shared.js +13 -8
- package/app/formatters/job/description.js +5 -3
- package/app/formatters/json-ld.js +242 -0
- package/app/formatters/microdata-shared.js +184 -0
- package/app/formatters/progress/shared.js +14 -11
- package/app/formatters/skill/dom.js +3 -0
- package/app/formatters/skill/microdata.js +151 -0
- package/app/formatters/stage/dom.js +3 -18
- package/app/formatters/stage/microdata.js +110 -0
- package/app/formatters/stage/shared.js +0 -27
- package/app/formatters/track/dom.js +5 -30
- package/app/formatters/track/markdown.js +2 -25
- package/app/formatters/track/microdata.js +111 -0
- package/app/formatters/track/shared.js +6 -58
- package/app/handout-main.js +26 -12
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/job-cache.js +12 -9
- package/app/lib/template-loader.js +66 -0
- package/app/lib/yaml-loader.js +25 -8
- package/app/main.js +8 -4
- package/app/model/agent.js +158 -130
- package/app/model/checklist.js +57 -91
- package/app/model/derivation.js +135 -68
- package/app/model/index-generator.js +1 -7
- package/app/model/job.js +19 -13
- package/app/model/levels.js +20 -12
- package/app/model/loader.js +41 -17
- package/app/model/matching.js +33 -3
- package/app/model/profile.js +38 -45
- package/app/model/schema-validation.js +438 -0
- package/app/model/validation.js +747 -68
- package/app/pages/agent-builder.js +119 -25
- package/app/pages/assessment-results.js +10 -4
- package/app/pages/discipline.js +36 -6
- package/app/pages/driver.js +9 -47
- package/app/pages/interview-builder.js +3 -1
- package/app/pages/interview.js +15 -4
- package/app/pages/job-builder.js +4 -1
- package/app/pages/job.js +15 -4
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +72 -21
- package/app/pages/stage.js +3 -126
- package/app/slide-main.js +45 -17
- package/app/slides/index.js +3 -1
- package/app/slides/overview.js +40 -4
- package/app/slides/progress.js +4 -2
- package/bin/pathway.js +18 -64
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
- package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
- package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
- package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
- package/examples/agents/.vscode/settings.json +1 -1
- package/examples/behaviours/outcome_ownership.yaml +1 -2
- package/examples/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/behaviours/precise_communication.yaml +1 -2
- package/examples/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/behaviours/systems_thinking.yaml +1 -2
- package/examples/capabilities/business.yaml +80 -142
- package/examples/capabilities/delivery.yaml +155 -219
- package/examples/capabilities/people.yaml +2 -34
- package/examples/capabilities/reliability.yaml +161 -80
- package/examples/capabilities/scale.yaml +234 -252
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +1 -0
- package/examples/disciplines/data_engineering.yaml +14 -12
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +14 -12
- package/examples/drivers.yaml +1 -4
- package/examples/framework.yaml +1 -2
- package/examples/grades.yaml +1 -3
- package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
- package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/questions/behaviours/precise_communication.yaml +1 -2
- package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/questions/behaviours/systems_thinking.yaml +1 -2
- package/examples/questions/skills/architecture_design.yaml +1 -2
- package/examples/questions/skills/cloud_platforms.yaml +1 -2
- package/examples/questions/skills/code_quality.yaml +1 -2
- package/examples/questions/skills/data_modeling.yaml +1 -2
- package/examples/questions/skills/devops.yaml +1 -2
- package/examples/questions/skills/full_stack_development.yaml +1 -2
- package/examples/questions/skills/sre_practices.yaml +1 -2
- package/examples/questions/skills/stakeholder_management.yaml +1 -2
- package/examples/questions/skills/team_collaboration.yaml +1 -2
- package/examples/questions/skills/technical_writing.yaml +1 -2
- package/examples/self-assessments.yaml +1 -3
- package/examples/stages.yaml +101 -46
- package/examples/tracks/_index.yaml +0 -1
- package/examples/tracks/platform.yaml +8 -13
- package/examples/tracks/sre.yaml +8 -18
- package/examples/vscode-settings.yaml +2 -7
- package/package.json +9 -3
- package/templates/agent.template.md +65 -0
- package/templates/skill.template.md +28 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
- package/examples/tracks/manager.yaml +0 -53
package/app/model/loader.js
CHANGED
|
@@ -121,6 +121,11 @@ async function loadDisciplinesFromDir(disciplinesDir) {
|
|
|
121
121
|
const {
|
|
122
122
|
specialization,
|
|
123
123
|
roleTitle,
|
|
124
|
+
// Track constraints
|
|
125
|
+
isProfessional,
|
|
126
|
+
isManagement,
|
|
127
|
+
validTracks,
|
|
128
|
+
minGrade,
|
|
124
129
|
// Shared content - now at root level
|
|
125
130
|
description,
|
|
126
131
|
// Structural properties (derivation inputs) - at top level
|
|
@@ -136,6 +141,11 @@ async function loadDisciplinesFromDir(disciplinesDir) {
|
|
|
136
141
|
id,
|
|
137
142
|
specialization,
|
|
138
143
|
roleTitle,
|
|
144
|
+
// Track constraints
|
|
145
|
+
isProfessional,
|
|
146
|
+
isManagement,
|
|
147
|
+
validTracks,
|
|
148
|
+
minGrade,
|
|
139
149
|
// Shared content at top level
|
|
140
150
|
description,
|
|
141
151
|
// Structural properties at top level
|
|
@@ -175,12 +185,10 @@ async function loadTracksFromDir(tracksDir) {
|
|
|
175
185
|
description,
|
|
176
186
|
roleContext,
|
|
177
187
|
// Structural properties (derivation inputs) - at top level
|
|
178
|
-
isProfessional,
|
|
179
|
-
isManagement,
|
|
180
188
|
skillModifiers,
|
|
181
189
|
behaviourModifiers,
|
|
182
190
|
assessmentWeights,
|
|
183
|
-
|
|
191
|
+
minGrade,
|
|
184
192
|
// Agent section (no human section anymore for tracks)
|
|
185
193
|
agent,
|
|
186
194
|
} = content;
|
|
@@ -191,12 +199,10 @@ async function loadTracksFromDir(tracksDir) {
|
|
|
191
199
|
description,
|
|
192
200
|
roleContext,
|
|
193
201
|
// Structural properties at top level
|
|
194
|
-
isProfessional,
|
|
195
|
-
isManagement,
|
|
196
202
|
skillModifiers,
|
|
197
203
|
behaviourModifiers,
|
|
198
204
|
assessmentWeights,
|
|
199
|
-
|
|
205
|
+
minGrade,
|
|
200
206
|
// Preserve agent section for agent generation
|
|
201
207
|
...(agent && { agent }),
|
|
202
208
|
};
|
|
@@ -486,7 +492,7 @@ export function loadAndValidate(data, options = {}) {
|
|
|
486
492
|
* Load agent-specific data for agent profile generation
|
|
487
493
|
* Uses co-located files: each entity file contains both human and agent sections
|
|
488
494
|
* @param {string} dataDir - Path to the data directory
|
|
489
|
-
* @returns {Promise<Object>} Agent data including disciplines, tracks, behaviours, vscodeSettings
|
|
495
|
+
* @returns {Promise<Object>} Agent data including disciplines, tracks, behaviours, vscodeSettings, devcontainer, copilotSetupSteps
|
|
490
496
|
*/
|
|
491
497
|
export async function loadAgentData(dataDir) {
|
|
492
498
|
const disciplinesDir = join(dataDir, "disciplines");
|
|
@@ -494,15 +500,27 @@ export async function loadAgentData(dataDir) {
|
|
|
494
500
|
const behavioursDir = join(dataDir, "behaviours");
|
|
495
501
|
|
|
496
502
|
// Load from co-located files
|
|
497
|
-
const [
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
503
|
+
const [
|
|
504
|
+
disciplineFiles,
|
|
505
|
+
trackFiles,
|
|
506
|
+
behaviourFiles,
|
|
507
|
+
vscodeSettings,
|
|
508
|
+
devcontainer,
|
|
509
|
+
copilotSetupSteps,
|
|
510
|
+
] = await Promise.all([
|
|
511
|
+
loadDisciplinesFromDir(disciplinesDir),
|
|
512
|
+
loadTracksFromDir(tracksDir),
|
|
513
|
+
loadBehavioursFromDir(behavioursDir),
|
|
514
|
+
fileExists(join(dataDir, "vscode-settings.yaml"))
|
|
515
|
+
? loadYamlFile(join(dataDir, "vscode-settings.yaml"))
|
|
516
|
+
: {},
|
|
517
|
+
fileExists(join(dataDir, "devcontainer.yaml"))
|
|
518
|
+
? loadYamlFile(join(dataDir, "devcontainer.yaml"))
|
|
519
|
+
: {},
|
|
520
|
+
fileExists(join(dataDir, "copilot-setup-steps.yaml"))
|
|
521
|
+
? loadYamlFile(join(dataDir, "copilot-setup-steps.yaml"))
|
|
522
|
+
: null,
|
|
523
|
+
]);
|
|
506
524
|
|
|
507
525
|
// Extract agent sections from co-located files
|
|
508
526
|
const disciplines = disciplineFiles
|
|
@@ -531,6 +549,8 @@ export async function loadAgentData(dataDir) {
|
|
|
531
549
|
tracks,
|
|
532
550
|
behaviours,
|
|
533
551
|
vscodeSettings,
|
|
552
|
+
devcontainer,
|
|
553
|
+
copilotSetupSteps,
|
|
534
554
|
};
|
|
535
555
|
}
|
|
536
556
|
|
|
@@ -551,11 +571,15 @@ export async function loadSkillsWithAgentData(dataDir) {
|
|
|
551
571
|
const allSkills = [];
|
|
552
572
|
|
|
553
573
|
for (const file of yamlFiles) {
|
|
574
|
+
const capabilityId = basename(file, ".yaml"); // Derive ID from filename
|
|
554
575
|
const capability = await loadYamlFile(join(capabilitiesDir, file));
|
|
555
576
|
|
|
556
577
|
if (capability.skills && Array.isArray(capability.skills)) {
|
|
557
578
|
for (const skill of capability.skills) {
|
|
558
|
-
allSkills.push(
|
|
579
|
+
allSkills.push({
|
|
580
|
+
...skill,
|
|
581
|
+
capability: capabilityId, // Add capability from parent filename
|
|
582
|
+
});
|
|
559
583
|
}
|
|
560
584
|
}
|
|
561
585
|
}
|
package/app/model/matching.js
CHANGED
|
@@ -275,9 +275,9 @@ function calculateExpectationsScore(selfExpectations, jobExpectations) {
|
|
|
275
275
|
* @returns {import('./levels.js').MatchAnalysis}
|
|
276
276
|
*/
|
|
277
277
|
export function calculateJobMatch(selfAssessment, job) {
|
|
278
|
-
// Get weights from track or use defaults
|
|
279
|
-
const skillWeight = job.track
|
|
280
|
-
const behaviourWeight = job.track
|
|
278
|
+
// Get weights from track or use defaults (track may be null for trackless jobs)
|
|
279
|
+
const skillWeight = job.track?.assessmentWeights?.skillWeight ?? 0.5;
|
|
280
|
+
const behaviourWeight = job.track?.assessmentWeights?.behaviourWeight ?? 0.5;
|
|
281
281
|
|
|
282
282
|
// Calculate skill score
|
|
283
283
|
const skillResult = calculateSkillScore(
|
|
@@ -362,6 +362,36 @@ export function findMatchingJobs({
|
|
|
362
362
|
|
|
363
363
|
// Generate all valid job combinations
|
|
364
364
|
for (const discipline of disciplines) {
|
|
365
|
+
// First generate trackless jobs for each discipline × grade
|
|
366
|
+
for (const grade of grades) {
|
|
367
|
+
if (
|
|
368
|
+
!isValidJobCombination({
|
|
369
|
+
discipline,
|
|
370
|
+
grade,
|
|
371
|
+
track: null,
|
|
372
|
+
validationRules,
|
|
373
|
+
grades,
|
|
374
|
+
})
|
|
375
|
+
) {
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const job = deriveJob({
|
|
380
|
+
discipline,
|
|
381
|
+
grade,
|
|
382
|
+
track: null,
|
|
383
|
+
skills,
|
|
384
|
+
behaviours,
|
|
385
|
+
validationRules,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
if (job) {
|
|
389
|
+
const analysis = calculateJobMatch(selfAssessment, job);
|
|
390
|
+
matches.push({ job, analysis });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Then generate jobs with valid tracks
|
|
365
395
|
for (const track of tracks) {
|
|
366
396
|
for (const grade of grades) {
|
|
367
397
|
// Skip invalid combinations
|
package/app/model/profile.js
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The core derivation (deriveSkillMatrix, deriveBehaviourProfile) remains in
|
|
12
12
|
* derivation.js. This module adds post-processing for specific use cases.
|
|
13
|
+
*
|
|
14
|
+
* Agent filtering keeps only skills at the highest derived level. This ensures
|
|
15
|
+
* track modifiers are respected—a broad skill boosted by a +1 track modifier
|
|
16
|
+
* may reach the same level as primary skills and thus be included.
|
|
13
17
|
*/
|
|
14
18
|
|
|
15
19
|
import { SKILL_LEVEL_ORDER, BEHAVIOUR_MATURITY_ORDER } from "./levels.js";
|
|
@@ -47,44 +51,40 @@ export function filterHumanOnlySkills(skillMatrix) {
|
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
/**
|
|
50
|
-
* Filter
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* @param {
|
|
54
|
-
* @returns {Array} Filtered skill matrix
|
|
54
|
+
* Filter skills to keep only those at the highest derived level
|
|
55
|
+
* After track modifiers are applied, some skills will be at higher levels
|
|
56
|
+
* than others. This filter keeps only the skills at the maximum level.
|
|
57
|
+
* @param {Array} skillMatrix - Skill matrix entries with derived levels
|
|
58
|
+
* @returns {Array} Filtered skill matrix containing only highest-level skills
|
|
55
59
|
*/
|
|
56
|
-
export function
|
|
57
|
-
skillMatrix
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
export function filterByHighestLevel(skillMatrix) {
|
|
61
|
+
if (skillMatrix.length === 0) return [];
|
|
62
|
+
|
|
63
|
+
// Find the highest level index in the matrix
|
|
64
|
+
const maxLevelIndex = Math.max(
|
|
65
|
+
...skillMatrix.map((entry) => SKILL_LEVEL_ORDER.indexOf(entry.level)),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Keep only skills at that level
|
|
60
69
|
return skillMatrix.filter(
|
|
61
|
-
(entry) =>
|
|
62
|
-
entry.type !== "broad" || positiveCapabilities.has(entry.capability),
|
|
70
|
+
(entry) => SKILL_LEVEL_ORDER.indexOf(entry.level) === maxLevelIndex,
|
|
63
71
|
);
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
/**
|
|
67
75
|
* Apply agent-specific skill filters
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
76
|
+
* Filters to human-only skills and keeps only skills at the highest derived level.
|
|
77
|
+
* This approach respects track modifiers—a broad skill boosted to the same level
|
|
78
|
+
* as primary skills will be included.
|
|
79
|
+
* @param {Array} skillMatrix - Skill matrix entries with derived levels
|
|
71
80
|
* @returns {Array} Filtered skill matrix
|
|
72
81
|
*/
|
|
73
|
-
export function filterSkillsForAgent(skillMatrix
|
|
74
|
-
|
|
82
|
+
export function filterSkillsForAgent(skillMatrix) {
|
|
83
|
+
// First exclude human-only skills
|
|
84
|
+
const withoutHumanOnly = filterHumanOnlySkills(skillMatrix);
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (entry.isHumanOnly) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
// For broad skills, only include if capability has positive modifier
|
|
82
|
-
if (entry.type === "broad") {
|
|
83
|
-
return positiveCapabilities.has(entry.capability);
|
|
84
|
-
}
|
|
85
|
-
// Include primary, secondary, and track-added skills
|
|
86
|
-
return true;
|
|
87
|
-
});
|
|
86
|
+
// Then keep only skills at the highest level
|
|
87
|
+
return filterByHighestLevel(withoutHumanOnly);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// =============================================================================
|
|
@@ -126,7 +126,7 @@ export function sortByMaturityDescending(behaviourProfile) {
|
|
|
126
126
|
/**
|
|
127
127
|
* @typedef {Object} ProfileOptions
|
|
128
128
|
* @property {boolean} [excludeHumanOnly=false] - Filter out human-only skills
|
|
129
|
-
* @property {boolean} [
|
|
129
|
+
* @property {boolean} [keepHighestLevelOnly=false] - Keep only skills at the highest derived level
|
|
130
130
|
* @property {boolean} [sortByLevel=false] - Sort skills by level descending
|
|
131
131
|
* @property {boolean} [sortByMaturity=false] - Sort behaviours by maturity descending
|
|
132
132
|
*/
|
|
@@ -148,7 +148,7 @@ export function sortByMaturityDescending(behaviourProfile) {
|
|
|
148
148
|
* and AI agents use this function, with different options:
|
|
149
149
|
*
|
|
150
150
|
* - Human jobs: No filtering, default sorting by type
|
|
151
|
-
* - AI agents: Filter humanOnly,
|
|
151
|
+
* - AI agents: Filter humanOnly, keep only highest-level skills, sort by level
|
|
152
152
|
*
|
|
153
153
|
* @param {Object} params
|
|
154
154
|
* @param {Object} params.discipline - The discipline
|
|
@@ -171,7 +171,7 @@ export function prepareBaseProfile({
|
|
|
171
171
|
}) {
|
|
172
172
|
const {
|
|
173
173
|
excludeHumanOnly = false,
|
|
174
|
-
|
|
174
|
+
keepHighestLevelOnly = false,
|
|
175
175
|
sortByLevel = false,
|
|
176
176
|
sortByMaturity = false,
|
|
177
177
|
} = options;
|
|
@@ -186,18 +186,11 @@ export function prepareBaseProfile({
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
// Apply skill filters
|
|
189
|
-
if (excludeHumanOnly
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
if (excludeBroadWithoutPositiveModifier) {
|
|
196
|
-
skillMatrix = filterBroadWithoutPositiveModifier(
|
|
197
|
-
skillMatrix,
|
|
198
|
-
positiveCapabilities,
|
|
199
|
-
);
|
|
200
|
-
}
|
|
189
|
+
if (excludeHumanOnly) {
|
|
190
|
+
skillMatrix = filterHumanOnlySkills(skillMatrix);
|
|
191
|
+
}
|
|
192
|
+
if (keepHighestLevelOnly) {
|
|
193
|
+
skillMatrix = filterByHighestLevel(skillMatrix);
|
|
201
194
|
}
|
|
202
195
|
|
|
203
196
|
// Apply sorting
|
|
@@ -230,12 +223,12 @@ export function prepareBaseProfile({
|
|
|
230
223
|
|
|
231
224
|
/**
|
|
232
225
|
* Preset options for agent profile derivation
|
|
233
|
-
* Excludes human-only skills,
|
|
226
|
+
* Excludes human-only skills, keeps only skills at the highest derived level,
|
|
234
227
|
* and sorts by level/maturity descending
|
|
235
228
|
*/
|
|
236
229
|
export const AGENT_PROFILE_OPTIONS = {
|
|
237
230
|
excludeHumanOnly: true,
|
|
238
|
-
|
|
231
|
+
keepHighestLevelOnly: true,
|
|
239
232
|
sortByLevel: true,
|
|
240
233
|
sortByMaturity: true,
|
|
241
234
|
};
|