@forwardimpact/pathway 0.6.0 → 0.8.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.
@@ -12,6 +12,12 @@ import Mustache from "mustache";
12
12
 
13
13
  import { trimValue, trimRequired, trimFields } from "../shared.js";
14
14
 
15
+ /**
16
+ * @typedef {Object} WorkingStyleEntry
17
+ * @property {string} title - Section title
18
+ * @property {string} content - Working style content (markdown)
19
+ */
20
+
15
21
  /**
16
22
  * Prepare agent profile data for template rendering
17
23
  * Normalizes string values by trimming trailing newlines for consistent template output.
@@ -27,30 +33,32 @@ import { trimValue, trimRequired, trimFields } from "../shared.js";
27
33
  * @param {string} params.bodyData.identity - Core identity text
28
34
  * @param {string} [params.bodyData.priority] - Priority/philosophy statement
29
35
  * @param {Array<{name: string, dirname: string, useWhen: string}>} params.bodyData.skillIndex - Skill index entries
30
- * @param {Array<{index: number, text: string}>} params.bodyData.beforeMakingChanges - Numbered steps
31
- * @param {string} [params.bodyData.delegation] - Delegation guidance
32
- * @param {string} params.bodyData.operationalContext - Operational context text
33
- * @param {string} params.bodyData.workingStyle - Working style markdown section
36
+ * @param {string} params.bodyData.roleContext - Role context text
37
+ * @param {WorkingStyleEntry[]} params.bodyData.workingStyles - Working style entries
34
38
  * @param {string} [params.bodyData.beforeHandoff] - Before handoff checklist markdown
35
39
  * @param {string[]} params.bodyData.constraints - List of constraints
40
+ * @param {Array<{id: string, name: string, description: string}>} [params.bodyData.agentIndex] - List of all available agents
41
+ * @param {boolean} [params.bodyData.hasAgentIndex] - Whether agent index is available
36
42
  * @returns {Object} Data object ready for Mustache template
37
43
  */
38
44
  function prepareAgentProfileData({ frontmatter, bodyData }) {
39
- // Trim array fields using helpers
45
+ // Trim array fields using shared helpers
40
46
  const handoffs = trimFields(frontmatter.handoffs, { prompt: "required" });
41
- const beforeMakingChanges = trimFields(bodyData.beforeMakingChanges, {
42
- text: "required",
43
- });
44
-
45
- // Trim simple string arrays
46
47
  const constraints = (bodyData.constraints || []).map((c) => trimRequired(c));
47
-
48
- // Trim skill index entries
49
- const skillIndex = (bodyData.skillIndex || []).map((s) => ({
50
- name: trimRequired(s.name),
51
- dirname: trimRequired(s.dirname),
52
- useWhen: trimRequired(s.useWhen),
53
- }));
48
+ const skillIndex = trimFields(bodyData.skillIndex, {
49
+ name: "required",
50
+ dirname: "required",
51
+ useWhen: "required",
52
+ });
53
+ const agentIndex = trimFields(bodyData.agentIndex, {
54
+ id: "required",
55
+ name: "required",
56
+ description: "required",
57
+ });
58
+ const workingStyles = trimFields(bodyData.workingStyles, {
59
+ title: "required",
60
+ content: "required",
61
+ });
54
62
 
55
63
  return {
56
64
  // Frontmatter
@@ -66,12 +74,14 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
66
74
  priority: trimValue(bodyData.priority),
67
75
  skillIndex,
68
76
  hasSkills: skillIndex.length > 0,
69
- beforeMakingChanges,
70
- delegation: trimValue(bodyData.delegation),
71
- operationalContext: trimValue(bodyData.operationalContext),
72
- workingStyle: trimValue(bodyData.workingStyle),
77
+ roleContext: trimValue(bodyData.roleContext),
78
+ workingStyles,
79
+ hasWorkingStyles: workingStyles.length > 0,
73
80
  beforeHandoff: trimValue(bodyData.beforeHandoff),
74
81
  constraints,
82
+ hasConstraints: constraints.length > 0,
83
+ agentIndex,
84
+ hasAgentIndex: agentIndex.length > 0,
75
85
  };
76
86
  }
77
87
 
@@ -90,10 +100,8 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
90
100
  * @param {string} profile.bodyData.identity - Core identity text
