@forwardimpact/pathway 0.25.15 → 0.25.21

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.
Files changed (34) hide show
  1. package/bin/fit-pathway.js +62 -54
  2. package/package.json +1 -3
  3. package/src/commands/agent-io.js +120 -0
  4. package/src/commands/agent.js +266 -349
  5. package/src/commands/init.js +2 -2
  6. package/src/commands/job.js +237 -183
  7. package/src/components/comparison-radar.js +118 -103
  8. package/src/components/progression-table.js +244 -208
  9. package/src/formatters/index.js +0 -19
  10. package/src/formatters/interview/markdown.js +100 -88
  11. package/src/formatters/job/description.js +76 -75
  12. package/src/formatters/job/dom.js +113 -97
  13. package/src/formatters/level/dom.js +87 -102
  14. package/src/formatters/questions/markdown.js +37 -33
  15. package/src/formatters/questions/shared.js +142 -75
  16. package/src/formatters/skill/dom.js +102 -93
  17. package/src/lib/comparison-radar-chart.js +256 -0
  18. package/src/lib/radar-utils.js +199 -0
  19. package/src/lib/radar.js +25 -662
  20. package/src/pages/agent-builder-download.js +170 -0
  21. package/src/pages/agent-builder-preview.js +344 -0
  22. package/src/pages/agent-builder.js +6 -550
  23. package/src/pages/progress-comparison.js +110 -0
  24. package/src/pages/progress.js +11 -111
  25. package/src/pages/self-assessment-steps.js +494 -0
  26. package/src/pages/self-assessment.js +54 -504
  27. package/src/formatters/behaviour/microdata.js +0 -106
  28. package/src/formatters/discipline/microdata.js +0 -117
  29. package/src/formatters/driver/microdata.js +0 -91
  30. package/src/formatters/level/microdata.js +0 -141
  31. package/src/formatters/microdata-shared.js +0 -184
  32. package/src/formatters/skill/microdata.js +0 -151
  33. package/src/formatters/stage/microdata.js +0 -116
  34. package/src/formatters/track/microdata.js +0 -111
@@ -11,39 +11,25 @@ import {
11
11
  div,
12
12
  h1,
13
13
  h2,
14
- h3,
15
14
  p,
16
- span,
17
15
  label,
18
- section,
19
16
  select,
20
17
  option,
21
18
  } from "../lib/render.js";
22
19
  import { getState } from "../lib/state.js";
23
20
  import { loadAgentDataBrowser } from "../lib/yaml-loader.js";
24
- import {
25
- generateStageAgentProfile,
26
- deriveStageAgent,
27
- generateSkillMarkdown,
28
- deriveAgentSkills,
29
- deriveReferenceLevel,
30
- deriveToolkit,
31
- } from "@forwardimpact/libskill";
21
+ import { deriveReferenceLevel } from "@forwardimpact/libskill";
32
22
  import {
33
23
  createSelectWithValue,
34
24
  createDisciplineSelect,
35
25
  } from "../lib/form-controls.js";
36
26
  import { createReactive } from "../lib/reactive.js";
37
27
  import { getStageEmoji } from "../formatters/stage/shared.js";
38
- import { formatAgentProfile } from "../formatters/agent/profile.js";
39
28
  import {
40
- formatAgentSkill,
41
- formatInstallScript,
42
- formatReference,
43
- } from "../formatters/agent/skill.js";
44
- import { createFileCard } from "../components/file-card.js";
45
- import { createToolkitTable } from "../formatters/toolkit/dom.js";
46
- import { createDetailSection } from "../components/detail.js";
29
+ createAllStagesPreview,
30
+ createSingleStagePreview,
31
+ createHelpSection,
32
+ } from "./agent-builder-preview.js";
47
33
 
48
34
  /** All stages option value */
49
35
  const ALL_STAGES_VALUE = "all";
@@ -193,6 +179,7 @@ export async function renderAgentBuilder() {
193
179
  // Supports: /agent/discipline, /agent/discipline/track, /agent/discipline/track/stage
194
180
  const hash = window.location.hash;
195
181
  const pathMatch = hash.match(
182
+ // eslint-disable-next-line security/detect-unsafe-regex -- negated char classes prevent backtracking; parses internal URL hash
196
183
  /#\/agent\/([^/]+)(?:\/([^/]+))?(?:\/([^/?]+))?/,
197
184
  );
198
185
  const initialDiscipline = pathMatch ? pathMatch[1] : "";
@@ -426,534 +413,3 @@ function createEmptyState(disciplineCount, trackCount) {
426
413
  ),
427
414
  );
