@forwardimpact/pathway 0.25.22 → 0.25.24
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 +117 -325
- package/package.json +2 -2
- package/src/commands/agent-io.js +1 -1
- package/src/commands/agent.js +13 -13
- package/src/commands/behaviour.js +7 -7
- package/src/commands/build-packs.js +208 -34
- package/src/commands/build.js +2 -2
- package/src/commands/command-factory.js +2 -2
- package/src/commands/discipline.js +7 -7
- package/src/commands/driver.js +8 -8
- package/src/commands/index.js +0 -1
- package/src/commands/interview.js +4 -4
- package/src/commands/job.js +19 -17
- package/src/commands/level.js +7 -7
- package/src/commands/progress.js +4 -4
- package/src/commands/questions.js +10 -8
- package/src/commands/skill.js +9 -9
- package/src/commands/stage.js +7 -7
- package/src/commands/tool.js +6 -6
- package/src/commands/track.js +7 -7
- package/src/formatters/questions/yaml.js +1 -1
- package/src/index.html +1 -1
- package/src/lib/cli-command.js +33 -33
- package/src/lib/cli-output.js +9 -189
- package/src/pages/agent-builder-install.js +6 -5
- package/src/commands/init.js +0 -64
- package/starter/behaviours/systems_thinking.yaml +0 -32
- package/starter/capabilities/delivery.yaml +0 -105
- package/starter/capabilities/reliability.yaml +0 -72
- package/starter/disciplines/software_engineering.yaml +0 -46
- package/starter/drivers.yaml +0 -10
- package/starter/framework.yaml +0 -49
- package/starter/levels.yaml +0 -39
- package/starter/questions/behaviours/.gitkeep +0 -0
- package/starter/questions/capabilities/.gitkeep +0 -0
- package/starter/questions/skills/.gitkeep +0 -0
- package/starter/stages.yaml +0 -21
- package/starter/tracks/forward_deployed.yaml +0 -33
- package/starter/tracks/platform.yaml +0 -33
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pack generation for Pathway distribution.
|
|
3
3
|
*
|
|
4
|
-
* Emits one pre-built agent/skill pack per valid discipline/track combination
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
4
|
+
* Emits one pre-built agent/skill pack per valid discipline/track combination.
|
|
5
|
+
* Each pack becomes its own `npx skills`-compatible repository at
|
|
6
|
+
* `packs/{name}/.well-known/skills/`, with an aggregate repository at
|
|
7
|
+
* `packs/.well-known/skills/` listing every skill from every pack.
|
|
8
|
+
* An `apm.yml` for Microsoft APM is also written at the site root.
|
|
9
|
+
*
|
|
10
|
+
* See specs/320-pathway-ecosystem-distribution for context.
|
|
8
11
|
*
|
|
9
12
|
* Invoked from build.js after the distribution bundle has been generated.
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
|
-
import { mkdir, rm, readFile, writeFile } from "fs/promises";
|
|
15
|
+
import { mkdir, rm, readFile, writeFile, readdir, cp } from "fs/promises";
|
|
16
|
+
import { utimesSync } from "fs";
|
|
13
17
|
import { join } from "path";
|
|
14
18
|
import { execFileSync } from "child_process";
|
|
15
19
|
import { createHash } from "crypto";
|
|
@@ -209,50 +213,205 @@ function derivePackContent({
|
|
|
209
213
|
return { profiles, skillFiles, teamInstructions };
|
|
210
214
|
}
|
|
211
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Recursively collect all paths (files and directories) under `dir`,
|
|
218
|
+
* relative to `dir`, in sorted order.
|
|
219
|
+
*/
|
|
220
|
+
async function collectPaths(dir, prefix = ".") {
|
|
221
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
222
|
+
const result = [];
|
|
223
|
+
for (const entry of entries) {
|
|
224
|
+
const rel = prefix + "/" + entry.name;
|
|
225
|
+
const abs = join(dir, entry.name);
|
|
226
|
+
if (entry.isDirectory()) {
|
|
227
|
+
result.push(rel);
|
|
228
|
+
result.push(...(await collectPaths(abs, rel)));
|
|
229
|
+
} else {
|
|
230
|
+
result.push(rel);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Set mtime and atime to the Unix epoch for every entry under `dir`.
|
|
238
|
+
*/
|
|
239
|
+
async function resetTimestamps(dir) {
|
|
240
|
+
const epoch = new Date(0);
|
|
241
|
+
const paths = await collectPaths(dir);
|
|
242
|
+
for (const rel of paths) {
|
|
243
|
+
utimesSync(join(dir, rel), epoch, epoch);
|
|
244
|
+
}
|
|
245
|
+
utimesSync(dir, epoch, epoch);
|
|
246
|
+
}
|
|
247
|
+
|
|
212
248
|
/**
|
|
213
249
|
* Archive a staged pack directory as a deterministic tar.gz and return its
|
|
214
250
|
* sha256 digest.
|
|
251
|
+
*
|
|
252
|
+
* Determinism strategy (works on GNU tar and BSD tar):
|
|
253
|
+
* 1. Reset all file timestamps to epoch via Node's utimesSync.
|
|
254
|
+
* 2. Collect and sort the file list in JS — no reliance on --sort=name.
|
|
255
|
+
* 3. Create an uncompressed tar to stdout with the sorted list.
|
|
256
|
+
* 4. Pipe through `gzip -n` to suppress the gzip header timestamp.
|
|
257
|
+
*
|
|
215
258
|
* @param {string} packDir - Staging directory containing the pack files
|
|
216
259
|
* @param {string} archivePath - Destination path for the tar.gz
|
|
217
260
|
* @returns {Promise<string>} sha256 digest string (e.g. "sha256:abc...")
|
|
218
261
|
*/
|
|
219
262
|
async function archivePack(packDir, archivePath) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"-C",
|
|
229
|
-
packDir,
|
|
230
|
-
".",
|
|
231
|
-
]);
|
|
263
|
+
await resetTimestamps(packDir);
|
|
264
|
+
|
|
265
|
+
const files = await collectPaths(packDir);
|
|
266
|
+
files.sort();
|
|
267
|
+
|
|
268
|
+
const tarBuf = execFileSync("tar", ["-cf", "-", "-C", packDir, ...files]);
|
|
269
|
+
const gzBuf = execFileSync("gzip", ["-n"], { input: tarBuf });
|
|
270
|
+
await writeFile(archivePath, gzBuf);
|
|
232
271
|
|
|
233
272
|
const bytes = await readFile(archivePath);
|
|
234
273
|
return "sha256:" + createHash("sha256").update(bytes).digest("hex");
|
|
235
274
|
}
|
|
236
275
|
|
|
237
276
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* @param {
|
|
241
|
-
* @param {string}
|
|
277
|
+
* Collect all file paths under `dir`, relative to `dir`, for the manifest
|
|
278
|
+
* `files` array. Returns sorted paths with forward slashes.
|
|
279
|
+
* @param {string} dir
|
|
280
|
+
* @param {string} [prefix]
|
|
281
|
+
* @returns {Promise<string[]>}
|
|
282
|
+
*/
|
|
283
|
+
async function collectFileList(dir, prefix = "") {
|
|
284
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
285
|
+
const result = [];
|
|
286
|
+
for (const entry of entries) {
|
|
287
|
+
const rel = prefix ? prefix + "/" + entry.name : entry.name;
|
|
288
|
+
if (entry.isDirectory()) {
|
|
289
|
+
result.push(...(await collectFileList(join(dir, entry.name), rel)));
|
|
290
|
+
} else {
|
|
291
|
+
result.push(rel);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result.sort();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Parse YAML frontmatter from a SKILL.md file. Returns an object with
|
|
299
|
+
* the key/value pairs found between the `---` fences.
|
|
300
|
+
* @param {string} content
|
|
301
|
+
* @returns {Record<string, string>}
|
|
302
|
+
*/
|
|
303
|
+
function parseFrontmatter(content) {
|
|
304
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
305
|
+
if (!match) return {};
|
|
306
|
+
const result = {};
|
|
307
|
+
for (const line of match[1].split("\n")) {
|
|
308
|
+
const idx = line.indexOf(":");
|
|
309
|
+
if (idx > 0) result[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Build a single skill index entry from a staged skill directory.
|
|
316
|
+
* @param {string} skillDir - Path containing SKILL.md and optional extras
|
|
317
|
+
* @param {string} name - Skill name for the manifest
|
|
318
|
+
* @returns {Promise<{name: string, description: string, files: string[]}>}
|
|
319
|
+
*/
|
|
320
|
+
async function buildSkillEntry(skillDir, name) {
|
|
321
|
+
const skillMd = await readFile(join(skillDir, "SKILL.md"), "utf-8");
|
|
322
|
+
const fm = parseFrontmatter(skillMd);
|
|
323
|
+
const files = await collectFileList(skillDir);
|
|
324
|
+
return { description: fm.description || "", files, name };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Write a `npx skills`-compatible repository for a single pack.
|
|
329
|
+
*
|
|
330
|
+
* Each pack becomes its own skill repository at
|
|
331
|
+
* `packs/{name}/.well-known/skills/` so that individual skills
|
|
332
|
+
* within the pack can be discovered and installed independently:
|
|
333
|
+
*
|
|
334
|
+
* npx skills add domain.org/packs/se-platform --all
|
|
335
|
+
* npx skills add domain.org/packs/se-platform -s architecture-design
|
|
336
|
+
*
|
|
337
|
+
* @param {string} packsOutputDir - The `packs/` output directory
|
|
338
|
+
* @param {string} packStagingDir - Staging directory for this pack
|
|
339
|
+
* @param {string} packName - Pack name (e.g. "se-platform")
|
|
340
|
+
* @returns {Promise<Array<{name: string, description: string, files: string[]}>>}
|
|
341
|
+
* The skill entries written, for use in the aggregate manifest.
|
|
242
342
|
*/
|
|
243
|
-
async function
|
|
244
|
-
const wellKnownDir = join(
|
|
343
|
+
async function writePackRepository(packsOutputDir, packStagingDir, packName) {
|
|
344
|
+
const wellKnownDir = join(packsOutputDir, packName, ".well-known", "skills");
|
|
245
345
|
await mkdir(wellKnownDir, { recursive: true });
|
|
346
|
+
|
|
347
|
+
// Discover individual skills from the staged pack's .claude/skills/
|
|
348
|
+
const skillsSrcDir = join(packStagingDir, ".claude", "skills");
|
|
349
|
+
const skillDirs = (
|
|
350
|
+
await readdir(skillsSrcDir, { withFileTypes: true })
|
|
351
|
+
).filter((e) => e.isDirectory());
|
|
352
|
+
|
|
353
|
+
const entries = [];
|
|
354
|
+
for (const dir of skillDirs) {
|
|
355
|
+
const src = join(skillsSrcDir, dir.name);
|
|
356
|
+
const dest = join(wellKnownDir, dir.name);
|
|
357
|
+
await cp(src, dest, { recursive: true });
|
|
358
|
+
entries.push(await buildSkillEntry(dest, dir.name));
|
|
359
|
+
}
|
|
360
|
+
|
|
246
361
|
const manifest = {
|
|
247
362
|
$schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
|
|
248
|
-
skills:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
363
|
+
skills: entries,
|
|
364
|
+
};
|
|
365
|
+
await writeFile(
|
|
366
|
+
join(wellKnownDir, "index.json"),
|
|
367
|
+
stringifySorted(manifest),
|
|
368
|
+
"utf-8",
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
return entries;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Write an aggregate `npx skills` repository at `packs/` that lists every
|
|
376
|
+
* unique skill across all packs. Skills with the same name produce identical
|
|
377
|
+
* SKILL.md content regardless of discipline/track, so we deduplicate by name
|
|
378
|
+
* and write one copy.
|
|
379
|
+
*
|
|
380
|
+
* npx skills add domain.org/packs --list
|
|
381
|
+
*
|
|
382
|
+
* @param {string} packsOutputDir
|
|
383
|
+
* @param {Array<{packName: string, entries: Array}>} allPackEntries
|
|
384
|
+
*/
|
|
385
|
+
async function writeAggregateRepository(packsOutputDir, allPackEntries) {
|
|
386
|
+
const wellKnownDir = join(packsOutputDir, ".well-known", "skills");
|
|
387
|
+
await mkdir(wellKnownDir, { recursive: true });
|
|
388
|
+
|
|
389
|
+
// Deduplicate: first occurrence of each skill name wins (content is identical)
|
|
390
|
+
const seen = new Map();
|
|
391
|
+
for (const { packName, entries } of allPackEntries) {
|
|
392
|
+
for (const entry of entries) {
|
|
393
|
+
if (seen.has(entry.name)) continue;
|
|
394
|
+
seen.set(entry.name, { packName, entry });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const skills = [];
|
|
399
|
+
for (const [, { packName, entry }] of seen) {
|
|
400
|
+
const dest = join(wellKnownDir, entry.name);
|
|
401
|
+
const src = join(
|
|
402
|
+
packsOutputDir,
|
|
403
|
+
packName,
|
|
404
|
+
".well-known",
|
|
405
|
+
"skills",
|
|
406
|
+
entry.name,
|
|
407
|
+
);
|
|
408
|
+
await cp(src, dest, { recursive: true });
|
|
409
|
+
skills.push(entry);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const manifest = {
|
|
413
|
+
$schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
|
|
414
|
+
skills,
|
|
256
415
|
};
|
|
257
416
|
await writeFile(
|
|
258
417
|
join(wellKnownDir, "index.json"),
|
|
@@ -382,10 +541,25 @@ export async function generatePacks({
|
|
|
382
541
|
console.log(` ✓ packs/${agentName}.tar.gz`);
|
|
383
542
|
}
|
|
384
543
|
|
|
385
|
-
|
|
544
|
+
// Write per-pack skill repositories (one per discipline/track combination)
|
|
545
|
+
const allPackEntries = [];
|
|
546
|
+
for (const pack of packs) {
|
|
547
|
+
const entries = await writePackRepository(
|
|
548
|
+
packsDir,
|
|
549
|
+
join(stagingDir, pack.name),
|
|
550
|
+
pack.name,
|
|
551
|
+
);
|
|
552
|
+
allPackEntries.push({ packName: pack.name, entries });
|
|
553
|
+
console.log(
|
|
554
|
+
` ✓ packs/${pack.name}/.well-known/skills/ (${entries.length} skills)`,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
386
557
|
|
|
387
|
-
|
|
388
|
-
|
|
558
|
+
// Write aggregate repository at packs/ level
|
|
559
|
+
await writeAggregateRepository(packsDir, allPackEntries);
|
|
560
|
+
console.log(" ✓ packs/.well-known/skills/index.json (aggregate)");
|
|
561
|
+
|
|
562
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
389
563
|
|
|
390
564
|
await writeApmManifest(outputDir, packs, version, frameworkTitle);
|
|
391
565
|
console.log(" ✓ apm.yml");
|
package/src/commands/build.js
CHANGED
|
@@ -222,10 +222,10 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
|
|
|
222
222
|
✅ Site generated successfully!
|
|
223
223
|
|
|
224
224
|
Output: ${outputDir}
|
|
225
|
-
${siteUrl ? `\nDistribution:\n ${outputDir}/bundle.tar.gz\n ${outputDir}/install.sh\n ${outputDir}/packs/ (agent/skill packs)\n ${outputDir}/.well-known/
|
|
225
|
+
${siteUrl ? `\nDistribution:\n ${outputDir}/bundle.tar.gz\n ${outputDir}/install.sh\n ${outputDir}/packs/ (agent/skill packs)\n ${outputDir}/packs/{name}/.well-known/skills/ (per-pack skill repositories)\n ${outputDir}/packs/.well-known/skills/ (aggregate skill repository)\n ${outputDir}/apm.yml\n` : ""}
|
|
226
226
|
To serve locally:
|
|
227
227
|
cd ${relative(process.cwd(), outputDir) || "."}
|
|
228
|
-
|
|
228
|
+
npx serve .
|
|
229
229
|
`);
|
|
230
230
|
}
|
|
231
231
|
|
|
@@ -89,7 +89,7 @@ export function createEntityCommand({
|
|
|
89
89
|
function handleValidate({ data, _entityName, pluralName, validate }) {
|
|
90
90
|
if (!validate) {
|
|
91
91
|
console.log(`No specific validation for ${pluralName}.`);
|
|
92
|
-
console.log(`Run '
|
|
92
|
+
console.log(`Run 'npx fit-pathway --validate' for full data validation.`);
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -178,7 +178,7 @@ export function createCompositeCommand({
|
|
|
178
178
|
return async function runCommand({ data, args, options }) {
|
|
179
179
|
if (args.length < requiredArgs.length) {
|
|
180
180
|
const argsList = requiredArgs.map((arg) => `<${arg}>`).join(" ");
|
|
181
|
-
console.error(`Usage:
|
|
181
|
+
console.error(`Usage: npx fit-pathway ${commandName} ${argsList}`);
|
|
182
182
|
if (usageExample) {
|
|
183
183
|
console.error(`Example: ${usageExample}`);
|
|
184
184
|
}
|
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
* Handles discipline summary, listing, and detail display in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* npx fit-pathway discipline # Summary with stats
|
|
8
|
+
* npx fit-pathway discipline --list # IDs only (for piping)
|
|
9
|
+
* npx fit-pathway discipline <id> # Detail view
|
|
10
|
+
* npx fit-pathway discipline --validate # Validation checks
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { createEntityCommand } from "./command-factory.js";
|
|
14
14
|
import { disciplineToMarkdown } from "../formatters/discipline/markdown.js";
|
|
15
|
-
import { formatTable } from "
|
|
15
|
+
import { formatTable } from "@forwardimpact/libcli";
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Format discipline list item for --list output
|
|
@@ -43,8 +43,8 @@ function formatSummary(disciplines) {
|
|
|
43
43
|
|
|
44
44
|
console.log(formatTable(["ID", "Specialization", "Type", "Tracks"], rows));
|
|
45
45
|
console.log(`\nTotal: ${disciplines.length} disciplines`);
|
|
46
|
-
console.log(`\nRun '
|
|
47
|
-
console.log(`Run '
|
|
46
|
+
console.log(`\nRun 'npx fit-pathway discipline --list' for IDs and names`);
|
|
47
|
+
console.log(`Run 'npx fit-pathway discipline <id>' for details\n`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
package/src/commands/driver.js
CHANGED
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
* Handles driver summary, listing, and detail display in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* npx fit-pathway driver # Summary with stats
|
|
8
|
+
* npx fit-pathway driver --list # IDs only (for piping)
|
|
9
|
+
* npx fit-pathway driver <id> # Detail view
|
|
10
|
+
* npx fit-pathway driver --validate # Validation checks
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { createEntityCommand } from "./command-factory.js";
|
|
14
14
|
import { prepareDriverDetail } from "../formatters/driver/shared.js";
|
|
15
|
-
import { formatTable } from "../lib/cli-output.js";
|
|
16
15
|
import {
|
|
16
|
+
formatTable,
|
|
17
17
|
formatHeader,
|
|
18
18
|
formatSubheader,
|
|
19
19
|
formatBullet,
|
|
20
|
-
} from "
|
|
20
|
+
} from "@forwardimpact/libcli";
|
|
21
21
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -52,8 +52,8 @@ function formatSummary(drivers, data) {
|
|
|
52
52
|
|
|
53
53
|
console.log(formatTable(["ID", "Name", "Skills", "Behaviours"], rows));
|
|
54
54
|
console.log(`\nTotal: ${drivers.length} drivers`);
|
|
55
|
-
console.log(`\nRun '
|
|
56
|
-
console.log(`Run '
|
|
55
|
+
console.log(`\nRun 'npx fit-pathway driver --list' for IDs and names`);
|
|
56
|
+
console.log(`Run 'npx fit-pathway driver <id>' for details\n`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
package/src/commands/index.js
CHANGED
|
@@ -17,6 +17,5 @@ export { runInterviewCommand } from "./interview.js";
|
|
|
17
17
|
export { runProgressCommand } from "./progress.js";
|
|
18
18
|
export { runQuestionsCommand } from "./questions.js";
|
|
19
19
|
export { runServeCommand } from "./serve.js";
|
|
20
|
-
export { runInitCommand } from "./init.js";
|
|
21
20
|
export { runSiteCommand } from "./site.js";
|
|
22
21
|
export { runUpdateCommand } from "./update.js";
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Generates and displays interview questions in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* npx fit-pathway interview <discipline> <level> # All interview types
|
|
8
|
+
* npx fit-pathway interview <discipline> <level> --track=<track> # With track
|
|
9
|
+
* npx fit-pathway interview <discipline> <level> --track=<track> --type=mission # Single type
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createCompositeCommand } from "./command-factory.js";
|
|
@@ -109,5 +109,5 @@ export const runInterviewCommand = createCompositeCommand({
|
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
111
|
usageExample:
|
|
112
|
-
"
|
|
112
|
+
"npx fit-pathway interview software_engineering J090 --track=platform --type=mission",
|
|
113
113
|
});
|
package/src/commands/job.js
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Generates and displays job definitions in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
7
|
+
* npx fit-pathway job # Summary with stats
|
|
8
|
+
* npx fit-pathway job --list # All valid combinations (for piping)
|
|
9
|
+
* npx fit-pathway job <discipline> <level> # Detail view (trackless)
|
|
10
|
+
* npx fit-pathway job <discipline> <level> --track=<track> # Detail view (with track)
|
|
11
|
+
* npx fit-pathway job <d> <l> [--track=<t>] --skills # Plain list of skill IDs
|
|
12
|
+
* npx fit-pathway job <d> <l> [--track=<t>] --tools # Plain list of tool names
|
|
13
|
+
* npx fit-pathway job se L3 --track=platform --checklist=code # Show checklist for handoff
|
|
14
|
+
* npx fit-pathway job --validate # Validation checks
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { prepareJobDetail } from "@forwardimpact/libskill/job";
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
generateJobTitle,
|
|
21
21
|
generateAllJobs,
|
|
22
22
|
} from "@forwardimpact/libskill/derivation";
|
|
23
|
-
import { formatTable } from "
|
|
23
|
+
import { formatTable } from "@forwardimpact/libcli";
|
|
24
24
|
import {
|
|
25
25
|
deriveChecklist,
|
|
26
26
|
formatChecklistMarkdown,
|
|
@@ -96,10 +96,10 @@ function printJobSummary(filteredJobs, options) {
|
|
|
96
96
|
);
|
|
97
97
|
console.log(`\nTotal: ${filteredJobs.length} valid job combinations`);
|
|
98
98
|
console.log(
|
|
99
|
-
`\nRun '
|
|
99
|
+
`\nRun 'npx fit-pathway job --list' for all combinations with titles`,
|
|
100
100
|
);
|
|
101
101
|
console.log(
|
|
102
|
-
`Run '
|
|
102
|
+
`Run 'npx fit-pathway job <discipline> <level> [--track=<track>]' for details\n`,
|
|
103
103
|
);
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -113,7 +113,7 @@ function handleSingleArg(arg, data) {
|
|
|
113
113
|
const isTrack = data.tracks.some((t) => t.id === arg);
|
|
114
114
|
if (isLevel) {
|
|
115
115
|
console.error(
|
|
116
|
-
`Missing discipline. Usage:
|
|
116
|
+
`Missing discipline. Usage: npx fit-pathway job <discipline> ${arg} [--track=<track>]`,
|
|
117
117
|
);
|
|
118
118
|
console.error(
|
|
119
119
|
`Disciplines: ${data.disciplines.map((d) => d.id).join(", ")}`,
|
|
@@ -121,13 +121,13 @@ function handleSingleArg(arg, data) {
|
|
|
121
121
|
} else if (isTrack) {
|
|
122
122
|
console.error(`Track must be passed as a flag: --track=${arg}`);
|
|
123
123
|
console.error(
|
|
124
|
-
`Usage:
|
|
124
|
+
`Usage: npx fit-pathway job <discipline> <level> --track=${arg}`,
|
|
125
125
|
);
|
|
126
126
|
} else {
|
|
127
127
|
console.error(
|
|
128
|
-
"Usage:
|
|
128
|
+
"Usage: npx fit-pathway job <discipline> <level> [--track=<track>]",
|
|
129
129
|
);
|
|
130
|
-
console.error("
|
|
130
|
+
console.error(" npx fit-pathway job --list");
|
|
131
131
|
}
|
|
132
132
|
process.exit(1);
|
|
133
133
|
}
|
|
@@ -152,7 +152,7 @@ function resolveJobEntities(data, args, options) {
|
|
|
152
152
|
if (maybeLevel && maybeDiscipline) {
|
|
153
153
|
console.error(`Arguments are in the wrong order. Try:`);
|
|
154
154
|
console.error(
|
|
155
|
-
`
|
|
155
|
+
` npx fit-pathway job ${args[1]} ${args[0]}${options.track ? ` --track=${options.track}` : ""}`,
|
|
156
156
|
);
|
|
157
157
|
} else {
|
|
158
158
|
console.error(`Discipline not found: ${args[0]}`);
|
|
@@ -169,7 +169,9 @@ function resolveJobEntities(data, args, options) {
|
|
|
169
169
|
console.error(
|
|
170
170
|
`Track must be passed as a flag, not a positional argument:`,
|
|
171
171
|
);
|
|
172
|
-
console.error(
|
|
172
|
+
console.error(
|
|
173
|
+
` npx fit-pathway job ${args[0]} <level> --track=${args[1]}`,
|
|
174
|
+
);
|
|
173
175
|
console.error(`Levels: ${data.levels.map((g) => g.id).join(", ")}`);
|
|
174
176
|
} else {
|
|
175
177
|
console.error(`Level not found: ${args[1]}`);
|
package/src/commands/level.js
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
* Handles level summary, listing, and detail display in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* npx fit-pathway level # Summary with stats
|
|
8
|
+
* npx fit-pathway level --list # IDs only (for piping)
|
|
9
|
+
* npx fit-pathway level <id> # Detail view
|
|
10
|
+
* npx fit-pathway level --validate # Validation checks
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { createEntityCommand } from "./command-factory.js";
|
|
14
14
|
import { levelToMarkdown } from "../formatters/level/markdown.js";
|
|
15
|
-
import { formatTable } from "
|
|
15
|
+
import { formatTable } from "@forwardimpact/libcli";
|
|
16
16
|
import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
17
17
|
import { capitalize } from "../formatters/shared.js";
|
|
18
18
|
|
|
@@ -57,8 +57,8 @@ function formatSummary(levels, data) {
|
|
|
57
57
|
),
|
|
58
58
|
);
|
|
59
59
|
console.log(`\nTotal: ${levels.length} levels`);
|
|
60
|
-
console.log(`\nRun '
|
|
61
|
-
console.log(`Run '
|
|
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
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
package/src/commands/progress.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Shows career progression analysis in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* npx fit-pathway progress <discipline> <level> # Progress for trackless job
|
|
8
|
+
* npx fit-pathway progress <discipline> <level> --track=<track> # Progress with track
|
|
9
|
+
* npx fit-pathway progress <discipline> <from_level> --compare=<to_level> # Compare levels
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createCompositeCommand } from "./command-factory.js";
|
|
@@ -80,5 +80,5 @@ export const runProgressCommand = createCompositeCommand({
|
|
|
80
80
|
}),
|
|
81
81
|
formatter: formatProgress,
|
|
82
82
|
usageExample:
|
|
83
|
-
"
|
|
83
|
+
"npx fit-pathway progress software_engineering L3 --track=platform --compare=L4",
|
|
84
84
|
});
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Browse and compare interview questions across skills and behaviours.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* npx fit-pathway questions # Summary with stats
|
|
8
|
+
* npx fit-pathway questions --list # Question IDs (for piping)
|
|
9
|
+
* npx fit-pathway questions --level=practitioner # Filter by level
|
|
10
|
+
* npx fit-pathway questions --stats # Detailed statistics
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
import { questionsToMarkdown } from "../formatters/questions/markdown.js";
|
|
18
18
|
import { questionsToYaml } from "../formatters/questions/yaml.js";
|
|
19
19
|
import { questionsToJson } from "../formatters/questions/json.js";
|
|
20
|
-
import { formatTable } from "
|
|
20
|
+
import { formatTable } from "@forwardimpact/libcli";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Parse questions command options
|
|
@@ -97,9 +97,11 @@ function showQuestionsSummary(data) {
|
|
|
97
97
|
console.log("\nBehaviour Questions:");
|
|
98
98
|
console.log(formatTable(["Maturity", "Count"], behaviourRows));
|
|
99
99
|
|
|
100
|
-
console.log(`\nRun '
|
|
101
|
-
console.log(`Run '
|
|
102
|
-
console.log(
|
|
100
|
+
console.log(`\nRun 'npx fit-pathway questions --list' for question IDs`);
|
|
101
|
+
console.log(`Run 'npx fit-pathway questions --stats' for detailed stats`);
|
|
102
|
+
console.log(
|
|
103
|
+
`Run 'npx fit-pathway questions --level=practitioner' to filter\n`,
|
|
104
|
+
);
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
/**
|
package/src/commands/skill.js
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
* Handles skill summary, listing, and detail display in the terminal.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* npx fit-pathway skill # Summary with stats
|
|
8
|
+
* npx fit-pathway skill --list # IDs only (for piping)
|
|
9
|
+
* npx fit-pathway skill <id> # Detail view
|
|
10
|
+
* npx fit-pathway skill <id> --agent # Agent SKILL.md output
|
|
11
|
+
* npx fit-pathway skill --validate # Validation checks
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { createEntityCommand } from "./command-factory.js";
|
|
15
15
|
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
|
-
import { formatTable, formatError } from "
|
|
18
|
+
import { formatTable, formatError } from "@forwardimpact/libcli";
|
|
19
19
|
import { generateSkillMarkdown } from "@forwardimpact/libskill/agent";
|
|
20
20
|
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
21
21
|
|
|
@@ -40,8 +40,8 @@ function formatSummary(skills, data) {
|
|
|
40
40
|
|
|
41
41
|
console.log(formatTable(["Capability", "Count", "Agent"], rows));
|
|
42
42
|
console.log(`\nTotal: ${skills.length} skills`);
|
|
43
|
-
console.log(`\nRun '
|
|
44
|
-
console.log(`Run '
|
|
43
|
+
console.log(`\nRun 'npx fit-pathway skill --list' for IDs`);
|
|
44
|
+
console.log(`Run 'npx fit-pathway skill <id>' for details\n`);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -73,7 +73,7 @@ async function formatAgentDetail(skill, stages, templateLoader, dataDir) {
|
|
|
73
73
|
console.error(formatError(`Skill '${skill.id}' has no agent section`));
|
|
74
74
|
console.error(`\nSkills with agent support:`);
|
|
75
75
|
console.error(
|
|
76
|
-
`
|
|
76
|
+
` npx fit-pathway skill --list | xargs -I{} sh -c 'npx fit-pathway skill {} --json | jq -e .skill.agent > /dev/null && echo {}'`,
|
|
77
77
|
);
|
|
78
78
|
process.exit(1);
|
|
79
79
|
}
|