@a-company/paradigm 3.34.0 → 3.44.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-XXANWJVZ.js → accept-orchestration-ZUWQUHSK.js} +6 -6
  2. package/dist/add-VSPZ6FM4.js +81 -0
  3. package/dist/{aggregate-XHQ6GI3Z.js → aggregate-SV3VGEIL.js} +2 -2
  4. package/dist/assess-UHBDYIK7.js +68 -0
  5. package/dist/{beacon-BTLQMYQL.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-S5TDFT5Q.js → chunk-7COU5S2Z.js} +2 -2
  9. package/dist/{chunk-H4TVBJD4.js → chunk-AKIMFN6I.js} +3 -3
  10. package/dist/{chunk-3DYYXGDC.js → chunk-CDMAMDSG.js} +33 -0
  11. package/dist/chunk-F3BCHPYT.js +143 -0
  12. package/dist/{chunk-R2SGQ22F.js → chunk-FKJUBQU3.js} +461 -2
  13. package/dist/chunk-GT5QGC2H.js +253 -0
  14. package/dist/{chunk-UQNTJ5VB.js → chunk-HIKKOCXY.js} +1 -1
  15. package/dist/{chunk-J26YQVAK.js → chunk-J4E6K5MG.js} +1 -1
  16. package/dist/chunk-L27I3CPZ.js +357 -0
  17. package/dist/{chunk-WOONGZ3C.js → chunk-P7XSBJE3.js} +1 -1
  18. package/dist/{chunk-Z7W7HNRG.js → chunk-QDXI2DHR.js} +1 -1
  19. package/dist/{chunk-BRILIG7Z.js → chunk-QIOCFXDQ.js} +42 -0
  20. package/dist/{chunk-3BGSDKWD.js → chunk-QWA26UNO.js} +7 -7
  21. package/dist/{lore-server-ILPHKWLK.js → chunk-RAB5IKPR.js} +77 -112
  22. package/dist/chunk-SOBTKFSP.js +616 -0
  23. package/dist/{chunk-BKMNLROM.js → chunk-ZDHLG5VP.js} +461 -147
  24. package/dist/{chunk-CTF6RHKG.js → chunk-ZGUAAVMA.js} +17 -2
  25. package/dist/{chunk-PFLWLC6J.js → chunk-ZMQA6SCO.js} +855 -34
  26. package/dist/{chunk-3BAMPB6I.js → chunk-ZSYVKSY6.js} +2 -147
  27. package/dist/{commands-KPT2T2OZ.js → commands-5N4ILTPH.js} +465 -1
  28. package/dist/config-schema-3YNIFJCJ.js +152 -0
  29. package/dist/{constellation-LZ6XIKDT.js → constellation-FAGT45TU.js} +2 -2
  30. package/dist/{context-audit-RI4R2WRH.js → context-audit-557EO6PK.js} +138 -8
  31. package/dist/{cost-4SZM7OUS.js → cost-UD3WPEKZ.js} +1 -1
  32. package/dist/{delete-YTASL4SM.js → delete-RRK4RL6Y.js} +1 -1
  33. package/dist/{diff-T6YJSAAC.js → diff-IP5CIARP.js} +6 -6
  34. package/dist/{dist-AG5JNIZU-HW2FWNTZ.js → dist-5QE2BB2B-X6DYVSUL.js} +59 -5
  35. package/dist/{dist-IKBGY7FQ.js → dist-CM3MVWWW.js} +3 -1
  36. package/dist/{dist-OH4DBV2O.js → dist-OGTSAZ55.js} +16 -1
  37. package/dist/{dist-RMAIFRTW.js → dist-POMVY6WP.js} +5 -3
  38. package/dist/{dist-QSBAGCZT.js → dist-UXWV4OKX.js} +2 -2
  39. package/dist/{doctor-INBOLZC7.js → doctor-GKZJU7QG.js} +1 -1
  40. package/dist/{edit-S7NZD7H7.js → edit-4CLNN5JG.js} +1 -1
  41. package/dist/{graph-ERNQQQ7C.js → graph-YYUXI3F7.js} +1 -1
  42. package/dist/graph-server-ZPXRSGCW.js +116 -0
  43. package/dist/{habits-7BORPC2F.js → habits-RG5SVKXP.js} +2 -2
  44. package/dist/index.js +200 -86
  45. package/dist/integrity-MK2OP5TA.js +194 -0
  46. package/dist/integrity-checker-J7YXRTBT.js +11 -0
  47. package/dist/{lint-MTRZB5EC.js → lint-HYWGS3JJ.js} +1 -1
  48. package/dist/{list-QTFWN35D.js → list-BTLFHSRC.js} +1 -1
  49. package/dist/list-IUCYPGMK.js +57 -0
  50. package/dist/{lore-loader-S5BXMH27.js → lore-loader-VTEEZDX3.js} +3 -1
  51. package/dist/lore-server-NOOAHKJX.js +118 -0
  52. package/dist/mcp.js +2591 -112
  53. package/dist/{migrate-HRN5TUBQ.js → migrate-FQVGQNXZ.js} +21 -3
  54. package/dist/{migrate-assessments-FPR6C35Z.js → migrate-assessments-JP6Q5KME.js} +1 -1
  55. package/dist/{orchestrate-3SI6ON33.js → orchestrate-A226N6FC.js} +6 -6
  56. package/dist/platform-server-KHL6ZPPN.js +900 -0
  57. package/dist/{probe-ABMGCXQG.js → probe-7JK7IDNI.js} +4 -4
  58. package/dist/{providers-YW3FG6DA.js → providers-YNFSL6HK.js} +1 -1
  59. package/dist/quiz-I75NU2QQ.js +99 -0
  60. package/dist/{record-UGN75GTB.js → record-46CLR4OG.js} +11 -2
  61. package/dist/{reindex-YC7LD4MN.js → reindex-WIJMCJ4A.js} +3 -2
  62. package/dist/{remember-WR6ZVXLT.js → remember-4EUZKIIB.js} +1 -1
  63. package/dist/{retag-URLJLMSK.js → retag-KC4JVRLE.js} +1 -1
  64. package/dist/{review-725ZKA7U.js → review-Q7M4CRB5.js} +1 -1
  65. package/dist/{ripple-QTXKJCEI.js → ripple-RI3LOT6R.js} +2 -2
  66. package/dist/{sentinel-FUR3QKCJ.js → sentinel-UOIGJWHH.js} +1 -1
  67. package/dist/sentinel-bridge-APDXYAZS.js +109 -0
  68. package/dist/sentinel-mcp.js +13 -0
  69. package/dist/sentinel-ui/assets/{index-Zh1YM0C9.css → index-CJ1Wx083.css} +1 -1
  70. package/dist/sentinel-ui/assets/index-S1VJ67dT.js +62 -0
  71. package/dist/sentinel-ui/assets/index-S1VJ67dT.js.map +1 -0
  72. package/dist/sentinel-ui/index.html +2 -2
  73. package/dist/sentinel.js +6 -6
  74. package/dist/{serve-DIALBCTU.js → serve-22A4XOIG.js} +1 -1
  75. package/dist/{university-A66BMZ4Z.js → serve-2YJ6D2Y6.js} +9 -8
  76. package/dist/serve-JVXSRSUB.js +33 -0
  77. package/dist/{server-2VICPDUR.js → server-JV6UFGWZ.js} +25 -2
  78. package/dist/{server-OWBK2WFS.js → server-RDLQ3DK7.js} +49 -4
  79. package/dist/{setup-ASR6OMKV.js → setup-M2ZKLKNN.js} +2 -2
  80. package/dist/{shift-7XLSBLDW.js → shift-LNMKFYLR.js} +63 -14
  81. package/dist/{show-GEVVQWWG.js → show-P7GYO43X.js} +1 -1
  82. package/dist/show-PKZMYKRN.js +82 -0
  83. package/dist/{snapshot-QZFD7YBI.js → snapshot-Y3COXK4T.js} +2 -2
  84. package/dist/{spawn-DIY7T4QW.js → spawn-SSXZX45U.js} +2 -2
  85. package/dist/status-KLHALGW4.js +71 -0
  86. package/dist/{summary-R4CSYNNP.js → summary-5NQNOD3F.js} +2 -2
  87. package/dist/{sweep-5POCF2E4.js → sweep-EZU3GU6S.js} +1 -1
  88. package/dist/symphony-EYRGGVNE.js +470 -0
  89. package/dist/symphony-QWOEKZMC.js +308 -0
  90. package/dist/{team-VH3HYABB.js → team-HGLJXWQG.js} +7 -7
  91. package/dist/{timeline-RKXNRMKF.js → timeline-ANC7LVDL.js} +1 -1
  92. package/dist/{triage-GJ6GK647.js → triage-IZ4MDYNB.js} +2 -2
  93. package/dist/university-content/courses/.purpose +7 -1
  94. package/dist/university-content/courses/para-501.json +166 -0
  95. package/dist/university-content/plsat/.purpose +6 -0
  96. package/dist/university-content/plsat/v3.0.json +323 -1
  97. package/dist/university-content/reference.json +48 -0
  98. package/dist/university-ui/assets/{index-TcsCEBMo.js → index-tfi5xN4Q.js} +2 -2
  99. package/dist/university-ui/assets/{index-TcsCEBMo.js.map → index-tfi5xN4Q.js.map} +1 -1
  100. package/dist/university-ui/index.html +1 -1
  101. package/dist/validate-GD5XWILV.js +134 -0
  102. package/dist/{validate-OUHUBZPO.js → validate-ZVPNN4FL.js} +1 -1
  103. package/dist/{workspace-5RBSALXC.js → workspace-UIUTHZTD.js} +5 -5
  104. package/package.json +4 -2
  105. package/platform-ui/dist/assets/GitSection-BD3Ze06e.js +4 -0
  106. package/platform-ui/dist/assets/GitSection-C-GQWHcu.css +1 -0
  107. package/platform-ui/dist/assets/GraphSection-BlgXTl53.css +1 -0
  108. package/platform-ui/dist/assets/GraphSection-SglITfSs.js +8 -0
  109. package/platform-ui/dist/assets/LoreSection-C3EixkjW.css +1 -0
  110. package/platform-ui/dist/assets/LoreSection-bR5Km4Fd.js +1 -0
  111. package/platform-ui/dist/assets/SentinelSection-BI-aIYKL.css +1 -0
  112. package/platform-ui/dist/assets/SentinelSection-QSpAZArG.js +1 -0
  113. package/platform-ui/dist/assets/SymphonySection-CobYJgvg.js +1 -0
  114. package/platform-ui/dist/assets/SymphonySection-zY0C5tFl.css +1 -0
  115. package/platform-ui/dist/assets/index-CfpZFjea.css +1 -0
  116. package/platform-ui/dist/assets/index-DbxeSMkV.js +57 -0
  117. package/platform-ui/dist/index.html +14 -0
  118. package/dist/graph-server-BZ73HTAT.js +0 -251
  119. package/dist/sentinel-ui/assets/index-C_Wstm64.js +0 -62
  120. package/dist/sentinel-ui/assets/index-C_Wstm64.js.map +0 -1
  121. /package/dist/{chunk-VUSCJJ4A.js → chunk-EDOAWN7J.js} +0 -0
  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-ZDHLG5VP.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-PFLWLC6J.js";
91
+ validatePurposeFile,
92
+ validateUniversityContent
93
+ } from "./chunk-ZMQA6SCO.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);
@@ -7525,6 +7590,16 @@ var SEED_HABITS = [
7525
7590
  check: { type: "lore-recorded", params: {} },
7526
7591
  enabled: true
7527
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
+ },
7528
7603
  {
7529
7604
  id: "gates-for-routes",
7530
7605
  name: "Gates for Routes",
@@ -7534,6 +7609,26 @@ var SEED_HABITS = [
7534
7609
  severity: "warn",
7535
7610
  check: { type: "gates-declared", params: { requireRoutes: true } },
7536
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
7537
7632
  }
7538
7633
  ];
7539
7634
  var HABITS_CACHE_TTL_MS = 30 * 1e3;
@@ -8292,6 +8387,22 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
8292
8387
  suggestion: "Use paradigm_wisdom_record to capture architectural decisions or antipatterns."
8293
8388
  });
8294
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
+ }
8295
8406
  const errors = violations.filter((v) => v.severity === "error").length;
8296
8407
  const warnings = violations.filter((v) => v.severity === "warning").length;
8297
8408
  let status = "pass";
@@ -8352,8 +8463,8 @@ function runPostflightCheck(filesModified, symbolsTouched, ctx) {
8352
8463
  status,
8353
8464
  violations,
8354
8465
  summary: {
8355
- totalChecks: 6,
8356
- passed: 6 - (errors > 0 ? 1 : 0) - (warnings > 0 ? 1 : 0),
8466
+ totalChecks: 7,
8467
+ passed: 7 - (errors > 0 ? 1 : 0) - (warnings > 0 ? 1 : 0),
8357
8468
  warnings,
8358
8469
  errors
8359
8470
  },
@@ -8438,6 +8549,14 @@ function getLoreToolsList() {
8438
8549
  type: "boolean",
8439
8550
  description: "Filter for entries with/without reviews"
8440
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
+ },
8441
8560
  limit: {
8442
8561
  type: "number",
8443
8562
  description: "Maximum results (default: 20)"
@@ -8566,6 +8685,10 @@ function getLoreToolsList() {
8566
8685
  type: "array",
8567
8686
  items: { type: "string" },
8568
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)"
8569
8692
  }
8570
8693
  },
8571
8694
  required: ["title", "summary", "symbols_touched"]
@@ -8664,6 +8787,10 @@ function getLoreToolsList() {
8664
8787
  tags: {
8665
8788
  type: "array",
8666
8789
  items: { type: "string" }
8790
+ },
8791
+ confidence: {
8792
+ type: "number",
8793
+ description: "Agent confidence in correctness (0.0 to 1.0)"
8667
8794
  }
8668
8795
  },
