@a-company/paradigm 3.28.0 → 3.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/dist/{accept-orchestration-6EM5EHXA.js → accept-orchestration-ZUWQUHSK.js} +6 -6
  2. package/dist/add-VSPZ6FM4.js +81 -0
  3. package/dist/{aggregate-M5WMUI6B.js → aggregate-SV3VGEIL.js} +2 -2
  4. package/dist/assess-UHBDYIK7.js +68 -0
  5. package/dist/{beacon-XL2ALH5O.js → beacon-3SJV4DAP.js} +2 -2
  6. package/dist/calibration-WWHK73WU.js +135 -0
  7. package/dist/{chunk-C5ZE6WEX.js → chunk-2SKXFXIT.js} +91 -1
  8. package/dist/{chunk-AK5M6KJB.js → chunk-36TKPM5Z.js} +20 -2
  9. package/dist/{chunk-W4VFKZVF.js → chunk-7COU5S2Z.js} +3 -3
  10. package/dist/{chunk-3BAMPB6I.js → chunk-7WEKMZ46.js} +2 -147
  11. package/dist/{chunk-SCC77UUP.js → chunk-AKIMFN6I.js} +3 -3
  12. package/dist/{chunk-3DYYXGDC.js → chunk-CDMAMDSG.js} +33 -0
  13. package/dist/{chunk-7IJ5JVKT.js → chunk-CZEIK3Y2.js} +913 -40
  14. package/dist/{chunk-MRENOFTR.js → chunk-EDOAWN7J.js} +6 -1
  15. package/dist/chunk-F3BCHPYT.js +143 -0
  16. package/dist/chunk-GT5QGC2H.js +253 -0
  17. package/dist/{chunk-N6RNYCZD.js → chunk-HIKKOCXY.js} +1 -1
  18. package/dist/{chunk-J26YQVAK.js → chunk-J4E6K5MG.js} +1 -1
  19. package/dist/chunk-L27I3CPZ.js +357 -0
  20. package/dist/{chunk-KWDTBXP2.js → chunk-LHLIAYQ3.js} +1 -1
  21. package/dist/{chunk-OXG5GVDJ.js → chunk-P7XSBJE3.js} +1 -1
  22. package/dist/{chunk-Z7W7HNRG.js → chunk-QDXI2DHR.js} +1 -1
  23. package/dist/{chunk-BRILIG7Z.js → chunk-QIOCFXDQ.js} +42 -0
  24. package/dist/{chunk-ZOH24ZPF.js → chunk-QWA26UNO.js} +7 -7
  25. package/dist/{lore-server-ILPHKWLK.js → chunk-RAB5IKPR.js} +77 -112
  26. package/dist/{chunk-BKMNLROM.js → chunk-RGFANZ4Q.js} +448 -147
  27. package/dist/{chunk-R2SGQ22F.js → chunk-YW5OCVKB.js} +448 -2
  28. package/dist/{chunk-6P4IFIK2.js → chunk-ZGUAAVMA.js} +53 -4
  29. package/dist/{commands-6ZVTD74M.js → commands-LEPFD7S5.js} +452 -1
  30. package/dist/config-schema-3YNIFJCJ.js +152 -0
  31. package/dist/{constellation-NXU6Q2HM.js → constellation-FAGT45TU.js} +2 -2
  32. package/dist/{context-audit-RI4R2WRH.js → context-audit-557EO6PK.js} +138 -8
  33. package/dist/{cost-CTGSLSOC.js → cost-UD3WPEKZ.js} +1 -1
  34. package/dist/{cursorrules-XBWFX66V.js → cursorrules-3TKZ4E4R.js} +2 -2
  35. package/dist/{delete-YTASL4SM.js → delete-RRK4RL6Y.js} +1 -1
  36. package/dist/{diff-AH7L4PRQ.js → diff-IP5CIARP.js} +6 -6
  37. package/dist/{dist-AG5JNIZU-HW2FWNTZ.js → dist-5QE2BB2B-X6DYVSUL.js} +59 -5
  38. package/dist/{dist-KY5HGDDL.js → dist-OGTSAZ55.js} +58 -4
  39. package/dist/{dist-IKBGY7FQ.js → dist-RVKYUCRU.js} +3 -1
  40. package/dist/{dist-7U64HDSC.js → dist-UXWV4OKX.js} +8 -2
  41. package/dist/{dist-RMAIFRTW.js → dist-Y7I3CFY5.js} +5 -3
  42. package/dist/{doctor-INBOLZC7.js → doctor-GKZJU7QG.js} +1 -1
  43. package/dist/{edit-S7NZD7H7.js → edit-4CLNN5JG.js} +1 -1
  44. package/dist/{graph-ERNQQQ7C.js → graph-YYUXI3F7.js} +1 -1
  45. package/dist/graph-server-ZPXRSGCW.js +116 -0
  46. package/dist/{habits-7BORPC2F.js → habits-O37HTUKE.js} +2 -2
  47. package/dist/index.js +207 -89
  48. package/dist/integrity-MK2OP5TA.js +194 -0
  49. package/dist/integrity-checker-J7YXRTBT.js +11 -0
  50. package/dist/{lint-53GPXKKI.js → lint-HYWGS3JJ.js} +1 -1
  51. package/dist/{list-QTFWN35D.js → list-BTLFHSRC.js} +1 -1
  52. package/dist/list-IUCYPGMK.js +57 -0
  53. package/dist/{lore-loader-S5BXMH27.js → lore-loader-VTEEZDX3.js} +3 -1
  54. package/dist/lore-server-NOOAHKJX.js +118 -0
  55. package/dist/mcp.js +2616 -112
  56. package/dist/migrate-FQVGQNXZ.js +889 -0
  57. package/dist/{migrate-assessments-FPR6C35Z.js → migrate-assessments-JP6Q5KME.js} +1 -1
  58. package/dist/{orchestrate-HMSQ2CED.js → orchestrate-A226N6FC.js} +6 -6
  59. package/dist/platform-server-KK4OCRTV.js +891 -0
  60. package/dist/{probe-SN4BNXOC.js → probe-7JK7IDNI.js} +5 -5
  61. package/dist/{providers-YW3FG6DA.js → providers-YNFSL6HK.js} +1 -1
  62. package/dist/quiz-I75NU2QQ.js +99 -0
  63. package/dist/{record-UGN75GTB.js → record-46CLR4OG.js} +11 -2
  64. package/dist/{reindex-YG3KIXAK.js → reindex-NZQRGKPN.js} +3 -2
  65. package/dist/{remember-IEBQHXHZ.js → remember-4EUZKIIB.js} +1 -1
  66. package/dist/{retag-URLJLMSK.js → retag-KC4JVRLE.js} +1 -1
  67. package/dist/{review-725ZKA7U.js → review-Q7M4CRB5.js} +1 -1
  68. package/dist/{ripple-DFMXLFWI.js → ripple-RI3LOT6R.js} +2 -2
  69. package/dist/{sentinel-FUR3QKCJ.js → sentinel-BKYTBT7M.js} +1 -1
  70. package/dist/sentinel-bridge-IZTXYS5M.js +109 -0
  71. package/dist/sentinel-ui/assets/{index-Zh1YM0C9.css → index-CJ1Wx083.css} +1 -1
  72. package/dist/sentinel-ui/assets/index-S1VJ67dT.js +62 -0
  73. package/dist/sentinel-ui/assets/index-S1VJ67dT.js.map +1 -0
  74. package/dist/sentinel-ui/index.html +2 -2
  75. package/dist/sentinel.js +6 -6
  76. package/dist/{serve-DIALBCTU.js → serve-22A4XOIG.js} +1 -1
  77. package/dist/{university-A66BMZ4Z.js → serve-2YJ6D2Y6.js} +9 -8
  78. package/dist/serve-3V2WXLGM.js +33 -0
  79. package/dist/{server-2VICPDUR.js → server-OFEJ2HJP.js} +25 -2
  80. package/dist/{server-OWBK2WFS.js → server-RDLQ3DK7.js} +49 -4
  81. package/dist/{setup-HOI52TN3.js → setup-M2ZKLKNN.js} +4 -4
  82. package/dist/{shift-DRF5M3G6.js → shift-LNMKFYLR.js} +73 -14
  83. package/dist/{show-GEVVQWWG.js → show-P7GYO43X.js} +1 -1
  84. package/dist/show-PKZMYKRN.js +82 -0
  85. package/dist/{snapshot-XHINQBZS.js → snapshot-Y3COXK4T.js} +2 -2
  86. package/dist/{spawn-DIY7T4QW.js → spawn-SSXZX45U.js} +2 -2
  87. package/dist/status-KLHALGW4.js +71 -0
  88. package/dist/{summary-NV7SBV5O.js → summary-5NQNOD3F.js} +2 -2
  89. package/dist/{sweep-5POCF2E4.js → sweep-EZU3GU6S.js} +1 -1
  90. package/dist/symphony-ROEKK7VD.js +999 -0
  91. package/dist/{team-YOGT2Q2X.js → team-HGLJXWQG.js} +7 -7
  92. package/dist/{timeline-RKXNRMKF.js → timeline-ANC7LVDL.js} +1 -1
  93. package/dist/{triage-GJ6GK647.js → triage-POXJ2TIX.js} +2 -2
  94. package/dist/university-content/courses/.purpose +7 -1
  95. package/dist/university-content/courses/para-101.json +53 -0
  96. package/dist/university-content/courses/para-501.json +166 -0
  97. package/dist/university-content/plsat/.purpose +6 -0
  98. package/dist/university-content/plsat/v3.0.json +400 -1
  99. package/dist/university-content/reference.json +48 -0
  100. package/dist/university-ui/assets/{index-TcsCEBMo.js → index-tfi5xN4Q.js} +2 -2
  101. package/dist/university-ui/assets/{index-TcsCEBMo.js.map → index-tfi5xN4Q.js.map} +1 -1
  102. package/dist/university-ui/index.html +1 -1
  103. package/dist/{upgrade-65QOQXRC.js → upgrade-ANX3LVSA.js} +1 -0
  104. package/dist/validate-GD5XWILV.js +134 -0
  105. package/dist/{validate-TKKRGJKC.js → validate-ZVPNN4FL.js} +1 -1
  106. package/dist/{workspace-L27RR5MF.js → workspace-UIUTHZTD.js} +6 -6
  107. package/package.json +4 -2
  108. package/platform-ui/dist/assets/GitSection-C-GQWHcu.css +1 -0
  109. package/platform-ui/dist/assets/GitSection-DvyJBF_-.js +4 -0
  110. package/platform-ui/dist/assets/GraphSection-BiQrXqfs.js +8 -0
  111. package/platform-ui/dist/assets/GraphSection-BlgXTl53.css +1 -0
  112. package/platform-ui/dist/assets/LoreSection-BaH1FaRb.js +1 -0
  113. package/platform-ui/dist/assets/LoreSection-C3EixkjW.css +1 -0
  114. package/platform-ui/dist/assets/SentinelSection-BI-aIYKL.css +1 -0
  115. package/platform-ui/dist/assets/SentinelSection-DemAznjI.js +1 -0
  116. package/platform-ui/dist/assets/index-CfpZFjea.css +1 -0
  117. package/platform-ui/dist/assets/index-DDKhCt-w.js +57 -0
  118. package/platform-ui/dist/index.html +14 -0
  119. package/dist/graph-server-BZ73HTAT.js +0 -251
  120. package/dist/sentinel-ui/assets/index-C_Wstm64.js +0 -62
  121. package/dist/sentinel-ui/assets/index-C_Wstm64.js.map +0 -1
  122. /package/dist/{chunk-5SXMV4SP.js → chunk-FS3WTUHY.js} +0 -0
package/dist/mcp.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  StatsCalculator,
15
15
  TimelineBuilder,
16
16
  loadAllSeedPatterns
