@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
package/app/index.html
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Engineering Pathway</title>
|
|
7
7
|
<link rel="stylesheet" href="css/bundles/app.css" />
|
|
8
|
+
<script type="importmap">
|
|
9
|
+
{
|
|
10
|
+
"imports": {
|
|
11
|
+
"mustache": "https://esm.sh/mustache@4.2.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
8
15
|
</head>
|
|
9
16
|
<body>
|
|
10
17
|
<div id="app">
|
|
@@ -51,6 +58,10 @@
|
|
|
51
58
|
|
|
52
59
|
<footer id="app-footer">
|
|
53
60
|
<p>Engineering Pathway</p>
|
|
61
|
+
<nav class="footer-links">
|
|
62
|
+
<a href="slides.html">Slides</a>
|
|
63
|
+
<a href="handout.html">Handouts</a>
|
|
64
|
+
</nav>
|
|
54
65
|
</footer>
|
|
55
66
|
</div>
|
|
56
67
|
|
package/app/lib/card-mappers.js
CHANGED
|
@@ -15,10 +15,18 @@ import { getCapabilityEmoji } from "../model/levels.js";
|
|
|
15
15
|
* @returns {Object}
|
|
16
16
|
*/
|
|
17
17
|
export function disciplineToCardConfig(discipline) {
|
|
18
|
+
const badges = [];
|
|
19
|
+
if (discipline.isProfessional) {
|
|
20
|
+
badges.push(createBadge("Professional", "secondary"));
|
|
21
|
+
}
|
|
22
|
+
if (discipline.isManagement) {
|
|
23
|
+
badges.push(createBadge("Management", "primary"));
|
|
24
|
+
}
|
|
18
25
|
return {
|
|
19
26
|
title: discipline.name,
|
|
20
27
|
description: discipline.truncatedDescription,
|
|
21
28
|
href: `/discipline/${discipline.id}`,
|
|
29
|
+
badges,
|
|
22
30
|
meta: [
|
|
23
31
|
createBadge(`${discipline.coreSkillsCount} core`, "primary"),
|
|
24
32
|
createBadge(
|
|
@@ -118,19 +126,11 @@ export function gradeToCardConfig(grade) {
|
|
|
118
126
|
* @returns {Object}
|
|
119
127
|
*/
|
|
120
128
|
export function trackToCardConfig(track) {
|
|
121
|
-
const badges = [];
|
|
122
|
-
if (track.isProfessional) {
|
|
123
|
-
badges.push(createBadge("Professional", "secondary"));
|
|
124
|
-
}
|
|
125
|
-
if (track.isManagement) {
|
|
126
|
-
badges.push(createBadge("Management", "default"));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
129
|
return {
|
|
130
130
|
title: track.name,
|
|
131
131
|
description: track.truncatedDescription,
|
|
132
132
|
href: `/track/${track.id}`,
|
|
133
|
-
meta:
|
|
133
|
+
meta: [],
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -140,12 +140,17 @@ export function trackToCardConfig(track) {
|
|
|
140
140
|
* @returns {Object}
|
|
141
141
|
*/
|
|
142
142
|
export function jobToCardConfig(job) {
|
|
143
|
+
const href = job.track
|
|
144
|
+
? `/job/${job.discipline.id}/${job.grade.id}/${job.track.id}`
|
|
145
|
+
: `/job/${job.discipline.id}/${job.grade.id}`;
|
|
143
146
|
return {
|
|
144
147
|
title: job.title,
|
|
145
|
-
description:
|
|
146
|
-
|
|
148
|
+
description: job.track
|
|
149
|
+
? `${job.discipline.specialization || job.discipline.name} at ${job.grade.professionalTitle} level on ${job.track.name} track`
|
|
150
|
+
: `${job.discipline.specialization || job.discipline.name} at ${job.grade.professionalTitle} level`,
|
|
151
|
+
href,
|
|
147
152
|
badges: [createBadge(job.grade.id, "default")],
|
|
148
|
-
meta: [createBadge(job.track.name, "secondary")],
|
|
153
|
+
meta: job.track ? [createBadge(job.track.name, "secondary")] : [],
|
|
149
154
|
};
|
|
150
155
|
}
|
|
151
156
|
|
package/app/lib/form-controls.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Reusable form control components
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { select, option } from "./render.js";
|
|
5
|
+
import { select, option, optgroup } from "./render.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Create a select element with initial value and change handler
|
|
@@ -45,3 +45,66 @@ export function createSelectWithValue({
|
|
|
45
45
|
|
|
46
46
|
return selectEl;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a discipline select with optgroups for Professional and Management
|
|
51
|
+
* @param {Object} options - Configuration options
|
|
52
|
+
* @param {string} options.id - Element ID
|
|
53
|
+
* @param {Array} options.disciplines - Array of discipline objects
|
|
54
|
+
* @param {string} options.initialValue - Initial selected value
|
|
55
|
+
* @param {string} options.placeholder - Placeholder text for empty option
|
|
56
|
+
* @param {Function} options.onChange - Callback when selection changes
|
|
57
|
+
* @param {Function} [options.getDisplayName] - Optional function to get display name from item
|
|
58
|
+
* @returns {HTMLElement}
|
|
59
|
+
*/
|
|
60
|
+
export function createDisciplineSelect({
|
|
61
|
+
id,
|
|
62
|
+
disciplines,
|
|
63
|
+
initialValue,
|
|
64
|
+
placeholder,
|
|
65
|
+
onChange,
|
|
66
|
+
getDisplayName,
|
|
67
|
+
}) {
|
|
68
|
+
const displayFn = getDisplayName || ((d) => d.specialization || d.name);
|
|
69
|
+
|
|
70
|
+
// Separate disciplines by type
|
|
71
|
+
const professional = disciplines.filter((d) => d.isProfessional);
|
|
72
|
+
const management = disciplines.filter((d) => d.isManagement);
|
|
73
|
+
|
|
74
|
+
// Sort each group alphabetically by display name
|
|
75
|
+
professional.sort((a, b) => displayFn(a).localeCompare(displayFn(b)));
|
|
76
|
+
management.sort((a, b) => displayFn(a).localeCompare(displayFn(b)));
|
|
77
|
+
|
|
78
|
+
// Build options for a group
|
|
79
|
+
const buildOptions = (items) =>
|
|
80
|
+
items.map((item) => {
|
|
81
|
+
const opt = option({ value: item.id }, displayFn(item));
|
|
82
|
+
if (item.id === initialValue) {
|
|
83
|
+
opt.selected = true;
|
|
84
|
+
}
|
|
85
|
+
return opt;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Build optgroups - Professional first, then Management
|
|
89
|
+
const groups = [];
|
|
90
|
+
if (professional.length > 0) {
|
|
91
|
+
groups.push(
|
|
92
|
+
optgroup({ label: "Professional" }, ...buildOptions(professional)),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (management.length > 0) {
|
|
96
|
+
groups.push(optgroup({ label: "Management" }, ...buildOptions(management)));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const selectEl = select(
|
|
100
|
+
{ className: "form-select", id },
|
|
101
|
+
option({ value: "" }, placeholder),
|
|
102
|
+
...groups,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
selectEl.addEventListener("change", (e) => {
|
|
106
|
+
onChange(e.target.value);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return selectEl;
|
|
110
|
+
}
|
package/app/lib/job-cache.js
CHANGED
|
@@ -13,12 +13,15 @@ const cache = new Map();
|
|
|
13
13
|
/**
|
|
14
14
|
* Create a consistent cache key from job parameters
|
|
15
15
|
* @param {string} disciplineId
|
|
16
|
-
* @param {string} trackId
|
|
17
16
|
* @param {string} gradeId
|
|
17
|
+
* @param {string} [trackId] - Optional track ID
|
|
18
18
|
* @returns {string}
|
|
19
19
|
*/
|
|
20
|
-
export function makeJobKey(disciplineId,
|
|
21
|
-
|
|
20
|
+
export function makeJobKey(disciplineId, gradeId, trackId = null) {
|
|
21
|
+
if (trackId) {
|
|
22
|
+
return `${disciplineId}_${gradeId}_${trackId}`;
|
|
23
|
+
}
|
|
24
|
+
return `${disciplineId}_${gradeId}`;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -26,7 +29,7 @@ export function makeJobKey(disciplineId, trackId, gradeId) {
|
|
|
26
29
|
* @param {Object} params
|
|
27
30
|
* @param {Object} params.discipline
|
|
28
31
|
* @param {Object} params.grade
|
|
29
|
-
* @param {Object} params.track
|
|
32
|
+
* @param {Object} [params.track] - Optional track
|
|
30
33
|
* @param {Array} params.skills
|
|
31
34
|
* @param {Array} params.behaviours
|
|
32
35
|
* @param {Array} [params.capabilities]
|
|
@@ -35,12 +38,12 @@ export function makeJobKey(disciplineId, trackId, gradeId) {
|
|
|
35
38
|
export function getOrCreateJob({
|
|
36
39
|
discipline,
|
|
37
40
|
grade,
|
|
38
|
-
track,
|
|
41
|
+
track = null,
|
|
39
42
|
skills,
|
|
40
43
|
behaviours,
|
|
41
44
|
capabilities,
|
|
42
45
|
}) {
|
|
43
|
-
const key = makeJobKey(discipline.id,
|
|
46
|
+
const key = makeJobKey(discipline.id, grade.id, track?.id);
|
|
44
47
|
|
|
45
48
|
if (!cache.has(key)) {
|
|
46
49
|
const job = deriveJob({
|
|
@@ -70,11 +73,11 @@ export function clearJobCache() {
|
|
|
70
73
|
/**
|
|
71
74
|
* Invalidate a specific job from the cache
|
|
72
75
|
* @param {string} disciplineId
|
|
73
|
-
* @param {string} trackId
|
|
74
76
|
* @param {string} gradeId
|
|
77
|
+
* @param {string} [trackId] - Optional track ID
|
|
75
78
|
*/
|
|
76
|
-
export function invalidateJob(disciplineId,
|
|
77
|
-
cache.delete(makeJobKey(disciplineId,
|
|
79
|
+
export function invalidateJob(disciplineId, gradeId, trackId = null) {
|
|
80
|
+
cache.delete(makeJobKey(disciplineId, gradeId, trackId));
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/**
|
package/app/lib/render.js
CHANGED
|
@@ -118,6 +118,8 @@ export const select = (attrs, ...children) =>
|
|
|
118
118
|
createElement("select", attrs, ...children);
|
|
119
119
|
export const option = (attrs, ...children) =>
|
|
120
120
|
createElement("option", attrs, ...children);
|
|
121
|
+
export const optgroup = (attrs, ...children) =>
|
|
122
|
+
createElement("optgroup", attrs, ...children);
|
|
121
123
|
export const label = (attrs, ...children) =>
|
|
122
124
|
createElement("label", attrs, ...children);
|
|
123
125
|
export const form = (attrs, ...children) =>
|
|
@@ -185,12 +187,17 @@ export function showError(message) {
|
|
|
185
187
|
|
|
186
188
|
/**
|
|
187
189
|
* Format a skill level or behaviour maturity for display
|
|
190
|
+
* Handles both snake_case and camelCase
|
|
188
191
|
* @param {string} value - The level/maturity value
|
|
189
192
|
* @returns {string}
|
|
190
193
|
*/
|
|
191
194
|
export function formatLevel(value) {
|
|
192
195
|
if (!value) return "";
|
|
193
|
-
|
|
196
|
+
// Insert space before uppercase letters (for camelCase), then handle snake_case
|
|
197
|
+
return value
|
|
198
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
199
|
+
.replace(/_/g, " ")
|
|
200
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
/**
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads Mustache templates from the data directory with fallback to the
|
|
5
|
+
* top-level templates directory. This allows users to customize agent
|
|
6
|
+
* and skill templates by placing them in their data directory.
|
|
7
|
+
*
|
|
8
|
+
* Resolution order:
|
|
9
|
+
* 1. {dataDir}/templates/{name} (user customization)
|
|
10
|
+
* 2. {codebaseDir}/templates/{name} (fallback)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFile } from "fs/promises";
|
|
14
|
+
import { join, dirname } from "path";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
import { existsSync } from "fs";
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const CODEBASE_TEMPLATES_DIR = join(__dirname, "..", "..", "templates");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load a template file with fallback to codebase templates
|
|
23
|
+
* @param {string} templateName - Template filename (e.g., 'agent.template.md')
|
|
24
|
+
* @param {string} dataDir - Path to data directory
|
|
25
|
+
* @returns {Promise<string>} Template content
|
|
26
|
+
* @throws {Error} If template not found in either location
|
|
27
|
+
*/
|
|
28
|
+
export async function loadTemplate(templateName, dataDir) {
|
|
29
|
+
// Build list of paths to try
|
|
30
|
+
const paths = [];
|
|
31
|
+
if (dataDir) {
|
|
32
|
+
paths.push(join(dataDir, "templates", templateName));
|
|
33
|
+
}
|
|
34
|
+
paths.push(join(CODEBASE_TEMPLATES_DIR, templateName));
|
|
35
|
+
|
|
36
|
+
// Try each path in order
|
|
37
|
+
for (const path of paths) {
|
|
38
|
+
if (existsSync(path)) {
|
|
39
|
+
return await readFile(path, "utf-8");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Not found
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Template '${templateName}' not found. Checked:\n` +
|
|
46
|
+
paths.map((p) => ` - ${p}`).join("\n"),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load agent profile template
|
|
52
|
+
* @param {string} dataDir - Path to data directory
|
|
53
|
+
* @returns {Promise<string>} Agent template content
|
|
54
|
+
*/
|
|
55
|
+
export async function loadAgentTemplate(dataDir) {
|
|
56
|
+
return loadTemplate("agent.template.md", dataDir);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Load agent skill template
|
|
61
|
+
* @param {string} dataDir - Path to data directory
|
|
62
|
+
* @returns {Promise<string>} Skill template content
|
|
63
|
+
*/
|
|
64
|
+
export async function loadSkillTemplate(dataDir) {
|
|
65
|
+
return loadTemplate("skill.template.md", dataDir);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load job description template
|
|
70
|
+
* @param {string} dataDir - Path to data directory
|
|
71
|
+
* @returns {Promise<string>} Job template content
|
|
72
|
+
*/
|
|
73
|
+
export async function loadJobTemplate(dataDir) {
|
|
74
|
+
return loadTemplate("job.template.md", dataDir);
|
|
75
|
+
}
|
package/app/lib/yaml-loader.js
CHANGED
|
@@ -103,6 +103,11 @@ async function loadDisciplinesFromDir(disciplinesDir) {
|
|
|
103
103
|
const {
|
|
104
104
|
specialization,
|
|
105
105
|
roleTitle,
|
|
106
|
+
// Track constraints
|
|
107
|
+
isProfessional,
|
|
108
|
+
isManagement,
|
|
109
|
+
validTracks,
|
|
110
|
+
minGrade,
|
|
106
111
|
// Shared content - now at root level
|
|
107
112
|
description,
|
|
108
113
|
// Structural properties (derivation inputs)
|
|
@@ -118,6 +123,11 @@ async function loadDisciplinesFromDir(disciplinesDir) {
|
|
|
118
123
|
id,
|
|
119
124
|
specialization,
|
|
120
125
|
roleTitle,
|
|
126
|
+
// Track constraints
|
|
127
|
+
isProfessional,
|
|
128
|
+
isManagement,
|
|
129
|
+
validTracks,
|
|
130
|
+
minGrade,
|
|
121
131
|
// Shared content at top level
|
|
122
132
|
description,
|
|
123
133
|
// Structural properties
|
|
@@ -152,12 +162,10 @@ async function loadTracksFromDir(tracksDir) {
|
|
|
152
162
|
description,
|
|
153
163
|
roleContext,
|
|
154
164
|
// Structural properties (derivation inputs)
|
|
155
|
-
isProfessional,
|
|
156
|
-
isManagement,
|
|
157
165
|
skillModifiers,
|
|
158
166
|
behaviourModifiers,
|
|
159
167
|
matchingWeights,
|
|
160
|
-
|
|
168
|
+
minGrade,
|
|
161
169
|
// Agent section (no human section anymore for tracks)
|
|
162
170
|
agent,
|
|
163
171
|
} = content;
|
|
@@ -168,12 +176,10 @@ async function loadTracksFromDir(tracksDir) {
|
|
|
168
176
|
description,
|
|
169
177
|
roleContext,
|
|
170
178
|
// Structural properties
|
|
171
|
-
isProfessional,
|
|
172
|
-
isManagement,
|
|
173
179
|
skillModifiers,
|
|
174
180
|
behaviourModifiers,
|
|
175
181
|
matchingWeights,
|
|
176
|
-
|
|
182
|
+
minGrade,
|
|
177
183
|
...(agent && { agent }),
|
|
178
184
|
};
|
|
179
185
|
}),
|
|
@@ -301,14 +307,23 @@ export async function loadAllData(dataDir = "./data") {
|
|
|
301
307
|
* Load agent-specific data for browser-based agent generation
|
|
302
308
|
* Uses co-located files where agent sections are embedded in entity files
|
|
303
309
|
* @param {string} [dataDir='./data'] - Path to data directory
|
|
304
|
-
* @returns {Promise<Object>} Agent data including disciplines, tracks, behaviours, vscodeSettings
|
|
310
|
+
* @returns {Promise<Object>} Agent data including disciplines, tracks, behaviours, vscodeSettings, devcontainer, copilotSetupSteps
|
|
305
311
|
*/
|
|
306
312
|
export async function loadAgentDataBrowser(dataDir = "./data") {
|
|
307
|
-
const [
|
|
313
|
+
const [
|
|
314
|
+
disciplines,
|
|
315
|
+
tracks,
|
|
316
|
+
behaviours,
|
|
317
|
+
vscodeSettings,
|
|
318
|
+
devcontainer,
|
|
319
|
+
copilotSetupSteps,
|
|
320
|
+
] = await Promise.all([
|
|
308
321
|
loadDisciplinesFromDir(`${dataDir}/disciplines`),
|
|
309
322
|
loadTracksFromDir(`${dataDir}/tracks`),
|
|
310
323
|
loadBehavioursFromDir(`${dataDir}/behaviours`),
|
|
311
324
|
tryLoadYamlFile(`${dataDir}/vscode-settings.yaml`),
|
|
325
|
+
tryLoadYamlFile(`${dataDir}/devcontainer.yaml`),
|
|
326
|
+
tryLoadYamlFile(`${dataDir}/copilot-setup-steps.yaml`),
|
|
312
327
|
]);
|
|
313
328
|
|
|
314
329
|
// Extract agent sections from co-located files
|
|
@@ -323,5 +338,7 @@ export async function loadAgentDataBrowser(dataDir = "./data") {
|
|
|
323
338
|
.filter((b) => b.agent)
|
|
324
339
|
.map((b) => ({ id: b.id, ...b.agent })),
|
|
325
340
|
vscodeSettings: vscodeSettings || {},
|
|
341
|
+
devcontainer: devcontainer || {},
|
|
342
|
+
copilotSetupSteps: copilotSetupSteps || null,
|
|
326
343
|
};
|
|
327
344
|
}
|
package/app/main.js
CHANGED
|
@@ -101,15 +101,18 @@ function setupRoutes() {
|
|
|
101
101
|
|
|
102
102
|
// Job builder
|
|
103
103
|
router.on("/job-builder", renderJobBuilder);
|
|
104
|
-
router.on("/job/:discipline/:track
|
|
104
|
+
router.on("/job/:discipline/:grade/:track", renderJobDetail);
|
|
105
|
+
router.on("/job/:discipline/:grade", renderJobDetail);
|
|
105
106
|
|
|
106
107
|
// Interview prep
|
|
107
108
|
router.on("/interview-prep", renderInterviewPrep);
|
|
108
|
-
router.on("/interview/:discipline/:track
|
|
109
|
+
router.on("/interview/:discipline/:grade/:track", renderInterviewDetail);
|
|
110
|
+
router.on("/interview/:discipline/:grade", renderInterviewDetail);
|
|
109
111
|
|
|
110
112
|
// Career progress
|
|
111
113
|
router.on("/career-progress", renderCareerProgress);
|
|
112
|
-
router.on("/progress/:discipline/:track
|
|
114
|
+
router.on("/progress/:discipline/:grade/:track", renderProgressDetail);
|
|
115
|
+
router.on("/progress/:discipline/:grade", renderProgressDetail);
|
|
113
116
|
|
|
114
117
|
// Self-assessment
|
|
115
118
|
router.on("/self-assessment", renderSelfAssessment);
|
|
@@ -117,8 +120,9 @@ function setupRoutes() {
|
|
|
117
120
|
|
|
118
121
|
// Agent builder
|
|
119
122
|
router.on("/agent-builder", renderAgentBuilder);
|
|
120
|
-
router.on("/agent/:discipline/:track", renderAgentBuilder);
|
|
121
123
|
router.on("/agent/:discipline/:track/:stage", renderAgentBuilder);
|
|
124
|
+
router.on("/agent/:discipline/:track", renderAgentBuilder);
|
|
125
|
+
router.on("/agent/:discipline", renderAgentBuilder);
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
/**
|