@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,702 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Generation Model
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for generating AI coding agent configurations
|
|
5
|
+
* from Engineering Pathway data. Outputs follow GitHub Copilot specifications:
|
|
6
|
+
* - Agent Profiles (.agent.md files)
|
|
7
|
+
* - Agent Skills (SKILL.md files)
|
|
8
|
+
*
|
|
9
|
+
* Agent profiles are derived using the SAME modifier logic as human job profiles.
|
|
10
|
+
* Emphasized behaviours and skills (those with positive modifiers) drive agent
|
|
11
|
+
* identity, creating distinct profiles for each discipline × track combination.
|
|
12
|
+
*
|
|
13
|
+
* Stage-based agents (plan, code, review) use lifecycle stages for tool sets,
|
|
14
|
+
* handoffs, and constraints. See concept/lifecycle.md for details.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: This module uses prepareAgentProfile() from profile.js for unified
|
|
17
|
+
* skill/behaviour derivation. The deriveAgentSkills() and deriveAgentBehaviours()
|
|
18
|
+
* functions are thin wrappers for backward compatibility.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { deriveSkillMatrix, deriveBehaviourProfile } from "./derivation.js";
|
|
22
|
+
import { deriveChecklist, formatChecklistMarkdown } from "./checklist.js";
|
|
23
|
+
import {
|
|
24
|
+
filterSkillsForAgent,
|
|
25
|
+
sortByLevelDescending,
|
|
26
|
+
sortByMaturityDescending,
|
|
27
|
+
} from "./profile.js";
|
|
28
|
+
import { SkillLevel } from "./levels.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Derive the reference grade for agent generation.
|
|
32
|
+
*
|
|
33
|
+
* The reference grade determines the skill and behaviour expectations for agents.
|
|
34
|
+
* We select the first grade where primary skills reach "practitioner" level,
|
|
35
|
+
* as this represents substantive senior-level expertise suitable for AI agents.
|
|
36
|
+
*
|
|
37
|
+
* Fallback logic:
|
|
38
|
+
* 1. First grade with practitioner-level primary skills
|
|
39
|
+
* 2. First grade with working-level primary skills (if no practitioner found)
|
|
40
|
+
* 3. Middle grade by level (if neither found)
|
|
41
|
+
*
|
|
42
|
+
* @param {Array<Object>} grades - Array of grade definitions, each with baseSkillLevels.primary
|
|
43
|
+
* @returns {Object} The reference grade
|
|
44
|
+
* @throws {Error} If no grades are provided
|
|
45
|
+
*/
|
|
46
|
+
export function deriveReferenceGrade(grades) {
|
|
47
|
+
if (!grades || grades.length === 0) {
|
|
48
|
+
throw new Error("No grades configured");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Sort by level to ensure consistent ordering
|
|
52
|
+
const sorted = [...grades].sort((a, b) => a.ordinalRank - b.ordinalRank);
|
|
53
|
+
|
|
54
|
+
// First: find the first grade with practitioner-level primary skills
|
|
55
|
+
const practitionerGrade = sorted.find(
|
|
56
|
+
(g) => g.baseSkillLevels?.primary === SkillLevel.PRACTITIONER,
|
|
57
|
+
);
|
|
58
|
+
if (practitionerGrade) {
|
|
59
|
+
return practitionerGrade;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fallback: find the first grade with working-level primary skills
|
|
63
|
+
const workingGrade = sorted.find(
|
|
64
|
+
(g) => g.baseSkillLevels?.primary === SkillLevel.WORKING,
|
|
65
|
+
);
|
|
66
|
+
if (workingGrade) {
|
|
67
|
+
return workingGrade;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Final fallback: use the middle grade
|
|
71
|
+
const middleIndex = Math.floor(sorted.length / 2);
|
|
72
|
+
return sorted[middleIndex];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Discipline ID to abbreviation mapping for file naming
|
|
77
|
+
* Falls back to first letters of discipline name if not specified
|
|
78
|
+
* @type {Object.<string, string>}
|
|
79
|
+
*/
|
|
80
|
+
const DISCIPLINE_ABBREVIATIONS = {
|
|
81
|
+
software_engineering: "se",
|
|
82
|
+
data_engineering: "de",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get abbreviation for a discipline ID
|
|
87
|
+
* Falls back to first two letters if no mapping exists
|
|
88
|
+
* @param {string} disciplineId - Discipline identifier
|
|
89
|
+
* @returns {string} Short form abbreviation
|
|
90
|
+
*/
|
|
91
|
+
export function getDisciplineAbbreviation(disciplineId) {
|
|
92
|
+
return DISCIPLINE_ABBREVIATIONS[disciplineId] || disciplineId.slice(0, 2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convert snake_case id to kebab-case for agent naming
|
|
97
|
+
* @param {string} id - Snake case identifier
|
|
98
|
+
* @returns {string} Kebab case identifier
|
|
99
|
+
*/
|
|
100
|
+
export function toKebabCase(id) {
|
|
101
|
+
return id.replace(/_/g, "-");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Derive agent skills using the unified profile system
|
|
106
|
+
* Returns skills sorted by level (highest first) for the given discipline × track
|
|
107
|
+
* Excludes human-only skills (those requiring human presence/experience)
|
|
108
|
+
* Excludes broad skills unless they're in a capability with positive track modifier
|
|
109
|
+
* @param {Object} params - Parameters
|
|
110
|
+
* @param {Object} params.discipline - Human discipline definition
|
|
111
|
+
* @param {Object} params.track - Human track definition
|
|
112
|
+
* @param {Object} params.grade - Reference grade for derivation
|
|
113
|
+
* @param {Array} params.skills - All available skills
|
|
114
|
+
* @returns {Array} Skills sorted by derived level (highest first)
|
|
115
|
+
*/
|
|
116
|
+
export function deriveAgentSkills({ discipline, track, grade, skills }) {
|
|
117
|
+
// Use shared derivation
|
|
118
|
+
const skillMatrix = deriveSkillMatrix({
|
|
119
|
+
discipline,
|
|
120
|
+
grade,
|
|
121
|
+
track,
|
|
122
|
+
skills,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Apply agent-specific filtering and sorting
|
|
126
|
+
const filtered = filterSkillsForAgent(skillMatrix, track);
|
|
127
|
+
return sortByLevelDescending(filtered);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Derive agent behaviours using the unified profile system
|
|
132
|
+
* Returns behaviours sorted by maturity (highest first) for the given discipline × track
|
|
133
|
+
* @param {Object} params - Parameters
|
|
134
|
+
* @param {Object} params.discipline - Human discipline definition
|
|
135
|
+
* @param {Object} params.track - Human track definition
|
|
136
|
+
* @param {Object} params.grade - Reference grade for derivation
|
|
137
|
+
* @param {Array} params.behaviours - All available behaviours
|
|
138
|
+
* @returns {Array} Behaviours sorted by derived maturity (highest first)
|
|
139
|
+
*/
|
|
140
|
+
export function deriveAgentBehaviours({
|
|
141
|
+
discipline,
|
|
142
|
+
track,
|
|
143
|
+
grade,
|
|
144
|
+
behaviours,
|
|
145
|
+
}) {
|
|
146
|
+
const profile = deriveBehaviourProfile({
|
|
147
|
+
discipline,
|
|
148
|
+
grade,
|
|
149
|
+
track,
|
|
150
|
+
behaviours,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return sortByMaturityDescending(profile);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Substitute template variables in text
|
|
158
|
+
* @param {string} text - Text with {roleTitle}, {specialization} placeholders
|
|
159
|
+
* @param {Object} discipline - Discipline with roleTitle, specialization properties
|
|
160
|
+
* @returns {string} Text with substituted values
|
|
161
|
+
*/
|
|
162
|
+
function substituteTemplateVars(text, discipline) {
|
|
163
|
+
return text
|
|
164
|
+
.replace(/\{roleTitle\}/g, discipline.roleTitle)
|
|
165
|
+
.replace(/\{specialization\}/g, discipline.specialization);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Find an agent behaviour by id
|
|
170
|
+
* @param {Array} agentBehaviours - Array of agent behaviour definitions
|
|
171
|
+
* @param {string} id - Behaviour id to find
|
|
172
|
+
* @returns {Object|undefined} Agent behaviour or undefined
|
|
173
|
+
*/
|
|
174
|
+
function findAgentBehaviour(agentBehaviours, id) {
|
|
175
|
+
return agentBehaviours.find((b) => b.id === id);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Build working style section from emphasized behaviours
|
|
180
|
+
* Includes workflow patterns when available
|
|
181
|
+
* @param {Array} derivedBehaviours - Behaviours sorted by maturity (highest first)
|
|
182
|
+
* @param {Array} agentBehaviours - Agent behaviour definitions with principles
|
|
183
|
+
* @param {number} topN - Number of top behaviours to include
|
|
184
|
+
* @returns {string} Working style markdown section
|
|
185
|
+
*/
|
|
186
|
+
function buildWorkingStyleFromBehaviours(
|
|
187
|
+
derivedBehaviours,
|
|
188
|
+
agentBehaviours,
|
|
189
|
+
topN = 3,
|
|
190
|
+
) {
|
|
191
|
+
const sections = [];
|
|
192
|
+
sections.push("## Working Style");
|
|
193
|
+
sections.push("");
|
|
194
|
+
|
|
195
|
+
// Get top N behaviours by maturity
|
|
196
|
+
const topBehaviours = derivedBehaviours.slice(0, topN);
|
|
197
|
+
|
|
198
|
+
for (const derived of topBehaviours) {
|
|
199
|
+
const agentBehaviour = findAgentBehaviour(
|
|
200
|
+
agentBehaviours,
|
|
201
|
+
derived.behaviourId,
|
|
202
|
+
);
|
|
203
|
+
// Skip if no agent behaviour data or no content to display
|
|
204
|
+
if (!agentBehaviour) continue;
|
|
205
|
+
if (!agentBehaviour.workingStyle && !agentBehaviour.principles) continue;
|
|
206
|
+
|
|
207
|
+
// Use title as section header
|
|
208
|
+
const title = agentBehaviour.title || derived.behaviourName;
|
|
209
|
+
sections.push(`### ${title}`);
|
|
210
|
+
sections.push("");
|
|
211
|
+
|
|
212
|
+
// Include workingStyle if available (structured guidance)
|
|
213
|
+
if (agentBehaviour.workingStyle) {
|
|
214
|
+
sections.push(agentBehaviour.workingStyle.trim());
|
|
215
|
+
sections.push("");
|
|
216
|
+
} else if (agentBehaviour.principles) {
|
|
217
|
+
// Fall back to principles
|
|
218
|
+
const principles = agentBehaviour.principles.trim();
|
|
219
|
+
sections.push(principles);
|
|
220
|
+
sections.push("");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return sections.join("\n");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Generate SKILL.md content from skill data
|
|
229
|
+
* @param {Object} skillData - Skill with agent section
|
|
230
|
+
* @returns {Object} Skill with frontmatter, body, and dirname
|
|
231
|
+
*/
|
|
232
|
+
export function generateSkillMd(skillData) {
|
|
233
|
+
const { agent } = skillData;
|
|
234
|
+
|
|
235
|
+
if (!agent) {
|
|
236
|
+
throw new Error(`Skill ${skillData.id} has no agent section`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
frontmatter: {
|
|
241
|
+
name: agent.name,
|
|
242
|
+
description: agent.description.trim(),
|
|
243
|
+
},
|
|
244
|
+
body: agent.body.trim(),
|
|
245
|
+
dirname: agent.name,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate agent profile against spec constraints
|
|
251
|
+
* @param {Object} profile - Generated profile
|
|
252
|
+
* @returns {Array<string>} Array of error messages (empty if valid)
|
|
253
|
+
*/
|
|
254
|
+
export function validateAgentProfile(profile) {
|
|
255
|
+
const errors = [];
|
|
256
|
+
|
|
257
|
+
// Required: description
|
|
258
|
+
if (!profile.frontmatter.description) {
|
|
259
|
+
errors.push("Missing required field: description");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Name format (if provided)
|
|
263
|
+
if (profile.frontmatter.name) {
|
|
264
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(profile.frontmatter.name)) {
|
|
265
|
+
errors.push("Name contains invalid characters");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Body length limit (30,000 chars)
|
|
270
|
+
if (profile.body.length > 30000) {
|
|
271
|
+
errors.push(`Body exceeds 30,000 character limit (${profile.body.length})`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Tools format
|
|
275
|
+
if (profile.frontmatter.tools && !Array.isArray(profile.frontmatter.tools)) {
|
|
276
|
+
errors.push("Tools must be an array");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return errors;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Validate agent skill against spec constraints
|
|
284
|
+
* @param {Object} skill - Generated skill
|
|
285
|
+
* @returns {Array<string>} Array of error messages (empty if valid)
|
|
286
|
+
*/
|
|
287
|
+
export function validateAgentSkill(skill) {
|
|
288
|
+
const errors = [];
|
|
289
|
+
|
|
290
|
+
// Required: name
|
|
291
|
+
if (!skill.frontmatter.name) {
|
|
292
|
+
errors.push("Missing required field: name");
|
|
293
|
+
} else {
|
|
294
|
+
const name = skill.frontmatter.name;
|
|
295
|
+
|
|
296
|
+
// Name format: lowercase, hyphens, 1-64 chars
|
|
297
|
+
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
298
|
+
errors.push("Name must be lowercase alphanumeric with hyphens");
|
|
299
|
+
}
|
|
300
|
+
if (name.length > 64) {
|
|
301
|
+
errors.push("Name exceeds 64 character limit");
|
|
302
|
+
}
|
|
303
|
+
if (name.startsWith("-") || name.endsWith("-")) {
|
|
304
|
+
errors.push("Name cannot start or end with hyphen");
|
|
305
|
+
}
|
|
306
|
+
if (name.includes("--")) {
|
|
307
|
+
errors.push("Name cannot contain consecutive hyphens");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Required: description
|
|
312
|
+
if (!skill.frontmatter.description) {
|
|
313
|
+
errors.push("Missing required field: description");
|
|
314
|
+
} else if (skill.frontmatter.description.length > 1024) {
|
|
315
|
+
errors.push("Description exceeds 1024 character limit");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return errors;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// =============================================================================
|
|
322
|
+
// Stage-Based Agent Generation
|
|
323
|
+
// =============================================================================
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Derive tools for a stage-based agent
|
|
327
|
+
* Stages define the authoritative tool set for each lifecycle phase
|
|
328
|
+
* @param {Object} params - Parameters
|
|
329
|
+
* @param {Object} params.stage - Stage definition from stages.yaml
|
|
330
|
+
* @returns {string[]} Array of tool names
|
|
331
|
+
*/
|
|
332
|
+
export function deriveAgentTools({ stage }) {
|
|
333
|
+
return stage.availableTools || [];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Derive handoff buttons for a stage-based agent
|
|
338
|
+
* Generates handoff button definitions from stage.handoffs with rich prompts
|
|
339
|
+
* that include summary instructions and target stage entry criteria
|
|
340
|
+
* @param {Object} params - Parameters
|
|
341
|
+
* @param {Object} params.stage - Stage definition
|
|
342
|
+
* @param {Object} params.discipline - Human discipline definition (for naming)
|
|
343
|
+
* @param {Object} params.track - Human track definition (for naming)
|
|
344
|
+
* @param {Array} params.stages - All stages (to look up target stage entry criteria)
|
|
345
|
+
* @returns {Array<{label: string, agent: string, prompt: string, send: boolean}>} Handoff definitions
|
|
346
|
+
*/
|
|
347
|
+
export function deriveHandoffs({ stage, discipline, track, stages }) {
|
|
348
|
+
if (!stage.handoffs || stage.handoffs.length === 0) {
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Build base name for target agents
|
|
353
|
+
const baseName = `${toKebabCase(discipline.id)}-${toKebabCase(track.id)}`;
|
|
354
|
+
|
|
355
|
+
return stage.handoffs.map((handoff) => {
|
|
356
|
+
// Find the target stage to get its entry criteria
|
|
357
|
+
const targetStage = stages.find((s) => s.id === handoff.targetStage);
|
|
358
|
+
const entryCriteria = targetStage?.entryCriteria || [];
|
|
359
|
+
|
|
360
|
+
// Build rich prompt - formatted for single-line display
|
|
361
|
+
const promptParts = [handoff.prompt];
|
|
362
|
+
|
|
363
|
+
// Add summary instruction
|
|
364
|
+
promptParts.push(
|
|
365
|
+
`Summarize what was completed in the ${stage.name} stage.`,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// Add entry criteria from target stage with inline numbered list
|
|
369
|
+
if (entryCriteria.length > 0) {
|
|
370
|
+
const formattedCriteria = entryCriteria
|
|
371
|
+
.map((item, index) => `(${index + 1}) ${item}`)
|
|
372
|
+
.join(", ");
|
|
373
|
+
promptParts.push(
|
|
374
|
+
`Before starting, the ${targetStage.name} stage requires: ${formattedCriteria}.`,
|
|
375
|
+
);
|
|
376
|
+
promptParts.push(
|
|
377
|
+
`If critical items are missing, hand back to ${stage.name}.`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
label: handoff.label,
|
|
383
|
+
agent: `${baseName}-${handoff.targetStage}`,
|
|
384
|
+
prompt: promptParts.join(" "),
|
|
385
|
+
send: true,
|
|
386
|
+
};
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get the handoff type for a stage (used for checklist derivation)
|
|
392
|
+
* @param {string} stageId - Stage ID (plan, code, review)
|
|
393
|
+
* @returns {string|null} Handoff type or null
|
|
394
|
+
*/
|
|
395
|
+
function getHandoffForStage(stageId) {
|
|
396
|
+
const handoffMap = {
|
|
397
|
+
plan: "plan_to_code",
|
|
398
|
+
code: "code_to_review",
|
|
399
|
+
review: null, // Review stage doesn't need a checklist
|
|
400
|
+
};
|
|
401
|
+
return handoffMap[stageId] || null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Build the profile body for a stage-based agent
|
|
406
|
+
* @param {Object} params - Parameters
|
|
407
|
+
* @param {Object} params.stage - Stage definition
|
|
408
|
+
* @param {Object} params.humanDiscipline - Human discipline definition
|
|
409
|
+
* @param {Object} params.humanTrack - Human track definition
|
|
410
|
+
* @param {Object} params.agentDiscipline - Agent discipline definition
|
|
411
|
+
* @param {Object} params.agentTrack - Agent track definition
|
|
412
|
+
* @param {Array} params.derivedSkills - Skills sorted by level
|
|
413
|
+
* @param {Array} params.derivedBehaviours - Behaviours sorted by maturity
|
|
414
|
+
* @param {Array} params.agentBehaviours - Agent behaviour definitions
|
|
415
|
+
* @param {string} params.checklistMarkdown - Pre-formatted checklist markdown
|
|
416
|
+
* @returns {string} Profile body markdown
|
|
417
|
+
*/
|
|
418
|
+
function buildStageProfileBody({
|
|
419
|
+
stage,
|
|
420
|
+
humanDiscipline,
|
|
421
|
+
humanTrack,
|
|
422
|
+
agentDiscipline,
|
|
423
|
+
agentTrack,
|
|
424
|
+
derivedSkills,
|
|
425
|
+
derivedBehaviours,
|
|
426
|
+
agentBehaviours,
|
|
427
|
+
checklistMarkdown,
|
|
428
|
+
}) {
|
|
429
|
+
const name = `${humanDiscipline.specialization || humanDiscipline.name} - ${humanTrack.name}`;
|
|
430
|
+
const stageName = stage.name.charAt(0).toUpperCase() + stage.name.slice(1);
|
|
431
|
+
const sections = [];
|
|
432
|
+
|
|
433
|
+
// Title with stage indicator
|
|
434
|
+
sections.push(`# ${name} - ${stageName} Agent`);
|
|
435
|
+
sections.push("");
|
|
436
|
+
|
|
437
|
+
// Stage description
|
|
438
|
+
sections.push(stage.description);
|
|
439
|
+
sections.push("");
|
|
440
|
+
|
|
441
|
+
// Core Identity
|
|
442
|
+
sections.push("## Core Identity");
|
|
443
|
+
sections.push("");
|
|
444
|
+
|
|
445
|
+
// Use track coreInstructions if available, with template substitution
|
|
446
|
+
const rawInstructions =
|
|
447
|
+
agentTrack.coreInstructions || agentDiscipline.coreInstructions;
|
|
448
|
+
const coreInstructions = substituteTemplateVars(
|
|
449
|
+
rawInstructions,
|
|
450
|
+
humanDiscipline,
|
|
451
|
+
);
|
|
452
|
+
sections.push(coreInstructions.trim());
|
|
453
|
+
sections.push("");
|
|
454
|
+
|
|
455
|
+
// Primary capabilities from derived skills
|
|
456
|
+
const topSkills = derivedSkills.slice(0, 6);
|
|
457
|
+
if (topSkills.length > 0) {
|
|
458
|
+
sections.push("Your primary capabilities:");
|
|
459
|
+
for (const skill of topSkills) {
|
|
460
|
+
sections.push(`- ${skill.skillName}`);
|
|
461
|
+
}
|
|
462
|
+
sections.push("");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Operational Context - use track's roleContext (shared with human job descriptions)
|
|
466
|
+
sections.push("## Operational Context");
|
|
467
|
+
sections.push("");
|
|
468
|
+
sections.push(humanTrack.roleContext.trim());
|
|
469
|
+
sections.push("");
|
|
470
|
+
|
|
471
|
+
// Working Style from derived behaviours
|
|
472
|
+
const workingStyle = buildWorkingStyleFromBehaviours(
|
|
473
|
+
derivedBehaviours,
|
|
474
|
+
agentBehaviours,
|
|
475
|
+
3,
|
|
476
|
+
);
|
|
477
|
+
sections.push(workingStyle);
|
|
478
|
+
|
|
479
|
+
// Before Handoff (if provided)
|
|
480
|
+
if (checklistMarkdown) {
|
|
481
|
+
sections.push("## Before Handoff");
|
|
482
|
+
sections.push("");
|
|
483
|
+
sections.push(
|
|
484
|
+
"Before offering a handoff, verify and summarize completion of these items:",
|
|
485
|
+
);
|
|
486
|
+
sections.push("");
|
|
487
|
+
sections.push(checklistMarkdown);
|
|
488
|
+
sections.push("");
|
|
489
|
+
sections.push(
|
|
490
|
+
"When verified, summarize what was accomplished then offer the handoff.",
|
|
491
|
+
);
|
|
492
|
+
sections.push("If items are incomplete, explain what remains.");
|
|
493
|
+
sections.push("");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Return Format section
|
|
497
|
+
sections.push("## Return Format");
|
|
498
|
+
sections.push("");
|
|
499
|
+
sections.push(
|
|
500
|
+
"When completing work (for handoff or as a subagent), provide:",
|
|
501
|
+
);
|
|
502
|
+
sections.push("");
|
|
503
|
+
sections.push("1. **Work completed**: What was accomplished");
|
|
504
|
+
sections.push(
|
|
505
|
+
"2. **Checklist status**: Items verified from Before Handoff section",
|
|
506
|
+
);
|
|
507
|
+
sections.push(
|
|
508
|
+
"3. **Recommendation**: Ready for next stage, or needs more work",
|
|
509
|
+
);
|
|
510
|
+
sections.push("");
|
|
511
|
+
|
|
512
|
+
// Constraints (stage + discipline + track)
|
|
513
|
+
const allConstraints = [
|
|
514
|
+
...(stage.constraints || []),
|
|
515
|
+
...(agentDiscipline.constraints || []),
|
|
516
|
+
...(agentTrack.constraints || []),
|
|
517
|
+
];
|
|
518
|
+
if (allConstraints.length > 0) {
|
|
519
|
+
sections.push("## Constraints");
|
|
520
|
+
sections.push("");
|
|
521
|
+
for (const constraint of allConstraints) {
|
|
522
|
+
sections.push(`- ${constraint}`);
|
|
523
|
+
}
|
|
524
|
+
sections.push("");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return sections.join("\n");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Derive a stage-specific agent profile
|
|
532
|
+
* Combines discipline, track, and stage to produce a complete agent definition
|
|
533
|
+
* @param {Object} params - Parameters
|
|
534
|
+
* @param {Object} params.discipline - Human discipline definition
|
|
535
|
+
* @param {Object} params.track - Human track definition
|
|
536
|
+
* @param {Object} params.stage - Stage definition from stages.yaml
|
|
537
|
+
* @param {Object} params.grade - Reference grade for skill derivation
|
|
538
|
+
* @param {Array} params.skills - All available skills
|
|
539
|
+
* @param {Array} params.behaviours - All available behaviours
|
|
540
|
+
* @param {Array} params.agentBehaviours - Agent behaviour definitions
|
|
541
|
+
* @param {Object} params.agentDiscipline - Agent discipline definition
|
|
542
|
+
* @param {Object} params.agentTrack - Agent track definition
|
|
543
|
+
* @param {Array} params.capabilities - Capabilities with checklists
|
|
544
|
+
* @param {Array} params.stages - All stages (for handoff entry criteria)
|
|
545
|
+
* @returns {Object} Agent definition with skills, behaviours, tools, handoffs, constraints, checklist
|
|
546
|
+
*/
|
|
547
|
+
export function deriveStageAgent({
|
|
548
|
+
discipline,
|
|
549
|
+
track,
|
|
550
|
+
stage,
|
|
551
|
+
grade,
|
|
552
|
+
skills,
|
|
553
|
+
behaviours,
|
|
554
|
+
agentBehaviours,
|
|
555
|
+
agentDiscipline,
|
|
556
|
+
agentTrack,
|
|
557
|
+
capabilities,
|
|
558
|
+
stages,
|
|
559
|
+
}) {
|
|
560
|
+
// Derive skills and behaviours
|
|
561
|
+
const derivedSkills = deriveAgentSkills({
|
|
562
|
+
discipline,
|
|
563
|
+
track,
|
|
564
|
+
grade,
|
|
565
|
+
skills,
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const derivedBehaviours = deriveAgentBehaviours({
|
|
569
|
+
discipline,
|
|
570
|
+
track,
|
|
571
|
+
grade,
|
|
572
|
+
behaviours,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
// Derive tools from stage
|
|
576
|
+
const tools = deriveAgentTools({ stage });
|
|
577
|
+
|
|
578
|
+
// Derive handoffs from stage
|
|
579
|
+
const handoffs = deriveHandoffs({
|
|
580
|
+
stage,
|
|
581
|
+
discipline,
|
|
582
|
+
track,
|
|
583
|
+
stages,
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Derive checklist if applicable
|
|
587
|
+
const handoffType = getHandoffForStage(stage.id);
|
|
588
|
+
let checklist = [];
|
|
589
|
+
if (handoffType && capabilities) {
|
|
590
|
+
checklist = deriveChecklist({
|
|
591
|
+
handoff: handoffType,
|
|
592
|
+
skillMatrix: derivedSkills,
|
|
593
|
+
capabilities,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
stage,
|
|
599
|
+
discipline,
|
|
600
|
+
track,
|
|
601
|
+
derivedSkills,
|
|
602
|
+
derivedBehaviours,
|
|
603
|
+
tools,
|
|
604
|
+
handoffs,
|
|
605
|
+
constraints: [
|
|
606
|
+
...(stage.constraints || []),
|
|
607
|
+
...(agentDiscipline.constraints || []),
|
|
608
|
+
...(agentTrack.constraints || []),
|
|
609
|
+
],
|
|
610
|
+
checklist,
|
|
611
|
+
agentDiscipline,
|
|
612
|
+
agentTrack,
|
|
613
|
+
agentBehaviours,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Generate a stage-specific agent profile (.agent.md)
|
|
619
|
+
* Produces the complete profile with frontmatter, body, and filename
|
|
620
|
+
* @param {Object} params - Parameters
|
|
621
|
+
* @param {Object} params.discipline - Human discipline definition
|
|
622
|
+
* @param {Object} params.track - Human track definition
|
|
623
|
+
* @param {Object} params.stage - Stage definition
|
|
624
|
+
* @param {Object} params.grade - Reference grade
|
|
625
|
+
* @param {Array} params.skills - All skills
|
|
626
|
+
* @param {Array} params.behaviours - All behaviours
|
|
627
|
+
* @param {Array} params.agentBehaviours - Agent behaviour definitions
|
|
628
|
+
* @param {Object} params.agentDiscipline - Agent discipline definition
|
|
629
|
+
* @param {Object} params.agentTrack - Agent track definition
|
|
630
|
+
* @param {Array} params.capabilities - Capabilities with checklists
|
|
631
|
+
* @param {Array} params.stages - All stages (for handoff entry criteria)
|
|
632
|
+
* @returns {Object} Profile with frontmatter, body, and filename
|
|
633
|
+
*/
|
|
634
|
+
export function generateStageAgentProfile({
|
|
635
|
+
discipline,
|
|
636
|
+
track,
|
|
637
|
+
stage,
|
|
638
|
+
grade,
|
|
639
|
+
skills,
|
|
640
|
+
behaviours,
|
|
641
|
+
agentBehaviours,
|
|
642
|
+
agentDiscipline,
|
|
643
|
+
agentTrack,
|
|
644
|
+
capabilities,
|
|
645
|
+
stages,
|
|
646
|
+
}) {
|
|
647
|
+
// Derive the complete agent
|
|
648
|
+
const agent = deriveStageAgent({
|
|
649
|
+
discipline,
|
|
650
|
+
track,
|
|
651
|
+
stage,
|
|
652
|
+
grade,
|
|
653
|
+
skills,
|
|
654
|
+
behaviours,
|
|
655
|
+
agentBehaviours,
|
|
656
|
+
agentDiscipline,
|
|
657
|
+
agentTrack,
|
|
658
|
+
capabilities,
|
|
659
|
+
stages,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Build names
|
|
663
|
+
const fullName = `${toKebabCase(discipline.id)}-${toKebabCase(track.id)}-${stage.id}`;
|
|
664
|
+
const abbrev = getDisciplineAbbreviation(discipline.id);
|
|
665
|
+
const filename = `${abbrev}-${toKebabCase(track.id)}-${stage.id}.agent.md`;
|
|
666
|
+
|
|
667
|
+
// Build description
|
|
668
|
+
const disciplineDesc = discipline.description.trim().split("\n")[0];
|
|
669
|
+
const stageDesc = stage.description.split(" - ")[0]; // Just the short part
|
|
670
|
+
const description = `${stageDesc} agent for ${discipline.specialization || discipline.name} on ${track.name} track. ${disciplineDesc}`;
|
|
671
|
+
|
|
672
|
+
// Format checklist as markdown
|
|
673
|
+
const checklistMarkdown = formatChecklistMarkdown(agent.checklist);
|
|
674
|
+
|
|
675
|
+
// Build profile body
|
|
676
|
+
const body = buildStageProfileBody({
|
|
677
|
+
stage,
|
|
678
|
+
humanDiscipline: discipline,
|
|
679
|
+
humanTrack: track,
|
|
680
|
+
agentDiscipline,
|
|
681
|
+
agentTrack,
|
|
682
|
+
derivedSkills: agent.derivedSkills,
|
|
683
|
+
derivedBehaviours: agent.derivedBehaviours,
|
|
684
|
+
agentBehaviours,
|
|
685
|
+
checklistMarkdown,
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Build frontmatter
|
|
689
|
+
const frontmatter = {
|
|
690
|
+
name: fullName,
|
|
691
|
+
description,
|
|
692
|
+
tools: agent.tools,
|
|
693
|
+
infer: true,
|
|
694
|
+
...(agent.handoffs.length > 0 && { handoffs: agent.handoffs }),
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
frontmatter,
|
|
699
|
+
body,
|
|
700
|
+
filename,
|
|
701
|
+
};
|
|
702
|
+
}
|