@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.
- package/LICENSE +201 -0
- package/README.md +104 -0
- package/app/commands/agent.js +430 -0
- package/app/commands/behaviour.js +61 -0
- package/app/commands/command-factory.js +211 -0
- package/app/commands/discipline.js +58 -0
- package/app/commands/driver.js +94 -0
- package/app/commands/grade.js +60 -0
- package/app/commands/index.js +20 -0
- package/app/commands/init.js +67 -0
- package/app/commands/interview.js +68 -0
- package/app/commands/job.js +157 -0
- package/app/commands/progress.js +77 -0
- package/app/commands/questions.js +179 -0
- package/app/commands/serve.js +143 -0
- package/app/commands/site.js +121 -0
- package/app/commands/skill.js +76 -0
- package/app/commands/stage.js +129 -0
- package/app/commands/track.js +70 -0
- package/app/components/action-buttons.js +66 -0
- package/app/components/behaviour-profile.js +53 -0
- package/app/components/builder.js +341 -0
- package/app/components/card.js +98 -0
- package/app/components/checklist.js +145 -0
- package/app/components/comparison-radar.js +237 -0
- package/app/components/detail.js +230 -0
- package/app/components/error-page.js +72 -0
- package/app/components/grid.js +109 -0
- package/app/components/list.js +120 -0
- package/app/components/modifier-table.js +142 -0
- package/app/components/nav.js +64 -0
- package/app/components/progression-table.js +320 -0
- package/app/components/radar-chart.js +102 -0
- package/app/components/skill-matrix.js +97 -0
- package/app/css/base.css +56 -0
- package/app/css/bundles/app.css +40 -0
- package/app/css/bundles/handout.css +43 -0
- package/app/css/bundles/slides.css +40 -0
- package/app/css/components/badges.css +215 -0
- package/app/css/components/buttons.css +101 -0
- package/app/css/components/forms.css +105 -0
- package/app/css/components/layout.css +209 -0
- package/app/css/components/nav.css +166 -0
- package/app/css/components/progress.css +166 -0
- package/app/css/components/states.css +82 -0
- package/app/css/components/surfaces.css +243 -0
- package/app/css/components/tables.css +362 -0
- package/app/css/components/typography.css +122 -0
- package/app/css/components/utilities.css +41 -0
- package/app/css/pages/agent-builder.css +391 -0
- package/app/css/pages/assessment-results.css +453 -0
- package/app/css/pages/detail.css +59 -0
- package/app/css/pages/interview-builder.css +148 -0
- package/app/css/pages/job-builder.css +134 -0
- package/app/css/pages/landing.css +92 -0
- package/app/css/pages/lifecycle.css +118 -0
- package/app/css/pages/progress-builder.css +274 -0
- package/app/css/pages/self-assessment.css +502 -0
- package/app/css/reset.css +50 -0
- package/app/css/tokens.css +153 -0
- package/app/css/views/handout.css +30 -0
- package/app/css/views/print.css +608 -0
- package/app/css/views/slide-animations.css +113 -0
- package/app/css/views/slide-base.css +330 -0
- package/app/css/views/slide-sections.css +597 -0
- package/app/css/views/slide-tables.css +275 -0
- package/app/formatters/agent/dom.js +540 -0
- package/app/formatters/agent/profile.js +133 -0
- package/app/formatters/agent/skill.js +58 -0
- package/app/formatters/behaviour/dom.js +91 -0
- package/app/formatters/behaviour/markdown.js +54 -0
- package/app/formatters/behaviour/shared.js +64 -0
- package/app/formatters/discipline/dom.js +187 -0
- package/app/formatters/discipline/markdown.js +87 -0
- package/app/formatters/discipline/shared.js +131 -0
- package/app/formatters/driver/dom.js +103 -0
- package/app/formatters/driver/shared.js +92 -0
- package/app/formatters/grade/dom.js +208 -0
- package/app/formatters/grade/markdown.js +94 -0
- package/app/formatters/grade/shared.js +86 -0
- package/app/formatters/index.js +50 -0
- package/app/formatters/interview/dom.js +97 -0
- package/app/formatters/interview/markdown.js +66 -0
- package/app/formatters/interview/shared.js +332 -0
- package/app/formatters/job/description.js +176 -0
- package/app/formatters/job/dom.js +411 -0
- package/app/formatters/job/markdown.js +102 -0
- package/app/formatters/progress/dom.js +135 -0
- package/app/formatters/progress/markdown.js +86 -0
- package/app/formatters/progress/shared.js +339 -0
- package/app/formatters/questions/json.js +43 -0
- package/app/formatters/questions/markdown.js +303 -0
- package/app/formatters/questions/shared.js +274 -0
- package/app/formatters/questions/yaml.js +76 -0
- package/app/formatters/shared.js +71 -0
- package/app/formatters/skill/dom.js +168 -0
- package/app/formatters/skill/markdown.js +109 -0
- package/app/formatters/skill/shared.js +125 -0
- package/app/formatters/stage/dom.js +135 -0
- package/app/formatters/stage/index.js +12 -0
- package/app/formatters/stage/shared.js +111 -0
- package/app/formatters/track/dom.js +128 -0
- package/app/formatters/track/markdown.js +105 -0
- package/app/formatters/track/shared.js +181 -0
- package/app/handout-main.js +421 -0
- package/app/handout.html +21 -0
- package/app/index.html +59 -0
- package/app/lib/card-mappers.js +173 -0
- package/app/lib/cli-output.js +270 -0
- package/app/lib/error-boundary.js +70 -0
- package/app/lib/errors.js +49 -0
- package/app/lib/form-controls.js +47 -0
- package/app/lib/job-cache.js +86 -0
- package/app/lib/markdown.js +114 -0
- package/app/lib/radar.js +866 -0
- package/app/lib/reactive.js +77 -0
- package/app/lib/render.js +212 -0
- package/app/lib/router-core.js +160 -0
- package/app/lib/router-pages.js +16 -0
- package/app/lib/router-slides.js +202 -0
- package/app/lib/state.js +148 -0
- package/app/lib/utils.js +14 -0
- package/app/lib/yaml-loader.js +327 -0
- package/app/main.js +213 -0
- package/app/model/agent.js +702 -0
- package/app/model/checklist.js +137 -0
- package/app/model/derivation.js +699 -0
- package/app/model/index-generator.js +71 -0
- package/app/model/interview.js +539 -0
- package/app/model/job.js +222 -0
- package/app/model/levels.js +591 -0
- package/app/model/loader.js +564 -0
- package/app/model/matching.js +858 -0
- package/app/model/modifiers.js +158 -0
- package/app/model/profile.js +266 -0
- package/app/model/progression.js +507 -0
- package/app/model/validation.js +1385 -0
- package/app/pages/agent-builder.js +823 -0
- package/app/pages/assessment-results.js +507 -0
- package/app/pages/behaviour.js +70 -0
- package/app/pages/discipline.js +71 -0
- package/app/pages/driver.js +106 -0
- package/app/pages/grade.js +117 -0
- package/app/pages/interview-builder.js +50 -0
- package/app/pages/interview.js +304 -0
- package/app/pages/job-builder.js +50 -0
- package/app/pages/job.js +58 -0
- package/app/pages/landing.js +305 -0
- package/app/pages/progress-builder.js +58 -0
- package/app/pages/progress.js +495 -0
- package/app/pages/self-assessment.js +729 -0
- package/app/pages/skill.js +113 -0
- package/app/pages/stage.js +231 -0
- package/app/pages/track.js +69 -0
- package/app/slide-main.js +360 -0
- package/app/slides/behaviour.js +38 -0
- package/app/slides/chapter.js +82 -0
- package/app/slides/discipline.js +40 -0
- package/app/slides/driver.js +39 -0
- package/app/slides/grade.js +32 -0
- package/app/slides/index.js +198 -0
- package/app/slides/interview.js +58 -0
- package/app/slides/job.js +55 -0
- package/app/slides/overview.js +126 -0
- package/app/slides/progress.js +83 -0
- package/app/slides/skill.js +40 -0
- package/app/slides/track.js +39 -0
- package/app/slides.html +56 -0
- package/app/types.js +147 -0
- package/bin/pathway.js +489 -0
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
- package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
- package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
- package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
- package/examples/agents/.vscode/settings.json +8 -0
- package/examples/behaviours/_index.yaml +8 -0
- package/examples/behaviours/outcome_ownership.yaml +44 -0
- package/examples/behaviours/polymathic_knowledge.yaml +42 -0
- package/examples/behaviours/precise_communication.yaml +40 -0
- package/examples/behaviours/relentless_curiosity.yaml +38 -0
- package/examples/behaviours/systems_thinking.yaml +41 -0
- package/examples/capabilities/_index.yaml +8 -0
- package/examples/capabilities/business.yaml +251 -0
- package/examples/capabilities/delivery.yaml +352 -0
- package/examples/capabilities/people.yaml +100 -0
- package/examples/capabilities/reliability.yaml +318 -0
- package/examples/capabilities/scale.yaml +394 -0
- package/examples/disciplines/_index.yaml +5 -0
- package/examples/disciplines/data_engineering.yaml +76 -0
- package/examples/disciplines/software_engineering.yaml +76 -0
- package/examples/drivers.yaml +205 -0
- package/examples/framework.yaml +58 -0
- package/examples/grades.yaml +118 -0
- package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
- package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
- package/examples/questions/behaviours/precise_communication.yaml +55 -0
- package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
- package/examples/questions/behaviours/systems_thinking.yaml +53 -0
- package/examples/questions/skills/architecture_design.yaml +54 -0
- package/examples/questions/skills/cloud_platforms.yaml +48 -0
- package/examples/questions/skills/code_quality.yaml +49 -0
- package/examples/questions/skills/data_modeling.yaml +46 -0
- package/examples/questions/skills/devops.yaml +47 -0
- package/examples/questions/skills/full_stack_development.yaml +48 -0
- package/examples/questions/skills/sre_practices.yaml +44 -0
- package/examples/questions/skills/stakeholder_management.yaml +49 -0
- package/examples/questions/skills/team_collaboration.yaml +43 -0
- package/examples/questions/skills/technical_writing.yaml +43 -0
- package/examples/self-assessments.yaml +66 -0
- package/examples/stages.yaml +76 -0
- package/examples/tracks/_index.yaml +6 -0
- package/examples/tracks/manager.yaml +53 -0
- package/examples/tracks/platform.yaml +54 -0
- package/examples/tracks/sre.yaml +58 -0
- package/examples/vscode-settings.yaml +22 -0
- 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();
|