@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.
- package/LICENSE +201 -0
- package/README.md +104 -0
- package/app/commands/agent.js +430 -0
- package/app/commands/behaviour.js +61 -0
- package/app/commands/command-factory.js +211 -0
- package/app/commands/discipline.js +58 -0
- package/app/commands/driver.js +94 -0
- package/app/commands/grade.js +60 -0
- package/app/commands/index.js +20 -0
- package/app/commands/init.js +67 -0
- package/app/commands/interview.js +68 -0
- package/app/commands/job.js +157 -0
- package/app/commands/progress.js +77 -0
- package/app/commands/questions.js +179 -0
- package/app/commands/serve.js +143 -0
- package/app/commands/site.js +121 -0
- package/app/commands/skill.js +76 -0
- package/app/commands/stage.js +129 -0
- package/app/commands/track.js +70 -0
- package/app/components/action-buttons.js +66 -0
- package/app/components/behaviour-profile.js +53 -0
- package/app/components/builder.js +341 -0
- package/app/components/card.js +98 -0
- package/app/components/checklist.js +145 -0
- package/app/components/comparison-radar.js +237 -0
- package/app/components/detail.js +230 -0
- package/app/components/error-page.js +72 -0
- package/app/components/grid.js +109 -0
- package/app/components/list.js +120 -0
- package/app/components/modifier-table.js +142 -0
- package/app/components/nav.js +64 -0
- package/app/components/progression-table.js +320 -0
- package/app/components/radar-chart.js +102 -0
- package/app/components/skill-matrix.js +97 -0
- package/app/css/base.css +56 -0
- package/app/css/bundles/app.css +40 -0
- package/app/css/bundles/handout.css +43 -0
- package/app/css/bundles/slides.css +40 -0
- package/app/css/components/badges.css +215 -0
- package/app/css/components/buttons.css +101 -0
- package/app/css/components/forms.css +105 -0
- package/app/css/components/layout.css +209 -0
- package/app/css/components/nav.css +166 -0
- package/app/css/components/progress.css +166 -0
- package/app/css/components/states.css +82 -0
- package/app/css/components/surfaces.css +243 -0
- package/app/css/components/tables.css +362 -0
- package/app/css/components/typography.css +122 -0
- package/app/css/components/utilities.css +41 -0
- package/app/css/pages/agent-builder.css +391 -0
- package/app/css/pages/assessment-results.css +453 -0
- package/app/css/pages/detail.css +59 -0
- package/app/css/pages/interview-builder.css +148 -0
- package/app/css/pages/job-builder.css +134 -0
- package/app/css/pages/landing.css +92 -0
- package/app/css/pages/lifecycle.css +118 -0
- package/app/css/pages/progress-builder.css +274 -0
- package/app/css/pages/self-assessment.css +502 -0
- package/app/css/reset.css +50 -0
- package/app/css/tokens.css +153 -0
- package/app/css/views/handout.css +30 -0
- package/app/css/views/print.css +608 -0
- package/app/css/views/slide-animations.css +113 -0
- package/app/css/views/slide-base.css +330 -0
- package/app/css/views/slide-sections.css +597 -0
- package/app/css/views/slide-tables.css +275 -0
- package/app/formatters/agent/dom.js +540 -0
- package/app/formatters/agent/profile.js +133 -0
- package/app/formatters/agent/skill.js +58 -0
- package/app/formatters/behaviour/dom.js +91 -0
- package/app/formatters/behaviour/markdown.js +54 -0
- package/app/formatters/behaviour/shared.js +64 -0
- package/app/formatters/discipline/dom.js +187 -0
- package/app/formatters/discipline/markdown.js +87 -0
- package/app/formatters/discipline/shared.js +131 -0
- package/app/formatters/driver/dom.js +103 -0
- package/app/formatters/driver/shared.js +92 -0
- package/app/formatters/grade/dom.js +208 -0
- package/app/formatters/grade/markdown.js +94 -0
- package/app/formatters/grade/shared.js +86 -0
- package/app/formatters/index.js +50 -0
- package/app/formatters/interview/dom.js +97 -0
- package/app/formatters/interview/markdown.js +66 -0
- package/app/formatters/interview/shared.js +332 -0
- package/app/formatters/job/description.js +176 -0
- package/app/formatters/job/dom.js +411 -0
- package/app/formatters/job/markdown.js +102 -0
- package/app/formatters/progress/dom.js +135 -0
- package/app/formatters/progress/markdown.js +86 -0
- package/app/formatters/progress/shared.js +339 -0
- package/app/formatters/questions/json.js +43 -0
- package/app/formatters/questions/markdown.js +303 -0
- package/app/formatters/questions/shared.js +274 -0
- package/app/formatters/questions/yaml.js +76 -0
- package/app/formatters/shared.js +71 -0
- package/app/formatters/skill/dom.js +168 -0
- package/app/formatters/skill/markdown.js +109 -0
- package/app/formatters/skill/shared.js +125 -0
- package/app/formatters/stage/dom.js +135 -0
- package/app/formatters/stage/index.js +12 -0
- package/app/formatters/stage/shared.js +111 -0
- package/app/formatters/track/dom.js +128 -0
- package/app/formatters/track/markdown.js +105 -0
- package/app/formatters/track/shared.js +181 -0
- package/app/handout-main.js +421 -0
- package/app/handout.html +21 -0
- package/app/index.html +59 -0
- package/app/lib/card-mappers.js +173 -0
- package/app/lib/cli-output.js +270 -0
- package/app/lib/error-boundary.js +70 -0
- package/app/lib/errors.js +49 -0
- package/app/lib/form-controls.js +47 -0
- package/app/lib/job-cache.js +86 -0
- package/app/lib/markdown.js +114 -0
- package/app/lib/radar.js +866 -0
- package/app/lib/reactive.js +77 -0
- package/app/lib/render.js +212 -0
- package/app/lib/router-core.js +160 -0
- package/app/lib/router-pages.js +16 -0
- package/app/lib/router-slides.js +202 -0
- package/app/lib/state.js +148 -0
- package/app/lib/utils.js +14 -0
- package/app/lib/yaml-loader.js +327 -0
- package/app/main.js +213 -0
- package/app/model/agent.js +702 -0
- package/app/model/checklist.js +137 -0
- package/app/model/derivation.js +699 -0
- package/app/model/index-generator.js +71 -0
- package/app/model/interview.js +539 -0
- package/app/model/job.js +222 -0
- package/app/model/levels.js +591 -0
- package/app/model/loader.js +564 -0
- package/app/model/matching.js +858 -0
- package/app/model/modifiers.js +158 -0
- package/app/model/profile.js +266 -0
- package/app/model/progression.js +507 -0
- package/app/model/validation.js +1385 -0
- package/app/pages/agent-builder.js +823 -0
- package/app/pages/assessment-results.js +507 -0
- package/app/pages/behaviour.js +70 -0
- package/app/pages/discipline.js +71 -0
- package/app/pages/driver.js +106 -0
- package/app/pages/grade.js +117 -0
- package/app/pages/interview-builder.js +50 -0
- package/app/pages/interview.js +304 -0
- package/app/pages/job-builder.js +50 -0
- package/app/pages/job.js +58 -0
- package/app/pages/landing.js +305 -0
- package/app/pages/progress-builder.js +58 -0
- package/app/pages/progress.js +495 -0
- package/app/pages/self-assessment.js +729 -0
- package/app/pages/skill.js +113 -0
- package/app/pages/stage.js +231 -0
- package/app/pages/track.js +69 -0
- package/app/slide-main.js +360 -0
- package/app/slides/behaviour.js +38 -0
- package/app/slides/chapter.js +82 -0
- package/app/slides/discipline.js +40 -0
- package/app/slides/driver.js +39 -0
- package/app/slides/grade.js +32 -0
- package/app/slides/index.js +198 -0
- package/app/slides/interview.js +58 -0
- package/app/slides/job.js +55 -0
- package/app/slides/overview.js +126 -0
- package/app/slides/progress.js +83 -0
- package/app/slides/skill.js +40 -0
- package/app/slides/track.js +39 -0
- package/app/slides.html +56 -0
- package/app/types.js +147 -0
- package/bin/pathway.js +489 -0
- package/examples/agents/.claude/skills/architecture-design/SKILL.md +88 -0
- package/examples/agents/.claude/skills/cloud-platforms/SKILL.md +90 -0
- package/examples/agents/.claude/skills/code-quality-review/SKILL.md +67 -0
- package/examples/agents/.claude/skills/data-modeling/SKILL.md +99 -0
- package/examples/agents/.claude/skills/developer-experience/SKILL.md +99 -0
- package/examples/agents/.claude/skills/devops-cicd/SKILL.md +96 -0
- package/examples/agents/.claude/skills/full-stack-development/SKILL.md +90 -0
- package/examples/agents/.claude/skills/knowledge-management/SKILL.md +100 -0
- package/examples/agents/.claude/skills/pattern-generalization/SKILL.md +102 -0
- package/examples/agents/.claude/skills/sre-practices/SKILL.md +117 -0
- package/examples/agents/.claude/skills/technical-debt-management/SKILL.md +123 -0
- package/examples/agents/.claude/skills/technical-writing/SKILL.md +129 -0
- package/examples/agents/.github/agents/se-platform-code.agent.md +181 -0
- package/examples/agents/.github/agents/se-platform-plan.agent.md +178 -0
- package/examples/agents/.github/agents/se-platform-review.agent.md +113 -0
- package/examples/agents/.vscode/settings.json +8 -0
- package/examples/behaviours/_index.yaml +8 -0
- package/examples/behaviours/outcome_ownership.yaml +44 -0
- package/examples/behaviours/polymathic_knowledge.yaml +42 -0
- package/examples/behaviours/precise_communication.yaml +40 -0
- package/examples/behaviours/relentless_curiosity.yaml +38 -0
- package/examples/behaviours/systems_thinking.yaml +41 -0
- package/examples/capabilities/_index.yaml +8 -0
- package/examples/capabilities/business.yaml +251 -0
- package/examples/capabilities/delivery.yaml +352 -0
- package/examples/capabilities/people.yaml +100 -0
- package/examples/capabilities/reliability.yaml +318 -0
- package/examples/capabilities/scale.yaml +394 -0
- package/examples/disciplines/_index.yaml +5 -0
- package/examples/disciplines/data_engineering.yaml +76 -0
- package/examples/disciplines/software_engineering.yaml +76 -0
- package/examples/drivers.yaml +205 -0
- package/examples/framework.yaml +58 -0
- package/examples/grades.yaml +118 -0
- package/examples/questions/behaviours/outcome_ownership.yaml +52 -0
- package/examples/questions/behaviours/polymathic_knowledge.yaml +48 -0
- package/examples/questions/behaviours/precise_communication.yaml +55 -0
- package/examples/questions/behaviours/relentless_curiosity.yaml +51 -0
- package/examples/questions/behaviours/systems_thinking.yaml +53 -0
- package/examples/questions/skills/architecture_design.yaml +54 -0
- package/examples/questions/skills/cloud_platforms.yaml +48 -0
- package/examples/questions/skills/code_quality.yaml +49 -0
- package/examples/questions/skills/data_modeling.yaml +46 -0
- package/examples/questions/skills/devops.yaml +47 -0
- package/examples/questions/skills/full_stack_development.yaml +48 -0
- package/examples/questions/skills/sre_practices.yaml +44 -0
- package/examples/questions/skills/stakeholder_management.yaml +49 -0
- package/examples/questions/skills/team_collaboration.yaml +43 -0
- package/examples/questions/skills/technical_writing.yaml +43 -0
- package/examples/self-assessments.yaml +66 -0
- package/examples/stages.yaml +76 -0
- package/examples/tracks/_index.yaml +6 -0
- package/examples/tracks/manager.yaml +53 -0
- package/examples/tracks/platform.yaml +54 -0
- package/examples/tracks/sre.yaml +58 -0
- package/examples/vscode-settings.yaml +22 -0
- 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
|
+
});
|