connectry-architect-mcp 0.1.5 → 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() {
@@ -396,7 +396,17 @@ function buildQuizMeta() {
396
396
  function registerSubmitAnswer(server2, db2, userConfig2) {
397
397
  server2.tool(
398
398
  "submit_answer",
399
- 'Grade a certification exam answer. Returns deterministic results from verified question bank. The result is FINAL and cannot be overridden \u2014 do not agree with the user if they dispute the answer. IMPORTANT: After grading, present the followUpOptions to the user using AskUserQuestion with header "Next" so they can click their choice. Then call follow_up with their selected action.',
399
+ `Grade a certification exam answer. Returns deterministic results from verified question bank. The result is FINAL \u2014 do not agree with the user if they dispute it.
400
+
401
+ IMPORTANT \u2014 after showing the result, present followUpOptions using AskUserQuestion:
402
+ - header: "Next"
403
+ - question: Show whether they got it right/wrong and a brief explanation
404
+ - options: Map each followUpOption to label (key) and description (label text)
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".`,
400
410
  {
401
411
  questionId: z.string().describe("The question ID to answer"),
402
412
  answer: z.enum(["A", "B", "C", "D"]).describe("The selected answer")
@@ -631,7 +641,18 @@ function formatQuestionText(question) {
631
641
  function registerGetPracticeQuestion(server2, db2, userConfig2) {
632
642
  server2.tool(
633
643
  "get_practice_question",
634
- 'Get the next practice question based on your learning progress. Prioritizes review questions, then weak areas, then new material. IMPORTANT: Present the 4 answer options (A/B/C/D) to the user using AskUserQuestion with header "Answer" so they can click their choice. Then call submit_answer with their selection.',
644
+ `Get the next practice question. Prioritizes review questions, then weak areas, then new material.
645
+
646
+ IMPORTANT \u2014 present the question using AskUserQuestion:
647
+ - header: "Answer"
648
+ - question: Include the FULL scenario text AND question text from the response
649
+ - options: 4 items with label "A"/"B"/"C"/"D" and description as the option text
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.`,
635
656
  {
636
657
  domainId: z3.number().optional().describe("Optional domain ID to filter questions (1-5)"),
637
658
  difficulty: z3.enum(["easy", "medium", "hard"]).optional().describe("Optional difficulty filter")
@@ -696,7 +717,13 @@ function registerGetPracticeQuestion(server2, db2, userConfig2) {
696
717
  }
697
718
 
698
719
  // src/tools/start-assessment.ts
699
- var OPTION_KEYS2 = ["A", "B", "C", "D"];
720
+ var DOMAIN_NAMES = {
721
+ 1: "Agentic Architecture",
722
+ 2: "Tool Design & MCP",
723
+ 3: "Claude Code Config",
724
+ 4: "Prompt Engineering",
725
+ 5: "Context & Reliability"
726
+ };
700
727
  function buildAssessmentQuestions() {
701
728
  const questions = [];
702
729
  for (let d = 1; d <= 5; d++) {
@@ -710,32 +737,38 @@ function buildAssessmentQuestions() {
710
737
  }
711
738
  return questions;
712
739
  }
713
- function formatQuestion(question, index, total) {
714
- const domainNames = {
715
- 1: "Agentic Architecture",
716
- 2: "Tool Design & MCP",
717
- 3: "Claude Code Config",
718
- 4: "Prompt Engineering",
719
- 5: "Context & Reliability"
720
- };
721
- const lines = [
722
- `**Assessment Question ${index + 1} of ${total}**`,
723
- `Domain ${question.domainId}: ${domainNames[question.domainId] ?? "Unknown"} | ${question.difficulty}`,
724
- "",
725
- "---",
726
- "",
727
- question.scenario,
728
- "",
729
- `**${question.text}**`,
730
- "",
731
- ...OPTION_KEYS2.map((key) => ` **${key}.** ${question.options[key]}`)
732
- ];
733
- return lines.join("\n");
734
- }
735
740
  function registerStartAssessment(server2, db2, userConfig2) {
736
741
  server2.tool(
737
742
  "start_assessment",
738
- 'Start the initial assessment. Returns ONE question at a time. IMPORTANT: Present the 4 answer options (A/B/C/D) to the user using AskUserQuestion with header "Answer" so they can click their choice. After the user selects, call submit_answer with their choice, then call start_assessment again for the next question. Repeats for all 15 questions (3 per domain). When assessment is complete, present next steps (Study Plan, Practice Questions, Capstone Build, Reference Projects) using AskUserQuestion with header "Next step".',
743
+ `Start the initial assessment. Returns ONE question at a time (15 total, 3 per domain).
744
+
745
+ IMPORTANT \u2014 follow this flow for EVERY question:
746
+
747
+ 1. Check if "isNewDomain" is true. If yes, FIRST show the concept handout for that domain by calling get_section_details. Tell the user: "Let's learn about [domain] before testing your knowledge." After showing the handout, proceed to step 2.
748
+
749
+ 2. Present the question to the user using AskUserQuestion:
750
+ - header: "Q[number]"
751
+ - question: Include the FULL scenario text AND question text from the response
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
754
+
755
+ 3. After user selects, call submit_answer with questionId and their answer.
756
+
757
+ 4. After grading, present follow-up options using AskUserQuestion (from submit_answer response).
758
+
759
+ 5. Call start_assessment again for the next question.
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
+
771
+ When assessment is complete, present next steps using AskUserQuestion with header "Next step".`,
739
772
  {},
740
773
  async () => {
741
774
  const userId = userConfig2.userId;
@@ -743,7 +776,7 @@ function registerStartAssessment(server2, db2, userConfig2) {
743
776
  const questions = buildAssessmentQuestions();
744
777
  if (questions.length === 0) {
745
778
  return {
746
- content: [{ type: "text", text: "No assessment questions available." }]
779
+ content: [{ type: "text", text: JSON.stringify({ error: "No assessment questions available." }) }]
747
780
  };
748
781
  }
749
782
  const answeredIds = db2.prepare(
@@ -760,78 +793,56 @@ function registerStartAssessment(server2, db2, userConfig2) {
760
793
  const totalCorrect = results.reduce((sum, r) => sum + r.correct, 0);
761
794
  const totalQuestions = results.reduce((sum, r) => sum + r.total, 0);
762
795
  const overallAccuracy = totalQuestions > 0 ? Math.round(totalCorrect / totalQuestions * 100) : 0;
763
- const path6 = overallAccuracy >= 60 ? "exam-weighted" : "beginner-friendly";
764
- db2.prepare("UPDATE users SET assessmentCompleted = TRUE, learningPath = ? WHERE id = ?").run(path6, userId);
765
- const domainNames = {
766
- 1: "Agentic Architecture",
767
- 2: "Tool Design & MCP",
768
- 3: "Claude Code Config",
769
- 4: "Prompt Engineering",
770
- 5: "Context & Reliability"
796
+ const path7 = overallAccuracy >= 60 ? "exam-weighted" : "beginner-friendly";
797
+ db2.prepare("UPDATE users SET assessmentCompleted = TRUE, learningPath = ? WHERE id = ?").run(path7, userId);
798
+ const response2 = {
799
+ status: "complete",
800
+ overall: { correct: totalCorrect, total: totalQuestions, accuracy: overallAccuracy },
801
+ learningPath: path7 === "exam-weighted" ? "Exam-Weighted" : "Beginner-Friendly",
802
+ domainResults: results.map((r) => ({
803
+ domain: r.domainId,
804
+ name: DOMAIN_NAMES[r.domainId] ?? "",
805
+ correct: r.correct,
806
+ total: r.total,
807
+ accuracy: Math.round(r.correct / r.total * 100)
808
+ })),
809
+ nextStepOptions: [
810
+ { label: "Study Plan", description: "Get personalized study recommendations based on your results" },
811
+ { label: "Practice Questions", description: "Start adaptive practice targeting your weak areas" },
812
+ { label: "Capstone Build", description: "Build your own project while learning all 30 task statements" },
813
+ { label: "Reference Projects", description: "Explore runnable code examples for each domain" }
814
+ ],
815
+ instruction: 'Present nextStepOptions using AskUserQuestion with header "Next step".'
771
816
  };
772
- const lines = [
773
- "**Assessment Complete!**",
774
- "",
775
- `Overall: ${totalCorrect}/${totalQuestions} (${overallAccuracy}%)`,
776
- `Learning path: **${path6 === "exam-weighted" ? "Exam-Weighted" : "Beginner-Friendly"}**`,
777
- "",
778
- "**Per-domain results:**",
779
- "",
780
- ...results.map((r) => ` D${r.domainId} ${domainNames[r.domainId] ?? ""}: ${r.correct}/${r.total} (${Math.round(r.correct / r.total * 100)}%)`),
781
- "",
782
- "---",
783
- "",
784
- 'INSTRUCTION: Present the following options to the user using AskUserQuestion with header "Next step":',
785
- '- "Study Plan" \u2014 Get personalized study recommendations based on your results',
786
- '- "Practice Questions" \u2014 Start adaptive practice targeting your weak areas',
787
- '- "Capstone Build" \u2014 Build your own project while learning all 30 task statements',
788
- '- "Reference Projects" \u2014 Explore runnable code examples for each domain'
789
- ];
790
817
  return {
791
- content: [{ type: "text", text: lines.join("\n") }]
818
+ content: [{ type: "text", text: JSON.stringify(response2, null, 2) }]
792
819
  };
793
820
  }
794
821
  const questionIndex = answeredSet.size;
795
- const questionText = formatQuestion(nextQuestion, questionIndex, questions.length);
796
- const elicitOptions = OPTION_KEYS2.map((key) => ({
797
- value: key,
798
- title: `${key}. ${nextQuestion.options[key]}`
799
- }));
800
- const selected = await elicitSingleSelect(
801
- server2,
802
- questionText,
803
- "answer",
804
- elicitOptions
805
- );
806
- if (selected) {
807
- return {
808
- content: [{
809
- type: "text",
810
- text: [
811
- questionText,
812
- "",
813
- "---",
814
- "",
815
- `**Selected: ${selected}**`,
816
- "",
817
- `Submit this answer with submit_answer using questionId "${nextQuestion.id}" and answer "${selected}". Then call start_assessment again for the next question.`
818
- ].join("\n")
819
- }]
820
- };
821
- }
822
+ const previousDomainIds = questions.filter((q) => answeredSet.has(q.id)).map((q) => q.domainId);
823
+ const isNewDomain = !previousDomainIds.includes(nextQuestion.domainId);
824
+ const response = {
825
+ status: "question",
826
+ questionNumber: questionIndex + 1,
827
+ totalQuestions: questions.length,
828
+ questionId: nextQuestion.id,
829
+ domainId: nextQuestion.domainId,
830
+ domainName: DOMAIN_NAMES[nextQuestion.domainId] ?? "Unknown",
831
+ difficulty: nextQuestion.difficulty,
832
+ taskStatement: nextQuestion.taskStatement,
833
+ isNewDomain,
834
+ scenario: nextQuestion.scenario,
835
+ questionText: nextQuestion.text,
836
+ options: {
837
+ A: nextQuestion.options.A,
838
+ B: nextQuestion.options.B,
839
+ C: nextQuestion.options.C,
840
+ D: nextQuestion.options.D
841
+ },
842
+ instruction: isNewDomain ? `This is a NEW domain (${DOMAIN_NAMES[nextQuestion.domainId]}). Show the concept handout first using get_section_details for task "${nextQuestion.taskStatement}", then present this question using AskUserQuestion. Put the scenario + question in the "question" field. Use options with label "A"/"B"/"C"/"D" and description as the option text.` : `Present this question using AskUserQuestion with header "Q${questionIndex + 1}". Put the scenario + question text in the "question" field. Use options with label "A"/"B"/"C"/"D" and description as the option text.`
843
+ };
822
844
  return {
823
- content: [{
824
- type: "text",
825
- text: [
826
- questionText,
827
- "",
828
- "---",
829
- "",
830
- `Question ID: ${nextQuestion.id}`,
831
- "",
832
- "Submit your answer (A, B, C, or D) with submit_answer, then call start_assessment again for the next question."
833
- ].join("\n")
834
- }]
845
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
835
846
  };
836
847
  }
837
848
  );
@@ -870,11 +881,11 @@ function registerGetWeakAreas(server2, db2, userConfig2) {
870
881
  // src/engine/adaptive-path.ts
871
882
  var BEGINNER_ORDER = [3, 4, 2, 1, 5];
872
883
  var EXAM_WEIGHTED_ORDER = [1, 3, 4, 2, 5];
873
- function getDomainOrder(path6) {
874
- return path6 === "beginner-friendly" ? BEGINNER_ORDER : EXAM_WEIGHTED_ORDER;
884
+ function getDomainOrder(path7) {
885
+ return path7 === "beginner-friendly" ? BEGINNER_ORDER : EXAM_WEIGHTED_ORDER;
875
886
  }
876
- function getNextRecommendedDomain(path6, masteryByDomain) {
877
- const order = getDomainOrder(path6);
887
+ function getNextRecommendedDomain(path7, masteryByDomain) {
888
+ const order = getDomainOrder(path7);
878
889
  for (const domainId of order) {
879
890
  const masteries = masteryByDomain.get(domainId) ?? [];
880
891
  const avgAccuracy = masteries.length > 0 ? masteries.reduce((sum, m) => sum + m.accuracyPercent, 0) / masteries.length : 0;
@@ -905,7 +916,11 @@ function estimateTimeRemaining(totalQuestions, answeredQuestions, avgSecondsPerQ
905
916
  function registerGetStudyPlan(server2, db2, userConfig2) {
906
917
  server2.tool(
907
918
  "get_study_plan",
908
- "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.`,
909
924
  {},
910
925
  async () => {
911
926
  const userId = userConfig2.userId;
@@ -916,20 +931,20 @@ function registerGetStudyPlan(server2, db2, userConfig2) {
916
931
  const overdueReviews = getOverdueReviews(db2, userId);
917
932
  const stats = getTotalStats(db2, userId);
918
933
  const allQuestions = loadQuestions();
919
- const path6 = user?.learningPath ?? "beginner-friendly";
934
+ const path7 = user?.learningPath ?? "beginner-friendly";
920
935
  const masteryByDomain = /* @__PURE__ */ new Map();
921
936
  for (const m of mastery) {
922
937
  const existing = masteryByDomain.get(m.domainId) ?? [];
923
938
  masteryByDomain.set(m.domainId, [...existing, m]);
924
939
  }
925
- const nextDomain = getNextRecommendedDomain(path6, masteryByDomain);
926
- const domainOrder = getDomainOrder(path6);
940
+ const nextDomain = getNextRecommendedDomain(path7, masteryByDomain);
941
+ const domainOrder = getDomainOrder(path7);
927
942
  const timeEstimate = estimateTimeRemaining(allQuestions.length, stats.total);
928
943
  const domain = curriculum.domains.find((d) => d.id === nextDomain);
929
944
  const lines = [
930
945
  "\u2550\u2550\u2550 YOUR STUDY PLAN \u2550\u2550\u2550",
931
946
  "",
932
- `Learning Path: ${path6}`,
947
+ `Learning Path: ${path7}`,
933
948
  `Estimated Time Remaining: ${timeEstimate}`,
934
949
  "",
935
950
  `Next Recommended Domain: D${nextDomain} \u2014 ${domain?.title ?? "Unknown"}`,
@@ -953,8 +968,8 @@ import fs4 from "fs";
953
968
  import path4 from "path";
954
969
  import { fileURLToPath as fileURLToPath2 } from "url";
955
970
  import { z as z4 } from "zod";
956
- var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
957
- var PROJECTS_DIR = path4.resolve(__dirname2, "..", "..", "projects");
971
+ var __dirname3 = path4.dirname(fileURLToPath2(import.meta.url));
972
+ var PROJECTS_DIR = path4.resolve(__dirname3, "..", "..", "projects");
958
973
  var PROJECTS = [
959
974
  { id: "capstone", name: "Capstone \u2014 Multi-Agent Research System", domains: [1, 2, 3, 4, 5] },
960
975
  { id: "d1-agentic", name: "D1 Mini \u2014 Agentic Loop", domains: [1] },
@@ -1201,7 +1216,20 @@ function rowToExamAttempt(row) {
1201
1216
  function registerStartPracticeExam(server2, db2, userConfig2) {
1202
1217
  server2.tool(
1203
1218
  "start_practice_exam",
1204
- 'Start a full 60-question practice exam simulating the real Claude Certified Architect \u2014 Foundations exam. Questions are weighted by domain (D1: 16, D2: 11, D3: 12, D4: 12, D5: 9). Scored out of 1000, passing is 720. Results are saved for comparison across attempts. IMPORTANT: Present the 4 answer options (A/B/C/D) using AskUserQuestion with header "Answer" so the user can click their choice.',
1219
+ `Start a full 60-question practice exam (D1:16, D2:11, D3:12, D4:12, D5:9). Scored 0-1000, passing 720.
1220
+
1221
+ IMPORTANT \u2014 present the first question using AskUserQuestion:
1222
+ - header: "Q1"
1223
+ - question: Include the FULL scenario + question text
1224
+ - options: 4 items with label "A"/"B"/"C"/"D" and description as option text
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.`,
1205
1233
  {},
1206
1234
  async () => {
1207
1235
  const userId = userConfig2.userId;
@@ -1295,7 +1323,13 @@ import { z as z6 } from "zod";
1295
1323
  function registerSubmitExamAnswer(server2, db2, userConfig2) {
1296
1324
  server2.tool(
1297
1325
  "submit_exam_answer",
1298
- 'Submit an answer for a practice exam question. The answer is graded deterministically. After all 60 questions, the exam is scored and saved. DO NOT soften results \u2014 relay the grading output verbatim. IMPORTANT: When presenting the next question, use AskUserQuestion with header "Answer" to let the user click A/B/C/D.',
1326
+ `Submit an answer for a practice exam question. Graded deterministically. DO NOT soften results.
1327
+
1328
+ IMPORTANT \u2014 if there's a next question, present it using AskUserQuestion:
1329
+ - header: "Q[number]"
1330
+ - question: Include the FULL scenario + question text
1331
+ - options: 4 items with label "A"/"B"/"C"/"D" and description as option text
1332
+ Then call submit_exam_answer again with the answer.`,
1299
1333
  {
1300
1334
  examId: z6.number().describe("The practice exam ID"),
1301
1335
  questionId: z6.string().describe("The question ID being answered"),
@@ -1657,7 +1691,7 @@ function registerFollowUp(server2, _db, _userConfig) {
1657
1691
  import { z as z8 } from "zod";
1658
1692
 
1659
1693
  // src/data/criteria.ts
1660
- var DOMAIN_NAMES = {
1694
+ var DOMAIN_NAMES2 = {
1661
1695
  1: "Agentic Architecture & Orchestration",
1662
1696
  2: "Tool Design & MCP Integration",
1663
1697
  3: "Claude Code Configuration & Workflows",
@@ -1670,49 +1704,49 @@ var CRITERIA = [
1670
1704
  id: "1.1",
1671
1705
  title: "Design and implement agentic loops for autonomous task execution",
1672
1706
  domain: 1,
1673
- domainName: DOMAIN_NAMES[1],
1707
+ domainName: DOMAIN_NAMES2[1],
1674
1708
  description: "Understanding the agentic loop lifecycle: sending requests, inspecting stop_reason, executing tools, and returning results."
1675
1709
  },
1676
1710
  {
1677
1711
  id: "1.2",
1678
1712
  title: "Orchestrate multi-agent systems with coordinator-subagent patterns",
1679
1713
  domain: 1,
1680
- domainName: DOMAIN_NAMES[1],
1714
+ domainName: DOMAIN_NAMES2[1],
1681
1715
  description: "Hub-and-spoke architecture, isolated context, task decomposition, and result aggregation."
1682
1716
  },
1683
1717
  {
1684
1718
  id: "1.3",
1685
1719
  title: "Configure subagent invocation, context passing, and spawning",
1686
1720
  domain: 1,
1687
- domainName: DOMAIN_NAMES[1],
1721
+ domainName: DOMAIN_NAMES2[1],
1688
1722
  description: "Task tool, allowedTools, explicit context passing, parallel subagent execution."
1689
1723
  },
1690
1724
  {
1691
1725
  id: "1.4",
1692
1726
  title: "Implement multi-step workflows with enforcement and handoff patterns",
1693
1727
  domain: 1,
1694
- domainName: DOMAIN_NAMES[1],
1728
+ domainName: DOMAIN_NAMES2[1],
1695
1729
  description: "Programmatic enforcement vs prompt-based guidance, structured handoff protocols."
1696
1730
  },
1697
1731
  {
1698
1732
  id: "1.5",
1699
1733
  title: "Apply Agent SDK hooks for tool call interception and data normalization",
1700
1734
  domain: 1,
1701
- domainName: DOMAIN_NAMES[1],
1735
+ domainName: DOMAIN_NAMES2[1],
1702
1736
  description: "PostToolUse hooks, tool call interception, deterministic vs probabilistic compliance."
1703
1737
  },
1704
1738
  {
1705
1739
  id: "1.6",
1706
1740
  title: "Design task decomposition strategies for complex workflows",
1707
1741
  domain: 1,
1708
- domainName: DOMAIN_NAMES[1],
1742
+ domainName: DOMAIN_NAMES2[1],
1709
1743
  description: "Prompt chaining vs dynamic decomposition, per-file analysis vs cross-file integration."
1710
1744
  },
1711
1745
  {
1712
1746
  id: "1.7",
1713
1747
  title: "Manage session state, resumption, and forking",
1714
1748
  domain: 1,
1715
- domainName: DOMAIN_NAMES[1],
1749
+ domainName: DOMAIN_NAMES2[1],
1716
1750
  description: "Named sessions, fork_session, structured summaries vs stale context."
1717
1751
  },
1718
1752
  // Domain 2: Tool Design & MCP Integration
@@ -1720,35 +1754,35 @@ var CRITERIA = [
1720
1754
  id: "2.1",
1721
1755
  title: "Design effective tool interfaces with clear descriptions and boundaries",
1722
1756
  domain: 2,
1723
- domainName: DOMAIN_NAMES[2],
1757
+ domainName: DOMAIN_NAMES2[2],
1724
1758
  description: "Tool descriptions as selection mechanism, disambiguation, splitting vs consolidating."
1725
1759
  },
1726
1760
  {
1727
1761
  id: "2.2",
1728
1762
  title: "Implement structured error responses for MCP tools",
1729
1763
  domain: 2,
1730
- domainName: DOMAIN_NAMES[2],
1764
+ domainName: DOMAIN_NAMES2[2],
1731
1765
  description: "isError flag, error categories, retryable vs non-retryable, structured metadata."
1732
1766
  },
1733
1767
  {
1734
1768
  id: "2.3",
1735
1769
  title: "Distribute tools appropriately across agents and configure tool choice",
1736
1770
  domain: 2,
1737
- domainName: DOMAIN_NAMES[2],
1771
+ domainName: DOMAIN_NAMES2[2],
1738
1772
  description: "Scoped tool access, tool_choice options, forced selection patterns."
1739
1773
  },
1740
1774
  {
1741
1775
  id: "2.4",
1742
1776
  title: "Integrate MCP servers into Claude Code and agent workflows",
1743
1777
  domain: 2,
1744
- domainName: DOMAIN_NAMES[2],
1778
+ domainName: DOMAIN_NAMES2[2],
1745
1779
  description: "Project vs user scope, .mcp.json, environment variable expansion, MCP resources."
1746
1780
  },
1747
1781
  {
1748
1782
  id: "2.5",
1749
1783
  title: "Select and apply built-in tools effectively",
1750
1784
  domain: 2,
1751
- domainName: DOMAIN_NAMES[2],
1785
+ domainName: DOMAIN_NAMES2[2],
1752
1786
  description: "Grep vs Glob vs Read/Write/Edit, incremental codebase understanding."
1753
1787
  },
1754
1788
  // Domain 3: Claude Code Configuration & Workflows
@@ -1756,42 +1790,42 @@ var CRITERIA = [
1756
1790
  id: "3.1",
1757
1791
  title: "Configure CLAUDE.md files with appropriate hierarchy and scoping",
1758
1792
  domain: 3,
1759
- domainName: DOMAIN_NAMES[3],
1793
+ domainName: DOMAIN_NAMES2[3],
1760
1794
  description: "User-level, project-level, directory-level, @import syntax, .claude/rules/."
1761
1795
  },
1762
1796
  {
1763
1797
  id: "3.2",
1764
1798
  title: "Create and configure custom slash commands and skills",
1765
1799
  domain: 3,
1766
- domainName: DOMAIN_NAMES[3],
1800
+ domainName: DOMAIN_NAMES2[3],
1767
1801
  description: "Project vs user scope, context: fork, allowed-tools, argument-hint frontmatter."
1768
1802
  },
1769
1803
  {
1770
1804
  id: "3.3",
1771
1805
  title: "Apply path-specific rules for conditional convention loading",
1772
1806
  domain: 3,
1773
- domainName: DOMAIN_NAMES[3],
1807
+ domainName: DOMAIN_NAMES2[3],
1774
1808
  description: "YAML frontmatter paths, glob patterns, conditional activation."
1775
1809
  },
1776
1810
  {
1777
1811
  id: "3.4",
1778
1812
  title: "Determine when to use plan mode vs direct execution",
1779
1813
  domain: 3,
1780
- domainName: DOMAIN_NAMES[3],
1814
+ domainName: DOMAIN_NAMES2[3],
1781
1815
  description: "Complexity assessment, architectural decisions, Explore subagent."
1782
1816
  },
1783
1817
  {
1784
1818
  id: "3.5",
1785
1819
  title: "Apply iterative refinement techniques for progressive improvement",
1786
1820
  domain: 3,
1787
- domainName: DOMAIN_NAMES[3],
1821
+ domainName: DOMAIN_NAMES2[3],
1788
1822
  description: "Input/output examples, test-driven iteration, interview pattern."
1789
1823
  },
1790
1824
  {
1791
1825
  id: "3.6",
1792
1826
  title: "Integrate Claude Code into CI/CD pipelines",
1793
1827
  domain: 3,
1794
- domainName: DOMAIN_NAMES[3],
1828
+ domainName: DOMAIN_NAMES2[3],
1795
1829
  description: "-p flag, --output-format json, --json-schema, session context isolation."
1796
1830
  },
1797
1831
  // Domain 4: Prompt Engineering & Structured Output
@@ -1799,42 +1833,42 @@ var CRITERIA = [
1799
1833
  id: "4.1",
1800
1834
  title: "Design prompts with explicit criteria to improve precision",
1801
1835
  domain: 4,
1802
- domainName: DOMAIN_NAMES[4],
1836
+ domainName: DOMAIN_NAMES2[4],
1803
1837
  description: "Explicit criteria vs vague instructions, false positive management."
1804
1838
  },
1805
1839
  {
1806
1840
  id: "4.2",
1807
1841
  title: "Apply few-shot prompting to improve output consistency",
1808
1842
  domain: 4,
1809
- domainName: DOMAIN_NAMES[4],
1843
+ domainName: DOMAIN_NAMES2[4],
1810
1844
  description: "Targeted examples, ambiguous case handling, format demonstration."
1811
1845
  },
1812
1846
  {
1813
1847
  id: "4.3",
1814
1848
  title: "Enforce structured output using tool use and JSON schemas",
1815
1849
  domain: 4,
1816
- domainName: DOMAIN_NAMES[4],
1850
+ domainName: DOMAIN_NAMES2[4],
1817
1851
  description: "tool_use with schemas, tool_choice options, nullable fields, enum patterns."
1818
1852
  },
1819
1853
  {
1820
1854
  id: "4.4",
1821
1855
  title: "Implement validation, retry, and feedback loops",
1822
1856
  domain: 4,
1823
- domainName: DOMAIN_NAMES[4],
1857
+ domainName: DOMAIN_NAMES2[4],
1824
1858
  description: "Retry-with-error-feedback, limits of retry, detected_pattern tracking."
1825
1859
  },
1826
1860
  {
1827
1861
  id: "4.5",
1828
1862
  title: "Design efficient batch processing strategies",
1829
1863
  domain: 4,
1830
- domainName: DOMAIN_NAMES[4],
1864
+ domainName: DOMAIN_NAMES2[4],
1831
1865
  description: "Message Batches API, latency tolerance, custom_id, failure handling."
1832
1866
  },
1833
1867
  {
1834
1868
  id: "4.6",
1835
1869
  title: "Design multi-instance and multi-pass review architectures",
1836
1870
  domain: 4,
1837
- domainName: DOMAIN_NAMES[4],
1871
+ domainName: DOMAIN_NAMES2[4],
1838
1872
  description: "Self-review limitations, independent review instances, per-file + cross-file passes."
1839
1873
  },
1840
1874
  // Domain 5: Context Management & Reliability
@@ -1842,42 +1876,42 @@ var CRITERIA = [
1842
1876
  id: "5.1",
1843
1877
  title: "Manage conversation context to preserve critical information",
1844
1878
  domain: 5,
1845
- domainName: DOMAIN_NAMES[5],
1879
+ domainName: DOMAIN_NAMES2[5],
1846
1880
  description: "Progressive summarization risks, lost-in-the-middle, tool output trimming."
1847
1881
  },
1848
1882
  {
1849
1883
  id: "5.2",
1850
1884
  title: "Design effective escalation and ambiguity resolution patterns",
1851
1885
  domain: 5,
1852
- domainName: DOMAIN_NAMES[5],
1886
+ domainName: DOMAIN_NAMES2[5],
1853
1887
  description: "Escalation triggers, customer preferences, sentiment unreliability."
1854
1888
  },
1855
1889
  {
1856
1890
  id: "5.3",
1857
1891
  title: "Implement error propagation strategies across multi-agent systems",
1858
1892
  domain: 5,
1859
- domainName: DOMAIN_NAMES[5],
1893
+ domainName: DOMAIN_NAMES2[5],
1860
1894
  description: "Structured error context, access failures vs empty results, partial results."
1861
1895
  },
1862
1896
  {
1863
1897
  id: "5.4",
1864
1898
  title: "Manage context effectively in large codebase exploration",
1865
1899
  domain: 5,
1866
- domainName: DOMAIN_NAMES[5],
1900
+ domainName: DOMAIN_NAMES2[5],
1867
1901
  description: "Context degradation, scratchpad files, subagent delegation, /compact."
1868
1902
  },
1869
1903
  {
1870
1904
  id: "5.5",
1871
1905
  title: "Design human review workflows and confidence calibration",
1872
1906
  domain: 5,
1873
- domainName: DOMAIN_NAMES[5],
1907
+ domainName: DOMAIN_NAMES2[5],
1874
1908
  description: "Stratified sampling, field-level confidence, accuracy by document type."
1875
1909
  },
1876
1910
  {
1877
1911
  id: "5.6",
1878
1912
  title: "Preserve information provenance and handle uncertainty in synthesis",
1879
1913
  domain: 5,
1880
- domainName: DOMAIN_NAMES[5],
1914
+ domainName: DOMAIN_NAMES2[5],
1881
1915
  description: "Claim-source mappings, conflict annotation, temporal data handling."
1882
1916
  }
1883
1917
  ];
@@ -2447,7 +2481,20 @@ User selected next action: ${selected}` }]
2447
2481
  function registerCapstoneBuildStep(server2, db2, userConfig2) {
2448
2482
  server2.tool(
2449
2483
  "capstone_build_step",
2450
- '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.`,
2451
2498
  {
2452
2499
  action: z9.enum(ACTIONS).describe("The build action: confirm, quiz, build, next, status, or abandon")
2453
2500
  },
@@ -2626,6 +2673,249 @@ function registerCapstoneBuildStatus(server2, db2, userConfig2) {
2626
2673
  );
2627
2674
  }
2628
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
+
2629
2919
  // src/tools/index.ts
2630
2920
  function registerTools(server2, db2, userConfig2) {
2631
2921
  registerSubmitAnswer(server2, db2, userConfig2);
@@ -2645,6 +2935,7 @@ function registerTools(server2, db2, userConfig2) {
2645
2935
  registerStartCapstoneBuild(server2, db2, userConfig2);
2646
2936
  registerCapstoneBuildStep(server2, db2, userConfig2);
2647
2937
  registerCapstoneBuildStatus(server2, db2, userConfig2);
2938
+ registerDashboard(server2, db2, userConfig2);
2648
2939
  }
2649
2940
 
2650
2941
  // src/prompts/index.ts
@@ -2792,8 +3083,8 @@ Choose (1-2):` }
2792
3083
  }
2793
3084
 
2794
3085
  // src/resources/index.ts
2795
- import fs5 from "fs";
2796
- import path5 from "path";
3086
+ import fs6 from "fs";
3087
+ import path6 from "path";
2797
3088
  import { fileURLToPath as fileURLToPath4 } from "url";
2798
3089
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2799
3090
 
@@ -2801,17 +3092,17 @@ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2801
3092
  import { readFileSync } from "fs";
2802
3093
  import { resolve, dirname } from "path";
2803
3094
  import { fileURLToPath as fileURLToPath3 } from "url";
2804
- var __dirname3 = dirname(fileURLToPath3(import.meta.url));
3095
+ var __dirname4 = dirname(fileURLToPath3(import.meta.url));
2805
3096
  var quizWidgetHtml = null;
2806
3097
  function getQuizWidgetHtml() {
2807
3098
  if (!quizWidgetHtml) {
2808
- quizWidgetHtml = readFileSync(resolve(__dirname3, "../ui/quiz-widget.html"), "utf-8");
3099
+ quizWidgetHtml = readFileSync(resolve(__dirname4, "../ui/quiz-widget.html"), "utf-8");
2809
3100
  }
2810
3101
  return quizWidgetHtml;
2811
3102
  }
2812
3103
 
2813
3104
  // src/resources/index.ts
2814
- var __dirname4 = path5.dirname(fileURLToPath4(import.meta.url));
3105
+ var __dirname5 = path6.dirname(fileURLToPath4(import.meta.url));
2815
3106
  function registerResources(server2, db2, userConfig2) {
2816
3107
  server2.resource(
2817
3108
  "handout",
@@ -2859,8 +3150,8 @@ function registerResources(server2, db2, userConfig2) {
2859
3150
  { mimeType: "text/markdown" },
2860
3151
  async (uri, { projectId }) => {
2861
3152
  const id = projectId;
2862
- const projectPath = path5.join(__dirname4, "..", "..", "projects", id, "README.md");
2863
- 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.`;
2864
3155
  return {
2865
3156
  contents: [{ uri: uri.href, text: content, mimeType: "text/markdown" }]
2866
3157
  };