@forwardimpact/pathway 0.22.0 → 0.23.1
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/bin/fit-pathway.js +8 -4
- package/package.json +6 -2
- package/src/commands/agent.js +6 -3
- package/src/commands/behaviour.js +11 -1
- package/src/commands/build.js +11 -2
- package/src/commands/command-factory.js +4 -2
- package/src/commands/dev.js +9 -2
- package/src/commands/discipline.js +25 -10
- package/src/commands/driver.js +11 -1
- package/src/commands/job.js +127 -28
- package/src/commands/level.js +25 -3
- package/src/commands/skill.js +11 -1
- package/src/commands/stage.js +11 -1
- package/src/commands/tool.js +6 -3
- package/src/commands/track.js +20 -4
- package/src/components/card.js +8 -104
- package/src/components/comparison-radar.js +1 -1
- package/src/components/detail.js +16 -118
- package/src/components/error-page.js +8 -68
- package/src/components/grid.js +12 -106
- package/src/components/list.js +7 -116
- package/src/components/nav.js +7 -60
- package/src/css/bundles/app.css +25 -21
- package/src/css/bundles/handout.css +33 -33
- package/src/css/bundles/slides.css +25 -25
- package/src/formatters/discipline/markdown.js +16 -1
- package/src/formatters/interview/shared.js +3 -3
- package/src/formatters/job/description.js +2 -2
- package/src/formatters/progress/shared.js +3 -3
- package/src/formatters/skill/shared.js +1 -1
- package/src/formatters/track/markdown.js +14 -0
- package/src/formatters/track/shared.js +1 -1
- package/src/handout.html +32 -13
- package/src/index.html +32 -13
- package/src/lib/error-boundary.js +3 -66
- package/src/lib/errors.js +7 -45
- package/src/lib/job-cache.js +1 -1
- package/src/lib/markdown.js +2 -109
- package/src/lib/reactive.js +7 -73
- package/src/lib/render.js +49 -197
- package/src/lib/router-core.js +2 -156
- package/src/lib/router-pages.js +2 -11
- package/src/lib/router-slides.js +2 -197
- package/src/lib/state.js +14 -63
- package/src/lib/utils.js +3 -10
- package/src/lib/yaml-loader.js +13 -71
- package/src/pages/agent-builder.js +1 -1
- package/src/pages/assessment-results.js +1 -1
- package/src/pages/job-builder.js +1 -1
- package/src/pages/job.js +1 -1
- package/src/pages/skill.js +1 -1
- package/src/slide-main.js +1 -1
- package/src/slides/index.js +1 -1
- package/src/slides/job.js +1 -1
- package/src/slides/overview.js +1 -1
- package/src/slides.html +32 -13
- package/src/css/base.css +0 -56
- package/src/css/components/badges.css +0 -232
- package/src/css/components/buttons.css +0 -101
- package/src/css/components/forms.css +0 -191
- package/src/css/components/layout.css +0 -218
- package/src/css/components/nav.css +0 -206
- package/src/css/components/progress.css +0 -166
- package/src/css/components/states.css +0 -82
- package/src/css/components/surfaces.css +0 -347
- package/src/css/components/tables.css +0 -362
- package/src/css/components/top-bar.css +0 -180
- package/src/css/components/typography.css +0 -121
- package/src/css/components/utilities.css +0 -41
- package/src/css/pages/detail.css +0 -119
- package/src/css/reset.css +0 -50
- package/src/css/tokens.css +0 -162
- package/src/css/views/handout.css +0 -30
- package/src/css/views/print.css +0 -634
- package/src/css/views/slide-animations.css +0 -113
- package/src/css/views/slide-base.css +0 -331
- package/src/css/views/slide-sections.css +0 -597
- package/src/css/views/slide-tables.css +0 -275
package/bin/fit-pathway.js
CHANGED
|
@@ -117,7 +117,9 @@ Generate job definitions from discipline × level × track combinations.
|
|
|
117
117
|
|
|
118
118
|
Usage:
|
|
119
119
|
npx fit-pathway job Summary with stats
|
|
120
|
+
npx fit-pathway job --track=<track> Summary filtered by track
|
|
120
121
|
npx fit-pathway job --list All valid combinations
|
|
122
|
+
npx fit-pathway job --list --track=<track> Combinations for a track
|
|
121
123
|
npx fit-pathway job <discipline> <level> Detail view (trackless)
|
|
122
124
|
npx fit-pathway job <d> <l> --track=<track> Detail view (with track)
|
|
123
125
|
npx fit-pathway job <d> <l> --skills Plain list of skill IDs
|
|
@@ -126,15 +128,17 @@ Usage:
|
|
|
126
128
|
|
|
127
129
|
Options:
|
|
128
130
|
--track=TRACK Track specialization (e.g., platform, forward_deployed)
|
|
131
|
+
Also filters --list and summary modes
|
|
129
132
|
--skills Output plain list of skill IDs (for piping)
|
|
130
133
|
--tools Output plain list of tool names (for piping)
|
|
131
134
|
--checklist=STAGE Show checklist for stage handoff (plan, code)
|
|
132
135
|
|
|
133
136
|
Examples:
|
|
134
|
-
npx fit-pathway job
|
|
135
|
-
npx fit-pathway job
|
|
136
|
-
npx fit-pathway job
|
|
137
|
-
npx fit-pathway job
|
|
137
|
+
npx fit-pathway job # overview of all jobs
|
|
138
|
+
npx fit-pathway job --track=forward_deployed # jobs on a specific track
|
|
139
|
+
npx fit-pathway job --list --track=forward_deployed # list for piping
|
|
140
|
+
npx fit-pathway job software_engineering J060 # trackless job detail
|
|
141
|
+
npx fit-pathway job software_engineering J060 --track=platform # with track
|
|
138
142
|
|
|
139
143
|
────────────────────────────────────────────────────────────────────────────────
|
|
140
144
|
AGENT COMMAND
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/pathway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
4
4
|
"description": "Career progression web app and CLI for exploring roles and generating agent teams",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -41,12 +41,16 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@forwardimpact/map": "^0.11.0",
|
|
44
|
-
"@forwardimpact/
|
|
44
|
+
"@forwardimpact/libskill": "^3.0.0",
|
|
45
|
+
"@forwardimpact/libui": "^1.0.0",
|
|
45
46
|
"mustache": "^4.2.0",
|
|
46
47
|
"simple-icons": "^16.7.0",
|
|
47
48
|
"yaml": "^2.3.4"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
51
55
|
}
|
|
52
56
|
}
|
package/src/commands/agent.js
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
buildAgentIndex,
|
|
43
43
|
getDisciplineAbbreviation,
|
|
44
44
|
toKebabCase,
|
|
45
|
-
} from "@forwardimpact/
|
|
45
|
+
} from "@forwardimpact/libskill";
|
|
46
46
|
import { formatAgentProfile } from "../formatters/agent/profile.js";
|
|
47
47
|
import {
|
|
48
48
|
formatAgentSkill,
|
|
@@ -199,7 +199,7 @@ function showAgentSummary(data, agentData, skillsWithAgent) {
|
|
|
199
199
|
*/
|
|
200
200
|
function listAgentCombinations(data, agentData, verbose = false) {
|
|
201
201
|
if (!verbose) {
|
|
202
|
-
//
|
|
202
|
+
// Descriptive output for piping and AI agent discovery
|
|
203
203
|
for (const discipline of agentData.disciplines) {
|
|
204
204
|
for (const track of agentData.tracks) {
|
|
205
205
|
const humanDiscipline = data.disciplines.find(
|
|
@@ -209,7 +209,10 @@ function listAgentCombinations(data, agentData, verbose = false) {
|
|
|
209
209
|
if (humanDiscipline && humanTrack) {
|
|
210
210
|
const abbrev = getDisciplineAbbreviation(discipline.id);
|
|
211
211
|
const agentName = `${abbrev}-${toKebabCase(track.id)}`;
|
|
212
|
-
|
|
212
|
+
const specName = humanDiscipline.specialization || humanDiscipline.id;
|
|
213
|
+
console.log(
|
|
214
|
+
`${agentName} ${discipline.id} ${track.id}, ${specName} (${humanTrack.name})`,
|
|
215
|
+
);
|
|
213
216
|
}
|
|
214
217
|
}
|
|
215
218
|
}
|
|
@@ -14,6 +14,15 @@ import { createEntityCommand } from "./command-factory.js";
|
|
|
14
14
|
import { behaviourToMarkdown } from "../formatters/behaviour/markdown.js";
|
|
15
15
|
import { formatTable } from "../lib/cli-output.js";
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Format behaviour list item for --list output
|
|
19
|
+
* @param {Object} behaviour - Behaviour entity
|
|
20
|
+
* @returns {string} Formatted list line
|
|
21
|
+
*/
|
|
22
|
+
function formatListItem(behaviour) {
|
|
23
|
+
return `${behaviour.id}, ${behaviour.name}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
17
26
|
/**
|
|
18
27
|
* Format behaviour summary output
|
|
19
28
|
* @param {Array} behaviours - Raw behaviour entities
|
|
@@ -34,7 +43,7 @@ function formatSummary(behaviours, data) {
|
|
|
34
43
|
|
|
35
44
|
console.log(formatTable(["ID", "Name", "Drivers"], rows));
|
|
36
45
|
console.log(`\nTotal: ${behaviours.length} behaviours`);
|
|
37
|
-
console.log(`\nRun 'npx pathway behaviour --list' for IDs`);
|
|
46
|
+
console.log(`\nRun 'npx pathway behaviour --list' for IDs and names`);
|
|
38
47
|
console.log(`Run 'npx pathway behaviour <id>' for details\n`);
|
|
39
48
|
}
|
|
40
49
|
|
|
@@ -57,5 +66,6 @@ export const runBehaviourCommand = createEntityCommand({
|
|
|
57
66
|
}),
|
|
58
67
|
formatSummary,
|
|
59
68
|
formatDetail,
|
|
69
|
+
formatListItem,
|
|
60
70
|
emojiIcon: "🧠",
|
|
61
71
|
});
|
package/src/commands/build.js
CHANGED
|
@@ -42,7 +42,8 @@ function resolvePackageLib(packageName) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const mapLibDir = resolvePackageLib("@forwardimpact/map");
|
|
45
|
-
const modelLibDir = resolvePackageLib("@forwardimpact/
|
|
45
|
+
const modelLibDir = resolvePackageLib("@forwardimpact/libskill");
|
|
46
|
+
const uiLibDir = resolvePackageLib("@forwardimpact/libui");
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* Files and directories to copy from app/
|
|
@@ -141,13 +142,21 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
// Copy @forwardimpact/map and @forwardimpact/
|
|
145
|
+
// Copy @forwardimpact/map and @forwardimpact/libskill packages
|
|
145
146
|
// These are needed by the browser's import map
|
|
146
147
|
console.log("📚 Copying package dependencies...");
|
|
147
148
|
await cp(mapLibDir, join(outputDir, "map/lib"), { recursive: true });
|
|
148
149
|
console.log(` ✓ map/lib`);
|
|
149
150
|
await cp(modelLibDir, join(outputDir, "model/lib"), { recursive: true });
|
|
150
151
|
console.log(` ✓ model/lib`);
|
|
152
|
+
// Copy libui JS (src/) and CSS (src/css/)
|
|
153
|
+
await cp(uiLibDir, join(outputDir, "ui/lib"), { recursive: true });
|
|
154
|
+
// CSS is within uiLibDir/css/ so it's already copied as ui/lib/css/
|
|
155
|
+
// Create ui/css/ symlink-like copy for the CSS @import paths
|
|
156
|
+
await cp(join(uiLibDir, "css"), join(outputDir, "ui/css"), {
|
|
157
|
+
recursive: true,
|
|
158
|
+
});
|
|
159
|
+
console.log(` ✓ ui/lib + ui/css`);
|
|
151
160
|
|
|
152
161
|
// Copy data directory (dereference symlinks to copy actual content)
|
|
153
162
|
console.log("📁 Copying data files...");
|
|
@@ -22,6 +22,7 @@ import { capitalize } from "../formatters/shared.js";
|
|
|
22
22
|
* @param {Function} config.presentDetail - Function to present detail: (entity, data, options) => view
|
|
23
23
|
* @param {Function} config.formatSummary - Function to format summary output: (items, data) => void
|
|
24
24
|
* @param {Function} config.formatDetail - Function to format detail output: (view, framework) => void
|
|
25
|
+
* @param {Function} [config.formatListItem] - Optional function to format list item: (item) => string (defaults to item.id)
|
|
25
26
|
* @param {Function} [config.sortItems] - Optional function to sort items: (items) => sortedItems
|
|
26
27
|
* @param {Function} [config.validate] - Optional validation function: (data) => {errors: [], warnings: []}
|
|
27
28
|
* @param {string} [config.emojiIcon] - Optional emoji for the entity
|
|
@@ -34,6 +35,7 @@ export function createEntityCommand({
|
|
|
34
35
|
presentDetail,
|
|
35
36
|
formatSummary,
|
|
36
37
|
formatDetail,
|
|
38
|
+
formatListItem,
|
|
37
39
|
sortItems,
|
|
38
40
|
validate,
|
|
39
41
|
_emojiIcon = "",
|
|
@@ -48,10 +50,10 @@ export function createEntityCommand({
|
|
|
48
50
|
return handleValidate({ data, entityName, pluralName, validate });
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
// --list: Output
|
|
53
|
+
// --list: Output descriptive comma-separated lines for piping and AI agent discovery
|
|
52
54
|
if (options.list) {
|
|
53
55
|
for (const item of items) {
|
|
54
|
-
console.log(item.id);
|
|
56
|
+
console.log(formatListItem ? formatListItem(item) : item.id);
|
|
55
57
|
}
|
|
56
58
|
return;
|
|
57
59
|
}
|
package/src/commands/dev.js
CHANGED
|
@@ -31,7 +31,8 @@ function resolvePackageLib(packageName) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const mapLibDir = resolvePackageLib("@forwardimpact/map");
|
|
34
|
-
const modelLibDir = resolvePackageLib("@forwardimpact/
|
|
34
|
+
const modelLibDir = resolvePackageLib("@forwardimpact/libskill");
|
|
35
|
+
const uiLibDir = resolvePackageLib("@forwardimpact/libui");
|
|
35
36
|
|
|
36
37
|
const MIME_TYPES = {
|
|
37
38
|
".html": "text/html; charset=utf-8",
|
|
@@ -138,8 +139,14 @@ export async function runDevCommand({ dataDir, options }) {
|
|
|
138
139
|
// Serve @forwardimpact/map package files (resolved via Node module resolution)
|
|
139
140
|
filePath = join(mapLibDir, pathname.slice(9));
|
|
140
141
|
} else if (pathname.startsWith("/model/lib/")) {
|
|
141
|
-
// Serve @forwardimpact/
|
|
142
|
+
// Serve @forwardimpact/libskill package files (resolved via Node module resolution)
|
|
142
143
|
filePath = join(modelLibDir, pathname.slice(11));
|
|
144
|
+
} else if (pathname.startsWith("/ui/lib/")) {
|
|
145
|
+
// Serve @forwardimpact/libui package JS files
|
|
146
|
+
filePath = join(uiLibDir, pathname.slice(8));
|
|
147
|
+
} else if (pathname.startsWith("/ui/css/")) {
|
|
148
|
+
// Serve @forwardimpact/libui package CSS files
|
|
149
|
+
filePath = join(uiLibDir, "css", pathname.slice(8));
|
|
143
150
|
} else if (pathname === "/" || pathname === "") {
|
|
144
151
|
// Serve index.html for root
|
|
145
152
|
filePath = join(publicDir, "index.html");
|
|
@@ -14,6 +14,19 @@ import { createEntityCommand } from "./command-factory.js";
|
|
|
14
14
|
import { disciplineToMarkdown } from "../formatters/discipline/markdown.js";
|
|
15
15
|
import { formatTable } from "../lib/cli-output.js";
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Format discipline list item for --list output
|
|
19
|
+
* @param {Object} discipline - Discipline entity
|
|
20
|
+
* @returns {string} Formatted list line
|
|
21
|
+
*/
|
|
22
|
+
function formatListItem(discipline) {
|
|
23
|
+
const type = discipline.isProfessional ? "professional" : "management";
|
|
24
|
+
const tracks = (discipline.validTracks || [])
|
|
25
|
+
.filter((t) => t !== null)
|
|
26
|
+
.join("|");
|
|
27
|
+
return `${discipline.id}, ${discipline.specialization || discipline.id}, ${type}, ${tracks || "—"}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
/**
|
|
18
31
|
* Format discipline summary output
|
|
19
32
|
* @param {Array} disciplines - Raw discipline entities
|
|
@@ -21,16 +34,16 @@ import { formatTable } from "../lib/cli-output.js";
|
|
|
21
34
|
function formatSummary(disciplines) {
|
|
22
35
|
console.log(`\n📋 Disciplines\n`);
|
|
23
36
|
|
|
24
|
-
const rows = disciplines.map((d) =>
|
|
25
|
-
d.
|
|
26
|
-
d.
|
|
27
|
-
|
|
28
|
-
d.
|
|
29
|
-
|
|
37
|
+
const rows = disciplines.map((d) => {
|
|
38
|
+
const type = d.isProfessional ? "Professional" : "Management";
|
|
39
|
+
const validTracks = (d.validTracks || []).filter((t) => t !== null);
|
|
40
|
+
const trackStr = validTracks.length > 0 ? validTracks.join(", ") : "—";
|
|
41
|
+
return [d.id, d.specialization || d.id, type, trackStr];
|
|
42
|
+
});
|
|
30
43
|
|
|
31
|
-
console.log(formatTable(["ID", "
|
|
44
|
+
console.log(formatTable(["ID", "Specialization", "Type", "Tracks"], rows));
|
|
32
45
|
console.log(`\nTotal: ${disciplines.length} disciplines`);
|
|
33
|
-
console.log(`\nRun 'npx pathway discipline --list' for IDs`);
|
|
46
|
+
console.log(`\nRun 'npx pathway discipline --list' for IDs and names`);
|
|
34
47
|
console.log(`Run 'npx pathway discipline <id>' for details\n`);
|
|
35
48
|
}
|
|
36
49
|
|
|
@@ -39,8 +52,8 @@ function formatSummary(disciplines) {
|
|
|
39
52
|
* @param {Object} viewAndContext - Contains discipline entity and context
|
|
40
53
|
*/
|
|
41
54
|
function formatDetail(viewAndContext) {
|
|
42
|
-
const { discipline, skills, behaviours } = viewAndContext;
|
|
43
|
-
console.log(disciplineToMarkdown(discipline, { skills, behaviours }));
|
|
55
|
+
const { discipline, skills, behaviours, tracks } = viewAndContext;
|
|
56
|
+
console.log(disciplineToMarkdown(discipline, { skills, behaviours, tracks }));
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
export const runDisciplineCommand = createEntityCommand({
|
|
@@ -51,8 +64,10 @@ export const runDisciplineCommand = createEntityCommand({
|
|
|
51
64
|
discipline: entity,
|
|
52
65
|
skills: data.skills,
|
|
53
66
|
behaviours: data.behaviours,
|
|
67
|
+
tracks: data.tracks,
|
|
54
68
|
}),
|
|
55
69
|
formatSummary,
|
|
56
70
|
formatDetail,
|
|
71
|
+
formatListItem,
|
|
57
72
|
emojiIcon: "📋",
|
|
58
73
|
});
|
package/src/commands/driver.js
CHANGED
|
@@ -20,6 +20,15 @@ import {
|
|
|
20
20
|
} from "../lib/cli-output.js";
|
|
21
21
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Format driver list item for --list output
|
|
25
|
+
* @param {Object} driver - Driver entity
|
|
26
|
+
* @returns {string} Formatted list line
|
|
27
|
+
*/
|
|
28
|
+
function formatListItem(driver) {
|
|
29
|
+
return `${driver.id}, ${driver.name}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
/**
|
|
24
33
|
* Format driver summary output
|
|
25
34
|
* @param {Array} drivers - Raw driver entities
|
|
@@ -43,7 +52,7 @@ function formatSummary(drivers, data) {
|
|
|
43
52
|
|
|
44
53
|
console.log(formatTable(["ID", "Name", "Skills", "Behaviours"], rows));
|
|
45
54
|
console.log(`\nTotal: ${drivers.length} drivers`);
|
|
46
|
-
console.log(`\nRun 'npx pathway driver --list' for IDs`);
|
|
55
|
+
console.log(`\nRun 'npx pathway driver --list' for IDs and names`);
|
|
47
56
|
console.log(`Run 'npx pathway driver <id>' for details\n`);
|
|
48
57
|
}
|
|
49
58
|
|
|
@@ -90,5 +99,6 @@ export const runDriverCommand = createEntityCommand({
|
|
|
90
99
|
}),
|
|
91
100
|
formatSummary,
|
|
92
101
|
formatDetail,
|
|
102
|
+
formatListItem,
|
|
93
103
|
emojiIcon: "🎯",
|
|
94
104
|
});
|
package/src/commands/job.js
CHANGED
|
@@ -14,14 +14,17 @@
|
|
|
14
14
|
* npx pathway job --validate # Validation checks
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { prepareJobDetail } from "@forwardimpact/
|
|
17
|
+
import { prepareJobDetail } from "@forwardimpact/libskill/job";
|
|
18
18
|
import { jobToMarkdown } from "../formatters/job/markdown.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
generateJobTitle,
|
|
21
|
+
generateAllJobs,
|
|
22
|
+
} from "@forwardimpact/libskill/derivation";
|
|
20
23
|
import { formatTable } from "../lib/cli-output.js";
|
|
21
24
|
import {
|
|
22
25
|
deriveChecklist,
|
|
23
26
|
formatChecklistMarkdown,
|
|
24
|
-
} from "@forwardimpact/
|
|
27
|
+
} from "@forwardimpact/libskill/checklist";
|
|
25
28
|
import { loadJobTemplate } from "../lib/template-loader.js";
|
|
26
29
|
import { toolkitToPlainList } from "../formatters/toolkit/markdown.js";
|
|
27
30
|
|
|
@@ -54,13 +57,40 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
54
57
|
validationRules: data.framework.validationRules,
|
|
55
58
|
});
|
|
56
59
|
|
|
57
|
-
// --
|
|
60
|
+
// Apply --track filter to list and summary modes
|
|
61
|
+
const filteredJobs = options.track
|
|
62
|
+
? jobs.filter((j) => j.track && j.track.id === options.track)
|
|
63
|
+
: jobs;
|
|
64
|
+
|
|
65
|
+
if (options.track && filteredJobs.length === 0 && args.length === 0) {
|
|
66
|
+
const trackExists = data.tracks.some((t) => t.id === options.track);
|
|
67
|
+
if (!trackExists) {
|
|
68
|
+
console.error(`Track not found: ${options.track}`);
|
|
69
|
+
console.error(`Available: ${data.tracks.map((t) => t.id).join(", ")}`);
|
|
70
|
+
} else {
|
|
71
|
+
console.error(`No jobs found for track: ${options.track}`);
|
|
72
|
+
const trackDisciplines = data.disciplines
|
|
73
|
+
.filter((d) => d.validTracks && d.validTracks.includes(options.track))
|
|
74
|
+
.map((d) => d.id);
|
|
75
|
+
if (trackDisciplines.length > 0) {
|
|
76
|
+
console.error(
|
|
77
|
+
`Disciplines with this track: ${trackDisciplines.join(", ")}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --list: Output descriptive comma-separated lines for piping and AI agent discovery
|
|
58
85
|
if (options.list) {
|
|
59
|
-
for (const job of
|
|
86
|
+
for (const job of filteredJobs) {
|
|
87
|
+
const title = generateJobTitle(job.discipline, job.level, job.track);
|
|
60
88
|
if (job.track) {
|
|
61
|
-
console.log(
|
|
89
|
+
console.log(
|
|
90
|
+
`${job.discipline.id} ${job.level.id} ${job.track.id}, ${title}`,
|
|
91
|
+
);
|
|
62
92
|
} else {
|
|
63
|
-
console.log(`${job.discipline.id} ${job.level.id}`);
|
|
93
|
+
console.log(`${job.discipline.id} ${job.level.id}, ${title}`);
|
|
64
94
|
}
|
|
65
95
|
}
|
|
66
96
|
return;
|
|
@@ -68,19 +98,40 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
68
98
|
|
|
69
99
|
// No args: Show summary
|
|
70
100
|
if (args.length === 0) {
|
|
71
|
-
|
|
101
|
+
const trackLabel = options.track ? ` — ${options.track}` : "";
|
|
102
|
+
console.log(`\n💼 Jobs${trackLabel}\n`);
|
|
72
103
|
|
|
73
|
-
// Count by discipline
|
|
104
|
+
// Count by discipline with name, grouped by track
|
|
74
105
|
const byDiscipline = {};
|
|
75
|
-
for (const job of
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
for (const job of filteredJobs) {
|
|
107
|
+
const key = job.discipline.id;
|
|
108
|
+
if (!byDiscipline[key]) {
|
|
109
|
+
byDiscipline[key] = {
|
|
110
|
+
name: job.discipline.specialization || job.discipline.id,
|
|
111
|
+
roleTitle: job.discipline.roleTitle || job.discipline.id,
|
|
112
|
+
type: job.discipline.isProfessional ? "Professional" : "Management",
|
|
113
|
+
tracks: new Set(),
|
|
114
|
+
count: 0,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (job.track) byDiscipline[key].tracks.add(job.track.id);
|
|
118
|
+
byDiscipline[key].count++;
|
|
78
119
|
}
|
|
79
120
|
|
|
80
|
-
const rows = Object.entries(byDiscipline).map(([id,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
121
|
+
const rows = Object.entries(byDiscipline).map(([id, info]) => [
|
|
122
|
+
id,
|
|
123
|
+
info.name,
|
|
124
|
+
info.type,
|
|
125
|
+
info.count,
|
|
126
|
+
info.tracks.size > 0 ? [...info.tracks].join(", ") : "—",
|
|
127
|
+
]);
|
|
128
|
+
console.log(
|
|
129
|
+
formatTable(["ID", "Specialization", "Type", "Jobs", "Tracks"], rows),
|
|
130
|
+
);
|
|
131
|
+
console.log(`\nTotal: ${filteredJobs.length} valid job combinations`);
|
|
132
|
+
console.log(
|
|
133
|
+
`\nRun 'npx pathway job --list' for all combinations with titles`,
|
|
134
|
+
);
|
|
84
135
|
console.log(
|
|
85
136
|
`Run 'npx pathway job <discipline> <level> [--track=<track>]' for details\n`,
|
|
86
137
|
);
|
|
@@ -89,14 +140,28 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
89
140
|
|
|
90
141
|
// Handle job detail view - requires discipline and level
|
|
91
142
|
if (args.length < 2) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
143
|
+
// Check if the single arg is a level or track, hinting at what's missing
|
|
144
|
+
const arg = args[0];
|
|
145
|
+
const isLevel = data.levels.some((g) => g.id === arg);
|
|
146
|
+
const isTrack = data.tracks.some((t) => t.id === arg);
|
|
147
|
+
if (isLevel) {
|
|
148
|
+
console.error(
|
|
149
|
+
`Missing discipline. Usage: npx pathway job <discipline> ${arg} [--track=<track>]`,
|
|
150
|
+
);
|
|
151
|
+
console.error(
|
|
152
|
+
`Disciplines: ${data.disciplines.map((d) => d.id).join(", ")}`,
|
|
153
|
+
);
|
|
154
|
+
} else if (isTrack) {
|
|
155
|
+
console.error(`Track must be passed as a flag: --track=${arg}`);
|
|
156
|
+
console.error(
|
|
157
|
+
`Usage: npx pathway job <discipline> <level> --track=${arg}`,
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
console.error(
|
|
161
|
+
"Usage: npx pathway job <discipline> <level> [--track=<track>]",
|
|
162
|
+
);
|
|
163
|
+
console.error(" npx pathway job --list");
|
|
164
|
+
}
|
|
100
165
|
process.exit(1);
|
|
101
166
|
}
|
|
102
167
|
|
|
@@ -107,14 +172,36 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
107
172
|
: null;
|
|
108
173
|
|
|
109
174
|
if (!discipline) {
|
|
110
|
-
|
|
111
|
-
|
|
175
|
+
// Check if args are swapped (level first, discipline second)
|
|
176
|
+
const maybeLevel = data.levels.find((g) => g.id === args[0]);
|
|
177
|
+
const maybeDiscipline = data.disciplines.find((d) => d.id === args[1]);
|
|
178
|
+
if (maybeLevel && maybeDiscipline) {
|
|
179
|
+
console.error(`Arguments are in the wrong order. Try:`);
|
|
180
|
+
console.error(
|
|
181
|
+
` npx pathway job ${args[1]} ${args[0]}${options.track ? ` --track=${options.track}` : ""}`,
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
console.error(`Discipline not found: ${args[0]}`);
|
|
185
|
+
console.error(
|
|
186
|
+
`Available: ${data.disciplines.map((d) => d.id).join(", ")}`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
112
189
|
process.exit(1);
|
|
113
190
|
}
|
|
114
191
|
|
|
115
192
|
if (!level) {
|
|
116
|
-
|
|
117
|
-
|
|
193
|
+
// Check if the second arg is a track ID passed as positional
|
|
194
|
+
const isTrack = data.tracks.some((t) => t.id === args[1]);
|
|
195
|
+
if (isTrack) {
|
|
196
|
+
console.error(
|
|
197
|
+
`Track must be passed as a flag, not a positional argument:`,
|
|
198
|
+
);
|
|
199
|
+
console.error(` npx pathway job ${args[0]} <level> --track=${args[1]}`);
|
|
200
|
+
console.error(`Levels: ${data.levels.map((g) => g.id).join(", ")}`);
|
|
201
|
+
} else {
|
|
202
|
+
console.error(`Level not found: ${args[1]}`);
|
|
203
|
+
console.error(`Available: ${data.levels.map((g) => g.id).join(", ")}`);
|
|
204
|
+
}
|
|
118
205
|
process.exit(1);
|
|
119
206
|
}
|
|
120
207
|
|
|
@@ -151,6 +238,18 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
151
238
|
console.error(`${discipline.id} does not support tracks`);
|
|
152
239
|
}
|
|
153
240
|
}
|
|
241
|
+
// Check if it's a minLevel issue
|
|
242
|
+
if (discipline.minLevel) {
|
|
243
|
+
const levelIndex = data.levels.findIndex((g) => g.id === level.id);
|
|
244
|
+
const minIndex = data.levels.findIndex(
|
|
245
|
+
(g) => g.id === discipline.minLevel,
|
|
246
|
+
);
|
|
247
|
+
if (levelIndex >= 0 && minIndex >= 0 && levelIndex < minIndex) {
|
|
248
|
+
console.error(
|
|
249
|
+
`${discipline.id} requires minimum level: ${discipline.minLevel}`,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
154
253
|
process.exit(1);
|
|
155
254
|
}
|
|
156
255
|
|
package/src/commands/level.js
CHANGED
|
@@ -16,6 +16,15 @@ import { formatTable } from "../lib/cli-output.js";
|
|
|
16
16
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
17
17
|
import { capitalize } from "../formatters/shared.js";
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Format level list item for --list output
|
|
21
|
+
* @param {Object} level - Level entity
|
|
22
|
+
* @returns {string} Formatted list line
|
|
23
|
+
*/
|
|
24
|
+
function formatListItem(level) {
|
|
25
|
+
return `${level.id}, ${level.professionalTitle || level.id}, ${level.managementTitle || level.id}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
/**
|
|
20
29
|
* Format level summary output
|
|
21
30
|
* @param {Array} levels - Raw level entities
|
|
@@ -29,14 +38,26 @@ function formatSummary(levels, data) {
|
|
|
29
38
|
|
|
30
39
|
const rows = levels.map((g) => [
|
|
31
40
|
g.id,
|
|
32
|
-
g.displayName || g.id,
|
|
41
|
+
g.professionalTitle || g.displayName || g.id,
|
|
42
|
+
g.managementTitle || "-",
|
|
33
43
|
g.typicalExperienceRange || "-",
|
|
34
44
|
capitalize(g.baseSkillProficiencies?.primary || "-"),
|
|
35
45
|
]);
|
|
36
46
|
|
|
37
|
-
console.log(
|
|
47
|
+
console.log(
|
|
48
|
+
formatTable(
|
|
49
|
+
[
|
|
50
|
+
"ID",
|
|
51
|
+
"Professional Title",
|
|
52
|
+
"Management Title",
|
|
53
|
+
"Experience",
|
|
54
|
+
"Primary Level",
|
|
55
|
+
],
|
|
56
|
+
rows,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
38
59
|
console.log(`\nTotal: ${levels.length} levels`);
|
|
39
|
-
console.log(`\nRun 'npx pathway level --list' for IDs`);
|
|
60
|
+
console.log(`\nRun 'npx pathway level --list' for IDs and titles`);
|
|
40
61
|
console.log(`Run 'npx pathway level <id>' for details\n`);
|
|
41
62
|
}
|
|
42
63
|
|
|
@@ -56,5 +77,6 @@ export const runLevelCommand = createEntityCommand({
|
|
|
56
77
|
presentDetail: (entity) => entity,
|
|
57
78
|
formatSummary,
|
|
58
79
|
formatDetail,
|
|
80
|
+
formatListItem,
|
|
59
81
|
emojiIcon: "📊",
|
|
60
82
|
});
|
package/src/commands/skill.js
CHANGED
|
@@ -16,7 +16,7 @@ import { skillToMarkdown } from "../formatters/skill/markdown.js";
|
|
|
16
16
|
import { prepareSkillsList } from "../formatters/skill/shared.js";
|
|
17
17
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
18
18
|
import { formatTable, formatError } from "../lib/cli-output.js";
|
|
19
|
-
import { generateSkillMarkdown } from "@forwardimpact/
|
|
19
|
+
import { generateSkillMarkdown } from "@forwardimpact/libskill/agent";
|
|
20
20
|
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
21
21
|
import { loadSkillTemplate } from "../lib/template-loader.js";
|
|
22
22
|
|
|
@@ -85,6 +85,15 @@ async function formatAgentDetail(skill, stages, dataDir) {
|
|
|
85
85
|
console.log(output);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Format skill list item for --list output
|
|
90
|
+
* @param {Object} skill - Skill entity
|
|
91
|
+
* @returns {string} Formatted list line
|
|
92
|
+
*/
|
|
93
|
+
function formatListItem(skill) {
|
|
94
|
+
return `${skill.id}, ${skill.name}, ${skill.capability || "-"}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
88
97
|
const baseSkillCommand = createEntityCommand({
|
|
89
98
|
entityName: "skill",
|
|
90
99
|
pluralName: "skills",
|
|
@@ -98,6 +107,7 @@ const baseSkillCommand = createEntityCommand({
|
|
|
98
107
|
}),
|
|
99
108
|
formatSummary,
|
|
100
109
|
formatDetail,
|
|
110
|
+
formatListItem,
|
|
101
111
|
emojiIcon: "📚",
|
|
102
112
|
});
|
|
103
113
|
|
package/src/commands/stage.js
CHANGED
|
@@ -21,6 +21,15 @@ import {
|
|
|
21
21
|
formatBullet,
|
|
22
22
|
} from "../lib/cli-output.js";
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Format stage list item for --list output
|
|
26
|
+
* @param {Object} stage - Stage entity
|
|
27
|
+
* @returns {string} Formatted list line
|
|
28
|
+
*/
|
|
29
|
+
function formatListItem(stage) {
|
|
30
|
+
return `${stage.id}, ${stage.name}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
/**
|
|
25
34
|
* Format stage summary output
|
|
26
35
|
* @param {Array} stages - Raw stage entities
|
|
@@ -43,7 +52,7 @@ function formatSummary(stages, _data) {
|
|
|
43
52
|
|
|
44
53
|
console.log(formatTable(["ID", "Name", "Mode", "Tools", "Handoffs"], rows));
|
|
45
54
|
console.log(`\nTotal: ${stages.length} stages`);
|
|
46
|
-
console.log(`\nRun 'npx pathway stage --list' for IDs`);
|
|
55
|
+
console.log(`\nRun 'npx pathway stage --list' for IDs and names`);
|
|
47
56
|
console.log(`Run 'npx pathway stage <id>' for details\n`);
|
|
48
57
|
}
|
|
49
58
|
|
|
@@ -115,5 +124,6 @@ export const runStageCommand = createEntityCommand({
|
|
|
115
124
|
}),
|
|
116
125
|
formatSummary,
|
|
117
126
|
formatDetail,
|
|
127
|
+
formatListItem,
|
|
118
128
|
emojiIcon: "🔄",
|
|
119
129
|
});
|