8669
8796
  required: ["id"]
@@ -8673,6 +8800,71 @@ function getLoreToolsList() {
8673
8800
  destructiveHint: false
8674
8801
  }
8675
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
+ },
8676
8868
  {
8677
8869
  name: "paradigm_lore_delete",
8678
8870
  description: "Delete a lore entry. Requires explicit confirmation to prevent accidental deletion. ~100 tokens.",
@@ -8712,6 +8904,8 @@ async function handleLoreTool(name, args, ctx) {
8712
8904
  hasBody: args.hasBody,
8713
8905
  tags: args.tags,
8714
8906
  hasReview: args.hasReview,
8907
+ hasConfidence: args.hasConfidence,
8908
+ hasAssessment: args.hasAssessment,
8715
8909
  limit: args.limit || 20,
8716
8910
  offset: args.offset
8717
8911
  };
@@ -8749,7 +8943,8 @@ async function handleLoreTool(name, args, ctx) {
8749
8943
  body,
8750
8944
  linked_lore,
8751
8945
  linked_tasks,
8752
- linked_commits
8946
+ linked_commits,
8947
+ confidence
8753
8948
  } = args;
8754
8949
  let habit_compliance;
8755
8950
  try {
@@ -8795,7 +8990,8 @@ async function handleLoreTool(name, args, ctx) {
8795
8990
  body,
8796
8991
  linked_lore,
8797
8992
  linked_tasks,
8798
- linked_commits
8993
+ linked_commits,
8994
+ confidence: confidence != null && confidence >= 0 && confidence <= 1 ? confidence : void 0
8799
8995
  };
8800
8996
  const id = await recordLoreEntry(ctx.rootDir, entry);
8801
8997
  getSessionTracker().setLastLoreEntryId(id);
@@ -8893,6 +9089,154 @@ async function handleLoreTool(name, args, ctx) {
8893
9089
  })
8894
9090
  };
8895
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
+ }
8896
9240
  case "paradigm_lore_delete": {
8897
9241
  const id = args.id;
8898
9242
  const confirm = args.confirm;
@@ -8935,6 +9279,9 @@ function summarizeEntry(entry) {
8935
9279
  completeness: entry.review.completeness,
8936
9280
  quality: entry.review.quality
8937
9281
  } : null,
9282
+ confidence: entry.confidence ?? null,
9283
+ assessment: entry.assessment ? entry.assessment.verdict : null,
9284
+ assessment_delta: entry.assessment_delta ?? null,
8938
9285
  tags: entry.tags
8939
9286
  };
8940
9287
  }
