@forwardimpact/pathway 0.1.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 (227) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/app/commands/agent.js +430 -0
  4. package/app/commands/behaviour.js +61 -0
  5. package/app/commands/command-factory.js +211 -0
  6. package/app/commands/discipline.js +58 -0
  7. package/app/commands/driver.js +94 -0
  8. package/app/commands/grade.js +60 -0
  9. package/app/commands/index.js +20 -0
  10. package/app/commands/init.js +67 -0
  11. package/app/commands/interview.js +68 -0
  12. package/app/commands/job.js +157 -0
  13. package/app/commands/progress.js +77 -0
  14. package/app/commands/questions.js +179 -0
  15. package/app/commands/serve.js +143 -0
  16. package/app/commands/site.js +121 -0
  17. package/app/commands/skill.js +76 -0
  18. package/app/commands/stage.js +129 -0
  19. package/app/commands/track.js +70 -0
  20. package/app/components/action-buttons.js +66 -0
  21. package/app/components/behaviour-profile.js +53 -0
  22. package/app/components/builder.js +341 -0
  23. package/app/components/card.js +98 -0
  24. package/app/components/checklist.js +145 -0
  25. package/app/components/comparison-radar.js +237 -0
  26. package/app/components/detail.js +230 -0
  27. package/app/components/error-page.js +72 -0
  28. package/app/components/grid.js +109 -0
  29. package/app/components/list.js +120 -0
  30. package/app/components/modifier-table.js +142 -0
  31. package/app/components/nav.js +64 -0
  32. package/app/components/progression-table.js +320 -0
  33. package/app/components/radar-chart.js +102 -0
  34. package/app/components/skill-matrix.js +97 -0
  35. package/app/css/base.css +56 -0
  36. package/app/css/bundles/app.css +40 -0
  37. package/app/css/bundles/handout.css +43 -0
  38. package/app/css/bundles/slides.css +40 -0
  39. package/app/css/components/badges.css +215 -0
  40. package/app/css/components/buttons.css +101 -0
  41. package/app/css/components/forms.css +105 -0
  42. package/app/css/components/layout.css +209 -0
  43. package/app/css/components/nav.css +166 -0
  44. package/app/css/components/progress.css +166 -0
  45. package/app/css/components/states.css +82 -0
  46. package/app/css/components/surfaces.css +243 -0
  47. package/app/css/components/tables.css +362 -0
  48. package/app/css/components/typography.css +122 -0
  49. package/app/css/components/utilities.css +41 -0
  50. package/app/css/pages/agent-builder.css +391 -0
  51. package/app/css/pages/assessment-results.css +453 -0
  52. package/app/css/pages/detail.css +59 -0
  53. package/app/css/pages/interview-builder.css +148 -0
  54. package/app/css/pages/job-builder.css +134 -0
  55. package/app/css/pages/landing.css +92 -0
  56. package/app/css/pages/lifecycle.css +118 -0
  57. package/app/css/pages/progress-builder.css +274 -0
  58. package/app/css/pages/self-assessment.css +502 -0
  59. package/app/css/reset.css +50 -0
  60. package/app/css/tokens.css +153 -0
  61. package/app/css/views/handout.css +30 -0
  62. package/app/css/views/print.css +608 -0
  63. package/app/css/views/slide-animations.css +113 -0
  64. package/app/css/views/slide-base.css +330 -0
  65. package/app/css/views/slide-sections.css +597 -0
  66. package/app/css/views/slide-tables.css +275 -0
  67. package/app/formatters/agent/dom.js +540 -0
  68. package/app/formatters/agent/profile.js +133 -0
  69. package/app/formatters/agent/skill.js +58 -0
  70. package/app/formatters/behaviour/dom.js +91 -0
  71. package/app/formatters/behaviour/markdown.js +54 -0
  72. package/app/formatters/behaviour/shared.js +64 -0
  73. package/app/formatters/discipline/dom.js +187 -0
  74. package/app/formatters/discipline/markdown.js +87 -0
  75. package/app/formatters/discipline/shared.js +131 -0
  76. package/app/formatters/driver/dom.js +103 -0
  77. package/app/formatters/driver/shared.js +92 -0
  78. package/app/formatters/grade/dom.js +208 -0
  79. package/app/formatters/grade/markdown.js +94 -0
  80. package/app/formatters/grade/shared.js +86 -0
  81. package/app/formatters/index.js +50 -0
  82. package/app/formatters/interview/dom.js +97 -0
  83. package/app/formatters/interview/markdown.js +66 -0
  84. package/app/formatters/interview/shared.js +332 -0
  85. package/app/formatters/job/description.js +176 -0
  86. package/app/formatters/job/dom.js +411 -0
  87. package/app/formatters/job/markdown.js +102 -0
  88. package/app/formatters/progress/dom.js +135 -0
  89. package/app/formatters/progress/markdown.js +86 -0
  90. package/app/formatters/progress/shared.js +339 -0
  91. package/app/formatters/questions/json.js +43 -0
  92. package/app/formatters/questions/markdown.js +303 -0
  93. package/app/formatters/questions/shared.js +274 -0
  94. package/app/formatters/questions/yaml.js +76 -0
  95. package/app/formatters/shared.js +71 -0
  96. package/app/formatters/skill/dom.js +168 -0
  97. package/app/formatters/skill/markdown.js +109 -0
  98. package/app/formatters/skill/shared.js +125 -0
  99. package/app/formatters/stage/dom.js +135 -0
  100. package/app/formatters/stage/index.js +12 -0
  101. package/app/formatters/stage/shared.js +111 -0
  102. package/app/formatters/track/dom.js +128 -0
  103. package/app/formatters/track/markdown.js +105 -0
  104. package/app/formatters/track/shared.js +181 -0
  105. package/app/handout-main.js +421 -0
  106. package/app/handout.html +21 -0
  107. package/app/index.html +59 -0
  108. package/app/lib/card-mappers.js +173 -0
  109. package/app/lib/cli-output.js +270 -0
  110. package/app/lib/error-boundary.js +70 -0
  111. package/app/lib/errors.js +49 -0
  112. package/app/lib/form-controls.js +47 -0
  113. package/app/lib/job-cache.js +86 -0
  114. package/app/lib/markdown.js +114 -0
  115. package/app/lib/radar.js +866 -0
  116. package/app/lib/reactive.js +77 -0
  117. package/app/lib/render.js +212 -0
  118. package/app/lib/router-core.js +160 -0
  119. package/app/lib/router-pages.js +16 -0
  120. package/app/lib/router-slides.js +202 -0
  121. package/app/lib/state.js +148 -0
  122. package/app/lib/utils.js +14 -0
  123. package/app/lib/yaml-loader.js +327 -0
  124. package/app/main.js +213 -0
  125. package/app/model/agent.js +702 -0
  126. package/app/model/checklist.js +137 -0
  127. package/app/model/derivation.js +699 -0
  128. package/app/model/index-generator.js +71 -0
  129. package/app/model/interview.js +539 -0
  130. package/app/model/job.js +222 -0
  131. package/app/model/levels.js +591 -0
  132. package/app/model/loader.js +564 -0
  133. package/app/model/matching.js +858 -0
  134. package/app/model/modifiers.js +158 -0
  135. package/app/model/profile.js +266 -0
  136. package/app/model/progression.js +507 -0
  137. package/app/model/validation.js +1385 -0
  138. package/app/pages/agent-builder.js +823 -0
  139. package/app/pages/assessment-results.js +507 -0
  140. package/app/pages/behaviour.js +70 -0
  141. package/app/pages/discipline.js +71 -0
  142. package/app/pages/driver.js +106 -0
  143. package/app/pages/grade.js +117 -0
  144. package/app/pages/interview-builder.js +50 -0
  145. package/app/pages/interview.js +304 -0
  146. package/app/pages/job-builder.js +50 -0
  147. package/app/pages/job.js +58 -0
  148. package/app/pages/landing.js +305 -0
  149. package/app/pages/progress-builder.js +58 -0
  150. package/app/pages/progress.js +495 -0
  151. package/app/pages/self-assessment.js +729 -0
  152. package/app/pages/skill.js +113 -0
  153. package/app/pages/stage.js +231 -0
  154. package/app/pages/track.js +69 -0
  155. package/app/slide-main.js +360 -0
  156. package/app/slides/behaviour.js +38 -0
  157. package/app/slides/chapter.js +82 -0
  158. package/app/slides/discipline.js +40 -0
  159. package/app/slides/driver.js +39 -0
  160. package/app/slides/grade.js +32 -0
  161. package/app/slides/index.js +198 -0
  162. package/app/slides/interview.js +58 -0
  163. package/app/slides/job.js +55 -0
  164. package/app/slides/overview.js +126 -0
  165. package/app/slides/progress.js +83 -0
  166. package/app/slides/skill.js +40 -0
  167. package/app/slides/track.js +39 -0
  168. package/app/slides.html +56 -0
  169. package/app/types.js +147 -0
  170. package/bin/pathway.js +489 -0
  171. package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
  172. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
  173. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
  174. package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
  175. package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
  176. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
  177. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
  178. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
  179. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
  180. package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
  181. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
  182. package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
  183. package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
  184. package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
  185. package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
  186. package/examples/agents/.vscode/settings.json +8 -0
  187. package/examples/behaviours/_index.yaml +8 -0
  188. package/examples/behaviours/outcome_ownership.yaml +44 -0
  189. package/examples/behaviours/polymathic_knowledge.yaml +42 -0
  190. package/examples/behaviours/precise_communication.yaml +40 -0
  191. package/examples/behaviours/relentless_curiosity.yaml +38 -0
  192. package/examples/behaviours/systems_thinking.yaml +41 -0
  193. package/examples/capabilities/_index.yaml +8 -0
  194. package/examples/capabilities/business.yaml +251 -0
  195. package/examples/capabilities/delivery.yaml +352 -0
  196. package/examples/capabilities/people.yaml +100 -0
  197. package/examples/capabilities/reliability.yaml +318 -0
  198. package/examples/capabilities/scale.yaml +394 -0
  199. package/examples/disciplines/_index.yaml +5 -0
  200. package/examples/disciplines/data_engineering.yaml +76 -0
  201. package/examples/disciplines/software_engineering.yaml +76 -0
  202. package/examples/drivers.yaml +205 -0
  203. package/examples/framework.yaml +58 -0
  204. package/examples/grades.yaml +118 -0
  205. package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
  206. package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
  207. package/examples/questions/behaviours/precise_communication.yaml +55 -0
  208. package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
  209. package/examples/questions/behaviours/systems_thinking.yaml +53 -0
  210. package/examples/questions/skills/architecture_design.yaml +54 -0
  211. package/examples/questions/skills/cloud_platforms.yaml +48 -0
  212. package/examples/questions/skills/code_quality.yaml +49 -0
  213. package/examples/questions/skills/data_modeling.yaml +46 -0
  214. package/examples/questions/skills/devops.yaml +47 -0
  215. package/examples/questions/skills/full_stack_development.yaml +48 -0
  216. package/examples/questions/skills/sre_practices.yaml +44 -0
  217. package/examples/questions/skills/stakeholder_management.yaml +49 -0
  218. package/examples/questions/skills/team_collaboration.yaml +43 -0
  219. package/examples/questions/skills/technical_writing.yaml +43 -0
  220. package/examples/self-assessments.yaml +66 -0
  221. package/examples/stages.yaml +76 -0
  222. package/examples/tracks/_index.yaml +6 -0
  223. package/examples/tracks/manager.yaml +53 -0
  224. package/examples/tracks/platform.yaml +54 -0
  225. package/examples/tracks/sre.yaml +58 -0
  226. package/examples/vscode-settings.yaml +22 -0
  227. package/package.json +68 -0
