@forwardimpact/pathway 0.4.0 → 0.5.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.
Files changed (69) hide show
  1. package/app/commands/behaviour.js +1 -1
  2. package/app/commands/command-factory.js +2 -2
  3. package/app/commands/discipline.js +1 -1
  4. package/app/commands/driver.js +1 -1
  5. package/app/commands/grade.js +1 -1
  6. package/app/commands/serve.js +2 -2
  7. package/app/commands/site.js +22 -2
  8. package/app/commands/skill.js +1 -1
  9. package/app/commands/stage.js +1 -1
  10. package/app/commands/track.js +1 -1
  11. package/app/components/card.js +11 -1
  12. package/app/components/code-display.js +153 -0
  13. package/app/components/markdown-textarea.js +68 -47
  14. package/app/css/bundles/app.css +14 -0
  15. package/app/css/components/badges.css +15 -8
  16. package/app/css/components/forms.css +23 -13
  17. package/app/css/components/surfaces.css +49 -3
  18. package/app/css/components/typography.css +1 -2
  19. package/app/css/pages/agent-builder.css +11 -102
  20. package/app/css/pages/detail.css +11 -1
  21. package/app/css/tokens.css +3 -0
  22. package/app/formatters/agent/dom.js +26 -71
  23. package/app/formatters/agent/profile.js +11 -6
  24. package/app/formatters/grade/dom.js +6 -6
  25. package/app/formatters/job/dom.js +3 -3
  26. package/app/formatters/json-ld.js +1 -1
  27. package/app/formatters/skill/dom.js +68 -56
  28. package/app/formatters/skill/shared.js +3 -1
  29. package/app/formatters/stage/microdata.js +2 -2
  30. package/app/formatters/stage/shared.js +3 -3
  31. package/app/formatters/tool/shared.js +6 -0
  32. package/app/handout-main.js +12 -11
  33. package/app/index.html +7 -1
  34. package/app/lib/card-mappers.js +27 -0
  35. package/app/model/agent.js +21 -5
  36. package/app/model/checklist.js +2 -2
  37. package/app/model/derivation.js +2 -2
  38. package/app/model/levels.js +2 -2
  39. package/app/model/validation.js +3 -3
  40. package/app/pages/agent-builder.js +119 -75
  41. package/app/pages/landing.js +1 -1
  42. package/app/pages/stage.js +4 -4
  43. package/app/slide-main.js +1 -1
  44. package/app/slides/chapter.js +8 -8
  45. package/app/slides/index.js +1 -1
  46. package/app/slides/overview.js +8 -8
  47. package/app/slides/skill.js +1 -0
  48. package/examples/capabilities/business.yaml +1 -1
  49. package/examples/capabilities/delivery.yaml +3 -1
  50. package/examples/capabilities/people.yaml +1 -1
  51. package/examples/capabilities/reliability.yaml +3 -1
  52. package/examples/capabilities/scale.yaml +1 -1
  53. package/examples/framework.yaml +11 -11
  54. package/examples/stages.yaml +18 -10
  55. package/package.json +2 -1
  56. package/templates/agent.template.md +47 -17
  57. package/templates/job.template.md +8 -8
  58. package/templates/skill.template.md +12 -9
  59. package/examples/agents/.claude/skills/architecture-design/SKILL.md +0 -130
  60. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +0 -131
  61. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +0 -108
  62. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +0 -142
  63. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +0 -134
  64. package/examples/agents/.claude/skills/sre-practices/SKILL.md +0 -163
  65. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +0 -164
  66. package/examples/agents/.github/agents/se-platform-code.agent.md +0 -132
  67. package/examples/agents/.github/agents/se-platform-plan.agent.md +0 -131
  68. package/examples/agents/.github/agents/se-platform-review.agent.md +0 -136
  69. package/examples/agents/.vscode/settings.json +0 -8
@@ -40,6 +40,30 @@
40
40
  font-size: var(--font-size-lg);
