@forwardimpact/pathway 0.20.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.
Files changed (96) hide show
  1. package/README.md +3 -3
  2. package/bin/fit-pathway.js +28 -28
  3. package/package.json +4 -4
  4. package/src/commands/agent.js +8 -8
  5. package/src/commands/build.js +7 -7
  6. package/src/commands/dev.js +7 -7
  7. package/src/commands/driver.js +1 -1
  8. package/src/commands/index.js +1 -1
  9. package/src/commands/init.js +1 -1
  10. package/src/commands/interview.js +8 -8
  11. package/src/commands/job.js +19 -19
  12. package/src/commands/level.js +60 -0
  13. package/src/commands/progress.js +20 -20
  14. package/src/commands/questions.js +3 -3
  15. package/src/commands/skill.js +1 -1
  16. package/src/commands/track.js +1 -1
  17. package/src/commands/update.js +12 -14
  18. package/src/components/action-buttons.js +3 -3
  19. package/src/components/builder.js +25 -25
  20. package/src/components/checklist.js +1 -1
  21. package/src/components/comparison-radar.js +3 -3
  22. package/src/components/detail.js +3 -3
  23. package/src/components/grid.js +1 -1
  24. package/src/components/radar-chart.js +3 -3
  25. package/src/components/skill-matrix.js +7 -7
  26. package/src/css/pages/landing.css +5 -5
  27. package/src/formatters/behaviour/dom.js +1 -1
  28. package/src/formatters/discipline/dom.js +1 -1
  29. package/src/formatters/driver/dom.js +1 -1
  30. package/src/formatters/index.js +5 -5
  31. package/src/formatters/interview/dom.js +1 -1
  32. package/src/formatters/interview/markdown.js +1 -1
  33. package/src/formatters/interview/shared.js +20 -20
  34. package/src/formatters/job/description.js +18 -18
  35. package/src/formatters/job/dom.js +12 -12
  36. package/src/formatters/job/markdown.js +7 -7
  37. package/src/formatters/json-ld.js +24 -24
  38. package/src/formatters/{grade → level}/dom.js +32 -28
  39. package/src/formatters/{grade → level}/markdown.js +20 -29
  40. package/src/formatters/{grade → level}/microdata.js +28 -38
  41. package/src/formatters/level/shared.js +86 -0
  42. package/src/formatters/progress/markdown.js +2 -2
  43. package/src/formatters/progress/shared.js +48 -48
  44. package/src/formatters/questions/markdown.js +8 -6
  45. package/src/formatters/questions/shared.js +7 -7
  46. package/src/formatters/skill/dom.js +4 -4
  47. package/src/formatters/skill/markdown.js +2 -2
  48. package/src/formatters/skill/microdata.js +3 -3
  49. package/src/formatters/skill/shared.js +3 -3
  50. package/src/formatters/track/dom.js +1 -1
  51. package/src/formatters/track/markdown.js +1 -1
  52. package/src/handout-main.js +13 -16
  53. package/src/handout.html +4 -4
  54. package/src/index.html +5 -5
  55. package/src/lib/card-mappers.js +17 -17
  56. package/src/lib/cli-command.js +3 -3
  57. package/src/lib/cli-output.js +2 -2
  58. package/src/lib/job-cache.js +11 -11
  59. package/src/lib/render.js +6 -6
  60. package/src/lib/state.js +2 -2
  61. package/src/lib/yaml-loader.js +9 -9
  62. package/src/main.js +10 -10
  63. package/src/pages/agent-builder.js +11 -11
  64. package/src/pages/assessment-results.js +27 -23
  65. package/src/pages/behaviour.js +1 -1
  66. package/src/pages/discipline.js +1 -1
  67. package/src/pages/driver.js +1 -1
  68. package/src/pages/interview-builder.js +6 -6
  69. package/src/pages/interview.js +9 -9
  70. package/src/pages/job-builder.js +6 -6
  71. package/src/pages/job.js +7 -7
  72. package/src/pages/landing.js +9 -9
  73. package/src/pages/level.js +122 -0
  74. package/src/pages/progress-builder.js +8 -8
  75. package/src/pages/progress.js +74 -74
  76. package/src/pages/self-assessment.js +8 -8
  77. package/src/pages/skill.js +1 -4
  78. package/src/pages/stage.js +1 -1
  79. package/src/pages/tool.js +1 -1
  80. package/src/pages/track.js +1 -1
  81. package/src/slide-main.js +22 -22
  82. package/src/slides/chapter.js +4 -4
  83. package/src/slides/index.js +11 -11
  84. package/src/slides/interview.js +2 -2
  85. package/src/slides/job.js +3 -3
  86. package/src/slides/level.js +32 -0
  87. package/src/slides/overview.js +9 -9
  88. package/src/slides/progress.js +13 -13
  89. package/src/slides.html +4 -4
  90. package/src/types.js +1 -1
  91. package/templates/install.template.sh +11 -8
  92. package/templates/job.template.md +2 -2
  93. package/src/commands/grade.js +0 -60
  94. package/src/formatters/grade/shared.js +0 -86
  95. package/src/pages/grade.js +0 -122
  96. 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
  *
