@forwardimpact/pathway 0.1.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/LICENSE +201 -0
- package/README.md +104 -0
- package/app/commands/agent.js +430 -0
- package/app/commands/behaviour.js +61 -0
- package/app/commands/command-factory.js +211 -0
- package/app/commands/discipline.js +58 -0
- package/app/commands/driver.js +94 -0
- package/app/commands/grade.js +60 -0
- package/app/commands/index.js +20 -0
- package/app/commands/init.js +67 -0
- package/app/commands/interview.js +68 -0
- package/app/commands/job.js +157 -0
- package/app/commands/progress.js +77 -0
- package/app/commands/questions.js +179 -0
- package/app/commands/serve.js +143 -0
- package/app/commands/site.js +121 -0
- package/app/commands/skill.js +76 -0
- package/app/commands/stage.js +129 -0
- package/app/commands/track.js +70 -0
- package/app/components/action-buttons.js +66 -0
- package/app/components/behaviour-profile.js +53 -0
- package/app/components/builder.js +341 -0
- package/app/components/card.js +98 -0
- package/app/components/checklist.js +145 -0
- package/app/components/comparison-radar.js +237 -0
- package/app/components/detail.js +230 -0
- package/app/components/error-page.js +72 -0
- package/app/components/grid.js +109 -0
- package/app/components/list.js +120 -0
- package/app/components/modifier-table.js +142 -0
- package/app/components/nav.js +64 -0
- package/app/components/progression-table.js +320 -0
- package/app/components/radar-chart.js +102 -0
- package/app/components/skill-matrix.js +97 -0
- package/app/css/base.css +56 -0
- package/app/css/bundles/app.css +40 -0
- package/app/css/bundles/handout.css +43 -0
- package/app/css/bundles/slides.css +40 -0
- package/app/css/components/badges.css +215 -0
- package/app/css/components/buttons.css +101 -0
- package/app/css/components/forms.css +105 -0
- package/app/css/components/layout.css +209 -0
- package/app/css/components/nav.css +166 -0
- package/app/css/components/progress.css +166 -0
- package/app/css/components/states.css +82 -0
- package/app/css/components/surfaces.css +243 -0
- package/app/css/components/tables.css +362 -0
- package/app/css/components/typography.css +122 -0
- package/app/css/components/utilities.css +41 -0
- package/app/css/pages/agent-builder.css +391 -0
- package/app/css/pages/assessment-results.css +453 -0
- package/app/css/pages/detail.css +59 -0
- package/app/css/pages/interview-builder.css +148 -0
- package/app/css/pages/job-builder.css +134 -0
- package/app/css/pages/landing.css +92 -0
- package/app/css/pages/lifecycle.css +118 -0
- package/app/css/pages/progress-builder.css +274 -0
- package/app/css/pages/self-assessment.css +502 -0
- package/app/css/reset.css +50 -0
- package/app/css/tokens.css +153 -0
- package/app/css/views/handout.css +30 -0
- package/app/css/views/print.css +608 -0
- package/app/css/views/slide-animations.css +113 -0
- package/app/css/views/slide-base.css +330 -0
- package/app/css/views/slide-sections.css +597 -0
- package/app/css/views/slide-tables.css +275 -0
- package/app/formatters/agent/dom.js +540 -0
- package/app/formatters/agent/profile.js +133 -0
- package/app/formatters/agent/skill.js +58 -0
- package/app/formatters/behaviour/dom.js +91 -0
- package/app/formatters/behaviour/markdown.js +54 -0
- package/app/formatters/behaviour/shared.js +64 -0
- package/app/formatters/discipline/dom.js +187 -0
- package/app/formatters/discipline/markdown.js +87 -0
- package/app/formatters/discipline/shared.js +131 -0
- package/app/formatters/driver/dom.js +103 -0
- package/app/formatters/driver/shared.js +92 -0
- package/app/formatters/grade/dom.js +208 -0
- package/app/formatters/grade/markdown.js +94 -0
- package/app/formatters/grade/shared.js +86 -0
- package/app/formatters/index.js +50 -0
- package/app/formatters/interview/dom.js +97 -0
- package/app/formatters/interview/markdown.js +66 -0
- package/app/formatters/interview/shared.js +332 -0
- package/app/formatters/job/description.js +176 -0
- package/app/formatters/job/dom.js +411 -0
- package/app/formatters/job/markdown.js +102 -0
- package/app/formatters/progress/dom.js +135 -0
- package/app/formatters/progress/markdown.js +86 -0
- package/app/formatters/progress/shared.js +339 -0
- package/app/formatters/questions/json.js +43 -0
- package/app/formatters/questions/markdown.js +303 -0
- package/app/formatters/questions/shared.js +274 -0
- package/app/formatters/questions/yaml.js +76 -0
- package/app/formatters/shared.js +71 -0
- package/app/formatters/skill/dom.js +168 -0
- package/app/formatters/skill/markdown.js +109 -0
- package/app/formatters/skill/shared.js +125 -0
- package/app/formatters/stage/dom.js +135 -0
- package/app/formatters/stage/index.js +12 -0
- package/app/formatters/stage/shared.js +111 -0
- package/app/formatters/track/dom.js +128 -0
- package/app/formatters/track/markdown.js +105 -0
- package/app/formatters/track/shared.js +181 -0
- package/app/handout-main.js +421 -0
- package/app/handout.html +21 -0
- package/app/index.html +59 -0
- package/app/lib/card-mappers.js +173 -0
- package/app/lib/cli-output.js +270 -0
- package/app/lib/error-boundary.js +70 -0
- package/app/lib/errors.js +49 -0
- package/app/lib/form-controls.js +47 -0
- package/app/lib/job-cache.js +86 -0
- package/app/lib/markdown.js +114 -0
- package/app/lib/radar.js +866 -0
- package/app/lib/reactive.js +77 -0
- package/app/lib/render.js +212 -0
- package/app/lib/router-core.js +160 -0
- package/app/lib/router-pages.js +16 -0
- package/app/lib/router-slides.js +202 -0
- package/app/lib/state.js +148 -0
- package/app/lib/utils.js +14 -0
- package/app/lib/yaml-loader.js +327 -0
- package/app/main.js +213 -0
- package/app/model/agent.js +702 -0
- package/app/model/checklist.js +137 -0
- package/app/model/derivation.js +699 -0
- package/app/model/index-generator.js +71 -0
- package/app/model/interview.js +539 -0
- package/app/model/job.js +222 -0
- package/app/model/levels.js +591 -0
- package/app/model/loader.js +564 -0
- package/app/model/matching.js +858 -0
- package/app/model/modifiers.js +158 -0
- package/app/model/profile.js +266 -0
- package/app/model/progression.js +507 -0
- package/app/model/validation.js +1385 -0
- package/app/pages/agent-builder.js +823 -0
- package/app/pages/assessment-results.js +507 -0
- package/app/pages/behaviour.js +70 -0
- package/app/pages/discipline.js +71 -0
- package/app/pages/driver.js +106 -0
- package/app/pages/grade.js +117 -0
- package/app/pages/interview-builder.js +50 -0
- package/app/pages/interview.js +304 -0
- package/app/pages/job-builder.js +50 -0
- package/app/pages/job.js +58 -0
- package/app/pages/landing.js +305 -0
- package/app/pages/progress-builder.js +58 -0
- package/app/pages/progress.js +495 -0
- package/app/pages/self-assessment.js +729 -0
- package/app/pages/skill.js +113 -0
- package/app/pages/stage.js +231 -0
- package/app/pages/track.js +69 -0
- package/app/slide-main.js +360 -0
- package/app/slides/behaviour.js +38 -0
- package/app/slides/chapter.js +82 -0
- package/app/slides/discipline.js +40 -0
- package/app/slides/driver.js +39 -0
- package/app/slides/grade.js +32 -0
- package/app/slides/index.js +198 -0
- package/app/slides/interview.js +58 -0
- package/app/slides/job.js +55 -0
- package/app/slides/overview.js +126 -0
- package/app/slides/progress.js +83 -0
- package/app/slides/skill.js +40 -0
- package/app/slides/track.js +39 -0
- package/app/slides.html +56 -0
- package/app/types.js +147 -0
- package/bin/pathway.js +489 -0
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
- package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
- package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
- package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
- package/examples/agents/.vscode/settings.json +8 -0
- package/examples/behaviours/_index.yaml +8 -0
- package/examples/behaviours/outcome_ownership.yaml +44 -0
- package/examples/behaviours/polymathic_knowledge.yaml +42 -0
- package/examples/behaviours/precise_communication.yaml +40 -0
- package/examples/behaviours/relentless_curiosity.yaml +38 -0
- package/examples/behaviours/systems_thinking.yaml +41 -0
- package/examples/capabilities/_index.yaml +8 -0
- package/examples/capabilities/business.yaml +251 -0
- package/examples/capabilities/delivery.yaml +352 -0
- package/examples/capabilities/people.yaml +100 -0
- package/examples/capabilities/reliability.yaml +318 -0
- package/examples/capabilities/scale.yaml +394 -0
- package/examples/disciplines/_index.yaml +5 -0
- package/examples/disciplines/data_engineering.yaml +76 -0
- package/examples/disciplines/software_engineering.yaml +76 -0
- package/examples/drivers.yaml +205 -0
- package/examples/framework.yaml +58 -0
- package/examples/grades.yaml +118 -0
- package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
- package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
- package/examples/questions/behaviours/precise_communication.yaml +55 -0
- package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
- package/examples/questions/behaviours/systems_thinking.yaml +53 -0
- package/examples/questions/skills/architecture_design.yaml +54 -0
- package/examples/questions/skills/cloud_platforms.yaml +48 -0
- package/examples/questions/skills/code_quality.yaml +49 -0
- package/examples/questions/skills/data_modeling.yaml +46 -0
- package/examples/questions/skills/devops.yaml +47 -0
- package/examples/questions/skills/full_stack_development.yaml +48 -0
- package/examples/questions/skills/sre_practices.yaml +44 -0
- package/examples/questions/skills/stakeholder_management.yaml +49 -0
- package/examples/questions/skills/team_collaboration.yaml +43 -0
- package/examples/questions/skills/technical_writing.yaml +43 -0
- package/examples/self-assessments.yaml +66 -0
- package/examples/stages.yaml +76 -0
- package/examples/tracks/_index.yaml +6 -0
- package/examples/tracks/manager.yaml +53 -0
- package/examples/tracks/platform.yaml +54 -0
- package/examples/tracks/sre.yaml +58 -0
- package/examples/vscode-settings.yaml +22 -0
- package/package.json +68 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent DOM Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats agent deployment data into DOM elements for browser display.
|
|
5
|
+
* Includes copy and download functionality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
div,
|
|
10
|
+
h2,
|
|
11
|
+
h3,
|
|
12
|
+
p,
|
|
13
|
+
span,
|
|
14
|
+
button,
|
|
15
|
+
section,
|
|
16
|
+
details,
|
|
17
|
+
summary,
|
|
18
|
+
} from "../../lib/render.js";
|
|
19
|
+
import { formatAgentProfile } from "./profile.js";
|
|
20
|
+
import { formatAgentSkill } from "./skill.js";
|
|
21
|
+
import { getStageEmoji } from "../stage/shared.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert agent deployment to DOM elements
|
|
25
|
+
* @param {Object} deployment - Generated deployment
|
|
26
|
+
* @param {Object} deployment.profile - Agent profile
|
|
27
|
+
* @param {Array} deployment.skills - Agent skills
|
|
28
|
+
* @param {Array} [deployment.roleAgents] - Role variant agents (plan, review)
|
|
29
|
+
* @param {Object} [deployment.vscodeSettings] - VS Code settings to include in download
|
|
30
|
+
* @returns {HTMLElement}
|
|
31
|
+
*/
|
|
32
|
+
export function agentDeploymentToDOM({
|
|
33
|
+
profile,
|
|
34
|
+
skills,
|
|
35
|
+
roleAgents = [],
|
|
36
|
+
vscodeSettings = {},
|
|
37
|
+
}) {
|
|
38
|
+
const profileContent = formatAgentProfile(profile);
|
|
39
|
+
const agentName = profile.frontmatter.name;
|
|
40
|
+
|
|
41
|
+
return div(
|
|
42
|
+
{ className: "agent-deployment" },
|
|
43
|
+
|
|
44
|
+
// Download all button
|
|
45
|
+
createDownloadButton(
|
|
46
|
+
profile,
|
|
47
|
+
skills,
|
|
48
|
+
roleAgents,
|
|
49
|
+
vscodeSettings,
|
|
50
|
+
agentName,
|
|
51
|
+
),
|
|
52
|
+
|
|
53
|
+
// Profile section
|
|
54
|
+
section(
|
|
55
|
+
{ className: "agent-section" },
|
|
56
|
+
div(
|
|
57
|
+
{ className: "section-header" },
|
|
58
|
+
h2({}, "Agent Profile"),
|
|
59
|
+
createCopyButton(profileContent),
|
|
60
|
+
),
|
|
61
|
+
p({ className: "filename" }, profile.filename),
|
|
62
|
+
createCodeBlock(profileContent),
|
|
63
|
+
),
|
|
64
|
+
|
|
65
|
+
// Role Agents section
|
|
66
|
+
roleAgents.length > 0
|
|
67
|
+
? section(
|
|
68
|
+
{ className: "agent-section" },
|
|
69
|
+
h2({}, `Role Variants (${roleAgents.length})`),
|
|
70
|
+
p(
|
|
71
|
+
{ className: "text-muted" },
|
|
72
|
+
"Specialized agents for specific workflow phases. These can be used as subagents or standalone.",
|
|
73
|
+
),
|
|
74
|
+
div(
|
|
75
|
+
{ className: "role-agents-list" },
|
|
76
|
+
...roleAgents.map((agent) => createRoleAgentCard(agent)),
|
|
77
|
+
),
|
|
78
|
+
)
|
|
79
|
+
: null,
|
|
80
|
+
|
|
81
|
+
// Skills section
|
|
82
|
+
section(
|
|
83
|
+
{ className: "agent-section" },
|
|
84
|
+
h2({}, `Skills (${skills.length})`),
|
|
85
|
+
skills.length > 0
|
|
86
|
+
? div(
|
|
87
|
+
{ className: "skills-list" },
|
|
88
|
+
...skills.map((skill) => createSkillCard(skill)),
|
|
89
|
+
)
|
|
90
|
+
: p(
|
|
91
|
+
{ className: "text-muted" },
|
|
92
|
+
"No skills with agent sections found for this discipline.",
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
|
|
96
|
+
// CLI hint section
|
|
97
|
+
section(
|
|
98
|
+
{ className: "agent-section cli-hint" },
|
|
99
|
+
h2({}, "CLI Alternative"),
|
|
100
|
+
p({}, "Generate this agent from the command line:"),
|
|
101
|
+
createCliCommand(agentName),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create download all button
|
|
108
|
+
* @param {Object} profile - Agent profile
|
|
109
|
+
* @param {Array} skills - Agent skills
|
|
110
|
+
* @param {Array} roleAgents - Role variant agents
|
|
111
|
+
* @param {Object} vscodeSettings - VS Code settings to include
|
|
112
|
+
* @param {string} agentName - Agent name for zip filename
|
|
113
|
+
* @returns {HTMLElement}
|
|
114
|
+
*/
|
|
115
|
+
function createDownloadButton(
|
|
116
|
+
profile,
|
|
117
|
+
skills,
|
|
118
|
+
roleAgents,
|
|
119
|
+
vscodeSettings,
|
|
120
|
+
agentName,
|
|
121
|
+
) {
|
|
122
|
+
const btn = button(
|
|
123
|
+
{ className: "btn btn-primary download-all-btn" },
|
|
124
|
+
"📦 Download All (.zip)",
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
btn.addEventListener("click", async () => {
|
|
128
|
+
btn.disabled = true;
|
|
129
|
+
btn.textContent = "Generating...";
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await downloadAllAsZip(
|
|
133
|
+
profile,
|
|
134
|
+
skills,
|
|
135
|
+
roleAgents,
|
|
136
|
+
vscodeSettings,
|
|
137
|
+
agentName,
|
|
138
|
+
);
|
|
139
|
+
} finally {
|
|
140
|
+
btn.disabled = false;
|
|
141
|
+
btn.textContent = "📦 Download All (.zip)";
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return btn;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a copy button for content
|
|
150
|
+
* @param {string} content - Content to copy
|
|
151
|
+
* @returns {HTMLElement}
|
|
152
|
+
*/
|
|
153
|
+
function createCopyButton(content) {
|
|
154
|
+
const btn = button({ className: "btn btn-sm copy-btn" }, "📋 Copy");
|
|
155
|
+
|
|
156
|
+
btn.addEventListener("click", async () => {
|
|
157
|
+
try {
|
|
158
|
+
await navigator.clipboard.writeText(content);
|
|
159
|
+
btn.textContent = "✓ Copied";
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
btn.textContent = "📋 Copy";
|
|
162
|
+
}, 2000);
|
|
163
|
+
} catch {
|
|
164
|
+
btn.textContent = "Failed";
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
btn.textContent = "📋 Copy";
|
|
167
|
+
}, 2000);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return btn;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Create a code block with content
|
|
176
|
+
* @param {string} content - Code content
|
|
177
|
+
* @returns {HTMLElement}
|
|
178
|
+
*/
|
|
179
|
+
function createCodeBlock(content) {
|
|
180
|
+
const pre = document.createElement("pre");
|
|
181
|
+
pre.className = "code-block";
|
|
182
|
+
|
|
183
|
+
const code = document.createElement("code");
|
|
184
|
+
code.textContent = content;
|
|
185
|
+
|
|
186
|
+
pre.appendChild(code);
|
|
187
|
+
return pre;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create a skill card with content and copy button
|
|
192
|
+
* @param {Object} skill - Skill with frontmatter and body
|
|
193
|
+
* @returns {HTMLElement}
|
|
194
|
+
*/
|
|
195
|
+
function createSkillCard(skill) {
|
|
196
|
+
const content = formatAgentSkill(skill);
|
|
197
|
+
const filename = `${skill.dirname}/SKILL.md`;
|
|
198
|
+
|
|
199
|
+
return div(
|
|
200
|
+
{ className: "skill-card" },
|
|
201
|
+
div(
|
|
202
|
+
{ className: "skill-header" },
|
|
203
|
+
span({ className: "skill-filename" }, filename),
|
|
204
|
+
createCopyButton(content),
|
|
205
|
+
),
|
|
206
|
+
createCodeBlock(content),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a role agent card with collapsible content
|
|
212
|
+
* @param {Object} agent - Role agent with frontmatter, body, filename
|
|
213
|
+
* @returns {HTMLElement}
|
|
214
|
+
*/
|
|
215
|
+
function createRoleAgentCard(agent) {
|
|
216
|
+
const content = formatAgentProfile(agent);
|
|
217
|
+
const roleName = agent.frontmatter.name.split("-").pop(); // Extract role suffix (plan, review)
|
|
218
|
+
|
|
219
|
+
return details(
|
|
220
|
+
{ className: "role-agent-card" },
|
|
221
|
+
summary(
|
|
222
|
+
{},
|
|
223
|
+
div(
|
|
224
|
+
{ className: "role-agent-header" },
|
|
225
|
+
span(
|
|
226
|
+
{ className: "role-name" },
|
|
227
|
+
`${roleName.charAt(0).toUpperCase() + roleName.slice(1)} Agent`,
|
|
228
|
+
),
|
|
229
|
+
span({ className: "role-filename" }, agent.filename),
|
|
230
|
+
),
|
|
231
|
+
),
|
|
232
|
+
div(
|
|
233
|
+
{ className: "role-agent-content" },
|
|
234
|
+
p(
|
|
235
|
+
{ className: "text-muted role-description" },
|
|
236
|
+
agent.frontmatter.description,
|
|
237
|
+
),
|
|
238
|
+
div({ className: "role-agent-actions" }, createCopyButton(content)),
|
|
239
|
+
createCodeBlock(content),
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Create CLI command display
|
|
246
|
+
* @param {string} agentName - Agent name (kebab-case)
|
|
247
|
+
* @returns {HTMLElement}
|
|
248
|
+
*/
|
|
249
|
+
function createCliCommand(agentName) {
|
|
250
|
+
// Convert kebab-case name to discipline and track
|
|
251
|
+
const parts = agentName.split("-");
|
|
252
|
+
const track = parts.pop();
|
|
253
|
+
const discipline = parts.join("_");
|
|
254
|
+
|
|
255
|
+
const command = `npx pathway agent ${discipline} ${track} --output=.github --all-roles`;
|
|
256
|
+
|
|
257
|
+
const container = div(
|
|
258
|
+
{ className: "cli-command" },
|
|
259
|
+
createCodeBlock(command),
|
|
260
|
+
createCopyButton(command),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return container;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Download all agent files as a ZIP
|
|
268
|
+
* @param {Object} profile - Agent profile
|
|
269
|
+
* @param {Array} skills - Agent skills
|
|
270
|
+
* @param {Array} roleAgents - Role variant agents
|
|
271
|
+
* @param {Object} vscodeSettings - VS Code settings to include
|
|
272
|
+
* @param {string} agentName - Agent name for zip filename
|
|
273
|
+
*/
|
|
274
|
+
async function downloadAllAsZip(
|
|
275
|
+
profile,
|
|
276
|
+
skills,
|
|
277
|
+
roleAgents,
|
|
278
|
+
vscodeSettings,
|
|
279
|
+
agentName,
|
|
280
|
+
) {
|
|
281
|
+
// Dynamically import JSZip
|
|
282
|
+
const JSZip = await importJSZip();
|
|
283
|
+
const zip = new JSZip();
|
|
284
|
+
|
|
285
|
+
// Add main profile to .github/agents/ folder
|
|
286
|
+
const profileContent = formatAgentProfile(profile);
|
|
287
|
+
zip.file(`.github/agents/${profile.filename}`, profileContent);
|
|
288
|
+
|
|
289
|
+
// Add role agent profiles to .github/agents/ folder
|
|
290
|
+
for (const roleAgent of roleAgents) {
|
|
291
|
+
const roleContent = formatAgentProfile(roleAgent);
|
|
292
|
+
zip.file(`.github/agents/${roleAgent.filename}`, roleContent);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Add skills to .claude/skills/ folder
|
|
296
|
+
for (const skill of skills) {
|
|
297
|
+
const skillContent = formatAgentSkill(skill);
|
|
298
|
+
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, skillContent);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Add VS Code settings for multi-agent features
|
|
302
|
+
if (Object.keys(vscodeSettings).length > 0) {
|
|
303
|
+
zip.file(
|
|
304
|
+
".vscode/settings.json",
|
|
305
|
+
JSON.stringify(vscodeSettings, null, 2) + "\n",
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Generate and download
|
|
310
|
+
const blob = await zip.generateAsync({ type: "blob" });
|
|
311
|
+
const url = URL.createObjectURL(blob);
|
|
312
|
+
|
|
313
|
+
const link = document.createElement("a");
|
|
314
|
+
link.href = url;
|
|
315
|
+
link.download = `${agentName}-agent.zip`;
|
|
316
|
+
document.body.appendChild(link);
|
|
317
|
+
link.click();
|
|
318
|
+
document.body.removeChild(link);
|
|
319
|
+
|
|
320
|
+
URL.revokeObjectURL(url);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Dynamically import JSZip from CDN
|
|
325
|
+
* @returns {Promise<typeof JSZip>}
|
|
326
|
+
*/
|
|
327
|
+
async function importJSZip() {
|
|
328
|
+
// Use dynamic import from CDN
|
|
329
|
+
const module = await import("https://cdn.jsdelivr.net/npm/jszip@3.10.1/+esm");
|
|
330
|
+
return module.default;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Format a stage-specific agent preview
|
|
335
|
+
* @param {Object} stageAgent - Derived stage agent data
|
|
336
|
+
* @param {Object} profile - Generated profile with frontmatter and body
|
|
337
|
+
* @param {Object} options - Options
|
|
338
|
+
* @param {Array} [options.stages] - All stages for emoji lookup
|
|
339
|
+
* @param {Object} [options.vscodeSettings] - VS Code settings for download
|
|
340
|
+
* @returns {HTMLElement}
|
|
341
|
+
*/
|
|
342
|
+
export function stageAgentToDOM(stageAgent, profile, options = {}) {
|
|
343
|
+
const { vscodeSettings = {}, stages = [] } = options;
|
|
344
|
+
const { stage, tools, handoffs, constraints, checklist, derivedSkills } =
|
|
345
|
+
stageAgent;
|
|
346
|
+
const stageEmoji = getStageEmoji(stages, stage.id);
|
|
347
|
+
const profileContent = formatAgentProfile(profile);
|
|
348
|
+
|
|
349
|
+
return div(
|
|
350
|
+
{ className: "agent-deployment stage-agent-preview" },
|
|
351
|
+
|
|
352
|
+
// Stage info header
|
|
353
|
+
section(
|
|
354
|
+
{ className: "agent-section stage-info" },
|
|
355
|
+
div(
|
|
356
|
+
{ className: "stage-header" },
|
|
357
|
+
span({ className: "stage-emoji" }, stageEmoji),
|
|
358
|
+
h2({}, `${stage.name} Agent`),
|
|
359
|
+
),
|
|
360
|
+
p({ className: "text-muted" }, stage.description),
|
|
361
|
+
),
|
|
362
|
+
|
|
363
|
+
// Tools section
|
|
364
|
+
tools.length > 0
|
|
365
|
+
? section(
|
|
366
|
+
{ className: "agent-section" },
|
|
367
|
+
h3({}, "Tools"),
|
|
368
|
+
div(
|
|
369
|
+
{ className: "tool-badges" },
|
|
370
|
+
...tools.map((tool) =>
|
|
371
|
+
span({ className: "badge badge-tool" }, tool),
|
|
372
|
+
),
|
|
373
|
+
),
|
|
374
|
+
)
|
|
375
|
+
: null,
|
|
376
|
+
|
|
377
|
+
// Constraints section
|
|
378
|
+
constraints.length > 0
|
|
379
|
+
? section(
|
|
380
|
+
{ className: "agent-section" },
|
|
381
|
+
h3({}, "Constraints"),
|
|
382
|
+
div(
|
|
383
|
+
{ className: "constraint-list" },
|
|
384
|
+
...constraints.map((c) =>
|
|
385
|
+
div({ className: "constraint-item" }, `⚠️ ${c}`),
|
|
386
|
+
),
|
|
387
|
+
),
|
|
388
|
+
)
|
|
389
|
+
: null,
|
|
390
|
+
|
|
391
|
+
// Handoffs section
|
|
392
|
+
handoffs.length > 0
|
|
393
|
+
? section(
|
|
394
|
+
{ className: "agent-section" },
|
|
395
|
+
h3({}, "Handoffs"),
|
|
396
|
+
div(
|
|
397
|
+
{ className: "handoff-buttons" },
|
|
398
|
+
...handoffs.map((h) => {
|
|
399
|
+
const targetEmoji = getStageEmoji(stages, h.target);
|
|
400
|
+
return div(
|
|
401
|
+
{ className: "handoff-button-preview" },
|
|
402
|
+
span({ className: "handoff-icon" }, targetEmoji),
|
|
403
|
+
span({ className: "handoff-label" }, h.label),
|
|
404
|
+
);
|
|
405
|
+
}),
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
: null,
|
|
409
|
+
|
|
410
|
+
// Checklist section
|
|
411
|
+
checklist.length > 0
|
|
412
|
+
? section(
|
|
413
|
+
{ className: "agent-section" },
|
|
414
|
+
h3({}, "Handoff Checklist"),
|
|
415
|
+
p(
|
|
416
|
+
{ className: "text-muted" },
|
|
417
|
+
"Items to verify before transitioning to the next stage.",
|
|
418
|
+
),
|
|
419
|
+
createChecklistPreview(checklist),
|
|
420
|
+
)
|
|
421
|
+
: null,
|
|
422
|
+
|
|
423
|
+
// Skills summary
|
|
424
|
+
derivedSkills.length > 0
|
|
425
|
+
? section(
|
|
426
|
+
{ className: "agent-section" },
|
|
427
|
+
h3({}, `Derived Skills (${derivedSkills.length})`),
|
|
428
|
+
div(
|
|
429
|
+
{ className: "skill-badges" },
|
|
430
|
+
...derivedSkills
|
|
431
|
+
.slice(0, 8)
|
|
432
|
+
.map((skill) =>
|
|
433
|
+
span({ className: "badge badge-default" }, skill.name),
|
|
434
|
+
),
|
|
435
|
+
derivedSkills.length > 8
|
|
436
|
+
? span(
|
|
437
|
+
{ className: "text-muted" },
|
|
438
|
+
` +${derivedSkills.length - 8} more`,
|
|
439
|
+
)
|
|
440
|
+
: null,
|
|
441
|
+
),
|
|
442
|
+
)
|
|
443
|
+
: null,
|
|
444
|
+
|
|
445
|
+
// Profile section
|
|
446
|
+
section(
|
|
447
|
+
{ className: "agent-section" },
|
|
448
|
+
div(
|
|
449
|
+
{ className: "section-header" },
|
|
450
|
+
h3({}, "Agent Profile"),
|
|
451
|
+
createCopyButton(profileContent),
|
|
452
|
+
),
|
|
453
|
+
p({ className: "filename" }, profile.filename),
|
|
454
|
+
createCodeBlock(profileContent),
|
|
455
|
+
),
|
|
456
|
+
|
|
457
|
+
// Download button
|
|
458
|
+
createStageAgentDownloadButton(profile, vscodeSettings),
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Create checklist preview grouped by capability
|
|
464
|
+
* @param {Array} checklist - Checklist items with capability and items
|
|
465
|
+
* @returns {HTMLElement}
|
|
466
|
+
*/
|
|
467
|
+
function createChecklistPreview(checklist) {
|
|
468
|
+
return div(
|
|
469
|
+
{ className: "checklist-preview" },
|
|
470
|
+
...checklist.map((group) =>
|
|
471
|
+
div(
|
|
472
|
+
{ className: "checklist-group" },
|
|
473
|
+
div({ className: "checklist-capability" }, group.capability),
|
|
474
|
+
div(
|
|
475
|
+
{ className: "checklist-items" },
|
|
476
|
+
...group.items.map((item) =>
|
|
477
|
+
div(
|
|
478
|
+
{ className: "checklist-item" },
|
|
479
|
+
span({ className: "checklist-checkbox" }, "☐"),
|
|
480
|
+
span({}, item),
|
|
481
|
+
),
|
|
482
|
+
),
|
|
483
|
+
),
|
|
484
|
+
),
|
|
485
|
+
),
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Create download button for stage agent
|
|
491
|
+
* @param {Object} profile - Agent profile
|
|
492
|
+
* @param {Object} vscodeSettings - VS Code settings
|
|
493
|
+
* @returns {HTMLElement}
|
|
494
|
+
*/
|
|
495
|
+
function createStageAgentDownloadButton(profile, vscodeSettings) {
|
|
496
|
+
const btn = button(
|
|
497
|
+
{ className: "btn btn-primary download-all-btn" },
|
|
498
|
+
"📥 Download Agent Profile",
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
btn.addEventListener("click", async () => {
|
|
502
|
+
btn.disabled = true;
|
|
503
|
+
btn.textContent = "Generating...";
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
const JSZip = await importJSZip();
|
|
507
|
+
const zip = new JSZip();
|
|
508
|
+
|
|
509
|
+
// Add profile
|
|
510
|
+
const profileContent = formatAgentProfile(profile);
|
|
511
|
+
zip.file(`.github/agents/${profile.filename}`, profileContent);
|
|
512
|
+
|
|
513
|
+
// Add VS Code settings
|
|
514
|
+
if (Object.keys(vscodeSettings).length > 0) {
|
|
515
|
+
zip.file(
|
|
516
|
+
".vscode/settings.json",
|
|
517
|
+
JSON.stringify(vscodeSettings, null, 2) + "\n",
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Generate and download
|
|
522
|
+
const blob = await zip.generateAsync({ type: "blob" });
|
|
523
|
+
const url = URL.createObjectURL(blob);
|
|
524
|
+
|
|
525
|
+
const link = document.createElement("a");
|
|
526
|
+
link.href = url;
|
|
527
|
+
link.download = `${profile.frontmatter.name}-agent.zip`;
|
|
528
|
+
document.body.appendChild(link);
|
|
529
|
+
link.click();
|
|
530
|
+
document.body.removeChild(link);
|
|
531
|
+
|
|
532
|
+
URL.revokeObjectURL(url);
|
|
533
|
+
} finally {
|
|
534
|
+
btn.disabled = false;
|
|
535
|
+
btn.textContent = "📥 Download Agent Profile";
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
return btn;
|
|
540
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Profile Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats agent profile data into .agent.md file content
|
|
5
|
+
* following the GitHub Copilot Custom Agents specification.
|
|
6
|
+
*/
|
|
7
|
+
|
|
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
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Format agent profile as .agent.md file content
|
|
60
|
+
* @param {Object} profile - Profile with frontmatter and body
|
|
61
|
+
* @param {Object} profile.frontmatter - YAML frontmatter data
|
|
62
|
+
* @param {string} profile.frontmatter.name - Agent name
|
|
63
|
+
* @param {string} profile.frontmatter.description - Agent description
|
|
64
|
+
* @param {string[]} profile.frontmatter.tools - Available tools
|
|
65
|
+
* @param {boolean} profile.frontmatter.infer - Whether to auto-select
|
|
66
|
+
* @param {Array} [profile.frontmatter.handoffs] - Handoff definitions
|
|
67
|
+
* @param {string} profile.body - Markdown body content
|
|
68
|
+
* @returns {string} Complete .agent.md file content
|
|
69
|
+
*/
|
|
70
|
+
export function formatAgentProfile({ frontmatter, body }) {
|
|
71
|
+
const lines = ["---"];
|
|
72
|
+
|
|
73
|
+
// Name (optional but recommended)
|
|
74
|
+
if (frontmatter.name) {
|
|
75
|
+
lines.push(`name: ${formatYamlValue(frontmatter.name)}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Description (required)
|
|
79
|
+
lines.push(`description: ${formatYamlValue(frontmatter.description)}`);
|
|
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");
|
|
133
|
+
}
|