@forwardimpact/pathway 0.1.0 → 0.2.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 +109 -21
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +43 -29
- package/app/commands/progress.js +14 -7
- package/app/commands/serve.js +5 -0
- package/app/commands/stage.js +0 -10
- package/app/commands/track.js +5 -8
- package/app/components/builder.js +111 -27
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +113 -87
- package/app/formatters/agent/skill.js +64 -31
- package/app/formatters/behaviour/dom.js +3 -0
- package/app/formatters/behaviour/microdata.js +106 -0
- package/app/formatters/discipline/dom.js +28 -1
- package/app/formatters/discipline/microdata.js +117 -0
- package/app/formatters/discipline/shared.js +49 -8
- package/app/formatters/driver/dom.js +3 -0
- package/app/formatters/driver/microdata.js +91 -0
- package/app/formatters/grade/dom.js +3 -0
- package/app/formatters/grade/microdata.js +151 -0
- package/app/formatters/index.js +32 -1
- package/app/formatters/interview/shared.js +13 -8
- package/app/formatters/job/description.js +5 -3
- package/app/formatters/json-ld.js +242 -0
- package/app/formatters/microdata-shared.js +184 -0
- package/app/formatters/progress/shared.js +14 -11
- package/app/formatters/skill/dom.js +3 -0
- package/app/formatters/skill/microdata.js +151 -0
- package/app/formatters/stage/dom.js +3 -18
- package/app/formatters/stage/microdata.js +110 -0
- package/app/formatters/stage/shared.js +0 -27
- package/app/formatters/track/dom.js +5 -30
- package/app/formatters/track/markdown.js +2 -25
- package/app/formatters/track/microdata.js +111 -0
- package/app/formatters/track/shared.js +6 -58
- package/app/handout-main.js +26 -12
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/job-cache.js +12 -9
- package/app/lib/template-loader.js +66 -0
- package/app/lib/yaml-loader.js +25 -8
- package/app/main.js +8 -4
- package/app/model/agent.js +158 -130
- package/app/model/checklist.js +57 -91
- package/app/model/derivation.js +135 -68
- package/app/model/index-generator.js +1 -7
- package/app/model/job.js +19 -13
- package/app/model/levels.js +20 -12
- package/app/model/loader.js +41 -17
- package/app/model/matching.js +33 -3
- package/app/model/profile.js +38 -45
- package/app/model/schema-validation.js +438 -0
- package/app/model/validation.js +747 -68
- package/app/pages/agent-builder.js +119 -25
- package/app/pages/assessment-results.js +10 -4
- package/app/pages/discipline.js +36 -6
- package/app/pages/driver.js +9 -47
- package/app/pages/interview-builder.js +3 -1
- package/app/pages/interview.js +15 -4
- package/app/pages/job-builder.js +4 -1
- package/app/pages/job.js +15 -4
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +72 -21
- package/app/pages/stage.js +3 -126
- package/app/slide-main.js +45 -17
- package/app/slides/index.js +3 -1
- package/app/slides/overview.js +40 -4
- package/app/slides/progress.js +4 -2
- package/bin/pathway.js +18 -64
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
- package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
- package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
- package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
- package/examples/agents/.vscode/settings.json +1 -1
- package/examples/behaviours/outcome_ownership.yaml +1 -2
- package/examples/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/behaviours/precise_communication.yaml +1 -2
- package/examples/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/behaviours/systems_thinking.yaml +1 -2
- package/examples/capabilities/business.yaml +80 -142
- package/examples/capabilities/delivery.yaml +155 -219
- package/examples/capabilities/people.yaml +2 -34
- package/examples/capabilities/reliability.yaml +161 -80
- package/examples/capabilities/scale.yaml +234 -252
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +1 -0
- package/examples/disciplines/data_engineering.yaml +14 -12
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +14 -12
- package/examples/drivers.yaml +1 -4
- package/examples/framework.yaml +1 -2
- package/examples/grades.yaml +1 -3
- package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
- package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/questions/behaviours/precise_communication.yaml +1 -2
- package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/questions/behaviours/systems_thinking.yaml +1 -2
- package/examples/questions/skills/architecture_design.yaml +1 -2
- package/examples/questions/skills/cloud_platforms.yaml +1 -2
- package/examples/questions/skills/code_quality.yaml +1 -2
- package/examples/questions/skills/data_modeling.yaml +1 -2
- package/examples/questions/skills/devops.yaml +1 -2
- package/examples/questions/skills/full_stack_development.yaml +1 -2
- package/examples/questions/skills/sre_practices.yaml +1 -2
- package/examples/questions/skills/stakeholder_management.yaml +1 -2
- package/examples/questions/skills/team_collaboration.yaml +1 -2
- package/examples/questions/skills/technical_writing.yaml +1 -2
- package/examples/self-assessments.yaml +1 -3
- package/examples/stages.yaml +101 -46
- package/examples/tracks/_index.yaml +0 -1
- package/examples/tracks/platform.yaml +8 -13
- package/examples/tracks/sre.yaml +8 -18
- package/examples/vscode-settings.yaml +2 -7
- package/package.json +9 -3
- package/templates/agent.template.md +65 -0
- package/templates/skill.template.md +28 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
- package/examples/tracks/manager.yaml +0 -53
|
@@ -38,6 +38,9 @@ const ALL_STAGES_VALUE = "all";
|
|
|
38
38
|
/** @type {Object|null} Cached agent data */
|
|
39
39
|
let agentDataCache = null;
|
|
40
40
|
|
|
41
|
+
/** @type {{agent: string, skill: string}|null} Cached templates */
|
|
42
|
+
let templateCache = null;
|
|
43
|
+
|
|
41
44
|
/**
|
|
42
45
|
* Load agent data with caching
|
|
43
46
|
* @param {string} dataDir - Data directory path
|
|
@@ -50,6 +53,24 @@ async function getAgentData(dataDir = "./data") {
|
|
|
50
53
|
return agentDataCache;
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Load templates with caching
|
|
58
|
+
* @returns {Promise<{agent: string, skill: string}>}
|
|
59
|
+
*/
|
|
60
|
+
async function getTemplates() {
|
|
61
|
+
if (!templateCache) {
|
|
62
|
+
const [agentRes, skillRes] = await Promise.all([
|
|
63
|
+
fetch("./templates/agent.template.md"),
|
|
64
|
+
fetch("./templates/skill.template.md"),
|
|
65
|
+
]);
|
|
66
|
+
templateCache = {
|
|
67
|
+
agent: await agentRes.text(),
|
|
68
|
+
skill: await skillRes.text(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return templateCache;
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
/**
|
|
54
75
|
* Render agent builder page
|
|
55
76
|
*/
|
|
@@ -64,8 +85,11 @@ export async function renderAgentBuilder() {
|
|
|
64
85
|
),
|
|
65
86
|
);
|
|
66
87
|
|
|
67
|
-
// Load agent-specific data
|
|
68
|
-
const agentData = await
|
|
88
|
+
// Load agent-specific data and templates
|
|
89
|
+
const [agentData, templates] = await Promise.all([
|
|
90
|
+
getAgentData(),
|
|
91
|
+
getTemplates(),
|
|
92
|
+
]);
|
|
69
93
|
|
|
70
94
|
// Filter to only disciplines/tracks that have agent definitions
|
|
71
95
|
const agentDisciplineIds = new Set(agentData.disciplines.map((d) => d.id));
|
|
@@ -87,10 +111,13 @@ export async function renderAgentBuilder() {
|
|
|
87
111
|
];
|
|
88
112
|
|
|
89
113
|
// Parse URL params for pre-selection
|
|
114
|
+
// Supports: /agent/discipline, /agent/discipline/track, /agent/discipline/track/stage
|
|
90
115
|
const hash = window.location.hash;
|
|
91
|
-
const pathMatch = hash.match(
|
|
116
|
+
const pathMatch = hash.match(
|
|
117
|
+
/#\/agent\/([^/]+)(?:\/([^/]+))?(?:\/([^/?]+))?/,
|
|
118
|
+
);
|
|
92
119
|
const initialDiscipline = pathMatch ? pathMatch[1] : "";
|
|
93
|
-
const initialTrack = pathMatch ? pathMatch[2] : "";
|
|
120
|
+
const initialTrack = pathMatch && pathMatch[2] ? pathMatch[2] : "";
|
|
94
121
|
const initialStage =
|
|
95
122
|
pathMatch && pathMatch[3] ? pathMatch[3] : ALL_STAGES_VALUE;
|
|
96
123
|
|
|
@@ -113,9 +140,10 @@ export async function renderAgentBuilder() {
|
|
|
113
140
|
*/
|
|
114
141
|
function updatePreview({ discipline, track, stage }) {
|
|
115
142
|
// Update URL without triggering navigation
|
|
116
|
-
if (discipline
|
|
143
|
+
if (discipline) {
|
|
144
|
+
const trackPart = track ? `/${track}` : "";
|
|
117
145
|
const stagePart = stage && stage !== ALL_STAGES_VALUE ? `/${stage}` : "";
|
|
118
|
-
const newHash = `#/agent/${discipline}
|
|
146
|
+
const newHash = `#/agent/${discipline}${trackPart}${stagePart}`;
|
|
119
147
|
if (window.location.hash !== newHash) {
|
|
120
148
|
history.replaceState(null, "", newHash);
|
|
121
149
|
}
|
|
@@ -123,7 +151,7 @@ export async function renderAgentBuilder() {
|
|
|
123
151
|
|
|
124
152
|
previewContainer.innerHTML = "";
|
|
125
153
|
|
|
126
|
-
if (!discipline
|
|
154
|
+
if (!discipline) {
|
|
127
155
|
previewContainer.appendChild(
|
|
128
156
|
createEmptyState(availableDisciplines.length, availableTracks.length),
|
|
129
157
|
);
|
|
@@ -132,7 +160,7 @@ export async function renderAgentBuilder() {
|
|
|
132
160
|
|
|
133
161
|
// Get full objects
|
|
134
162
|
const humanDiscipline = data.disciplines.find((d) => d.id === discipline);
|
|
135
|
-
const humanTrack = data.tracks.find((t) => t.id === track);
|
|
163
|
+
const humanTrack = track ? data.tracks.find((t) => t.id === track) : null;
|
|
136
164
|
const agentDiscipline = agentData.disciplines.find(
|
|
137
165
|
(d) => d.id === discipline,
|
|
138
166
|
);
|
|
@@ -164,6 +192,8 @@ export async function renderAgentBuilder() {
|
|
|
164
192
|
agentBehaviours: agentData.behaviours,
|
|
165
193
|
capabilities: data.capabilities,
|
|
166
194
|
vscodeSettings: agentData.vscodeSettings,
|
|
195
|
+
devcontainer: agentData.devcontainer,
|
|
196
|
+
templates,
|
|
167
197
|
};
|
|
168
198
|
|
|
169
199
|
// Generate preview based on stage selection
|
|
@@ -325,6 +355,8 @@ function createAllStagesPreview(context) {
|
|
|
325
355
|
agentBehaviours,
|
|
326
356
|
capabilities,
|
|
327
357
|
vscodeSettings,
|
|
358
|
+
devcontainer,
|
|
359
|
+
templates,
|
|
328
360
|
} = context;
|
|
329
361
|
|
|
330
362
|
// Generate all stage agents
|
|
@@ -378,7 +410,13 @@ function createAllStagesPreview(context) {
|
|
|
378
410
|
{ className: "agent-deployment" },
|
|
379
411
|
|
|
380
412
|
// Download all button
|
|
381
|
-
createDownloadAllButton(
|
|
413
|
+
createDownloadAllButton(
|
|
414
|
+
stageAgents,
|
|
415
|
+
skillFiles,
|
|
416
|
+
vscodeSettings,
|
|
417
|
+
devcontainer,
|
|
418
|
+
context,
|
|
419
|
+
),
|
|
382
420
|
|
|
383
421
|
// Agents section
|
|
384
422
|
section(
|
|
@@ -391,7 +429,7 @@ function createAllStagesPreview(context) {
|
|
|
391
429
|
div(
|
|
392
430
|
{ className: "agent-cards-grid" },
|
|
393
431
|
...stageAgents.map(({ stage, profile }) =>
|
|
394
|
-
createAgentCard(stage, profile, stages),
|
|
432
|
+
createAgentCard(stage, profile, stages, templates.agent),
|
|
395
433
|
),
|
|
396
434
|
),
|
|
397
435
|
),
|
|
@@ -403,7 +441,9 @@ function createAllStagesPreview(context) {
|
|
|
403
441
|
skillFiles.length > 0
|
|
404
442
|
? div(
|
|
405
443
|
{ className: "skill-cards-grid" },
|
|
406
|
-
...skillFiles.map((skill) =>
|
|
444
|
+
...skillFiles.map((skill) =>
|
|
445
|
+
createSkillCard(skill, templates.skill),
|
|
446
|
+
),
|
|
407
447
|
)
|
|
408
448
|
: p(
|
|
409
449
|
{ className: "text-muted" },
|
|
@@ -434,7 +474,9 @@ function createSingleStagePreview(context, stage) {
|
|
|
434
474
|
agentBehaviours,
|
|
435
475
|
capabilities,
|
|
436
476
|
vscodeSettings,
|
|
477
|
+
devcontainer,
|
|
437
478
|
stages,
|
|
479
|
+
templates,
|
|
438
480
|
} = context;
|
|
439
481
|
|
|
440
482
|
// Derive stage agent
|
|
@@ -483,7 +525,13 @@ function createSingleStagePreview(context, stage) {
|
|
|
483
525
|
{ className: "agent-deployment" },
|
|
484
526
|
|
|
485
527
|
// Download button for single stage
|
|
486
|
-
createDownloadSingleButton(
|
|
528
|
+
createDownloadSingleButton(
|
|
529
|
+
profile,
|
|
530
|
+
skillFiles,
|
|
531
|
+
vscodeSettings,
|
|
532
|
+
devcontainer,
|
|
533
|
+
templates,
|
|
534
|
+
),
|
|
487
535
|
|
|
488
536
|
// Agents section (single card)
|
|
489
537
|
section(
|
|
@@ -491,7 +539,7 @@ function createSingleStagePreview(context, stage) {
|
|
|
491
539
|
h2({}, "Agent"),
|
|
492
540
|
div(
|
|
493
541
|
{ className: "agent-cards-grid single" },
|
|
494
|
-
createAgentCard(stage, profile, stages, derived),
|
|
542
|
+
createAgentCard(stage, profile, stages, templates.agent, derived),
|
|
495
543
|
),
|
|
496
544
|
),
|
|
497
545
|
|
|
@@ -502,7 +550,9 @@ function createSingleStagePreview(context, stage) {
|
|
|
502
550
|
skillFiles.length > 0
|
|
503
551
|
? div(
|
|
504
552
|
{ className: "skill-cards-grid" },
|
|
505
|
-
...skillFiles.map((skill) =>
|
|
553
|
+
...skillFiles.map((skill) =>
|
|
554
|
+
createSkillCard(skill, templates.skill),
|
|
555
|
+
),
|
|
506
556
|
)
|
|
507
557
|
: p(
|
|
508
558
|
{ className: "text-muted" },
|
|
@@ -520,11 +570,12 @@ function createSingleStagePreview(context, stage) {
|
|
|
520
570
|
* @param {Object} stage - Stage object
|
|
521
571
|
* @param {Object} profile - Generated profile
|
|
522
572
|
* @param {Array} stages - All stages for emoji lookup
|
|
573
|
+
* @param {string} agentTemplate - Mustache template for agent profile
|
|
523
574
|
* @param {Object} [_derived] - Optional derived agent data for extra info
|
|
524
575
|
* @returns {HTMLElement}
|
|
525
576
|
*/
|
|
526
|
-
function createAgentCard(stage, profile, stages, _derived) {
|
|
527
|
-
const content = formatAgentProfile(profile);
|
|
577
|
+
function createAgentCard(stage, profile, stages, agentTemplate, _derived) {
|
|
578
|
+
const content = formatAgentProfile(profile, agentTemplate);
|
|
528
579
|
const stageEmoji = getStageEmoji(stages, stage.id);
|
|
529
580
|
|
|
530
581
|
const card = div(
|
|
@@ -548,10 +599,11 @@ function createAgentCard(stage, profile, stages, _derived) {
|
|
|
548
599
|
/**
|
|
549
600
|
* Create a skill card
|
|
550
601
|
* @param {Object} skill - Skill with frontmatter and body
|
|
602
|
+
* @param {string} skillTemplate - Mustache template for skill
|
|
551
603
|
* @returns {HTMLElement}
|
|
552
604
|
*/
|
|
553
|
-
function createSkillCard(skill) {
|
|
554
|
-
const content = formatAgentSkill(skill);
|
|
605
|
+
function createSkillCard(skill, skillTemplate) {
|
|
606
|
+
const content = formatAgentSkill(skill, skillTemplate);
|
|
555
607
|
const filename = `${skill.dirname}/SKILL.md`;
|
|
556
608
|
|
|
557
609
|
return div(
|
|
@@ -615,16 +667,18 @@ function createCopyButton(content) {
|
|
|
615
667
|
* @param {Array} stageAgents - Array of {stage, derived, profile}
|
|
616
668
|
* @param {Array} skillFiles - Array of skill file objects
|
|
617
669
|
* @param {Object} vscodeSettings - VS Code settings
|
|
618
|
-
* @param {Object}
|
|
670
|
+
* @param {Object} devcontainer - Devcontainer config
|
|
671
|
+
* @param {Object} context - Context with discipline/track info and templates
|
|
619
672
|
* @returns {HTMLElement}
|
|
620
673
|
*/
|
|
621
674
|
function createDownloadAllButton(
|
|
622
675
|
stageAgents,
|
|
623
676
|
skillFiles,
|
|
624
677
|
vscodeSettings,
|
|
678
|
+
devcontainer,
|
|
625
679
|
context,
|
|
626
680
|
) {
|
|
627
|
-
const { humanDiscipline, humanTrack } = context;
|
|
681
|
+
const { humanDiscipline, humanTrack, templates } = context;
|
|
628
682
|
const agentName = `${humanDiscipline.id}-${humanTrack.id}`.replace(/_/g, "-");
|
|
629
683
|
|
|
630
684
|
const btn = document.createElement("button");
|
|
@@ -641,13 +695,13 @@ function createDownloadAllButton(
|
|
|
641
695
|
|
|
642
696
|
// Add all stage agent profiles
|
|
643
697
|
for (const { profile } of stageAgents) {
|
|
644
|
-
const content = formatAgentProfile(profile);
|
|
698
|
+
const content = formatAgentProfile(profile, templates.agent);
|
|
645
699
|
zip.file(`.github/agents/${profile.filename}`, content);
|
|
646
700
|
}
|
|
647
701
|
|
|
648
702
|
// Add skills
|
|
649
703
|
for (const skill of skillFiles) {
|
|
650
|
-
const content = formatAgentSkill(skill);
|
|
704
|
+
const content = formatAgentSkill(skill, templates.skill);
|
|
651
705
|
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, content);
|
|
652
706
|
}
|
|
653
707
|
|
|
@@ -659,6 +713,22 @@ function createDownloadAllButton(
|
|
|
659
713
|
);
|
|
660
714
|
}
|
|
661
715
|
|
|
716
|
+
// Add devcontainer.json with VS Code settings embedded
|
|
717
|
+
if (devcontainer && Object.keys(devcontainer).length > 0) {
|
|
718
|
+
const devcontainerJson = {
|
|
719
|
+
...devcontainer,
|
|
720
|
+
customizations: {
|
|
721
|
+
vscode: {
|
|
722
|
+
settings: vscodeSettings,
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
};
|
|
726
|
+
zip.file(
|
|
727
|
+
".devcontainer/devcontainer.json",
|
|
728
|
+
JSON.stringify(devcontainerJson, null, 2) + "\n",
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
662
732
|
// Generate and download
|
|
663
733
|
const blob = await zip.generateAsync({ type: "blob" });
|
|
664
734
|
const url = URL.createObjectURL(blob);
|
|
@@ -685,9 +755,17 @@ function createDownloadAllButton(
|
|
|
685
755
|
* @param {Object} profile - Agent profile
|
|
686
756
|
* @param {Array} skillFiles - Skill files
|
|
687
757
|
* @param {Object} vscodeSettings - VS Code settings
|
|
758
|
+
* @param {Object} devcontainer - Devcontainer config
|
|
759
|
+
* @param {{agent: string, skill: string}} templates - Mustache templates
|
|
688
760
|
* @returns {HTMLElement}
|
|
689
761
|
*/
|
|
690
|
-
function createDownloadSingleButton(
|
|
762
|
+
function createDownloadSingleButton(
|
|
763
|
+
profile,
|
|
764
|
+
skillFiles,
|
|
765
|
+
vscodeSettings,
|
|
766
|
+
devcontainer,
|
|
767
|
+
templates,
|
|
768
|
+
) {
|
|
691
769
|
const btn = document.createElement("button");
|
|
692
770
|
btn.className = "btn btn-primary download-all-btn";
|
|
693
771
|
btn.textContent = "📥 Download Agent (.zip)";
|
|
@@ -701,12 +779,12 @@ function createDownloadSingleButton(profile, skillFiles, vscodeSettings) {
|
|
|
701
779
|
const zip = new JSZip();
|
|
702
780
|
|
|
703
781
|
// Add profile
|
|
704
|
-
const content = formatAgentProfile(profile);
|
|
782
|
+
const content = formatAgentProfile(profile, templates.agent);
|
|
705
783
|
zip.file(`.github/agents/${profile.filename}`, content);
|
|
706
784
|
|
|
707
785
|
// Add skills
|
|
708
786
|
for (const skill of skillFiles) {
|
|
709
|
-
const skillContent = formatAgentSkill(skill);
|
|
787
|
+
const skillContent = formatAgentSkill(skill, templates.skill);
|
|
710
788
|
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, skillContent);
|
|
711
789
|
}
|
|
712
790
|
|
|
@@ -718,6 +796,22 @@ function createDownloadSingleButton(profile, skillFiles, vscodeSettings) {
|
|
|
718
796
|
);
|
|
719
797
|
}
|
|
720
798
|
|
|
799
|
+
// Add devcontainer.json with VS Code settings embedded
|
|
800
|
+
if (devcontainer && Object.keys(devcontainer).length > 0) {
|
|
801
|
+
const devcontainerJson = {
|
|
802
|
+
...devcontainer,
|
|
803
|
+
customizations: {
|
|
804
|
+
vscode: {
|
|
805
|
+
settings: vscodeSettings,
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
};
|
|
809
|
+
zip.file(
|
|
810
|
+
".devcontainer/devcontainer.json",
|
|
811
|
+
JSON.stringify(devcontainerJson, null, 2) + "\n",
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
|
|
721
815
|
// Generate and download
|
|
722
816
|
const blob = await zip.generateAsync({ type: "blob" });
|
|
723
817
|
const url = URL.createObjectURL(blob);
|
|
@@ -379,7 +379,9 @@ function createMatchCard(match, _index, _selfAssessment, _data) {
|
|
|
379
379
|
{ className: "match-job-title" },
|
|
380
380
|
a(
|
|
381
381
|
{
|
|
382
|
-
href:
|
|
382
|
+
href: job.track
|
|
383
|
+
? `#/job/${job.discipline.id}/${job.grade.id}/${job.track.id}`
|
|
384
|
+
: `#/job/${job.discipline.id}/${job.grade.id}`,
|
|
383
385
|
},
|
|
384
386
|
job.title,
|
|
385
387
|
),
|
|
@@ -388,7 +390,7 @@ function createMatchCard(match, _index, _selfAssessment, _data) {
|
|
|
388
390
|
{ className: "match-badges" },
|
|
389
391
|
createBadge(job.discipline.name, "default"),
|
|
390
392
|
createBadge(job.grade.name, "secondary"),
|
|
391
|
-
createBadge(job.track.name, "broad"),
|
|
393
|
+
job.track && createBadge(job.track.name, "broad"),
|
|
392
394
|
),
|
|
393
395
|
),
|
|
394
396
|
div(
|
|
@@ -437,14 +439,18 @@ function createMatchCard(match, _index, _selfAssessment, _data) {
|
|
|
437
439
|
{ className: "match-card-actions" },
|
|
438
440
|
a(
|
|
439
441
|
{
|
|
440
|
-
href:
|
|
442
|
+
href: job.track
|
|
443
|
+
? `#/job/${job.discipline.id}/${job.grade.id}/${job.track.id}`
|
|
444
|
+
: `#/job/${job.discipline.id}/${job.grade.id}`,
|
|
441
445
|
className: "btn btn-secondary btn-sm",
|
|
442
446
|
},
|
|
443
447
|
"View Job Details",
|
|
444
448
|
),
|
|
445
449
|
a(
|
|
446
450
|
{
|
|
447
|
-
href:
|
|
451
|
+
href: job.track
|
|
452
|
+
? `#/interview/${job.discipline.id}/${job.grade.id}/${job.track.id}`
|
|
453
|
+
: `#/interview/${job.discipline.id}/${job.grade.id}`,
|
|
448
454
|
className: "btn btn-secondary btn-sm",
|
|
449
455
|
},
|
|
450
456
|
"Interview Prep",
|
package/app/pages/discipline.js
CHANGED
|
@@ -2,14 +2,40 @@
|
|
|
2
2
|
* Disciplines pages
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { render, div, h1, p } from "../lib/render.js";
|
|
5
|
+
import { render, div, h1, h2, p } from "../lib/render.js";
|
|
6
6
|
import { getState } from "../lib/state.js";
|
|
7
|
-
import {
|
|
7
|
+
import { createGroupedList } from "../components/list.js";
|
|
8
|
+
import { createBadge } from "../components/card.js";
|
|
8
9
|
import { renderNotFound } from "../components/error-page.js";
|
|
9
10
|
import { prepareDisciplinesList } from "../formatters/discipline/shared.js";
|
|
10
11
|
import { disciplineToDOM } from "../formatters/discipline/dom.js";
|
|
11
12
|
import { disciplineToCardConfig } from "../lib/card-mappers.js";
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Format discipline group name for display
|
|
16
|
+
* @param {string} groupName - Group name (professional/management)
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function formatDisciplineGroupName(groupName) {
|
|
20
|
+
if (groupName === "professional") return "Professional";
|
|
21
|
+
if (groupName === "management") return "Management";
|
|
22
|
+
return groupName.charAt(0).toUpperCase() + groupName.slice(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Render discipline group header
|
|
27
|
+
* @param {string} groupName - Group name
|
|
28
|
+
* @param {number} count - Number of items in group
|
|
29
|
+
* @returns {HTMLElement}
|
|
30
|
+
*/
|
|
31
|
+
function renderDisciplineGroupHeader(groupName, count) {
|
|
32
|
+
return div(
|
|
33
|
+
{ className: "capability-header" },
|
|
34
|
+
h2({ className: "capability-title" }, formatDisciplineGroupName(groupName)),
|
|
35
|
+
createBadge(`${count}`, "default"),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
/**
|
|
14
40
|
* Render disciplines list page
|
|
15
41
|
*/
|
|
@@ -17,8 +43,8 @@ export function renderDisciplinesList() {
|
|
|
17
43
|
const { data } = getState();
|
|
18
44
|
const { framework } = data;
|
|
19
45
|
|
|
20
|
-
// Transform data for list view
|
|
21
|
-
const {
|
|
46
|
+
// Transform data for list view (grouped by professional/management)
|
|
47
|
+
const { groups } = prepareDisciplinesList(data.disciplines);
|
|
22
48
|
|
|
23
49
|
const page = div(
|
|
24
50
|
{ className: "disciplines-page" },
|
|
@@ -35,8 +61,12 @@ export function renderDisciplinesList() {
|
|
|
35
61
|
),
|
|
36
62
|
),
|
|
37
63
|
|
|
38
|
-
// Disciplines list
|
|
39
|
-
|
|
64
|
+
// Disciplines list (grouped by type)
|
|
65
|
+
createGroupedList(
|
|
66
|
+
groups,
|
|
67
|
+
disciplineToCardConfig,
|
|
68
|
+
renderDisciplineGroupHeader,
|
|
69
|
+
),
|
|
40
70
|
);
|
|
41
71
|
|
|
42
72
|
render(page);
|
package/app/pages/driver.js
CHANGED
|
@@ -2,15 +2,12 @@
|
|
|
2
2
|
* Drivers pages
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { render, div, h1,
|
|
5
|
+
import { render, div, h1, p } from "../lib/render.js";
|
|
6
6
|
import { getState } from "../lib/state.js";
|
|
7
7
|
import { createCardList } from "../components/list.js";
|
|
8
|
-
import { createDetailHeader, createLinksList } from "../components/detail.js";
|
|
9
8
|
import { renderNotFound } from "../components/error-page.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
prepareDriverDetail,
|
|
13
|
-
} from "../formatters/driver/shared.js";
|
|
9
|
+
import { prepareDriversList } from "../formatters/driver/shared.js";
|
|
10
|
+
import { driverToDOM } from "../formatters/driver/dom.js";
|
|
14
11
|
import { driverToCardConfig } from "../lib/card-mappers.js";
|
|
15
12
|
|
|
16
13
|
/**
|
|
@@ -60,47 +57,12 @@ export function renderDriverDetail(params) {
|
|
|
60
57
|
return;
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const page = div(
|
|
70
|
-
{ className: "driver-detail" },
|
|
71
|
-
createDetailHeader({
|
|
72
|
-
title: view.name,
|
|
73
|
-
description: view.description,
|
|
74
|
-
backLink: "/driver",
|
|
75
|
-
backText: "← Back to Drivers",
|
|
60
|
+
// Use DOM formatter - it handles transformation internally
|
|
61
|
+
render(
|
|
62
|
+
driverToDOM(driver, {
|
|
63
|
+
skills: data.skills,
|
|
64
|
+
behaviours: data.behaviours,
|
|
65
|
+
framework: data.framework,
|
|
76
66
|
}),
|
|
77
|
-
|
|
78
|
-
// Contributing Skills and Contributing Behaviours in two columns
|
|
79
|
-
view.contributingSkills.length > 0 || view.contributingBehaviours.length > 0
|
|
80
|
-
? section(
|
|
81
|
-
{ className: "section section-detail" },
|
|
82
|
-
div(
|
|
83
|
-
{ className: "content-columns" },
|
|
84
|
-
// Contributing Skills column
|
|
85
|
-
view.contributingSkills.length > 0
|
|
86
|
-
? div(
|
|
87
|
-
{ className: "column" },
|
|
88
|
-
h2({ className: "section-title" }, "Contributing Skills"),
|
|
89
|
-
createLinksList(view.contributingSkills, "/skill"),
|
|
90
|
-
)
|
|
91
|
-
: null,
|
|
92
|
-
// Contributing Behaviours column
|
|
93
|
-
view.contributingBehaviours.length > 0
|
|
94
|
-
? div(
|
|
95
|
-
{ className: "column" },
|
|
96
|
-
h2({ className: "section-title" }, "Contributing Behaviours"),
|
|
97
|
-
createLinksList(view.contributingBehaviours, "/behaviour"),
|
|
98
|
-
)
|
|
99
|
-
: null,
|
|
100
|
-
),
|
|
101
|
-
)
|
|
102
|
-
: null,
|
|
103
67
|
);
|
|
104
|
-
|
|
105
|
-
render(page);
|
|
106
68
|
}
|
|
@@ -29,7 +29,9 @@ export function renderInterviewPrep() {
|
|
|
29
29
|
grades: data.grades,
|
|
30
30
|
}),
|
|
31
31
|
detailPath: (sel) =>
|
|
32
|
-
|
|
32
|
+
sel.track
|
|
33
|
+
? `/interview/${sel.discipline}/${sel.grade}/${sel.track}`
|
|
34
|
+
: `/interview/${sel.discipline}/${sel.grade}`,
|
|
33
35
|
renderPreview: createStandardPreview,
|
|
34
36
|
helpItems: [
|
|
35
37
|
{
|
package/app/pages/interview.js
CHANGED
|
@@ -32,18 +32,29 @@ import {
|
|
|
32
32
|
* @param {Object} params - Route params
|
|
33
33
|
*/
|
|
34
34
|
export function renderInterviewDetail(params) {
|
|
35
|
-
const { discipline: disciplineId,
|
|
35
|
+
const { discipline: disciplineId, grade: gradeId, track: trackId } = params;
|
|
36
36
|
const { data } = getState();
|
|
37
37
|
|
|
38
38
|
// Find the components
|
|
39
39
|
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
40
|
-
const track = data.tracks.find((t) => t.id === trackId);
|
|
41
40
|
const grade = data.grades.find((g) => g.id === gradeId);
|
|
41
|
+
const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
42
42
|
|
|
43
|
-
if (!discipline || !
|
|
43
|
+
if (!discipline || !grade) {
|
|
44
44
|
renderError({
|
|
45
45
|
title: "Interview Not Found",
|
|
46
|
-
message: "Invalid combination.
|
|
46
|
+
message: "Invalid combination. Discipline or grade not found.",
|
|
47
|
+
backPath: "/interview-prep",
|
|
48
|
+
backText: "← Back to Interview Prep",
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If trackId was provided but not found, error
|
|
54
|
+
if (trackId && !track) {
|
|
55
|
+
renderError({
|
|
56
|
+
title: "Interview Not Found",
|
|
57
|
+
message: `Track "${trackId}" not found.`,
|
|
47
58
|
backPath: "/interview-prep",
|
|
48
59
|
backText: "← Back to Interview Prep",
|
|
49
60
|
});
|
package/app/pages/job-builder.js
CHANGED
|
@@ -29,7 +29,10 @@ export function renderJobBuilder() {
|
|
|
29
29
|
behaviourCount: data.behaviours.length,
|
|
30
30
|
grades: data.grades,
|
|
31
31
|
}),
|
|
32
|
-
detailPath: (sel) =>
|
|
32
|
+
detailPath: (sel) =>
|
|
33
|
+
sel.track
|
|
34
|
+
? `/job/${sel.discipline}/${sel.grade}/${sel.track}`
|
|
35
|
+
: `/job/${sel.discipline}/${sel.grade}`,
|
|
33
36
|
renderPreview: createStandardPreview,
|
|
34
37
|
helpItems: [
|
|
35
38
|
{
|
package/app/pages/job.js
CHANGED
|
@@ -13,18 +13,29 @@ import { jobToDOM } from "../formatters/job/dom.js";
|
|
|
13
13
|
* @param {Object} params - Route params
|
|
14
14
|
*/
|
|
15
15
|
export function renderJobDetail(params) {
|
|
16
|
-
const { discipline: disciplineId,
|
|
16
|
+
const { discipline: disciplineId, grade: gradeId, track: trackId } = params;
|
|
17
17
|
const { data } = getState();
|
|
18
18
|
|
|
19
19
|
// Find the components
|
|
20
20
|
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
21
|
-
const track = data.tracks.find((t) => t.id === trackId);
|
|
22
21
|
const grade = data.grades.find((g) => g.id === gradeId);
|
|
22
|
+
const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
23
23
|
|
|
24
|
-
if (!discipline || !
|
|
24
|
+
if (!discipline || !grade) {
|
|
25
25
|
renderError({
|
|
26
26
|
title: "Job Not Found",
|
|
27
|
-
message: "Invalid job combination.
|
|
27
|
+
message: "Invalid job combination. Discipline or grade not found.",
|
|
28
|
+
backPath: "/job-builder",
|
|
29
|
+
backText: "← Back to Job Builder",
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// If trackId was provided but not found, error
|
|
35
|
+
if (trackId && !track) {
|
|
36
|
+
renderError({
|
|
37
|
+
title: "Job Not Found",
|
|
38
|
+
message: `Track "${trackId}" not found.`,
|
|
28
39
|
backPath: "/job-builder",
|
|
29
40
|
backText: "← Back to Job Builder",
|
|
30
41
|
});
|
package/app/pages/landing.js
CHANGED
|
@@ -89,16 +89,16 @@ export function renderLanding() {
|
|
|
89
89
|
label: "Disciplines",
|
|
90
90
|
href: "/discipline",
|
|
91
91
|
}),
|
|
92
|
-
createStatCard({
|
|
93
|
-
value: data.tracks.length,
|
|
94
|
-
label: "Tracks",
|
|
95
|
-
href: "/track",
|
|
96
|
-
}),
|
|
97
92
|
createStatCard({
|
|
98
93
|
value: data.grades.length,
|
|
99
94
|
label: "Grades",
|
|
100
95
|
href: "/grade",
|
|
101
96
|
}),
|
|
97
|
+
createStatCard({
|
|
98
|
+
value: data.tracks.length,
|
|
99
|
+
label: "Tracks",
|
|
100
|
+
href: "/track",
|
|
101
|
+
}),
|
|
102
102
|
createStatCard({
|
|
103
103
|
value: data.skills.length,
|
|
104
104
|
label: "Skills",
|
|
@@ -140,16 +140,16 @@ export function renderLanding() {
|
|
|
140
140
|
`${data.disciplines.length} ${framework.entityDefinitions.discipline.title.toLowerCase()} — ${framework.entityDefinitions.discipline.description.trim().split("\n")[0]}`,
|
|
141
141
|
"/discipline",
|
|
142
142
|
),
|
|
143
|
-
createQuickLinkCard(
|
|
144
|
-
`${getConceptEmoji(framework, "track")} ${framework.entityDefinitions.track.title}`,
|
|
145
|
-
`${data.tracks.length} ${framework.entityDefinitions.track.title.toLowerCase()} — ${framework.entityDefinitions.track.description.trim().split("\n")[0]}`,
|
|
146
|
-
"/track",
|
|
147
|
-
),
|
|
148
143
|
createQuickLinkCard(
|
|
149
144
|
`${getConceptEmoji(framework, "grade")} ${framework.entityDefinitions.grade.title}`,
|
|
150
145
|
`${data.grades.length} ${framework.entityDefinitions.grade.title.toLowerCase()} — ${framework.entityDefinitions.grade.description.trim().split("\n")[0]}`,
|
|
151
146
|
"/grade",
|
|
152
147
|
),
|
|
148
|
+
createQuickLinkCard(
|
|
149
|
+
`${getConceptEmoji(framework, "track")} ${framework.entityDefinitions.track.title}`,
|
|
150
|
+
`${data.tracks.length} ${framework.entityDefinitions.track.title.toLowerCase()} — ${framework.entityDefinitions.track.description.trim().split("\n")[0]}`,
|
|
151
|
+
"/track",
|
|
152
|
+
),
|
|
153
153
|
createQuickLinkCard(
|
|
154
154
|
`${getConceptEmoji(framework, "skill")} ${framework.entityDefinitions.skill.title}`,
|
|
155
155
|
`${data.skills.length} ${framework.entityDefinitions.skill.title.toLowerCase()} across ${capabilityCount} capabilities — ${framework.entityDefinitions.skill.description.trim().split("\n")[0]}`,
|
|
@@ -30,7 +30,9 @@ export function renderCareerProgress() {
|
|
|
30
30
|
tracks: data.tracks,
|
|
31
31
|
}),
|
|
32
32
|
detailPath: (sel) =>
|
|
33
|
-
|
|
33
|
+
sel.track
|
|
34
|
+
? `/progress/${sel.discipline}/${sel.grade}/${sel.track}`
|
|
35
|
+
: `/progress/${sel.discipline}/${sel.grade}`,
|
|
34
36
|
renderPreview: createProgressPreview,
|
|
35
37
|
labels: {
|
|
36
38
|
grade: "Current Grade",
|