@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,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Skill Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats agent skill data into SKILL.md file content
|
|
5
|
+
* following the Agent Skills Standard specification.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format agent skill as SKILL.md file content
|
|
10
|
+
* @param {Object} skill - Skill with frontmatter and body
|
|
11
|
+
* @param {Object} skill.frontmatter - YAML frontmatter data
|
|
12
|
+
* @param {string} skill.frontmatter.name - Skill name (required)
|
|
13
|
+
* @param {string} skill.frontmatter.description - Skill description (required)
|
|
14
|
+
* @param {string} skill.body - Markdown body content
|
|
15
|
+
* @returns {string} Complete SKILL.md file content
|
|
16
|
+
*/
|
|
17
|
+
export function formatAgentSkill({ frontmatter, body }) {
|
|
18
|
+
const lines = ["---"];
|
|
19
|
+
|
|
20
|
+
// Name (required)
|
|
21
|
+
lines.push(`name: ${frontmatter.name}`);
|
|
22
|
+
|
|
23
|
+
// Description (required) - handle multiline
|
|
24
|
+
const description = frontmatter.description.trim();
|
|
25
|
+
if (description.includes("\n")) {
|
|
26
|
+
lines.push("description: |");
|
|
27
|
+
for (const line of description.split("\n")) {
|
|
28
|
+
lines.push(` ${line}`);
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
lines.push(`description: ${description}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
lines.push("---");
|
|
35
|
+
lines.push("");
|
|
36
|
+
lines.push(body);
|
|
37
|
+
|
|
38
|
+
return lines.join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Format agent skill for CLI output (markdown)
|
|
43
|
+
* @param {Object} skill - Skill with frontmatter and body
|
|
44
|
+
* @returns {string} Markdown formatted for CLI display
|
|
45
|
+
*/
|
|
46
|
+
export function formatAgentSkillForCli({ frontmatter, body }) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
|
|
49
|
+
lines.push(`# Skill: ${frontmatter.name}`);
|
|
50
|
+
lines.push("");
|
|
51
|
+
lines.push(`**Description:** ${frontmatter.description.trim()}`);
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push("---");
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push(body);
|
|
56
|
+
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behaviour formatting for DOM output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
div,
|
|
7
|
+
heading1,
|
|
8
|
+
heading2,
|
|
9
|
+
p,
|
|
10
|
+
table,
|
|
11
|
+
tbody,
|
|
12
|
+
thead,
|
|
13
|
+
tr,
|
|
14
|
+
th,
|
|
15
|
+
td,
|
|
16
|
+
ul,
|
|
17
|
+
li,
|
|
18
|
+
} from "../../lib/render.js";
|
|
19
|
+
import { createBackLink } from "../../components/nav.js";
|
|
20
|
+
import { createLevelCell } from "../../components/detail.js";
|
|
21
|
+
import {
|
|
22
|
+
BEHAVIOUR_MATURITY_ORDER,
|
|
23
|
+
getConceptEmoji,
|
|
24
|
+
} from "../../model/levels.js";
|
|
25
|
+
import { prepareBehaviourDetail } from "./shared.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format behaviour detail as DOM elements
|
|
29
|
+
* @param {Object} behaviour - Raw behaviour entity
|
|
30
|
+
* @param {Object} context - Additional context and options
|
|
31
|
+
* @param {Array} context.drivers - All drivers
|
|
32
|
+
* @param {Object} [context.framework] - Framework data for emoji lookup
|
|
33
|
+
* @param {boolean} [context.showBackLink=true] - Whether to show back navigation link
|
|
34
|
+
* @returns {HTMLElement}
|
|
35
|
+
*/
|
|
36
|
+
export function behaviourToDOM(
|
|
37
|
+
behaviour,
|
|
38
|
+
{ drivers, framework, showBackLink = true } = {},
|
|
39
|
+
) {
|
|
40
|
+
const view = prepareBehaviourDetail(behaviour, { drivers });
|
|
41
|
+
const emoji = getConceptEmoji(framework, "behaviour");
|
|
42
|
+
return div(
|
|
43
|
+
{ className: "detail-page behaviour-detail" },
|
|
44
|
+
// Header
|
|
45
|
+
div(
|
|
46
|
+
{ className: "page-header" },
|
|
47
|
+
showBackLink
|
|
48
|
+
? createBackLink("/behaviour", "← Back to Behaviours")
|
|
49
|
+
: null,
|
|
50
|
+
heading1({ className: "page-title" }, `${emoji} `, view.name),
|
|
51
|
+
p({ className: "page-description" }, view.description),
|
|
52
|
+
),
|
|
53
|
+
|
|
54
|
+
// Maturity descriptions
|
|
55
|
+
div(
|
|
56
|
+
{ className: "detail-section" },
|
|
57
|
+
heading2({ className: "section-title" }, "Maturity Levels"),
|
|
58
|
+
table(
|
|
59
|
+
{ className: "level-table" },
|
|
60
|
+
thead({}, tr({}, th({}, "Level"), th({}, "Description"))),
|
|
61
|
+
tbody(
|
|
62
|
+
{},
|
|
63
|
+
...BEHAVIOUR_MATURITY_ORDER.map((maturity, index) => {
|
|
64
|
+
const description = view.maturityDescriptions[maturity] || "—";
|
|
65
|
+
return tr(
|
|
66
|
+
{},
|
|
67
|
+
createLevelCell(
|
|
68
|
+
index + 1,
|
|
69
|
+
BEHAVIOUR_MATURITY_ORDER.length,
|
|
70
|
+
maturity,
|
|
71
|
+
),
|
|
72
|
+
td({}, description),
|
|
73
|
+
);
|
|
74
|
+
}),
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
|
|
79
|
+
// Related drivers
|
|
80
|
+
view.relatedDrivers.length > 0
|
|
81
|
+
? div(
|
|
82
|
+
{ className: "detail-section" },
|
|
83
|
+
heading2({ className: "section-title" }, "Linked to Drivers"),
|
|
84
|
+
ul(
|
|
85
|
+
{ className: "related-list" },
|
|
86
|
+
...view.relatedDrivers.map((d) => li({}, d.name)),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
: null,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behaviour formatting for markdown/CLI output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { tableToMarkdown, capitalize } from "../shared.js";
|
|
6
|
+
import { prepareBehavioursList, prepareBehaviourDetail } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format behaviour list as markdown
|
|
10
|
+
* @param {Array} behaviours - Raw behaviour entities
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function behaviourListToMarkdown(behaviours) {
|
|
14
|
+
const { items } = prepareBehavioursList(behaviours);
|
|
15
|
+
const lines = ["# 🧠 Behaviours", ""];
|
|
16
|
+
|
|
17
|
+
for (const behaviour of items) {
|
|
18
|
+
lines.push(`- **${behaviour.name}**: ${behaviour.truncatedDescription}`);
|
|
19
|
+
}
|
|
20
|
+
lines.push("");
|
|
21
|
+
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format behaviour detail as markdown
|
|
27
|
+
* @param {Object} behaviour - Raw behaviour entity
|
|
28
|
+
* @param {Object} context - Additional context
|
|
29
|
+
* @param {Array} context.drivers - All drivers
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function behaviourToMarkdown(behaviour, { drivers }) {
|
|
33
|
+
const view = prepareBehaviourDetail(behaviour, { drivers });
|
|
34
|
+
const lines = [`# 🧠 ${view.name}`, "", view.description, ""];
|
|
35
|
+
|
|
36
|
+
// Maturity descriptions table
|
|
37
|
+
lines.push("## Maturity Levels", "");
|
|
38
|
+
const maturityRows = Object.entries(view.maturityDescriptions).map(
|
|
39
|
+
([maturity, desc]) => [capitalize(maturity.replace(/_/g, " ")), desc],
|
|
40
|
+
);
|
|
41
|
+
lines.push(tableToMarkdown(["Maturity", "Description"], maturityRows));
|
|
42
|
+
lines.push("");
|
|
43
|
+
|
|
44
|
+
// Related drivers
|
|
45
|
+
if (view.relatedDrivers.length > 0) {
|
|
46
|
+
lines.push("## Linked to Drivers", "");
|
|
47
|
+
for (const d of view.relatedDrivers) {
|
|
48
|
+
lines.push(`- ${d.name}`);
|
|
49
|
+
}
|
|
50
|
+
lines.push("");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behaviour presentation helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for formatting behaviour data across DOM and markdown outputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { truncate } from "../shared.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} BehaviourListItem
|
|
11
|
+
* @property {string} id
|
|
12
|
+
* @property {string} name
|
|
13
|
+
* @property {string} description
|
|
14
|
+
* @property {string} truncatedDescription
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transform behaviours for list view
|
|
19
|
+
* @param {Array} behaviours - Raw behaviour entities
|
|
20
|
+
* @param {number} [descriptionLimit=150] - Maximum description length
|
|
21
|
+
* @returns {{ items: BehaviourListItem[] }}
|
|
22
|
+
*/
|
|
23
|
+
export function prepareBehavioursList(behaviours, descriptionLimit = 150) {
|
|
24
|
+
const items = behaviours.map((behaviour) => ({
|
|
25
|
+
id: behaviour.id,
|
|
26
|
+
name: behaviour.name,
|
|
27
|
+
description: behaviour.description,
|
|
28
|
+
truncatedDescription: truncate(behaviour.description, descriptionLimit),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
return { items };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {Object} BehaviourDetailView
|
|
36
|
+
* @property {string} id
|
|
37
|
+
* @property {string} name
|
|
38
|
+
* @property {string} description
|
|
39
|
+
* @property {Object<string, string>} maturityDescriptions
|
|
40
|
+
* @property {Array<{id: string, name: string}>} relatedDrivers
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Transform behaviour for detail view
|
|
45
|
+
* @param {Object} behaviour - Raw behaviour entity
|
|
46
|
+
* @param {Object} context - Additional context
|
|
47
|
+
* @param {Array} context.drivers - All drivers
|
|
48
|
+
* @returns {BehaviourDetailView|null}
|
|
49
|
+
*/
|
|
50
|
+
export function prepareBehaviourDetail(behaviour, { drivers }) {
|
|
51
|
+
if (!behaviour) return null;
|
|
52
|
+
|
|
53
|
+
const relatedDrivers = drivers
|
|
54
|
+
.filter((d) => d.contributingBehaviours?.includes(behaviour.id))
|
|
55
|
+
.map((d) => ({ id: d.id, name: d.name }));
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
id: behaviour.id,
|
|
59
|
+
name: behaviour.name,
|
|
60
|
+
description: behaviour.description,
|
|
61
|
+
maturityDescriptions: behaviour.maturityDescriptions,
|
|
62
|
+
relatedDrivers,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discipline formatting for DOM output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
a,
|
|
7
|
+
div,
|
|
8
|
+
heading1,
|
|
9
|
+
heading2,
|
|
10
|
+
heading3,
|
|
11
|
+
p,
|
|
12
|
+
span,
|
|
13
|
+
ul,
|
|
14
|
+
li,
|
|
15
|
+
} from "../../lib/render.js";
|
|
16
|
+
import { createBackLink } from "../../components/nav.js";
|
|
17
|
+
import {
|
|
18
|
+
createJobBuilderButton,
|
|
19
|
+
createInterviewPrepButton,
|
|
20
|
+
} from "../../components/action-buttons.js";
|
|
21
|
+
import { getConceptEmoji } from "../../model/levels.js";
|
|
22
|
+
import { prepareDisciplineDetail } from "./shared.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Format discipline detail as DOM elements
|
|
26
|
+
* @param {Object} discipline - Raw discipline entity
|
|
27
|
+
* @param {Object} context - Additional context and options
|
|
28
|
+
* @param {Array} context.skills - All skills
|
|
29
|
+
* @param {Array} context.behaviours - All behaviours
|
|
30
|
+
* @param {Object} [context.framework] - Framework data for emoji lookup
|
|
31
|
+
* @param {boolean} [context.showBackLink=true] - Whether to show back navigation link
|
|
32
|
+
* @param {boolean} [context.showBehaviourModifiers=true] - Whether to show behaviour modifiers section
|
|
33
|
+
* @returns {HTMLElement}
|
|
34
|
+
*/
|
|
35
|
+
export function disciplineToDOM(
|
|
36
|
+
discipline,
|
|
37
|
+
{
|
|
38
|
+
skills,
|
|
39
|
+
behaviours,
|
|
40
|
+
framework,
|
|
41
|
+
showBackLink = true,
|
|
42
|
+
showBehaviourModifiers = true,
|
|
43
|
+
} = {},
|
|
44
|
+
) {
|
|
45
|
+
const view = prepareDisciplineDetail(discipline, { skills, behaviours });
|
|
46
|
+
const emoji = getConceptEmoji(framework, "discipline");
|
|
47
|
+
return div(
|
|
48
|
+
{ className: "detail-page discipline-detail" },
|
|
49
|
+
// Header
|
|
50
|
+
div(
|
|
51
|
+
{ className: "page-header" },
|
|
52
|
+
showBackLink
|
|
53
|
+
? createBackLink("/discipline", "← Back to Disciplines")
|
|
54
|
+
: null,
|
|
55
|
+
heading1({ className: "page-title" }, `${emoji} `, view.name),
|
|
56
|
+
p({ className: "page-description" }, view.description),
|
|
57
|
+
showBackLink
|
|
58
|
+
? div(
|
|
59
|
+
{ className: "page-actions" },
|
|
60
|
+
createJobBuilderButton({
|
|
61
|
+
paramName: "discipline",
|
|
62
|
+
paramValue: discipline.id,
|
|
63
|
+
}),
|
|
64
|
+
createInterviewPrepButton({
|
|
65
|
+
paramName: "discipline",
|
|
66
|
+
paramValue: discipline.id,
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
: null,
|
|
70
|
+
),
|
|
71
|
+
|
|
72
|
+
// Stats
|
|
73
|
+
div(
|
|
74
|
+
{ className: "stats" },
|
|
75
|
+
div(
|
|
76
|
+
{ className: "stat" },
|
|
77
|
+
div({ className: "stat-value" }, String(view.coreSkills.length)),
|
|
78
|
+
div({ className: "stat-label" }, "Core Skills"),
|
|
79
|
+
),
|
|
80
|
+
div(
|
|
81
|
+
{ className: "stat" },
|
|
82
|
+
div({ className: "stat-value" }, String(view.supportingSkills.length)),
|
|
83
|
+
div({ className: "stat-label" }, "Supporting Skills"),
|
|
84
|
+
),
|
|
85
|
+
div(
|
|
86
|
+
{ className: "stat" },
|
|
87
|
+
div({ className: "stat-value" }, String(view.broadSkills.length)),
|
|
88
|
+
div({ className: "stat-label" }, "Broad Skills"),
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
|
|
92
|
+
// Skills sections - all three columns
|
|
93
|
+
div(
|
|
94
|
+
{ className: "detail-section" },
|
|
95
|
+
heading2({ className: "section-title" }, "Skill Profile"),
|
|
96
|
+
div(
|
|
97
|
+
{ className: "content-columns" },
|
|
98
|
+
// Core skills
|
|
99
|
+
div(
|
|
100
|
+
{},
|
|
101
|
+
heading3(
|
|
102
|
+
{},
|
|
103
|
+
span({ className: "badge badge-primary" }, "Core Skills"),
|
|
104
|
+
),
|
|
105
|
+
view.coreSkills.length > 0
|
|
106
|
+
? ul(
|
|
107
|
+
{ className: "related-list" },
|
|
108
|
+
...view.coreSkills.map((s) =>
|
|
109
|
+
li(
|
|
110
|
+
{},
|
|
111
|
+
showBackLink
|
|
112
|
+
? a({ href: `#/skill/${s.id}` }, s.name)
|
|
113
|
+
: span({}, s.name),
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
: p({ className: "text-muted" }, "None"),
|
|
118
|
+
),
|
|
119
|
+
// Supporting skills
|
|
120
|
+
div(
|
|
121
|
+
{},
|
|
122
|
+
heading3(
|
|
123
|
+
{},
|
|
124
|
+
span({ className: "badge badge-secondary" }, "Supporting Skills"),
|
|
125
|
+
),
|
|
126
|
+
view.supportingSkills.length > 0
|
|
127
|
+
? ul(
|
|
128
|
+
{ className: "related-list" },
|
|
129
|
+
...view.supportingSkills.map((s) =>
|
|
130
|
+
li(
|
|
131
|
+
{},
|
|
132
|
+
showBackLink
|
|
133
|
+
? a({ href: `#/skill/${s.id}` }, s.name)
|
|
134
|
+
: span({}, s.name),
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
: p({ className: "text-muted" }, "None"),
|
|
139
|
+
),
|
|
140
|
+
// Broad skills
|
|
141
|
+
div(
|
|
142
|
+
{},
|
|
143
|
+
heading3(
|
|
144
|
+
{},
|
|
145
|
+
span({ className: "badge badge-broad" }, "Broad Skills"),
|
|
146
|
+
),
|
|
147
|
+
view.broadSkills.length > 0
|
|
148
|
+
? ul(
|
|
149
|
+
{ className: "related-list" },
|
|
150
|
+
...view.broadSkills.map((s) =>
|
|
151
|
+
li(
|
|
152
|
+
{},
|
|
153
|
+
showBackLink
|
|
154
|
+
? a({ href: `#/skill/${s.id}` }, s.name)
|
|
155
|
+
: span({}, s.name),
|
|
156
|
+
),
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
: p({ className: "text-muted" }, "None"),
|
|
160
|
+
),
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
|
|
164
|
+
// Behaviour modifiers
|
|
165
|
+
showBehaviourModifiers && view.behaviourModifiers.length > 0
|
|
166
|
+
? div(
|
|
167
|
+
{ className: "detail-section" },
|
|
168
|
+
heading2({ className: "section-title" }, "Behaviour Modifiers"),
|
|
169
|
+
...view.behaviourModifiers.map((b) =>
|
|
170
|
+
div(
|
|
171
|
+
{ className: "list-item" },
|
|
172
|
+
showBackLink
|
|
173
|
+
? a({ href: `#/behaviour/${b.id}` }, b.name)
|
|
174
|
+
: span({}, b.name),
|
|
175
|
+
" ",
|
|
176
|
+
span(
|
|
177
|
+
{
|
|
178
|
+
className: `modifier ${b.modifier > 0 ? "modifier-positive" : b.modifier < 0 ? "modifier-negative" : "modifier-neutral"}`,
|
|
179
|
+
},
|
|
180
|
+
b.modifier > 0 ? `+${b.modifier}` : String(b.modifier),
|
|
181
|
+
),
|
|
182
|
+
),
|
|
183
|
+
),
|
|
184
|
+
)
|
|
185
|
+
: null,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discipline formatting for markdown/CLI output
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { tableToMarkdown } from "../shared.js";
|
|
6
|
+
import { prepareDisciplinesList, prepareDisciplineDetail } from "./shared.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format discipline list as markdown
|
|
10
|
+
* @param {Array} disciplines - Raw discipline entities
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function disciplineListToMarkdown(disciplines) {
|
|
14
|
+
const { items } = prepareDisciplinesList(disciplines);
|
|
15
|
+
const lines = ["# 📋 Disciplines", ""];
|
|
16
|
+
|
|
17
|
+
const rows = items.map((d) => [
|
|
18
|
+
d.name,
|
|
19
|
+
`${d.coreSkillsCount}C`,
|
|
20
|
+
`${d.supportingSkillsCount}S`,
|
|
21
|
+
`${d.broadSkillsCount}B`,
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
lines.push(
|
|
25
|
+
tableToMarkdown(["Discipline", "Core", "Supporting", "Broad"], rows),
|
|
26
|
+
);
|
|
27
|
+
lines.push("");
|
|
28
|
+
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format discipline detail as markdown
|
|
34
|
+
* @param {Object} discipline - Raw discipline entity
|
|
35
|
+
* @param {Object} context - Additional context
|
|
36
|
+
* @param {Array} context.skills - All skills
|
|
37
|
+
* @param {Array} context.behaviours - All behaviours
|
|
38
|
+
* @param {boolean} [context.showBehaviourModifiers=true] - Whether to show behaviour modifiers section
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function disciplineToMarkdown(
|
|
42
|
+
discipline,
|
|
43
|
+
{ skills, behaviours, showBehaviourModifiers = true } = {},
|
|
44
|
+
) {
|
|
45
|
+
const view = prepareDisciplineDetail(discipline, { skills, behaviours });
|
|
46
|
+
const lines = [`# 📋 ${view.name}`, "", view.description, ""];
|
|
47
|
+
|
|
48
|
+
// Core skills
|
|
49
|
+
if (view.coreSkills.length > 0) {
|
|
50
|
+
lines.push("## Core Skills", "");
|
|
51
|
+
for (const s of view.coreSkills) {
|
|
52
|
+
lines.push(`- ${s.name}`);
|
|
53
|
+
}
|
|
54
|
+
lines.push("");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Supporting skills
|
|
58
|
+
if (view.supportingSkills.length > 0) {
|
|
59
|
+
lines.push("## Supporting Skills", "");
|
|
60
|
+
for (const s of view.supportingSkills) {
|
|
61
|
+
lines.push(`- ${s.name}`);
|
|
62
|
+
}
|
|
63
|
+
lines.push("");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Broad skills
|
|
67
|
+
if (view.broadSkills.length > 0) {
|
|
68
|
+
lines.push("## Broad Skills", "");
|
|
69
|
+
for (const s of view.broadSkills) {
|
|
70
|
+
lines.push(`- ${s.name}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push("");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Behaviour modifiers
|
|
76
|
+
if (showBehaviourModifiers && view.behaviourModifiers.length > 0) {
|
|
77
|
+
lines.push("## Behaviour Modifiers", "");
|
|
78
|
+
const modifierRows = view.behaviourModifiers.map((b) => [
|
|
79
|
+
b.name,
|
|
80
|
+
b.modifier > 0 ? `+${b.modifier}` : `${b.modifier}`,
|
|
81
|
+
]);
|
|
82
|
+
lines.push(tableToMarkdown(["Behaviour", "Modifier"], modifierRows));
|
|
83
|
+
lines.push("");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discipline presentation helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for formatting discipline data across DOM and markdown outputs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { truncate } from "../shared.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get items by their IDs
|
|
11
|
+
* @param {Array} items - Array of items with id property
|
|
12
|
+
* @param {string[]} ids - Array of IDs to find
|
|
13
|
+
* @returns {Array} - Found items
|
|
14
|
+
*/
|
|
15
|
+
function getItemsByIds(items, ids) {
|
|
16
|
+
if (!ids) return [];
|
|
17
|
+
return ids.map((id) => items.find((item) => item.id === id)).filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get discipline display name
|
|
22
|
+
* @param {Object} discipline
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function getDisciplineDisplayName(discipline) {
|
|
26
|
+
return discipline.specialization || discipline.name || discipline.id;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} DisciplineListItem
|
|
31
|
+
* @property {string} id
|
|
32
|
+
* @property {string} name
|
|
33
|
+
* @property {string} description
|
|
34
|
+
* @property {string} truncatedDescription
|
|
35
|
+
* @property {number} coreSkillsCount
|
|
36
|
+
* @property {number} supportingSkillsCount
|
|
37
|
+
* @property {number} broadSkillsCount
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Transform disciplines for list view
|
|
42
|
+
* @param {Array} disciplines - Raw discipline entities
|
|
43
|
+
* @param {number} [descriptionLimit=120] - Maximum description length
|
|
44
|
+
* @returns {{ items: DisciplineListItem[] }}
|
|
45
|
+
*/
|
|
46
|
+
export function prepareDisciplinesList(disciplines, descriptionLimit = 120) {
|
|
47
|
+
const items = disciplines.map((discipline) => ({
|
|
48
|
+
id: discipline.id,
|
|
49
|
+
name: getDisciplineDisplayName(discipline),
|
|
50
|
+
description: discipline.description,
|
|
51
|
+
truncatedDescription: truncate(discipline.description, descriptionLimit),
|
|
52
|
+
coreSkillsCount: discipline.coreSkills?.length || 0,
|
|
53
|
+
supportingSkillsCount: discipline.supportingSkills?.length || 0,
|
|
54
|
+
broadSkillsCount: discipline.broadSkills?.length || 0,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
return { items };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {Object} SkillReference
|
|
62
|
+
* @property {string} id
|
|
63
|
+
* @property {string} name
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} BehaviourModifier
|
|
68
|
+
* @property {string} id
|
|
69
|
+
* @property {string} name
|
|
70
|
+
* @property {number} modifier
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @typedef {Object} DisciplineDetailView
|
|
75
|
+
* @property {string} id
|
|
76
|
+
* @property {string} name
|
|
77
|
+
* @property {string} description
|
|
78
|
+
* @property {SkillReference[]} coreSkills
|
|
79
|
+
* @property {SkillReference[]} supportingSkills
|
|
80
|
+
* @property {SkillReference[]} broadSkills
|
|
81
|
+
* @property {BehaviourModifier[]} behaviourModifiers
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Transform discipline for detail view
|
|
86
|
+
* @param {Object} discipline - Raw discipline entity
|
|
87
|
+
* @param {Object} context - Additional context
|
|
88
|
+
* @param {Array} context.skills - All skills
|
|
89
|
+
* @param {Array} context.behaviours - All behaviours
|
|
90
|
+
* @returns {DisciplineDetailView|null}
|
|
91
|
+
*/
|
|
92
|
+
export function prepareDisciplineDetail(discipline, { skills, behaviours }) {
|
|
93
|
+
if (!discipline) return null;
|
|
94
|
+
|
|
95
|
+
const coreSkills = getItemsByIds(skills, discipline.coreSkills).map((s) => ({
|
|
96
|
+
id: s.id,
|
|
97
|
+
name: s.name,
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
const supportingSkills = getItemsByIds(
|
|
101
|
+
skills,
|
|
102
|
+
discipline.supportingSkills,
|
|
103
|
+
).map((s) => ({ id: s.id, name: s.name }));
|
|
104
|
+
|
|
105
|
+
const broadSkills = getItemsByIds(skills, discipline.broadSkills).map(
|
|
106
|
+
(s) => ({ id: s.id, name: s.name }),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const behaviourModifiers = discipline.behaviourModifiers
|
|
110
|
+
? Object.entries(discipline.behaviourModifiers).map(
|
|
111
|
+
([behaviourId, modifier]) => {
|
|
112
|
+
const behaviour = behaviours.find((b) => b.id === behaviourId);
|
|
113
|
+
return {
|
|
114
|
+
id: behaviourId,
|
|
115
|
+
name: behaviour?.name || behaviourId,
|
|
116
|
+
modifier,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
: [];
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
id: discipline.id,
|
|
124
|
+
name: getDisciplineDisplayName(discipline),
|
|
125
|
+
description: discipline.description,
|
|
126
|
+
coreSkills,
|
|
127
|
+
supportingSkills,
|
|
128
|
+
broadSkills,
|
|
129
|
+
behaviourModifiers,
|
|
130
|
+
};
|
|
131
|
+
}
|