@forwardimpact/pathway 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/commands/agent.js +109 -21
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +43 -29
- package/app/commands/progress.js +14 -7
- package/app/commands/serve.js +5 -0
- package/app/commands/stage.js +0 -10
- package/app/commands/track.js +5 -8
- package/app/components/builder.js +111 -27
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +113 -87
- package/app/formatters/agent/skill.js +64 -31
- package/app/formatters/behaviour/dom.js +3 -0
- package/app/formatters/behaviour/microdata.js +106 -0
- package/app/formatters/discipline/dom.js +28 -1
- package/app/formatters/discipline/microdata.js +117 -0
- package/app/formatters/discipline/shared.js +49 -8
- package/app/formatters/driver/dom.js +3 -0
- package/app/formatters/driver/microdata.js +91 -0
- package/app/formatters/grade/dom.js +3 -0
- package/app/formatters/grade/microdata.js +151 -0
- package/app/formatters/index.js +32 -1
- package/app/formatters/interview/shared.js +13 -8
- package/app/formatters/job/description.js +5 -3
- package/app/formatters/json-ld.js +242 -0
- package/app/formatters/microdata-shared.js +184 -0
- package/app/formatters/progress/shared.js +14 -11
- package/app/formatters/skill/dom.js +3 -0
- package/app/formatters/skill/microdata.js +151 -0
- package/app/formatters/stage/dom.js +3 -18
- package/app/formatters/stage/microdata.js +110 -0
- package/app/formatters/stage/shared.js +0 -27
- package/app/formatters/track/dom.js +5 -30
- package/app/formatters/track/markdown.js +2 -25
- package/app/formatters/track/microdata.js +111 -0
- package/app/formatters/track/shared.js +6 -58
- package/app/handout-main.js +26 -12
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/job-cache.js +12 -9
- package/app/lib/template-loader.js +66 -0
- package/app/lib/yaml-loader.js +25 -8
- package/app/main.js +8 -4
- package/app/model/agent.js +158 -130
- package/app/model/checklist.js +57 -91
- package/app/model/derivation.js +135 -68
- package/app/model/index-generator.js +1 -7
- package/app/model/job.js +19 -13
- package/app/model/levels.js +20 -12
- package/app/model/loader.js +41 -17
- package/app/model/matching.js +33 -3
- package/app/model/profile.js +38 -45
- package/app/model/schema-validation.js +438 -0
- package/app/model/validation.js +747 -68
- package/app/pages/agent-builder.js +119 -25
- package/app/pages/assessment-results.js +10 -4
- package/app/pages/discipline.js +36 -6
- package/app/pages/driver.js +9 -47
- package/app/pages/interview-builder.js +3 -1
- package/app/pages/interview.js +15 -4
- package/app/pages/job-builder.js +4 -1
- package/app/pages/job.js +15 -4
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +72 -21
- package/app/pages/stage.js +3 -126
- package/app/slide-main.js +45 -17
- package/app/slides/index.js +3 -1
- package/app/slides/overview.js +40 -4
- package/app/slides/progress.js +4 -2
- package/bin/pathway.js +18 -64
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
- package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
- package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
- package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
- package/examples/agents/.vscode/settings.json +1 -1
- package/examples/behaviours/outcome_ownership.yaml +1 -2
- package/examples/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/behaviours/precise_communication.yaml +1 -2
- package/examples/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/behaviours/systems_thinking.yaml +1 -2
- package/examples/capabilities/business.yaml +80 -142
- package/examples/capabilities/delivery.yaml +155 -219
- package/examples/capabilities/people.yaml +2 -34
- package/examples/capabilities/reliability.yaml +161 -80
- package/examples/capabilities/scale.yaml +234 -252
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +1 -0
- package/examples/disciplines/data_engineering.yaml +14 -12
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +14 -12
- package/examples/drivers.yaml +1 -4
- package/examples/framework.yaml +1 -2
- package/examples/grades.yaml +1 -3
- package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
- package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/questions/behaviours/precise_communication.yaml +1 -2
- package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/questions/behaviours/systems_thinking.yaml +1 -2
- package/examples/questions/skills/architecture_design.yaml +1 -2
- package/examples/questions/skills/cloud_platforms.yaml +1 -2
- package/examples/questions/skills/code_quality.yaml +1 -2
- package/examples/questions/skills/data_modeling.yaml +1 -2
- package/examples/questions/skills/devops.yaml +1 -2
- package/examples/questions/skills/full_stack_development.yaml +1 -2
- package/examples/questions/skills/sre_practices.yaml +1 -2
- package/examples/questions/skills/stakeholder_management.yaml +1 -2
- package/examples/questions/skills/team_collaboration.yaml +1 -2
- package/examples/questions/skills/technical_writing.yaml +1 -2
- package/examples/self-assessments.yaml +1 -3
- package/examples/stages.yaml +101 -46
- package/examples/tracks/_index.yaml +0 -1
- package/examples/tracks/platform.yaml +8 -13
- package/examples/tracks/sre.yaml +8 -18
- package/examples/vscode-settings.yaml +2 -7
- package/package.json +9 -3
- package/templates/agent.template.md +65 -0
- package/templates/skill.template.md +28 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
- package/examples/tracks/manager.yaml +0 -53
|
@@ -12,6 +12,8 @@ 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";
|
|
@@ -89,9 +91,70 @@ export function createBuilder({
|
|
|
89
91
|
buttonText,
|
|
90
92
|
);
|
|
91
93
|
|
|
94
|
+
// Track select element - created once, options updated when discipline changes
|
|
95
|
+
const trackSelectEl = select(
|
|
96
|
+
{ className: "form-select", id: "track-select" },
|
|
97
|
+
option({ value: "" }, "Generalist"),
|
|
98
|
+
);
|
|
99
|
+
// Initially disabled until discipline is selected
|
|
100
|
+
trackSelectEl.disabled = true;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a discipline allows trackless (generalist) jobs
|
|
104
|
+
* @param {Object|null} disciplineObj
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
function allowsTrackless(disciplineObj) {
|
|
108
|
+
if (!disciplineObj) return false;
|
|
109
|
+
const validTracks = disciplineObj.validTracks ?? [];
|
|
110
|
+
// Empty array = trackless only (legacy), or null in array = trackless allowed
|
|
111
|
+
return validTracks.length === 0 || validTracks.includes(null);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get available tracks for a discipline (excludes null entries)
|
|
116
|
+
* @param {Object|null} disciplineObj
|
|
117
|
+
* @returns {Array}
|
|
118
|
+
*/
|
|
119
|
+
function getAvailableTracks(disciplineObj) {
|
|
120
|
+
if (!disciplineObj) return [];
|
|
121
|
+
const validTracks = disciplineObj.validTracks ?? [];
|
|
122
|
+
if (validTracks.length === 0) return [];
|
|
123
|
+
// Filter to actual track IDs (exclude null which means "trackless")
|
|
124
|
+
const trackIds = validTracks.filter((t) => t !== null);
|
|
125
|
+
return data.tracks.filter((t) => trackIds.includes(t.id));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Update track select options based on selected discipline
|
|
130
|
+
* @param {string} disciplineId
|
|
131
|
+
*/
|
|
132
|
+
function updateTrackOptions(disciplineId) {
|
|
133
|
+
const disciplineObj = data.disciplines.find((d) => d.id === disciplineId);
|
|
134
|
+
const availableTracks = getAvailableTracks(disciplineObj);
|
|
135
|
+
const canBeTrackless = allowsTrackless(disciplineObj);
|
|
136
|
+
|
|
137
|
+
// Clear existing options
|
|
138
|
+
trackSelectEl.innerHTML = "";
|
|
139
|
+
|
|
140
|
+
// Add generalist option if trackless is allowed
|
|
141
|
+
if (canBeTrackless) {
|
|
142
|
+
trackSelectEl.appendChild(option({ value: "" }, "Generalist"));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Add available track options
|
|
146
|
+
availableTracks.forEach((t) => {
|
|
147
|
+
trackSelectEl.appendChild(option({ value: t.id }, t.name));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Disable if no options (neither trackless nor tracks)
|
|
151
|
+
trackSelectEl.disabled = !canBeTrackless && availableTracks.length === 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
92
154
|
// Subscribe to selection changes - all updates happen here
|
|
93
155
|
selection.subscribe(({ discipline, track, grade }) => {
|
|
94
|
-
|
|
156
|
+
// Track is now optional - only discipline and grade are required
|
|
157
|
+
if (!discipline || !grade) {
|
|
95
158
|
previewContainer.innerHTML = "";
|
|
96
159
|
previewContainer.appendChild(
|
|
97
160
|
p({ className: "text-muted" }, emptyPreviewText),
|
|
@@ -101,10 +164,10 @@ export function createBuilder({
|
|
|
101
164
|
}
|
|
102
165
|
|
|
103
166
|
const disciplineObj = data.disciplines.find((d) => d.id === discipline);
|
|
104
|
-
const trackObj = data.tracks.find((t) => t.id === track);
|
|
167
|
+
const trackObj = track ? data.tracks.find((t) => t.id === track) : null;
|
|
105
168
|
const gradeObj = data.grades.find((g) => g.id === grade);
|
|
106
169
|
|
|
107
|
-
if (!disciplineObj || !
|
|
170
|
+
if (!disciplineObj || !gradeObj) {
|
|
108
171
|
previewContainer.innerHTML = "";
|
|
109
172
|
previewContainer.appendChild(
|
|
110
173
|
p({ className: "text-muted" }, "Invalid selection. Please try again."),
|
|
@@ -146,7 +209,7 @@ export function createBuilder({
|
|
|
146
209
|
h2({}, formTitle),
|
|
147
210
|
div(
|
|
148
211
|
{ className: "auto-grid-sm gap-lg" },
|
|
149
|
-
// Discipline selector
|
|
212
|
+
// Discipline selector (first)
|
|
150
213
|
div(
|
|
151
214
|
{ className: "form-group" },
|
|
152
215
|
label({ className: "form-label" }, labels.discipline || "Discipline"),
|
|
@@ -156,27 +219,19 @@ export function createBuilder({
|
|
|
156
219
|
initialValue: selection.get().discipline,
|
|
157
220
|
placeholder: "Select a discipline...",
|
|
158
221
|
onChange: (value) => {
|
|
159
|
-
|
|
222
|
+
// Update track options when discipline changes
|
|
223
|
+
updateTrackOptions(value);
|
|
224
|
+
// Reset track selection when discipline changes
|
|
225
|
+
selection.update((prev) => ({
|
|
226
|
+
...prev,
|
|
227
|
+
discipline: value,
|
|
228
|
+
track: "",
|
|
229
|
+
}));
|
|
160
230
|
},
|
|
161
231
|
getDisplayName: (d) => d.specialization || d.name,
|
|
162
232
|
}),
|
|
163
233
|
),
|
|
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
|
|
234
|
+
// Grade selector (second)
|
|
180
235
|
div(
|
|
181
236
|
{ className: "form-group" },
|
|
182
237
|
label({ className: "form-label" }, labels.grade || "Grade"),
|
|
@@ -191,6 +246,31 @@ export function createBuilder({
|
|
|
191
246
|
getDisplayName: (g) => g.id,
|
|
192
247
|
}),
|
|
193
248
|
),
|
|
249
|
+
// Track selector (third, optional)
|
|
250
|
+
div(
|
|
251
|
+
{ className: "form-group" },
|
|
252
|
+
label(
|
|
253
|
+
{ className: "form-label" },
|
|
254
|
+
labels.track || "Track (optional)",
|
|
255
|
+
),
|
|
256
|
+
(() => {
|
|
257
|
+
// Wire up track select change handler
|
|
258
|
+
trackSelectEl.addEventListener("change", (e) => {
|
|
259
|
+
selection.update((prev) => ({ ...prev, track: e.target.value }));
|
|
260
|
+
});
|
|
261
|
+
// Initialize track options if discipline is pre-selected
|
|
262
|
+
const initialDiscipline = selection.get().discipline;
|
|
263
|
+
if (initialDiscipline) {
|
|
264
|
+
updateTrackOptions(initialDiscipline);
|
|
265
|
+
// Set initial track value if provided
|
|
266
|
+
const initialTrack = selection.get().track;
|
|
267
|
+
if (initialTrack) {
|
|
268
|
+
trackSelectEl.value = initialTrack;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return trackSelectEl;
|
|
272
|
+
})(),
|
|
273
|
+
),
|
|
194
274
|
),
|
|
195
275
|
previewContainer,
|
|
196
276
|
div({ className: "page-actions" }, actionButton),
|
|
@@ -289,6 +369,15 @@ export function createProgressPreview(preview, selection) {
|
|
|
289
369
|
|
|
290
370
|
const { discipline, grade, track } = selection;
|
|
291
371
|
|
|
372
|
+
// Build badges array - track is optional
|
|
373
|
+
const badges = [
|
|
374
|
+
createBadge(discipline.specialization, "discipline"),
|
|
375
|
+
createBadge(grade.id, "grade"),
|
|
376
|
+
];
|
|
377
|
+
if (track) {
|
|
378
|
+
badges.push(createBadge(track.name, "track"));
|
|
379
|
+
}
|
|
380
|
+
|
|
292
381
|
return div(
|
|
293
382
|
{ className: "job-preview-content" },
|
|
294
383
|
div(
|
|
@@ -296,12 +385,7 @@ export function createProgressPreview(preview, selection) {
|
|
|
296
385
|
div({ className: "preview-label" }, "Current Role"),
|
|
297
386
|
div({ className: "preview-title" }, preview.title),
|
|
298
387
|
),
|
|
299
|
-
div(
|
|
300
|
-
{ className: "preview-badges" },
|
|
301
|
-
createBadge(discipline.specialization, "discipline"),
|
|
302
|
-
createBadge(grade.id, "grade"),
|
|
303
|
-
createBadge(track.name, "track"),
|
|
304
|
-
),
|
|
388
|
+
div({ className: "preview-badges" }, ...badges),
|
|
305
389
|
div(
|
|
306
390
|
{ className: "preview-section", style: "margin-top: 1rem" },
|
|
307
391
|
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,116 +3,62 @@
|
|
|
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");
|
|
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);
|
|
101
48
|
}
|
|
102
49
|
|
|
103
50
|
/**
|
|
104
51
|
* Format agent profile for CLI output (markdown)
|
|
105
|
-
* @param {Object} profile - Profile with frontmatter and
|
|
52
|
+
* @param {Object} profile - Profile with frontmatter and bodyData
|
|
106
53
|
* @returns {string} Markdown formatted for CLI display
|
|
107
54
|
*/
|
|
108
|
-
export function formatAgentProfileForCli({ frontmatter,
|
|
55
|
+
export function formatAgentProfileForCli({ frontmatter, bodyData }) {
|
|
109
56
|
const lines = [];
|
|
110
57
|
|
|
111
58
|
lines.push(`# Agent Profile: ${frontmatter.name}`);
|
|
112
59
|
lines.push("");
|
|
113
60
|
lines.push(`**Description:** ${frontmatter.description}`);
|
|
114
61
|
lines.push("");
|
|
115
|
-
lines.push(`**Tools:** ${frontmatter.tools.join(", ")}`);
|
|
116
62
|
lines.push(`**Infer:** ${frontmatter.infer}`);
|
|
117
63
|
|
|
118
64
|
if (frontmatter.handoffs && frontmatter.handoffs.length > 0) {
|
|
@@ -127,7 +73,87 @@ export function formatAgentProfileForCli({ frontmatter, body }) {
|
|
|
127
73
|
lines.push("");
|
|
128
74
|
lines.push("---");
|
|
129
75
|
lines.push("");
|
|
130
|
-
|
|
76
|
+
|
|
77
|
+
// Render structured body data
|
|
78
|
+
lines.push(`# ${bodyData.title}`);
|
|
79
|
+
lines.push("");
|
|
80
|
+
lines.push(bodyData.stageDescription);
|
|
81
|
+
lines.push("");
|
|
82
|
+
|
|
83
|
+
lines.push("## Core Identity");
|
|
84
|
+
lines.push("");
|
|
85
|
+
lines.push(bodyData.identity);
|
|
86
|
+
lines.push("");
|
|
87
|
+
|
|
88
|
+
if (bodyData.priority) {
|
|
89
|
+
lines.push(bodyData.priority);
|
|
90
|
+
lines.push("");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (bodyData.capabilities && bodyData.capabilities.length > 0) {
|
|
94
|
+
lines.push("Your primary capabilities:");
|
|
95
|
+
for (const cap of bodyData.capabilities) {
|
|
96
|
+
lines.push(`- ${cap}`);
|
|
97
|
+
}
|
|
98
|
+
lines.push("");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (bodyData.beforeMakingChanges && bodyData.beforeMakingChanges.length > 0) {
|
|
102
|
+
lines.push("Before making changes:");
|
|
103
|
+
for (const step of bodyData.beforeMakingChanges) {
|
|
104
|
+
lines.push(`${step.index}. ${step.text}`);
|
|
105
|
+
}
|
|
106
|
+
lines.push("");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (bodyData.delegation) {
|
|
110
|
+
lines.push("## Delegation");
|
|
111
|
+
lines.push("");
|
|
112
|
+
lines.push(bodyData.delegation);
|
|
113
|
+
lines.push("");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
lines.push("## Operational Context");
|
|
117
|
+
lines.push("");
|
|
118
|
+
lines.push(bodyData.operationalContext);
|
|
119
|
+
lines.push("");
|
|
120
|
+
|
|
121
|
+
lines.push(bodyData.workingStyle);
|
|
122
|
+
|
|
123
|
+
if (bodyData.beforeHandoff) {
|
|
124
|
+
lines.push("## Before Handoff");
|
|
125
|
+
lines.push("");
|
|
126
|
+
lines.push(
|
|
127
|
+
"Before offering a handoff, verify and summarize completion of these items:",
|
|
128
|
+
);
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push(bodyData.beforeHandoff);
|
|
131
|
+
lines.push("");
|
|
132
|
+
lines.push(
|
|
133
|
+
"When verified, summarize what was accomplished then offer the handoff.",
|
|
134
|
+
);
|
|
135
|
+
lines.push("If items are incomplete, explain what remains.");
|
|
136
|
+
lines.push("");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
lines.push("## Return Format");
|
|
140
|
+
lines.push("");
|
|
141
|
+
lines.push("When completing work (for handoff or as a subagent), provide:");
|
|
142
|
+
lines.push("");
|
|
143
|
+
lines.push("1. **Work completed**: What was accomplished");
|
|
144
|
+
lines.push(
|
|
145
|
+
"2. **Checklist status**: Items verified from Before Handoff section",
|
|
146
|
+
);
|
|
147
|
+
lines.push("3. **Recommendation**: Ready for next stage, or needs more work");
|
|
148
|
+
lines.push("");
|
|
149
|
+
|
|
150
|
+
if (bodyData.constraints && bodyData.constraints.length > 0) {
|
|
151
|
+
lines.push("## Constraints");
|
|
152
|
+
lines.push("");
|
|
153
|
+
for (const constraint of bodyData.constraints) {
|
|
154
|
+
lines.push(`- ${constraint}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
131
157
|
|
|
132
158
|
return lines.join("\n");
|
|
133
159
|
}
|
|
@@ -3,56 +3,89 @@
|
|
|
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");
|
|
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);
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
/**
|
|
42
40
|
* Format agent skill for CLI output (markdown)
|
|
43
|
-
* @param {Object} skill - Skill with frontmatter
|
|
41
|
+
* @param {Object} skill - Skill with frontmatter, title, stages, reference
|
|
44
42
|
* @returns {string} Markdown formatted for CLI display
|
|
45
43
|
*/
|
|
46
|
-
export function formatAgentSkillForCli({
|
|
44
|
+
export function formatAgentSkillForCli({
|
|
45
|
+
frontmatter,
|
|
46
|
+
title,
|
|
47
|
+
stages,
|
|
48
|
+
reference,
|
|
49
|
+
}) {
|
|
47
50
|
const lines = [];
|
|
48
51
|
|
|
49
|
-
lines.push(`#
|
|
52
|
+
lines.push(`# ${title}`);
|
|
50
53
|
lines.push("");
|
|
51
|
-
lines.push(`**
|
|
54
|
+
lines.push(`**Name:** ${frontmatter.name}`);
|
|
52
55
|
lines.push("");
|
|
53
|
-
lines.push(
|
|
56
|
+
lines.push(`**Description:** ${frontmatter.description.trim()}`);
|
|
54
57
|
lines.push("");
|
|
55
|
-
|
|
58
|
+
|
|
59
|
+
if (stages && stages.length > 0) {
|
|
60
|
+
lines.push("## Stage Guidance");
|
|
61
|
+
lines.push("");
|
|
62
|
+
for (const stage of stages) {
|
|
63
|
+
lines.push(`### ${stage.stageName} Stage`);
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push(`**Focus:** ${stage.focus.trim()}`);
|
|
66
|
+
lines.push("");
|
|
67
|
+
if (stage.activities && stage.activities.length > 0) {
|
|
68
|
+
lines.push("**Activities:**");
|
|
69
|
+
for (const item of stage.activities) {
|
|
70
|
+
lines.push(`- ${item}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push("");
|
|
73
|
+
}
|
|
74
|
+
if (stage.ready && stage.ready.length > 0) {
|
|
75
|
+
lines.push(`**Ready for ${stage.nextStageName} when:**`);
|
|
76
|
+
for (const item of stage.ready) {
|
|
77
|
+
lines.push(`- [ ] ${item}`);
|
|
78
|
+
}
|
|
79
|
+
lines.push("");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (reference) {
|
|
85
|
+
lines.push("## Reference");
|
|
86
|
+
lines.push("");
|
|
87
|
+
lines.push(reference.trim());
|
|
88
|
+
}
|
|
56
89
|
|
|
57
90
|
return lines.join("\n");
|
|
58
91
|
}
|
|
@@ -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" },
|