@forwardimpact/pathway 0.2.0 → 0.4.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 (62) hide show
  1. package/app/commands/agent.js +20 -20
  2. package/app/commands/index.js +4 -3
  3. package/app/commands/job.js +9 -4
  4. package/app/commands/skill.js +56 -2
  5. package/app/commands/tool.js +112 -0
  6. package/app/components/builder.js +6 -3
  7. package/app/components/checklist.js +6 -4
  8. package/app/components/markdown-textarea.js +132 -0
  9. package/app/css/components/forms.css +45 -0
  10. package/app/css/components/layout.css +12 -0
  11. package/app/css/components/surfaces.css +22 -0
  12. package/app/css/pages/detail.css +50 -0
  13. package/app/css/pages/job-builder.css +0 -42
  14. package/app/formatters/agent/profile.js +61 -120
  15. package/app/formatters/agent/skill.js +48 -60
  16. package/app/formatters/grade/dom.js +2 -4
  17. package/app/formatters/job/description.js +74 -82
  18. package/app/formatters/job/dom.js +45 -179
  19. package/app/formatters/job/markdown.js +17 -13
  20. package/app/formatters/shared.js +65 -2
  21. package/app/formatters/skill/dom.js +57 -2
  22. package/app/formatters/skill/markdown.js +18 -0
  23. package/app/formatters/skill/shared.js +12 -4
  24. package/app/formatters/stage/microdata.js +1 -1
  25. package/app/formatters/stage/shared.js +1 -1
  26. package/app/formatters/tool/shared.js +72 -0
  27. package/app/handout-main.js +7 -7
  28. package/app/handout.html +7 -0
  29. package/app/index.html +10 -3
  30. package/app/lib/card-mappers.js +64 -17
  31. package/app/lib/form-controls.js +64 -1
  32. package/app/lib/render.js +12 -1
  33. package/app/lib/template-loader.js +9 -0
  34. package/app/lib/yaml-loader.js +12 -1
  35. package/app/main.js +4 -0
  36. package/app/model/agent.js +26 -18
  37. package/app/model/derivation.js +3 -3
  38. package/app/model/levels.js +2 -0
  39. package/app/model/loader.js +12 -1
  40. package/app/model/validation.js +74 -8
  41. package/app/pages/agent-builder.js +8 -5
  42. package/app/pages/job.js +28 -4
  43. package/app/pages/landing.js +34 -14
  44. package/app/pages/progress.js +6 -5
  45. package/app/pages/self-assessment.js +10 -8
  46. package/app/pages/skill.js +5 -17
  47. package/app/pages/stage.js +10 -6
  48. package/app/pages/tool.js +50 -0
  49. package/app/slides/index.js +25 -25
  50. package/app/slides.html +7 -0
  51. package/bin/pathway.js +41 -27
  52. package/examples/capabilities/business.yaml +17 -17
  53. package/examples/capabilities/delivery.yaml +51 -36
  54. package/examples/capabilities/reliability.yaml +127 -114
  55. package/examples/capabilities/scale.yaml +38 -36
  56. package/examples/disciplines/engineering_management.yaml +1 -1
  57. package/examples/framework.yaml +12 -0
  58. package/examples/grades.yaml +18 -19
  59. package/examples/self-assessments.yaml +1 -1
  60. package/package.json +1 -1
  61. package/templates/job.template.md +47 -0
  62. package/templates/skill.template.md +31 -12
package/app/pages/job.js CHANGED
@@ -2,17 +2,32 @@
2
2
  * Job detail page with visualizations
3
3
  */
4
4
 
5
- import { render } from "../lib/render.js";
5
+ import { render, div, p } from "../lib/render.js";
6
6
  import { getState } from "../lib/state.js";
7
7
  import { renderError } from "../components/error-page.js";
8
8
  import { prepareJobDetail } from "../model/job.js";
9
9
  import { jobToDOM } from "../formatters/job/dom.js";
10
10
 