91
101
  * @param {string} [profile.bodyData.priority] - Priority/philosophy statement (optional)
92
102
  * @param {Array<{name: string, dirname: string, useWhen: string}>} profile.bodyData.skillIndex - Skill index entries
93
- * @param {Array<{index: number, text: string}>} profile.bodyData.beforeMakingChanges - Numbered steps
94
- * @param {string} [profile.bodyData.delegation] - Delegation guidance (optional)
95
- * @param {string} profile.bodyData.operationalContext - Operational context text
96
- * @param {string} profile.bodyData.workingStyle - Working style markdown section
103
+ * @param {string} profile.bodyData.roleContext - Role context text
104
+ * @param {WorkingStyleEntry[]} profile.bodyData.workingStyles - Working style entries
97
105
  * @param {string} [profile.bodyData.beforeHandoff] - Before handoff checklist markdown (optional)
98
106
  * @param {string[]} profile.bodyData.constraints - List of constraints
99
107
  * @param {string} template - Mustache template string
@@ -40,14 +40,24 @@ function prepareAgentSkillData({
40
40
  ready: "array",
41
41
  });
42
42
 
43
+ const descriptionLines = splitLines(frontmatter.description);
44
+ const useWhenLines = splitLines(frontmatter.useWhen);
45
+ const trimmedReference = trimValue(reference) || "";
46
+ const tools = toolReferences || [];
47
+
43
48
  return {
44
49
  name: frontmatter.name,
45
- descriptionLines: splitLines(frontmatter.description),
46
- useWhenLines: splitLines(frontmatter.useWhen),
50
+ descriptionLines,
51
+ hasDescription: descriptionLines.length > 0,
52
+ useWhenLines,
53
+ hasUseWhen: useWhenLines.length > 0,
47
54
  title,
48
55
  stages: processedStages,
49
- reference: trimValue(reference) || "",
50
- toolReferences: toolReferences || [],
56
+ hasStages: processedStages.length > 0,
57
+ reference: trimmedReference,
58
+ hasReference: !!trimmedReference,
59
+ toolReferences: tools,
60
+ hasToolReferences: tools.length > 0,
51
61
  };
52
62
  }
53
63
 
@@ -2,7 +2,7 @@
2
2
  * Behaviour formatting for microdata HTML output
3
3
  *
4
4
  * Generates clean, class-less HTML with microdata aligned with behaviour.schema.json