428
415
  }
429
-
430
- /**
431
- * Create preview for all stages
432
- * Shows cards for each stage agent and all skills
433
- * @param {Object} context - Generation context
434
- * @returns {HTMLElement}
435
- */
436
- function createAllStagesPreview(context) {
437
- const {
438
- humanDiscipline,
439
- humanTrack,
440
- agentDiscipline,
441
- agentTrack,
442
- level,
443
- stages,
444
- skills,
445
- behaviours,
446
- agentBehaviours,
447
- claudeCodeSettings,
448
- templates,
449
- } = context;
450
-
451
- // Generate all stage agents
452
- const stageAgents = stages.map((stage) => {
453
- const derived = deriveStageAgent({
454
- discipline: humanDiscipline,
455
- track: humanTrack,
456
- stage,
457
- level,
458
- skills,
459
- behaviours,
460
- agentBehaviours,
461
- agentDiscipline,
462
- agentTrack,
463
- });
464
-
465
- const profile = generateStageAgentProfile({
466
- discipline: humanDiscipline,
467
- track: humanTrack,
468
- stage,
469
- level,
470
- skills,
471
- behaviours,
472
- agentBehaviours,
473
- agentDiscipline,
474
- agentTrack,
475
- stages,
476
- });
477
-
478
- return { stage, derived, profile };
479
- });
480
-
481
- // Get derived skills for skill cards
482
- const derivedSkills = deriveAgentSkills({
483
- discipline: humanDiscipline,
484
- track: humanTrack,
485
- level,
486
- skills,
487
- });
488
-
489
- // Generate skill files
490
- const skillFiles = derivedSkills
491
- .map((derived) => skills.find((s) => s.id === derived.skillId))
492
- .filter((skill) => skill?.agent)
493
- .map((skill) => generateSkillMarkdown(skill, stages));
494
-
495
- // Derive toolkit from agent skills
496
- const toolkit = deriveToolkit({
497
- skillMatrix: derivedSkills,
498
- skills,
499
- });
500
-
501
- return div(
502
- { className: "agent-deployment" },
503
-
504
- // Download all button
505
- createDownloadAllButton(
506
- stageAgents,
507
- skillFiles,
508
- claudeCodeSettings,
509
- context,
510
- ),
511
-
512
- // Agents section
513
- section(
514
- { className: "agent-section" },
515
- h2({}, `Agents (${stageAgents.length})`),
516
- p(
517
- { className: "text-muted" },
518
- "Stage-specific agents with skills, constraints, and stage transitions.",
519
- ),
520
- div(
521
- { className: "agent-cards-grid" },
522
- ...stageAgents.map(({ stage, profile }) => {
523
- const content = formatAgentProfile(profile, templates.agent);
524
- const stageEmoji = getStageEmoji(stages, stage.id);
525
- return createFileCard({
526
- header: [
527
- span({ className: "file-card-emoji" }, stageEmoji),
528
- h3({}, `${stage.name} Agent`),
529
- ],
530
- files: [
531
- {
532
- filename: profile.filename,
533
- content,
534
- language: "markdown",
535
- },
536
- ],
537
- maxHeight: 400,
538
- });
539
- }),
540
- ),
541
- ),
542
-
543
- // Skills section
544
- section(
545
- { className: "agent-section" },
546
- h2({}, `Skills (${skillFiles.length})`),
547
- skillFiles.length > 0
548
- ? div(
549
- { className: "skill-cards-grid" },
550
- ...skillFiles.map((skill) => buildSkillFileCard(skill, templates)),
551
- )
552
- : p(
553
- { className: "text-muted" },
554
- "No skills with agent sections found for this discipline.",
555
- ),
556
- ),
557
-
558
- // Tool Kit section
559
- toolkit.length > 0
560
- ? createDetailSection({
561
- title: `Tool Kit (${toolkit.length})`,
562
- content: createToolkitTable(toolkit),
563
- })
564
- : null,
565
- );
566
- }
567
-
568
- /**
569
- * Create preview for a single stage
570
- * @param {Object} context - Generation context
571
- * @param {Object} stage - Selected stage
572
- * @returns {HTMLElement}
573
- */
574
- function createSingleStagePreview(context, stage) {
575
- const {
576
- humanDiscipline,
577
- humanTrack,
578
- agentDiscipline,
579
- agentTrack,
580
- level,
581
- skills,
582
- behaviours,
583
- agentBehaviours,
584
- claudeCodeSettings,
585
- stages,
586
- templates,
587
- } = context;
588
-
589
- const profile = generateStageAgentProfile({
590
- discipline: humanDiscipline,
591
- track: humanTrack,
592
- stage,
593
- level,
594
- skills,
595
- behaviours,
596
- agentBehaviours,
597
- agentDiscipline,
598
- agentTrack,
599
- stages,
600
- });
601
-
602
- // Get skills for this stage (using full derived skills)
603
- const derivedSkills = deriveAgentSkills({
604
- discipline: humanDiscipline,
605
- track: humanTrack,
606
- level,
607
- skills,
608
- });
609
-
610
- const skillFiles = derivedSkills
611
- .map((d) => skills.find((s) => s.id === d.skillId))
612
- .filter((skill) => skill?.agent)
613
- .map((skill) => generateSkillMarkdown(skill, stages));
614
-
615
- // Derive toolkit from agent skills
616
- const toolkit = deriveToolkit({
617
- skillMatrix: derivedSkills,
618
- skills,
619
- });
620
-
621
- return div(
622
- { className: "agent-deployment" },
623
-
624
- // Download button for single stage
625
- createDownloadSingleButton(
626
- profile,
627
- skillFiles,
628
- claudeCodeSettings,
629
- templates,
630
- ),
631
-
632
- // Agents section (single card)
633
- section(
634
- { className: "agent-section" },
635
- h2({}, "Agent"),
636
- div(
637
- { className: "agent-cards-grid single" },
638
- (() => {
639
- const content = formatAgentProfile(profile, templates.agent);
640
- const stageEmoji = getStageEmoji(stages, stage.id);
641
- return createFileCard({
642
- header: [
643
- span({ className: "file-card-emoji" }, stageEmoji),
644
- h3({}, `${stage.name} Agent`),
645
- ],
646
- files: [
647
- {
648
- filename: profile.filename,
649
- content,
650
- language: "markdown",
651
- },
652
- ],
653
- maxHeight: 400,
654
- });
655
- })(),
656
- ),
657
- ),
658
-
659
- // Skills section
660
- section(
661
- { className: "agent-section" },
662
- h2({}, `Skills (${skillFiles.length})`),
663
- skillFiles.length > 0
664
- ? div(
665
- { className: "skill-cards-grid" },
666
- ...skillFiles.map((skill) => buildSkillFileCard(skill, templates)),
667
- )
668
- : p(
669
- { className: "text-muted" },
670
- "No skills with agent sections found for this discipline.",
671
- ),
672
- ),
673
-
674
- // Tool Kit section
675
- toolkit.length > 0
676
- ? createDetailSection({
677
- title: `Tool Kit (${toolkit.length})`,
678
- content: createToolkitTable(toolkit),
679
- })
680
- : null,
681
- );
682
- }
683
-
684
- /**
685
- * Build a file card for a skill with 1–3 file panes (accordion).
686
- * @param {Object} skill - Skill with frontmatter and body
687
- * @param {{skill: string, install: string, reference: string}} templates - Mustache templates
688
- * @returns {HTMLElement}
689
- */
690
- function buildSkillFileCard(skill, templates) {
691
- const content = formatAgentSkill(skill, templates.skill);
692
-
693
- /** @type {import('../components/file-card.js').FileDescriptor[]} */
694
- const files = [
695
- {
696
- filename: `${skill.dirname}/SKILL.md`,
697
- content,
698
- language: "markdown",
699
- },
700
- ];
701
-
702
- if (skill.installScript) {
703
- files.push({
704
- filename: `${skill.dirname}/scripts/install.sh`,
705
- content: formatInstallScript(skill, templates.install),
706
- language: "bash",
707
- });
708
- }
709
-
710
- if (skill.implementationReference) {
711
- files.push({
712
- filename: `${skill.dirname}/references/REFERENCE.md`,
713
- content: formatReference(skill, templates.reference),
714
- language: "markdown",
715
- });
716
- }
717
-
718
- const headerChildren = [
719
- span({ className: "file-card-name" }, skill.frontmatter.name),
720
- ];
721
- if (files.length > 1) {
722
- headerChildren.push(
723
- span({ className: "file-card-badge" }, `${files.length} files`),
724
- );
725
- }
726
-
727
- return createFileCard({
728
- header: headerChildren,
729
- files,
730
- maxHeight: 300,
731
- });
732
- }
733
-
734
- /**
735
- * Create download all button for all stages
736
- * @param {Array} stageAgents - Array of {stage, derived, profile}
737
- * @param {Array} skillFiles - Array of skill file objects
738
- * @param {Object} claudeCodeSettings - Claude Code settings
739
- * @param {Object} context - Context with discipline/track info and templates
740
- * @returns {HTMLElement}
741
- */
742
- function createDownloadAllButton(
743
- stageAgents,
744
- skillFiles,
745
- claudeCodeSettings,
746
- context,
747
- ) {
748
- const { humanDiscipline, humanTrack, templates } = context;
749
- const agentName = `${humanDiscipline.id}-${humanTrack.id}`.replace(/_/g, "-");
750
-
751
- const btn = document.createElement("button");
752
- btn.className = "btn btn-primary download-all-btn";
753
- btn.textContent = "📦 Download All (.zip)";
754
-
755
- btn.addEventListener("click", async () => {
756
- btn.disabled = true;
757
- btn.textContent = "Generating...";
758
-
759
- try {
760
- const JSZip = await importJSZip();
761
- const zip = new JSZip();
762
-
763
- // Add all stage agent profiles to .claude/agents/
764
- for (const { profile } of stageAgents) {
765
- const content = formatAgentProfile(profile, templates.agent);
766
- zip.file(`.claude/agents/${profile.filename}`, content);
767
- }
768
-
769
- // Add skills (SKILL.md + optional install script + optional reference)
770
- for (const skill of skillFiles) {
771
- const content = formatAgentSkill(skill, templates.skill);
772
- zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, content);
773
-
774
- if (skill.installScript) {
775
- const installContent = formatInstallScript(skill, templates.install);
776
- zip.file(
777
- `.claude/skills/${skill.dirname}/scripts/install.sh`,
778
- installContent,
779
- { unixPermissions: "755" },
780
- );
781
- }
782
-
783
- if (skill.implementationReference) {
784
- const refContent = formatReference(skill, templates.reference);
785
- zip.file(
786
- `.claude/skills/${skill.dirname}/references/REFERENCE.md`,
787
- refContent,
788
- );
789
- }
790
- }
791
-
792
- // Add Claude Code settings
793
- if (Object.keys(claudeCodeSettings).length > 0) {
794
- zip.file(
795
- ".claude/settings.json",
796
- JSON.stringify(claudeCodeSettings, null, 2) + "\n",
797
- );
798
- }
799
-
800
- // Generate and download
801
- const blob = await zip.generateAsync({ type: "blob" });
802
- const url = URL.createObjectURL(blob);
803
-
804
- const link = document.createElement("a");
805
- link.href = url;
806
- link.download = `${agentName}-agents.zip`;
807
- document.body.appendChild(link);
808
- link.click();
809
- document.body.removeChild(link);
810
-
811
- URL.revokeObjectURL(url);
812
- } finally {
813
- btn.disabled = false;
814
- btn.textContent = "📦 Download All (.zip)";
815
- }
816
- });
817
-
818
- return btn;
819
- }
820
-
821
- /**
822
- * Create download button for single stage
823
- * @param {Object} profile - Agent profile
824
- * @param {Array} skillFiles - Skill files
825
- * @param {Object} claudeCodeSettings - Claude Code settings
826
- * @param {{agent: string, skill: string}} templates - Mustache templates
827
- * @returns {HTMLElement}
828
- */
829
- function createDownloadSingleButton(
830
- profile,
831
- skillFiles,
832
- claudeCodeSettings,
833
- templates,
834
- ) {
835
- const btn = document.createElement("button");
836
- btn.className = "btn btn-primary download-all-btn";
837
- btn.textContent = "📥 Download Agent (.zip)";
838
-
839
- btn.addEventListener("click", async () => {
840
- btn.disabled = true;
841
- btn.textContent = "Generating...";
842
-
843
- try {
844
- const JSZip = await importJSZip();
845
- const zip = new JSZip();
846
-
847
- // Add profile to .claude/agents/
848
- const content = formatAgentProfile(profile, templates.agent);
849
- zip.file(`.claude/agents/${profile.filename}`, content);
850
-
851
- // Add skills (SKILL.md + optional install script + optional reference)
852
- for (const skill of skillFiles) {
853
- const skillContent = formatAgentSkill(skill, templates.skill);
854
- zip.file(`.claude/skills/${skill.dirname}/SKILL.md`, skillContent);
855
-
856
- if (skill.installScript) {
857
- const installContent = formatInstallScript(skill, templates.install);
858
- zip.file(
859
- `.claude/skills/${skill.dirname}/scripts/install.sh`,
860
- installContent,
861
- { unixPermissions: "755" },
862
- );
863
- }
864
-
865
- if (skill.implementationReference) {
866
- const refContent = formatReference(skill, templates.reference);
867
- zip.file(
868
- `.claude/skills/${skill.dirname}/references/REFERENCE.md`,
869
- refContent,
870
- );
871
- }
872
- }
873
-
874
- // Add Claude Code settings
875
- if (Object.keys(claudeCodeSettings).length > 0) {
876
- zip.file(
877
- ".claude/settings.json",
878
- JSON.stringify(claudeCodeSettings, null, 2) + "\n",
879
- );
880
- }
881
-
882
- // Generate and download
883
- const blob = await zip.generateAsync({ type: "blob" });
884
- const url = URL.createObjectURL(blob);
885
-
886
- const link = document.createElement("a");
887
- link.href = url;
888
- link.download = `${profile.frontmatter.name}-agent.zip`;
889
- document.body.appendChild(link);
890
- link.click();
891
- document.body.removeChild(link);
892
-
893
- URL.revokeObjectURL(url);
894
- } finally {
895
- btn.disabled = false;
896
- btn.textContent = "📥 Download Agent (.zip)";
897
- }
898
- });
899
-
900
- return btn;
901
- }
902
-
903
- /**
904
- * Dynamically import JSZip from CDN
905
- * @returns {Promise<typeof JSZip>}
906
- */
907
- async function importJSZip() {
908
- const module = await import("https://cdn.jsdelivr.net/npm/jszip@3.10.1/+esm");
909
- return module.default;
910
- }
911
-
912
- /**
913
- * Create help section explaining how agent builder works
914
- * @returns {HTMLElement}
915
- */
916
- function createHelpSection() {
917
- return section(
918
- { className: "section section-detail" },
919
- h2({ className: "section-title" }, "How It Works"),
920
- div(
921
- { className: "auto-grid-md" },
922
- div(
923
- { className: "detail-item" },
924
- div({ className: "detail-item-label" }, "Stages"),
925
- p(
926
- {},
927
- "Agents are generated for each stage: Plan (research), Code (implement), and Review (verify). " +
928
- "Each stage has specific skills, constraints, and stage transitions.",
929
- ),
930
- ),
931
- div(
932
- { className: "detail-item" },
933
- div({ className: "detail-item-label" }, "Agent Profiles"),
934
- p(
935
- {},
936
- "The .md files contain the agent's identity, skills, and constraints. " +
937
- "Place them in .claude/agents/ for Claude Code to discover.",
938
- ),
939
- ),
940
- div(
941
- { className: "detail-item" },
942
- div({ className: "detail-item-label" }, "Skills"),
943
- p(
944
- {},
945
- "SKILL.md files provide specialized knowledge that agents can use. " +
946
- "Place them in .claude/skills/{skill-name}/ directories.",
947
- ),
948
- ),
949
- div(
950
- { className: "detail-item" },
951
- div({ className: "detail-item-label" }, "All Stages"),
952
- p(
953
- {},
954
- "Select 'All Stages' to download a complete agent deployment with all stage agents and skills in one zip file.",
955
- ),
956
- ),
957
- ),
958
- );
959
- }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Comparison result builder for progress page
3
+ */
4
+
5
+ import { div, h2, a, section } from "../lib/render.js";
6
+ import { createStatCard } from "../components/card.js";
7
+ import {
8
+ createComparisonSkillRadar,
9
+ createComparisonBehaviourRadar,
10
+ } from "../components/comparison-radar.js";
11
+ import { createProgressionTable } from "../components/progression-table.js";
12
+
13
+ /**
14
+ * Build the comparison result DOM from a progression view
15
+ * @param {Object} progressionView
16
+ * @param {Object} currentJobView
17
+ * @param {Object} currentLevel
18
+ * @param {Object} targetLevel
19
+ * @param {Object|null} targetTrack
20
+ * @param {Object} targetDiscipline
21
+ * @param {Object} data
22
+ * @returns {HTMLElement}
23
+ */
24
+ export function buildComparisonResult(
25
+ progressionView,
26
+ currentJobView,
27
+ currentLevel,
28
+ targetLevel,
29
+ targetTrack,
30
+ targetDiscipline,
31
+ data,
32
+ ) {
33
+ const { skillChanges, behaviourChanges, summary, target } = progressionView;
34
+
35
+ return div(
36
+ { className: "comparison-result" },
37
+ div(
38
+ { className: "grid grid-6" },
39
+ summary.skillsGained > 0
40
+ ? createStatCard({ value: summary.skillsGained, label: "New Skills" })
41
+ : null,
42
+ createStatCard({ value: summary.skillsUp, label: "Skills to Grow" }),
43
+ summary.skillsDown > 0
44
+ ? createStatCard({
45
+ value: summary.skillsDown,
46
+ label: "Skills Decrease",
47
+ })
48
+ : null,
49
+ summary.skillsLost > 0
50
+ ? createStatCard({ value: summary.skillsLost, label: "Skills Removed" })
51
+ : null,
52
+ createStatCard({
53
+ value: summary.behavioursUp,
54
+ label: "Behaviours to Mature",
55
+ }),
56
+ summary.behavioursDown > 0
57
+ ? createStatCard({
58
+ value: summary.behavioursDown,
59
+ label: "Behaviours Decrease",
60
+ })
61
+ : null,
62
+ ),
63
+ div(
64
+ { className: "section auto-grid-lg" },
65
+ createComparisonSkillRadar(
66
+ currentJobView.skillMatrix,
67
+ target.skillMatrix,
68
+ {
69
+ title: "Skills Comparison",
70
+ currentLabel: `Current (${currentLevel.id})`,
71
+ targetLabel: `Target (${targetLevel.id})`,
72
+ size: 400,
73
+ capabilities: data.capabilities,
74
+ },
75
+ ),
76
+ createComparisonBehaviourRadar(
77
+ currentJobView.behaviourProfile,
78
+ target.behaviourProfile,
79
+ {
80
+ title: "Behaviours Comparison",
81
+ currentLabel: `Current (${currentLevel.id})`,
82
+ targetLabel: `Target (${targetLevel.id})`,
83
+ size: 400,
84
+ },
85
+ ),
86
+ ),
87
+ section(
88
+ { className: "section section-detail" },
89
+ h2({ className: "section-title" }, "Skill Changes"),
90
+ createProgressionTable(skillChanges, "skill"),
91
+ ),
92
+ section(
93
+ { className: "section section-detail" },
94
+ h2({ className: "section-title" }, "Behaviour Changes"),
95
+ createProgressionTable(behaviourChanges, "behaviour"),
96
+ ),
97
+ div(
98
+ { className: "page-actions" },
99
+ a(
100
+ {
101
+ href: targetTrack
102
+ ? `#/job/${targetDiscipline.id}/${targetLevel.id}/${targetTrack.id}`
103
+ : `#/job/${targetDiscipline.id}/${targetLevel.id}`,
104
+ className: "btn btn-secondary",
105
+ },
106
+ `View ${targetLevel.id}${targetTrack ? ` ${targetTrack.name}` : ""} Job Definition →`,
107
+ ),
108
+ ),
109
+ );
110
+ }