@forwardimpact/pathway 0.12.0 → 0.14.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 (54) hide show
  1. package/bin/fit-pathway.js +32 -12
  2. package/package.json +3 -3
  3. package/src/commands/build.js +98 -2
  4. package/src/commands/index.js +1 -0
  5. package/src/commands/interview.js +52 -14
  6. package/src/commands/job.js +1 -0
  7. package/src/commands/questions.js +13 -10
  8. package/src/commands/stage.js +8 -8
  9. package/src/commands/update.js +133 -0
  10. package/src/components/command-prompt.js +85 -0
  11. package/src/components/nav.js +2 -2
  12. package/src/components/top-bar.js +97 -0
  13. package/src/css/bundles/app.css +2 -0
  14. package/src/css/components/badges.css +41 -11
  15. package/src/css/components/command-prompt.css +98 -0
  16. package/src/css/components/layout.css +0 -3
  17. package/src/css/components/nav.css +121 -81
  18. package/src/css/components/surfaces.css +1 -1
  19. package/src/css/components/top-bar.css +180 -0
  20. package/src/css/pages/agent-builder.css +0 -9
  21. package/src/css/pages/landing.css +4 -0
  22. package/src/css/pages/lifecycle.css +5 -2
  23. package/src/css/reset.css +1 -1
  24. package/src/css/tokens.css +25 -11
  25. package/src/css/views/slide-base.css +2 -1
  26. package/src/formatters/agent/dom.js +0 -26
  27. package/src/formatters/agent/profile.js +13 -7
  28. package/src/formatters/agent/skill.js +4 -4
  29. package/src/formatters/interview/markdown.js +62 -3
  30. package/src/formatters/interview/shared.js +89 -52
  31. package/src/formatters/questions/markdown.js +15 -0
  32. package/src/formatters/questions/shared.js +70 -58
  33. package/src/formatters/stage/dom.js +13 -10
  34. package/src/formatters/stage/microdata.js +14 -8
  35. package/src/formatters/stage/shared.js +4 -4
  36. package/src/index.html +69 -44
  37. package/src/lib/cli-command.js +145 -0
  38. package/src/lib/state.js +2 -0
  39. package/src/lib/yaml-loader.js +39 -21
  40. package/src/main.js +47 -26
  41. package/src/pages/agent-builder.js +0 -28
  42. package/src/pages/behaviour.js +3 -1
  43. package/src/pages/discipline.js +3 -1
  44. package/src/pages/driver.js +6 -1
  45. package/src/pages/grade.js +6 -1
  46. package/src/pages/interview.js +61 -5
  47. package/src/pages/job.js +1 -0
  48. package/src/pages/landing.js +7 -0
  49. package/src/pages/skill.js +9 -2
  50. package/src/pages/track.js +6 -1
  51. package/src/slides/job.js +1 -0
  52. package/templates/agent.template.md +17 -10
  53. package/templates/install.template.sh +33 -0
  54. package/templates/skill.template.md +15 -7
@@ -81,8 +81,8 @@ export function renderInterviewDetail(params) {
81
81
  return;
82
82
  }
83
83
 
84
- // State for current interview type (default to first: Screening)
85
- let currentType = "short";
84
+ // State for current interview type (default to first: Mission Fit)
85
+ let currentType = "mission";
86
86
 
87
87
  const page = div(
88
88
  { className: "interview-detail-page" },
@@ -194,23 +194,32 @@ function createInterviewSummary(interview) {
194
194
  { className: "interview-summary-header" },
195
195
  h2({}, `${typeInfo.icon} ${typeInfo.name}`),
196
196
  p({ className: "text-muted" }, typeInfo.description),
197
+ typeInfo.panel
198
+ ? p({ className: "text-muted" }, `Panel: ${typeInfo.panel}`)
199
+ : null,
197
200
  ),
198
201
  div(
199
202
  { className: "interview-summary-stats" },
200
203
  createBadge(`${interview.questions.length} questions`, "default"),
201
204
  createBadge(`~${interview.expectedDurationMinutes} minutes`, "secondary"),
202
- interview.coverage.skills.length > 0
205
+ interview.coverage.skills?.length > 0
203
206
  ? createBadge(
204
207
  `${interview.coverage.skills.length} skills covered`,
205
208
  "primary",
206
209
  )
207
210
  : null,
208
- interview.coverage.behaviours.length > 0
211
+ interview.coverage.behaviours?.length > 0
209
212
  ? createBadge(
210
213
  `${interview.coverage.behaviours.length} behaviours covered`,
211
214
  "primary",
212
215
  )
213
216
  : null,
217
+ interview.coverage.capabilities?.length > 0
218
+ ? createBadge(
219
+ `${interview.coverage.capabilities.length} capabilities covered`,
220
+ "primary",
221
+ )
222
+ : null,
214
223
  ),
215
224
  );
