@forwardimpact/pathway 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/app/commands/agent.js +430 -0
  4. package/app/commands/behaviour.js +61 -0
  5. package/app/commands/command-factory.js +211 -0
  6. package/app/commands/discipline.js +58 -0
  7. package/app/commands/driver.js +94 -0
  8. package/app/commands/grade.js +60 -0
  9. package/app/commands/index.js +20 -0
  10. package/app/commands/init.js +67 -0
  11. package/app/commands/interview.js +68 -0
  12. package/app/commands/job.js +157 -0
  13. package/app/commands/progress.js +77 -0
  14. package/app/commands/questions.js +179 -0
  15. package/app/commands/serve.js +143 -0
  16. package/app/commands/site.js +121 -0
  17. package/app/commands/skill.js +76 -0
  18. package/app/commands/stage.js +129 -0
  19. package/app/commands/track.js +70 -0
  20. package/app/components/action-buttons.js +66 -0
  21. package/app/components/behaviour-profile.js +53 -0
  22. package/app/components/builder.js +341 -0
  23. package/app/components/card.js +98 -0
  24. package/app/components/checklist.js +145 -0
  25. package/app/components/comparison-radar.js +237 -0
  26. package/app/components/detail.js +230 -0
  27. package/app/components/error-page.js +72 -0
  28. package/app/components/grid.js +109 -0
  29. package/app/components/list.js +120 -0
  30. package/app/components/modifier-table.js +142 -0
  31. package/app/components/nav.js +64 -0
  32. package/app/components/progression-table.js +320 -0
  33. package/app/components/radar-chart.js +102 -0
  34. package/app/components/skill-matrix.js +97 -0
  35. package/app/css/base.css +56 -0
  36. package/app/css/bundles/app.css +40 -0
  37. package/app/css/bundles/handout.css +43 -0
  38. package/app/css/bundles/slides.css +40 -0
  39. package/app/css/components/badges.css +215 -0
  40. package/app/css/components/buttons.css +101 -0
  41. package/app/css/components/forms.css +105 -0
  42. package/app/css/components/layout.css +209 -0
  43. package/app/css/components/nav.css +166 -0
  44. package/app/css/components/progress.css +166 -0
  45. package/app/css/components/states.css +82 -0
  46. package/app/css/components/surfaces.css +243 -0
  47. package/app/css/components/tables.css +362 -0
  48. package/app/css/components/typography.css +122 -0
  49. package/app/css/components/utilities.css +41 -0
  50. package/app/css/pages/agent-builder.css +391 -0
  51. package/app/css/pages/assessment-results.css +453 -0
  52. package/app/css/pages/detail.css +59 -0
  53. package/app/css/pages/interview-builder.css +148 -0
  54. package/app/css/pages/job-builder.css +134 -0
  55. package/app/css/pages/landing.css +92 -0
  56. package/app/css/pages/lifecycle.css +118 -0
  57. package/app/css/pages/progress-builder.css +274 -0
  58. package/app/css/pages/self-assessment.css +502 -0
  59. package/app/css/reset.css +50 -0
  60. package/app/css/tokens.css +153 -0
  61. package/app/css/views/handout.css +30 -0
  62. package/app/css/views/print.css +608 -0
  63. package/app/css/views/slide-animations.css +113 -0
  64. package/app/css/views/slide-base.css +330 -0
  65. package/app/css/views/slide-sections.css +597 -0
  66. package/app/css/views/slide-tables.css +275 -0
  67. package/app/formatters/agent/dom.js +540 -0
  68. package/app/formatters/agent/profile.js +133 -0
  69. package/app/formatters/agent/skill.js +58 -0
  70. package/app/formatters/behaviour/dom.js +91 -0
  71. package/app/formatters/behaviour/markdown.js +54 -0
  72. package/app/formatters/behaviour/shared.js +64 -0
  73. package/app/formatters/discipline/dom.js +187 -0
  74. package/app/formatters/discipline/markdown.js +87 -0
  75. package/app/formatters/discipline/shared.js +131 -0
  76. package/app/formatters/driver/dom.js +103 -0
  77. package/app/formatters/driver/shared.js +92 -0
  78. package/app/formatters/grade/dom.js +208 -0
  79. package/app/formatters/grade/markdown.js +94 -0
  80. package/app/formatters/grade/shared.js +86 -0
  81. package/app/formatters/index.js +50 -0
  82. package/app/formatters/interview/dom.js +97 -0
  83. package/app/formatters/interview/markdown.js +66 -0
  84. package/app/formatters/interview/shared.js +332 -0
  85. package/app/formatters/job/description.js +176 -0
  86. package/app/formatters/job/dom.js +411 -0
  87. package/app/formatters/job/markdown.js +102 -0
  88. package/app/formatters/progress/dom.js +135 -0
  89. package/app/formatters/progress/markdown.js +86 -0
  90. package/app/formatters/progress/shared.js +339 -0
  91. package/app/formatters/questions/json.js +43 -0
  92. package/app/formatters/questions/markdown.js +303 -0
  93. package/app/formatters/questions/shared.js +274 -0
  94. package/app/formatters/questions/yaml.js +76 -0
  95. package/app/formatters/shared.js +71 -0
  96. package/app/formatters/skill/dom.js +168 -0
  97. package/app/formatters/skill/markdown.js +109 -0
  98. package/app/formatters/skill/shared.js +125 -0
  99. package/app/formatters/stage/dom.js +135 -0
  100. package/app/formatters/stage/index.js +12 -0
  101. package/app/formatters/stage/shared.js +111 -0
  102. package/app/formatters/track/dom.js +128 -0
  103. package/app/formatters/track/markdown.js +105 -0
  104. package/app/formatters/track/shared.js +181 -0
  105. package/app/handout-main.js +421 -0
  106. package/app/handout.html +21 -0
  107. package/app/index.html +59 -0
  108. package/app/lib/card-mappers.js +173 -0
  109. package/app/lib/cli-output.js +270 -0
  110. package/app/lib/error-boundary.js +70 -0
  111. package/app/lib/errors.js +49 -0
  112. package/app/lib/form-controls.js +47 -0
  113. package/app/lib/job-cache.js +86 -0
  114. package/app/lib/markdown.js +114 -0
  115. package/app/lib/radar.js +866 -0
  116. package/app/lib/reactive.js +77 -0
  117. package/app/lib/render.js +212 -0
  118. package/app/lib/router-core.js +160 -0
  119. package/app/lib/router-pages.js +16 -0
  120. package/app/lib/router-slides.js +202 -0
  121. package/app/lib/state.js +148 -0
  122. package/app/lib/utils.js +14 -0
  123. package/app/lib/yaml-loader.js +327 -0
  124. package/app/main.js +213 -0
  125. package/app/model/agent.js +702 -0
  126. package/app/model/checklist.js +137 -0
  127. package/app/model/derivation.js +699 -0
  128. package/app/model/index-generator.js +71 -0
  129. package/app/model/interview.js +539 -0
  130. package/app/model/job.js +222 -0
  131. package/app/model/levels.js +591 -0
  132. package/app/model/loader.js +564 -0
  133. package/app/model/matching.js +858 -0
  134. package/app/model/modifiers.js +158 -0
  135. package/app/model/profile.js +266 -0
  136. package/app/model/progression.js +507 -0
  137. package/app/model/validation.js +1385 -0
  138. package/app/pages/agent-builder.js +823 -0
  139. package/app/pages/assessment-results.js +507 -0
  140. package/app/pages/behaviour.js +70 -0
  141. package/app/pages/discipline.js +71 -0
  142. package/app/pages/driver.js +106 -0
  143. package/app/pages/grade.js +117 -0
  144. package/app/pages/interview-builder.js +50 -0
  145. package/app/pages/interview.js +304 -0
  146. package/app/pages/job-builder.js +50 -0
  147. package/app/pages/job.js +58 -0
  148. package/app/pages/landing.js +305 -0
  149. package/app/pages/progress-builder.js +58 -0
  150. package/app/pages/progress.js +495 -0
  151. package/app/pages/self-assessment.js +729 -0
  152. package/app/pages/skill.js +113 -0
  153. package/app/pages/stage.js +231 -0
  154. package/app/pages/track.js +69 -0
  155. package/app/slide-main.js +360 -0
  156. package/app/slides/behaviour.js +38 -0
  157. package/app/slides/chapter.js +82 -0
  158. package/app/slides/discipline.js +40 -0
  159. package/app/slides/driver.js +39 -0
  160. package/app/slides/grade.js +32 -0
  161. package/app/slides/index.js +198 -0
  162. package/app/slides/interview.js +58 -0
  163. package/app/slides/job.js +55 -0
  164. package/app/slides/overview.js +126 -0
  165. package/app/slides/progress.js +83 -0
  166. package/app/slides/skill.js +40 -0
  167. package/app/slides/track.js +39 -0
  168. package/app/slides.html +56 -0
  169. package/app/types.js +147 -0
  170. package/bin/pathway.js +489 -0
  171. package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
  172. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
  173. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
  174. package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
  175. package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
  176. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
  177. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
  178. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
  179. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
  180. package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
  181. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
  182. package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
  183. package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
  184. package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
  185. package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
  186. package/examples/agents/.vscode/settings.json +8 -0
  187. package/examples/behaviours/_index.yaml +8 -0
  188. package/examples/behaviours/outcome_ownership.yaml +44 -0
  189. package/examples/behaviours/polymathic_knowledge.yaml +42 -0
  190. package/examples/behaviours/precise_communication.yaml +40 -0
  191. package/examples/behaviours/relentless_curiosity.yaml +38 -0
  192. package/examples/behaviours/systems_thinking.yaml +41 -0
  193. package/examples/capabilities/_index.yaml +8 -0
  194. package/examples/capabilities/business.yaml +251 -0
  195. package/examples/capabilities/delivery.yaml +352 -0
  196. package/examples/capabilities/people.yaml +100 -0
  197. package/examples/capabilities/reliability.yaml +318 -0
  198. package/examples/capabilities/scale.yaml +394 -0
  199. package/examples/disciplines/_index.yaml +5 -0
  200. package/examples/disciplines/data_engineering.yaml +76 -0
  201. package/examples/disciplines/software_engineering.yaml +76 -0
  202. package/examples/drivers.yaml +205 -0
  203. package/examples/framework.yaml +58 -0
  204. package/examples/grades.yaml +118 -0
  205. package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
  206. package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
  207. package/examples/questions/behaviours/precise_communication.yaml +55 -0
  208. package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
  209. package/examples/questions/behaviours/systems_thinking.yaml +53 -0
  210. package/examples/questions/skills/architecture_design.yaml +54 -0
  211. package/examples/questions/skills/cloud_platforms.yaml +48 -0
  212. package/examples/questions/skills/code_quality.yaml +49 -0
  213. package/examples/questions/skills/data_modeling.yaml +46 -0
  214. package/examples/questions/skills/devops.yaml +47 -0
  215. package/examples/questions/skills/full_stack_development.yaml +48 -0
  216. package/examples/questions/skills/sre_practices.yaml +44 -0
  217. package/examples/questions/skills/stakeholder_management.yaml +49 -0
  218. package/examples/questions/skills/team_collaboration.yaml +43 -0
  219. package/examples/questions/skills/technical_writing.yaml +43 -0
  220. package/examples/self-assessments.yaml +66 -0
  221. package/examples/stages.yaml +76 -0
  222. package/examples/tracks/_index.yaml +6 -0
  223. package/examples/tracks/manager.yaml +53 -0
  224. package/examples/tracks/platform.yaml +54 -0
  225. package/examples/tracks/sre.yaml +58 -0
  226. package/examples/vscode-settings.yaml +22 -0
  227. package/package.json +68 -0
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Agent Command
3
+ *
4
+ * CLI command for generating AI coding agent configurations
5
+ * from Engineering Pathway data.
6
+ *
7
+ * All agents are stage-specific. Use --stage for a single stage
8
+ * or --all-stages (default) for all stages.
9
+ *
10
+ * Usage:
11
+ * npx pathway agent <discipline> <track> [--output=PATH] [--preview]
12
+ * npx pathway agent <discipline> <track> --stage=plan
13
+ * npx pathway agent <discipline> <track> --all-stages
14
+ * npx pathway agent --list
15
+ *
16
+ * Examples:
17
+ * npx pathway agent software_engineering platform
18
+ * npx pathway agent software_engineering platform --stage=plan
19
+ * npx pathway agent software_engineering platform --preview
20
+ */
21
+
22
+ import { writeFile, mkdir, readFile } from "fs/promises";
23
+ import { join, dirname } from "path";
24
+ import { existsSync } from "fs";
25
+ import { loadAgentData, loadSkillsWithAgentData } from "../model/loader.js";
26
+ import {
27
+ generateStageAgentProfile,
28
+ validateAgentProfile,
29
+ validateAgentSkill,
30
+ deriveReferenceGrade,
31
+ deriveAgentSkills,
32
+ generateSkillMd,
33
+ } from "../model/agent.js";
34
+ import {
35
+ formatAgentProfile,
36
+ formatAgentProfileForCli,
37
+ } from "../formatters/agent/profile.js";
38
+ import { formatAgentSkill } from "../formatters/agent/skill.js";
39
+ import { formatError, formatSuccess } from "../lib/cli-output.js";
40
+
41
+ /**
42
+ * Ensure directory exists for a file path
43
+ * @param {string} filePath - Full file path
44
+ */
45
+ async function ensureDir(filePath) {
46
+ await mkdir(dirname(filePath), { recursive: true });
47
+ }
48
+
49
+ /**
50
+ * Generate VS Code settings file with required settings
51
+ * Merges with existing settings if file exists
52
+ * @param {string} baseDir - Base output directory
53
+ * @param {Object} vscodeSettings - Settings loaded from data
54
+ */
55
+ async function generateVSCodeSettings(baseDir, vscodeSettings) {
56
+ const settingsPath = join(baseDir, ".vscode", "settings.json");
57
+
58
+ let settings = {};
59
+ if (existsSync(settingsPath)) {
60
+ const content = await readFile(settingsPath, "utf-8");
61
+ settings = JSON.parse(content);
62
+ }
63
+
64
+ const merged = { ...settings, ...vscodeSettings };
65
+
66
+ await ensureDir(settingsPath);
67
+ await writeFile(
68
+ settingsPath,
69
+ JSON.stringify(merged, null, 2) + "\n",
70
+ "utf-8",
71
+ );
72
+ console.log(formatSuccess(`Updated: ${settingsPath}`));
73
+ }
74
+
75
+ /**
76
+ * Show agent summary with stats
77
+ * @param {Object} data - Pathway data
78
+ * @param {Object} agentData - Agent-specific data
79
+ * @param {Array} skillsWithAgent - Skills with agent sections
80
+ */
81
+ function showAgentSummary(data, agentData, skillsWithAgent) {
82
+ // Count valid combinations
83
+ let validCombinations = 0;
84
+ for (const discipline of agentData.disciplines) {
85
+ for (const track of agentData.tracks) {
86
+ const humanDiscipline = data.disciplines.find(
87
+ (d) => d.id === discipline.id,
88
+ );
89
+ const humanTrack = data.tracks.find((t) => t.id === track.id);
90
+ if (humanDiscipline && humanTrack) {
91
+ validCombinations++;
92
+ }
93
+ }
94
+ }
95
+
96
+ const skillsWithAgentCount = skillsWithAgent.filter((s) => s.agent).length;
97
+
98
+ console.log(`\n🤖 Agent\n`);
99
+ console.log(
100
+ `Disciplines: ${agentData.disciplines.length}/${data.disciplines.length} with agent definitions`,
101
+ );
102
+ console.log(
103
+ `Tracks: ${agentData.tracks.length}/${data.tracks.length} with agent definitions`,
104
+ );
105
+ console.log(
106
+ `Skills: ${skillsWithAgentCount}/${skillsWithAgent.length} with agent sections`,
107
+ );
108
+ console.log(`Stages: ${data.stages.length} available`);
109
+ console.log(`\nValid combinations: ${validCombinations}`);
110
+ console.log(`\nRun 'npx pathway agent --list' for all combinations`);
111
+ console.log(
112
+ `Run 'npx pathway agent <discipline> <track>' to generate files\n`,
113
+ );
114
+ }
115
+
116
+ /**
117
+ * List available agent combinations (clean output for piping)
118
+ * @param {Object} data - Pathway data
119
+ * @param {Object} agentData - Agent-specific data
120
+ * @param {boolean} verbose - Show verbose output
121
+ */
122
+ function listAgentCombinations(data, agentData, verbose = false) {
123
+ if (!verbose) {
124
+ // Clean output for piping
125
+ for (const discipline of agentData.disciplines) {
126
+ for (const track of agentData.tracks) {
127
+ const humanDiscipline = data.disciplines.find(
128
+ (d) => d.id === discipline.id,
129
+ );
130
+ const humanTrack = data.tracks.find((t) => t.id === track.id);
131
+ if (humanDiscipline && humanTrack) {
132
+ console.log(`${discipline.id} ${track.id}`);
133
+ }
134
+ }
135
+ }
136
+ return;
137
+ }
138
+
139
+ // Verbose output
140
+ console.log("# 🤖 Available Agent Combinations\n");
141
+
142
+ const agentDisciplineIds = new Set(agentData.disciplines.map((d) => d.id));
143
+ const agentTrackIds = new Set(agentData.tracks.map((t) => t.id));
144
+
145
+ console.log("## Disciplines with agent definitions:\n");
146
+ for (const discipline of data.disciplines) {
147
+ const hasAgent = agentDisciplineIds.has(discipline.id);
148
+ const status = hasAgent ? "✅" : "⬜";
149
+ console.log(
150
+ ` ${status} ${discipline.id} - ${discipline.specialization || discipline.name}`,
151
+ );
152
+ }
153
+
154
+ console.log("\n## Tracks with agent definitions:\n");
155
+ for (const track of data.tracks) {
156
+ const hasAgent = agentTrackIds.has(track.id);
157
+ const status = hasAgent ? "✅" : "⬜";
158
+ console.log(` ${status} ${track.id} - ${track.name}`);
159
+ }
160
+
161
+ console.log("\n## Valid combinations:\n");
162
+ for (const discipline of agentData.disciplines) {
163
+ for (const track of agentData.tracks) {
164
+ const humanDiscipline = data.disciplines.find(
165
+ (d) => d.id === discipline.id,
166
+ );
167
+ const humanTrack = data.tracks.find((t) => t.id === track.id);
168
+ if (humanDiscipline && humanTrack) {
169
+ console.log(` npx pathway agent ${discipline.id} ${track.id}`);
170
+ }
171
+ }
172
+ }
173
+
174
+ console.log("\n## Available stages:\n");
175
+ for (const stage of data.stages) {
176
+ console.log(` --stage=${stage.id}: ${stage.description.split(" - ")[0]}`);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Write agent profile to file
182
+ * @param {Object} profile - Generated profile
183
+ * @param {string} baseDir - Base output directory
184
+ */
185
+ async function writeProfile(profile, baseDir) {
186
+ const profilePath = join(baseDir, ".github", "agents", profile.filename);
187
+ const profileContent = formatAgentProfile(profile);
188
+ await ensureDir(profilePath);
189
+ await writeFile(profilePath, profileContent, "utf-8");
190
+ console.log(formatSuccess(`Created: ${profilePath}`));
191
+ return profilePath;
192
+ }
193
+
194
+ /**
195
+ * Write skill files
196
+ * @param {Array} skills - Generated skills
197
+ * @param {string} baseDir - Base output directory
198
+ */
199
+ async function writeSkills(skills, baseDir) {
200
+ for (const skill of skills) {
201
+ const skillPath = join(
202
+ baseDir,
203
+ ".claude",
204
+ "skills",
205
+ skill.dirname,
206
+ "SKILL.md",
207
+ );
208
+ const skillContent = formatAgentSkill(skill);
209
+ await ensureDir(skillPath);
210
+ await writeFile(skillPath, skillContent, "utf-8");
211
+ console.log(formatSuccess(`Created: ${skillPath}`));
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Run the agent command
217
+ * @param {Object} params - Command parameters
218
+ * @param {Object} params.data - Loaded pathway data
219
+ * @param {string[]} params.args - Command arguments [discipline_id, track_id]
220
+ * @param {Object} params.options - Command options
221
+ * @param {string} params.dataDir - Path to data directory
222
+ */
223
+ export async function runAgentCommand({ data, args, options, dataDir }) {
224
+ // Load agent-specific data
225
+ const agentData = await loadAgentData(dataDir);
226
+ const skillsWithAgent = await loadSkillsWithAgentData(dataDir);
227
+
228
+ // --list: Output clean lines for piping
229
+ if (options.list) {
230
+ listAgentCombinations(data, agentData, false);
231
+ return;
232
+ }
233
+
234
+ // No args: Show summary
235
+ if (args.length === 0) {
236
+ showAgentSummary(data, agentData, skillsWithAgent);
237
+ return;
238
+ }
239
+
240
+ const [disciplineId, trackId] = args;
241
+
242
+ if (!disciplineId || !trackId) {
243
+ console.error(
244
+ formatError("Usage: npx pathway agent <discipline_id> <track_id>"),
245
+ );
246
+ console.error(
247
+ "\nRun 'npx pathway agent --list' to see available combinations.",
248
+ );
249
+ process.exit(1);
250
+ }
251
+
252
+ // Look up human definitions
253
+ const humanDiscipline = data.disciplines.find((d) => d.id === disciplineId);
254
+ const humanTrack = data.tracks.find((t) => t.id === trackId);
255
+
256
+ if (!humanDiscipline) {
257
+ console.error(formatError(`Unknown discipline: ${disciplineId}`));
258
+ console.error("\nAvailable disciplines:");
259
+ for (const d of data.disciplines) {
260
+ console.error(` - ${d.id}`);
261
+ }
262
+ process.exit(1);
263
+ }
264
+
265
+ if (!humanTrack) {
266
+ console.error(formatError(`Unknown track: ${trackId}`));
267
+ console.error("\nAvailable tracks:");
268
+ for (const t of data.tracks) {
269
+ console.error(` - ${t.id}`);
270
+ }
271
+ process.exit(1);
272
+ }
273
+
274
+ // Look up agent definitions
275
+ const agentDiscipline = agentData.disciplines.find(
276
+ (d) => d.id === disciplineId,
277
+ );
278
+ const agentTrack = agentData.tracks.find((t) => t.id === trackId);
279
+
280
+ if (!agentDiscipline) {
281
+ console.error(
282
+ formatError(`No agent definition for discipline: ${disciplineId}`),
283
+ );
284
+ console.error("\nAgent definitions exist for:");
285
+ for (const d of agentData.disciplines) {
286
+ console.error(` - ${d.id}`);
287
+ }
288
+ process.exit(1);
289
+ }
290
+
291
+ if (!agentTrack) {
292
+ console.error(formatError(`No agent definition for track: ${trackId}`));
293
+ console.error("\nAgent definitions exist for:");
294
+ for (const t of agentData.tracks) {
295
+ console.error(` - ${t.id}`);
296
+ }
297
+ process.exit(1);
298
+ }
299
+
300
+ // Get reference grade for derivation
301
+ const grade = deriveReferenceGrade(data.grades);
302
+
303
+ const baseDir = options.output || ".";
304
+
305
+ // Common params for stage-based generation
306
+ const stageParams = {
307
+ discipline: humanDiscipline,
308
+ track: humanTrack,
309
+ grade,
310
+ skills: skillsWithAgent,
311
+ behaviours: data.behaviours,
312
+ agentBehaviours: agentData.behaviours,
313
+ agentDiscipline,
314
+ agentTrack,
315
+ capabilities: data.capabilities,
316
+ stages: data.stages,
317
+ };
318
+
319
+ // Handle --stage flag for single stage agent
320
+ if (options.stage) {
321
+ const stage = data.stages.find((s) => s.id === options.stage);
322
+ if (!stage) {
323
+ console.error(formatError(`Unknown stage: ${options.stage}`));
324
+ console.error("\nAvailable stages:");
325
+ for (const s of data.stages) {
326
+ console.error(` - ${s.id}`);
327
+ }
328
+ process.exit(1);
329
+ }
330
+
331
+ const profile = generateStageAgentProfile({ ...stageParams, stage });
332
+
333
+ // Validate
334
+ const errors = validateAgentProfile(profile);
335
+ if (errors.length > 0) {
336
+ console.error(formatError("Profile validation failed:"));
337
+ for (const err of errors) {
338
+ console.error(` - ${err}`);
339
+ }
340
+ process.exit(1);
341
+ }
342
+
343
+ // Preview or write
344
+ if (options.preview) {
345
+ console.log(formatAgentProfileForCli(profile));
346
+ return;
347
+ }
348
+
349
+ await writeProfile(profile, baseDir);
350
+ await generateVSCodeSettings(baseDir, agentData.vscodeSettings);
351
+ console.log("");
352
+ console.log(
353
+ formatSuccess(`Generated stage agent: ${profile.frontmatter.name}`),
354
+ );
355
+ return;
356
+ }
357
+
358
+ // Default behavior: generate all stage agents (or single stage if --stage specified)
359
+ // No generic agents - all agents are stage-specific
360
+ const profiles = [];
361
+
362
+ // Generate all stage agents
363
+ for (const stage of data.stages) {
364
+ const profile = generateStageAgentProfile({ ...stageParams, stage });
365
+ profiles.push(profile);
366
+ }
367
+
368
+ // Derive skills once for all stages
369
+ const derivedSkills = deriveAgentSkills({
370
+ discipline: humanDiscipline,
371
+ track: humanTrack,
372
+ grade,
373
+ skills: skillsWithAgent,
374
+ });
375
+
376
+ const skillFiles = derivedSkills
377
+ .map((derived) => skillsWithAgent.find((s) => s.id === derived.skillId))
378
+ .filter((skill) => skill?.agent)
379
+ .map((skill) => generateSkillMd(skill));
380
+
381
+ // Validate all profiles
382
+ for (const profile of profiles) {
383
+ const errors = validateAgentProfile(profile);
384
+ if (errors.length > 0) {
385
+ console.error(
386
+ formatError(`Profile ${profile.frontmatter.name} validation failed:`),
387
+ );
388
+ for (const err of errors) {
389
+ console.error(` - ${err}`);
390
+ }
391
+ process.exit(1);
392
+ }
393
+ }
394
+
395
+ // Validate all skills
396
+ for (const skill of skillFiles) {
397
+ const errors = validateAgentSkill(skill);
398
+ if (errors.length > 0) {
399
+ console.error(
400
+ formatError(`Skill ${skill.frontmatter.name} validation failed:`),
401
+ );
402
+ for (const err of errors) {
403
+ console.error(` - ${err}`);
404
+ }
405
+ process.exit(1);
406
+ }
407
+ }
408
+
409
+ // Preview or write
410
+ if (options.preview) {
411
+ for (const profile of profiles) {
412
+ console.log(formatAgentProfileForCli(profile));
413
+ console.log("\n---\n");
414
+ }
415
+ return;
416
+ }
417
+
418
+ for (const profile of profiles) {
419
+ await writeProfile(profile, baseDir);
420
+ }
421
+ await writeSkills(skillFiles, baseDir);
422
+ await generateVSCodeSettings(baseDir, agentData.vscodeSettings);
423
+
424
+ console.log("");
425
+ console.log(formatSuccess(`Generated ${profiles.length} agents:`));
426
+ for (const profile of profiles) {
427
+ console.log(` - ${profile.frontmatter.name}`);
428
+ }
429
+ console.log(` Skills: ${skillFiles.length} files`);
430
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Behaviour CLI Command
3
+ *
4
+ * Handles behaviour summary, listing, and detail display in the terminal.
5
+ *
6
+ * Usage:
7
+ * npx pathway behaviour # Summary with stats
8
+ * npx pathway behaviour --list # IDs only (for piping)
9
+ * npx pathway behaviour <id> # Detail view
10
+ * npx pathway behaviour --validate # Validation checks
11
+ */
12
+
13
+ import { createEntityCommand } from "./command-factory.js";
14
+ import { behaviourToMarkdown } from "../formatters/behaviour/markdown.js";
15
+ import { formatTable } from "../lib/cli-output.js";
16
+
17
+ /**
18
+ * Format behaviour summary output
19
+ * @param {Array} behaviours - Raw behaviour entities
20
+ * @param {Object} data - Full data context
21
+ */
22
+ function formatSummary(behaviours, data) {
23
+ const { drivers } = data;
24
+
25
+ console.log(`\n🧠 Behaviours\n`);
26
+
27
+ // Summary table
28
+ const rows = behaviours.map((b) => {
29
+ const linkedDrivers = drivers.filter((d) =>
30
+ d.contributingBehaviours?.includes(b.id),
31
+ ).length;
32
+ return [b.id, b.name, linkedDrivers];
33
+ });
34
+
35
+ console.log(formatTable(["ID", "Name", "Drivers"], rows));
36
+ console.log(`\nTotal: ${behaviours.length} behaviours`);
37
+ console.log(`\nRun 'npx pathway behaviour --list' for IDs`);
38
+ console.log(`Run 'npx pathway behaviour <id>' for details\n`);
39
+ }
40
+
41
+ /**
42
+ * Format behaviour detail output
43
+ * @param {Object} viewAndContext - Contains behaviour entity and context
44
+ */
45
+ function formatDetail(viewAndContext) {
46
+ const { behaviour, drivers } = viewAndContext;
47
+ console.log(behaviourToMarkdown(behaviour, { drivers }));
48
+ }
49
+
50
+ export const runBehaviourCommand = createEntityCommand({
51
+ entityName: "behaviour",
52
+ pluralName: "behaviours",
53
+ findEntity: (data, id) => data.behaviours.find((b) => b.id === id),
54
+ presentDetail: (entity, data) => ({
55
+ behaviour: entity,
56
+ drivers: data.drivers,
57
+ }),
58
+ formatSummary,
59
+ formatDetail,
60
+ emoji: "🧠",
61
+ });
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Command Factory
3
+ *
4
+ * Provides factory functions to create CLI commands with standard behavior.
5
+ * Reduces boilerplate and ensures consistency across commands.
6
+ *
7
+ * All entity commands support three modes:
8
+ * - Base (no args): Concise summary with stats
9
+ * - --list: Clean newline-separated list of IDs (for piping)
10
+ * - <id>: Detailed entity view
11
+ * - --validate: Run data validation checks
12
+ */
13
+
14
+ import { capitalize } from "../formatters/shared.js";
15
+
16
+ /**
17
+ * Create an entity command with standard behavior
18
+ * @param {Object} config - Command configuration
19
+ * @param {string} config.entityName - Entity name (singular, e.g., 'skill')
20
+ * @param {string} config.pluralName - Entity name (plural, e.g., 'skills')
21
+ * @param {Function} config.findEntity - Function to find entity by ID: (data, id) => entity
22
+ * @param {Function} config.presentDetail - Function to present detail: (entity, data, options) => view
23
+ * @param {Function} config.formatSummary - Function to format summary output: (items, data) => void
24
+ * @param {Function} config.formatDetail - Function to format detail output: (view, framework) => void
25
+ * @param {Function} [config.sortItems] - Optional function to sort items: (items) => sortedItems
26
+ * @param {Function} [config.validate] - Optional validation function: (data) => {errors: [], warnings: []}
27
+ * @param {string} [config.emoji] - Optional emoji for the entity
28
+ * @returns {Function} Command handler
29
+ */
30
+ export function createEntityCommand({
31
+ entityName,
32
+ pluralName,
33
+ findEntity,
34
+ presentDetail,
35
+ formatSummary,
36
+ formatDetail,
37
+ sortItems,
38
+ validate,
39
+ _emoji = "",
40
+ }) {
41
+ return async function runCommand({ data, args, options }) {
42
+ const [id] = args;
43
+ const rawItems = data[pluralName];
44
+ const items = sortItems ? sortItems(rawItems) : rawItems;
45
+
46
+ // --validate: Run validation checks
47
+ if (options.validate) {
48
+ return handleValidate({ data, entityName, pluralName, validate });
49
+ }
50
+
51
+ // --list: Output clean newline-separated IDs for piping
52
+ if (options.list) {
53
+ for (const item of items) {
54
+ console.log(item.id);
55
+ }
56
+ return;
57
+ }
58
+
59
+ // No args: Show summary
60
+ if (!id) {
61
+ if (options.json) {
62
+ console.log(JSON.stringify(items, null, 2));
63
+ return;
64
+ }
65
+ formatSummary(items, data);
66
+ return;
67
+ }
68
+
69
+ // With ID: Show detail
70
+ return handleDetail({
71
+ data,
72
+ id,
73
+ options,
74
+ entityName,
75
+ pluralName,
76
+ findEntity,
77
+ presentDetail,
78
+ formatDetail,
79
+ });
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Handle validation for an entity type
85
+ * @param {Object} params
86
+ */
87
+ function handleValidate({ data, _entityName, pluralName, validate }) {
88
+ if (!validate) {
89
+ console.log(`No specific validation for ${pluralName}.`);
90
+ console.log(`Run 'npx pathway --validate' for full data validation.`);
91
+ return;
92
+ }
93
+
94
+ const result = validate(data);
95
+ const { errors = [], warnings = [] } = result;
96
+
97
+ if (errors.length === 0 && warnings.length === 0) {
98
+ console.log(`✅ ${capitalize(pluralName)} validation passed`);
99
+ return;
100
+ }
101
+
102
+ if (warnings.length > 0) {
103
+ console.log(`⚠️ Warnings:`);
104
+ for (const w of warnings) {
105
+ console.log(` - ${w}`);
106
+ }
107
+ }
108
+
109
+ if (errors.length > 0) {
110
+ console.log(`❌ Errors:`);
111
+ for (const e of errors) {
112
+ console.log(` - ${e}`);
113
+ }
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Handle detail view for an entity
120
+ * @param {Object} params
121
+ */
122
+ function handleDetail({
123
+ data,
124
+ id,
125
+ options,
126
+ entityName,
127
+ pluralName,
128
+ findEntity,
129
+ presentDetail,
130
+ formatDetail,
131
+ }) {
132
+ const entity = findEntity(data, id);
133
+
134
+ if (!entity) {
135
+ console.error(`${capitalize(entityName)} not found: ${id}`);
136
+ console.error(`Available: ${data[pluralName].map((e) => e.id).join(", ")}`);
137
+ process.exit(1);
138
+ }
139
+
140
+ const view = presentDetail(entity, data, options);
141
+
142
+ if (!view) {
143
+ console.error(`Failed to present ${entityName}: ${id}`);
144
+ process.exit(1);
145
+ }
146
+
147
+ if (options.json) {
148
+ console.log(JSON.stringify(view, null, 2));
149
+ return;
150
+ }
151
+
152
+ formatDetail(view, data.framework);
153
+ }
154
+
155
+ /**
156
+ * Create a composite command for multi-entity operations (job, interview, progress)
157
+ * @param {Object} config - Command configuration
158
+ * @param {string} config.commandName - Command name for error messages
159
+ * @param {string[]} config.requiredArgs - Array of required argument names
160
+ * @param {Function} config.findEntities - Function to find entities: (data, args) => entities object
161
+ * @param {Function} config.validateEntities - Function to validate entities: (entities, data) => error string | null
162
+ * @param {Function} config.presenter - Function to present data: (entities, data, options) => view
163
+ * @param {Function} config.formatter - Function to format output: (view, options, data) => void
164
+ * @param {string} [config.usageExample] - Optional usage example
165
+ * @returns {Function} Command handler
166
+ */
167
+ export function createCompositeCommand({
168
+ commandName,
169
+ requiredArgs,
170
+ findEntities,
171
+ validateEntities,
172
+ presenter,
173
+ formatter,
174
+ usageExample,
175
+ }) {
176
+ return async function runCommand({ data, args, options }) {
177
+ if (args.length < requiredArgs.length) {
178
+ const argsList = requiredArgs.map((arg) => `<${arg}>`).join(" ");
179
+ console.error(`Usage: npx pathway ${commandName} ${argsList}`);
180
+ if (usageExample) {
181
+ console.error(`Example: ${usageExample}`);
182
+ }
183
+ process.exit(1);
184
+ }
185
+
186
+ const entities = findEntities(data, args, options);
187
+ const validationError = validateEntities(entities, data);
188
+
189
+ if (validationError) {
190
+ console.error(validationError);
191
+ process.exit(1);
192
+ }
193
+
194
+ const view = presenter(entities, data, options);
195
+
196
+ if (!view) {
197
+ console.error(`Failed to generate ${commandName} output.`);
198
+ process.exit(1);
199
+ }
200
+
201
+ if (options.json) {
202
+ console.log(JSON.stringify(view, null, 2));
203
+ return;
204
+ }
205
+
206
+ formatter(view, options, data);
207
+ };
208
+ }
209
+
210
+ // Legacy alias for backward compatibility during refactor
211
+ export const createListDetailCommand = createEntityCommand;