connectry-architect-mcp 0.1.6 → 0.1.7

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/dist/index.js CHANGED
@@ -254,8 +254,8 @@ import fs3 from "fs";
254
254
  import path3 from "path";
255
255
  import { fileURLToPath } from "url";
256
256
  var __filename = fileURLToPath(import.meta.url);
257
- var __dirname = path3.dirname(__filename);
258
- var DATA_DIR = fs3.existsSync(path3.join(__dirname, "data", "curriculum.json")) ? path3.join(__dirname, "data") : __dirname;
257
+ var __dirname2 = path3.dirname(__filename);
258
+ var DATA_DIR = fs3.existsSync(path3.join(__dirname2, "data", "curriculum.json")) ? path3.join(__dirname2, "data") : __dirname2;
259
259
  var cachedCurriculum = null;
260
260
  var cachedQuestions = null;
261
261
  function loadCurriculum() {
@@ -402,7 +402,11 @@ IMPORTANT \u2014 after showing the result, present followUpOptions using AskUser
402
402
  - header: "Next"
403
403
  - question: Show whether they got it right/wrong and a brief explanation
404
404
  - options: Map each followUpOption to label (key) and description (label text)
405
- Then call follow_up with questionId and the selected action key.`,
405
+ Then call follow_up with questionId and the selected action key.
406
+
407
+ EDGE CASES:
408
+ - "Other": Answer the user's question about this answer, then re-present the SAME follow-up options via AskUserQuestion.
409
+ - "Skip": Treat as "next question" \u2014 call follow_up with action "next".`,
406
410
  {
407
411
  questionId: z.string().describe("The question ID to answer"),
408
412
  answer: z.enum(["A", "B", "C", "D"]).describe("The selected answer")
@@ -643,7 +647,12 @@ IMPORTANT \u2014 present the question using AskUserQuestion:
643
647
  - header: "Answer"
644
648
  - question: Include the FULL scenario text AND question text from the response
645
649
  - options: 4 items with label "A"/"B"/"C"/"D" and description as the option text
646
- Then call submit_answer with the questionId and selected answer.`,
650
+ - If the scenario contains code, add a "preview" field on each option showing the code snippet
651
+ Then call submit_answer with the questionId and selected answer.
652
+
653
+ EDGE CASES:
654
+ - "Other": Answer the user's question, then re-present the SAME question via AskUserQuestion.
655
+ - "Skip": Call get_practice_question again for a new question. Never break the flow.`,
647
656
  {
648
657
  domainId: z3.number().optional().describe("Optional domain ID to filter questions (1-5)"),
649
658
  difficulty: z3.enum(["easy", "medium", "hard"]).optional().describe("Optional difficulty filter")
@@ -741,6 +750,7 @@ IMPORTANT \u2014 follow this flow for EVERY question:
741
750
  - header: "Q[number]"
742
751
  - question: Include the FULL scenario text AND question text from the response
743
752
  - options: Use the 4 answer options (A/B/C/D) with label as the letter and description as the option text
753
+ - If the scenario contains code, add a "preview" field on each option showing the relevant code snippet so the user can reference it while choosing
744
754
 
745
755
  3. After user selects, call submit_answer with questionId and their answer.
746
756
 
@@ -748,6 +758,16 @@ IMPORTANT \u2014 follow this flow for EVERY question:
748
758
 
749
759
  5. Call start_assessment again for the next question.
750
760
 
761
+ EDGE CASES:
762
+ - If user selects "Other" and types a question/comment: Answer their question helpfully, then re-present the SAME quiz question using AskUserQuestion again. Never lose the current question.
763
+ - If user clicks "Skip": Treat it as moving to the next question. Call start_assessment again immediately. The skipped question remains unanswered and will appear again later.
764
+ - NEVER let Other or Skip break the assessment flow. Always continue to the next question or re-ask the current one.
765
+
766
+ PROGRESS TRACKING:
767
+ - At the START of the assessment, create a TodoWrite checklist with all 15 questions (Q1-Q15) grouped by domain, all set to "pending".
768
+ - After each answer, update the corresponding todo item to "completed" (with correct/incorrect note).
769
+ - This gives the user a visual progress tracker throughout the assessment.
770
+
751
771
  When assessment is complete, present next steps using AskUserQuestion with header "Next step".`,
752
772
  {},
753
773
  async () => {
@@ -773,12 +793,12 @@ When assessment is complete, present next steps using AskUserQuestion with heade
773
793
  const totalCorrect = results.reduce((sum, r) => sum + r.correct, 0);
774
794
  const totalQuestions = results.reduce((sum, r) => sum + r.total, 0);
775
795
  const overallAccuracy = totalQuestions > 0 ? Math.round(totalCorrect / totalQuestions * 100) : 0;
776
- const path6 = overallAccuracy >= 60 ? "exam-weighted" : "beginner-friendly";
777
- db2.prepare("UPDATE users SET assessmentCompleted = TRUE, learningPath = ? WHERE id = ?").run(path6, userId);
796
+ const path7 = overallAccuracy >= 60 ? "exam-weighted" : "beginner-friendly";
797
+ db2.prepare("UPDATE users SET assessmentCompleted = TRUE, learningPath = ? WHERE id = ?").run(path7, userId);
778
798
  const response2 = {
779
799
  status: "complete",
780
800
  overall: { correct: totalCorrect, total: totalQuestions, accuracy: overallAccuracy },
781
- learningPath: path6 === "exam-weighted" ? "Exam-Weighted" : "Beginner-Friendly",
801
+ learningPath: path7 === "exam-weighted" ? "Exam-Weighted" : "Beginner-Friendly",
782
802
  domainResults: results.map((r) => ({
783
803
  domain: r.domainId,
784
804
  name: DOMAIN_NAMES[r.domainId] ?? "",
@@ -861,11 +881,11 @@ function registerGetWeakAreas(server2, db2, userConfig2) {
861
881
  // src/engine/adaptive-path.ts
862
882
  var BEGINNER_ORDER = [3, 4, 2, 1, 5];
863
883
  var EXAM_WEIGHTED_ORDER = [1, 3, 4, 2, 5];
864
- function getDomainOrder(path6) {
865
- return path6 === "beginner-friendly" ? BEGINNER_ORDER : EXAM_WEIGHTED_ORDER;
884
+ function getDomainOrder(path7) {
885
+ return path7 === "beginner-friendly" ? BEGINNER_ORDER : EXAM_WEIGHTED_ORDER;
866
886
  }
867
- function getNextRecommendedDomain(path6, masteryByDomain) {
868
- const order = getDomainOrder(path6);
887
+ function getNextRecommendedDomain(path7, masteryByDomain) {
888
+ const order = getDomainOrder(path7);
869
889
  for (const domainId of order) {
870
890
  const masteries = masteryByDomain.get(domainId) ?? [];
871
891
  const avgAccuracy = masteries.length > 0 ? masteries.reduce((sum, m) => sum + m.accuracyPercent, 0) / masteries.length : 0;
@@ -896,7 +916,11 @@ function estimateTimeRemaining(totalQuestions, answeredQuestions, avgSecondsPerQ
896
916
  function registerGetStudyPlan(server2, db2, userConfig2) {
897
917
  server2.tool(
898
918
  "get_study_plan",
899
- "Get a personalized study plan based on your assessment results, weak areas, and learning path.",
919
+ `Get a personalized study plan based on your assessment results, weak areas, and learning path.
920
+
921
+ IMPORTANT \u2014 after showing the study plan, use AskUserQuestion with header "Focus" and multiSelect: true to let the user pick which domains they want to focus on. Options should be the 5 domains with their current mastery as descriptions. Then use their selection to filter get_practice_question calls.
922
+
923
+ Also use TodoWrite to create a study checklist showing each recommended topic with status (pending/in_progress/completed) so the user can track progress visually.`,
900
924
  {},
901
925
  async () => {
902
926
  const userId = userConfig2.userId;
@@ -907,20 +931,20 @@ function registerGetStudyPlan(server2, db2, userConfig2) {
907
931
  const overdueReviews = getOverdueReviews(db2, userId);
908
932
  const stats = getTotalStats(db2, userId);
909
933
  const allQuestions = loadQuestions();
910
- const path6 = user?.learningPath ?? "beginner-friendly";
934
+ const path7 = user?.learningPath ?? "beginner-friendly";
911
935
  const masteryByDomain = /* @__PURE__ */ new Map();
912
936
  for (const m of mastery) {
913
937
  const existing = masteryByDomain.get(m.domainId) ?? [];
914
938
  masteryByDomain.set(m.domainId, [...existing, m]);
915
939
  }
916
- const nextDomain = getNextRecommendedDomain(path6, masteryByDomain);
917
- const domainOrder = getDomainOrder(path6);
940
+ const nextDomain = getNextRecommendedDomain(path7, masteryByDomain);
941
+ const domainOrder = getDomainOrder(path7);
918
942
  const timeEstimate = estimateTimeRemaining(allQuestions.length, stats.total);
919
943
  const domain = curriculum.domains.find((d) => d.id === nextDomain);
920
944
  const lines = [
921
945
  "\u2550\u2550\u2550 YOUR STUDY PLAN \u2550\u2550\u2550",
922
946
  "",
923
- `Learning Path: ${path6}`,
947
+ `Learning Path: ${path7}`,
924
948
  `Estimated Time Remaining: ${timeEstimate}`,
925
949
  "",
926
950
  `Next Recommended Domain: D${nextDomain} \u2014 ${domain?.title ?? "Unknown"}`,
@@ -944,8 +968,8 @@ import fs4 from "fs";
944
968
  import path4 from "path";
945
969
  import { fileURLToPath as fileURLToPath2 } from "url";
946
970
  import { z as z4 } from "zod";
947
- var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
948
- var PROJECTS_DIR = path4.resolve(__dirname2, "..", "..", "projects");
971
+ var __dirname3 = path4.dirname(fileURLToPath2(import.meta.url));
972
+ var PROJECTS_DIR = path4.resolve(__dirname3, "..", "..", "projects");
949
973
  var PROJECTS = [
950
974
  { id: "capstone", name: "Capstone \u2014 Multi-Agent Research System", domains: [1, 2, 3, 4, 5] },
951
975
  { id: "d1-agentic", name: "D1 Mini \u2014 Agentic Loop", domains: [1] },
@@ -1198,7 +1222,14 @@ IMPORTANT \u2014 present the first question using AskUserQuestion:
1198
1222
  - header: "Q1"
1199
1223
  - question: Include the FULL scenario + question text
1200
1224
  - options: 4 items with label "A"/"B"/"C"/"D" and description as option text
1201
- Then call submit_exam_answer with the answer.`,
1225
+ - If code in scenario, add preview field on options
1226
+ Then call submit_exam_answer with the answer.
1227
+
1228
+ PROGRESS TRACKING: Create a TodoWrite checklist "Practice Exam Q1-Q60" grouped by domain, all "pending". Update each to "completed" after grading.
1229
+
1230
+ EDGE CASES:
1231
+ - "Other": Answer the question, re-present the SAME exam question via AskUserQuestion.
1232
+ - "Skip": Move to next exam question without grading. Never break the flow.`,
1202
1233
  {},
1203
1234
  async () => {
1204
1235
  const userId = userConfig2.userId;
@@ -2450,7 +2481,20 @@ User selected next action: ${selected}` }]
2450
2481
  function registerCapstoneBuildStep(server2, db2, userConfig2) {
2451
2482
  server2.tool(
2452
2483
  "capstone_build_step",
2453
- 'Drive your guided capstone build \u2014 quiz, build, and advance through 18 progressive steps. IMPORTANT: When presenting quiz questions, use AskUserQuestion with header "Answer" for A/B/C/D selection. When presenting action choices (quiz/build/next), use AskUserQuestion with header "Action".',
2484
+ `Drive your guided capstone build \u2014 quiz, build, and advance through 18 progressive steps.
2485
+
2486
+ IMPORTANT:
2487
+ - When presenting quiz questions, use AskUserQuestion with header "Answer" for A/B/C/D selection. If code is in the scenario, add preview fields.
2488
+ - When presenting action choices (quiz/build/next), use AskUserQuestion with header "Action".
2489
+
2490
+ PROGRESS TRACKING:
2491
+ - On "confirm": Create a TodoWrite checklist with all 18 build steps, all set to "pending".
2492
+ - On "next": Update the completed step to "completed" and the new current step to "in_progress".
2493
+ - This gives the user a visual build progress tracker.
2494
+
2495
+ EDGE CASES:
2496
+ - "Other": Answer the question, then re-present the current options via AskUserQuestion.
2497
+ - "Skip": During quiz, treat as moving to the build phase. During build, treat as advancing to next step.`,
2454
2498
  {
2455
2499
  action: z9.enum(ACTIONS).describe("The build action: confirm, quiz, build, next, status, or abandon")
2456
2500
  },
@@ -2629,6 +2673,249 @@ function registerCapstoneBuildStatus(server2, db2, userConfig2) {
2629
2673
  );
2630
2674
  }
2631
2675
 
2676
+ // src/ui/server.ts
2677
+ import http from "http";
2678
+ import fs5 from "fs";
2679
+ import path5 from "path";
2680
+ var DOMAIN_NAMES3 = {
2681
+ 1: "Agentic Architecture",
2682
+ 2: "Tool Design & MCP",
2683
+ 3: "Claude Code Config",
2684
+ 4: "Prompt Engineering",
2685
+ 5: "Context & Reliability"
2686
+ };
2687
+ var ALL_DOMAIN_IDS = [1, 2, 3, 4, 5];
2688
+ function buildDashboardData(db2, userId) {
2689
+ const domainRows = db2.prepare(`
2690
+ SELECT domainId,
2691
+ SUM(totalAttempts) as totalAttempts,
2692
+ AVG(accuracyPercent) as avgAccuracy,
2693
+ MIN(masteryLevel) as masteryLevel,
2694
+ COUNT(*) as taskCount
2695
+ FROM domain_mastery
2696
+ WHERE userId = ?
2697
+ GROUP BY domainId
2698
+ ORDER BY domainId ASC
2699
+ `).all(userId);
2700
+ const domainMap = new Map(domainRows.map((r) => [r.domainId, r]));
2701
+ const domains = ALL_DOMAIN_IDS.map((id) => {
2702
+ const row = domainMap.get(id);
2703
+ return {
2704
+ id,
2705
+ name: DOMAIN_NAMES3[id] ?? `Domain ${id}`,
2706
+ mastery: row ? Math.round(row.avgAccuracy) : 0,
2707
+ level: row ? deriveDomainLevel(row.avgAccuracy) : "unassessed",
2708
+ answered: row ? row.totalAttempts : 0,
2709
+ total: row ? row.taskCount : 0
2710
+ };
2711
+ });
2712
+ const overallReadiness = domains.length > 0 ? Math.round(domains.reduce((sum, d) => sum + d.mastery, 0) / domains.length) : 0;
2713
+ const examRows = db2.prepare(`
2714
+ SELECT completedAt, score, passed
2715
+ FROM exam_attempts
2716
+ WHERE userId = ? AND completedAt IS NOT NULL
2717
+ ORDER BY completedAt DESC
2718
+ `).all(userId);
2719
+ const examHistory = examRows.map((r) => ({
2720
+ date: r.completedAt,
2721
+ score: r.score,
2722
+ passed: Boolean(r.passed)
2723
+ }));
2724
+ const answerRows = db2.prepare(`
2725
+ SELECT questionId, domainId, isCorrect, answeredAt
2726
+ FROM answers
2727
+ WHERE userId = ?
2728
+ ORDER BY answeredAt DESC
2729
+ LIMIT 10
2730
+ `).all(userId);
2731
+ const recentActivity = answerRows.map((r) => ({
2732
+ questionId: r.questionId,
2733
+ domain: DOMAIN_NAMES3[r.domainId] ?? `Domain ${r.domainId}`,
2734
+ correct: Boolean(r.isCorrect),
2735
+ timestamp: r.answeredAt
2736
+ }));
2737
+ const capstoneRow = db2.prepare(`
2738
+ SELECT id, theme, currentStep
2739
+ FROM capstone_builds
2740
+ WHERE userId = ? AND status IN ('shaping', 'building')
2741
+ ORDER BY createdAt DESC
2742
+ LIMIT 1
2743
+ `).get(userId);
2744
+ let capstoneBuild = null;
2745
+ if (capstoneRow) {
2746
+ const steps = db2.prepare(`
2747
+ SELECT buildCompleted, taskStatements
2748
+ FROM capstone_build_steps
2749
+ WHERE buildId = ?
2750
+ ORDER BY stepIndex ASC
2751
+ `).all(capstoneRow.id);
2752
+ const coveredCriteria = /* @__PURE__ */ new Set();
2753
+ for (const step of steps) {
2754
+ if (!step.buildCompleted) continue;
2755
+ const taskStatements = JSON.parse(step.taskStatements);
2756
+ for (const ts of taskStatements) {
2757
+ coveredCriteria.add(ts);
2758
+ }
2759
+ }
2760
+ capstoneBuild = {
2761
+ theme: capstoneRow.theme,
2762
+ currentStep: capstoneRow.currentStep,
2763
+ totalSteps: BUILD_STEPS.length,
2764
+ criteriaCompleted: coveredCriteria.size,
2765
+ totalCriteria: BUILD_STEPS.reduce((sum, s) => sum + s.taskStatements.length, 0)
2766
+ };
2767
+ }
2768
+ return { overallReadiness, domains, examHistory, recentActivity, capstoneBuild };
2769
+ }
2770
+ function deriveDomainLevel(avgAccuracy) {
2771
+ if (avgAccuracy >= 90) return "mastered";
2772
+ if (avgAccuracy >= 70) return "strong";
2773
+ if (avgAccuracy >= 50) return "developing";
2774
+ if (avgAccuracy > 0) return "weak";
2775
+ return "unassessed";
2776
+ }
2777
+ var MIME_TYPES = {
2778
+ ".html": "text/html; charset=utf-8",
2779
+ ".css": "text/css; charset=utf-8",
2780
+ ".js": "application/javascript; charset=utf-8",
2781
+ ".json": "application/json; charset=utf-8",
2782
+ ".png": "image/png",
2783
+ ".svg": "image/svg+xml"
2784
+ };
2785
+ function getMimeType(filePath) {
2786
+ const ext = path5.extname(filePath).toLowerCase();
2787
+ return MIME_TYPES[ext] ?? "application/octet-stream";
2788
+ }
2789
+ function startDashboardServer(db2, userConfig2) {
2790
+ const distUiDir = path5.resolve(__dirname, "../../dist/ui");
2791
+ const userId = userConfig2.userId;
2792
+ const server2 = http.createServer((req, res) => {
2793
+ const url = new URL(req.url ?? "/", `http://localhost`);
2794
+ const pathname = url.pathname;
2795
+ res.setHeader("Access-Control-Allow-Origin", "*");
2796
+ if (pathname === "/api/data") {
2797
+ const data = buildDashboardData(db2, userId);
2798
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
2799
+ res.end(JSON.stringify(data));
2800
+ return;
2801
+ }
2802
+ if (pathname === "/dashboard" || pathname === "/") {
2803
+ const htmlPath = path5.join(distUiDir, "dashboard.html");
2804
+ let html;
2805
+ try {
2806
+ html = fs5.readFileSync(htmlPath, "utf-8");
2807
+ } catch {
2808
+ res.writeHead(404, { "Content-Type": "text/plain" });
2809
+ res.end("dashboard.html not found");
2810
+ return;
2811
+ }
2812
+ const data = buildDashboardData(db2, userId);
2813
+ const injection = `<script>window.__DASHBOARD_DATA__ = ${JSON.stringify(data)};</script>`;
2814
+ const injectedHtml = html.replace("</head>", `${injection}
2815
+ </head>`);
2816
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
2817
+ res.end(injectedHtml);
2818
+ return;
2819
+ }
2820
+ const safePath = path5.normalize(pathname).replace(/^(\.\.[/\\])+/, "");
2821
+ const filePath = path5.join(distUiDir, safePath);
2822
+ if (!filePath.startsWith(distUiDir)) {
2823
+ res.writeHead(403, { "Content-Type": "text/plain" });
2824
+ res.end("Forbidden");
2825
+ return;
2826
+ }
2827
+ try {
2828
+ const content = fs5.readFileSync(filePath);
2829
+ res.writeHead(200, { "Content-Type": getMimeType(filePath) });
2830
+ res.end(content);
2831
+ } catch {
2832
+ res.writeHead(404, { "Content-Type": "text/plain" });
2833
+ res.end("Not found");
2834
+ }
2835
+ });
2836
+ return new Promise((resolve2, reject) => {
2837
+ server2.on("error", reject);
2838
+ server2.listen(0, "127.0.0.1", () => {
2839
+ const addr = server2.address();
2840
+ if (!addr || typeof addr === "string") {
2841
+ reject(new Error("Failed to get server address"));
2842
+ return;
2843
+ }
2844
+ resolve2({
2845
+ port: addr.port,
2846
+ close: () => server2.close()
2847
+ });
2848
+ });
2849
+ });
2850
+ }
2851
+
2852
+ // src/tools/dashboard.ts
2853
+ var cachedServer = null;
2854
+ function registerDashboard(server2, db2, userConfig2) {
2855
+ server2.tool(
2856
+ "get_dashboard",
2857
+ 'Open the study progress dashboard in Claude Preview. Shows mastery levels, exam history, activity timeline, and capstone progress.\n\nIMPORTANT: After getting the URL, use the preview_start tool to open it in Claude Preview. If the user says "show dashboard" or "open dashboard", call this tool.',
2858
+ {},
2859
+ async () => {
2860
+ if (!cachedServer) {
2861
+ cachedServer = await startDashboardServer(db2, userConfig2);
2862
+ }
2863
+ const url = `http://127.0.0.1:${cachedServer.port}/dashboard`;
2864
+ const summary = buildTextSummary(db2, userConfig2.userId);
2865
+ return {
2866
+ content: [
2867
+ {
2868
+ type: "text",
2869
+ text: `Dashboard ready at: ${url}
2870
+
2871
+ Use preview_start to open this URL in Claude Preview.
2872
+
2873
+ ${summary}`
2874
+ }
2875
+ ]
2876
+ };
2877
+ }
2878
+ );
2879
+ }
2880
+ function buildTextSummary(db2, userId) {
2881
+ const DOMAIN_NAMES4 = {
2882
+ 1: "Agentic Architecture",
2883
+ 2: "Tool Design & MCP",
2884
+ 3: "Claude Code Config",
2885
+ 4: "Prompt Engineering",
2886
+ 5: "Context & Reliability"
2887
+ };
2888
+ const domainRows = db2.prepare(`
2889
+ SELECT domainId, AVG(accuracyPercent) as avgAccuracy, SUM(totalAttempts) as totalAttempts
2890
+ FROM domain_mastery
2891
+ WHERE userId = ?
2892
+ GROUP BY domainId
2893
+ ORDER BY domainId ASC
2894
+ `).all(userId);
2895
+ const domainMap = new Map(domainRows.map((r) => [r.domainId, r]));
2896
+ const domainLines = [1, 2, 3, 4, 5].map((id) => {
2897
+ const row = domainMap.get(id);
2898
+ const mastery = row ? Math.round(row.avgAccuracy) : 0;
2899
+ const answered = row ? row.totalAttempts : 0;
2900
+ return ` D${id}: ${DOMAIN_NAMES4[id]} \u2014 ${mastery}% mastery, ${answered} answered`;
2901
+ });
2902
+ const overallMastery = domainRows.length > 0 ? Math.round(domainRows.reduce((sum, r) => sum + r.avgAccuracy, 0) / 5) : 0;
2903
+ const examStats = db2.prepare(`
2904
+ SELECT COUNT(*) as total, SUM(CASE WHEN passed THEN 1 ELSE 0 END) as passed
2905
+ FROM exam_attempts
2906
+ WHERE userId = ? AND completedAt IS NOT NULL
2907
+ `).get(userId);
2908
+ return [
2909
+ "--- TEXT SUMMARY ---",
2910
+ `Overall Readiness: ${overallMastery}%`,
2911
+ "",
2912
+ "Domain Progress:",
2913
+ ...domainLines,
2914
+ "",
2915
+ `Practice Exams: ${examStats.total} taken, ${examStats.passed ?? 0} passed`
2916
+ ].join("\n");
2917
+ }
2918
+
2632
2919
  // src/tools/index.ts
2633
2920
  function registerTools(server2, db2, userConfig2) {
2634
2921
  registerSubmitAnswer(server2, db2, userConfig2);
@@ -2648,6 +2935,7 @@ function registerTools(server2, db2, userConfig2) {
2648
2935
  registerStartCapstoneBuild(server2, db2, userConfig2);
2649
2936
  registerCapstoneBuildStep(server2, db2, userConfig2);
2650
2937
  registerCapstoneBuildStatus(server2, db2, userConfig2);
2938
+ registerDashboard(server2, db2, userConfig2);
2651
2939
  }
2652
2940
 
2653
2941
  // src/prompts/index.ts
@@ -2795,8 +3083,8 @@ Choose (1-2):` }
2795
3083
  }
2796
3084
 
2797
3085
  // src/resources/index.ts
2798
- import fs5 from "fs";
2799
- import path5 from "path";
3086
+ import fs6 from "fs";
3087
+ import path6 from "path";
2800
3088
  import { fileURLToPath as fileURLToPath4 } from "url";
2801
3089
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2802
3090
 
@@ -2804,17 +3092,17 @@ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2804
3092
  import { readFileSync } from "fs";
2805
3093
  import { resolve, dirname } from "path";
2806
3094
  import { fileURLToPath as fileURLToPath3 } from "url";
2807
- var __dirname3 = dirname(fileURLToPath3(import.meta.url));
3095
+ var __dirname4 = dirname(fileURLToPath3(import.meta.url));
2808
3096
  var quizWidgetHtml = null;
2809
3097
  function getQuizWidgetHtml() {
2810
3098
  if (!quizWidgetHtml) {
2811
- quizWidgetHtml = readFileSync(resolve(__dirname3, "../ui/quiz-widget.html"), "utf-8");
3099
+ quizWidgetHtml = readFileSync(resolve(__dirname4, "../ui/quiz-widget.html"), "utf-8");
2812
3100
  }
2813
3101
  return quizWidgetHtml;
2814
3102
  }
2815
3103
 
2816
3104
  // src/resources/index.ts
2817
- var __dirname4 = path5.dirname(fileURLToPath4(import.meta.url));
3105
+ var __dirname5 = path6.dirname(fileURLToPath4(import.meta.url));
2818
3106
  function registerResources(server2, db2, userConfig2) {
2819
3107
  server2.resource(
2820
3108
  "handout",
@@ -2862,8 +3150,8 @@ function registerResources(server2, db2, userConfig2) {
2862
3150
  { mimeType: "text/markdown" },
2863
3151
  async (uri, { projectId }) => {
2864
3152
  const id = projectId;
2865
- const projectPath = path5.join(__dirname4, "..", "..", "projects", id, "README.md");
2866
- const content = fs5.existsSync(projectPath) ? fs5.readFileSync(projectPath, "utf-8") : `Reference project "${id}" is not yet available. It will be added in the content creation phase.`;
3153
+ const projectPath = path6.join(__dirname5, "..", "..", "projects", id, "README.md");
3154
+ const content = fs6.existsSync(projectPath) ? fs6.readFileSync(projectPath, "utf-8") : `Reference project "${id}" is not yet available. It will be added in the content creation phase.`;
2867
3155
  return {
2868
3156
  contents: [{ uri: uri.href, text: content, mimeType: "text/markdown" }]
2869
3157
  };