@forwardimpact/pathway 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/app/commands/agent.js +109 -21
  2. package/app/commands/command-factory.js +3 -3
  3. package/app/commands/interview.js +14 -7
  4. package/app/commands/job.js +43 -29
  5. package/app/commands/progress.js +14 -7
  6. package/app/commands/serve.js +5 -0
  7. package/app/commands/stage.js +0 -10
  8. package/app/commands/track.js +5 -8
  9. package/app/components/builder.js +111 -27
  10. package/app/css/components/surfaces.css +16 -0
  11. package/app/formatters/agent/profile.js +113 -87
  12. package/app/formatters/agent/skill.js +64 -31
  13. package/app/formatters/behaviour/dom.js +3 -0
  14. package/app/formatters/behaviour/microdata.js +106 -0
  15. package/app/formatters/discipline/dom.js +28 -1
  16. package/app/formatters/discipline/microdata.js +117 -0
  17. package/app/formatters/discipline/shared.js +49 -8
  18. package/app/formatters/driver/dom.js +3 -0
  19. package/app/formatters/driver/microdata.js +91 -0
  20. package/app/formatters/grade/dom.js +3 -0
  21. package/app/formatters/grade/microdata.js +151 -0
  22. package/app/formatters/index.js +32 -1
  23. package/app/formatters/interview/shared.js +13 -8
  24. package/app/formatters/job/description.js +5 -3
  25. package/app/formatters/json-ld.js +242 -0
  26. package/app/formatters/microdata-shared.js +184 -0
  27. package/app/formatters/progress/shared.js +14 -11
  28. package/app/formatters/skill/dom.js +3 -0
  29. package/app/formatters/skill/microdata.js +151 -0
  30. package/app/formatters/stage/dom.js +3 -18
  31. package/app/formatters/stage/microdata.js +110 -0
  32. package/app/formatters/stage/shared.js +0 -27
  33. package/app/formatters/track/dom.js +5 -30
  34. package/app/formatters/track/markdown.js +2 -25
  35. package/app/formatters/track/microdata.js +111 -0
  36. package/app/formatters/track/shared.js +6 -58
  37. package/app/handout-main.js +26 -12
  38. package/app/index.html +11 -0
  39. package/app/lib/card-mappers.js +17 -12
  40. package/app/lib/job-cache.js +12 -9
  41. package/app/lib/template-loader.js +66 -0
  42. package/app/lib/yaml-loader.js +25 -8
  43. package/app/main.js +8 -4
  44. package/app/model/agent.js +158 -130
  45. package/app/model/checklist.js +57 -91
  46. package/app/model/derivation.js +135 -68
  47. package/app/model/index-generator.js +1 -7
  48. package/app/model/job.js +19 -13
  49. package/app/model/levels.js +20 -12
  50. package/app/model/loader.js +41 -17
  51. package/app/model/matching.js +33 -3
  52. package/app/model/profile.js +38 -45
  53. package/app/model/schema-validation.js +438 -0
  54. package/app/model/validation.js +747 -68
  55. package/app/pages/agent-builder.js +119 -25
  56. package/app/pages/assessment-results.js +10 -4
  57. package/app/pages/discipline.js +36 -6
  58. package/app/pages/driver.js +9 -47
  59. package/app/pages/interview-builder.js +3 -1
  60. package/app/pages/interview.js +15 -4
  61. package/app/pages/job-builder.js +4 -1
  62. package/app/pages/job.js +15 -4
  63. package/app/pages/landing.js +10 -10
  64. package/app/pages/progress-builder.js +3 -1
  65. package/app/pages/progress.js +72 -21
  66. package/app/pages/stage.js +3 -126
  67. package/app/slide-main.js +45 -17
  68. package/app/slides/index.js +3 -1
  69. package/app/slides/overview.js +40 -4
  70. package/app/slides/progress.js +4 -2
  71. package/bin/pathway.js +18 -64
  72. package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
  73. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
  74. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
  75. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
  76. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
  77. package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
  78. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
  79. package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
  80. package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
  81. package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
  82. package/examples/agents/.vscode/settings.json +1 -1
  83. package/examples/behaviours/outcome_ownership.yaml +1 -2
  84. package/examples/behaviours/polymathic_knowledge.yaml +1 -2
  85. package/examples/behaviours/precise_communication.yaml +1 -2
  86. package/examples/behaviours/relentless_curiosity.yaml +1 -2
  87. package/examples/behaviours/systems_thinking.yaml +1 -2
  88. package/examples/capabilities/business.yaml +80 -142
  89. package/examples/capabilities/delivery.yaml +155 -219
  90. package/examples/capabilities/people.yaml +2 -34
  91. package/examples/capabilities/reliability.yaml +161 -80
  92. package/examples/capabilities/scale.yaml +234 -252
  93. package/examples/copilot-setup-steps.yaml +25 -0
  94. package/examples/devcontainer.yaml +21 -0
  95. package/examples/disciplines/_index.yaml +1 -0
  96. package/examples/disciplines/data_engineering.yaml +14 -12
  97. package/examples/disciplines/engineering_management.yaml +63 -0
  98. package/examples/disciplines/software_engineering.yaml +14 -12
  99. package/examples/drivers.yaml +1 -4
  100. package/examples/framework.yaml +1 -2
  101. package/examples/grades.yaml +1 -3
  102. package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
  103. package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
  104. package/examples/questions/behaviours/precise_communication.yaml +1 -2
  105. package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
  106. package/examples/questions/behaviours/systems_thinking.yaml +1 -2
  107. package/examples/questions/skills/architecture_design.yaml +1 -2
  108. package/examples/questions/skills/cloud_platforms.yaml +1 -2
  109. package/examples/questions/skills/code_quality.yaml +1 -2
  110. package/examples/questions/skills/data_modeling.yaml +1 -2
  111. package/examples/questions/skills/devops.yaml +1 -2
  112. package/examples/questions/skills/full_stack_development.yaml +1 -2
  113. package/examples/questions/skills/sre_practices.yaml +1 -2
  114. package/examples/questions/skills/stakeholder_management.yaml +1 -2
  115. package/examples/questions/skills/team_collaboration.yaml +1 -2
  116. package/examples/questions/skills/technical_writing.yaml +1 -2
  117. package/examples/self-assessments.yaml +1 -3
  118. package/examples/stages.yaml +101 -46
  119. package/examples/tracks/_index.yaml +0 -1
  120. package/examples/tracks/platform.yaml +8 -13
  121. package/examples/tracks/sre.yaml +8 -18
  122. package/examples/vscode-settings.yaml +2 -7
  123. package/package.json +9 -3
  124. package/templates/agent.template.md +65 -0
  125. package/templates/skill.template.md +28 -0
  126. package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
  127. package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
  128. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
  129. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
  130. package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
  131. package/examples/tracks/manager.yaml +0 -53