41
41
  }
42
42
 
43
+ .card-title-with-icon {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: var(--space-sm);
47
+ }
48
+
49
+ .card-title-with-icon .card-title {
50
+ margin: 0;
51
+ }
52
+
53
+ /* Tool icons */
54
+ .tool-icon {
55
+ width: 28px;
56
+ height: 28px;
57
+ flex-shrink: 0;
58
+ }
59
+
60
+ /* Tool name cell with icon */
61
+ .tool-name-cell {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: var(--space-sm);
65
+ }
66
+
43
67
  .card-description {
44
68
  color: var(--color-text-muted);
45
69
  margin: 0;
@@ -122,7 +146,7 @@
122
146
  .section-title {
123
147
  font-size: var(--font-size-xl);
124
148
  font-weight: 600;
125
- margin: 0 0 var(--space-sm);
149
+ margin: 0 0 var(--space-md);
126
150
  color: var(--color-text);
127
151
  }
128
152
 
@@ -209,11 +233,33 @@
209
233
  font-weight: 500;
210
234
  }
211
235
 
236
+ /*
237
+ * List items: 60px row height = 16px padding-top + 28px content + 16px padding-bottom
238
+ * Uses bottom-border only for 1px gaps between items (like table rows)
239
+ * Left/right borders and rounded corners on first/last items match table styling
240
+ */
212
241
  .list-item {
213
- padding: var(--space-sm);
242
+ display: flex;
243
+ align-items: center;
244
+ gap: var(--space-sm);
245
+ min-height: 60px;
246
+ padding: var(--space-md) var(--space-lg);
214
247
  background: var(--color-surface);
215
- border-radius: var(--radius-md);
216
248
  border: 1px solid var(--color-border);
249
+ border-top: none;
250
+ }
251
+
252
+ /* First list-item in a sequence (handles heading before list items) */
253
+ .list-item:first-child,
254
+ :not(.list-item) + .list-item {
255
+ border-top: 1px solid var(--color-border);
256
+ border-top-left-radius: var(--radius-lg);
257
+ border-top-right-radius: var(--radius-lg);
258
+ }
259
+
260
+ .list-item:last-child {
261
+ border-bottom-left-radius: var(--radius-lg);
262
+ border-bottom-right-radius: var(--radius-lg);
217
263
  }
218
264
 
219
265
  /* Accent borders */
@@ -111,8 +111,7 @@
111
111
 
112
112
  /* Code inline */
113
113
  .code-inline {
114
- font-family:
115
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
114
+ font-family: var(--font-family-mono);
116
115
  font-size: 0.9em;
117
116
  background: var(--color-bg);
118
117
  padding: 0.1em 0.4em;
@@ -51,32 +51,12 @@
51
51
  }
52
52
 
53
53
  .agent-section .filename {
54
- font-family:
55
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
56
- font-size: var(--font-size-sm);
54
+ font-family: var(--font-family-mono);
55
+ font-size: var(--font-size-xs);
57
56
  color: var(--color-text-muted);
58
57
  margin-bottom: var(--space-sm);
59
58
  }
60
59
 
61
- .agent-section .code-block {
62
- background: var(--color-bg);
63
- border: 1px solid var(--color-border);
64
- border-radius: var(--radius-md);
65
- padding: var(--space-md);
66
- overflow-x: auto;
67
- font-family:
68
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
69
- font-size: var(--font-size-sm);
70
- line-height: 1.5;
71
- white-space: pre-wrap;
72
- word-break: break-word;
73
- }
74
-
75
- .agent-section .code-block code {
76
- font-family: inherit;
77
- font-size: inherit;
78
- }
79
-
80
60
  /* Agent cards grid */
81
61
  .agent-cards-grid {
82
62
  display: grid;
@@ -122,24 +102,16 @@
122
102
  font-size: 1.5rem;
123
103
  }
124
104
 
