@forwardimpact/pathway 0.24.0 → 0.25.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/README.md +3 -3
- package/bin/fit-pathway.js +18 -47
- package/package.json +4 -4
- package/src/commands/agent.js +11 -93
- package/src/commands/build.js +18 -0
- package/src/commands/dev.js +15 -0
- package/src/commands/update.js +8 -4
- package/src/components/skill-matrix.js +21 -9
- package/src/formatters/agent/dom.js +27 -47
- package/src/formatters/agent/profile.js +49 -35
- package/src/formatters/job/dom.js +5 -59
- package/src/handout.html +1 -1
- package/src/index.html +9 -3
- package/src/lib/radar.js +19 -5
- package/src/lib/yaml-loader.js +10 -24
- package/src/pages/agent-builder.js +26 -83
- package/src/pages/landing.js +1 -1
- package/src/slide-main.js +16 -2
- package/src/slides.html +1 -1
- package/templates/agent.template.md +55 -76
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ web experience and command line.
|
|
|
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 teams** — Generate
|
|
18
|
-
(
|
|
17
|
+
- **Agent teams** — Generate Claude Code agent teams (`.claude/agents/`) and
|
|
18
|
+
skills (`.claude/skills/`)
|
|
19
19
|
- **Interview prep** — Build interview question sets by role
|
|
20
20
|
- **Static site** — Export everything as a static site
|
|
21
21
|
|
|
@@ -30,7 +30,7 @@ npx fit-pathway skill --list
|
|
|
30
30
|
npx fit-pathway job software_engineering senior --track=platform
|
|
31
31
|
|
|
32
32
|
# Generate agent teams and skills
|
|
33
|
-
npx fit-pathway agent software_engineering --track=platform --output
|
|
33
|
+
npx fit-pathway agent software_engineering --track=platform --output=.
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## CLI Commands
|
package/bin/fit-pathway.js
CHANGED
|
@@ -30,11 +30,13 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { join, resolve, dirname } from "path";
|
|
33
|
-
import { existsSync } from "fs";
|
|
34
|
-
import { homedir } from "os";
|
|
35
33
|
import { fileURLToPath } from "url";
|
|
34
|
+
import fs from "fs/promises";
|
|
35
|
+
import { homedir } from "os";
|
|
36
36
|
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
37
37
|
import { validateAllData } from "@forwardimpact/map/validation";
|
|
38
|
+
import { Finder } from "@forwardimpact/libutil";
|
|
39
|
+
import { createLogger } from "@forwardimpact/libtelemetry";
|
|
38
40
|
import { formatError } from "../src/lib/cli-output.js";
|
|
39
41
|
import { createTemplateLoader } from "@forwardimpact/libtemplate";
|
|
40
42
|
|
|
@@ -336,50 +338,6 @@ function printHelp() {
|
|
|
336
338
|
console.log(HELP_TEXT);
|
|
337
339
|
}
|
|
338
340
|
|
|
339
|
-
/**
|
|
340
|
-
* Resolve the data directory path.
|
|
341
|
-
* @param {Object} options - Parsed command options
|
|
342
|
-
* @returns {string} Resolved absolute path to data directory
|
|
343
|
-
*/
|
|
344
|
-
function resolveDataPath(options) {
|
|
345
|
-
if (options.data) {
|
|
346
|
-
return resolve(options.data);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (process.env.PATHWAY_DATA) {
|
|
350
|
-
return resolve(process.env.PATHWAY_DATA);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const homeData = join(homedir(), ".fit", "pathway", "data");
|
|
354
|
-
if (existsSync(homeData)) {
|
|
355
|
-
return homeData;
|
|
356
|
-
}
|
|
357
|
-
|
|
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
|
-
|
|
368
|
-
const cwdData = join(process.cwd(), "data");
|
|
369
|
-
if (existsSync(cwdData)) {
|
|
370
|
-
return cwdData;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const cwdExamples = join(process.cwd(), "examples");
|
|
374
|
-
if (existsSync(cwdExamples)) {
|
|
375
|
-
return cwdExamples;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
throw new Error(
|
|
379
|
-
"No data directory found. Create ./data/pathway/ or use --data=<path>",
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
341
|
/**
|
|
384
342
|
* Main CLI entry point
|
|
385
343
|
*/
|
|
@@ -404,7 +362,20 @@ async function main() {
|
|
|
404
362
|
process.exit(0);
|
|
405
363
|
}
|
|
406
364
|
|
|
407
|
-
|
|
365
|
+
let dataDir;
|
|
366
|
+
if (options.data) {
|
|
367
|
+
dataDir = resolve(options.data);
|
|
368
|
+
} else {
|
|
369
|
+
const logger = createLogger("pathway");
|
|
370
|
+
const finder = new Finder(fs, logger, process);
|
|
371
|
+
try {
|
|
372
|
+
dataDir = join(finder.findData("data", homedir()), "pathway");
|
|
373
|
+
} catch {
|
|
374
|
+
throw new Error(
|
|
375
|
+
"No data directory found. Use --data=<path> to specify location.",
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
408
379
|
|
|
409
380
|
if (command === "dev") {
|
|
410
381
|
await runDevCommand({ dataDir, options });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/pathway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@forwardimpact/map": "^0.14.0",
|
|
44
|
-
"@forwardimpact/libskill": "^
|
|
44
|
+
"@forwardimpact/libskill": "^4.0.0",
|
|
45
45
|
"@forwardimpact/libtemplate": "^0.2.0",
|
|
46
46
|
"@forwardimpact/libui": "^1.0.0",
|
|
47
47
|
"mustache": "^4.2.0",
|
|
48
|
-
"simple-icons": "^16.
|
|
49
|
-
"yaml": "^2.3
|
|
48
|
+
"simple-icons": "^16.13.0",
|
|
49
|
+
"yaml": "^2.8.3"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=18.0.0"
|
package/src/commands/agent.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Agent Command
|
|
3
3
|
*
|
|
4
4
|
* CLI command for generating AI coding agent configurations
|
|
5
|
-
* from Engineering Pathway data.
|
|
5
|
+
* from Engineering Pathway data. Outputs follow the Claude Code
|
|
6
|
+
* agent specification.
|
|
6
7
|
*
|
|
7
8
|
* All agents are stage-specific. Use --stage for a single stage
|
|
8
9
|
* or --all-stages (default) for all stages.
|
|
@@ -26,7 +27,6 @@
|
|
|
26
27
|
import { writeFile, mkdir, readFile } from "fs/promises";
|
|
27
28
|
import { join, dirname } from "path";
|
|
28
29
|
import { existsSync } from "fs";
|
|
29
|
-
import { stringify as stringifyYaml } from "yaml";
|
|
30
30
|
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
31
31
|
import {
|
|
32
32
|
generateStageAgentProfile,
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
deriveAgentSkills,
|
|
37
37
|
generateSkillMarkdown,
|
|
38
38
|
deriveToolkit,
|
|
39
|
-
buildAgentIndex,
|
|
40
39
|
getDisciplineAbbreviation,
|
|
41
40
|
toKebabCase,
|
|
42
41
|
} from "@forwardimpact/libskill";
|
|
@@ -58,13 +57,13 @@ async function ensureDir(filePath) {
|
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
|
-
* Generate
|
|
60
|
+
* Generate Claude Code settings file
|
|
62
61
|
* Merges with existing settings if file exists
|
|
63
62
|
* @param {string} baseDir - Base output directory
|
|
64
|
-
* @param {Object}
|
|
63
|
+
* @param {Object} claudeCodeSettings - Settings loaded from data
|
|
65
64
|
*/
|
|
66
|
-
async function
|
|
67
|
-
const settingsPath = join(baseDir, ".
|
|
65
|
+
async function generateClaudeCodeSettings(baseDir, claudeCodeSettings) {
|
|
66
|
+
const settingsPath = join(baseDir, ".claude", "settings.json");
|
|
68
67
|
|
|
69
68
|
let settings = {};
|
|
70
69
|
if (existsSync(settingsPath)) {
|
|
@@ -72,7 +71,7 @@ async function generateVSCodeSettings(baseDir, vscodeSettings) {
|
|
|
72
71
|
settings = JSON.parse(content);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
const merged = { ...settings, ...
|
|
74
|
+
const merged = { ...settings, ...claudeCodeSettings };
|
|
76
75
|
|
|
77
76
|
await ensureDir(settingsPath);
|
|
78
77
|
await writeFile(
|
|
@@ -83,64 +82,6 @@ async function generateVSCodeSettings(baseDir, vscodeSettings) {
|
|
|
83
82
|
console.log(formatSuccess(`Updated: ${settingsPath}`));
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
/**
|
|
87
|
-
* Generate devcontainer.json from template with VS Code settings embedded
|
|
88
|
-
* @param {string} baseDir - Base output directory
|
|
89
|
-
* @param {Object} devcontainerConfig - Devcontainer config loaded from data
|
|
90
|
-
* @param {Object} vscodeSettings - VS Code settings to embed in customizations
|
|
91
|
-
*/
|
|
92
|
-
async function generateDevcontainer(
|
|
93
|
-
baseDir,
|
|
94
|
-
devcontainerConfig,
|
|
95
|
-
vscodeSettings,
|
|
96
|
-
) {
|
|
97
|
-
if (!devcontainerConfig || Object.keys(devcontainerConfig).length === 0) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const devcontainerPath = join(baseDir, ".devcontainer", "devcontainer.json");
|
|
102
|
-
|
|
103
|
-
// Build devcontainer.json with VS Code settings embedded
|
|
104
|
-
const devcontainer = {
|
|
105
|
-
...devcontainerConfig,
|
|
106
|
-
customizations: {
|
|
107
|
-
vscode: {
|
|
108
|
-
settings: vscodeSettings,
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
await ensureDir(devcontainerPath);
|
|
114
|
-
await writeFile(
|
|
115
|
-
devcontainerPath,
|
|
116
|
-
JSON.stringify(devcontainer, null, 2) + "\n",
|
|
117
|
-
"utf-8",
|
|
118
|
-
);
|
|
119
|
-
console.log(formatSuccess(`Created: ${devcontainerPath}`));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Generate GitHub Actions workflow for Copilot Coding Agent setup steps
|
|
124
|
-
* @param {string} baseDir - Base output directory
|
|
125
|
-
* @param {Object|null} copilotSetupSteps - Workflow config loaded from data
|
|
126
|
-
*/
|
|
127
|
-
async function generateCopilotSetupSteps(baseDir, copilotSetupSteps) {
|
|
128
|
-
if (!copilotSetupSteps) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const workflowPath = join(
|
|
133
|
-
baseDir,
|
|
134
|
-
".github",
|
|
135
|
-
"workflows",
|
|
136
|
-
"copilot-setup-steps.yml",
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
await ensureDir(workflowPath);
|
|
140
|
-
await writeFile(workflowPath, stringifyYaml(copilotSetupSteps), "utf-8");
|
|
141
|
-
console.log(formatSuccess(`Created: ${workflowPath}`));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
85
|
/**
|
|
145
86
|
* Show agent summary with stats
|
|
146
87
|
* @param {Object} data - Pathway data
|
|
@@ -258,7 +199,7 @@ function listAgentCombinations(data, agentData, verbose = false) {
|
|
|
258
199
|
* @param {string} template - Mustache template for agent profile
|
|
259
200
|
*/
|
|
260
201
|
async function writeProfile(profile, baseDir, template) {
|
|
261
|
-
const profilePath = join(baseDir, ".
|
|
202
|
+
const profilePath = join(baseDir, ".claude", "agents", profile.filename);
|
|
262
203
|
const profileContent = formatAgentProfile(profile, template);
|
|
263
204
|
await ensureDir(profilePath);
|
|
264
205
|
await writeFile(profilePath, profileContent, "utf-8");
|
|
@@ -451,15 +392,6 @@ export async function runAgentCommand({
|
|
|
451
392
|
|
|
452
393
|
const baseDir = options.output || ".";
|
|
453
394
|
|
|
454
|
-
// Build agent index for all valid combinations
|
|
455
|
-
const agentIndex = buildAgentIndex({
|
|
456
|
-
disciplines: data.disciplines,
|
|
457
|
-
tracks: data.tracks,
|
|
458
|
-
stages: data.stages,
|
|
459
|
-
agentDisciplines: agentData.disciplines,
|
|
460
|
-
agentTracks: agentData.tracks,
|
|
461
|
-
});
|
|
462
|
-
|
|
463
395
|
// Common params for stage-based generation
|
|
464
396
|
const stageParams = {
|
|
465
397
|
discipline: humanDiscipline,
|
|
@@ -471,7 +403,6 @@ export async function runAgentCommand({
|
|
|
471
403
|
agentDiscipline,
|
|
472
404
|
agentTrack,
|
|
473
405
|
stages: data.stages,
|
|
474
|
-
agentIndex,
|
|
475
406
|
};
|
|
476
407
|
|
|
477
408
|
// Handle --stage flag for single stage agent
|
|
@@ -508,13 +439,7 @@ export async function runAgentCommand({
|
|
|
508
439
|
}
|
|
509
440
|
|
|
510
441
|
await writeProfile(profile, baseDir, agentTemplate);
|
|
511
|
-
await
|
|
512
|
-
await generateDevcontainer(
|
|
513
|
-
baseDir,
|
|
514
|
-
agentData.devcontainer,
|
|
515
|
-
agentData.vscodeSettings,
|
|
516
|
-
);
|
|
517
|
-
await generateCopilotSetupSteps(baseDir, agentData.copilotSetupSteps);
|
|
442
|
+
await generateClaudeCodeSettings(baseDir, agentData.claudeCodeSettings);
|
|
518
443
|
console.log("");
|
|
519
444
|
console.log(
|
|
520
445
|
formatSuccess(`Generated stage agent: ${profile.frontmatter.name}`),
|
|
@@ -522,8 +447,7 @@ export async function runAgentCommand({
|
|
|
522
447
|
return;
|
|
523
448
|
}
|
|
524
449
|
|
|
525
|
-
// Default behavior: generate all stage agents
|
|
526
|
-
// No generic agents - all agents are stage-specific
|
|
450
|
+
// Default behavior: generate all stage agents
|
|
527
451
|
const profiles = [];
|
|
528
452
|
|
|
529
453
|
// Generate all stage agents
|
|
@@ -603,13 +527,7 @@ export async function runAgentCommand({
|
|
|
603
527
|
await writeProfile(profile, baseDir, agentTemplate);
|
|
604
528
|
}
|
|
605
529
|
const fileCount = await writeSkills(skillFiles, baseDir, skillTemplates);
|
|
606
|
-
await
|
|
607
|
-
await generateDevcontainer(
|
|
608
|
-
baseDir,
|
|
609
|
-
agentData.devcontainer,
|
|
610
|
-
agentData.vscodeSettings,
|
|
611
|
-
);
|
|
612
|
-
await generateCopilotSetupSteps(baseDir, agentData.copilotSetupSteps);
|
|
530
|
+
await generateClaudeCodeSettings(baseDir, agentData.claudeCodeSettings);
|
|
613
531
|
|
|
614
532
|
console.log("");
|
|
615
533
|
console.log(formatSuccess(`Generated ${profiles.length} agents:`));
|
package/src/commands/build.js
CHANGED
|
@@ -160,6 +160,24 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
|
|
|
160
160
|
});
|
|
161
161
|
console.log(` ✓ ui/lib + ui/css`);
|
|
162
162
|
|
|
163
|
+
// Copy vendor dependencies for offline usage
|
|
164
|
+
console.log("📦 Copying vendor dependencies...");
|
|
165
|
+
const vendorDir = join(outputDir, "vendor");
|
|
166
|
+
await mkdir(vendorDir, { recursive: true });
|
|
167
|
+
|
|
168
|
+
// mustache (ESM module)
|
|
169
|
+
const mustacheSrc = fileURLToPath(import.meta.resolve("mustache"));
|
|
170
|
+
const mustacheMjs = join(dirname(mustacheSrc), "mustache.mjs");
|
|
171
|
+
await cp(mustacheMjs, join(vendorDir, "mustache.mjs"));
|
|
172
|
+
console.log(" ✓ vendor/mustache.mjs");
|
|
173
|
+
|
|
174
|
+
// yaml (browser ESM build — not in package exports, resolve via filesystem)
|
|
175
|
+
// import.meta.resolve("yaml") → .../yaml/dist/index.js, go up two levels
|
|
176
|
+
const yamlPkg = dirname(dirname(fileURLToPath(import.meta.resolve("yaml"))));
|
|
177
|
+
const yamlBrowserDist = join(yamlPkg, "browser", "dist");
|
|
178
|
+
await cp(yamlBrowserDist, join(vendorDir, "yaml"), { recursive: true });
|
|
179
|
+
console.log(" ✓ vendor/yaml/");
|
|
180
|
+
|
|
163
181
|
// Copy data directory (dereference symlinks to copy actual content)
|
|
164
182
|
console.log("📁 Copying data files...");
|
|
165
183
|
const dataOutputDir = join(outputDir, "data");
|
package/src/commands/dev.js
CHANGED
|
@@ -34,10 +34,19 @@ const mapLibDir = resolvePackageLib("@forwardimpact/map");
|
|
|
34
34
|
const modelLibDir = resolvePackageLib("@forwardimpact/libskill");
|
|
35
35
|
const uiLibDir = resolvePackageLib("@forwardimpact/libui");
|
|
36
36
|
|
|
37
|
+
// Vendor dependencies — mirror the paths that build.js copies to vendor/
|
|
38
|
+
const mustacheDir = dirname(fileURLToPath(import.meta.resolve("mustache")));
|
|
39
|
+
const yamlBrowserDir = join(
|
|
40
|
+
dirname(dirname(fileURLToPath(import.meta.resolve("yaml")))),
|
|
41
|
+
"browser",
|
|
42
|
+
"dist",
|
|
43
|
+
);
|
|
44
|
+
|
|
37
45
|
const MIME_TYPES = {
|
|
38
46
|
".html": "text/html; charset=utf-8",
|
|
39
47
|
".css": "text/css; charset=utf-8",
|
|
40
48
|
".js": "application/javascript; charset=utf-8",
|
|
49
|
+
".mjs": "application/javascript; charset=utf-8",
|
|
41
50
|
".yaml": "text/yaml; charset=utf-8",
|
|
42
51
|
".yml": "text/yaml; charset=utf-8",
|
|
43
52
|
".json": "application/json; charset=utf-8",
|
|
@@ -149,6 +158,12 @@ export async function runDevCommand({ dataDir, options }) {
|
|
|
149
158
|
} else if (pathname.startsWith("/ui/css/")) {
|
|
150
159
|
// Serve @forwardimpact/libui package CSS files
|
|
151
160
|
filePath = join(uiLibDir, "css", pathname.slice(8));
|
|
161
|
+
} else if (pathname === "/vendor/mustache.mjs") {
|
|
162
|
+
// Serve vendored mustache ESM module
|
|
163
|
+
filePath = join(mustacheDir, "mustache.mjs");
|
|
164
|
+
} else if (pathname.startsWith("/vendor/yaml/")) {
|
|
165
|
+
// Serve vendored yaml browser ESM build
|
|
166
|
+
filePath = join(yamlBrowserDir, pathname.slice(13));
|
|
152
167
|
} else if (pathname === "/" || pathname === "") {
|
|
153
168
|
// Serve index.html for root
|
|
154
169
|
filePath = join(publicDir, "index.html");
|
package/src/commands/update.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { cp, mkdir, rm, readFile, writeFile, access } from "fs/promises";
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
|
-
import { execFileSync
|
|
12
|
+
import { execFileSync } from "child_process";
|
|
13
13
|
import { createDataLoader } from "@forwardimpact/map/loader";
|
|
14
14
|
|
|
15
15
|
const INSTALL_DIR = join(homedir(), ".fit", "pathway");
|
|
@@ -112,9 +112,13 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
|
|
|
112
112
|
// 6. Update global pathway package if version changed
|
|
113
113
|
if (oldVersion !== newVersion) {
|
|
114
114
|
console.log(` Updating pathway ${oldVersion} → ${newVersion}...`);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
execFileSync(
|
|
116
|
+
"npm",
|
|
117
|
+
["install", "-g", `@forwardimpact/pathway@${newVersion}`],
|
|
118
|
+
{
|
|
119
|
+
stdio: "ignore",
|
|
120
|
+
},
|
|
121
|
+
);
|
|
118
122
|
console.log(" ✓ Global package updated");
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -22,17 +22,24 @@ import { SKILL_PROFICIENCY_ORDER } from "@forwardimpact/map/levels";
|
|
|
22
22
|
import { truncate } from "../formatters/shared.js";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Sort skills by level descending
|
|
25
|
+
* Sort skills by capability group order, then by level descending within each group
|
|
26
26
|
* @param {SkillMatrixItem[]} skills
|
|
27
|
+
* @param {string[]} capabilityOrder - Ordered capability IDs
|
|
27
28
|
* @returns {SkillMatrixItem[]}
|
|
28
29
|
*/
|
|
29
|
-
function
|
|
30
|
+
function sortByCapabilityThenLevel(skills, capabilityOrder) {
|
|
31
|
+
const orderMap = new Map(capabilityOrder.map((id, i) => [id, i]));
|
|
30
32
|
return [...skills].sort((a, b) => {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const capA = orderMap.has(a.capability)
|
|
34
|
+
? orderMap.get(a.capability)
|
|
35
|
+
: capabilityOrder.length;
|
|
36
|
+
const capB = orderMap.has(b.capability)
|
|
37
|
+
? orderMap.get(b.capability)
|
|
38
|
+
: capabilityOrder.length;
|
|
39
|
+
if (capA !== capB) return capA - capB;
|
|
40
|
+
const levelA = SKILL_PROFICIENCY_ORDER.indexOf(a.proficiency);
|
|
41
|
+
const levelB = SKILL_PROFICIENCY_ORDER.indexOf(b.proficiency);
|
|
42
|
+
if (levelB !== levelA) return levelB - levelA;
|
|
36
43
|
return a.skillName.localeCompare(b.skillName);
|
|
37
44
|
});
|
|
38
45
|
}
|
|
@@ -40,14 +47,19 @@ function sortByLevelDescending(skills) {
|
|
|
40
47
|
/**
|
|
41
48
|
* Create a skill matrix table
|
|
42
49
|
* @param {SkillMatrixItem[]} skillMatrix - Skill matrix entries
|
|
50
|
+
* @param {Object} [options]
|
|
51
|
+
* @param {string[]} [options.capabilityOrder] - Capability IDs in desired display order
|
|
43
52
|
* @returns {HTMLElement}
|
|
44
53
|
*/
|
|
45
|
-
export function createSkillMatrix(skillMatrix) {
|
|
54
|
+
export function createSkillMatrix(skillMatrix, options = {}) {
|
|
46
55
|
if (!skillMatrix || skillMatrix.length === 0) {
|
|
47
56
|
return div({ className: "empty-state" }, "No skills in matrix");
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
const
|
|
59
|
+
const { capabilityOrder } = options;
|
|
60
|
+
const sortedSkills = capabilityOrder
|
|
61
|
+
? sortByCapabilityThenLevel(skillMatrix, capabilityOrder)
|
|
62
|
+
: [...skillMatrix];
|
|
51
63
|
|
|
52
64
|
const rows = sortedSkills.map((skill) => {
|
|
53
65
|
const levelIndex = getSkillProficiencyIndex(skill.proficiency);
|
|
@@ -17,14 +17,14 @@ import { getStageEmoji } from "../stage/shared.js";
|
|
|
17
17
|
* @param {Object} deployment.profile - Agent profile
|
|
18
18
|
* @param {Array} deployment.skills - Agent skills
|
|
19
19
|
* @param {Array} [deployment.roleAgents] - Role variant agents (plan, review)
|
|
20
|
-
* @param {Object} [deployment.
|
|
20
|
+
* @param {Object} [deployment.claudeCodeSettings] - Claude Code settings to include in download
|
|
21
21
|
* @returns {HTMLElement}
|
|
22
22
|
*/
|
|
23
23
|
export function agentDeploymentToDOM({
|
|
24
24
|
profile,
|
|
25
25
|
skills,
|
|
26
26
|
roleAgents = [],
|
|
27
|
-
|
|
27
|
+
claudeCodeSettings = {},
|
|
28
28
|
}) {
|
|
29
29
|
const profileContent = formatAgentProfile(profile);
|
|
30
30
|
const agentName = profile.frontmatter.name;
|
|
@@ -37,7 +37,7 @@ export function agentDeploymentToDOM({
|
|
|
37
37
|
profile,
|
|
38
38
|
skills,
|
|
39
39
|
roleAgents,
|
|
40
|
-
|
|
40
|
+
claudeCodeSettings,
|
|
41
41
|
agentName,
|
|
42
42
|
),
|
|
43
43
|
|
|
@@ -90,7 +90,7 @@ export function agentDeploymentToDOM({
|
|
|
90
90
|
* @param {Object} profile - Agent profile
|
|
91
91
|
* @param {Array} skills - Agent skills
|
|
92
92
|
* @param {Array} roleAgents - Role variant agents
|
|
93
|
-
* @param {Object}
|
|
93
|
+
* @param {Object} claudeCodeSettings - Claude Code settings to include
|
|
94
94
|
* @param {string} agentName - Agent name for zip filename
|
|
95
95
|
* @returns {HTMLElement}
|
|
96
96
|
*/
|
|
@@ -98,7 +98,7 @@ function createDownloadButton(
|
|
|
98
98
|
profile,
|
|
99
99
|
skills,
|
|
100
100
|
roleAgents,
|
|
101
|
-
|
|
101
|
+
claudeCodeSettings,
|
|
102
102
|
agentName,
|
|
103
103
|
) {
|
|
104
104
|
const btn = button(
|
|
@@ -115,7 +115,7 @@ function createDownloadButton(
|
|
|
115
115
|
profile,
|
|
116
116
|
skills,
|
|
117
117
|
roleAgents,
|
|
118
|
-
|
|
118
|
+
claudeCodeSettings,
|
|
119
119
|
agentName,
|
|
120
120
|
);
|
|
121
121
|
} finally {
|
|
@@ -167,28 +167,28 @@ function createRoleAgentCard(agent) {
|
|
|
167
167
|
* @param {Object} profile - Agent profile
|
|
168
168
|
* @param {Array} skills - Agent skills
|
|
169
169
|
* @param {Array} roleAgents - Role variant agents
|
|
170
|
-
* @param {Object}
|
|
170
|
+
* @param {Object} claudeCodeSettings - Claude Code settings to include
|
|
171
171
|
* @param {string} agentName - Agent name for zip filename
|
|
172
172
|
*/
|
|
173
173
|
async function downloadAllAsZip(
|
|
174
174
|
profile,
|
|
175
175
|
skills,
|
|
176
176
|
roleAgents,
|
|
177
|
-
|
|
177
|
+
claudeCodeSettings,
|
|
178
178
|
agentName,
|
|
179
179
|
) {
|
|
180
180
|
// Dynamically import JSZip
|
|
181
181
|
const JSZip = await importJSZip();
|
|
182
182
|
const zip = new JSZip();
|
|
183
183
|
|
|
184
|
-
// Add main profile to .
|
|
184
|
+
// Add main profile to .claude/agents/ folder
|
|
185
185
|
const profileContent = formatAgentProfile(profile);
|
|
186
|
-
zip.file(`.
|
|
186
|
+
zip.file(`.claude/agents/${profile.filename}`, profileContent);
|
|
187
187
|
|
|
188
|
-
// Add role agent profiles to .
|
|
188
|
+
// Add role agent profiles to .claude/agents/ folder
|
|
189
189
|
for (const roleAgent of roleAgents) {
|
|
190
190
|
const roleContent = formatAgentProfile(roleAgent);
|
|
191
|
-
zip.file(`.
|
|
191
|
+
zip.file(`.claude/agents/${roleAgent.filename}`, roleContent);
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Add skills to .claude/skills/ folder
|
|
@@ -197,11 +197,11 @@ async function downloadAllAsZip(
|
|
|
197
197
|
zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, skillContent);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
// Add
|
|
201
|
-
if (Object.keys(
|
|
200
|
+
// Add Claude Code settings
|
|
201
|
+
if (Object.keys(claudeCodeSettings).length > 0) {
|
|
202
202
|
zip.file(
|
|
203
|
-
".
|
|
204
|
-
JSON.stringify(
|
|
203
|
+
".claude/settings.json",
|
|
204
|
+
JSON.stringify(claudeCodeSettings, null, 2) + "\n",
|
|
205
205
|
);
|
|
206
206
|
}
|
|
207
207
|
|
|
@@ -235,13 +235,12 @@ async function importJSZip() {
|
|
|
235
235
|
* @param {Object} profile - Generated profile with frontmatter and body
|
|
236
236
|
* @param {Object} options - Options
|
|
237
237
|
* @param {Array} [options.stages] - All stages for emoji lookup
|
|
238
|
-
* @param {Object} [options.
|
|
238
|
+
* @param {Object} [options.claudeCodeSettings] - Claude Code settings for download
|
|
239
239
|
* @returns {HTMLElement}
|
|
240
240
|
*/
|
|
241
241
|
export function stageAgentToDOM(stageAgent, profile, options = {}) {
|
|
242
|
-
const {
|
|
243
|
-
const { stage, tools,
|
|
244
|
-
stageAgent;
|
|
242
|
+
const { claudeCodeSettings = {}, stages = [] } = options;
|
|
243
|
+
const { stage, tools, constraints, checklist, derivedSkills } = stageAgent;
|
|
245
244
|
const stageEmoji = getStageEmoji(stages, stage.id);
|
|
246
245
|
const profileContent = formatAgentProfile(profile);
|
|
247
246
|
|
|
@@ -287,25 +286,6 @@ export function stageAgentToDOM(stageAgent, profile, options = {}) {
|
|
|
287
286
|
)
|
|
288
287
|
: null,
|
|
289
288
|
|
|
290
|
-
// Handoffs section
|
|
291
|
-
handoffs.length > 0
|
|
292
|
-
? section(
|
|
293
|
-
{ className: "agent-section" },
|
|
294
|
-
h3({}, "Handoffs"),
|
|
295
|
-
div(
|
|
296
|
-
{ className: "handoff-buttons" },
|
|
297
|
-
...handoffs.map((h) => {
|
|
298
|
-
const targetEmoji = getStageEmoji(stages, h.target);
|
|
299
|
-
return div(
|
|
300
|
-
{ className: "handoff-button-preview" },
|
|
301
|
-
span({ className: "handoff-icon" }, targetEmoji),
|
|
302
|
-
span({ className: "handoff-label" }, h.label),
|
|
303
|
-
);
|
|
304
|
-
}),
|
|
305
|
-
),
|
|
306
|
-
)
|
|
307
|
-
: null,
|
|
308
|
-
|
|
309
289
|
// Checklist section
|
|
310
290
|
checklist.length > 0
|
|
311
291
|
? section(
|
|
@@ -353,7 +333,7 @@ export function stageAgentToDOM(stageAgent, profile, options = {}) {
|
|
|
353
333
|
),
|
|
354
334
|
|
|
355
335
|
// Download button
|
|
356
|
-
createStageAgentDownloadButton(profile,
|
|
336
|
+
createStageAgentDownloadButton(profile, claudeCodeSettings),
|
|
357
337
|
);
|
|
358
338
|
}
|
|
359
339
|
|
|
@@ -387,10 +367,10 @@ function createChecklistPreview(checklist) {
|
|
|
387
367
|
/**
|
|
388
368
|
* Create download button for stage agent
|
|
389
369
|
* @param {Object} profile - Agent profile
|
|
390
|
-
* @param {Object}
|
|
370
|
+
* @param {Object} claudeCodeSettings - Claude Code settings
|
|
391
371
|
* @returns {HTMLElement}
|
|
392
372
|
*/
|
|
393
|
-
function createStageAgentDownloadButton(profile,
|
|
373
|
+
function createStageAgentDownloadButton(profile, claudeCodeSettings) {
|
|
394
374
|
const btn = button(
|
|
395
375
|
{ className: "btn btn-primary download-all-btn" },
|
|
396
376
|
"📥 Download Agent Profile",
|
|
@@ -406,13 +386,13 @@ function createStageAgentDownloadButton(profile, vscodeSettings) {
|
|
|
406
386
|
|
|
407
387
|
// Add profile
|
|
408
388
|
const profileContent = formatAgentProfile(profile);
|
|
409
|
-
zip.file(`.
|
|
389
|
+
zip.file(`.claude/agents/${profile.filename}`, profileContent);
|
|
410
390
|
|
|
411
|
-
// Add
|
|
412
|
-
if (Object.keys(
|
|
391
|
+
// Add Claude Code settings
|
|
392
|
+
if (Object.keys(claudeCodeSettings).length > 0) {
|
|
413
393
|
zip.file(
|
|
414
|
-
".
|
|
415
|
-
JSON.stringify(
|
|
394
|
+
".claude/settings.json",
|
|
395
|
+
JSON.stringify(claudeCodeSettings, null, 2) + "\n",
|
|
416
396
|
);
|
|
417
397
|
}
|
|
418
398
|
|