@forwardimpact/pathway 0.21.0 → 0.23.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 (123) hide show
  1. package/README.md +3 -3
  2. package/bin/fit-pathway.js +22 -22
  3. package/package.json +4 -3
  4. package/src/commands/agent.js +14 -10
  5. package/src/commands/behaviour.js +11 -1
  6. package/src/commands/build.js +11 -2
  7. package/src/commands/command-factory.js +4 -2
  8. package/src/commands/dev.js +9 -2
  9. package/src/commands/discipline.js +19 -2
  10. package/src/commands/driver.js +11 -1
  11. package/src/commands/index.js +1 -1
  12. package/src/commands/init.js +1 -1
  13. package/src/commands/interview.js +8 -8
  14. package/src/commands/job.js +41 -28
  15. package/src/commands/level.js +76 -0
  16. package/src/commands/progress.js +20 -20
  17. package/src/commands/questions.js +3 -3
  18. package/src/commands/skill.js +11 -1
  19. package/src/commands/stage.js +11 -1
  20. package/src/commands/tool.js +4 -3
  21. package/src/commands/track.js +11 -1
  22. package/src/components/action-buttons.js +3 -3
  23. package/src/components/builder.js +25 -25
  24. package/src/components/card.js +8 -104
  25. package/src/components/comparison-radar.js +4 -4
  26. package/src/components/detail.js +18 -120
  27. package/src/components/error-page.js +8 -68
  28. package/src/components/grid.js +12 -106
  29. package/src/components/list.js +7 -116
  30. package/src/components/nav.js +7 -60
  31. package/src/components/radar-chart.js +3 -3
  32. package/src/components/skill-matrix.js +7 -7
  33. package/src/css/bundles/app.css +25 -21
  34. package/src/css/bundles/handout.css +33 -33
  35. package/src/css/bundles/slides.css +25 -25
  36. package/src/css/pages/landing.css +5 -5
  37. package/src/formatters/index.js +5 -5
  38. package/src/formatters/interview/shared.js +23 -23
  39. package/src/formatters/job/description.js +18 -18
  40. package/src/formatters/job/dom.js +12 -12
  41. package/src/formatters/job/markdown.js +7 -7
  42. package/src/formatters/json-ld.js +24 -24
  43. package/src/formatters/{grade → level}/dom.js +31 -27
  44. package/src/formatters/{grade → level}/markdown.js +19 -28
  45. package/src/formatters/{grade → level}/microdata.js +28 -38
  46. package/src/formatters/level/shared.js +86 -0
  47. package/src/formatters/progress/markdown.js +2 -2
  48. package/src/formatters/progress/shared.js +51 -51
  49. package/src/formatters/questions/markdown.js +8 -6
  50. package/src/formatters/questions/shared.js +7 -7
  51. package/src/formatters/skill/dom.js +4 -4
  52. package/src/formatters/skill/markdown.js +1 -1
  53. package/src/formatters/skill/microdata.js +3 -3
  54. package/src/formatters/skill/shared.js +3 -3
  55. package/src/formatters/track/shared.js +1 -1
  56. package/src/handout-main.js +12 -12
  57. package/src/handout.html +32 -13
  58. package/src/index.html +33 -14
  59. package/src/lib/card-mappers.js +16 -16
  60. package/src/lib/cli-command.js +3 -3
  61. package/src/lib/cli-output.js +2 -2
  62. package/src/lib/error-boundary.js +3 -66
  63. package/src/lib/errors.js +7 -45
  64. package/src/lib/job-cache.js +12 -12
  65. package/src/lib/markdown.js +2 -109
  66. package/src/lib/reactive.js +7 -73
  67. package/src/lib/render.js +53 -201
  68. package/src/lib/router-core.js +2 -156
  69. package/src/lib/router-pages.js +2 -11
  70. package/src/lib/router-slides.js +2 -197
  71. package/src/lib/state.js +16 -65
  72. package/src/lib/utils.js +3 -10
  73. package/src/lib/yaml-loader.js +22 -80
  74. package/src/main.js +10 -10
  75. package/src/pages/agent-builder.js +12 -12
  76. package/src/pages/assessment-results.js +28 -24
  77. package/src/pages/interview-builder.js +6 -6
  78. package/src/pages/interview.js +8 -8
  79. package/src/pages/job-builder.js +7 -7
  80. package/src/pages/job.js +8 -8
  81. package/src/pages/landing.js +8 -8
  82. package/src/pages/level.js +122 -0
  83. package/src/pages/progress-builder.js +8 -8
  84. package/src/pages/progress.js +74 -74
  85. package/src/pages/self-assessment.js +7 -7
  86. package/src/pages/skill.js +1 -1
  87. package/src/slide-main.js +23 -23
  88. package/src/slides/chapter.js +4 -4
  89. package/src/slides/index.js +11 -11
  90. package/src/slides/interview.js +2 -2
  91. package/src/slides/job.js +4 -4
  92. package/src/slides/level.js +32 -0
  93. package/src/slides/overview.js +10 -10
  94. package/src/slides/progress.js +13 -13
  95. package/src/slides.html +32 -13
  96. package/src/types.js +1 -1
  97. package/templates/job.template.md +2 -2
  98. package/src/commands/grade.js +0 -60
  99. package/src/css/base.css +0 -56
  100. package/src/css/components/badges.css +0 -232
  101. package/src/css/components/buttons.css +0 -101
  102. package/src/css/components/forms.css +0 -191
  103. package/src/css/components/layout.css +0 -218
  104. package/src/css/components/nav.css +0 -206
  105. package/src/css/components/progress.css +0 -166
  106. package/src/css/components/states.css +0 -82
  107. package/src/css/components/surfaces.css +0 -347
  108. package/src/css/components/tables.css +0 -362
  109. package/src/css/components/top-bar.css +0 -180
  110. package/src/css/components/typography.css +0 -121
  111. package/src/css/components/utilities.css +0 -41
  112. package/src/css/pages/detail.css +0 -119
  113. package/src/css/reset.css +0 -50
  114. package/src/css/tokens.css +0 -162
  115. package/src/css/views/handout.css +0 -30
  116. package/src/css/views/print.css +0 -634
  117. package/src/css/views/slide-animations.css +0 -113
  118. package/src/css/views/slide-base.css +0 -331
  119. package/src/css/views/slide-sections.css +0 -597
  120. package/src/css/views/slide-tables.css +0 -275
  121. package/src/formatters/grade/shared.js +0 -86
  122. package/src/pages/grade.js +0 -122
  123. 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 grade to explore roles
