@forwardimpact/pathway 0.1.0 → 0.3.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 +119 -31
- package/app/commands/command-factory.js +3 -3
- package/app/commands/interview.js +14 -7
- package/app/commands/job.js +52 -33
- 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 +117 -30
- package/app/css/components/surfaces.css +16 -0
- package/app/formatters/agent/profile.js +30 -115
- package/app/formatters/agent/skill.js +23 -44
- 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 +5 -4
- 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 +70 -81
- package/app/formatters/job/dom.js +40 -113
- package/app/formatters/job/markdown.js +17 -13
- 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/shared.js +7 -2
- 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/handout.html +7 -0
- package/app/index.html +11 -0
- package/app/lib/card-mappers.js +17 -12
- package/app/lib/form-controls.js +64 -1
- package/app/lib/job-cache.js +12 -9
- package/app/lib/render.js +8 -1
- package/app/lib/template-loader.js +75 -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 +125 -28
- 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 +43 -8
- package/app/pages/landing.js +10 -10
- package/app/pages/progress-builder.js +3 -1
- package/app/pages/progress.js +78 -26
- package/app/pages/self-assessment.js +3 -3
- 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/app/slides.html +7 -0
- package/bin/pathway.js +28 -75
- 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 +14 -15
- 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/job.template.md +47 -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
|
@@ -7,21 +7,24 @@
|
|
|
7
7
|
* All agents are stage-specific. Use --stage for a single stage
|
|
8
8
|
* or --all-stages (default) for all stages.
|
|
9
9
|
*
|
|
10
|
+
* By default, outputs to console. Use --output to write files.
|
|
11
|
+
*
|
|
10
12
|
* Usage:
|
|
11
|
-
* npx pathway agent <discipline>
|
|
12
|
-
* npx pathway agent <discipline>
|
|
13
|
-
* npx pathway agent <discipline>
|
|
13
|
+
* npx pathway agent <discipline> [--track=<track>]
|
|
14
|
+
* npx pathway agent <discipline> --track=<track> --stage=plan
|
|
15
|
+
* npx pathway agent <discipline> --track=<track> --output=./agents
|
|
14
16
|
* npx pathway agent --list
|
|
15
17
|
*
|
|
16
18
|
* Examples:
|
|
17
|
-
* npx pathway agent software_engineering platform
|
|
18
|
-
* npx pathway agent software_engineering platform --stage=plan
|
|
19
|
-
* npx pathway agent software_engineering platform --
|
|
19
|
+
* npx pathway agent software_engineering --track=platform
|
|
20
|
+
* npx pathway agent software_engineering --track=platform --stage=plan
|
|
21
|
+
* npx pathway agent software_engineering --track=platform --output=./agents
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
import { writeFile, mkdir, readFile } from "fs/promises";
|
|
23
25
|
import { join, dirname } from "path";
|
|
24
26
|
import { existsSync } from "fs";
|
|
27
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
25
28
|
import { loadAgentData, loadSkillsWithAgentData } from "../model/loader.js";
|
|
26
29
|
import {
|
|
27
30
|
generateStageAgentProfile,
|
|
@@ -31,12 +34,13 @@ import {
|
|
|
31
34
|
deriveAgentSkills,
|
|
32
35
|
generateSkillMd,
|
|
33
36
|
} from "../model/agent.js";
|
|
34
|
-
import {
|
|
35
|
-
formatAgentProfile,
|
|
36
|
-
formatAgentProfileForCli,
|
|
37
|
-
} from "../formatters/agent/profile.js";
|
|
37
|
+
import { formatAgentProfile } from "../formatters/agent/profile.js";
|
|
38
38
|
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
39
39
|
import { formatError, formatSuccess } from "../lib/cli-output.js";
|
|
40
|
+
import {
|
|
41
|
+
loadAgentTemplate,
|
|
42
|
+
loadSkillTemplate,
|
|
43
|
+
} from "../lib/template-loader.js";
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* Ensure directory exists for a file path
|
|
@@ -72,6 +76,64 @@ async function generateVSCodeSettings(baseDir, vscodeSettings) {
|
|
|
72
76
|
console.log(formatSuccess(`Updated: ${settingsPath}`));
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Generate devcontainer.json from template with VS Code settings embedded
|
|
81
|
+
* @param {string} baseDir - Base output directory
|
|
82
|
+
* @param {Object} devcontainerConfig - Devcontainer config loaded from data
|
|
83
|
+
* @param {Object} vscodeSettings - VS Code settings to embed in customizations
|
|
84
|
+
*/
|
|
85
|
+
async function generateDevcontainer(
|
|
86
|
+
baseDir,
|
|
87
|
+
devcontainerConfig,
|
|
88
|
+
vscodeSettings,
|
|
89
|
+
) {
|
|
90
|
+
if (!devcontainerConfig || Object.keys(devcontainerConfig).length === 0) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const devcontainerPath = join(baseDir, ".devcontainer", "devcontainer.json");
|
|
95
|
+
|
|
96
|
+
// Build devcontainer.json with VS Code settings embedded
|
|
97
|
+
const devcontainer = {
|
|
98
|
+
...devcontainerConfig,
|
|
99
|
+
customizations: {
|
|
100
|
+
vscode: {
|
|
101
|
+
settings: vscodeSettings,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await ensureDir(devcontainerPath);
|
|
107
|
+
await writeFile(
|
|
108
|
+
devcontainerPath,
|
|
109
|
+
JSON.stringify(devcontainer, null, 2) + "\n",
|
|
110
|
+
"utf-8",
|
|
111
|
+
);
|
|
112
|
+
console.log(formatSuccess(`Created: ${devcontainerPath}`));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generate GitHub Actions workflow for Copilot Coding Agent setup steps
|
|
117
|
+
* @param {string} baseDir - Base output directory
|
|
118
|
+
* @param {Object|null} copilotSetupSteps - Workflow config loaded from data
|
|
119
|
+
*/
|
|
120
|
+
async function generateCopilotSetupSteps(baseDir, copilotSetupSteps) {
|
|
121
|
+
if (!copilotSetupSteps) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const workflowPath = join(
|
|
126
|
+
baseDir,
|
|
127
|
+
".github",
|
|
128
|
+
"workflows",
|
|
129
|
+
"copilot-setup-steps.yml",
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
await ensureDir(workflowPath);
|
|
133
|
+
await writeFile(workflowPath, stringifyYaml(copilotSetupSteps), "utf-8");
|
|
134
|
+
console.log(formatSuccess(`Created: ${workflowPath}`));
|
|
135
|
+
}
|
|
136
|
+
|
|
75
137
|
/**
|
|
76
138
|
* Show agent summary with stats
|
|
77
139
|
* @param {Object} data - Pathway data
|
|
@@ -181,10 +243,11 @@ function listAgentCombinations(data, agentData, verbose = false) {
|
|
|
181
243
|
* Write agent profile to file
|
|
182
244
|
* @param {Object} profile - Generated profile
|
|
183
245
|
* @param {string} baseDir - Base output directory
|
|
246
|
+
* @param {string} template - Mustache template for agent profile
|
|
184
247
|
*/
|
|
185
|
-
async function writeProfile(profile, baseDir) {
|
|
248
|
+
async function writeProfile(profile, baseDir, template) {
|
|
186
249
|
const profilePath = join(baseDir, ".github", "agents", profile.filename);
|
|
187
|
-
const profileContent = formatAgentProfile(profile);
|
|
250
|
+
const profileContent = formatAgentProfile(profile, template);
|
|
188
251
|
await ensureDir(profilePath);
|
|
189
252
|
await writeFile(profilePath, profileContent, "utf-8");
|
|
190
253
|
console.log(formatSuccess(`Created: ${profilePath}`));
|
|
@@ -195,8 +258,9 @@ async function writeProfile(profile, baseDir) {
|
|
|
195
258
|
* Write skill files
|
|
196
259
|
* @param {Array} skills - Generated skills
|
|
197
260
|
* @param {string} baseDir - Base output directory
|
|
261
|
+
* @param {string} template - Mustache template for skills
|
|
198
262
|
*/
|
|
199
|
-
async function writeSkills(skills, baseDir) {
|
|
263
|
+
async function writeSkills(skills, baseDir, template) {
|
|
200
264
|
for (const skill of skills) {
|
|
201
265
|
const skillPath = join(
|
|
202
266
|
baseDir,
|
|
@@ -205,7 +269,7 @@ async function writeSkills(skills, baseDir) {
|
|
|
205
269
|
skill.dirname,
|
|
206
270
|
"SKILL.md",
|
|
207
271
|
);
|
|
208
|
-
const skillContent = formatAgentSkill(skill);
|
|
272
|
+
const skillContent = formatAgentSkill(skill, template);
|
|
209
273
|
await ensureDir(skillPath);
|
|
210
274
|
await writeFile(skillPath, skillContent, "utf-8");
|
|
211
275
|
console.log(formatSuccess(`Created: ${skillPath}`));
|
|
@@ -216,7 +280,7 @@ async function writeSkills(skills, baseDir) {
|
|
|
216
280
|
* Run the agent command
|
|
217
281
|
* @param {Object} params - Command parameters
|
|
218
282
|
* @param {Object} params.data - Loaded pathway data
|
|
219
|
-
* @param {string[]} params.args - Command arguments [discipline_id
|
|
283
|
+
* @param {string[]} params.args - Command arguments [discipline_id]
|
|
220
284
|
* @param {Object} params.options - Command options
|
|
221
285
|
* @param {string} params.dataDir - Path to data directory
|
|
222
286
|
*/
|
|
@@ -237,11 +301,14 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
237
301
|
return;
|
|
238
302
|
}
|
|
239
303
|
|
|
240
|
-
const [disciplineId
|
|
304
|
+
const [disciplineId] = args;
|
|
305
|
+
const trackId = options.track;
|
|
241
306
|
|
|
242
|
-
if (!disciplineId
|
|
307
|
+
if (!disciplineId) {
|
|
243
308
|
console.error(
|
|
244
|
-
formatError(
|
|
309
|
+
formatError(
|
|
310
|
+
"Usage: npx pathway agent <discipline_id> [--track=<track_id>]",
|
|
311
|
+
),
|
|
245
312
|
);
|
|
246
313
|
console.error(
|
|
247
314
|
"\nRun 'npx pathway agent --list' to see available combinations.",
|
|
@@ -251,7 +318,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
251
318
|
|
|
252
319
|
// Look up human definitions
|
|
253
320
|
const humanDiscipline = data.disciplines.find((d) => d.id === disciplineId);
|
|
254
|
-
const humanTrack = data.tracks.find((t) => t.id === trackId);
|
|
321
|
+
const humanTrack = trackId ? data.tracks.find((t) => t.id === trackId) : null;
|
|
255
322
|
|
|
256
323
|
if (!humanDiscipline) {
|
|
257
324
|
console.error(formatError(`Unknown discipline: ${disciplineId}`));
|
|
@@ -262,7 +329,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
262
329
|
process.exit(1);
|
|
263
330
|
}
|
|
264
331
|
|
|
265
|
-
if (!humanTrack) {
|
|
332
|
+
if (trackId && !humanTrack) {
|
|
266
333
|
console.error(formatError(`Unknown track: ${trackId}`));
|
|
267
334
|
console.error("\nAvailable tracks:");
|
|
268
335
|
for (const t of data.tracks) {
|
|
@@ -275,7 +342,9 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
275
342
|
const agentDiscipline = agentData.disciplines.find(
|
|
276
343
|
(d) => d.id === disciplineId,
|
|
277
344
|
);
|
|
278
|
-
const agentTrack =
|
|
345
|
+
const agentTrack = trackId
|
|
346
|
+
? agentData.tracks.find((t) => t.id === trackId)
|
|
347
|
+
: null;
|
|
279
348
|
|
|
280
349
|
if (!agentDiscipline) {
|
|
281
350
|
console.error(
|
|
@@ -288,7 +357,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
288
357
|
process.exit(1);
|
|
289
358
|
}
|
|
290
359
|
|
|
291
|
-
if (!agentTrack) {
|
|
360
|
+
if (trackId && !agentTrack) {
|
|
292
361
|
console.error(formatError(`No agent definition for track: ${trackId}`));
|
|
293
362
|
console.error("\nAgent definitions exist for:");
|
|
294
363
|
for (const t of agentData.tracks) {
|
|
@@ -340,14 +409,23 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
340
409
|
process.exit(1);
|
|
341
410
|
}
|
|
342
411
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
|
|
412
|
+
// Load template
|
|
413
|
+
const agentTemplate = await loadAgentTemplate(dataDir);
|
|
414
|
+
|
|
415
|
+
// Output to console (default) or write to files (with --output)
|
|
416
|
+
if (!options.output) {
|
|
417
|
+
console.log(formatAgentProfile(profile, agentTemplate));
|
|
346
418
|
return;
|
|
347
419
|
}
|
|
348
420
|
|
|
349
|
-
await writeProfile(profile, baseDir);
|
|
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}`),
|
|
@@ -406,20 +484,30 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
406
484
|
}
|
|
407
485
|
}
|
|
408
486
|
|
|
409
|
-
//
|
|
410
|
-
|
|
487
|
+
// Load templates
|
|
488
|
+
const agentTemplate = await loadAgentTemplate(dataDir);
|
|
489
|
+
const skillTemplate = await loadSkillTemplate(dataDir);
|
|
490
|
+
|
|
491
|
+
// Output to console (default) or write to files (with --output)
|
|
492
|
+
if (!options.output) {
|
|
411
493
|
for (const profile of profiles) {
|
|
412
|
-
console.log(
|
|
494
|
+
console.log(formatAgentProfile(profile, agentTemplate));
|
|
413
495
|
console.log("\n---\n");
|
|
414
496
|
}
|
|
415
497
|
return;
|
|
416
498
|
}
|
|
417
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";
|
|
@@ -19,15 +20,17 @@ import {
|
|
|
19
20
|
deriveChecklist,
|
|
20
21
|
formatChecklistMarkdown,
|
|
21
22
|
} from "../model/checklist.js";
|
|
23
|
+
import { loadJobTemplate } from "../lib/template-loader.js";
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Format job output
|
|
25
27
|
* @param {Object} view - Presenter view
|
|
26
28
|
* @param {Object} _options - Command options
|
|
27
29
|
* @param {Object} entities - Original entities
|
|
30
|
+
* @param {string} jobTemplate - Mustache template for job description
|
|
28
31
|
*/
|
|
29
|
-
function formatJob(view, _options, entities) {
|
|
30
|
-
console.log(jobToMarkdown(view, entities));
|
|
32
|
+
function formatJob(view, _options, entities, jobTemplate) {
|
|
33
|
+
console.log(jobToMarkdown(view, entities, jobTemplate));
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
/**
|
|
@@ -36,8 +39,9 @@ function formatJob(view, _options, entities) {
|
|
|
36
39
|
* @param {Object} params.data - All loaded data
|
|
37
40
|
* @param {string[]} params.args - Command arguments
|
|
38
41
|
* @param {Object} params.options - Command options
|
|
42
|
+
* @param {string} params.dataDir - Path to data directory
|
|
39
43
|
*/
|
|
40
|
-
export async function runJobCommand({ data, args, options }) {
|
|
44
|
+
export async function runJobCommand({ data, args, options, dataDir }) {
|
|
41
45
|
const jobs = generateAllJobs({
|
|
42
46
|
disciplines: data.disciplines,
|
|
43
47
|
grades: data.grades,
|
|
@@ -47,10 +51,14 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
47
51
|
validationRules: data.framework.validationRules,
|
|
48
52
|
});
|
|
49
53
|
|
|
50
|
-
// --list: Output clean lines for piping
|
|
54
|
+
// --list: Output clean lines for piping (discipline grade track format)
|
|
51
55
|
if (options.list) {
|
|
52
56
|
for (const job of jobs) {
|
|
53
|
-
|
|
57
|
+
if (job.track) {
|
|
58
|
+
console.log(`${job.discipline.id} ${job.grade.id} ${job.track.id}`);
|
|
59
|
+
} else {
|
|
60
|
+
console.log(`${job.discipline.id} ${job.grade.id}`);
|
|
61
|
+
}
|
|
54
62
|
}
|
|
55
63
|
return;
|
|
56
64
|
}
|
|
@@ -71,22 +79,29 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
71
79
|
console.log(`\nTotal: ${jobs.length} valid job combinations`);
|
|
72
80
|
console.log(`\nRun 'npx pathway job --list' for all combinations`);
|
|
73
81
|
console.log(
|
|
74
|
-
`Run 'npx pathway job <discipline> <
|
|
82
|
+
`Run 'npx pathway job <discipline> <grade> [--track=<track>]' for details\n`,
|
|
75
83
|
);
|
|
76
84
|
return;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
// Handle job detail view
|
|
80
|
-
if (args.length <
|
|
81
|
-
console.error(
|
|
87
|
+
// Handle job detail view - requires discipline and grade
|
|
88
|
+
if (args.length < 2) {
|
|
89
|
+
console.error(
|
|
90
|
+
"Usage: npx pathway job <discipline> <grade> [--track=<track>]",
|
|
91
|
+
);
|
|
82
92
|
console.error(" npx pathway job --list");
|
|
83
|
-
console.error("Example: npx pathway job software_engineering
|
|
93
|
+
console.error("Example: npx pathway job software_engineering L4");
|
|
94
|
+
console.error(
|
|
95
|
+
"Example: npx pathway job software_engineering L4 --track=platform",
|
|
96
|
+
);
|
|
84
97
|
process.exit(1);
|
|
85
98
|
}
|
|
86
99
|
|
|
87
100
|
const discipline = data.disciplines.find((d) => d.id === args[0]);
|
|
88
|
-
const
|
|
89
|
-
const
|
|
101
|
+
const grade = data.grades.find((g) => g.id === args[1]);
|
|
102
|
+
const track = options.track
|
|
103
|
+
? data.tracks.find((t) => t.id === options.track)
|
|
104
|
+
: null;
|
|
90
105
|
|
|
91
106
|
if (!discipline) {
|
|
92
107
|
console.error(`Discipline not found: ${args[0]}`);
|
|
@@ -94,15 +109,15 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
94
109
|
process.exit(1);
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
if (!
|
|
98
|
-
console.error(`
|
|
99
|
-
console.error(`Available: ${data.
|
|
112
|
+
if (!grade) {
|
|
113
|
+
console.error(`Grade not found: ${args[1]}`);
|
|
114
|
+
console.error(`Available: ${data.grades.map((g) => g.id).join(", ")}`);
|
|
100
115
|
process.exit(1);
|
|
101
116
|
}
|
|
102
117
|
|
|
103
|
-
if (!
|
|
104
|
-
console.error(`
|
|
105
|
-
console.error(`Available: ${data.
|
|
118
|
+
if (options.track && !track) {
|
|
119
|
+
console.error(`Track not found: ${options.track}`);
|
|
120
|
+
console.error(`Available: ${data.tracks.map((t) => t.id).join(", ")}`);
|
|
106
121
|
process.exit(1);
|
|
107
122
|
}
|
|
108
123
|
|
|
@@ -126,32 +141,36 @@ export async function runJobCommand({ data, args, options }) {
|
|
|
126
141
|
return;
|
|
127
142
|
}
|
|
128
143
|
|
|
129
|
-
// --checklist: Show checklist for a specific
|
|
144
|
+
// --checklist: Show checklist for a specific stage
|
|
130
145
|
if (options.checklist) {
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
133
|
-
console.error(`Invalid
|
|
134
|
-
console.error(`Available: ${
|
|
146
|
+
const validStages = ["plan", "code"];
|
|
147
|
+
if (!validStages.includes(options.checklist)) {
|
|
148
|
+
console.error(`Invalid stage: ${options.checklist}`);
|
|
149
|
+
console.error(`Available: ${validStages.join(", ")}`);
|
|
135
150
|
process.exit(1);
|
|
136
151
|
}
|
|
137
152
|
|
|
138
153
|
const checklist = deriveChecklist({
|
|
139
|
-
|
|
154
|
+
stageId: options.checklist,
|
|
140
155
|
skillMatrix: view.skillMatrix,
|
|
156
|
+
skills: data.skills,
|
|
141
157
|
capabilities: data.capabilities,
|
|
142
158
|
});
|
|
143
159
|
|
|
144
160
|
if (checklist.length === 0) {
|
|
145
|
-
console.log(`\nNo checklist items for ${options.checklist}\n`);
|
|
161
|
+
console.log(`\nNo checklist items for ${options.checklist} stage\n`);
|
|
146
162
|
return;
|
|
147
163
|
}
|
|
148
164
|
|
|
149
|
-
const
|
|
150
|
-
|
|
165
|
+
const stageLabel =
|
|
166
|
+
options.checklist.charAt(0).toUpperCase() + options.checklist.slice(1);
|
|
167
|
+
console.log(`\n# ${view.title} — ${stageLabel} Stage Checklist\n`);
|
|
151
168
|
console.log(formatChecklistMarkdown(checklist));
|
|
152
169
|
console.log("");
|
|
153
170
|
return;
|
|
154
171
|
}
|
|
155
172
|
|
|
156
|
-
|
|
173
|
+
// Load job template for description formatting
|
|
174
|
+
const jobTemplate = await loadJobTemplate(dataDir);
|
|
175
|
+
formatJob(view, options, { discipline, grade, track }, jobTemplate);
|
|
157
176
|
}
|
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: "🛤️",
|