@@ -11301,8 +11648,8 @@ function generateRunId() {
11301
11648
  var TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g;
11302
11649
  function interpolate(value, scope) {
11303
11650
  if (typeof value === "string") {
11304
- return value.replace(TEMPLATE_REGEX, (_match, path30) => {
11305
- const resolved = resolvePath(path30.trim(), scope);
11651
+ return value.replace(TEMPLATE_REGEX, (_match, path32) => {
11652
+ const resolved = resolvePath(path32.trim(), scope);
11306
11653
  return resolved !== void 0 ? String(resolved) : _match;
11307
11654
  });
11308
11655
  }
@@ -11335,8 +11682,8 @@ function resolvePath(dotPath, scope) {
11335
11682
  return void 0;
11336
11683
  }
11337
11684
  }
11338
- function deepGet(obj, path30) {
11339
- const parts = path30.split(/[.\[\]]+/).filter(Boolean);
11685
+ function deepGet(obj, path32) {
11686
+ const parts = path32.split(/[.\[\]]+/).filter(Boolean);
11340
11687
  let current = obj;
11341
11688
  for (const part of parts) {
11342
11689
  if (current == null || typeof current !== "object") return void 0;
@@ -11572,11 +11919,11 @@ async function runPersonaObject(rootDir, persona, options) {
11572
11919
  }
11573
11920
  async function runChain(rootDir, chainId, options) {
11574
11921
  const start = Date.now();
11575
- const fs26 = await import("fs");
11576
- const path30 = await import("path");
11577
- const yaml15 = await import("js-yaml");
11578
- const chainPath = path30.join(rootDir, ".paradigm", "personas", "chains", `${chainId}.yaml`);
11579
- 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)) {
11580
11927
  return {
11581
11928
  chain_id: chainId,
11582
11929
  status: "error",
@@ -11585,7 +11932,7 @@ async function runChain(rootDir, chainId, options) {
11585
11932
  duration_ms: Date.now() - start
11586
11933
  };
11587
11934
  }
11588
- const chain = yaml15.load(fs26.readFileSync(chainPath, "utf8"));
11935
+ const chain = yaml16.load(fs28.readFileSync(chainPath, "utf8"));
11589
11936
  let permutation;
11590
11937
  if (options.permutation && chain.permutations) {
11591
11938
  permutation = chain.permutations.find((p) => p.id === options.permutation);
@@ -11689,8 +12036,8 @@ function validateInterpolation(persona) {
11689
12036
  const serialized = JSON.stringify(step);
11690
12037
  const templates = serialized.match(TEMPLATE_REGEX) || [];
11691
12038
  for (const template of templates) {
11692
- const path30 = template.replace("{{", "").replace("}}", "").trim();
11693
- const [namespace, ...rest] = path30.split(".");
12039
+ const path32 = template.replace("{{", "").replace("}}", "").trim();
12040
+ const [namespace, ...rest] = path32.split(".");
11694
12041
  const key = rest.join(".");
11695
12042
  switch (namespace) {
11696
12043
  case "fixtures":
@@ -11751,7 +12098,7 @@ var PERSONA_SCHEMA = {
11751
12098
  var sentinelSchemaRegistered = false;
11752
12099
  async function emitPersonaEvents(result) {
11753
12100
  try {
11754
- const { SentinelStorage: SentinelStorage2 } = await import("./dist-IKBGY7FQ.js");
12101
+ const { SentinelStorage: SentinelStorage2 } = await import("./dist-CM3MVWWW.js");
11755
12102
  const storage2 = new SentinelStorage2();
11756
12103
  if (!sentinelSchemaRegistered) {
11757
12104
  try {
@@ -14044,109 +14391,2188 @@ This session is no longer visible in the Conductor overlay.`,
14044
14391
  }
14045
14392
  }
14046
14393
 
14047
- // ../paradigm-mcp/src/tools/fallback-grep.ts
14394
+ // ../paradigm-mcp/src/utils/symphony-loader.ts
14395
+ import * as fs26 from "fs";
14048
14396
  import * as path28 from "path";
14049
- import { execSync as execSync7 } from "child_process";
14050
- function grepForReferences(rootDir, symbol, options = {}) {
14051
- const { maxResults = 20 } = options;
14052
- const results = [];
14053
- const escapedSymbol = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14054
- const grepCommands = [
14055
- // ripgrep - exclude common directories
14056
- `rg -n --no-heading "${escapedSymbol}" "${rootDir}" --glob "!node_modules" --glob "!.git" --glob "!dist" --glob "!build" --glob "!coverage" --max-count 50 2>/dev/null`,
14057
- // fallback to grep
14058
- `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`
14059
- ];
14060
- let output = "";
14061
- 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)) {
14062
14422
  try {
14063
- output = execSync7(cmd, { encoding: "utf8", maxBuffer: 1024 * 1024 });
14064
- if (output.trim()) break;
14423
+ fs26.renameSync(LEGACY_MAIL_DIR, SCORE_DIR);
14065
14424
  } catch {
14066
- continue;
14067
14425
  }
14068
14426
  }
14069
- if (!output.trim()) {
14070
- 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
+ }
14071
14434
  }
14072
- const lines = output.trim().split("\n");
14073
- for (const line of lines.slice(0, maxResults)) {
14074
- const match = line.match(/^(.+?):(\d+):(.*)$/);
14075
- if (match) {
14076
- const [, filePath, lineNum, content] = match;
14077
- const relativePath = path28.relative(rootDir, filePath);
14078
- let context2 = "unknown";
14079
- if (relativePath.includes(".purpose") || relativePath.includes("portal.yaml")) {
14080
- context2 = "purpose";
14081
- } else if (content.includes("//") || content.includes("#") || content.includes("*")) {
14082
- context2 = "comment";
14083
- } else {
14084
- context2 = "code";
14085
- }
14086
- results.push({
14087
- filePath: relativePath,
14088
- line: parseInt(lineNum, 10),
14089
- content: content.trim().slice(0, 200),
14090
- context: context2
14091
- });
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 {
14092
14455
  }
14093
14456
  }
14094
- return results;
14457
+ return items;
14095
14458
  }
14096
-
14097
- // ../paradigm-mcp/src/tools/fuzzy-match.ts
14098
- function levenshteinDistance(a, b) {
14099
- const matrix = [];
14100
- for (let i = 0; i <= b.length; i++) {
14101
- 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 });
14102
14463
  }
14103
- for (let j = 0; j <= a.length; j++) {
14104
- 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 {
14105
14478
  }
14106
- for (let i = 1; i <= b.length; i++) {
14107
- for (let j = 1; j <= a.length; j++) {
14108
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
14109
- matrix[i][j] = matrix[i - 1][j - 1];
14110
- } else {
14111
- matrix[i][j] = Math.min(
14112
- matrix[i - 1][j - 1] + 1,
14113
- // substitution
14114
- matrix[i][j - 1] + 1,
14115
- // insertion
14116
- matrix[i - 1][j] + 1
14117
- // deletion
14118
- );
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 {
14119
14534
  }
14120
14535
  }
14121
14536
  }
14122
- return matrix[b.length][a.length];
14537
+ return agents;
14123
14538
  }
14124
- function findFuzzyMatches(query, candidates, options = {}) {
14125
- const { maxDistance = 3, maxResults = 5 } = options;
14126
- const queryLower = query.toLowerCase();
14127
- const matches = [];
14128
- for (const candidate of candidates) {
14129
- const candidateLower = candidate.toLowerCase();
14130
- if (candidateLower === queryLower) {
14131
- matches.push({ match: candidate, distance: 0 });
14132
- continue;
14133
- }
14134
- if (candidateLower.includes(queryLower) || queryLower.includes(candidateLower)) {
14135
- matches.push({ match: candidate, distance: 1 });
14136
- continue;
14137
- }
14138
- const distance = levenshteinDistance(queryLower, candidateLower);
14139
- if (distance <= maxDistance) {
14140
- 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++;
14141
14546
  }
14142
14547
  }
14143
- matches.sort((a, b) => {
14144
- if (a.distance !== b.distance) return a.distance - b.distance;
14145
- return a.match.localeCompare(b.match);
14146
- });
14147
- return matches.slice(0, maxResults);
14548
+ return cleaned;
14148
14549
  }
14149
-
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, statusBlurb) {
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
+ if (statusBlurb !== void 0) {
14567
+ identity.statusBlurb = statusBlurb || void 0;
14568
+ }
14569
+ fs26.writeFileSync(identityPath, JSON.stringify(identity, null, 2), "utf-8");
14570
+ } catch {
14571
+ }
14572
+ }
14573
+ function isAgentAsleep(identity, thresholdMs = 6e4) {
14574
+ if (!identity.lastPoll) return true;
14575
+ const lastPoll = new Date(identity.lastPoll).getTime();
14576
+ return Date.now() - lastPoll > thresholdMs;
14577
+ }
14578
+ function inboxPath(agentId) {
14579
+ return path28.join(getAgentDir(agentId), "inbox.jsonl");
14580
+ }
14581
+ function outboxPath(agentId) {
14582
+ return path28.join(getAgentDir(agentId), "outbox.jsonl");
14583
+ }
14584
+ function ackPath(agentId) {
14585
+ return path28.join(getAgentDir(agentId), "ack.json");
14586
+ }
14587
+ function appendToInbox(agentId, message) {
14588
+ ensureAgentDir(agentId);
14589
+ appendJsonlLine(inboxPath(agentId), message);
14590
+ }
14591
+ function readInbox(agentId, afterAck) {
14592
+ const messages = readJsonlFile(inboxPath(agentId));
14593
+ if (!afterAck) {
14594
+ const ack = readAck(agentId);
14595
+ if (ack) {
14596
+ const ackIndex2 = messages.findIndex((m) => m.id === ack);
14597
+ if (ackIndex2 >= 0) return messages.slice(ackIndex2 + 1);
14598
+ }
14599
+ return messages;
14600
+ }
14601
+ const ackIndex = messages.findIndex((m) => m.id === afterAck);
14602
+ if (ackIndex >= 0) return messages.slice(ackIndex + 1);
14603
+ return messages;
14604
+ }
14605
+ function appendToOutbox(agentId, message) {
14606
+ ensureAgentDir(agentId);
14607
+ appendJsonlLine(outboxPath(agentId), message);
14608
+ }
14609
+ function acknowledgeMessages(agentId, lastMessageId) {
14610
+ const filePath = ackPath(agentId);
14611
+ ensureAgentDir(agentId);
14612
+ fs26.writeFileSync(filePath, JSON.stringify({ lastAck: lastMessageId }), "utf-8");
14613
+ }
14614
+ function readAck(agentId) {
14615
+ const filePath = ackPath(agentId);
14616
+ if (!fs26.existsSync(filePath)) return null;
14617
+ try {
14618
+ const content = JSON.parse(fs26.readFileSync(filePath, "utf-8"));
14619
+ return content.lastAck || null;
14620
+ } catch {
14621
+ return null;
14622
+ }
14623
+ }
14624
+ function garbageCollect(agentId) {
14625
+ const ack = readAck(agentId);
14626
+ if (!ack) return 0;
14627
+ const filePath = inboxPath(agentId);
14628
+ const messages = readJsonlFile(filePath);
14629
+ const ackIndex = messages.findIndex((m) => m.id === ack);
14630
+ if (ackIndex < 0) return 0;
14631
+ const kept = messages.slice(ackIndex + 1);
14632
+ const removed = messages.length - kept.length;
14633
+ if (kept.length === 0) {
14634
+ if (fs26.existsSync(filePath)) fs26.writeFileSync(filePath, "", "utf-8");
14635
+ } else {
14636
+ fs26.writeFileSync(filePath, kept.map((m) => JSON.stringify(m)).join("\n") + "\n", "utf-8");
14637
+ }
14638
+ return removed;
14639
+ }
14640
+ function threadPath(threadId) {
14641
+ return path28.join(THREADS_DIR, `${threadId}.json`);
14642
+ }
14643
+ function createThread(topic, initiator) {
14644
+ ensureScoreDirs();
14645
+ const id = "thr-" + crypto.randomBytes(4).toString("hex");
14646
+ const now = (/* @__PURE__ */ new Date()).toISOString();
14647
+ const thread = {
14648
+ id,
14649
+ topic,
14650
+ initiator,
14651
+ participants: [initiator],
14652
+ status: "active",
14653
+ createdAt: now,
14654
+ lastActivity: now,
14655
+ messageCount: 0
14656
+ };
14657
+ fs26.writeFileSync(threadPath(id), JSON.stringify(thread, null, 2), "utf-8");
14658
+ return thread;
14659
+ }
14660
+ function loadThread(threadId) {
14661
+ const filePath = threadPath(threadId);
14662
+ if (!fs26.existsSync(filePath)) return null;
14663
+ try {
14664
+ return JSON.parse(fs26.readFileSync(filePath, "utf-8"));
14665
+ } catch {
14666
+ return null;
14667
+ }
14668
+ }
14669
+ function listThreads(status) {
14670
+ ensureScoreDirs();
14671
+ if (!fs26.existsSync(THREADS_DIR)) return [];
14672
+ const files = fs26.readdirSync(THREADS_DIR).filter((f) => f.endsWith(".json"));
14673
+ const threads = [];
14674
+ for (const file of files) {
14675
+ try {
14676
+ const content = fs26.readFileSync(path28.join(THREADS_DIR, file), "utf-8");
14677
+ const thread = JSON.parse(content);
14678
+ if (!status || thread.status === status) {
14679
+ threads.push(thread);
14680
+ }
14681
+ } catch {
14682
+ }
14683
+ }
14684
+ return threads.sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
14685
+ }
14686
+ function updateThread(threadId, partial) {
14687
+ const thread = loadThread(threadId);
14688
+ if (!thread) return false;
14689
+ const updated = { ...thread, ...partial };
14690
+ fs26.writeFileSync(threadPath(threadId), JSON.stringify(updated, null, 2), "utf-8");
14691
+ return true;
14692
+ }
14693
+ function getThreadMessages(threadId) {
14694
+ const agents = listAgents();
14695
+ const messages = [];
14696
+ for (const agent of agents) {
14697
+ const inbox = readJsonlFile(inboxPath(agent.id));
14698
+ const outbox = readJsonlFile(outboxPath(agent.id));
14699
+ for (const msg of [...inbox, ...outbox]) {
14700
+ if (msg.threadRoot === threadId || msg.id === threadId) {
14701
+ if (!messages.some((m) => m.id === msg.id)) {
14702
+ messages.push(msg);
14703
+ }
14704
+ }
14705
+ }
14706
+ }
14707
+ return messages.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
14708
+ }
14709
+ function buildMessage(params) {
14710
+ return {
14711
+ id: crypto.randomUUID(),
14712
+ parentId: params.parentId,
14713
+ threadRoot: params.threadRoot,
14714
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14715
+ sender: params.sender,
14716
+ recipients: params.recipients,
14717
+ intent: params.intent,
14718
+ content: {
14719
+ text: params.text,
14720
+ diff: params.diff,
14721
+ decision: params.decision
14722
+ },
14723
+ symbols: params.symbols || [],
14724
+ attachments: params.attachments,
14725
+ metadata: params.metadata
14726
+ };
14727
+ }
14728
+ function routeMessage(message) {
14729
+ ensureScoreDirs();
14730
+ appendToOutbox(message.sender.id, message);
14731
+ let deliveryCount = 0;
14732
+ if (message.recipients && message.recipients.length > 0) {
14733
+ for (const recipient of message.recipients) {
14734
+ appendToInbox(recipient.id, message);
14735
+ deliveryCount++;
14736
+ }
14737
+ } else {
14738
+ const agents = listAgents();
14739
+ for (const agent of agents) {
14740
+ if (agent.id !== message.sender.id) {
14741
+ appendToInbox(agent.id, message);
14742
+ deliveryCount++;
14743
+ }
14744
+ }
14745
+ }
14746
+ if (message.threadRoot) {
14747
+ const thread = loadThread(message.threadRoot);
14748
+ if (thread) {
14749
+ const isParticipant = thread.participants.some((p) => p.id === message.sender.id);
14750
+ const updatedParticipants = isParticipant ? thread.participants : [...thread.participants, message.sender];
14751
+ updateThread(message.threadRoot, {
14752
+ participants: updatedParticipants,
14753
+ lastActivity: message.timestamp,
14754
+ messageCount: thread.messageCount + 1
14755
+ });
14756
+ }
14757
+ }
14758
+ return deliveryCount;
14759
+ }
14760
+ function fileRequestPath(requestId) {
14761
+ return path28.join(FILE_REQUESTS_DIR, `${requestId}.json`);
14762
+ }
14763
+ function loadTrustConfig() {
14764
+ if (!fs26.existsSync(TRUST_CONFIG_PATH)) return DEFAULT_TRUST;
14765
+ try {
14766
+ const content = fs26.readFileSync(TRUST_CONFIG_PATH, "utf-8");
14767
+ try {
14768
+ return JSON.parse(content);
14769
+ } catch {
14770
+ return DEFAULT_TRUST;
14771
+ }
14772
+ } catch {
14773
+ return DEFAULT_TRUST;
14774
+ }
14775
+ }
14776
+ function createFileRequest(params) {
14777
+ ensureScoreDirs();
14778
+ const requestId = "freq-" + crypto.randomBytes(4).toString("hex");
14779
+ const record = {
14780
+ request: {
14781
+ requestId,
14782
+ filePath: params.filePath,
14783
+ reason: params.reason,
14784
+ requester: params.requester,
14785
+ urgency: params.urgency || "normal",
14786
+ snippet: params.snippet,
14787
+ threadRoot: params.threadRoot
14788
+ },
14789
+ status: "pending",
14790
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
14791
+ };
14792
+ fs26.writeFileSync(fileRequestPath(requestId), JSON.stringify(record, null, 2), "utf-8");
14793
+ return record;
14794
+ }
14795
+ function loadFileRequest(requestId) {
14796
+ const filePath = fileRequestPath(requestId);
14797
+ if (!fs26.existsSync(filePath)) return null;
14798
+ try {
14799
+ return JSON.parse(fs26.readFileSync(filePath, "utf-8"));
14800
+ } catch {
14801
+ return null;
14802
+ }
14803
+ }
14804
+ function listFileRequests(status) {
14805
+ ensureScoreDirs();
14806
+ if (!fs26.existsSync(FILE_REQUESTS_DIR)) return [];
14807
+ const files = fs26.readdirSync(FILE_REQUESTS_DIR).filter((f) => f.endsWith(".json"));
14808
+ const requests = [];
14809
+ for (const file of files) {
14810
+ try {
14811
+ const content = fs26.readFileSync(path28.join(FILE_REQUESTS_DIR, file), "utf-8");
14812
+ const record = JSON.parse(content);
14813
+ if (!status || record.status === status) {
14814
+ requests.push(record);
14815
+ }
14816
+ } catch {
14817
+ }
14818
+ }
14819
+ return requests.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
14820
+ }
14821
+ function approveFileRequest(requestId, projectDir2, redact) {
14822
+ const record = loadFileRequest(requestId);
14823
+ if (!record) return { success: false, error: `File request not found: ${requestId}` };
14824
+ if (record.status !== "pending") return { success: false, error: `Request already ${record.status}` };
14825
+ const absolutePath = path28.resolve(projectDir2, record.request.filePath);
14826
+ if (!absolutePath.startsWith(path28.resolve(projectDir2))) {
14827
+ return { success: false, error: "File path escapes project directory" };
14828
+ }
14829
+ if (!fs26.existsSync(absolutePath)) {
14830
+ return { success: false, error: `File not found: ${record.request.filePath}` };
14831
+ }
14832
+ try {
14833
+ let content = fs26.readFileSync(absolutePath, "utf-8");
14834
+ let encoding = "utf8";
14835
+ if (redact) {
14836
+ const secretPatterns = [
14837
+ /(?:api[_-]?key|secret|token|password|credential|auth)\s*[:=]/i,
14838
+ /(?:^|\s)(?:export\s+)?[A-Z_]+(?:KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL)\s*=/,
14839
+ /-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/
14840
+ ];
14841
+ content = content.split("\n").map((line) => {
14842
+ for (const pattern of secretPatterns) {
14843
+ if (pattern.test(line)) return "[REDACTED]";
14844
+ }
14845
+ return line;
14846
+ }).join("\n");
14847
+ }
14848
+ const hash = crypto.createHash("sha256").update(content).digest("hex");
14849
+ const delivery = {
14850
+ requestId,
14851
+ filePath: record.request.filePath,
14852
+ content,
14853
+ encoding,
14854
+ size: Buffer.byteLength(content),
14855
+ hash
14856
+ };
14857
+ record.status = "approved";
14858
+ record.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
14859
+ record.delivery = delivery;
14860
+ fs26.writeFileSync(fileRequestPath(requestId), JSON.stringify(record, null, 2), "utf-8");
14861
+ const deliveryMessage = buildMessage({
14862
+ sender: { id: "system", name: "File Transfer", type: "human" },
14863
+ recipients: [record.request.requester],
14864
+ intent: "fileDelivery",
14865
+ text: `File delivered: ${record.request.filePath} (${delivery.size} bytes, SHA-256: ${hash.slice(0, 12)}...)`,
14866
+ threadRoot: record.request.threadRoot,
14867
+ symbols: []
14868
+ });
14869
+ deliveryMessage.attachments = [{
14870
+ name: path28.basename(record.request.filePath),
14871
+ type: "file",
14872
+ content: delivery.content,
14873
+ encoding: delivery.encoding
14874
+ }];
14875
+ routeMessage(deliveryMessage);
14876
+ return { success: true, delivery };
14877
+ } catch (err2) {
14878
+ return { success: false, error: `Failed to read file: ${err2.message}` };
14879
+ }
14880
+ }
14881
+ function denyFileRequest(requestId, reason) {
14882
+ const record = loadFileRequest(requestId);
14883
+ if (!record || record.status !== "pending") return false;
14884
+ record.status = "denied";
14885
+ record.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
14886
+ record.denyReason = reason;
14887
+ fs26.writeFileSync(fileRequestPath(requestId), JSON.stringify(record, null, 2), "utf-8");
14888
+ const denialMessage = buildMessage({
14889
+ sender: { id: "system", name: "File Transfer", type: "human" },
14890
+ recipients: [record.request.requester],
14891
+ intent: "fileDenied",
14892
+ text: `File request denied: ${record.request.filePath}${reason ? ` \u2014 ${reason}` : ""}`,
14893
+ threadRoot: record.request.threadRoot,
14894
+ symbols: []
14895
+ });
14896
+ routeMessage(denialMessage);
14897
+ return true;
14898
+ }
14899
+ function matchesGlob(filePath, pattern) {
14900
+ let regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
14901
+ return new RegExp(`^${regex}$`).test(filePath);
14902
+ }
14903
+ function isPathDenied(filePath, config, user) {
14904
+ const trust = config || loadTrustConfig();
14905
+ if (user && trust.users[user]) {
14906
+ for (const pattern of trust.users[user].neverApprove) {
14907
+ if (matchesGlob(filePath, pattern)) return true;
14908
+ }
14909
+ }
14910
+ for (const pattern of trust.defaults.neverApprove) {
14911
+ if (matchesGlob(filePath, pattern)) return true;
14912
+ }
14913
+ return false;
14914
+ }
14915
+ function expireOldRequests() {
14916
+ const requests = listFileRequests("pending");
14917
+ let expired = 0;
14918
+ for (const record of requests) {
14919
+ const age = Date.now() - new Date(record.createdAt).getTime();
14920
+ if (age > FILE_REQUEST_TTL_MS) {
14921
+ record.status = "expired";
14922
+ record.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
14923
+ fs26.writeFileSync(
14924
+ fileRequestPath(record.request.requestId),
14925
+ JSON.stringify(record, null, 2),
14926
+ "utf-8"
14927
+ );
14928
+ expired++;
14929
+ }
14930
+ }
14931
+ return expired;
14932
+ }
14933
+ function isProcessAlive2(pid) {
14934
+ try {
14935
+ process.kill(pid, 0);
14936
+ return true;
14937
+ } catch {
14938
+ return false;
14939
+ }
14940
+ }
14941
+
14942
+ // ../paradigm-mcp/src/tools/symphony.ts
14943
+ function getSymphonyToolsList() {
14944
+ return [
14945
+ {
14946
+ name: "paradigm_symphony_poll",
14947
+ 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 and optional status blurb. ~200 tokens.",
14948
+ inputSchema: {
14949
+ type: "object",
14950
+ properties: {
14951
+ status: {
14952
+ type: "string",
14953
+ description: 'Short status blurb describing what you are currently working on (e.g., "Implementing auth middleware \u2014 3 files modified"). Visible to humans and other agents in the Network view.'
14954
+ }
14955
+ }
14956
+ },
14957
+ annotations: {
14958
+ readOnlyHint: false,
14959
+ // Updates ack + poll time
14960
+ destructiveHint: false
14961
+ }
14962
+ },
14963
+ {
14964
+ name: "paradigm_symphony_send",
14965
+ 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.",
14966
+ inputSchema: {
14967
+ type: "object",
14968
+ properties: {
14969
+ intent: {
14970
+ type: "string",
14971
+ enum: [
14972
+ "question",
14973
+ "context",
14974
+ "clarification",
14975
+ "proposal",
14976
+ "verification",
14977
+ "action",
14978
+ "decision",
14979
+ "alert",
14980
+ "approval",
14981
+ "rejection",
14982
+ "reference",
14983
+ "handoff"
14984
+ ],
14985
+ description: "Message intent (what kind of message this is)"
14986
+ },
14987
+ text: {
14988
+ type: "string",
14989
+ description: "Message text content"
14990
+ },
14991
+ parentId: {
14992
+ type: "string",
14993
+ description: "ID of message being replied to"
14994
+ },
14995
+ threadRoot: {
14996
+ type: "string",
14997
+ description: "Thread ID to post in (auto-created if omitted)"
14998
+ },
14999
+ recipients: {
15000
+ type: "array",
15001
+ items: { type: "string" },
15002
+ description: "Agent IDs to send to (omit for broadcast)"
15003
+ },
15004
+ symbols: {
15005
+ type: "array",
15006
+ items: { type: "string" },
15007
+ description: 'Paradigm symbols referenced (e.g., ["#auth-service", "$login-flow"])'
15008
+ },
15009
+ diff: {
15010
+ type: "string",
15011
+ description: "Code diff to include with the message"
15012
+ },
15013
+ decision: {
15014
+ type: "string",
15015
+ description: "Decision text to record (for intent=decision)"
15016
+ }
15017
+ },
15018
+ required: ["intent", "text"]
15019
+ },
15020
+ annotations: {
15021
+ readOnlyHint: false,
15022
+ destructiveHint: false
15023
+ }
15024
+ },
15025
+ {
15026
+ name: "paradigm_symphony_status",
15027
+ description: "Show Symphony network status: linked agents (with awake/asleep detection), active threads, unread count, pending file requests. ~150 tokens.",
15028
+ inputSchema: {
15029
+ type: "object",
15030
+ properties: {}
15031
+ },
15032
+ annotations: {
15033
+ readOnlyHint: true,
15034
+ destructiveHint: false
15035
+ }
15036
+ },
15037
+ {
15038
+ name: "paradigm_symphony_thread",
15039
+ description: "Get full thread context: all notes in order, participants, extracted decisions, and referenced symbols. ~200 tokens.",
15040
+ inputSchema: {
15041
+ type: "object",
15042
+ properties: {
15043
+ threadId: {
15044
+ type: "string",
15045
+ description: "Thread ID (thr-XXXXXXXX format)"
15046
+ },
15047
+ depth: {
15048
+ type: "number",
15049
+ description: "Maximum messages to return (default: 50)"
15050
+ }
15051
+ },
15052
+ required: ["threadId"]
15053
+ },
15054
+ annotations: {
15055
+ readOnlyHint: true,
15056
+ destructiveHint: false
15057
+ }
15058
+ },
15059
+ {
15060
+ name: "paradigm_symphony_request_file",
15061
+ 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.",
15062
+ inputSchema: {
15063
+ type: "object",
15064
+ properties: {
15065
+ filePath: {
15066
+ type: "string",
15067
+ description: 'Relative file path to request (e.g., "src/auth/middleware.ts")'
15068
+ },
15069
+ from: {
15070
+ type: "string",
15071
+ description: 'Agent ID to request file from (e.g., "backend/core")'
15072
+ },
15073
+ reason: {
15074
+ type: "string",
15075
+ description: "Why this file is needed"
15076
+ },
15077
+ snippet: {
15078
+ type: "string",
15079
+ description: 'Specific function or line range needed (e.g., "validateToken function" or "lines 50-100")'
15080
+ }
15081
+ },
15082
+ required: ["filePath", "from", "reason"]
15083
+ },
15084
+ annotations: {
15085
+ readOnlyHint: false,
15086
+ destructiveHint: false
15087
+ }
15088
+ },
15089
+ {
15090
+ name: "paradigm_symphony_approve_file",
15091
+ 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.',
15092
+ inputSchema: {
15093
+ type: "object",
15094
+ properties: {
15095
+ requestId: {
15096
+ type: "string",
15097
+ description: "File request ID (freq-XXXXXXXX format)"
15098
+ },
15099
+ action: {
15100
+ type: "string",
15101
+ enum: ["approve", "deny", "approve-redacted"],
15102
+ description: "Approve, deny, or approve with redaction"
15103
+ },
15104
+ reason: {
15105
+ type: "string",
15106
+ description: "Reason for denial (required for deny action)"
15107
+ }
15108
+ },
15109
+ required: ["requestId", "action"]
15110
+ },
15111
+ annotations: {
15112
+ readOnlyHint: false,
15113
+ destructiveHint: false
15114
+ }
15115
+ }
15116
+ ];
15117
+ }
15118
+ async function handleSymphonyTool(name, args, ctx) {
15119
+ let identity = getMyIdentity(ctx.rootDir);
15120
+ if (!identity) {
15121
+ identity = registerAgent(ctx.rootDir);
15122
+ }
15123
+ switch (name) {
15124
+ case "paradigm_symphony_poll": {
15125
+ cleanStaleAgents();
15126
+ expireOldRequests();
15127
+ const statusBlurb = args.status;
15128
+ markAgentPollTime(identity.id, statusBlurb);
15129
+ const messages = readInbox(identity.id);
15130
+ if (messages.length > 0) {
15131
+ const lastId = messages[messages.length - 1].id;
15132
+ acknowledgeMessages(identity.id, lastId);
15133
+ }
15134
+ garbageCollect(identity.id);
15135
+ const pendingRequests = listFileRequests("pending");
15136
+ if (messages.length === 0 && pendingRequests.length === 0) {
15137
+ return {
15138
+ handled: true,
15139
+ text: JSON.stringify({
15140
+ messages: 0,
15141
+ note: "No new notes. Score is quiet.",
15142
+ identity: identity.id
15143
+ })
15144
+ };
15145
+ }
15146
+ const formatted = formatPollOutput(messages, pendingRequests);
15147
+ return {
15148
+ handled: true,
15149
+ text: formatted
15150
+ };
15151
+ }
15152
+ case "paradigm_symphony_send": {
15153
+ const intent = args.intent;
15154
+ const text = args.text;
15155
+ const parentId = args.parentId;
15156
+ let threadRoot = args.threadRoot;
15157
+ const recipientIds = args.recipients;
15158
+ const symbols = args.symbols;
15159
+ const diff = args.diff;
15160
+ const decision = args.decision;
15161
+ let threadCreated = false;
15162
+ if (!threadRoot && !parentId) {
15163
+ const topic = text.length > 60 ? text.slice(0, 60) + "..." : text;
15164
+ const thread = createThread(topic, identityToParticipant(identity));
15165
+ threadRoot = thread.id;
15166
+ threadCreated = true;
15167
+ }
15168
+ let recipients;
15169
+ if (recipientIds && recipientIds.length > 0) {
15170
+ const allAgents = listAgents();
15171
+ recipients = recipientIds.map((id) => {
15172
+ const agent = allAgents.find((a) => a.id === id);
15173
+ if (agent) return identityToParticipant(agent);
15174
+ return { id, name: id, type: "agent" };
15175
+ });
15176
+ }
15177
+ const message = buildMessage({
15178
+ sender: identityToParticipant(identity),
15179
+ recipients,
15180
+ intent,
15181
+ text,
15182
+ parentId,
15183
+ threadRoot,
15184
+ symbols,
15185
+ diff,
15186
+ decision
15187
+ });
15188
+ const deliveryCount = routeMessage(message);
15189
+ emitSymphonyEvent(message).catch(() => {
15190
+ });
15191
+ if (threadCreated && threadRoot) {
15192
+ const threadEvent = {
15193
+ id: `thread-created-${threadRoot}`,
15194
+ threadRoot,
15195
+ timestamp: message.timestamp,
15196
+ sender: message.sender,
15197
+ intent: "context",
15198
+ content: { text: `Thread created: ${message.content.text.slice(0, 60)}` },
15199
+ symbols: []
15200
+ };
15201
+ fetch(`${SENTINEL_URL}/api/events`, {
15202
+ method: "POST",
15203
+ headers: { "Content-Type": "application/json" },
15204
+ body: JSON.stringify({
15205
+ schemaId: "paradigm-symphony",
15206
+ eventType: "thread:created",
15207
+ category: "lifecycle",
15208
+ timestamp: message.timestamp,
15209
+ scopeValue: threadRoot,
15210
+ service: message.sender.project || "symphony",
15211
+ severity: "info",
15212
+ data: {
15213
+ topic: message.content.text.slice(0, 60),
15214
+ initiator: message.sender.name,
15215
+ threadId: threadRoot
15216
+ }
15217
+ })
15218
+ }).catch(() => {
15219
+ });
15220
+ }
15221
+ return {
15222
+ handled: true,
15223
+ text: JSON.stringify({
15224
+ sent: true,
15225
+ messageId: message.id,
15226
+ threadId: threadRoot,
15227
+ threadCreated,
15228
+ deliveredTo: deliveryCount,
15229
+ intent
15230
+ })
15231
+ };
15232
+ }
15233
+ case "paradigm_symphony_status": {
15234
+ cleanStaleAgents();
15235
+ const agents = listAgents();
15236
+ const threads = listThreads("active");
15237
+ const unread = readInbox(identity.id);
15238
+ const pendingRequests = listFileRequests("pending");
15239
+ return {
15240
+ handled: true,
15241
+ text: JSON.stringify({
15242
+ identity: {
15243
+ id: identity.id,
15244
+ project: identity.project,
15245
+ role: identity.role
15246
+ },
15247
+ agents: agents.map((a) => ({
15248
+ id: a.id,
15249
+ name: a.name,
15250
+ project: a.project,
15251
+ role: a.role,
15252
+ status: isAgentAsleep(a) ? "asleep" : "awake",
15253
+ lastPoll: a.lastPoll,
15254
+ statusBlurb: a.statusBlurb
15255
+ })),
15256
+ activeThreads: threads.map((t) => ({
15257
+ id: t.id,
15258
+ topic: t.topic,
15259
+ participants: t.participants.length,
15260
+ messageCount: t.messageCount,
15261
+ lastActivity: t.lastActivity
15262
+ })),
15263
+ unreadCount: unread.length,
15264
+ pendingFileRequests: pendingRequests.length
15265
+ }, null, 2)
15266
+ };
15267
+ }
15268
+ case "paradigm_symphony_thread": {
15269
+ const threadId = args.threadId;
15270
+ const depth = args.depth || 50;
15271
+ const thread = loadThread(threadId);
15272
+ if (!thread) {
15273
+ return {
15274
+ handled: true,
15275
+ text: JSON.stringify({ error: `Thread not found: ${threadId}` })
15276
+ };
15277
+ }
15278
+ const messages = getThreadMessages(threadId).slice(0, depth);
15279
+ const decisions = [];
15280
+ const symbolsDiscussed = /* @__PURE__ */ new Set();
15281
+ const filesReferenced = /* @__PURE__ */ new Set();
15282
+ for (const msg of messages) {
15283
+ if (msg.content.decision) decisions.push(msg.content.decision);
15284
+ if (msg.intent === "decision" && msg.content.text) decisions.push(msg.content.text);
15285
+ for (const sym of msg.symbols) symbolsDiscussed.add(sym);
15286
+ if (msg.attachments) {
15287
+ for (const att of msg.attachments) {
15288
+ if (att.type === "file") filesReferenced.add(att.name);
15289
+ }
15290
+ }
15291
+ }
15292
+ return {
15293
+ handled: true,
15294
+ text: JSON.stringify({
15295
+ thread: {
15296
+ id: thread.id,
15297
+ topic: thread.topic,
15298
+ status: thread.status,
15299
+ createdAt: thread.createdAt,
15300
+ decision: thread.decision
15301
+ },
15302
+ participants: thread.participants,
15303
+ messages: messages.map((m) => ({
15304
+ id: m.id,
15305
+ sender: m.sender.name,
15306
+ intent: m.intent,
15307
+ text: m.content.text,
15308
+ timestamp: m.timestamp,
15309
+ symbols: m.symbols,
15310
+ hasDiff: !!m.content.diff,
15311
+ hasDecision: !!m.content.decision
15312
+ })),
15313
+ decisions,
15314
+ symbolsDiscussed: [...symbolsDiscussed],
15315
+ filesReferenced: [...filesReferenced]
15316
+ }, null, 2)
15317
+ };
15318
+ }
15319
+ case "paradigm_symphony_request_file": {
15320
+ const filePath = args.filePath;
15321
+ const from = args.from;
15322
+ const reason = args.reason;
15323
+ const snippet = args.snippet;
15324
+ const trustConfig = loadTrustConfig();
15325
+ if (isPathDenied(filePath, trustConfig)) {
15326
+ return {
15327
+ handled: true,
15328
+ text: JSON.stringify({
15329
+ error: `File path "${filePath}" is on the hard-deny list and cannot be requested.`,
15330
+ deniedPatterns: trustConfig.defaults.neverApprove
15331
+ })
15332
+ };
15333
+ }
15334
+ const record = createFileRequest({
15335
+ filePath,
15336
+ requester: identityToParticipant(identity),
15337
+ reason,
15338
+ snippet,
15339
+ threadRoot: void 0
15340
+ });
15341
+ const requestMessage = buildMessage({
15342
+ sender: identityToParticipant(identity),
15343
+ recipients: [{ id: from, name: from, type: "agent" }],
15344
+ intent: "fileRequest",
15345
+ text: `Requesting file: ${filePath}
15346
+ Reason: ${reason}${snippet ? `
15347
+ Snippet: ${snippet}` : ""}`,
15348
+ symbols: []
15349
+ });
15350
+ routeMessage(requestMessage);
15351
+ return {
15352
+ handled: true,
15353
+ text: JSON.stringify({
15354
+ requestId: record.request.requestId,
15355
+ status: "pending",
15356
+ filePath,
15357
+ from,
15358
+ message: `File request created. The owning agent's human must approve via "paradigm symphony approve ${record.request.requestId}" or "paradigm_symphony_approve_file".`
15359
+ })
15360
+ };
15361
+ }
15362
+ case "paradigm_symphony_approve_file": {
15363
+ const requestId = args.requestId;
15364
+ const action = args.action;
15365
+ const reason = args.reason;
15366
+ if (action === "deny") {
15367
+ const success = denyFileRequest(requestId, reason);
15368
+ return {
15369
+ handled: true,
15370
+ text: JSON.stringify({
15371
+ success,
15372
+ requestId,
15373
+ action: "denied",
15374
+ reason: reason || "No reason provided"
15375
+ })
15376
+ };
15377
+ }
15378
+ const redact = action === "approve-redacted";
15379
+ const result = approveFileRequest(requestId, ctx.rootDir, redact);
15380
+ if (!result.success) {
15381
+ return {
15382
+ handled: true,
15383
+ text: JSON.stringify({
15384
+ success: false,
15385
+ requestId,
15386
+ error: result.error
15387
+ })
15388
+ };
15389
+ }
15390
+ return {
15391
+ handled: true,
15392
+ text: JSON.stringify({
15393
+ success: true,
15394
+ requestId,
15395
+ action: redact ? "approved-redacted" : "approved",
15396
+ filePath: result.delivery?.filePath,
15397
+ size: result.delivery?.size,
15398
+ hash: result.delivery?.hash
15399
+ })
15400
+ };
15401
+ }
15402
+ default:
15403
+ return { handled: false, text: "" };
15404
+ }
15405
+ }
15406
+ function identityToParticipant(identity) {
15407
+ return {
15408
+ id: identity.id,
15409
+ name: identity.name,
15410
+ type: identity.type || "agent",
15411
+ project: identity.project,
15412
+ role: identity.role
15413
+ };
15414
+ }
15415
+ function formatPollOutput(messages, pendingRequests) {
15416
+ const parts = [];
15417
+ const byThread = /* @__PURE__ */ new Map();
15418
+ for (const msg of messages) {
15419
+ const threadId = msg.threadRoot || "direct";
15420
+ if (!byThread.has(threadId)) byThread.set(threadId, []);
15421
+ byThread.get(threadId).push(msg);
15422
+ }
15423
+ for (const [threadId, threadMsgs] of byThread) {
15424
+ let threadTopic = threadId;
15425
+ if (threadId !== "direct") {
15426
+ const thread = loadThread(threadId);
15427
+ if (thread) threadTopic = thread.topic;
15428
+ }
15429
+ parts.push(`## Symphony: ${threadMsgs.length} new note${threadMsgs.length !== 1 ? "s" : ""} in "${threadTopic}"
15430
+ `);
15431
+ for (let i = 0; i < threadMsgs.length; i++) {
15432
+ const msg = threadMsgs[i];
15433
+ const time = new Date(msg.timestamp).toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
15434
+ const senderLabel = `${msg.sender.name}${msg.sender.project ? ` (${msg.sender.project})` : ""}`;
15435
+ parts.push(`### ${i + 1}. ${senderLabel} \u2014 ${capitalize(msg.intent)} (${time})`);
15436
+ parts.push(`> ${msg.content.text.split("\n").join("\n> ")}`);
15437
+ if (msg.symbols.length > 0) {
15438
+ parts.push(`> Symbols: ${msg.symbols.join(", ")}`);
15439
+ }
15440
+ if (msg.content.diff) {
15441
+ parts.push(`
15442
+ \`\`\`diff
15443
+ ${msg.content.diff}
15444
+ \`\`\``);
15445
+ }
15446
+ if (msg.content.decision) {
15447
+ parts.push(`
15448
+ **Decision:** ${msg.content.decision}`);
15449
+ }
15450
+ const action = suggestAction(msg);
15451
+ if (action) parts.push(`
15452
+ **Suggested action:** ${action}`);
15453
+ parts.push("");
15454
+ }
15455
+ }
15456
+ if (pendingRequests.length > 0) {
15457
+ parts.push(`## Pending File Requests (${pendingRequests.length})
15458
+ `);
15459
+ for (const req of pendingRequests) {
15460
+ parts.push(`- **${req.request.filePath}** from ${req.request.requester.name}: ${req.request.reason}`);
15461
+ parts.push(` Approve: \`paradigm_symphony_approve_file({ requestId: "${req.request.requestId}", action: "approve" })\``);
15462
+ }
15463
+ parts.push("");
15464
+ }
15465
+ return parts.join("\n");
15466
+ }
15467
+ function suggestAction(msg) {
15468
+ switch (msg.intent) {
15469
+ case "question":
15470
+ return 'Reply with paradigm_symphony_send using intent "context" or "clarification"';
15471
+ case "proposal":
15472
+ return 'Reply with intent "approval" or "rejection"';
15473
+ case "fileRequest":
15474
+ return "Use paradigm_symphony_approve_file to approve or deny";
15475
+ case "handoff":
15476
+ return "Review handoff context and continue the work";
15477
+ case "alert":
15478
+ return 'Investigate the alert and reply with intent "action"';
15479
+ case "verification":
15480
+ return 'Confirm with intent "approval" or clarify with "clarification"';
15481
+ default:
15482
+ return null;
15483
+ }
15484
+ }
15485
+ function capitalize(s) {
15486
+ return s.charAt(0).toUpperCase() + s.slice(1);
15487
+ }
15488
+ var SENTINEL_URL = "http://localhost:3838";
15489
+ var symphonySchemaRegistered = false;
15490
+ function intentToEventType(intent) {
15491
+ switch (intent) {
15492
+ case "question":
15493
+ return "note:question";
15494
+ case "context":
15495
+ return "note:context";
15496
+ case "clarification":
15497
+ return "note:clarification";
15498
+ case "proposal":
15499
+ return "note:proposal";
15500
+ case "verification":
15501
+ return "note:verification";
15502
+ case "action":
15503
+ return "note:action";
15504
+ case "decision":
15505
+ return "note:decision";
15506
+ case "alert":
15507
+ return "note:alert";
15508
+ case "approval":
15509
+ return "note:approval";
15510
+ case "rejection":
15511
+ return "note:rejection";
15512
+ case "reference":
15513
+ return "note:reference";
15514
+ case "handoff":
15515
+ return "note:handoff";
15516
+ case "fileRequest":
15517
+ return "file:requested";
15518
+ case "fileApproved":
15519
+ return "file:approved";
15520
+ case "fileDenied":
15521
+ return "file:denied";
15522
+ case "fileDelivery":
15523
+ return "file:delivered";
15524
+ default:
15525
+ return "note:context";
15526
+ }
15527
+ }
15528
+ function eventTypeToCategory(eventType) {
15529
+ if (eventType.startsWith("note:")) {
15530
+ const intent = eventType.split(":")[1];
15531
+ if (["question", "context", "clarification", "verification", "reference"].includes(intent)) return "dialogue";
15532
+ if (["proposal", "action"].includes(intent)) return "action";
15533
+ if (["decision", "approval", "rejection"].includes(intent)) return "outcome";
15534
+ if (intent === "alert") return "system";
15535
+ if (intent === "handoff") return "lifecycle";
15536
+ }
15537
+ if (eventType.startsWith("thread:") || eventType.startsWith("participant:")) return "lifecycle";
15538
+ if (eventType.startsWith("file:")) return "transfer";
15539
+ return "dialogue";
15540
+ }
15541
+ async function emitSymphonyEvent(note) {
15542
+ try {
15543
+ if (!symphonySchemaRegistered) {
15544
+ await fetch(`${SENTINEL_URL}/api/schemas`, {
15545
+ method: "POST",
15546
+ headers: { "Content-Type": "application/json" },
15547
+ body: JSON.stringify({
15548
+ id: "paradigm-symphony",
15549
+ version: "1.0.0",
15550
+ name: "Symphony Conversations",
15551
+ description: "Agent-to-agent messaging events from The Score protocol"
15552
+ })
15553
+ }).catch(() => {
15554
+ });
15555
+ symphonySchemaRegistered = true;
15556
+ }
15557
+ const eventType = intentToEventType(note.intent);
15558
+ const category = eventTypeToCategory(eventType);
15559
+ await fetch(`${SENTINEL_URL}/api/events`, {
15560
+ method: "POST",
15561
+ headers: { "Content-Type": "application/json" },
15562
+ body: JSON.stringify({
15563
+ schemaId: "paradigm-symphony",
15564
+ eventType,
15565
+ category,
15566
+ timestamp: note.timestamp,
15567
+ scopeValue: note.threadRoot || note.id,
15568
+ service: note.sender.project || "symphony",
15569
+ severity: category === "system" ? "error" : category === "outcome" ? "warn" : "info",
15570
+ parentEventId: note.parentId,
15571
+ data: {
15572
+ sender: note.sender.name,
15573
+ senderRole: note.sender.role || "core",
15574
+ text: note.content.text,
15575
+ symbols: note.symbols,
15576
+ diff: note.content.diff,
15577
+ decision: note.content.decision,
15578
+ parentId: note.parentId,
15579
+ threadId: note.threadRoot
15580
+ }
15581
+ })
15582
+ });
15583
+ } catch {
15584
+ }
15585
+ }
15586
+
15587
+ // ../paradigm-mcp/src/tools/university.ts
15588
+ import { execSync as execSync7 } from "child_process";
15589
+ import * as os5 from "os";
15590
+ function resolveAuthor2() {
15591
+ const envAuthor = process.env.PARADIGM_AUTHOR;
15592
+ if (envAuthor) return sanitize3(envAuthor);
15593
+ try {
15594
+ const gitName = execSync7("git config user.name", { encoding: "utf-8", timeout: 3e3 }).trim();
15595
+ if (gitName) return sanitize3(gitName);
15596
+ } catch {
15597
+ }
15598
+ try {
15599
+ const username = os5.userInfo().username;
15600
+ if (username) return sanitize3(username);
15601
+ } catch {
15602
+ }
15603
+ return "unknown";
15604
+ }
15605
+ function sanitize3(name) {
15606
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 20) || "unknown";
15607
+ }
15608
+ function todayStr() {
15609
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
15610
+ }
15611
+ function getUniversityToolsList() {
15612
+ return [
15613
+ {
15614
+ name: "paradigm_university_search",
15615
+ description: "Search project university content by type, tag, difficulty, or symbol. Returns matching content items. ~150 tokens.",
15616
+ inputSchema: {
15617
+ type: "object",
15618
+ properties: {
15619
+ type: {
15620
+ type: "string",
15621
+ enum: ["note", "policy", "guide", "runbook", "quiz", "path"],
15622
+ description: "Filter by content type"
15623
+ },
15624
+ tag: {
15625
+ type: "string",
15626
+ description: "Filter by tag prefix"
15627
+ },
15628
+ difficulty: {
15629
+ type: "string",
15630
+ enum: ["beginner", "intermediate", "advanced"],
15631
+ description: "Filter by difficulty level"
15632
+ },
15633
+ symbol: {
15634
+ type: "string",
15635
+ description: 'Filter by Paradigm symbol (e.g., "#api-gateway")'
15636
+ },
15637
+ query: {
15638
+ type: "string",
15639
+ description: "Free-text search in title and tags"
15640
+ },
15641
+ limit: {
15642
+ type: "number",
15643
+ description: "Maximum results (default: 20)"
15644
+ }
15645
+ }
15646
+ },
15647
+ annotations: {
15648
+ readOnlyHint: true,
15649
+ destructiveHint: false
15650
+ }
15651
+ },
15652
+ {
15653
+ name: "paradigm_university_get",
15654
+ description: "Fetch a university content item by ID. Returns full content including body for notes/policies and questions for quizzes. ~300 tokens.",
15655
+ inputSchema: {
15656
+ type: "object",
15657
+ properties: {
15658
+ id: {
15659
+ type: "string",
15660
+ description: 'Content ID (e.g., "N-architecture-overview", "Q-onboarding-basics", "LP-new-engineer")'
15661
+ }
15662
+ },
15663
+ required: ["id"]
15664
+ },
15665
+ annotations: {
15666
+ readOnlyHint: true,
15667
+ destructiveHint: false
15668
+ }
15669
+ },
15670
+ {
15671
+ name: "paradigm_university_create",
15672
+ description: "Create a new university content item (note, policy, quiz, or learning path). Auto-generates timestamps and resolves author. ~100 tokens.",
15673
+ inputSchema: {
15674
+ type: "object",
15675
+ properties: {
15676
+ type: {
15677
+ type: "string",
15678
+ enum: ["note", "policy", "guide", "runbook", "quiz", "path"],
15679
+ description: "Content type to create"
15680
+ },
15681
+ title: {
15682
+ type: "string",
15683
+ description: "Content title"
15684
+ },
15685
+ body: {
15686
+ type: "string",
15687
+ description: "Markdown body for notes/policies. Quiz/path YAML content for those types."
15688
+ },
15689
+ tags: {
15690
+ type: "array",
15691
+ items: { type: "string" },
15692
+ description: "Tags for classification"
15693
+ },
15694
+ symbols: {
15695
+ type: "array",
15696
+ items: { type: "string" },
15697
+ description: "Paradigm symbols referenced by this content"
15698
+ },
15699
+ difficulty: {
15700
+ type: "string",
15701
+ enum: ["beginner", "intermediate", "advanced"],
15702
+ description: "Difficulty level (default: beginner)"
15703
+ },
15704
+ estimatedMinutes: {
15705
+ type: "number",
15706
+ description: "Estimated reading/completion time in minutes"
15707
+ },
15708
+ prerequisites: {
15709
+ type: "array",
15710
+ items: { type: "string" },
15711
+ description: "IDs of prerequisite content items"
15712
+ },
15713
+ // Quiz-specific fields
15714
+ passThreshold: {
15715
+ type: "number",
15716
+ description: "For quizzes: pass threshold 0.0-1.0 (default: 0.7)"
15717
+ },
15718
+ questions: {
15719
+ type: "array",
15720
+ description: "For quizzes: array of {id, question, choices: {A:..., B:...}, correct, explanation?}"
15721
+ },
15722
+ // Path-specific fields
15723
+ ordered: {
15724
+ type: "boolean",
15725
+ description: "For learning paths: whether steps must be completed in order"
15726
+ },
15727
+ steps: {
15728
+ type: "array",
15729
+ description: "For learning paths: array of {content, required, passRequired?, note?}"
15730
+ }
15731
+ },
15732
+ required: ["type", "title"]
15733
+ },
15734
+ annotations: {
15735
+ readOnlyHint: false,
15736
+ destructiveHint: false
15737
+ }
15738
+ },
15739
+ {
15740
+ name: "paradigm_university_update",
15741
+ description: "Update an existing university content item. Specify only the fields to change. ~100 tokens.",
15742
+ inputSchema: {
15743
+ type: "object",
15744
+ properties: {
15745
+ id: {
15746
+ type: "string",
15747
+ description: "Content ID to update"
15748
+ },
15749
+ title: { type: "string", description: "New title" },
15750
+ body: { type: "string", description: "New body content" },
15751
+ tags: { type: "array", items: { type: "string" }, description: "New tags" },
15752
+ symbols: { type: "array", items: { type: "string" }, description: "New symbols" },
15753
+ difficulty: { type: "string", enum: ["beginner", "intermediate", "advanced"] },
15754
+ estimatedMinutes: { type: "number" }
15755
+ },
15756
+ required: ["id"]
15757
+ },
15758
+ annotations: {
15759
+ readOnlyHint: false,
15760
+ destructiveHint: false
15761
+ }
15762
+ },
15763
+ {
15764
+ name: "paradigm_university_quiz",
15765
+ description: "Get a quiz for taking \u2014 returns questions WITHOUT answers. Use paradigm_university_submit to submit answers. ~200 tokens.",
15766
+ inputSchema: {
15767
+ type: "object",
15768
+ properties: {
15769
+ id: {
15770
+ type: "string",
15771
+ description: 'Quiz ID (e.g., "Q-onboarding-basics")'
15772
+ }
15773
+ },
15774
+ required: ["id"]
15775
+ },
15776
+ annotations: {
15777
+ readOnlyHint: true,
15778
+ destructiveHint: false
15779
+ }
15780
+ },
15781
+ {
15782
+ name: "paradigm_university_submit",
15783
+ description: "Submit quiz answers for grading. Returns score and saves diploma if passed. ~150 tokens.",
15784
+ inputSchema: {
15785
+ type: "object",
15786
+ properties: {
15787
+ quizId: {
15788
+ type: "string",
15789
+ description: "Quiz ID"
15790
+ },
15791
+ answers: {
15792
+ type: "object",
15793
+ description: 'Map of question ID to answer key (e.g., {"q1": "B", "q2": "A"})'
15794
+ },
15795
+ student: {
15796
+ type: "string",
15797
+ description: "Student name (auto-resolved if omitted)"
15798
+ }
15799
+ },
15800
+ required: ["quizId", "answers"]
15801
+ },
15802
+ annotations: {
15803
+ readOnlyHint: false,
15804
+ destructiveHint: false
15805
+ }
15806
+ },
15807
+ {
15808
+ name: "paradigm_university_onboard",
15809
+ description: "Get recommended onboarding sequence for the project university. Shows learning paths, suggested content, and completion status. ~200 tokens.",
15810
+ inputSchema: {
15811
+ type: "object",
15812
+ properties: {
15813
+ student: {
15814
+ type: "string",
15815
+ description: "Student name to check completion (auto-resolved if omitted)"
15816
+ }
15817
+ }
15818
+ },
15819
+ annotations: {
15820
+ readOnlyHint: true,
15821
+ destructiveHint: false
15822
+ }
15823
+ },
15824
+ {
15825
+ name: "paradigm_university_diplomas",
15826
+ description: "List earned diplomas (PLSAT, quiz completions, path completions). ~100 tokens.",
15827
+ inputSchema: {
15828
+ type: "object",
15829
+ properties: {
15830
+ student: {
15831
+ type: "string",
15832
+ description: "Filter by student name"
15833
+ },
15834
+ type: {
15835
+ type: "string",
15836
+ enum: ["plsat", "quiz", "path"],
15837
+ description: "Filter by diploma type"
15838
+ }
15839
+ }
15840
+ },
15841
+ annotations: {
15842
+ readOnlyHint: true,
15843
+ destructiveHint: false
15844
+ }
15845
+ },
15846
+ {
15847
+ name: "paradigm_university_validate",
15848
+ description: "Validate university content integrity: schema, symbol refs, prerequisites, quiz structure. ~200 tokens.",
15849
+ inputSchema: {
15850
+ type: "object",
15851
+ properties: {
15852
+ id: {
15853
+ type: "string",
15854
+ description: "Content ID to validate (validates all if omitted)"
15855
+ },
15856
+ deep: {
15857
+ type: "boolean",
15858
+ description: "Enable deep cross-reference checks against scan-index (default: false)"
15859
+ }
15860
+ }
15861
+ },
15862
+ annotations: {
15863
+ readOnlyHint: true,
15864
+ destructiveHint: false
15865
+ }
15866
+ }
15867
+ ];
15868
+ }
15869
+ async function handleUniversityTool(name, args, ctx) {
15870
+ if (name === "paradigm_university_search") {
15871
+ const results = searchContent(ctx.rootDir, {
15872
+ type: args.type,
15873
+ tag: args.tag,
15874
+ difficulty: args.difficulty,
15875
+ symbol: args.symbol,
15876
+ query: args.query,
15877
+ limit: args.limit
15878
+ });
15879
+ const text = JSON.stringify({
15880
+ count: results.length,
15881
+ results: results.map((r) => ({
15882
+ id: r.id,
15883
+ title: r.title,
15884
+ type: r.type,
15885
+ difficulty: r.difficulty,
15886
+ tags: r.tags,
15887
+ symbols: r.symbols
15888
+ }))
15889
+ }, null, 2);
15890
+ trackToolCall(text.length, name);
15891
+ return { handled: true, text };
15892
+ }
15893
+ if (name === "paradigm_university_get") {
15894
+ const id = args.id;
15895
+ if (!id) return { handled: true, text: JSON.stringify({ error: "id is required" }) };
15896
+ const note = loadNote(ctx.rootDir, id);
15897
+ if (note) {
15898
+ const text2 = JSON.stringify({
15899
+ id: note.frontmatter.id,
15900
+ title: note.frontmatter.title,
15901
+ type: note.frontmatter.type,
15902
+ author: note.frontmatter.author,
15903
+ created: note.frontmatter.created,
15904
+ updated: note.frontmatter.updated,
15905
+ tags: note.frontmatter.tags,
15906
+ symbols: note.frontmatter.symbols,
15907
+ difficulty: note.frontmatter.difficulty,
15908
+ prerequisites: note.frontmatter.prerequisites,
15909
+ body: note.body
15910
+ }, null, 2);
15911
+ trackToolCall(text2.length, name);
15912
+ return { handled: true, text: text2 };
15913
+ }
15914
+ const quiz = loadQuiz(ctx.rootDir, id);
15915
+ if (quiz) {
15916
+ const text2 = JSON.stringify(quiz, null, 2);
15917
+ trackToolCall(text2.length, name);
15918
+ return { handled: true, text: text2 };
15919
+ }
15920
+ const lp = loadPath(ctx.rootDir, id);
15921
+ if (lp) {
15922
+ const text2 = JSON.stringify(lp, null, 2);
15923
+ trackToolCall(text2.length, name);
15924
+ return { handled: true, text: text2 };
15925
+ }
15926
+ const text = JSON.stringify({ error: `Content "${id}" not found` });
15927
+ trackToolCall(text.length, name);
15928
+ return { handled: true, text };
15929
+ }
15930
+ if (name === "paradigm_university_create") {
15931
+ const contentType = args.type;
15932
+ const title = args.title;
15933
+ if (!contentType || !title) {
15934
+ return { handled: true, text: JSON.stringify({ error: "type and title are required" }) };
15935
+ }
15936
+ const author = resolveAuthor2();
15937
+ const today = todayStr();
15938
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
15939
+ if (contentType === "quiz") {
15940
+ const id2 = `Q-${slug}`;
15941
+ const quiz = {
15942
+ id: id2,
15943
+ title,
15944
+ description: args.body || "",
15945
+ author,
15946
+ created: today,
15947
+ updated: today,
15948
+ tags: args.tags || [],
15949
+ symbols: args.symbols || [],
15950
+ difficulty: args.difficulty || "beginner",
15951
+ estimatedMinutes: args.estimatedMinutes,
15952
+ passThreshold: args.passThreshold ?? 0.7,
15953
+ questions: args.questions || []
15954
+ };
15955
+ saveQuiz(ctx.rootDir, quiz);
15956
+ rebuildUniversityIndex(ctx.rootDir);
15957
+ const text2 = JSON.stringify({ created: id2, type: "quiz", file: `content/quizzes/${id2}.yaml` }, null, 2);
15958
+ trackToolCall(text2.length, name);
15959
+ return { handled: true, text: text2 };
15960
+ }
15961
+ if (contentType === "path") {
15962
+ const id2 = `LP-${slug}`;
15963
+ const lp = {
15964
+ id: id2,
15965
+ title,
15966
+ description: args.body || "",
15967
+ author,
15968
+ created: today,
15969
+ updated: today,
15970
+ tags: args.tags || [],
15971
+ ordered: args.ordered ?? true,
15972
+ steps: args.steps || []
15973
+ };
15974
+ savePath(ctx.rootDir, lp);
15975
+ rebuildUniversityIndex(ctx.rootDir);
15976
+ const text2 = JSON.stringify({ created: id2, type: "path", file: `content/paths/${id2}.yaml` }, null, 2);
15977
+ trackToolCall(text2.length, name);
15978
+ return { handled: true, text: text2 };
15979
+ }
15980
+ const prefix = contentType === "policy" ? "P" : "N";
15981
+ const id = `${prefix}-${slug}`;
15982
+ const frontmatter = {
15983
+ id,
15984
+ title,
15985
+ type: contentType,
15986
+ author,
15987
+ created: today,
15988
+ updated: today,
15989
+ tags: args.tags || [],
15990
+ symbols: args.symbols || [],
15991
+ difficulty: args.difficulty || "beginner",
15992
+ estimatedMinutes: args.estimatedMinutes,
15993
+ prerequisites: args.prerequisites || []
15994
+ };
15995
+ saveNote(ctx.rootDir, frontmatter, args.body || "");
15996
+ rebuildUniversityIndex(ctx.rootDir);
15997
+ const subdir = contentType === "policy" ? "policies" : "notes";
15998
+ const text = JSON.stringify({ created: id, type: contentType, file: `content/${subdir}/${id}.md` }, null, 2);
15999
+ trackToolCall(text.length, name);
16000
+ return { handled: true, text };
16001
+ }
16002
+ if (name === "paradigm_university_update") {
16003
+ const id = args.id;
16004
+ if (!id) return { handled: true, text: JSON.stringify({ error: "id is required" }) };
16005
+ const today = todayStr();
16006
+ const note = loadNote(ctx.rootDir, id);
16007
+ if (note) {
16008
+ const fm = { ...note.frontmatter };
16009
+ if (args.title) fm.title = args.title;
16010
+ if (args.tags) fm.tags = args.tags;
16011
+ if (args.symbols) fm.symbols = args.symbols;
16012
+ if (args.difficulty) fm.difficulty = args.difficulty;
16013
+ if (args.estimatedMinutes !== void 0) fm.estimatedMinutes = args.estimatedMinutes;
16014
+ fm.updated = today;
16015
+ const body = args.body ?? note.body;
16016
+ saveNote(ctx.rootDir, fm, body);
16017
+ rebuildUniversityIndex(ctx.rootDir);
16018
+ const text2 = JSON.stringify({ updated: id, type: fm.type }, null, 2);
16019
+ trackToolCall(text2.length, name);
16020
+ return { handled: true, text: text2 };
16021
+ }
16022
+ const quiz = loadQuiz(ctx.rootDir, id);
16023
+ if (quiz) {
16024
+ if (args.title) quiz.title = args.title;
16025
+ if (args.tags) quiz.tags = args.tags;
16026
+ if (args.symbols) quiz.symbols = args.symbols;
16027
+ if (args.difficulty) quiz.difficulty = args.difficulty;
16028
+ quiz.updated = today;
16029
+ saveQuiz(ctx.rootDir, quiz);
16030
+ rebuildUniversityIndex(ctx.rootDir);
16031
+ const text2 = JSON.stringify({ updated: id, type: "quiz" }, null, 2);
16032
+ trackToolCall(text2.length, name);
16033
+ return { handled: true, text: text2 };
16034
+ }
16035
+ const lp = loadPath(ctx.rootDir, id);
16036
+ if (lp) {
16037
+ if (args.title) lp.title = args.title;
16038
+ if (args.tags) lp.tags = args.tags;
16039
+ lp.updated = today;
16040
+ savePath(ctx.rootDir, lp);
16041
+ rebuildUniversityIndex(ctx.rootDir);
16042
+ const text2 = JSON.stringify({ updated: id, type: "path" }, null, 2);
16043
+ trackToolCall(text2.length, name);
16044
+ return { handled: true, text: text2 };
16045
+ }
16046
+ const text = JSON.stringify({ error: `Content "${id}" not found` });
16047
+ trackToolCall(text.length, name);
16048
+ return { handled: true, text };
16049
+ }
16050
+ if (name === "paradigm_university_quiz") {
16051
+ const id = args.id;
16052
+ const quiz = loadQuiz(ctx.rootDir, id);
16053
+ if (!quiz) {
16054
+ const text2 = JSON.stringify({ error: `Quiz "${id}" not found` });
16055
+ trackToolCall(text2.length, name);
16056
+ return { handled: true, text: text2 };
16057
+ }
16058
+ const sanitized = {
16059
+ id: quiz.id,
16060
+ title: quiz.title,
16061
+ description: quiz.description,
16062
+ difficulty: quiz.difficulty,
16063
+ passThreshold: quiz.passThreshold,
16064
+ questionCount: quiz.questions.length,
16065
+ questions: quiz.questions.map((q) => ({
16066
+ id: q.id,
16067
+ question: q.question,
16068
+ choices: q.choices
16069
+ }))
16070
+ };
16071
+ const text = JSON.stringify(sanitized, null, 2);
16072
+ trackToolCall(text.length, name);
16073
+ return { handled: true, text };
16074
+ }
16075
+ if (name === "paradigm_university_submit") {
16076
+ const quizId = args.quizId;
16077
+ const answers = args.answers;
16078
+ const student = args.student || resolveAuthor2();
16079
+ const quiz = loadQuiz(ctx.rootDir, quizId);
16080
+ if (!quiz) {
16081
+ const text2 = JSON.stringify({ error: `Quiz "${quizId}" not found` });
16082
+ trackToolCall(text2.length, name);
16083
+ return { handled: true, text: text2 };
16084
+ }
16085
+ let correct = 0;
16086
+ const total = quiz.questions.length;
16087
+ const feedback = [];
16088
+ for (const q of quiz.questions) {
16089
+ const answer = answers[q.id];
16090
+ const isCorrect = answer === q.correct;
16091
+ if (isCorrect) correct++;
16092
+ feedback.push({
16093
+ id: q.id,
16094
+ correct: isCorrect,
16095
+ ...isCorrect ? {} : { expected: q.correct },
16096
+ ...q.explanation ? { explanation: q.explanation } : {}
16097
+ });
16098
+ }
16099
+ const percentage = total > 0 ? Math.round(correct / total * 1e4) / 100 : 0;
16100
+ const passed = percentage / 100 >= quiz.passThreshold;
16101
+ const diplomaId = `D-${todayStr()}-${student}-${quizId.replace(/^Q-/, "")}`;
16102
+ const diploma = {
16103
+ id: diplomaId,
16104
+ type: "quiz",
16105
+ student,
16106
+ earnedAt: (/* @__PURE__ */ new Date()).toISOString(),
16107
+ source: quizId,
16108
+ score: correct,
16109
+ total,
16110
+ percentage,
16111
+ passed
16112
+ };
16113
+ saveDiploma(ctx.rootDir, diploma);
16114
+ const text = JSON.stringify({
16115
+ quizId,
16116
+ student,
16117
+ score: correct,
16118
+ total,
16119
+ percentage,
16120
+ passThreshold: quiz.passThreshold * 100,
16121
+ passed,
16122
+ diplomaId,
16123
+ feedback
16124
+ }, null, 2);
16125
+ trackToolCall(text.length, name);
16126
+ return { handled: true, text };
16127
+ }
16128
+ if (name === "paradigm_university_onboard") {
16129
+ const student = args.student || resolveAuthor2();
16130
+ const config = loadUniversityConfig(ctx.rootDir);
16131
+ const sequence = getOnboardingSequence(ctx.rootDir, student);
16132
+ const text = JSON.stringify({
16133
+ university: config.branding.name,
16134
+ student,
16135
+ ...sequence
16136
+ }, null, 2);
16137
+ trackToolCall(text.length, name);
16138
+ return { handled: true, text };
16139
+ }
16140
+ if (name === "paradigm_university_diplomas") {
16141
+ const diplomas = loadDiplomas(ctx.rootDir, {
16142
+ student: args.student,
16143
+ type: args.type
16144
+ });
16145
+ const text = JSON.stringify({
16146
+ count: diplomas.length,
16147
+ diplomas: diplomas.map((d) => ({
16148
+ id: d.id,
16149
+ type: d.type,
16150
+ student: d.student,
16151
+ source: d.source,
16152
+ score: d.score,
16153
+ total: d.total,
16154
+ percentage: d.percentage,
16155
+ passed: d.passed,
16156
+ earnedAt: d.earnedAt
16157
+ }))
16158
+ }, null, 2);
16159
+ trackToolCall(text.length, name);
16160
+ return { handled: true, text };
16161
+ }
16162
+ if (name === "paradigm_university_validate") {
16163
+ const result = validateUniversityContent(ctx.rootDir, {
16164
+ id: args.id,
16165
+ deep: args.deep
16166
+ });
16167
+ const text = JSON.stringify(result, null, 2);
16168
+ trackToolCall(text.length, name);
16169
+ return { handled: true, text };
16170
+ }
16171
+ return { handled: false, text: "" };
16172
+ }
16173
+
16174
+ // ../paradigm-mcp/src/utils/platform-bridge.ts
16175
+ import * as fs27 from "fs";
16176
+ import * as path29 from "path";
16177
+ import * as yaml15 from "js-yaml";
16178
+ function resolvePlatformPort(projectDir2) {
16179
+ try {
16180
+ const configPath = path29.join(projectDir2, ".paradigm", "config.yaml");
16181
+ if (fs27.existsSync(configPath)) {
16182
+ const config = yaml15.load(fs27.readFileSync(configPath, "utf-8"));
16183
+ const platform2 = config.platform;
16184
+ if (platform2?.port && typeof platform2.port === "number") {
16185
+ return platform2.port;
16186
+ }
16187
+ }
16188
+ } catch {
16189
+ }
16190
+ return 3850;
16191
+ }
16192
+ function resolveAgentId(projectDir2) {
16193
+ try {
16194
+ const configPath = path29.join(projectDir2, ".paradigm", "config.yaml");
16195
+ if (fs27.existsSync(configPath)) {
16196
+ const config = yaml15.load(fs27.readFileSync(configPath, "utf-8"));
16197
+ const project = config.project || path29.basename(projectDir2);
16198
+ const role = config.role || "core";
16199
+ return `${project}/${role}`;
16200
+ }
16201
+ } catch {
16202
+ }
16203
+ return `${path29.basename(projectDir2)}/core`;
16204
+ }
16205
+ async function sendAgentCommand(projectDir2, command, payload) {
16206
+ const port = resolvePlatformPort(projectDir2);
16207
+ const agentId = resolveAgentId(projectDir2);
16208
+ const url = `http://localhost:${port}/api/platform/agent-command`;
16209
+ try {
16210
+ const response = await fetch(url, {
16211
+ method: "POST",
16212
+ headers: { "Content-Type": "application/json" },
16213
+ body: JSON.stringify({ command, agentId, payload }),
16214
+ signal: AbortSignal.timeout(5e3)
16215
+ });
16216
+ if (!response.ok) {
16217
+ const text = await response.text();
16218
+ return { ok: false, error: `HTTP ${response.status}: ${text}` };
16219
+ }
16220
+ const data = await response.json();
16221
+ return { ok: true, data };
16222
+ } catch (err2) {
16223
+ const msg = err2 instanceof Error ? err2.message : String(err2);
16224
+ return { ok: false, error: `Platform server unreachable (${msg}). Is \`paradigm serve\` running?` };
16225
+ }
16226
+ }
16227
+
16228
+ // ../paradigm-mcp/src/tools/platform.ts
16229
+ function getPlatformToolsList() {
16230
+ return [
16231
+ {
16232
+ name: "paradigm_platform_navigate",
16233
+ 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.",
16234
+ inputSchema: {
16235
+ type: "object",
16236
+ properties: {
16237
+ section: {
16238
+ type: "string",
16239
+ enum: ["overview", "lore", "graph", "sentinel", "university", "symphony"],
16240
+ description: "Section to navigate to"
16241
+ },
16242
+ symbol: {
16243
+ type: "string",
16244
+ description: 'Symbol to select (e.g., "#payment-service")'
16245
+ },
16246
+ loreId: {
16247
+ type: "string",
16248
+ description: "Lore entry ID to open in lore section"
16249
+ }
16250
+ }
16251
+ },
16252
+ annotations: {
16253
+ readOnlyHint: false,
16254
+ destructiveHint: false
16255
+ }
16256
+ },
16257
+ {
16258
+ name: "paradigm_platform_highlight",
16259
+ 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.",
16260
+ inputSchema: {
16261
+ type: "object",
16262
+ properties: {
16263
+ symbols: {
16264
+ type: "array",
16265
+ items: { type: "string" },
16266
+ description: 'Symbol IDs to highlight (e.g., ["#payment-service", "#api-gateway"])'
16267
+ },
16268
+ color: {
16269
+ type: "string",
16270
+ description: "Highlight color (CSS color, defaults to agent color)"
16271
+ },
16272
+ duration: {
16273
+ type: "number",
16274
+ description: "Duration in milliseconds (default: 5000)"
16275
+ },
16276
+ pulse: {
16277
+ type: "boolean",
16278
+ description: "Whether to pulse the highlight (default: true)"
16279
+ },
16280
+ label: {
16281
+ type: "string",
16282
+ description: "Optional label shown near highlighted symbols"
16283
+ }
16284
+ },
16285
+ required: ["symbols"]
16286
+ },
16287
+ annotations: {
16288
+ readOnlyHint: false,
16289
+ destructiveHint: false
16290
+ }
16291
+ },
16292
+ {
16293
+ name: "paradigm_platform_annotate",
16294
+ 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.",
16295
+ inputSchema: {
16296
+ type: "object",
16297
+ properties: {
16298
+ type: {
16299
+ type: "string",
16300
+ enum: ["toast", "callout", "badge"],
16301
+ description: "Annotation type: toast (notification), callout (floating note on graph node), badge (icon on symbol)"
16302
+ },
16303
+ message: {
16304
+ type: "string",
16305
+ description: "Annotation message text"
16306
+ },
16307
+ symbol: {
16308
+ type: "string",
16309
+ description: "Symbol to attach callout/badge to (required for callout/badge)"
16310
+ },
16311
+ severity: {
16312
+ type: "string",
16313
+ enum: ["info", "warning", "error", "success"],
16314
+ description: "Visual severity (default: info)"
16315
+ },
16316
+ duration: {
16317
+ type: "number",
16318
+ description: "Auto-dismiss duration in milliseconds (default: 6000, 0 = persistent)"
16319
+ }
16320
+ },
16321
+ required: ["type", "message"]
16322
+ },
16323
+ annotations: {
16324
+ readOnlyHint: false,
16325
+ destructiveHint: false
16326
+ }
16327
+ },
16328
+ {
16329
+ name: "paradigm_platform_observe",
16330
+ 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.",
16331
+ inputSchema: {
16332
+ type: "object",
16333
+ properties: {
16334
+ detail: {
16335
+ type: "string",
16336
+ enum: ["summary", "full"],
16337
+ description: "Level of detail (default: summary)"
16338
+ }
16339
+ }
16340
+ },
16341
+ annotations: {
16342
+ readOnlyHint: true,
16343
+ destructiveHint: false
16344
+ }
16345
+ },
16346
+ {
16347
+ name: "paradigm_platform_clear",
16348
+ description: "Remove agent highlights, annotations, or all agent effects from the Platform UI. ~50 tokens.",
16349
+ inputSchema: {
16350
+ type: "object",
16351
+ properties: {
16352
+ target: {
16353
+ type: "string",
16354
+ enum: ["highlights", "annotations", "all"],
16355
+ description: "What to clear (default: all)"
16356
+ }
16357
+ }
16358
+ },
16359
+ annotations: {
16360
+ readOnlyHint: false,
16361
+ destructiveHint: false
16362
+ }
16363
+ }
16364
+ ];
16365
+ }
16366
+ async function handlePlatformTool(name, args, ctx) {
16367
+ switch (name) {
16368
+ case "paradigm_platform_navigate": {
16369
+ const result = await sendAgentCommand(ctx.projectDir, "navigate", {
16370
+ section: args.section,
16371
+ symbol: args.symbol,
16372
+ loreId: args.loreId
16373
+ });
16374
+ if (!result.ok) {
16375
+ return { handled: true, text: `**Navigate failed:** ${result.error}` };
16376
+ }
16377
+ const d = result.data;
16378
+ if (d.navigated) {
16379
+ const parts = [];
16380
+ if (d.section) parts.push(`section: **${d.section}**`);
16381
+ if (d.symbol) parts.push(`symbol: **${d.symbol}**`);
16382
+ const activeNote = d.userActive ? " (user was active \u2014 shown as prompt)" : "";
16383
+ return { handled: true, text: `Navigated to ${parts.join(", ")}${activeNote}` };
16384
+ }
16385
+ return { handled: true, text: `Navigation skipped: ${d.reason}` };
16386
+ }
16387
+ case "paradigm_platform_highlight": {
16388
+ const result = await sendAgentCommand(ctx.projectDir, "highlight", {
16389
+ symbols: args.symbols,
16390
+ color: args.color,
16391
+ duration: args.duration,
16392
+ pulse: args.pulse,
16393
+ label: args.label
16394
+ });
16395
+ if (!result.ok) {
16396
+ return { handled: true, text: `**Highlight failed:** ${result.error}` };
16397
+ }
16398
+ const d = result.data;
16399
+ if (d.highlighted) {
16400
+ return { handled: true, text: `Highlighted **${d.count}** symbol(s)${args.label ? ` with label "${args.label}"` : ""}` };
16401
+ }
16402
+ return { handled: true, text: `Highlight skipped: ${d.reason}` };
16403
+ }
16404
+ case "paradigm_platform_annotate": {
16405
+ const result = await sendAgentCommand(ctx.projectDir, "annotate", {
16406
+ type: args.type,
16407
+ message: args.message,
16408
+ symbol: args.symbol,
16409
+ severity: args.severity,
16410
+ duration: args.duration
16411
+ });
16412
+ if (!result.ok) {
16413
+ return { handled: true, text: `**Annotate failed:** ${result.error}` };
16414
+ }
16415
+ const d = result.data;
16416
+ if (d.annotated) {
16417
+ return { handled: true, text: `Created ${args.type} annotation: "${args.message}"` };
16418
+ }
16419
+ return { handled: true, text: `Annotation skipped: ${d.reason}` };
16420
+ }
16421
+ case "paradigm_platform_observe": {
16422
+ const result = await sendAgentCommand(ctx.projectDir, "observe", {
16423
+ detail: args.detail
16424
+ });
16425
+ if (!result.ok) {
16426
+ return { handled: true, text: `**Observe failed:** ${result.error}` };
16427
+ }
16428
+ const d = result.data;
16429
+ const state = d.state;
16430
+ const lines = ["## Platform UI State\n"];
16431
+ lines.push(`- **Connected:** ${d.connected ? "Yes" : "No"} (${d.users} browser client(s))`);
16432
+ lines.push(`- **Section:** ${state.section}`);
16433
+ lines.push(`- **Selected symbol:** ${state.selectedSymbol || "none"}`);
16434
+ lines.push(`- **Theme:** ${state.theme}`);
16435
+ lines.push(`- **Muted:** ${state.muted ? "Yes \u2014 agent actions silently discarded" : "No"}`);
16436
+ const agents = d.agents;
16437
+ if (agents?.length) {
16438
+ lines.push(`
16439
+ ### Connected Agents (${agents.length})`);
16440
+ for (const a of agents) {
16441
+ lines.push(`- \`${a.agentId}\` (since ${a.connectedAt})`);
16442
+ }
16443
+ }
16444
+ if (args.detail === "full") {
16445
+ const highlights = d.highlights;
16446
+ const annotations = d.annotations;
16447
+ if (highlights?.length) {
16448
+ lines.push(`
16449
+ ### Active Highlights: ${highlights.length}`);
16450
+ }
16451
+ if (annotations?.length) {
16452
+ lines.push(`
16453
+ ### Active Annotations: ${annotations.length}`);
16454
+ }
16455
+ }
16456
+ return { handled: true, text: lines.join("\n") };
16457
+ }
16458
+ case "paradigm_platform_clear": {
16459
+ const result = await sendAgentCommand(ctx.projectDir, "clear", {
16460
+ target: args.target
16461
+ });
16462
+ if (!result.ok) {
16463
+ return { handled: true, text: `**Clear failed:** ${result.error}` };
16464
+ }
16465
+ const d = result.data;
16466
+ return { handled: true, text: `Cleared ${d.target} agent effects` };
16467
+ }
16468
+ default:
16469
+ return { handled: false, text: "" };
16470
+ }
16471
+ }
16472
+
16473
+ // ../paradigm-mcp/src/tools/fallback-grep.ts
16474
+ import * as path30 from "path";
16475
+ import { execSync as execSync8 } from "child_process";
16476
+ function grepForReferences(rootDir, symbol, options = {}) {
16477
+ const { maxResults = 20 } = options;
16478
+ const results = [];
16479
+ const escapedSymbol = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16480
+ const grepCommands = [
16481
+ // ripgrep - exclude common directories
16482
+ `rg -n --no-heading "${escapedSymbol}" "${rootDir}" --glob "!node_modules" --glob "!.git" --glob "!dist" --glob "!build" --glob "!coverage" --max-count 50 2>/dev/null`,
16483
+ // fallback to grep
16484
+ `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`
16485
+ ];
16486
+ let output = "";
16487
+ for (const cmd of grepCommands) {
16488
+ try {
16489
+ output = execSync8(cmd, { encoding: "utf8", maxBuffer: 1024 * 1024 });
16490
+ if (output.trim()) break;
16491
+ } catch {
16492
+ continue;
16493
+ }
16494
+ }
16495
+ if (!output.trim()) {
16496
+ return results;
16497
+ }
16498
+ const lines = output.trim().split("\n");
16499
+ for (const line of lines.slice(0, maxResults)) {
16500
+ const match = line.match(/^(.+?):(\d+):(.*)$/);
16501
+ if (match) {
16502
+ const [, filePath, lineNum, content] = match;
16503
+ const relativePath = path30.relative(rootDir, filePath);
16504
+ let context2 = "unknown";
16505
+ if (relativePath.includes(".purpose") || relativePath.includes("portal.yaml")) {
16506
+ context2 = "purpose";
16507
+ } else if (content.includes("//") || content.includes("#") || content.includes("*")) {
16508
+ context2 = "comment";
16509
+ } else {
16510
+ context2 = "code";
16511
+ }
16512
+ results.push({
16513
+ filePath: relativePath,
16514
+ line: parseInt(lineNum, 10),
16515
+ content: content.trim().slice(0, 200),
16516
+ context: context2
16517
+ });
16518
+ }
16519
+ }
16520
+ return results;
16521
+ }
16522
+
16523
+ // ../paradigm-mcp/src/tools/fuzzy-match.ts
16524
+ function levenshteinDistance(a, b) {
16525
+ const matrix = [];
16526
+ for (let i = 0; i <= b.length; i++) {
16527
+ matrix[i] = [i];
16528
+ }
16529
+ for (let j = 0; j <= a.length; j++) {
16530
+ matrix[0][j] = j;
16531
+ }
16532
+ for (let i = 1; i <= b.length; i++) {
16533
+ for (let j = 1; j <= a.length; j++) {
16534
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
16535
+ matrix[i][j] = matrix[i - 1][j - 1];
16536
+ } else {
16537
+ matrix[i][j] = Math.min(
16538
+ matrix[i - 1][j - 1] + 1,
16539
+ // substitution
16540
+ matrix[i][j - 1] + 1,
16541
+ // insertion
16542
+ matrix[i - 1][j] + 1
16543
+ // deletion
16544
+ );
16545
+ }
16546
+ }
16547
+ }
16548
+ return matrix[b.length][a.length];
16549
+ }
16550
+ function findFuzzyMatches(query, candidates, options = {}) {
16551
+ const { maxDistance = 3, maxResults = 5 } = options;
16552
+ const queryLower = query.toLowerCase();
16553
+ const matches = [];
16554
+ for (const candidate of candidates) {
16555
+ const candidateLower = candidate.toLowerCase();
16556
+ if (candidateLower === queryLower) {
16557
+ matches.push({ match: candidate, distance: 0 });
16558
+ continue;
16559
+ }
16560
+ if (candidateLower.includes(queryLower) || queryLower.includes(candidateLower)) {
16561
+ matches.push({ match: candidate, distance: 1 });
16562
+ continue;
16563
+ }
16564
+ const distance = levenshteinDistance(queryLower, candidateLower);
16565
+ if (distance <= maxDistance) {
16566
+ matches.push({ match: candidate, distance });
16567
+ }
16568
+ }
16569
+ matches.sort((a, b) => {
16570
+ if (a.distance !== b.distance) return a.distance - b.distance;
16571
+ return a.match.localeCompare(b.match);
16572
+ });
16573
+ return matches.slice(0, maxResults);
16574
+ }
16575
+
14150
16576
  // ../paradigm-mcp/src/tools/index.ts
14151
16577
  function calculateRouteSimilarity(route1, route2) {
14152
16578
  const normalize = (r) => r.toLowerCase().replace(/\/+$/, "");
@@ -14349,6 +16775,12 @@ function registerTools(server, getContext2, reloadContext2) {
14349
16775
  ...getPipelineToolsList(),
14350
16776
  // Conductor session registration tools
14351
16777
  ...getConductorToolsList(),
16778
+ // Symphony (The Score) tools
16779
+ ...getSymphonyToolsList(),
16780
+ // University (per-project knowledge base) tools
16781
+ ...getUniversityToolsList(),
16782
+ // Platform agent-driven UI tools
16783
+ ...getPlatformToolsList(),
14352
16784
  // Plugin update check
14353
16785
  {
14354
16786
  name: "paradigm_plugin_check",
@@ -14624,6 +17056,18 @@ function registerTools(server, getContext2, reloadContext2) {
14624
17056
  }
14625
17057
  } catch {
14626
17058
  }
17059
+ try {
17060
+ const universityAffected = getAffectedUniversityContent(ctx.rootDir, symbol);
17061
+ if (universityAffected.length > 0) {
17062
+ response.university_content_affected = universityAffected.map((c) => ({
17063
+ id: c.id,
17064
+ title: c.title,
17065
+ type: c.type,
17066
+ stale: c.stale
17067
+ }));
17068
+ }
17069
+ } catch {
17070
+ }
14627
17071
  if (includeWorkspace && ctx.workspace) {
14628
17072
  const wsRipple = rippleWorkspace(ctx.workspace, symbol);
14629
17073
  if (wsRipple.length > 0) {
@@ -14744,7 +17188,7 @@ function registerTools(server, getContext2, reloadContext2) {
14744
17188
  const symbols = getSymbolsByType(ctx.index, type);
14745
17189
  examples[type] = symbols.slice(0, 3).map((s) => s.symbol);
14746
17190
  }
14747
- const platform2 = os4.platform();
17191
+ const platform2 = os6.platform();
14748
17192
  const isWindows = platform2 === "win32";
14749
17193
  const shell = isWindows ? "PowerShell/CMD" : platform2 === "darwin" ? "zsh/bash" : "bash";
14750
17194
  let protocols;
@@ -14763,6 +17207,13 @@ function registerTools(server, getContext2, reloadContext2) {
14763
17207
  }
14764
17208
  }
14765
17209
  const untypedCount = allComponents.filter((c) => !c.componentType).length;
17210
+ let purposeHealthScore;
17211
+ try {
17212
+ const { checkPurposeHealth } = await import("./integrity-checker-J7YXRTBT.js");
17213
+ const healthReport = checkPurposeHealth(ctx.aggregation.purposeFiles, ctx.rootDir);
17214
+ purposeHealthScore = healthReport.healthScore;
17215
+ } catch {
17216
+ }
14766
17217
  return JSON.stringify({
14767
17218
  project: ctx.projectName,
14768
17219
  symbolSystem: "v2",
@@ -14783,6 +17234,7 @@ function registerTools(server, getContext2, reloadContext2) {
14783
17234
  examples,
14784
17235
  hasPortalYaml: ctx.gateConfig !== null,
14785
17236
  purposeFiles: ctx.aggregation.purposeFiles.length,
17237
+ ...purposeHealthScore !== void 0 ? { purposeHealthScore } : {},
14786
17238
  ...protocols ? { protocols } : {},
14787
17239
  note: "Symbol System v2: Use tags [feature], [state], [integration], [idea] for classification. Use type field for structural role (view, service, tool, etc.)",
14788
17240
  environment: {
@@ -14998,10 +17450,10 @@ Update command:
14998
17450
  trackToolCall(noWsText.length, name);
14999
17451
  return { content: [{ type: "text", text: noWsText }] };
15000
17452
  }
15001
- const { rebuildStaticFiles: rebuildStaticFiles2 } = await import("./reindex-YC7LD4MN.js");
17453
+ const { rebuildStaticFiles: rebuildStaticFiles2 } = await import("./reindex-WIJMCJ4A.js");
15002
17454
  const memberResults = [];
15003
17455
  for (const member of ctx.workspace.config.members) {
15004
- const memberAbsPath = path29.resolve(path29.dirname(ctx.workspace.workspacePath), member.path);
17456
+ const memberAbsPath = path31.resolve(path31.dirname(ctx.workspace.workspacePath), member.path);
15005
17457
  try {
15006
17458
  const result = await rebuildStaticFiles2(memberAbsPath);
15007
17459
  memberResults.push({
@@ -15225,6 +17677,33 @@ Update command:
15225
17677
  };
15226
17678
  }
15227
17679
  }
17680
+ if (name.startsWith("paradigm_symphony_")) {
17681
+ const result = await handleSymphonyTool(name, args, ctx);
17682
+ if (result.handled) {
17683
+ trackToolCall(result.text.length, name);
17684
+ return {
17685
+ content: [{ type: "text", text: result.text }]
17686
+ };
17687
+ }
17688
+ }
17689
+ if (name.startsWith("paradigm_university_")) {
17690
+ const result = await handleUniversityTool(name, args, ctx);
17691
+ if (result.handled) {
17692
+ trackToolCall(result.text.length, name);
17693
+ return {
17694
+ content: [{ type: "text", text: result.text }]
17695
+ };
17696
+ }
17697
+ }
17698
+ if (name.startsWith("paradigm_platform_")) {
17699
+ const result = await handlePlatformTool(name, args, ctx);
17700
+ if (result.handled) {
17701
+ trackToolCall(result.text.length, name);
17702
+ return {
17703
+ content: [{ type: "text", text: result.text }]
17704
+ };
17705
+ }
17706
+ }
15228
17707
  if (name === "paradigm_reindex") {
15229
17708
  const reload = reloadContext2 || (async () => {
15230
17709
  });