56
- - **Skill Browser** — View all skills with level descriptions
57
- - **Career Progression** — Compare grades and identify growth areas
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
 
@@ -10,16 +10,16 @@
10
10
  *
11
11
  * Commands:
12
12
  * discipline [<id>] Show disciplines
13
- * grade [<id>] Show grades
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> <grade>] [--track=TRACK] Generate job definition
21
- * interview <discipline> <grade> [--track=TRACK] [--type=mission|decomposition|stakeholder] Generate interview
22
- * progress <discipline> <grade> [--track=TRACK] [--compare=GRADE] Career progression
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 { runGradeCommand } from "../src/commands/grade.js";
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
- grade: runGradeCommand,
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
- grade [<id>] Browse career grades/levels
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 × grade × track combinations.
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> <grade> Detail view (trackless)
122
- npx fit-pathway job <d> <g> --track=<track> Detail view (with track)
123
- npx fit-pathway job <d> <g> --skills Plain list of skill IDs
124
- npx fit-pathway job <d> <g> --tools Plain list of tool names
125
- npx fit-pathway job <d> <g> --checklist=<stage> Show handoff checklist
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> <grade> All types
174
- npx fit-pathway interview <d> <g> --track=<track> With track
175
- npx fit-pathway interview <d> <g> --track=<t> --type=<type> Single type
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 grades.
186
+ Analyze career progression between levels.
187
187
 
188
188
  Usage:
189
- npx fit-pathway progress <discipline> <grade>
190
- npx fit-pathway progress <d> <g> --track=<track>
191
- npx fit-pathway progress <d> <g> --compare=<to_grade>
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=GRADE Compare to specific grade
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 level
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.21.0",
3
+ "version": "0.23.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,9 @@
40
40
  "./commands": "./src/commands/index.js"
41
41
  },
