@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.
Files changed (227) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/app/commands/agent.js +430 -0
  4. package/app/commands/behaviour.js +61 -0
  5. package/app/commands/command-factory.js +211 -0
  6. package/app/commands/discipline.js +58 -0
  7. package/app/commands/driver.js +94 -0
  8. package/app/commands/grade.js +60 -0
  9. package/app/commands/index.js +20 -0
  10. package/app/commands/init.js +67 -0
  11. package/app/commands/interview.js +68 -0
  12. package/app/commands/job.js +157 -0
  13. package/app/commands/progress.js +77 -0
  14. package/app/commands/questions.js +179 -0
  15. package/app/commands/serve.js +143 -0
  16. package/app/commands/site.js +121 -0
  17. package/app/commands/skill.js +76 -0
  18. package/app/commands/stage.js +129 -0
  19. package/app/commands/track.js +70 -0
  20. package/app/components/action-buttons.js +66 -0
  21. package/app/components/behaviour-profile.js +53 -0
  22. package/app/components/builder.js +341 -0
  23. package/app/components/card.js +98 -0
  24. package/app/components/checklist.js +145 -0
  25. package/app/components/comparison-radar.js +237 -0
  26. package/app/components/detail.js +230 -0
  27. package/app/components/error-page.js +72 -0
  28. package/app/components/grid.js +109 -0
  29. package/app/components/list.js +120 -0
  30. package/app/components/modifier-table.js +142 -0
  31. package/app/components/nav.js +64 -0
  32. package/app/components/progression-table.js +320 -0
  33. package/app/components/radar-chart.js +102 -0
  34. package/app/components/skill-matrix.js +97 -0
  35. package/app/css/base.css +56 -0
  36. package/app/css/bundles/app.css +40 -0
  37. package/app/css/bundles/handout.css +43 -0
  38. package/app/css/bundles/slides.css +40 -0
  39. package/app/css/components/badges.css +215 -0
  40. package/app/css/components/buttons.css +101 -0
  41. package/app/css/components/forms.css +105 -0
  42. package/app/css/components/layout.css +209 -0
  43. package/app/css/components/nav.css +166 -0
  44. package/app/css/components/progress.css +166 -0
  45. package/app/css/components/states.css +82 -0
  46. package/app/css/components/surfaces.css +243 -0
  47. package/app/css/components/tables.css +362 -0
  48. package/app/css/components/typography.css +122 -0
  49. package/app/css/components/utilities.css +41 -0
  50. package/app/css/pages/agent-builder.css +391 -0
  51. package/app/css/pages/assessment-results.css +453 -0
  52. package/app/css/pages/detail.css +59 -0
  53. package/app/css/pages/interview-builder.css +148 -0
  54. package/app/css/pages/job-builder.css +134 -0
  55. package/app/css/pages/landing.css +92 -0
  56. package/app/css/pages/lifecycle.css +118 -0
  57. package/app/css/pages/progress-builder.css +274 -0
  58. package/app/css/pages/self-assessment.css +502 -0
  59. package/app/css/reset.css +50 -0
  60. package/app/css/tokens.css +153 -0
  61. package/app/css/views/handout.css +30 -0
  62. package/app/css/views/print.css +608 -0
  63. package/app/css/views/slide-animations.css +113 -0
  64. package/app/css/views/slide-base.css +330 -0
  65. package/app/css/views/slide-sections.css +597 -0
  66. package/app/css/views/slide-tables.css +275 -0
  67. package/app/formatters/agent/dom.js +540 -0
  68. package/app/formatters/agent/profile.js +133 -0
  69. package/app/formatters/agent/skill.js +58 -0
  70. package/app/formatters/behaviour/dom.js +91 -0
  71. package/app/formatters/behaviour/markdown.js +54 -0
  72. package/app/formatters/behaviour/shared.js +64 -0
  73. package/app/formatters/discipline/dom.js +187 -0
  74. package/app/formatters/discipline/markdown.js +87 -0
  75. package/app/formatters/discipline/shared.js +131 -0
  76. package/app/formatters/driver/dom.js +103 -0
  77. package/app/formatters/driver/shared.js +92 -0
  78. package/app/formatters/grade/dom.js +208 -0
  79. package/app/formatters/grade/markdown.js +94 -0
  80. package/app/formatters/grade/shared.js +86 -0
  81. package/app/formatters/index.js +50 -0
  82. package/app/formatters/interview/dom.js +97 -0
  83. package/app/formatters/interview/markdown.js +66 -0
  84. package/app/formatters/interview/shared.js +332 -0
  85. package/app/formatters/job/description.js +176 -0
  86. package/app/formatters/job/dom.js +411 -0
  87. package/app/formatters/job/markdown.js +102 -0
  88. package/app/formatters/progress/dom.js +135 -0
  89. package/app/formatters/progress/markdown.js +86 -0
  90. package/app/formatters/progress/shared.js +339 -0
  91. package/app/formatters/questions/json.js +43 -0
  92. package/app/formatters/questions/markdown.js +303 -0
  93. package/app/formatters/questions/shared.js +274 -0
  94. package/app/formatters/questions/yaml.js +76 -0
  95. package/app/formatters/shared.js +71 -0
  96. package/app/formatters/skill/dom.js +168 -0
  97. package/app/formatters/skill/markdown.js +109 -0
  98. package/app/formatters/skill/shared.js +125 -0
  99. package/app/formatters/stage/dom.js +135 -0
  100. package/app/formatters/stage/index.js +12 -0
  101. package/app/formatters/stage/shared.js +111 -0
  102. package/app/formatters/track/dom.js +128 -0
  103. package/app/formatters/track/markdown.js +105 -0
  104. package/app/formatters/track/shared.js +181 -0
  105. package/app/handout-main.js +421 -0
  106. package/app/handout.html +21 -0
  107. package/app/index.html +59 -0
  108. package/app/lib/card-mappers.js +173 -0
  109. package/app/lib/cli-output.js +270 -0
  110. package/app/lib/error-boundary.js +70 -0
  111. package/app/lib/errors.js +49 -0
  112. package/app/lib/form-controls.js +47 -0
  113. package/app/lib/job-cache.js +86 -0
  114. package/app/lib/markdown.js +114 -0
  115. package/app/lib/radar.js +866 -0
  116. package/app/lib/reactive.js +77 -0
  117. package/app/lib/render.js +212 -0
  118. package/app/lib/router-core.js +160 -0
  119. package/app/lib/router-pages.js +16 -0
  120. package/app/lib/router-slides.js +202 -0
  121. package/app/lib/state.js +148 -0
  122. package/app/lib/utils.js +14 -0
  123. package/app/lib/yaml-loader.js +327 -0
  124. package/app/main.js +213 -0
  125. package/app/model/agent.js +702 -0
  126. package/app/model/checklist.js +137 -0
  127. package/app/model/derivation.js +699 -0
  128. package/app/model/index-generator.js +71 -0
  129. package/app/model/interview.js +539 -0
  130. package/app/model/job.js +222 -0
  131. package/app/model/levels.js +591 -0
  132. package/app/model/loader.js +564 -0
  133. package/app/model/matching.js +858 -0
  134. package/app/model/modifiers.js +158 -0
  135. package/app/model/profile.js +266 -0
  136. package/app/model/progression.js +507 -0
  137. package/app/model/validation.js +1385 -0
  138. package/app/pages/agent-builder.js +823 -0
  139. package/app/pages/assessment-results.js +507 -0
  140. package/app/pages/behaviour.js +70 -0
  141. package/app/pages/discipline.js +71 -0
  142. package/app/pages/driver.js +106 -0
  143. package/app/pages/grade.js +117 -0
  144. package/app/pages/interview-builder.js +50 -0
  145. package/app/pages/interview.js +304 -0
  146. package/app/pages/job-builder.js +50 -0
  147. package/app/pages/job.js +58 -0
  148. package/app/pages/landing.js +305 -0
  149. package/app/pages/progress-builder.js +58 -0
  150. package/app/pages/progress.js +495 -0
  151. package/app/pages/self-assessment.js +729 -0
  152. package/app/pages/skill.js +113 -0
  153. package/app/pages/stage.js +231 -0
  154. package/app/pages/track.js +69 -0
  155. package/app/slide-main.js +360 -0
  156. package/app/slides/behaviour.js +38 -0
  157. package/app/slides/chapter.js +82 -0
  158. package/app/slides/discipline.js +40 -0
  159. package/app/slides/driver.js +39 -0
  160. package/app/slides/grade.js +32 -0
  161. package/app/slides/index.js +198 -0
  162. package/app/slides/interview.js +58 -0
  163. package/app/slides/job.js +55 -0
  164. package/app/slides/overview.js +126 -0
  165. package/app/slides/progress.js +83 -0
  166. package/app/slides/skill.js +40 -0
  167. package/app/slides/track.js +39 -0
  168. package/app/slides.html +56 -0
  169. package/app/types.js +147 -0
  170. package/bin/pathway.js +489 -0
  171. package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
  172. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
  173. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
  174. package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
  175. package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
  176. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
  177. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
  178. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
  179. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
  180. package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
  181. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
  182. package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
  183. package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
  184. package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
  185. package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
  186. package/examples/agents/.vscode/settings.json +8 -0
  187. package/examples/behaviours/_index.yaml +8 -0
  188. package/examples/behaviours/outcome_ownership.yaml +44 -0
  189. package/examples/behaviours/polymathic_knowledge.yaml +42 -0
  190. package/examples/behaviours/precise_communication.yaml +40 -0
  191. package/examples/behaviours/relentless_curiosity.yaml +38 -0
  192. package/examples/behaviours/systems_thinking.yaml +41 -0
  193. package/examples/capabilities/_index.yaml +8 -0
  194. package/examples/capabilities/business.yaml +251 -0
  195. package/examples/capabilities/delivery.yaml +352 -0
  196. package/examples/capabilities/people.yaml +100 -0
  197. package/examples/capabilities/reliability.yaml +318 -0
  198. package/examples/capabilities/scale.yaml +394 -0
  199. package/examples/disciplines/_index.yaml +5 -0
  200. package/examples/disciplines/data_engineering.yaml +76 -0
  201. package/examples/disciplines/software_engineering.yaml +76 -0
  202. package/examples/drivers.yaml +205 -0
  203. package/examples/framework.yaml +58 -0
  204. package/examples/grades.yaml +118 -0
  205. package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
  206. package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
  207. package/examples/questions/behaviours/precise_communication.yaml +55 -0
  208. package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
  209. package/examples/questions/behaviours/systems_thinking.yaml +53 -0
  210. package/examples/questions/skills/architecture_design.yaml +54 -0
  211. package/examples/questions/skills/cloud_platforms.yaml +48 -0
  212. package/examples/questions/skills/code_quality.yaml +49 -0
  213. package/examples/questions/skills/data_modeling.yaml +46 -0
  214. package/examples/questions/skills/devops.yaml +47 -0
  215. package/examples/questions/skills/full_stack_development.yaml +48 -0
  216. package/examples/questions/skills/sre_practices.yaml +44 -0
  217. package/examples/questions/skills/stakeholder_management.yaml +49 -0
  218. package/examples/questions/skills/team_collaboration.yaml +43 -0
  219. package/examples/questions/skills/technical_writing.yaml +43 -0
  220. package/examples/self-assessments.yaml +66 -0
  221. package/examples/stages.yaml +76 -0
  222. package/examples/tracks/_index.yaml +6 -0
  223. package/examples/tracks/manager.yaml +53 -0
  224. package/examples/tracks/platform.yaml +54 -0
  225. package/examples/tracks/sre.yaml +58 -0
  226. package/examples/vscode-settings.yaml +22 -0
  227. package/package.json +68 -0
