@forwardimpact/pathway 0.1.0 → 0.3.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 +119 -31
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +52 -33
- 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 +117 -30
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +30 -115
- package/app/formatters/agent/skill.js +23 -44
- 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 +5 -4
- 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 +70 -81
- package/app/formatters/job/dom.js +40 -113
- package/app/formatters/job/markdown.js +17 -13
- 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/shared.js +7 -2
- 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/handout.html +7 -0
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/form-controls.js +64 -1
- package/app/lib/job-cache.js +12 -9
- package/app/lib/render.js +8 -1
- package/app/lib/template-loader.js +75 -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 +125 -28
- 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 +43 -8
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +78 -26
- package/app/pages/self-assessment.js +3 -3
- 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/app/slides.html +7 -0
- package/bin/pathway.js +28 -75
- 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 +14 -15
- 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/job.template.md +47 -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
|
@@ -26,7 +26,10 @@ import {
|
|
|
26
26
|
deriveAgentSkills,
|
|
27
27
|
deriveReferenceGrade,
|
|
28
28
|
} from "../model/agent.js";
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
createSelectWithValue,
|
|
31
|
+
createDisciplineSelect,
|
|
32
|
+
} from "../lib/form-controls.js";
|
|
30
33
|
import { createReactive } from "../lib/reactive.js";
|
|
31
34
|
import { getStageEmoji } from "../formatters/stage/shared.js";
|
|
32
35
|
import { formatAgentProfile } from "../formatters/agent/profile.js";
|
|
@@ -38,6 +41,9 @@ const ALL_STAGES_VALUE = "all";
|
|
|
38
41
|
/** @type {Object|null} Cached agent data */
|
|
39
42
|
let agentDataCache = null;
|
|
40
43
|
|
|
44
|
+
/** @type {{agent: string, skill: string}|null} Cached templates */
|
|
45
|
+
let templateCache = null;
|
|
46
|
+
|
|
41
47
|
/**
|
|
42
48
|
* Load agent data with caching
|
|
43
49
|
* @param {string} dataDir - Data directory path
|
|
@@ -50,6 +56,24 @@ async function getAgentData(dataDir = "./data") {
|
|
|
50
56
|
return agentDataCache;
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Load templates with caching
|
|
61
|
+
* @returns {Promise<{agent: string, skill: string}>}
|
|
62
|
+
*/
|
|
63
|
+
async function getTemplates() {
|
|
64
|
+
if (!templateCache) {
|
|
65
|
+
const [agentRes, skillRes] = await Promise.all([
|
|
66
|
+
fetch("./templates/agent.template.md"),
|
|
67
|
+
fetch("./templates/skill.template.md"),
|
|
68
|
+
]);
|
|
69
|
+
templateCache = {
|
|
70
|
+
agent: await agentRes.text(),
|
|
71
|
+
skill: await skillRes.text(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return templateCache;
|
|
75
|
+
}
|
|
76
|
+
|
|
53
77
|
/**
|
|
54
78
|
* Render agent builder page
|
|
55
79
|
*/
|
|
@@ -64,8 +88,11 @@ export async function renderAgentBuilder() {
|
|
|
64
88
|
),
|
|
65
89
|
);
|
|
66
90
|
|
|
67
|
-
// Load agent-specific data
|
|
68
|
-
const agentData = await
|
|
91
|
+
// Load agent-specific data and templates
|
|
92
|
+
const [agentData, templates] = await Promise.all([
|
|
93
|
+
getAgentData(),
|
|
94
|
+
getTemplates(),
|
|
95
|
+
]);
|
|
69
96
|
|
|
70
97
|
// Filter to only disciplines/tracks that have agent definitions
|
|
71
98
|
const agentDisciplineIds = new Set(agentData.disciplines.map((d) => d.id));
|
|
@@ -87,10 +114,13 @@ export async function renderAgentBuilder() {
|
|
|
87
114
|
];
|
|
88
115
|
|
|
89
116
|
// Parse URL params for pre-selection
|
|
117
|
+
// Supports: /agent/discipline, /agent/discipline/track, /agent/discipline/track/stage
|
|
90
118
|
const hash = window.location.hash;
|
|
91
|
-
const pathMatch = hash.match(
|
|
119
|
+
const pathMatch = hash.match(
|
|
120
|
+
/#\/agent\/([^/]+)(?:\/([^/]+))?(?:\/([^/?]+))?/,
|
|
121
|
+
);
|
|
92
122
|
const initialDiscipline = pathMatch ? pathMatch[1] : "";
|
|
93
|
-
const initialTrack = pathMatch ? pathMatch[2] : "";
|
|
123
|
+
const initialTrack = pathMatch && pathMatch[2] ? pathMatch[2] : "";
|
|
94
124
|
const initialStage =
|
|
95
125
|
pathMatch && pathMatch[3] ? pathMatch[3] : ALL_STAGES_VALUE;
|
|
96
126
|
|
|
@@ -113,9 +143,10 @@ export async function renderAgentBuilder() {
|
|
|
113
143
|
*/
|
|
114
144
|
function updatePreview({ discipline, track, stage }) {
|
|
115
145
|
// Update URL without triggering navigation
|
|
116
|
-
if (discipline
|
|
146
|
+
if (discipline) {
|
|
147
|
+
const trackPart = track ? `/${track}` : "";
|
|
117
148
|
const stagePart = stage && stage !== ALL_STAGES_VALUE ? `/${stage}` : "";
|
|
118
|
-
const newHash = `#/agent/${discipline}
|
|
149
|
+
const newHash = `#/agent/${discipline}${trackPart}${stagePart}`;
|
|
119
150
|
if (window.location.hash !== newHash) {
|
|
120
151
|
history.replaceState(null, "", newHash);
|
|
121
152
|
}
|
|
@@ -123,7 +154,7 @@ export async function renderAgentBuilder() {
|
|
|
123
154
|
|
|
124
155
|
previewContainer.innerHTML = "";
|
|
125
156
|
|
|
126
|
-
if (!discipline
|
|
157
|
+
if (!discipline) {
|
|
127
158
|
previewContainer.appendChild(
|
|
128
159
|
createEmptyState(availableDisciplines.length, availableTracks.length),
|
|
129
160
|
);
|
|
@@ -132,7 +163,7 @@ export async function renderAgentBuilder() {
|
|
|
132
163
|
|
|
133
164
|
// Get full objects
|
|
134
165
|
const humanDiscipline = data.disciplines.find((d) => d.id === discipline);
|
|
135
|
-
const humanTrack = data.tracks.find((t) => t.id === track);
|
|
166
|
+
const humanTrack = track ? data.tracks.find((t) => t.id === track) : null;
|
|
136
167
|
const agentDiscipline = agentData.disciplines.find(
|
|
137
168
|
(d) => d.id === discipline,
|
|
138
169
|
);
|
|
@@ -164,6 +195,8 @@ export async function renderAgentBuilder() {
|
|
|
164
195
|
agentBehaviours: agentData.behaviours,
|
|
165
196
|
capabilities: data.capabilities,
|
|
166
197
|
vscodeSettings: agentData.vscodeSettings,
|
|
198
|
+
devcontainer: agentData.devcontainer,
|
|
199
|
+
templates,
|
|
167
200
|
};
|
|
168
201
|
|
|
169
202
|
// Generate preview based on stage selection
|
|
@@ -212,9 +245,9 @@ export async function renderAgentBuilder() {
|
|
|
212
245
|
{ className: "form-group" },
|
|
213
246
|
label({ className: "form-label" }, "Discipline"),
|
|
214
247
|
availableDisciplines.length > 0
|
|
215
|
-
?
|
|
248
|
+
? createDisciplineSelect({
|
|
216
249
|
id: "agent-discipline-select",
|
|
217
|
-
|
|
250
|
+
disciplines: availableDisciplines,
|
|
218
251
|
initialValue: selection.get().discipline,
|
|
219
252
|
placeholder: "Select a discipline...",
|
|
220
253
|
onChange: (value) => {
|
|
@@ -325,6 +358,8 @@ function createAllStagesPreview(context) {
|
|
|
325
358
|
agentBehaviours,
|
|
326
359
|
capabilities,
|
|
327
360
|
vscodeSettings,
|
|
361
|
+
devcontainer,
|
|
362
|
+
templates,
|
|
328
363
|
} = context;
|
|
329
364
|
|
|
330
365
|
// Generate all stage agents
|
|
@@ -378,7 +413,13 @@ function createAllStagesPreview(context) {
|
|
|
378
413
|
{ className: "agent-deployment" },
|
|
379
414
|
|
|
380
415
|
// Download all button
|
|
381
|
-
createDownloadAllButton(
|
|
416
|
+
createDownloadAllButton(
|
|
417
|
+
stageAgents,
|
|
418
|
+
skillFiles,
|
|
419
|
+
vscodeSettings,
|
|
420
|
+
devcontainer,
|
|
421
|
+
context,
|
|
422
|
+
),
|
|
382
423
|
|
|
383
424
|
// Agents section
|
|
384
425
|
section(
|
|
@@ -391,7 +432,7 @@ function createAllStagesPreview(context) {
|
|
|
391
432
|
div(
|
|
392
433
|
{ className: "agent-cards-grid" },
|
|
393
434
|
...stageAgents.map(({ stage, profile }) =>
|
|
394
|
-
createAgentCard(stage, profile, stages),
|
|
435
|
+
createAgentCard(stage, profile, stages, templates.agent),
|
|
395
436
|
),
|
|
396
437
|
),
|
|
397
438
|
),
|
|
@@ -403,7 +444,9 @@ function createAllStagesPreview(context) {
|
|
|
403
444
|
skillFiles.length > 0
|
|
404
445
|
? div(
|
|
405
446
|
{ className: "skill-cards-grid" },
|
|
406
|
-
...skillFiles.map((skill) =>
|
|
447
|
+
...skillFiles.map((skill) =>
|
|
448
|
+
createSkillCard(skill, templates.skill),
|
|
449
|
+
),
|
|
407
450
|
)
|
|
408
451
|
: p(
|
|
409
452
|
{ className: "text-muted" },
|
|
@@ -434,7 +477,9 @@ function createSingleStagePreview(context, stage) {
|
|
|
434
477
|
agentBehaviours,
|
|
435
478
|
capabilities,
|
|
436
479
|
vscodeSettings,
|
|
480
|
+
devcontainer,
|
|
437
481
|
stages,
|
|
482
|
+
templates,
|
|
438
483
|
} = context;
|
|
439
484
|
|
|
440
485
|
// Derive stage agent
|
|
@@ -483,7 +528,13 @@ function createSingleStagePreview(context, stage) {
|
|
|
483
528
|
{ className: "agent-deployment" },
|
|
484
529
|
|
|
485
530
|
// Download button for single stage
|
|
486
|
-
createDownloadSingleButton(
|
|
531
|
+
createDownloadSingleButton(
|
|
532
|
+
profile,
|
|
533
|
+
skillFiles,
|
|
534
|
+
vscodeSettings,
|
|
535
|
+
devcontainer,
|
|
536
|
+
templates,
|
|
537
|
+
),
|
|
487
538
|
|
|
488
539
|
// Agents section (single card)
|
|
489
540
|
section(
|
|
@@ -491,7 +542,7 @@ function createSingleStagePreview(context, stage) {
|
|
|
491
542
|
h2({}, "Agent"),
|
|
492
543
|
div(
|
|
493
544
|
{ className: "agent-cards-grid single" },
|
|
494
|
-
createAgentCard(stage, profile, stages, derived),
|
|
545
|
+
createAgentCard(stage, profile, stages, templates.agent, derived),
|
|
495
546
|
),
|
|
496
547
|
),
|
|
497
548
|
|
|
@@ -502,7 +553,9 @@ function createSingleStagePreview(context, stage) {
|
|
|
502
553
|
skillFiles.length > 0
|
|
503
554
|
? div(
|
|
504
555
|
{ className: "skill-cards-grid" },
|
|
505
|
-
...skillFiles.map((skill) =>
|
|
556
|
+
...skillFiles.map((skill) =>
|
|
557
|
+
createSkillCard(skill, templates.skill),
|
|
558
|
+
),
|
|
506
559
|
)
|
|
507
560
|
: p(
|
|
508
561
|
{ className: "text-muted" },
|
|
@@ -520,11 +573,12 @@ function createSingleStagePreview(context, stage) {
|
|
|
520
573
|
* @param {Object} stage - Stage object
|
|
521
574
|
* @param {Object} profile - Generated profile
|
|
522
575
|
* @param {Array} stages - All stages for emoji lookup
|
|
576
|
+
* @param {string} agentTemplate - Mustache template for agent profile
|
|
523
577
|
* @param {Object} [_derived] - Optional derived agent data for extra info
|
|
524
578
|
* @returns {HTMLElement}
|
|
525
579
|
*/
|
|
526
|
-
function createAgentCard(stage, profile, stages, _derived) {
|
|
527
|
-
const content = formatAgentProfile(profile);
|
|
580
|
+
function createAgentCard(stage, profile, stages, agentTemplate, _derived) {
|
|
581
|
+
const content = formatAgentProfile(profile, agentTemplate);
|
|
528
582
|
const stageEmoji = getStageEmoji(stages, stage.id);
|
|
529
583
|
|
|
530
584
|
const card = div(
|
|
@@ -548,10 +602,11 @@ function createAgentCard(stage, profile, stages, _derived) {
|
|
|
548
602
|
/**
|
|
549
603
|
* Create a skill card
|
|
550
604
|
* @param {Object} skill - Skill with frontmatter and body
|
|
605
|
+
* @param {string} skillTemplate - Mustache template for skill
|
|
551
606
|
* @returns {HTMLElement}
|
|
552
607
|
*/
|
|
553
|
-
function createSkillCard(skill) {
|
|
554
|
-
const content = formatAgentSkill(skill);
|
|
608
|
+
function createSkillCard(skill, skillTemplate) {
|
|
609
|
+
const content = formatAgentSkill(skill, skillTemplate);
|
|
555
610
|
const filename = `${skill.dirname}/SKILL.md`;
|
|
556
611
|
|
|
557
612
|
return div(
|
|
@@ -615,16 +670,18 @@ function createCopyButton(content) {
|
|
|
615
670
|
* @param {Array} stageAgents - Array of {stage, derived, profile}
|
|
616
671
|
* @param {Array} skillFiles - Array of skill file objects
|
|
617
672
|
* @param {Object} vscodeSettings - VS Code settings
|
|
618
|
-
* @param {Object}
|
|
673
|
+
* @param {Object} devcontainer - Devcontainer config
|
|
674
|
+
* @param {Object} context - Context with discipline/track info and templates
|
|
619
675
|
* @returns {HTMLElement}
|
|
620
676
|
*/
|
|
621
677
|
function createDownloadAllButton(
|
|
622
678
|
stageAgents,
|
|
623
679
|
skillFiles,
|
|
624
680
|
vscodeSettings,
|
|
681
|
+
devcontainer,
|
|
625
682
|
context,
|
|
626
683
|
) {
|
|
627
|
-
const { humanDiscipline, humanTrack } = context;
|
|
684
|
+
const { humanDiscipline, humanTrack, templates } = context;
|
|
628
685
|
const agentName = `${humanDiscipline.id}-${humanTrack.id}`.replace(/_/g, "-");
|
|
629
686
|
|
|
630
687
|
const btn = document.createElement("button");
|
|
@@ -641,13 +698,13 @@ function createDownloadAllButton(
|
|
|
641
698
|
|
|
642
699
|
// Add all stage agent profiles
|
|
643
700
|
for (const { profile } of stageAgents) {
|
|
644
|
-
const content = formatAgentProfile(profile);
|
|
701
|
+
const content = formatAgentProfile(profile, templates.agent);
|
|
645
702
|
zip.file(`.github/agents/${profile.filename}`, content);
|
|
646
703
|
}
|
|
647
704
|
|
|
648
705
|
// Add skills
|
|
649
706
|
for (const skill of skillFiles) {
|
|
650
|
-
const content = formatAgentSkill(skill);
|
|
707
|
+
const content = formatAgentSkill(skill, templates.skill);
|
|
651
708
|
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, content);
|
|
652
709
|
}
|
|
653
710
|
|
|
@@ -659,6 +716,22 @@ function createDownloadAllButton(
|
|
|
659
716
|
);
|
|
660
717
|
}
|
|
661
718
|
|
|
719
|
+
// Add devcontainer.json with VS Code settings embedded
|
|
720
|
+
if (devcontainer && Object.keys(devcontainer).length > 0) {
|
|
721
|
+
const devcontainerJson = {
|
|
722
|
+
...devcontainer,
|
|
723
|
+
customizations: {
|
|
724
|
+
vscode: {
|
|
725
|
+
settings: vscodeSettings,
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
};
|
|
729
|
+
zip.file(
|
|
730
|
+
".devcontainer/devcontainer.json",
|
|
731
|
+
JSON.stringify(devcontainerJson, null, 2) + "\n",
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
662
735
|
// Generate and download
|
|
663
736
|
const blob = await zip.generateAsync({ type: "blob" });
|
|
664
737
|
const url = URL.createObjectURL(blob);
|
|
@@ -685,9 +758,17 @@ function createDownloadAllButton(
|
|
|
685
758
|
* @param {Object} profile - Agent profile
|
|
686
759
|
* @param {Array} skillFiles - Skill files
|
|
687
760
|
* @param {Object} vscodeSettings - VS Code settings
|
|
761
|
+
* @param {Object} devcontainer - Devcontainer config
|
|
762
|
+
* @param {{agent: string, skill: string}} templates - Mustache templates
|
|
688
763
|
* @returns {HTMLElement}
|
|
689
764
|
*/
|
|
690
|
-
function createDownloadSingleButton(
|
|
765
|
+
function createDownloadSingleButton(
|
|
766
|
+
profile,
|
|
767
|
+
skillFiles,
|
|
768
|
+
vscodeSettings,
|
|
769
|
+
devcontainer,
|
|
770
|
+
templates,
|
|
771
|
+
) {
|
|
691
772
|
const btn = document.createElement("button");
|
|
692
773
|
btn.className = "btn btn-primary download-all-btn";
|
|
693
774
|
btn.textContent = "📥 Download Agent (.zip)";
|
|
@@ -701,12 +782,12 @@ function createDownloadSingleButton(profile, skillFiles, vscodeSettings) {
|
|
|
701
782
|
const zip = new JSZip();
|
|
702
783
|
|
|
703
784
|
// Add profile
|
|
704
|
-
const content = formatAgentProfile(profile);
|
|
785
|
+
const content = formatAgentProfile(profile, templates.agent);
|
|
705
786
|
zip.file(`.github/agents/${profile.filename}`, content);
|
|
706
787
|
|
|
707
788
|
// Add skills
|
|
708
789
|
for (const skill of skillFiles) {
|
|
709
|
-
const skillContent = formatAgentSkill(skill);
|
|
790
|
+
const skillContent = formatAgentSkill(skill, templates.skill);
|
|
710
791
|
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, skillContent);
|
|
711
792
|
}
|
|
712
793
|
|
|
@@ -718,6 +799,22 @@ function createDownloadSingleButton(profile, skillFiles, vscodeSettings) {
|
|
|
718
799
|
);
|
|
719
800
|
}
|
|
720
801
|
|
|
802
|
+
// Add devcontainer.json with VS Code settings embedded
|
|
803
|
+
if (devcontainer && Object.keys(devcontainer).length > 0) {
|
|
804
|
+
const devcontainerJson = {
|
|
805
|
+
...devcontainer,
|
|
806
|
+
customizations: {
|
|
807
|
+
vscode: {
|
|
808
|
+
settings: vscodeSettings,
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
};
|
|
812
|
+
zip.file(
|
|
813
|
+
".devcontainer/devcontainer.json",
|
|
814
|
+
JSON.stringify(devcontainerJson, null, 2) + "\n",
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
|
|
721
818
|
// Generate and download
|
|
722
819
|
const blob = await zip.generateAsync({ type: "blob" });
|
|
723
820
|
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
|
@@ -2,29 +2,55 @@
|
|
|
2
2
|
* Job detail page with visualizations
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { render } from "../lib/render.js";
|
|
5
|
+
import { render, div, p } from "../lib/render.js";
|
|
6
6
|
import { getState } from "../lib/state.js";
|
|
7
7
|
import { renderError } from "../components/error-page.js";
|
|
8
8
|
import { prepareJobDetail } from "../model/job.js";
|
|
9
9
|
import { jobToDOM } from "../formatters/job/dom.js";
|
|
10
10
|
|
|
11
|
+
/** @type {string|null} Cached job template */
|
|
12
|
+
let jobTemplateCache = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Load job template with caching
|
|
16
|
+
* @returns {Promise<string>}
|
|
17
|
+
*/
|
|
18
|
+
async function getJobTemplate() {
|
|
19
|
+
if (!jobTemplateCache) {
|
|
20
|
+
const response = await fetch("./templates/job.template.md");
|
|
21
|
+
jobTemplateCache = await response.text();
|
|
22
|
+
}
|
|
23
|
+
return jobTemplateCache;
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
/**
|
|
12
27
|
* Render job detail page
|
|
13
28
|
* @param {Object} params - Route params
|
|
14
29
|
*/
|
|
15
|
-
export function renderJobDetail(params) {
|
|
16
|
-
const { discipline: disciplineId,
|
|
30
|
+
export async function renderJobDetail(params) {
|
|
31
|
+
const { discipline: disciplineId, grade: gradeId, track: trackId } = params;
|
|
17
32
|
const { data } = getState();
|
|
18
33
|
|
|
19
34
|
// Find the components
|
|
20
35
|
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
21
|
-
const track = data.tracks.find((t) => t.id === trackId);
|
|
22
36
|
const grade = data.grades.find((g) => g.id === gradeId);
|
|
37
|
+
const track = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
23
38
|
|
|
24
|
-
if (!discipline || !
|
|
39
|
+
if (!discipline || !grade) {
|
|
25
40
|
renderError({
|
|
26
41
|
title: "Job Not Found",
|
|
27
|
-
message: "Invalid job combination.
|
|
42
|
+
message: "Invalid job combination. Discipline or grade not found.",
|
|
43
|
+
backPath: "/job-builder",
|
|
44
|
+
backText: "← Back to Job Builder",
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If trackId was provided but not found, error
|
|
50
|
+
if (trackId && !track) {
|
|
51
|
+
renderError({
|
|
52
|
+
title: "Job Not Found",
|
|
53
|
+
message: `Track "${trackId}" not found.`,
|
|
28
54
|
backPath: "/job-builder",
|
|
29
55
|
backText: "← Back to Job Builder",
|
|
30
56
|
});
|
|
@@ -52,7 +78,16 @@ export function renderJobDetail(params) {
|
|
|
52
78
|
return;
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
//
|
|
56
|
-
|
|
81
|
+
// Show loading while fetching template
|
|
82
|
+
render(
|
|
83
|
+
div(
|
|
84
|
+
{ className: "job-detail-page" },
|
|
85
|
+
div({ className: "loading" }, p({}, "Loading...")),
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Load template and format
|
|
90
|
+
const jobTemplate = await getJobTemplate();
|
|
91
|
+
const page = jobToDOM(jobView, { discipline, grade, track, jobTemplate });
|
|
57
92
|
render(page);
|
|
58
93
|
}
|