@forwardimpact/pathway 0.25.25 → 0.25.27

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.25.25",
3
+ "version": "0.25.27",
4
4
  "description": "Career progression web app and CLI for exploring roles and generating agent teams",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -20,6 +20,7 @@
20
20
  "engineering"
21
21
  ],
22
22
  "type": "module",
23
+ "main": "./src/index.js",
23
24
  "bin": {
24
25
  "fit-pathway": "./bin/fit-pathway.js"
25
26
  },
@@ -34,10 +35,12 @@
34
35
  "templates/"
35
36
  ],
36
37
  "exports": {
38
+ ".": "./src/index.js",
37
39
  "./formatters": "./src/formatters/index.js",
38
40
  "./commands": "./src/commands/index.js"
39
41
  },
40
42
  "dependencies": {
43
+ "@forwardimpact/libtelemetry": "^0.1.33",
41
44
  "@forwardimpact/map": "^0.15.5",
42
45
  "@forwardimpact/libcli": "^0.1.0",
43
46
  "@forwardimpact/libskill": "^4.0.0",
@@ -15,6 +15,9 @@ import {
15
15
  formatReference,
16
16
  } from "../formatters/agent/skill.js";
17
17
  import { formatSuccess } from "@forwardimpact/libcli";
18
+ import { createLogger } from "@forwardimpact/libtelemetry";
19
+
20
+ const logger = createLogger("pathway");
18
21
 
19
22
  /**
20
23
  * Ensure directory exists for a file path
@@ -47,7 +50,7 @@ export async function generateClaudeCodeSettings(baseDir, claudeCodeSettings) {
47
50
  JSON.stringify(merged, null, 2) + "\n",
48
51
  "utf-8",
49
52
  );
50
- console.log(formatSuccess(`Updated: ${settingsPath}`));
53
+ logger.info(formatSuccess(`Updated: ${settingsPath}`));
51
54
  }
52
55
 
53
56
  /**
@@ -61,7 +64,7 @@ export async function writeProfile(profile, baseDir, template) {
61
64
  const profileContent = formatAgentProfile(profile, template);
62
65
  await ensureDir(profilePath);
63
66
  await writeFile(profilePath, profileContent, "utf-8");
64
- console.log(formatSuccess(`Created: ${profilePath}`));
67
+ logger.info(formatSuccess(`Created: ${profilePath}`));
65
68
  return profilePath;
66
69
  }
67
70
 
@@ -76,7 +79,7 @@ export async function writeTeamInstructions(teamInstructions, baseDir) {
76
79
  const filePath = join(baseDir, ".claude", "CLAUDE.md");
77
80
  await ensureDir(filePath);
78
81
  await writeFile(filePath, teamInstructions.trim() + "\n", "utf-8");
79
- console.log(formatSuccess(`Created: ${filePath}`));
82
+ logger.info(formatSuccess(`Created: ${filePath}`));
80
83
  return filePath;
81
84
  }
82
85
 
@@ -95,7 +98,7 @@ export async function writeSkills(skills, baseDir, templates) {
95
98
  const skillContent = formatAgentSkill(skill, templates.skill);
96
99
  await ensureDir(skillPath);
97
100
  await writeFile(skillPath, skillContent, "utf-8");
98
- console.log(formatSuccess(`Created: ${skillPath}`));
101
+ logger.info(formatSuccess(`Created: ${skillPath}`));
99
102
  fileCount++;
100
103
 
101
104
  if (skill.installScript) {
@@ -103,7 +106,7 @@ export async function writeSkills(skills, baseDir, templates) {
103
106
  const installContent = formatInstallScript(skill, templates.install);
104
107
  await ensureDir(installPath);
105
108
  await writeFile(installPath, installContent, { mode: 0o755 });
106
- console.log(formatSuccess(`Created: ${installPath}`));
109
+ logger.info(formatSuccess(`Created: ${installPath}`));
107
110
  fileCount++;
108
111
  }
109
112
 
@@ -112,7 +115,7 @@ export async function writeSkills(skills, baseDir, templates) {
112
115
  const refContent = formatReference(skill, templates.reference);
113
116
  await ensureDir(refPath);
114
117
  await writeFile(refPath, refContent, "utf-8");
115
- console.log(formatSuccess(`Created: ${refPath}`));
118
+ logger.info(formatSuccess(`Created: ${refPath}`));
116
119
  fileCount++;
117
120
  }
118
121
  }
@@ -16,8 +16,6 @@ import {
16
16
  SummaryRenderer,
17
17
  } from "@forwardimpact/libcli";
18
18
 
19
- const summary = new SummaryRenderer({ process });
20
-
21
19
  /**
22
20
  * Find valid agent combination pairs
23
21
  * @param {Object} data - Pathway data
@@ -47,6 +45,7 @@ export function findValidCombinations(data, agentData) {
47
45
  * @param {Array} skillsWithAgent - Skills with agent sections
48
46
  */
49
47
  export function showAgentSummary(data, agentData, skillsWithAgent) {
48
+ const summary = new SummaryRenderer({ process });
50
49
  const validCombinations = findValidCombinations(data, agentData).length;
51
50
  const skillsWithAgentCount = skillsWithAgent.filter((s) => s.agent).length;
52
51
 
@@ -101,9 +100,8 @@ function listAgentCombinationsCompact(data, agentData) {
101
100
  const abbrev = getDisciplineAbbreviation(discipline.id);
102
101
  const agentName = `${abbrev}-${toKebabCase(track.id)}`;
103
102
  const specName = humanDiscipline.specialization || humanDiscipline.id;
104
- // Piped output — keep as plain console.log for stable format
105
- console.log(
106
- `${agentName} ${discipline.id} ${track.id}, ${specName} (${humanTrack.name})`,
103
+ process.stdout.write(
104
+ `${agentName} ${discipline.id} ${track.id}, ${specName} (${humanTrack.name})\n`,
107
105
  );
108
106
  }
109
107
  }
@@ -347,7 +347,8 @@ export async function runAgentCommand({
347
347
  level,
348
348
  skills: skillsWithAgent,
349
349
  });
350
- for (const skill of derivedSkills) console.log(skill.skillId);
350
+ for (const skill of derivedSkills)
351
+ process.stdout.write(skill.skillId + "\n");
351
352
  return;
352
353
  }
353
354
 
@@ -362,7 +363,7 @@ export async function runAgentCommand({
362
363
  skillMatrix: derivedSkills,
363
364
  skills: skillsWithAgent,
364
365
  });
365
- console.log(toolkitToPlainList(toolkit));
366
+ process.stdout.write(toolkitToPlainList(toolkit) + "\n");
366
367
  return;
367
368
  }
