@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.
- package/dist/{accept-orchestration-XXANWJVZ.js → accept-orchestration-ZUWQUHSK.js} +6 -6
- package/dist/add-VSPZ6FM4.js +81 -0
- package/dist/{aggregate-XHQ6GI3Z.js → aggregate-SV3VGEIL.js} +2 -2
- package/dist/assess-UHBDYIK7.js +68 -0
- package/dist/{beacon-BTLQMYQL.js → beacon-3SJV4DAP.js} +2 -2
- package/dist/calibration-WWHK73WU.js +135 -0
- package/dist/{chunk-C5ZE6WEX.js → chunk-2SKXFXIT.js} +91 -1
- package/dist/{chunk-S5TDFT5Q.js → chunk-7COU5S2Z.js} +2 -2
- package/dist/{chunk-H4TVBJD4.js → chunk-AKIMFN6I.js} +3 -3
- package/dist/{chunk-3DYYXGDC.js → chunk-CDMAMDSG.js} +33 -0
- package/dist/chunk-F3BCHPYT.js +143 -0
- package/dist/{chunk-R2SGQ22F.js → chunk-FKJUBQU3.js} +461 -2
- package/dist/chunk-GT5QGC2H.js +253 -0
- package/dist/{chunk-UQNTJ5VB.js → chunk-HIKKOCXY.js} +1 -1
- package/dist/{chunk-J26YQVAK.js → chunk-J4E6K5MG.js} +1 -1
- package/dist/chunk-L27I3CPZ.js +357 -0
- package/dist/{chunk-WOONGZ3C.js → chunk-P7XSBJE3.js} +1 -1
- package/dist/{chunk-Z7W7HNRG.js → chunk-QDXI2DHR.js} +1 -1
- package/dist/{chunk-BRILIG7Z.js → chunk-QIOCFXDQ.js} +42 -0
- package/dist/{chunk-3BGSDKWD.js → chunk-QWA26UNO.js} +7 -7
- package/dist/{lore-server-ILPHKWLK.js → chunk-RAB5IKPR.js} +77 -112
- package/dist/chunk-SOBTKFSP.js +616 -0
- package/dist/{chunk-BKMNLROM.js → chunk-ZDHLG5VP.js} +461 -147
- package/dist/{chunk-CTF6RHKG.js → chunk-ZGUAAVMA.js} +17 -2
- package/dist/{chunk-PFLWLC6J.js → chunk-ZMQA6SCO.js} +855 -34
- package/dist/{chunk-3BAMPB6I.js → chunk-ZSYVKSY6.js} +2 -147
- package/dist/{commands-KPT2T2OZ.js → commands-5N4ILTPH.js} +465 -1
- package/dist/config-schema-3YNIFJCJ.js +152 -0
- package/dist/{constellation-LZ6XIKDT.js → constellation-FAGT45TU.js} +2 -2
- package/dist/{context-audit-RI4R2WRH.js → context-audit-557EO6PK.js} +138 -8
- package/dist/{cost-4SZM7OUS.js → cost-UD3WPEKZ.js} +1 -1
- package/dist/{delete-YTASL4SM.js → delete-RRK4RL6Y.js} +1 -1
- package/dist/{diff-T6YJSAAC.js → diff-IP5CIARP.js} +6 -6
- package/dist/{dist-AG5JNIZU-HW2FWNTZ.js → dist-5QE2BB2B-X6DYVSUL.js} +59 -5
- package/dist/{dist-IKBGY7FQ.js → dist-CM3MVWWW.js} +3 -1
- package/dist/{dist-OH4DBV2O.js → dist-OGTSAZ55.js} +16 -1
- package/dist/{dist-RMAIFRTW.js → dist-POMVY6WP.js} +5 -3
- package/dist/{dist-QSBAGCZT.js → dist-UXWV4OKX.js} +2 -2
- package/dist/{doctor-INBOLZC7.js → doctor-GKZJU7QG.js} +1 -1
- package/dist/{edit-S7NZD7H7.js → edit-4CLNN5JG.js} +1 -1
- package/dist/{graph-ERNQQQ7C.js → graph-YYUXI3F7.js} +1 -1
- package/dist/graph-server-ZPXRSGCW.js +116 -0
- package/dist/{habits-7BORPC2F.js → habits-RG5SVKXP.js} +2 -2
- package/dist/index.js +200 -86
- package/dist/integrity-MK2OP5TA.js +194 -0
- package/dist/integrity-checker-J7YXRTBT.js +11 -0
- package/dist/{lint-MTRZB5EC.js → lint-HYWGS3JJ.js} +1 -1
- package/dist/{list-QTFWN35D.js → list-BTLFHSRC.js} +1 -1
- package/dist/list-IUCYPGMK.js +57 -0
- package/dist/{lore-loader-S5BXMH27.js → lore-loader-VTEEZDX3.js} +3 -1
- package/dist/lore-server-NOOAHKJX.js +118 -0
- package/dist/mcp.js +2591 -112
- package/dist/{migrate-HRN5TUBQ.js → migrate-FQVGQNXZ.js} +21 -3
- package/dist/{migrate-assessments-FPR6C35Z.js → migrate-assessments-JP6Q5KME.js} +1 -1
- package/dist/{orchestrate-3SI6ON33.js → orchestrate-A226N6FC.js} +6 -6
- package/dist/platform-server-KHL6ZPPN.js +900 -0
- package/dist/{probe-ABMGCXQG.js → probe-7JK7IDNI.js} +4 -4
- package/dist/{providers-YW3FG6DA.js → providers-YNFSL6HK.js} +1 -1
- package/dist/quiz-I75NU2QQ.js +99 -0
- package/dist/{record-UGN75GTB.js → record-46CLR4OG.js} +11 -2
- package/dist/{reindex-YC7LD4MN.js → reindex-WIJMCJ4A.js} +3 -2
- package/dist/{remember-WR6ZVXLT.js → remember-4EUZKIIB.js} +1 -1
- package/dist/{retag-URLJLMSK.js → retag-KC4JVRLE.js} +1 -1
- package/dist/{review-725ZKA7U.js → review-Q7M4CRB5.js} +1 -1
- package/dist/{ripple-QTXKJCEI.js → ripple-RI3LOT6R.js} +2 -2
- package/dist/{sentinel-FUR3QKCJ.js → sentinel-UOIGJWHH.js} +1 -1
- package/dist/sentinel-bridge-APDXYAZS.js +109 -0
- package/dist/sentinel-mcp.js +13 -0
- package/dist/sentinel-ui/assets/{index-Zh1YM0C9.css → index-CJ1Wx083.css} +1 -1
- package/dist/sentinel-ui/assets/index-S1VJ67dT.js +62 -0
- package/dist/sentinel-ui/assets/index-S1VJ67dT.js.map +1 -0
- package/dist/sentinel-ui/index.html +2 -2
- package/dist/sentinel.js +6 -6
- package/dist/{serve-DIALBCTU.js → serve-22A4XOIG.js} +1 -1
- package/dist/{university-A66BMZ4Z.js → serve-2YJ6D2Y6.js} +9 -8
- package/dist/serve-JVXSRSUB.js +33 -0
- package/dist/{server-2VICPDUR.js → server-JV6UFGWZ.js} +25 -2
- package/dist/{server-OWBK2WFS.js → server-RDLQ3DK7.js} +49 -4
- package/dist/{setup-ASR6OMKV.js → setup-M2ZKLKNN.js} +2 -2
- package/dist/{shift-7XLSBLDW.js → shift-LNMKFYLR.js} +63 -14
- package/dist/{show-GEVVQWWG.js → show-P7GYO43X.js} +1 -1
- package/dist/show-PKZMYKRN.js +82 -0
- package/dist/{snapshot-QZFD7YBI.js → snapshot-Y3COXK4T.js} +2 -2
- package/dist/{spawn-DIY7T4QW.js → spawn-SSXZX45U.js} +2 -2
- package/dist/status-KLHALGW4.js +71 -0
- package/dist/{summary-R4CSYNNP.js → summary-5NQNOD3F.js} +2 -2
- package/dist/{sweep-5POCF2E4.js → sweep-EZU3GU6S.js} +1 -1
- package/dist/symphony-EYRGGVNE.js +470 -0
- package/dist/symphony-QWOEKZMC.js +308 -0
- package/dist/{team-VH3HYABB.js → team-HGLJXWQG.js} +7 -7
- package/dist/{timeline-RKXNRMKF.js → timeline-ANC7LVDL.js} +1 -1
- package/dist/{triage-GJ6GK647.js → triage-IZ4MDYNB.js} +2 -2
- package/dist/university-content/courses/.purpose +7 -1
- package/dist/university-content/courses/para-501.json +166 -0
- package/dist/university-content/plsat/.purpose +6 -0
- package/dist/university-content/plsat/v3.0.json +323 -1
- package/dist/university-content/reference.json +48 -0
- package/dist/university-ui/assets/{index-TcsCEBMo.js → index-tfi5xN4Q.js} +2 -2
- package/dist/university-ui/assets/{index-TcsCEBMo.js.map → index-tfi5xN4Q.js.map} +1 -1
- package/dist/university-ui/index.html +1 -1
- package/dist/validate-GD5XWILV.js +134 -0
- package/dist/{validate-OUHUBZPO.js → validate-ZVPNN4FL.js} +1 -1
- package/dist/{workspace-5RBSALXC.js → workspace-UIUTHZTD.js} +5 -5
- package/package.json +4 -2
- package/platform-ui/dist/assets/GitSection-BD3Ze06e.js +4 -0
- package/platform-ui/dist/assets/GitSection-C-GQWHcu.css +1 -0
- package/platform-ui/dist/assets/GraphSection-BlgXTl53.css +1 -0
- package/platform-ui/dist/assets/GraphSection-SglITfSs.js +8 -0
- package/platform-ui/dist/assets/LoreSection-C3EixkjW.css +1 -0
- package/platform-ui/dist/assets/LoreSection-bR5Km4Fd.js +1 -0
- package/platform-ui/dist/assets/SentinelSection-BI-aIYKL.css +1 -0
- package/platform-ui/dist/assets/SentinelSection-QSpAZArG.js +1 -0
- package/platform-ui/dist/assets/SymphonySection-CobYJgvg.js +1 -0
- package/platform-ui/dist/assets/SymphonySection-zY0C5tFl.css +1 -0
- package/platform-ui/dist/assets/index-CfpZFjea.css +1 -0
- package/platform-ui/dist/assets/index-DbxeSMkV.js +57 -0
- package/platform-ui/dist/index.html +14 -0
- package/dist/graph-server-BZ73HTAT.js +0 -251
- package/dist/sentinel-ui/assets/index-C_Wstm64.js +0 -62
- package/dist/sentinel-ui/assets/index-C_Wstm64.js.map +0 -1
- /package/dist/{chunk-VUSCJJ4A.js → chunk-EDOAWN7J.js} +0 -0
- /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-
|
|
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
|
-
|
|
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-
|
|
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
|
|
2030
|
-
import * as
|
|
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,
|
|
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:
|
|
8356
|
-
passed:
|
|
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,
|
|
11305
|
-
const resolved = resolvePath(
|
|
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,
|
|
11339
|
-
const parts =
|
|
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
|
|
11576
|
-
const
|
|
11577
|
-
const
|
|
11578
|
-
const chainPath =
|
|
11579
|
-
if (!
|
|
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 =
|
|
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
|
|
11693
|
-
const [namespace, ...rest] =
|
|
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-
|
|
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/
|
|
14394
|
+
// ../paradigm-mcp/src/utils/symphony-loader.ts
|
|
14395
|
+
import * as fs26 from "fs";
|
|
14048
14396
|
import * as path28 from "path";
|
|
14049
|
-
import
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
|
|
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
|
-
|
|
14064
|
-
if (output.trim()) break;
|
|
14423
|
+
fs26.renameSync(LEGACY_MAIL_DIR, SCORE_DIR);
|
|
14065
14424
|
} catch {
|
|
14066
|
-
continue;
|
|
14067
14425
|
}
|
|
14068
14426
|
}
|
|
14069
|
-
|
|
14070
|
-
|
|
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
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
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
|
|
14457
|
+
return items;
|
|
14095
14458
|
}
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
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
|
-
|
|
14104
|
-
|
|
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
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
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
|
|
14537
|
+
return agents;
|
|
14123
14538
|
}
|
|
14124
|
-
function
|
|
14125
|
-
const
|
|
14126
|
-
|
|
14127
|
-
const
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
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
|
-
|
|
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 =
|
|
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-
|
|
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 =
|
|
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
|
});
|