@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.
- package/app/commands/agent.js +20 -20
- package/app/commands/index.js +4 -3
- package/app/commands/job.js +9 -4
- package/app/commands/skill.js +56 -2
- package/app/commands/tool.js +112 -0
- package/app/components/builder.js +6 -3
- package/app/components/checklist.js +6 -4
- package/app/components/markdown-textarea.js +132 -0
- package/app/css/components/forms.css +45 -0
- package/app/css/components/layout.css +12 -0
- package/app/css/components/surfaces.css +22 -0
- package/app/css/pages/detail.css +50 -0
- package/app/css/pages/job-builder.css +0 -42
- package/app/formatters/agent/profile.js +61 -120
- package/app/formatters/agent/skill.js +48 -60
- package/app/formatters/grade/dom.js +2 -4
- package/app/formatters/job/description.js +74 -82
- package/app/formatters/job/dom.js +45 -179
- package/app/formatters/job/markdown.js +17 -13
- package/app/formatters/shared.js +65 -2
- package/app/formatters/skill/dom.js +57 -2
- package/app/formatters/skill/markdown.js +18 -0
- package/app/formatters/skill/shared.js +12 -4
- package/app/formatters/stage/microdata.js +1 -1
- package/app/formatters/stage/shared.js +1 -1
- package/app/formatters/tool/shared.js +72 -0
- package/app/handout-main.js +7 -7
- package/app/handout.html +7 -0
- package/app/index.html +10 -3
- package/app/lib/card-mappers.js +64 -17
- package/app/lib/form-controls.js +64 -1
- package/app/lib/render.js +12 -1
- package/app/lib/template-loader.js +9 -0
- package/app/lib/yaml-loader.js +12 -1
- package/app/main.js +4 -0
- package/app/model/agent.js +26 -18
- package/app/model/derivation.js +3 -3
- package/app/model/levels.js +2 -0
- package/app/model/loader.js +12 -1
- package/app/model/validation.js +74 -8
- package/app/pages/agent-builder.js +8 -5
- package/app/pages/job.js +28 -4
- package/app/pages/landing.js +34 -14
- package/app/pages/progress.js +6 -5
- package/app/pages/self-assessment.js +10 -8
- package/app/pages/skill.js +5 -17
- package/app/pages/stage.js +10 -6
- package/app/pages/tool.js +50 -0
- package/app/slides/index.js +25 -25
- package/app/slides.html +7 -0
- package/bin/pathway.js +41 -27
- package/examples/capabilities/business.yaml +17 -17
- package/examples/capabilities/delivery.yaml +51 -36
- package/examples/capabilities/reliability.yaml +127 -114
- package/examples/capabilities/scale.yaml +38 -36
- package/examples/disciplines/engineering_management.yaml +1 -1
- package/examples/framework.yaml +12 -0
- package/examples/grades.yaml +18 -19
- package/examples/self-assessments.yaml +1 -1
- package/package.json +1 -1
- package/templates/job.template.md +47 -0
- 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
|
-
//
|
|
67
|
-
|
|
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
|
}
|
package/app/pages/landing.js
CHANGED
|
@@ -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-
|
|
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.
|
|
109
|
-
label: "
|
|
110
|
-
href: "/
|
|
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(
|
|
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-
|
|
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
|
-
"
|
|
165
|
-
`${
|
|
166
|
-
"/
|
|
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
|
|
package/app/pages/progress.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
519
|
+
createDisciplineSelect({
|
|
517
520
|
id: "compare-discipline-select",
|
|
518
|
-
|
|
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 {
|
|
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}
|
|
116
|
+
* @param {string} capabilityId
|
|
117
|
+
* @param {Array} capabilities
|
|
117
118
|
* @returns {string}
|
|
118
119
|
*/
|
|
119
|
-
function formatCapability(
|
|
120
|
-
|
|
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
|
-
|
|
311
|
+
createDisciplineSelect({
|
|
310
312
|
id: "discipline-filter-select",
|
|
311
|
-
|
|
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" },
|
package/app/pages/skill.js
CHANGED
|
@@ -89,25 +89,13 @@ export function renderSkillDetail(params) {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Format capability for display
|
|
92
|
-
* @param {string}
|
|
92
|
+
* @param {string} capabilityId
|
|
93
93
|
* @param {Array} capabilities
|
|
94
94
|
* @returns {string}
|
|
95
95
|
*/
|
|
96
|
-
function formatCapability(
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
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
|
}
|
package/app/pages/stage.js
CHANGED
|
@@ -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
|
|
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(
|
|
65
|
+
h1(
|
|
66
|
+
{ className: "page-title" },
|
|
67
|
+
`${stageEmoji} ${framework.entityDefinitions.stage.title}`,
|
|
68
|
+
),
|
|
64
69
|
p(
|
|
65
70
|
{ className: "page-description" },
|
|
66
|
-
|
|
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
|
+
}
|
package/app/slides/index.js
CHANGED
|
@@ -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
|
-
//
|
|
83
|
+
// Tracks
|
|
103
84
|
div(
|
|
104
85
|
{ className: "slide-section" },
|
|
105
86
|
a(
|
|
106
|
-
{ href: "#/overview/
|
|
87
|
+
{ href: "#/overview/track" },
|
|
107
88
|
heading2(
|
|
108
89
|
{ className: "slide-section-title" },
|
|
109
|
-
`${getConceptEmoji(data.framework, "
|
|
110
|
-
span({ className: "gradient-text" }, "
|
|
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.
|
|
116
|
-
li({}, a({ href: `#/
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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> <
|
|
99
|
-
interview <discipline> <
|
|
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> <
|
|
107
|
+
progress <discipline> <grade> [--track=TRACK] [--compare=GRADE]
|
|
102
108
|
Show career progression
|
|
103
109
|
questions [filters] Browse interview questions
|
|
104
|
-
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
|
-
--
|
|
125
|
-
--
|
|
126
|
-
--
|
|
127
|
-
--all-
|
|
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
|
|
137
|
-
npx pathway job
|
|
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=")) {
|