17
- } from "./chunk-BKMNLROM.js";
17
+ } from "./chunk-RGFANZ4Q.js";
18
18
  import {
19
19
  addStep,
20
20
  addToolBreadcrumb,
@@ -28,6 +28,7 @@ import {
28
28
  detectProtocolSuggestion,
29
29
  findPurposeFiles,
30
30
  getAffectedPersonas,
31
+ getAffectedUniversityContent,
31
32
  getAllEdgesFor,
32
33
  getAllSymbols,
33
34
  getAnchorsForAspect,
@@ -37,6 +38,7 @@ import {
37
38
  getEdgesTo,
38
39
  getHeatmap,
39
40
  getLoreForAspect,
41
+ getOnboardingSequence,
40
42
  getPersonaCoverage,
41
43
  getReferencesFrom,
42
44
  getReferencesTo,
@@ -48,22 +50,33 @@ import {
48
50
  handleContextTool,
49
51
  handleReindexTool,
50
52
  incrementHeatmap,
53
+ loadDiplomas,
51
54
  loadGlobalAntipatterns,
52
55
  loadGlobalDecisions,
53
56
  loadGlobalPreferences,
57
+ loadNote,
58
+ loadPath,
54
59
  loadPersona,
55
60
  loadPersonas,
56
61
  loadProtocol,
57
62
  loadProtocolIndex,
58
63
  loadProtocols,
64
+ loadQuiz,
65
+ loadUniversityConfig,
59
66
  openAspectGraph,
60
67
  parseGateConfig,
61
68
  parsePurposeFileDetailed,
62
69
  rebuildStaticFiles,
70
+ rebuildUniversityIndex,
63
71
  recordGlobalAntipattern,
64
72
  recordGlobalDecision,
65
73
  recordProtocol,
66
74
  removeStep,
75
+ saveDiploma,
76
+ saveNote,
77
+ savePath,
78
+ saveQuiz,
79
+ searchContent,
67
80
  searchProtocols,
68
81
  searchSymbols,
69
82
  serializePurposeFile,
@@ -75,16 +88,19 @@ import {
75
88
  validateAgainstSentinel,
76
89
  validatePersona,
77
90
  validateProtocol,
78
- validatePurposeFile
79
- } from "./chunk-7IJ5JVKT.js";
91
+ validatePurposeFile,
92
+ validateUniversityContent
93
+ } from "./chunk-CZEIK3Y2.js";
80
94
  import {
95
+ addLoreAssessment,
81
96
  deleteLoreEntry,
82
97
  loadLoreEntries,
83
98
  loadLoreEntry,
84
99
  loadLoreTimeline,
85
100
  recordLoreEntry,
86
101
  updateLoreEntry
87
- } from "./chunk-3DYYXGDC.js";
102
+ } from "./chunk-CDMAMDSG.js";
103
+ import "./chunk-L27I3CPZ.js";
88
104
  import {
89
105
  getPluginUpdateNotice,
90
106
  schedulePluginUpdateCheck
@@ -754,6 +770,14 @@ async function loadProjectContext(rootDir) {
754
770
  try {
755
771
  const configContent = fs4.readFileSync(configPath, "utf8");
756
772
  const config = yaml4.load(configContent);
773
+ if (config && typeof config === "object") {
774
+ if (!config.version) {
775
+ console.error('[paradigm] Warning: config.yaml missing "version" field');
776
+ }
777
+ if (!config.project) {
778
+ console.error('[paradigm] Warning: config.yaml missing "project" field');
779
+ }
780
+ }
757
781
  if (config && typeof config.workspace === "string") {
758
782
  workspace = loadWorkspaceContext(absoluteRoot, config.workspace);
759
783
  }
@@ -2026,8 +2050,8 @@ function registerResources(server, getContext2) {
2026
2050
  }
2027
2051
 
2028
2052
  // ../paradigm-mcp/src/tools/index.ts
2029
- import * as os4 from "os";
2030
- import * as path29 from "path";
2053
+ import * as os6 from "os";
2054
+ import * as path31 from "path";
2031
2055
  import {
2032
2056
  ListToolsRequestSchema,
2033
2057
  CallToolRequestSchema
@@ -2258,6 +2282,47 @@ async function handleWisdomTool(name, args, ctx) {
2258
2282
  if (totalAntipatterns > 0) {
2259
2283
  result.warning = "There are antipatterns for these symbols - review before implementing";
2260
2284
  }
2285
+ try {
2286
+ const calibration = {};
2287
+ const calibration_warnings = [];
2288
+ for (const sym of symbols) {
2289
+ const assessed = await loadLoreEntries(ctx.rootDir, {
2290
+ symbol: sym,
2291
+ hasAssessment: true,
2292
+ limit: 100
2293
+ });
2294
+ if (assessed.length === 0) continue;
2295
+ const breakdown = { correct: 0, partial: 0, incorrect: 0 };
2296
+ let confSum = 0;
2297
+ let confCount = 0;
2298
+ for (const e of assessed) {
2299
+ const v = e.assessment.verdict;
2300
+ breakdown[v]++;
2301
+ if (e.confidence != null) {
2302
+ confSum += e.confidence;
2303
+ confCount++;
2304
+ }
2305
+ }
2306
+ calibration[sym] = {
2307
+ total: assessed.length,
2308
+ ...breakdown,
2309
+ avgConfidence: confCount > 0 ? Math.round(confSum / confCount * 1e3) / 1e3 : null
2310
+ };
2311
+ const accuracyRate = (breakdown.correct + breakdown.partial * 0.5) / assessed.length;
2312
+ if (accuracyRate < 0.6 && assessed.length >= 3) {
2313
+ calibration_warnings.push(
2314
+ `Low historical accuracy for ${sym}: ${Math.round(accuracyRate * 100)}% across ${assessed.length} entries. Proceed with extra caution.`
2315
+ );
2316
+ }
2317
+ }
2318
+ if (Object.keys(calibration).length > 0) {
2319
+ result.calibration = calibration;
2320
+ }
2321
+ if (calibration_warnings.length > 0) {
2322
+ result.calibration_warnings = calibration_warnings;
2323
+ }
2324
+ } catch {
2325
+ }
2261
2326
  return {
2262
2327
  handled: true,
2263
2328
  text: JSON.stringify(result, null, 2)
@@ -2791,7 +2856,7 @@ function navigateExplore(config, target, rootDir) {
2791
2856
  }
2792
2857
  if (result.paths.length === 0) {
2793
2858
  const areaSymbols = Object.entries(config.symbols).filter(
2794
- ([sym, path30]) => sym.toLowerCase().includes(targetLower) || path30.toLowerCase().includes(targetLower)
2859
+ ([sym, path32]) => sym.toLowerCase().includes(targetLower) || path32.toLowerCase().includes(targetLower)
2795
2860
  ).slice(0, 10);
2796
2861
  result.paths = [...new Set(areaSymbols.map(([, p]) => p))];
2797
2862
  result.symbols = areaSymbols.map(([s]) => s);
@@ -6420,6 +6485,14 @@ var purposeAddComponentTool = {
6420
6485
  type: "array",
6421
6486
  items: { type: "string" },
6422
6487
  description: 'Component references (e.g. ["#stripe-service"])'
6488
+ },
6489
+ type: {
6490
+ type: "string",
6491
+ description: 'Component type (e.g., "view", "service", "model", "tool"). Open string per project vocabulary.'
6492
+ },
6493
+ parent: {
6494
+ type: "string",
6495
+ description: 'Parent component (e.g., "#payment-page"). Establishes hierarchy.'
6423
6496
  }
6424
6497
  },
6425
6498
  required: ["purposeFile", "id", "description"]
@@ -6957,7 +7030,9 @@ async function handleAddComponent(args, ctx, reloadContext2) {
6957
7030
  gates,
6958
7031
  signals,
6959
7032
  aspects,
6960
- components
7033
+ components,
7034
+ type: componentType,
7035
+ parent
6961
7036
  } = args;
6962
7037
  const filePath = resolvePurposeFilePath(purposeFile, ctx.rootDir);
6963
7038
  const data = readPurposeFile(filePath);
@@ -6977,6 +7052,8 @@ async function handleAddComponent(args, ctx, reloadContext2) {
6977
7052
  if (signals !== void 0) item.signals = signals.map((s) => ensurePrefix(s, "!"));
6978
7053
  if (aspects !== void 0) item.aspects = aspects.map((a) => ensurePrefix(a, "~"));
6979
7054
  if (components !== void 0) item.components = components.map((c) => ensurePrefix(c, "#"));
7055
+ if (componentType !== void 0) item.type = componentType;
7056
+ if (parent !== void 0) item.parent = ensurePrefix(parent, "#");
6980
7057
  existing[bareId] = item;
6981
7058
  data[sec] = existing;
6982
7059
  writePurposeFile(filePath, data);
@@ -7513,6 +7590,16 @@ var SEED_HABITS = [
7513
7590
  check: { type: "lore-recorded", params: {} },
7514
7591
  enabled: true
7515
7592
  },
7593
+ {
7594
+ id: "confidence-on-decisions",
7595
+ name: "Confidence on Decisions",
7596
+ description: "When recording lore, include a confidence score (0.0-1.0) to enable calibration tracking over time",
7597
+ category: "documentation",
7598
+ trigger: "on-stop",
7599
+ severity: "advisory",
7600
+ check: { type: "tool-called", params: { tools: ["paradigm_lore_record"] } },
7601
+ enabled: true
7602
+ },
7516
7603
  {
7517
7604
  id: "gates-for-routes",
7518
7605
  name: "Gates for Routes",
@@ -7522,6 +7609,26 @@ var SEED_HABITS = [
7522
7609
  severity: "warn",
7523
7610
  check: { type: "gates-declared", params: { requireRoutes: true } },
7524
7611
  enabled: true
7612
+ },
7613
+ {
7614
+ id: "university-content-valid",
7615
+ name: "University Content Valid",
7616
+ description: "Validate university content integrity when files in symbol-covered areas change",
7617
+ category: "quality",
7618
+ trigger: "on-stop",
7619
+ severity: "advisory",
7620
+ check: { type: "tool-called", params: { tools: ["paradigm_university_validate"] } },
7621
+ enabled: true
7622
+ },
7623
+ {
7624
+ id: "university-onboarded",
7625
+ name: "University Onboarding",
7626
+ description: "Call paradigm_university_onboard at session start for project-specific learning content",
7627
+ category: "discovery",
7628
+ trigger: "preflight",
7629
+ severity: "advisory",
7630
+ check: { type: "tool-called", params: { tools: ["paradigm_university_onboard"] } },
7631
+ enabled: false
7525
7632
  }
7526
7633
  ];
7527
7634
  var HABITS_CACHE_TTL_MS = 30 * 1e3;
@@ -8280,6 +8387,22 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
8280
8387
  suggestion: "Use paradigm_wisdom_record to capture architectural decisions or antipatterns."
8281
8388
  });
8282
8389
  }
8390
+ for (const symbol of symbolsTouched) {
8391
+ const results = searchSymbols(ctx.index, symbol);
8392
+ if (results.length === 0) continue;
8393
+ const sym = results[0];
8394
+ if (sym.parentSymbol) {
8395
+ const parentResults = searchSymbols(ctx.index, sym.parentSymbol);
8396
+ if (parentResults.length === 0) {
8397
+ violations.push({
8398
+ type: "broken-reference",
8399
+ severity: "warning",
8400
+ message: `Symbol "${symbol}" references parent "${sym.parentSymbol}" which does not exist`,
8401
+ suggestion: `Create the parent symbol or update the parent reference in the .purpose file.`
8402
+ });
8403
+ }
8404
+ }
8405
+ }
8283
8406
  const errors = violations.filter((v) => v.severity === "error").length;
8284
8407
  const warnings = violations.filter((v) => v.severity === "warning").length;
8285
8408
  let status = "pass";
@@ -8340,8 +8463,8 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
8340
8463
  status,
8341
8464
  violations,
8342
8465
  summary: {
8343
- totalChecks: 6,
8344
- passed: 6 - (errors > 0 ? 1 : 0) - (warnings > 0 ? 1 : 0),
8466
+ totalChecks: 7,
8467
+ passed: 7 - (errors > 0 ? 1 : 0) - (warnings > 0 ? 1 : 0),
8345
8468
  warnings,
8346
8469
  errors
8347
8470
  },
@@ -8426,6 +8549,14 @@ function getLoreToolsList() {
8426
8549
  type: "boolean",
8427
8550
  description: "Filter for entries with/without reviews"
8428
8551
  },
8552
+ hasConfidence: {
8553
+ type: "boolean",
8554
+ description: "Filter for entries with/without confidence scores"
8555
+ },
8556
+ hasAssessment: {
8557
+ type: "boolean",
8558
+ description: "Filter for entries with/without assessment verdicts"
8559
+ },
8429
8560
  limit: {
8430
8561
  type: "number",
8431
8562
  description: "Maximum results (default: 20)"
@@ -8554,6 +8685,10 @@ function getLoreToolsList() {
8554
8685
  type: "array",
8555
8686
  items: { type: "string" },
8556
8687
  description: "Git commit SHAs related to this entry"
8688
+ },
8689
+ confidence: {
8690
+ type: "number",
8691
+ description: "Agent confidence in correctness of this work (0.0 to 1.0)"
8557
8692
  }
8558
8693
  },
8559
8694
  required: ["title", "summary", "symbols_touched"]
@@ -8652,6 +8787,10 @@ function getLoreToolsList() {
8652
8787
  tags: {
8653
8788
  type: "array",
8654
8789
  items: { type: "string" }
8790
+ },
8791
+ confidence: {
8792
+ type: "number",
8793
+ description: "Agent confidence in correctness (0.0 to 1.0)"
8655
8794
  }
8656
8795
  },
8657
8796
  required: ["id"]
@@ -8661,6 +8800,71 @@ function getLoreToolsList() {
8661
8800
  destructiveHint: false
8662
8801
  }
8663
8802
  },
8803
+ {
8804
+ name: "paradigm_lore_assess",
8805
+ description: "Record a human assessment verdict on a lore entry (correct/partial/incorrect). Computes calibration delta if confidence was recorded. ~100 tokens.",
8806
+ inputSchema: {
8807
+ type: "object",
8808
+ properties: {
8809
+ id: {
8810
+ type: "string",
8811
+ description: "Lore entry ID to assess"
8812
+ },
8813
+ verdict: {
8814
+ type: "string",
8815
+ enum: ["correct", "partial", "incorrect"],
8816
+ description: "Assessment verdict on the decisions/changes made"
8817
+ },
8818
+ notes: {
8819
+ type: "string",
8820
+ description: "Optional assessment notes"
8821
+ }
8822
+ },
8823
+ required: ["id", "verdict"]
8824
+ },
8825
+ annotations: {
8826
+ readOnlyHint: false,
8827
+ destructiveHint: false
8828
+ }
8829
+ },
8830
+ {
8831
+ name: "paradigm_lore_calibration",
8832
+ description: "Query calibration statistics across assessed lore entries. Returns accuracy rate, average confidence, calibration score, and verdict breakdown. Supports groupBy for domain-specific reliability maps. ~200 tokens.",
8833
+ inputSchema: {
8834
+ type: "object",
8835
+ properties: {
8836
+ symbol: {
8837
+ type: "string",
8838
+ description: 'Filter by symbol (e.g., "#auth-middleware")'
8839
+ },
8840
+ tag: {
8841
+ type: "string",
8842
+ description: "Filter by tag prefix"
8843
+ },
8844
+ author: {
8845
+ type: "string",
8846
+ description: "Filter by author"
8847
+ },
8848
+ dateFrom: {
8849
+ type: "string",
8850
+ description: "Filter from date (ISO 8601)"
8851
+ },
8852
+ dateTo: {
8853
+ type: "string",
8854
+ description: "Filter to date (ISO 8601)"
8855
+ },
8856
+ groupBy: {
8857
+ type: "string",
8858
+ enum: ["symbol", "tag", "type"],
8859
+ description: "Group calibration stats by dimension"
8860
+ }
8861
+ }
8862
+ },
8863
+ annotations: {
8864
+ readOnlyHint: true,
8865
+ destructiveHint: false
8866
+ }
8867
+ },
8664
8868
  {
8665
8869
  name: "paradigm_lore_delete",
8666
8870
  description: "Delete a lore entry. Requires explicit confirmation to prevent accidental deletion. ~100 tokens.",
@@ -8700,6 +8904,8 @@ async function handleLoreTool(name, args, ctx) {
8700
8904
  hasBody: args.hasBody,
8701
8905
  tags: args.tags,
8702
8906
  hasReview: args.hasReview,
8907
+ hasConfidence: args.hasConfidence,
8908
+ hasAssessment: args.hasAssessment,
8703
8909
  limit: args.limit || 20,
8704
8910
  offset: args.offset
8705
8911
  };
@@ -8737,7 +8943,8 @@ async function handleLoreTool(name, args, ctx) {
8737
8943
  body,
8738
8944
  linked_lore,
8739
8945
  linked_tasks,
8740
- linked_commits
8946
+ linked_commits,
8947
+ confidence
8741
8948
  } = args;
8742
8949
  let habit_compliance;
8743
8950
  try {
@@ -8783,7 +8990,8 @@ async function handleLoreTool(name, args, ctx) {
8783
8990
  body,
8784
8991
  linked_lore,
8785
8992
  linked_tasks,
8786
- linked_commits
8993
+ linked_commits,
8994
+ confidence: confidence != null && confidence >= 0 && confidence <= 1 ? confidence : void 0
8787
8995
  };
8788
8996
  const id = await recordLoreEntry(ctx.rootDir, entry);
8789
8997
  getSessionTracker().setLastLoreEntryId(id);
@@ -8881,6 +9089,154 @@ async function handleLoreTool(name, args, ctx) {
8881
9089
  })
8882
9090
  };
8883
9091
  }
9092
+ case "paradigm_lore_assess": {
9093
+ const id = args.id;
9094
+ const verdict = args.verdict;
9095
+ const notes = args.notes;
9096
+ const entryToAssess = await loadLoreEntry(ctx.rootDir, id);
9097
+ if (!entryToAssess) {
9098
+ return {
9099
+ handled: true,
9100
+ text: JSON.stringify({ error: `Lore entry not found: ${id}` })
9101
+ };
9102
+ }
9103
+ const assessment = {
9104
+ verdict,
9105
+ assessed_by: resolveAuthorForMcp(),
9106
+ assessed_at: (/* @__PURE__ */ new Date()).toISOString(),
9107
+ notes
9108
+ };
9109
+ const success = await addLoreAssessment(ctx.rootDir, id, assessment);
9110
+ const impliedScore = verdict === "correct" ? 1 : verdict === "partial" ? 0.5 : 0;
9111
+ const delta = entryToAssess.confidence != null ? impliedScore - entryToAssess.confidence : null;
9112
+ const deltaDescription = delta != null ? delta > 0.1 ? "Under-confident (actual outcome better than predicted)" : delta < -0.1 ? "Over-confident (actual outcome worse than predicted)" : "Well-calibrated" : "No confidence recorded \u2014 delta not computed";
9113
+ return {
9114
+ handled: true,
9115
+ text: JSON.stringify({
9116
+ success,
9117
+ id,
9118
+ verdict,
9119
+ confidence: entryToAssess.confidence ?? null,
9120
+ delta,
9121
+ deltaDescription,
9122
+ message: success ? `Assessment recorded: ${verdict}${delta != null ? ` (delta: ${delta > 0 ? "+" : ""}${delta.toFixed(2)})` : ""}` : `Failed to assess: ${id}`
9123
+ })
9124
+ };
9125
+ }
9126
+ case "paradigm_lore_calibration": {
9127
+ const filter = {
9128
+ symbol: args.symbol,
9129
+ tag: args.tag,
9130
+ author: args.author,
9131
+ dateFrom: args.dateFrom,
9132
+ dateTo: args.dateTo,
9133
+ hasAssessment: true
9134
+ };
9135
+ const entries = await loadLoreEntries(ctx.rootDir, filter);
9136
+ const withConfidence = entries.filter((e) => e.confidence != null);
9137
+ const totalAssessed = entries.length;
9138
+ const totalWithConfidence = withConfidence.length;
9139
+ const verdictBreakdown = { correct: 0, partial: 0, incorrect: 0 };
9140
+ let totalImpliedScore = 0;
9141
+ let totalConfidence = 0;
9142
+ let totalAbsDelta = 0;
9143
+ for (const e of entries) {
9144
+ const v = e.assessment.verdict;
9145
+ verdictBreakdown[v]++;
9146
+ const implied = v === "correct" ? 1 : v === "partial" ? 0.5 : 0;
9147
+ totalImpliedScore += implied;
9148
+ if (e.confidence != null) {
9149
+ totalConfidence += e.confidence;
9150
+ totalAbsDelta += Math.abs(implied - e.confidence);
9151
+ }
9152
+ }
9153
+ const accuracyRate = totalAssessed > 0 ? totalImpliedScore / totalAssessed : 0;
9154
+ const avgConfidence = totalWithConfidence > 0 ? totalConfidence / totalWithConfidence : null;
9155
+ const avgDelta = totalWithConfidence > 0 ? totalImpliedScore / totalAssessed - totalConfidence / totalWithConfidence : null;
9156
+ const calibrationScore = totalWithConfidence > 0 ? 1 - totalAbsDelta / totalWithConfidence : null;
9157
+ const groupBy = args.groupBy;
9158
+ let groups;
9159
+ if (groupBy && totalAssessed > 0) {
9160
+ const groupMap = /* @__PURE__ */ new Map();
9161
+ for (const e of entries) {
9162
+ let keys = [];
9163
+ if (groupBy === "symbol") {
9164
+ keys = e.symbols_touched || [];
9165
+ } else if (groupBy === "tag") {
9166
+ keys = e.tags || [];
9167
+ } else if (groupBy === "type") {
9168
+ keys = [e.type || "agent-session"];
9169
+ }
9170
+ for (const key of keys) {
9171
+ if (!groupMap.has(key)) groupMap.set(key, []);
9172
+ groupMap.get(key).push(e);
9173
+ }
9174
+ }
9175
+ groups = Array.from(groupMap.entries()).map(([key, gEntries]) => {
9176
+ const gWithConf = gEntries.filter((e) => e.confidence != null);
9177
+ const gBreakdown = { correct: 0, partial: 0, incorrect: 0 };
9178
+ let gImplied = 0;
9179
+ let gConf = 0;
9180
+ let gAbsDelta = 0;
9181
+ for (const e of gEntries) {
9182
+ const v = e.assessment.verdict;
9183
+ gBreakdown[v]++;
9184
+ const implied = v === "correct" ? 1 : v === "partial" ? 0.5 : 0;
9185
+ gImplied += implied;
9186
+ if (e.confidence != null) {
9187
+ gConf += e.confidence;
9188
+ gAbsDelta += Math.abs(implied - e.confidence);
9189
+ }
9190
+ }
9191
+ return {
9192
+ key,
9193
+ total: gEntries.length,
9194
+ accuracyRate: gImplied / gEntries.length,
9195
+ avgConfidence: gWithConf.length > 0 ? gConf / gWithConf.length : null,
9196
+ calibrationScore: gWithConf.length > 0 ? 1 - gAbsDelta / gWithConf.length : null,
9197
+ verdictBreakdown: gBreakdown
9198
+ };
9199
+ }).sort((a, b) => b.total - a.total);
9200
+ }
9201
+ const insights = [];
9202
+ const caveat = totalAssessed < 5 ? `Low sample size (N=${totalAssessed}). Stats may not be representative.` : totalAssessed < 15 ? `Moderate sample (N=${totalAssessed}). Trends are directional, not conclusive.` : null;
9203
+ if (caveat) insights.push(caveat);
9204
+ if (calibrationScore != null) {
9205
+ if (calibrationScore >= 0.9) {
9206
+ insights.push("Excellent calibration \u2014 confidence predictions closely match outcomes.");
9207
+ } else if (calibrationScore >= 0.7) {
9208
+ insights.push("Good calibration \u2014 some room for improvement in confidence estimates.");
9209
+ } else if (calibrationScore >= 0.5) {
9210
+ insights.push("Fair calibration \u2014 significant gap between predicted confidence and outcomes.");
9211
+ } else {
9212
+ insights.push("Poor calibration \u2014 confidence predictions diverge substantially from outcomes.");
9213
+ }
9214
+ }
9215
+ if (avgDelta != null) {
9216
+ if (avgDelta > 0.15) {
9217
+ insights.push("Tendency toward under-confidence \u2014 outcomes are better than predicted.");
9218
+ } else if (avgDelta < -0.15) {
9219
+ insights.push("Tendency toward over-confidence \u2014 outcomes are worse than predicted.");
9220
+ }
9221
+ }
9222
+ if (verdictBreakdown.incorrect > totalAssessed * 0.3 && totalAssessed >= 5) {
9223
+ insights.push(`High error rate: ${verdictBreakdown.incorrect}/${totalAssessed} entries assessed as incorrect.`);
9224
+ }
9225
+ return {
9226
+ handled: true,
9227
+ text: JSON.stringify({
9228
+ totalAssessed,
9229
+ totalWithConfidence,
9230
+ accuracyRate: Math.round(accuracyRate * 1e3) / 1e3,
9231
+ avgConfidence: avgConfidence != null ? Math.round(avgConfidence * 1e3) / 1e3 : null,
9232
+ avgDelta: avgDelta != null ? Math.round(avgDelta * 1e3) / 1e3 : null,
9233
+ calibrationScore: calibrationScore != null ? Math.round(calibrationScore * 1e3) / 1e3 : null,
9234
+ verdictBreakdown,
9235
+ ...groups ? { groups } : {},
9236
+ insights
9237
+ }, null, 2)
9238
+ };
9239
+ }
8884
9240
  case "paradigm_lore_delete": {
8885
9241
  const id = args.id;
8886
9242
  const confirm = args.confirm;
@@ -8923,6 +9279,9 @@ function summarizeEntry(entry) {
8923
9279
  completeness: entry.review.completeness,
8924
9280
  quality: entry.review.quality
8925
9281
  } : null,
9282
+ confidence: entry.confidence ?? null,
9283
+ assessment: entry.assessment ? entry.assessment.verdict : null,
9284
+ assessment_delta: entry.assessment_delta ?? null,
8926
9285
  tags: entry.tags
8927
9286
  };
8928
9287
  }
@@ -11289,8 +11648,8 @@ function generateRunId() {
11289
11648
  var TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g;
11290
11649
  function interpolate(value, scope) {
11291
11650
  if (typeof value === "string") {
11292
- return value.replace(TEMPLATE_REGEX, (_match, path30) => {
11293
- const resolved = resolvePath(path30.trim(), scope);
11651
+ return value.replace(TEMPLATE_REGEX, (_match, path32) => {
11652
+ const resolved = resolvePath(path32.trim(), scope);
11294
11653
  return resolved !== void 0 ? String(resolved) : _match;
11295
11654
  });
11296
11655
  }
@@ -11323,8 +11682,8 @@ function resolvePath(dotPath, scope) {
11323
11682
  return void 0;
11324
11683
  }
11325
11684
  }
11326
- function deepGet(obj, path30) {
11327
- const parts = path30.split(/[.\[\]]+/).filter(Boolean);
11685
+ function deepGet(obj, path32) {
11686
+ const parts = path32.split(/[.\[\]]+/).filter(Boolean);
11328
11687
  let current = obj;
11329
11688
  for (const part of parts) {
11330
11689
  if (current == null || typeof current !== "object") return void 0;
@@ -11560,11 +11919,11 @@ async function runPersonaObject(rootDir, persona, options) {
11560
11919
  }
11561
11920
  async function runChain(rootDir, chainId, options) {
11562
11921
  const start = Date.now();
11563
- const fs26 = await import("fs");
11564
- const path30 = await import("path");
11565
- const yaml15 = await import("js-yaml");
11566
- const chainPath = path30.join(rootDir, ".paradigm", "personas", "chains", `${chainId}.yaml`);
11567
- if (!fs26.existsSync(chainPath)) {
11922
+ const fs28 = await import("fs");
11923
+ const path32 = await import("path");
11924
+ const yaml16 = await import("js-yaml");
11925
+ const chainPath = path32.join(rootDir, ".paradigm", "personas", "chains", `${chainId}.yaml`);
11926
+ if (!fs28.existsSync(chainPath)) {
11568
11927
  return {
11569
11928
  chain_id: chainId,
11570
11929
  status: "error",
@@ -11573,7 +11932,7 @@ async function runChain(rootDir, chainId, options) {
11573
11932
  duration_ms: Date.now() - start
11574
11933
  };
11575
11934
  }
11576
- const chain = yaml15.load(fs26.readFileSync(chainPath, "utf8"));
11935
+ const chain = yaml16.load(fs28.readFileSync(chainPath, "utf8"));
11577
11936
  let permutation;
11578
11937
  if (options.permutation && chain.permutations) {
11579
11938
  permutation = chain.permutations.find((p) => p.id === options.permutation);
@@ -11677,8 +12036,8 @@ function validateInterpolation(persona) {
11677
12036
  const serialized = JSON.stringify(step);
11678
12037
  const templates = serialized.match(TEMPLATE_REGEX) || [];
11679
12038
  for (const template of templates) {
11680
- const path30 = template.replace("{{", "").replace("}}", "").trim();
11681
- const [namespace, ...rest] = path30.split(".");
12039
+ const path32 = template.replace("{{", "").replace("}}", "").trim();
12040
+ const [namespace, ...rest] = path32.split(".");
11682
12041
  const key = rest.join(".");
11683
12042
  switch (namespace) {
11684
12043
  case "fixtures":
@@ -11739,7 +12098,7 @@ var PERSONA_SCHEMA = {
11739
12098
  var sentinelSchemaRegistered = false;
11740
12099
  async function emitPersonaEvents(result) {
11741
12100
  try {
11742
- const { SentinelStorage: SentinelStorage2 } = await import("./dist-IKBGY7FQ.js");
12101
+ const { SentinelStorage: SentinelStorage2 } = await import("./dist-RVKYUCRU.js");
11743
12102
  const storage2 = new SentinelStorage2();
11744
12103
  if (!sentinelSchemaRegistered) {
11745
12104
  try {
@@ -14032,103 +14391,2172 @@ This session is no longer visible in the Conductor overlay.`,
14032
14391
  }
14033
14392
  }
14034
14393
 
14035
- // ../paradigm-mcp/src/tools/fallback-grep.ts
14394
+ // ../paradigm-mcp/src/utils/symphony-loader.ts
14395
+ import * as fs26 from "fs";
14036
14396
  import * as path28 from "path";
14037
- import { execSync as execSync7 } from "child_process";
14038
- function grepForReferences(rootDir, symbol, options = {}) {
14039
- const { maxResults = 20 } = options;
14040
- const results = [];
14041
- const escapedSymbol = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14042
- const grepCommands = [
14043
- // ripgrep - exclude common directories
14044
- `rg -n --no-heading "${escapedSymbol}" "${rootDir}" --glob "!node_modules" --glob "!.git" --glob "!dist" --glob "!build" --glob "!coverage" --max-count 50 2>/dev/null`,
14045
- // fallback to grep
14046
- `grep -rn "${escapedSymbol}" "${rootDir}" --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude-dir=build --exclude-dir=coverage 2>/dev/null | head -50`
14047
- ];
14048
- let output = "";
14049
- for (const cmd of grepCommands) {
14397
+ import * as os4 from "os";
14398
+ import * as crypto from "crypto";
14399
+ var SCORE_DIR = path28.join(os4.homedir(), ".paradigm", "score");
14400
+ var LEGACY_MAIL_DIR = path28.join(os4.homedir(), ".paradigm", "mail");
14401
+ var AGENTS_DIR = path28.join(SCORE_DIR, "agents");
14402
+ var THREADS_DIR = path28.join(SCORE_DIR, "threads");
14403
+ var FILE_REQUESTS_DIR = path28.join(SCORE_DIR, "file-requests");
14404
+ var TRUST_CONFIG_PATH = path28.join(SCORE_DIR, "trust.yaml");
14405
+ var FILE_REQUEST_TTL_MS = 60 * 60 * 1e3;
14406
+ var DEFAULT_TRUST = {
14407
+ users: {},
14408
+ defaults: {
14409
+ level: "restricted",
14410
+ autoApprove: [],
14411
+ neverApprove: [
14412
+ ".env*",
14413
+ "**/*.key",
14414
+ "**/*.pem",
14415
+ "**/credentials*",
14416
+ "**/secrets/**"
14417
+ ]
14418
+ }
14419
+ };
14420
+ function migrateFromLegacyMail() {
14421
+ if (fs26.existsSync(LEGACY_MAIL_DIR) && !fs26.existsSync(SCORE_DIR)) {
14050
14422
  try {
14051
- output = execSync7(cmd, { encoding: "utf8", maxBuffer: 1024 * 1024 });
14052
- if (output.trim()) break;
14423
+ fs26.renameSync(LEGACY_MAIL_DIR, SCORE_DIR);
14053
14424
  } catch {
14054
- continue;
14055
14425
  }
14056
14426
  }
14057
- if (!output.trim()) {
14058
- return results;
14427
+ }
14428
+ function ensureScoreDirs() {
14429
+ migrateFromLegacyMail();
14430
+ for (const dir of [AGENTS_DIR, THREADS_DIR, FILE_REQUESTS_DIR]) {
14431
+ if (!fs26.existsSync(dir)) {
14432
+ fs26.mkdirSync(dir, { recursive: true });
14433
+ }
14059
14434
  }
14060
- const lines = output.trim().split("\n");
14061
- for (const line of lines.slice(0, maxResults)) {
14062
- const match = line.match(/^(.+?):(\d+):(.*)$/);
14063
- if (match) {
14064
- const [, filePath, lineNum, content] = match;
14065
- const relativePath = path28.relative(rootDir, filePath);
14066
- let context2 = "unknown";
14067
- if (relativePath.includes(".purpose") || relativePath.includes("portal.yaml")) {
14068
- context2 = "purpose";
14069
- } else if (content.includes("//") || content.includes("#") || content.includes("*")) {
14070
- context2 = "comment";
14071
- } else {
14072
- context2 = "code";
14073
- }
14074
- results.push({
14075
- filePath: relativePath,
14076
- line: parseInt(lineNum, 10),
14077
- content: content.trim().slice(0, 200),
14078
- context: context2
14079
- });
14435
+ }
14436
+ function getAgentDir(agentId) {
14437
+ return path28.join(AGENTS_DIR, agentId);
14438
+ }
14439
+ function ensureAgentDir(agentId) {
14440
+ const dir = getAgentDir(agentId);
14441
+ if (!fs26.existsSync(dir)) {
14442
+ fs26.mkdirSync(dir, { recursive: true });
14443
+ }
14444
+ return dir;
14445
+ }
14446
+ function readJsonlFile(filePath) {
14447
+ if (!fs26.existsSync(filePath)) return [];
14448
+ const content = fs26.readFileSync(filePath, "utf-8");
14449
+ const lines = content.split("\n").filter((line) => line.trim().length > 0);
14450
+ const items = [];
14451
+ for (const line of lines) {
14452
+ try {
14453
+ items.push(JSON.parse(line));
14454
+ } catch {
14080
14455
  }
14081
14456
  }
14082
- return results;
14457
+ return items;
14083
14458
  }
14084
-
14085
- // ../paradigm-mcp/src/tools/fuzzy-match.ts
14086
- function levenshteinDistance(a, b) {
14087
- const matrix = [];
14088
- for (let i = 0; i <= b.length; i++) {
14089
- matrix[i] = [i];
14459
+ function appendJsonlLine(filePath, item) {
14460
+ const dir = path28.dirname(filePath);
14461
+ if (!fs26.existsSync(dir)) {
14462
+ fs26.mkdirSync(dir, { recursive: true });
14090
14463
  }
14091
- for (let j = 0; j <= a.length; j++) {
14092
- matrix[0][j] = j;
14464
+ fs26.appendFileSync(filePath, JSON.stringify(item) + "\n", "utf-8");
14465
+ }
14466
+ function sanitizeId(name) {
14467
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "unknown";
14468
+ }
14469
+ function resolveProjectName(projectDir2) {
14470
+ try {
14471
+ const configPath = path28.join(projectDir2, ".paradigm", "config.yaml");
14472
+ if (fs26.existsSync(configPath)) {
14473
+ const content = fs26.readFileSync(configPath, "utf-8");
14474
+ const match = content.match(/^project:\s*(.+)$/m);
14475
+ if (match) return sanitizeId(match[1].trim().replace(/["']/g, ""));
14476
+ }
14477
+ } catch {
14093
14478
  }
14094
- for (let i = 1; i <= b.length; i++) {
14095
- for (let j = 1; j <= a.length; j++) {
14096
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
14097
- matrix[i][j] = matrix[i - 1][j - 1];
14098
- } else {
14099
- matrix[i][j] = Math.min(
14100
- matrix[i - 1][j - 1] + 1,
14101
- // substitution
14102
- matrix[i][j - 1] + 1,
14103
- // insertion
14104
- matrix[i - 1][j] + 1
14105
- // deletion
14106
- );
14479
+ return sanitizeId(path28.basename(projectDir2));
14480
+ }
14481
+ function resolveAgentIdentity(projectDir2, role) {
14482
+ const project = resolveProjectName(projectDir2);
14483
+ const agentRole = role || "core";
14484
+ return `${project}/${agentRole}`;
14485
+ }
14486
+ function registerAgent(projectDir2, role, label) {
14487
+ ensureScoreDirs();
14488
+ const agentId = resolveAgentIdentity(projectDir2, role);
14489
+ const agentDir = ensureAgentDir(agentId);
14490
+ const project = resolveProjectName(projectDir2);
14491
+ const identity = {
14492
+ id: agentId,
14493
+ name: label || `${project} (${role || "core"})`,
14494
+ type: "agent",
14495
+ project,
14496
+ role: role || "core",
14497
+ pid: process.pid,
14498
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
14499
+ label
14500
+ };
14501
+ fs26.writeFileSync(
14502
+ path28.join(agentDir, "identity.json"),
14503
+ JSON.stringify(identity, null, 2),
14504
+ "utf-8"
14505
+ );
14506
+ return identity;
14507
+ }
14508
+ function unregisterAgent(agentId) {
14509
+ const agentDir = getAgentDir(agentId);
14510
+ if (!fs26.existsSync(agentDir)) return false;
14511
+ try {
14512
+ fs26.rmSync(agentDir, { recursive: true, force: true });
14513
+ return true;
14514
+ } catch {
14515
+ return false;
14516
+ }
14517
+ }
14518
+ function listAgents() {
14519
+ ensureScoreDirs();
14520
+ if (!fs26.existsSync(AGENTS_DIR)) return [];
14521
+ const agents = [];
14522
+ const projectDirs = fs26.readdirSync(AGENTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
14523
+ for (const projectDir2 of projectDirs) {
14524
+ const projectPath = path28.join(AGENTS_DIR, projectDir2.name);
14525
+ const roleDirs = fs26.readdirSync(projectPath, { withFileTypes: true }).filter((d) => d.isDirectory());
14526
+ for (const roleDir of roleDirs) {
14527
+ const identityPath = path28.join(projectPath, roleDir.name, "identity.json");
14528
+ if (!fs26.existsSync(identityPath)) continue;
14529
+ try {
14530
+ const content = fs26.readFileSync(identityPath, "utf-8");
14531
+ const identity = JSON.parse(content);
14532
+ agents.push(identity);
14533
+ } catch {
14107
14534
  }
14108
14535
  }
14109
14536
  }
14110
- return matrix[b.length][a.length];
14537
+ return agents;
14111
14538
  }
14112
- function findFuzzyMatches(query, candidates, options = {}) {
14113
- const { maxDistance = 3, maxResults = 5 } = options;
14114
- const queryLower = query.toLowerCase();
14115
- const matches = [];
14116
- for (const candidate of candidates) {
14117
- const candidateLower = candidate.toLowerCase();
14118
- if (candidateLower === queryLower) {
14119
- matches.push({ match: candidate, distance: 0 });
14120
- continue;
14121
- }
14122
- if (candidateLower.includes(queryLower) || queryLower.includes(candidateLower)) {
14123
- matches.push({ match: candidate, distance: 1 });
14124
- continue;
14125
- }
14126
- const distance = levenshteinDistance(queryLower, candidateLower);
14127
- if (distance <= maxDistance) {
14128
- matches.push({ match: candidate, distance });
14539
+ function cleanStaleAgents() {
14540
+ const agents = listAgents();
14541
+ let cleaned = 0;
14542
+ for (const agent of agents) {
14543
+ if (!isProcessAlive2(agent.pid)) {
14544
+ unregisterAgent(agent.id);
14545
+ cleaned++;
14129
14546
  }
14130
14547
  }
14131
- matches.sort((a, b) => {
14548
+ return cleaned;
14549
+ }
14550
+ function getMyIdentity(projectDir2) {
14551
+ const agentId = resolveAgentIdentity(projectDir2);
14552
+ const identityPath = path28.join(getAgentDir(agentId), "identity.json");
14553
+ if (!fs26.existsSync(identityPath)) return null;
14554
+ try {
14555
+ return JSON.parse(fs26.readFileSync(identityPath, "utf-8"));
14556
+ } catch {
14557
+ return null;
14558
+ }
14559
+ }
14560
+ function markAgentPollTime(agentId) {
14561
+ const identityPath = path28.join(getAgentDir(agentId), "identity.json");
14562
+ if (!fs26.existsSync(identityPath)) return;
14563
+ try {
14564
+ const identity = JSON.parse(fs26.readFileSync(identityPath, "utf-8"));
14565
+ identity.lastPoll = (/* @__PURE__ */ new Date()).toISOString();
14566
+ fs26.writeFileSync(identityPath, JSON.stringify(identity, null, 2), "utf-8");
14567
+ } catch {
14568
+ }
14569
+ }
14570
+ function isAgentAsleep(identity, thresholdMs = 6e4) {
14571
+ if (!identity.lastPoll) return true;
14572
+ const lastPoll = new Date(identity.lastPoll).getTime();
14573
+ return Date.now() - lastPoll > thresholdMs;
14574
+ }
14575
+ function inboxPath(agentId) {
14576
+ return path28.join(getAgentDir(agentId), "inbox.jsonl");
14577
+ }
14578
+ function outboxPath(agentId) {
14579
+ return path28.join(getAgentDir(agentId), "outbox.jsonl");
14580
+ }
14581
+ function ackPath(agentId) {
14582
+ return path28.join(getAgentDir(agentId), "ack.json");
14583
+ }
14584
+ function appendToInbox(agentId, message) {
14585
+ ensureAgentDir(agentId);
14586
+ appendJsonlLine(inboxPath(agentId), message);
14587
+ }
14588
+ function readInbox(agentId, afterAck) {
14589
+ const messages = readJsonlFile(inboxPath(agentId));
14590
+ if (!afterAck) {
14591
+ const ack = readAck(agentId);
14592
+ if (ack) {
14593
+ const ackIndex2 = messages.findIndex((m) => m.id === ack);
14594
+ if (ackIndex2 >= 0) return messages.slice(ackIndex2 + 1);
14595
+ }
14596
+ return messages;
14597
+ }
14598
+ const ackIndex = messages.findIndex((m) => m.id === afterAck);
14599
+ if (ackIndex >= 0) return messages.slice(ackIndex + 1);
14600
+ return messages;
14601
+ }
14602
+ function appendToOutbox(agentId, message) {
14603
+ ensureAgentDir(agentId);
14604
+ appendJsonlLine(outboxPath(agentId), message);
14605
+ }
14606
+ function acknowledgeMessages(agentId, lastMessageId) {
14607
+ const filePath = ackPath(agentId);
14608
+ ensureAgentDir(agentId);
14609
+ fs26.writeFileSync(filePath, JSON.stringify({ lastAck: lastMessageId }), "utf-8");
14610
+ }
14611
+ function readAck(agentId) {
14612
+ const filePath = ackPath(agentId);
14613
+ if (!fs26.existsSync(filePath)) return null;
14614
+ try {
14615
+ const content = JSON.parse(fs26.readFileSync(filePath, "utf-8"));
14616
+ return content.lastAck || null;
14617
+ } catch {
14618
+ return null;
14619
+ }
14620
+ }
14621
+ function garbageCollect(agentId) {
14622
+ const ack = readAck(agentId);
14623
+ if (!ack) return 0;
14624
+ const filePath = inboxPath(agentId);
14625
+ const messages = readJsonlFile(filePath);
14626
+ const ackIndex = messages.findIndex((m) => m.id === ack);
14627
+ if (ackIndex < 0) return 0;
14628
+ const kept = messages.slice(ackIndex + 1);
14629
+ const removed = messages.length - kept.length;
14630
+ if (kept.length === 0) {
14631
+ if (fs26.existsSync(filePath)) fs26.writeFileSync(filePath, "", "utf-8");
14632
+ } else {
14633
+ fs26.writeFileSync(filePath, kept.map((m) => JSON.stringify(m)).join("\n") + "\n", "utf-8");
14634
+ }
14635
+ return removed;
14636
+ }
14637
+ function threadPath(threadId) {
14638
+ return path28.join(THREADS_DIR, `${threadId}.json`);
14639
+ }
14640
+ function createThread(topic, initiator) {
14641
+ ensureScoreDirs();
14642
+ const id = "thr-" + crypto.randomBytes(4).toString("hex");
14643
+ const now = (/* @__PURE__ */ new Date()).toISOString();
14644
+ const thread = {
14645
+ id,
14646
+ topic,
14647
+ initiator,
14648
+ participants: [initiator],
14649
+ status: "active",
14650
+ createdAt: now,
14651
+ lastActivity: now,
14652
+ messageCount: 0
14653
+ };
14654
+ fs26.writeFileSync(threadPath(id), JSON.stringify(thread, null, 2), "utf-8");
14655
+ return thread;
14656
+ }
14657
+ function loadThread(threadId) {
14658
+ const filePath = threadPath(threadId);
14659
+ if (!fs26.existsSync(filePath)) return null;
14660
+ try {
14661
+ return JSON.parse(fs26.readFileSync(filePath, "utf-8"));
14662
+ } catch {
14663
+ return null;
14664
+ }
14665
+ }
14666
+ function listThreads(status) {
14667
+ ensureScoreDirs();
14668
+ if (!fs26.existsSync(THREADS_DIR)) return [];
14669
+ const files = fs26.readdirSync(THREADS_DIR).filter((f) => f.endsWith(".json"));
14670
+ const threads = [];
14671
+ for (const file of files) {
14672
+ try {
14673
+ const content = fs26.readFileSync(path28.join(THREADS_DIR, file), "utf-8");
14674
+ const thread = JSON.parse(content);
14675
+ if (!status || thread.status === status) {
14676
+ threads.push(thread);
14677
+ }
14678
+ } catch {
14679
+ }
14680
+ }
14681
+ return threads.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
14682
+ }
14683
+ function updateThread(threadId, partial) {
14684
+ const thread = loadThread(threadId);
14685
+ if (!thread) return false;
14686
+ const updated = { ...thread, ...partial };
14687
+ fs26.writeFileSync(threadPath(threadId), JSON.stringify(updated, null, 2), "utf-8");
14688
+ return true;
14689
+ }
14690
+ function getThreadMessages(threadId) {
14691
+ const agents = listAgents();
14692
+ const messages = [];
14693
+ for (const agent of agents) {
14694
+ const inbox = readJsonlFile(inboxPath(agent.id));
14695
+ const outbox = readJsonlFile(outboxPath(agent.id));
14696
+ for (const msg of [...inbox, ...outbox]) {
14697
+ if (msg.threadRoot === threadId || msg.id === threadId) {
14698
+ if (!messages.some((m) => m.id === msg.id)) {
14699
+ messages.push(msg);
14700
+ }
14701
+ }
14702
+ }
14703
+ }
14704
+ return messages.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
14705
+ }
14706
+ function buildMessage(params) {
14707
+ return {
14708
+ id: crypto.randomUUID(),
14709
+ parentId: params.parentId,
14710
+ threadRoot: params.threadRoot,
14711
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14712
+ sender: params.sender,
14713
+ recipients: params.recipients,
14714
+ intent: params.intent,
14715
+ content: {
14716
+ text: params.text,
14717
+ diff: params.diff,
14718
+ decision: params.decision
14719
+ },
14720
+ symbols: params.symbols || [],
14721
+ attachments: params.attachments,
14722
+ metadata: params.metadata
14723
+ };
14724
+ }
14725
+ function routeMessage(message) {
14726
+ ensureScoreDirs();
14727
+ appendToOutbox(message.sender.id, message);
14728
+ let deliveryCount = 0;
14729
+ if (message.recipients && message.recipients.length > 0) {
14730
+ for (const recipient of message.recipients) {
14731
+ appendToInbox(recipient.id, message);
14732
+ deliveryCount++;
14733
+ }
14734
+ } else {
14735
+ const agents = listAgents();
14736
+ for (const agent of agents) {
14737
+ if (agent.id !== message.sender.id) {
14738
+ appendToInbox(agent.id, message);
14739
+ deliveryCount++;
14740
+ }
14741
+ }
14742
+ }
14743
+ if (message.threadRoot) {
14744
+ const thread = loadThread(message.threadRoot);
14745
+ if (thread) {
14746
+ const isParticipant = thread.participants.some((p) => p.id === message.sender.id);
14747
+ const updatedParticipants = isParticipant ? thread.participants : [...thread.participants, message.sender];
14748
+ updateThread(message.threadRoot, {
14749
+ participants: updatedParticipants,
14750
+ lastActivity: message.timestamp,
14751
+ messageCount: thread.messageCount + 1
14752
+ });
14753
+ }
14754
+ }
14755
+ return deliveryCount;
14756
+ }
14757
+ function fileRequestPath(requestId) {
14758
+ return path28.join(FILE_REQUESTS_DIR, `${requestId}.json`);
14759
+ }
14760
+ function loadTrustConfig() {
14761
+ if (!fs26.existsSync(TRUST_CONFIG_PATH)) return DEFAULT_TRUST;
14762
+ try {
14763
+ const content = fs26.readFileSync(TRUST_CONFIG_PATH, "utf-8");
14764
+ try {
14765
+ return JSON.parse(content);
14766
+ } catch {
14767
+ return DEFAULT_TRUST;
14768
+ }
14769
+ } catch {
14770
+ return DEFAULT_TRUST;
14771
+ }
14772
+ }
14773
+ function createFileRequest(params) {
14774
+ ensureScoreDirs();
14775
+ const requestId = "freq-" + crypto.randomBytes(4).toString("hex");
14776
+ const record = {
14777
+ request: {
14778
+ requestId,
14779
+ filePath: params.filePath,
14780
+ reason: params.reason,
14781
+ requester: params.requester,
14782
+ urgency: params.urgency || "normal",
14783
+ snippet: params.snippet,
14784
+ threadRoot: params.threadRoot
14785
+ },
14786
+ status: "pending",
14787
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
14788
+ };
14789
+ fs26.writeFileSync(fileRequestPath(requestId), JSON.stringify(record, null, 2), "utf-8");
14790
+ return record;
14791
+ }
14792
+ function loadFileRequest(requestId) {
14793
+ const filePath = fileRequestPath(requestId);
14794
+ if (!fs26.existsSync(filePath)) return null;
14795
+ try {
14796
+ return JSON.parse(fs26.readFileSync(filePath, "utf-8"));
14797
+ } catch {
14798
+ return null;
14799
+ }
14800
+ }
14801
+ function listFileRequests(status) {
14802
+ ensureScoreDirs();
14803
+ if (!fs26.existsSync(FILE_REQUESTS_DIR)) return [];
14804
+ const files = fs26.readdirSync(FILE_REQUESTS_DIR).filter((f) => f.endsWith(".json"));
14805
+ const requests = [];
14806
+ for (const file of files) {
14807
+ try {
14808
+ const content = fs26.readFileSync(path28.join(FILE_REQUESTS_DIR, file), "utf-8");
14809
+ const record = JSON.parse(content);
14810
+ if (!status || record.status === status) {
14811
+ requests.push(record);
14812
+ }
14813
+ } catch {
14814
+ }
14815
+ }
14816
+ return requests.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
14817
+ }
14818
+ function approveFileRequest(requestId, projectDir2, redact) {
14819
+ const record = loadFileRequest(requestId);
14820
+ if (!record) return { success: false, error: `File request not found: ${requestId}` };
14821
+ if (record.status !== "pending") return { success: false, error: `Request already ${record.status}` };
14822
+ const absolutePath = path28.resolve(projectDir2, record.request.filePath);
14823
+ if (!absolutePath.startsWith(path28.resolve(projectDir2))) {
14824
+ return { success: false, error: "File path escapes project directory" };
14825
+ }
14826
+ if (!fs26.existsSync(absolutePath)) {
14827
+ return { success: false, error: `File not found: ${record.request.filePath}` };
14828
+ }
14829
+ try {
14830
+ let content = fs26.readFileSync(absolutePath, "utf-8");
14831
+ let encoding = "utf8";
14832
+ if (redact) {
14833
+ const secretPatterns = [
14834
+ /(?:api[_-]?key|secret|token|password|credential|auth)\s*[:=]/i,
14835
+ /(?:^|\s)(?:export\s+)?[A-Z_]+(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)\s*=/,
14836
+ /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/
14837
+ ];
14838
+ content = content.split("\n").map((line) => {
14839
+ for (const pattern of secretPatterns) {
14840
+ if (pattern.test(line)) return "[REDACTED]";
14841
+ }
14842
+ return line;
14843
+ }).join("\n");
14844
+ }
14845
+ const hash = crypto.createHash("sha256").update(content).digest("hex");
14846
+ const delivery = {
14847
+ requestId,
14848
+ filePath: record.request.filePath,
14849
+ content,
14850
+ encoding,
14851
+ size: Buffer.byteLength(content),
14852
+ hash
14853
+ };
14854
+ record.status = "approved";
14855
+ record.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
14856
+ record.delivery = delivery;
14857
+ fs26.writeFileSync(fileRequestPath(requestId), JSON.stringify(record, null, 2), "utf-8");
14858
+ const deliveryMessage = buildMessage({
14859
+ sender: { id: "system", name: "File Transfer", type: "human" },
14860
+ recipients: [record.request.requester],
14861
+ intent: "fileDelivery",
14862
+ text: `File delivered: ${record.request.filePath} (${delivery.size} bytes, SHA-256: ${hash.slice(0, 12)}...)`,
14863
+ threadRoot: record.request.threadRoot,
14864
+ symbols: []
14865
+ });
14866
+ deliveryMessage.attachments = [{
14867
+ name: path28.basename(record.request.filePath),
14868
+ type: "file",
14869
+ content: delivery.content,
14870
+ encoding: delivery.encoding
14871
+ }];
14872
+ routeMessage(deliveryMessage);
14873
+ return { success: true, delivery };
14874
+ } catch (err2) {
14875
+ return { success: false, error: `Failed to read file: ${err2.message}` };
14876
+ }
14877
+ }
14878
+ function denyFileRequest(requestId, reason) {
14879
+ const record = loadFileRequest(requestId);
14880
+ if (!record || record.status !== "pending") return false;
14881
+ record.status = "denied";
14882
+ record.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
14883
+ record.denyReason = reason;
14884
+ fs26.writeFileSync(fileRequestPath(requestId), JSON.stringify(record, null, 2), "utf-8");
14885
+ const denialMessage = buildMessage({
14886
+ sender: { id: "system", name: "File Transfer", type: "human" },
14887
+ recipients: [record.request.requester],
14888
+ intent: "fileDenied",
14889
+ text: `File request denied: ${record.request.filePath}${reason ? ` \u2014 ${reason}` : ""}`,
14890
+ threadRoot: record.request.threadRoot,
14891
+ symbols: []
14892
+ });
14893
+ routeMessage(denialMessage);
14894
+ return true;
14895
+ }
14896
+ function matchesGlob(filePath, pattern) {
14897
+ let regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
14898
+ return new RegExp(`^${regex}$`).test(filePath);
14899
+ }
14900
+ function isPathDenied(filePath, config, user) {
14901
+ const trust = config || loadTrustConfig();
14902
+ if (user && trust.users[user]) {
14903
+ for (const pattern of trust.users[user].neverApprove) {
14904
+ if (matchesGlob(filePath, pattern)) return true;
14905
+ }
14906
+ }
14907
+ for (const pattern of trust.defaults.neverApprove) {
14908
+ if (matchesGlob(filePath, pattern)) return true;
14909
+ }
14910
+ return false;
14911
+ }
14912
+ function expireOldRequests() {
14913
+ const requests = listFileRequests("pending");
14914
+ let expired = 0;
14915
+ for (const record of requests) {
14916
+ const age = Date.now() - new Date(record.createdAt).getTime();
14917
+ if (age > FILE_REQUEST_TTL_MS) {
14918
+ record.status = "expired";
14919
+ record.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
14920
+ fs26.writeFileSync(
14921
+ fileRequestPath(record.request.requestId),
14922
+ JSON.stringify(record, null, 2),
14923
+ "utf-8"
14924
+ );
14925
+ expired++;
14926
+ }
14927
+ }
14928
+ return expired;
14929
+ }
14930
+ function isProcessAlive2(pid) {
14931
+ try {
14932
+ process.kill(pid, 0);
14933
+ return true;
14934
+ } catch {
14935
+ return false;
14936
+ }
14937
+ }
14938
+
14939
+ // ../paradigm-mcp/src/tools/symphony.ts
14940
+ function getSymphonyToolsList() {
14941
+ return [
14942
+ {
14943
+ name: "paradigm_symphony_poll",
14944
+ description: "Poll inbox for new notes. Call via /loop for continuous agent messaging. Returns unread notes formatted as markdown with thread context and suggested actions. Updates heartbeat. ~200 tokens.",
14945
+ inputSchema: {
14946
+ type: "object",
14947
+ properties: {}
14948
+ },
14949
+ annotations: {
14950
+ readOnlyHint: false,
14951
+ // Updates ack + poll time
14952
+ destructiveHint: false
14953
+ }
14954
+ },
14955
+ {
14956
+ name: "paradigm_symphony_send",
14957
+ description: "Send a note to other agents or broadcast. Auto-creates thread if no threadRoot provided. Supports intents: question, context, proposal, decision, action, etc. ~100 tokens.",
14958
+ inputSchema: {
14959
+ type: "object",
14960
+ properties: {
14961
+ intent: {
14962
+ type: "string",
14963
+ enum: [
14964
+ "question",
14965
+ "context",
14966
+ "clarification",
14967
+ "proposal",
14968
+ "verification",
14969
+ "action",
14970
+ "decision",
14971
+ "alert",
14972
+ "approval",
14973
+ "rejection",
14974
+ "reference",
14975
+ "handoff"
14976
+ ],
14977
+ description: "Message intent (what kind of message this is)"
14978
+ },
14979
+ text: {
14980
+ type: "string",
14981
+ description: "Message text content"
14982
+ },
14983
+ parentId: {
14984
+ type: "string",
14985
+ description: "ID of message being replied to"
14986
+ },
14987
+ threadRoot: {
14988
+ type: "string",
14989
+ description: "Thread ID to post in (auto-created if omitted)"
14990
+ },
14991
+ recipients: {
14992
+ type: "array",
14993
+ items: { type: "string" },
14994
+ description: "Agent IDs to send to (omit for broadcast)"
14995
+ },
14996
+ symbols: {
14997
+ type: "array",
14998
+ items: { type: "string" },
14999
+ description: 'Paradigm symbols referenced (e.g., ["#auth-service", "$login-flow"])'
15000
+ },
15001
+ diff: {
15002
+ type: "string",
15003
+ description: "Code diff to include with the message"
15004
+ },
15005
+ decision: {
15006
+ type: "string",
15007
+ description: "Decision text to record (for intent=decision)"
15008
+ }
15009
+ },
15010
+ required: ["intent", "text"]
15011
+ },
15012
+ annotations: {
15013
+ readOnlyHint: false,
15014
+ destructiveHint: false
15015
+ }
15016
+ },
15017
+ {
15018
+ name: "paradigm_symphony_status",
15019
+ description: "Show Symphony network status: linked agents (with awake/asleep detection), active threads, unread count, pending file requests. ~150 tokens.",
15020
+ inputSchema: {
15021
+ type: "object",
15022
+ properties: {}
15023
+ },
15024
+ annotations: {
15025
+ readOnlyHint: true,
15026
+ destructiveHint: false
15027
+ }
15028
+ },
15029
+ {
15030
+ name: "paradigm_symphony_thread",
15031
+ description: "Get full thread context: all notes in order, participants, extracted decisions, and referenced symbols. ~200 tokens.",
15032
+ inputSchema: {
15033
+ type: "object",
15034
+ properties: {
15035
+ threadId: {
15036
+ type: "string",
15037
+ description: "Thread ID (thr-XXXXXXXX format)"
15038
+ },
15039
+ depth: {
15040
+ type: "number",
15041
+ description: "Maximum messages to return (default: 50)"
15042
+ }
15043
+ },
15044
+ required: ["threadId"]
15045
+ },
15046
+ annotations: {
15047
+ readOnlyHint: true,
15048
+ destructiveHint: false
15049
+ }
15050
+ },
15051
+ {
15052
+ name: "paradigm_symphony_request_file",
15053
+ description: "Request a file from another agent's project. Human approval required (unless auto-approved in trust config). Files matching hard-deny patterns (.env, *.key, etc.) are always blocked. ~100 tokens.",
15054
+ inputSchema: {
15055
+ type: "object",
15056
+ properties: {
15057
+ filePath: {
15058
+ type: "string",
15059
+ description: 'Relative file path to request (e.g., "src/auth/middleware.ts")'
15060
+ },
15061
+ from: {
15062
+ type: "string",
15063
+ description: 'Agent ID to request file from (e.g., "backend/core")'
15064
+ },
15065
+ reason: {
15066
+ type: "string",
15067
+ description: "Why this file is needed"
15068
+ },
15069
+ snippet: {
15070
+ type: "string",
15071
+ description: 'Specific function or line range needed (e.g., "validateToken function" or "lines 50-100")'
15072
+ }
15073
+ },
15074
+ required: ["filePath", "from", "reason"]
15075
+ },
15076
+ annotations: {
15077
+ readOnlyHint: false,
15078
+ destructiveHint: false
15079
+ }
15080
+ },
15081
+ {
15082
+ name: "paradigm_symphony_approve_file",
15083
+ description: 'Approve or deny a pending file request. Use action "approve" to send file, "deny" to reject, or "approve-redacted" to send with secrets stripped. ~100 tokens.',
15084
+ inputSchema: {
15085
+ type: "object",
15086
+ properties: {
15087
+ requestId: {
15088
+ type: "string",
15089
+ description: "File request ID (freq-XXXXXXXX format)"
15090
+ },
15091
+ action: {
15092
+ type: "string",
15093
+ enum: ["approve", "deny", "approve-redacted"],
15094
+ description: "Approve, deny, or approve with redaction"
15095
+ },
15096
+ reason: {
15097
+ type: "string",
15098
+ description: "Reason for denial (required for deny action)"
15099
+ }
15100
+ },
15101
+ required: ["requestId", "action"]
15102
+ },
15103
+ annotations: {
15104
+ readOnlyHint: false,
15105
+ destructiveHint: false
15106
+ }
15107
+ }
15108
+ ];
15109
+ }
15110
+ async function handleSymphonyTool(name, args, ctx) {
15111
+ let identity = getMyIdentity(ctx.rootDir);
15112
+ if (!identity) {
15113
+ identity = registerAgent(ctx.rootDir);
15114
+ }
15115
+ switch (name) {
15116
+ case "paradigm_symphony_poll": {
15117
+ cleanStaleAgents();
15118
+ expireOldRequests();
15119
+ markAgentPollTime(identity.id);
15120
+ const messages = readInbox(identity.id);
15121
+ if (messages.length > 0) {
15122
+ const lastId = messages[messages.length - 1].id;
15123
+ acknowledgeMessages(identity.id, lastId);
15124
+ }
15125
+ garbageCollect(identity.id);
15126
+ const pendingRequests = listFileRequests("pending");
15127
+ if (messages.length === 0 && pendingRequests.length === 0) {
15128
+ return {
15129
+ handled: true,
15130
+ text: JSON.stringify({
15131
+ messages: 0,
15132
+ note: "No new notes. Score is quiet.",
15133
+ identity: identity.id
15134
+ })
15135
+ };
15136
+ }
15137
+ const formatted = formatPollOutput(messages, pendingRequests);
15138
+ return {
15139
+ handled: true,
15140
+ text: formatted
15141
+ };
15142
+ }
15143
+ case "paradigm_symphony_send": {
15144
+ const intent = args.intent;
15145
+ const text = args.text;
15146
+ const parentId = args.parentId;
15147
+ let threadRoot = args.threadRoot;
15148
+ const recipientIds = args.recipients;
15149
+ const symbols = args.symbols;
15150
+ const diff = args.diff;
15151
+ const decision = args.decision;
15152
+ let threadCreated = false;
15153
+ if (!threadRoot && !parentId) {
15154
+ const topic = text.length > 60 ? text.slice(0, 60) + "..." : text;
15155
+ const thread = createThread(topic, identityToParticipant(identity));
15156
+ threadRoot = thread.id;
15157
+ threadCreated = true;
15158
+ }
15159
+ let recipients;
15160
+ if (recipientIds && recipientIds.length > 0) {
15161
+ const allAgents = listAgents();
15162
+ recipients = recipientIds.map((id) => {
15163
+ const agent = allAgents.find((a) => a.id === id);
15164
+ if (agent) return identityToParticipant(agent);
15165
+ return { id, name: id, type: "agent" };
15166
+ });
15167
+ }
15168
+ const message = buildMessage({
15169
+ sender: identityToParticipant(identity),
15170
+ recipients,
15171
+ intent,
15172
+ text,
15173
+ parentId,
15174
+ threadRoot,
15175
+ symbols,
15176
+ diff,
15177
+ decision
15178
+ });
15179
+ const deliveryCount = routeMessage(message);
15180
+ emitSymphonyEvent(message).catch(() => {
15181
+ });
15182
+ if (threadCreated && threadRoot) {
15183
+ const threadEvent = {
15184
+ id: `thread-created-${threadRoot}`,
15185
+ threadRoot,
15186
+ timestamp: message.timestamp,
15187
+ sender: message.sender,
15188
+ intent: "context",
15189
+ content: { text: `Thread created: ${message.content.text.slice(0, 60)}` },
15190
+ symbols: []
15191
+ };
15192
+ fetch(`${SENTINEL_URL}/api/events`, {
15193
+ method: "POST",
15194
+ headers: { "Content-Type": "application/json" },
15195
+ body: JSON.stringify({
15196
+ schemaId: "paradigm-symphony",
15197
+ eventType: "thread:created",
15198
+ category: "lifecycle",
15199
+ timestamp: message.timestamp,
15200
+ scopeValue: threadRoot,
15201
+ service: message.sender.project || "symphony",
15202
+ severity: "info",
15203
+ data: {
15204
+ topic: message.content.text.slice(0, 60),
15205
+ initiator: message.sender.name,
15206
+ threadId: threadRoot
15207
+ }
15208
+ })
15209
+ }).catch(() => {
15210
+ });
15211
+ }
15212
+ return {
15213
+ handled: true,
15214
+ text: JSON.stringify({
15215
+ sent: true,
15216
+ messageId: message.id,
15217
+ threadId: threadRoot,
15218
+ threadCreated,
15219
+ deliveredTo: deliveryCount,
15220
+ intent
15221
+ })
15222
+ };
15223
+ }
15224
+ case "paradigm_symphony_status": {
15225
+ cleanStaleAgents();
15226
+ const agents = listAgents();
15227
+ const threads = listThreads("active");
15228
+ const unread = readInbox(identity.id);
15229
+ const pendingRequests = listFileRequests("pending");
15230
+ return {
15231
+ handled: true,
15232
+ text: JSON.stringify({
15233
+ identity: {
15234
+ id: identity.id,
15235
+ project: identity.project,
15236
+ role: identity.role
15237
+ },
15238
+ agents: agents.map((a) => ({
15239
+ id: a.id,
15240
+ name: a.name,
15241
+ project: a.project,
15242
+ role: a.role,
15243
+ status: isAgentAsleep(a) ? "asleep" : "awake",
15244
+ lastPoll: a.lastPoll
15245
+ })),
15246
+ activeThreads: threads.map((t) => ({
15247
+ id: t.id,
15248
+ topic: t.topic,
15249
+ participants: t.participants.length,
15250
+ messageCount: t.messageCount,
15251
+ lastActivity: t.lastActivity
15252
+ })),
15253
+ unreadCount: unread.length,
15254
+ pendingFileRequests: pendingRequests.length
15255
+ }, null, 2)
15256
+ };
15257
+ }
15258
+ case "paradigm_symphony_thread": {
15259
+ const threadId = args.threadId;
15260
+ const depth = args.depth || 50;
15261
+ const thread = loadThread(threadId);
15262
+ if (!thread) {
15263
+ return {
15264
+ handled: true,
15265
+ text: JSON.stringify({ error: `Thread not found: ${threadId}` })
15266
+ };
15267
+ }
15268
+ const messages = getThreadMessages(threadId).slice(0, depth);
15269
+ const decisions = [];
15270
+ const symbolsDiscussed = /* @__PURE__ */ new Set();
15271
+ const filesReferenced = /* @__PURE__ */ new Set();
15272
+ for (const msg of messages) {
15273
+ if (msg.content.decision) decisions.push(msg.content.decision);
15274
+ if (msg.intent === "decision" && msg.content.text) decisions.push(msg.content.text);
15275
+ for (const sym of msg.symbols) symbolsDiscussed.add(sym);
15276
+ if (msg.attachments) {
15277
+ for (const att of msg.attachments) {
15278
+ if (att.type === "file") filesReferenced.add(att.name);
15279
+ }
15280
+ }
15281
+ }
15282
+ return {
15283
+ handled: true,
15284
+ text: JSON.stringify({
15285
+ thread: {
15286
+ id: thread.id,
15287
+ topic: thread.topic,
15288
+ status: thread.status,
15289
+ createdAt: thread.createdAt,
15290
+ decision: thread.decision
15291
+ },
15292
+ participants: thread.participants,
15293
+ messages: messages.map((m) => ({
15294
+ id: m.id,
15295
+ sender: m.sender.name,
15296
+ intent: m.intent,
15297
+ text: m.content.text,
15298
+ timestamp: m.timestamp,
15299
+ symbols: m.symbols,
15300
+ hasDiff: !!m.content.diff,
15301
+ hasDecision: !!m.content.decision
15302
+ })),
15303
+ decisions,
15304
+ symbolsDiscussed: [...symbolsDiscussed],
15305
+ filesReferenced: [...filesReferenced]
15306
+ }, null, 2)
15307
+ };
15308
+ }
15309
+ case "paradigm_symphony_request_file": {
15310
+ const filePath = args.filePath;
15311
+ const from = args.from;
15312
+ const reason = args.reason;
15313
+ const snippet = args.snippet;
15314
+ const trustConfig = loadTrustConfig();
15315
+ if (isPathDenied(filePath, trustConfig)) {
15316
+ return {
15317
+ handled: true,
15318
+ text: JSON.stringify({
15319
+ error: `File path "${filePath}" is on the hard-deny list and cannot be requested.`,
15320
+ deniedPatterns: trustConfig.defaults.neverApprove
15321
+ })
15322
+ };
15323
+ }
15324
+ const record = createFileRequest({
15325
+ filePath,
15326
+ requester: identityToParticipant(identity),
15327
+ reason,
15328
+ snippet,
15329
+ threadRoot: void 0
15330
+ });
15331
+ const requestMessage = buildMessage({
15332
+ sender: identityToParticipant(identity),
15333
+ recipients: [{ id: from, name: from, type: "agent" }],
15334
+ intent: "fileRequest",
15335
+ text: `Requesting file: ${filePath}
15336
+ Reason: ${reason}${snippet ? `
15337
+ Snippet: ${snippet}` : ""}`,
15338
+ symbols: []
15339
+ });
15340
+ routeMessage(requestMessage);
15341
+ return {
15342
+ handled: true,
15343
+ text: JSON.stringify({
15344
+ requestId: record.request.requestId,
15345
+ status: "pending",
15346
+ filePath,
15347
+ from,
15348
+ message: `File request created. The owning agent's human must approve via "paradigm symphony approve ${record.request.requestId}" or "paradigm_symphony_approve_file".`
15349
+ })
15350
+ };
15351
+ }
15352
+ case "paradigm_symphony_approve_file": {
15353
+ const requestId = args.requestId;
15354
+ const action = args.action;
15355
+ const reason = args.reason;
15356
+ if (action === "deny") {
15357
+ const success = denyFileRequest(requestId, reason);
15358
+ return {
15359
+ handled: true,
15360
+ text: JSON.stringify({
15361
+ success,
15362
+ requestId,
15363
+ action: "denied",
15364
+ reason: reason || "No reason provided"
15365
+ })
15366
+ };
15367
+ }
15368
+ const redact = action === "approve-redacted";
15369
+ const result = approveFileRequest(requestId, ctx.rootDir, redact);
15370
+ if (!result.success) {
15371
+ return {
15372
+ handled: true,
15373
+ text: JSON.stringify({
15374
+ success: false,
15375
+ requestId,
15376
+ error: result.error
15377
+ })
15378
+ };
15379
+ }
15380
+ return {
15381
+ handled: true,
15382
+ text: JSON.stringify({
15383
+ success: true,
15384
+ requestId,
15385
+ action: redact ? "approved-redacted" : "approved",
15386
+ filePath: result.delivery?.filePath,
15387
+ size: result.delivery?.size,
15388
+ hash: result.delivery?.hash
15389
+ })
15390
+ };
15391
+ }
15392
+ default:
15393
+ return { handled: false, text: "" };
15394
+ }
15395
+ }
15396
+ function identityToParticipant(identity) {
15397
+ return {
15398
+ id: identity.id,
15399
+ name: identity.name,
15400
+ type: identity.type || "agent",
15401
+ project: identity.project,
15402
+ role: identity.role
15403
+ };
15404
+ }
15405
+ function formatPollOutput(messages, pendingRequests) {
15406
+ const parts = [];
15407
+ const byThread = /* @__PURE__ */ new Map();
15408
+ for (const msg of messages) {
15409
+ const threadId = msg.threadRoot || "direct";
15410
+ if (!byThread.has(threadId)) byThread.set(threadId, []);
15411
+ byThread.get(threadId).push(msg);
15412
+ }
15413
+ for (const [threadId, threadMsgs] of byThread) {
15414
+ let threadTopic = threadId;
15415
+ if (threadId !== "direct") {
15416
+ const thread = loadThread(threadId);
15417
+ if (thread) threadTopic = thread.topic;
15418
+ }
15419
+ parts.push(`## Symphony: ${threadMsgs.length} new note${threadMsgs.length !== 1 ? "s" : ""} in "${threadTopic}"
15420
+ `);
15421
+ for (let i = 0; i < threadMsgs.length; i++) {
15422
+ const msg = threadMsgs[i];
15423
+ const time = new Date(msg.timestamp).toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
15424
+ const senderLabel = `${msg.sender.name}${msg.sender.project ? ` (${msg.sender.project})` : ""}`;
15425
+ parts.push(`### ${i + 1}. ${senderLabel} \u2014 ${capitalize(msg.intent)} (${time})`);
15426
+ parts.push(`> ${msg.content.text.split("\n").join("\n> ")}`);
15427
+ if (msg.symbols.length > 0) {
15428
+ parts.push(`> Symbols: ${msg.symbols.join(", ")}`);
15429
+ }
15430
+ if (msg.content.diff) {
15431
+ parts.push(`
15432
+ \`\`\`diff
15433
+ ${msg.content.diff}
15434
+ \`\`\``);
15435
+ }
15436
+ if (msg.content.decision) {
15437
+ parts.push(`
15438
+ **Decision:** ${msg.content.decision}`);
15439
+ }
15440
+ const action = suggestAction(msg);
15441
+ if (action) parts.push(`
15442
+ **Suggested action:** ${action}`);
15443
+ parts.push("");
15444
+ }
15445
+ }
15446
+ if (pendingRequests.length > 0) {
15447
+ parts.push(`## Pending File Requests (${pendingRequests.length})
15448
+ `);
15449
+ for (const req of pendingRequests) {
15450
+ parts.push(`- **${req.request.filePath}** from ${req.request.requester.name}: ${req.request.reason}`);
15451
+ parts.push(` Approve: \`paradigm_symphony_approve_file({ requestId: "${req.request.requestId}", action: "approve" })\``);
15452
+ }
15453
+ parts.push("");
15454
+ }
15455
+ return parts.join("\n");
15456
+ }
15457
+ function suggestAction(msg) {
15458
+ switch (msg.intent) {
15459
+ case "question":
15460
+ return 'Reply with paradigm_symphony_send using intent "context" or "clarification"';
15461
+ case "proposal":
15462
+ return 'Reply with intent "approval" or "rejection"';
15463
+ case "fileRequest":
15464
+ return "Use paradigm_symphony_approve_file to approve or deny";
15465
+ case "handoff":
15466
+ return "Review handoff context and continue the work";
15467
+ case "alert":
15468
+ return 'Investigate the alert and reply with intent "action"';
15469
+ case "verification":
15470
+ return 'Confirm with intent "approval" or clarify with "clarification"';
15471
+ default:
15472
+ return null;
15473
+ }
15474
+ }
15475
+ function capitalize(s) {
15476
+ return s.charAt(0).toUpperCase() + s.slice(1);
15477
+ }
15478
+ var SENTINEL_URL = "http://localhost:3838";
15479
+ var symphonySchemaRegistered = false;
15480
+ function intentToEventType(intent) {
15481
+ switch (intent) {
15482
+ case "question":
15483
+ return "note:question";
15484
+ case "context":
15485
+ return "note:context";
15486
+ case "clarification":
15487
+ return "note:clarification";
15488
+ case "proposal":
15489
+ return "note:proposal";
15490
+ case "verification":
15491
+ return "note:verification";
15492
+ case "action":
15493
+ return "note:action";
15494
+ case "decision":
15495
+ return "note:decision";
15496
+ case "alert":
15497
+ return "note:alert";
15498
+ case "approval":
15499
+ return "note:approval";
15500
+ case "rejection":
15501
+ return "note:rejection";
15502
+ case "reference":
15503
+ return "note:reference";
15504
+ case "handoff":
15505
+ return "note:handoff";
15506
+ case "fileRequest":
15507
+ return "file:requested";
15508
+ case "fileApproved":
15509
+ return "file:approved";
15510
+ case "fileDenied":
15511
+ return "file:denied";
15512
+ case "fileDelivery":
15513
+ return "file:delivered";
15514
+ default:
15515
+ return "note:context";
15516
+ }
15517
+ }
15518
+ function eventTypeToCategory(eventType) {
15519
+ if (eventType.startsWith("note:")) {
15520
+ const intent = eventType.split(":")[1];
15521
+ if (["question", "context", "clarification", "verification", "reference"].includes(intent)) return "dialogue";
15522
+ if (["proposal", "action"].includes(intent)) return "action";
15523
+ if (["decision", "approval", "rejection"].includes(intent)) return "outcome";
15524
+ if (intent === "alert") return "system";
15525
+ if (intent === "handoff") return "lifecycle";
15526
+ }
15527
+ if (eventType.startsWith("thread:") || eventType.startsWith("participant:")) return "lifecycle";
15528
+ if (eventType.startsWith("file:")) return "transfer";
15529
+ return "dialogue";
15530
+ }
15531
+ async function emitSymphonyEvent(note) {
15532
+ try {
15533
+ if (!symphonySchemaRegistered) {
15534
+ await fetch(`${SENTINEL_URL}/api/schemas`, {
15535
+ method: "POST",
15536
+ headers: { "Content-Type": "application/json" },
15537
+ body: JSON.stringify({
15538
+ id: "paradigm-symphony",
15539
+ version: "1.0.0",
15540
+ name: "Symphony Conversations",
15541
+ description: "Agent-to-agent messaging events from The Score protocol"
15542
+ })
15543
+ }).catch(() => {
15544
+ });
15545
+ symphonySchemaRegistered = true;
15546
+ }
15547
+ const eventType = intentToEventType(note.intent);
15548
+ const category = eventTypeToCategory(eventType);
15549
+ await fetch(`${SENTINEL_URL}/api/events`, {
15550
+ method: "POST",
15551
+ headers: { "Content-Type": "application/json" },
15552
+ body: JSON.stringify({
15553
+ schemaId: "paradigm-symphony",
15554
+ eventType,
15555
+ category,
15556
+ timestamp: note.timestamp,
15557
+ scopeValue: note.threadRoot || note.id,
15558
+ service: note.sender.project || "symphony",
15559
+ severity: category === "system" ? "error" : category === "outcome" ? "warn" : "info",
15560
+ parentEventId: note.parentId,
15561
+ data: {
15562
+ sender: note.sender.name,
15563
+ senderRole: note.sender.role || "core",
15564
+ text: note.content.text,
15565
+ symbols: note.symbols,
15566
+ diff: note.content.diff,
15567
+ decision: note.content.decision,
15568
+ parentId: note.parentId,
15569
+ threadId: note.threadRoot
15570
+ }
15571
+ })
15572
+ });
15573
+ } catch {
15574
+ }
15575
+ }
15576
+
15577
+ // ../paradigm-mcp/src/tools/university.ts
15578
+ import { execSync as execSync7 } from "child_process";
15579
+ import * as os5 from "os";
15580
+ function resolveAuthor2() {
15581
+ const envAuthor = process.env.PARADIGM_AUTHOR;
15582
+ if (envAuthor) return sanitize3(envAuthor);
15583
+ try {
15584
+ const gitName = execSync7("git config user.name", { encoding: "utf-8", timeout: 3e3 }).trim();
15585
+ if (gitName) return sanitize3(gitName);
15586
+ } catch {
15587
+ }
15588
+ try {
15589
+ const username = os5.userInfo().username;
15590
+ if (username) return sanitize3(username);
15591
+ } catch {
15592
+ }
15593
+ return "unknown";
15594
+ }
15595
+ function sanitize3(name) {
15596
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 20) || "unknown";
15597
+ }
15598
+ function todayStr() {
15599
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
15600
+ }
15601
+ function getUniversityToolsList() {
15602
+ return [
15603
+ {
15604
+ name: "paradigm_university_search",
15605
+ description: "Search project university content by type, tag, difficulty, or symbol. Returns matching content items. ~150 tokens.",
15606
+ inputSchema: {
15607
+ type: "object",
15608
+ properties: {
15609
+ type: {
15610
+ type: "string",
15611
+ enum: ["note", "policy", "guide", "runbook", "quiz", "path"],
15612
+ description: "Filter by content type"
15613
+ },
15614
+ tag: {
15615
+ type: "string",
15616
+ description: "Filter by tag prefix"
15617
+ },
15618
+ difficulty: {
15619
+ type: "string",
15620
+ enum: ["beginner", "intermediate", "advanced"],
15621
+ description: "Filter by difficulty level"
15622
+ },
15623
+ symbol: {
15624
+ type: "string",
15625
+ description: 'Filter by Paradigm symbol (e.g., "#api-gateway")'
15626
+ },
15627
+ query: {
15628
+ type: "string",
15629
+ description: "Free-text search in title and tags"
15630
+ },
15631
+ limit: {
15632
+ type: "number",
15633
+ description: "Maximum results (default: 20)"
15634
+ }
15635
+ }
15636
+ },
15637
+ annotations: {
15638
+ readOnlyHint: true,
15639
+ destructiveHint: false
15640
+ }
15641
+ },
15642
+ {
15643
+ name: "paradigm_university_get",
15644
+ description: "Fetch a university content item by ID. Returns full content including body for notes/policies and questions for quizzes. ~300 tokens.",
15645
+ inputSchema: {
15646
+ type: "object",
15647
+ properties: {
15648
+ id: {
15649
+ type: "string",
15650
+ description: 'Content ID (e.g., "N-architecture-overview", "Q-onboarding-basics", "LP-new-engineer")'
15651
+ }
15652
+ },
15653
+ required: ["id"]
15654
+ },
15655
+ annotations: {
15656
+ readOnlyHint: true,
15657
+ destructiveHint: false
15658
+ }
15659
+ },
15660
+ {
15661
+ name: "paradigm_university_create",
15662
+ description: "Create a new university content item (note, policy, quiz, or learning path). Auto-generates timestamps and resolves author. ~100 tokens.",
15663
+ inputSchema: {
15664
+ type: "object",
15665
+ properties: {
15666
+ type: {
15667
+ type: "string",
15668
+ enum: ["note", "policy", "guide", "runbook", "quiz", "path"],
15669
+ description: "Content type to create"
15670
+ },
15671
+ title: {
15672
+ type: "string",
15673
+ description: "Content title"
15674
+ },
15675
+ body: {
15676
+ type: "string",
15677
+ description: "Markdown body for notes/policies. Quiz/path YAML content for those types."
15678
+ },
15679
+ tags: {
15680
+ type: "array",
15681
+ items: { type: "string" },
15682
+ description: "Tags for classification"
15683
+ },
15684
+ symbols: {
15685
+ type: "array",
15686
+ items: { type: "string" },
15687
+ description: "Paradigm symbols referenced by this content"
15688
+ },
15689
+ difficulty: {
15690
+ type: "string",
15691
+ enum: ["beginner", "intermediate", "advanced"],
15692
+ description: "Difficulty level (default: beginner)"
15693
+ },
15694
+ estimatedMinutes: {
15695
+ type: "number",
15696
+ description: "Estimated reading/completion time in minutes"
15697
+ },
15698
+ prerequisites: {
15699
+ type: "array",
15700
+ items: { type: "string" },
15701
+ description: "IDs of prerequisite content items"
15702
+ },
15703
+ // Quiz-specific fields
15704
+ passThreshold: {
15705
+ type: "number",
15706
+ description: "For quizzes: pass threshold 0.0-1.0 (default: 0.7)"
15707
+ },
15708
+ questions: {
15709
+ type: "array",
15710
+ description: "For quizzes: array of {id, question, choices: {A:..., B:...}, correct, explanation?}"
15711
+ },
15712
+ // Path-specific fields
15713
+ ordered: {
15714
+ type: "boolean",
15715
+ description: "For learning paths: whether steps must be completed in order"
15716
+ },
15717
+ steps: {
15718
+ type: "array",
15719
+ description: "For learning paths: array of {content, required, passRequired?, note?}"
15720
+ }
15721
+ },
15722
+ required: ["type", "title"]
15723
+ },
15724
+ annotations: {
15725
+ readOnlyHint: false,
15726
+ destructiveHint: false
15727
+ }
15728
+ },
15729
+ {
15730
+ name: "paradigm_university_update",
15731
+ description: "Update an existing university content item. Specify only the fields to change. ~100 tokens.",
15732
+ inputSchema: {
15733
+ type: "object",
15734
+ properties: {
15735
+ id: {
15736
+ type: "string",
15737
+ description: "Content ID to update"
15738
+ },
15739
+ title: { type: "string", description: "New title" },
15740
+ body: { type: "string", description: "New body content" },
15741
+ tags: { type: "array", items: { type: "string" }, description: "New tags" },
15742
+ symbols: { type: "array", items: { type: "string" }, description: "New symbols" },
15743
+ difficulty: { type: "string", enum: ["beginner", "intermediate", "advanced"] },
15744
+ estimatedMinutes: { type: "number" }
15745
+ },
15746
+ required: ["id"]
15747
+ },
15748
+ annotations: {
15749
+ readOnlyHint: false,
15750
+ destructiveHint: false
15751
+ }
15752
+ },
15753
+ {
15754
+ name: "paradigm_university_quiz",
15755
+ description: "Get a quiz for taking \u2014 returns questions WITHOUT answers. Use paradigm_university_submit to submit answers. ~200 tokens.",
15756
+ inputSchema: {
15757
+ type: "object",
15758
+ properties: {
15759
+ id: {
15760
+ type: "string",
15761
+ description: 'Quiz ID (e.g., "Q-onboarding-basics")'
15762
+ }
15763
+ },
15764
+ required: ["id"]
15765
+ },
15766
+ annotations: {
15767
+ readOnlyHint: true,
15768
+ destructiveHint: false
15769
+ }
15770
+ },
15771
+ {
15772
+ name: "paradigm_university_submit",
15773
+ description: "Submit quiz answers for grading. Returns score and saves diploma if passed. ~150 tokens.",
15774
+ inputSchema: {
15775
+ type: "object",
15776
+ properties: {
15777
+ quizId: {
15778
+ type: "string",
15779
+ description: "Quiz ID"
15780
+ },
15781
+ answers: {
15782
+ type: "object",
15783
+ description: 'Map of question ID to answer key (e.g., {"q1": "B", "q2": "A"})'
15784
+ },
15785
+ student: {
15786
+ type: "string",
15787
+ description: "Student name (auto-resolved if omitted)"
15788
+ }
15789
+ },
15790
+ required: ["quizId", "answers"]
15791
+ },
15792
+ annotations: {
15793
+ readOnlyHint: false,
15794
+ destructiveHint: false
15795
+ }
15796
+ },
15797
+ {
15798
+ name: "paradigm_university_onboard",
15799
+ description: "Get recommended onboarding sequence for the project university. Shows learning paths, suggested content, and completion status. ~200 tokens.",
15800
+ inputSchema: {
15801
+ type: "object",
15802
+ properties: {
15803
+ student: {
15804
+ type: "string",
15805
+ description: "Student name to check completion (auto-resolved if omitted)"
15806
+ }
15807
+ }
15808
+ },
15809
+ annotations: {
15810
+ readOnlyHint: true,
15811
+ destructiveHint: false
15812
+ }
15813
+ },
15814
+ {
15815
+ name: "paradigm_university_diplomas",
15816
+ description: "List earned diplomas (PLSAT, quiz completions, path completions). ~100 tokens.",
15817
+ inputSchema: {
15818
+ type: "object",
15819
+ properties: {
15820
+ student: {
15821
+ type: "string",
15822
+ description: "Filter by student name"
15823
+ },
15824
+ type: {
15825
+ type: "string",
15826
+ enum: ["plsat", "quiz", "path"],
15827
+ description: "Filter by diploma type"
15828
+ }
15829
+ }
15830
+ },
15831
+ annotations: {
15832
+ readOnlyHint: true,
15833
+ destructiveHint: false
15834
+ }
15835
+ },
15836
+ {
15837
+ name: "paradigm_university_validate",
15838
+ description: "Validate university content integrity: schema, symbol refs, prerequisites, quiz structure. ~200 tokens.",
15839
+ inputSchema: {
15840
+ type: "object",
15841
+ properties: {
15842
+ id: {
15843
+ type: "string",
15844
+ description: "Content ID to validate (validates all if omitted)"
15845
+ },
15846
+ deep: {
15847
+ type: "boolean",
15848
+ description: "Enable deep cross-reference checks against scan-index (default: false)"
15849
+ }
15850
+ }
15851
+ },
15852
+ annotations: {
15853
+ readOnlyHint: true,
15854
+ destructiveHint: false
15855
+ }
15856
+ }
15857
+ ];
15858
+ }
15859
+ async function handleUniversityTool(name, args, ctx) {
15860
+ if (name === "paradigm_university_search") {
15861
+ const results = searchContent(ctx.rootDir, {
15862
+ type: args.type,
15863
+ tag: args.tag,
15864
+ difficulty: args.difficulty,
15865
+ symbol: args.symbol,
15866
+ query: args.query,
15867
+ limit: args.limit
15868
+ });
15869
+ const text = JSON.stringify({
15870
+ count: results.length,
15871
+ results: results.map((r) => ({
15872
+ id: r.id,
15873
+ title: r.title,
15874
+ type: r.type,
15875
+ difficulty: r.difficulty,
15876
+ tags: r.tags,
15877
+ symbols: r.symbols
15878
+ }))
15879
+ }, null, 2);
15880
+ trackToolCall(text.length, name);
15881
+ return { handled: true, text };
15882
+ }
15883
+ if (name === "paradigm_university_get") {
15884
+ const id = args.id;
15885
+ if (!id) return { handled: true, text: JSON.stringify({ error: "id is required" }) };
15886
+ const note = loadNote(ctx.rootDir, id);
15887
+ if (note) {
15888
+ const text2 = JSON.stringify({
15889
+ id: note.frontmatter.id,
15890
+ title: note.frontmatter.title,
15891
+ type: note.frontmatter.type,
15892
+ author: note.frontmatter.author,
15893
+ created: note.frontmatter.created,
15894
+ updated: note.frontmatter.updated,
15895
+ tags: note.frontmatter.tags,
15896
+ symbols: note.frontmatter.symbols,
15897
+ difficulty: note.frontmatter.difficulty,
15898
+ prerequisites: note.frontmatter.prerequisites,
15899
+ body: note.body
15900
+ }, null, 2);
15901
+ trackToolCall(text2.length, name);
15902
+ return { handled: true, text: text2 };
15903
+ }
15904
+ const quiz = loadQuiz(ctx.rootDir, id);
15905
+ if (quiz) {
15906
+ const text2 = JSON.stringify(quiz, null, 2);
15907
+ trackToolCall(text2.length, name);
15908
+ return { handled: true, text: text2 };
15909
+ }
15910
+ const lp = loadPath(ctx.rootDir, id);
15911
+ if (lp) {
15912
+ const text2 = JSON.stringify(lp, null, 2);
15913
+ trackToolCall(text2.length, name);
15914
+ return { handled: true, text: text2 };
15915
+ }
15916
+ const text = JSON.stringify({ error: `Content "${id}" not found` });
15917
+ trackToolCall(text.length, name);
15918
+ return { handled: true, text };
15919
+ }
15920
+ if (name === "paradigm_university_create") {
15921
+ const contentType = args.type;
15922
+ const title = args.title;
15923
+ if (!contentType || !title) {
15924
+ return { handled: true, text: JSON.stringify({ error: "type and title are required" }) };
15925
+ }
15926
+ const author = resolveAuthor2();
15927
+ const today = todayStr();
15928
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
15929
+ if (contentType === "quiz") {
15930
+ const id2 = `Q-${slug}`;
15931
+ const quiz = {
15932
+ id: id2,
15933
+ title,
15934
+ description: args.body || "",
15935
+ author,
15936
+ created: today,
15937
+ updated: today,
15938
+ tags: args.tags || [],
15939
+ symbols: args.symbols || [],
15940
+ difficulty: args.difficulty || "beginner",
15941
+ estimatedMinutes: args.estimatedMinutes,
15942
+ passThreshold: args.passThreshold ?? 0.7,
15943
+ questions: args.questions || []
15944
+ };
15945
+ saveQuiz(ctx.rootDir, quiz);
15946
+ rebuildUniversityIndex(ctx.rootDir);
15947
+ const text2 = JSON.stringify({ created: id2, type: "quiz", file: `content/quizzes/${id2}.yaml` }, null, 2);
15948
+ trackToolCall(text2.length, name);
15949
+ return { handled: true, text: text2 };
15950
+ }
15951
+ if (contentType === "path") {
15952
+ const id2 = `LP-${slug}`;
15953
+ const lp = {
15954
+ id: id2,
15955
+ title,
15956
+ description: args.body || "",
15957
+ author,
15958
+ created: today,
15959
+ updated: today,
15960
+ tags: args.tags || [],
15961
+ ordered: args.ordered ?? true,
15962
+ steps: args.steps || []
15963
+ };
15964
+ savePath(ctx.rootDir, lp);
15965
+ rebuildUniversityIndex(ctx.rootDir);
15966
+ const text2 = JSON.stringify({ created: id2, type: "path", file: `content/paths/${id2}.yaml` }, null, 2);
15967
+ trackToolCall(text2.length, name);
15968
+ return { handled: true, text: text2 };
15969
+ }
15970
+ const prefix = contentType === "policy" ? "P" : "N";
15971
+ const id = `${prefix}-${slug}`;
15972
+ const frontmatter = {
15973
+ id,
15974
+ title,
15975
+ type: contentType,
15976
+ author,
15977
+ created: today,
15978
+ updated: today,
15979
+ tags: args.tags || [],
15980
+ symbols: args.symbols || [],
15981
+ difficulty: args.difficulty || "beginner",
15982
+ estimatedMinutes: args.estimatedMinutes,
15983
+ prerequisites: args.prerequisites || []
15984
+ };
15985
+ saveNote(ctx.rootDir, frontmatter, args.body || "");
15986
+ rebuildUniversityIndex(ctx.rootDir);
15987
+ const subdir = contentType === "policy" ? "policies" : "notes";
15988
+ const text = JSON.stringify({ created: id, type: contentType, file: `content/${subdir}/${id}.md` }, null, 2);
15989
+ trackToolCall(text.length, name);
15990
+ return { handled: true, text };
15991
+ }
15992
+ if (name === "paradigm_university_update") {
15993
+ const id = args.id;
15994
+ if (!id) return { handled: true, text: JSON.stringify({ error: "id is required" }) };
15995
+ const today = todayStr();
15996
+ const note = loadNote(ctx.rootDir, id);
15997
+ if (note) {
15998
+ const fm = { ...note.frontmatter };
15999
+ if (args.title) fm.title = args.title;
16000
+ if (args.tags) fm.tags = args.tags;
16001
+ if (args.symbols) fm.symbols = args.symbols;
16002
+ if (args.difficulty) fm.difficulty = args.difficulty;
16003
+ if (args.estimatedMinutes !== void 0) fm.estimatedMinutes = args.estimatedMinutes;
16004
+ fm.updated = today;
16005
+ const body = args.body ?? note.body;
16006
+ saveNote(ctx.rootDir, fm, body);
16007
+ rebuildUniversityIndex(ctx.rootDir);
16008
+ const text2 = JSON.stringify({ updated: id, type: fm.type }, null, 2);
16009
+ trackToolCall(text2.length, name);
16010
+ return { handled: true, text: text2 };
16011
+ }
16012
+ const quiz = loadQuiz(ctx.rootDir, id);
16013
+ if (quiz) {
16014
+ if (args.title) quiz.title = args.title;
16015
+ if (args.tags) quiz.tags = args.tags;
16016
+ if (args.symbols) quiz.symbols = args.symbols;
16017
+ if (args.difficulty) quiz.difficulty = args.difficulty;
16018
+ quiz.updated = today;
16019
+ saveQuiz(ctx.rootDir, quiz);
16020
+ rebuildUniversityIndex(ctx.rootDir);
16021
+ const text2 = JSON.stringify({ updated: id, type: "quiz" }, null, 2);
16022
+ trackToolCall(text2.length, name);
16023
+ return { handled: true, text: text2 };
16024
+ }
16025
+ const lp = loadPath(ctx.rootDir, id);
16026
+ if (lp) {
16027
+ if (args.title) lp.title = args.title;
16028
+ if (args.tags) lp.tags = args.tags;
16029
+ lp.updated = today;
16030
+ savePath(ctx.rootDir, lp);
16031
+ rebuildUniversityIndex(ctx.rootDir);
16032
+ const text2 = JSON.stringify({ updated: id, type: "path" }, null, 2);
16033
+ trackToolCall(text2.length, name);
16034
+ return { handled: true, text: text2 };
16035
+ }
16036
+ const text = JSON.stringify({ error: `Content "${id}" not found` });
16037
+ trackToolCall(text.length, name);
16038
+ return { handled: true, text };
16039
+ }
16040
+ if (name === "paradigm_university_quiz") {
16041
+ const id = args.id;
16042
+ const quiz = loadQuiz(ctx.rootDir, id);
16043
+ if (!quiz) {
16044
+ const text2 = JSON.stringify({ error: `Quiz "${id}" not found` });
16045
+ trackToolCall(text2.length, name);
16046
+ return { handled: true, text: text2 };
16047
+ }
16048
+ const sanitized = {
16049
+ id: quiz.id,
16050
+ title: quiz.title,
16051
+ description: quiz.description,
16052
+ difficulty: quiz.difficulty,
16053
+ passThreshold: quiz.passThreshold,
16054
+ questionCount: quiz.questions.length,
16055
+ questions: quiz.questions.map((q) => ({
16056
+ id: q.id,
16057
+ question: q.question,
16058
+ choices: q.choices
16059
+ }))
16060
+ };
16061
+ const text = JSON.stringify(sanitized, null, 2);
16062
+ trackToolCall(text.length, name);
16063
+ return { handled: true, text };
16064
+ }
16065
+ if (name === "paradigm_university_submit") {
16066
+ const quizId = args.quizId;
16067
+ const answers = args.answers;
16068
+ const student = args.student || resolveAuthor2();
16069
+ const quiz = loadQuiz(ctx.rootDir, quizId);
16070
+ if (!quiz) {
16071
+ const text2 = JSON.stringify({ error: `Quiz "${quizId}" not found` });
16072
+ trackToolCall(text2.length, name);
16073
+ return { handled: true, text: text2 };
16074
+ }
16075
+ let correct = 0;
16076
+ const total = quiz.questions.length;
16077
+ const feedback = [];
16078
+ for (const q of quiz.questions) {
16079
+ const answer = answers[q.id];
16080
+ const isCorrect = answer === q.correct;
16081
+ if (isCorrect) correct++;
16082
+ feedback.push({
16083
+ id: q.id,
16084
+ correct: isCorrect,
16085
+ ...isCorrect ? {} : { expected: q.correct },
16086
+ ...q.explanation ? { explanation: q.explanation } : {}
16087
+ });
16088
+ }
16089
+ const percentage = total > 0 ? Math.round(correct / total * 1e4) / 100 : 0;
16090
+ const passed = percentage / 100 >= quiz.passThreshold;
16091
+ const diplomaId = `D-${todayStr()}-${student}-${quizId.replace(/^Q-/, "")}`;
16092
+ const diploma = {
16093
+ id: diplomaId,
16094
+ type: "quiz",
16095
+ student,
16096
+ earnedAt: (/* @__PURE__ */ new Date()).toISOString(),
16097
+ source: quizId,
16098
+ score: correct,
16099
+ total,
16100
+ percentage,
16101
+ passed
16102
+ };
16103
+ saveDiploma(ctx.rootDir, diploma);
16104
+ const text = JSON.stringify({
16105
+ quizId,
16106
+ student,
16107
+ score: correct,
16108
+ total,
16109
+ percentage,
16110
+ passThreshold: quiz.passThreshold * 100,
16111
+ passed,
16112
+ diplomaId,
16113
+ feedback
16114
+ }, null, 2);
16115
+ trackToolCall(text.length, name);
16116
+ return { handled: true, text };
16117
+ }
16118
+ if (name === "paradigm_university_onboard") {
16119
+ const student = args.student || resolveAuthor2();
16120
+ const config = loadUniversityConfig(ctx.rootDir);
16121
+ const sequence = getOnboardingSequence(ctx.rootDir, student);
16122
+ const text = JSON.stringify({
16123
+ university: config.branding.name,
16124
+ student,
16125
+ ...sequence
16126
+ }, null, 2);
16127
+ trackToolCall(text.length, name);
16128
+ return { handled: true, text };
16129
+ }
16130
+ if (name === "paradigm_university_diplomas") {
16131
+ const diplomas = loadDiplomas(ctx.rootDir, {
16132
+ student: args.student,
16133
+ type: args.type
16134
+ });
16135
+ const text = JSON.stringify({
16136
+ count: diplomas.length,
16137
+ diplomas: diplomas.map((d) => ({
16138
+ id: d.id,
16139
+ type: d.type,
16140
+ student: d.student,
16141
+ source: d.source,
16142
+ score: d.score,
16143
+ total: d.total,
16144
+ percentage: d.percentage,
16145
+ passed: d.passed,
16146
+ earnedAt: d.earnedAt
16147
+ }))
16148
+ }, null, 2);
16149
+ trackToolCall(text.length, name);
16150
+ return { handled: true, text };
16151
+ }
16152
+ if (name === "paradigm_university_validate") {
16153
+ const result = validateUniversityContent(ctx.rootDir, {
16154
+ id: args.id,
16155
+ deep: args.deep
16156
+ });
16157
+ const text = JSON.stringify(result, null, 2);
16158
+ trackToolCall(text.length, name);
16159
+ return { handled: true, text };
16160
+ }
16161
+ return { handled: false, text: "" };
16162
+ }
16163
+
16164
+ // ../paradigm-mcp/src/utils/platform-bridge.ts
16165
+ import * as fs27 from "fs";
16166
+ import * as path29 from "path";
16167
+ import * as yaml15 from "js-yaml";
16168
+ function resolvePlatformPort(projectDir2) {
16169
+ try {
16170
+ const configPath = path29.join(projectDir2, ".paradigm", "config.yaml");
16171
+ if (fs27.existsSync(configPath)) {
16172
+ const config = yaml15.load(fs27.readFileSync(configPath, "utf-8"));
16173
+ const platform2 = config.platform;
16174
+ if (platform2?.port && typeof platform2.port === "number") {
16175
+ return platform2.port;
16176
+ }
16177
+ }
16178
+ } catch {
16179
+ }
16180
+ return 3850;
16181
+ }
16182
+ function resolveAgentId(projectDir2) {
16183
+ try {
16184
+ const configPath = path29.join(projectDir2, ".paradigm", "config.yaml");
16185
+ if (fs27.existsSync(configPath)) {
16186
+ const config = yaml15.load(fs27.readFileSync(configPath, "utf-8"));
16187
+ const project = config.project || path29.basename(projectDir2);
16188
+ const role = config.role || "core";
16189
+ return `${project}/${role}`;
16190
+ }
16191
+ } catch {
16192
+ }
16193
+ return `${path29.basename(projectDir2)}/core`;
16194
+ }
16195
+ async function sendAgentCommand(projectDir2, command, payload) {
16196
+ const port = resolvePlatformPort(projectDir2);
16197
+ const agentId = resolveAgentId(projectDir2);
16198
+ const url = `http://localhost:${port}/api/platform/agent-command`;
16199
+ try {
16200
+ const response = await fetch(url, {
16201
+ method: "POST",
16202
+ headers: { "Content-Type": "application/json" },
16203
+ body: JSON.stringify({ command, agentId, payload }),
16204
+ signal: AbortSignal.timeout(5e3)
16205
+ });
16206
+ if (!response.ok) {
16207
+ const text = await response.text();
16208
+ return { ok: false, error: `HTTP ${response.status}: ${text}` };
16209
+ }
16210
+ const data = await response.json();
16211
+ return { ok: true, data };
16212
+ } catch (err2) {
16213
+ const msg = err2 instanceof Error ? err2.message : String(err2);
16214
+ return { ok: false, error: `Platform server unreachable (${msg}). Is \`paradigm serve\` running?` };
16215
+ }
16216
+ }
16217
+
16218
+ // ../paradigm-mcp/src/tools/platform.ts
16219
+ function getPlatformToolsList() {
16220
+ return [
16221
+ {
16222
+ name: "paradigm_platform_navigate",
16223
+ description: "Navigate the Platform UI to a section, select a symbol, or open a lore entry. The browser updates in real-time. If the user is actively interacting, shows a prompt instead of auto-navigating. ~100 tokens.",
16224
+ inputSchema: {
16225
+ type: "object",
16226
+ properties: {
16227
+ section: {
16228
+ type: "string",
16229
+ enum: ["overview", "lore", "graph", "sentinel", "university", "symphony"],
16230
+ description: "Section to navigate to"
16231
+ },
16232
+ symbol: {
16233
+ type: "string",
16234
+ description: 'Symbol to select (e.g., "#payment-service")'
16235
+ },
16236
+ loreId: {
16237
+ type: "string",
16238
+ description: "Lore entry ID to open in lore section"
16239
+ }
16240
+ }
16241
+ },
16242
+ annotations: {
16243
+ readOnlyHint: false,
16244
+ destructiveHint: false
16245
+ }
16246
+ },
16247
+ {
16248
+ name: "paradigm_platform_highlight",
16249
+ description: "Temporarily highlight symbols in the Platform UI with a pulsing glow. Auto-expires after duration. Use to draw attention to specific components during explanations. ~100 tokens.",
16250
+ inputSchema: {
16251
+ type: "object",
16252
+ properties: {
16253
+ symbols: {
16254
+ type: "array",
16255
+ items: { type: "string" },
16256
+ description: 'Symbol IDs to highlight (e.g., ["#payment-service", "#api-gateway"])'
16257
+ },
16258
+ color: {
16259
+ type: "string",
16260
+ description: "Highlight color (CSS color, defaults to agent color)"
16261
+ },
16262
+ duration: {
16263
+ type: "number",
16264
+ description: "Duration in milliseconds (default: 5000)"
16265
+ },
16266
+ pulse: {
16267
+ type: "boolean",
16268
+ description: "Whether to pulse the highlight (default: true)"
16269
+ },
16270
+ label: {
16271
+ type: "string",
16272
+ description: "Optional label shown near highlighted symbols"
16273
+ }
16274
+ },
16275
+ required: ["symbols"]
16276
+ },
16277
+ annotations: {
16278
+ readOnlyHint: false,
16279
+ destructiveHint: false
16280
+ }
16281
+ },
16282
+ {
16283
+ name: "paradigm_platform_annotate",
16284
+ description: "Show a toast notification, callout on a graph node, or badge in the Platform UI. Use for communicating decisions, warnings, or context to the user visually. ~100 tokens.",
16285
+ inputSchema: {
16286
+ type: "object",
16287
+ properties: {
16288
+ type: {
16289
+ type: "string",
16290
+ enum: ["toast", "callout", "badge"],
16291
+ description: "Annotation type: toast (notification), callout (floating note on graph node), badge (icon on symbol)"
16292
+ },
16293
+ message: {
16294
+ type: "string",
16295
+ description: "Annotation message text"
16296
+ },
16297
+ symbol: {
16298
+ type: "string",
16299
+ description: "Symbol to attach callout/badge to (required for callout/badge)"
16300
+ },
16301
+ severity: {
16302
+ type: "string",
16303
+ enum: ["info", "warning", "error", "success"],
16304
+ description: "Visual severity (default: info)"
16305
+ },
16306
+ duration: {
16307
+ type: "number",
16308
+ description: "Auto-dismiss duration in milliseconds (default: 6000, 0 = persistent)"
16309
+ }
16310
+ },
16311
+ required: ["type", "message"]
16312
+ },
16313
+ annotations: {
16314
+ readOnlyHint: false,
16315
+ destructiveHint: false
16316
+ }
16317
+ },
16318
+ {
16319
+ name: "paradigm_platform_observe",
16320
+ description: "Read the current Platform UI state: what section the user is viewing, what symbol is selected, theme, connected agents, and active highlights/annotations. ~150 tokens.",
16321
+ inputSchema: {
16322
+ type: "object",
16323
+ properties: {
16324
+ detail: {
16325
+ type: "string",
16326
+ enum: ["summary", "full"],
16327
+ description: "Level of detail (default: summary)"
16328
+ }
16329
+ }
16330
+ },
16331
+ annotations: {
16332
+ readOnlyHint: true,
16333
+ destructiveHint: false
16334
+ }
16335
+ },
16336
+ {
16337
+ name: "paradigm_platform_clear",
16338
+ description: "Remove agent highlights, annotations, or all agent effects from the Platform UI. ~50 tokens.",
16339
+ inputSchema: {
16340
+ type: "object",
16341
+ properties: {
16342
+ target: {
16343
+ type: "string",
16344
+ enum: ["highlights", "annotations", "all"],
16345
+ description: "What to clear (default: all)"
16346
+ }
16347
+ }
16348
+ },
16349
+ annotations: {
16350
+ readOnlyHint: false,
16351
+ destructiveHint: false
16352
+ }
16353
+ }
16354
+ ];
16355
+ }
16356
+ async function handlePlatformTool(name, args, ctx) {
16357
+ switch (name) {
16358
+ case "paradigm_platform_navigate": {
16359
+ const result = await sendAgentCommand(ctx.projectDir, "navigate", {
16360
+ section: args.section,
16361
+ symbol: args.symbol,
16362
+ loreId: args.loreId
16363
+ });
16364
+ if (!result.ok) {
16365
+ return { handled: true, text: `**Navigate failed:** ${result.error}` };
16366
+ }
16367
+ const d = result.data;
16368
+ if (d.navigated) {
16369
+ const parts = [];
16370
+ if (d.section) parts.push(`section: **${d.section}**`);
16371
+ if (d.symbol) parts.push(`symbol: **${d.symbol}**`);
16372
+ const activeNote = d.userActive ? " (user was active \u2014 shown as prompt)" : "";
16373
+ return { handled: true, text: `Navigated to ${parts.join(", ")}${activeNote}` };
16374
+ }
16375
+ return { handled: true, text: `Navigation skipped: ${d.reason}` };
16376
+ }
16377
+ case "paradigm_platform_highlight": {
16378
+ const result = await sendAgentCommand(ctx.projectDir, "highlight", {
16379
+ symbols: args.symbols,
16380
+ color: args.color,
16381
+ duration: args.duration,
16382
+ pulse: args.pulse,
16383
+ label: args.label
16384
+ });
16385
+ if (!result.ok) {
16386
+ return { handled: true, text: `**Highlight failed:** ${result.error}` };
16387
+ }
16388
+ const d = result.data;
16389
+ if (d.highlighted) {
16390
+ return { handled: true, text: `Highlighted **${d.count}** symbol(s)${args.label ? ` with label "${args.label}"` : ""}` };
16391
+ }
16392
+ return { handled: true, text: `Highlight skipped: ${d.reason}` };
16393
+ }
16394
+ case "paradigm_platform_annotate": {
16395
+ const result = await sendAgentCommand(ctx.projectDir, "annotate", {
16396
+ type: args.type,
16397
+ message: args.message,
16398
+ symbol: args.symbol,
16399
+ severity: args.severity,
16400
+ duration: args.duration
16401
+ });
16402
+ if (!result.ok) {
16403
+ return { handled: true, text: `**Annotate failed:** ${result.error}` };
16404
+ }
16405
+ const d = result.data;
16406
+ if (d.annotated) {
16407
+ return { handled: true, text: `Created ${args.type} annotation: "${args.message}"` };
16408
+ }
16409
+ return { handled: true, text: `Annotation skipped: ${d.reason}` };
16410
+ }
16411
+ case "paradigm_platform_observe": {
16412
+ const result = await sendAgentCommand(ctx.projectDir, "observe", {
16413
+ detail: args.detail
16414
+ });
16415
+ if (!result.ok) {
16416
+ return { handled: true, text: `**Observe failed:** ${result.error}` };
16417
+ }
16418
+ const d = result.data;
16419
+ const state = d.state;
16420
+ const lines = ["## Platform UI State\n"];
16421
+ lines.push(`- **Connected:** ${d.connected ? "Yes" : "No"} (${d.users} browser client(s))`);
16422
+ lines.push(`- **Section:** ${state.section}`);
16423
+ lines.push(`- **Selected symbol:** ${state.selectedSymbol || "none"}`);
16424
+ lines.push(`- **Theme:** ${state.theme}`);
16425
+ lines.push(`- **Muted:** ${state.muted ? "Yes \u2014 agent actions silently discarded" : "No"}`);
16426
+ const agents = d.agents;
16427
+ if (agents?.length) {
16428
+ lines.push(`
16429
+ ### Connected Agents (${agents.length})`);
16430
+ for (const a of agents) {
16431
+ lines.push(`- \`${a.agentId}\` (since ${a.connectedAt})`);
16432
+ }
16433
+ }
16434
+ if (args.detail === "full") {
16435
+ const highlights = d.highlights;
16436
+ const annotations = d.annotations;
16437
+ if (highlights?.length) {
16438
+ lines.push(`
16439
+ ### Active Highlights: ${highlights.length}`);
16440
+ }
16441
+ if (annotations?.length) {
16442
+ lines.push(`
16443
+ ### Active Annotations: ${annotations.length}`);
16444
+ }
16445
+ }
16446
+ return { handled: true, text: lines.join("\n") };
16447
+ }
16448
+ case "paradigm_platform_clear": {
16449
+ const result = await sendAgentCommand(ctx.projectDir, "clear", {
16450
+ target: args.target
16451
+ });
16452
+ if (!result.ok) {
16453
+ return { handled: true, text: `**Clear failed:** ${result.error}` };
16454
+ }
16455
+ const d = result.data;
16456
+ return { handled: true, text: `Cleared ${d.target} agent effects` };
16457
+ }
16458
+ default:
16459
+ return { handled: false, text: "" };
16460
+ }
16461
+ }
16462
+
16463
+ // ../paradigm-mcp/src/tools/fallback-grep.ts
16464
+ import * as path30 from "path";
16465
+ import { execSync as execSync8 } from "child_process";
16466
+ function grepForReferences(rootDir, symbol, options = {}) {
16467
+ const { maxResults = 20 } = options;
16468
+ const results = [];
16469
+ const escapedSymbol = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16470
+ const grepCommands = [
16471
+ // ripgrep - exclude common directories
16472
+ `rg -n --no-heading "${escapedSymbol}" "${rootDir}" --glob "!node_modules" --glob "!.git" --glob "!dist" --glob "!build" --glob "!coverage" --max-count 50 2>/dev/null`,
16473
+ // fallback to grep
16474
+ `grep -rn "${escapedSymbol}" "${rootDir}" --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude-dir=build --exclude-dir=coverage 2>/dev/null | head -50`
16475
+ ];
16476
+ let output = "";
16477
+ for (const cmd of grepCommands) {
16478
+ try {
16479
+ output = execSync8(cmd, { encoding: "utf8", maxBuffer: 1024 * 1024 });
16480
+ if (output.trim()) break;
16481
+ } catch {
16482
+ continue;
16483
+ }
16484
+ }
16485
+ if (!output.trim()) {
16486
+ return results;
16487
+ }
16488
+ const lines = output.trim().split("\n");
16489
+ for (const line of lines.slice(0, maxResults)) {
16490
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
16491
+ if (match) {
16492
+ const [, filePath, lineNum, content] = match;
16493
+ const relativePath = path30.relative(rootDir, filePath);
16494
+ let context2 = "unknown";
16495
+ if (relativePath.includes(".purpose") || relativePath.includes("portal.yaml")) {
16496
+ context2 = "purpose";
16497
+ } else if (content.includes("//") || content.includes("#") || content.includes("*")) {
16498
+ context2 = "comment";
16499
+ } else {
16500
+ context2 = "code";
16501
+ }
16502
+ results.push({
16503
+ filePath: relativePath,
16504
+ line: parseInt(lineNum, 10),
16505
+ content: content.trim().slice(0, 200),
16506
+ context: context2
16507
+ });
16508
+ }
16509
+ }
16510
+ return results;
16511
+ }
16512
+
16513
+ // ../paradigm-mcp/src/tools/fuzzy-match.ts
16514
+ function levenshteinDistance(a, b) {
16515
+ const matrix = [];
16516
+ for (let i = 0; i <= b.length; i++) {
16517
+ matrix[i] = [i];
16518
+ }
16519
+ for (let j = 0; j <= a.length; j++) {
16520
+ matrix[0][j] = j;
16521
+ }
16522
+ for (let i = 1; i <= b.length; i++) {
16523
+ for (let j = 1; j <= a.length; j++) {
16524
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
16525
+ matrix[i][j] = matrix[i - 1][j - 1];
16526
+ } else {
16527
+ matrix[i][j] = Math.min(
16528
+ matrix[i - 1][j - 1] + 1,
16529
+ // substitution
16530
+ matrix[i][j - 1] + 1,
16531
+ // insertion
16532
+ matrix[i - 1][j] + 1
16533
+ // deletion
16534
+ );
16535
+ }
16536
+ }
16537
+ }
16538
+ return matrix[b.length][a.length];
16539
+ }
16540
+ function findFuzzyMatches(query, candidates, options = {}) {
16541
+ const { maxDistance = 3, maxResults = 5 } = options;
16542
+ const queryLower = query.toLowerCase();
16543
+ const matches = [];
16544
+ for (const candidate of candidates) {
16545
+ const candidateLower = candidate.toLowerCase();
16546
+ if (candidateLower === queryLower) {
16547
+ matches.push({ match: candidate, distance: 0 });
16548
+ continue;
16549
+ }
16550
+ if (candidateLower.includes(queryLower) || queryLower.includes(candidateLower)) {
16551
+ matches.push({ match: candidate, distance: 1 });
16552
+ continue;
16553
+ }
16554
+ const distance = levenshteinDistance(queryLower, candidateLower);
16555
+ if (distance <= maxDistance) {
16556
+ matches.push({ match: candidate, distance });
16557
+ }
16558
+ }
16559
+ matches.sort((a, b) => {
14132
16560
  if (a.distance !== b.distance) return a.distance - b.distance;
14133
16561
  return a.match.localeCompare(b.match);
14134
16562
  });
@@ -14200,6 +16628,10 @@ function registerTools(server, getContext2, reloadContext2) {
14200
16628
  includeWorkspace: {
14201
16629
  type: "boolean",
14202
16630
  description: "Also search sibling workspace projects (default: false). Requires workspace configured in config.yaml."
16631
+ },
16632
+ componentType: {
16633
+ type: "string",
16634
+ description: 'Filter components by type (e.g., "view", "service", "tool"). Only applies to #component symbols.'
14203
16635
  }
14204
16636
  },
14205
16637
  required: ["query"]
@@ -14333,6 +16765,12 @@ function registerTools(server, getContext2, reloadContext2) {
14333
16765
  ...getPipelineToolsList(),
14334
16766
  // Conductor session registration tools
14335
16767
  ...getConductorToolsList(),
16768
+ // Symphony (The Score) tools
16769
+ ...getSymphonyToolsList(),
16770
+ // University (per-project knowledge base) tools
16771
+ ...getUniversityToolsList(),
16772
+ // Platform agent-driven UI tools
16773
+ ...getPlatformToolsList(),
14336
16774
  // Plugin update check
14337
16775
  {
14338
16776
  name: "paradigm_plugin_check",
@@ -14382,13 +16820,16 @@ function registerTools(server, getContext2, reloadContext2) {
14382
16820
  const toolResult = await (async () => {
14383
16821
  switch (name) {
14384
16822
  case "paradigm_search": {
14385
- const { query, type, limit = 10, fuzzy = true, includeWorkspace = false } = args;
14386
- const cacheKey = `search:${query}:${type || ""}:${limit}:${fuzzy}:${includeWorkspace}`;
16823
+ const { query, type, limit = 10, fuzzy = true, includeWorkspace = false, componentType } = args;
16824
+ const cacheKey = `search:${query}:${type || ""}:${limit}:${fuzzy}:${includeWorkspace}:${componentType || ""}`;
14387
16825
  let results = await toolCache.getOrCompute(cacheKey, () => {
14388
16826
  let r = searchSymbols(ctx.index, query);
14389
16827
  if (type) {
14390
16828
  r = r.filter((s) => s.type === type);
14391
16829
  }
16830
+ if (componentType) {
16831
+ r = r.filter((s) => s.componentType === componentType);
16832
+ }
14392
16833
  return r;
14393
16834
  });
14394
16835
  let fuzzyMatches = [];
@@ -14411,7 +16852,9 @@ function registerTools(server, getContext2, reloadContext2) {
14411
16852
  symbol: s.symbol,
14412
16853
  type: s.type,
14413
16854
  description: s.description,
14414
- filePath: s.filePath
16855
+ filePath: s.filePath,
16856
+ ...s.componentType ? { componentType: s.componentType } : {},
16857
+ ...s.parentSymbol ? { parentSymbol: s.parentSymbol } : {}
14415
16858
  }))
14416
16859
  };
14417
16860
  if (fuzzyMatches.length > 0) {
@@ -14603,6 +17046,18 @@ function registerTools(server, getContext2, reloadContext2) {
14603
17046
  }
14604
17047
  } catch {
14605
17048
  }
17049
+ try {
17050
+ const universityAffected = getAffectedUniversityContent(ctx.rootDir, symbol);
17051
+ if (universityAffected.length > 0) {
17052
+ response.university_content_affected = universityAffected.map((c) => ({
17053
+ id: c.id,
17054
+ title: c.title,
17055
+ type: c.type,
17056
+ stale: c.stale
17057
+ }));
17058
+ }
17059
+ } catch {
17060
+ }
14606
17061
  if (includeWorkspace && ctx.workspace) {
14607
17062
  const wsRipple = rippleWorkspace(ctx.workspace, symbol);
14608
17063
  if (wsRipple.length > 0) {
@@ -14723,7 +17178,7 @@ function registerTools(server, getContext2, reloadContext2) {
14723
17178
  const symbols = getSymbolsByType(ctx.index, type);
14724
17179
  examples[type] = symbols.slice(0, 3).map((s) => s.symbol);
14725
17180
  }
14726
- const platform2 = os4.platform();
17181
+ const platform2 = os6.platform();
14727
17182
  const isWindows = platform2 === "win32";
14728
17183
  const shell = isWindows ? "PowerShell/CMD" : platform2 === "darwin" ? "zsh/bash" : "bash";
14729
17184
  let protocols;
@@ -14734,6 +17189,21 @@ function registerTools(server, getContext2, reloadContext2) {
14734
17189
  }
14735
17190
  } catch {
14736
17191
  }
17192
+ const allComponents = getSymbolsByType(ctx.index, "component");
17193
+ const componentTypeBreakdown = {};
17194
+ for (const comp of allComponents) {
17195
+ if (comp.componentType) {
17196
+ componentTypeBreakdown[comp.componentType] = (componentTypeBreakdown[comp.componentType] || 0) + 1;
17197
+ }
17198
+ }
17199
+ const untypedCount = allComponents.filter((c) => !c.componentType).length;
17200
+ let purposeHealthScore;
17201
+ try {
17202
+ const { checkPurposeHealth } = await import("./integrity-checker-J7YXRTBT.js");
17203
+ const healthReport = checkPurposeHealth(ctx.aggregation.purposeFiles, ctx.rootDir);
17204
+ purposeHealthScore = healthReport.healthScore;
17205
+ } catch {
17206
+ }
14737
17207
  return JSON.stringify({
14738
17208
  project: ctx.projectName,
14739
17209
  symbolSystem: "v2",
@@ -14745,11 +17215,18 @@ function registerTools(server, getContext2, reloadContext2) {
14745
17215
  "~ aspects": counts.aspect
14746
17216
  },
14747
17217
  total,
17218
+ ...Object.keys(componentTypeBreakdown).length > 0 ? {
17219
+ componentTypes: {
17220
+ ...componentTypeBreakdown,
17221
+ ...untypedCount > 0 ? { "(untyped)": untypedCount } : {}
17222
+ }
17223
+ } : {},
14748
17224
  examples,
14749
17225
  hasPortalYaml: ctx.gateConfig !== null,
14750
17226
  purposeFiles: ctx.aggregation.purposeFiles.length,
17227
+ ...purposeHealthScore !== void 0 ? { purposeHealthScore } : {},
14751
17228
  ...protocols ? { protocols } : {},
14752
- note: "Symbol System v2: Use tags [feature], [state], [integration], [idea] for classification",
17229
+ note: "Symbol System v2: Use tags [feature], [state], [integration], [idea] for classification. Use type field for structural role (view, service, tool, etc.)",
14753
17230
  environment: {
14754
17231
  os: platform2,
14755
17232
  shell,
@@ -14963,10 +17440,10 @@ Update command:
14963
17440
  trackToolCall(noWsText.length, name);
14964
17441
  return { content: [{ type: "text", text: noWsText }] };
14965
17442
  }
14966
- const { rebuildStaticFiles: rebuildStaticFiles2 } = await import("./reindex-YG3KIXAK.js");
17443
+ const { rebuildStaticFiles: rebuildStaticFiles2 } = await import("./reindex-NZQRGKPN.js");
14967
17444
  const memberResults = [];
14968
17445
  for (const member of ctx.workspace.config.members) {
14969
- const memberAbsPath = path29.resolve(path29.dirname(ctx.workspace.workspacePath), member.path);
17446
+ const memberAbsPath = path31.resolve(path31.dirname(ctx.workspace.workspacePath), member.path);
14970
17447
  try {
14971
17448
  const result = await rebuildStaticFiles2(memberAbsPath);
14972
17449
  memberResults.push({
@@ -15190,6 +17667,33 @@ Update command:
15190
17667
  };
15191
17668
  }
15192
17669
  }
17670
+ if (name.startsWith("paradigm_symphony_")) {
17671
+ const result = await handleSymphonyTool(name, args, ctx);
17672
+ if (result.handled) {
17673
+ trackToolCall(result.text.length, name);
17674
+ return {
17675
+ content: [{ type: "text", text: result.text }]
17676
+ };
17677
+ }
17678
+ }
17679
+ if (name.startsWith("paradigm_university_")) {
17680
+ const result = await handleUniversityTool(name, args, ctx);
17681
+ if (result.handled) {
17682
+ trackToolCall(result.text.length, name);
17683
+ return {
17684
+ content: [{ type: "text", text: result.text }]
17685
+ };
17686
+ }
17687
+ }
17688
+ if (name.startsWith("paradigm_platform_")) {
17689
+ const result = await handlePlatformTool(name, args, ctx);
17690
+ if (result.handled) {
17691
+ trackToolCall(result.text.length, name);
17692
+ return {
17693
+ content: [{ type: "text", text: result.text }]
17694
+ };
17695
+ }
17696
+ }
15193
17697
  if (name === "paradigm_reindex") {
15194
17698
  const reload = reloadContext2 || (async () => {
15195
17699
  });