@@ -32,12 +32,12 @@
32
32
  import { join, resolve } from "path";
33
33
  import { existsSync } from "fs";
34
34
  import { homedir } from "os";
35
- import { loadAllData } from "@forwardimpact/schema/loader";
35
+ import { loadAllData } from "@forwardimpact/map/loader";
36
36
  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
@@ -341,7 +341,7 @@ function printHelp() {
341
341
  * 3. ~/.fit/pathway/data/ (home directory install)
342
342
  * 4. ./data/ relative to current working directory
343
343
  * 5. ./examples/ relative to current working directory
344
- * 6. apps/schema/examples/ for monorepo development
344
+ * 6. products/map/examples/ for monorepo development
345
345
  *
346
346
  * @param {Object} options - Parsed command options
347
347
  * @returns {string} Resolved absolute path to data directory
@@ -375,10 +375,10 @@ function resolveDataPath(options) {
375
375
  return cwdExamples;
376
376
  }
377
377
 
378
- // 6. Monorepo: apps/schema/examples/
379
- const schemaExamples = join(process.cwd(), "apps/schema/examples");
380
- if (existsSync(schemaExamples)) {
381
- return schemaExamples;
378
+ // 6. Monorepo: products/map/examples/
379
+ const mapExamples = join(process.cwd(), "products/map/examples");
380
+ if (existsSync(mapExamples)) {
381
+ return mapExamples;
382
382
  }
383
383
 
384
384
  throw new Error(
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@forwardimpact/pathway",
3
- "version": "0.20.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": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/forwardimpact/monorepo",
9
- "directory": "apps/pathway"
9
+ "directory": "products/pathway"
10
10
  },
11
11
  "homepage": "https://www.forwardimpact.team/pathway",
12
12
  "keywords": [
@@ -40,8 +40,8 @@
40
40
  "./commands": "./src/commands/index.js"
41
41
  },
42
42
  "dependencies": {
43
- "@forwardimpact/schema": "^0.10.0",
44
- "@forwardimpact/libpathway": "^2.0.0",
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"
@@ -30,12 +30,12 @@ import { stringify as stringifyYaml } from "yaml";
30
30
  import {
31
31
  loadAgentData,
32
32
  loadSkillsWithAgentData,
33
- } from "@forwardimpact/schema/loader";
33
+ } from "@forwardimpact/map/loader";
34
34
  import {
35
35
  generateStageAgentProfile,
36
36
  validateAgentProfile,
37
37
  validateAgentSkill,
38
- deriveReferenceGrade,
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 grade for derivation
418
- const grade = deriveReferenceGrade(data.grades);
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
- grade,
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
- grade,
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
- grade,
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
- grade,
537
+ level,
538
538
  skills: skillsWithAgent,
539
539
  });
540
540
 
@@ -21,8 +21,8 @@ import { join, dirname, relative, resolve } from "path";
21
21
  import { fileURLToPath } from "url";
22
22
  import { execFileSync } from "child_process";
23
23
  import Mustache from "mustache";
24
- import { generateAllIndexes } from "@forwardimpact/schema/index-generator";
25
- import { loadFrameworkConfig } from "@forwardimpact/schema/loader";
24
+ import { generateAllIndexes } from "@forwardimpact/map/index-generator";
25
+ import { loadFrameworkConfig } from "@forwardimpact/map/loader";
26
26
 
27
27
  const __filename = fileURLToPath(import.meta.url);
28
28
  const __dirname = dirname(__filename);
@@ -31,7 +31,7 @@ const appDir = join(__dirname, "..");
31
31
  /**
32
32
  * Resolve package directory using Node's module resolution.
33
33
  * Works in both monorepo (development) and installed (production) contexts.
34
- * @param {string} packageName - Package specifier (e.g., '@forwardimpact/schema')
34
+ * @param {string} packageName - Package specifier (e.g., '@forwardimpact/map')
35
35
  * @returns {string} Absolute path to package lib directory
36
36
  */
37
37
  function resolvePackageLib(packageName) {
@@ -41,7 +41,7 @@ function resolvePackageLib(packageName) {
41
41
  return dirname(fileURLToPath(mainUrl));
42
42
  }
43
43
 
44
- const schemaLibDir = resolvePackageLib("@forwardimpact/schema");
44
+ const mapLibDir = resolvePackageLib("@forwardimpact/map");
45
45
  const modelLibDir = resolvePackageLib("@forwardimpact/libpathway");
46
46
 
47
47
  /**
@@ -141,11 +141,11 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
141
141
  }
142
142
  }
143
143
 
144
- // Copy @forwardimpact/schema and @forwardimpact/libpathway packages
144
+ // Copy @forwardimpact/map and @forwardimpact/libpathway packages
145
145
  // These are needed by the browser's import map
146
146
  console.log("📚 Copying package dependencies...");
147
- await cp(schemaLibDir, join(outputDir, "schema/lib"), { recursive: true });
148
- console.log(` ✓ schema/lib`);
147
+ await cp(mapLibDir, join(outputDir, "map/lib"), { recursive: true });
148
+ console.log(` ✓ map/lib`);
149
149
  await cp(modelLibDir, join(outputDir, "model/lib"), { recursive: true });
150
150
  console.log(` ✓ model/lib`);
151
151
 
@@ -9,8 +9,8 @@ import { createServer } from "http";
9
9
  import { readFile, stat } from "fs/promises";
10
10
  import { join, extname, dirname } from "path";
11
11
  import { fileURLToPath } from "url";
12
- import { generateAllIndexes } from "@forwardimpact/schema/index-generator";
13
- import { loadFrameworkConfig } from "@forwardimpact/schema/loader";
12
+ import { generateAllIndexes } from "@forwardimpact/map/index-generator";
13
+ import { loadFrameworkConfig } from "@forwardimpact/map/loader";
14
14
 
15
15
  const __filename = fileURLToPath(import.meta.url);
16
16
  const __dirname = dirname(__filename);
@@ -20,7 +20,7 @@ const rootDir = join(__dirname, "../..");
20
20
  /**
21
21
  * Resolve package directory using Node's module resolution.
22
22
  * Works in both monorepo (development) and installed (production) contexts.
23
- * @param {string} packageName - Package specifier (e.g., '@forwardimpact/schema')
23
+ * @param {string} packageName - Package specifier (e.g., '@forwardimpact/map')
24
24
  * @returns {string} Absolute path to package lib directory
25
25
  */
26
26
  function resolvePackageLib(packageName) {
@@ -30,7 +30,7 @@ function resolvePackageLib(packageName) {
30
30
  return dirname(fileURLToPath(mainUrl));
31
31
  }
32
32
 
33
- const schemaLibDir = resolvePackageLib("@forwardimpact/schema");
33
+ const mapLibDir = resolvePackageLib("@forwardimpact/map");
34
34
  const modelLibDir = resolvePackageLib("@forwardimpact/libpathway");
35
35
 
36
36
  const MIME_TYPES = {
@@ -134,9 +134,9 @@ export async function runDevCommand({ dataDir, options }) {
134
134
  } else if (pathname.startsWith("/templates/")) {
135
135
  // Serve from templates directory
136
136
  filePath = join(rootDir, pathname);
137
- } else if (pathname.startsWith("/schema/lib/")) {
138
- // Serve @forwardimpact/schema package files (resolved via Node module resolution)
139
- filePath = join(schemaLibDir, pathname.slice(12));
137
+ } else if (pathname.startsWith("/map/lib/")) {
138
+ // Serve @forwardimpact/map package files (resolved via Node module resolution)
139
+ filePath = join(mapLibDir, pathname.slice(9));
140
140
  } else if (pathname.startsWith("/model/lib/")) {
141
141
  // Serve @forwardimpact/libpathway package files (resolved via Node module resolution)
142
142
  filePath = join(modelLibDir, pathname.slice(11));
@@ -18,7 +18,7 @@ import {
18
18
  formatSubheader,
19
19
  formatBullet,
20
20
  } from "../lib/cli-output.js";
21
- import { getConceptEmoji } from "@forwardimpact/schema/levels";
21
+ import { getConceptEmoji } from "@forwardimpact/map/levels";
22
22
 
23
23
  /**
24
24
  * Format driver summary output
@@ -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,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> <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
  */
@@ -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
- 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 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.grade.id} ${job.track.id}`);
61
+ console.log(`${job.discipline.id} ${job.level.id} ${job.track.id}`);
62
62
  } else {
63
- console.log(`${job.discipline.id} ${job.grade.id}`);
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> <grade> [--track=<track>]' for details\n`,
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 grade
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> <grade> [--track=<track>]",
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 grade = data.grades.find((g) => g.id === args[1]);
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 (!grade) {
116
- console.error(`Grade not found: ${args[1]}`);
117
- console.error(`Available: ${data.grades.map((g) => g.id).join(", ")}`);
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
- grade,
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} × ${grade.id} × ${track.id}`
141
- : `${discipline.id} × ${grade.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, grade, track }, jobTemplate);
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
+ });