368
369
 
@@ -65,7 +65,7 @@ function formatSummary(behaviours, data) {
65
65
  */
66
66
  function formatDetail(viewAndContext) {
67
67
  const { behaviour, drivers } = viewAndContext;
68
- console.log(behaviourToMarkdown(behaviour, { drivers }));
68
+ process.stdout.write(behaviourToMarkdown(behaviour, { drivers }) + "\n");
69
69
  }
70
70
 
71
71
  export const runBehaviourCommand = createEntityCommand({
@@ -11,6 +11,9 @@ import { cp, mkdir, rm, readFile, writeFile } from "fs/promises";
11
11
  import { join } from "path";
12
12
  import { execFileSync } from "child_process";
13
13
  import Mustache from "mustache";
14
+ import { createLogger } from "@forwardimpact/libtelemetry";
15
+
16
+ const logger = createLogger("pathway");
14
17
 
15
18
  /**
16
19
  * Generate distribution bundle (bundle.tar.gz + install.sh)
@@ -30,7 +33,7 @@ export async function generateBundle({
30
33
  version,
31
34
  templatesDir,
32
35
  }) {
33
- console.log("šŸ“¦ Generating distribution bundle...");
36
+ logger.info("šŸ“¦ Generating distribution bundle...");
34
37
 
35
38
  const frameworkTitle = framework.title || "Engineering Pathway";
36
39
 
@@ -51,14 +54,14 @@ export async function generateBundle({
51
54
  join(bundleDir, "package.json"),
52
55
  JSON.stringify(bundlePkg, null, 2) + "\n",
53
56
  );
54
- console.log(` āœ“ package.json (pathway ^${version})`);
57
+ logger.info(` āœ“ package.json (pathway ^${version})`);
55
58
 
56
59
  // 3. Copy data files into bundle
57
60
  await cp(dataDir, join(bundleDir, "data"), {
58
61
  recursive: true,
59
62
  dereference: true,
60
63
  });
61
- console.log(" āœ“ data/");
64
+ logger.info(" āœ“ data/");
62
65
 
63
66
  // 4. Create tar.gz from the bundle directory
64
67
  execFileSync("tar", [
@@ -68,7 +71,7 @@ export async function generateBundle({
68
71
  outputDir,
69
72
  "_bundle",
70
73
  ]);
71
- console.log(" āœ“ bundle.tar.gz");
74
+ logger.info(" āœ“ bundle.tar.gz");
72
75
 
73
76
  // 5. Clean up temporary bundle directory
74
77
  await rm(bundleDir, { recursive: true });
@@ -84,5 +87,5 @@ export async function generateBundle({
84
87
  await writeFile(join(outputDir, "install.sh"), installScript, {
85
88
  mode: 0o755,
86
89
  });
87
- console.log(" āœ“ install.sh");
90
+ logger.info(" āœ“ install.sh");
88
91
  }
@@ -18,7 +18,10 @@ import { join } from "path";
18
18
  import { execFileSync } from "child_process";
19
19
  import { createHash } from "crypto";
20
20
 
21
+ import { createLogger } from "@forwardimpact/libtelemetry";
21
22
  import { createDataLoader } from "@forwardimpact/map/loader";
23
+
24
+ const logger = createLogger("pathway");
22
25
  import { createTemplateLoader } from "@forwardimpact/libtemplate";
23
26
  import {
24
27
  generateStageAgentProfile,
@@ -467,7 +470,7 @@ export async function generatePacks({
467
470
  version,
468
471
  templatesDir,
469
472
  }) {
470
- console.log("šŸ“¦ Generating agent/skill packs...");
473
+ logger.info("šŸ“¦ Generating agent/skill packs...");
471
474
 
472
475
  const normalizedSiteUrl = siteUrl.replace(/\/$/, "");
473
476
  const frameworkTitle = framework.title || "Engineering Pathway";
@@ -495,7 +498,7 @@ export async function generatePacks({
495
498
 
496
499
  const combinations = findValidCombinations(data, agentData);
497
500
  if (combinations.length === 0) {
498
- console.log(" (no valid discipline/track combinations — skipping)");
501
+ logger.info(" (no valid discipline/track combinations — skipping)");
499
502
  await rm(stagingDir, { recursive: true, force: true });
500
503
  return;
501
504
  }
@@ -538,7 +541,7 @@ export async function generatePacks({
538
541
  digest,
539
542
  });
540
543
 
541
- console.log(` āœ“ packs/${agentName}.tar.gz`);
544
+ logger.info(` āœ“ packs/${agentName}.tar.gz`);
542
545
  }
543
546
 
544
547
  // Write per-pack skill repositories (one per discipline/track combination)
@@ -550,17 +553,17 @@ export async function generatePacks({
550
553
  pack.name,
551
554
  );
552
555
  allPackEntries.push({ packName: pack.name, entries });
553
- console.log(
556
+ logger.info(
554
557
  ` āœ“ packs/${pack.name}/.well-known/skills/ (${entries.length} skills)`,
555
558
  );
556
559
  }
557
560
 
558
561
  // Write aggregate repository at packs/ level
559
562
  await writeAggregateRepository(packsDir, allPackEntries);
560
- console.log(" āœ“ packs/.well-known/skills/index.json (aggregate)");
563
+ logger.info(" āœ“ packs/.well-known/skills/index.json (aggregate)");
561
564
 
562
565
  await rm(stagingDir, { recursive: true, force: true });
563
566
 
564
567
  await writeApmManifest(outputDir, packs, version, frameworkTitle);
565
- console.log(" āœ“ apm.yml");
568
+ logger.info(" āœ“ apm.yml");
566
569
  }
@@ -13,8 +13,11 @@ import { cp, mkdir, rm, access, realpath, writeFile } from "fs/promises";
13
13
  import { readFileSync } from "fs";
14
14
  import { join, dirname, relative, resolve } from "path";
15
15
  import { fileURLToPath } from "url";
16
+ import { createLogger } from "@forwardimpact/libtelemetry";
16
17
  import { createIndexGenerator } from "@forwardimpact/map/index-generator";
17
18
  import { createDataLoader } from "@forwardimpact/map/loader";
19
+
20
+ const logger = createLogger("pathway");
18
21
  import { generateBundle } from "./build-bundle.js";
19
22
  import { generatePacks } from "./build-packs.js";
20
23
 
@@ -85,7 +88,7 @@ export async function runBuildCommand({ dataDir, options }) {
85
88
  framework = { emojiIcon: "šŸš€", title: "Engineering Pathway" };
86
89
  }
87
90
 
88
- console.log(`
91
+ logger.info(`
89
92
  ${framework.emojiIcon} Generating ${framework.title} static site...
90
93
  `);
91
94
 
@@ -93,7 +96,7 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
93
96
  if (clean) {
94
97
  try {
95
98
  await access(outputDir);
96
- console.log(`šŸ—‘ļø Cleaning ${outputDir}...`);
99
+ logger.info(`šŸ—‘ļø Cleaning ${outputDir}...`);
97
100
  await rm(outputDir, { recursive: true });
98
101
  } catch {
99
102
  // Directory doesn't exist, nothing to clean
@@ -104,12 +107,12 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
104
107
  await mkdir(outputDir, { recursive: true });
105
108
 
106
109
  // Generate index files in data directory
107
- console.log("šŸ“‡ Generating index files...");
110
+ logger.info("šŸ“‡ Generating index files...");
108
111
  const indexGenerator = createIndexGenerator();
109
112
  await indexGenerator.generateAllIndexes(dataDir);
110
113
 
111
114
  // Copy app assets
112
- console.log("šŸ“¦ Copying application files...");
115
+ logger.info("šŸ“¦ Copying application files...");
113
116
  for (const asset of PUBLIC_ASSETS) {
114
117
  const src = join(appDir, asset);
115
118
  const dest = join(outputDir, asset);
@@ -117,9 +120,9 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
117
120
  try {
118
121
  await access(src);
119
122
  await cp(src, dest, { recursive: true });
120
- console.log(` āœ“ ${asset}`);
123
+ logger.info(` āœ“ ${asset}`);
121
124
  } catch (err) {
122
- console.log(` āš ļø Skipped ${asset}: ${err.message}`);
125
+ logger.info(` āš ļø Skipped ${asset}: ${err.message}`);
123
126
  }
124
127
  }
125
128
 
@@ -132,19 +135,19 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
132
135
  try {
133
136
  await access(src);
134
137
  await cp(src, dest, { recursive: true });
135
- console.log(` āœ“ ${asset}`);
138
+ logger.info(` āœ“ ${asset}`);
136
139
  } catch (err) {
137
- console.log(` āš ļø Skipped ${asset}: ${err.message}`);
140
+ logger.info(` āš ļø Skipped ${asset}: ${err.message}`);
138
141
  }
139
142
  }
140
143
 
141
144
  // Copy @forwardimpact/map and @forwardimpact/libskill packages
142
145
  // These are needed by the browser's import map
143
- console.log("šŸ“š Copying package dependencies...");
146
+ logger.info("šŸ“š Copying package dependencies...");
144
147
  await cp(mapLibDir, join(outputDir, "map/lib"), { recursive: true });
145
- console.log(` āœ“ map/lib`);
148
+ logger.info(` āœ“ map/lib`);
146
149
  await cp(modelLibDir, join(outputDir, "model/lib"), { recursive: true });
147
- console.log(` āœ“ model/lib`);
150
+ logger.info(` āœ“ model/lib`);
148
151
  // Copy libui JS (src/) and CSS (src/css/)
149
152
  await cp(uiLibDir, join(outputDir, "ui/lib"), { recursive: true });
150
153
  // CSS is within uiLibDir/css/ so it's already copied as ui/lib/css/
@@ -152,10 +155,10 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
152
155
  await cp(join(uiLibDir, "css"), join(outputDir, "ui/css"), {
153
156
  recursive: true,
154
157
  });
155
- console.log(` āœ“ ui/lib + ui/css`);
158
+ logger.info(` āœ“ ui/lib + ui/css`);
156
159
 
157
160
  // Copy vendor dependencies for offline usage
158
- console.log("šŸ“¦ Copying vendor dependencies...");
161
+ logger.info("šŸ“¦ Copying vendor dependencies...");
159
162
  const vendorDir = join(outputDir, "vendor");
160
163
  await mkdir(vendorDir, { recursive: true });
161
164
 
@@ -163,17 +166,17 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
163
166
  const mustacheSrc = fileURLToPath(import.meta.resolve("mustache"));
164
167
  const mustacheMjs = join(dirname(mustacheSrc), "mustache.mjs");
165
168
  await cp(mustacheMjs, join(vendorDir, "mustache.mjs"));
166
- console.log(" āœ“ vendor/mustache.mjs");
169
+ logger.info(" āœ“ vendor/mustache.mjs");
167
170
 
168
171
  // yaml (browser ESM build — not in package exports, resolve via filesystem)
169
172
  // import.meta.resolve("yaml") → .../yaml/dist/index.js, go up two levels
170
173
  const yamlPkg = dirname(dirname(fileURLToPath(import.meta.resolve("yaml"))));
171
174
  const yamlBrowserDist = join(yamlPkg, "browser", "dist");
172
175
  await cp(yamlBrowserDist, join(vendorDir, "yaml"), { recursive: true });
173
- console.log(" āœ“ vendor/yaml/");
176
+ logger.info(" āœ“ vendor/yaml/");
174
177
 
175
178
  // Copy data directory (dereference symlinks to copy actual content)
176
- console.log("šŸ“ Copying data files...");
179
+ logger.info("šŸ“ Copying data files...");
177
180
  const dataOutputDir = join(outputDir, "data");
178
181
 
179
182
  // Check if source and destination are the same (e.g., when --output=.)
@@ -181,10 +184,10 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
181
184
  const resolvedDataOutputDir = resolve(dataOutputDir);
182
185
 
183
186
  if (resolvedDataDir === resolvedDataOutputDir) {
184
- console.log(` āœ“ data/ (already in place)`);
187
+ logger.info(` āœ“ data/ (already in place)`);
185
188
  } else {
186
189
  await cp(dataDir, dataOutputDir, { recursive: true, dereference: true });
187
- console.log(` āœ“ data/ (from ${relative(process.cwd(), dataDir)})`);
190
+ logger.info(` āœ“ data/ (from ${relative(process.cwd(), dataDir)})`);
188
191
  }
189
192
 
190
193
  // Write version.json for the web app footer
@@ -193,7 +196,7 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
193
196
  join(outputDir, "version.json"),
194
197
  JSON.stringify({ version }) + "\n",
195
198
  );
196
- console.log(` āœ“ version.json (${version})`);
199
+ logger.info(` āœ“ version.json (${version})`);
197
200
 
198
201
  // Generate distribution surfaces if siteUrl is configured
199
202
  const siteUrl = options.url || framework.distribution?.siteUrl;
@@ -218,7 +221,7 @@ ${framework.emojiIcon} Generating ${framework.title} static site...
218
221
  }
219
222
 
220
223
  // Show summary
221
- console.log(`
224
+ logger.info(`
222
225
  āœ… Site generated successfully!
223
226
 
224
227
  Output: ${outputDir}
@@ -59,7 +59,9 @@ export function createEntityCommand({
59
59
  // --list: Output descriptive comma-separated lines for piping and AI agent discovery
60
60
  if (options.list) {
61
61
  for (const item of items) {
62
- console.log(formatListItem ? formatListItem(item) : item.id);
62
+ process.stdout.write(
63
+ (formatListItem ? formatListItem(item) : item.id) + "\n",
64
+ );
63
65
  }
64
66
  return;
65
67
  }
@@ -67,7 +69,7 @@ export function createEntityCommand({
67
69
  // No args: Show summary
68
70
  if (!id) {
69
71
  if (options.json) {
70
- console.log(JSON.stringify(items, null, 2));
72
+ process.stdout.write(JSON.stringify(items, null, 2) + "\n");
71
73
  return;
72
74
  }
73
75
  formatSummary(items, data);
@@ -168,7 +170,7 @@ function handleDetail({
168
170
  }
169
171
 
170
172
  if (options.json) {
171
- console.log(JSON.stringify(view, null, 2));
173
+ process.stdout.write(JSON.stringify(view, null, 2) + "\n");
172
174
  return;
173
175
  }
174
176
 
@@ -226,7 +228,7 @@ export function createCompositeCommand({
226
228
  }
227
229
 
228
230
  if (options.json) {
229
- console.log(JSON.stringify(view, null, 2));
231
+ process.stdout.write(JSON.stringify(view, null, 2) + "\n");
230
232
  return;
231
233
  }
232
234
 
@@ -10,9 +10,12 @@ import { readFile, stat } from "fs/promises";
10
10
  import { readFileSync } from "fs";
11
11
  import { join, extname, dirname } from "path";
12
12
  import { fileURLToPath } from "url";
13
+ import { createLogger } from "@forwardimpact/libtelemetry";
13
14
  import { createIndexGenerator } from "@forwardimpact/map/index-generator";
14
15
  import { createDataLoader } from "@forwardimpact/map/loader";
15
16
 
17
+ const logger = createLogger("pathway");
18
+
16
19
  const __filename = fileURLToPath(import.meta.url);
17
20
  const __dirname = dirname(__filename);
18
21
  const publicDir = join(__dirname, "..");
@@ -131,7 +134,7 @@ export async function runDevCommand({ dataDir, options }) {
131
134
  }
132
135
 
133
136
  // Generate _index.yaml files before serving
134
- console.log("Generating index files...");
137
+ logger.info("Generating index files...");
135
138
  const indexGenerator = createIndexGenerator();
136
139
  await indexGenerator.generateAllIndexes(dataDir);
137
140
 
@@ -191,7 +194,7 @@ export async function runDevCommand({ dataDir, options }) {
191
194
  });
192
195
 
193
196
  server.listen(port, () => {
194
- console.log(`
197
+ logger.info(`
195
198
  ${framework.emojiIcon} ${framework.title} running at http://localhost:${port}
196
199
  šŸ“ Data directory: ${dataDir}
197
200
 
@@ -67,7 +67,9 @@ function formatSummary(disciplines) {
67
67
  */
68
68
  function formatDetail(viewAndContext) {
69
69
  const { discipline, skills, behaviours, tracks } = viewAndContext;
70
- console.log(disciplineToMarkdown(discipline, { skills, behaviours, tracks }));
70
+ process.stdout.write(
71
+ disciplineToMarkdown(discipline, { skills, behaviours, tracks }) + "\n",
72
+ );
71
73
  }
72
74
 
73
75
  export const runDisciplineCommand = createEntityCommand({
@@ -16,6 +16,4 @@ export { runJobCommand } from "./job.js";
16
16
  export { runInterviewCommand } from "./interview.js";
17
17
  export { runProgressCommand } from "./progress.js";
18
18
  export { runQuestionsCommand } from "./questions.js";
19
- export { runServeCommand } from "./serve.js";
20
- export { runSiteCommand } from "./site.js";
21
19
  export { runUpdateCommand } from "./update.js";
@@ -41,7 +41,7 @@ import { toolkitToPlainList } from "../formatters/toolkit/markdown.js";
41
41
  * @param {string} jobTemplate - Mustache template for job description
42
42
  */
43
43
  function formatJob(view, _options, entities, jobTemplate) {
44
- console.log(jobToMarkdown(view, entities, jobTemplate));
44
+ process.stdout.write(jobToMarkdown(view, entities, jobTemplate) + "\n");
45
45
  }
46
46
 
47
47
  /**
@@ -56,11 +56,11 @@ function printJobList(filteredJobs) {
56
56
  track: job.track,
57
57
  });
58
58
  if (job.track) {
59
- console.log(
60
- `${job.discipline.id} ${job.level.id} ${job.track.id}, ${title}`,
59
+ process.stdout.write(
60
+ `${job.discipline.id} ${job.level.id} ${job.track.id}, ${title}\n`,
61
61
  );
62
62
  } else {
63
- console.log(`${job.discipline.id} ${job.level.id}, ${title}`);
63
+ process.stdout.write(`${job.discipline.id} ${job.level.id}, ${title}\n`);
64
64
  }
65
65
  }
66
66
  }
@@ -401,18 +401,18 @@ export async function runJobCommand({
401
401
 
402
402
  if (options.skills) {
403
403
  for (const skill of view.skillMatrix) {
404
- console.log(skill.skillId);
404
+ process.stdout.write(skill.skillId + "\n");
405
405
  }
406
406
  return;
407
407
  }
408
408
 
409
409
  if (options.tools) {
410
- console.log(toolkitToPlainList(view.toolkit));
410
+ process.stdout.write(toolkitToPlainList(view.toolkit) + "\n");
411
411
  return;
412
412
  }
413
413
 
414
414
  if (options.json) {
415
- console.log(JSON.stringify(view, null, 2));
415
+ process.stdout.write(JSON.stringify(view, null, 2) + "\n");
416
416
  return;
417
417
  }
418
418
 
@@ -79,7 +79,7 @@ function formatSummary(levels, data) {
79
79
  * @param {Object} framework - Framework config
80
80
  */
81
81
  function formatDetail(level, framework) {
82
- console.log(levelToMarkdown(level, framework));
82
+ process.stdout.write(levelToMarkdown(level, framework) + "\n");
83
83
  }
84
84
 
85
85
  export const runLevelCommand = createEntityCommand({
@@ -162,7 +162,7 @@ export async function runQuestionsCommand({
162
162
  filter,
163
163
  });
164
164
  for (const q of view.questions) {
165
- console.log(q.id);
165
+ process.stdout.write(q.id + "\n");
166
166
  }
167
167
  return;
168
168
  }
@@ -196,5 +196,5 @@ export async function runQuestionsCommand({
196
196
  break;
197
197
  }
198
198
 
199
- console.log(output);
199
+ process.stdout.write(output + "\n");
200
200
  }
@@ -65,14 +65,14 @@ function formatSummary(skills, data) {
65
65
  */
66
66
  function formatDetail(viewAndContext, framework) {
67
67
  const { skill, disciplines, tracks, drivers, capabilities } = viewAndContext;
68
- console.log(
68
+ process.stdout.write(
69
69
  skillToMarkdown(skill, {
70
70
  disciplines,
71
71
  tracks,
72
72
  drivers,
73
73
  capabilities,
74
74
  framework,
75
- }),
75
+ }) + "\n",
76
76
  );
77
77
  }
78
78
 
@@ -33,7 +33,7 @@ export async function runToolCommand({ data, args, options }) {
33
33
  // --list: Output descriptive comma-separated tool lines for piping
34
34
  if (options.list) {
35
35
  for (const tool of tools) {
36
- console.log(`${tool.name}, ${truncate(tool.description, 60)}`);
36
+ process.stdout.write(`${tool.name}, ${truncate(tool.description, 60)}\n`);
37
37
  }
38
38
  return;
39
39
  }
@@ -41,7 +41,7 @@ export async function runToolCommand({ data, args, options }) {
41
41
  // No args: Show summary
42
42
  if (!name) {
43
43
  if (options.json) {
44
- console.log(JSON.stringify(tools, null, 2));
44
+ process.stdout.write(JSON.stringify(tools, null, 2) + "\n");
45
45
  return;
46
46
  }
47
47
  formatSummary(tools, totalCount);
@@ -58,7 +58,7 @@ export async function runToolCommand({ data, args, options }) {
58
58
  }
59
59
 
60
60
  if (options.json) {
61
- console.log(JSON.stringify(tool, null, 2));
61
+ process.stdout.write(JSON.stringify(tool, null, 2) + "\n");
62
62
  return;
63
63
  }
64
64
 
@@ -73,8 +73,9 @@ function formatSummary(tracks, data) {
73
73
  */
74
74
  function formatDetail(viewAndContext, framework) {
75
75
  const { track, skills, behaviours, disciplines } = viewAndContext;
76
- console.log(
77
- trackToMarkdown(track, { skills, behaviours, disciplines, framework }),
76
+ process.stdout.write(
77
+ trackToMarkdown(track, { skills, behaviours, disciplines, framework }) +
78
+ "\n",
78
79
  );
79
80
  }
80
81
 
@@ -11,8 +11,11 @@ import { join } from "path";
11
11
  import { homedir } from "os";
12
12
  import { execFileSync } from "child_process";
13
13
  import { tmpdir } from "os";
14
+ import { createLogger } from "@forwardimpact/libtelemetry";
14
15
  import { createDataLoader } from "@forwardimpact/map/loader";
15
16
 
17
+ const logger = createLogger("pathway");
18
+
16
19
  const BASE_DIR = join(homedir(), ".fit", "data");
17
20
  const INSTALL_DIR = join(BASE_DIR, "pathway");
18
21
 
@@ -53,7 +56,7 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
53
56
  const baseUrl = siteUrl.replace(/\/$/, "");
54
57
  const bundleName = "bundle.tar.gz";
55
58
 
56
- console.log(`\nšŸ”„ Updating from ${baseUrl}...\n`);
59
+ logger.info(`\nšŸ”„ Updating from ${baseUrl}...\n`);
57
60
 
58
61
  // 1. Download bundle to temp location
59
62
  const tmpDir = join(tmpdir(), "fit-pathway-update");
@@ -62,17 +65,17 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
62
65
  const tmpBundle = join(tmpDir, bundleName);
63
66
 
64
67
  try {
65
- console.log(" Downloading bundle...");
68
+ logger.info(" Downloading bundle...");
66
69
  execFileSync("curl", [
67
70
  "-fsSL",
68
71
  `${baseUrl}/${bundleName}`,
69
72
  "-o",
70
73
  tmpBundle,
71
74
  ]);
72
- console.log(" āœ“ Downloaded");
75
+ logger.info(" āœ“ Downloaded");
73
76
 
74
77
  // 2. Extract bundle
75
- console.log(" Extracting...");
78
+ logger.info(" Extracting...");
76
79
  const extractDir = join(tmpDir, "extracted");
77
80
  await mkdir(extractDir, { recursive: true });
78
81
  execFileSync("tar", [
@@ -82,7 +85,7 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
82
85
  extractDir,
83
86
  "--strip-components=1",
84
87
  ]);
85
- console.log(" āœ“ Extracted");
88
+ logger.info(" āœ“ Extracted");
86
89
 
87
90
  // 3. Compare versions from bundle's package.json (version manifest)
88
91
  const newPkgPath = join(extractDir, "package.json");
@@ -101,17 +104,17 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
101
104
  newPkg.dependencies?.["@forwardimpact/pathway"] || "unknown";
102
105
 
103
106
  // 4. Replace data
104
- console.log(" Updating data files...");
107
+ logger.info(" Updating data files...");
105
108
  await rm(INSTALL_DIR, { recursive: true });
106
109
  await cp(join(extractDir, "data"), INSTALL_DIR, { recursive: true });
107
- console.log(" āœ“ Data updated");
110
+ logger.info(" āœ“ Data updated");
108
111
 
109
112
  // 5. Update version manifest
110
113
  await writeFile(oldPkgPath, JSON.stringify(newPkg, null, 2) + "\n");
111
114
 
112
115
  // 6. Update global pathway package if version changed
113
116
  if (oldVersion !== newVersion) {
114
- console.log(` Updating pathway ${oldVersion} → ${newVersion}...`);
117
+ logger.info(` Updating pathway ${oldVersion} → ${newVersion}...`);
115
118
  execFileSync(
116
119
  "npm",
117
120
  ["install", "-g", `@forwardimpact/pathway@${newVersion}`],
@@ -119,11 +122,11 @@ export async function runUpdateCommand({ dataDir: _dataDir, options }) {
119
122
  stdio: "ignore",
120
123
  },
121
124
  );
122
- console.log(" āœ“ Global package updated");
125
+ logger.info(" āœ“ Global package updated");
123
126
  }
124
127
 
125
128
  // 7. Report
126
- console.log(`
129
+ logger.info(`
127
130
  āœ… Update complete!
128
131
 
129
132
  Pathway: ${oldVersion === newVersion ? newVersion + " (unchanged)" : oldVersion + " → " + newVersion}
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // Public entry point for @forwardimpact/pathway.
2
+ // The primary consumption mode is the CLI (fit-pathway) — this
3
+ // re-export exists so the package conforms to the repo-wide layout
4
+ // contract (spec 390) and so consumers who import
5
+ // @forwardimpact/pathway directly receive the shared type
6
+ // definitions.
7
+ export * from "./types.js";