@forwardimpact/pathway 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/pathway",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Career progression web app and CLI for exploring roles and generating agents",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@forwardimpact/schema": "^0.4.0",
44
- "@forwardimpact/model": "^0.4.0",
44
+ "@forwardimpact/model": "^0.5.0",
45
45
  "mustache": "^4.2.0",
46
46
  "simple-icons": "^16.7.0",
47
47
  "yaml": "^2.3.4"
@@ -11,6 +11,10 @@
11
11
  import Mustache from "mustache";
12
12
 
13
13
  import { trimValue, trimRequired, trimFields } from "../shared.js";
14
+ import {
15
+ flattenToLine,
16
+ preprocessArrayFrontmatter,
17
+ } from "../template-preprocess.js";
14
18
 
15
19
  /**
16
20
  * @typedef {Object} WorkingStyleEntry
@@ -18,6 +22,13 @@ import { trimValue, trimRequired, trimFields } from "../shared.js";
18
22
  * @property {string} content - Working style content (markdown)
19
23
  */
20
24
 
25
+ /**
26
+ * @typedef {Object} BeforeHandoffEntry
27
+ * @property {{id: string, name: string}} skill - Skill info
28
+ * @property {{id: string, name: string, emojiIcon: string}} capability - Capability info
29
+ * @property {string[]} items - Checklist items
30
+ */
31
+
21
32
  /**
22
33
  * Prepare agent profile data for template rendering
23
34
  * Normalizes string values by trimming trailing newlines for consistent template output.
@@ -35,15 +46,21 @@ import { trimValue, trimRequired, trimFields } from "../shared.js";
35
46
  * @param {Array<{name: string, dirname: string, useWhen: string}>} params.bodyData.skillIndex - Skill index entries
36
47
  * @param {string} params.bodyData.roleContext - Role context text
37
48
  * @param {WorkingStyleEntry[]} params.bodyData.workingStyles - Working style entries
38
- * @param {string} [params.bodyData.beforeHandoff] - Before handoff checklist markdown
49
+ * @param {BeforeHandoffEntry[]} [params.bodyData.beforeHandoff] - Before handoff checklist entries
39
50
  * @param {string[]} params.bodyData.constraints - List of constraints
40
51
  * @param {Array<{id: string, name: string, description: string}>} [params.bodyData.agentIndex] - List of all available agents
41
52
  * @param {boolean} [params.bodyData.hasAgentIndex] - Whether agent index is available
42
53
  * @returns {Object} Data object ready for Mustache template
43
54
  */
