@forwardimpact/pathway 0.1.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 (227) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +104 -0
  3. package/app/commands/agent.js +430 -0
  4. package/app/commands/behaviour.js +61 -0
  5. package/app/commands/command-factory.js +211 -0
  6. package/app/commands/discipline.js +58 -0
  7. package/app/commands/driver.js +94 -0
  8. package/app/commands/grade.js +60 -0
  9. package/app/commands/index.js +20 -0
  10. package/app/commands/init.js +67 -0
  11. package/app/commands/interview.js +68 -0
  12. package/app/commands/job.js +157 -0
  13. package/app/commands/progress.js +77 -0
  14. package/app/commands/questions.js +179 -0
  15. package/app/commands/serve.js +143 -0
  16. package/app/commands/site.js +121 -0
  17. package/app/commands/skill.js +76 -0
  18. package/app/commands/stage.js +129 -0
  19. package/app/commands/track.js +70 -0
  20. package/app/components/action-buttons.js +66 -0
  21. package/app/components/behaviour-profile.js +53 -0
  22. package/app/components/builder.js +341 -0
  23. package/app/components/card.js +98 -0
  24. package/app/components/checklist.js +145 -0
  25. package/app/components/comparison-radar.js +237 -0
  26. package/app/components/detail.js +230 -0
  27. package/app/components/error-page.js +72 -0
  28. package/app/components/grid.js +109 -0
  29. package/app/components/list.js +120 -0
  30. package/app/components/modifier-table.js +142 -0
  31. package/app/components/nav.js +64 -0
  32. package/app/components/progression-table.js +320 -0
  33. package/app/components/radar-chart.js +102 -0
  34. package/app/components/skill-matrix.js +97 -0
  35. package/app/css/base.css +56 -0
  36. package/app/css/bundles/app.css +40 -0
  37. package/app/css/bundles/handout.css +43 -0
  38. package/app/css/bundles/slides.css +40 -0
  39. package/app/css/components/badges.css +215 -0
  40. package/app/css/components/buttons.css +101 -0
  41. package/app/css/components/forms.css +105 -0
  42. package/app/css/components/layout.css +209 -0
  43. package/app/css/components/nav.css +166 -0
  44. package/app/css/components/progress.css +166 -0
  45. package/app/css/components/states.css +82 -0
  46. package/app/css/components/surfaces.css +243 -0
  47. package/app/css/components/tables.css +362 -0
  48. package/app/css/components/typography.css +122 -0
  49. package/app/css/components/utilities.css +41 -0
  50. package/app/css/pages/agent-builder.css +391 -0
  51. package/app/css/pages/assessment-results.css +453 -0
  52. package/app/css/pages/detail.css +59 -0
  53. package/app/css/pages/interview-builder.css +148 -0
  54. package/app/css/pages/job-builder.css +134 -0
  55. package/app/css/pages/landing.css +92 -0
  56. package/app/css/pages/lifecycle.css +118 -0
  57. package/app/css/pages/progress-builder.css +274 -0
  58. package/app/css/pages/self-assessment.css +502 -0
  59. package/app/css/reset.css +50 -0
  60. package/app/css/tokens.css +153 -0
  61. package/app/css/views/handout.css +30 -0
  62. package/app/css/views/print.css +608 -0
  63. package/app/css/views/slide-animations.css +113 -0
  64. package/app/css/views/slide-base.css +330 -0
  65. package/app/css/views/slide-sections.css +597 -0
  66. package/app/css/views/slide-tables.css +275 -0
  67. package/app/formatters/agent/dom.js +540 -0
  68. package/app/formatters/agent/profile.js +133 -0
  69. package/app/formatters/agent/skill.js +58 -0
  70. package/app/formatters/behaviour/dom.js +91 -0
  71. package/app/formatters/behaviour/markdown.js +54 -0
  72. package/app/formatters/behaviour/shared.js +64 -0
  73. package/app/formatters/discipline/dom.js +187 -0
  74. package/app/formatters/discipline/markdown.js +87 -0
  75. package/app/formatters/discipline/shared.js +131 -0
  76. package/app/formatters/driver/dom.js +103 -0
  77. package/app/formatters/driver/shared.js +92 -0
  78. package/app/formatters/grade/dom.js +208 -0
  79. package/app/formatters/grade/markdown.js +94 -0
  80. package/app/formatters/grade/shared.js +86 -0
  81. package/app/formatters/index.js +50 -0
  82. package/app/formatters/interview/dom.js +97 -0
  83. package/app/formatters/interview/markdown.js +66 -0
  84. package/app/formatters/interview/shared.js +332 -0
  85. package/app/formatters/job/description.js +176 -0
  86. package/app/formatters/job/dom.js +411 -0
  87. package/app/formatters/job/markdown.js +102 -0
  88. package/app/formatters/progress/dom.js +135 -0
  89. package/app/formatters/progress/markdown.js +86 -0
  90. package/app/formatters/progress/shared.js +339 -0
  91. package/app/formatters/questions/json.js +43 -0
  92. package/app/formatters/questions/markdown.js +303 -0
  93. package/app/formatters/questions/shared.js +274 -0
  94. package/app/formatters/questions/yaml.js +76 -0
  95. package/app/formatters/shared.js +71 -0
  96. package/app/formatters/skill/dom.js +168 -0
  97. package/app/formatters/skill/markdown.js +109 -0
  98. package/app/formatters/skill/shared.js +125 -0
  99. package/app/formatters/stage/dom.js +135 -0
  100. package/app/formatters/stage/index.js +12 -0
  101. package/app/formatters/stage/shared.js +111 -0
  102. package/app/formatters/track/dom.js +128 -0
  103. package/app/formatters/track/markdown.js +105 -0
  104. package/app/formatters/track/shared.js +181 -0
  105. package/app/handout-main.js +421 -0
  106. package/app/handout.html +21 -0
  107. package/app/index.html +59 -0
  108. package/app/lib/card-mappers.js +173 -0
  109. package/app/lib/cli-output.js +270 -0
  110. package/app/lib/error-boundary.js +70 -0
  111. package/app/lib/errors.js +49 -0
  112. package/app/lib/form-controls.js +47 -0
  113. package/app/lib/job-cache.js +86 -0
  114. package/app/lib/markdown.js +114 -0
  115. package/app/lib/radar.js +866 -0
  116. package/app/lib/reactive.js +77 -0
  117. package/app/lib/render.js +212 -0
  118. package/app/lib/router-core.js +160 -0
  119. package/app/lib/router-pages.js +16 -0
  120. package/app/lib/router-slides.js +202 -0
  121. package/app/lib/state.js +148 -0
  122. package/app/lib/utils.js +14 -0
  123. package/app/lib/yaml-loader.js +327 -0
  124. package/app/main.js +213 -0
  125. package/app/model/agent.js +702 -0
  126. package/app/model/checklist.js +137 -0
  127. package/app/model/derivation.js +699 -0
  128. package/app/model/index-generator.js +71 -0
  129. package/app/model/interview.js +539 -0
  130. package/app/model/job.js +222 -0
  131. package/app/model/levels.js +591 -0
  132. package/app/model/loader.js +564 -0
  133. package/app/model/matching.js +858 -0
  134. package/app/model/modifiers.js +158 -0
  135. package/app/model/profile.js +266 -0
  136. package/app/model/progression.js +507 -0
  137. package/app/model/validation.js +1385 -0
  138. package/app/pages/agent-builder.js +823 -0
  139. package/app/pages/assessment-results.js +507 -0
  140. package/app/pages/behaviour.js +70 -0
  141. package/app/pages/discipline.js +71 -0
  142. package/app/pages/driver.js +106 -0
  143. package/app/pages/grade.js +117 -0
  144. package/app/pages/interview-builder.js +50 -0
  145. package/app/pages/interview.js +304 -0
  146. package/app/pages/job-builder.js +50 -0
  147. package/app/pages/job.js +58 -0
  148. package/app/pages/landing.js +305 -0
  149. package/app/pages/progress-builder.js +58 -0
  150. package/app/pages/progress.js +495 -0
  151. package/app/pages/self-assessment.js +729 -0
  152. package/app/pages/skill.js +113 -0
  153. package/app/pages/stage.js +231 -0
  154. package/app/pages/track.js +69 -0
  155. package/app/slide-main.js +360 -0
  156. package/app/slides/behaviour.js +38 -0
  157. package/app/slides/chapter.js +82 -0
  158. package/app/slides/discipline.js +40 -0
  159. package/app/slides/driver.js +39 -0
  160. package/app/slides/grade.js +32 -0
  161. package/app/slides/index.js +198 -0
  162. package/app/slides/interview.js +58 -0
  163. package/app/slides/job.js +55 -0
  164. package/app/slides/overview.js +126 -0
  165. package/app/slides/progress.js +83 -0
  166. package/app/slides/skill.js +40 -0
  167. package/app/slides/track.js +39 -0
  168. package/app/slides.html +56 -0
  169. package/app/types.js +147 -0
  170. package/bin/pathway.js +489 -0
  171. package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
  172. package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
  173. package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
  174. package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
  175. package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
  176. package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
  177. package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
  178. package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
  179. package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
  180. package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
  181. package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
  182. package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
  183. package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
  184. package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
  185. package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
  186. package/examples/agents/.vscode/settings.json +8 -0
  187. package/examples/behaviours/_index.yaml +8 -0
  188. package/examples/behaviours/outcome_ownership.yaml +44 -0
  189. package/examples/behaviours/polymathic_knowledge.yaml +42 -0
  190. package/examples/behaviours/precise_communication.yaml +40 -0
  191. package/examples/behaviours/relentless_curiosity.yaml +38 -0
  192. package/examples/behaviours/systems_thinking.yaml +41 -0
  193. package/examples/capabilities/_index.yaml +8 -0
  194. package/examples/capabilities/business.yaml +251 -0
  195. package/examples/capabilities/delivery.yaml +352 -0
  196. package/examples/capabilities/people.yaml +100 -0
  197. package/examples/capabilities/reliability.yaml +318 -0
  198. package/examples/capabilities/scale.yaml +394 -0
  199. package/examples/disciplines/_index.yaml +5 -0
  200. package/examples/disciplines/data_engineering.yaml +76 -0
  201. package/examples/disciplines/software_engineering.yaml +76 -0
  202. package/examples/drivers.yaml +205 -0
  203. package/examples/framework.yaml +58 -0
  204. package/examples/grades.yaml +118 -0
  205. package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
  206. package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
  207. package/examples/questions/behaviours/precise_communication.yaml +55 -0
  208. package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
  209. package/examples/questions/behaviours/systems_thinking.yaml +53 -0
  210. package/examples/questions/skills/architecture_design.yaml +54 -0
  211. package/examples/questions/skills/cloud_platforms.yaml +48 -0
  212. package/examples/questions/skills/code_quality.yaml +49 -0
  213. package/examples/questions/skills/data_modeling.yaml +46 -0
  214. package/examples/questions/skills/devops.yaml +47 -0
  215. package/examples/questions/skills/full_stack_development.yaml +48 -0
  216. package/examples/questions/skills/sre_practices.yaml +44 -0
  217. package/examples/questions/skills/stakeholder_management.yaml +49 -0
  218. package/examples/questions/skills/team_collaboration.yaml +43 -0
  219. package/examples/questions/skills/technical_writing.yaml +43 -0
  220. package/examples/self-assessments.yaml +66 -0
  221. package/examples/stages.yaml +76 -0
  222. package/examples/tracks/_index.yaml +6 -0
  223. package/examples/tracks/manager.yaml +53 -0
  224. package/examples/tracks/platform.yaml +54 -0
  225. package/examples/tracks/sre.yaml +58 -0
  226. package/examples/vscode-settings.yaml +22 -0
  227. package/package.json +68 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Questions CLI Command