@@ -0,0 +1,823 @@
1
+ /**
2
+ * Agent builder page
3
+ *
4
+ * Single scrollable view for generating AI coding agent configurations.
5
+ * Uses dropdown pattern matching job builder: discipline × track × stage.
6
+ * Stage includes "All Stages" option for complete deployment downloads.
7
+ */
8
+
9
+ import {
10
+ render,
11
+ div,
12
+ h1,
13
+ h2,
14
+ h3,
15
+ p,
16
+ span,
17
+ label,
18
+ section,
19
+ } from "../lib/render.js";
20
+ import { getState } from "../lib/state.js";
21
+ import { loadAgentDataBrowser } from "../lib/yaml-loader.js";
22
+ import {
23
+ generateStageAgentProfile,
24
+ deriveStageAgent,
25
+ generateSkillMd,
26
+ deriveAgentSkills,
27
+ deriveReferenceGrade,
28
+ } from "../model/agent.js";
29
+ import { createSelectWithValue } from "../lib/form-controls.js";
30
+ import { createReactive } from "../lib/reactive.js";
31
+ import { getStageEmoji } from "../formatters/stage/shared.js";
32
+ import { formatAgentProfile } from "../formatters/agent/profile.js";
33
+ import { formatAgentSkill } from "../formatters/agent/skill.js";
34
+
35
+ /** All stages option value */
36
+ const ALL_STAGES_VALUE = "all";
37
+
38
+ /** @type {Object|null} Cached agent data */
39
+ let agentDataCache = null;
40
+
41
+ /**
42
+ * Load agent data with caching
43
+ * @param {string} dataDir - Data directory path
44
+ * @returns {Promise<Object>}
45
+ */
46
+ async function getAgentData(dataDir = "./data") {
47
+ if (!agentDataCache) {
48
+ agentDataCache = await loadAgentDataBrowser(dataDir);
49
+ }
50
+ return agentDataCache;
51
+ }
52
+
53
+ /**
54
+ * Render agent builder page
55
+ */
56
+ export async function renderAgentBuilder() {
57
+ const { data } = getState();
58
+
59
+ // Show loading state
60
+ render(
61
+ div(
62
+ { className: "agent-builder-page wide" },
63
+ div({ className: "loading" }, p({}, "Loading agent definitions...")),
64
+ ),
65
+ );
66
+
67
+ // Load agent-specific data
68
+ const agentData = await getAgentData();
69
+
70
+ // Filter to only disciplines/tracks that have agent definitions
71
+ const agentDisciplineIds = new Set(agentData.disciplines.map((d) => d.id));
72
+ const agentTrackIds = new Set(agentData.tracks.map((t) => t.id));
73
+
74
+ const availableDisciplines = data.disciplines.filter((d) =>
75
+ agentDisciplineIds.has(d.id),
76
+ );
77
+ const availableTracks = data.tracks.filter((t) => agentTrackIds.has(t.id));
78
+ const stages = data.stages || [];
79
+
80
+ // Build stage options with "All Stages" first
81
+ const stageOptions = [
82
+ { id: ALL_STAGES_VALUE, name: "All Stages" },
83
+ ...stages.map((s) => ({
84
+ id: s.id,
85
+ name: `${getStageEmoji(stages, s.id)} ${s.name}`,
86
+ })),
87
+ ];
88
+
89
+ // Parse URL params for pre-selection
90
+ const hash = window.location.hash;
91
+ const pathMatch = hash.match(/#\/agent\/([^/]+)\/([^/]+)(?:\/([^/?]+))?/);
92
+ const initialDiscipline = pathMatch ? pathMatch[1] : "";
93
+ const initialTrack = pathMatch ? pathMatch[2] : "";
94
+ const initialStage =
95
+ pathMatch && pathMatch[3] ? pathMatch[3] : ALL_STAGES_VALUE;
96
+
97
+ // Create reactive selection state
98
+ const selection = createReactive({
99
+ discipline: initialDiscipline,
100
+ track: initialTrack,
101
+ stage: initialStage,
102
+ });
103
+
104
+ // Preview container - will be updated reactively
105
+ const previewContainer = div(
106
+ { className: "agent-preview" },
107
+ createEmptyState(availableDisciplines.length, availableTracks.length),
108
+ );
109
+
110
+ /**
111
+ * Update the preview when selection changes
112
+ * @param {Object} sel - Current selection
113
+ */
114
+ function updatePreview({ discipline, track, stage }) {
115
+ // Update URL without triggering navigation
116
+ if (discipline && track) {
117
+ const stagePart = stage && stage !== ALL_STAGES_VALUE ? `/${stage}` : "";
118
+ const newHash = `#/agent/${discipline}/${track}${stagePart}`;
119
+ if (window.location.hash !== newHash) {
120
+ history.replaceState(null, "", newHash);
121
+ }
122
+ }
123
+
124
+ previewContainer.innerHTML = "";
125
+
126
+ if (!discipline || !track) {
127
+ previewContainer.appendChild(
128
+ createEmptyState(availableDisciplines.length, availableTracks.length),
129
+ );
130
+ return;
131
+ }
132
+
133
+ // Get full objects
134
+ const humanDiscipline = data.disciplines.find((d) => d.id === discipline);
135
+ const humanTrack = data.tracks.find((t) => t.id === track);
136
+ const agentDiscipline = agentData.disciplines.find(
137
+ (d) => d.id === discipline,
138
+ );
139
+ const agentTrack = agentData.tracks.find((t) => t.id === track);
140
+
141
+ if (!humanDiscipline || !humanTrack || !agentDiscipline || !agentTrack) {
142
+ previewContainer.appendChild(
143
+ div(
144
+ { className: "empty-state" },
145
+ p({ className: "text-muted" }, "Invalid combination selected."),
146
+ ),
147
+ );
148
+ return;
149
+ }
150
+
151
+ // Get reference grade for derivation
152
+ const grade = deriveReferenceGrade(data.grades);
153
+
154
+ // Build context for generation
155
+ const context = {
156
+ humanDiscipline,
157
+ humanTrack,
158
+ agentDiscipline,
159
+ agentTrack,
160
+ grade,
161
+ stages,
162
+ skills: data.skills,
163
+ behaviours: data.behaviours,
164
+ agentBehaviours: agentData.behaviours,
165
+ capabilities: data.capabilities,
166
+ vscodeSettings: agentData.vscodeSettings,
167
+ };
168
+
169
+ // Generate preview based on stage selection
170
+ if (stage === ALL_STAGES_VALUE) {
171
+ previewContainer.appendChild(createAllStagesPreview(context));
172
+ } else {
173
+ const stageObj = stages.find((s) => s.id === stage);
174
+ if (!stageObj) {
175
+ previewContainer.appendChild(
176
+ div(
177
+ { className: "empty-state" },
178
+ p({ className: "text-muted" }, `Stage "${stage}" not found.`),
179
+ ),
180
+ );
181
+ return;
182
+ }
183
+ previewContainer.appendChild(createSingleStagePreview(context, stageObj));
184
+ }
185
+ }
186
+
187
+ // Subscribe to selection changes
188
+ selection.subscribe(updatePreview);
189
+
190
+ // Build the page
191
+ const page = div(
192
+ { className: "agent-builder-page wide" },
193
+ // Header
194
+ div(
195
+ { className: "page-header" },
196
+ h1({ className: "page-title" }, "🤖 Agent Builder"),
197
+ p(
198
+ { className: "page-description" },
199
+ "Generate AI coding agents from discipline × track × stage combinations. " +
200
+ "Export complete agent profiles and skill files for GitHub Copilot.",
201
+ ),
202
+ ),
203
+
204
+ // Form section
205
+ div(
206
+ { className: "agent-builder-form" },
207
+ h2({}, "Select Components"),
208
+ div(
209
+ { className: "auto-grid-sm gap-lg" },
210
+ // Discipline selector
211
+ div(
212
+ { className: "form-group" },
213
+ label({ className: "form-label" }, "Discipline"),
214
+ availableDisciplines.length > 0
215
+ ? createSelectWithValue({
216
+ id: "agent-discipline-select",
217
+ items: availableDisciplines,
218
+ initialValue: selection.get().discipline,
219
+ placeholder: "Select a discipline...",
220
+ onChange: (value) => {
221
+ selection.update((prev) => ({ ...prev, discipline: value }));
222
+ },
223
+ getDisplayName: (d) => d.specialization || d.name,
224
+ })
225
+ : p(
226
+ { className: "text-muted" },
227
+ "No disciplines have agent definitions.",
228
+ ),
229
+ ),
230
+ // Track selector
231
+ div(
232
+ { className: "form-group" },
233
+ label({ className: "form-label" }, "Track"),
234
+ availableTracks.length > 0
235
+ ? createSelectWithValue({
236
+ id: "agent-track-select",
237
+ items: availableTracks,
238
+ initialValue: selection.get().track,
239
+ placeholder: "Select a track...",
240
+ onChange: (value) => {
241
+ selection.update((prev) => ({ ...prev, track: value }));
242
+ },
243
+ getDisplayName: (t) => t.name,
244
+ })
245
+ : p(
246
+ { className: "text-muted" },
247
+ "No tracks have agent definitions.",
248
+ ),
249
+ ),
250
+ // Stage selector (dropdown with All Stages option)
251
+ div(
252
+ { className: "form-group" },
253
+ label({ className: "form-label" }, "Stage"),
254
+ createSelectWithValue({
255
+ id: "agent-stage-select",
256
+ items: stageOptions,
257
+ initialValue: selection.get().stage,
258
+ placeholder: "Select a stage...",
259
+ onChange: (value) => {
260
+ selection.update((prev) => ({ ...prev, stage: value }));
261
+ },
262
+ getDisplayName: (s) => s.name,
263
+ }),
264
+ ),
265
+ ),
266
+ ),
267
+
268
+ // Preview section
269
+ previewContainer,
270
+
271
+ // Help section
272
+ createHelpSection(),
273
+ );
274
+
275
+ render(page);
276
+
277
+ // Trigger initial update if pre-selected
278
+ if (initialDiscipline && initialTrack) {
279
+ updatePreview(selection.get());
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Create empty state message
285
+ * @param {number} disciplineCount - Number of available disciplines
286
+ * @param {number} trackCount - Number of available tracks
287
+ * @returns {HTMLElement}
288
+ */
289
+ function createEmptyState(disciplineCount, trackCount) {
290
+ if (disciplineCount === 0 || trackCount === 0) {
291
+ return div(
292
+ { className: "empty-state" },
293
+ p(
294
+ { className: "text-muted" },
295
+ "No agent definitions found. Add agent.yaml files to disciplines and tracks.",
296
+ ),
297
+ );
298
+ }
299
+
300
+ return div(
301
+ { className: "empty-state" },
302
+ p(
303
+ { className: "text-muted" },
304
+ "Select a discipline, track, and stage to generate agents.",
305
+ ),
306
+ );
307
+ }
308
+
309
+ /**
310
+ * Create preview for all stages
311
+ * Shows cards for each stage agent and all skills
312
+ * @param {Object} context - Generation context
313
+ * @returns {HTMLElement}
314
+ */
315
+ function createAllStagesPreview(context) {
316
+ const {
317
+ humanDiscipline,
318
+ humanTrack,
319
+ agentDiscipline,
320
+ agentTrack,
321
+ grade,
322
+ stages,
323
+ skills,
324
+ behaviours,
325
+ agentBehaviours,
326
+ capabilities,
327
+ vscodeSettings,
328
+ } = context;
329
+
330
+ // Generate all stage agents
331
+ const stageAgents = stages.map((stage) => {
332
+ const derived = deriveStageAgent({
333
+ discipline: humanDiscipline,
334
+ track: humanTrack,
335
+ stage,
336
+ grade,
337
+ skills,
338
+ behaviours,
339
+ agentBehaviours,
340
+ agentDiscipline,
341
+ agentTrack,
342
+ capabilities,
343
+ stages,
344
+ });
345
+
346
+ const profile = generateStageAgentProfile({
347
+ discipline: humanDiscipline,
348
+ track: humanTrack,
349
+ stage,
350
+ grade,
351
+ skills,
352
+ behaviours,
353
+ agentBehaviours,
354
+ agentDiscipline,
355
+ agentTrack,
356
+ capabilities,
357
+ stages,
358
+ });
359
+
360
+ return { stage, derived, profile };
361
+ });
362
+
363
+ // Get derived skills for skill cards
364
+ const derivedSkills = deriveAgentSkills({
365
+ discipline: humanDiscipline,
366
+ track: humanTrack,
367
+ grade,
368
+ skills,
369
+ });
370
+
371
+ // Generate skill files
372
+ const skillFiles = derivedSkills
373
+ .map((derived) => skills.find((s) => s.id === derived.skillId))
374
+ .filter((skill) => skill?.agent)
375
+ .map((skill) => generateSkillMd(skill));
376
+
377
+ return div(
378
+ { className: "agent-deployment" },
379
+
380
+ // Download all button
381
+ createDownloadAllButton(stageAgents, skillFiles, vscodeSettings, context),
382
+
383
+ // Agents section
384
+ section(
385
+ { className: "agent-section" },
386
+ h2({}, `Agents (${stageAgents.length})`),
387
+ p(
388
+ { className: "text-muted" },
389
+ "Stage-specific agents with appropriate tools, constraints, and handoffs.",
390
+ ),
391
+ div(
392
+ { className: "agent-cards-grid" },
393
+ ...stageAgents.map(({ stage, profile }) =>
394
+ createAgentCard(stage, profile, stages),
395
+ ),
396
+ ),
397
+ ),
398
+
399
+ // Skills section
400
+ section(
401
+ { className: "agent-section" },
402
+ h2({}, `Skills (${skillFiles.length})`),
403
+ skillFiles.length > 0
404
+ ? div(
405
+ { className: "skill-cards-grid" },
406
+ ...skillFiles.map((skill) => createSkillCard(skill)),
407
+ )
408
+ : p(
409
+ { className: "text-muted" },
410
+ "No skills with agent sections found for this discipline.",
411
+ ),
412
+ ),
413
+
414
+ // CLI hint
415
+ createCliHint(humanDiscipline.id, humanTrack.id),
416
+ );
417
+ }
418
+
419
+ /**
420
+ * Create preview for a single stage
421
+ * @param {Object} context - Generation context
422
+ * @param {Object} stage - Selected stage
423
+ * @returns {HTMLElement}
424
+ */
425
+ function createSingleStagePreview(context, stage) {
426
+ const {
427
+ humanDiscipline,
428
+ humanTrack,
429
+ agentDiscipline,
430
+ agentTrack,
431
+ grade,
432
+ skills,
433
+ behaviours,
434
+ agentBehaviours,
435
+ capabilities,
436
+ vscodeSettings,
437
+ stages,
438
+ } = context;
439
+
440
+ // Derive stage agent
441
+ const derived = deriveStageAgent({
442
+ discipline: humanDiscipline,
443
+ track: humanTrack,
444
+ stage,
445
+ grade,
446
+ skills,
447
+ behaviours,
448
+ agentBehaviours,
449
+ agentDiscipline,
450
+ agentTrack,
451
+ capabilities,
452
+ stages,
453
+ });
454
+
455
+ const profile = generateStageAgentProfile({
456
+ discipline: humanDiscipline,
457
+ track: humanTrack,
458
+ stage,
459
+ grade,
460
+ skills,
461
+ behaviours,
462
+ agentBehaviours,
463
+ agentDiscipline,
464
+ agentTrack,
465
+ capabilities,
466
+ stages,
467
+ });
468
+
469
+ // Get skills for this stage (using full derived skills)
470
+ const derivedSkills = deriveAgentSkills({
471
+ discipline: humanDiscipline,
472
+ track: humanTrack,
473
+ grade,
474
+ skills,
475
+ });
476
+
477
+ const skillFiles = derivedSkills
478
+ .map((d) => skills.find((s) => s.id === d.skillId))
479
+ .filter((skill) => skill?.agent)
480
+ .map((skill) => generateSkillMd(skill));
481
+
482
+ return div(
483
+ { className: "agent-deployment" },
484
+
485
+ // Download button for single stage
486
+ createDownloadSingleButton(profile, skillFiles, vscodeSettings),
487
+
488
+ // Agents section (single card)
489
+ section(
490
+ { className: "agent-section" },
491
+ h2({}, "Agent"),
492
+ div(
493
+ { className: "agent-cards-grid single" },
494
+ createAgentCard(stage, profile, stages, derived),
495
+ ),
496
+ ),
497
+
498
+ // Skills section
499
+ section(
500
+ { className: "agent-section" },
501
+ h2({}, `Skills (${skillFiles.length})`),
502
+ skillFiles.length > 0
503
+ ? div(
504
+ { className: "skill-cards-grid" },
505
+ ...skillFiles.map((skill) => createSkillCard(skill)),
506
+ )
507
+ : p(
508
+ { className: "text-muted" },
509
+ "No skills with agent sections found for this discipline.",
510
+ ),
511
+ ),
512
+
513
+ // CLI hint
514
+ createCliHint(humanDiscipline.id, humanTrack.id, stage.id),
515
+ );
516
+ }
517
+
518
+ /**
519
+ * Create an agent card for a stage
520
+ * @param {Object} stage - Stage object
521
+ * @param {Object} profile - Generated profile
522
+ * @param {Array} stages - All stages for emoji lookup
523
+ * @param {Object} [_derived] - Optional derived agent data for extra info
524
+ * @returns {HTMLElement}
525
+ */
526
+ function createAgentCard(stage, profile, stages, _derived) {
527
+ const content = formatAgentProfile(profile);
528
+ const stageEmoji = getStageEmoji(stages, stage.id);
529
+
530
+ const card = div(
531
+ { className: "agent-card" },
532
+ div(
533
+ { className: "agent-card-header" },
534
+ div(
535
+ { className: "agent-card-title" },
536
+ span({ className: "agent-card-emoji" }, stageEmoji),
537
+ h3({}, `${stage.name} Agent`),
538
+ ),
539
+ createCopyButton(content),
540
+ ),
541
+ p({ className: "agent-card-filename" }, profile.filename),
542
+ div({ className: "agent-card-preview" }, createCodePreview(content)),
543
+ );
544
+
545
+ return card;
546
+ }
547
+
548
+ /**
549
+ * Create a skill card
550
+ * @param {Object} skill - Skill with frontmatter and body
551
+ * @returns {HTMLElement}
552
+ */
553
+ function createSkillCard(skill) {
554
+ const content = formatAgentSkill(skill);
555
+ const filename = `${skill.dirname}/SKILL.md`;
556
+
557
+ return div(
558
+ { className: "skill-card" },
559
+ div(
560
+ { className: "skill-card-header" },
561
+ span({ className: "skill-card-name" }, skill.frontmatter.name),
562
+ createCopyButton(content),
563
+ ),
564
+ p({ className: "skill-card-filename" }, filename),
565
+ div({ className: "skill-card-preview" }, createCodePreview(content)),
566
+ );
567
+ }
568
+
569
+ /**
570
+ * Create a code preview element
571
+ * @param {string} content - Code content
572
+ * @returns {HTMLElement}
573
+ */
574
+ function createCodePreview(content) {
575
+ const pre = document.createElement("pre");
576
+ pre.className = "code-block code-preview";
577
+
578
+ const code = document.createElement("code");
579
+ code.textContent = content;
580
+
581
+ pre.appendChild(code);
582
+ return pre;
583
+ }
584
+
585
+ /**
586
+ * Create a copy button
587
+ * @param {string} content - Content to copy
588
+ * @returns {HTMLElement}
589
+ */
590
+ function createCopyButton(content) {
591
+ const btn = document.createElement("button");
592
+ btn.className = "btn btn-sm copy-btn";
593
+ btn.textContent = "📋 Copy";
594
+
595
+ btn.addEventListener("click", async () => {
596
+ try {
597
+ await navigator.clipboard.writeText(content);
598
+ btn.textContent = "✓ Copied";
599
+ setTimeout(() => {
600
+ btn.textContent = "📋 Copy";
601
+ }, 2000);
602
+ } catch {
603
+ btn.textContent = "Failed";
604
+ setTimeout(() => {
605
+ btn.textContent = "📋 Copy";
606
+ }, 2000);
607
+ }
608
+ });
609
+
610
+ return btn;
611
+ }
612
+
613
+ /**
614
+ * Create download all button for all stages
615
+ * @param {Array} stageAgents - Array of {stage, derived, profile}
616
+ * @param {Array} skillFiles - Array of skill file objects
617
+ * @param {Object} vscodeSettings - VS Code settings
618
+ * @param {Object} context - Context with discipline/track info
619
+ * @returns {HTMLElement}
620
+ */
621
+ function createDownloadAllButton(
622
+ stageAgents,
623
+ skillFiles,
624
+ vscodeSettings,
625
+ context,
626
+ ) {
627
+ const { humanDiscipline, humanTrack } = context;
628
+ const agentName = `${humanDiscipline.id}-${humanTrack.id}`.replace(/_/g, "-");
629
+
630
+ const btn = document.createElement("button");
631
+ btn.className = "btn btn-primary download-all-btn";
632
+ btn.textContent = "📦 Download All (.zip)";
633
+
634
+ btn.addEventListener("click", async () => {
635
+ btn.disabled = true;
636
+ btn.textContent = "Generating...";
637
+
638
+ try {
639
+ const JSZip = await importJSZip();
640
+ const zip = new JSZip();
641
+
642
+ // Add all stage agent profiles
643
+ for (const { profile } of stageAgents) {
644
+ const content = formatAgentProfile(profile);
645
+ zip.file(`.github/agents/${profile.filename}`, content);
646
+ }
647
+
648
+ // Add skills
649
+ for (const skill of skillFiles) {
650
+ const content = formatAgentSkill(skill);
651
+ zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, content);
652
+ }
653
+
654
+ // Add VS Code settings
655
+ if (Object.keys(vscodeSettings).length > 0) {
656
+ zip.file(
657
+ ".vscode/settings.json",
658
+ JSON.stringify(vscodeSettings, null, 2) + "\n",
659
+ );
660
+ }
661
+
662
+ // Generate and download
663
+ const blob = await zip.generateAsync({ type: "blob" });
664
+ const url = URL.createObjectURL(blob);
665
+
666
+ const link = document.createElement("a");
667
+ link.href = url;
668
+ link.download = `${agentName}-agents.zip`;
669
+ document.body.appendChild(link);
670
+ link.click();
671
+ document.body.removeChild(link);
672
+
673
+ URL.revokeObjectURL(url);
674
+ } finally {
675
+ btn.disabled = false;
676
+ btn.textContent = "📦 Download All (.zip)";
677
+ }
678
+ });
679
+
680
+ return btn;
681
+ }
682
+
683
+ /**
684
+ * Create download button for single stage
685
+ * @param {Object} profile - Agent profile
686
+ * @param {Array} skillFiles - Skill files
687
+ * @param {Object} vscodeSettings - VS Code settings
688
+ * @returns {HTMLElement}
689
+ */
690
+ function createDownloadSingleButton(profile, skillFiles, vscodeSettings) {
691
+ const btn = document.createElement("button");
692
+ btn.className = "btn btn-primary download-all-btn";
693
+ btn.textContent = "📥 Download Agent (.zip)";
694
+
695
+ btn.addEventListener("click", async () => {
696
+ btn.disabled = true;
697
+ btn.textContent = "Generating...";
698
+
699
+ try {
700
+ const JSZip = await importJSZip();
701
+ const zip = new JSZip();
702
+
703
+ // Add profile
704
+ const content = formatAgentProfile(profile);
705
+ zip.file(`.github/agents/${profile.filename}`, content);
706
+
707
+ // Add skills
708
+ for (const skill of skillFiles) {
709
+ const skillContent = formatAgentSkill(skill);
710
+ zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, skillContent);
711
+ }
712
+
713
+ // Add VS Code settings
714
+ if (Object.keys(vscodeSettings).length > 0) {
715
+ zip.file(
716
+ ".vscode/settings.json",
717
+ JSON.stringify(vscodeSettings, null, 2) + "\n",
718
+ );
719
+ }
720
+
721
+ // Generate and download
722
+ const blob = await zip.generateAsync({ type: "blob" });
723
+ const url = URL.createObjectURL(blob);
724
+
725
+ const link = document.createElement("a");
726
+ link.href = url;
727
+ link.download = `${profile.frontmatter.name}-agent.zip`;
728
+ document.body.appendChild(link);
729
+ link.click();
730
+ document.body.removeChild(link);
731
+
732
+ URL.revokeObjectURL(url);
733
+ } finally {
734
+ btn.disabled = false;
735
+ btn.textContent = "📥 Download Agent (.zip)";
736
+ }
737
+ });
738
+
739
+ return btn;
740
+ }
741
+
742
+ /**
743
+ * Dynamically import JSZip from CDN
744
+ * @returns {Promise<typeof JSZip>}
745
+ */
746
+ async function importJSZip() {
747
+ const module = await import("https://cdn.jsdelivr.net/npm/jszip@3.10.1/+esm");
748
+ return module.default;
749
+ }
750
+
751
+ /**
752
+ * Create CLI hint section
753
+ * @param {string} disciplineId - Discipline ID
754
+ * @param {string} trackId - Track ID
755
+ * @param {string} [stageId] - Optional stage ID
756
+ * @returns {HTMLElement}
757
+ */
758
+ function createCliHint(disciplineId, trackId, stageId) {
759
+ const stageArg = stageId ? ` --stage=${stageId}` : "";
760
+ const command = `npx pathway agent ${disciplineId} ${trackId}${stageArg} --output=.github`;
761
+
762
+ const container = section(
763
+ { className: "agent-section cli-hint" },
764
+ h2({}, "CLI Alternative"),
765
+ p({}, "Generate this agent from the command line:"),
766
+ div(
767
+ { className: "cli-command" },
768
+ createCodePreview(command),
769
+ createCopyButton(command),
770
+ ),
771
+ );
772
+
773
+ return container;
774
+ }
775
+
776
+ /**
777
+ * Create help section explaining how agent builder works
778
+ * @returns {HTMLElement}
779
+ */
780
+ function createHelpSection() {
781
+ return section(
782
+ { className: "section section-detail" },
783
+ h2({ className: "section-title" }, "How It Works"),
784
+ div(
785
+ { className: "auto-grid-md" },
786
+ div(
787
+ { className: "detail-item" },
788
+ div({ className: "detail-item-label" }, "Stages"),
789
+ p(
790
+ {},
791
+ "Agents are generated for each stage: Plan (research), Code (implement), and Review (verify). " +
792
+ "Each stage has specific tools, constraints, and handoffs.",
793
+ ),
794
+ ),
795
+ div(
796
+ { className: "detail-item" },
797
+ div({ className: "detail-item-label" }, "Agent Profiles"),
798
+ p(
799
+ {},
800
+ "The .agent.md files contain the agent's identity, capabilities, and constraints. " +
801
+ "Place them in .github/agents/ to register with GitHub Copilot.",
802
+ ),
803
+ ),
804
+ div(
805
+ { className: "detail-item" },
806
+ div({ className: "detail-item-label" }, "Skills"),
807
+ p(
808
+ {},
809
+ "SKILL.md files provide specialized knowledge that agents can use. " +
810
+ "Place them in .claude/skills/{skill-name}/ directories.",
811
+ ),
812
+ ),
813
+ div(
814
+ { className: "detail-item" },
815
+ div({ className: "detail-item-label" }, "All Stages"),
816
+ p(
817
+ {},
818
+ "Select 'All Stages' to download a complete agent deployment with all stage agents and skills in one zip file.",
819
+ ),
820
+ ),
821
+ ),
822
+ );
823
+ }