@forwardimpact/pathway 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/commands/agent.js +109 -21
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +43 -29
- package/app/commands/progress.js +14 -7
- package/app/commands/serve.js +5 -0
- package/app/commands/stage.js +0 -10
- package/app/commands/track.js +5 -8
- package/app/components/builder.js +111 -27
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +113 -87
- package/app/formatters/agent/skill.js +64 -31
- package/app/formatters/behaviour/dom.js +3 -0
- package/app/formatters/behaviour/microdata.js +106 -0
- package/app/formatters/discipline/dom.js +28 -1
- package/app/formatters/discipline/microdata.js +117 -0
- package/app/formatters/discipline/shared.js +49 -8
- package/app/formatters/driver/dom.js +3 -0
- package/app/formatters/driver/microdata.js +91 -0
- package/app/formatters/grade/dom.js +3 -0
- package/app/formatters/grade/microdata.js +151 -0
- package/app/formatters/index.js +32 -1
- package/app/formatters/interview/shared.js +13 -8
- package/app/formatters/job/description.js +5 -3
- package/app/formatters/json-ld.js +242 -0
- package/app/formatters/microdata-shared.js +184 -0
- package/app/formatters/progress/shared.js +14 -11
- package/app/formatters/skill/dom.js +3 -0
- package/app/formatters/skill/microdata.js +151 -0
- package/app/formatters/stage/dom.js +3 -18
- package/app/formatters/stage/microdata.js +110 -0
- package/app/formatters/stage/shared.js +0 -27
- package/app/formatters/track/dom.js +5 -30
- package/app/formatters/track/markdown.js +2 -25
- package/app/formatters/track/microdata.js +111 -0
- package/app/formatters/track/shared.js +6 -58
- package/app/handout-main.js +26 -12
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/job-cache.js +12 -9
- package/app/lib/template-loader.js +66 -0
- package/app/lib/yaml-loader.js +25 -8
- package/app/main.js +8 -4
- package/app/model/agent.js +158 -130
- package/app/model/checklist.js +57 -91
- package/app/model/derivation.js +135 -68
- package/app/model/index-generator.js +1 -7
- package/app/model/job.js +19 -13
- package/app/model/levels.js +20 -12
- package/app/model/loader.js +41 -17
- package/app/model/matching.js +33 -3
- package/app/model/profile.js +38 -45
- package/app/model/schema-validation.js +438 -0
- package/app/model/validation.js +747 -68
- package/app/pages/agent-builder.js +119 -25
- package/app/pages/assessment-results.js +10 -4
- package/app/pages/discipline.js +36 -6
- package/app/pages/driver.js +9 -47
- package/app/pages/interview-builder.js +3 -1
- package/app/pages/interview.js +15 -4
- package/app/pages/job-builder.js +4 -1
- package/app/pages/job.js +15 -4
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +72 -21
- package/app/pages/stage.js +3 -126
- package/app/slide-main.js +45 -17
- package/app/slides/index.js +3 -1
- package/app/slides/overview.js +40 -4
- package/app/slides/progress.js +4 -2
- package/bin/pathway.js +18 -64
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +58 -16
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +59 -18
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +58 -17
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +64 -18
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +59 -15
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +64 -18
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +58 -17
- package/examples/agents/.github/agents/se-platform-code.agent.md +39 -88
- package/examples/agents/.github/agents/se-platform-plan.agent.md +41 -88
- package/examples/agents/.github/agents/se-platform-review.agent.md +38 -15
- package/examples/agents/.vscode/settings.json +1 -1
- package/examples/behaviours/outcome_ownership.yaml +1 -2
- package/examples/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/behaviours/precise_communication.yaml +1 -2
- package/examples/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/behaviours/systems_thinking.yaml +1 -2
- package/examples/capabilities/business.yaml +80 -142
- package/examples/capabilities/delivery.yaml +155 -219
- package/examples/capabilities/people.yaml +2 -34
- package/examples/capabilities/reliability.yaml +161 -80
- package/examples/capabilities/scale.yaml +234 -252
- package/examples/copilot-setup-steps.yaml +25 -0
- package/examples/devcontainer.yaml +21 -0
- package/examples/disciplines/_index.yaml +1 -0
- package/examples/disciplines/data_engineering.yaml +14 -12
- package/examples/disciplines/engineering_management.yaml +63 -0
- package/examples/disciplines/software_engineering.yaml +14 -12
- package/examples/drivers.yaml +1 -4
- package/examples/framework.yaml +1 -2
- package/examples/grades.yaml +1 -3
- package/examples/questions/behaviours/outcome_ownership.yaml +1 -2
- package/examples/questions/behaviours/polymathic_knowledge.yaml +1 -2
- package/examples/questions/behaviours/precise_communication.yaml +1 -2
- package/examples/questions/behaviours/relentless_curiosity.yaml +1 -2
- package/examples/questions/behaviours/systems_thinking.yaml +1 -2
- package/examples/questions/skills/architecture_design.yaml +1 -2
- package/examples/questions/skills/cloud_platforms.yaml +1 -2
- package/examples/questions/skills/code_quality.yaml +1 -2
- package/examples/questions/skills/data_modeling.yaml +1 -2
- package/examples/questions/skills/devops.yaml +1 -2
- package/examples/questions/skills/full_stack_development.yaml +1 -2
- package/examples/questions/skills/sre_practices.yaml +1 -2
- package/examples/questions/skills/stakeholder_management.yaml +1 -2
- package/examples/questions/skills/team_collaboration.yaml +1 -2
- package/examples/questions/skills/technical_writing.yaml +1 -2
- package/examples/self-assessments.yaml +1 -3
- package/examples/stages.yaml +101 -46
- package/examples/tracks/_index.yaml +0 -1
- package/examples/tracks/platform.yaml +8 -13
- package/examples/tracks/sre.yaml +8 -18
- package/examples/vscode-settings.yaml +2 -7
- package/package.json +9 -3
- package/templates/agent.template.md +65 -0
- package/templates/skill.template.md +28 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +0 -99
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +0 -99
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +0 -100
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +0 -102
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +0 -129
- package/examples/tracks/manager.yaml +0 -53
package/app/commands/agent.js
CHANGED
|
@@ -8,20 +8,21 @@
|
|
|
8
8
|
* or --all-stages (default) for all stages.
|
|
9
9
|
*
|
|
10
10
|
* Usage:
|
|
11
|
-
* npx pathway agent <discipline>
|
|
12
|
-
* npx pathway agent <discipline>
|
|
13
|
-
* npx pathway agent <discipline>
|
|
11
|
+
* npx pathway agent <discipline> [--track=<track>] [--output=PATH] [--preview]
|
|
12
|
+
* npx pathway agent <discipline> --track=<track> --stage=plan
|
|
13
|
+
* npx pathway agent <discipline> --track=<track> --all-stages
|
|
14
14
|
* npx pathway agent --list
|
|
15
15
|
*
|
|
16
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
|
|
17
|
+
* npx pathway agent software_engineering --track=platform
|
|
18
|
+
* npx pathway agent software_engineering --track=platform --stage=plan
|
|
19
|
+
* npx pathway agent software_engineering --track=platform --preview
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { writeFile, mkdir, readFile } from "fs/promises";
|
|
23
23
|
import { join, dirname } from "path";
|
|
24
24
|
import { existsSync } from "fs";
|
|
25
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
25
26
|
import { loadAgentData, loadSkillsWithAgentData } from "../model/loader.js";
|
|
26
27
|
import {
|
|
27
28
|
generateStageAgentProfile,
|
|
@@ -37,6 +38,10 @@ import {
|
|
|
37
38
|
} from "../formatters/agent/profile.js";
|
|
38
39
|
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
39
40
|
import { formatError, formatSuccess } from "../lib/cli-output.js";
|
|
41
|
+
import {
|
|
42
|
+
loadAgentTemplate,
|
|
43
|
+
loadSkillTemplate,
|
|
44
|
+
} from "../lib/template-loader.js";
|
|
40
45
|
|
|
41
46
|
/**
|
|
42
47
|
* Ensure directory exists for a file path
|
|
@@ -72,6 +77,64 @@ async function generateVSCodeSettings(baseDir, vscodeSettings) {
|
|
|
72
77
|
console.log(formatSuccess(`Updated: ${settingsPath}`));
|
|
73
78
|
}
|
|
74
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Generate devcontainer.json from template with VS Code settings embedded
|
|
82
|
+
* @param {string} baseDir - Base output directory
|
|
83
|
+
* @param {Object} devcontainerConfig - Devcontainer config loaded from data
|
|
84
|
+
* @param {Object} vscodeSettings - VS Code settings to embed in customizations
|
|
85
|
+
*/
|
|
86
|
+
async function generateDevcontainer(
|
|
87
|
+
baseDir,
|
|
88
|
+
devcontainerConfig,
|
|
89
|
+
vscodeSettings,
|
|
90
|
+
) {
|
|
91
|
+
if (!devcontainerConfig || Object.keys(devcontainerConfig).length === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const devcontainerPath = join(baseDir, ".devcontainer", "devcontainer.json");
|
|
96
|
+
|
|
97
|
+
// Build devcontainer.json with VS Code settings embedded
|
|
98
|
+
const devcontainer = {
|
|
99
|
+
...devcontainerConfig,
|
|
100
|
+
customizations: {
|
|
101
|
+
vscode: {
|
|
102
|
+
settings: vscodeSettings,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
await ensureDir(devcontainerPath);
|
|
108
|
+
await writeFile(
|
|
109
|
+
devcontainerPath,
|
|
110
|
+
JSON.stringify(devcontainer, null, 2) + "\n",
|
|
111
|
+
"utf-8",
|
|
112
|
+
);
|
|
113
|
+
console.log(formatSuccess(`Created: ${devcontainerPath}`));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate GitHub Actions workflow for Copilot Coding Agent setup steps
|
|
118
|
+
* @param {string} baseDir - Base output directory
|
|
119
|
+
* @param {Object|null} copilotSetupSteps - Workflow config loaded from data
|
|
120
|
+
*/
|
|
121
|
+
async function generateCopilotSetupSteps(baseDir, copilotSetupSteps) {
|
|
122
|
+
if (!copilotSetupSteps) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const workflowPath = join(
|
|
127
|
+
baseDir,
|
|
128
|
+
".github",
|
|
129
|
+
"workflows",
|
|
130
|
+
"copilot-setup-steps.yml",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
await ensureDir(workflowPath);
|
|
134
|
+
await writeFile(workflowPath, stringifyYaml(copilotSetupSteps), "utf-8");
|
|
135
|
+
console.log(formatSuccess(`Created: ${workflowPath}`));
|
|
136
|
+
}
|
|
137
|
+
|
|
75
138
|
/**
|
|
76
139
|
* Show agent summary with stats
|
|
77
140
|
* @param {Object} data - Pathway data
|
|
@@ -181,10 +244,11 @@ function listAgentCombinations(data, agentData, verbose = false) {
|
|
|
181
244
|
* Write agent profile to file
|
|
182
245
|
* @param {Object} profile - Generated profile
|
|
183
246
|
* @param {string} baseDir - Base output directory
|
|
247
|
+
* @param {string} template - Mustache template for agent profile
|
|
184
248
|
*/
|
|
185
|
-
async function writeProfile(profile, baseDir) {
|
|
249
|
+
async function writeProfile(profile, baseDir, template) {
|
|
186
250
|
const profilePath = join(baseDir, ".github", "agents", profile.filename);
|
|
187
|
-
const profileContent = formatAgentProfile(profile);
|
|
251
|
+
const profileContent = formatAgentProfile(profile, template);
|
|
188
252
|
await ensureDir(profilePath);
|
|
189
253
|
await writeFile(profilePath, profileContent, "utf-8");
|
|
190
254
|
console.log(formatSuccess(`Created: ${profilePath}`));
|
|
@@ -195,8 +259,9 @@ async function writeProfile(profile, baseDir) {
|
|
|
195
259
|
* Write skill files
|
|
196
260
|
* @param {Array} skills - Generated skills
|
|
197
261
|
* @param {string} baseDir - Base output directory
|
|
262
|
+
* @param {string} template - Mustache template for skills
|
|
198
263
|
*/
|
|
199
|
-
async function writeSkills(skills, baseDir) {
|
|
264
|
+
async function writeSkills(skills, baseDir, template) {
|
|
200
265
|
for (const skill of skills) {
|
|
201
266
|
const skillPath = join(
|
|
202
267
|
baseDir,
|
|
@@ -205,7 +270,7 @@ async function writeSkills(skills, baseDir) {
|
|
|
205
270
|
skill.dirname,
|
|
206
271
|
"SKILL.md",
|
|
207
272
|
);
|
|
208
|
-
const skillContent = formatAgentSkill(skill);
|
|
273
|
+
const skillContent = formatAgentSkill(skill, template);
|
|
209
274
|
await ensureDir(skillPath);
|
|
210
275
|
await writeFile(skillPath, skillContent, "utf-8");
|
|
211
276
|
console.log(formatSuccess(`Created: ${skillPath}`));
|
|
@@ -216,7 +281,7 @@ async function writeSkills(skills, baseDir) {
|
|
|
216
281
|
* Run the agent command
|
|
217
282
|
* @param {Object} params - Command parameters
|
|
218
283
|
* @param {Object} params.data - Loaded pathway data
|
|
219
|
-
* @param {string[]} params.args - Command arguments [discipline_id
|
|
284
|
+
* @param {string[]} params.args - Command arguments [discipline_id]
|
|
220
285
|
* @param {Object} params.options - Command options
|
|
221
286
|
* @param {string} params.dataDir - Path to data directory
|
|
222
287
|
*/
|
|
@@ -237,11 +302,14 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
237
302
|
return;
|
|
238
303
|
}
|
|
239
304
|
|
|
240
|
-
const [disciplineId
|
|
305
|
+
const [disciplineId] = args;
|
|
306
|
+
const trackId = options.track;
|
|
241
307
|
|
|
242
|
-
if (!disciplineId
|
|
308
|
+
if (!disciplineId) {
|
|
243
309
|
console.error(
|
|
244
|
-
formatError(
|
|
310
|
+
formatError(
|
|
311
|
+
"Usage: npx pathway agent <discipline_id> [--track=<track_id>]",
|
|
312
|
+
),
|
|
245
313
|
);
|
|
246
314
|
console.error(
|
|
247
315
|
"\nRun 'npx pathway agent --list' to see available combinations.",
|
|
@@ -251,7 +319,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
251
319
|
|
|
252
320
|
// Look up human definitions
|
|
253
321
|
const humanDiscipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
254
|
-
const humanTrack = data.tracks.find((t) => t.id === trackId);
|
|
322
|
+
const humanTrack = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
255
323
|
|
|
256
324
|
if (!humanDiscipline) {
|
|
257
325
|
console.error(formatError(`Unknown discipline: ${disciplineId}`));
|
|
@@ -262,7 +330,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
262
330
|
process.exit(1);
|
|
263
331
|
}
|
|
264
332
|
|
|
265
|
-
if (!humanTrack) {
|
|
333
|
+
if (trackId && !humanTrack) {
|
|
266
334
|
console.error(formatError(`Unknown track: ${trackId}`));
|
|
267
335
|
console.error("\nAvailable tracks:");
|
|
268
336
|
for (const t of data.tracks) {
|
|
@@ -275,7 +343,9 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
275
343
|
const agentDiscipline = agentData.disciplines.find(
|
|
276
344
|
(d) => d.id === disciplineId,
|
|
277
345
|
);
|
|
278
|
-
const agentTrack =
|
|
346
|
+
const agentTrack = trackId
|
|
347
|
+
? agentData.tracks.find((t) => t.id === trackId)
|
|
348
|
+
: null;
|
|
279
349
|
|
|
280
350
|
if (!agentDiscipline) {
|
|
281
351
|
console.error(
|
|
@@ -288,7 +358,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
288
358
|
process.exit(1);
|
|
289
359
|
}
|
|
290
360
|
|
|
291
|
-
if (!agentTrack) {
|
|
361
|
+
if (trackId && !agentTrack) {
|
|
292
362
|
console.error(formatError(`No agent definition for track: ${trackId}`));
|
|
293
363
|
console.error("\nAgent definitions exist for:");
|
|
294
364
|
for (const t of agentData.tracks) {
|
|
@@ -346,8 +416,16 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
346
416
|
return;
|
|
347
417
|
}
|
|
348
418
|
|
|
349
|
-
|
|
419
|
+
// Load templates only when writing files
|
|
420
|
+
const agentTemplate = await loadAgentTemplate(dataDir);
|
|
421
|
+
await writeProfile(profile, baseDir, agentTemplate);
|
|
350
422
|
await generateVSCodeSettings(baseDir, agentData.vscodeSettings);
|
|
423
|
+
await generateDevcontainer(
|
|
424
|
+
baseDir,
|
|
425
|
+
agentData.devcontainer,
|
|
426
|
+
agentData.vscodeSettings,
|
|
427
|
+
);
|
|
428
|
+
await generateCopilotSetupSteps(baseDir, agentData.copilotSetupSteps);
|
|
351
429
|
console.log("");
|
|
352
430
|
console.log(
|
|
353
431
|
formatSuccess(`Generated stage agent: ${profile.frontmatter.name}`),
|
|
@@ -415,11 +493,21 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
415
493
|
return;
|
|
416
494
|
}
|
|
417
495
|
|
|
496
|
+
// Load templates only when writing files
|
|
497
|
+
const agentTemplate = await loadAgentTemplate(dataDir);
|
|
498
|
+
const skillTemplate = await loadSkillTemplate(dataDir);
|
|
499
|
+
|
|
418
500
|
for (const profile of profiles) {
|
|
419
|
-
await writeProfile(profile, baseDir);
|
|
501
|
+
await writeProfile(profile, baseDir, agentTemplate);
|
|
420
502
|
}
|
|
421
|
-
await writeSkills(skillFiles, baseDir);
|
|
503
|
+
await writeSkills(skillFiles, baseDir, skillTemplate);
|
|
422
504
|
await generateVSCodeSettings(baseDir, agentData.vscodeSettings);
|
|
505
|
+
await generateDevcontainer(
|
|
506
|
+
baseDir,
|
|
507
|
+
agentData.devcontainer,
|
|
508
|
+
agentData.vscodeSettings,
|
|
509
|
+
);
|
|
510
|
+
await generateCopilotSetupSteps(baseDir, agentData.copilotSetupSteps);
|
|
423
511
|
|
|
424
512
|
console.log("");
|
|
425
513
|
console.log(formatSuccess(`Generated ${profiles.length} agents:`));
|
|
@@ -157,8 +157,8 @@ function handleDetail({
|
|
|
157
157
|
* @param {Object} config - Command configuration
|
|
158
158
|
* @param {string} config.commandName - Command name for error messages
|
|
159
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
|
|
160
|
+
* @param {Function} config.findEntities - Function to find entities: (data, args, options) => entities object
|
|
161
|
+
* @param {Function} config.validateEntities - Function to validate entities: (entities, data, options) => error string | null
|
|
162
162
|
* @param {Function} config.presenter - Function to present data: (entities, data, options) => view
|
|
163
163
|
* @param {Function} config.formatter - Function to format output: (view, options, data) => void
|
|
164
164
|
* @param {string} [config.usageExample] - Optional usage example
|
|
@@ -184,7 +184,7 @@ export function createCompositeCommand({
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
const entities = findEntities(data, args, options);
|
|
187
|
-
const validationError = validateEntities(entities, data);
|
|
187
|
+
const validationError = validateEntities(entities, data, options);
|
|
188
188
|
|
|
189
189
|
if (validationError) {
|
|
190
190
|
console.error(validationError);
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Interview CLI Command
|
|
3
3
|
*
|
|
4
4
|
* Generates and displays interview questions in the terminal.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx pathway interview <discipline> <grade> # Interview for trackless job
|
|
8
|
+
* npx pathway interview <discipline> <grade> --track=<track> # Interview with track
|
|
9
|
+
* npx pathway interview <discipline> <grade> --track=<track> --type=short
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { createCompositeCommand } from "./command-factory.js";
|
|
@@ -22,7 +27,7 @@ function formatInterview(view, options) {
|
|
|
22
27
|
|
|
23
28
|
export const runInterviewCommand = createCompositeCommand({
|
|
24
29
|
commandName: "interview",
|
|
25
|
-
requiredArgs: ["discipline_id", "
|
|
30
|
+
requiredArgs: ["discipline_id", "grade_id"],
|
|
26
31
|
findEntities: (data, args, options) => {
|
|
27
32
|
const interviewType = options.type || "full";
|
|
28
33
|
|
|
@@ -34,20 +39,22 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
34
39
|
|
|
35
40
|
return {
|
|
36
41
|
discipline: data.disciplines.find((d) => d.id === args[0]),
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
grade: data.grades.find((g) => g.id === args[1]),
|
|
43
|
+
track: options.track
|
|
44
|
+
? data.tracks.find((t) => t.id === options.track)
|
|
45
|
+
: null,
|
|
39
46
|
interviewType,
|
|
40
47
|
};
|
|
41
48
|
},
|
|
42
|
-
validateEntities: (entities, _data) => {
|
|
49
|
+
validateEntities: (entities, _data, options) => {
|
|
43
50
|
if (!entities.discipline) {
|
|
44
51
|
return `Discipline not found: ${entities.discipline}`;
|
|
45
52
|
}
|
|
46
53
|
if (!entities.grade) {
|
|
47
54
|
return `Grade not found: ${entities.grade}`;
|
|
48
55
|
}
|
|
49
|
-
if (!entities.track) {
|
|
50
|
-
return `Track not found: ${
|
|
56
|
+
if (options.track && !entities.track) {
|
|
57
|
+
return `Track not found: ${options.track}`;
|
|
51
58
|
}
|
|
52
59
|
return null;
|
|
53
60
|
},
|
|
@@ -64,5 +71,5 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
64
71
|
formatter: (view, options, data) =>
|
|
65
72
|
formatInterview(view, { ...options, framework: data.framework }),
|
|
66
73
|
usageExample:
|
|
67
|
-
"npx pathway interview software_engineering
|
|
74
|
+
"npx pathway interview software_engineering L4 --track=platform --type=short",
|
|
68
75
|
});
|
package/app/commands/job.js
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* Generates and displays job definitions in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npx pathway job
|
|
8
|
-
* npx pathway job --list
|
|
9
|
-
* npx pathway job <discipline> <
|
|
10
|
-
* npx pathway job
|
|
11
|
-
* npx pathway job --
|
|
7
|
+
* npx pathway job # Summary with stats
|
|
8
|
+
* npx pathway job --list # All valid combinations (for piping)
|
|
9
|
+
* npx pathway job <discipline> <grade> # Detail view (trackless)
|
|
10
|
+
* npx pathway job <discipline> <grade> --track=<track> # Detail view (with track)
|
|
11
|
+
* npx pathway job se L3 --track=platform --checklist=code # Show checklist for handoff
|
|
12
|
+
* npx pathway job --validate # Validation checks
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { prepareJobDetail } from "../model/job.js";
|
|
@@ -47,10 +48,14 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
47
48
|
validationRules: data.framework.validationRules,
|
|
48
49
|
});
|
|
49
50
|
|
|
50
|
-
// --list: Output clean lines for piping
|
|
51
|
+
// --list: Output clean lines for piping (discipline grade track format)
|
|
51
52
|
if (options.list) {
|
|
52
53
|
for (const job of jobs) {
|
|
53
|
-
|
|
54
|
+
if (job.track) {
|
|
55
|
+
console.log(`${job.discipline.id} ${job.grade.id} ${job.track.id}`);
|
|
56
|
+
} else {
|
|
57
|
+
console.log(`${job.discipline.id} ${job.grade.id}`);
|
|
58
|
+
}
|
|
54
59
|
}
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
@@ -71,22 +76,29 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
71
76
|
console.log(`\nTotal: ${jobs.length} valid job combinations`);
|
|
72
77
|
console.log(`\nRun 'npx pathway job --list' for all combinations`);
|
|
73
78
|
console.log(
|
|
74
|
-
`Run 'npx pathway job <discipline> <
|
|
79
|
+
`Run 'npx pathway job <discipline> <grade> [--track=<track>]' for details\n`,
|
|
75
80
|
);
|
|
76
81
|
return;
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
// Handle job detail view
|
|
80
|
-
if (args.length <
|
|
81
|
-
console.error(
|
|
84
|
+
// Handle job detail view - requires discipline and grade
|
|
85
|
+
if (args.length < 2) {
|
|
86
|
+
console.error(
|
|
87
|
+
"Usage: npx pathway job <discipline> <grade> [--track=<track>]",
|
|
88
|
+
);
|
|
82
89
|
console.error(" npx pathway job --list");
|
|
83
|
-
console.error("Example: npx pathway job software_engineering
|
|
90
|
+
console.error("Example: npx pathway job software_engineering L4");
|
|
91
|
+
console.error(
|
|
92
|
+
"Example: npx pathway job software_engineering L4 --track=platform",
|
|
93
|
+
);
|
|
84
94
|
process.exit(1);
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
const discipline = data.disciplines.find((d) => d.id === args[0]);
|
|
88
|
-
const
|
|
89
|
-
const
|
|
98
|
+
const grade = data.grades.find((g) => g.id === args[1]);
|
|
99
|
+
const track = options.track
|
|
100
|
+
? data.tracks.find((t) => t.id === options.track)
|
|
101
|
+
: null;
|
|
90
102
|
|
|
91
103
|
if (!discipline) {
|
|
92
104
|
console.error(`Discipline not found: ${args[0]}`);
|
|
@@ -94,15 +106,15 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
94
106
|
process.exit(1);
|
|
95
107
|
}
|
|
96
108
|
|
|
97
|
-
if (!
|
|
98
|
-
console.error(`
|
|
99
|
-
console.error(`Available: ${data.
|
|
109
|
+
if (!grade) {
|
|
110
|
+
console.error(`Grade not found: ${args[1]}`);
|
|
111
|
+
console.error(`Available: ${data.grades.map((g) => g.id).join(", ")}`);
|
|
100
112
|
process.exit(1);
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
if (!
|
|
104
|
-
console.error(`
|
|
105
|
-
console.error(`Available: ${data.
|
|
115
|
+
if (options.track && !track) {
|
|
116
|
+
console.error(`Track not found: ${options.track}`);
|
|
117
|
+
console.error(`Available: ${data.tracks.map((t) => t.id).join(", ")}`);
|
|
106
118
|
process.exit(1);
|
|
107
119
|
}
|
|
108
120
|
|
|
@@ -126,28 +138,30 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
126
138
|
return;
|
|
127
139
|
}
|
|
128
140
|
|
|
129
|
-
// --checklist: Show checklist for a specific
|
|
141
|
+
// --checklist: Show checklist for a specific stage
|
|
130
142
|
if (options.checklist) {
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
133
|
-
console.error(`Invalid
|
|
134
|
-
console.error(`Available: ${
|
|
143
|
+
const validStages = ["plan", "code"];
|
|
144
|
+
if (!validStages.includes(options.checklist)) {
|
|
145
|
+
console.error(`Invalid stage: ${options.checklist}`);
|
|
146
|
+
console.error(`Available: ${validStages.join(", ")}`);
|
|
135
147
|
process.exit(1);
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
const checklist = deriveChecklist({
|
|
139
|
-
|
|
151
|
+
stageId: options.checklist,
|
|
140
152
|
skillMatrix: view.skillMatrix,
|
|
153
|
+
skills: data.skills,
|
|
141
154
|
capabilities: data.capabilities,
|
|
142
155
|
});
|
|
143
156
|
|
|
144
157
|
if (checklist.length === 0) {
|
|
145
|
-
console.log(`\nNo checklist items for ${options.checklist}\n`);
|
|
158
|
+
console.log(`\nNo checklist items for ${options.checklist} stage\n`);
|
|
146
159
|
return;
|
|
147
160
|
}
|
|
148
161
|
|
|
149
|
-
const
|
|
150
|
-
|
|
162
|
+
const stageLabel =
|
|
163
|
+
options.checklist.charAt(0).toUpperCase() + options.checklist.slice(1);
|
|
164
|
+
console.log(`\n# ${view.title} — ${stageLabel} Stage Checklist\n`);
|
|
151
165
|
console.log(formatChecklistMarkdown(checklist));
|
|
152
166
|
console.log("");
|
|
153
167
|
return;
|
package/app/commands/progress.js
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Progress CLI Command
|
|
3
3
|
*
|
|
4
4
|
* Shows career progression analysis in the terminal.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx pathway progress <discipline> <grade> # Progress for trackless job
|
|
8
|
+
* npx pathway progress <discipline> <grade> --track=<track> # Progress with track
|
|
9
|
+
* npx pathway progress <discipline> <from_grade> --compare=<to_grade> # Compare grades
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
12
|
import { createCompositeCommand } from "./command-factory.js";
|
|
@@ -21,11 +26,13 @@ function formatProgress(view) {
|
|
|
21
26
|
|
|
22
27
|
export const runProgressCommand = createCompositeCommand({
|
|
23
28
|
commandName: "progress",
|
|
24
|
-
requiredArgs: ["discipline_id", "
|
|
29
|
+
requiredArgs: ["discipline_id", "grade_id"],
|
|
25
30
|
findEntities: (data, args, options) => {
|
|
26
31
|
const discipline = data.disciplines.find((d) => d.id === args[0]);
|
|
27
|
-
const
|
|
28
|
-
const
|
|
32
|
+
const grade = data.grades.find((g) => g.id === args[1]);
|
|
33
|
+
const track = options.track
|
|
34
|
+
? data.tracks.find((t) => t.id === options.track)
|
|
35
|
+
: null;
|
|
29
36
|
|
|
30
37
|
let targetGrade;
|
|
31
38
|
if (options.compare) {
|
|
@@ -44,15 +51,15 @@ export const runProgressCommand = createCompositeCommand({
|
|
|
44
51
|
|
|
45
52
|
return { discipline, grade, track, targetGrade };
|
|
46
53
|
},
|
|
47
|
-
validateEntities: (entities, _data) => {
|
|
54
|
+
validateEntities: (entities, _data, options) => {
|
|
48
55
|
if (!entities.discipline) {
|
|
49
56
|
return `Discipline not found`;
|
|
50
57
|
}
|
|
51
58
|
if (!entities.grade) {
|
|
52
59
|
return `Grade not found`;
|
|
53
60
|
}
|
|
54
|
-
if (!entities.track) {
|
|
55
|
-
return `Track not found`;
|
|
61
|
+
if (options.track && !entities.track) {
|
|
62
|
+
return `Track not found: ${options.track}`;
|
|
56
63
|
}
|
|
57
64
|
if (!entities.targetGrade) {
|
|
58
65
|
return `Target grade not found`;
|
|
@@ -73,5 +80,5 @@ export const runProgressCommand = createCompositeCommand({
|
|
|
73
80
|
}),
|
|
74
81
|
formatter: formatProgress,
|
|
75
82
|
usageExample:
|
|
76
|
-
"npx pathway progress software_engineering
|
|
83
|
+
"npx pathway progress software_engineering L3 --track=platform --compare=L4",
|
|
77
84
|
});
|
package/app/commands/serve.js
CHANGED
|
@@ -15,6 +15,7 @@ import { loadFrameworkConfig } from "../model/loader.js";
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = dirname(__filename);
|
|
17
17
|
const publicDir = join(__dirname, "..");
|
|
18
|
+
const rootDir = join(__dirname, "../..");
|
|
18
19
|
|
|
19
20
|
const MIME_TYPES = {
|
|
20
21
|
".html": "text/html; charset=utf-8",
|
|
@@ -23,6 +24,7 @@ const MIME_TYPES = {
|
|
|
23
24
|
".yaml": "text/yaml; charset=utf-8",
|
|
24
25
|
".yml": "text/yaml; charset=utf-8",
|
|
25
26
|
".json": "application/json; charset=utf-8",
|
|
27
|
+
".md": "text/markdown; charset=utf-8",
|
|
26
28
|
".svg": "image/svg+xml",
|
|
27
29
|
".png": "image/png",
|
|
28
30
|
".ico": "image/x-icon",
|
|
@@ -113,6 +115,9 @@ export async function runServeCommand({ dataDir, options }) {
|
|
|
113
115
|
if (pathname.startsWith("/data/")) {
|
|
114
116
|
// Serve from user's data directory
|
|
115
117
|
filePath = join(dataDir, pathname.slice(6));
|
|
118
|
+
} else if (pathname.startsWith("/templates/")) {
|
|
119
|
+
// Serve from templates directory
|
|
120
|
+
filePath = join(rootDir, pathname);
|
|
116
121
|
} else if (pathname === "/" || pathname === "") {
|
|
117
122
|
// Serve index.html for root
|
|
118
123
|
filePath = join(publicDir, "index.html");
|
package/app/commands/stage.js
CHANGED
|
@@ -58,18 +58,8 @@ function formatDetail(viewAndContext, _framework) {
|
|
|
58
58
|
const emoji = getStageEmoji(stages, stage.id);
|
|
59
59
|
|
|
60
60
|
console.log(formatHeader(`\n${emoji} ${view.name}\n`));
|
|
61
|
-
console.log(`Mode: ${view.modeBadge}\n`);
|
|
62
61
|
console.log(`${view.description}\n`);
|
|
63
62
|
|
|
64
|
-
// Tools
|
|
65
|
-
if (view.tools.length > 0) {
|
|
66
|
-
console.log(formatSubheader("Tools\n"));
|
|
67
|
-
for (const tool of view.tools) {
|
|
68
|
-
console.log(formatBullet(`${tool.icon} ${tool.label}`, 1));
|
|
69
|
-
}
|
|
70
|
-
console.log();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
63
|
// Entry criteria
|
|
74
64
|
if (view.entryCriteria.length > 0) {
|
|
75
65
|
console.log(formatSubheader("Entry Criteria\n"));
|
package/app/commands/track.js
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
import { createEntityCommand } from "./command-factory.js";
|
|
14
14
|
import { trackToMarkdown } from "../formatters/track/markdown.js";
|
|
15
|
-
import {
|
|
15
|
+
import { sortTracksByName } from "../formatters/track/shared.js";
|
|
16
16
|
import { formatTable } from "../lib/cli-output.js";
|
|
17
17
|
import { getConceptEmoji } from "../model/levels.js";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Format track summary output
|
|
21
|
-
* @param {Array} tracks - Raw track entities
|
|
21
|
+
* @param {Array} tracks - Raw track entities
|
|
22
22
|
* @param {Object} data - Full data context
|
|
23
23
|
*/
|
|
24
24
|
function formatSummary(tracks, data) {
|
|
@@ -28,14 +28,11 @@ function formatSummary(tracks, data) {
|
|
|
28
28
|
console.log(`\n${emoji} Tracks\n`);
|
|
29
29
|
|
|
30
30
|
const rows = tracks.map((t) => {
|
|
31
|
-
const types = [];
|
|
32
|
-
if (t.isProfessional) types.push("P");
|
|
33
|
-
if (t.isManagement) types.push("M");
|
|
34
31
|
const modCount = Object.keys(t.skillModifiers || {}).length;
|
|
35
|
-
return [t.id, t.name,
|
|
32
|
+
return [t.id, t.name, modCount];
|
|
36
33
|
});
|
|
37
34
|
|
|
38
|
-
console.log(formatTable(["ID", "Name", "
|
|
35
|
+
console.log(formatTable(["ID", "Name", "Modifiers"], rows));
|
|
39
36
|
console.log(`\nTotal: ${tracks.length} tracks`);
|
|
40
37
|
console.log(`\nRun 'npx pathway track --list' for IDs`);
|
|
41
38
|
console.log(`Run 'npx pathway track <id>' for details\n`);
|
|
@@ -63,7 +60,7 @@ export const runTrackCommand = createEntityCommand({
|
|
|
63
60
|
behaviours: data.behaviours,
|
|
64
61
|
disciplines: data.disciplines,
|
|
65
62
|
}),
|
|
66
|
-
sortItems:
|
|
63
|
+
sortItems: sortTracksByName,
|
|
67
64
|
formatSummary,
|
|
68
65
|
formatDetail,
|
|
69
66
|
emoji: "🛤️",
|