package/app/types.js ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Component prop types
3
+ *
4
+ * Central type definitions for component contracts.
5
+ * Components import these types for JSDoc documentation.
6
+ * Presenters return data matching these shapes.
7
+ */
8
+
9
+ // ============================================================================
10
+ // Skill Matrix Types
11
+ // ============================================================================
12
+
13
+ /**
14
+ * Skill matrix item for display in skill-matrix component
15
+ * @typedef {Object} SkillMatrixItem
16
+ * @property {string} skillId - Skill ID for linking
17
+ * @property {string} skillName - Display name
18
+ * @property {string} capability - Skill capability (e.g., "broad", "technical")
19
+ * @property {boolean} [humanOnly] - Whether this skill requires human presence
20
+ * @property {'primary'|'secondary'|'tertiary'} type - Skill type in this role
21
+ * @property {string} level - Level ID (e.g., "advanced", "expert")
22
+ * @property {string} levelDescription - Human-readable level description
23
+ */
24
+
25
+ // ============================================================================
26
+ // Behaviour Profile Types
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Behaviour profile item for display in behaviour-profile component
31
+ * @typedef {Object} BehaviourProfileItem
32
+ * @property {string} behaviourId - Behaviour ID for linking
33
+ * @property {string} behaviourName - Display name
34
+ * @property {'emerging'|'developing'|'practicing'|'role_modeling'|'exemplifying'} maturity - Maturity level
35
+ * @property {string} maturityDescription - Human-readable maturity description
36
+
37
+ */
38
+
39
+ // ============================================================================
40
+ // Progression Types
41
+ // ============================================================================
42
+
43
+ /**
44
+ * Skill change item for progression table
45
+ * @typedef {Object} SkillChangeItem
46
+ * @property {string} id - Skill ID for linking
47
+ * @property {string} name - Display name
48
+ * @property {string} capability - Skill capability
49
+ * @property {'primary'|'secondary'|'tertiary'} type - Skill type
50
+ * @property {string} currentLevel - Current level ID (or null if new)
51
+ * @property {string} targetLevel - Target level ID (or null if removed)
52
+ * @property {number} currentIndex - Current level as 0-5 index
53
+ * @property {number} targetIndex - Target level as 0-5 index
54
+ * @property {number} change - Level difference (positive = upgrade)
55
+ * @property {boolean} isGained - True if skill is new in target role
56
+ * @property {boolean} isLost - True if skill is removed in target role
57
+ */
58
+
59
+ /**
60
+ * Behaviour change item for progression table
61
+ * @typedef {Object} BehaviourChangeItem
62
+ * @property {string} id - Behaviour ID for linking
63
+ * @property {string} name - Display name
64
+ * @property {string} currentLevel - Current maturity (or null if new)
65
+ * @property {string} targetLevel - Target maturity (or null if removed)
66
+ * @property {number} currentIndex - Current maturity as 0-4 index
67
+ * @property {number} targetIndex - Target maturity as 0-4 index
68
+ * @property {number} change - Maturity difference (positive = upgrade)
69
+ * @property {boolean} isGained - True if behaviour is new in target role
70
+ * @property {boolean} isLost - True if behaviour is removed in target role
71
+ */
72
+
73
+ // ============================================================================
74
+ // Driver Coverage Types
75
+ // ============================================================================
76
+
77
+ /**
78
+ * Driver coverage item for driver coverage display
79
+ * @typedef {Object} DriverCoverageItem
80
+ * @property {string} id - Driver ID
81
+ * @property {string} name - Display name
82
+ * @property {number} coverage - Overall coverage percentage (0-100)
83
+ * @property {number} skillsCovered - Number of skills covered
84
+ * @property {number} skillsTotal - Total skills required by driver
85
+ * @property {number} behavioursCovered - Number of behaviours covered
86
+ * @property {number} behavioursTotal - Total behaviours required by driver
87
+ */
88
+
89
+ // ============================================================================
90
+ // Radar Chart Types
91
+ // ============================================================================
92
+
93
+ /**
94
+ * Data point for radar chart
95
+ * @typedef {Object} RadarDataPoint
96
+ * @property {string} label - Axis label
97
+ * @property {number} value - Value (0-maxValue)
98
+ * @property {number} maxValue - Maximum value for this axis
99
+ * @property {string} [description] - Tooltip description
100
+ */
101
+
102
+ // ============================================================================
103
+ // Card Types
104
+ // ============================================================================
105
+
106
+ /**
107
+ * Card list item for list/card views
108
+ * @typedef {Object} CardListItem
109
+ * @property {string} id - Item ID for linking
110
+ * @property {string} name - Display name
111
+ * @property {string} [description] - Optional description
112
+ * @property {string} [href] - Link destination
113
+ * @property {string[]} [badges] - Badge labels
114
+ * @property {Object} [meta] - Additional metadata
115
+ */
116
+
117
+ // ============================================================================
118
+ // Interview Types
119
+ // ============================================================================
120
+
121
+ /**
122
+ * Interview question for display
123
+ * @typedef {Object} InterviewQuestionItem
124
+ * @property {string} targetId - Skill or behaviour ID
125
+ * @property {string} targetName - Skill or behaviour name
126
+ * @property {'skill'|'behaviour'} targetType - Type of target
127
+ * @property {string} targetLevel - Required level
128
+ * @property {string} question - Main question text
129
+ * @property {string[]} followUps - Follow-up questions
130
+ */
131
+
132
+ /**
133
+ * Interview section grouping questions
134
+ * @typedef {Object} InterviewSectionItem
135
+ * @property {string} id - Section ID (skill or behaviour ID)
136
+ * @property {string} name - Section name
137
+ * @property {'skill'|'behaviour'} type - Type of section
138
+ * @property {string} level - Required level
139
+ * @property {InterviewQuestionItem[]} questions - Questions in this section
140
+ */
141
+
142
+ // ============================================================================
143
+ // Exports (for JSDoc imports)
144
+ // ============================================================================
145
+
146
+ // Types are exported via JSDoc @typedef, not runtime exports.
147
+ // Components import types using: /** @typedef {import('../types.js').TypeName} TypeName */
package/bin/pathway.js ADDED
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Engineering Pathway CLI
4
+ *
5
+ * A command-line interface for browsing and generating job definitions,
6
+ * interview questions, career progression analysis, and AI agent configurations.
7
+ *
8
+ * Usage:
9
+ * npx pathway <command> [options]
10
+ *
11
+ * Commands:
12
+ * skill [<id>] Show skills (summary, --list, or detail)
13
+ * behaviour [<id>] Show behaviours
14
+ * discipline [<id>] Show disciplines
15
+ * grade [<id>] Show grades
16
+ * track [<id>] Show tracks
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
21
+ * questions [options] Browse interview questions
22
+ * agent [<discipline> <track>] [--output=PATH] Generate AI agent
23
+ *
24
+ * Global Options:
25
+ * --list Output IDs only (for piping)
26
+ * --validate Run validation checks
27
+ * --json Output as JSON
28
+ * --help Show help
29
+ */
30
+
31
+ import { fileURLToPath } from "url";
32
+ import { dirname, join, resolve } from "path";
33
+ import { existsSync } from "fs";
34
+ import {
35
+ loadAllData,
36
+ loadAgentData,
37
+ loadSkillsWithAgentData,
38
+ loadQuestionBankFromFolder,
39
+ } from "../app/model/loader.js";
40
+ import { generateAllIndexes } from "../app/model/index-generator.js";
41
+ import { formatError } from "../app/lib/cli-output.js";
42
+
43
+ // Import command handlers
44
+ import { runSkillCommand } from "../app/commands/skill.js";
45
+ import { runBehaviourCommand } from "../app/commands/behaviour.js";
46
+ import { runDisciplineCommand } from "../app/commands/discipline.js";
47
+ import { runGradeCommand } from "../app/commands/grade.js";
48
+ import { runTrackCommand } from "../app/commands/track.js";
49
+ import { runDriverCommand } from "../app/commands/driver.js";
50
+ import { runStageCommand } from "../app/commands/stage.js";
51
+ import { runJobCommand } from "../app/commands/job.js";
52
+ import { runInterviewCommand } from "../app/commands/interview.js";
53
+ import { runProgressCommand } from "../app/commands/progress.js";
54
+ import { runQuestionsCommand } from "../app/commands/questions.js";
55
+ import { runAgentCommand } from "../app/commands/agent.js";
56
+ import { runServeCommand } from "../app/commands/serve.js";
57
+ import { runInitCommand } from "../app/commands/init.js";
58
+ import { runSiteCommand } from "../app/commands/site.js";
59
+
60
+ const __filename = fileURLToPath(import.meta.url);
61
+ const __dirname = dirname(__filename);
62
+ const rootDir = join(__dirname, "..");
63
+
64
+ const COMMANDS = {
65
+ skill: runSkillCommand,
66
+ behaviour: runBehaviourCommand,
67
+ discipline: runDisciplineCommand,
68
+ grade: runGradeCommand,
69
+ track: runTrackCommand,
70
+ driver: runDriverCommand,
71
+ stage: runStageCommand,
72
+ job: runJobCommand,
73
+ interview: runInterviewCommand,
74
+ progress: runProgressCommand,
75
+ questions: runQuestionsCommand,
76
+ agent: runAgentCommand,
77
+ };
78
+
79
+ const HELP_TEXT = `
80
+ Engineering Pathway CLI
81
+
82
+ Usage:
83
+ npx pathway <command> [options]
84
+ npx pathway --validate Run full data validation
85
+ npx pathway --generate-index Generate browser index files
86
+
87
+ Getting Started:
88
+ init Create ./data/ with example data
89
+ serve [--port=PORT] Serve web app at http://localhost:3000
90
+ site [--output=PATH] Generate static site to ./site/
91
+
92
+ Entity Commands (summary by default, --list for IDs, <id> for detail):
93
+ skill [<id>] Browse skills
94
+ behaviour [<id>] Browse behaviours
95
+ discipline [<id>] Browse disciplines
96
+ grade [<id>] Browse grades
97
+ track [<id>] Browse tracks
98
+ driver [<id>] Browse drivers
99
+ stage [<id>] Browse lifecycle stages
100
+
101
+ Composite Commands:
102
+ job [<discipline> <track> <grade>] Generate job definition
103
+ interview <discipline> <track> <grade> [--type=TYPE]
104
+ Generate interview questions
105
+ progress <discipline> <track> <grade> [--compare=GRADE]
106
+ Show career progression
107
+ questions [filters] Browse interview questions
108
+ agent [<discipline> <track>] Generate AI coding agent
109
+
110
+ Global Options:
111
+ --list Output IDs only (for piping to other commands)
112
+ --validate Run validation checks
113
+ --generate-index Generate _index.yaml files for browser loading
114
+ --json Output as JSON
115
+ --data=PATH Path to data directory (default: ./data or examples/)
116
+ --help Show this help message
117
+
118
+ Questions Filters:
119
+ --level=LEVEL Filter by skill level
120
+ --maturity=MAT Filter by behaviour maturity
121
+ --skill=ID Filter to specific skill
122
+ --behaviour=ID Filter to behaviour
123
+ --capability=CAP Filter by capability
124
+ --stats Show detailed statistics
125
+ --format=FORMAT Output format: table, yaml, json
126
+
127
+ Agent Options:
128
+ --output=PATH Output directory (default: current directory)
129
+ --preview Show output without writing files
130
+ --role=ROLE Generate specific role variant
131
+ --all-roles Generate default + all role variants
132
+
133
+ Examples:
134
+ npx pathway skill # Summary of all skills
135
+ npx pathway skill --list # Skill IDs for piping
136
+ npx pathway skill ai_evaluation # Detail view
137
+
138
+ npx pathway job # Summary of valid combinations
139
+ npx pathway job --list # All combinations for piping
140
+ npx pathway job software_engineering platform L4
141
+ npx pathway job se platform L3 --checklist=code_to_review
142
+
143
+ npx pathway questions --level=practitioner
144
+ npx pathway questions --stats
145
+
146
+ npx pathway agent software_engineering platform --output=./agents
147
+ npx pathway --validate # Validate all data
148
+ `;
149
+
150
+ /**
151
+ * Parse command line arguments
152
+ * @param {string[]} args
153
+ * @returns {Object}
154
+ */
155
+ function parseArgs(args) {
156
+ const result = {
157
+ command: null,
158
+ args: [],
159
+ list: false,
160
+ json: false,
161
+ help: false,
162
+ validate: false,
163
+ generateIndex: false,
164
+ type: "full",
165
+ compare: null,
166
+ data: null,
167
+ // Questions command options
168
+ level: null,
169
+ maturity: null,
170
+ skill: null,
171
+ behaviour: null,
172
+ capability: null,
173
+ format: null,
174
+ stats: false,
175
+ // Job command options
176
+ checklist: null,
177
+ // Agent command options
178
+ output: null,
179
+ preview: false,
180
+ role: null,
181
+ "all-roles": false,
182
+ stage: null,
183
+ "all-stages": false,
184
+ // Serve command options
185
+ port: null,
186
+ // Init command options
187
+ path: null,
188
+ // Site command options
189
+ clean: true,
190
+ };
191
+
192
+ for (const arg of args) {
193
+ if (arg === "--help" || arg === "-h") {
194
+ result.help = true;
195
+ } else if (arg === "--list" || arg === "-l") {
196
+ result.list = true;
197
+ } else if (arg === "--json") {
198
+ result.json = true;
199
+ } else if (arg === "--validate") {
200
+ result.validate = true;
201
+ } else if (arg === "--generate-index") {
202
+ result.generateIndex = true;
203
+ } else if (arg === "--preview") {
204
+ result.preview = true;
205
+ } else if (arg.startsWith("--type=")) {
206
+ result.type = arg.slice(7);
207
+ } else if (arg.startsWith("--compare=")) {
208
+ result.compare = arg.slice(10);
209
+ } else if (arg.startsWith("--data=")) {
210
+ result.data = arg.slice(7);
211
+ } else if (arg.startsWith("--output=")) {
212
+ result.output = arg.slice(9);
213
+ } else if (arg.startsWith("--level=")) {
214
+ result.level = arg.slice(8);
215
+ } else if (arg.startsWith("--maturity=")) {
216
+ result.maturity = arg.slice(11);
217
+ } else if (arg.startsWith("--skill=")) {
218
+ result.skill = arg.slice(8);
219
+ } else if (arg.startsWith("--behaviour=")) {
220
+ result.behaviour = arg.slice(12);
221
+ } else if (arg.startsWith("--capability=")) {
222
+ result.capability = arg.slice(14);
223
+ } else if (arg.startsWith("--format=")) {
224
+ result.format = arg.slice(9);
225
+ } else if (arg === "--stats") {
226
+ result.stats = true;
227
+ } else if (arg.startsWith("--role=")) {
228
+ result.role = arg.slice(7);
229
+ } else if (arg === "--all-roles") {
230
+ result["all-roles"] = true;
231
+ } else if (arg.startsWith("--stage=")) {
232
+ result.stage = arg.slice(8);
233
+ } else if (arg === "--all-stages") {
234
+ result["all-stages"] = true;
235
+ } else if (arg.startsWith("--checklist=")) {
236
+ result.checklist = arg.slice(12);
237
+ } else if (arg.startsWith("--port=")) {
238
+ result.port = parseInt(arg.slice(7), 10);
239
+ } else if (arg.startsWith("--path=")) {
240
+ result.path = arg.slice(7);
241
+ } else if (arg === "--no-clean") {
242
+ result.clean = false;
243
+ } else if (!arg.startsWith("-")) {
244
+ if (!result.command) {
245
+ result.command = arg;
246
+ } else {
247
+ result.args.push(arg);
248
+ }
249
+ }
250
+ }
251
+
252
+ return result;
253
+ }
254
+
255
+ /**
256
+ * Print help text
257
+ */
258
+ function printHelp() {
259
+ console.log(HELP_TEXT);
260
+ }
261
+
262
+ /**
263
+ * Run full data validation
264
+ * @param {string} dataDir - Path to data directory
265
+ */
266
+ async function runFullValidation(dataDir) {
267
+ console.log(`\n🔍 Validating data in: ${dataDir}\n`);
268
+
269
+ let hasErrors = false;
270
+
271
+ // Load and validate core data
272
+ const data = await loadAllData(dataDir, {
273
+ validate: true,
274
+ throwOnError: false,
275
+ });
276
+
277
+ if (data.validation.valid) {
278
+ console.log("✅ Core data validation passed");
279
+ } else {
280
+ console.log("❌ Core data validation failed");
281
+ hasErrors = true;
282
+ for (const e of data.validation.errors) {
283
+ console.log(` - [${e.type}] ${e.message}`);
284
+ }
285
+ }
286
+
287
+ if (data.validation.warnings.length > 0) {
288
+ console.log("\n⚠️ Warnings:");
289
+ for (const w of data.validation.warnings) {
290
+ console.log(` - [${w.type}] ${w.message}`);
291
+ }
292
+ }
293
+
294
+ // Validate question bank
295
+ try {
296
+ const questionBank = await loadQuestionBankFromFolder(
297
+ join(dataDir, "questions"),
298
+ data.skills,
299
+ data.behaviours,
300
+ { validate: true, throwOnError: false },
301
+ );
302
+
303
+ if (questionBank.validation?.valid) {
304
+ console.log("✅ Question bank validation passed");
305
+ } else if (questionBank.validation) {
306
+ console.log("❌ Question bank validation failed");
307
+ hasErrors = true;
308
+ for (const e of questionBank.validation.errors) {
309
+ console.log(` - [${e.type}] ${e.message}`);
310
+ }
311
+ }
312
+ } catch (err) {
313
+ console.log("⚠️ Could not validate question bank:", err.message);
314
+ }
315
+
316
+ // Validate agent data
317
+ try {
318
+ const agentData = await loadAgentData(dataDir);
319
+ const skillsWithAgent = await loadSkillsWithAgentData(dataDir);
320
+
321
+ const skillsWithAgentCount = skillsWithAgent.filter((s) => s.agent).length;
322
+
323
+ console.log(
324
+ `✅ Agent data: ${agentData.disciplines.length} disciplines, ${agentData.tracks.length} tracks, ${skillsWithAgentCount} skills with agent sections`,
325
+ );
326
+
327
+ // Check for orphaned agent definitions
328
+ for (const d of agentData.disciplines) {
329
+ if (!data.disciplines.find((h) => h.id === d.id)) {
330
+ console.log(` ⚠️ Agent discipline '${d.id}' has no human definition`);
331
+ }
332
+ }
333
+ for (const t of agentData.tracks) {
334
+ if (!data.tracks.find((h) => h.id === t.id)) {
335
+ console.log(` ⚠️ Agent track '${t.id}' has no human definition`);
336
+ }
337
+ }
338
+ } catch (err) {
339
+ console.log("⚠️ Could not validate agent data:", err.message);
340
+ }
341
+
342
+ // Summary
343
+ console.log("\n📊 Data Summary:");
344
+ console.log(` Skills: ${data.skills?.length || 0}`);
345
+ console.log(` Behaviours: ${data.behaviours?.length || 0}`);
346
+ console.log(` Disciplines: ${data.disciplines?.length || 0}`);
347
+ console.log(` Tracks: ${data.tracks?.length || 0}`);
348
+ console.log(` Grades: ${data.grades?.length || 0}`);
349
+ console.log(` Drivers: ${data.drivers?.length || 0}`);
350
+ console.log("");
351
+
352
+ return hasErrors ? 1 : 0;
353
+ }
354
+
355
+ /**
356
+ * Run index generation
357
+ * @param {string} dataDir - Path to data directory
358
+ */
359
+ async function runGenerateIndex(dataDir) {
360
+ console.log(`\n📁 Generating index files in: ${dataDir}\n`);
361
+
362
+ const results = await generateAllIndexes(dataDir);
363
+
364
+ for (const [dir, files] of Object.entries(results)) {
365
+ if (files.error) {
366
+ console.log(`❌ ${dir}: ${files.error}`);
367
+ } else {
368
+ console.log(`✅ ${dir}/_index.yaml (${files.length} files)`);
369
+ }
370
+ }
371
+
372
+ console.log("\n✨ Index generation complete\n");
373
+ return 0;
374
+ }
375
+
376
+ /**
377
+ * Resolve the data directory path.
378
+ * Resolution order:
379
+ * 1. --data=<path> flag (explicit override)
380
+ * 2. PATHWAY_DATA environment variable
381
+ * 3. ./data/ relative to current working directory
382
+ * 4. Package examples (for demo/testing)
383
+ *
384
+ * @param {Object} options - Parsed command options
385
+ * @returns {string} Resolved absolute path to data directory
386
+ */
387
+ function resolveDataPath(options) {
388
+ // 1. Explicit flag
389
+ if (options.data) {
390
+ return resolve(options.data);
391
+ }
392
+
393
+ // 2. Environment variable
394
+ if (process.env.PATHWAY_DATA) {
395
+ return resolve(process.env.PATHWAY_DATA);
396
+ }
397
+
398
+ // 3. Current working directory
399
+ const cwdData = join(process.cwd(), "data");
400
+ if (existsSync(cwdData)) {
401
+ return cwdData;
402
+ }
403
+
404
+ // 4. Package examples (for demo/testing)
405
+ const examplesData = join(rootDir, "examples");
406
+ if (existsSync(examplesData)) {
407
+ return examplesData;
408
+ }
409
+
410
+ throw new Error(
411
+ "No data directory found. Create ./data/ or use --data=<path>",
412
+ );
413
+ }
414
+
415
+ /**
416
+ * Main CLI entry point
417
+ */
418
+ async function main() {
419
+ const args = process.argv.slice(2);
420
+ const options = parseArgs(args);
421
+
422
+ if (options.help) {
423
+ printHelp();
424
+ process.exit(0);
425
+ }
426
+
427
+ const dataDir = resolveDataPath(options);
428
+
429
+ // Handle global --generate-index (no command)
430
+ if (options.generateIndex && !options.command) {
431
+ const exitCode = await runGenerateIndex(dataDir);
432
+ process.exit(exitCode);
433
+ }
434
+
435
+ // Handle global --validate (no command)
436
+ if (options.validate && !options.command) {
437
+ const exitCode = await runFullValidation(dataDir);
438
+ process.exit(exitCode);
439
+ }
440
+
441
+ // No command and no flags: show help
442
+ if (!options.command) {
443
+ printHelp();
444
+ process.exit(0);
445
+ }
446
+
447
+ const command = options.command;
448
+
449
+ // Handle init command (doesn't need data directory to exist)
450
+ if (command === "init") {
451
+ await runInitCommand({ options });
452
+ process.exit(0);
453
+ }
454
+
455
+ // Handle serve command (needs data directory)
456
+ if (command === "serve") {
457
+ await runServeCommand({ dataDir, options });
458
+ // serve doesn't exit, keeps running
459
+ return;
460
+ }
461
+
462
+ // Handle site command (generates static site)
463
+ if (command === "site") {
464
+ await runSiteCommand({ dataDir, options });
465
+ process.exit(0);
466
+ }
467
+
468
+ const handler = COMMANDS[command];
469
+
470
+ if (!handler) {
471
+ console.error(formatError(`Unknown command: ${command}`));
472
+ console.error(`Run 'npx pathway --help' for usage.`);
473
+ process.exit(1);
474
+ }
475
+
476
+ try {
477
+ const data = await loadAllData(dataDir, {
478
+ validate: true,
479
+ throwOnError: true,
480
+ });
481
+
482
+ await handler({ data, args: options.args, options, dataDir });
483
+ } catch (error) {
484
+ console.error(formatError(error.message));
485
+ process.exit(1);
486
+ }
487
+ }
488
+
489
+ main();