216
225
  }
@@ -226,6 +235,9 @@ function createQuestionsDisplay(interview, framework) {
226
235
  const behaviourQuestions = interview.questions.filter(
227
236
  (q) => q.targetType === "behaviour",
228
237
  );
238
+ const capabilityQuestions = interview.questions.filter(
239
+ (q) => q.targetType === "capability",
240
+ );
229
241
 
230
242
  const sections = [];
231
243
 
@@ -241,12 +253,21 @@ function createQuestionsDisplay(interview, framework) {
241
253
  if (behaviourQuestions.length > 0) {
242
254
  sections.push(
243
255
  createDetailSection({
244
- title: `${getConceptEmoji(framework, "behaviour")} Behaviour Questions (${behaviourQuestions.length})`,
256
+ title: `${getConceptEmoji(framework, "behaviour")} Stakeholder Simulation (${behaviourQuestions.length})`,
245
257
  content: createQuestionsList(behaviourQuestions),
246
258
  }),
247
259
  );
248
260
  }
249
261
 
262
+ if (capabilityQuestions.length > 0) {
263
+ sections.push(
264
+ createDetailSection({
265
+ title: `${getConceptEmoji(framework, "capability") || "🧩"} Decomposition Questions (${capabilityQuestions.length})`,
266
+ content: createQuestionsList(capabilityQuestions),
267
+ }),
268
+ );
269
+ }
270
+
250
271
  if (sections.length === 0) {
251
272
  return div(
252
273
  { className: "card" },
@@ -262,6 +283,7 @@ function createQuestionsDisplay(interview, framework) {
262
283
 
263
284
  /**
264
285
  * Create questions list
286
+ * @param {Array} questions - Questions to display
265
287
  */
266
288
  function createQuestionsList(questions) {
267
289
  return div(
@@ -272,10 +294,41 @@ function createQuestionsList(questions) {
272
294
 
273
295
  /**
274
296
  * Create question card
297
+ * @param {Object} questionEntry - Question entry
298
+ * @param {number} number - Question number
275
299
  */
276
300
  function createQuestionCard(questionEntry, number) {
277
301
  const { question, targetName, targetLevel } = questionEntry;
278
302
 
303
+ const contextSection = question.context
304
+ ? div(
305
+ { className: "question-context" },
306
+ h4({}, "Context:"),
307
+ p({}, question.context),
308
+ )
309
+ : null;
310
+
311
+ const decompositionPromptsList =
312
+ question.decompositionPrompts && question.decompositionPrompts.length > 0
313
+ ? div(
314
+ { className: "question-prompts" },
315
+ h4({}, "Guide candidate thinking:"),
316
+ ul(
317
+ {},
318
+ ...question.decompositionPrompts.map((prompt) => li({}, prompt)),
319
+ ),
320
+ )
321
+ : null;
322
+
323
+ const simulationPromptsList =
324
+ question.simulationPrompts && question.simulationPrompts.length > 0
325
+ ? div(
326
+ { className: "question-prompts" },
327
+ h4({}, "Steer the simulation:"),
328
+ ul({}, ...question.simulationPrompts.map((prompt) => li({}, prompt))),
329
+ )
330
+ : null;
331
+
279
332
  const followUpsList =
280
333
  question.followUps && question.followUps.length > 0
281
334
  ? div(
@@ -309,6 +362,9 @@ function createQuestionCard(questionEntry, number) {
309
362
  ),
310
363
  ),
311
364
  div({ className: "question-text" }, question.text),
365
+ contextSection,
366
+ decompositionPromptsList,
367
+ simulationPromptsList,
312
368
  followUpsList,
313
369
  lookingForList,
314
370
  );
package/src/pages/job.js CHANGED
@@ -66,6 +66,7 @@ export async function renderJobDetail(params) {
66
66
  behaviours: data.behaviours,
67
67
  drivers: data.drivers,
68
68
  capabilities: data.capabilities,
69
+ stages: data.stages,
69
70
  });
70
71
 
71
72
  if (!jobView) {
@@ -11,6 +11,7 @@ import {
11
11
  } from "@forwardimpact/schema/levels";
12
12
  import { getStageEmoji } from "../formatters/stage/shared.js";
13
13
  import { aggregateTools } from "../formatters/tool/shared.js";
14
+ import { createCommandPrompt } from "../components/command-prompt.js";
14
15
 
15
16
  /**
16
17
  * Create lifecycle flow visualization for landing page
@@ -63,6 +64,12 @@ export function renderLanding() {
63
64
  span({ className: "brand-tag brand-tag-hero" }, framework.tag),
64
65
  ),
65
66
  p({}, framework.description.trim()),
67
+ // Install command prompt (only if distribution URL is configured)
68
+ framework.distribution?.siteUrl
69
+ ? createCommandPrompt(
70
+ `curl -fsSL ${framework.distribution.siteUrl}/install.sh | bash`,
71
+ )
72
+ : null,
66
73
  // Job builder CTA
67
74
  div(
68
75
  { className: "page-actions", style: "justify-content: center" },
@@ -10,7 +10,10 @@ import { renderNotFound } from "../components/error-page.js";
10
10
  import { prepareSkillsList } from "../formatters/skill/shared.js";
11
11
  import { skillToDOM } from "../formatters/skill/dom.js";
12
12
  import { skillToCardConfig } from "../lib/card-mappers.js";
13
- import { getCapabilityEmoji } from "@forwardimpact/schema/levels";
13
+ import {
14
+ getCapabilityEmoji,
15
+ getConceptEmoji,
16
+ } from "@forwardimpact/schema/levels";
14
17
 
15
18
  /**
16
19
  * Render skills list page
@@ -18,6 +21,7 @@ import { getCapabilityEmoji } from "@forwardimpact/schema/levels";
18
21
  export function renderSkillsList() {
19
22
  const { data } = getState();
20
23
  const { framework } = data;
24
+ const skillEmoji = getConceptEmoji(framework, "skill");
21
25
 
22
26
  // Transform data for list view
23
27
  const { groups, groupOrder } = prepareSkillsList(
@@ -30,7 +34,10 @@ export function renderSkillsList() {
30
34
  // Header
31
35
  div(
32
36
  { className: "page-header" },
33
- h1({ className: "page-title" }, framework.entityDefinitions.skill.title),
37
+ h1(
38
+ { className: "page-title" },
39
+ `${skillEmoji} ${framework.entityDefinitions.skill.title}`,
40
+ ),
34
41
  p(
35
42
  { className: "page-description" },
36
43
  framework.entityDefinitions.skill.description.trim(),
@@ -9,6 +9,7 @@ import { renderNotFound } from "../components/error-page.js";
9
9
  import { prepareTracksList } from "../formatters/track/shared.js";
10
10
  import { trackToDOM } from "../formatters/track/dom.js";
11
11
  import { trackToCardConfig } from "../lib/card-mappers.js";
12
+ import { getConceptEmoji } from "@forwardimpact/schema/levels";
12
13
 
13
14
  /**
14
15
  * Render tracks list page
@@ -16,6 +17,7 @@ import { trackToCardConfig } from "../lib/card-mappers.js";
16
17
  export function renderTracksList() {
17
18
  const { data } = getState();
18
19
  const { framework } = data;
20
+ const trackEmoji = getConceptEmoji(framework, "track");
19
21
 
20
22
  // Transform data for list view
21
23
  const { items } = prepareTracksList(data.tracks);
@@ -25,7 +27,10 @@ export function renderTracksList() {
25
27
  // Header
26
28
  div(
27
29
  { className: "page-header" },
28
- h1({ className: "page-title" }, framework.entityDefinitions.track.title),
30
+ h1(
31
+ { className: "page-title" },
32
+ `${trackEmoji} ${framework.entityDefinitions.track.title}`,
33
+ ),
29
34
  p(
30
35
  { className: "page-description" },
31
36
  framework.entityDefinitions.track.description.trim(),
package/src/slides/job.js CHANGED
@@ -28,6 +28,7 @@ export function renderJobSlide({ render, data, params }) {
28
28
  behaviours: data.behaviours,
29
29
  drivers: data.drivers,
30
30
  capabilities: data.capabilities,
31
+ stages: data.stages,
31
32
  });
32
33
 
33
34
  if (!view) {
@@ -57,11 +57,18 @@ project-specific guidance, required tools, and technology standards. Pre-trainin
57
57
  knowledge alone is insufficient—skills contain organizational standards that
58
58
  override general knowledge.
59
59
 
60
- Skills represent mandatory organizational patterns. When a skill specifies tools
61
- in its "Required Tools" section, you MUST use them. If a blocking constraint
62
- prevents use, document in your output: (1) which skill requirement you cannot
63
- meet, (2) the specific constraint preventing compliance, and (3) the alternative
64
- approach with acknowledged trade-offs.
60
+ Each skill file contains XML-tagged sections for precise navigation:
61
+
62
+ - **`<{{stageId}}_stage_checklist>`** Your stage-specific checklist. Follow
63
+ the Read-Then-Do and Do-Then-Confirm items for the {{stageName}} stage.
64
+ - **`<required_tools>`** Mandatory tools for this skill. You MUST use these
65
+ organizational standards that override general knowledge or personal
66
+ preferences.
67
+ {{#isOnboard}}
68
+ - **`<onboarding_steps>`** — Step-by-step environment setup instructions.
69
+ Follow these to install prerequisites and configure the development
70
+ environment. Focus on setup only — do not begin feature implementation.
71
+ {{/isOnboard}}
65
72
 
66
73
  | Skill | Location | Use When |
67
74
  | ----- | -------- | -------- |
@@ -88,23 +95,23 @@ and (3) the compromised approach with acknowledged limitations.
88
95
  | `{{id}}` | {{{name}}} | {{{description}}} |
89
96
  {{/agentIndex}}
90
97
  {{/hasAgentIndex}}
91
- {{#hasBeforeHandoff}}
98
+ {{#hasConfirmChecklist}}
92
99
 
93
- ## Before Handoff
100
+ ## Do-Then-Confirm Checklist
94
101
 
95
102
  Before offering a handoff, verify and summarize completion of these items:
96
103
 
97
- {{#beforeHandoff}}
104
+ {{#confirmChecklist}}
98
105
  ### {{{capability.emojiIcon}}} {{{skill.name}}}
99
106
 
100
107
  {{#items}}
101
108
  - [ ] {{{.}}}
102
109
  {{/items}}
103
110
 
104
- {{/beforeHandoff}}
111
+ {{/confirmChecklist}}
105
112
  When verified, summarize what was accomplished then offer the handoff. If items
106
113
  are incomplete, explain what remains.
107
- {{/hasBeforeHandoff}}
114
+ {{/hasConfirmChecklist}}
108
115
 
109
116
  ## Return Format
110
117
 
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ # {{{frameworkTitle}}} — Local Install
3
+ # Generated by @forwardimpact/pathway v{{{version}}}
4
+ #
5
+ # Installs to ~/.fit/pathway/
6
+ #
7
+ # Usage:
8
+ # curl -fsSL {{{siteUrl}}}/install.sh | bash
9
+ #
10
+ set -euo pipefail
11
+
12
+ SITE_URL="{{{siteUrl}}}"
13
+ INSTALL_DIR="${HOME}/.fit/pathway"
14
+
15
+ command -v node >/dev/null 2>&1 || { echo "Error: Node.js 18+ is required. https://nodejs.org"; exit 1; }
16
+ command -v npm >/dev/null 2>&1 || { echo "Error: npm is required."; exit 1; }
17
+
18
+ echo "Installing to ${INSTALL_DIR}..."
19
+ mkdir -p "${INSTALL_DIR}"
20
+
21
+ TMPFILE=$(mktemp)
22
+ trap 'rm -f "$TMPFILE"' EXIT
23
+ curl -fsSL "${SITE_URL}/bundle.tar.gz" -o "$TMPFILE"
24
+ tar -xzf "$TMPFILE" -C "${INSTALL_DIR}" --strip-components=1
25
+
26
+ (cd "${INSTALL_DIR}" && npm install --production --ignore-scripts --no-audit --no-fund --silent)
27
+
28
+ echo ""
29
+ echo "Done. Usage:"
30
+ echo " cd ${INSTALL_DIR}"
31
+ echo " npx fit-pathway skill --list"
32
+ echo " npx fit-pathway job --list"
33
+ echo " npx fit-pathway agent --list"
@@ -13,23 +13,29 @@ description: {{{description}}}{{#hasUseWhen}} Use When: {{{useWhen}}}{{/hasUseWh
13
13
  ## Stage Guidance
14
14
  {{#stages}}
15
15
 
16
+ <{{stageId}}_stage_checklist>
17
+
16
18
  ### {{stageName}} Stage
17
19
 
18
20
  **Focus:** {{{focus}}}
19
21
 
20
- **Activities:**
21
- {{#activities}}
22
- - {{{.}}}
23
- {{/activities}}
22
+ **Read-Then-Do Checklist:**
23
+ {{#readChecklist}}
24
+ - [ ] {{{.}}}
25
+ {{/readChecklist}}
24
26
 
25
- **Ready for {{nextStageName}} when:**
26
- {{#ready}}
27
+ **Do-Then-Confirm Checklist:**
28
+ {{#confirmChecklist}}
27
29
  - [ ] {{{.}}}
28
- {{/ready}}
30
+ {{/confirmChecklist}}
31
+
32
+ </{{stageId}}_stage_checklist>
29
33
  {{/stages}}
30
34
  {{/hasStages}}
31
35
  {{#hasToolReferences}}
32
36
 
37
+ <required_tools>
38
+
33
39
  ## Required Tools
34
40
 
35
41
  **MANDATORY:** You MUST use these tools when applying this skill. These are
@@ -45,6 +51,8 @@ trade-offs.
45
51
  {{#toolReferences}}
46
52
  | {{#url}}[{{{name}}}]({{{url}}}){{/url}}{{^url}}{{{name}}}{{/url}} | {{{useWhen}}} |
47
53
  {{/toolReferences}}
54
+
55
+ </required_tools>
48
56
  {{/hasToolReferences}}
49
57
  {{#hasReference}}
50
58