@@ -8,20 +8,21 @@
8
8
  * or --all-stages (default) for all stages.
9
9
  *
10
10
  * Usage:
11
- * npx pathway agent <discipline> <track> [--output=PATH] [--preview]
12
- * npx pathway agent <discipline> <track> --stage=plan
13
- * npx pathway agent <discipline> <track> --all-stages
11
+ * npx pathway agent <discipline> [--track=<track>] [--output=PATH] [--preview]
12
+ * npx pathway agent <discipline> --track=<track> --stage=plan
13
+ * npx pathway agent <discipline> --track=<track> --all-stages
14
14
  * npx pathway agent --list
15
15
  *
16
16
  * Examples:
17
- * npx pathway agent software_engineering platform
18
- * npx pathway agent software_engineering platform --stage=plan
19
- * npx pathway agent software_engineering platform --preview
17
+ * npx pathway agent software_engineering --track=platform
18
+ * npx pathway agent software_engineering --track=platform --stage=plan
19
+ * npx pathway agent software_engineering --track=platform --preview
20
20
  */
21
21
 
22
22
  import { writeFile, mkdir, readFile } from "fs/promises";
23
23
  import { join, dirname } from "path";
24
24
  import { existsSync } from "fs";
25
+ import { stringify as stringifyYaml } from "yaml";
25
26
  import { loadAgentData, loadSkillsWithAgentData } from "../model/loader.js";
