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