@forwardimpact/pathway 0.3.0 → 0.5.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 +1 -1
- package/app/commands/behaviour.js +1 -1
- package/app/commands/command-factory.js +2 -2
- package/app/commands/discipline.js +1 -1
- package/app/commands/driver.js +1 -1
- package/app/commands/grade.js +1 -1
- package/app/commands/index.js +4 -3
- package/app/commands/serve.js +2 -2
- package/app/commands/site.js +22 -2
- package/app/commands/skill.js +57 -3
- package/app/commands/stage.js +1 -1
- package/app/commands/tool.js +112 -0
- package/app/commands/track.js +1 -1
- package/app/components/card.js +11 -1
- package/app/components/checklist.js +6 -4
- package/app/components/code-display.js +153 -0
- package/app/components/markdown-textarea.js +153 -0
- package/app/css/bundles/app.css +14 -0
- package/app/css/components/badges.css +15 -8
- package/app/css/components/forms.css +55 -0
- package/app/css/components/layout.css +12 -0
- package/app/css/components/surfaces.css +71 -3
- package/app/css/components/typography.css +1 -2
- package/app/css/pages/agent-builder.css +11 -102
- package/app/css/pages/detail.css +60 -0
- package/app/css/pages/job-builder.css +0 -42
- package/app/css/tokens.css +3 -0
- package/app/formatters/agent/dom.js +26 -71
- package/app/formatters/agent/profile.js +67 -10
- package/app/formatters/agent/skill.js +48 -6
- package/app/formatters/grade/dom.js +6 -6
- package/app/formatters/job/description.js +21 -16
- package/app/formatters/job/dom.js +9 -70
- package/app/formatters/json-ld.js +1 -1
- package/app/formatters/shared.js +58 -0
- package/app/formatters/skill/dom.js +70 -3
- package/app/formatters/skill/markdown.js +18 -0
- package/app/formatters/skill/shared.js +14 -4
- package/app/formatters/stage/microdata.js +2 -2
- package/app/formatters/stage/shared.js +3 -3
- package/app/formatters/tool/shared.js +78 -0
- package/app/handout-main.js +19 -18
- package/app/index.html +16 -3
- package/app/lib/card-mappers.js +91 -17
- package/app/lib/render.js +4 -0
- package/app/lib/yaml-loader.js +12 -1
- package/app/main.js +4 -0
- package/app/model/agent.js +47 -23
- package/app/model/checklist.js +2 -2
- package/app/model/derivation.js +5 -5
- package/app/model/levels.js +4 -2
- package/app/model/loader.js +12 -1
- package/app/model/validation.js +77 -11
- package/app/pages/agent-builder.js +121 -77
- package/app/pages/landing.js +35 -15
- package/app/pages/self-assessment.js +7 -5
- package/app/pages/skill.js +5 -17
- package/app/pages/stage.js +12 -8
- package/app/pages/tool.js +50 -0
- package/app/slide-main.js +1 -1
- package/app/slides/chapter.js +8 -8
- package/app/slides/index.js +26 -26
- package/app/slides/overview.js +8 -8
- package/app/slides/skill.js +1 -0
- package/bin/pathway.js +31 -16
- package/examples/capabilities/business.yaml +18 -18
- package/examples/capabilities/delivery.yaml +54 -37
- package/examples/capabilities/people.yaml +1 -1
- package/examples/capabilities/reliability.yaml +130 -115
- package/examples/capabilities/scale.yaml +39 -37
- package/examples/disciplines/engineering_management.yaml +1 -1
- package/examples/framework.yaml +21 -9
- package/examples/grades.yaml +5 -7
- package/examples/self-assessments.yaml +1 -1
- package/examples/stages.yaml +18 -10
- package/package.json +2 -1
- package/templates/agent.template.md +47 -17
- package/templates/job.template.md +8 -8
- package/templates/skill.template.md +33 -11
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
- package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
- package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
- package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
- package/examples/agents/.vscode/settings.json +0 -8
package/app/model/agent.js
CHANGED
|
@@ -225,21 +225,13 @@ function buildWorkingStyleFromBehaviours(
|
|
|
225
225
|
return sections.join("\n");
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
/**
|
|
229
|
-
* Stage ID to display name and next stage mapping
|
|
230
|
-
*/
|
|
231
|
-
const STAGE_INFO = {
|
|
232
|
-
plan: { name: "Plan", nextStage: "Code" },
|
|
233
|
-
code: { name: "Code", nextStage: "Review" },
|
|
234
|
-
review: { name: "Review", nextStage: "Complete" },
|
|
235
|
-
};
|
|
236
|
-
|
|
237
228
|
/**
|
|
238
229
|
* Generate SKILL.md content from skill data
|
|
239
230
|
* @param {Object} skillData - Skill with agent section containing stages
|
|
231
|
+
* @param {Array} stages - All stage entities
|
|
240
232
|
* @returns {Object} Skill with frontmatter, title, stages array, reference, dirname
|
|
241
233
|
*/
|
|
242
|
-
export function generateSkillMd(skillData) {
|
|
234
|
+
export function generateSkillMd(skillData, stages) {
|
|
243
235
|
const { agent, name } = skillData;
|
|
244
236
|
|
|
245
237
|
if (!agent) {
|
|
@@ -250,17 +242,31 @@ export function generateSkillMd(skillData) {
|
|
|
250
242
|
throw new Error(`Skill ${skillData.id} agent section missing stages`);
|
|
251
243
|
}
|
|
252
244
|
|
|
245
|
+
// Build stage lookup map
|
|
246
|
+
const stageMap = new Map(stages.map((s) => [s.id, s]));
|
|
247
|
+
|
|
253
248
|
// Transform stages object to array for template rendering
|
|
254
249
|
const stagesArray = Object.entries(agent.stages).map(
|
|
255
250
|
([stageId, stageData]) => {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
251
|
+
const stageEntity = stageMap.get(stageId);
|
|
252
|
+
const stageName = stageEntity?.name || stageId;
|
|
253
|
+
|
|
254
|
+
// Find next stage from handoffs
|
|
255
|
+
let nextStageName = "Complete";
|
|
256
|
+
if (stageEntity?.handoffs) {
|
|
257
|
+
const nextHandoff = stageEntity.handoffs.find(
|
|
258
|
+
(h) => h.targetStage !== stageId,
|
|
259
|
+
);
|
|
260
|
+
if (nextHandoff) {
|
|
261
|
+
const nextStage = stageMap.get(nextHandoff.targetStage);
|
|
262
|
+
nextStageName = nextStage?.name || nextHandoff.targetStage;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
260
266
|
return {
|
|
261
267
|
stageId,
|
|
262
|
-
stageName
|
|
263
|
-
nextStageName
|
|
268
|
+
stageName,
|
|
269
|
+
nextStageName,
|
|
264
270
|
focus: stageData.focus,
|
|
265
271
|
activities: stageData.activities || [],
|
|
266
272
|
ready: stageData.ready || [],
|
|
@@ -277,11 +283,13 @@ export function generateSkillMd(skillData) {
|
|
|
277
283
|
return {
|
|
278
284
|
frontmatter: {
|
|
279
285
|
name: agent.name,
|
|
280
|
-
description: agent.description
|
|
286
|
+
description: agent.description,
|
|
287
|
+
useWhen: agent.useWhen || "",
|
|
281
288
|
},
|
|
282
289
|
title: name,
|
|
283
290
|
stages: stagesArray,
|
|
284
|
-
reference:
|
|
291
|
+
reference: skillData.implementationReference || "",
|
|
292
|
+
toolReferences: skillData.toolReferences || [],
|
|
285
293
|
dirname: agent.name,
|
|
286
294
|
};
|
|
287
295
|
}
|
|
@@ -312,8 +320,11 @@ function estimateBodyDataLength(bodyData) {
|
|
|
312
320
|
}
|
|
313
321
|
|
|
314
322
|
// Array fields
|
|
315
|
-
if (bodyData.
|
|
316
|
-
|
|
323
|
+
if (bodyData.skillIndex) {
|
|
324
|
+
for (const skill of bodyData.skillIndex) {
|
|
325
|
+
length +=
|
|
326
|
+
skill.name.length + skill.dirname.length + skill.useWhen.length + 50;
|
|
327
|
+
}
|
|
317
328
|
}
|
|
318
329
|
if (bodyData.beforeMakingChanges) {
|
|
319
330
|
for (const item of bodyData.beforeMakingChanges) {
|
|
@@ -483,6 +494,7 @@ function getChecklistStage(stageId) {
|
|
|
483
494
|
* @param {Array} params.derivedSkills - Skills sorted by level
|
|
484
495
|
* @param {Array} params.derivedBehaviours - Behaviours sorted by maturity
|
|
485
496
|
* @param {Array} params.agentBehaviours - Agent behaviour definitions
|
|
497
|
+
* @param {Array} params.skills - All skill definitions (for agent section lookup)
|
|
486
498
|
* @param {string} params.checklistMarkdown - Pre-formatted checklist markdown
|
|
487
499
|
* @returns {Object} Structured profile body data
|
|
488
500
|
*/
|
|
@@ -495,6 +507,7 @@ function buildStageProfileBodyData({
|
|
|
495
507
|
derivedSkills,
|
|
496
508
|
derivedBehaviours,
|
|
497
509
|
agentBehaviours,
|
|
510
|
+
skills,
|
|
498
511
|
checklistMarkdown,
|
|
499
512
|
}) {
|
|
500
513
|
const name = `${humanDiscipline.specialization || humanDiscipline.name} - ${humanTrack.name}`;
|
|
@@ -524,8 +537,18 @@ function buildStageProfileBodyData({
|
|
|
524
537
|
? substituteTemplateVars(rawDelegation, humanDiscipline)
|
|
525
538
|
: null;
|
|
526
539
|
|
|
527
|
-
//
|
|
528
|
-
const
|
|
540
|
+
// Build skill index from derived skills with agent sections
|
|
541
|
+
const skillIndex = derivedSkills
|
|
542
|
+
.map((derived) => {
|
|
543
|
+
const skill = skills.find((s) => s.id === derived.skillId);
|
|
544
|
+
if (!skill?.agent) return null;
|
|
545
|
+
return {
|
|
546
|
+
name: derived.skillName,
|
|
547
|
+
dirname: skill.agent.name,
|
|
548
|
+
useWhen: skill.agent.useWhen?.trim() || "",
|
|
549
|
+
};
|
|
550
|
+
})
|
|
551
|
+
.filter(Boolean);
|
|
529
552
|
|
|
530
553
|
// Operational Context - use track's roleContext (shared with human job descriptions)
|
|
531
554
|
const operationalContext = humanTrack.roleContext.trim();
|
|
@@ -549,7 +572,7 @@ function buildStageProfileBodyData({
|
|
|
549
572
|
stageDescription: stage.description,
|
|
550
573
|
identity: identity.trim(),
|
|
551
574
|
priority: priority ? priority.trim() : null,
|
|
552
|
-
|
|
575
|
+
skillIndex,
|
|
553
576
|
beforeMakingChanges,
|
|
554
577
|
delegation: delegation ? delegation.trim() : null,
|
|
555
578
|
operationalContext,
|
|
@@ -711,6 +734,7 @@ export function generateStageAgentProfile({
|
|
|
711
734
|
derivedSkills: agent.derivedSkills,
|
|
712
735
|
derivedBehaviours: agent.derivedBehaviours,
|
|
713
736
|
agentBehaviours,
|
|
737
|
+
skills,
|
|
714
738
|
checklistMarkdown,
|
|
715
739
|
});
|
|
716
740
|
|
package/app/model/checklist.js
CHANGED
|
@@ -72,7 +72,7 @@ export function deriveChecklist({
|
|
|
72
72
|
capability: {
|
|
73
73
|
id: capability.id,
|
|
74
74
|
name: capability.name,
|
|
75
|
-
|
|
75
|
+
emojiIcon: capability.emojiIcon,
|
|
76
76
|
},
|
|
77
77
|
items: stageData.ready,
|
|
78
78
|
});
|
|
@@ -94,7 +94,7 @@ export function formatChecklistMarkdown(checklist) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
const sections = checklist.map(({ skill, capability, items }) => {
|
|
97
|
-
const header = `**${capability.
|
|
97
|
+
const header = `**${capability.emojiIcon} ${skill.name}**`;
|
|
98
98
|
const itemList = items.map((item) => `- [ ] ${item}`).join("\n");
|
|
99
99
|
return `${header}\n\n${itemList}`;
|
|
100
100
|
});
|
package/app/model/derivation.js
CHANGED
|
@@ -376,17 +376,17 @@ export function isValidJobCombination({
|
|
|
376
376
|
* @returns {string} Generated job title
|
|
377
377
|
*/
|
|
378
378
|
export function generateJobTitle(discipline, grade, track = null) {
|
|
379
|
-
const { roleTitle,
|
|
379
|
+
const { roleTitle, isManagement } = discipline;
|
|
380
380
|
const { professionalTitle, managementTitle } = grade;
|
|
381
381
|
|
|
382
382
|
// Management discipline (no track needed)
|
|
383
383
|
if (isManagement && !track) {
|
|
384
|
-
return `${managementTitle}, ${
|
|
384
|
+
return `${managementTitle}, ${roleTitle}`;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
// Management discipline with track
|
|
388
388
|
if (isManagement && track) {
|
|
389
|
-
return `${managementTitle}, ${track.name}`;
|
|
389
|
+
return `${managementTitle}, ${roleTitle} – ${track.name}`;
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
// IC discipline with track
|
|
@@ -437,7 +437,7 @@ function generateJobId(discipline, grade, track = null) {
|
|
|
437
437
|
* @param {import('./levels.js').SkillMatrixEntry[]} params.skillMatrix - Derived skill matrix for the job
|
|
438
438
|
* @param {Object[]} params.capabilities - Capability definitions with responsibilities
|
|
439
439
|
* @param {import('./levels.js').Discipline} params.discipline - The discipline (determines which responsibilities to use)
|
|
440
|
-
* @returns {Array<{capability: string, capabilityName: string,
|
|
440
|
+
* @returns {Array<{capability: string, capabilityName: string, emojiIcon: string, responsibility: string, level: string}>}
|
|
441
441
|
*/
|
|
442
442
|
export function deriveResponsibilities({
|
|
443
443
|
skillMatrix,
|
|
@@ -484,7 +484,7 @@ export function deriveResponsibilities({
|
|
|
484
484
|
responsibilities.push({
|
|
485
485
|
capability: capabilityId,
|
|
486
486
|
capabilityName: capability.name,
|
|
487
|
-
|
|
487
|
+
emojiIcon: capability.emojiIcon || "💡",
|
|
488
488
|
displayOrder: capability.displayOrder ?? 999,
|
|
489
489
|
responsibility: responsibilityText,
|
|
490
490
|
level,
|
package/app/model/levels.js
CHANGED
|
@@ -90,6 +90,7 @@ export const Capability = {
|
|
|
90
90
|
RELIABILITY: "reliability",
|
|
91
91
|
DATA: "data",
|
|
92
92
|
AI: "ai",
|
|
93
|
+
ML: "ml",
|
|
93
94
|
PROCESS: "process",
|
|
94
95
|
BUSINESS: "business",
|
|
95
96
|
PEOPLE: "people",
|
|
@@ -111,6 +112,7 @@ export const CAPABILITY_ORDER = [
|
|
|
111
112
|
Capability.DELIVERY,
|
|
112
113
|
Capability.DATA,
|
|
113
114
|
Capability.AI,
|
|
115
|
+
Capability.ML,
|
|
114
116
|
Capability.SCALE,
|
|
115
117
|
Capability.RELIABILITY,
|
|
116
118
|
Capability.PEOPLE,
|
|
@@ -218,7 +220,7 @@ export function getCapabilityOrder(capabilities) {
|
|
|
218
220
|
*/
|
|
219
221
|
export function getCapabilityEmoji(capabilities, capabilityId) {
|
|
220
222
|
const capability = getCapabilityById(capabilities, capabilityId);
|
|
221
|
-
return capability?.
|
|
223
|
+
return capability?.emojiIcon || "💡";
|
|
222
224
|
}
|
|
223
225
|
|
|
224
226
|
/**
|
|
@@ -595,5 +597,5 @@ export function behaviourMaturityMeetsRequirement(actual, required) {
|
|
|
595
597
|
* @returns {string} The emoji for the concept or default "💡"
|
|
596
598
|
*/
|
|
597
599
|
export function getConceptEmoji(framework, concept) {
|
|
598
|
-
return framework?.entityDefinitions?.[concept]?.
|
|
600
|
+
return framework?.entityDefinitions?.[concept]?.emojiIcon || "💡";
|
|
599
601
|
}
|
package/app/model/loader.js
CHANGED
|
@@ -84,7 +84,15 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
|
84
84
|
|
|
85
85
|
if (capability.skills && Array.isArray(capability.skills)) {
|
|
86
86
|
for (const skill of capability.skills) {
|
|
87
|
-
const {
|
|
87
|
+
const {
|
|
88
|
+
id,
|
|
89
|
+
name,
|
|
90
|
+
isHumanOnly,
|
|
91
|
+
human,
|
|
92
|
+
agent,
|
|
93
|
+
implementationReference,
|
|
94
|
+
toolReferences,
|
|
95
|
+
} = skill;
|
|
88
96
|
allSkills.push({
|
|
89
97
|
id,
|
|
90
98
|
name,
|
|
@@ -95,6 +103,9 @@ async function loadSkillsFromCapabilities(capabilitiesDir) {
|
|
|
95
103
|
...(isHumanOnly && { isHumanOnly }),
|
|
96
104
|
// Preserve agent section for agent generation
|
|
97
105
|
...(agent && { agent }),
|
|
106
|
+
// Include implementation reference and tool references (shared by human and agent)
|
|
107
|
+
...(implementationReference && { implementationReference }),
|
|
108
|
+
...(toolReferences && { toolReferences }),
|
|
98
109
|
});
|
|
99
110
|
}
|
|
100
111
|
}
|
package/app/model/validation.js
CHANGED
|
@@ -241,17 +241,13 @@ function validateSkill(skill, index, requiredStageIds = []) {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
-
//
|
|
245
|
-
if (
|
|
246
|
-
skill.agent.reference !== undefined &&
|
|
247
|
-
typeof skill.agent.reference !== "string"
|
|
248
|
-
) {
|
|
244
|
+
// Error if old 'reference' field is still present (moved to skill.implementationReference)
|
|
245
|
+
if (skill.agent.reference !== undefined) {
|
|
249
246
|
errors.push(
|
|
250
247
|
createError(
|
|
251
|
-
"
|
|
252
|
-
"Skill agent reference
|
|
248
|
+
"INVALID_FIELD",
|
|
249
|
+
"Skill agent 'reference' field is not supported. Use skill.implementationReference instead.",
|
|
253
250
|
`${agentPath}.reference`,
|
|
254
|
-
skill.agent.reference,
|
|
255
251
|
),
|
|
256
252
|
);
|
|
257
253
|
}
|
|
@@ -295,6 +291,76 @@ function validateSkill(skill, index, requiredStageIds = []) {
|
|
|
295
291
|
}
|
|
296
292
|
}
|
|
297
293
|
|
|
294
|
+
// Validate implementationReference if present (optional string)
|
|
295
|
+
if (
|
|
296
|
+
skill.implementationReference !== undefined &&
|
|
297
|
+
typeof skill.implementationReference !== "string"
|
|
298
|
+
) {
|
|
299
|
+
errors.push(
|
|
300
|
+
createError(
|
|
301
|
+
"INVALID_VALUE",
|
|
302
|
+
"Skill implementationReference must be a string",
|
|
303
|
+
`${path}.implementationReference`,
|
|
304
|
+
skill.implementationReference,
|
|
305
|
+
),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Validate toolReferences array if present
|
|
310
|
+
if (skill.toolReferences !== undefined) {
|
|
311
|
+
if (!Array.isArray(skill.toolReferences)) {
|
|
312
|
+
errors.push(
|
|
313
|
+
createError(
|
|
314
|
+
"INVALID_VALUE",
|
|
315
|
+
"Skill toolReferences must be an array",
|
|
316
|
+
`${path}.toolReferences`,
|
|
317
|
+
skill.toolReferences,
|
|
318
|
+
),
|
|
319
|
+
);
|
|
320
|
+
} else {
|
|
321
|
+
skill.toolReferences.forEach((tool, i) => {
|
|
322
|
+
const toolPath = `${path}.toolReferences[${i}]`;
|
|
323
|
+
if (!tool.name) {
|
|
324
|
+
errors.push(
|
|
325
|
+
createError(
|
|
326
|
+
"MISSING_REQUIRED",
|
|
327
|
+
"Tool reference missing name",
|
|
328
|
+
`${toolPath}.name`,
|
|
329
|
+
),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
if (!tool.description) {
|
|
333
|
+
errors.push(
|
|
334
|
+
createError(
|
|
335
|
+
"MISSING_REQUIRED",
|
|
336
|
+
"Tool reference missing description",
|
|
337
|
+
`${toolPath}.description`,
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
if (!tool.useWhen) {
|
|
342
|
+
errors.push(
|
|
343
|
+
createError(
|
|
344
|
+
"MISSING_REQUIRED",
|
|
345
|
+
"Tool reference missing useWhen",
|
|
346
|
+
`${toolPath}.useWhen`,
|
|
347
|
+
),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (tool.url !== undefined && typeof tool.url !== "string") {
|
|
351
|
+
errors.push(
|
|
352
|
+
createError(
|
|
353
|
+
"INVALID_VALUE",
|
|
354
|
+
"Tool reference url must be a string",
|
|
355
|
+
`${toolPath}.url`,
|
|
356
|
+
tool.url,
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
298
364
|
return { errors, warnings };
|
|
299
365
|
}
|
|
300
366
|
|
|
@@ -1173,12 +1239,12 @@ function validateCapability(capability, index) {
|
|
|
1173
1239
|
),
|
|
1174
1240
|
);
|
|
1175
1241
|
}
|
|
1176
|
-
if (!capability.
|
|
1242
|
+
if (!capability.emojiIcon) {
|
|
1177
1243
|
warnings.push(
|
|
1178
1244
|
createWarning(
|
|
1179
1245
|
"MISSING_OPTIONAL",
|
|
1180
|
-
"Capability missing
|
|
1181
|
-
`${path}.
|
|
1246
|
+
"Capability missing emojiIcon",
|
|
1247
|
+
`${path}.emojiIcon`,
|
|
1182
1248
|
),
|
|
1183
1249
|
);
|
|
1184
1250
|
}
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
span,
|
|
17
17
|
label,
|
|
18
18
|
section,
|
|
19
|
+
select,
|
|
20
|
+
option,
|
|
19
21
|
} from "../lib/render.js";
|
|
20
22
|
import { getState } from "../lib/state.js";
|
|
21
23
|
import { loadAgentDataBrowser } from "../lib/yaml-loader.js";
|
|
@@ -34,6 +36,7 @@ import { createReactive } from "../lib/reactive.js";
|
|
|
34
36
|
import { getStageEmoji } from "../formatters/stage/shared.js";
|
|
35
37
|
import { formatAgentProfile } from "../formatters/agent/profile.js";
|
|
36
38
|
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
39
|
+
import { createCodeDisplay } from "../components/code-display.js";
|
|
37
40
|
|
|
38
41
|
/** All stages option value */
|
|
39
42
|
const ALL_STAGES_VALUE = "all";
|
|
@@ -101,9 +104,71 @@ export async function renderAgentBuilder() {
|
|
|
101
104
|
const availableDisciplines = data.disciplines.filter((d) =>
|
|
102
105
|
agentDisciplineIds.has(d.id),
|
|
103
106
|
);
|
|
104
|
-
|
|
107
|
+
// All tracks with agent definitions (will be filtered per-discipline)
|
|
108
|
+
const allAgentTracks = data.tracks.filter((t) => agentTrackIds.has(t.id));
|
|
105
109
|
const stages = data.stages || [];
|
|
106
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Get tracks valid for a discipline that also have agent definitions
|
|
113
|
+
* @param {string} disciplineId - Discipline ID
|
|
114
|
+
* @returns {Array} - Valid tracks for the discipline
|
|
115
|
+
*/
|
|
116
|
+
function getValidTracksForDiscipline(disciplineId) {
|
|
117
|
+
const discipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
118
|
+
if (!discipline) return [];
|
|
119
|
+
|
|
120
|
+
const validTracks = discipline.validTracks ?? [];
|
|
121
|
+
// Filter to track IDs only (exclude null which means trackless)
|
|
122
|
+
const validTrackIds = validTracks.filter((t) => t !== null);
|
|
123
|
+
|
|
124
|
+
// Intersection: valid for discipline AND has agent definition
|
|
125
|
+
return allAgentTracks.filter((t) => validTrackIds.includes(t.id));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Track select element - created once, options updated when discipline changes
|
|
129
|
+
const trackSelectEl = select(
|
|
130
|
+
{ className: "form-select", id: "agent-track-select" },
|
|
131
|
+
option({ value: "", disabled: true, selected: true }, "Select a track..."),
|
|
132
|
+
);
|
|
133
|
+
trackSelectEl.disabled = true;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Update track select options based on selected discipline
|
|
137
|
+
* @param {string} disciplineId - Discipline ID
|
|
138
|
+
*/
|
|
139
|
+
function updateTrackOptions(disciplineId) {
|
|
140
|
+
const validTracks = getValidTracksForDiscipline(disciplineId);
|
|
141
|
+
|
|
142
|
+
// Clear existing options
|
|
143
|
+
trackSelectEl.innerHTML = "";
|
|
144
|
+
|
|
145
|
+
if (validTracks.length === 0) {
|
|
146
|
+
trackSelectEl.appendChild(
|
|
147
|
+
option(
|
|
148
|
+
{ value: "", disabled: true, selected: true },
|
|
149
|
+
"No tracks available for this discipline",
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
trackSelectEl.disabled = true;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Add placeholder
|
|
157
|
+
trackSelectEl.appendChild(
|
|
158
|
+
option(
|
|
159
|
+
{ value: "", disabled: true, selected: true },
|
|
160
|
+
"Select a track...",
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Add available track options
|
|
165
|
+
validTracks.forEach((t) => {
|
|
166
|
+
trackSelectEl.appendChild(option({ value: t.id }, t.name));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
trackSelectEl.disabled = false;
|
|
170
|
+
}
|
|
171
|
+
|
|
107
172
|
// Build stage options with "All Stages" first
|
|
108
173
|
const stageOptions = [
|
|
109
174
|
{ id: ALL_STAGES_VALUE, name: "All Stages" },
|
|
@@ -134,7 +199,7 @@ export async function renderAgentBuilder() {
|
|
|
134
199
|
// Preview container - will be updated reactively
|
|
135
200
|
const previewContainer = div(
|
|
136
201
|
{ className: "agent-preview" },
|
|
137
|
-
createEmptyState(availableDisciplines.length,
|
|
202
|
+
createEmptyState(availableDisciplines.length, allAgentTracks.length),
|
|
138
203
|
);
|
|
139
204
|
|
|
140
205
|
/**
|
|
@@ -156,7 +221,7 @@ export async function renderAgentBuilder() {
|
|
|
156
221
|
|
|
157
222
|
if (!discipline) {
|
|
158
223
|
previewContainer.appendChild(
|
|
159
|
-
createEmptyState(availableDisciplines.length,
|
|
224
|
+
createEmptyState(availableDisciplines.length, allAgentTracks.length),
|
|
160
225
|
);
|
|
161
226
|
return;
|
|
162
227
|
}
|
|
@@ -251,7 +316,14 @@ export async function renderAgentBuilder() {
|
|
|
251
316
|
initialValue: selection.get().discipline,
|
|
252
317
|
placeholder: "Select a discipline...",
|
|
253
318
|
onChange: (value) => {
|
|
254
|
-
|
|
319
|
+
// Update track options when discipline changes
|
|
320
|
+
updateTrackOptions(value);
|
|
321
|
+
// Reset track selection when discipline changes
|
|
322
|
+
selection.update((prev) => ({
|
|
323
|
+
...prev,
|
|
324
|
+
discipline: value,
|
|
325
|
+
track: "",
|
|
326
|
+
}));
|
|
255
327
|
},
|
|
256
328
|
getDisplayName: (d) => d.specialization || d.name,
|
|
257
329
|
})
|
|
@@ -260,25 +332,32 @@ export async function renderAgentBuilder() {
|
|
|
260
332
|
"No disciplines have agent definitions.",
|
|
261
333
|
),
|
|
262
334
|
),
|
|
263
|
-
// Track selector
|
|
335
|
+
// Track selector (dynamically filtered by discipline)
|
|
264
336
|
div(
|
|
265
337
|
{ className: "form-group" },
|
|
266
338
|
label({ className: "form-label" }, "Track"),
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
339
|
+
(() => {
|
|
340
|
+
// Wire up track select change handler
|
|
341
|
+
trackSelectEl.addEventListener("change", (e) => {
|
|
342
|
+
selection.update((prev) => ({ ...prev, track: e.target.value }));
|
|
343
|
+
});
|
|
344
|
+
// Initialize track options if discipline is pre-selected
|
|
345
|
+
const initialDiscipline = selection.get().discipline;
|
|
346
|
+
if (initialDiscipline) {
|
|
347
|
+
updateTrackOptions(initialDiscipline);
|
|
348
|
+
// Set initial track value if provided and valid
|
|
349
|
+
const initialTrack = selection.get().track;
|
|
350
|
+
const validTracks =
|
|
351
|
+
getValidTracksForDiscipline(initialDiscipline);
|
|
352
|
+
if (
|
|
353
|
+
initialTrack &&
|
|
354
|
+
validTracks.some((t) => t.id === initialTrack)
|
|
355
|
+
) {
|
|
356
|
+
trackSelectEl.value = initialTrack;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return trackSelectEl;
|
|
360
|
+
})(),
|
|
282
361
|
),
|
|
283
362
|
// Stage selector (dropdown with All Stages option)
|
|
284
363
|
div(
|
|
@@ -407,7 +486,7 @@ function createAllStagesPreview(context) {
|
|
|
407
486
|
const skillFiles = derivedSkills
|
|
408
487
|
.map((derived) => skills.find((s) => s.id === derived.skillId))
|
|
409
488
|
.filter((skill) => skill?.agent)
|
|
410
|
-
.map((skill) => generateSkillMd(skill));
|
|
489
|
+
.map((skill) => generateSkillMd(skill, stages));
|
|
411
490
|
|
|
412
491
|
return div(
|
|
413
492
|
{ className: "agent-deployment" },
|
|
@@ -522,7 +601,7 @@ function createSingleStagePreview(context, stage) {
|
|
|
522
601
|
const skillFiles = derivedSkills
|
|
523
602
|
.map((d) => skills.find((s) => s.id === d.skillId))
|
|
524
603
|
.filter((skill) => skill?.agent)
|
|
525
|
-
.map((skill) => generateSkillMd(skill));
|
|
604
|
+
.map((skill) => generateSkillMd(skill, stages));
|
|
526
605
|
|
|
527
606
|
return div(
|
|
528
607
|
{ className: "agent-deployment" },
|
|
@@ -590,10 +669,15 @@ function createAgentCard(stage, profile, stages, agentTemplate, _derived) {
|
|
|
590
669
|
span({ className: "agent-card-emoji" }, stageEmoji),
|
|
591
670
|
h3({}, `${stage.name} Agent`),
|
|
592
671
|
),
|
|
593
|
-
createCopyButton(content),
|
|
594
672
|
),
|
|
595
|
-
|
|
596
|
-
|
|
673
|
+
div(
|
|
674
|
+
{ className: "agent-card-preview" },
|
|
675
|
+
createCodeDisplay({
|
|
676
|
+
content,
|
|
677
|
+
filename: profile.filename,
|
|
678
|
+
maxHeight: 400,
|
|
679
|
+
}),
|
|
680
|
+
),
|
|
597
681
|
);
|
|
598
682
|
|
|
599
683
|
return card;
|
|
@@ -614,57 +698,18 @@ function createSkillCard(skill, skillTemplate) {
|
|
|
614
698
|
div(
|
|
615
699
|
{ className: "skill-card-header" },
|
|
616
700
|
span({ className: "skill-card-name" }, skill.frontmatter.name),
|
|
617
|
-
createCopyButton(content),
|
|
618
701
|
),
|
|
619
|
-
|
|
620
|
-
|
|
702
|
+
div(
|
|
703
|
+
{ className: "skill-card-preview" },
|
|
704
|
+
createCodeDisplay({
|
|
705
|
+
content,
|
|
706
|
+
filename,
|
|
707
|
+
maxHeight: 300,
|
|
708
|
+
}),
|
|
709
|
+
),
|
|
621
710
|
);
|
|
622
711
|
}
|
|
623
712
|
|
|
624
|
-
/**
|
|
625
|
-
* Create a code preview element
|
|
626
|
-
* @param {string} content - Code content
|
|
627
|
-
* @returns {HTMLElement}
|
|
628
|
-
*/
|
|
629
|
-
function createCodePreview(content) {
|
|
630
|
-
const pre = document.createElement("pre");
|
|
631
|
-
pre.className = "code-block code-preview";
|
|
632
|
-
|
|
633
|
-
const code = document.createElement("code");
|
|
634
|
-
code.textContent = content;
|
|
635
|
-
|
|
636
|
-
pre.appendChild(code);
|
|
637
|
-
return pre;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
/**
|
|
641
|
-
* Create a copy button
|
|
642
|
-
* @param {string} content - Content to copy
|
|
643
|
-
* @returns {HTMLElement}
|
|
644
|
-
*/
|
|
645
|
-
function createCopyButton(content) {
|
|
646
|
-
const btn = document.createElement("button");
|
|
647
|
-
btn.className = "btn btn-sm copy-btn";
|
|
648
|
-
btn.textContent = "📋 Copy";
|
|
649
|
-
|
|
650
|
-
btn.addEventListener("click", async () => {
|
|
651
|
-
try {
|
|
652
|
-
await navigator.clipboard.writeText(content);
|
|
653
|
-
btn.textContent = "✓ Copied";
|
|
654
|
-
setTimeout(() => {
|
|
655
|
-
btn.textContent = "📋 Copy";
|
|
656
|
-
}, 2000);
|
|
657
|
-
} catch {
|
|
658
|
-
btn.textContent = "Failed";
|
|
659
|
-
setTimeout(() => {
|
|
660
|
-
btn.textContent = "📋 Copy";
|
|
661
|
-
}, 2000);
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
|
|
665
|
-
return btn;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
713
|
/**
|
|
669
714
|
* Create download all button for all stages
|
|
670
715
|
* @param {Array} stageAgents - Array of {stage, derived, profile}
|
|
@@ -860,11 +905,10 @@ function createCliHint(disciplineId, trackId, stageId) {
|
|
|
860
905
|
{ className: "agent-section cli-hint" },
|
|
861
906
|
h2({}, "CLI Alternative"),
|
|
862
907
|
p({}, "Generate this agent from the command line:"),
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
),
|
|
908
|
+
createCodeDisplay({
|
|
909
|
+
content: command,
|
|
910
|
+
language: "bash",
|
|
911
|
+
}),
|
|
868
912
|
);
|
|
869
913
|
|
|
870
914
|
return container;
|