125
- .agent-card-filename {
126
- font-family:
127
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
128
- font-size: var(--font-size-sm);
129
- color: var(--color-text-muted);
130
- margin: 0;
131
- padding: 0 var(--space-md);
132
- padding-top: var(--space-sm);
133
- }
134
-
135
105
  .agent-card-preview {
136
106
  padding: var(--space-md);
137
107
  }
138
108
 
139
- .agent-card-preview .code-preview {
109
+ .agent-card-preview .code-display-container {
140
110
  margin: 0;
111
+ }
112
+
113
+ .agent-card-preview .code-display {
141
114
  max-height: 400px;
142
- overflow-y: auto;
143
115
  }
144
116
 
145
117
  /* Skill cards grid */
@@ -170,38 +142,16 @@
170
142
  font-size: var(--font-size-sm);
171
143
  }
172
144
 
173
- .skill-card-filename {
174
- font-family:
175
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
176
- font-size: var(--font-size-xs);
177
- color: var(--color-text-muted);
178
- margin: 0;
179
- padding: var(--space-xs) var(--space-md);
180
- }
181
-
182
145
  .skill-card-preview {
183
146
  padding: var(--space-sm) var(--space-md) var(--space-md);
184
147
  }
185
148
 
186
- .skill-card-preview .code-preview {
149
+ .skill-card-preview .code-display-container {
187
150
  margin: 0;
188
- max-height: 300px;
189
- overflow-y: auto;
190
151
  }
191
152
 
192
- /* Code preview */
193
- .code-preview {
194
- background: var(--color-bg);
195
- border: 1px solid var(--color-border);
196
- border-radius: var(--radius-md);
197
- padding: var(--space-sm);
198
- font-family:
199
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
200
- font-size: var(--font-size-xs);
201
- line-height: 1.4;
202
- white-space: pre-wrap;
203
- word-break: break-word;
204
- color: var(--color-text-muted);
153
+ .skill-card-preview .code-display {
154
+ max-height: 300px;
205
155
  }
206
156
 
207
157
  .skills-list {
@@ -210,30 +160,6 @@
210
160
  gap: var(--space-md);
211
161
  }
212
162
 
213
- .skill-header {
214
- display: flex;
215
- justify-content: space-between;
216
- align-items: center;
217
- padding: var(--space-sm) var(--space-md);
218
- background: var(--color-bg);
219
- border-bottom: 1px solid var(--color-border);
220
- }
221
-
222
- .skill-filename {
223
- font-family:
224
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
225
- font-size: var(--font-size-sm);
226
- color: var(--color-text-muted);
227
- }
228
-
229
- .skill-card .code-block {
230
- margin: 0;
231
- border: none;
232
- border-radius: 0;
233
- max-height: 300px;
234
- overflow-y: auto;
235
- }
236
-
237
163
  /* CLI hint */
238
164
  .cli-hint {
239
165
  background: var(--color-bg);
@@ -243,17 +169,6 @@
243
169
  margin-bottom: var(--space-sm);
244
170
  }
245
171
 
246
- .cli-command {
247
- display: flex;
248
- align-items: flex-start;
249
- gap: var(--space-md);
250
- }
251
-
252
- .cli-command .code-block {
253
- flex: 1;
254
- margin: 0;
255
- }
256
-
257
172
  /* Role agents list */
258
173
  .role-agents-list {
259
174
  display: flex;
@@ -295,9 +210,8 @@
295
210
  }
296
211
 
297
212
  .role-filename {
298
- font-family:
299
- ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
300
- font-size: var(--font-size-sm);
213
+ font-family: var(--font-family-mono);
214
+ font-size: var(--font-size-xs);
301
215
  color: var(--color-text-muted);
302
216
  }
303
217
 
@@ -315,11 +229,6 @@
315
229
  margin-bottom: var(--space-sm);
316
230
  }
317
231
 
318
- .role-agent-content .code-block {
319
- max-height: 400px;
320
- overflow-y: auto;
321
- }
322
-
323
232
  /* Stage agent preview */
324
233
  .stage-agent-preview .stage-header {
325
234
  display: flex;
@@ -69,11 +69,16 @@
69
69
  background: var(--color-surface);
70
70
  }
71
71
 
72
+ /*
73
+ * Table cells: 60px row height = 16px padding-top + 28px content + 16px padding-bottom
74
+ * Matches list-item height for visual consistency
75
+ */
72
76
  .tools-table th,
73
77
  .tools-table td {
74
78
  padding: var(--space-md) var(--space-lg);
75
79
  text-align: left;
76
80
  border-bottom: 1px solid var(--color-border);
81
+ height: 60px;
77
82
  }
78
83
 
79
84
  .tools-table th {
@@ -86,7 +91,12 @@
86
91
  }
87
92
 
88
93
  .tools-table th:first-child {
89
- width: 200px;
94
+ width: 260px;
95
+ white-space: nowrap;
96
+ }
97
+
98
+ .tools-table td:first-child {
99
+ white-space: nowrap;
90
100
  }
91
101
 
92
102
  .tools-table tbody tr:last-child td {
@@ -130,6 +130,9 @@
130
130
  --font-family:
131
131
  system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
132
132
  sans-serif;
133
+ --font-family-mono:
134
+ "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
135
+ Consolas, monospace;
133
136
  --font-size-xs: 0.75rem;
134
137
  --font-size-sm: 0.875rem;
135
138
  --font-size-base: 1rem;
@@ -16,6 +16,7 @@ import {
16
16
  details,
17
17
  summary,
18
18
  } from "../../lib/render.js";
19
+ import { createCodeDisplay } from "../../components/code-display.js";
19
20
  import { formatAgentProfile } from "./profile.js";
20
21
  import { formatAgentSkill } from "./skill.js";
21
22
  import { getStageEmoji } from "../stage/shared.js";
@@ -53,13 +54,12 @@ export function agentDeploymentToDOM({
53
54
  // Profile section
54
55
  section(
55
56
  { className: "agent-section" },
56
- div(
57
- { className: "section-header" },
58
- h2({}, "Agent Profile"),
59
- createCopyButton(profileContent),
60
- ),
61
- p({ className: "filename" }, profile.filename),
62
- createCodeBlock(profileContent),
57
+ h2({}, "Agent Profile"),
58
+ createCodeDisplay({
59
+ content: profileContent,
60
+ filename: profile.filename,
61
+ maxHeight: 600,
62
+ }),
63
63
  ),
64
64
 
65
65
  // Role Agents section
@@ -145,48 +145,6 @@ function createDownloadButton(
145
145
  return btn;
146
146
  }
147
147
 
148
- /**
149
- * Create a copy button for content
150
- * @param {string} content - Content to copy
151
- * @returns {HTMLElement}
152
- */
153
- function createCopyButton(content) {
154
- const btn = button({ className: "btn btn-sm copy-btn" }, "📋 Copy");
155
-
156
- btn.addEventListener("click", async () => {
157
- try {
158
- await navigator.clipboard.writeText(content);
159
- btn.textContent = "✓ Copied";
160
- setTimeout(() => {
161
- btn.textContent = "📋 Copy";
162
- }, 2000);
163
- } catch {
164
- btn.textContent = "Failed";
165
- setTimeout(() => {
166
- btn.textContent = "📋 Copy";
167
- }, 2000);
168
- }
169
- });
170
-
171
- return btn;
172
- }
173
-
174
- /**
175
- * Create a code block with content
176
- * @param {string} content - Code content
177
- * @returns {HTMLElement}
178
- */
179
- function createCodeBlock(content) {
180
- const pre = document.createElement("pre");
181
- pre.className = "code-block";
182
-
183
- const code = document.createElement("code");
184
- code.textContent = content;
185
-
186
- pre.appendChild(code);
187
- return pre;
188
- }
189
-
190
148
  /**
191
149
  * Create a skill card with content and copy button
192
150
  * @param {Object} skill - Skill with frontmatter and body
@@ -198,12 +156,11 @@ function createSkillCard(skill) {
198
156
 
199
157
  return div(
200
158
  { className: "skill-card" },
201
- div(
202
- { className: "skill-header" },
203
- span({ className: "skill-filename" }, filename),
204
- createCopyButton(content),
205
- ),
206
- createCodeBlock(content),
159
+ createCodeDisplay({
160
+ content,
161
+ filename,
162
+ maxHeight: 300,
163
+ }),
207
164
  );
208
165
  }
209
166
 
@@ -235,8 +192,10 @@ function createRoleAgentCard(agent) {
235
192
  { className: "text-muted role-description" },
236
193
  agent.frontmatter.description,
237
194
  ),
238
- div({ className: "role-agent-actions" }, createCopyButton(content)),
239
- createCodeBlock(content),
195
+ createCodeDisplay({
196
+ content,
197
+ maxHeight: 400,
198
+ }),
240
199
  ),
241
200
  );
242
201
  }
@@ -254,13 +213,10 @@ function createCliCommand(agentName) {
254
213
 
255
214
  const command = `npx pathway agent ${discipline} ${track} --output=.github --all-roles`;
256
215
 
257
- const container = div(
258
- { className: "cli-command" },
259
- createCodeBlock(command),
260
- createCopyButton(command),
261
- );
262
-
263
- return container;
216
+ return createCodeDisplay({
217
+ content: command,
218
+ language: "bash",
219
+ });
264
220
  }
265
221
 
266
222
  /**
@@ -445,13 +401,12 @@ export function stageAgentToDOM(stageAgent, profile, options = {}) {
445
401
  // Profile section
446
402
  section(
447
403
  { className: "agent-section" },
448
- div(
449
- { className: "section-header" },
450
- h3({}, "Agent Profile"),
451
- createCopyButton(profileContent),
452
- ),
453
- p({ className: "filename" }, profile.filename),
454
- createCodeBlock(profileContent),
404
+ h3({}, "Agent Profile"),
405
+ createCodeDisplay({
406
+ content: profileContent,
407
+ filename: profile.filename,
408
+ maxHeight: 600,
409
+ }),
455
410
  ),
456
411
 
457
412
  // Download button
@@ -26,7 +26,7 @@ import { trimValue, trimRequired, trimFields } from "../shared.js";
26
26
  * @param {string} params.bodyData.stageDescription - Stage description text
27
27
  * @param {string} params.bodyData.identity - Core identity text
28
28
  * @param {string} [params.bodyData.priority] - Priority/philosophy statement
29
- * @param {string[]} params.bodyData.capabilities - List of capability names
29
+ * @param {Array<{name: string, dirname: string, useWhen: string}>} params.bodyData.skillIndex - Skill index entries
30
30
  * @param {Array<{index: number, text: string}>} params.bodyData.beforeMakingChanges - Numbered steps
31
31
  * @param {string} [params.bodyData.delegation] - Delegation guidance
32
32
  * @param {string} params.bodyData.operationalContext - Operational context text
@@ -44,9 +44,13 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
44
44
 
45
45
  // Trim simple string arrays
46
46
  const constraints = (bodyData.constraints || []).map((c) => trimRequired(c));
47
- const capabilities = (bodyData.capabilities || []).map((c) =>
48
- trimRequired(c),
49
- );
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
+ }));
50
54
 
51
55
  return {
52
56
  // Frontmatter
@@ -60,7 +64,8 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
60
64
  stageDescription: trimValue(bodyData.stageDescription),
61
65
  identity: trimValue(bodyData.identity),
62
66
  priority: trimValue(bodyData.priority),
63
- capabilities,
67
+ skillIndex,
68
+ hasSkills: skillIndex.length > 0,
64
69
  beforeMakingChanges,
65
70
  delegation: trimValue(bodyData.delegation),
66
71
  operationalContext: trimValue(bodyData.operationalContext),
@@ -84,7 +89,7 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
84
89
  * @param {string} profile.bodyData.stageDescription - Stage description text
85
90
  * @param {string} profile.bodyData.identity - Core identity text
86
91
  * @param {string} [profile.bodyData.priority] - Priority/philosophy statement (optional)
87
- * @param {string[]} profile.bodyData.capabilities - List of capability names
92
+ * @param {Array<{name: string, dirname: string, useWhen: string}>} profile.bodyData.skillIndex - Skill index entries
88
93
  * @param {Array<{index: number, text: string}>} profile.bodyData.beforeMakingChanges - Numbered steps
89
94
  * @param {string} [profile.bodyData.delegation] - Delegation guidance (optional)
90
95
  * @param {string} profile.bodyData.operationalContext - Operational context text
@@ -77,16 +77,16 @@ export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
77
77
  { className: "content-columns" },
78
78
  view.professionalTitle
79
79
  ? div(
80
- { className: "list-item" },
80
+ { className: "card" },
81
81
  p({ className: "label" }, "Professional Track"),
82
- p({}, view.professionalTitle),
82
+ p({ className: "card-description" }, view.professionalTitle),
83
83
  )
84
84
  : null,
85
85
  view.managementTitle
86
86
  ? div(
87
- { className: "list-item" },
87
+ { className: "card" },
88
88
  p({ className: "label" }, "Management Track"),
89
- p({}, view.managementTitle),
89
+ p({ className: "card-description" }, view.managementTitle),
90
90
  )
91
91
  : null,
92
92
  ),
@@ -102,9 +102,9 @@ export function gradeToDOM(grade, { framework, showBackLink = true } = {}) {
102
102
  { className: "content-columns" },
103
103
  ...Object.entries(view.expectations).map(([key, value]) =>
104
104
  div(
105
- { className: "list-item" },
105
+ { className: "card" },
106
106
  p({ className: "label" }, formatLevel(key)),
107
- p({}, value),
107
+ p({ className: "card-description" }, value),
108
108
  ),
109
109
  ),
110
110
  ),
@@ -14,7 +14,7 @@ import {
14
14
  } from "../../components/radar-chart.js";
15
15
  import { createSkillMatrix } from "../../components/skill-matrix.js";
16
16
  import { createBehaviourProfile } from "../../components/behaviour-profile.js";
17
- import { createMarkdownTextarea } from "../../components/markdown-textarea.js";
17
+ import { createCodeDisplay } from "../../components/code-display.js";
18
18
  import { markdownToHtml } from "../../lib/markdown.js";
19
19
  import { formatJobDescription } from "./description.js";
20
20
 
@@ -226,8 +226,8 @@ export function createJobDescriptionSection({
226
226
 
227
227
  return createDetailSection({
228
228
  title: "Job Description",
229
- content: createMarkdownTextarea({
230
- markdown,
229
+ content: createCodeDisplay({
230
+ content: markdown,
231
231
  description:
232
232
  "Copy this markdown-formatted job description for use in job postings, documentation, or sharing.",
233
233
  toHtml: markdownToHtml,
@@ -228,7 +228,7 @@ export function stageToJsonLd(stage) {
228
228
  identifier: stage.id,
229
229
  name: stage.name,
230
230
  description: stage.description,
231
- ...(stage.emoji && { emoji: stage.emoji }),
231
+ ...(stage.emojiIcon && { emojiIcon: stage.emojiIcon }),
232
232
  ...(stage.tools?.length > 0 && { tools: stage.tools }),
233
233
  ...(stage.constraints?.length > 0 && { constraints: stage.constraints }),
234
234
  ...(stage.handoffs && {