42
42
  "dependencies": {
43
- "@forwardimpact/map": "^0.10.0",
44
- "@forwardimpact/libpathway": "^2.0.0",
43
+ "@forwardimpact/map": "^0.11.0",
44
+ "@forwardimpact/libskill": "^3.0.0",
45
+ "@forwardimpact/libui": "^1.0.0",
45
46
  "mustache": "^4.2.0",
46
47
  "simple-icons": "^16.7.0",
47
48
  "yaml": "^2.3.4"
@@ -35,14 +35,14 @@ import {
35
35
  generateStageAgentProfile,
36
36
  validateAgentProfile,
37
37
  validateAgentSkill,
38
- deriveReferenceGrade,
38
+ deriveReferenceLevel,
39
39
  deriveAgentSkills,
40
40
  generateSkillMarkdown,
41
41
  deriveToolkit,
42
42
  buildAgentIndex,
43
43
  getDisciplineAbbreviation,
44
44
  toKebabCase,
45
- } from "@forwardimpact/libpathway";
45
+ } from "@forwardimpact/libskill";
46
46
  import { formatAgentProfile } from "../formatters/agent/profile.js";
47
47
  import {
48
48
  formatAgentSkill,
@@ -199,7 +199,7 @@ function showAgentSummary(data, agentData, skillsWithAgent) {
199
199
  */
200
200
  function listAgentCombinations(data, agentData, verbose = false) {
201
201
  if (!verbose) {
202
- // Clean output for piping
202
+ // Descriptive output for piping and AI agent discovery
203
203
  for (const discipline of agentData.disciplines) {
204
204
  for (const track of agentData.tracks) {
205
205
  const humanDiscipline = data.disciplines.find(
@@ -209,7 +209,11 @@ function listAgentCombinations(data, agentData, verbose = false) {
209
209
  if (humanDiscipline && humanTrack) {
210
210
  const abbrev = getDisciplineAbbreviation(discipline.id);
211
211
  const agentName = `${abbrev}-${toKebabCase(track.id)}`;
212
- console.log(`${agentName} ${discipline.id} ${track.id}`);
212
+ const specName =
213
+ humanDiscipline.specialization || humanDiscipline.id;
214
+ console.log(
215
+ `${agentName} ${discipline.id} ${track.id}, ${specName} (${humanTrack.name})`,
216
+ );
213
217
  }
214
218
  }
215
219
  }
@@ -414,15 +418,15 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
414
418
  process.exit(1);
415
419
  }
416
420
 
417
- // Get reference grade for derivation
418
- const grade = deriveReferenceGrade(data.grades);
421
+ // Get reference level for derivation
422
+ const level = deriveReferenceLevel(data.levels);
419
423
 
420
424
  // --skills: Output plain list of skill IDs (for piping)
421
425
  if (options.skills) {
422
426
  const derivedSkills = deriveAgentSkills({
423
427
  discipline: humanDiscipline,
424
428
  track: humanTrack,
425
- grade,
429
+ level,
426
430
  skills: skillsWithAgent,
427
431
  });
428
432
  for (const skill of derivedSkills) {
@@ -436,7 +440,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
436
440
  const derivedSkills = deriveAgentSkills({
437
441
  discipline: humanDiscipline,
438
442
  track: humanTrack,
439
- grade,
443
+ level,
440
444
  skills: skillsWithAgent,
441
445
  });
442
446
  const toolkit = deriveToolkit({
@@ -462,7 +466,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
462
466
  const stageParams = {
463
467
  discipline: humanDiscipline,
464
468
  track: humanTrack,
465
- grade,
469
+ level,
466
470
  skills: skillsWithAgent,
467
471
  behaviours: data.behaviours,
468
472
  agentBehaviours: agentData.behaviours,
@@ -534,7 +538,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
534
538
  const derivedSkills = deriveAgentSkills({
535
539
  discipline: humanDiscipline,
536
540
  track: humanTrack,
537
- grade,
541
+ level,
538
542
  skills: skillsWithAgent,
539
543
  });
540
544
 
@@ -14,6 +14,15 @@ import { createEntityCommand } from "./command-factory.js";
14
14
  import { behaviourToMarkdown } from "../formatters/behaviour/markdown.js";
15
15
  import { formatTable } from "../lib/cli-output.js";
16
16
 
17
+ /**
18
+ * Format behaviour list item for --list output
19
+ * @param {Object} behaviour - Behaviour entity
20
+ * @returns {string} Formatted list line
21
+ */
22
+ function formatListItem(behaviour) {
23
+ return `${behaviour.id}, ${behaviour.name}`;
24
+ }
25
+
17
26
  /**
18
27
  * Format behaviour summary output
19
28
  * @param {Array} behaviours - Raw behaviour entities
@@ -34,7 +43,7 @@ function formatSummary(behaviours, data) {
34
43
 
35
44
  console.log(formatTable(["ID", "Name", "Drivers"], rows));
36
45
  console.log(`\nTotal: ${behaviours.length} behaviours`);
37
- console.log(`\nRun 'npx pathway behaviour --list' for IDs`);
46
+ console.log(`\nRun 'npx pathway behaviour --list' for IDs and names`);
38
47
  console.log(`Run 'npx pathway behaviour <id>' for details\n`);
39
48
  }
40
49
 
@@ -57,5 +66,6 @@ export const runBehaviourCommand = createEntityCommand({
57
66
  }),
58
67
  formatSummary,
59
68
  formatDetail,
69
+ formatListItem,
60
70
  emojiIcon: "🧠",
61
71
  });
@@ -42,7 +42,8 @@ function resolvePackageLib(packageName) {
42
42
  }
43
43
 
44
44
  const mapLibDir = resolvePackageLib("@forwardimpact/map");
45
- const modelLibDir = resolvePackageLib("@forwardimpact/libpathway");
45
+ const modelLibDir = resolvePackageLib("@forwardimpact/libskill");
46
+ const uiLibDir = resolvePackageLib("@forwardimpact/libui");
46
47
 
47
48
  /**
48
49
  * Files and directories to copy from app/
@@ -141,13 +142,21 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
141
142
  }
142
143
  }
143
144
 
144
- // Copy @forwardimpact/map and @forwardimpact/libpathway packages
145
+ // Copy @forwardimpact/map and @forwardimpact/libskill packages
145
146
  // These are needed by the browser's import map
146
147
  console.log("📚 Copying package dependencies...");
147
148
  await cp(mapLibDir, join(outputDir, "map/lib"), { recursive: true });
148
149
  console.log(` ✓ map/lib`);
149
150
  await cp(modelLibDir, join(outputDir, "model/lib"), { recursive: true });
150
151
  console.log(` ✓ model/lib`);
152
+ // Copy libui JS (src/) and CSS (src/css/)
153
+ await cp(uiLibDir, join(outputDir, "ui/lib"), { recursive: true });
154
+ // CSS is within uiLibDir/css/ so it's already copied as ui/lib/css/
155
+ // Create ui/css/ symlink-like copy for the CSS @import paths
156
+ await cp(join(uiLibDir, "css"), join(outputDir, "ui/css"), {
157
+ recursive: true,
158
+ });
159
+ console.log(` ✓ ui/lib + ui/css`);
151
160
 
152
161
  // Copy data directory (dereference symlinks to copy actual content)
153
162
  console.log("📁 Copying data files...");
@@ -22,6 +22,7 @@ import { capitalize } from "../formatters/shared.js";
22
22
  * @param {Function} config.presentDetail - Function to present detail: (entity, data, options) => view
23
23
  * @param {Function} config.formatSummary - Function to format summary output: (items, data) => void
24
24
  * @param {Function} config.formatDetail - Function to format detail output: (view, framework) => void
25
+ * @param {Function} [config.formatListItem] - Optional function to format list item: (item) => string (defaults to item.id)
25
26
  * @param {Function} [config.sortItems] - Optional function to sort items: (items) => sortedItems
26
27
  * @param {Function} [config.validate] - Optional validation function: (data) => {errors: [], warnings: []}
27
28
  * @param {string} [config.emojiIcon] - Optional emoji for the entity
@@ -34,6 +35,7 @@ export function createEntityCommand({
34
35
  presentDetail,
35
36
  formatSummary,
36
37
  formatDetail,
38
+ formatListItem,
37
39
  sortItems,
38
40
  validate,
39
41
  _emojiIcon = "",
@@ -48,10 +50,10 @@ export function createEntityCommand({
48
50
  return handleValidate({ data, entityName, pluralName, validate });
49
51
  }
50
52
 
51
- // --list: Output clean newline-separated IDs for piping
53
+ // --list: Output descriptive comma-separated lines for piping and AI agent discovery
52
54
  if (options.list) {
53
55
  for (const item of items) {
54
- console.log(item.id);
56
+ console.log(formatListItem ? formatListItem(item) : item.id);
55
57
  }
56
58
  return;
57
59
  }
@@ -31,7 +31,8 @@ function resolvePackageLib(packageName) {
31
31
  }
32
32
 
33
33
  const mapLibDir = resolvePackageLib("@forwardimpact/map");
34
- const modelLibDir = resolvePackageLib("@forwardimpact/libpathway");
34
+ const modelLibDir = resolvePackageLib("@forwardimpact/libskill");
35
+ const uiLibDir = resolvePackageLib("@forwardimpact/libui");
35
36
 
36
37
  const MIME_TYPES = {
37
38
  ".html": "text/html; charset=utf-8",
@@ -138,8 +139,14 @@ export async function runDevCommand({ dataDir, options }) {
138
139
  // Serve @forwardimpact/map package files (resolved via Node module resolution)
139
140
  filePath = join(mapLibDir, pathname.slice(9));
140
141
  } else if (pathname.startsWith("/model/lib/")) {
141
- // Serve @forwardimpact/libpathway package files (resolved via Node module resolution)
142
+ // Serve @forwardimpact/libskill package files (resolved via Node module resolution)
142
143
  filePath = join(modelLibDir, pathname.slice(11));
144
+ } else if (pathname.startsWith("/ui/lib/")) {
145
+ // Serve @forwardimpact/libui package JS files
146
+ filePath = join(uiLibDir, pathname.slice(8));
147
+ } else if (pathname.startsWith("/ui/css/")) {
148
+ // Serve @forwardimpact/libui package CSS files
149
+ filePath = join(uiLibDir, "css", pathname.slice(8));
143
150
  } else if (pathname === "/" || pathname === "") {
144
151
  // Serve index.html for root
145
152
  filePath = join(publicDir, "index.html");
@@ -14,6 +14,15 @@ import { createEntityCommand } from "./command-factory.js";
14
14
  import { disciplineToMarkdown } from "../formatters/discipline/markdown.js";
15
15
  import { formatTable } from "../lib/cli-output.js";
16
16
 
17
+ /**
18
+ * Format discipline list item for --list output
19
+ * @param {Object} discipline - Discipline entity
20
+ * @returns {string} Formatted list line
21
+ */
22
+ function formatListItem(discipline) {
23
+ return `${discipline.id}, ${discipline.specialization || discipline.id}, ${discipline.roleTitle || discipline.id}`;
24
+ }
25
+
17
26
  /**
18
27
  * Format discipline summary output
19
28
  * @param {Array} disciplines - Raw discipline entities
@@ -23,14 +32,21 @@ function formatSummary(disciplines) {
23
32
 
24
33
  const rows = disciplines.map((d) => [
25
34
  d.id,
35
+ d.specialization || d.id,
36
+ d.roleTitle || d.id,
26
37
  d.coreSkills?.length || 0,
27
38
  d.supportingSkills?.length || 0,
28
39
  d.broadSkills?.length || 0,
29
40
  ]);
30
41
 
31
- console.log(formatTable(["ID", "Core", "Supporting", "Broad"], rows));
42
+ console.log(
43
+ formatTable(
44
+ ["ID", "Specialization", "Role Title", "Core", "Supporting", "Broad"],
45
+ rows,
46
+ ),
47
+ );
32
48
  console.log(`\nTotal: ${disciplines.length} disciplines`);
33
- console.log(`\nRun 'npx pathway discipline --list' for IDs`);
49
+ console.log(`\nRun 'npx pathway discipline --list' for IDs and names`);
34
50
  console.log(`Run 'npx pathway discipline <id>' for details\n`);
35
51
  }
36
52
 
@@ -54,5 +70,6 @@ export const runDisciplineCommand = createEntityCommand({
54
70
  }),
55
71
  formatSummary,
56
72
  formatDetail,
73
+ formatListItem,
57
74
  emojiIcon: "📋",
58
75
  });
@@ -20,6 +20,15 @@ import {
20
20
  } from "../lib/cli-output.js";
21
21
  import { getConceptEmoji } from "@forwardimpact/map/levels";
22
22
 
23
+ /**
24
+ * Format driver list item for --list output
25
+ * @param {Object} driver - Driver entity
26
+ * @returns {string} Formatted list line
27
+ */
28
+ function formatListItem(driver) {
29
+ return `${driver.id}, ${driver.name}`;
30
+ }
31
+
23
32
  /**
24
33
  * Format driver summary output
25
34
  * @param {Array} drivers - Raw driver entities
@@ -43,7 +52,7 @@ function formatSummary(drivers, data) {
43
52
 
44
53
  console.log(formatTable(["ID", "Name", "Skills", "Behaviours"], rows));
45
54
  console.log(`\nTotal: ${drivers.length} drivers`);
46
- console.log(`\nRun 'npx pathway driver --list' for IDs`);
55
+ console.log(`\nRun 'npx pathway driver --list' for IDs and names`);
47
56
  console.log(`Run 'npx pathway driver <id>' for details\n`);
48
57
  }
49
58
 
@@ -90,5 +99,6 @@ export const runDriverCommand = createEntityCommand({
90
99
  }),
91
100
  formatSummary,
92
101
  formatDetail,
102
+ formatListItem,
93
103
  emojiIcon: "🎯",
94
104
  });
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  export { runDisciplineCommand } from "./discipline.js";
8
- export { runGradeCommand } from "./grade.js";
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";
@@ -54,7 +54,7 @@ Next steps:
54
54
  Data structure:
55
55
  data/
56
56
  ├── framework.yaml # Framework metadata
57
- ├── grades.yaml # Career levels
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> <grade> # All interview types
8
- * npx fit-pathway interview <discipline> <grade> --track=<track> # With track
9
- * npx fit-pathway interview <discipline> <grade> --track=<track> --type=mission # Single type
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", "grade_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
- grade: data.grades.find((g) => g.id === args[1]),
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.grade) {
72
- return `Grade not found: ${entities.grade}`;
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
- grade: entities.grade,
82
+ level: entities.level,
83
83
  track: entities.track,
84
84
  skills: data.skills,
85
85
  behaviours: data.behaviours,
@@ -6,22 +6,22 @@
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> <grade> # Detail view (trackless)
10
- * npx pathway job <discipline> <grade> --track=<track> # Detail view (with track)
11
- * npx pathway job <d> <g> [--track=<t>] --skills # Plain list of skill IDs
12
- * npx pathway job <d> <g> [--track=<t>] --tools # Plain list of tool names
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
  */
16
16
 
17
- import { prepareJobDetail } from "@forwardimpact/libpathway/job";
17
+ import { prepareJobDetail } from "@forwardimpact/libskill/job";
18
18
  import { jobToMarkdown } from "../formatters/job/markdown.js";
19
- import { generateAllJobs } from "@forwardimpact/libpathway/derivation";
19
+ import { generateJobTitle, generateAllJobs } from "@forwardimpact/libskill/derivation";
20
20
  import { formatTable } from "../lib/cli-output.js";
21
21
  import {
22
22
  deriveChecklist,
23
23
  formatChecklistMarkdown,
24
- } from "@forwardimpact/libpathway/checklist";
24
+ } from "@forwardimpact/libskill/checklist";
25
25
  import { loadJobTemplate } from "../lib/template-loader.js";
26
26
  import { toolkitToPlainList } from "../formatters/toolkit/markdown.js";
27
27
 
@@ -47,20 +47,23 @@ 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
- grades: data.grades,
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 grade track format)
57
+ // --list: Output descriptive comma-separated lines for piping and AI agent discovery
58
58
  if (options.list) {
59
59
  for (const job of jobs) {
60
+ const title = generateJobTitle(job.discipline, job.level, job.track);
60
61
  if (job.track) {
61
- console.log(`${job.discipline.id} ${job.grade.id} ${job.track.id}`);
62
+ console.log(
63
+ `${job.discipline.id} ${job.level.id} ${job.track.id}, ${title}`,
64
+ );
62
65
  } else {
63
- console.log(`${job.discipline.id} ${job.grade.id}`);
66
+ console.log(`${job.discipline.id} ${job.level.id}, ${title}`);
64
67
  }
65
68
  }
66
69
  return;
@@ -70,27 +73,37 @@ export async function runJobCommand({ data, args, options, dataDir }) {
70
73
  if (args.length === 0) {
71
74
  console.log(`\n💼 Jobs\n`);
72
75
 
73
- // Count by discipline
76
+ // Count by discipline with name
74
77
  const byDiscipline = {};
75
78
  for (const job of jobs) {
76
- byDiscipline[job.discipline.id] =
77
- (byDiscipline[job.discipline.id] || 0) + 1;
79
+ const key = job.discipline.id;
80
+ if (!byDiscipline[key]) {
81
+ byDiscipline[key] = {
82
+ name: job.discipline.specialization || job.discipline.id,
83
+ count: 0,
84
+ };
85
+ }
86
+ byDiscipline[key].count++;
78
87
  }
79
88
 
80
- const rows = Object.entries(byDiscipline).map(([id, count]) => [id, count]);
81
- console.log(formatTable(["Discipline", "Combinations"], rows));
89
+ const rows = Object.entries(byDiscipline).map(([id, info]) => [
90
+ id,
91
+ info.name,
92
+ info.count,
93
+ ]);
94
+ console.log(formatTable(["ID", "Specialization", "Combinations"], rows));
82
95
  console.log(`\nTotal: ${jobs.length} valid job combinations`);
83
- console.log(`\nRun 'npx pathway job --list' for all combinations`);
96
+ console.log(`\nRun 'npx pathway job --list' for all combinations with titles`);
84
97
  console.log(
85
- `Run 'npx pathway job <discipline> <grade> [--track=<track>]' for details\n`,
98
+ `Run 'npx pathway job <discipline> <level> [--track=<track>]' for details\n`,
86
99
  );
87
100
  return;
88
101
  }
89
102
 
90
- // Handle job detail view - requires discipline and grade
103
+ // Handle job detail view - requires discipline and level
91
104
  if (args.length < 2) {
92
105
  console.error(
93
- "Usage: npx pathway job <discipline> <grade> [--track=<track>]",
106
+ "Usage: npx pathway job <discipline> <level> [--track=<track>]",
94
107
  );
95
108
  console.error(" npx pathway job --list");
96
109
  console.error("Example: npx pathway job software_engineering L4");
@@ -101,7 +114,7 @@ export async function runJobCommand({ data, args, options, dataDir }) {
101
114
  }
102
115
 
103
116
  const discipline = data.disciplines.find((d) => d.id === args[0]);
104
- const grade = data.grades.find((g) => g.id === args[1]);
117
+ const level = data.levels.find((g) => g.id === args[1]);
105
118
  const track = options.track
106
119
  ? data.tracks.find((t) => t.id === options.track)
107
120
  : null;
@@ -112,9 +125,9 @@ export async function runJobCommand({ data, args, options, dataDir }) {
112
125
  process.exit(1);
113
126
  }
114
127
 
115
- if (!grade) {
116
- console.error(`Grade not found: ${args[1]}`);
117
- console.error(`Available: ${data.grades.map((g) => g.id).join(", ")}`);
128
+ if (!level) {
129
+ console.error(`Level not found: ${args[1]}`);
130
+ console.error(`Available: ${data.levels.map((g) => g.id).join(", ")}`);
118
131
  process.exit(1);
119
132
  }
120
133
 
@@ -126,7 +139,7 @@ export async function runJobCommand({ data, args, options, dataDir }) {
126
139
 
127
140
  const view = prepareJobDetail({
128
141
  discipline,
129
- grade,
142
+ level,
130
143
  track,
131
144
  skills: data.skills,
132
145
  behaviours: data.behaviours,
@@ -137,8 +150,8 @@ export async function runJobCommand({ data, args, options, dataDir }) {
137
150
 
138
151
  if (!view) {
139
152
  const combo = track
140
- ? `${discipline.id} × ${grade.id} × ${track.id}`
141
- : `${discipline.id} × ${grade.id}`;
153
+ ? `${discipline.id} × ${level.id} × ${track.id}`
154
+ : `${discipline.id} × ${level.id}`;
142
155
  console.error(`Invalid combination: ${combo}`);
143
156
  if (track) {
144
157
  const validTracks =
@@ -212,5 +225,5 @@ export async function runJobCommand({ data, args, options, dataDir }) {
212
225
 
213
226
  // Load job template for description formatting
214
227
  const jobTemplate = await loadJobTemplate(dataDir);
215
- formatJob(view, options, { discipline, grade, track }, jobTemplate);
228
+ formatJob(view, options, { discipline, level, track }, jobTemplate);
216
229
  }