26
27
  import {
27
28
  generateStageAgentProfile,
@@ -37,6 +38,10 @@ import {
37
38
  } from "../formatters/agent/profile.js";
38
39
  import { formatAgentSkill } from "../formatters/agent/skill.js";
39
40
  import { formatError, formatSuccess } from "../lib/cli-output.js";
41
+ import {
42
+ loadAgentTemplate,
43
+ loadSkillTemplate,
44
+ } from "../lib/template-loader.js";
40
45
 
41
46
  /**
42
47
  * Ensure directory exists for a file path
@@ -72,6 +77,64 @@ async function generateVSCodeSettings(baseDir, vscodeSettings) {
72
77
  console.log(formatSuccess(`Updated: ${settingsPath}`));
73
78
  }
74
79
 
80
+ /**
81
+ * Generate devcontainer.json from template with VS Code settings embedded
82
+ * @param {string} baseDir - Base output directory
83
+ * @param {Object} devcontainerConfig - Devcontainer config loaded from data
84
+ * @param {Object} vscodeSettings - VS Code settings to embed in customizations
85
+ */
86
+ async function generateDevcontainer(
87
+ baseDir,
88
+ devcontainerConfig,
89
+ vscodeSettings,
90
+ ) {
91
+ if (!devcontainerConfig || Object.keys(devcontainerConfig).length === 0) {
92
+ return;
93
+ }
94
+
95
+ const devcontainerPath = join(baseDir, ".devcontainer", "devcontainer.json");
96
+
97
+ // Build devcontainer.json with VS Code settings embedded
98
+ const devcontainer = {
99
+ ...devcontainerConfig,
100
+ customizations: {
101
+ vscode: {
102
+ settings: vscodeSettings,
103
+ },
104
+ },
105
+ };
106
+
107
+ await ensureDir(devcontainerPath);
108
+ await writeFile(
109
+ devcontainerPath,
110
+ JSON.stringify(devcontainer, null, 2) + "\n",
111
+ "utf-8",
112
+ );
113
+ console.log(formatSuccess(`Created: ${devcontainerPath}`));
114
+ }
115
+
116
+ /**
117
+ * Generate GitHub Actions workflow for Copilot Coding Agent setup steps
118
+ * @param {string} baseDir - Base output directory
119
+ * @param {Object|null} copilotSetupSteps - Workflow config loaded from data
120
+ */
121
+ async function generateCopilotSetupSteps(baseDir, copilotSetupSteps) {
122
+ if (!copilotSetupSteps) {
123
+ return;
124
+ }
125
+
126
+ const workflowPath = join(
127
+ baseDir,
128
+ ".github",
129
+ "workflows",
130
+ "copilot-setup-steps.yml",
131
+ );
132
+
133
+ await ensureDir(workflowPath);
134
+ await writeFile(workflowPath, stringifyYaml(copilotSetupSteps), "utf-8");
135
+ console.log(formatSuccess(`Created: ${workflowPath}`));
136
+ }
137
+
75
138
  /**
76
139
  * Show agent summary with stats
77
140
  * @param {Object} data - Pathway data
@@ -181,10 +244,11 @@ function listAgentCombinations(data, agentData, verbose = false) {
181
244
  * Write agent profile to file
182
245
  * @param {Object} profile - Generated profile
183
246
  * @param {string} baseDir - Base output directory
247
+ * @param {string} template - Mustache template for agent profile
184
248
  */
185
- async function writeProfile(profile, baseDir) {
249
+ async function writeProfile(profile, baseDir, template) {
186
250
  const profilePath = join(baseDir, ".github", "agents", profile.filename);
187
- const profileContent = formatAgentProfile(profile);
251
+ const profileContent = formatAgentProfile(profile, template);
188
252
  await ensureDir(profilePath);
189
253
  await writeFile(profilePath, profileContent, "utf-8");
190
254
  console.log(formatSuccess(`Created: ${profilePath}`));
@@ -195,8 +259,9 @@ async function writeProfile(profile, baseDir) {
195
259
  * Write skill files
196
260
  * @param {Array} skills - Generated skills
197
261
  * @param {string} baseDir - Base output directory
262
+ * @param {string} template - Mustache template for skills
198
263
  */
199
- async function writeSkills(skills, baseDir) {
264
+ async function writeSkills(skills, baseDir, template) {
200
265
  for (const skill of skills) {
201
266
  const skillPath = join(
202
267
  baseDir,
@@ -205,7 +270,7 @@ async function writeSkills(skills, baseDir) {
205
270
  skill.dirname,
206
271
  "SKILL.md",
207
272
  );
208
- const skillContent = formatAgentSkill(skill);
273
+ const skillContent = formatAgentSkill(skill, template);
209
274
  await ensureDir(skillPath);
210
275
  await writeFile(skillPath, skillContent, "utf-8");
211
276
  console.log(formatSuccess(`Created: ${skillPath}`));
@@ -216,7 +281,7 @@ async function writeSkills(skills, baseDir) {
216
281
  * Run the agent command
217
282
  * @param {Object} params - Command parameters
218
283
  * @param {Object} params.data - Loaded pathway data
219
- * @param {string[]} params.args - Command arguments [discipline_id, track_id]
284
+ * @param {string[]} params.args - Command arguments [discipline_id]
220
285
  * @param {Object} params.options - Command options
221
286
  * @param {string} params.dataDir - Path to data directory
222
287
  */
@@ -237,11 +302,14 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
237
302
  return;
238
303
  }
239
304
 
240
- const [disciplineId, trackId] = args;
305
+ const [disciplineId] = args;
306
+ const trackId = options.track;
241
307
 
242
- if (!disciplineId || !trackId) {
308
+ if (!disciplineId) {
243
309
  console.error(
244
- formatError("Usage: npx pathway agent <discipline_id> <track_id>"),
310
+ formatError(
311
+ "Usage: npx pathway agent <discipline_id> [--track=<track_id>]",
312
+ ),
245
313
  );
246
314
  console.error(
247
315
  "\nRun 'npx pathway agent --list' to see available combinations.",
@@ -251,7 +319,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
251
319
 
252
320
  // Look up human definitions
253
321
  const humanDiscipline = data.disciplines.find((d) => d.id === disciplineId);
254
- const humanTrack = data.tracks.find((t) => t.id === trackId);
322
+ const humanTrack = trackId ? data.tracks.find((t) => t.id === trackId) : null;
255
323
 
256
324
  if (!humanDiscipline) {
257
325
  console.error(formatError(`Unknown discipline: ${disciplineId}`));
@@ -262,7 +330,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
262
330
  process.exit(1);
263
331
  }
264
332
 
265
- if (!humanTrack) {
333
+ if (trackId && !humanTrack) {
266
334
  console.error(formatError(`Unknown track: ${trackId}`));
267
335
  console.error("\nAvailable tracks:");
268
336
  for (const t of data.tracks) {
@@ -275,7 +343,9 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
275
343
  const agentDiscipline = agentData.disciplines.find(
276
344
  (d) => d.id === disciplineId,
277
345
  );
278
- const agentTrack = agentData.tracks.find((t) => t.id === trackId);
346
+ const agentTrack = trackId
347
+ ? agentData.tracks.find((t) => t.id === trackId)
348
+ : null;
279
349
 
280
350
  if (!agentDiscipline) {
281
351
  console.error(
@@ -288,7 +358,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
288
358
  process.exit(1);
289
359
  }
290
360
 
291
- if (!agentTrack) {
361
+ if (trackId && !agentTrack) {
292
362
  console.error(formatError(`No agent definition for track: ${trackId}`));
293
363
  console.error("\nAgent definitions exist for:");
294
364
  for (const t of agentData.tracks) {
@@ -346,8 +416,16 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
346
416
  return;
347
417
  }
348
418
 
349
- await writeProfile(profile, baseDir);
419
+ // Load templates only when writing files
420
+ const agentTemplate = await loadAgentTemplate(dataDir);
421
+ await writeProfile(profile, baseDir, agentTemplate);
350
422
  await generateVSCodeSettings(baseDir, agentData.vscodeSettings);
423
+ await generateDevcontainer(
424
+ baseDir,
425
+ agentData.devcontainer,
426
+ agentData.vscodeSettings,
427
+ );
428
+ await generateCopilotSetupSteps(baseDir, agentData.copilotSetupSteps);
351
429
  console.log("");
352
430
  console.log(
353
431
  formatSuccess(`Generated stage agent: ${profile.frontmatter.name}`),
@@ -415,11 +493,21 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
415
493
  return;
416
494
  }
417
495
 
496
+ // Load templates only when writing files
497
+ const agentTemplate = await loadAgentTemplate(dataDir);
498
+ const skillTemplate = await loadSkillTemplate(dataDir);
499
+
418
500
  for (const profile of profiles) {
419
- await writeProfile(profile, baseDir);
501
+ await writeProfile(profile, baseDir, agentTemplate);
420
502
  }
421
- await writeSkills(skillFiles, baseDir);
503
+ await writeSkills(skillFiles, baseDir, skillTemplate);
422
504
  await generateVSCodeSettings(baseDir, agentData.vscodeSettings);
505
+ await generateDevcontainer(
506
+ baseDir,
507
+ agentData.devcontainer,
508
+ agentData.vscodeSettings,
509
+ );
510
+ await generateCopilotSetupSteps(baseDir, agentData.copilotSetupSteps);
423
511
 
424
512
  console.log("");
425
513
  console.log(formatSuccess(`Generated ${profiles.length} agents:`));
@@ -157,8 +157,8 @@ function handleDetail({
157
157
  * @param {Object} config - Command configuration
158
158
  * @param {string} config.commandName - Command name for error messages
159
159
  * @param {string[]} config.requiredArgs - Array of required argument names
160
- * @param {Function} config.findEntities - Function to find entities: (data, args) => entities object
161
- * @param {Function} config.validateEntities - Function to validate entities: (entities, data) => error string | null
160
+ * @param {Function} config.findEntities - Function to find entities: (data, args, options) => entities object
161
+ * @param {Function} config.validateEntities - Function to validate entities: (entities, data, options) => error string | null
162
162
  * @param {Function} config.presenter - Function to present data: (entities, data, options) => view
163
163
  * @param {Function} config.formatter - Function to format output: (view, options, data) => void
164
164
  * @param {string} [config.usageExample] - Optional usage example
@@ -184,7 +184,7 @@ export function createCompositeCommand({
184
184
  }
185
185
 
186
186
  const entities = findEntities(data, args, options);
187
- const validationError = validateEntities(entities, data);
187
+ const validationError = validateEntities(entities, data, options);
188
188
 
189
189
  if (validationError) {
190
190
  console.error(validationError);
@@ -2,6 +2,11 @@
2
2
  * Interview CLI Command
3
3
  *
4
4
  * Generates and displays interview questions in the terminal.
5
+ *
6
+ * Usage:
7
+ * npx pathway interview <discipline> <grade> # Interview for trackless job
8
+ * npx pathway interview <discipline> <grade> --track=<track> # Interview with track
9
+ * npx pathway interview <discipline> <grade> --track=<track> --type=short
5
10
  */
6
11
 
7
12
  import { createCompositeCommand } from "./command-factory.js";
@@ -22,7 +27,7 @@ function formatInterview(view, options) {
22
27
 
23
28
  export const runInterviewCommand = createCompositeCommand({
24
29
  commandName: "interview",
25
- requiredArgs: ["discipline_id", "track_id", "grade_id"],
30
+ requiredArgs: ["discipline_id", "grade_id"],
26
31
  findEntities: (data, args, options) => {
27
32
  const interviewType = options.type || "full";
28
33
 
@@ -34,20 +39,22 @@ export const runInterviewCommand = createCompositeCommand({
34
39
 
35
40
  return {
36
41
  discipline: data.disciplines.find((d) => d.id === args[0]),
37
- track: data.tracks.find((t) => t.id === args[1]),
38
- grade: data.grades.find((g) => g.id === args[2]),
42
+ grade: data.grades.find((g) => g.id === args[1]),
43
+ track: options.track
44
+ ? data.tracks.find((t) => t.id === options.track)
45
+ : null,
39
46
  interviewType,
40
47
  };
41
48
  },
42
- validateEntities: (entities, _data) => {
49
+ validateEntities: (entities, _data, options) => {
43
50
  if (!entities.discipline) {
44
51
  return `Discipline not found: ${entities.discipline}`;
45
52
  }
46
53
  if (!entities.grade) {
47
54
  return `Grade not found: ${entities.grade}`;
48
55
  }
49
- if (!entities.track) {
50
- return `Track not found: ${entities.track}`;
56
+ if (options.track && !entities.track) {
57
+ return `Track not found: ${options.track}`;
51
58
  }
52
59
  return null;
53
60
  },
@@ -64,5 +71,5 @@ export const runInterviewCommand = createCompositeCommand({
64
71
  formatter: (view, options, data) =>
65
72
  formatInterview(view, { ...options, framework: data.framework }),
66
73
  usageExample:
67
- "npx pathway interview software_engineering platform L4 --type=short",
74
+ "npx pathway interview software_engineering L4 --track=platform --type=short",
68
75
  });
@@ -4,11 +4,12 @@
4
4
  * Generates and displays job definitions in the terminal.
5
5
  *
6
6
  * Usage:
7
- * npx pathway job # Summary with stats
8
- * npx pathway job --list # All valid combinations (for piping)
9
- * npx pathway job <discipline> <track> <grade> # Detail view
10
- * npx pathway job se platform L3 --checklist=code_to_review # Show checklist for handoff
11
- * npx pathway job --validate # Validation checks
7
+ * npx pathway job # Summary with stats
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 se L3 --track=platform --checklist=code # Show checklist for handoff
12
+ * npx pathway job --validate # Validation checks
12
13
  */
13
14
 
14
15
  import { prepareJobDetail } from "../model/job.js";
@@ -47,10 +48,14 @@ export async function runJobCommand({ data, args, options }) {
47
48
  validationRules: data.framework.validationRules,
48
49
  });
49
50
 
50
- // --list: Output clean lines for piping
51
+ // --list: Output clean lines for piping (discipline grade track format)
51
52
  if (options.list) {
52
53
  for (const job of jobs) {
53
- console.log(`${job.discipline.id} ${job.track.id} ${job.grade.id}`);
54
+ if (job.track) {
55
+ console.log(`${job.discipline.id} ${job.grade.id} ${job.track.id}`);
56
+ } else {
57
+ console.log(`${job.discipline.id} ${job.grade.id}`);
58
+ }
54
59
  }
55
60
  return;
56
61
  }
@@ -71,22 +76,29 @@ export async function runJobCommand({ data, args, options }) {
71
76
  console.log(`\nTotal: ${jobs.length} valid job combinations`);
72
77
  console.log(`\nRun 'npx pathway job --list' for all combinations`);
73
78
  console.log(
74
- `Run 'npx pathway job <discipline> <track> <grade>' for details\n`,
79
+ `Run 'npx pathway job <discipline> <grade> [--track=<track>]' for details\n`,
75
80
  );
76
81
  return;
77
82
  }
78
83
 
79
- // Handle job detail view
80
- if (args.length < 3) {
81
- console.error("Usage: npx pathway job <discipline> <track> <grade>");
84
+ // Handle job detail view - requires discipline and grade
85
+ if (args.length < 2) {
86
+ console.error(
87
+ "Usage: npx pathway job <discipline> <grade> [--track=<track>]",
88
+ );
82
89
  console.error(" npx pathway job --list");
83
- console.error("Example: npx pathway job software_engineering platform L4");
90
+ console.error("Example: npx pathway job software_engineering L4");
91
+ console.error(
92
+ "Example: npx pathway job software_engineering L4 --track=platform",
93
+ );
84
94
  process.exit(1);
85
95
  }
86
96
 
87
97
  const discipline = data.disciplines.find((d) => d.id === args[0]);
88
- const track = data.tracks.find((t) => t.id === args[1]);
89
- const grade = data.grades.find((g) => g.id === args[2]);
98
+ const grade = data.grades.find((g) => g.id === args[1]);
99
+ const track = options.track
100
+ ? data.tracks.find((t) => t.id === options.track)
101
+ : null;
90
102
 
91
103
  if (!discipline) {
92
104
  console.error(`Discipline not found: ${args[0]}`);
@@ -94,15 +106,15 @@ export async function runJobCommand({ data, args, options }) {
94
106
  process.exit(1);
95
107
  }
96
108
 
97
- if (!track) {
98
- console.error(`Track not found: ${args[1]}`);
99
- console.error(`Available: ${data.tracks.map((t) => t.id).join(", ")}`);
109
+ if (!grade) {
110
+ console.error(`Grade not found: ${args[1]}`);
111
+ console.error(`Available: ${data.grades.map((g) => g.id).join(", ")}`);
100
112
  process.exit(1);
101
113
  }
102
114
 
103
- if (!grade) {
104
- console.error(`Grade not found: ${args[2]}`);
105
- console.error(`Available: ${data.grades.map((g) => g.id).join(", ")}`);
115
+ if (options.track && !track) {
116
+ console.error(`Track not found: ${options.track}`);
117
+ console.error(`Available: ${data.tracks.map((t) => t.id).join(", ")}`);
106
118
  process.exit(1);
107
119
  }
108
120
 
@@ -126,28 +138,30 @@ export async function runJobCommand({ data, args, options }) {
126
138
  return;
127
139
  }
128
140
 
129
- // --checklist: Show checklist for a specific handoff
141
+ // --checklist: Show checklist for a specific stage
130
142
  if (options.checklist) {
131
- const validHandoffs = ["plan_to_code", "code_to_review"];
132
- if (!validHandoffs.includes(options.checklist)) {
133
- console.error(`Invalid handoff: ${options.checklist}`);
134
- console.error(`Available: ${validHandoffs.join(", ")}`);
143
+ const validStages = ["plan", "code"];
144
+ if (!validStages.includes(options.checklist)) {
145
+ console.error(`Invalid stage: ${options.checklist}`);
146
+ console.error(`Available: ${validStages.join(", ")}`);
135
147
  process.exit(1);
136
148
  }
137
149
 
138
150
  const checklist = deriveChecklist({
139
- handoff: options.checklist,
151
+ stageId: options.checklist,
140
152
  skillMatrix: view.skillMatrix,
153
+ skills: data.skills,
141
154
  capabilities: data.capabilities,
142
155
  });
143
156
 
144
157
  if (checklist.length === 0) {
145
- console.log(`\nNo checklist items for ${options.checklist}\n`);
158
+ console.log(`\nNo checklist items for ${options.checklist} stage\n`);
146
159
  return;
147
160
  }
148
161
 
149
- const handoffLabel = options.checklist.replace(/_/g, " → ");
150
- console.log(`\n# ${view.title} ${handoffLabel}\n`);
162
+ const stageLabel =
163
+ options.checklist.charAt(0).toUpperCase() + options.checklist.slice(1);
164
+ console.log(`\n# ${view.title} — ${stageLabel} Stage Checklist\n`);
151
165
  console.log(formatChecklistMarkdown(checklist));
152
166
  console.log("");
153
167
  return;
@@ -2,6 +2,11 @@
2
2
  * Progress CLI Command
3
3
  *
4
4
  * Shows career progression analysis in the terminal.
5
+ *
6
+ * Usage:
7
+ * npx pathway progress <discipline> <grade> # Progress for trackless job
8
+ * npx pathway progress <discipline> <grade> --track=<track> # Progress with track
9
+ * npx pathway progress <discipline> <from_grade> --compare=<to_grade> # Compare grades
5
10
  */
6
11
 
7
12
  import { createCompositeCommand } from "./command-factory.js";
@@ -21,11 +26,13 @@ function formatProgress(view) {
21
26
 
22
27
  export const runProgressCommand = createCompositeCommand({
23
28
  commandName: "progress",
24
- requiredArgs: ["discipline_id", "track_id", "grade_id"],
29
+ requiredArgs: ["discipline_id", "grade_id"],
25
30
  findEntities: (data, args, options) => {
26
31
  const discipline = data.disciplines.find((d) => d.id === args[0]);
27
- const track = data.tracks.find((t) => t.id === args[1]);
28
- const grade = data.grades.find((g) => g.id === args[2]);
32
+ const grade = data.grades.find((g) => g.id === args[1]);
33
+ const track = options.track
34
+ ? data.tracks.find((t) => t.id === options.track)
35
+ : null;
29
36
 
30
37
  let targetGrade;
31
38
  if (options.compare) {
@@ -44,15 +51,15 @@ export const runProgressCommand = createCompositeCommand({
44
51
 
45
52
  return { discipline, grade, track, targetGrade };
46
53
  },
47
- validateEntities: (entities, _data) => {
54
+ validateEntities: (entities, _data, options) => {
48
55
  if (!entities.discipline) {
49
56
  return `Discipline not found`;
50
57
  }
51
58
  if (!entities.grade) {
52
59
  return `Grade not found`;
53
60
  }
54
- if (!entities.track) {
55
- return `Track not found`;
61
+ if (options.track && !entities.track) {
62
+ return `Track not found: ${options.track}`;
56
63
  }
57
64
  if (!entities.targetGrade) {
58
65
  return `Target grade not found`;
@@ -73,5 +80,5 @@ export const runProgressCommand = createCompositeCommand({
73
80
  }),
74
81
  formatter: formatProgress,
75
82
  usageExample:
76
- "npx pathway progress software_engineering platform L3 --compare=L4",
83
+ "npx pathway progress software_engineering L3 --track=platform --compare=L4",
77
84
  });
@@ -15,6 +15,7 @@ import { loadFrameworkConfig } from "../model/loader.js";
15
15
  const __filename = fileURLToPath(import.meta.url);
16
16
  const __dirname = dirname(__filename);
17
17
  const publicDir = join(__dirname, "..");
18
+ const rootDir = join(__dirname, "../..");
18
19
 
19
20
  const MIME_TYPES = {
20
21
  ".html": "text/html; charset=utf-8",
@@ -23,6 +24,7 @@ const MIME_TYPES = {
23
24
  ".yaml": "text/yaml; charset=utf-8",
24
25
  ".yml": "text/yaml; charset=utf-8",
25
26
  ".json": "application/json; charset=utf-8",
27
+ ".md": "text/markdown; charset=utf-8",
26
28
  ".svg": "image/svg+xml",
27
29
  ".png": "image/png",
28
30
  ".ico": "image/x-icon",
@@ -113,6 +115,9 @@ export async function runServeCommand({ dataDir, options }) {
113
115
  if (pathname.startsWith("/data/")) {
114
116
  // Serve from user's data directory
115
117
  filePath = join(dataDir, pathname.slice(6));
118
+ } else if (pathname.startsWith("/templates/")) {
119
+ // Serve from templates directory
120
+ filePath = join(rootDir, pathname);
116
121
  } else if (pathname === "/" || pathname === "") {
117
122
  // Serve index.html for root
118
123
  filePath = join(publicDir, "index.html");
@@ -58,18 +58,8 @@ function formatDetail(viewAndContext, _framework) {
58
58
  const emoji = getStageEmoji(stages, stage.id);
59
59
 
60
60
  console.log(formatHeader(`\n${emoji} ${view.name}\n`));
61
- console.log(`Mode: ${view.modeBadge}\n`);
62
61
  console.log(`${view.description}\n`);
63
62
 
64
- // Tools
65
- if (view.tools.length > 0) {
66
- console.log(formatSubheader("Tools\n"));
67
- for (const tool of view.tools) {
68
- console.log(formatBullet(`${tool.icon} ${tool.label}`, 1));
69
- }
70
- console.log();
71
- }
72
-
73
63
  // Entry criteria
74
64
  if (view.entryCriteria.length > 0) {
75
65
  console.log(formatSubheader("Entry Criteria\n"));
@@ -12,13 +12,13 @@
12
12
 
13
13
  import { createEntityCommand } from "./command-factory.js";
14
14
  import { trackToMarkdown } from "../formatters/track/markdown.js";
15
- import { sortTracksByType } from "../formatters/track/shared.js";
15
+ import { sortTracksByName } from "../formatters/track/shared.js";
16
16
  import { formatTable } from "../lib/cli-output.js";
17
17
  import { getConceptEmoji } from "../model/levels.js";
18
18
 
19
19
  /**
20
20
  * Format track summary output
21
- * @param {Array} tracks - Raw track entities (already sorted by type)
21
+ * @param {Array} tracks - Raw track entities
22
22
  * @param {Object} data - Full data context
23
23
  */
24
24
  function formatSummary(tracks, data) {
@@ -28,14 +28,11 @@ function formatSummary(tracks, data) {
28
28
  console.log(`\n${emoji} Tracks\n`);
29
29
 
30
30
  const rows = tracks.map((t) => {
31
- const types = [];
32
- if (t.isProfessional) types.push("P");
33
- if (t.isManagement) types.push("M");
34
31
  const modCount = Object.keys(t.skillModifiers || {}).length;
35
- return [t.id, t.name, types.join("/") || "-", modCount];
32
+ return [t.id, t.name, modCount];
36
33
  });
37
34
 
38
- console.log(formatTable(["ID", "Name", "Type", "Modifiers"], rows));
35
+ console.log(formatTable(["ID", "Name", "Modifiers"], rows));
39
36
  console.log(`\nTotal: ${tracks.length} tracks`);
40
37
  console.log(`\nRun 'npx pathway track --list' for IDs`);
41
38
  console.log(`Run 'npx pathway track <id>' for details\n`);
@@ -63,7 +60,7 @@ export const runTrackCommand = createEntityCommand({
63
60
  behaviours: data.behaviours,
64
61
  disciplines: data.disciplines,
65
62
  }),
66
- sortItems: sortTracksByType,
63
+ sortItems: sortTracksByName,
67
64
  formatSummary,
68
65
  formatDetail,
69
66
  emoji: "🛤️",