@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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage presentation helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for formatting stage data across DOM and CLI outputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { truncate } from "../shared.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tool display configuration
|
|
11
|
+
* @type {Object<string, {icon: string, label: string}>}
|
|
12
|
+
*/
|
|
13
|
+
const TOOL_CONFIG = {
|
|
14
|
+
search: { icon: "🔍", label: "Search" },
|
|
15
|
+
fetch: { icon: "🌐", label: "Fetch" },
|
|
16
|
+
codebase: { icon: "📂", label: "Codebase" },
|
|
17
|
+
read: { icon: "📖", label: "Read" },
|
|
18
|
+
edit: { icon: "✏️", label: "Edit" },
|
|
19
|
+
terminal: { icon: "💻", label: "Terminal" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} StageListItem
|
|
24
|
+
* @property {string} id
|
|
25
|
+
* @property {string} name
|
|
26
|
+
* @property {string} emoji
|
|
27
|
+
* @property {string} description
|
|
28
|
+
* @property {string} truncatedDescription
|
|
29
|
+
* @property {Array<{id: string, icon: string, label: string}>} tools
|
|
30
|
+
* @property {Array<{target: string, label: string}>} handoffs
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Transform stages for list view
|
|
35
|
+
* @param {Array} stages - Raw stage entities
|
|
36
|
+
* @param {number} [descriptionLimit=150] - Maximum description length
|
|
37
|
+
* @returns {{ items: StageListItem[] }}
|
|
38
|
+
*/
|
|
39
|
+
export function prepareStagesList(stages, descriptionLimit = 150) {
|
|
40
|
+
const items = stages.map((stage) => {
|
|
41
|
+
const tools = stage.availableTools || [];
|
|
42
|
+
return {
|
|
43
|
+
id: stage.id,
|
|
44
|
+
name: stage.name,
|
|
45
|
+
emoji: stage.emoji,
|
|
46
|
+
description: stage.description,
|
|
47
|
+
truncatedDescription: truncate(stage.description, descriptionLimit),
|
|
48
|
+
tools: tools.map((toolId) => ({
|
|
49
|
+
id: toolId,
|
|
50
|
+
icon: TOOL_CONFIG[toolId]?.icon || "🔧",
|
|
51
|
+
label: TOOL_CONFIG[toolId]?.label || toolId,
|
|
52
|
+
})),
|
|
53
|
+
handoffs: (stage.handoffs || []).map((h) => ({
|
|
54
|
+
target: h.targetStage,
|
|
55
|
+
label: h.label,
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return { items };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {Object} StageDetailView
|
|
65
|
+
* @property {string} id
|
|
66
|
+
* @property {string} name
|
|
67
|
+
* @property {string} description
|
|
68
|
+
* @property {Array<{id: string, icon: string, label: string}>} tools
|
|
69
|
+
* @property {string[]} constraints
|
|
70
|
+
* @property {string[]} entryCriteria
|
|
71
|
+
* @property {string[]} exitCriteria
|
|
72
|
+
* @property {Array<{target: string, label: string, prompt: string}>} handoffs
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Transform stage for detail view
|
|
77
|
+
* @param {Object} stage - Raw stage entity
|
|
78
|
+
* @returns {StageDetailView}
|
|
79
|
+
*/
|
|
80
|
+
export function prepareStageDetail(stage) {
|
|
81
|
+
const tools = stage.availableTools || [];
|
|
82
|
+
return {
|
|
83
|
+
id: stage.id,
|
|
84
|
+
name: stage.name,
|
|
85
|
+
description: stage.description,
|
|
86
|
+
tools: tools.map((toolId) => ({
|
|
87
|
+
id: toolId,
|
|
88
|
+
icon: TOOL_CONFIG[toolId]?.icon || "🔧",
|
|
89
|
+
label: TOOL_CONFIG[toolId]?.label || toolId,
|
|
90
|
+
})),
|
|
91
|
+
constraints: stage.constraints || [],
|
|
92
|
+
entryCriteria: stage.entryCriteria || [],
|
|
93
|
+
exitCriteria: stage.exitCriteria || [],
|
|
94
|
+
handoffs: (stage.handoffs || []).map((h) => ({
|
|
95
|
+
target: h.targetStage,
|
|
96
|
+
label: h.label,
|
|
97
|
+
prompt: h.prompt,
|
|
98
|
+
})),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get the stage emoji from loaded stages data
|
|
104
|
+
* @param {Object[]} stages - Loaded stages array
|
|
105
|
+
* @param {string} stageId - Stage identifier
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
export function getStageEmoji(stages, stageId) {
|
|
109
|
+
const stage = stages.find((s) => s.id === stageId);
|
|
110
|
+
return stage?.emoji || "🔄";
|
|
111
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track formatting for DOM/web output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { div, h1, p } from "../../lib/render.js";
|
|
6
|
+
import { createBackLink } from "../../components/nav.js";
|
|
7
|
+
import { createBadge, createStatCard } from "../../components/card.js";
|
|
8
|
+
import { createStatsGrid } from "../../components/grid.js";
|
|
9
|
+
import {
|
|
10
|
+
createDetailSection,
|
|
11
|
+
createLinksList,
|
|
12
|
+
} from "../../components/detail.js";
|
|
13
|
+
import {
|
|
14
|
+
createJobBuilderButton,
|
|
15
|
+
createInterviewPrepButton,
|
|
16
|
+
} from "../../components/action-buttons.js";
|
|
17
|
+
import {
|
|
18
|
+
createBehaviourModifierTable,
|
|
19
|
+
createSkillModifierTableWithCapabilities,
|
|
20
|
+
} from "../../components/modifier-table.js";
|
|
21
|
+
import { getConceptEmoji } from "../../model/levels.js";
|
|
22
|
+
import { prepareTrackDetail } from "./shared.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get track type badge(s)
|
|
26
|
+
* @param {Object} view
|
|
27
|
+
* @returns {HTMLElement[]}
|
|
28
|
+
*/
|
|
29
|
+
function getTrackTypeBadges(view) {
|
|
30
|
+
const badges = [];
|
|
31
|
+
if (view.isProfessional) {
|
|
32
|
+
badges.push(createBadge("Professional", "secondary"));
|
|
33
|
+
}
|
|
34
|
+
if (view.isManagement) {
|
|
35
|
+
badges.push(createBadge("Management", "default"));
|
|
36
|
+
}
|
|
37
|
+
return badges;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Format track detail as DOM elements
|
|
42
|
+
* @param {Object} track - Raw track entity
|
|
43
|
+
* @param {Object} context - Additional context
|
|
44
|
+
* @param {Array} context.skills - All skills
|
|
45
|
+
* @param {Array} context.behaviours - All behaviours
|
|
46
|
+
* @param {Array} context.disciplines - All disciplines
|
|
47
|
+
* @param {Object} [context.framework] - Framework data for emoji lookup
|
|
48
|
+
* @returns {HTMLElement}
|
|
49
|
+
*/
|
|
50
|
+
export function trackToDOM(
|
|
51
|
+
track,
|
|
52
|
+
{ skills, behaviours, disciplines, framework },
|
|
53
|
+
) {
|
|
54
|
+
const view = prepareTrackDetail(track, { skills, behaviours, disciplines });
|
|
55
|
+
const emoji = getConceptEmoji(framework, "track");
|
|
56
|
+
// Build modifier sections - group them together for print layout
|
|
57
|
+
const hasSkillModifiers = view.skillModifiers.length > 0;
|
|
58
|
+
const hasBehaviourModifiers = view.behaviourModifiers.length > 0;
|
|
59
|
+
|
|
60
|
+
const modifiersSection =
|
|
61
|
+
hasSkillModifiers || hasBehaviourModifiers
|
|
62
|
+
? div(
|
|
63
|
+
{ className: "print-columns" },
|
|
64
|
+
hasSkillModifiers
|
|
65
|
+
? createDetailSection({
|
|
66
|
+
title: "Skill Modifiers",
|
|
67
|
+
content: createSkillModifierTableWithCapabilities(
|
|
68
|
+
view.skillModifiers,
|
|
69
|
+
),
|
|
70
|
+
})
|
|
71
|
+
: null,
|
|
72
|
+
hasBehaviourModifiers
|
|
73
|
+
? createDetailSection({
|
|
74
|
+
title: "Behaviour Modifiers",
|
|
75
|
+
content: createBehaviourModifierTable(view.behaviourModifiers),
|
|
76
|
+
})
|
|
77
|
+
: null,
|
|
78
|
+
)
|
|
79
|
+
: null;
|
|
80
|
+
|
|
81
|
+
return div(
|
|
82
|
+
{ className: "detail-page track-detail" },
|
|
83
|
+
// Header
|
|
84
|
+
div(
|
|
85
|
+
{ className: "page-header" },
|
|
86
|
+
createBackLink("/track", "← Back to Tracks"),
|
|
87
|
+
h1({ className: "page-title" }, `${emoji} `, view.name),
|
|
88
|
+
div({ className: "page-meta" }, ...getTrackTypeBadges(view)),
|
|
89
|
+
p(
|
|
90
|
+
{ className: "text-muted", style: "margin-top: 0.5rem" },
|
|
91
|
+
view.description,
|
|
92
|
+
),
|
|
93
|
+
div(
|
|
94
|
+
{ className: "page-actions" },
|
|
95
|
+
createJobBuilderButton({ paramName: "track", paramValue: track.id }),
|
|
96
|
+
createInterviewPrepButton({ paramName: "track", paramValue: track.id }),
|
|
97
|
+
),
|
|
98
|
+
),
|
|
99
|
+
|
|
100
|
+
// Valid disciplines (if restricted)
|
|
101
|
+
view.validDisciplines.length > 0
|
|
102
|
+
? createDetailSection({
|
|
103
|
+
title: "Valid Disciplines",
|
|
104
|
+
content: createLinksList(view.validDisciplines, "/discipline"),
|
|
105
|
+
})
|
|
106
|
+
: null,
|
|
107
|
+
|
|
108
|
+
// Matching weights (stat cards)
|
|
109
|
+
track.matchingWeights
|
|
110
|
+
? createDetailSection({
|
|
111
|
+
title: "Matching Weights",
|
|
112
|
+
content: createStatsGrid([
|
|
113
|
+
createStatCard({
|
|
114
|
+
value: `${(track.matchingWeights.skills * 100).toFixed(0)}%`,
|
|
115
|
+
label: "Skills Weight",
|
|
116
|
+
}),
|
|
117
|
+
createStatCard({
|
|
118
|
+
value: `${(track.matchingWeights.behaviours * 100).toFixed(0)}%`,
|
|
119
|
+
label: "Behaviours Weight",
|
|
120
|
+
}),
|
|
121
|
+
]),
|
|
122
|
+
})
|
|
123
|
+
: null,
|
|
124
|
+
|
|
125
|
+
// Skill and Behaviour modifiers in columns for print
|
|
126
|
+
modifiersSection,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track formatting for markdown/CLI output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { tableToMarkdown } from "../shared.js";
|
|
6
|
+
import { prepareTracksList, prepareTrackDetail } from "./shared.js";
|
|
7
|
+
import { getConceptEmoji } from "../../model/levels.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Format track list as markdown
|
|
11
|
+
* @param {Array} tracks - Raw track entities
|
|
12
|
+
* @param {Object} [framework] - Framework config for emojis
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
export function trackListToMarkdown(tracks, framework) {
|
|
16
|
+
const { items } = prepareTracksList(tracks);
|
|
17
|
+
const emoji = framework ? getConceptEmoji(framework, "track") : "🛤️";
|
|
18
|
+
const lines = [`# ${emoji} Tracks`, ""];
|
|
19
|
+
|
|
20
|
+
for (const track of items) {
|
|
21
|
+
const types = [];
|
|
22
|
+
if (track.isProfessional) types.push("Professional");
|
|
23
|
+
if (track.isManagement) types.push("Management");
|
|
24
|
+
lines.push(`- **${track.name}**: ${types.join(", ")}`);
|
|
25
|
+
}
|
|
26
|
+
lines.push("");
|
|
27
|
+
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Format track detail as markdown
|
|
33
|
+
* @param {Object} track - Raw track entity
|
|
34
|
+
* @param {Object} context - Additional context
|
|
35
|
+
* @param {Array} context.skills - All skills
|
|
36
|
+
* @param {Array} context.behaviours - All behaviours
|
|
37
|
+
* @param {Array} context.disciplines - All disciplines
|
|
38
|
+
* @param {Object} [context.framework] - Framework config for emojis
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function trackToMarkdown(
|
|
42
|
+
track,
|
|
43
|
+
{ skills, behaviours, disciplines, framework },
|
|
44
|
+
) {
|
|
45
|
+
const view = prepareTrackDetail(track, { skills, behaviours, disciplines });
|
|
46
|
+
|
|
47
|
+
const types = [];
|
|
48
|
+
if (view.isProfessional) types.push("Professional");
|
|
49
|
+
if (view.isManagement) types.push("Management");
|
|
50
|
+
|
|
51
|
+
const emoji = framework ? getConceptEmoji(framework, "track") : "🛤️";
|
|
52
|
+
const lines = [
|
|
53
|
+
`# ${emoji} ${view.name}`,
|
|
54
|
+
"",
|
|
55
|
+
`**Type**: ${types.join(", ")}`,
|
|
56
|
+
"",
|
|
57
|
+
view.description,
|
|
58
|
+
"",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// Skill modifiers - show expanded skills for capabilities
|
|
62
|
+
if (view.skillModifiers.length > 0) {
|
|
63
|
+
lines.push("## Skill Modifiers", "");
|
|
64
|
+
for (const m of view.skillModifiers) {
|
|
65
|
+
const modifierStr = m.modifier > 0 ? `+${m.modifier}` : `${m.modifier}`;
|
|
66
|
+
if (m.isCapability && m.skills && m.skills.length > 0) {
|
|
67
|
+
// Capability with expanded skills
|
|
68
|
+
lines.push(`### ${m.name} Capability (${modifierStr})`, "");
|
|
69
|
+
for (const skill of m.skills) {
|
|
70
|
+
lines.push(`- ${skill.name}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push("");
|
|
73
|
+
} else if (m.isCapability) {
|
|
74
|
+
// Capability without expanded skills
|
|
75
|
+
lines.push(`- **All ${m.name} skills**: ${modifierStr}`);
|
|
76
|
+
} else {
|
|
77
|
+
// Individual skill
|
|
78
|
+
lines.push(`- **${m.name}**: ${modifierStr}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
lines.push("");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Behaviour modifiers
|
|
85
|
+
if (view.behaviourModifiers.length > 0) {
|
|
86
|
+
lines.push("## Behaviour Modifiers", "");
|
|
87
|
+
const modifierRows = view.behaviourModifiers.map((b) => [
|
|
88
|
+
b.name,
|
|
89
|
+
b.modifier > 0 ? `+${b.modifier}` : `${b.modifier}`,
|
|
90
|
+
]);
|
|
91
|
+
lines.push(tableToMarkdown(["Behaviour", "Modifier"], modifierRows));
|
|
92
|
+
lines.push("");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Valid disciplines
|
|
96
|
+
if (view.validDisciplines.length > 0) {
|
|
97
|
+
lines.push("## Valid Disciplines", "");
|
|
98
|
+
for (const d of view.validDisciplines) {
|
|
99
|
+
lines.push(`- ${d.name}`);
|
|
100
|
+
}
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track presentation helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for formatting track data across DOM and markdown outputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isCapability, getSkillsByCapability } from "../../model/modifiers.js";
|
|
8
|
+
import { truncate } from "../shared.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sort tracks by type: professional tracks first, then management tracks.
|
|
12
|
+
* Within each type, preserves original order.
|
|
13
|
+
* @param {Array} tracks - Raw track entities
|
|
14
|
+
* @returns {Array} Sorted tracks array
|
|
15
|
+
*/
|
|
16
|
+
export function sortTracksByType(tracks) {
|
|
17
|
+
return [...tracks].sort((a, b) => {
|
|
18
|
+
const aFlags = getTrackTypeFlags(a);
|
|
19
|
+
const bFlags = getTrackTypeFlags(b);
|
|
20
|
+
|
|
21
|
+
// Professional tracks come first
|
|
22
|
+
if (aFlags.isProfessional && !bFlags.isProfessional) return -1;
|
|
23
|
+
if (!aFlags.isProfessional && bFlags.isProfessional) return 1;
|
|
24
|
+
|
|
25
|
+
// Preserve original order within same type
|
|
26
|
+
return 0;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Determine track type flags from track data.
|
|
32
|
+
*
|
|
33
|
+
* Logic: Only one flag needs to be explicitly set to true; the other defaults to false.
|
|
34
|
+
* - If isManagement: true → management track (isProfessional = false)
|
|
35
|
+
* - If isProfessional: true (or neither set) → professional track (isManagement = false)
|
|
36
|
+
*
|
|
37
|
+
* @param {Object} track
|
|
38
|
+
* @param {boolean} [track.isProfessional] - Whether this is a professional/IC track
|
|
39
|
+
* @param {boolean} [track.isManagement] - Whether this is a management track
|
|
40
|
+
* @returns {{isProfessional: boolean, isManagement: boolean}}
|
|
41
|
+
*/
|
|
42
|
+
export function getTrackTypeFlags(track) {
|
|
43
|
+
// Management takes precedence if explicitly set to true
|
|
44
|
+
const isManagement = track.isManagement === true;
|
|
45
|
+
// Professional is true if management is not true (default behavior)
|
|
46
|
+
const isProfessional = !isManagement && track.isProfessional !== false;
|
|
47
|
+
return { isProfessional, isManagement };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} TrackListItem
|
|
52
|
+
* @property {string} id
|
|
53
|
+
* @property {string} name
|
|
54
|
+
* @property {string} description
|
|
55
|
+
* @property {string} truncatedDescription
|
|
56
|
+
* @property {boolean} isProfessional
|
|
57
|
+
* @property {boolean} isManagement
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Transform tracks for list view
|
|
62
|
+
* @param {Array} tracks - Raw track entities
|
|
63
|
+
* @param {number} [descriptionLimit=120] - Maximum description length
|
|
64
|
+
* @returns {{ items: TrackListItem[] }}
|
|
65
|
+
*/
|
|
66
|
+
export function prepareTracksList(tracks, descriptionLimit = 120) {
|
|
67
|
+
const sortedTracks = sortTracksByType(tracks);
|
|
68
|
+
const items = sortedTracks.map((track) => {
|
|
69
|
+
const { isProfessional, isManagement } = getTrackTypeFlags(track);
|
|
70
|
+
return {
|
|
71
|
+
id: track.id,
|
|
72
|
+
name: track.name,
|
|
73
|
+
description: track.description,
|
|
74
|
+
truncatedDescription: truncate(track.description, descriptionLimit),
|
|
75
|
+
isProfessional,
|
|
76
|
+
isManagement,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return { items };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @typedef {Object} SkillModifierRow
|
|
85
|
+
* @property {string} id
|
|
86
|
+
* @property {string} name
|
|
87
|
+
* @property {number} modifier
|
|
88
|
+
* @property {boolean} isCapability
|
|
89
|
+
* @property {Array<{id: string, name: string}>} [skills]
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @typedef {Object} BehaviourModifierRow
|
|
94
|
+
* @property {string} id
|
|
95
|
+
* @property {string} name
|
|
96
|
+
* @property {number} modifier
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @typedef {Object} TrackDetailView
|
|
101
|
+
* @property {string} id
|
|
102
|
+
* @property {string} name
|
|
103
|
+
* @property {string} description
|
|
104
|
+
* @property {boolean} isProfessional
|
|
105
|
+
* @property {boolean} isManagement
|
|
106
|
+
* @property {SkillModifierRow[]} skillModifiers
|
|
107
|
+
* @property {BehaviourModifierRow[]} behaviourModifiers
|
|
108
|
+
* @property {Array<{id: string, name: string}>} validDisciplines
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Transform track for detail view
|
|
113
|
+
* @param {Object} track - Raw track entity
|
|
114
|
+
* @param {Object} context - Additional context
|
|
115
|
+
* @param {Array} context.skills - All skills
|
|
116
|
+
* @param {Array} context.behaviours - All behaviours
|
|
117
|
+
* @param {Array} context.disciplines - All disciplines
|
|
118
|
+
* @returns {TrackDetailView|null}
|
|
119
|
+
*/
|
|
120
|
+
export function prepareTrackDetail(track, { skills, behaviours, disciplines }) {
|
|
121
|
+
if (!track) return null;
|
|
122
|
+
|
|
123
|
+
const { isProfessional, isManagement } = getTrackTypeFlags(track);
|
|
124
|
+
|
|
125
|
+
// Build skill modifiers
|
|
126
|
+
const skillModifiers = track.skillModifiers
|
|
127
|
+
? Object.entries(track.skillModifiers).map(([key, modifier]) => {
|
|
128
|
+
if (isCapability(key)) {
|
|
129
|
+
const capabilitySkills = getSkillsByCapability(skills, key);
|
|
130
|
+
return {
|
|
131
|
+
id: key,
|
|
132
|
+
name: key.charAt(0).toUpperCase() + key.slice(1),
|
|
133
|
+
modifier,
|
|
134
|
+
isCapability: true,
|
|
135
|
+
skills: capabilitySkills.map((s) => ({ id: s.id, name: s.name })),
|
|
136
|
+
};
|
|
137
|
+
} else {
|
|
138
|
+
const skill = skills.find((s) => s.id === key);
|
|
139
|
+
return {
|
|
140
|
+
id: key,
|
|
141
|
+
name: skill?.name || key,
|
|
142
|
+
modifier,
|
|
143
|
+
isCapability: false,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
: [];
|
|
148
|
+
|
|
149
|
+
// Build behaviour modifiers
|
|
150
|
+
const behaviourModifiers = track.behaviourModifiers
|
|
151
|
+
? Object.entries(track.behaviourModifiers).map(
|
|
152
|
+
([behaviourId, modifier]) => {
|
|
153
|
+
const behaviour = behaviours.find((b) => b.id === behaviourId);
|
|
154
|
+
return {
|
|
155
|
+
id: behaviourId,
|
|
156
|
+
name: behaviour?.name || behaviourId,
|
|
157
|
+
modifier,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
: [];
|
|
162
|
+
|
|
163
|
+
// Get valid disciplines
|
|
164
|
+
const validDisciplines = track.validDisciplines
|
|
165
|
+
? track.validDisciplines
|
|
166
|
+
.map((id) => disciplines.find((d) => d.id === id))
|
|
167
|
+
.filter(Boolean)
|
|
168
|
+
.map((d) => ({ id: d.id, name: d.specialization || d.name }))
|
|
169
|
+
: [];
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
id: track.id,
|
|
173
|
+
name: track.name,
|
|
174
|
+
description: track.description,
|
|
175
|
+
isProfessional,
|
|
176
|
+
isManagement,
|
|
177
|
+
skillModifiers,
|
|
178
|
+
behaviourModifiers,
|
|
179
|
+
validDisciplines,
|
|
180
|
+
};
|
|
181
|
+
}
|