@forwardimpact/pathway 0.21.0 → 0.22.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/README.md +3 -3
- package/bin/fit-pathway.js +22 -22
- package/package.json +3 -3
- package/src/commands/agent.js +7 -7
- package/src/commands/index.js +1 -1
- package/src/commands/init.js +1 -1
- package/src/commands/interview.js +8 -8
- package/src/commands/job.js +19 -19
- package/src/commands/level.js +60 -0
- package/src/commands/progress.js +20 -20
- package/src/commands/questions.js +3 -3
- package/src/components/action-buttons.js +3 -3
- package/src/components/builder.js +25 -25
- package/src/components/comparison-radar.js +3 -3
- package/src/components/detail.js +2 -2
- package/src/components/grid.js +1 -1
- package/src/components/radar-chart.js +3 -3
- package/src/components/skill-matrix.js +7 -7
- package/src/css/pages/landing.css +5 -5
- package/src/formatters/index.js +5 -5
- package/src/formatters/interview/shared.js +20 -20
- package/src/formatters/job/description.js +17 -17
- package/src/formatters/job/dom.js +12 -12
- package/src/formatters/job/markdown.js +7 -7
- package/src/formatters/json-ld.js +24 -24
- package/src/formatters/{grade → level}/dom.js +31 -27
- package/src/formatters/{grade → level}/markdown.js +19 -28
- package/src/formatters/{grade → level}/microdata.js +28 -38
- package/src/formatters/level/shared.js +86 -0
- package/src/formatters/progress/markdown.js +2 -2
- package/src/formatters/progress/shared.js +48 -48
- package/src/formatters/questions/markdown.js +8 -6
- package/src/formatters/questions/shared.js +7 -7
- package/src/formatters/skill/dom.js +4 -4
- package/src/formatters/skill/markdown.js +1 -1
- package/src/formatters/skill/microdata.js +3 -3
- package/src/formatters/skill/shared.js +2 -2
- package/src/handout-main.js +12 -12
- package/src/index.html +1 -1
- package/src/lib/card-mappers.js +16 -16
- package/src/lib/cli-command.js +3 -3
- package/src/lib/cli-output.js +2 -2
- package/src/lib/job-cache.js +11 -11
- package/src/lib/render.js +5 -5
- package/src/lib/state.js +2 -2
- package/src/lib/yaml-loader.js +9 -9
- package/src/main.js +10 -10
- package/src/pages/agent-builder.js +11 -11
- package/src/pages/assessment-results.js +27 -23
- package/src/pages/interview-builder.js +6 -6
- package/src/pages/interview.js +8 -8
- package/src/pages/job-builder.js +6 -6
- package/src/pages/job.js +7 -7
- package/src/pages/landing.js +8 -8
- package/src/pages/level.js +122 -0
- package/src/pages/progress-builder.js +8 -8
- package/src/pages/progress.js +74 -74
- package/src/pages/self-assessment.js +7 -7
- package/src/slide-main.js +22 -22
- package/src/slides/chapter.js +4 -4
- package/src/slides/index.js +10 -10
- package/src/slides/interview.js +2 -2
- package/src/slides/job.js +3 -3
- package/src/slides/level.js +32 -0
- package/src/slides/overview.js +9 -9
- package/src/slides/progress.js +13 -13
- package/src/types.js +1 -1
- package/templates/job.template.md +2 -2
- package/src/commands/grade.js +0 -60
- package/src/formatters/grade/shared.js +0 -86
- package/src/pages/grade.js +0 -122
- package/src/slides/grade.js +0 -32
package/README.md
CHANGED
|
@@ -52,9 +52,9 @@ Use `--help` with any command for full options.
|
|
|
52
52
|
|
|
53
53
|
## Web App Features
|
|
54
54
|
|
|
55
|
-
- **Job Builder** — Select discipline, track, and
|
|
56
|
-
- **Skill Browser** — View all skills with
|
|
57
|
-
- **Career Progression** — Compare
|
|
55
|
+
- **Job Builder** — Select discipline, track, and level to explore roles
|
|
56
|
+
- **Skill Browser** — View all skills with proficiency descriptions
|
|
57
|
+
- **Career Progression** — Compare levels and identify growth areas
|
|
58
58
|
- **Interview Prep** — Generate role-specific question sets
|
|
59
59
|
- **Agent Preview** — Preview generated agent profiles
|
|
60
60
|
|
package/bin/fit-pathway.js
CHANGED
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Commands:
|
|
12
12
|
* discipline [<id>] Show disciplines
|
|
13
|
-
*
|
|
13
|
+
* level [<id>] Show levels
|
|
14
14
|
* track [<id>] Show tracks
|
|
15
15
|
* behaviour [<id>] Show behaviours
|
|
16
16
|
* skill [<id>] Show skills (summary, --list, or detail)
|
|
17
17
|
* driver [<id>] Show drivers
|
|
18
18
|
* stage [<id>] Show stages
|
|
19
19
|
* tool [<name>] Show tools
|
|
20
|
-
* job [<discipline> <
|
|
21
|
-
* interview <discipline> <
|
|
22
|
-
* progress <discipline> <
|
|
20
|
+
* job [<discipline> <level>] [--track=TRACK] Generate job definition
|
|
21
|
+
* interview <discipline> <level> [--track=TRACK] [--type=mission|decomposition|stakeholder] Generate interview
|
|
22
|
+
* progress <discipline> <level> [--track=TRACK] [--compare=LEVEL] Career progression
|
|
23
23
|
* questions [options] Browse interview questions
|
|
24
24
|
* agent [<discipline> <track>] [--output=PATH] Generate AI agent
|
|
25
25
|
*
|
|
@@ -37,7 +37,7 @@ import { formatError } from "../src/lib/cli-output.js";
|
|
|
37
37
|
|
|
38
38
|
// Import command handlers
|
|
39
39
|
import { runDisciplineCommand } from "../src/commands/discipline.js";
|
|
40
|
-
import {
|
|
40
|
+
import { runLevelCommand } from "../src/commands/level.js";
|
|
41
41
|
import { runTrackCommand } from "../src/commands/track.js";
|
|
42
42
|
import { runBehaviourCommand } from "../src/commands/behaviour.js";
|
|
43
43
|
import { runSkillCommand } from "../src/commands/skill.js";
|
|
@@ -56,7 +56,7 @@ import { runUpdateCommand } from "../src/commands/update.js";
|
|
|
56
56
|
|
|
57
57
|
const COMMANDS = {
|
|
58
58
|
discipline: runDisciplineCommand,
|
|
59
|
-
|
|
59
|
+
level: runLevelCommand,
|
|
60
60
|
track: runTrackCommand,
|
|
61
61
|
behaviour: runBehaviourCommand,
|
|
62
62
|
skill: runSkillCommand,
|
|
@@ -98,7 +98,7 @@ ENTITY COMMANDS
|
|
|
98
98
|
All entity commands support: summary (default), --list (IDs for piping), <id> (detail)
|
|
99
99
|
|
|
100
100
|
discipline [<id>] Browse engineering disciplines
|
|
101
|
-
|
|
101
|
+
level [<id>] Browse career levels
|
|
102
102
|
track [<id>] Browse track specializations
|
|
103
103
|
behaviour [<id>] Browse professional behaviours
|
|
104
104
|
driver [<id>] Browse outcome drivers
|
|
@@ -113,16 +113,16 @@ All entity commands support: summary (default), --list (IDs for piping), <id> (d
|
|
|
113
113
|
JOB COMMAND
|
|
114
114
|
────────────────────────────────────────────────────────────────────────────────
|
|
115
115
|
|
|
116
|
-
Generate job definitions from discipline ×
|
|
116
|
+
Generate job definitions from discipline × level × track combinations.
|
|
117
117
|
|
|
118
118
|
Usage:
|
|
119
119
|
npx fit-pathway job Summary with stats
|
|
120
120
|
npx fit-pathway job --list All valid combinations
|
|
121
|
-
npx fit-pathway job <discipline> <
|
|
122
|
-
npx fit-pathway job <d> <
|
|
123
|
-
npx fit-pathway job <d> <
|
|
124
|
-
npx fit-pathway job <d> <
|
|
125
|
-
npx fit-pathway job <d> <
|
|
121
|
+
npx fit-pathway job <discipline> <level> Detail view (trackless)
|
|
122
|
+
npx fit-pathway job <d> <l> --track=<track> Detail view (with track)
|
|
123
|
+
npx fit-pathway job <d> <l> --skills Plain list of skill IDs
|
|
124
|
+
npx fit-pathway job <d> <l> --tools Plain list of tool names
|
|
125
|
+
npx fit-pathway job <d> <l> --checklist=<stage> Show handoff checklist
|
|
126
126
|
|
|
127
127
|
Options:
|
|
128
128
|
--track=TRACK Track specialization (e.g., platform, forward_deployed)
|
|
@@ -170,9 +170,9 @@ INTERVIEW COMMAND
|
|
|
170
170
|
Generate interview question sets based on job requirements.
|
|
171
171
|
|
|
172
172
|
Usage:
|
|
173
|
-
npx fit-pathway interview <discipline> <
|
|
174
|
-
npx fit-pathway interview <d> <
|
|
175
|
-
npx fit-pathway interview <d> <
|
|
173
|
+
npx fit-pathway interview <discipline> <level> All types
|
|
174
|
+
npx fit-pathway interview <d> <l> --track=<track> With track
|
|
175
|
+
npx fit-pathway interview <d> <l> --track=<t> --type=<type> Single type
|
|
176
176
|
|
|
177
177
|
Options:
|
|
178
178
|
--track=TRACK Track specialization
|
|
@@ -183,16 +183,16 @@ Options:
|
|
|
183
183
|
PROGRESS COMMAND
|
|
184
184
|
────────────────────────────────────────────────────────────────────────────────
|
|
185
185
|
|
|
186
|
-
Analyze career progression between
|
|
186
|
+
Analyze career progression between levels.
|
|
187
187
|
|
|
188
188
|
Usage:
|
|
189
|
-
npx fit-pathway progress <discipline> <
|
|
190
|
-
npx fit-pathway progress <d> <
|
|
191
|
-
npx fit-pathway progress <d> <
|
|
189
|
+
npx fit-pathway progress <discipline> <level>
|
|
190
|
+
npx fit-pathway progress <d> <l> --track=<track>
|
|
191
|
+
npx fit-pathway progress <d> <l> --compare=<to_level>
|
|
192
192
|
|
|
193
193
|
Options:
|
|
194
194
|
--track=TRACK Track specialization
|
|
195
|
-
--compare=
|
|
195
|
+
--compare=LEVEL Compare to specific level
|
|
196
196
|
|
|
197
197
|
────────────────────────────────────────────────────────────────────────────────
|
|
198
198
|
QUESTIONS COMMAND
|
|
@@ -207,7 +207,7 @@ Usage:
|
|
|
207
207
|
npx fit-pathway questions --stats
|
|
208
208
|
|
|
209
209
|
Options:
|
|
210
|
-
--level=LEVEL Filter by skill
|
|
210
|
+
--level=LEVEL Filter by skill proficiency
|
|
211
211
|
--maturity=MATURITY Filter by behaviour maturity
|
|
212
212
|
--skill=ID Filter to specific skill
|
|
213
213
|
--behaviour=ID Filter to specific behaviour
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/pathway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Career progression web app and CLI for exploring roles and generating agent teams",
|
|
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/map": "^0.
|
|
44
|
-
"@forwardimpact/libpathway": "^
|
|
43
|
+
"@forwardimpact/map": "^0.11.0",
|
|
44
|
+
"@forwardimpact/libpathway": "^3.0.0",
|
|
45
45
|
"mustache": "^4.2.0",
|
|
46
46
|
"simple-icons": "^16.7.0",
|
|
47
47
|
"yaml": "^2.3.4"
|
package/src/commands/agent.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
generateStageAgentProfile,
|
|
36
36
|
validateAgentProfile,
|
|
37
37
|
validateAgentSkill,
|
|
38
|
-
|
|
38
|
+
deriveReferenceLevel,
|
|
39
39
|
deriveAgentSkills,
|
|
40
40
|
generateSkillMarkdown,
|
|
41
41
|
deriveToolkit,
|
|
@@ -414,15 +414,15 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
414
414
|
process.exit(1);
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
// Get reference
|
|
418
|
-
const
|
|
417
|
+
// Get reference level for derivation
|
|
418
|
+
const level = deriveReferenceLevel(data.levels);
|
|
419
419
|
|
|
420
420
|
// --skills: Output plain list of skill IDs (for piping)
|
|
421
421
|
if (options.skills) {
|
|
422
422
|
const derivedSkills = deriveAgentSkills({
|
|
423
423
|
discipline: humanDiscipline,
|
|
424
424
|
track: humanTrack,
|
|
425
|
-
|
|
425
|
+
level,
|
|
426
426
|
skills: skillsWithAgent,
|
|
427
427
|
});
|
|
428
428
|
for (const skill of derivedSkills) {
|
|
@@ -436,7 +436,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
436
436
|
const derivedSkills = deriveAgentSkills({
|
|
437
437
|
discipline: humanDiscipline,
|
|
438
438
|
track: humanTrack,
|
|
439
|
-
|
|
439
|
+
level,
|
|
440
440
|
skills: skillsWithAgent,
|
|
441
441
|
});
|
|
442
442
|
const toolkit = deriveToolkit({
|
|
@@ -462,7 +462,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
462
462
|
const stageParams = {
|
|
463
463
|
discipline: humanDiscipline,
|
|
464
464
|
track: humanTrack,
|
|
465
|
-
|
|
465
|
+
level,
|
|
466
466
|
skills: skillsWithAgent,
|
|
467
467
|
behaviours: data.behaviours,
|
|
468
468
|
agentBehaviours: agentData.behaviours,
|
|
@@ -534,7 +534,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
534
534
|
const derivedSkills = deriveAgentSkills({
|
|
535
535
|
discipline: humanDiscipline,
|
|
536
536
|
track: humanTrack,
|
|
537
|
-
|
|
537
|
+
level,
|
|
538
538
|
skills: skillsWithAgent,
|
|
539
539
|
});
|
|
540
540
|
|
package/src/commands/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export { runDisciplineCommand } from "./discipline.js";
|
|
8
|
-
export {
|
|
8
|
+
export { runLevelCommand } from "./level.js";
|
|
9
9
|
export { runTrackCommand } from "./track.js";
|
|
10
10
|
export { runBehaviourCommand } from "./behaviour.js";
|
|
11
11
|
export { runSkillCommand } from "./skill.js";
|
package/src/commands/init.js
CHANGED
|
@@ -54,7 +54,7 @@ Next steps:
|
|
|
54
54
|
Data structure:
|
|
55
55
|
data/
|
|
56
56
|
├── framework.yaml # Framework metadata
|
|
57
|
-
├──
|
|
57
|
+
├── levels.yaml # Career levels
|
|
58
58
|
├── stages.yaml # Lifecycle stages
|
|
59
59
|
├── drivers.yaml # Business drivers
|
|
60
60
|
├── capabilities.yaml # Capability areas
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Generates and displays interview questions in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npx fit-pathway interview <discipline> <
|
|
8
|
-
* npx fit-pathway interview <discipline> <
|
|
9
|
-
* npx fit-pathway interview <discipline> <
|
|
7
|
+
* npx fit-pathway interview <discipline> <level> # All interview types
|
|
8
|
+
* npx fit-pathway interview <discipline> <level> --track=<track> # With track
|
|
9
|
+
* npx fit-pathway interview <discipline> <level> --track=<track> --type=mission # Single type
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createCompositeCommand } from "./command-factory.js";
|
|
@@ -45,7 +45,7 @@ function formatAllInterviews(views, options) {
|
|
|
45
45
|
|
|
46
46
|
export const runInterviewCommand = createCompositeCommand({
|
|
47
47
|
commandName: "interview",
|
|
48
|
-
requiredArgs: ["discipline_id", "
|
|
48
|
+
requiredArgs: ["discipline_id", "level_id"],
|
|
49
49
|
findEntities: (data, args, options) => {
|
|
50
50
|
const interviewType = options.type === "full" ? null : options.type;
|
|
51
51
|
|
|
@@ -57,7 +57,7 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
57
57
|
|
|
58
58
|
return {
|
|
59
59
|
discipline: data.disciplines.find((d) => d.id === args[0]),
|
|
60
|
-
|
|
60
|
+
level: data.levels.find((g) => g.id === args[1]),
|
|
61
61
|
track: options.track
|
|
62
62
|
? data.tracks.find((t) => t.id === options.track)
|
|
63
63
|
: null,
|
|
@@ -68,8 +68,8 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
68
68
|
if (!entities.discipline) {
|
|
69
69
|
return `Discipline not found: ${entities.discipline}`;
|
|
70
70
|
}
|
|
71
|
-
if (!entities.
|
|
72
|
-
return `
|
|
71
|
+
if (!entities.level) {
|
|
72
|
+
return `Level not found: ${entities.level}`;
|
|
73
73
|
}
|
|
74
74
|
if (options.track && !entities.track) {
|
|
75
75
|
return `Track not found: ${options.track}`;
|
|
@@ -79,7 +79,7 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
79
79
|
presenter: (entities, data, _options) => {
|
|
80
80
|
const params = {
|
|
81
81
|
discipline: entities.discipline,
|
|
82
|
-
|
|
82
|
+
level: entities.level,
|
|
83
83
|
track: entities.track,
|
|
84
84
|
skills: data.skills,
|
|
85
85
|
behaviours: data.behaviours,
|
package/src/commands/job.js
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* Usage:
|
|
7
7
|
* npx pathway job # Summary with stats
|
|
8
8
|
* npx pathway job --list # All valid combinations (for piping)
|
|
9
|
-
* npx pathway job <discipline> <
|
|
10
|
-
* npx pathway job <discipline> <
|
|
11
|
-
* npx pathway job <d> <
|
|
12
|
-
* npx pathway job <d> <
|
|
9
|
+
* npx pathway job <discipline> <level> # Detail view (trackless)
|
|
10
|
+
* npx pathway job <discipline> <level> --track=<track> # Detail view (with track)
|
|
11
|
+
* npx pathway job <d> <l> [--track=<t>] --skills # Plain list of skill IDs
|
|
12
|
+
* npx pathway job <d> <l> [--track=<t>] --tools # Plain list of tool names
|
|
13
13
|
* npx pathway job se L3 --track=platform --checklist=code # Show checklist for handoff
|
|
14
14
|
* npx pathway job --validate # Validation checks
|
|
15
15
|
*/
|
|
@@ -47,20 +47,20 @@ function formatJob(view, _options, entities, jobTemplate) {
|
|
|
47
47
|
export async function runJobCommand({ data, args, options, dataDir }) {
|
|
48
48
|
const jobs = generateAllJobs({
|
|
49
49
|
disciplines: data.disciplines,
|
|
50
|
-
|
|
50
|
+
levels: data.levels,
|
|
51
51
|
tracks: data.tracks,
|
|
52
52
|
skills: data.skills,
|
|
53
53
|
behaviours: data.behaviours,
|
|
54
54
|
validationRules: data.framework.validationRules,
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
// --list: Output clean lines for piping (discipline
|
|
57
|
+
// --list: Output clean lines for piping (discipline level track format)
|
|
58
58
|
if (options.list) {
|
|
59
59
|
for (const job of jobs) {
|
|
60
60
|
if (job.track) {
|
|
61
|
-
console.log(`${job.discipline.id} ${job.
|
|
61
|
+
console.log(`${job.discipline.id} ${job.level.id} ${job.track.id}`);
|
|
62
62
|
} else {
|
|
63
|
-
console.log(`${job.discipline.id} ${job.
|
|
63
|
+
console.log(`${job.discipline.id} ${job.level.id}`);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
return;
|
|
@@ -82,15 +82,15 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
82
82
|
console.log(`\nTotal: ${jobs.length} valid job combinations`);
|
|
83
83
|
console.log(`\nRun 'npx pathway job --list' for all combinations`);
|
|
84
84
|
console.log(
|
|
85
|
-
`Run 'npx pathway job <discipline> <
|
|
85
|
+
`Run 'npx pathway job <discipline> <level> [--track=<track>]' for details\n`,
|
|
86
86
|
);
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Handle job detail view - requires discipline and
|
|
90
|
+
// Handle job detail view - requires discipline and level
|
|
91
91
|
if (args.length < 2) {
|
|
92
92
|
console.error(
|
|
93
|
-
"Usage: npx pathway job <discipline> <
|
|
93
|
+
"Usage: npx pathway job <discipline> <level> [--track=<track>]",
|
|
94
94
|
);
|
|
95
95
|
console.error(" npx pathway job --list");
|
|
96
96
|
console.error("Example: npx pathway job software_engineering L4");
|
|
@@ -101,7 +101,7 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
const discipline = data.disciplines.find((d) => d.id === args[0]);
|
|
104
|
-
const
|
|
104
|
+
const level = data.levels.find((g) => g.id === args[1]);
|
|
105
105
|
const track = options.track
|
|
106
106
|
? data.tracks.find((t) => t.id === options.track)
|
|
107
107
|
: null;
|
|
@@ -112,9 +112,9 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
112
112
|
process.exit(1);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
if (!
|
|
116
|
-
console.error(`
|
|
117
|
-
console.error(`Available: ${data.
|
|
115
|
+
if (!level) {
|
|
116
|
+
console.error(`Level not found: ${args[1]}`);
|
|
117
|
+
console.error(`Available: ${data.levels.map((g) => g.id).join(", ")}`);
|
|
118
118
|
process.exit(1);
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -126,7 +126,7 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
126
126
|
|
|
127
127
|
const view = prepareJobDetail({
|
|
128
128
|
discipline,
|
|
129
|
-
|
|
129
|
+
level,
|
|
130
130
|
track,
|
|
131
131
|
skills: data.skills,
|
|
132
132
|
behaviours: data.behaviours,
|
|
@@ -137,8 +137,8 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
137
137
|
|
|
138
138
|
if (!view) {
|
|
139
139
|
const combo = track
|
|
140
|
-
? `${discipline.id} × ${
|
|
141
|
-
: `${discipline.id} × ${
|
|
140
|
+
? `${discipline.id} × ${level.id} × ${track.id}`
|
|
141
|
+
: `${discipline.id} × ${level.id}`;
|
|
142
142
|
console.error(`Invalid combination: ${combo}`);
|
|
143
143
|
if (track) {
|
|
144
144
|
const validTracks =
|
|
@@ -212,5 +212,5 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
212
212
|
|
|
213
213
|
// Load job template for description formatting
|
|
214
214
|
const jobTemplate = await loadJobTemplate(dataDir);
|
|
215
|
-
formatJob(view, options, { discipline,
|
|
215
|
+
formatJob(view, options, { discipline, level, track }, jobTemplate);
|
|
216
216
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level CLI Command
|
|
3
|
+
*
|
|
4
|
+
* Handles level summary, listing, and detail display in the terminal.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx pathway level # Summary with stats
|
|
8
|
+
* npx pathway level --list # IDs only (for piping)
|
|
9
|
+
* npx pathway level <id> # Detail view
|
|
10
|
+
* npx pathway level --validate # Validation checks
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createEntityCommand } from "./command-factory.js";
|
|
14
|
+
import { levelToMarkdown } from "../formatters/level/markdown.js";
|
|
15
|
+
import { formatTable } from "../lib/cli-output.js";
|
|
16
|
+
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
17
|
+
import { capitalize } from "../formatters/shared.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Format level summary output
|
|
21
|
+
* @param {Array} levels - Raw level entities
|
|
22
|
+
* @param {Object} data - Full data context
|
|
23
|
+
*/
|
|
24
|
+
function formatSummary(levels, data) {
|
|
25
|
+
const { framework } = data;
|
|
26
|
+
const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
|
|
27
|
+
|
|
28
|
+
console.log(`\n${emoji} Levels\n`);
|
|
29
|
+
|
|
30
|
+
const rows = levels.map((g) => [
|
|
31
|
+
g.id,
|
|
32
|
+
g.displayName || g.id,
|
|
33
|
+
g.typicalExperienceRange || "-",
|
|
34
|
+
capitalize(g.baseSkillProficiencies?.primary || "-"),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
console.log(formatTable(["ID", "Name", "Experience", "Primary Level"], rows));
|
|
38
|
+
console.log(`\nTotal: ${levels.length} levels`);
|
|
39
|
+
console.log(`\nRun 'npx pathway level --list' for IDs`);
|
|
40
|
+
console.log(`Run 'npx pathway level <id>' for details\n`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Format level detail output
|
|
45
|
+
* @param {Object} level - Raw level entity
|
|
46
|
+
* @param {Object} framework - Framework config
|
|
47
|
+
*/
|
|
48
|
+
function formatDetail(level, framework) {
|
|
49
|
+
console.log(levelToMarkdown(level, framework));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const runLevelCommand = createEntityCommand({
|
|
53
|
+
entityName: "level",
|
|
54
|
+
pluralName: "levels",
|
|
55
|
+
findEntity: (data, id) => data.levels.find((g) => g.id === id),
|
|
56
|
+
presentDetail: (entity) => entity,
|
|
57
|
+
formatSummary,
|
|
58
|
+
formatDetail,
|
|
59
|
+
emojiIcon: "📊",
|
|
60
|
+
});
|
package/src/commands/progress.js
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
* Shows career progression analysis in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npx pathway progress <discipline> <
|
|
8
|
-
* npx pathway progress <discipline> <
|
|
9
|
-
* npx pathway progress <discipline> <
|
|
7
|
+
* npx pathway progress <discipline> <level> # Progress for trackless job
|
|
8
|
+
* npx pathway progress <discipline> <level> --track=<track> # Progress with track
|
|
9
|
+
* npx pathway progress <discipline> <from_level> --compare=<to_level> # Compare levels
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createCompositeCommand } from "./command-factory.js";
|
|
13
13
|
import {
|
|
14
14
|
prepareProgressDetail,
|
|
15
|
-
|
|
15
|
+
getDefaultTargetLevel,
|
|
16
16
|
} from "../formatters/progress/shared.js";
|
|
17
17
|
import { progressToMarkdown } from "../formatters/progress/markdown.js";
|
|
18
18
|
|
|
@@ -26,53 +26,53 @@ function formatProgress(view) {
|
|
|
26
26
|
|
|
27
27
|
export const runProgressCommand = createCompositeCommand({
|
|
28
28
|
commandName: "progress",
|
|
29
|
-
requiredArgs: ["discipline_id", "
|
|
29
|
+
requiredArgs: ["discipline_id", "level_id"],
|
|
30
30
|
findEntities: (data, args, options) => {
|
|
31
31
|
const discipline = data.disciplines.find((d) => d.id === args[0]);
|
|
32
|
-
const
|
|
32
|
+
const level = data.levels.find((g) => g.id === args[1]);
|
|
33
33
|
const track = options.track
|
|
34
34
|
? data.tracks.find((t) => t.id === options.track)
|
|
35
35
|
: null;
|
|
36
36
|
|
|
37
|
-
let
|
|
37
|
+
let targetLevel;
|
|
38
38
|
if (options.compare) {
|
|
39
|
-
|
|
40
|
-
if (!
|
|
41
|
-
console.error(`Target
|
|
39
|
+
targetLevel = data.levels.find((g) => g.id === options.compare);
|
|
40
|
+
if (!targetLevel) {
|
|
41
|
+
console.error(`Target level not found: ${options.compare}`);
|
|
42
42
|
process.exit(1);
|
|
43
43
|
}
|
|
44
44
|
} else {
|
|
45
|
-
|
|
46
|
-
if (!
|
|
47
|
-
console.error("No next
|
|
45
|
+
targetLevel = getDefaultTargetLevel(level, data.levels);
|
|
46
|
+
if (!targetLevel) {
|
|
47
|
+
console.error("No next level available for progression.");
|
|
48
48
|
process.exit(1);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
return { discipline,
|
|
52
|
+
return { discipline, level, track, targetLevel };
|
|
53
53
|
},
|
|
54
54
|
validateEntities: (entities, _data, options) => {
|
|
55
55
|
if (!entities.discipline) {
|
|
56
56
|
return `Discipline not found`;
|
|
57
57
|
}
|
|
58
|
-
if (!entities.
|
|
59
|
-
return `
|
|
58
|
+
if (!entities.level) {
|
|
59
|
+
return `Level not found`;
|
|
60
60
|
}
|
|
61
61
|
if (options.track && !entities.track) {
|
|
62
62
|
return `Track not found: ${options.track}`;
|
|
63
63
|
}
|
|
64
|
-
if (!entities.
|
|
65
|
-
return `Target
|
|
64
|
+
if (!entities.targetLevel) {
|
|
65
|
+
return `Target level not found`;
|
|
66
66
|
}
|
|
67
67
|
return null;
|
|
68
68
|
},
|
|
69
69
|
presenter: (entities, data) =>
|
|
70
70
|
prepareProgressDetail({
|
|
71
71
|
fromDiscipline: entities.discipline,
|
|
72
|
-
|
|
72
|
+
fromLevel: entities.level,
|
|
73
73
|
fromTrack: entities.track,
|
|
74
74
|
toDiscipline: entities.discipline,
|
|
75
|
-
|
|
75
|
+
toLevel: entities.targetLevel,
|
|
76
76
|
toTrack: entities.track,
|
|
77
77
|
skills: data.skills,
|
|
78
78
|
behaviours: data.behaviours,
|
|
@@ -49,7 +49,7 @@ function showQuestionsSummary(data) {
|
|
|
49
49
|
console.log(`\n❓ Questions\n`);
|
|
50
50
|
|
|
51
51
|
// Skill questions by level
|
|
52
|
-
const
|
|
52
|
+
const skillProficiencies = [
|
|
53
53
|
"awareness",
|
|
54
54
|
"foundational",
|
|
55
55
|
"working",
|
|
@@ -57,10 +57,10 @@ function showQuestionsSummary(data) {
|
|
|
57
57
|
"expert",
|
|
58
58
|
];
|
|
59
59
|
const roleTypes = ["professionalQuestions", "managementQuestions"];
|
|
60
|
-
const skillRows =
|
|
60
|
+
const skillRows = skillProficiencies.map((level) => {
|
|
61
61
|
let count = 0;
|
|
62
62
|
for (const skill of skills) {
|
|
63
|
-
const sq = questions.
|
|
63
|
+
const sq = questions.skillProficiencies?.[skill.id];
|
|
64
64
|
if (sq) {
|
|
65
65
|
for (const roleType of roleTypes) {
|
|
66
66
|
count += (sq[roleType]?.[level] || []).length;
|
|
@@ -30,7 +30,7 @@ export function createNavButton({ label, href, variant = "primary" }) {
|
|
|
30
30
|
/**
|
|
31
31
|
* Create a button to navigate to job builder with a parameter
|
|
32
32
|
* @param {Object} options - Configuration options
|
|
33
|
-
* @param {string} options.paramName - Parameter name (discipline,
|
|
33
|
+
* @param {string} options.paramName - Parameter name (discipline, level, track)
|
|
34
34
|
* @param {string} options.paramValue - Parameter value (the ID)
|
|
35
35
|
* @param {string} [options.label] - Optional custom label
|
|
36
36
|
* @returns {HTMLElement}
|
|
@@ -38,7 +38,7 @@ export function createNavButton({ label, href, variant = "primary" }) {
|
|
|
38
38
|
export function createJobBuilderButton({ paramName, paramValue, label }) {
|
|
39
39
|
const defaultLabels = {
|
|
40
40
|
discipline: "Build Job with this Discipline →",
|
|
41
|
-
|
|
41
|
+
level: "Build Job at this Level →",
|
|
42
42
|
track: "Build Job with this Track →",
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -52,7 +52,7 @@ export function createJobBuilderButton({ paramName, paramValue, label }) {
|
|
|
52
52
|
/**
|
|
53
53
|
* Create a button to navigate to interview prep with a parameter
|
|
54
54
|
* @param {Object} options - Configuration options
|
|
55
|
-
* @param {string} options.paramName - Parameter name (discipline,
|
|
55
|
+
* @param {string} options.paramName - Parameter name (discipline, level, track)
|
|
56
56
|
* @param {string} options.paramValue - Parameter value (the ID)
|
|
57
57
|
* @param {string} [options.label] - Optional custom label
|
|
58
58
|
* @returns {HTMLElement}
|