5
- * RDF vocab: https://schema.forwardimpact.team/rdf/
5
+ * RDF vocab: https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
8
  import {
@@ -2,7 +2,7 @@
2
2
  * Discipline formatting for microdata HTML output
3
3
  *
4
4
  * Generates clean, class-less HTML with microdata aligned with discipline.schema.json
5
- * RDF vocab: https://schema.forwardimpact.team/rdf/
5
+ * RDF vocab: https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
8
  import {
@@ -2,7 +2,7 @@
2
2
  * Driver formatting for microdata HTML output
3
3
  *
4
4
  * Generates clean, class-less HTML with microdata aligned with drivers.schema.json
5
- * RDF vocab: https://schema.forwardimpact.team/rdf/
5
+ * RDF vocab: https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
8
  import {
@@ -2,7 +2,7 @@
2
2
  * Grade formatting for microdata HTML output
3
3
  *
4
4
  * Generates clean, class-less HTML with microdata aligned with grades.schema.json
5
- * RDF vocab: https://schema.forwardimpact.team/rdf/
5
+ * RDF vocab: https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
8
  import {
@@ -129,25 +129,38 @@ function prepareJobDescriptionData({ job, discipline, grade, track }) {
129
129
  grade.typicalExperienceRange || "",
130
130
  ) || null;
131
131
 
132
+ const responsibilities = trimFields(job.derivedResponsibilities, {
133
+ responsibility: "required",
134
+ });
135
+ const behaviours = trimFields(sortedBehaviours, {
136
+ maturityDescription: "optional",
137
+ });
138
+ const trimmedTrackRoleContext = trimValue(track?.roleContext);
139
+ const trimmedExpectationsParagraph = trimValue(expectationsParagraph);
140
+ const trimmedQualificationSummary = trimValue(qualificationSummary);
141
+
132
142
  return {
133
143
  title: job.title,
134
144
  gradeId: grade.id,
135
145
  typicalExperienceRange: grade.typicalExperienceRange,
136
146
  trackName: track?.name || null,
147
+ hasTrack: !!track,
137
148
  roleSummary: trimValue(roleSummary),
138
- trackRoleContext: trimValue(track?.roleContext),
139
- expectationsParagraph: trimValue(expectationsParagraph),
140
- responsibilities: trimFields(job.derivedResponsibilities, {
141
- responsibility: "required",
142
- }),
143
- behaviours: trimFields(sortedBehaviours, {
144
- maturityDescription: "optional",
145
- }),
149
+ trackRoleContext: trimmedTrackRoleContext,
150
+ hasTrackRoleContext: !!trimmedTrackRoleContext,
151
+ expectationsParagraph: trimmedExpectationsParagraph,
152
+ hasExpectationsParagraph: !!trimmedExpectationsParagraph,
153
+ responsibilities,
154
+ hasResponsibilities: responsibilities.length > 0,
155
+ behaviours,
156
+ hasBehaviours: behaviours.length > 0,
146
157
  skillLevels: skillLevels.map((level) => ({
147
158
  ...level,
148
159
  skills: trimFields(level.skills, { levelDescription: "optional" }),
149
160
  })),
150
- qualificationSummary: trimValue(qualificationSummary),
161
+ hasSkillLevels: skillLevels.length > 0,
162
+ qualificationSummary: trimmedQualificationSummary,
163
+ hasQualificationSummary: !!trimmedQualificationSummary,
151
164
  };
152
165
  }
153
166
 
@@ -17,6 +17,7 @@ import { createBehaviourProfile } from "../../components/behaviour-profile.js";
17
17
  import { createCodeDisplay } from "../../components/code-display.js";
18
18
  import { markdownToHtml } from "../../lib/markdown.js";
19
19
  import { formatJobDescription } from "./description.js";
20
+ import { createToolkitTable } from "../toolkit/dom.js";
20
21
 
21
22
  /**
22
23
  * Format job detail as DOM elements
@@ -77,14 +78,14 @@ export function jobToDOM(view, options = {}) {
77
78
  // Radar charts
78
79
  div(
79
80
  { className: "section auto-grid-lg" },
80
- createSkillRadar(view.skillMatrix, {
81
- title: "Skills Radar",
82
- size: 420,
83
- }),
84
81
  createBehaviourRadar(view.behaviourProfile, {
85
82
  title: "Behaviours Radar",
86
83
  size: 420,
87
84
  }),
85
+ createSkillRadar(view.skillMatrix, {
86
+ title: "Skills Radar",
87
+ size: 420,
88
+ }),
88
89
  ),
89
90
 
90
91
  // Job Description HTML (for print view)
@@ -104,21 +105,29 @@ export function jobToDOM(view, options = {}) {
104
105
  })
105
106
  : null,
106
107
 
107
- // Skill matrix, Behaviour profile, Driver coverage tables
108
+ // Behaviour profile, Skill matrix, Toolkit, Driver coverage tables
108
109
  showTables
109
110
  ? div(
110
111
  { className: "job-tables-section" },
111
- createDetailSection({
112
- title: "Skill Matrix",
113
- content: createSkillMatrix(view.skillMatrix),
114
- }),
115
-
116
112
  // Behaviour profile table
117
113
  createDetailSection({
118
114
  title: "Behaviour Profile",
119
115
  content: createBehaviourProfile(view.behaviourProfile),
120
116
  }),
121
117
 
118
+ createDetailSection({
119
+ title: "Skill Matrix",
120
+ content: createSkillMatrix(view.skillMatrix),
121
+ }),
122
+
123
+ // Toolkit (after skill matrix)
124
+ view.toolkit && view.toolkit.length > 0
125
+ ? createDetailSection({
126
+ title: "Tool Kit",
127
+ content: createToolkitTable(view.toolkit),
128
+ })
129
+ : null,
130
+
122
131
  // Driver coverage
123
132
  view.driverCoverage.length > 0
124
133
  ? createDetailSection({
@@ -10,6 +10,7 @@ import {
10
10
  import { formatLevel } from "../../lib/render.js";
11
11
  import { formatJobDescription } from "./description.js";
12
12
  import { SKILL_LEVEL_ORDER } from "@forwardimpact/schema/levels";
13
+ import { toolkitToMarkdown } from "../toolkit/markdown.js";
13
14
 
14
15
  /**
15
16
  * Format job detail as markdown
@@ -33,6 +34,15 @@ export function jobToMarkdown(view, entities = {}, jobTemplate) {
33
34
  lines.push("");
34
35
  }
35
36
 
37
+ // Behaviour Profile
38
+ lines.push("## Behaviour Profile", "");
39
+ const behaviourRows = view.behaviourProfile.map((b) => [
40
+ b.behaviourName,
41
+ formatLevel(b.maturity),
42
+ ]);
43
+ lines.push(tableToMarkdown(["Behaviour", "Maturity"], behaviourRows));
44
+ lines.push("");
45
+
36
46
  // Skill Matrix - sorted by level descending
37
47
  lines.push("## Skill Matrix", "");
38
48
  const sortedSkills = [...view.skillMatrix].sort((a, b) => {
@@ -50,14 +60,12 @@ export function jobToMarkdown(view, entities = {}, jobTemplate) {
50
60
  lines.push(tableToMarkdown(["Skill", "Level"], skillRows));
51
61
  lines.push("");
52
62
 
53
- // Behaviour Profile
54
- lines.push("## Behaviour Profile", "");
55
- const behaviourRows = view.behaviourProfile.map((b) => [
56
- b.behaviourName,
57
- formatLevel(b.maturity),
58
- ]);
59
- lines.push(tableToMarkdown(["Behaviour", "Maturity"], behaviourRows));
60
- lines.push("");
63
+ // Toolkit
64
+ if (view.toolkit && view.toolkit.length > 0) {
65
+ lines.push("## Tool Kit", "");
66
+ lines.push(toolkitToMarkdown(view.toolkit));
67
+ lines.push("");
68
+ }
61
69
 
62
70
  // Driver Coverage
63
71
  if (view.driverCoverage.length > 0) {
@@ -2,10 +2,10 @@
2
2
  * JSON-LD structured data generation
3
3
  *
4
4
  * Generates JSON-LD for entity pages to enable machine-readable data.
5
- * Aligns with the RDF schema at https://schema.forwardimpact.team/rdf/
5
+ * Aligns with the RDF schema at https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
- const VOCAB_BASE = "https://schema.forwardimpact.team/rdf/";
8
+ const VOCAB_BASE = "https://www.forwardimpact.team/schema/rdf/";
9
9
 
10
10
  /**
11
11
  * Create a JSON-LD script element
@@ -2,10 +2,10 @@
2
2
  * Shared microdata HTML utilities
3
3
  *
4
4
  * Helper functions for generating clean, class-less HTML with microdata attributes
5
- * aligned with the RDF schema at https://schema.forwardimpact.team/rdf/
5
+ * aligned with the RDF schema at https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
- const VOCAB_BASE = "https://schema.forwardimpact.team/rdf/";
8
+ const VOCAB_BASE = "https://www.forwardimpact.team/schema/rdf/";
9
9
 
10
10
  /**
11
11
  * Create an opening tag with microdata attributes
@@ -2,7 +2,7 @@
2
2
  * Skill formatting for microdata HTML output
3
3
  *
4
4
  * Generates clean, class-less HTML with microdata aligned with capability.schema.json
5
- * RDF vocab: https://schema.forwardimpact.team/rdf/
5
+ * RDF vocab: https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
8
  import {
@@ -2,7 +2,7 @@
2
2
  * Stage formatting for microdata HTML output
3
3
  *
4
4
  * Generates clean, class-less HTML with microdata aligned with stages.schema.json
5
- * RDF vocab: https://schema.forwardimpact.team/rdf/
5
+ * RDF vocab: https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
8
  import {
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Toolkit formatting for DOM/web output
3
+ *
4
+ * Displays a compact toolkit table showing tools with icons and descriptions.
5
+ */
6
+
7
+ import {
8
+ div,
9
+ table,
10
+ thead,
11
+ tbody,
12
+ tr,
13
+ th,
14
+ td,
15
+ a,
16
+ span,
17
+ } from "../../lib/render.js";
18
+ import { createToolIcon } from "../../lib/card-mappers.js";
19
+
20
+ /**
21
+ * Create a toolkit table for display in job/agent detail pages
22
+ * @param {Array<{name: string, description: string, url?: string, simpleIcon?: string, skillIds: string[]}>} toolkit - Derived toolkit entries
23
+ * @returns {HTMLElement}
24
+ */
25
+ export function createToolkitTable(toolkit) {
26
+ if (!toolkit || toolkit.length === 0) {
27
+ return div({ className: "empty-state" }, "No tools in toolkit");
28
+ }
29
+
30
+ const rows = toolkit.map((tool) => {
31
+ const iconCell = tool.simpleIcon
32
+ ? td(
33
+ { className: "tool-icon-cell" },
34
+ createToolIcon(tool.simpleIcon, tool.name),
35
+ )
36
+ : td({ className: "tool-icon-cell" });
37
+
38
+ const nameContent = tool.url
39
+ ? a(
40
+ {
41
+ href: tool.url,
42
+ target: "_blank",
43
+ rel: "noopener noreferrer",
44
+ className: "tool-link",
45
+ },
46
+ tool.name,
47
+ span({ className: "external-icon" }, " ↗"),
48
+ )
49
+ : span({}, tool.name);
50
+
51
+ return tr(
52
+ {},
53
+ iconCell,
54
+ td({ className: "tool-name-cell" }, nameContent),
55
+ td({ className: "tool-description-cell" }, tool.description),
56
+ );
57
+ });
58
+
59
+ return div(
60
+ { className: "table-container" },
61
+ table(
62
+ { className: "table toolkit-table" },
63
+ thead(
64
+ {},
65
+ tr(
66
+ {},
67
+ th({ style: "width: 40px" }, ""),
68
+ th({}, "Tool"),
69
+ th({}, "Description"),
70
+ ),
71
+ ),
72
+ tbody({}, ...rows),
73
+ ),
74
+ );
75
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Toolkit formatting for markdown/CLI output
3
+ *
4
+ * Displays toolkit as a markdown table with tools, icons, and descriptions.
5
+ */
6
+
7
+ import { tableToMarkdown } from "../shared.js";
8
+
9
+ /**
10
+ * Format toolkit as markdown table
11
+ * @param {Array<{name: string, description: string, url?: string, simpleIcon?: string, skillIds: string[]}>} toolkit - Derived toolkit entries
12
+ * @returns {string}
13
+ */
14
+ export function toolkitToMarkdown(toolkit) {
15
+ if (!toolkit || toolkit.length === 0) {
16
+ return "";
17
+ }
18
+
19
+ const rows = toolkit.map((tool) => {
20
+ const name = tool.url ? `[${tool.name}](${tool.url})` : tool.name;
21
+ return [name, tool.description];
22
+ });
23
+
24
+ return tableToMarkdown(["Tool", "Description"], rows);
25
+ }
26
+
27
+ /**
28
+ * Format toolkit as a plain list of tool names (for --tools flag)
29
+ * @param {Array<{name: string}>} toolkit - Derived toolkit entries
30
+ * @returns {string}
31
+ */
32
+ export function toolkitToPlainList(toolkit) {
33
+ if (!toolkit || toolkit.length === 0) {
34
+ return "";
35
+ }
36
+
37
+ return toolkit.map((tool) => tool.name).join("\n");
38
+ }
@@ -2,7 +2,7 @@
2
2
  * Track formatting for microdata HTML output
3
3
  *
4
4
  * Generates clean, class-less HTML with microdata aligned with track.schema.json
5
- * RDF vocab: https://schema.forwardimpact.team/rdf/
5
+ * RDF vocab: https://www.forwardimpact.team/schema/rdf/
6
6
  */
7
7
 
8
8
  import {
@@ -27,7 +27,9 @@ import {
27
27
  generateSkillMd,
28
28
  deriveAgentSkills,
29
29
  deriveReferenceGrade,
30
- } from "@forwardimpact/model/agent";
30
+ deriveToolkit,
31
+ buildAgentIndex,
32
+ } from "@forwardimpact/model";
31
33
  import {
32
34
  createSelectWithValue,
33
35
  createDisciplineSelect,
@@ -37,6 +39,8 @@ import { getStageEmoji } from "../formatters/stage/shared.js";
37
39
  import { formatAgentProfile } from "../formatters/agent/profile.js";
38
40
  import { formatAgentSkill } from "../formatters/agent/skill.js";
39
41
  import { createCodeDisplay } from "../components/code-display.js";
42
+ import { createToolkitTable } from "../formatters/toolkit/dom.js";
43
+ import { createDetailSection } from "../components/detail.js";
40
44
 
41
45
  /** All stages option value */
42
46
  const ALL_STAGES_VALUE = "all";
@@ -247,6 +251,15 @@ export async function renderAgentBuilder() {
247
251
  // Get reference grade for derivation
248
252
  const grade = deriveReferenceGrade(data.grades);
249
253
 
254
+ // Build agent index for all valid combinations
255
+ const agentIndex = buildAgentIndex({
256
+ disciplines: data.disciplines,
257
+ tracks: data.tracks,
258
+ stages,
259
+ agentDisciplines: agentData.disciplines,
260
+ agentTracks: agentData.tracks,
261
+ });
262
+
250
263
  // Build context for generation
251
264
  const context = {
252
265
  humanDiscipline,
@@ -262,6 +275,7 @@ export async function renderAgentBuilder() {
262
275
  vscodeSettings: agentData.vscodeSettings,
263
276
  devcontainer: agentData.devcontainer,
264
277
  templates,
278
+ agentIndex,
265
279
  };
266
280
 
267
281
  // Generate preview based on stage selection
@@ -439,6 +453,7 @@ function createAllStagesPreview(context) {
439
453
  vscodeSettings,
440
454
  devcontainer,
441
455
  templates,
456
+ agentIndex,
442
457
  } = context;
443
458
 
444
459
  // Generate all stage agents
@@ -469,6 +484,7 @@ function createAllStagesPreview(context) {
469
484
  agentTrack,
470
485
  capabilities,
471
486
  stages,
487
+ agentIndex,
472
488
  });
473
489
 
474
490
  return { stage, derived, profile };
@@ -488,6 +504,12 @@ function createAllStagesPreview(context) {
488
504
  .filter((skill) => skill?.agent)
489
505
  .map((skill) => generateSkillMd(skill, stages));
490
506
 
507
+ // Derive toolkit from agent skills
508
+ const toolkit = deriveToolkit({
509
+ skillMatrix: derivedSkills,
510
+ skills,
511
+ });
512
+
491
513
  return div(
492
514
  { className: "agent-deployment" },
493
515
 
@@ -533,6 +555,14 @@ function createAllStagesPreview(context) {
533
555
  ),
534
556
  ),
535
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
+
536
566
  // CLI hint
537
567
  createCliHint(humanDiscipline.id, humanTrack.id),
538
568
  );
@@ -559,6 +589,7 @@ function createSingleStagePreview(context, stage) {
559
589
  devcontainer,
560
590
  stages,
561
591
  templates,
592
+ agentIndex,
562
593
  } = context;
563
594
 
564
595
  // Derive stage agent
@@ -588,6 +619,7 @@ function createSingleStagePreview(context, stage) {
588
619
  agentTrack,
589
620
  capabilities,
590
621
  stages,
622
+ agentIndex,
591
623
  });
592
624
 
593
625
  // Get skills for this stage (using full derived skills)
@@ -603,6 +635,12 @@ function createSingleStagePreview(context, stage) {
603
635
  .filter((skill) => skill?.agent)
604
636
  .map((skill) => generateSkillMd(skill, stages));
605
637
 
638
+ // Derive toolkit from agent skills
639
+ const toolkit = deriveToolkit({
640
+ skillMatrix: derivedSkills,
641
+ skills,
642
+ });
643
+
606
644
  return div(
607
645
  { className: "agent-deployment" },
608
646
 
@@ -642,6 +680,14 @@ function createSingleStagePreview(context, stage) {
642
680
  ),
643
681
  ),
644
682
 
683
+ // Tool Kit section
684
+ toolkit.length > 0
685
+ ? createDetailSection({
686
+ title: `Tool Kit (${toolkit.length})`,
687
+ content: createToolkitTable(toolkit),
688
+ })
689
+ : null,
690
+
645
691
  // CLI hint
646
692
  createCliHint(humanDiscipline.id, humanTrack.id, stage.id),
647
693
  );