@forwardimpact/pathway 0.4.0 → 0.6.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 (245) hide show
  1. package/bin/{pathway.js → fit-pathway.js} +65 -153
  2. package/package.json +18 -41
  3. package/{app → src}/commands/agent.js +5 -2
  4. package/{app → src}/commands/behaviour.js +1 -1
  5. package/{app → src}/commands/command-factory.js +2 -2
  6. package/{app → src}/commands/discipline.js +1 -1
  7. package/{app → src}/commands/driver.js +2 -2
  8. package/{app → src}/commands/grade.js +2 -2
  9. package/{app → src}/commands/job.js +3 -3
  10. package/{app → src}/commands/serve.js +26 -4
  11. package/{app → src}/commands/site.js +24 -4
  12. package/{app → src}/commands/skill.js +3 -3
  13. package/{app → src}/commands/stage.js +1 -1
  14. package/{app → src}/commands/track.js +2 -2
  15. package/{app → src}/components/card.js +11 -1
  16. package/{app → src}/components/checklist.js +1 -1
  17. package/src/components/code-display.js +153 -0
  18. package/{app → src}/components/comparison-radar.js +1 -1
  19. package/{app → src}/components/detail.js +1 -1
  20. package/src/components/markdown-textarea.js +153 -0
  21. package/{app → src}/components/skill-matrix.js +1 -1
  22. package/{app → src}/css/bundles/app.css +14 -0
  23. package/{app → src}/css/components/badges.css +15 -8
  24. package/{app → src}/css/components/forms.css +23 -13
  25. package/{app → src}/css/components/surfaces.css +49 -3
  26. package/{app → src}/css/components/typography.css +1 -2
  27. package/{app → src}/css/pages/agent-builder.css +11 -102
  28. package/{app → src}/css/pages/detail.css +11 -1
  29. package/{app → src}/css/tokens.css +3 -0
  30. package/{app → src}/formatters/agent/dom.js +26 -71
  31. package/{app → src}/formatters/agent/profile.js +11 -6
  32. package/{app → src}/formatters/behaviour/dom.js +1 -1
  33. package/{app → src}/formatters/discipline/dom.js +1 -1
  34. package/{app → src}/formatters/driver/dom.js +1 -1
  35. package/{app → src}/formatters/grade/dom.js +7 -7
  36. package/{app → src}/formatters/grade/markdown.js +1 -1
  37. package/{app → src}/formatters/interview/dom.js +1 -1
  38. package/{app → src}/formatters/interview/markdown.js +1 -1
  39. package/{app → src}/formatters/interview/shared.js +3 -3
  40. package/{app → src}/formatters/job/description.js +1 -1
  41. package/{app → src}/formatters/job/dom.js +3 -3
  42. package/{app → src}/formatters/job/markdown.js +1 -1
  43. package/{app → src}/formatters/json-ld.js +1 -1
  44. package/{app → src}/formatters/progress/shared.js +3 -3
  45. package/{app → src}/formatters/skill/dom.js +69 -57
  46. package/{app → src}/formatters/skill/markdown.js +1 -1
  47. package/{app → src}/formatters/skill/shared.js +5 -3
  48. package/{app → src}/formatters/stage/microdata.js +2 -2
  49. package/{app → src}/formatters/stage/shared.js +3 -3
  50. package/{app → src}/formatters/tool/shared.js +6 -0
  51. package/{app → src}/formatters/track/dom.js +1 -1
  52. package/{app → src}/formatters/track/markdown.js +1 -1
  53. package/{app → src}/formatters/track/shared.js +4 -1
  54. package/{app → src}/handout-main.js +16 -12
  55. package/src/handout.html +43 -0
  56. package/{app → src}/index.html +23 -2
  57. package/{app → src}/lib/card-mappers.js +28 -1
  58. package/{app → src}/lib/job-cache.js +1 -1
  59. package/{app → src}/lib/render.js +1 -1
  60. package/{app → src}/pages/agent-builder.js +120 -76
  61. package/{app → src}/pages/assessment-results.js +1 -1
  62. package/{app → src}/pages/interview.js +1 -1
  63. package/{app → src}/pages/job-builder.js +1 -1
  64. package/{app → src}/pages/job.js +1 -1
  65. package/{app → src}/pages/landing.js +5 -2
  66. package/{app → src}/pages/self-assessment.js +1 -1
  67. package/{app → src}/pages/skill.js +1 -1
  68. package/{app → src}/pages/stage.js +5 -5
  69. package/{app → src}/pages/tool.js +1 -1
  70. package/{app → src}/slide-main.js +2 -2
  71. package/{app → src}/slides/chapter.js +8 -8
  72. package/{app → src}/slides/index.js +3 -3
  73. package/{app → src}/slides/job.js +1 -1
  74. package/{app → src}/slides/overview.js +9 -9
  75. package/{app → src}/slides/skill.js +1 -0
  76. package/{app → src}/slides.html +16 -1
  77. package/templates/agent.template.md +44 -13
  78. package/templates/job.template.md +14 -20
  79. package/templates/skill.template.md +20 -23
  80. package/LICENSE +0 -201
  81. package/README.md +0 -104
  82. package/app/components/markdown-textarea.js +0 -132
  83. package/app/handout.html +0 -28
  84. package/app/model/agent.js +0 -738
  85. package/app/model/checklist.js +0 -103
  86. package/app/model/derivation.js +0 -766
  87. package/app/model/index-generator.js +0 -65
  88. package/app/model/interview.js +0 -539
  89. package/app/model/job.js +0 -228
  90. package/app/model/levels.js +0 -601
  91. package/app/model/loader.js +0 -599
  92. package/app/model/matching.js +0 -888
  93. package/app/model/modifiers.js +0 -158
  94. package/app/model/profile.js +0 -259
  95. package/app/model/progression.js +0 -507
  96. package/app/model/schema-validation.js +0 -438
  97. package/app/model/validation.js +0 -2130
  98. package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
  99. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
  100. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
  101. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
  102. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
  103. package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
  104. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
  105. package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
  106. package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
  107. package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
  108. package/examples/agents/.vscode/settings.json +0 -8
  109. package/examples/behaviours/_index.yaml +0 -8
  110. package/examples/behaviours/outcome_ownership.yaml +0 -43
  111. package/examples/behaviours/polymathic_knowledge.yaml +0 -41
  112. package/examples/behaviours/precise_communication.yaml +0 -39
  113. package/examples/behaviours/relentless_curiosity.yaml +0 -37
  114. package/examples/behaviours/systems_thinking.yaml +0 -40
  115. package/examples/capabilities/_index.yaml +0 -8
  116. package/examples/capabilities/business.yaml +0 -189
  117. package/examples/capabilities/delivery.yaml +0 -303
  118. package/examples/capabilities/people.yaml +0 -68
  119. package/examples/capabilities/reliability.yaml +0 -412
  120. package/examples/capabilities/scale.yaml +0 -378
  121. package/examples/copilot-setup-steps.yaml +0 -25
  122. package/examples/devcontainer.yaml +0 -21
  123. package/examples/disciplines/_index.yaml +0 -6
  124. package/examples/disciplines/data_engineering.yaml +0 -78
  125. package/examples/disciplines/engineering_management.yaml +0 -63
  126. package/examples/disciplines/software_engineering.yaml +0 -78
  127. package/examples/drivers.yaml +0 -202
  128. package/examples/framework.yaml +0 -69
  129. package/examples/grades.yaml +0 -115
  130. package/examples/questions/behaviours/outcome_ownership.yaml +0 -51
  131. package/examples/questions/behaviours/polymathic_knowledge.yaml +0 -47
  132. package/examples/questions/behaviours/precise_communication.yaml +0 -54
  133. package/examples/questions/behaviours/relentless_curiosity.yaml +0 -50
  134. package/examples/questions/behaviours/systems_thinking.yaml +0 -52
  135. package/examples/questions/skills/architecture_design.yaml +0 -53
  136. package/examples/questions/skills/cloud_platforms.yaml +0 -47
  137. package/examples/questions/skills/code_quality.yaml +0 -48
  138. package/examples/questions/skills/data_modeling.yaml +0 -45
  139. package/examples/questions/skills/devops.yaml +0 -46
  140. package/examples/questions/skills/full_stack_development.yaml +0 -47
  141. package/examples/questions/skills/sre_practices.yaml +0 -43
  142. package/examples/questions/skills/stakeholder_management.yaml +0 -48
  143. package/examples/questions/skills/team_collaboration.yaml +0 -42
  144. package/examples/questions/skills/technical_writing.yaml +0 -42
  145. package/examples/self-assessments.yaml +0 -64
  146. package/examples/stages.yaml +0 -131
  147. package/examples/tracks/_index.yaml +0 -5
  148. package/examples/tracks/platform.yaml +0 -49
  149. package/examples/tracks/sre.yaml +0 -48
  150. package/examples/vscode-settings.yaml +0 -17
  151. /package/{app → src}/commands/index.js +0 -0
  152. /package/{app → src}/commands/init.js +0 -0
  153. /package/{app → src}/commands/interview.js +0 -0
  154. /package/{app → src}/commands/progress.js +0 -0
  155. /package/{app → src}/commands/questions.js +0 -0
  156. /package/{app → src}/commands/tool.js +0 -0
  157. /package/{app → src}/components/action-buttons.js +0 -0
  158. /package/{app → src}/components/behaviour-profile.js +0 -0
  159. /package/{app → src}/components/builder.js +0 -0
  160. /package/{app → src}/components/error-page.js +0 -0
  161. /package/{app → src}/components/grid.js +0 -0
  162. /package/{app → src}/components/list.js +0 -0
  163. /package/{app → src}/components/modifier-table.js +0 -0
  164. /package/{app → src}/components/nav.js +0 -0
  165. /package/{app → src}/components/progression-table.js +0 -0
  166. /package/{app → src}/components/radar-chart.js +0 -0
  167. /package/{app → src}/css/base.css +0 -0
  168. /package/{app → src}/css/bundles/handout.css +0 -0
  169. /package/{app → src}/css/bundles/slides.css +0 -0
  170. /package/{app → src}/css/components/buttons.css +0 -0
  171. /package/{app → src}/css/components/layout.css +0 -0
  172. /package/{app → src}/css/components/nav.css +0 -0
  173. /package/{app → src}/css/components/progress.css +0 -0
  174. /package/{app → src}/css/components/states.css +0 -0
  175. /package/{app → src}/css/components/tables.css +0 -0
  176. /package/{app → src}/css/components/utilities.css +0 -0
  177. /package/{app → src}/css/pages/assessment-results.css +0 -0
  178. /package/{app → src}/css/pages/interview-builder.css +0 -0
  179. /package/{app → src}/css/pages/job-builder.css +0 -0
  180. /package/{app → src}/css/pages/landing.css +0 -0
  181. /package/{app → src}/css/pages/lifecycle.css +0 -0
  182. /package/{app → src}/css/pages/progress-builder.css +0 -0
  183. /package/{app → src}/css/pages/self-assessment.css +0 -0
  184. /package/{app → src}/css/reset.css +0 -0
  185. /package/{app → src}/css/views/handout.css +0 -0
  186. /package/{app → src}/css/views/print.css +0 -0
  187. /package/{app → src}/css/views/slide-animations.css +0 -0
  188. /package/{app → src}/css/views/slide-base.css +0 -0
  189. /package/{app → src}/css/views/slide-sections.css +0 -0
  190. /package/{app → src}/css/views/slide-tables.css +0 -0
  191. /package/{app → src}/formatters/agent/skill.js +0 -0
  192. /package/{app → src}/formatters/behaviour/markdown.js +0 -0
  193. /package/{app → src}/formatters/behaviour/microdata.js +0 -0
  194. /package/{app → src}/formatters/behaviour/shared.js +0 -0
  195. /package/{app → src}/formatters/discipline/markdown.js +0 -0
  196. /package/{app → src}/formatters/discipline/microdata.js +0 -0
  197. /package/{app → src}/formatters/discipline/shared.js +0 -0
  198. /package/{app → src}/formatters/driver/microdata.js +0 -0
  199. /package/{app → src}/formatters/driver/shared.js +0 -0
  200. /package/{app → src}/formatters/grade/microdata.js +0 -0
  201. /package/{app → src}/formatters/grade/shared.js +0 -0
  202. /package/{app → src}/formatters/index.js +0 -0
  203. /package/{app → src}/formatters/microdata-shared.js +0 -0
  204. /package/{app → src}/formatters/progress/dom.js +0 -0
  205. /package/{app → src}/formatters/progress/markdown.js +0 -0
  206. /package/{app → src}/formatters/questions/json.js +0 -0
  207. /package/{app → src}/formatters/questions/markdown.js +0 -0
  208. /package/{app → src}/formatters/questions/shared.js +0 -0
  209. /package/{app → src}/formatters/questions/yaml.js +0 -0
  210. /package/{app → src}/formatters/shared.js +0 -0
  211. /package/{app → src}/formatters/skill/microdata.js +0 -0
  212. /package/{app → src}/formatters/stage/dom.js +0 -0
  213. /package/{app → src}/formatters/stage/index.js +0 -0
  214. /package/{app → src}/formatters/track/microdata.js +0 -0
  215. /package/{app → src}/lib/cli-output.js +0 -0
  216. /package/{app → src}/lib/error-boundary.js +0 -0
  217. /package/{app → src}/lib/errors.js +0 -0
  218. /package/{app → src}/lib/form-controls.js +0 -0
  219. /package/{app → src}/lib/markdown.js +0 -0
  220. /package/{app → src}/lib/radar.js +0 -0
  221. /package/{app → src}/lib/reactive.js +0 -0
  222. /package/{app → src}/lib/router-core.js +0 -0
  223. /package/{app → src}/lib/router-pages.js +0 -0
  224. /package/{app → src}/lib/router-slides.js +0 -0
  225. /package/{app → src}/lib/state.js +0 -0
  226. /package/{app → src}/lib/template-loader.js +0 -0
  227. /package/{app → src}/lib/utils.js +0 -0
  228. /package/{app → src}/lib/yaml-loader.js +0 -0
  229. /package/{app → src}/main.js +0 -0
  230. /package/{app → src}/pages/behaviour.js +0 -0
  231. /package/{app → src}/pages/discipline.js +0 -0
  232. /package/{app → src}/pages/driver.js +0 -0
  233. /package/{app → src}/pages/grade.js +0 -0
  234. /package/{app → src}/pages/interview-builder.js +0 -0
  235. /package/{app → src}/pages/progress-builder.js +0 -0
  236. /package/{app → src}/pages/progress.js +0 -0
  237. /package/{app → src}/pages/track.js +0 -0
  238. /package/{app → src}/slides/behaviour.js +0 -0
  239. /package/{app → src}/slides/discipline.js +0 -0
  240. /package/{app → src}/slides/driver.js +0 -0
  241. /package/{app → src}/slides/grade.js +0 -0
  242. /package/{app → src}/slides/interview.js +0 -0
  243. /package/{app → src}/slides/progress.js +0 -0
  244. /package/{app → src}/slides/track.js +0 -0
  245. /package/{app → src}/types.js +0 -0