3
+ *
4
+ * Browse and compare interview questions across skills and behaviours.
5
+ *
6
+ * Usage:
7
+ * npx pathway questions # Summary with stats
8
+ * npx pathway questions --list # Question IDs (for piping)
9
+ * npx pathway questions --level=practitioner # Filter by level
10
+ * npx pathway questions --stats # Detailed statistics
11
+ */
12
+
13
+ import {
14
+ parseFilters,
15
+ prepareQuestionsView,
16
+ } from "../formatters/questions/shared.js";
17
+ import { questionsToMarkdown } from "../formatters/questions/markdown.js";
18
+ import { questionsToYaml } from "../formatters/questions/yaml.js";
19
+ import { questionsToJson } from "../formatters/questions/json.js";
20
+ import { formatTable } from "../lib/cli-output.js";
21
+
22
+ /**
23
+ * Parse questions command options
24
+ * @param {Object} rawOptions - Raw CLI options
25
+ * @returns {Object} Parsed options
26
+ */
27
+ function parseOptions(rawOptions) {
28
+ return {
29
+ level: rawOptions.level || null,
30
+ maturity: rawOptions.maturity || null,
31
+ skill: rawOptions.skill || null,
32
+ behaviour: rawOptions.behaviour || null,
33
+ capability: rawOptions.capability || null,
34
+ format: rawOptions.format || "table",
35
+ stats: rawOptions.stats || false,
36
+ json: rawOptions.json || false,
37
+ list: rawOptions.list || false,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Show questions summary
43
+ * @param {Object} data - Loaded data
44
+ */
45
+ function showQuestionsSummary(data) {
46
+ const { skills, behaviours } = data;
47
+ const questions = data.questions;
48
+
49
+ console.log(`\nā“ Questions\n`);
50
+
51
+ // Skill questions by level
52
+ const skillLevels = [
53
+ "awareness",
54
+ "foundational",
55
+ "working",
56
+ "practitioner",
57
+ "expert",
58
+ ];
59
+ const skillRows = skillLevels.map((level) => {
60
+ let count = 0;
61
+ for (const skill of skills) {
62
+ const sq = questions.skills?.[skill.id];
63
+ if (sq?.[level]) {
64
+ count += sq[level].length;
65
+ }
66
+ }
67
+ return [level, count];
68
+ });
69
+
70
+ console.log("Skill Questions:");
71
+ console.log(formatTable(["Level", "Count"], skillRows));
72
+
73
+ // Behaviour questions by maturity
74
+ const maturities = [
75
+ "emerging",
76
+ "developing",
77
+ "practicing",
78
+ "role_modeling",
79
+ "exemplifying",
80
+ ];
81
+ const behaviourRows = maturities.map((maturity) => {
82
+ let count = 0;
83
+ for (const behaviour of behaviours) {
84
+ const bq = questions.behaviours?.[behaviour.id];
85
+ if (bq?.[maturity]) {
86
+ count += bq[maturity].length;
87
+ }
88
+ }
89
+ return [maturity.replace(/_/g, " "), count];
90
+ });
91
+
92
+ console.log("\nBehaviour Questions:");
93
+ console.log(formatTable(["Maturity", "Count"], behaviourRows));
94
+
95
+ console.log(`\nRun 'npx pathway questions --list' for question IDs`);
96
+ console.log(`Run 'npx pathway questions --stats' for detailed stats`);
97
+ console.log(`Run 'npx pathway questions --level=practitioner' to filter\n`);
98
+ }
99
+
100
+ /**
101
+ * Run the questions command
102
+ * @param {Object} params
103
+ * @param {Object} params.data - Loaded data
104
+ * @param {string[]} params.args - Command arguments
105
+ * @param {Object} params.options - Parsed options
106
+ */
107
+ export async function runQuestionsCommand({
108
+ data,
109
+ args: _args,
110
+ options: rawOptions,
111
+ }) {
112
+ const options = parseOptions(rawOptions);
113
+
114
+ // Handle --json as alias for --format=json
115
+ if (options.json) {
116
+ options.format = "json";
117
+ }
118
+
119
+ // No filters and no format: Show summary
120
+ const hasFilters =
121
+ options.level ||
122
+ options.maturity ||
123
+ options.skill ||
124
+ options.behaviour ||
125
+ options.capability;
126
+
127
+ if (!hasFilters && !options.stats && !options.list) {
128
+ showQuestionsSummary(data);
129
+ return;
130
+ }
131
+
132
+ // --list: Output question IDs for piping
133
+ if (options.list) {
134
+ const filter = parseFilters(options);
135
+ const view = prepareQuestionsView({
136
+ questionBank: data.questions,
137
+ skills: data.skills,
138
+ behaviours: data.behaviours,
139
+ filter,
140
+ });
141
+ for (const section of view.sections) {
142
+ for (const q of section.questions) {
143
+ console.log(q.id);
144
+ }
145
+ }
146
+ return;
147
+ }
148
+
149
+ // Parse filters
150
+ const filter = parseFilters(options);
151
+
152
+ // Prepare view
153
+ const view = prepareQuestionsView({
154
+ questionBank: data.questions,
155
+ skills: data.skills,
156
+ behaviours: data.behaviours,
157
+ filter,
158
+ });
159
+
160
+ // Format output
161
+ let output;
162
+ switch (options.format) {
163
+ case "yaml":
164
+ output = questionsToYaml(view, options);
165
+ break;
166
+ case "json":
167
+ output = questionsToJson(view, options);
168
+ break;
169
+ case "table":
170
+ default:
171
+ output = questionsToMarkdown(view, {
172
+ stats: options.stats,
173
+ skills: data.skills,
174
+ });
175
+ break;
176
+ }
177
+
178
+ console.log(output);
179
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Serve Command
3
+ *
4
+ * Serves the Engineering Pathway web application with local data.
5
+ * Uses Node.js built-in http module (no external dependencies).
6
+ */
7
+
8
+ import { createServer } from "http";
9
+ import { readFile, stat } from "fs/promises";
10
+ import { join, extname, dirname } from "path";
11
+ import { fileURLToPath } from "url";
12
+ import { generateAllIndexes } from "../model/index-generator.js";
13
+ import { loadFrameworkConfig } from "../model/loader.js";
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const publicDir = join(__dirname, "..");
18
+
19
+ const MIME_TYPES = {
20
+ ".html": "text/html; charset=utf-8",
21
+ ".css": "text/css; charset=utf-8",
22
+ ".js": "application/javascript; charset=utf-8",
23
+ ".yaml": "text/yaml; charset=utf-8",
24
+ ".yml": "text/yaml; charset=utf-8",
25
+ ".json": "application/json; charset=utf-8",
26
+ ".svg": "image/svg+xml",
27
+ ".png": "image/png",
28
+ ".ico": "image/x-icon",
29
+ ".woff": "font/woff",
30
+ ".woff2": "font/woff2",
31
+ };
32
+
33
+ /**
34
+ * Get MIME type for a file extension
35
+ * @param {string} ext - File extension including dot
36
+ * @returns {string} MIME type
37
+ */
38
+ function getMimeType(ext) {
39
+ return MIME_TYPES[ext.toLowerCase()] || "application/octet-stream";
40
+ }
41
+
42
+ /**
43
+ * Serve a static file
44
+ * @param {import('http').ServerResponse} res - HTTP response
45
+ * @param {string} filePath - Path to file
46
+ */
47
+ async function serveFile(res, filePath) {
48
+ try {
49
+ const content = await readFile(filePath);
50
+ const ext = extname(filePath);
51
+ res.setHeader("Content-Type", getMimeType(ext));
52
+ res.end(content);
53
+ } catch (err) {
54
+ if (err.code === "ENOENT") {
55
+ res.statusCode = 404;
56
+ res.setHeader("Content-Type", "text/plain");
57
+ res.end("Not Found");
58
+ } else {
59
+ res.statusCode = 500;
60
+ res.setHeader("Content-Type", "text/plain");
61
+ res.end("Internal Server Error");
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Check if a path is a directory
68
+ * @param {string} path - Path to check
69
+ * @returns {Promise<boolean>}
70
+ */
71
+ async function isDirectory(path) {
72
+ try {
73
+ const stats = await stat(path);
74
+ return stats.isDirectory();
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Run the serve command
82
+ * @param {Object} params - Command parameters
83
+ * @param {string} params.dataDir - Path to data directory
84
+ * @param {Object} params.options - Command options
85
+ */
86
+ export async function runServeCommand({ dataDir, options }) {
87
+ const port = options.port || 3000;
88
+
89
+ // Load framework config for display
90
+ let framework;
91
+ try {
92
+ framework = await loadFrameworkConfig(dataDir);
93
+ } catch {
94
+ // Fallback if framework config fails
95
+ framework = { emoji: "šŸš€", title: "Engineering Pathway" };
96
+ }
97
+
98
+ // Generate _index.yaml files before serving
99
+ console.log("Generating index files...");
100
+ await generateAllIndexes(dataDir);
101
+
102
+ const server = createServer(async (req, res) => {
103
+ const url = new URL(req.url, `http://localhost:${port}`);
104
+ let pathname = url.pathname;
105
+
106
+ // Handle trailing slash for directories
107
+ if (pathname.endsWith("/") && pathname !== "/") {
108
+ pathname = pathname.slice(0, -1);
109
+ }
110
+
111
+ let filePath;
112
+
113
+ if (pathname.startsWith("/data/")) {
114
+ // Serve from user's data directory
115
+ filePath = join(dataDir, pathname.slice(6));
116
+ } else if (pathname === "/" || pathname === "") {
117
+ // Serve index.html for root
118
+ filePath = join(publicDir, "index.html");
119
+ } else {
120
+ // Serve from package's public directory
121
+ filePath = join(publicDir, pathname);
122
+ }
123
+
124
+ // Check if path is a directory, serve index.html if so
125
+ if (await isDirectory(filePath)) {
126
+ filePath = join(filePath, "index.html");
127
+ }
128
+
129
+ await serveFile(res, filePath);
130
+ });
131
+
132
+ server.listen(port, () => {
133
+ console.log(`
134
+ ${framework.emoji} ${framework.title} running at http://localhost:${port}
135
+ šŸ“ Data directory: ${dataDir}
136
+
137
+ Press Ctrl+C to stop the server.
138
+ `);
139
+ });
140
+
141
+ // Keep the process running
142
+ return new Promise(() => {});
143
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Site Command
3
+ *
4
+ * Generates a static site from the Engineering Pathway data.
5
+ * Copies all necessary files (HTML, JS, CSS) and data to an output directory.
6
+ */
7
+
8
+ import { cp, mkdir, rm, access, realpath } from "fs/promises";
9
+ import { join, dirname, relative, resolve } from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { generateAllIndexes } from "../model/index-generator.js";
12
+ import { loadFrameworkConfig } from "../model/loader.js";
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ const appDir = join(__dirname, "..");
17
+
18
+ /**
19
+ * Files and directories to copy from app/
20
+ */
21
+ const PUBLIC_ASSETS = [
22
+ // HTML entry points
23
+ "index.html",
24
+ "slides.html",
25
+ "handout.html",
26
+ // JavaScript entry points
27
+ "main.js",
28
+ "slide-main.js",
29
+ "handout-main.js",
30
+ "types.js",
31
+ // Directories
32
+ "css",
33
+ "lib",
34
+ "model",
35
+ "pages",
36
+ "slides",
37
+ "components",
38
+ "formatters",
39
+ ];
40
+
41
+ /**
42
+ * Run the site command
43
+ * @param {Object} params - Command parameters
44
+ * @param {string} params.dataDir - Path to data directory
45
+ * @param {Object} params.options - Command options
46
+ */
47
+ export async function runSiteCommand({ dataDir, options }) {
48
+ const outputDir = options.output || join(process.cwd(), "site");
49
+ const clean = options.clean !== false;
50
+
51
+ // Load framework config for display
52
+ let framework;
53
+ try {
54
+ framework = await loadFrameworkConfig(dataDir);
55
+ } catch {
56
+ framework = { emoji: "šŸš€", title: "Engineering Pathway" };
57
+ }
58
+
59
+ console.log(`
60
+ ${framework.emoji} Generating ${framework.title} static site...
61
+ `);
62
+
63
+ // Clean output directory if requested
64
+ if (clean) {
65
+ try {
66
+ await access(outputDir);
67
+ console.log(`šŸ—‘ļø Cleaning ${outputDir}...`);
68
+ await rm(outputDir, { recursive: true });
69
+ } catch {
70
+ // Directory doesn't exist, nothing to clean
71
+ }
72
+ }
73
+
74
+ // Create output directory
75
+ await mkdir(outputDir, { recursive: true });
76
+
77
+ // Generate index files in data directory
78
+ console.log("šŸ“‡ Generating index files...");
79
+ await generateAllIndexes(dataDir);
80
+
81
+ // Copy app assets
82
+ console.log("šŸ“¦ Copying application files...");
83
+ for (const asset of PUBLIC_ASSETS) {
84
+ const src = join(appDir, asset);
85
+ const dest = join(outputDir, asset);
86
+
87
+ try {
88
+ await access(src);
89
+ await cp(src, dest, { recursive: true });
90
+ console.log(` āœ“ ${asset}`);
91
+ } catch (err) {
92
+ console.log(` āš ļø Skipped ${asset}: ${err.message}`);
93
+ }
94
+ }
95
+
96
+ // Copy data directory (dereference symlinks to copy actual content)
97
+ console.log("šŸ“ Copying data files...");
98
+ const dataOutputDir = join(outputDir, "data");
99
+
100
+ // Check if source and destination are the same (e.g., when --output=.)
101
+ const resolvedDataDir = await realpath(dataDir).catch(() => resolve(dataDir));
102
+ const resolvedDataOutputDir = resolve(dataOutputDir);
103
+
104
+ if (resolvedDataDir === resolvedDataOutputDir) {
105
+ console.log(` āœ“ data/ (already in place)`);
106
+ } else {
107
+ await cp(dataDir, dataOutputDir, { recursive: true, dereference: true });
108
+ console.log(` āœ“ data/ (from ${relative(process.cwd(), dataDir)})`);
109
+ }
110
+
111
+ // Show summary
112
+ console.log(`
113
+ āœ… Site generated successfully!
114
+
115
+ Output: ${outputDir}
116
+
117
+ To serve locally:
118
+ cd ${relative(process.cwd(), outputDir) || "."}
119
+ npx serve .
120
+ `);
121
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Skill CLI Command
3
+ *
4
+ * Handles skill summary, listing, and detail display in the terminal.
5
+ *
6
+ * Usage:
7
+ * npx pathway skill # Summary with stats
8
+ * npx pathway skill --list # IDs only (for piping)
9
+ * npx pathway skill <id> # Detail view
10
+ * npx pathway skill --validate # Validation checks
11
+ */
12
+
13
+ import { createEntityCommand } from "./command-factory.js";
14
+ import { skillToMarkdown } from "../formatters/skill/markdown.js";
15
+ import { prepareSkillsList } from "../formatters/skill/shared.js";
16
+ import { getConceptEmoji } from "../model/levels.js";
17
+ import { formatTable } from "../lib/cli-output.js";
18
+
19
+ /**
20
+ * Format skill summary output
21
+ * @param {Array} skills - Raw skill entities
22
+ * @param {Object} data - Full data context
23
+ */
24
+ function formatSummary(skills, data) {
25
+ const { capabilities, framework } = data;
26
+ const { groups, groupOrder } = prepareSkillsList(skills, capabilities);
27
+ const emoji = framework ? getConceptEmoji(framework, "skill") : "šŸ“š";
28
+
29
+ console.log(`\n${emoji} Skills\n`);
30
+
31
+ // Summary table by capability
32
+ const rows = groupOrder.map((capability) => {
33
+ const count = groups[capability]?.length || 0;
34
+ const withAgent = groups[capability]?.filter((s) => s.agent).length || 0;
35
+ return [capability, count, withAgent];
36
+ });
37
+
38
+ console.log(formatTable(["Capability", "Count", "Agent"], rows));
39
+ console.log(`\nTotal: ${skills.length} skills`);
40
+ console.log(`\nRun 'npx pathway skill --list' for IDs`);
41
+ console.log(`Run 'npx pathway skill <id>' for details\n`);
42
+ }
43
+
44
+ /**
45
+ * Format skill detail output
46
+ * @param {Object} viewAndContext - Contains skill entity and context
47
+ * @param {Object} framework - Framework config
48
+ */
49
+ function formatDetail(viewAndContext, framework) {
50
+ const { skill, disciplines, tracks, drivers, capabilities } = viewAndContext;
51
+ console.log(
52
+ skillToMarkdown(skill, {
53
+ disciplines,
54
+ tracks,
55
+ drivers,
56
+ capabilities,
57
+ framework,
58
+ }),
59
+ );
60
+ }
61
+
62
+ export const runSkillCommand = createEntityCommand({
63
+ entityName: "skill",
64
+ pluralName: "skills",
65
+ findEntity: (data, id) => data.skills.find((s) => s.id === id),
66
+ presentDetail: (entity, data) => ({
67
+ skill: entity,
68
+ disciplines: data.disciplines,
69
+ tracks: data.tracks,
70
+ drivers: data.drivers,
71
+ capabilities: data.capabilities,
72
+ }),
73
+ formatSummary,
74
+ formatDetail,
75
+ emoji: "šŸ“š",
76
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Stage CLI Command
3
+ *
4
+ * Handles stage summary, listing, and detail display in the terminal.
5
+ *
6
+ * Usage:
7
+ * npx pathway stage # Summary with lifecycle flow
8
+ * npx pathway stage --list # IDs only (for piping)
9
+ * npx pathway stage <id> # Detail view
10
+ */
11
+
12
+ import { createEntityCommand } from "./command-factory.js";
13
+ import {
14
+ prepareStageDetail,
15
+ getStageEmoji,
16
+ } from "../formatters/stage/shared.js";
17
+ import { formatTable } from "../lib/cli-output.js";
18
+ import {
19
+ formatHeader,
20
+ formatSubheader,
21
+ formatBullet,
22
+ } from "../lib/cli-output.js";
23
+
24
+ /**
25
+ * Format stage summary output
26
+ * @param {Array} stages - Raw stage entities
27
+ * @param {Object} _data - Full data context (unused)
28
+ */
29
+ function formatSummary(stages, _data) {
30
+ console.log("\nšŸ”„ Stages\n");
31
+
32
+ // Show lifecycle flow
33
+ const flow = stages
34
+ .map((s) => `${getStageEmoji(stages, s.id)} ${s.name}`)
35
+ .join(" → ");
36
+ console.log(`Lifecycle: ${flow}\n`);
37
+
38
+ const rows = stages.map((s) => {
39
+ const toolCount = s.tools?.length || 0;
40
+ const handoffCount = s.handoffs?.length || 0;
41
+ return [s.id, s.name, s.mode, toolCount, handoffCount];
42
+ });
43
+
44
+ console.log(formatTable(["ID", "Name", "Mode", "Tools", "Handoffs"], rows));
45
+ console.log(`\nTotal: ${stages.length} stages`);
46
+ console.log(`\nRun 'npx pathway stage --list' for IDs`);
47
+ console.log(`Run 'npx pathway stage <id>' for details\n`);
48
+ }
49
+
50
+ /**
51
+ * Format stage detail output
52
+ * @param {Object} viewAndContext - Contains stage entity and context
53
+ * @param {Object} _framework - Framework config (unused)
54
+ */
55
+ function formatDetail(viewAndContext, _framework) {
56
+ const { stage, stages } = viewAndContext;
57
+ const view = prepareStageDetail(stage);
58
+ const emoji = getStageEmoji(stages, stage.id);
59
+
60
+ console.log(formatHeader(`\n${emoji} ${view.name}\n`));
61
+ console.log(`Mode: ${view.modeBadge}\n`);
62
+ console.log(`${view.description}\n`);
63
+
64
+ // Tools
65
+ if (view.tools.length > 0) {
66
+ console.log(formatSubheader("Tools\n"));
67
+ for (const tool of view.tools) {
68
+ console.log(formatBullet(`${tool.icon} ${tool.label}`, 1));
69
+ }
70
+ console.log();
71
+ }
72
+
73
+ // Entry criteria
74
+ if (view.entryCriteria.length > 0) {
75
+ console.log(formatSubheader("Entry Criteria\n"));
76
+ for (const item of view.entryCriteria) {
77
+ console.log(formatBullet(item, 1));
78
+ }
79
+ console.log();
80
+ }
81
+
82
+ // Exit criteria
83
+ if (view.exitCriteria.length > 0) {
84
+ console.log(formatSubheader("Exit Criteria\n"));
85
+ for (const item of view.exitCriteria) {
86
+ console.log(formatBullet(item, 1));
87
+ }
88
+ console.log();
89
+ }
90
+
91
+ // Constraints
92
+ if (view.constraints.length > 0) {
93
+ console.log(formatSubheader("Constraints\n"));
94
+ for (const item of view.constraints) {
95
+ console.log(formatBullet(`āš ļø ${item}`, 1));
96
+ }
97
+ console.log();
98
+ }
99
+
100
+ // Handoffs
101
+ if (view.handoffs.length > 0) {
102
+ console.log(formatSubheader("Handoffs\n"));
103
+ for (const handoff of view.handoffs) {
104
+ const targetStage = stages.find((s) => s.id === handoff.target);
105
+ const targetEmoji = getStageEmoji(stages, handoff.target);
106
+ const targetName = targetStage?.name || handoff.target;
107
+ console.log(
108
+ formatBullet(`${targetEmoji} ${handoff.label} → ${targetName}`, 1),
109
+ );
110
+ if (handoff.prompt) {
111
+ console.log(` "${handoff.prompt}"`);
112
+ }
113
+ }
114
+ console.log();
115
+ }
116
+ }
117
+
118
+ export const runStageCommand = createEntityCommand({
119
+ entityName: "stage",
120
+ pluralName: "stages",
121
+ findEntity: (data, id) => data.stages?.find((s) => s.id === id),
122
+ presentDetail: (entity, data) => ({
123
+ stage: entity,
124
+ stages: data.stages || [],
125
+ }),
126
+ formatSummary,
127
+ formatDetail,
128
+ emoji: "šŸ”„",
129
+ });