@forwardimpact/pathway 0.23.1 → 0.24.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/README.md +19 -19
- package/bin/fit-pathway.js +32 -40
- package/package.json +3 -2
- package/src/commands/agent.js +23 -18
- package/src/commands/build.js +6 -4
- package/src/commands/dev.js +6 -4
- package/src/commands/init.js +23 -6
- package/src/commands/job.js +8 -3
- package/src/commands/skill.js +10 -5
- package/src/commands/update.js +3 -2
- package/src/formatters/job/description.js +2 -7
- package/src/lib/yaml-loader.js +9 -3
- package/src/main.js +1 -0
- package/src/lib/template-loader.js +0 -93
package/README.md
CHANGED
|
@@ -7,15 +7,15 @@ teams.
|
|
|
7
7
|
|
|
8
8
|
Pathway is the primary interface for interacting with engineering competency
|
|
9
9
|
data. It provides tools for browsing career paths, generating job descriptions,
|
|
10
|
-
|
|
11
|
-
experience and command line.
|
|
10
|
+
generating agent teams and skills, and preparing interviews—all from a unified
|
|
11
|
+
web experience and command line.
|
|
12
12
|
|
|
13
13
|
## What It Does
|
|
14
14
|
|
|
15
15
|
- **Web application** — Interactive browser for jobs, skills, and career paths
|
|
16
16
|
- **CLI tools** — Command-line access to all functionality
|
|
17
|
-
- **Agent
|
|
18
|
-
|
|
17
|
+
- **Agent teams** — Generate VS Code Custom Agent teams (`.agent.md`) and skills
|
|
18
|
+
(`SKILL.md`)
|
|
19
19
|
- **Interview prep** — Build interview question sets by role
|
|
20
20
|
- **Static site** — Export everything as a static site
|
|
21
21
|
|
|
@@ -29,24 +29,24 @@ npx fit-pathway serve
|
|
|
29
29
|
npx fit-pathway skill --list
|
|
30
30
|
npx fit-pathway job software_engineering senior --track=platform
|
|
31
31
|
|
|
32
|
-
# Generate agent
|
|
32
|
+
# Generate agent teams and skills
|
|
33
33
|
npx fit-pathway agent software_engineering --track=platform --output=./.github/agents
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## CLI Commands
|
|
37
37
|
|
|
38
|
-
| Command | Description
|
|
39
|
-
| ----------- |
|
|
40
|
-
| `serve` | Start web server
|
|
41
|
-
| `site` | Generate static site
|
|
42
|
-
| `init` | Create data directory
|
|
43
|
-
| `skill` | Browse skills
|
|
44
|
-
| `behaviour` | Browse behaviours
|
|
45
|
-
| `job` | Generate job definitions
|
|
46
|
-
| `agent` | Generate agent
|
|
47
|
-
| `interview` | Generate interview questions
|
|
48
|
-
| `progress` | Analyze career progression
|
|
49
|
-
| `questions` | Browse interview questions
|
|
38
|
+
| Command | Description |
|
|
39
|
+
| ----------- | ------------------------------- |
|
|
40
|
+
| `serve` | Start web server |
|
|
41
|
+
| `site` | Generate static site |
|
|
42
|
+
| `init` | Create data directory |
|
|
43
|
+
| `skill` | Browse skills |
|
|
44
|
+
| `behaviour` | Browse behaviours |
|
|
45
|
+
| `job` | Generate job definitions |
|
|
46
|
+
| `agent` | Generate agent teams and skills |
|
|
47
|
+
| `interview` | Generate interview questions |
|
|
48
|
+
| `progress` | Analyze career progression |
|
|
49
|
+
| `questions` | Browse interview questions |
|
|
50
50
|
|
|
51
51
|
Use `--help` with any command for full options.
|
|
52
52
|
|
|
@@ -56,7 +56,7 @@ Use `--help` with any command for full options.
|
|
|
56
56
|
- **Skill Browser** — View all skills with proficiency descriptions
|
|
57
57
|
- **Career Progression** — Compare levels and identify growth areas
|
|
58
58
|
- **Interview Prep** — Generate role-specific question sets
|
|
59
|
-
- **Agent Preview** — Preview generated agent
|
|
59
|
+
- **Agent Preview** — Preview generated agent teams and skills
|
|
60
60
|
|
|
61
61
|
## Package Exports
|
|
62
62
|
|
|
@@ -65,4 +65,4 @@ import { formatSkillForMarkdown } from "@forwardimpact/pathway/formatters";
|
|
|
65
65
|
import { runCommand } from "@forwardimpact/pathway/commands";
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
See the [documentation](../../docs/pathway/index.md) for usage details.
|
|
68
|
+
See the [documentation](../../website/docs/pathway/index.md) for usage details.
|
package/bin/fit-pathway.js
CHANGED
|
@@ -29,11 +29,14 @@
|
|
|
29
29
|
* --help Show help
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
import { join, resolve } from "path";
|
|
32
|
+
import { join, resolve, dirname } from "path";
|
|
33
33
|
import { existsSync } from "fs";
|
|
34
34
|
import { homedir } from "os";
|
|
35
|
-
import {
|
|
35
|
+
import { fileURLToPath } from "url";
|
|
36
|
+
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
37
|
+
import { validateAllData } from "@forwardimpact/map/validation";
|
|
36
38
|
import { formatError } from "../src/lib/cli-output.js";
|
|
39
|
+
import { createTemplateLoader } from "@forwardimpact/libtemplate";
|
|
37
40
|
|
|
38
41
|
// Import command handlers
|
|
39
42
|
import { runDisciplineCommand } from "../src/commands/discipline.js";
|
|
@@ -54,6 +57,9 @@ import { runInitCommand } from "../src/commands/init.js";
|
|
|
54
57
|
import { runBuildCommand } from "../src/commands/build.js";
|
|
55
58
|
import { runUpdateCommand } from "../src/commands/update.js";
|
|
56
59
|
|
|
60
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
61
|
+
const TEMPLATE_DIR = join(__dirname, "..", "templates");
|
|
62
|
+
|
|
57
63
|
const COMMANDS = {
|
|
58
64
|
discipline: runDisciplineCommand,
|
|
59
65
|
level: runLevelCommand,
|
|
@@ -235,9 +241,7 @@ function parseArgs(args) {
|
|
|
235
241
|
type: "full",
|
|
236
242
|
compare: null,
|
|
237
243
|
data: null,
|
|
238
|
-
// Shared command options
|
|
239
244
|
track: null,
|
|
240
|
-
// Questions command options
|
|
241
245
|
level: null,
|
|
242
246
|
maturity: null,
|
|
243
247
|
skill: null,
|
|
@@ -245,20 +249,15 @@ function parseArgs(args) {
|
|
|
245
249
|
capability: null,
|
|
246
250
|
format: null,
|
|
247
251
|
stats: false,
|
|
248
|
-
// Job command options
|
|
249
252
|
checklist: null,
|
|
250
253
|
skills: false,
|
|
251
254
|
tools: false,
|
|
252
|
-
// Agent command options
|
|
253
255
|
output: null,
|
|
254
256
|
stage: null,
|
|
255
257
|
"all-stages": false,
|
|
256
258
|
agent: false,
|
|
257
|
-
// Serve command options
|
|
258
259
|
port: null,
|
|
259
|
-
// Init command options
|
|
260
260
|
path: null,
|
|
261
|
-
// Site command options
|
|
262
261
|
clean: true,
|
|
263
262
|
url: null,
|
|
264
263
|
};
|
|
@@ -339,54 +338,45 @@ function printHelp() {
|
|
|
339
338
|
|
|
340
339
|
/**
|
|
341
340
|
* Resolve the data directory path.
|
|
342
|
-
* Resolution order:
|
|
343
|
-
* 1. --data=<path> flag (explicit override)
|
|
344
|
-
* 2. PATHWAY_DATA environment variable
|
|
345
|
-
* 3. ~/.fit/pathway/data/ (home directory install)
|
|
346
|
-
* 4. ./data/ relative to current working directory
|
|
347
|
-
* 5. ./examples/ relative to current working directory
|
|
348
|
-
* 6. products/map/examples/ for monorepo development
|
|
349
|
-
*
|
|
350
341
|
* @param {Object} options - Parsed command options
|
|
351
342
|
* @returns {string} Resolved absolute path to data directory
|
|
352
343
|
*/
|
|
353
344
|
function resolveDataPath(options) {
|
|
354
|
-
// 1. Explicit flag
|
|
355
345
|
if (options.data) {
|
|
356
346
|
return resolve(options.data);
|
|
357
347
|
}
|
|
358
348
|
|
|
359
|
-
// 2. Environment variable
|
|
360
349
|
if (process.env.PATHWAY_DATA) {
|
|
361
350
|
return resolve(process.env.PATHWAY_DATA);
|
|
362
351
|
}
|
|
363
352
|
|
|
364
|
-
// 3. Home directory install (~/.fit/pathway/data/)
|
|
365
353
|
const homeData = join(homedir(), ".fit", "pathway", "data");
|
|
366
354
|
if (existsSync(homeData)) {
|
|
367
355
|
return homeData;
|
|
368
356
|
}
|
|
369
357
|
|
|
370
|
-
|
|
358
|
+
const cwdDataPathway = join(process.cwd(), "data/pathway");
|
|
359
|
+
if (existsSync(cwdDataPathway)) {
|
|
360
|
+
return cwdDataPathway;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const cwdExamplesPathway = join(process.cwd(), "examples/pathway");
|
|
364
|
+
if (existsSync(cwdExamplesPathway)) {
|
|
365
|
+
return cwdExamplesPathway;
|
|
366
|
+
}
|
|
367
|
+
|
|
371
368
|
const cwdData = join(process.cwd(), "data");
|
|
372
369
|
if (existsSync(cwdData)) {
|
|
373
370
|
return cwdData;
|
|
374
371
|
}
|
|
375
372
|
|
|
376
|
-
// 5. Current working directory ./examples/
|
|
377
373
|
const cwdExamples = join(process.cwd(), "examples");
|
|
378
374
|
if (existsSync(cwdExamples)) {
|
|
379
375
|
return cwdExamples;
|
|
380
376
|
}
|
|
381
377
|
|
|
382
|
-
// 6. Monorepo: products/map/examples/
|
|
383
|
-
const mapExamples = join(process.cwd(), "products/map/examples");
|
|
384
|
-
if (existsSync(mapExamples)) {
|
|
385
|
-
return mapExamples;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
378
|
throw new Error(
|
|
389
|
-
"No data directory found. Create ./data/ or use --data=<path>",
|
|
379
|
+
"No data directory found. Create ./data/pathway/ or use --data=<path>",
|
|
390
380
|
);
|
|
391
381
|
}
|
|
392
382
|
|
|
@@ -402,7 +392,6 @@ async function main() {
|
|
|
402
392
|
process.exit(0);
|
|
403
393
|
}
|
|
404
394
|
|
|
405
|
-
// No command: show help
|
|
406
395
|
if (!options.command) {
|
|
407
396
|
printHelp();
|
|
408
397
|
process.exit(0);
|
|
@@ -410,7 +399,6 @@ async function main() {
|
|
|
410
399
|
|
|
411
400
|
const command = options.command;
|
|
412
401
|
|
|
413
|
-
// Handle init command (doesn't need data directory to exist)
|
|
414
402
|
if (command === "init") {
|
|
415
403
|
await runInitCommand({ options });
|
|
416
404
|
process.exit(0);
|
|
@@ -418,20 +406,16 @@ async function main() {
|
|
|
418
406
|
|
|
419
407
|
const dataDir = resolveDataPath(options);
|
|
420
408
|
|
|
421
|
-
// Handle dev command (needs data directory)
|
|
422
409
|
if (command === "dev") {
|
|
423
410
|
await runDevCommand({ dataDir, options });
|
|
424
|
-
// dev doesn't exit, keeps running
|
|
425
411
|
return;
|
|
426
412
|
}
|
|
427
413
|
|
|
428
|
-
// Handle build command (generates static site)
|
|
429
414
|
if (command === "build") {
|
|
430
415
|
await runBuildCommand({ dataDir, options });
|
|
431
416
|
process.exit(0);
|
|
432
417
|
}
|
|
433
418
|
|
|
434
|
-
// Handle update command (re-downloads bundle for local install)
|
|
435
419
|
if (command === "update") {
|
|
436
420
|
await runUpdateCommand({ dataDir, options });
|
|
437
421
|
process.exit(0);
|
|
@@ -446,12 +430,20 @@ async function main() {
|
|
|
446
430
|
}
|
|
447
431
|
|
|
448
432
|
try {
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
433
|
+
const loader = createDataLoader();
|
|
434
|
+
const templateLoader = createTemplateLoader(TEMPLATE_DIR);
|
|
435
|
+
|
|
436
|
+
const data = await loader.loadAllData(dataDir);
|
|
437
|
+
validateAllData(data);
|
|
438
|
+
|
|
439
|
+
await handler({
|
|
440
|
+
data,
|
|
441
|
+
args: options.args,
|
|
442
|
+
options,
|
|
443
|
+
dataDir,
|
|
444
|
+
templateLoader,
|
|
445
|
+
loader,
|
|
452
446
|
});
|
|
453
|
-
|
|
454
|
-
await handler({ data, args: options.args, options, dataDir });
|
|
455
447
|
} catch (error) {
|
|
456
448
|
console.error(formatError(error.message));
|
|
457
449
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/pathway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
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": {
|
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
"./commands": "./src/commands/index.js"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@forwardimpact/map": "^0.
|
|
43
|
+
"@forwardimpact/map": "^0.14.0",
|
|
44
44
|
"@forwardimpact/libskill": "^3.0.0",
|
|
45
|
+
"@forwardimpact/libtemplate": "^0.2.0",
|
|
45
46
|
"@forwardimpact/libui": "^1.0.0",
|
|
46
47
|
"mustache": "^4.2.0",
|
|
47
48
|
"simple-icons": "^16.7.0",
|
package/src/commands/agent.js
CHANGED
|
@@ -27,10 +27,7 @@ import { writeFile, mkdir, readFile } from "fs/promises";
|
|
|
27
27
|
import { join, dirname } from "path";
|
|
28
28
|
import { existsSync } from "fs";
|
|
29
29
|
import { stringify as stringifyYaml } from "yaml";
|
|
30
|
-
import {
|
|
31
|
-
loadAgentData,
|
|
32
|
-
loadSkillsWithAgentData,
|
|
33
|
-
} from "@forwardimpact/map/loader";
|
|
30
|
+
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
34
31
|
import {
|
|
35
32
|
generateStageAgentProfile,
|
|
36
33
|
validateAgentProfile,
|
|
@@ -50,12 +47,6 @@ import {
|
|
|
50
47
|
formatReference,
|
|
51
48
|
} from "../formatters/agent/skill.js";
|
|
52
49
|
import { formatError, formatSuccess } from "../lib/cli-output.js";
|
|
53
|
-
import {
|
|
54
|
-
loadAgentTemplate,
|
|
55
|
-
loadSkillTemplate,
|
|
56
|
-
loadSkillInstallTemplate,
|
|
57
|
-
loadSkillReferenceTemplate,
|
|
58
|
-
} from "../lib/template-loader.js";
|
|
59
50
|
import { toolkitToPlainList } from "../formatters/toolkit/markdown.js";
|
|
60
51
|
|
|
61
52
|
/**
|
|
@@ -325,10 +316,18 @@ async function writeSkills(skills, baseDir, templates) {
|
|
|
325
316
|
* @param {Object} params.options - Command options
|
|
326
317
|
* @param {string} params.dataDir - Path to data directory
|
|
327
318
|
*/
|
|
328
|
-
export async function runAgentCommand({
|
|
319
|
+
export async function runAgentCommand({
|
|
320
|
+
data,
|
|
321
|
+
args,
|
|
322
|
+
options,
|
|
323
|
+
dataDir,
|
|
324
|
+
templateLoader,
|
|
325
|
+
loader,
|
|
326
|
+
}) {
|
|
329
327
|
// Load agent-specific data
|
|
330
|
-
const
|
|
331
|
-
const
|
|
328
|
+
const dataLoader = loader || createDataLoader();
|
|
329
|
+
const agentData = await dataLoader.loadAgentData(dataDir);
|
|
330
|
+
const skillsWithAgent = await dataLoader.loadSkillsWithAgentData(dataDir);
|
|
332
331
|
|
|
333
332
|
// --list: Output clean lines for piping
|
|
334
333
|
if (options.list) {
|
|
@@ -500,7 +499,7 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
500
499
|
}
|
|
501
500
|
|
|
502
501
|
// Load template
|
|
503
|
-
const agentTemplate =
|
|
502
|
+
const agentTemplate = templateLoader.load("agent.template.md", dataDir);
|
|
504
503
|
|
|
505
504
|
// Output to console (default) or write to files (with --output)
|
|
506
505
|
if (!options.output) {
|
|
@@ -575,10 +574,16 @@ export async function runAgentCommand({ data, args, options, dataDir }) {
|
|
|
575
574
|
}
|
|
576
575
|
|
|
577
576
|
// Load templates
|
|
578
|
-
const agentTemplate =
|
|
579
|
-
const skillTemplate =
|
|
580
|
-
const installTemplate =
|
|
581
|
-
|
|
577
|
+
const agentTemplate = templateLoader.load("agent.template.md", dataDir);
|
|
578
|
+
const skillTemplate = templateLoader.load("skill.template.md", dataDir);
|
|
579
|
+
const installTemplate = templateLoader.load(
|
|
580
|
+
"skill-install.template.sh",
|
|
581
|
+
dataDir,
|
|
582
|
+
);
|
|
583
|
+
const referenceTemplate = templateLoader.load(
|
|
584
|
+
"skill-reference.template.md",
|
|
585
|
+
dataDir,
|
|
586
|
+
);
|
|
582
587
|
const skillTemplates = {
|
|
583
588
|
skill: skillTemplate,
|
|
584
589
|
install: installTemplate,
|
package/src/commands/build.js
CHANGED
|
@@ -21,8 +21,8 @@ import { join, dirname, relative, resolve } from "path";
|
|
|
21
21
|
import { fileURLToPath } from "url";
|
|
22
22
|
import { execFileSync } from "child_process";
|
|
23
23
|
import Mustache from "mustache";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
24
|
+
import { createIndexGenerator } from "@forwardimpact/map/index-generator";
|
|
25
|
+
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
26
26
|
|
|
27
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
28
28
|
const __dirname = dirname(__filename);
|
|
@@ -85,7 +85,8 @@ export async function runBuildCommand({ dataDir, options }) {
|
|
|
85
85
|
// Load framework config for display
|
|
86
86
|
let framework;
|
|
87
87
|
try {
|
|
88
|
-
|
|
88
|
+
const loader = createDataLoader();
|
|
89
|
+
framework = await loader.loadFrameworkConfig(dataDir);
|
|
89
90
|
} catch {
|
|
90
91
|
framework = { emojiIcon: "🚀", title: "Engineering Pathway" };
|
|
91
92
|
}
|
|
@@ -110,7 +111,8 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
|
|
|
110
111
|
|
|
111
112
|
// Generate index files in data directory
|
|
112
113
|
console.log("📇 Generating index files...");
|
|
113
|
-
|
|
114
|
+
const indexGenerator = createIndexGenerator();
|
|
115
|
+
await indexGenerator.generateAllIndexes(dataDir);
|
|
114
116
|
|
|
115
117
|
// Copy app assets
|
|
116
118
|
console.log("📦 Copying application files...");
|
package/src/commands/dev.js
CHANGED
|
@@ -9,8 +9,8 @@ import { createServer } from "http";
|
|
|
9
9
|
import { readFile, stat } from "fs/promises";
|
|
10
10
|
import { join, extname, dirname } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { createIndexGenerator } from "@forwardimpact/map/index-generator";
|
|
13
|
+
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
14
14
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = dirname(__filename);
|
|
@@ -108,7 +108,8 @@ export async function runDevCommand({ dataDir, options }) {
|
|
|
108
108
|
// Load framework config for display
|
|
109
109
|
let framework;
|
|
110
110
|
try {
|
|
111
|
-
|
|
111
|
+
const loader = createDataLoader();
|
|
112
|
+
framework = await loader.loadFrameworkConfig(dataDir);
|
|
112
113
|
} catch {
|
|
113
114
|
// Fallback if framework config fails
|
|
114
115
|
framework = { emojiIcon: "🚀", title: "Engineering Pathway" };
|
|
@@ -116,7 +117,8 @@ export async function runDevCommand({ dataDir, options }) {
|
|
|
116
117
|
|
|
117
118
|
// Generate _index.yaml files before serving
|
|
118
119
|
console.log("Generating index files...");
|
|
119
|
-
|
|
120
|
+
const indexGenerator = createIndexGenerator();
|
|
121
|
+
await indexGenerator.generateAllIndexes(dataDir);
|
|
120
122
|
|
|
121
123
|
const server = createServer(async (req, res) => {
|
|
122
124
|
const url = new URL(req.url, `http://localhost:${port}`);
|
package/src/commands/init.js
CHANGED
|
@@ -10,7 +10,17 @@ import { fileURLToPath } from "url";
|
|
|
10
10
|
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
13
|
-
|
|
13
|
+
// Prefer monorepo root examples/framework/, fall back to legacy co-located examples/
|
|
14
|
+
const monorepoExamplesDir = join(
|
|
15
|
+
__dirname,
|
|
16
|
+
"..",
|
|
17
|
+
"..",
|
|
18
|
+
"..",
|
|
19
|
+
"..",
|
|
20
|
+
"examples",
|
|
21
|
+
"framework",
|
|
22
|
+
);
|
|
23
|
+
const legacyExamplesDir = join(__dirname, "..", "..", "examples");
|
|
14
24
|
|
|
15
25
|
/**
|
|
16
26
|
* Run the init command
|
|
@@ -31,13 +41,20 @@ export async function runInitCommand({ options }) {
|
|
|
31
41
|
// Directory doesn't exist, proceed
|
|
32
42
|
}
|
|
33
43
|
|
|
34
|
-
//
|
|
44
|
+
// Find examples directory — monorepo root first, then legacy
|
|
45
|
+
let examplesDir;
|
|
35
46
|
try {
|
|
36
|
-
await access(
|
|
47
|
+
await access(monorepoExamplesDir);
|
|
48
|
+
examplesDir = monorepoExamplesDir;
|
|
37
49
|
} catch {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
try {
|
|
51
|
+
await access(legacyExamplesDir);
|
|
52
|
+
examplesDir = legacyExamplesDir;
|
|
53
|
+
} catch {
|
|
54
|
+
console.error("Error: Examples directory not found in package.");
|
|
55
|
+
console.error("This may indicate a corrupted package installation.");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
// Copy example data
|
package/src/commands/job.js
CHANGED
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
deriveChecklist,
|
|
26
26
|
formatChecklistMarkdown,
|
|
27
27
|
} from "@forwardimpact/libskill/checklist";
|
|
28
|
-
import { loadJobTemplate } from "../lib/template-loader.js";
|
|
29
28
|
import { toolkitToPlainList } from "../formatters/toolkit/markdown.js";
|
|
30
29
|
|
|
31
30
|
/**
|
|
@@ -47,7 +46,13 @@ function formatJob(view, _options, entities, jobTemplate) {
|
|
|
47
46
|
* @param {Object} params.options - Command options
|
|
48
47
|
* @param {string} params.dataDir - Path to data directory
|
|
49
48
|
*/
|
|
50
|
-
export async function runJobCommand({
|
|
49
|
+
export async function runJobCommand({
|
|
50
|
+
data,
|
|
51
|
+
args,
|
|
52
|
+
options,
|
|
53
|
+
dataDir,
|
|
54
|
+
templateLoader,
|
|
55
|
+
}) {
|
|
51
56
|
const jobs = generateAllJobs({
|
|
52
57
|
disciplines: data.disciplines,
|
|
53
58
|
levels: data.levels,
|
|
@@ -310,6 +315,6 @@ export async function runJobCommand({ data, args, options, dataDir }) {
|
|
|
310
315
|
}
|
|
311
316
|
|
|
312
317
|
// Load job template for description formatting
|
|
313
|
-
const jobTemplate =
|
|
318
|
+
const jobTemplate = templateLoader.load("job.template.md", dataDir);
|
|
314
319
|
formatJob(view, options, { discipline, level, track }, jobTemplate);
|
|
315
320
|
}
|
package/src/commands/skill.js
CHANGED
|
@@ -18,7 +18,6 @@ import { getConceptEmoji } from "@forwardimpact/map/levels";
|
|
|
18
18
|
import { formatTable, formatError } from "../lib/cli-output.js";
|
|
19
19
|
import { generateSkillMarkdown } from "@forwardimpact/libskill/agent";
|
|
20
20
|
import { formatAgentSkill } from "../formatters/agent/skill.js";
|
|
21
|
-
import { loadSkillTemplate } from "../lib/template-loader.js";
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* Format skill summary output
|
|
@@ -69,7 +68,7 @@ function formatDetail(viewAndContext, framework) {
|
|
|
69
68
|
* @param {Array} stages - All stage entities
|
|
70
69
|
* @param {string} dataDir - Path to data directory for template loading
|
|
71
70
|
*/
|
|
72
|
-
async function formatAgentDetail(skill, stages, dataDir) {
|
|
71
|
+
async function formatAgentDetail(skill, stages, templateLoader, dataDir) {
|
|
73
72
|
if (!skill.agent) {
|
|
74
73
|
console.error(formatError(`Skill '${skill.id}' has no agent section`));
|
|
75
74
|
console.error(`\nSkills with agent support:`);
|
|
@@ -79,7 +78,7 @@ async function formatAgentDetail(skill, stages, dataDir) {
|
|
|
79
78
|
process.exit(1);
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
const template =
|
|
81
|
+
const template = templateLoader.load("skill.template.md", dataDir);
|
|
83
82
|
const skillMd = generateSkillMarkdown(skill, stages);
|
|
84
83
|
const output = formatAgentSkill(skillMd, template);
|
|
85
84
|
console.log(output);
|
|
@@ -119,7 +118,13 @@ const baseSkillCommand = createEntityCommand({
|
|
|
119
118
|
* @param {Object} params.options - Command options
|
|
120
119
|
* @param {string} params.dataDir - Path to data directory
|
|
121
120
|
*/
|
|
122
|
-
export async function runSkillCommand({
|
|
121
|
+
export async function runSkillCommand({
|
|
122
|
+
data,
|
|
123
|
+
args,
|
|
124
|
+
options,
|
|
125
|
+
dataDir,
|
|
126
|
+
templateLoader,
|
|
127
|
+
}) {
|
|
123
128
|
// Handle --agent flag for detail view
|
|
124
129
|
if (options.agent && args.length > 0) {
|
|
125
130
|
const [id] = args;
|
|
@@ -131,7 +136,7 @@ export async function runSkillCommand({ data, args, options, dataDir }) {
|
|
|
131
136
|
process.exit(1);
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
await formatAgentDetail(skill, data.stages, dataDir);
|
|
139
|
+
await formatAgentDetail(skill, data.stages, templateLoader, dataDir);
|
|
135
140
|
return;
|
|
136
141
|
}
|
|
137
142
|
|
package/src/commands/update.js
CHANGED
|
@@ -10,7 +10,7 @@ import { cp, mkdir, rm, readFile, writeFile, access } from "fs/promises";
|
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import { execFileSync, execSync } from "child_process";
|
|
13
|
-
import {
|
|
13
|
+
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
14
14
|
|
|
15
15
|
const INSTALL_DIR = join(homedir(), ".fit", "pathway");
|
|
16
16
|
|
|
@@ -38,7 +38,8 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
// Load framework config to get siteUrl
|
|
41
|
-
const
|
|
41
|
+
const loader = createDataLoader();
|
|
42
|
+
const framework = await loader.loadFrameworkConfig(installDataDir);
|
|
42
43
|
const siteUrl = options.url || framework.distribution?.siteUrl;
|
|
43
44
|
|
|
44
45
|
if (!siteUrl) {
|
|
@@ -23,14 +23,9 @@ import { trimValue, trimFields } from "../shared.js";
|
|
|
23
23
|
* @returns {Object} Data object ready for Mustache template
|
|
24
24
|
*/
|
|
25
25
|
function prepareJobDescriptionData({ job, discipline, level, track }) {
|
|
26
|
-
// Build role summary from discipline
|
|
27
|
-
const isManagement = discipline.isManagement === true;
|
|
28
|
-
let roleSummary =
|
|
29
|
-
isManagement && discipline.managementRoleSummary
|
|
30
|
-
? discipline.managementRoleSummary
|
|
31
|
-
: discipline.professionalRoleSummary || discipline.description;
|
|
32
|
-
// Replace placeholders
|
|
26
|
+
// Build role summary from discipline
|
|
33
27
|
const { roleTitle, specialization } = discipline;
|
|
28
|
+
let roleSummary = discipline.roleSummary || discipline.description;
|
|
34
29
|
roleSummary = roleSummary.replace(/\{roleTitle\}/g, roleTitle);
|
|
35
30
|
roleSummary = roleSummary.replace(/\{specialization\}/g, specialization);
|
|
36
31
|
|
package/src/lib/yaml-loader.js
CHANGED
|
@@ -298,9 +298,15 @@ export async function loadAgentDataBrowser(dataDir = "./data") {
|
|
|
298
298
|
loadDisciplinesFromDir(`${dataDir}/disciplines`),
|
|
299
299
|
loadTracksFromDir(`${dataDir}/tracks`),
|
|
300
300
|
loadBehavioursFromDir(`${dataDir}/behaviours`),
|
|
301
|
-
tryLoadYamlFile(`${dataDir}/vscode-settings.yaml`)
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
tryLoadYamlFile(`${dataDir}/repository/vscode-settings.yaml`).then(
|
|
302
|
+
(r) => r ?? tryLoadYamlFile(`${dataDir}/vscode-settings.yaml`),
|
|
303
|
+
),
|
|
304
|
+
tryLoadYamlFile(`${dataDir}/repository/devcontainer.yaml`).then(
|
|
305
|
+
(r) => r ?? tryLoadYamlFile(`${dataDir}/devcontainer.yaml`),
|
|
306
|
+
),
|
|
307
|
+
tryLoadYamlFile(`${dataDir}/repository/copilot-setup-steps.yaml`).then(
|
|
308
|
+
(r) => r ?? tryLoadYamlFile(`${dataDir}/copilot-setup-steps.yaml`),
|
|
309
|
+
),
|
|
304
310
|
]);
|
|
305
311
|
|
|
306
312
|
return {
|
package/src/main.js
CHANGED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template Loader
|
|
3
|
-
*
|
|
4
|
-
* Loads Mustache templates from the data directory with fallback to the
|
|
5
|
-
* top-level templates directory. This allows users to customize agent
|
|
6
|
-
* and skill templates by placing them in their data directory.
|
|
7
|
-
*
|
|
8
|
-
* Resolution order:
|
|
9
|
-
* 1. {dataDir}/templates/{name} (user customization)
|
|
10
|
-
* 2. {codebaseDir}/templates/{name} (fallback)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { readFile } from "fs/promises";
|
|
14
|
-
import { join, dirname } from "path";
|
|
15
|
-
import { fileURLToPath } from "url";
|
|
16
|
-
import { existsSync } from "fs";
|
|
17
|
-
|
|
18
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const CODEBASE_TEMPLATES_DIR = join(__dirname, "..", "..", "templates");
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Load a template file with fallback to codebase templates
|
|
23
|
-
* @param {string} templateName - Template filename (e.g., 'agent.template.md')
|
|
24
|
-
* @param {string} dataDir - Path to data directory
|
|
25
|
-
* @returns {Promise<string>} Template content
|
|
26
|
-
* @throws {Error} If template not found in either location
|
|
27
|
-
*/
|
|
28
|
-
export async function loadTemplate(templateName, dataDir) {
|
|
29
|
-
// Build list of paths to try
|
|
30
|
-
const paths = [];
|
|
31
|
-
if (dataDir) {
|
|
32
|
-
paths.push(join(dataDir, "templates", templateName));
|
|
33
|
-
}
|
|
34
|
-
paths.push(join(CODEBASE_TEMPLATES_DIR, templateName));
|
|
35
|
-
|
|
36
|
-
// Try each path in order
|
|
37
|
-
for (const path of paths) {
|
|
38
|
-
if (existsSync(path)) {
|
|
39
|
-
return await readFile(path, "utf-8");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Not found
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Template '${templateName}' not found. Checked:\n` +
|
|
46
|
-
paths.map((p) => ` - ${p}`).join("\n"),
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Load agent profile template
|
|
52
|
-
* @param {string} dataDir - Path to data directory
|
|
53
|
-
* @returns {Promise<string>} Agent template content
|
|
54
|
-
*/
|
|
55
|
-
export async function loadAgentTemplate(dataDir) {
|
|
56
|
-
return loadTemplate("agent.template.md", dataDir);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Load agent skill template
|
|
61
|
-
* @param {string} dataDir - Path to data directory
|
|
62
|
-
* @returns {Promise<string>} Skill template content
|
|
63
|
-
*/
|
|
64
|
-
export async function loadSkillTemplate(dataDir) {
|
|
65
|
-
return loadTemplate("skill.template.md", dataDir);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Load skill install script template
|
|
70
|
-
* @param {string} dataDir - Path to data directory
|
|
71
|
-
* @returns {Promise<string>} Install script template content
|
|
72
|
-
*/
|
|
73
|
-
export async function loadSkillInstallTemplate(dataDir) {
|
|
74
|
-
return loadTemplate("skill-install.template.sh", dataDir);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Load skill reference template
|
|
79
|
-
* @param {string} dataDir - Path to data directory
|
|
80
|
-
* @returns {Promise<string>} Reference template content
|
|
81
|
-
*/
|
|
82
|
-
export async function loadSkillReferenceTemplate(dataDir) {
|
|
83
|
-
return loadTemplate("skill-reference.template.md", dataDir);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Load job description template
|
|
88
|
-
* @param {string} dataDir - Path to data directory
|
|
89
|
-
* @returns {Promise<string>} Job template content
|
|
90
|
-
*/
|
|
91
|
-
export async function loadJobTemplate(dataDir) {
|
|
92
|
-
return loadTemplate("job.template.md", dataDir);
|
|
93
|
-
}
|