@@ -1,738 +0,0 @@
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 and keeps only skills at the highest derived level.
108
- * This approach respects track modifiers—a broad skill boosted to the same level
109
- * as primary skills will be included.
110
- * @param {Object} params - Parameters
111
- * @param {Object} params.discipline - Human discipline definition
112
- * @param {Object} params.track - Human track definition
113
- * @param {Object} params.grade - Reference grade for derivation
114
- * @param {Array} params.skills - All available skills
115
- * @returns {Array} Skills sorted by derived level (highest first)
116
- */
117
- export function deriveAgentSkills({ discipline, track, grade, skills }) {
118
- // Use shared derivation
119
- const skillMatrix = deriveSkillMatrix({
120
- discipline,
121
- grade,
122
- track,
123
- skills,
124
- });
125
-
126
- // Apply agent-specific filtering and sorting
127
- const filtered = filterSkillsForAgent(skillMatrix);
128
- return sortByLevelDescending(filtered);
129
- }
130
-
131
- /**
132
- * Derive agent behaviours using the unified profile system
133
- * Returns behaviours sorted by maturity (highest first) for the given discipline × track
134
- * @param {Object} params - Parameters
135
- * @param {Object} params.discipline - Human discipline definition
136
- * @param {Object} params.track - Human track definition
137
- * @param {Object} params.grade - Reference grade for derivation
138
- * @param {Array} params.behaviours - All available behaviours
139
- * @returns {Array} Behaviours sorted by derived maturity (highest first)
140
- */
141
- export function deriveAgentBehaviours({
142
- discipline,
143
- track,
144
- grade,
145
- behaviours,
146
- }) {
147
- const profile = deriveBehaviourProfile({
148
- discipline,
149
- grade,
150
- track,
151
- behaviours,
152
- });
153
-
154
- return sortByMaturityDescending(profile);
155
- }
156
-
157
- /**
158
- * Substitute template variables in text
159
- * @param {string} text - Text with {roleTitle}, {specialization} placeholders
160
- * @param {Object} discipline - Discipline with roleTitle, specialization properties
161
- * @returns {string} Text with substituted values
162
- */
163
- function substituteTemplateVars(text, discipline) {
164
- return text
165
- .replace(/\{roleTitle\}/g, discipline.roleTitle)
166
- .replace(/\{specialization\}/g, discipline.specialization);
167
- }
168
-
169
- /**
170
- * Find an agent behaviour by id
171
- * @param {Array} agentBehaviours - Array of agent behaviour definitions
172
- * @param {string} id - Behaviour id to find
173
- * @returns {Object|undefined} Agent behaviour or undefined
174
- */
175
- function findAgentBehaviour(agentBehaviours, id) {
176
- return agentBehaviours.find((b) => b.id === id);
177
- }
178
-
179
- /**
180
- * Build working style section from emphasized behaviours
181
- * Includes workflow patterns when available
182
- * @param {Array} derivedBehaviours - Behaviours sorted by maturity (highest first)
183
- * @param {Array} agentBehaviours - Agent behaviour definitions with principles
184
- * @param {number} topN - Number of top behaviours to include
185
- * @returns {string} Working style markdown section
186
- */
187
- function buildWorkingStyleFromBehaviours(
188
- derivedBehaviours,
189
- agentBehaviours,
190
- topN = 3,
191
- ) {
192
- const sections = [];
193
- sections.push("## Working Style");
194
- sections.push("");
195
-
196
- // Get top N behaviours by maturity
197
- const topBehaviours = derivedBehaviours.slice(0, topN);
198
-
199
- for (const derived of topBehaviours) {
200
- const agentBehaviour = findAgentBehaviour(
201
- agentBehaviours,
202
- derived.behaviourId,
203
- );
204
- // Skip if no agent behaviour data or no content to display
205
- if (!agentBehaviour) continue;
206
- if (!agentBehaviour.workingStyle && !agentBehaviour.principles) continue;
207
-
208
- // Use title as section header
209
- const title = agentBehaviour.title || derived.behaviourName;
210
- sections.push(`### ${title}`);
211
- sections.push("");
212
-
213
- // Include workingStyle if available (structured guidance)
214
- if (agentBehaviour.workingStyle) {
215
- sections.push(agentBehaviour.workingStyle.trim());
216
- sections.push("");
217
- } else if (agentBehaviour.principles) {
218
- // Fall back to principles
219
- const principles = agentBehaviour.principles.trim();
220
- sections.push(principles);
221
- sections.push("");
222
- }
223
- }
224
-
225
- return sections.join("\n");
226
- }
227
-
228
- /**
229
- * Generate SKILL.md content from skill data
230
- * @param {Object} skillData - Skill with agent section containing stages
231
- * @param {Array} stages - All stage entities
232
- * @returns {Object} Skill with frontmatter, title, stages array, reference, dirname
233
- */
234
- export function generateSkillMd(skillData, stages) {
235
- const { agent, name } = skillData;
236
-
237
- if (!agent) {
238
- throw new Error(`Skill ${skillData.id} has no agent section`);
239
- }
240
-
241
- if (!agent.stages) {
242
- throw new Error(`Skill ${skillData.id} agent section missing stages`);
243
- }
244
-
245
- // Build stage lookup map
246
- const stageMap = new Map(stages.map((s) => [s.id, s]));
247
-
248
- // Transform stages object to array for template rendering
249
- const stagesArray = Object.entries(agent.stages).map(
250
- ([stageId, stageData]) => {
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
-
266
- return {
267
- stageId,
268
- stageName,
269
- nextStageName,
270
- focus: stageData.focus,
271
- activities: stageData.activities || [],
272
- ready: stageData.ready || [],
273
- };
274
- },
275
- );
276
-
277
- // Sort stages in order: plan, code, review
278
- const stageOrder = ["plan", "code", "review"];
279
- stagesArray.sort(
280
- (a, b) => stageOrder.indexOf(a.stageId) - stageOrder.indexOf(b.stageId),
281
- );
282
-
283
- return {
284
- frontmatter: {
285
- name: agent.name,
286
- description: agent.description,
287
- useWhen: agent.useWhen || "",
288
- },
289
- title: name,
290
- stages: stagesArray,
291
- reference: skillData.implementationReference || "",
292
- toolReferences: skillData.toolReferences || [],
293
- dirname: agent.name,
294
- };
295
- }
296
-
297
- /**
298
- * Estimate total character length of bodyData fields
299
- * @param {Object} bodyData - Structured profile body data
300
- * @returns {number} Estimated character count
301
- */
302
- function estimateBodyDataLength(bodyData) {
303
- let length = 0;
304
-
305
- // String fields
306
- const stringFields = [
307
- "title",
308
- "stageDescription",
309
- "identity",
310
- "priority",
311
- "delegation",
312
- "operationalContext",
313
- "workingStyle",
314
- "beforeHandoff",
315
- ];
316
- for (const field of stringFields) {
317
- if (bodyData[field]) {
318
- length += bodyData[field].length;
319
- }
320
- }
321
-
322
- // Array fields
323
- if (bodyData.capabilities) {
324
- length += bodyData.capabilities.join(", ").length;
325
- }
326
- if (bodyData.beforeMakingChanges) {
327
- for (const item of bodyData.beforeMakingChanges) {
328
- length += item.text.length + 5; // +5 for "1. " prefix
329
- }
330
- }
331
- if (bodyData.constraints) {
332
- for (const c of bodyData.constraints) {
333
- length += c.length + 2; // +2 for "- " prefix
334
- }
335
- }
336
-
337
- return length;
338
- }
339
-
340
- /**
341
- * Validate agent profile against spec constraints
342
- * @param {Object} profile - Generated profile
343
- * @returns {Array<string>} Array of error messages (empty if valid)
344
- */
345
- export function validateAgentProfile(profile) {
346
- const errors = [];
347
-
348
- // Required: description
349
- if (!profile.frontmatter.description) {
350
- errors.push("Missing required field: description");
351
- }
352
-
353
- // Name format (if provided)
354
- if (profile.frontmatter.name) {
355
- if (!/^[a-zA-Z0-9._-]+$/.test(profile.frontmatter.name)) {
356
- errors.push("Name contains invalid characters");
357
- }
358
- }
359
-
360
- // Body length limit (30,000 chars) - estimate from bodyData fields
361
- const bodyLength = estimateBodyDataLength(profile.bodyData);
362
- if (bodyLength > 30000) {
363
- errors.push(`Body exceeds 30,000 character limit (${bodyLength})`);
364
- }
365
-
366
- // Tools format
367
- if (profile.frontmatter.tools && !Array.isArray(profile.frontmatter.tools)) {
368
- errors.push("Tools must be an array");
369
- }
370
-
371
- return errors;
372
- }
373
-
374
- /**
375
- * Validate agent skill against spec constraints
376
- * @param {Object} skill - Generated skill
377
- * @returns {Array<string>} Array of error messages (empty if valid)
378
- */
379
- export function validateAgentSkill(skill) {
380
- const errors = [];
381
-
382
- // Required: name
383
- if (!skill.frontmatter.name) {
384
- errors.push("Missing required field: name");
385
- } else {
386
- const name = skill.frontmatter.name;
387
-
388
- // Name format: lowercase, hyphens, 1-64 chars
389
- if (!/^[a-z0-9-]+$/.test(name)) {
390
- errors.push("Name must be lowercase alphanumeric with hyphens");
391
- }
392
- if (name.length > 64) {
393
- errors.push("Name exceeds 64 character limit");
394
- }
395
- if (name.startsWith("-") || name.endsWith("-")) {
396
- errors.push("Name cannot start or end with hyphen");
397
- }
398
- if (name.includes("--")) {
399
- errors.push("Name cannot contain consecutive hyphens");
400
- }
401
- }
402
-
403
- // Required: description
404
- if (!skill.frontmatter.description) {
405
- errors.push("Missing required field: description");
406
- } else if (skill.frontmatter.description.length > 1024) {
407
- errors.push("Description exceeds 1024 character limit");
408
- }
409
-
410
- return errors;
411
- }
412
-
413
- // =============================================================================
414
- // Stage-Based Agent Generation
415
- // =============================================================================
416
-
417
- /**
418
- * Derive handoff buttons for a stage-based agent
419
- * Generates handoff button definitions from stage.handoffs with rich prompts
420
- * that include summary instructions and target stage entry criteria
421
- * @param {Object} params - Parameters
422
- * @param {Object} params.stage - Stage definition
423
- * @param {Object} params.discipline - Human discipline definition (for naming)
424
- * @param {Object} params.track - Human track definition (for naming)
425
- * @param {Array} params.stages - All stages (to look up target stage entry criteria)
426
- * @returns {Array<{label: string, agent: string, prompt: string, send: boolean}>} Handoff definitions
427
- */
428
- export function deriveHandoffs({ stage, discipline, track, stages }) {
429
- if (!stage.handoffs || stage.handoffs.length === 0) {
430
- return [];
431
- }
432
-
433
- // Build base name for target agents (matches filename without .agent.md)
434
- const abbrev = getDisciplineAbbreviation(discipline.id);
435
- const baseName = `${abbrev}-${toKebabCase(track.id)}`;
436
-
437
- return stage.handoffs.map((handoff) => {
438
- // Find the target stage to get its entry criteria
439
- const targetStage = stages.find((s) => s.id === handoff.targetStage);
440
- const entryCriteria = targetStage?.entryCriteria || [];
441
-
442
- // Build rich prompt - formatted for single-line display
443
- const promptParts = [handoff.prompt];
444
-
445
- // Add summary instruction
446
- promptParts.push(
447
- `Summarize what was completed in the ${stage.name} stage.`,
448
- );
449
-
450
- // Add entry criteria from target stage with inline numbered list
451
- if (entryCriteria.length > 0) {
452
- const formattedCriteria = entryCriteria
453
- .map((item, index) => `(${index + 1}) ${item}`)
454
- .join(", ");
455
- promptParts.push(
456
- `Before starting, the ${targetStage.name} stage requires: ${formattedCriteria}.`,
457
- );
458
- promptParts.push(
459
- `If critical items are missing, hand back to ${stage.name}.`,
460
- );
461
- }
462
-
463
- return {
464
- label: handoff.label,
465
- agent: `${baseName}-${handoff.targetStage}`,
466
- prompt: promptParts.join(" "),
467
- send: true,
468
- };
469
- });
470
- }
471
-
472
- /**
473
- * Get the handoff type for a stage (used for checklist derivation)
474
- * @param {string} stageId - Stage ID (plan, code, review)
475
- * @returns {string|null} Stage ID for checklist or null
476
- */
477
- function getChecklistStage(stageId) {
478
- // Plan and code stages have checklists, review doesn't
479
- return stageId === "review" ? null : stageId;
480
- }
481
-
482
- /**
483
- * Build the profile body data for a stage-based agent
484
- * Returns structured data for template rendering
485
- * @param {Object} params - Parameters
486
- * @param {Object} params.stage - Stage definition
487
- * @param {Object} params.humanDiscipline - Human discipline definition
488
- * @param {Object} params.humanTrack - Human track definition
489
- * @param {Object} params.agentDiscipline - Agent discipline definition
490
- * @param {Object} params.agentTrack - Agent track definition
491
- * @param {Array} params.derivedSkills - Skills sorted by level
492
- * @param {Array} params.derivedBehaviours - Behaviours sorted by maturity
493
- * @param {Array} params.agentBehaviours - Agent behaviour definitions
494
- * @param {string} params.checklistMarkdown - Pre-formatted checklist markdown
495
- * @returns {Object} Structured profile body data
496
- */
497
- function buildStageProfileBodyData({
498
- stage,
499
- humanDiscipline,
500
- humanTrack,
501
- agentDiscipline,
502
- agentTrack,
503
- derivedSkills,
504
- derivedBehaviours,
505
- agentBehaviours,
506
- checklistMarkdown,
507
- }) {
508
- const name = `${humanDiscipline.specialization || humanDiscipline.name} - ${humanTrack.name}`;
509
- const stageName = stage.name.charAt(0).toUpperCase() + stage.name.slice(1);
510
-
511
- // Build identity - prefer track, fall back to discipline
512
- const rawIdentity = agentTrack.identity || agentDiscipline.identity;
513
- const identity = substituteTemplateVars(rawIdentity, humanDiscipline);
514
-
515
- // Build priority - prefer track, fall back to discipline (optional)
516
- const rawPriority = agentTrack.priority || agentDiscipline.priority;
517
- const priority = rawPriority
518
- ? substituteTemplateVars(rawPriority, humanDiscipline)
519
- : null;
520
-
521
- // Build beforeMakingChanges list - prefer track, fall back to discipline
522
- const rawSteps =
523
- agentTrack.beforeMakingChanges || agentDiscipline.beforeMakingChanges || [];
524
- const beforeMakingChanges = rawSteps.map((text, i) => ({
525
- index: i + 1,
526
- text: substituteTemplateVars(text, humanDiscipline),
527
- }));
528
-
529
- // Delegation (from discipline only, optional)
530
- const rawDelegation = agentDiscipline.delegation;
531
- const delegation = rawDelegation
532
- ? substituteTemplateVars(rawDelegation, humanDiscipline)
533
- : null;
534
-
535
- // Primary capabilities from derived skills
536
- const capabilities = derivedSkills.slice(0, 6).map((s) => s.skillName);
537
-
538
- // Operational Context - use track's roleContext (shared with human job descriptions)
539
- const operationalContext = humanTrack.roleContext.trim();
540
-
541
- // Working Style from derived behaviours (still markdown for now)
542
- const workingStyle = buildWorkingStyleFromBehaviours(
543
- derivedBehaviours,
544
- agentBehaviours,
545
- 3,
546
- );
547
-
548
- // Constraints (stage + discipline + track)
549
- const constraints = [
550
- ...(stage.constraints || []),
551
- ...(agentDiscipline.constraints || []),
552
- ...(agentTrack.constraints || []),
553
- ];
554
-
555
- return {
556
- title: `${name} - ${stageName} Agent`,
557
- stageDescription: stage.description,
558
- identity: identity.trim(),
559
- priority: priority ? priority.trim() : null,
560
- capabilities,
561
- beforeMakingChanges,
562
- delegation: delegation ? delegation.trim() : null,
563
- operationalContext,
564
- workingStyle,
565
- beforeHandoff: checklistMarkdown || null,
566
- constraints,
567
- };
568
- }
569
-
570
- /**
571
- * Derive a stage-specific agent profile
572
- * Combines discipline, track, and stage to produce a complete agent definition
573
- * @param {Object} params - Parameters
574
- * @param {Object} params.discipline - Human discipline definition
575
- * @param {Object} params.track - Human track definition
576
- * @param {Object} params.stage - Stage definition from stages.yaml
577
- * @param {Object} params.grade - Reference grade for skill derivation
578
- * @param {Array} params.skills - All available skills
579
- * @param {Array} params.behaviours - All available behaviours
580
- * @param {Array} params.agentBehaviours - Agent behaviour definitions
581
- * @param {Object} params.agentDiscipline - Agent discipline definition
582
- * @param {Object} params.agentTrack - Agent track definition
583
- * @param {Array} params.capabilities - Capabilities for checklist grouping
584
- * @param {Array} params.stages - All stages (for handoff entry criteria)
585
- * @returns {Object} Agent definition with skills, behaviours, tools, handoffs, constraints, checklist
586
- */
587
- export function deriveStageAgent({
588
- discipline,
589
- track,
590
- stage,
591
- grade,
592
- skills,
593
- behaviours,
594
- agentBehaviours,
595
- agentDiscipline,
596
- agentTrack,
597
- capabilities,
598
- stages,
599
- }) {
600
- // Derive skills and behaviours
601
- const derivedSkills = deriveAgentSkills({
602
- discipline,
603
- track,
604
- grade,
605
- skills,
606
- });
607
-
608
- const derivedBehaviours = deriveAgentBehaviours({
609
- discipline,
610
- track,
611
- grade,
612
- behaviours,
613
- });
614
-
615
- // Derive handoffs from stage
616
- const handoffs = deriveHandoffs({
617
- stage,
618
- discipline,
619
- track,
620
- stages,
621
- });
622
-
623
- // Derive checklist if applicable
624
- const checklistStage = getChecklistStage(stage.id);
625
- let checklist = [];
626
- if (checklistStage && capabilities) {
627
- checklist = deriveChecklist({
628
- stageId: checklistStage,
629
- skillMatrix: derivedSkills,
630
- skills,
631
- capabilities,
632
- });
633
- }
634
-
635
- return {
636
- stage,
637
- discipline,
638
- track,
639
- derivedSkills,
640
- derivedBehaviours,
641
- handoffs,
642
- constraints: [
643
- ...(stage.constraints || []),
644
- ...(agentDiscipline.constraints || []),
645
- ...(agentTrack.constraints || []),
646
- ],
647
- checklist,
648
- agentDiscipline,
649
- agentTrack,
650
- agentBehaviours,
651
- };
652
- }
653
-
654
- /**
655
- * Generate a stage-specific agent profile (.agent.md)
656
- * Produces the complete profile with frontmatter, bodyData, and filename
657
- * @param {Object} params - Parameters
658
- * @param {Object} params.discipline - Human discipline definition
659
- * @param {Object} params.track - Human track definition
660
- * @param {Object} params.stage - Stage definition
661
- * @param {Object} params.grade - Reference grade
662
- * @param {Array} params.skills - All skills
663
- * @param {Array} params.behaviours - All behaviours
664
- * @param {Array} params.agentBehaviours - Agent behaviour definitions
665
- * @param {Object} params.agentDiscipline - Agent discipline definition
666
- * @param {Object} params.agentTrack - Agent track definition
667
- * @param {Array} params.capabilities - Capabilities with checklists
668
- * @param {Array} params.stages - All stages (for handoff entry criteria)
669
- * @returns {Object} Profile with frontmatter, bodyData, and filename
670
- */
671
- export function generateStageAgentProfile({
672
- discipline,
673
- track,
674
- stage,
675
- grade,
676
- skills,
677
- behaviours,
678
- agentBehaviours,
679
- agentDiscipline,
680
- agentTrack,
681
- capabilities,
682
- stages,
683
- }) {
684
- // Derive the complete agent
685
- const agent = deriveStageAgent({
686
- discipline,
687
- track,
688
- stage,
689
- grade,
690
- skills,
691
- behaviours,
692
- agentBehaviours,
693
- agentDiscipline,
694
- agentTrack,
695
- capabilities,
696
- stages,
697
- });
698
-
699
- // Build names (abbreviated form used consistently for filename, name, and handoffs)
700
- const abbrev = getDisciplineAbbreviation(discipline.id);
701
- const fullName = `${abbrev}-${toKebabCase(track.id)}-${stage.id}`;
702
- const filename = `${fullName}.agent.md`;
703
-
704
- // Build description
705
- const disciplineDesc = discipline.description.trim().split("\n")[0];
706
- const stageDesc = stage.description.split(" - ")[0]; // Just the short part
707
- const description = `${stageDesc} agent for ${discipline.specialization || discipline.name} on ${track.name} track. ${disciplineDesc}`;
708
-
709
- // Format checklist as markdown
710
- const checklistMarkdown = formatChecklistMarkdown(agent.checklist);
711
-
712
- // Build structured profile body data
713
- const bodyData = buildStageProfileBodyData({
714
- stage,
715
- humanDiscipline: discipline,
716
- humanTrack: track,
717
- agentDiscipline,
718
- agentTrack,
719
- derivedSkills: agent.derivedSkills,
720
- derivedBehaviours: agent.derivedBehaviours,
721
- agentBehaviours,
722
- checklistMarkdown,
723
- });
724
-
725
- // Build frontmatter
726
- const frontmatter = {
727
- name: fullName,
728
- description,
729
- infer: true,
730
- ...(agent.handoffs.length > 0 && { handoffs: agent.handoffs }),
731
- };
732
-
733
- return {
734
- frontmatter,
735
- bodyData,
736
- filename,
737
- };
738
- }