11
+ /** @type {string|null} Cached job template */
12
+ let jobTemplateCache = null;
13
+
14
+ /**
15
+ * Load job template with caching
16
+ * @returns {Promise<string>}
17
+ */
18
+ async function getJobTemplate() {
19
+ if (!jobTemplateCache) {
20
+ const response = await fetch("./templates/job.template.md");
21
+ jobTemplateCache = await response.text();
22
+ }
23
+ return jobTemplateCache;
24
+ }
25
+
11
26
  /**
12
27
  * Render job detail page
13
28
  * @param {Object} params - Route params
14
29
  */
15
- export function renderJobDetail(params) {
30
+ export async function renderJobDetail(params) {
16
31
  const { discipline: disciplineId, grade: gradeId, track: trackId } = params;
17
32
  const { data } = getState();
18
33
 
@@ -63,7 +78,16 @@ export function renderJobDetail(params) {
63
78
  return;
64
79
  }
65
80
 
66
- // Format using DOM formatter
67
- const page = jobToDOM(jobView, { discipline, grade, track });
81
+ // Show loading while fetching template
82
+ render(
83
+ div(
84
+ { className: "job-detail-page" },
85
+ div({ className: "loading" }, p({}, "Loading...")),
86
+ ),
87
+ );
88
+
89
+ // Load template and format
90
+ const jobTemplate = await getJobTemplate();
91
+ const page = jobToDOM(jobView, { discipline, grade, track, jobTemplate });
68
92
  render(page);
69
93
  }
@@ -7,6 +7,7 @@ import { getState } from "../lib/state.js";
7
7
  import { createStatCard } from "../components/card.js";
8
8
  import { groupSkillsByCapability, getConceptEmoji } from "../model/levels.js";
9
9
  import { getStageEmoji } from "../formatters/stage/shared.js";
10
+ import { aggregateTools } from "../formatters/tool/shared.js";
10
11
 
11
12
  /**
12
13
  * Create lifecycle flow visualization for landing page
@@ -43,6 +44,7 @@ export function renderLanding() {
43
44
  // Calculate stats using centralized capability ordering
44
45
  const skillsByCapability = groupSkillsByCapability(data.skills);
45
46
  const capabilityCount = Object.keys(skillsByCapability).length;
47
+ const tools = aggregateTools(data.skills);
46
48
 
47
49
  const page = div(
48
50
  { className: "landing-page" },
@@ -83,7 +85,7 @@ export function renderLanding() {
83
85
 
84
86
  // Stats grid
85
87
  div(
86
- { className: "grid grid-6" },
88
+ { className: "grid grid-4" },
87
89
  createStatCard({
88
90
  value: data.disciplines.length,
89
91
  label: "Disciplines",
@@ -99,28 +101,41 @@ export function renderLanding() {
99
101
  label: "Tracks",
100
102
  href: "/track",
101
103
  }),
104
+ createStatCard({
105
+ value: data.behaviours.length,
106
+ label: "Behaviours",
107
+ href: "/behaviour",
108
+ }),
102
109
  createStatCard({
103
110
  value: data.skills.length,
104
111
  label: "Skills",
105
112
  href: "/skill",
106
113
  }),
107
114
  createStatCard({
108
- value: data.behaviours.length,
109
- label: "Behaviours",
110
- href: "/behaviour",
115
+ value: data.drivers.length,
116
+ label: "Drivers",
117
+ href: "/driver",
111
118
  }),
112
119
  createStatCard({
113
120
  value: stages.length,
114
121
  label: "Stages",
115
122
  href: "/stage",
116
123
  }),
124
+ createStatCard({
125
+ value: tools.length,
126
+ label: "Tools",
127
+ href: "/tool",
128
+ }),
117
129
  ),
118
130
 
119
131
  // Lifecycle flow visualization
120
132
  stages.length > 0
121
133
  ? div(
122
134
  { className: "section section-detail" },
123
- h2({ className: "section-title" }, "🔄 Engineering Lifecycle"),
135
+ h2(
136
+ { className: "section-title" },
137
+ `${getConceptEmoji(framework, "stage")} Engineering Lifecycle`,
138
+ ),
124
139
  p(
125
140
  { className: "text-muted", style: "margin-bottom: 1rem" },
126
141
  "The three stages of engineering work, from planning through review.",
@@ -134,7 +149,7 @@ export function renderLanding() {
134
149
  { className: "section section-detail" },
135
150
  h2({ className: "section-title" }, "Explore the Framework"),
136
151
  div(
137
- { className: "grid grid-3" },
152
+ { className: "grid grid-4" },
138
153
  createQuickLinkCard(
139
154
  `${getConceptEmoji(framework, "discipline")} ${framework.entityDefinitions.discipline.title}`,
140
155
  `${data.disciplines.length} ${framework.entityDefinitions.discipline.title.toLowerCase()} — ${framework.entityDefinitions.discipline.description.trim().split("\n")[0]}`,
@@ -150,26 +165,31 @@ export function renderLanding() {
150
165
  `${data.tracks.length} ${framework.entityDefinitions.track.title.toLowerCase()} — ${framework.entityDefinitions.track.description.trim().split("\n")[0]}`,
151
166
  "/track",
152
167
  ),
153
- createQuickLinkCard(
154
- `${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
155
- `${data.skills.length} ${framework.entityDefinitions.skill.title.toLowerCase()} across ${capabilityCount} capabilities — ${framework.entityDefinitions.skill.description.trim().split("\n")[0]}`,
156
- "/skill",
157
- ),
158
168
  createQuickLinkCard(
159
169
  `${getConceptEmoji(framework, "behaviour")} ${framework.entityDefinitions.behaviour.title}`,
160
170
  `${data.behaviours.length} ${framework.entityDefinitions.behaviour.title.toLowerCase()} — ${framework.entityDefinitions.behaviour.description.trim().split("\n")[0]}`,
161
171
  "/behaviour",
162
172
  ),
163
173
  createQuickLinkCard(
164
- "🔄 Stages",
165
- `${stages.length} stages The engineering lifecycle from planning through review.`,
166
- "/stage",
174
+ `${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
175
+ `${data.skills.length} ${framework.entityDefinitions.skill.title.toLowerCase()} across ${capabilityCount} capabilities ${framework.entityDefinitions.skill.description.trim().split("\n")[0]}`,
176
+ "/skill",
167
177
  ),
168
178
  createQuickLinkCard(
169
179
  `${getConceptEmoji(framework, "driver")} ${framework.entityDefinitions.driver.title}`,
170
180
  `${data.drivers.length} ${framework.entityDefinitions.driver.title.toLowerCase()} — ${framework.entityDefinitions.driver.description.trim().split("\n")[0]}`,
171
181
  "/driver",
172
182
  ),
183
+ createQuickLinkCard(
184
+ `${getConceptEmoji(framework, "stage")} ${framework.entityDefinitions.stage.title}`,
185
+ `${stages.length} ${framework.entityDefinitions.stage.title.toLowerCase()} — ${framework.entityDefinitions.stage.description.trim().split("\n")[0]}`,
186
+ "/stage",
187
+ ),
188
+ createQuickLinkCard(
189
+ `${getConceptEmoji(framework, "tool")} ${framework.entityDefinitions.tool.title}`,
190
+ `${tools.length} ${framework.entityDefinitions.tool.title.toLowerCase()} — ${framework.entityDefinitions.tool.description.trim().split("\n")[0]}`,
191
+ "/tool",
192
+ ),
173
193
  ),
174
194
  ),
175
195
 
@@ -13,7 +13,10 @@ import {
13
13
  } from "../components/comparison-radar.js";
14
14
  import { createProgressionTable } from "../components/progression-table.js";
15
15
  import { renderError } from "../components/error-page.js";
16
- import { createSelectWithValue } from "../lib/form-controls.js";
16
+ import {
17
+ createSelectWithValue,
18
+ createDisciplineSelect,
19
+ } from "../lib/form-controls.js";
17
20
  import {
18
21
  prepareCurrentJob,
19
22
  prepareCustomProgression,
@@ -513,11 +516,9 @@ function createComparisonSelectorsSection({
513
516
  div(
514
517
  { className: "form-group" },
515
518
  label({ for: "compare-discipline-select" }, "Target Discipline"),
516
- createSelectWithValue({
519
+ createDisciplineSelect({
517
520
  id: "compare-discipline-select",
518
- items: data.disciplines.sort((a, b) =>
519
- a.specialization.localeCompare(b.specialization),
520
- ),
521
+ disciplines: data.disciplines,
521
522
  initialValue: selectedDisciplineId,
522
523
  placeholder: "Select discipline...",
523
524
  getDisplayName: (d) => d.specialization,
@@ -17,7 +17,7 @@ import {
17
17
  } from "../lib/render.js";
18
18
  import { getState } from "../lib/state.js";
19
19
  import { createBadge } from "../components/card.js";
20
- import { createSelectWithValue } from "../lib/form-controls.js";
20
+ import { createDisciplineSelect } from "../lib/form-controls.js";
21
21
  import {
22
22
  SKILL_LEVEL_ORDER,
23
23
  BEHAVIOUR_MATURITY_ORDER,
@@ -82,7 +82,7 @@ function getWizardSteps(data) {
82
82
  if (skills && skills.length > 0) {
83
83
  steps.push({
84
84
  id: `skills-${capability}`,
85
- name: formatCapability(capability),
85
+ name: formatCapability(capability, data.capabilities),
86
86
  icon: getCapabilityEmoji(data.capabilities, capability),
87
87
  type: "skills",
88
88
  capability: capability,
@@ -113,11 +113,13 @@ function getWizardSteps(data) {
113
113
 
114
114
  /**
115
115
  * Format capability name for display
116
- * @param {string} capability
116
+ * @param {string} capabilityId
117
+ * @param {Array} capabilities
117
118
  * @returns {string}
118
119
  */
119
- function formatCapability(capability) {
120
- return capability.charAt(0).toUpperCase() + capability.slice(1);
120
+ function formatCapability(capabilityId, capabilities) {
121
+ const capability = capabilities.find((c) => c.id === capabilityId);
122
+ return capability?.name || capabilityId;
121
123
  }
122
124
 
123
125
  /**
@@ -306,9 +308,9 @@ function renderIntroStep(data) {
306
308
  "Select a discipline to highlight which skills are most relevant for that role. " +
307
309
  "You can still assess all skills.",
308
310
  ),
309
- createSelectWithValue({
311
+ createDisciplineSelect({
310
312
  id: "discipline-filter-select",
311
- items: data.disciplines,
313
+ disciplines: data.disciplines,
312
314
  initialValue: assessmentState.discipline || "",
313
315
  placeholder: "Select discipline",
314
316
  onChange: (value) => {
@@ -412,7 +414,7 @@ function renderSkillsStep(step, data) {
412
414
  h2(
413
415
  {},
414
416
  span({ className: "step-header-icon" }, step.icon),
415
- ` ${formatCapability(capability)} Skills`,
417
+ ` ${formatCapability(capability, data.capabilities)} Skills`,
416
418
  ),
417
419
  span(
418
420
  { className: "step-progress" },
@@ -89,25 +89,13 @@ export function renderSkillDetail(params) {
89
89
 
90
90
  /**
91
91
  * Format capability for display
92
- * @param {string} capability
92
+ * @param {string} capabilityId
93
93
  * @param {Array} capabilities
94
94
  * @returns {string}
95
95
  */
96
- function formatCapability(capability, capabilities) {
97
- const capabilityLabels = {
98
- delivery: "Delivery",
99
- scale: "Scale",
100
- reliability: "Reliability",
101
- data: "Data",
102
- ai: "AI",
103
- process: "Process",
104
- business: "Business",
105
- people: "People",
106
- documentation: "Documentation",
107
- };
108
- const label =
109
- capabilityLabels[capability] ||
110
- capability.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
111
- const emoji = getCapabilityEmoji(capabilities, capability);
96
+ function formatCapability(capabilityId, capabilities) {
97
+ const capability = capabilities.find((c) => c.id === capabilityId);
98
+ const label = capability?.name || capabilityId;
99
+ const emoji = getCapabilityEmoji(capabilities, capabilityId);
112
100
  return `${emoji} ${label}`;
113
101
  }
@@ -7,6 +7,7 @@ import { getState } from "../lib/state.js";
7
7
  import { createCardList } from "../components/list.js";
8
8
  import { renderNotFound } from "../components/error-page.js";
9
9
  import { prepareStagesList, stageToDOM } from "../formatters/stage/index.js";
10
+ import { getConceptEmoji } from "../model/levels.js";
10
11
 
11
12
  /**
12
13
  * Map stage to card configuration
@@ -15,7 +16,7 @@ import { prepareStagesList, stageToDOM } from "../formatters/stage/index.js";
15
16
  */
16
17
  function stageToCardConfig(stage) {
17
18
  return {
18
- title: `${stage.emoji || "🔄"} ${stage.name}`,
19
+ title: `${stage.emoji} ${stage.name}`,
19
20
  description: stage.truncatedDescription,
20
21
  href: `/stage/${stage.id}`,
21
22
  };
@@ -28,14 +29,13 @@ function stageToCardConfig(stage) {
28
29
  */
29
30
  function createLifecycleFlow(stages) {
30
31
  const flowItems = stages.map((stage, index) => {
31
- const emoji = stage.emoji || "🔄";
32
32
  const isLast = index === stages.length - 1;
33
33
 
34
34
  return div(
35
35
  { className: "lifecycle-flow-item" },
36
36
  a(
37
37
  { href: `#/stage/${stage.id}`, className: "lifecycle-stage" },
38
- span({ className: "lifecycle-emoji" }, emoji),
38
+ span({ className: "lifecycle-emoji" }, stage.emoji),
39
39
  span({ className: "lifecycle-name" }, stage.name),
40
40
  ),
41
41
  !isLast ? span({ className: "lifecycle-arrow" }, "→") : null,
@@ -50,7 +50,9 @@ function createLifecycleFlow(stages) {
50
50
  */
51
51
  export function renderStagesList() {
52
52
  const { data } = getState();
53
+ const { framework } = data;
53
54
  const stages = data.stages || [];
55
+ const stageEmoji = getConceptEmoji(framework, "stage");
54
56
 
55
57
  // Transform data for list view
56
58
  const { items } = prepareStagesList(stages);
@@ -60,11 +62,13 @@ export function renderStagesList() {
60
62
  // Header
61
63
  div(
62
64
  { className: "page-header" },
63
- h1({ className: "page-title" }, "🔄 Stages"),
65
+ h1(
66
+ { className: "page-title" },
67
+ `${stageEmoji} ${framework.entityDefinitions.stage.title}`,
68
+ ),
64
69
  p(
65
70
  { className: "page-description" },
66
- "The engineering lifecycle stages. Each stage has specific tools, " +
67
- "constraints, and handoffs to guide work from planning through review.",
71
+ framework.entityDefinitions.stage.description.trim().split("\n")[0],
68
72
  ),
69
73
  ),
70
74
 
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tools page
3
+ *
4
+ * Displays aggregated tools from all skills with links to skill context.
5
+ */
6
+
7
+ import { render, div, h1, p } from "../lib/render.js";
8
+ import { getState } from "../lib/state.js";
9
+ import { prepareToolsList } from "../formatters/tool/shared.js";
10
+ import { createBadge } from "../components/card.js";
11
+ import { createCardList } from "../components/list.js";
12
+ import { toolToCardConfig } from "../lib/card-mappers.js";
13
+ import { getConceptEmoji } from "../model/levels.js";
14
+
15
+ /**
16
+ * Render tools list page
17
+ */
18
+ export function renderToolsList() {
19
+ const { data } = getState();
20
+ const { framework } = data;
21
+ const toolEmoji = getConceptEmoji(framework, "tool");
22
+
23
+ const { tools, totalCount } = prepareToolsList(data.skills);
24
+
25
+ const page = div(
26
+ { className: "tools-page" },
27
+ // Header
28
+ div(
29
+ { className: "page-header" },
30
+ h1(
31
+ { className: "page-title" },
32
+ `${toolEmoji} ${framework.entityDefinitions.tool.title}`,
33
+ ),
34
+ p(
35
+ { className: "page-description" },
36
+ framework.entityDefinitions.tool.description.trim().split("\n")[0],
37
+ ),
38
+ createBadge(`${totalCount} tools`, "default"),
39
+ ),
40
+
41
+ // Tools list using standard card grid
42
+ createCardList(
43
+ tools,
44
+ (tool) => toolToCardConfig(tool, data.capabilities),
45
+ "No tools defined yet.",
46
+ ),
47
+ );
48
+
49
+ render(page);
50
+ }
@@ -55,25 +55,6 @@ export function renderSlideIndex({ render, data }) {
55
55
  ),
56
56
  ),
57
57
 
58
- // Tracks
59
- div(
60
- { className: "slide-section" },
61
- a(
62
- { href: "#/overview/track" },
63
- heading2(
64
- { className: "slide-section-title" },
65
- `${getConceptEmoji(data.framework, "track")} `,
66
- span({ className: "gradient-text" }, "Tracks"),
67
- ),
68
- ),
69
- ul(
70
- { className: "related-list" },
71
- ...data.tracks.map((track) =>
72
- li({}, a({ href: `#/track/${track.id}` }, track.name)),
73
- ),
74
- ),
75
- ),
76
-
77
58
  // Grades
78
59
  div(
79
60
  { className: "slide-section" },
@@ -99,21 +80,21 @@ export function renderSlideIndex({ render, data }) {
99
80
  ),
100
81
  ),
101
82
 
102
- // Skills
83
+ // Tracks
103
84
  div(
104
85
  { className: "slide-section" },
105
86
  a(
106
- { href: "#/overview/skill" },
87
+ { href: "#/overview/track" },
107
88
  heading2(
108
89
  { className: "slide-section-title" },
109
- `${getConceptEmoji(data.framework, "skill")} `,
110
- span({ className: "gradient-text" }, "Skills"),
90
+ `${getConceptEmoji(data.framework, "track")} `,
91
+ span({ className: "gradient-text" }, "Tracks"),
111
92
  ),
112
93
  ),
113
94
  ul(
114
95
  { className: "related-list" },
115
- ...data.skills.map((skill) =>
116
- li({}, a({ href: `#/skill/${skill.id}` }, skill.name)),
96
+ ...data.tracks.map((track) =>
97
+ li({}, a({ href: `#/track/${track.id}` }, track.name)),
117
98
  ),
118
99
  ),
119
100
  ),
@@ -137,6 +118,25 @@ export function renderSlideIndex({ render, data }) {
137
118
  ),
138
119
  ),
139
120
 
121
+ // Skills
122
+ div(
123
+ { className: "slide-section" },
124
+ a(
125
+ { href: "#/overview/skill" },
126
+ heading2(
127
+ { className: "slide-section-title" },
128
+ `${getConceptEmoji(data.framework, "skill")} `,
129
+ span({ className: "gradient-text" }, "Skills"),
130
+ ),
131
+ ),
132
+ ul(
133
+ { className: "related-list" },
134
+ ...data.skills.map((skill) =>
135
+ li({}, a({ href: `#/skill/${skill.id}` }, skill.name)),
136
+ ),
137
+ ),
138
+ ),
139
+
140
140
  // Drivers
141
141
  div(
142
142
  { className: "slide-section" },
package/app/slides.html CHANGED
@@ -5,6 +5,13 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Engineering Pathway - Slide View</title>
7
7
  <link rel="stylesheet" href="css/bundles/slides.css" />
8
+ <script type="importmap">
9
+ {
10
+ "imports": {
11
+ "mustache": "https://esm.sh/mustache@4.2.0"
12
+ }
13
+ }
14
+ </script>
8
15
  </head>
9
16
  <body class="slide-view">
10
17
  <header
package/bin/pathway.js CHANGED
@@ -9,15 +9,17 @@
9
9
  * npx pathway <command> [options]
10
10
  *
11
11
  * Commands:
12
- * skill [<id>] Show skills (summary, --list, or detail)
13
- * behaviour [<id>] Show behaviours
14
12
  * discipline [<id>] Show disciplines
15
13
  * grade [<id>] Show grades
16
14
  * track [<id>] Show tracks
15
+ * behaviour [<id>] Show behaviours
16
+ * skill [<id>] Show skills (summary, --list, or detail)
17
17
  * driver [<id>] Show drivers
18
- * job [<discipline> <track> <grade>] Generate job definition
19
- * interview <discipline> <track> <grade> [--type=TYPE] Generate interview
20
- * progress <discipline> <track> <grade> [--compare=GRADE] Career progression
18
+ * stage [<id>] Show stages
19
+ * tool [<name>] Show tools
20
+ * job [<discipline> <grade>] [--track=TRACK] Generate job definition
21
+ * interview <discipline> <grade> [--track=TRACK] [--type=TYPE] Generate interview
22
+ * progress <discipline> <grade> [--track=TRACK] [--compare=GRADE] Career progression
21
23
  * questions [options] Browse interview questions
22
24
  * agent [<discipline> <track>] [--output=PATH] Generate AI agent
23
25
  *
@@ -37,13 +39,14 @@ import { formatError } from "../app/lib/cli-output.js";
37
39
  import { runSchemaValidation } from "../app/model/schema-validation.js";
38
40
 
39
41
  // Import command handlers
40
- import { runSkillCommand } from "../app/commands/skill.js";
41
- import { runBehaviourCommand } from "../app/commands/behaviour.js";
42
42
  import { runDisciplineCommand } from "../app/commands/discipline.js";
43
43
  import { runGradeCommand } from "../app/commands/grade.js";
44
44
  import { runTrackCommand } from "../app/commands/track.js";
45
+ import { runBehaviourCommand } from "../app/commands/behaviour.js";
46
+ import { runSkillCommand } from "../app/commands/skill.js";
45
47
  import { runDriverCommand } from "../app/commands/driver.js";
46
48
  import { runStageCommand } from "../app/commands/stage.js";
49
+ import { runToolCommand } from "../app/commands/tool.js";
47
50
  import { runJobCommand } from "../app/commands/job.js";
48
51
  import { runInterviewCommand } from "../app/commands/interview.js";
49
52
  import { runProgressCommand } from "../app/commands/progress.js";
@@ -58,13 +61,14 @@ const __dirname = dirname(__filename);
58
61
  const rootDir = join(__dirname, "..");
59
62
 
60
63
  const COMMANDS = {
61
- skill: runSkillCommand,
62
- behaviour: runBehaviourCommand,
63
64
  discipline: runDisciplineCommand,
64
65
  grade: runGradeCommand,
65
66
  track: runTrackCommand,
67
+ behaviour: runBehaviourCommand,
68
+ skill: runSkillCommand,
66
69
  driver: runDriverCommand,
67
70
  stage: runStageCommand,
71
+ tool: runToolCommand,
68
72
  job: runJobCommand,
69
73
  interview: runInterviewCommand,
70
74
  progress: runProgressCommand,
@@ -86,22 +90,24 @@ Getting Started:
86
90
  site [--output=PATH] Generate static site to ./site/
87
91
 
88
92
  Entity Commands (summary by default, --list for IDs, <id> for detail):
89
- skill [<id>] Browse skills
90
- behaviour [<id>] Browse behaviours
91
93
  discipline [<id>] Browse disciplines
92
94
  grade [<id>] Browse grades
93
95
  track [<id>] Browse tracks
96
+ behaviour [<id>] Browse behaviours
97
+ skill [<id>] Browse skills
98
+ --agent Output as agent SKILL.md format
94
99
  driver [<id>] Browse drivers
95
100
  stage [<id>] Browse lifecycle stages
101
+ tool [<name>] Browse recommended tools
96
102
 
97
103
  Composite Commands:
98
- job [<discipline> <track> <grade>] Generate job definition
99
- interview <discipline> <track> <grade> [--type=TYPE]
104
+ job [<discipline> <grade>] [--track=TRACK] Generate job definition
105
+ interview <discipline> <grade> [--track=TRACK] [--type=TYPE]
100
106
  Generate interview questions
101
- progress <discipline> <track> <grade> [--compare=GRADE]
107
+ progress <discipline> <grade> [--track=TRACK] [--compare=GRADE]
102
108
  Show career progression
103
109
  questions [filters] Browse interview questions
104
- agent [<discipline> <track>] Generate AI coding agent
110
+ agent <discipline> [--track=<track>] Generate AI coding agent
105
111
 
106
112
  Global Options:
107
113
  --list Output IDs only (for piping to other commands)
@@ -121,25 +127,31 @@ Questions Filters:
121
127
  --format=FORMAT Output format: table, yaml, json
122
128
 
123
129
  Agent Options:
124
- --output=PATH Output directory (default: current directory)
125
- --preview Show output without writing files
126
- --role=ROLE Generate specific role variant
127
- --all-roles Generate default + all role variants
130
+ --track=TRACK Track for the agent (e.g., platform, forward_deployed)
131
+ --output=PATH Write files to directory (without this, outputs to console)
132
+ --stage=STAGE Generate specific stage agent (plan, code, review)
133
+ --all-stages Generate all stage agents (default)
128
134
 
129
135
  Examples:
130
136
  npx pathway skill # Summary of all skills
131
137
  npx pathway skill --list # Skill IDs for piping
132
138
  npx pathway skill ai_evaluation # Detail view
139
+ npx pathway skill architecture_design --agent # Agent SKILL.md output
140
+
141
+ npx pathway tool # Summary of all tools
142
+ npx pathway tool --list # Tool names for piping
143
+ npx pathway tool DuckDB # Tool detail with skill usages
133
144
 
134
145
  npx pathway job # Summary of valid combinations
135
146
  npx pathway job --list # All combinations for piping
136
- npx pathway job software_engineering platform L4
137
- npx pathway job se platform L3 --checklist=code_to_review
147
+ npx pathway job software_engineering L4
148
+ npx pathway job software_engineering L4 --track=platform
149
+ npx pathway job se L3 --track=platform --checklist=code
138
150
 
139
151
  npx pathway questions --level=practitioner
140
152
  npx pathway questions --stats
141
153
 
142
- npx pathway agent software_engineering platform --output=./agents
154
+ npx pathway agent software_engineering --track=platform --output=./agents
143
155
  npx pathway --validate # Validate all data
144
156
  `;
145
157
 
@@ -160,6 +172,8 @@ function parseArgs(args) {
160
172
  type: "full",
161
173
  compare: null,
162
174
  data: null,
175
+ // Shared command options
176
+ track: null,
163
177
  // Questions command options
164
178
  level: null,
165
179
  maturity: null,
@@ -172,11 +186,9 @@ function parseArgs(args) {
172
186
  checklist: null,
173
187
  // Agent command options
174
188
  output: null,
175
- preview: false,
176
- role: null,
177
- "all-roles": false,
178
189
  stage: null,
179
190
  "all-stages": false,
191
+ agent: false,
180
192
  // Serve command options
181
193
  port: null,
182
194
  // Init command options
@@ -196,14 +208,14 @@ function parseArgs(args) {
196
208
  result.validate = true;
197
209
  } else if (arg === "--generate-index") {
198
210
  result.generateIndex = true;
199
- } else if (arg === "--preview") {
200
- result.preview = true;
201
211
  } else if (arg.startsWith("--type=")) {
202
212
  result.type = arg.slice(7);
203
213
  } else if (arg.startsWith("--compare=")) {
204
214
  result.compare = arg.slice(10);
205
215
  } else if (arg.startsWith("--data=")) {
206
216
  result.data = arg.slice(7);
217
+ } else if (arg.startsWith("--track=")) {
218
+ result.track = arg.slice(8);
207
219
  } else if (arg.startsWith("--output=")) {
208
220
  result.output = arg.slice(9);
209
221
  } else if (arg.startsWith("--level=")) {
@@ -228,6 +240,8 @@ function parseArgs(args) {
228
240
  result.stage = arg.slice(8);
229
241
  } else if (arg === "--all-stages") {
230
242
  result["all-stages"] = true;
243
+ } else if (arg === "--agent") {
244
+ result.agent = true;
231
245
  } else if (arg.startsWith("--checklist=")) {
232
246
  result.checklist = arg.slice(12);
233
247
  } else if (arg.startsWith("--port=")) {