@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
|
@@ -12,10 +12,15 @@ import {
|
|
|
12
12
|
button,
|
|
13
13
|
label,
|
|
14
14
|
section,
|
|
15
|
+
select,
|
|
16
|
+
option,
|
|
15
17
|
} from "../lib/render.js";
|
|
16
18
|
import { getState } from "../lib/state.js";
|
|
17
19
|
import { createBadge } from "./card.js";
|
|
18
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
createSelectWithValue,
|
|
22
|
+
createDisciplineSelect,
|
|
23
|
+
} from "../lib/form-controls.js";
|
|
19
24
|
import { createReactive } from "../lib/reactive.js";
|
|
20
25
|
|
|
21
26
|
/**
|
|
@@ -89,9 +94,70 @@ export function createBuilder({
|
|
|
89
94
|
buttonText,
|
|
90
95
|
);
|
|
91
96
|
|
|
97
|
+
// Track select element - created once, options updated when discipline changes
|
|
98
|
+
const trackSelectEl = select(
|
|
99
|
+
{ className: "form-select", id: "track-select" },
|
|
100
|
+
option({ value: "" }, "Generalist"),
|
|
101
|
+
);
|
|
102
|
+
// Initially disabled until discipline is selected
|
|
103
|
+
trackSelectEl.disabled = true;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a discipline allows trackless (generalist) jobs
|
|
107
|
+
* @param {Object|null} disciplineObj
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
*/
|
|
110
|
+
function allowsTrackless(disciplineObj) {
|
|
111
|
+
if (!disciplineObj) return false;
|
|
112
|
+
const validTracks = disciplineObj.validTracks ?? [];
|
|
113
|
+
// Empty array = trackless only (legacy), or null in array = trackless allowed
|
|
114
|
+
return validTracks.length === 0 || validTracks.includes(null);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get available tracks for a discipline (excludes null entries)
|
|
119
|
+
* @param {Object|null} disciplineObj
|
|
120
|
+
* @returns {Array}
|
|
121
|
+
*/
|
|
122
|
+
function getAvailableTracks(disciplineObj) {
|
|
123
|
+
if (!disciplineObj) return [];
|
|
124
|
+
const validTracks = disciplineObj.validTracks ?? [];
|
|
125
|
+
if (validTracks.length === 0) return [];
|
|
126
|
+
// Filter to actual track IDs (exclude null which means "trackless")
|
|
127
|
+
const trackIds = validTracks.filter((t) => t !== null);
|
|
128
|
+
return data.tracks.filter((t) => trackIds.includes(t.id));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Update track select options based on selected discipline
|
|
133
|
+
* @param {string} disciplineId
|
|
134
|
+
*/
|
|
135
|
+
function updateTrackOptions(disciplineId) {
|
|
136
|
+
const disciplineObj = data.disciplines.find((d) => d.id === disciplineId);
|
|
137
|
+
const availableTracks = getAvailableTracks(disciplineObj);
|
|
138
|
+
const canBeTrackless = allowsTrackless(disciplineObj);
|
|
139
|
+
|
|
140
|
+
// Clear existing options
|
|
141
|
+
trackSelectEl.innerHTML = "";
|
|
142
|
+
|
|
143
|
+
// Add generalist option if trackless is allowed
|
|
144
|
+
if (canBeTrackless) {
|
|
145
|
+
trackSelectEl.appendChild(option({ value: "" }, "Generalist"));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Add available track options
|
|
149
|
+
availableTracks.forEach((t) => {
|
|
150
|
+
trackSelectEl.appendChild(option({ value: t.id }, t.name));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Disable if no options (neither trackless nor tracks)
|
|
154
|
+
trackSelectEl.disabled = !canBeTrackless && availableTracks.length === 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
92
157
|
// Subscribe to selection changes - all updates happen here
|
|
93
158
|
selection.subscribe(({ discipline, track, grade }) => {
|
|
94
|
-
|
|
159
|
+
// Track is now optional - only discipline and grade are required
|
|
160
|
+
if (!discipline || !grade) {
|
|
95
161
|
previewContainer.innerHTML = "";
|
|
96
162
|
previewContainer.appendChild(
|
|
97
163
|
p({ className: "text-muted" }, emptyPreviewText),
|
|
@@ -101,10 +167,10 @@ export function createBuilder({
|
|
|
101
167
|
}
|
|
102
168
|
|
|
103
169
|
const disciplineObj = data.disciplines.find((d) => d.id === discipline);
|
|
104
|
-
const trackObj = data.tracks.find((t) => t.id === track);
|
|
170
|
+
const trackObj = track ? data.tracks.find((t) => t.id === track) : null;
|
|
105
171
|
const gradeObj = data.grades.find((g) => g.id === grade);
|
|
106
172
|
|
|
107
|
-
if (!disciplineObj || !
|
|
173
|
+
if (!disciplineObj || !gradeObj) {
|
|
108
174
|
previewContainer.innerHTML = "";
|
|
109
175
|
previewContainer.appendChild(
|
|
110
176
|
p({ className: "text-muted" }, "Invalid selection. Please try again."),
|
|
@@ -146,37 +212,29 @@ export function createBuilder({
|
|
|
146
212
|
h2({}, formTitle),
|
|
147
213
|
div(
|
|
148
214
|
{ className: "auto-grid-sm gap-lg" },
|
|
149
|
-
// Discipline selector
|
|
215
|
+
// Discipline selector (first)
|
|
150
216
|
div(
|
|
151
217
|
{ className: "form-group" },
|
|
152
218
|
label({ className: "form-label" }, labels.discipline || "Discipline"),
|
|
153
|
-
|
|
219
|
+
createDisciplineSelect({
|
|
154
220
|
id: "discipline-select",
|
|
155
|
-
|
|
221
|
+
disciplines: data.disciplines,
|
|
156
222
|
initialValue: selection.get().discipline,
|
|
157
223
|
placeholder: "Select a discipline...",
|
|
158
224
|
onChange: (value) => {
|
|
159
|
-
|
|
225
|
+
// Update track options when discipline changes
|
|
226
|
+
updateTrackOptions(value);
|
|
227
|
+
// Reset track selection when discipline changes
|
|
228
|
+
selection.update((prev) => ({
|
|
229
|
+
...prev,
|
|
230
|
+
discipline: value,
|
|
231
|
+
track: "",
|
|
232
|
+
}));
|
|
160
233
|
},
|
|
161
234
|
getDisplayName: (d) => d.specialization || d.name,
|
|
162
235
|
}),
|
|
163
236
|
),
|
|
164
|
-
//
|
|
165
|
-
div(
|
|
166
|
-
{ className: "form-group" },
|
|
167
|
-
label({ className: "form-label" }, labels.track || "Track"),
|
|
168
|
-
createSelectWithValue({
|
|
169
|
-
id: "track-select",
|
|
170
|
-
items: data.tracks,
|
|
171
|
-
initialValue: selection.get().track,
|
|
172
|
-
placeholder: "Select a track...",
|
|
173
|
-
onChange: (value) => {
|
|
174
|
-
selection.update((prev) => ({ ...prev, track: value }));
|
|
175
|
-
},
|
|
176
|
-
getDisplayName: (t) => t.name,
|
|
177
|
-
}),
|
|
178
|
-
),
|
|
179
|
-
// Grade selector
|
|
237
|
+
// Grade selector (second)
|
|
180
238
|
div(
|
|
181
239
|
{ className: "form-group" },
|
|
182
240
|
label({ className: "form-label" }, labels.grade || "Grade"),
|
|
@@ -191,6 +249,31 @@ export function createBuilder({
|
|
|
191
249
|
getDisplayName: (g) => g.id,
|
|
192
250
|
}),
|
|
193
251
|
),
|
|
252
|
+
// Track selector (third, optional)
|
|
253
|
+
div(
|
|
254
|
+
{ className: "form-group" },
|
|
255
|
+
label(
|
|
256
|
+
{ className: "form-label" },
|
|
257
|
+
labels.track || "Track (optional)",
|
|
258
|
+
),
|
|
259
|
+
(() => {
|
|
260
|
+
// Wire up track select change handler
|
|
261
|
+
trackSelectEl.addEventListener("change", (e) => {
|
|
262
|
+
selection.update((prev) => ({ ...prev, track: e.target.value }));
|
|
263
|
+
});
|
|
264
|
+
// Initialize track options if discipline is pre-selected
|
|
265
|
+
const initialDiscipline = selection.get().discipline;
|
|
266
|
+
if (initialDiscipline) {
|
|
267
|
+
updateTrackOptions(initialDiscipline);
|
|
268
|
+
// Set initial track value if provided
|
|
269
|
+
const initialTrack = selection.get().track;
|
|
270
|
+
if (initialTrack) {
|
|
271
|
+
trackSelectEl.value = initialTrack;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return trackSelectEl;
|
|
275
|
+
})(),
|
|
276
|
+
),
|
|
194
277
|
),
|
|
195
278
|
previewContainer,
|
|
196
279
|
div({ className: "page-actions" }, actionButton),
|
|
@@ -289,6 +372,15 @@ export function createProgressPreview(preview, selection) {
|
|
|
289
372
|
|
|
290
373
|
const { discipline, grade, track } = selection;
|
|
291
374
|
|
|
375
|
+
// Build badges array - track is optional
|
|
376
|
+
const badges = [
|
|
377
|
+
createBadge(discipline.specialization, "discipline"),
|
|
378
|
+
createBadge(grade.id, "grade"),
|
|
379
|
+
];
|
|
380
|
+
if (track) {
|
|
381
|
+
badges.push(createBadge(track.name, "track"));
|
|
382
|
+
}
|
|
383
|
+
|
|
292
384
|
return div(
|
|
293
385
|
{ className: "job-preview-content" },
|
|
294
386
|
div(
|
|
@@ -296,12 +388,7 @@ export function createProgressPreview(preview, selection) {
|
|
|
296
388
|
div({ className: "preview-label" }, "Current Role"),
|
|
297
389
|
div({ className: "preview-title" }, preview.title),
|
|
298
390
|
),
|
|
299
|
-
div(
|
|
300
|
-
{ className: "preview-badges" },
|
|
301
|
-
createBadge(discipline.specialization, "discipline"),
|
|
302
|
-
createBadge(grade.id, "grade"),
|
|
303
|
-
createBadge(track.name, "track"),
|
|
304
|
-
),
|
|
391
|
+
div({ className: "preview-badges" }, ...badges),
|
|
305
392
|
div(
|
|
306
393
|
{ className: "preview-section", style: "margin-top: 1rem" },
|
|
307
394
|
div({ className: "preview-label" }, "Progression Paths Available"),
|
|
@@ -240,4 +240,20 @@
|
|
|
240
240
|
#app-footer p {
|
|
241
241
|
margin: 0 0 var(--space-sm) 0;
|
|
242
242
|
}
|
|
243
|
+
|
|
244
|
+
.footer-links {
|
|
245
|
+
display: flex;
|
|
246
|
+
gap: var(--space-md);
|
|
247
|
+
justify-content: center;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.footer-links a {
|
|
251
|
+
color: var(--color-text-muted);
|
|
252
|
+
text-decoration: none;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.footer-links a:hover {
|
|
256
|
+
color: var(--color-primary);
|
|
257
|
+
text-decoration: underline;
|
|
258
|
+
}
|
|
243
259
|
}
|
|
@@ -3,131 +3,46 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Formats agent profile data into .agent.md file content
|
|
5
5
|
* following the GitHub Copilot Custom Agents specification.
|
|
6
|
+
*
|
|
7
|
+
* Uses Mustache templates for flexible output formatting.
|
|
8
|
+
* Templates are loaded from data/ directory with fallback to templates/ directory.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
* Format YAML frontmatter value
|
|
10
|
-
* @param {any} value - Value to format
|
|
11
|
-
* @returns {string} Formatted value
|
|
12
|
-
*/
|
|
13
|
-
function formatYamlValue(value) {
|
|
14
|
-
if (Array.isArray(value)) {
|
|
15
|
-
return JSON.stringify(value);
|
|
16
|
-
}
|
|
17
|
-
if (typeof value === "boolean") {
|
|
18
|
-
return String(value);
|
|
19
|
-
}
|
|
20
|
-
if (typeof value === "string") {
|
|
21
|
-
// Quote strings that contain special characters or newlines
|
|
22
|
-
if (value.includes("\n") || value.includes(":") || value.includes("#")) {
|
|
23
|
-
return `"${value.replace(/"/g, '\\"')}"`;
|
|
24
|
-
}
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
return String(value);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Format handoffs array as YAML
|
|
32
|
-
* @param {Array} handoffs - Array of handoff objects
|
|
33
|
-
* @returns {string[]} YAML lines for handoffs
|
|
34
|
-
*/
|
|
35
|
-
function formatHandoffs(handoffs) {
|
|
36
|
-
const lines = ["handoffs:"];
|
|
37
|
-
for (const handoff of handoffs) {
|
|
38
|
-
lines.push(` - label: ${formatYamlValue(handoff.label)}`);
|
|
39
|
-
if (handoff.agent) {
|
|
40
|
-
lines.push(` agent: ${formatYamlValue(handoff.agent)}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Format prompt as single-line string, replacing newlines with spaces
|
|
44
|
-
const singleLinePrompt = handoff.prompt
|
|
45
|
-
.replace(/\n\n+/g, " ")
|
|
46
|
-
.replace(/\n/g, " ")
|
|
47
|
-
.replace(/\s+/g, " ")
|
|
48
|
-
.trim();
|
|
49
|
-
lines.push(` prompt: ${formatYamlValue(singleLinePrompt)}`);
|
|
50
|
-
|
|
51
|
-
if (handoff.send !== undefined) {
|
|
52
|
-
lines.push(` send: ${formatYamlValue(handoff.send)}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return lines;
|
|
56
|
-
}
|
|
11
|
+
import Mustache from "mustache";
|
|
57
12
|
|
|
58
13
|
/**
|
|
59
|
-
* Format agent profile as .agent.md file content
|
|
60
|
-
* @param {Object} profile - Profile with frontmatter and
|
|
14
|
+
* Format agent profile as .agent.md file content using Mustache template
|
|
15
|
+
* @param {Object} profile - Profile with frontmatter and bodyData
|
|
61
16
|
* @param {Object} profile.frontmatter - YAML frontmatter data
|
|
62
17
|
* @param {string} profile.frontmatter.name - Agent name
|
|
63
18
|
* @param {string} profile.frontmatter.description - Agent description
|
|
64
19
|
* @param {string[]} profile.frontmatter.tools - Available tools
|
|
65
20
|
* @param {boolean} profile.frontmatter.infer - Whether to auto-select
|
|
66
21
|
* @param {Array} [profile.frontmatter.handoffs] - Handoff definitions
|
|
67
|
-
* @param {
|
|
22
|
+
* @param {Object} profile.bodyData - Structured body data
|
|
23
|
+
* @param {string} profile.bodyData.title - Agent title (e.g. "Software Engineering - Platform - Plan Agent")
|
|
24
|
+
* @param {string} profile.bodyData.stageDescription - Stage description text
|
|
25
|
+
* @param {string} profile.bodyData.identity - Core identity text
|
|
26
|
+
* @param {string} [profile.bodyData.priority] - Priority/philosophy statement (optional)
|
|
27
|
+
* @param {string[]} profile.bodyData.capabilities - List of capability names
|
|
28
|
+
* @param {Array<{index: number, text: string}>} profile.bodyData.beforeMakingChanges - Numbered steps
|
|
29
|
+
* @param {string} [profile.bodyData.delegation] - Delegation guidance (optional)
|
|
30
|
+
* @param {string} profile.bodyData.operationalContext - Operational context text
|
|
31
|
+
* @param {string} profile.bodyData.workingStyle - Working style markdown section
|
|
32
|
+
* @param {string} [profile.bodyData.beforeHandoff] - Before handoff checklist markdown (optional)
|
|
33
|
+
* @param {string[]} profile.bodyData.constraints - List of constraints
|
|
34
|
+
* @param {string} template - Mustache template string
|
|
68
35
|
* @returns {string} Complete .agent.md file content
|
|
69
36
|
*/
|
|
70
|
-
export function formatAgentProfile({ frontmatter,
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Tools (optional, defaults to all)
|
|
82
|
-
if (frontmatter.tools && frontmatter.tools.length > 0) {
|
|
83
|
-
lines.push(`tools: ${formatYamlValue(frontmatter.tools)}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Infer (optional)
|
|
87
|
-
if (frontmatter.infer !== undefined) {
|
|
88
|
-
lines.push(`infer: ${formatYamlValue(frontmatter.infer)}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Handoffs (optional)
|
|
92
|
-
if (frontmatter.handoffs && frontmatter.handoffs.length > 0) {
|
|
93
|
-
lines.push(...formatHandoffs(frontmatter.handoffs));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
lines.push("---");
|
|
97
|
-
lines.push("");
|
|
98
|
-
lines.push(body);
|
|
99
|
-
|
|
100
|
-
return lines.join("\n");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Format agent profile for CLI output (markdown)
|
|
105
|
-
* @param {Object} profile - Profile with frontmatter and body
|
|
106
|
-
* @returns {string} Markdown formatted for CLI display
|
|
107
|
-
*/
|
|
108
|
-
export function formatAgentProfileForCli({ frontmatter, body }) {
|
|
109
|
-
const lines = [];
|
|
110
|
-
|
|
111
|
-
lines.push(`# Agent Profile: ${frontmatter.name}`);
|
|
112
|
-
lines.push("");
|
|
113
|
-
lines.push(`**Description:** ${frontmatter.description}`);
|
|
114
|
-
lines.push("");
|
|
115
|
-
lines.push(`**Tools:** ${frontmatter.tools.join(", ")}`);
|
|
116
|
-
lines.push(`**Infer:** ${frontmatter.infer}`);
|
|
117
|
-
|
|
118
|
-
if (frontmatter.handoffs && frontmatter.handoffs.length > 0) {
|
|
119
|
-
lines.push("");
|
|
120
|
-
lines.push("**Handoffs:**");
|
|
121
|
-
for (const handoff of frontmatter.handoffs) {
|
|
122
|
-
const target = handoff.agent ? ` → ${handoff.agent}` : " (self)";
|
|
123
|
-
lines.push(` - ${handoff.label}${target}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
lines.push("");
|
|
128
|
-
lines.push("---");
|
|
129
|
-
lines.push("");
|
|
130
|
-
lines.push(body);
|
|
131
|
-
|
|
132
|
-
return lines.join("\n");
|
|
37
|
+
export function formatAgentProfile({ frontmatter, bodyData }, template) {
|
|
38
|
+
const data = {
|
|
39
|
+
// Frontmatter
|
|
40
|
+
name: frontmatter.name,
|
|
41
|
+
description: frontmatter.description,
|
|
42
|
+
infer: frontmatter.infer,
|
|
43
|
+
handoffs: frontmatter.handoffs || [],
|
|
44
|
+
// Body data
|
|
45
|
+
...bodyData,
|
|
46
|
+
};
|
|
47
|
+
return Mustache.render(template, data);
|
|
133
48
|
}
|
|
@@ -3,56 +3,35 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Formats agent skill data into SKILL.md file content
|
|
5
5
|
* following the Agent Skills Standard specification.
|
|
6
|
+
*
|
|
7
|
+
* Uses Mustache templates for flexible output formatting.
|
|
8
|
+
* Templates are loaded from data/ directory with fallback to templates/ directory.
|
|
6
9
|
*/
|
|
7
10
|
|
|
11
|
+
import Mustache from "mustache";
|
|
12
|
+
|
|
8
13
|
/**
|
|
9
|
-
* Format agent skill as SKILL.md file content
|
|
10
|
-
* @param {Object} skill - Skill with frontmatter
|
|
14
|
+
* Format agent skill as SKILL.md file content using Mustache template
|
|
15
|
+
* @param {Object} skill - Skill with frontmatter, title, stages, reference
|
|
11
16
|
* @param {Object} skill.frontmatter - YAML frontmatter data
|
|
12
17
|
* @param {string} skill.frontmatter.name - Skill name (required)
|
|
13
18
|
* @param {string} skill.frontmatter.description - Skill description (required)
|
|
14
|
-
* @param {string} skill.
|
|
19
|
+
* @param {string} skill.title - Human-readable skill title for heading
|
|
20
|
+
* @param {Array} skill.stages - Array of stage objects with stageName, focus, activities, ready
|
|
21
|
+
* @param {string} skill.reference - Reference content (markdown)
|
|
22
|
+
* @param {string} template - Mustache template string
|
|
15
23
|
* @returns {string} Complete SKILL.md file content
|
|
16
24
|
*/
|
|
17
|
-
export function formatAgentSkill(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
} else {
|
|
31
|
-
lines.push(`description: ${description}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
lines.push("---");
|
|
35
|
-
lines.push("");
|
|
36
|
-
lines.push(body);
|
|
37
|
-
|
|
38
|
-
return lines.join("\n");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Format agent skill for CLI output (markdown)
|
|
43
|
-
* @param {Object} skill - Skill with frontmatter and body
|
|
44
|
-
* @returns {string} Markdown formatted for CLI display
|
|
45
|
-
*/
|
|
46
|
-
export function formatAgentSkillForCli({ frontmatter, body }) {
|
|
47
|
-
const lines = [];
|
|
48
|
-
|
|
49
|
-
lines.push(`# Skill: ${frontmatter.name}`);
|
|
50
|
-
lines.push("");
|
|
51
|
-
lines.push(`**Description:** ${frontmatter.description.trim()}`);
|
|
52
|
-
lines.push("");
|
|
53
|
-
lines.push("---");
|
|
54
|
-
lines.push("");
|
|
55
|
-
lines.push(body);
|
|
56
|
-
|
|
57
|
-
return lines.join("\n");
|
|
25
|
+
export function formatAgentSkill(
|
|
26
|
+
{ frontmatter, title, stages, reference },
|
|
27
|
+
template,
|
|
28
|
+
) {
|
|
29
|
+
const data = {
|
|
30
|
+
name: frontmatter.name,
|
|
31
|
+
descriptionLines: frontmatter.description.trim().split("\n"),
|
|
32
|
+
title,
|
|
33
|
+
stages,
|
|
34
|
+
reference: reference ? reference.trim() : "",
|
|
35
|
+
};
|
|
36
|
+
return Mustache.render(template, data);
|
|
58
37
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getConceptEmoji,
|
|
24
24
|
} from "../../model/levels.js";
|
|
25
25
|
import { prepareBehaviourDetail } from "./shared.js";
|
|
26
|
+
import { createJsonLdScript, behaviourToJsonLd } from "../json-ld.js";
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Format behaviour detail as DOM elements
|
|
@@ -41,6 +42,8 @@ export function behaviourToDOM(
|
|
|
41
42
|
const emoji = getConceptEmoji(framework, "behaviour");
|
|
42
43
|
return div(
|
|
43
44
|
{ className: "detail-page behaviour-detail" },
|
|
45
|
+
// JSON-LD structured data
|
|
46
|
+
createJsonLdScript(behaviourToJsonLd(behaviour)),
|
|
44
47
|
// Header
|
|
45
48
|
div(
|
|
46
49
|
{ className: "page-header" },
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behaviour formatting for microdata HTML output
|
|
3
|
+
*
|
|
4
|
+
* Generates clean, class-less HTML with microdata aligned with behaviour.schema.json
|
|
5
|
+
* RDF vocab: https://schema.forwardimpact.team/rdf/
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
openTag,
|
|
10
|
+
prop,
|
|
11
|
+
propRaw,
|
|
12
|
+
metaTag,
|
|
13
|
+
section,
|
|
14
|
+
dl,
|
|
15
|
+
ul,
|
|
16
|
+
escapeHtml,
|
|
17
|
+
formatLevelName,
|
|
18
|
+
htmlDocument,
|
|
19
|
+
} from "../microdata-shared.js";
|
|
20
|
+
import { prepareBehavioursList, prepareBehaviourDetail } from "./shared.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format behaviour list as microdata HTML
|
|
24
|
+
* @param {Array} behaviours - Raw behaviour entities
|
|
25
|
+
* @returns {string} HTML with microdata
|
|
26
|
+
*/
|
|
27
|
+
export function behaviourListToMicrodata(behaviours) {
|
|
28
|
+
const { items } = prepareBehavioursList(behaviours);
|
|
29
|
+
|
|
30
|
+
const content = items
|
|
31
|
+
.map(
|
|
32
|
+
(
|
|
33
|
+
behaviour,
|
|
34
|
+
) => `${openTag("article", { itemtype: "Behaviour", itemid: `#${behaviour.id}` })}
|
|
35
|
+
${prop("h2", "name", behaviour.name)}
|
|
36
|
+
${prop("p", "description", behaviour.truncatedDescription)}
|
|
37
|
+
</article>`,
|
|
38
|
+
)
|
|
39
|
+
.join("\n");
|
|
40
|
+
|
|
41
|
+
return htmlDocument(
|
|
42
|
+
"Behaviours",
|
|
43
|
+
`<main>
|
|
44
|
+
<h1>Behaviours</h1>
|
|
45
|
+
${content}
|
|
46
|
+
</main>`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format behaviour detail as microdata HTML
|
|
52
|
+
* @param {Object} behaviour - Raw behaviour entity
|
|
53
|
+
* @param {Object} context - Additional context
|
|
54
|
+
* @param {Array} context.drivers - All drivers
|
|
55
|
+
* @returns {string} HTML with microdata
|
|
56
|
+
*/
|
|
57
|
+
export function behaviourToMicrodata(behaviour, { drivers }) {
|
|
58
|
+
const view = prepareBehaviourDetail(behaviour, { drivers });
|
|
59
|
+
|
|
60
|
+
if (!view) return "";
|
|
61
|
+
|
|
62
|
+
const sections = [];
|
|
63
|
+
|
|
64
|
+
// Maturity descriptions - uses MaturityDescriptions itemtype
|
|
65
|
+
const maturityPairs = Object.entries(view.maturityDescriptions).map(
|
|
66
|
+
([maturity, desc]) => ({
|
|
67
|
+
term: formatLevelName(maturity),
|
|
68
|
+
definition: desc,
|
|
69
|
+
itemprop: `${maturity.replace(/_([a-z])/g, (_, c) => c.toUpperCase())}Description`,
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
sections.push(
|
|
73
|
+
section(
|
|
74
|
+
"Maturity Levels",
|
|
75
|
+
`${openTag("div", { itemtype: "MaturityDescriptions", itemprop: "maturityDescriptions" })}
|
|
76
|
+
${dl(maturityPairs)}
|
|
77
|
+
</div>`,
|
|
78
|
+
2,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Related drivers
|
|
83
|
+
if (view.relatedDrivers.length > 0) {
|
|
84
|
+
const driverItems = view.relatedDrivers.map(
|
|
85
|
+
(d) => `<a href="#${escapeHtml(d.id)}">${escapeHtml(d.name)}</a>`,
|
|
86
|
+
);
|
|
87
|
+
sections.push(section("Linked to Drivers", ul(driverItems), 2));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const body = `<main>
|
|
91
|
+
${openTag("article", { itemtype: "Behaviour", itemid: `#${view.id}` })}
|
|
92
|
+
${prop("h1", "name", view.name)}
|
|
93
|
+
${metaTag("id", view.id)}
|
|
94
|
+
${propRaw(
|
|
95
|
+
"div",
|
|
96
|
+
"human",
|
|
97
|
+
`${openTag("div", { itemtype: "BehaviourHumanSection" })}
|
|
98
|
+
${prop("p", "description", view.description)}
|
|
99
|
+
${sections.join("\n")}
|
|
100
|
+
</div>`,
|
|
101
|
+
)}
|
|
102
|
+
</article>
|
|
103
|
+
</main>`;
|
|
104
|
+
|
|
105
|
+
return htmlDocument(view.name, body);
|
|
106
|
+
}
|
|
@@ -20,6 +20,24 @@ import {
|
|
|
20
20
|
} from "../../components/action-buttons.js";
|
|
21
21
|
import { getConceptEmoji } from "../../model/levels.js";
|
|
22
22
|
import { prepareDisciplineDetail } from "./shared.js";
|
|
23
|
+
import { createJsonLdScript, disciplineToJsonLd } from "../json-ld.js";
|
|
24
|
+
import { createBadge } from "../../components/card.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get type badges for discipline (Management/Professional)
|
|
28
|
+
* @param {Object} discipline - Raw discipline entity
|
|
29
|
+
* @returns {HTMLElement[]}
|
|
30
|
+
*/
|
|
31
|
+
function getDisciplineTypeBadges(discipline) {
|
|
32
|
+
const badges = [];
|
|
33
|
+
if (discipline.isProfessional) {
|
|
34
|
+
badges.push(createBadge("Professional", "secondary"));
|
|
35
|
+
}
|
|
36
|
+
if (discipline.isManagement) {
|
|
37
|
+
badges.push(createBadge("Management", "primary"));
|
|
38
|
+
}
|
|
39
|
+
return badges;
|
|
40
|
+
}
|
|
23
41
|
|
|
24
42
|
/**
|
|
25
43
|
* Format discipline detail as DOM elements
|
|
@@ -44,15 +62,24 @@ export function disciplineToDOM(
|
|
|
44
62
|
) {
|
|
45
63
|
const view = prepareDisciplineDetail(discipline, { skills, behaviours });
|
|
46
64
|
const emoji = getConceptEmoji(framework, "discipline");
|
|
65
|
+
const typeBadges = getDisciplineTypeBadges(discipline);
|
|
47
66
|
return div(
|
|
48
67
|
{ className: "detail-page discipline-detail" },
|
|
68
|
+
// JSON-LD structured data
|
|
69
|
+
createJsonLdScript(disciplineToJsonLd(discipline, { skills })),
|
|
49
70
|
// Header
|
|
50
71
|
div(
|
|
51
72
|
{ className: "page-header" },
|
|
52
73
|
showBackLink
|
|
53
74
|
? createBackLink("/discipline", "← Back to Disciplines")
|
|
54
75
|
: null,
|
|
55
|
-
|
|
76
|
+
div(
|
|
77
|
+
{ className: "page-title-row" },
|
|
78
|
+
heading1({ className: "page-title" }, `${emoji} `, view.name),
|
|
79
|
+
typeBadges.length > 0
|
|
80
|
+
? div({ className: "page-title-badges" }, ...typeBadges)
|
|
81
|
+
: null,
|
|
82
|
+
),
|
|
56
83
|
p({ className: "page-description" }, view.description),
|
|
57
84
|
showBackLink
|
|
58
85
|
? div(
|