@forwardimpact/pathway 0.3.0 → 0.5.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 +1 -1
- package/app/commands/behaviour.js +1 -1
- package/app/commands/command-factory.js +2 -2
- package/app/commands/discipline.js +1 -1
- package/app/commands/driver.js +1 -1
- package/app/commands/grade.js +1 -1
- package/app/commands/index.js +4 -3
- package/app/commands/serve.js +2 -2
- package/app/commands/site.js +22 -2
- package/app/commands/skill.js +57 -3
- package/app/commands/stage.js +1 -1
- package/app/commands/tool.js +112 -0
- package/app/commands/track.js +1 -1
- package/app/components/card.js +11 -1
- package/app/components/checklist.js +6 -4
- package/app/components/code-display.js +153 -0
- package/app/components/markdown-textarea.js +153 -0
- package/app/css/bundles/app.css +14 -0
- package/app/css/components/badges.css +15 -8
- package/app/css/components/forms.css +55 -0
- package/app/css/components/layout.css +12 -0
- package/app/css/components/surfaces.css +71 -3
- package/app/css/components/typography.css +1 -2
- package/app/css/pages/agent-builder.css +11 -102
- package/app/css/pages/detail.css +60 -0
- package/app/css/pages/job-builder.css +0 -42
- package/app/css/tokens.css +3 -0
- package/app/formatters/agent/dom.js +26 -71
- package/app/formatters/agent/profile.js +67 -10
- package/app/formatters/agent/skill.js +48 -6
- package/app/formatters/grade/dom.js +6 -6
- package/app/formatters/job/description.js +21 -16
- package/app/formatters/job/dom.js +9 -70
- package/app/formatters/json-ld.js +1 -1
- package/app/formatters/shared.js +58 -0
- package/app/formatters/skill/dom.js +70 -3
- package/app/formatters/skill/markdown.js +18 -0
- package/app/formatters/skill/shared.js +14 -4
- package/app/formatters/stage/microdata.js +2 -2
- package/app/formatters/stage/shared.js +3 -3
- package/app/formatters/tool/shared.js +78 -0
- package/app/handout-main.js +19 -18
- package/app/index.html +16 -3
- package/app/lib/card-mappers.js +91 -17
- package/app/lib/render.js +4 -0
- package/app/lib/yaml-loader.js +12 -1
- package/app/main.js +4 -0
- package/app/model/agent.js +47 -23
- package/app/model/checklist.js +2 -2
- package/app/model/derivation.js +5 -5
- package/app/model/levels.js +4 -2
- package/app/model/loader.js +12 -1
- package/app/model/validation.js +77 -11
- package/app/pages/agent-builder.js +121 -77
- package/app/pages/landing.js +35 -15
- package/app/pages/self-assessment.js +7 -5
- package/app/pages/skill.js +5 -17
- package/app/pages/stage.js +12 -8
- package/app/pages/tool.js +50 -0
- package/app/slide-main.js +1 -1
- package/app/slides/chapter.js +8 -8
- package/app/slides/index.js +26 -26
- package/app/slides/overview.js +8 -8
- package/app/slides/skill.js +1 -0
- package/bin/pathway.js +31 -16
- package/examples/capabilities/business.yaml +18 -18
- package/examples/capabilities/delivery.yaml +54 -37
- package/examples/capabilities/people.yaml +1 -1
- package/examples/capabilities/reliability.yaml +130 -115
- package/examples/capabilities/scale.yaml +39 -37
- package/examples/disciplines/engineering_management.yaml +1 -1
- package/examples/framework.yaml +21 -9
- package/examples/grades.yaml +5 -7
- package/examples/self-assessments.yaml +1 -1
- package/examples/stages.yaml +18 -10
- package/package.json +2 -1
- package/templates/agent.template.md +47 -17
- package/templates/job.template.md +8 -8
- package/templates/skill.template.md +33 -11
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
- package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
- package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
- package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
- package/examples/agents/.vscode/settings.json +0 -8
|
@@ -18,8 +18,10 @@ import {
|
|
|
18
18
|
} from "../../lib/render.js";
|
|
19
19
|
import { createBackLink } from "../../components/nav.js";
|
|
20
20
|
import { createLevelCell } from "../../components/detail.js";
|
|
21
|
+
import { createCodeDisplay } from "../../components/code-display.js";
|
|
22
|
+
import { createToolIcon } from "../../lib/card-mappers.js";
|
|
21
23
|
import { SKILL_LEVEL_ORDER } from "../../model/levels.js";
|
|
22
|
-
import { prepareSkillDetail
|
|
24
|
+
import { prepareSkillDetail } from "./shared.js";
|
|
23
25
|
import { createJsonLdScript, skillToJsonLd } from "../json-ld.js";
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -31,11 +33,19 @@ import { createJsonLdScript, skillToJsonLd } from "../json-ld.js";
|
|
|
31
33
|
* @param {Array} context.drivers - All drivers
|
|
32
34
|
* @param {Array} context.capabilities - Capability entities
|
|
33
35
|
* @param {boolean} [context.showBackLink=true] - Whether to show back navigation link
|
|
36
|
+
* @param {boolean} [context.showToolsAndPatterns=true] - Whether to show recommended tools and implementation patterns
|
|
34
37
|
* @returns {HTMLElement}
|
|
35
38
|
*/
|
|
36
39
|
export function skillToDOM(
|
|
37
40
|
skill,
|
|
38
|
-
{
|
|
41
|
+
{
|
|
42
|
+
disciplines,
|
|
43
|
+
tracks,
|
|
44
|
+
drivers,
|
|
45
|
+
capabilities,
|
|
46
|
+
showBackLink = true,
|
|
47
|
+
showToolsAndPatterns = true,
|
|
48
|
+
} = {},
|
|
39
49
|
) {
|
|
40
50
|
const view = prepareSkillDetail(skill, {
|
|
41
51
|
disciplines,
|
|
@@ -61,7 +71,7 @@ export function skillToDOM(
|
|
|
61
71
|
{ className: "page-meta" },
|
|
62
72
|
span(
|
|
63
73
|
{ className: "badge badge-default" },
|
|
64
|
-
|
|
74
|
+
`${view.capabilityEmoji} ${view.capability.toUpperCase()}`,
|
|
65
75
|
),
|
|
66
76
|
view.isHumanOnly
|
|
67
77
|
? span(
|
|
@@ -167,5 +177,62 @@ export function skillToDOM(
|
|
|
167
177
|
),
|
|
168
178
|
)
|
|
169
179
|
: null,
|
|
180
|
+
|
|
181
|
+
// Recommended Tools
|
|
182
|
+
showToolsAndPatterns && view.toolReferences.length > 0
|
|
183
|
+
? div(
|
|
184
|
+
{ className: "detail-section" },
|
|
185
|
+
heading2({ className: "section-title" }, "Recommended Tools"),
|
|
186
|
+
table(
|
|
187
|
+
{ className: "tools-table" },
|
|
188
|
+
thead({}, tr({}, th({}, "Tool"), th({}, "Use When"))),
|
|
189
|
+
tbody(
|
|
190
|
+
{},
|
|
191
|
+
...view.toolReferences.map((tool) =>
|
|
192
|
+
tr(
|
|
193
|
+
{},
|
|
194
|
+
td(
|
|
195
|
+
{ className: "tool-name-cell" },
|
|
196
|
+
tool.simpleIcon
|
|
197
|
+
? createToolIcon(tool.simpleIcon, tool.name)
|
|
198
|
+
: null,
|
|
199
|
+
tool.url
|
|
200
|
+
? a(
|
|
201
|
+
{
|
|
202
|
+
href: tool.url,
|
|
203
|
+
target: "_blank",
|
|
204
|
+
rel: "noopener noreferrer",
|
|
205
|
+
},
|
|
206
|
+
tool.name,
|
|
207
|
+
)
|
|
208
|
+
: tool.name,
|
|
209
|
+
),
|
|
210
|
+
td({}, tool.useWhen),
|
|
211
|
+
),
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
),
|
|
215
|
+
showBackLink
|
|
216
|
+
? p(
|
|
217
|
+
{ className: "see-all-link" },
|
|
218
|
+
a({ href: "#/tool" }, "See all tools →"),
|
|
219
|
+
)
|
|
220
|
+
: null,
|
|
221
|
+
)
|
|
222
|
+
: null,
|
|
223
|
+
|
|
224
|
+
// Implementation Reference
|
|
225
|
+
showToolsAndPatterns && view.implementationReference
|
|
226
|
+
? div(
|
|
227
|
+
{ className: "detail-section" },
|
|
228
|
+
heading2({ className: "section-title" }, "Implementation Patterns"),
|
|
229
|
+
createCodeDisplay({
|
|
230
|
+
content: view.implementationReference,
|
|
231
|
+
description:
|
|
232
|
+
"Project-specific implementation guidance for this skill.",
|
|
233
|
+
minHeight: 450,
|
|
234
|
+
}),
|
|
235
|
+
)
|
|
236
|
+
: null,
|
|
170
237
|
);
|
|
171
238
|
}
|
|
@@ -105,5 +105,23 @@ export function skillToMarkdown(
|
|
|
105
105
|
lines.push("");
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Recommended tools
|
|
109
|
+
if (view.toolReferences.length > 0) {
|
|
110
|
+
lines.push("## Recommended Tools", "");
|
|
111
|
+
const toolRows = view.toolReferences.map((tool) => [
|
|
112
|
+
tool.url ? `[${tool.name}](${tool.url})` : tool.name,
|
|
113
|
+
tool.useWhen,
|
|
114
|
+
]);
|
|
115
|
+
lines.push(tableToMarkdown(["Tool", "Use When"], toolRows));
|
|
116
|
+
lines.push("");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Implementation reference
|
|
120
|
+
if (view.implementationReference) {
|
|
121
|
+
lines.push("## Implementation Patterns", "");
|
|
122
|
+
lines.push(view.implementationReference);
|
|
123
|
+
lines.push("");
|
|
124
|
+
}
|
|
125
|
+
|
|
108
126
|
return lines.join("\n");
|
|
109
127
|
}
|
|
@@ -13,12 +13,12 @@ import { truncate } from "../shared.js";
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Format capability name for display
|
|
16
|
-
* @param {string} capability
|
|
16
|
+
* @param {string} capabilityName - The capability name to display
|
|
17
17
|
* @returns {string}
|
|
18
18
|
*/
|
|
19
|
-
export function formatCapability(
|
|
20
|
-
if (!
|
|
21
|
-
return
|
|
19
|
+
export function formatCapability(capabilityName) {
|
|
20
|
+
if (!capabilityName) return "";
|
|
21
|
+
return capabilityName;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -66,12 +66,15 @@ export function prepareSkillsList(
|
|
|
66
66
|
* @property {string} name
|
|
67
67
|
* @property {string} description
|
|
68
68
|
* @property {string} capability
|
|
69
|
+
* @property {string} capabilityName
|
|
69
70
|
* @property {boolean} isHumanOnly
|
|
70
71
|
* @property {string} capabilityEmoji
|
|
71
72
|
* @property {Object<string, string>} levelDescriptions
|
|
72
73
|
* @property {Array<{id: string, name: string, skillType: string}>} relatedDisciplines
|
|
73
74
|
* @property {Array<{id: string, name: string, modifier: number}>} relatedTracks
|
|
74
75
|
* @property {Array<{id: string, name: string}>} relatedDrivers
|
|
76
|
+
* @property {Array<{name: string, url?: string, description: string, useWhen: string}>} toolReferences
|
|
77
|
+
* @property {string|null} implementationReference
|
|
75
78
|
*/
|
|
76
79
|
|
|
77
80
|
/**
|
|
@@ -110,16 +113,23 @@ export function prepareSkillDetail(
|
|
|
110
113
|
.filter((d) => d.contributingSkills?.includes(skill.id))
|
|
111
114
|
.map((d) => ({ id: d.id, name: d.name }));
|
|
112
115
|
|
|
116
|
+
const capabilityEntity = capabilities.find((c) => c.id === skill.capability);
|
|
117
|
+
|
|
113
118
|
return {
|
|
114
119
|
id: skill.id,
|
|
115
120
|
name: skill.name,
|
|
116
121
|
description: skill.description,
|
|
117
122
|
capability: skill.capability,
|
|
123
|
+
capabilityName: capabilityEntity?.name || skill.capability,
|
|
118
124
|
isHumanOnly: skill.isHumanOnly || false,
|
|
119
125
|
capabilityEmoji: getCapabilityEmoji(capabilities, skill.capability),
|
|
120
126
|
levelDescriptions: skill.levelDescriptions,
|
|
121
127
|
relatedDisciplines,
|
|
122
128
|
relatedTracks,
|
|
123
129
|
relatedDrivers,
|
|
130
|
+
toolReferences: (skill.toolReferences || [])
|
|
131
|
+
.slice()
|
|
132
|
+
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
133
|
+
implementationReference: skill.implementationReference || null,
|
|
124
134
|
};
|
|
125
135
|
}
|
|
@@ -32,7 +32,7 @@ export function stageListToMicrodata(stages) {
|
|
|
32
32
|
? `→ ${stage.handoffs.map((h) => h.target).join(", ")}`
|
|
33
33
|
: "";
|
|
34
34
|
return `${openTag("article", { itemtype: "Stage", itemid: `#${stage.id}` })}
|
|
35
|
-
${prop("h2", "name", `${stage.
|
|
35
|
+
${prop("h2", "name", `${stage.emojiIcon} ${stage.name}`)}
|
|
36
36
|
${prop("p", "description", stage.truncatedDescription)}
|
|
37
37
|
${handoffText ? `<p>Handoffs: ${handoffText}</p>` : ""}
|
|
38
38
|
</article>`;
|
|
@@ -100,7 +100,7 @@ ${prop("p", "prompt", h.prompt)}
|
|
|
100
100
|
${openTag("article", { itemtype: "Stage", itemid: `#${view.id}` })}
|
|
101
101
|
${prop("h1", "name", view.name)}
|
|
102
102
|
${metaTag("id", view.id)}
|
|
103
|
-
${stage.
|
|
103
|
+
${stage.emojiIcon ? metaTag("emojiIcon", stage.emojiIcon) : ""}
|
|
104
104
|
${prop("p", "description", view.description)}
|
|
105
105
|
${sections.join("\n")}
|
|
106
106
|
</article>
|
|
@@ -10,7 +10,7 @@ import { truncate } from "../shared.js";
|
|
|
10
10
|
* @typedef {Object} StageListItem
|
|
11
11
|
* @property {string} id
|
|
12
12
|
* @property {string} name
|
|
13
|
-
* @property {string}
|
|
13
|
+
* @property {string} emojiIcon
|
|
14
14
|
* @property {string} description
|
|
15
15
|
* @property {string} truncatedDescription
|
|
16
16
|
* @property {Array<{target: string, label: string}>} handoffs
|
|
@@ -27,7 +27,7 @@ export function prepareStagesList(stages, descriptionLimit = 150) {
|
|
|
27
27
|
return {
|
|
28
28
|
id: stage.id,
|
|
29
29
|
name: stage.name,
|
|
30
|
-
|
|
30
|
+
emojiIcon: stage.emojiIcon,
|
|
31
31
|
description: stage.description,
|
|
32
32
|
truncatedDescription: truncate(stage.description, descriptionLimit),
|
|
33
33
|
handoffs: (stage.handoffs || []).map((h) => ({
|
|
@@ -80,5 +80,5 @@ export function prepareStageDetail(stage) {
|
|
|
80
80
|
*/
|
|
81
81
|
export function getStageEmoji(stages, stageId) {
|
|
82
82
|
const stage = stages.find((s) => s.id === stageId);
|
|
83
|
-
return stage?.
|
|
83
|
+
return stage?.emojiIcon;
|
|
84
84
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool presentation helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for formatting tool data across DOM and CLI outputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} ToolUsage
|
|
9
|
+
* @property {string} skillId
|
|
10
|
+
* @property {string} skillName
|
|
11
|
+
* @property {string} capabilityId
|
|
12
|
+
* @property {string} useWhen
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} AggregatedTool
|
|
17
|
+
* @property {string} name
|
|
18
|
+
* @property {string} [url]
|
|
19
|
+
* @property {string} [simpleIcon]
|
|
20
|
+
* @property {string} description
|
|
21
|
+
* @property {ToolUsage[]} usages
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Aggregate tools from all skills, deduplicating by name
|
|
26
|
+
* @param {Array} skills - All skills with toolReferences
|
|
27
|
+
* @returns {AggregatedTool[]}
|
|
28
|
+
*/
|
|
29
|
+
export function aggregateTools(skills) {
|
|
30
|
+
const toolMap = new Map();
|
|
31
|
+
|
|
32
|
+
for (const skill of skills) {
|
|
33
|
+
if (!skill.toolReferences) continue;
|
|
34
|
+
|
|
35
|
+
for (const tool of skill.toolReferences) {
|
|
36
|
+
const usage = {
|
|
37
|
+
skillId: skill.id,
|
|
38
|
+
skillName: skill.name,
|
|
39
|
+
capabilityId: skill.capability,
|
|
40
|
+
useWhen: tool.useWhen,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const existing = toolMap.get(tool.name);
|
|
44
|
+
if (existing) {
|
|
45
|
+
existing.usages.push(usage);
|
|
46
|
+
// Prefer simpleIcon from first occurrence that has one
|
|
47
|
+
if (!existing.simpleIcon && tool.simpleIcon) {
|
|
48
|
+
existing.simpleIcon = tool.simpleIcon;
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
toolMap.set(tool.name, {
|
|
52
|
+
name: tool.name,
|
|
53
|
+
url: tool.url,
|
|
54
|
+
simpleIcon: tool.simpleIcon,
|
|
55
|
+
description: tool.description,
|
|
56
|
+
usages: [usage],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Array.from(toolMap.values()).sort((a, b) =>
|
|
63
|
+
a.name.localeCompare(b.name),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Prepare tools list view data
|
|
69
|
+
* @param {Array} skills - All skills
|
|
70
|
+
* @returns {{ tools: AggregatedTool[], totalCount: number }}
|
|
71
|
+
*/
|
|
72
|
+
export function prepareToolsList(skills) {
|
|
73
|
+
const tools = aggregateTools(skills);
|
|
74
|
+
return {
|
|
75
|
+
tools,
|
|
76
|
+
totalCount: tools.length,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/app/handout-main.js
CHANGED
|
@@ -41,17 +41,17 @@ import { sortTracksByName } from "./formatters/track/shared.js";
|
|
|
41
41
|
/**
|
|
42
42
|
* Create a chapter cover page
|
|
43
43
|
* @param {Object} params
|
|
44
|
-
* @param {string} params.
|
|
44
|
+
* @param {string} params.emojiIcon - Chapter emoji
|
|
45
45
|
* @param {string} params.title - Chapter title
|
|
46
46
|
* @param {string} params.description - Chapter description
|
|
47
47
|
* @returns {HTMLElement}
|
|
48
48
|
*/
|
|
49
|
-
function createChapterCover({
|
|
49
|
+
function createChapterCover({ emojiIcon, title, description }) {
|
|
50
50
|
return div(
|
|
51
51
|
{ className: "chapter-cover" },
|
|
52
52
|
h1(
|
|
53
53
|
{ className: "chapter-title" },
|
|
54
|
-
|
|
54
|
+
emojiIcon,
|
|
55
55
|
" ",
|
|
56
56
|
span({ className: "gradient-text" }, title),
|
|
57
57
|
),
|
|
@@ -111,7 +111,7 @@ function renderIndex(data) {
|
|
|
111
111
|
{ className: "page-header" },
|
|
112
112
|
heading1(
|
|
113
113
|
{ className: "page-title" },
|
|
114
|
-
`${framework.
|
|
114
|
+
`${framework.emojiIcon} ${framework.title} Handouts`,
|
|
115
115
|
),
|
|
116
116
|
p(
|
|
117
117
|
{ className: "page-description" },
|
|
@@ -135,25 +135,25 @@ function renderIndex(data) {
|
|
|
135
135
|
`${getConceptEmoji(framework, "job")} ${framework.entityDefinitions.job.title}`,
|
|
136
136
|
),
|
|
137
137
|
" - ",
|
|
138
|
-
`${data.disciplines.length} disciplines, ${data.
|
|
138
|
+
`${data.disciplines.length} disciplines, ${data.grades.length} grades, ${data.tracks.length} tracks`,
|
|
139
139
|
),
|
|
140
140
|
li(
|
|
141
141
|
{},
|
|
142
142
|
a(
|
|
143
|
-
{ href: "#/
|
|
144
|
-
`${getConceptEmoji(framework, "
|
|
143
|
+
{ href: "#/behaviour" },
|
|
144
|
+
`${getConceptEmoji(framework, "behaviour")} ${framework.entityDefinitions.behaviour.title}`,
|
|
145
145
|
),
|
|
146
146
|
" - ",
|
|
147
|
-
`${data.
|
|
147
|
+
`${data.behaviours.length} behaviour definitions`,
|
|
148
148
|
),
|
|
149
149
|
li(
|
|
150
150
|
{},
|
|
151
151
|
a(
|
|
152
|
-
{ href: "#/
|
|
153
|
-
`${getConceptEmoji(framework, "
|
|
152
|
+
{ href: "#/skill" },
|
|
153
|
+
`${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
|
|
154
154
|
),
|
|
155
155
|
" - ",
|
|
156
|
-
`${data.
|
|
156
|
+
`${data.skills.length} skill definitions`,
|
|
157
157
|
),
|
|
158
158
|
li(
|
|
159
159
|
{},
|
|
@@ -190,7 +190,7 @@ function renderDriverHandout(data) {
|
|
|
190
190
|
const content = div(
|
|
191
191
|
{},
|
|
192
192
|
createChapterCover({
|
|
193
|
-
|
|
193
|
+
emojiIcon: getConceptEmoji(framework, "driver"),
|
|
194
194
|
title: framework.entityDefinitions.driver.title,
|
|
195
195
|
description: framework.entityDefinitions.driver.description,
|
|
196
196
|
}),
|
|
@@ -227,13 +227,14 @@ function renderSkillHandout(data) {
|
|
|
227
227
|
drivers: data.drivers,
|
|
228
228
|
capabilities: data.capabilities,
|
|
229
229
|
showBackLink: false,
|
|
230
|
+
showToolsAndPatterns: false,
|
|
230
231
|
});
|
|
231
232
|
});
|
|
232
233
|
|
|
233
234
|
const content = div(
|
|
234
235
|
{},
|
|
235
236
|
createChapterCover({
|
|
236
|
-
|
|
237
|
+
emojiIcon: getConceptEmoji(framework, "skill"),
|
|
237
238
|
title: framework.entityDefinitions.skill.title,
|
|
238
239
|
description: framework.entityDefinitions.skill.description,
|
|
239
240
|
}),
|
|
@@ -261,7 +262,7 @@ function renderBehaviourHandout(data) {
|
|
|
261
262
|
const content = div(
|
|
262
263
|
{},
|
|
263
264
|
createChapterCover({
|
|
264
|
-
|
|
265
|
+
emojiIcon: getConceptEmoji(framework, "behaviour"),
|
|
265
266
|
title: framework.entityDefinitions.behaviour.title,
|
|
266
267
|
description: framework.entityDefinitions.behaviour.description,
|
|
267
268
|
}),
|
|
@@ -322,7 +323,7 @@ function renderJobHandout(data) {
|
|
|
322
323
|
{},
|
|
323
324
|
// Disciplines chapter
|
|
324
325
|
createChapterCover({
|
|
325
|
-
|
|
326
|
+
emojiIcon: getConceptEmoji(framework, "discipline"),
|
|
326
327
|
title: framework.entityDefinitions.discipline.title,
|
|
327
328
|
description: framework.entityDefinitions.discipline.description,
|
|
328
329
|
}),
|
|
@@ -330,7 +331,7 @@ function renderJobHandout(data) {
|
|
|
330
331
|
|
|
331
332
|
// Grades chapter (moved before Tracks)
|
|
332
333
|
createChapterCover({
|
|
333
|
-
|
|
334
|
+
emojiIcon: getConceptEmoji(framework, "grade"),
|
|
334
335
|
title: framework.entityDefinitions.grade.title,
|
|
335
336
|
description: framework.entityDefinitions.grade.description,
|
|
336
337
|
}),
|
|
@@ -338,7 +339,7 @@ function renderJobHandout(data) {
|
|
|
338
339
|
|
|
339
340
|
// Tracks chapter (moved after Grades)
|
|
340
341
|
createChapterCover({
|
|
341
|
-
|
|
342
|
+
emojiIcon: getConceptEmoji(framework, "track"),
|
|
342
343
|
title: framework.entityDefinitions.track.title,
|
|
343
344
|
description: framework.entityDefinitions.track.description,
|
|
344
345
|
}),
|
|
@@ -393,7 +394,7 @@ function populateBrandHeader(framework) {
|
|
|
393
394
|
header.appendChild(
|
|
394
395
|
a(
|
|
395
396
|
{ className: "brand-title", href: "#/" },
|
|
396
|
-
`${framework.
|
|
397
|
+
`${framework.emojiIcon} ${framework.title}`,
|
|
397
398
|
),
|
|
398
399
|
);
|
|
399
400
|
header.appendChild(span({ className: "brand-tag" }, framework.tag));
|
package/app/index.html
CHANGED
|
@@ -4,7 +4,19 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Engineering Pathway</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
+
<link
|
|
10
|
+
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap"
|
|
11
|
+
rel="stylesheet"
|
|
12
|
+
/>
|
|
13
|
+
<link
|
|
14
|
+
rel="stylesheet"
|
|
15
|
+
href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css"
|
|
16
|
+
/>
|
|
7
17
|
<link rel="stylesheet" href="css/bundles/app.css" />
|
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-markdown.min.js"></script>
|
|
8
20
|
<script type="importmap">
|
|
9
21
|
{
|
|
10
22
|
"imports": {
|
|
@@ -31,12 +43,13 @@
|
|
|
31
43
|
</button>
|
|
32
44
|
<ul class="nav-links" id="nav-links">
|
|
33
45
|
<li><a href="#/discipline">Disciplines</a></li>
|
|
34
|
-
<li><a href="#/track">Tracks</a></li>
|
|
35
46
|
<li><a href="#/grade">Grades</a></li>
|
|
36
|
-
<li><a href="#/
|
|
47
|
+
<li><a href="#/track">Tracks</a></li>
|
|
37
48
|
<li><a href="#/behaviour">Behaviours</a></li>
|
|
38
|
-
<li><a href="#/
|
|
49
|
+
<li><a href="#/skill">Skills</a></li>
|
|
39
50
|
<li><a href="#/driver">Drivers</a></li>
|
|
51
|
+
<li><a href="#/stage">Stages</a></li>
|
|
52
|
+
<li><a href="#/tool">Tools</a></li>
|
|
40
53
|
<li><a href="#/job-builder" class="nav-cta">Build a Job</a></li>
|
|
41
54
|
<li><a href="#/agent-builder" class="nav-cta">Build an Agent</a></li>
|
|
42
55
|
<li><a href="#/interview-prep" class="nav-cta">Interview Prep</a></li>
|
package/app/lib/card-mappers.js
CHANGED
|
@@ -9,6 +9,23 @@ import { createBadge } from "../components/card.js";
|
|
|
9
9
|
import { formatLevel } from "./render.js";
|
|
10
10
|
import { getCapabilityEmoji } from "../model/levels.js";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Create an external link element styled as a badge
|
|
14
|
+
* @param {string} text - Link text
|
|
15
|
+
* @param {string} url - External URL
|
|
16
|
+
* @returns {HTMLElement}
|
|
17
|
+
*/
|
|
18
|
+
function createExternalLink(text, url) {
|
|
19
|
+
const link = document.createElement("a");
|
|
20
|
+
link.href = url;
|
|
21
|
+
link.target = "_blank";
|
|
22
|
+
link.rel = "noopener noreferrer";
|
|
23
|
+
link.className = "badge badge-primary";
|
|
24
|
+
link.textContent = text;
|
|
25
|
+
link.addEventListener("click", (e) => e.stopPropagation()); // Don't trigger card click
|
|
26
|
+
return link;
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
/**
|
|
13
30
|
* Map discipline to card config
|
|
14
31
|
* @param {Object} discipline
|
|
@@ -155,24 +172,81 @@ export function jobToCardConfig(job) {
|
|
|
155
172
|
}
|
|
156
173
|
|
|
157
174
|
/**
|
|
158
|
-
*
|
|
159
|
-
* @param {
|
|
175
|
+
* Map tool to card config
|
|
176
|
+
* @param {Object} tool - Aggregated tool with usages
|
|
177
|
+
* @param {Array} capabilities - Capability entities for emoji lookup
|
|
178
|
+
* @returns {Object}
|
|
179
|
+
*/
|
|
180
|
+
export function toolToCardConfig(tool, capabilities) {
|
|
181
|
+
// Create skills list as card content
|
|
182
|
+
const skillsList = createSkillsList(tool.usages, capabilities);
|
|
183
|
+
|
|
184
|
+
// Create icon element if available
|
|
185
|
+
const icon = tool.simpleIcon
|
|
186
|
+
? createToolIcon(tool.simpleIcon, tool.name)
|
|
187
|
+
: null;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
title: tool.name,
|
|
191
|
+
description: tool.description,
|
|
192
|
+
// Docs link in header badges (upper right)
|
|
193
|
+
badges: tool.url ? [createExternalLink("Docs →", tool.url)] : [],
|
|
194
|
+
content: skillsList,
|
|
195
|
+
icon,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create a tool icon element using Simple Icons CDN
|
|
201
|
+
* @param {string} slug - Simple Icons slug (e.g., 'terraform', 'docker')
|
|
202
|
+
* @param {string} name - Tool name for alt text
|
|
203
|
+
* @returns {HTMLElement}
|
|
204
|
+
*/
|
|
205
|
+
export function createToolIcon(slug, name) {
|
|
206
|
+
const img = document.createElement("img");
|
|
207
|
+
// Use black color for consistent monochrome appearance
|
|
208
|
+
img.src = `https://cdn.simpleicons.org/${slug}/000000`;
|
|
209
|
+
img.alt = `${name} icon`;
|
|
210
|
+
img.className = "tool-icon";
|
|
211
|
+
img.width = 28;
|
|
212
|
+
img.height = 28;
|
|
213
|
+
// Gracefully handle missing icons
|
|
214
|
+
img.onerror = () => {
|
|
215
|
+
img.style.display = "none";
|
|
216
|
+
};
|
|
217
|
+
return img;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create an unordered list of skill links with capability emoji
|
|
222
|
+
* @param {Array} usages - Tool usage objects with skillId, skillName, capabilityId
|
|
223
|
+
* @param {Array} capabilities - Capability entities
|
|
224
|
+
* @returns {HTMLElement}
|
|
225
|
+
*/
|
|
226
|
+
function createSkillsList(usages, capabilities) {
|
|
227
|
+
const ul = document.createElement("ul");
|
|
228
|
+
ul.className = "tool-skills-list";
|
|
229
|
+
|
|
230
|
+
for (const usage of usages) {
|
|
231
|
+
const emoji = getCapabilityEmoji(capabilities, usage.capabilityId);
|
|
232
|
+
const li = document.createElement("li");
|
|
233
|
+
const link = document.createElement("a");
|
|
234
|
+
link.href = `#/skill/${usage.skillId}`;
|
|
235
|
+
link.textContent = `${emoji} ${usage.skillName}`;
|
|
236
|
+
li.appendChild(link);
|
|
237
|
+
ul.appendChild(li);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return ul;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Format capability for badge display (short, tag-like)
|
|
245
|
+
* @param {string} capabilityId
|
|
160
246
|
* @param {Array} capabilities
|
|
161
247
|
* @returns {string}
|
|
162
248
|
*/
|
|
163
|
-
function formatCapability(
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
scale: "Scale",
|
|
167
|
-
reliability: "Reliability",
|
|
168
|
-
data: "Data",
|
|
169
|
-
ai: "AI",
|
|
170
|
-
process: "Process",
|
|
171
|
-
business: "Business",
|
|
172
|
-
people: "People",
|
|
173
|
-
documentation: "Documentation",
|
|
174
|
-
};
|
|
175
|
-
const label = capabilityLabels[capability] || formatLevel(capability);
|
|
176
|
-
const emoji = getCapabilityEmoji(capabilities, capability);
|
|
177
|
-
return `${emoji} ${label}`;
|
|
249
|
+
function formatCapability(capabilityId, capabilities) {
|
|
250
|
+
const emoji = getCapabilityEmoji(capabilities, capabilityId);
|
|
251
|
+
return `${emoji} ${capabilityId.toUpperCase()}`;
|
|
178
252
|
}
|
package/app/lib/render.js
CHANGED
|
@@ -111,6 +111,10 @@ export const th = (attrs, ...children) =>
|
|
|
111
111
|
createElement("th", attrs, ...children);
|
|
112
112
|
export const td = (attrs, ...children) =>
|
|
113
113
|
createElement("td", attrs, ...children);
|
|
114
|
+
export const pre = (attrs, ...children) =>
|
|
115
|
+
createElement("pre", attrs, ...children);
|
|
116
|
+
export const code = (attrs, ...children) =>
|
|
117
|
+
createElement("code", attrs, ...children);
|
|
114
118
|
export const button = (attrs, ...children) =>
|
|
115
119
|
createElement("button", attrs, ...children);
|
|
116
120
|
export const input = (attrs) => createElement("input", attrs);
|
package/app/lib/yaml-loader.js
CHANGED
|
@@ -70,7 +70,15 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
|
70
70
|
|
|
71
71
|
if (capability.skills && Array.isArray(capability.skills)) {
|
|
72
72
|
for (const skill of capability.skills) {
|
|
73
|
-
const {
|
|
73
|
+
const {
|
|
74
|
+
id,
|
|
75
|
+
name,
|
|
76
|
+
isHumanOnly,
|
|
77
|
+
human,
|
|
78
|
+
agent,
|
|
79
|
+
implementationReference,
|
|
80
|
+
toolReferences,
|
|
81
|
+
} = skill;
|
|
74
82
|
allSkills.push({
|
|
75
83
|
id,
|
|
76
84
|
name,
|
|
@@ -80,6 +88,9 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
|
80
88
|
// Include isHumanOnly flag for agent filtering (defaults to false)
|
|
81
89
|
...(isHumanOnly && { isHumanOnly }),
|
|
82
90
|
...(agent && { agent }),
|
|
91
|
+
// Include implementation reference and tool references (shared by human and agent)
|
|
92
|
+
...(implementationReference && { implementationReference }),
|
|
93
|
+
...(toolReferences && { toolReferences }),
|
|
83
94
|
});
|
|
84
95
|
}
|
|
85
96
|
}
|
package/app/main.js
CHANGED
|
@@ -26,6 +26,7 @@ import { renderTracksList, renderTrackDetail } from "./pages/track.js";
|
|
|
26
26
|
import { renderGradesList, renderGradeDetail } from "./pages/grade.js";
|
|
27
27
|
import { renderDriversList, renderDriverDetail } from "./pages/driver.js";
|
|
28
28
|
import { renderStagesList, renderStageDetail } from "./pages/stage.js";
|
|
29
|
+
import { renderToolsList } from "./pages/tool.js";
|
|
29
30
|
import { renderJobBuilder } from "./pages/job-builder.js";
|
|
30
31
|
import { renderJobDetail } from "./pages/job.js";
|
|
31
32
|
import { renderInterviewPrep } from "./pages/interview-builder.js";
|
|
@@ -99,6 +100,9 @@ function setupRoutes() {
|
|
|
99
100
|
router.on("/stage", renderStagesList);
|
|
100
101
|
router.on("/stage/:id", renderStageDetail);
|
|
101
102
|
|
|
103
|
+
// Tool
|
|
104
|
+
router.on("/tool", renderToolsList);
|
|
105
|
+
|
|
102
106
|
// Job builder
|
|
103
107
|
router.on("/job-builder", renderJobBuilder);
|
|
104
108
|
router.on("/job/:discipline/:grade/:track", renderJobDetail);
|