44
55
  function prepareAgentProfileData({ frontmatter, bodyData }) {
45
- // Trim array fields using shared helpers
46
- const handoffs = trimFields(frontmatter.handoffs, { prompt: "required" });
56
+ // Preprocess handoffs - flatten prompt field for front matter compatibility
57
+ const preprocessedHandoffs = preprocessArrayFrontmatter(
58
+ frontmatter.handoffs,
59
+ ["prompt"],
60
+ );
61
+ // Then trim as before
62
+ const handoffs = trimFields(preprocessedHandoffs, { prompt: "required" });
63
+
47
64
  const constraints = (bodyData.constraints || []).map((c) => trimRequired(c));
48
65
  const skillIndex = trimFields(bodyData.skillIndex, {
49
66
  name: "required",
@@ -60,10 +77,17 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
60
77
  content: "required",
61
78
  });
62
79
 
80
+ // Process beforeHandoff: trim items in each entry
81
+ const beforeHandoff = (bodyData.beforeHandoff || []).map((entry) => ({
82
+ skill: entry.skill,
83
+ capability: entry.capability,
84
+ items: (entry.items || []).map((item) => trimRequired(item)),
85
+ }));
86
+
63
87
  return {
64
- // Frontmatter
88
+ // Frontmatter - flatten description for single-line front matter
65
89
  name: frontmatter.name,
66
- description: trimRequired(frontmatter.description),
90
+ description: flattenToLine(frontmatter.description),
67
91
  infer: frontmatter.infer,
68
92
  handoffs,
69
93
 
@@ -77,7 +101,8 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
77
101
  roleContext: trimValue(bodyData.roleContext),
78
102
  workingStyles,
79
103
  hasWorkingStyles: workingStyles.length > 0,
80
- beforeHandoff: trimValue(bodyData.beforeHandoff),
104
+ beforeHandoff,
105
+ hasBeforeHandoff: beforeHandoff.length > 0,
81
106
  constraints,
82
107
  hasConstraints: constraints.length > 0,
83
108
  agentIndex,
@@ -102,7 +127,7 @@ function prepareAgentProfileData({ frontmatter, bodyData }) {
102
127
  * @param {Array<{name: string, dirname: string, useWhen: string}>} profile.bodyData.skillIndex - Skill index entries
103
128
  * @param {string} profile.bodyData.roleContext - Role context text
104
129
  * @param {WorkingStyleEntry[]} profile.bodyData.workingStyles - Working style entries
105
- * @param {string} [profile.bodyData.beforeHandoff] - Before handoff checklist markdown (optional)
130
+ * @param {BeforeHandoffEntry[]} [profile.bodyData.beforeHandoff] - Before handoff checklist entries (optional)
106
131
  * @param {string[]} profile.bodyData.constraints - List of constraints
107
132
  * @param {string} template - Mustache template string
108
133
  * @returns {string} Complete .agent.md file content
@@ -11,6 +11,7 @@
11
11
  import Mustache from "mustache";
12
12
 
13
13
  import { trimValue, splitLines, trimFields } from "../shared.js";
14
+ import { flattenToLine } from "../template-preprocess.js";
14
15
 
15
16
  /**
16
17
  * Prepare agent skill data for template rendering
@@ -40,17 +41,25 @@ function prepareAgentSkillData({
40
41
  ready: "array",
41
42
  });
42
43
 
44
+ // Flatten multi-line strings to single line for front matter compatibility
45
+ const description = flattenToLine(frontmatter.description);
46
+ const useWhen = flattenToLine(frontmatter.useWhen);
47
+
48
+ // Keep line arrays for body rendering
43
49
  const descriptionLines = splitLines(frontmatter.description);
44
- const useWhenLines = splitLines(frontmatter.useWhen);
50
+
45
51
  const trimmedReference = trimValue(reference) || "";
46
52
  const tools = toolReferences || [];
47
53
 
48
54
  return {
49
55
  name: frontmatter.name,
56
+ // Single-line versions for front matter
57
+ description,
58
+ hasDescription: !!description,
59
+ useWhen,
60
+ hasUseWhen: !!useWhen,
61
+ // Line arrays for body content
50
62
  descriptionLines,
51
- hasDescription: descriptionLines.length > 0,
52
- useWhenLines,
53
- hasUseWhen: useWhenLines.length > 0,
54
63
  title,
55
64
  stages: processedStages,
56
65
  hasStages: processedStages.length > 0,
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Template Preprocessing Utilities
3
+ *
4
+ * Functions to prepare template data for front matter compatibility.
5
+ * Ensures all front matter values are single-line strings for maximum
6
+ * compatibility with coding agents that have limited YAML parsing.
7
+ */
8
+
9
+ /**
10
+ * Flatten a multi-line string into a single line
11
+ * Replaces newlines with spaces and collapses multiple spaces.
12
+ * @param {string|null|undefined} value - Value to flatten
13
+ * @returns {string} Single-line string (empty string if no value)
14
+ */
15
+ export function flattenToLine(value) {
16
+ if (value == null) return "";
17
+ return value
18
+ .replace(/\s*\n\s*/g, " ") // Replace newlines (with surrounding whitespace) with single space
19
+ .replace(/\s+/g, " ") // Collapse multiple spaces
20
+ .trim();
21
+ }
22
+
23
+ /**
24
+ * Join an array of strings into a single line
25
+ * @param {string[]|null|undefined} lines - Array of lines to join
26
+ * @param {string} separator - Separator between lines (default: single space)
27
+ * @returns {string} Single-line string
28
+ */
29
+ export function joinLines(lines, separator = " ") {
30
+ if (!lines || !Array.isArray(lines)) return "";
31
+ return lines.map((line) => line.trim()).join(separator);
32
+ }
33
+
34
+ /**
35
+ * Preprocess an object's string fields for front matter
36
+ * Flattens specified fields to single-line strings.
37
+ * @param {Object} obj - Object to preprocess
38
+ * @param {string[]} fields - Field names to flatten
39
+ * @returns {Object} New object with flattened fields
40
+ */
41
+ export function preprocessFrontmatter(obj, fields) {
42
+ const result = { ...obj };
43
+ for (const field of fields) {
44
+ if (result[field] != null) {
45
+ result[field] = flattenToLine(result[field]);
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+
51
+ /**
52
+ * Preprocess an array of objects, flattening specified fields in each
53
+ * @param {Array<Object>|null|undefined} array - Array to preprocess
54
+ * @param {string[]} fields - Field names to flatten in each object
55
+ * @returns {Array<Object>} Array with preprocessed objects
56
+ */
57
+ export function preprocessArrayFrontmatter(array, fields) {
58
+ if (!array) return [];
59
+ return array.map((item) => preprocessFrontmatter(item, fields));
60
+ }
@@ -88,17 +88,23 @@ and (3) the compromised approach with acknowledged limitations.
88
88
  | `{{id}}` | {{{name}}} | {{{description}}} |
89
89
  {{/agentIndex}}
90
90
  {{/hasAgentIndex}}
91
- {{#beforeHandoff}}
91
+ {{#hasBeforeHandoff}}
92
92
 
93
93
  ## Before Handoff
94
94
 
95
95
  Before offering a handoff, verify and summarize completion of these items:
96
96
 
97
- {{{beforeHandoff}}}
97
+ {{#beforeHandoff}}
98
+ ### {{{capability.emojiIcon}}} {{{skill.name}}}
99
+
100
+ {{#items}}
101
+ - [ ] {{{.}}}
102
+ {{/items}}
98
103
 
104
+ {{/beforeHandoff}}
99
105
  When verified, summarize what was accomplished then offer the handoff. If items
100
106
  are incomplete, explain what remains.
101
- {{/beforeHandoff}}
107
+ {{/hasBeforeHandoff}}
102
108
 
103
109
  ## Return Format
104
110
 
@@ -1,20 +1,13 @@
1
1
  ---
2
2
  name: {{name}}
3
- description: |
4
- {{#descriptionLines}}
5
- {{{.}}}
6
- {{/descriptionLines}}
7
- {{#hasUseWhen}}
8
- **Use When:** {{#useWhenLines}}{{{.}}}{{/useWhenLines}}
9
- {{/hasUseWhen}}
3
+ description: {{{description}}}{{#hasUseWhen}} Use When: {{{useWhen}}}{{/hasUseWhen}}
10
4
  ---
11
5
 
12
6
  # {{{title}}}
13
- {{#hasUseWhen}}
14
7
 
15
- **Use This Skill When:**
16
- {{#useWhenLines}}{{{.}}}{{/useWhenLines}}
17
- {{/hasUseWhen}}
8
+ {{#descriptionLines}}
9
+ {{{.}}}
10
+ {{/descriptionLines}}
18
11
  {{#hasStages}}
19
12
 
20
13
  ## Stage Guidance