@forwardimpact/pathway 0.25.24 → 0.25.25
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/package.json +1 -1
- package/src/commands/agent-list.js +164 -0
- package/src/commands/agent.js +74 -175
- package/src/commands/behaviour.js +18 -6
- package/src/commands/command-factory.js +39 -14
- package/src/commands/discipline.js +20 -6
- package/src/commands/driver.js +22 -13
- package/src/commands/interview.js +11 -6
- package/src/commands/job.js +101 -55
- package/src/commands/level.js +19 -7
- package/src/commands/progress.js +8 -3
- package/src/commands/questions.js +26 -10
- package/src/commands/skill.js +31 -13
- package/src/commands/stage.js +32 -22
- package/src/commands/tool.js +25 -15
- package/src/commands/track.js +19 -6
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { capitalize } from "../formatters/shared.js";
|
|
15
|
+
import {
|
|
16
|
+
formatSuccess,
|
|
17
|
+
formatWarning,
|
|
18
|
+
formatError,
|
|
19
|
+
formatBullet,
|
|
20
|
+
} from "@forwardimpact/libcli";
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Create an entity command with standard behavior
|
|
@@ -88,8 +94,15 @@ export function createEntityCommand({
|
|
|
88
94
|
*/
|
|
89
95
|
function handleValidate({ data, _entityName, pluralName, validate }) {
|
|
90
96
|
if (!validate) {
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
process.stdout.write(
|
|
98
|
+
formatBullet(`No specific validation for ${pluralName}.`, 0) + "\n",
|
|
99
|
+
);
|
|
100
|
+
process.stdout.write(
|
|
101
|
+
formatBullet(
|
|
102
|
+
"Run 'npx fit-pathway --validate' for full data validation.",
|
|
103
|
+
0,
|
|
104
|
+
) + "\n",
|
|
105
|
+
);
|
|
93
106
|
return;
|
|
94
107
|
}
|
|
95
108
|
|
|
@@ -97,21 +110,23 @@ function handleValidate({ data, _entityName, pluralName, validate }) {
|
|
|
97
110
|
const { errors = [], warnings = [] } = result;
|
|
98
111
|
|
|
99
112
|
if (errors.length === 0 && warnings.length === 0) {
|
|
100
|
-
|
|
113
|
+
process.stdout.write(
|
|
114
|
+
formatSuccess(`${capitalize(pluralName)} validation passed`) + "\n",
|
|
115
|
+
);
|
|
101
116
|
return;
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
if (warnings.length > 0) {
|
|
105
|
-
|
|
120
|
+
process.stdout.write(formatWarning(`${warnings.length} warning(s)`) + "\n");
|
|
106
121
|
for (const w of warnings) {
|
|
107
|
-
|
|
122
|
+
process.stdout.write(formatBullet(w, 1) + "\n");
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
if (errors.length > 0) {
|
|
112
|
-
|
|
127
|
+
process.stderr.write(formatError(`${errors.length} error(s)`) + "\n");
|
|
113
128
|
for (const e of errors) {
|
|
114
|
-
|
|
129
|
+
process.stderr.write(formatBullet(e, 1) + "\n");
|
|
115
130
|
}
|
|
116
131
|
process.exit(1);
|
|
117
132
|
}
|
|
@@ -134,15 +149,21 @@ function handleDetail({
|
|
|
134
149
|
const entity = findEntity(data, id);
|
|
135
150
|
|
|
136
151
|
if (!entity) {
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
process.stderr.write(
|
|
153
|
+
formatError(`${capitalize(entityName)} not found: ${id}`) + "\n",
|
|
154
|
+
);
|
|
155
|
+
process.stderr.write(
|
|
156
|
+
`Available: ${data[pluralName].map((e) => e.id).join(", ")}\n`,
|
|
157
|
+
);
|
|
139
158
|
process.exit(1);
|
|
140
159
|
}
|
|
141
160
|
|
|
142
161
|
const view = presentDetail(entity, data, options);
|
|
143
162
|
|
|
144
163
|
if (!view) {
|
|
145
|
-
|
|
164
|
+
process.stderr.write(
|
|
165
|
+
formatError(`Failed to present ${entityName}: ${id}`) + "\n",
|
|
166
|
+
);
|
|
146
167
|
process.exit(1);
|
|
147
168
|
}
|
|
148
169
|
|
|
@@ -178,9 +199,11 @@ export function createCompositeCommand({
|
|
|
178
199
|
return async function runCommand({ data, args, options }) {
|
|
179
200
|
if (args.length < requiredArgs.length) {
|
|
180
201
|
const argsList = requiredArgs.map((arg) => `<${arg}>`).join(" ");
|
|
181
|
-
|
|
202
|
+
process.stderr.write(
|
|
203
|
+
formatError(`Usage: npx fit-pathway ${commandName} ${argsList}`) + "\n",
|
|
204
|
+
);
|
|
182
205
|
if (usageExample) {
|
|
183
|
-
|
|
206
|
+
process.stderr.write(`Example: ${usageExample}\n`);
|
|
184
207
|
}
|
|
185
208
|
process.exit(1);
|
|
186
209
|
}
|
|
@@ -189,14 +212,16 @@ export function createCompositeCommand({
|
|
|
189
212
|
const validationError = validateEntities(entities, data, options);
|
|
190
213
|
|
|
191
214
|
if (validationError) {
|
|
192
|
-
|
|
215
|
+
process.stderr.write(formatError(validationError) + "\n");
|
|
193
216
|
process.exit(1);
|
|
194
217
|
}
|
|
195
218
|
|
|
196
219
|
const view = presenter(entities, data, options);
|
|
197
220
|
|
|
198
221
|
if (!view) {
|
|
199
|
-
|
|
222
|
+
process.stderr.write(
|
|
223
|
+
formatError(`Failed to generate ${commandName} output.`) + "\n",
|
|
224
|
+
);
|
|
200
225
|
process.exit(1);
|
|
201
226
|
}
|
|
202
227
|
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
import { createEntityCommand } from "./command-factory.js";
|
|
14
14
|
import { disciplineToMarkdown } from "../formatters/discipline/markdown.js";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
formatTable,
|
|
17
|
+
formatHeader,
|
|
18
|
+
formatSubheader,
|
|
19
|
+
formatBullet,
|
|
20
|
+
} from "@forwardimpact/libcli";
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
23
|
* Format discipline list item for --list output
|
|
@@ -32,7 +37,7 @@ function formatListItem(discipline) {
|
|
|
32
37
|
* @param {Array} disciplines - Raw discipline entities
|
|
33
38
|
*/
|
|
34
39
|
function formatSummary(disciplines) {
|
|
35
|
-
|
|
40
|
+
process.stdout.write("\n" + formatHeader("\u{1F4CB} Disciplines") + "\n\n");
|
|
36
41
|
|
|
37
42
|
const rows = disciplines.map((d) => {
|
|
38
43
|
const type = d.isProfessional ? "Professional" : "Management";
|
|
@@ -41,10 +46,19 @@ function formatSummary(disciplines) {
|
|
|
41
46
|
return [d.id, d.specialization || d.id, type, trackStr];
|
|
42
47
|
});
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
process.stdout.write(
|
|
50
|
+
formatTable(["ID", "Specialization", "Type", "Tracks"], rows) + "\n",
|
|
51
|
+
);
|
|
52
|
+
process.stdout.write(
|
|
53
|
+
"\n" + formatSubheader(`Total: ${disciplines.length} disciplines`) + "\n\n",
|
|
54
|
+
);
|
|
55
|
+
process.stdout.write(
|
|
56
|
+
formatBullet("Run 'npx fit-pathway discipline --list' for IDs and names") +
|
|
57
|
+
"\n",
|
|
58
|
+
);
|
|
59
|
+
process.stdout.write(
|
|
60
|
+
formatBullet("Run 'npx fit-pathway discipline <id>' for details") + "\n\n",
|
|
61
|
+
);
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
/**
|
package/src/commands/driver.js
CHANGED
|
@@ -38,7 +38,7 @@ function formatSummary(drivers, data) {
|
|
|
38
38
|
const { skills, behaviours, framework } = data;
|
|
39
39
|
const emoji = framework ? getConceptEmoji(framework, "driver") : "🎯";
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
process.stdout.write("\n" + formatHeader(`${emoji} Drivers`) + "\n\n");
|
|
42
42
|
|
|
43
43
|
const rows = drivers.map((d) => {
|
|
44
44
|
const contributingSkills = skills.filter((s) =>
|
|
@@ -50,10 +50,19 @@ function formatSummary(drivers, data) {
|
|
|
50
50
|
return [d.id, d.name, contributingSkills, contributingBehaviours];
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
process.stdout.write(
|
|
54
|
+
formatTable(["ID", "Name", "Skills", "Behaviours"], rows) + "\n",
|
|
55
|
+
);
|
|
56
|
+
process.stdout.write(
|
|
57
|
+
"\n" + formatSubheader(`Total: ${drivers.length} drivers`) + "\n\n",
|
|
58
|
+
);
|
|
59
|
+
process.stdout.write(
|
|
60
|
+
formatBullet("Run 'npx fit-pathway driver --list' for IDs and names") +
|
|
61
|
+
"\n",
|
|
62
|
+
);
|
|
63
|
+
process.stdout.write(
|
|
64
|
+
formatBullet("Run 'npx fit-pathway driver <id>' for details") + "\n\n",
|
|
65
|
+
);
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
/**
|
|
@@ -66,25 +75,25 @@ function formatDetail(viewAndContext, framework) {
|
|
|
66
75
|
const view = prepareDriverDetail(driver, { skills, behaviours });
|
|
67
76
|
const emoji = framework ? getConceptEmoji(framework, "driver") : "🎯";
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
process.stdout.write("\n" + formatHeader(`${emoji} ${view.name}`) + "\n\n");
|
|
79
|
+
process.stdout.write(view.description + "\n\n");
|
|
71
80
|
|
|
72
81
|
// Contributing skills
|
|
73
82
|
if (view.contributingSkills.length > 0) {
|
|
74
|
-
|
|
83
|
+
process.stdout.write(formatSubheader("Contributing Skills") + "\n\n");
|
|
75
84
|
for (const s of view.contributingSkills) {
|
|
76
|
-
|
|
85
|
+
process.stdout.write(formatBullet(s.name, 1) + "\n");
|
|
77
86
|
}
|
|
78
|
-
|
|
87
|
+
process.stdout.write("\n");
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
// Contributing behaviours
|
|
82
91
|
if (view.contributingBehaviours.length > 0) {
|
|
83
|
-
|
|
92
|
+
process.stdout.write(formatSubheader("Contributing Behaviours") + "\n\n");
|
|
84
93
|
for (const b of view.contributingBehaviours) {
|
|
85
|
-
|
|
94
|
+
process.stdout.write(formatBullet(b.name, 1) + "\n");
|
|
86
95
|
}
|
|
87
|
-
|
|
96
|
+
process.stdout.write("\n");
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
INTERVIEW_TYPES,
|
|
16
16
|
} from "../formatters/interview/shared.js";
|
|
17
17
|
import { interviewToMarkdown } from "../formatters/interview/markdown.js";
|
|
18
|
+
import { formatError, horizontalRule } from "@forwardimpact/libcli";
|
|
18
19
|
|
|
19
20
|
const VALID_TYPES = Object.keys(INTERVIEW_TYPES);
|
|
20
21
|
|
|
@@ -24,7 +25,9 @@ const VALID_TYPES = Object.keys(INTERVIEW_TYPES);
|
|
|
24
25
|
* @param {Object} options - Options including framework
|
|
25
26
|
*/
|
|
26
27
|
function formatInterview(view, options) {
|
|
27
|
-
|
|
28
|
+
process.stdout.write(
|
|
29
|
+
interviewToMarkdown(view, { framework: options.framework }) + "\n",
|
|
30
|
+
);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/**
|
|
@@ -35,10 +38,10 @@ function formatInterview(view, options) {
|
|
|
35
38
|
function formatAllInterviews(views, options) {
|
|
36
39
|
for (let i = 0; i < views.length; i++) {
|
|
37
40
|
if (i > 0) {
|
|
38
|
-
|
|
41
|
+
process.stdout.write("\n" + horizontalRule(80) + "\n\n");
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
interviewToMarkdown(views[i], { framework: options.framework }),
|
|
43
|
+
process.stdout.write(
|
|
44
|
+
interviewToMarkdown(views[i], { framework: options.framework }) + "\n",
|
|
42
45
|
);
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -50,8 +53,10 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
50
53
|
const interviewType = options.type === "full" ? null : options.type;
|
|
51
54
|
|
|
52
55
|
if (interviewType && !INTERVIEW_TYPES[interviewType]) {
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
process.stderr.write(
|
|
57
|
+
formatError(`Unknown interview type: ${interviewType}`) + "\n",
|
|
58
|
+
);
|
|
59
|
+
process.stderr.write(`Available types: ${VALID_TYPES.join(", ")}\n`);
|
|
55
60
|
process.exit(1);
|
|
56
61
|
}
|
|
57
62
|
|
package/src/commands/job.js
CHANGED
|
@@ -20,7 +20,13 @@ import {
|
|
|
20
20
|
generateJobTitle,
|
|
21
21
|
generateAllJobs,
|
|
22
22
|
} from "@forwardimpact/libskill/derivation";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
formatTable,
|
|
25
|
+
formatHeader,
|
|
26
|
+
formatSubheader,
|
|
27
|
+
formatBullet,
|
|
28
|
+
formatError,
|
|
29
|
+
} from "@forwardimpact/libcli";
|
|
24
30
|
import {
|
|
25
31
|
deriveChecklist,
|
|
26
32
|
formatChecklistMarkdown,
|
|
@@ -66,7 +72,9 @@ function printJobList(filteredJobs) {
|
|
|
66
72
|
*/
|
|
67
73
|
function printJobSummary(filteredJobs, options) {
|
|
68
74
|
const trackLabel = options.track ? ` — ${options.track}` : "";
|
|
69
|
-
|
|
75
|
+
process.stdout.write(
|
|
76
|
+
"\n" + formatHeader(`\u{1F4BC} Jobs${trackLabel}`) + "\n\n",
|
|
77
|
+
);
|
|
70
78
|
|
|
71
79
|
const byDiscipline = {};
|
|
72
80
|
for (const job of filteredJobs) {
|
|
@@ -91,15 +99,24 @@ function printJobSummary(filteredJobs, options) {
|
|
|
91
99
|
info.count,
|
|
92
100
|
info.tracks.size > 0 ? [...info.tracks].join(", ") : "—",
|
|
93
101
|
]);
|
|
94
|
-
|
|
95
|
-
formatTable(["ID", "Specialization", "Type", "Jobs", "Tracks"], rows)
|
|
102
|
+
process.stdout.write(
|
|
103
|
+
formatTable(["ID", "Specialization", "Type", "Jobs", "Tracks"], rows) +
|
|
104
|
+
"\n",
|
|
96
105
|
);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
process.stdout.write(
|
|
107
|
+
"\n" +
|
|
108
|
+
formatSubheader(`Total: ${filteredJobs.length} valid job combinations`) +
|
|
109
|
+
"\n\n",
|
|
100
110
|
);
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
process.stdout.write(
|
|
112
|
+
formatBullet(
|
|
113
|
+
"Run 'npx fit-pathway job --list' for all combinations with titles",
|
|
114
|
+
) + "\n",
|
|
115
|
+
);
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
formatBullet(
|
|
118
|
+
"Run 'npx fit-pathway job <discipline> <level> [--track=<track>]' for details",
|
|
119
|
+
) + "\n\n",
|
|
103
120
|
);
|
|
104
121
|
}
|
|
105
122
|
|
|
@@ -112,22 +129,28 @@ function handleSingleArg(arg, data) {
|
|
|
112
129
|
const isLevel = data.levels.some((g) => g.id === arg);
|
|
113
130
|
const isTrack = data.tracks.some((t) => t.id === arg);
|
|
114
131
|
if (isLevel) {
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
process.stderr.write(
|
|
133
|
+
formatError(
|
|
134
|
+
`Missing discipline. Usage: npx fit-pathway job <discipline> ${arg} [--track=<track>]`,
|
|
135
|
+
) + "\n",
|
|
117
136
|
);
|
|
118
|
-
|
|
119
|
-
`Disciplines: ${data.disciplines.map((d) => d.id).join(", ")}`,
|
|
137
|
+
process.stderr.write(
|
|
138
|
+
`Disciplines: ${data.disciplines.map((d) => d.id).join(", ")}\n`,
|
|
120
139
|
);
|
|
121
140
|
} else if (isTrack) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
141
|
+
process.stderr.write(
|
|
142
|
+
formatError(`Track must be passed as a flag: --track=${arg}`) + "\n",
|
|
143
|
+
);
|
|
144
|
+
process.stderr.write(
|
|
145
|
+
`Usage: npx fit-pathway job <discipline> <level> --track=${arg}\n`,
|
|
125
146
|
);
|
|
126
147
|
} else {
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
process.stderr.write(
|
|
149
|
+
formatError(
|
|
150
|
+
"Usage: npx fit-pathway job <discipline> <level> [--track=<track>]",
|
|
151
|
+
) + "\n",
|
|
129
152
|
);
|
|
130
|
-
|
|
153
|
+
process.stderr.write(" npx fit-pathway job --list\n");
|
|
131
154
|
}
|
|
132
155
|
process.exit(1);
|
|
133
156
|
}
|
|
@@ -150,14 +173,18 @@ function resolveJobEntities(data, args, options) {
|
|
|
150
173
|
const maybeLevel = data.levels.find((g) => g.id === args[0]);
|
|
151
174
|
const maybeDiscipline = data.disciplines.find((d) => d.id === args[1]);
|
|
152
175
|
if (maybeLevel && maybeDiscipline) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
176
|
+
process.stderr.write(
|
|
177
|
+
formatError("Arguments are in the wrong order. Try:") + "\n",
|
|
178
|
+
);
|
|
179
|
+
process.stderr.write(
|
|
180
|
+
` npx fit-pathway job ${args[1]} ${args[0]}${options.track ? ` --track=${options.track}` : ""}\n`,
|
|
156
181
|
);
|
|
157
182
|
} else {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
process.stderr.write(
|
|
184
|
+
formatError(`Discipline not found: ${args[0]}`) + "\n",
|
|
185
|
+
);
|
|
186
|
+
process.stderr.write(
|
|
187
|
+
`Available: ${data.disciplines.map((d) => d.id).join(", ")}\n`,
|
|
161
188
|
);
|
|
162
189
|
}
|
|
163
190
|
process.exit(1);
|
|
@@ -166,23 +193,33 @@ function resolveJobEntities(data, args, options) {
|
|
|
166
193
|
if (!level) {
|
|
167
194
|
const isTrack = data.tracks.some((t) => t.id === args[1]);
|
|
168
195
|
if (isTrack) {
|
|
169
|
-
|
|
170
|
-
|
|
196
|
+
process.stderr.write(
|
|
197
|
+
formatError(
|
|
198
|
+
"Track must be passed as a flag, not a positional argument:",
|
|
199
|
+
) + "\n",
|
|
200
|
+
);
|
|
201
|
+
process.stderr.write(
|
|
202
|
+
` npx fit-pathway job ${args[0]} <level> --track=${args[1]}\n`,
|
|
171
203
|
);
|
|
172
|
-
|
|
173
|
-
`
|
|
204
|
+
process.stderr.write(
|
|
205
|
+
`Levels: ${data.levels.map((g) => g.id).join(", ")}\n`,
|
|
174
206
|
);
|
|
175
|
-
console.error(`Levels: ${data.levels.map((g) => g.id).join(", ")}`);
|
|
176
207
|
} else {
|
|
177
|
-
|
|
178
|
-
|
|
208
|
+
process.stderr.write(formatError(`Level not found: ${args[1]}`) + "\n");
|
|
209
|
+
process.stderr.write(
|
|
210
|
+
`Available: ${data.levels.map((g) => g.id).join(", ")}\n`,
|
|
211
|
+
);
|
|
179
212
|
}
|
|
180
213
|
process.exit(1);
|
|
181
214
|
}
|
|
182
215
|
|
|
183
216
|
if (options.track && !track) {
|
|
184
|
-
|
|
185
|
-
|
|
217
|
+
process.stderr.write(
|
|
218
|
+
formatError(`Track not found: ${options.track}`) + "\n",
|
|
219
|
+
);
|
|
220
|
+
process.stderr.write(
|
|
221
|
+
`Available: ${data.tracks.map((t) => t.id).join(", ")}\n`,
|
|
222
|
+
);
|
|
186
223
|
process.exit(1);
|
|
187
224
|
}
|
|
188
225
|
|
|
@@ -198,8 +235,8 @@ function resolveJobEntities(data, args, options) {
|
|
|
198
235
|
function handleChecklist(view, data, stageId) {
|
|
199
236
|
const validStages = data.stages.map((s) => s.id);
|
|
200
237
|
if (!validStages.includes(stageId)) {
|
|
201
|
-
|
|
202
|
-
|
|
238
|
+
process.stderr.write(formatError(`Invalid stage: ${stageId}`) + "\n");
|
|
239
|
+
process.stderr.write(`Available: ${validStages.join(", ")}\n`);
|
|
203
240
|
process.exit(1);
|
|
204
241
|
}
|
|
205
242
|
|
|
@@ -211,21 +248,24 @@ function handleChecklist(view, data, stageId) {
|
|
|
211
248
|
});
|
|
212
249
|
|
|
213
250
|
if (readChecklist.length === 0 && confirmChecklist.length === 0) {
|
|
214
|
-
|
|
251
|
+
process.stdout.write(
|
|
252
|
+
"\n" +
|
|
253
|
+
formatSubheader(`No checklist items for ${stageId} stage`) +
|
|
254
|
+
"\n\n",
|
|
255
|
+
);
|
|
215
256
|
return;
|
|
216
257
|
}
|
|
217
258
|
|
|
259
|
+
// Markdown output (#/##) is intentional — this is consumed as markdown.
|
|
218
260
|
const stageLabel = stageId.charAt(0).toUpperCase() + stageId.slice(1);
|
|
219
|
-
|
|
261
|
+
process.stdout.write(`\n# ${view.title} — ${stageLabel} Stage Checklist\n\n`);
|
|
220
262
|
if (readChecklist.length > 0) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
console.log("");
|
|
263
|
+
process.stdout.write("## Read-Then-Do\n\n");
|
|
264
|
+
process.stdout.write(formatChecklistMarkdown(readChecklist) + "\n\n");
|
|
224
265
|
}
|
|
225
266
|
if (confirmChecklist.length > 0) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
console.log("");
|
|
267
|
+
process.stdout.write("## Do-Then-Confirm\n\n");
|
|
268
|
+
process.stdout.write(formatChecklistMarkdown(confirmChecklist) + "\n\n");
|
|
229
269
|
}
|
|
230
270
|
}
|
|
231
271
|
|
|
@@ -247,16 +287,22 @@ function validateTrackFilter(filteredJobs, data, options) {
|
|
|
247
287
|
if (!options.track || filteredJobs.length > 0) return;
|
|
248
288
|
const trackExists = data.tracks.some((t) => t.id === options.track);
|
|
249
289
|
if (!trackExists) {
|
|
250
|
-
|
|
251
|
-
|
|
290
|
+
process.stderr.write(
|
|
291
|
+
formatError(`Track not found: ${options.track}`) + "\n",
|
|
292
|
+
);
|
|
293
|
+
process.stderr.write(
|
|
294
|
+
`Available: ${data.tracks.map((t) => t.id).join(", ")}\n`,
|
|
295
|
+
);
|
|
252
296
|
} else {
|
|
253
|
-
|
|
297
|
+
process.stderr.write(
|
|
298
|
+
formatError(`No jobs found for track: ${options.track}`) + "\n",
|
|
299
|
+
);
|
|
254
300
|
const trackDisciplines = data.disciplines
|
|
255
301
|
.filter((d) => d.validTracks && d.validTracks.includes(options.track))
|
|
256
302
|
.map((d) => d.id);
|
|
257
303
|
if (trackDisciplines.length > 0) {
|
|
258
|
-
|
|
259
|
-
`Disciplines with this track: ${trackDisciplines.join(", ")}`,
|
|
304
|
+
process.stderr.write(
|
|
305
|
+
`Disciplines with this track: ${trackDisciplines.join(", ")}\n`,
|
|
260
306
|
);
|
|
261
307
|
}
|
|
262
308
|
}
|
|
@@ -274,23 +320,23 @@ function reportInvalidCombination(discipline, level, track, data) {
|
|
|
274
320
|
const combo = track
|
|
275
321
|
? `${discipline.id} × ${level.id} × ${track.id}`
|
|
276
322
|
: `${discipline.id} × ${level.id}`;
|
|
277
|
-
|
|
323
|
+
process.stderr.write(formatError(`Invalid combination: ${combo}`) + "\n");
|
|
278
324
|
if (track) {
|
|
279
325
|
const validTracks = discipline.validTracks?.filter((t) => t !== null) || [];
|
|
280
326
|
if (validTracks.length > 0) {
|
|
281
|
-
|
|
282
|
-
`Valid tracks for ${discipline.id}: ${validTracks.join(", ")}`,
|
|
327
|
+
process.stderr.write(
|
|
328
|
+
`Valid tracks for ${discipline.id}: ${validTracks.join(", ")}\n`,
|
|
283
329
|
);
|
|
284
330
|
} else {
|
|
285
|
-
|
|
331
|
+
process.stderr.write(`${discipline.id} does not support tracks\n`);
|
|
286
332
|
}
|
|
287
333
|
}
|
|
288
334
|
if (discipline.minLevel) {
|
|
289
335
|
const levelIndex = data.levels.findIndex((g) => g.id === level.id);
|
|
290
336
|
const minIndex = data.levels.findIndex((g) => g.id === discipline.minLevel);
|
|
291
337
|
if (levelIndex >= 0 && minIndex >= 0 && levelIndex < minIndex) {
|
|
292
|
-
|
|
293
|
-
`${discipline.id} requires minimum level: ${discipline.minLevel}`,
|
|
338
|
+
process.stderr.write(
|
|
339
|
+
`${discipline.id} requires minimum level: ${discipline.minLevel}\n`,
|
|
294
340
|
);
|
|
295
341
|
}
|
|
296
342
|
}
|
package/src/commands/level.js
CHANGED
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
|
|
13
13
|
import { createEntityCommand } from "./command-factory.js";
|
|
14
14
|
import { levelToMarkdown } from "../formatters/level/markdown.js";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
formatTable,
|
|
17
|
+
formatHeader,
|
|
18
|
+
formatSubheader,
|
|
19
|
+
formatBullet,
|
|
20
|
+
} from "@forwardimpact/libcli";
|
|
16
21
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
17
22
|
import { capitalize } from "../formatters/shared.js";
|
|
18
23
|
|
|
@@ -34,7 +39,7 @@ function formatSummary(levels, data) {
|
|
|
34
39
|
const { framework } = data;
|
|
35
40
|
const emoji = framework ? getConceptEmoji(framework, "level") : "📊";
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
process.stdout.write("\n" + formatHeader(`${emoji} Levels`) + "\n\n");
|
|
38
43
|
|
|
39
44
|
const rows = levels.map((g) => [
|
|
40
45
|
g.id,
|
|
@@ -44,7 +49,7 @@ function formatSummary(levels, data) {
|
|
|
44
49
|
capitalize(g.baseSkillProficiencies?.primary || "-"),
|
|
45
50
|
]);
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
process.stdout.write(
|
|
48
53
|
formatTable(
|
|
49
54
|
[
|
|
50
55
|
"ID",
|
|
@@ -54,11 +59,18 @@ function formatSummary(levels, data) {
|
|
|
54
59
|
"Primary Level",
|
|
55
60
|
],
|
|
56
61
|
rows,
|
|
57
|
-
),
|
|
62
|
+
) + "\n",
|
|
63
|
+
);
|
|
64
|
+
process.stdout.write(
|
|
65
|
+
"\n" + formatSubheader(`Total: ${levels.length} levels`) + "\n\n",
|
|
66
|
+
);
|
|
67
|
+
process.stdout.write(
|
|
68
|
+
formatBullet("Run 'npx fit-pathway level --list' for IDs and titles") +
|
|
69
|
+
"\n",
|
|
70
|
+
);
|
|
71
|
+
process.stdout.write(
|
|
72
|
+
formatBullet("Run 'npx fit-pathway level <id>' for details") + "\n\n",
|
|
58
73
|
);
|
|
59
|
-
console.log(`\nTotal: ${levels.length} levels`);
|
|
60
|
-
console.log(`\nRun 'npx fit-pathway level --list' for IDs and titles`);
|
|
61
|
-
console.log(`Run 'npx fit-pathway level <id>' for details\n`);
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
/**
|
package/src/commands/progress.js
CHANGED
|
@@ -15,13 +15,14 @@ import {
|
|
|
15
15
|
getDefaultTargetLevel,
|
|
16
16
|
} from "../formatters/progress/shared.js";
|
|
17
17
|
import { progressToMarkdown } from "../formatters/progress/markdown.js";
|
|
18
|
+
import { formatError } from "@forwardimpact/libcli";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Format progress output
|
|
21
22
|
* @param {Object} view - Presenter view
|
|
22
23
|
*/
|
|
23
24
|
function formatProgress(view) {
|
|
24
|
-
|
|
25
|
+
process.stdout.write(progressToMarkdown(view) + "\n");
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export const runProgressCommand = createCompositeCommand({
|
|
@@ -38,13 +39,17 @@ export const runProgressCommand = createCompositeCommand({
|
|
|
38
39
|
if (options.compare) {
|
|
39
40
|
targetLevel = data.levels.find((g) => g.id === options.compare);
|
|
40
41
|
if (!targetLevel) {
|
|
41
|
-
|
|
42
|
+
process.stderr.write(
|
|
43
|
+
formatError(`Target level not found: ${options.compare}`) + "\n",
|
|
44
|
+
);
|
|
42
45
|
process.exit(1);
|
|
43
46
|
}
|
|
44
47
|
} else {
|
|
45
48
|
targetLevel = getDefaultTargetLevel(level, data.levels);
|
|
46
49
|
if (!targetLevel) {
|
|
47
|
-
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
formatError("No next level available for progression.") + "\n",
|
|
52
|
+
);
|
|
48
53
|
process.exit(1);
|
|
49
54
|
}
|
|
50
55
|
}
|