@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,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;
|