@a-company/paradigm 5.9.0 → 5.10.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-GX2YRWM4.js → accept-orchestration-UQLM7PTQ.js} +4 -4
- package/dist/{agent-loader-X7TDYLFL.js → agent-loader-TFIANSF4.js} +1 -1
- package/dist/agent-state-S5DAWPTF.js +24 -0
- package/dist/{chunk-3UCH56D5.js → chunk-4BLYIB7J.js} +270 -928
- package/dist/chunk-4L3UTYQX.js +677 -0
- package/dist/chunk-54LTTQBH.js +138 -0
- package/dist/chunk-5OUOLN6M.js +659 -0
- package/dist/{chunk-SDDCVUCV.js → chunk-CL7JSK52.js} +23 -0
- package/dist/{chunk-MA7G4CTI.js → chunk-RJE5G7WO.js} +27 -1
- package/dist/{chunk-EI32ZBE6.js → chunk-RTHA3XRE.js} +19 -672
- package/dist/{chunk-V7BZBBI6.js → chunk-VPPK3SY4.js} +1 -1
- package/dist/{chunk-WQITYKHM.js → chunk-YRZ5RPEB.js} +7 -7
- package/dist/{diff-RQLLNAFI.js → diff-D4X53HAC.js} +4 -4
- package/dist/{docs-AIY6VNF7.js → docs-QIYKO3BR.js} +1 -1
- package/dist/index.js +19 -19
- package/dist/mcp.js +140 -66
- package/dist/model-discovery-D2H3VBGC.js +8 -0
- package/dist/{nomination-engine-LLREC5BZ.js → nomination-engine-RV5CNO5B.js} +2 -2
- package/dist/{orchestrate-XZA33TJC.js → orchestrate-JLILBBJE.js} +4 -4
- package/dist/{reindex-U2HEB6GW.js → reindex-5LTD53ZC.js} +3 -2
- package/dist/{serve-QWWJP2EW.js → serve-CAH3PHE7.js} +1 -1
- package/dist/session-tracker-C4BMD5WG.js +13 -0
- package/dist/{session-work-log-KDOH4GER.js → session-work-log-MZ47OAPB.js} +1 -1
- package/dist/{shift-VJUGMADR.js → shift-D2JOHHBF.js} +33 -5
- package/dist/{spawn-AW6GDECS.js → spawn-RCHNXDHE.js} +4 -4
- package/dist/{team-7HG7XK5C.js → team-O5MIIFMA.js} +6 -5
- package/package.json +1 -1
- package/dist/{chunk-LSRABQIY.js → chunk-45MUDW6E.js} +3 -3
- /package/dist/{platform-server-U5L2G3EU.js → platform-server-H5YO3DQD.js} +0 -0
|
@@ -4,15 +4,18 @@ import {
|
|
|
4
4
|
checkIntegrity,
|
|
5
5
|
checkPurposeHealth
|
|
6
6
|
} from "./chunk-L27I3CPZ.js";
|
|
7
|
-
import {
|
|
8
|
-
init_session_work_log,
|
|
9
|
-
session_work_log_exports
|
|
10
|
-
} from "./chunk-SDDCVUCV.js";
|
|
11
7
|
import {
|
|
12
8
|
init_lore_loader,
|
|
13
9
|
loadLoreEntries,
|
|
14
10
|
loadLoreEntry
|
|
15
11
|
} from "./chunk-5VKJBNJL.js";
|
|
12
|
+
import {
|
|
13
|
+
getSessionTracker,
|
|
14
|
+
loadPendingHandoffs,
|
|
15
|
+
markHandoffDelivered,
|
|
16
|
+
resetSessionTracker,
|
|
17
|
+
writePendingHandoff
|
|
18
|
+
} from "./chunk-4L3UTYQX.js";
|
|
16
19
|
import {
|
|
17
20
|
__esm,
|
|
18
21
|
__export,
|
|
@@ -30,9 +33,9 @@ __export(aspect_fingerprint_exports, {
|
|
|
30
33
|
searchSiblingFiles: () => searchSiblingFiles,
|
|
31
34
|
slidingWindowSearch: () => slidingWindowSearch
|
|
32
35
|
});
|
|
33
|
-
import * as
|
|
34
|
-
import * as
|
|
35
|
-
import * as
|
|
36
|
+
import * as fs3 from "fs";
|
|
37
|
+
import * as path4 from "path";
|
|
38
|
+
import * as crypto from "crypto";
|
|
36
39
|
import { execSync } from "child_process";
|
|
37
40
|
function generateFingerprint(content) {
|
|
38
41
|
const lines = content.split("\n").filter((l) => l.trim() !== "");
|
|
@@ -48,7 +51,7 @@ function extractStructuralHash(lines) {
|
|
|
48
51
|
const match = l.match(STRUCTURAL_TOKENS);
|
|
49
52
|
return match ? match[1].trim() : "";
|
|
50
53
|
}).join("|");
|
|
51
|
-
return
|
|
54
|
+
return crypto.createHash("sha256").update(structural).digest("hex").slice(0, 16);
|
|
52
55
|
}
|
|
53
56
|
function normalizeLine(line) {
|
|
54
57
|
return line.trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -170,18 +173,18 @@ function detectFileRename(rootDir, oldPath) {
|
|
|
170
173
|
}
|
|
171
174
|
}
|
|
172
175
|
function searchSiblingFiles(rootDir, dirPath, fingerprint, originalContent, maxFiles = 10) {
|
|
173
|
-
const absoluteDir =
|
|
174
|
-
if (!
|
|
176
|
+
const absoluteDir = path4.isAbsolute(dirPath) ? dirPath : path4.join(rootDir, dirPath);
|
|
177
|
+
if (!fs3.existsSync(absoluteDir)) return [];
|
|
175
178
|
const results = [];
|
|
176
179
|
try {
|
|
177
|
-
const files =
|
|
180
|
+
const files = fs3.readdirSync(absoluteDir).filter((f) => !f.startsWith(".") && fs3.statSync(path4.join(absoluteDir, f)).isFile()).slice(0, maxFiles);
|
|
178
181
|
for (const file of files) {
|
|
179
182
|
try {
|
|
180
|
-
const content =
|
|
183
|
+
const content = fs3.readFileSync(path4.join(absoluteDir, file), "utf8");
|
|
181
184
|
const lines = content.split("\n");
|
|
182
185
|
const matches = slidingWindowSearch(lines, fingerprint, originalContent, 1);
|
|
183
186
|
if (matches.length > 0 && matches[0].score >= 0.7) {
|
|
184
|
-
const relPath =
|
|
187
|
+
const relPath = path4.relative(rootDir, path4.join(absoluteDir, file));
|
|
185
188
|
results.push({
|
|
186
189
|
file: relPath,
|
|
187
190
|
score: matches[0].score,
|
|
@@ -199,9 +202,9 @@ function searchSiblingFiles(rootDir, dirPath, fingerprint, originalContent, maxF
|
|
|
199
202
|
}
|
|
200
203
|
function contentSearch(rootDir, filePath, originalContent, autoHeal = true) {
|
|
201
204
|
const fingerprint = generateFingerprint(originalContent);
|
|
202
|
-
const absolutePath =
|
|
203
|
-
if (
|
|
204
|
-
const fileContent =
|
|
205
|
+
const absolutePath = path4.isAbsolute(filePath) ? filePath : path4.join(rootDir, filePath);
|
|
206
|
+
if (fs3.existsSync(absolutePath)) {
|
|
207
|
+
const fileContent = fs3.readFileSync(absolutePath, "utf8");
|
|
205
208
|
const fileLines = fileContent.split("\n");
|
|
206
209
|
const matches = slidingWindowSearch(fileLines, fingerprint, originalContent);
|
|
207
210
|
if (matches.length > 0) {
|
|
@@ -217,9 +220,9 @@ function contentSearch(rootDir, filePath, originalContent, autoHeal = true) {
|
|
|
217
220
|
}
|
|
218
221
|
const renamedTo = detectFileRename(rootDir, filePath);
|
|
219
222
|
if (renamedTo) {
|
|
220
|
-
const renamedPath =
|
|
221
|
-
if (
|
|
222
|
-
const renamedContent =
|
|
223
|
+
const renamedPath = path4.join(rootDir, renamedTo);
|
|
224
|
+
if (fs3.existsSync(renamedPath)) {
|
|
225
|
+
const renamedContent = fs3.readFileSync(renamedPath, "utf8");
|
|
223
226
|
const renamedLines = renamedContent.split("\n");
|
|
224
227
|
const matches = slidingWindowSearch(renamedLines, fingerprint, originalContent);
|
|
225
228
|
if (matches.length > 0 && matches[0].score >= 0.7) {
|
|
@@ -234,7 +237,7 @@ function contentSearch(rootDir, filePath, originalContent, autoHeal = true) {
|
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
239
|
}
|
|
237
|
-
const dirPath =
|
|
240
|
+
const dirPath = path4.dirname(filePath);
|
|
238
241
|
const siblingResults = searchSiblingFiles(rootDir, dirPath, fingerprint, originalContent);
|
|
239
242
|
if (siblingResults.length > 0 && siblingResults[0].score >= 0.7) {
|
|
240
243
|
const best = siblingResults[0];
|
|
@@ -263,9 +266,9 @@ var init_aspect_fingerprint = __esm({
|
|
|
263
266
|
});
|
|
264
267
|
|
|
265
268
|
// ../paradigm-mcp/src/tools/reindex.ts
|
|
266
|
-
import * as
|
|
267
|
-
import * as
|
|
268
|
-
import * as
|
|
269
|
+
import * as fs8 from "fs";
|
|
270
|
+
import * as path9 from "path";
|
|
271
|
+
import * as yaml7 from "js-yaml";
|
|
269
272
|
|
|
270
273
|
// ../premise/core/dist/index.js
|
|
271
274
|
import * as yaml3 from "js-yaml";
|
|
@@ -1932,661 +1935,6 @@ function serializeScanIndex(index) {
|
|
|
1932
1935
|
return JSON.stringify(index, null, 2);
|
|
1933
1936
|
}
|
|
1934
1937
|
|
|
1935
|
-
// ../paradigm-mcp/src/utils/session-tracker.ts
|
|
1936
|
-
import * as fs4 from "fs";
|
|
1937
|
-
import * as path5 from "path";
|
|
1938
|
-
|
|
1939
|
-
// ../paradigm-mcp/src/utils/global-store.ts
|
|
1940
|
-
import * as fs3 from "fs";
|
|
1941
|
-
import * as path4 from "path";
|
|
1942
|
-
import * as os from "os";
|
|
1943
|
-
import * as crypto from "crypto";
|
|
1944
|
-
import * as yaml4 from "js-yaml";
|
|
1945
|
-
function getGlobalDir() {
|
|
1946
|
-
const dir = path4.join(os.homedir(), ".paradigm");
|
|
1947
|
-
if (!fs3.existsSync(dir)) {
|
|
1948
|
-
fs3.mkdirSync(dir, { recursive: true });
|
|
1949
|
-
}
|
|
1950
|
-
return dir;
|
|
1951
|
-
}
|
|
1952
|
-
function getProjectHash(rootDir) {
|
|
1953
|
-
const absolute = path4.resolve(rootDir);
|
|
1954
|
-
return crypto.createHash("sha256").update(absolute).digest("hex").slice(0, 12);
|
|
1955
|
-
}
|
|
1956
|
-
function getSessionDir(rootDir) {
|
|
1957
|
-
const hash = getProjectHash(rootDir);
|
|
1958
|
-
const dir = path4.join(getGlobalDir(), "sessions", hash);
|
|
1959
|
-
if (!fs3.existsSync(dir)) {
|
|
1960
|
-
fs3.mkdirSync(dir, { recursive: true });
|
|
1961
|
-
}
|
|
1962
|
-
const handoffsDir = path4.join(dir, "pending-handoffs");
|
|
1963
|
-
if (!fs3.existsSync(handoffsDir)) {
|
|
1964
|
-
fs3.mkdirSync(handoffsDir, { recursive: true });
|
|
1965
|
-
}
|
|
1966
|
-
return dir;
|
|
1967
|
-
}
|
|
1968
|
-
function writeProjectMeta(rootDir) {
|
|
1969
|
-
const sessionDir = getSessionDir(rootDir);
|
|
1970
|
-
const metaPath = path4.join(sessionDir, "_project-meta.json");
|
|
1971
|
-
const projectName = path4.basename(path4.resolve(rootDir));
|
|
1972
|
-
const meta = {
|
|
1973
|
-
name: projectName,
|
|
1974
|
-
path: path4.resolve(rootDir),
|
|
1975
|
-
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
|
|
1976
|
-
};
|
|
1977
|
-
fs3.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
|
|
1978
|
-
}
|
|
1979
|
-
function writePendingHandoff(rootDir, handoff) {
|
|
1980
|
-
const sessionDir = getSessionDir(rootDir);
|
|
1981
|
-
const filePath = path4.join(sessionDir, "pending-handoffs", `${handoff.id}.json`);
|
|
1982
|
-
fs3.writeFileSync(filePath, JSON.stringify(handoff, null, 2));
|
|
1983
|
-
}
|
|
1984
|
-
function loadPendingHandoffs(rootDir) {
|
|
1985
|
-
const sessionDir = getSessionDir(rootDir);
|
|
1986
|
-
const handoffsDir = path4.join(sessionDir, "pending-handoffs");
|
|
1987
|
-
if (!fs3.existsSync(handoffsDir)) {
|
|
1988
|
-
return [];
|
|
1989
|
-
}
|
|
1990
|
-
const handoffs = [];
|
|
1991
|
-
try {
|
|
1992
|
-
const files = fs3.readdirSync(handoffsDir);
|
|
1993
|
-
for (const file of files) {
|
|
1994
|
-
if (!file.endsWith(".json")) continue;
|
|
1995
|
-
try {
|
|
1996
|
-
const content = fs3.readFileSync(path4.join(handoffsDir, file), "utf8");
|
|
1997
|
-
const handoff = JSON.parse(content);
|
|
1998
|
-
if (handoff.status === "pending") {
|
|
1999
|
-
handoffs.push(handoff);
|
|
2000
|
-
}
|
|
2001
|
-
} catch {
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
} catch {
|
|
2005
|
-
}
|
|
2006
|
-
handoffs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
2007
|
-
return handoffs;
|
|
2008
|
-
}
|
|
2009
|
-
function markHandoffDelivered(rootDir, handoffId) {
|
|
2010
|
-
const sessionDir = getSessionDir(rootDir);
|
|
2011
|
-
const filePath = path4.join(sessionDir, "pending-handoffs", `${handoffId}.json`);
|
|
2012
|
-
if (!fs3.existsSync(filePath)) return;
|
|
2013
|
-
try {
|
|
2014
|
-
const content = fs3.readFileSync(filePath, "utf8");
|
|
2015
|
-
const handoff = JSON.parse(content);
|
|
2016
|
-
handoff.status = "delivered";
|
|
2017
|
-
fs3.writeFileSync(filePath, JSON.stringify(handoff, null, 2));
|
|
2018
|
-
} catch {
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
function getGlobalWisdomDir() {
|
|
2022
|
-
const dir = path4.join(getGlobalDir(), "wisdom");
|
|
2023
|
-
if (!fs3.existsSync(dir)) {
|
|
2024
|
-
fs3.mkdirSync(dir, { recursive: true });
|
|
2025
|
-
}
|
|
2026
|
-
return dir;
|
|
2027
|
-
}
|
|
2028
|
-
function loadGlobalAntipatterns() {
|
|
2029
|
-
const filePath = path4.join(getGlobalWisdomDir(), "antipatterns.yaml");
|
|
2030
|
-
if (!fs3.existsSync(filePath)) return [];
|
|
2031
|
-
try {
|
|
2032
|
-
const content = fs3.readFileSync(filePath, "utf8");
|
|
2033
|
-
const data = yaml4.load(content);
|
|
2034
|
-
return data?.antipatterns || [];
|
|
2035
|
-
} catch {
|
|
2036
|
-
return [];
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
function loadGlobalDecisions() {
|
|
2040
|
-
const decisionsDir = path4.join(getGlobalWisdomDir(), "decisions");
|
|
2041
|
-
if (!fs3.existsSync(decisionsDir)) return [];
|
|
2042
|
-
const decisions = [];
|
|
2043
|
-
try {
|
|
2044
|
-
const files = fs3.readdirSync(decisionsDir);
|
|
2045
|
-
for (const file of files) {
|
|
2046
|
-
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
|
|
2047
|
-
try {
|
|
2048
|
-
const content = fs3.readFileSync(path4.join(decisionsDir, file), "utf8");
|
|
2049
|
-
const decision = yaml4.load(content);
|
|
2050
|
-
decisions.push(decision);
|
|
2051
|
-
} catch {
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
} catch {
|
|
2055
|
-
}
|
|
2056
|
-
decisions.sort((a, b) => a.id.localeCompare(b.id));
|
|
2057
|
-
return decisions;
|
|
2058
|
-
}
|
|
2059
|
-
function loadGlobalPreferences() {
|
|
2060
|
-
const filePath = path4.join(getGlobalWisdomDir(), "preferences.yaml");
|
|
2061
|
-
if (!fs3.existsSync(filePath)) return null;
|
|
2062
|
-
try {
|
|
2063
|
-
const content = fs3.readFileSync(filePath, "utf8");
|
|
2064
|
-
return yaml4.load(content);
|
|
2065
|
-
} catch {
|
|
2066
|
-
return null;
|
|
2067
|
-
}
|
|
2068
|
-
}
|
|
2069
|
-
function recordGlobalAntipattern(antipattern) {
|
|
2070
|
-
const filePath = path4.join(getGlobalWisdomDir(), "antipatterns.yaml");
|
|
2071
|
-
let data = { version: "1.0", antipatterns: [] };
|
|
2072
|
-
if (fs3.existsSync(filePath)) {
|
|
2073
|
-
try {
|
|
2074
|
-
const content = fs3.readFileSync(filePath, "utf8");
|
|
2075
|
-
data = yaml4.load(content);
|
|
2076
|
-
if (!data.antipatterns) data.antipatterns = [];
|
|
2077
|
-
} catch {
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
data.antipatterns.push({
|
|
2081
|
-
...antipattern,
|
|
2082
|
-
added: (/* @__PURE__ */ new Date()).toISOString()
|
|
2083
|
-
});
|
|
2084
|
-
fs3.writeFileSync(filePath, yaml4.dump(data, { lineWidth: -1 }));
|
|
2085
|
-
}
|
|
2086
|
-
function recordGlobalDecision(decision) {
|
|
2087
|
-
const decisionsDir = path4.join(getGlobalWisdomDir(), "decisions");
|
|
2088
|
-
if (!fs3.existsSync(decisionsDir)) {
|
|
2089
|
-
fs3.mkdirSync(decisionsDir, { recursive: true });
|
|
2090
|
-
}
|
|
2091
|
-
const slug = decision.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2092
|
-
const fileName = `${decision.id}-${slug}.yaml`;
|
|
2093
|
-
const filePath = path4.join(decisionsDir, fileName);
|
|
2094
|
-
fs3.writeFileSync(filePath, yaml4.dump(decision, { lineWidth: -1 }));
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
// ../paradigm-mcp/src/utils/session-tracker.ts
|
|
2098
|
-
var MODEL_PRICING = {
|
|
2099
|
-
"claude-opus-4": { input: 15, output: 75, name: "Claude Opus 4" },
|
|
2100
|
-
"claude-sonnet-4": { input: 3, output: 15, name: "Claude Sonnet 4" },
|
|
2101
|
-
"claude-haiku-3.5": { input: 0.8, output: 4, name: "Claude Haiku 3.5" }
|
|
2102
|
-
};
|
|
2103
|
-
var MAX_BREADCRUMBS = 50;
|
|
2104
|
-
var BREADCRUMBS_FILE = ".paradigm/session-breadcrumbs.json";
|
|
2105
|
-
var CHECKPOINT_FILE = ".paradigm/session-checkpoint.json";
|
|
2106
|
-
var CHECKPOINT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2107
|
-
var SessionTracker = class {
|
|
2108
|
-
session;
|
|
2109
|
-
rootDir = null;
|
|
2110
|
-
_recovered = false;
|
|
2111
|
-
lastLoreEntryId = null;
|
|
2112
|
-
constructor() {
|
|
2113
|
-
this.session = this.createNewSession();
|
|
2114
|
-
}
|
|
2115
|
-
/**
|
|
2116
|
-
* Set the project root directory (for persisting breadcrumbs)
|
|
2117
|
-
*/
|
|
2118
|
-
setRootDir(rootDir) {
|
|
2119
|
-
this.rootDir = rootDir;
|
|
2120
|
-
try {
|
|
2121
|
-
const { clearSessionWorkLog } = (init_session_work_log(), __toCommonJS(session_work_log_exports));
|
|
2122
|
-
clearSessionWorkLog(rootDir);
|
|
2123
|
-
} catch {
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
createNewSession() {
|
|
2127
|
-
return {
|
|
2128
|
-
sessionId: `s${Date.now().toString(36)}`,
|
|
2129
|
-
startTime: Date.now(),
|
|
2130
|
-
lastActivity: Date.now(),
|
|
2131
|
-
model: "claude-sonnet-4",
|
|
2132
|
-
resourceReads: [],
|
|
2133
|
-
toolCalls: [],
|
|
2134
|
-
breadcrumbs: [],
|
|
2135
|
-
totals: {
|
|
2136
|
-
resourceReadCount: 0,
|
|
2137
|
-
toolCallCount: 0,
|
|
2138
|
-
totalBytes: 0,
|
|
2139
|
-
totalTokens: 0,
|
|
2140
|
-
estimatedCostUsd: 0
|
|
2141
|
-
}
|
|
2142
|
-
};
|
|
2143
|
-
}
|
|
2144
|
-
/**
|
|
2145
|
-
* Add a breadcrumb (summarized action for session recovery)
|
|
2146
|
-
*/
|
|
2147
|
-
addBreadcrumb(action, summary, options = {}) {
|
|
2148
|
-
this.session.breadcrumbs.push({
|
|
2149
|
-
timestamp: Date.now(),
|
|
2150
|
-
action,
|
|
2151
|
-
tool: options.tool,
|
|
2152
|
-
symbol: options.symbol,
|
|
2153
|
-
summary
|
|
2154
|
-
});
|
|
2155
|
-
if (this.session.breadcrumbs.length > MAX_BREADCRUMBS) {
|
|
2156
|
-
this.session.breadcrumbs = this.session.breadcrumbs.slice(-MAX_BREADCRUMBS);
|
|
2157
|
-
}
|
|
2158
|
-
this.persistBreadcrumbs();
|
|
2159
|
-
}
|
|
2160
|
-
/**
|
|
2161
|
-
* Get recent breadcrumbs
|
|
2162
|
-
*/
|
|
2163
|
-
getBreadcrumbs(limit = 20) {
|
|
2164
|
-
return this.session.breadcrumbs.slice(-limit);
|
|
2165
|
-
}
|
|
2166
|
-
/**
|
|
2167
|
-
* Persist breadcrumbs to file (dual-write: local + global)
|
|
2168
|
-
*/
|
|
2169
|
-
persistBreadcrumbs() {
|
|
2170
|
-
if (!this.rootDir) return;
|
|
2171
|
-
const data = {
|
|
2172
|
-
sessionId: this.session.sessionId,
|
|
2173
|
-
startTime: this.session.startTime,
|
|
2174
|
-
lastActivity: this.session.lastActivity,
|
|
2175
|
-
breadcrumbs: this.session.breadcrumbs,
|
|
2176
|
-
symbolsModified: this.extractSymbolsFromBreadcrumbs(),
|
|
2177
|
-
filesExplored: this.extractFilesFromBreadcrumbs()
|
|
2178
|
-
};
|
|
2179
|
-
let jsonData;
|
|
2180
|
-
try {
|
|
2181
|
-
jsonData = JSON.stringify(data, null, 2);
|
|
2182
|
-
} catch (err) {
|
|
2183
|
-
console.error("[paradigm-mcp] persistBreadcrumbs: JSON.stringify failed:", err.message);
|
|
2184
|
-
return;
|
|
2185
|
-
}
|
|
2186
|
-
try {
|
|
2187
|
-
const filePath = path5.join(this.rootDir, BREADCRUMBS_FILE);
|
|
2188
|
-
const dir = path5.dirname(filePath);
|
|
2189
|
-
if (!fs4.existsSync(dir)) {
|
|
2190
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
2191
|
-
}
|
|
2192
|
-
fs4.writeFileSync(filePath, jsonData);
|
|
2193
|
-
} catch (err) {
|
|
2194
|
-
console.error("[paradigm-mcp] persistBreadcrumbs: local write failed:", err.message);
|
|
2195
|
-
}
|
|
2196
|
-
try {
|
|
2197
|
-
const globalSessionDir = getSessionDir(this.rootDir);
|
|
2198
|
-
fs4.writeFileSync(path5.join(globalSessionDir, "breadcrumbs.json"), jsonData);
|
|
2199
|
-
writeProjectMeta(this.rootDir);
|
|
2200
|
-
} catch (err) {
|
|
2201
|
-
console.error("[paradigm-mcp] persistBreadcrumbs: global write failed:", err.message);
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
/**
|
|
2205
|
-
* Load previous session breadcrumbs from file.
|
|
2206
|
-
* Prefers global path (~/.paradigm/sessions/{hash}/breadcrumbs.json),
|
|
2207
|
-
* falls back to local (.paradigm/session-breadcrumbs.json).
|
|
2208
|
-
*/
|
|
2209
|
-
loadPreviousSession() {
|
|
2210
|
-
if (!this.rootDir) return null;
|
|
2211
|
-
try {
|
|
2212
|
-
const globalSessionDir = getSessionDir(this.rootDir);
|
|
2213
|
-
const globalPath = path5.join(globalSessionDir, "breadcrumbs.json");
|
|
2214
|
-
if (fs4.existsSync(globalPath)) {
|
|
2215
|
-
const content = fs4.readFileSync(globalPath, "utf8");
|
|
2216
|
-
return JSON.parse(content);
|
|
2217
|
-
}
|
|
2218
|
-
} catch {
|
|
2219
|
-
}
|
|
2220
|
-
try {
|
|
2221
|
-
const filePath = path5.join(this.rootDir, BREADCRUMBS_FILE);
|
|
2222
|
-
if (!fs4.existsSync(filePath)) return null;
|
|
2223
|
-
const content = fs4.readFileSync(filePath, "utf8");
|
|
2224
|
-
return JSON.parse(content);
|
|
2225
|
-
} catch {
|
|
2226
|
-
return null;
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
/**
|
|
2230
|
-
* Save a cognitive-transition checkpoint for crash recovery.
|
|
2231
|
-
* Fills in timestamp, sessionId, and snapshots recent breadcrumbs.
|
|
2232
|
-
* Returns the checkpoint and whether it was persisted to disk.
|
|
2233
|
-
*/
|
|
2234
|
-
saveCheckpoint(data) {
|
|
2235
|
-
const checkpoint = {
|
|
2236
|
-
phase: data.phase,
|
|
2237
|
-
context: data.context,
|
|
2238
|
-
timestamp: Date.now(),
|
|
2239
|
-
sessionId: this.session.sessionId,
|
|
2240
|
-
externalId: data.externalId,
|
|
2241
|
-
plan: data.plan,
|
|
2242
|
-
modifiedFiles: data.modifiedFiles,
|
|
2243
|
-
symbolsTouched: data.symbolsTouched,
|
|
2244
|
-
decisions: data.decisions,
|
|
2245
|
-
recentBreadcrumbs: this.session.breadcrumbs.slice(-10)
|
|
2246
|
-
};
|
|
2247
|
-
const persisted = this.persistCheckpoint(checkpoint);
|
|
2248
|
-
return { checkpoint, persisted };
|
|
2249
|
-
}
|
|
2250
|
-
/**
|
|
2251
|
-
* Load the most recent checkpoint.
|
|
2252
|
-
* Prefers global path, falls back to local.
|
|
2253
|
-
* Returns null for checkpoints older than 7 days.
|
|
2254
|
-
*/
|
|
2255
|
-
loadCheckpoint() {
|
|
2256
|
-
if (!this.rootDir) return null;
|
|
2257
|
-
let checkpoint = null;
|
|
2258
|
-
try {
|
|
2259
|
-
const globalSessionDir = getSessionDir(this.rootDir);
|
|
2260
|
-
const globalPath = path5.join(globalSessionDir, "checkpoint.json");
|
|
2261
|
-
if (fs4.existsSync(globalPath)) {
|
|
2262
|
-
const content = fs4.readFileSync(globalPath, "utf8");
|
|
2263
|
-
checkpoint = JSON.parse(content);
|
|
2264
|
-
}
|
|
2265
|
-
} catch {
|
|
2266
|
-
}
|
|
2267
|
-
if (!checkpoint) {
|
|
2268
|
-
try {
|
|
2269
|
-
const localPath = path5.join(this.rootDir, CHECKPOINT_FILE);
|
|
2270
|
-
if (fs4.existsSync(localPath)) {
|
|
2271
|
-
const content = fs4.readFileSync(localPath, "utf8");
|
|
2272
|
-
checkpoint = JSON.parse(content);
|
|
2273
|
-
}
|
|
2274
|
-
} catch {
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
if (checkpoint && Date.now() - checkpoint.timestamp > CHECKPOINT_MAX_AGE_MS) {
|
|
2278
|
-
return null;
|
|
2279
|
-
}
|
|
2280
|
-
if (checkpoint) {
|
|
2281
|
-
for (const key of ["modifiedFiles", "symbolsTouched", "decisions"]) {
|
|
2282
|
-
const val = checkpoint[key];
|
|
2283
|
-
if (typeof val === "string") {
|
|
2284
|
-
try {
|
|
2285
|
-
checkpoint[key] = JSON.parse(val);
|
|
2286
|
-
} catch {
|
|
2287
|
-
checkpoint[key] = [];
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
return checkpoint;
|
|
2293
|
-
}
|
|
2294
|
-
/**
|
|
2295
|
-
* Persist checkpoint to both local and global paths.
|
|
2296
|
-
* Returns which writes succeeded so callers can report accurately.
|
|
2297
|
-
*/
|
|
2298
|
-
persistCheckpoint(checkpoint) {
|
|
2299
|
-
const result = { local: false, global: false };
|
|
2300
|
-
if (!this.rootDir) {
|
|
2301
|
-
console.error("[paradigm-mcp] persistCheckpoint: rootDir not set, skipping write");
|
|
2302
|
-
return result;
|
|
2303
|
-
}
|
|
2304
|
-
let jsonData;
|
|
2305
|
-
try {
|
|
2306
|
-
jsonData = JSON.stringify(checkpoint, null, 2);
|
|
2307
|
-
} catch (err) {
|
|
2308
|
-
console.error("[paradigm-mcp] persistCheckpoint: JSON.stringify failed:", err.message);
|
|
2309
|
-
return result;
|
|
2310
|
-
}
|
|
2311
|
-
try {
|
|
2312
|
-
const filePath = path5.join(this.rootDir, CHECKPOINT_FILE);
|
|
2313
|
-
const dir = path5.dirname(filePath);
|
|
2314
|
-
if (!fs4.existsSync(dir)) {
|
|
2315
|
-
fs4.mkdirSync(dir, { recursive: true });
|
|
2316
|
-
}
|
|
2317
|
-
fs4.writeFileSync(filePath, jsonData);
|
|
2318
|
-
result.local = true;
|
|
2319
|
-
} catch (err) {
|
|
2320
|
-
console.error("[paradigm-mcp] persistCheckpoint: local write failed:", err.message);
|
|
2321
|
-
}
|
|
2322
|
-
try {
|
|
2323
|
-
const globalSessionDir = getSessionDir(this.rootDir);
|
|
2324
|
-
fs4.writeFileSync(path5.join(globalSessionDir, "checkpoint.json"), jsonData);
|
|
2325
|
-
writeProjectMeta(this.rootDir);
|
|
2326
|
-
result.global = true;
|
|
2327
|
-
} catch (err) {
|
|
2328
|
-
console.error("[paradigm-mcp] persistCheckpoint: global write failed:", err.message);
|
|
2329
|
-
}
|
|
2330
|
-
return result;
|
|
2331
|
-
}
|
|
2332
|
-
/**
|
|
2333
|
-
* Set the last lore entry ID recorded in this session
|
|
2334
|
-
*/
|
|
2335
|
-
setLastLoreEntryId(id) {
|
|
2336
|
-
this.lastLoreEntryId = id;
|
|
2337
|
-
}
|
|
2338
|
-
/**
|
|
2339
|
-
* Get the last lore entry ID recorded in this session
|
|
2340
|
-
*/
|
|
2341
|
-
getLastLoreEntryId() {
|
|
2342
|
-
return this.lastLoreEntryId;
|
|
2343
|
-
}
|
|
2344
|
-
/**
|
|
2345
|
-
* Check whether auto-recovery has already fired this session.
|
|
2346
|
-
*/
|
|
2347
|
-
hasRecoveredThisSession() {
|
|
2348
|
-
return this._recovered;
|
|
2349
|
-
}
|
|
2350
|
-
/**
|
|
2351
|
-
* Mark that auto-recovery has fired (so it only fires once per session).
|
|
2352
|
-
*/
|
|
2353
|
-
markRecovered() {
|
|
2354
|
-
this._recovered = true;
|
|
2355
|
-
}
|
|
2356
|
-
/**
|
|
2357
|
-
* Extract symbols from breadcrumbs
|
|
2358
|
-
*/
|
|
2359
|
-
extractSymbolsFromBreadcrumbs() {
|
|
2360
|
-
const symbols = /* @__PURE__ */ new Set();
|
|
2361
|
-
for (const bc of this.session.breadcrumbs) {
|
|
2362
|
-
if (bc.symbol) symbols.add(bc.symbol);
|
|
2363
|
-
}
|
|
2364
|
-
return Array.from(symbols);
|
|
2365
|
-
}
|
|
2366
|
-
/**
|
|
2367
|
-
* Extract files from breadcrumbs
|
|
2368
|
-
*/
|
|
2369
|
-
extractFilesFromBreadcrumbs() {
|
|
2370
|
-
const files = /* @__PURE__ */ new Set();
|
|
2371
|
-
for (const bc of this.session.breadcrumbs) {
|
|
2372
|
-
const matches = bc.summary.match(/\b[\w./]+\.(ts|js|tsx|jsx|py|go|rs|yaml|json|md)\b/g);
|
|
2373
|
-
if (matches) {
|
|
2374
|
-
for (const m of matches) {
|
|
2375
|
-
files.add(m);
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
}
|
|
2379
|
-
return Array.from(files);
|
|
2380
|
-
}
|
|
2381
|
-
/**
|
|
2382
|
-
* Estimate tokens from text (approx 3.5 chars per token)
|
|
2383
|
-
*/
|
|
2384
|
-
estimateTokens(text) {
|
|
2385
|
-
const len = typeof text === "number" ? text : text.length;
|
|
2386
|
-
return Math.ceil(len / 3.5);
|
|
2387
|
-
}
|
|
2388
|
-
/**
|
|
2389
|
-
* Calculate cost for tokens using current model pricing
|
|
2390
|
-
*/
|
|
2391
|
-
calculateCost(tokens, isOutput = true) {
|
|
2392
|
-
const pricing = MODEL_PRICING[this.session.model];
|
|
2393
|
-
const rate = isOutput ? pricing.output : pricing.input;
|
|
2394
|
-
return tokens / 1e6 * rate;
|
|
2395
|
-
}
|
|
2396
|
-
/**
|
|
2397
|
-
* Set the model for cost calculations
|
|
2398
|
-
*/
|
|
2399
|
-
setModel(model) {
|
|
2400
|
-
this.session.model = model;
|
|
2401
|
-
this.recalculateTotals();
|
|
2402
|
-
}
|
|
2403
|
-
/**
|
|
2404
|
-
* Get current model
|
|
2405
|
-
*/
|
|
2406
|
-
getModel() {
|
|
2407
|
-
return this.session.model;
|
|
2408
|
-
}
|
|
2409
|
-
/**
|
|
2410
|
-
* Track a resource read
|
|
2411
|
-
*/
|
|
2412
|
-
trackResourceRead(uri, bytes) {
|
|
2413
|
-
const resourceType = this.extractResourceType(uri);
|
|
2414
|
-
const tokens = this.estimateTokens(bytes);
|
|
2415
|
-
this.session.resourceReads.push({
|
|
2416
|
-
timestamp: Date.now(),
|
|
2417
|
-
resourceType,
|
|
2418
|
-
uri,
|
|
2419
|
-
bytes,
|
|
2420
|
-
tokens
|
|
2421
|
-
});
|
|
2422
|
-
this.session.lastActivity = Date.now();
|
|
2423
|
-
this.updateTotals(bytes, tokens);
|
|
2424
|
-
}
|
|
2425
|
-
/**
|
|
2426
|
-
* Track a tool call
|
|
2427
|
-
*/
|
|
2428
|
-
trackToolCall(toolName, responseBytes) {
|
|
2429
|
-
const tokens = this.estimateTokens(responseBytes);
|
|
2430
|
-
this.session.toolCalls.push({
|
|
2431
|
-
timestamp: Date.now(),
|
|
2432
|
-
toolName,
|
|
2433
|
-
responseBytes,
|
|
2434
|
-
responseTokens: tokens
|
|
2435
|
-
});
|
|
2436
|
-
this.session.lastActivity = Date.now();
|
|
2437
|
-
this.updateTotals(responseBytes, tokens);
|
|
2438
|
-
}
|
|
2439
|
-
/**
|
|
2440
|
-
* Update running totals
|
|
2441
|
-
*/
|
|
2442
|
-
updateTotals(bytes, tokens) {
|
|
2443
|
-
this.session.totals.resourceReadCount = this.session.resourceReads.length;
|
|
2444
|
-
this.session.totals.toolCallCount = this.session.toolCalls.length;
|
|
2445
|
-
this.session.totals.totalBytes += bytes;
|
|
2446
|
-
this.session.totals.totalTokens += tokens;
|
|
2447
|
-
this.session.totals.estimatedCostUsd = this.calculateCost(this.session.totals.totalTokens);
|
|
2448
|
-
}
|
|
2449
|
-
/**
|
|
2450
|
-
* Recalculate totals (used when model changes)
|
|
2451
|
-
*/
|
|
2452
|
-
recalculateTotals() {
|
|
2453
|
-
this.session.totals.estimatedCostUsd = this.calculateCost(this.session.totals.totalTokens);
|
|
2454
|
-
}
|
|
2455
|
-
/**
|
|
2456
|
-
* Extract resource type from URI
|
|
2457
|
-
*/
|
|
2458
|
-
extractResourceType(uri) {
|
|
2459
|
-
const path12 = uri.replace("paradigm://", "");
|
|
2460
|
-
const firstPart = path12.split("/")[0];
|
|
2461
|
-
return firstPart || "unknown";
|
|
2462
|
-
}
|
|
2463
|
-
/**
|
|
2464
|
-
* Get session statistics
|
|
2465
|
-
*/
|
|
2466
|
-
getStats() {
|
|
2467
|
-
return { ...this.session };
|
|
2468
|
-
}
|
|
2469
|
-
/**
|
|
2470
|
-
* Get detailed cost breakdown
|
|
2471
|
-
*/
|
|
2472
|
-
getCostBreakdown() {
|
|
2473
|
-
const resourcesByType = {};
|
|
2474
|
-
let resourceBytes = 0;
|
|
2475
|
-
let resourceTokens = 0;
|
|
2476
|
-
for (const read of this.session.resourceReads) {
|
|
2477
|
-
if (!resourcesByType[read.resourceType]) {
|
|
2478
|
-
resourcesByType[read.resourceType] = { count: 0, bytes: 0, tokens: 0 };
|
|
2479
|
-
}
|
|
2480
|
-
resourcesByType[read.resourceType].count++;
|
|
2481
|
-
resourcesByType[read.resourceType].bytes += read.bytes;
|
|
2482
|
-
resourcesByType[read.resourceType].tokens += read.tokens;
|
|
2483
|
-
resourceBytes += read.bytes;
|
|
2484
|
-
resourceTokens += read.tokens;
|
|
2485
|
-
}
|
|
2486
|
-
const toolsByName = {};
|
|
2487
|
-
let toolBytes = 0;
|
|
2488
|
-
let toolTokens = 0;
|
|
2489
|
-
for (const call of this.session.toolCalls) {
|
|
2490
|
-
if (!toolsByName[call.toolName]) {
|
|
2491
|
-
toolsByName[call.toolName] = { count: 0, bytes: 0, tokens: 0 };
|
|
2492
|
-
}
|
|
2493
|
-
toolsByName[call.toolName].count++;
|
|
2494
|
-
toolsByName[call.toolName].bytes += call.responseBytes;
|
|
2495
|
-
toolsByName[call.toolName].tokens += call.responseTokens;
|
|
2496
|
-
toolBytes += call.responseBytes;
|
|
2497
|
-
toolTokens += call.responseTokens;
|
|
2498
|
-
}
|
|
2499
|
-
const totalTokens = resourceTokens + toolTokens;
|
|
2500
|
-
const totalCost = this.calculateCost(totalTokens);
|
|
2501
|
-
return {
|
|
2502
|
-
model: MODEL_PRICING[this.session.model].name,
|
|
2503
|
-
modelId: this.session.model,
|
|
2504
|
-
pricing: MODEL_PRICING[this.session.model],
|
|
2505
|
-
resources: {
|
|
2506
|
-
count: this.session.resourceReads.length,
|
|
2507
|
-
bytes: resourceBytes,
|
|
2508
|
-
tokens: resourceTokens,
|
|
2509
|
-
costUsd: this.calculateCost(resourceTokens),
|
|
2510
|
-
byType: resourcesByType
|
|
2511
|
-
},
|
|
2512
|
-
tools: {
|
|
2513
|
-
count: this.session.toolCalls.length,
|
|
2514
|
-
bytes: toolBytes,
|
|
2515
|
-
tokens: toolTokens,
|
|
2516
|
-
costUsd: this.calculateCost(toolTokens),
|
|
2517
|
-
byName: toolsByName
|
|
2518
|
-
},
|
|
2519
|
-
total: {
|
|
2520
|
-
tokens: totalTokens,
|
|
2521
|
-
costUsd: totalCost
|
|
2522
|
-
}
|
|
2523
|
-
};
|
|
2524
|
-
}
|
|
2525
|
-
/**
|
|
2526
|
-
* Get handoff recommendation based on context usage
|
|
2527
|
-
*/
|
|
2528
|
-
getHandoffRecommendation(contextWindowSize = 2e5, estimatedTotalTokens) {
|
|
2529
|
-
const mcpTokens = this.session.totals.totalTokens;
|
|
2530
|
-
const estimatedConversationOverhead = mcpTokens * 4;
|
|
2531
|
-
const totalEstimate = estimatedTotalTokens || mcpTokens + estimatedConversationOverhead;
|
|
2532
|
-
const usagePercent = Math.round(totalEstimate / contextWindowSize * 100);
|
|
2533
|
-
let recommendation;
|
|
2534
|
-
let message;
|
|
2535
|
-
if (usagePercent >= 85) {
|
|
2536
|
-
recommendation = "handoff-urgent";
|
|
2537
|
-
message = "Context is nearly full. Initiate handoff immediately to preserve session continuity.";
|
|
2538
|
-
} else if (usagePercent >= 70) {
|
|
2539
|
-
recommendation = "handoff-recommended";
|
|
2540
|
-
message = "Context usage is high. Consider initiating handoff soon to ensure smooth transition.";
|
|
2541
|
-
} else if (usagePercent >= 50) {
|
|
2542
|
-
recommendation = "consider-handoff";
|
|
2543
|
-
message = "Context usage is moderate. Plan a good stopping point for potential handoff.";
|
|
2544
|
-
} else {
|
|
2545
|
-
recommendation = "continue";
|
|
2546
|
-
message = "Context usage is healthy. Continue working.";
|
|
2547
|
-
}
|
|
2548
|
-
const signals = [];
|
|
2549
|
-
const durationMin = Math.round((Date.now() - this.session.startTime) / 6e4);
|
|
2550
|
-
const totalCalls = this.session.toolCalls.length + this.session.resourceReads.length;
|
|
2551
|
-
if (totalCalls > 50) {
|
|
2552
|
-
signals.push(`High number of MCP interactions (${totalCalls})`);
|
|
2553
|
-
}
|
|
2554
|
-
if (durationMin > 30) {
|
|
2555
|
-
signals.push(`Session duration >30 min (${durationMin} min)`);
|
|
2556
|
-
}
|
|
2557
|
-
if (this.session.totals.totalBytes > 5e5) {
|
|
2558
|
-
signals.push(`Large data volume (${Math.round(this.session.totals.totalBytes / 1024)}KB)`);
|
|
2559
|
-
}
|
|
2560
|
-
return { recommendation, message, usagePercent, signals };
|
|
2561
|
-
}
|
|
2562
|
-
/**
|
|
2563
|
-
* Get session duration in minutes
|
|
2564
|
-
*/
|
|
2565
|
-
getDurationMinutes() {
|
|
2566
|
-
return Math.round((Date.now() - this.session.startTime) / 6e4);
|
|
2567
|
-
}
|
|
2568
|
-
/**
|
|
2569
|
-
* Reset session (for handoff or new session)
|
|
2570
|
-
*/
|
|
2571
|
-
reset() {
|
|
2572
|
-
this.session = this.createNewSession();
|
|
2573
|
-
this._recovered = false;
|
|
2574
|
-
this.lastLoreEntryId = null;
|
|
2575
|
-
}
|
|
2576
|
-
};
|
|
2577
|
-
var tracker = null;
|
|
2578
|
-
function getSessionTracker() {
|
|
2579
|
-
if (!tracker) {
|
|
2580
|
-
tracker = new SessionTracker();
|
|
2581
|
-
}
|
|
2582
|
-
return tracker;
|
|
2583
|
-
}
|
|
2584
|
-
function resetSessionTracker() {
|
|
2585
|
-
if (tracker) {
|
|
2586
|
-
tracker.reset();
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
1938
|
// ../paradigm-mcp/src/tools/context.ts
|
|
2591
1939
|
function trackToolCall(responseSize, toolName = "unknown") {
|
|
2592
1940
|
getSessionTracker().trackToolCall(toolName, responseSize);
|
|
@@ -2694,9 +2042,9 @@ function extractBreadcrumbInfo(toolName, args) {
|
|
|
2694
2042
|
}
|
|
2695
2043
|
}
|
|
2696
2044
|
function addToolBreadcrumb(toolName, args) {
|
|
2697
|
-
const
|
|
2045
|
+
const tracker = getSessionTracker();
|
|
2698
2046
|
const { summary, symbol } = extractBreadcrumbInfo(toolName, args);
|
|
2699
|
-
|
|
2047
|
+
tracker.addBreadcrumb("tool-call", summary, { tool: toolName, symbol });
|
|
2700
2048
|
}
|
|
2701
2049
|
function getContextToolsList() {
|
|
2702
2050
|
return [
|
|
@@ -2878,16 +2226,16 @@ function getContextToolsList() {
|
|
|
2878
2226
|
];
|
|
2879
2227
|
}
|
|
2880
2228
|
async function handleContextTool(name, args, _ctx) {
|
|
2881
|
-
const
|
|
2229
|
+
const tracker = getSessionTracker();
|
|
2882
2230
|
if (name === "paradigm_context_check") {
|
|
2883
2231
|
const contextWindowSize = args.contextWindowSize || 2e5;
|
|
2884
2232
|
const estimatedTotal = args.estimatedTotalTokens;
|
|
2885
|
-
const stats =
|
|
2886
|
-
const { recommendation, message, usagePercent, signals } =
|
|
2233
|
+
const stats = tracker.getStats();
|
|
2234
|
+
const { recommendation, message, usagePercent, signals } = tracker.getHandoffRecommendation(
|
|
2887
2235
|
contextWindowSize,
|
|
2888
2236
|
estimatedTotal
|
|
2889
2237
|
);
|
|
2890
|
-
const durationMin =
|
|
2238
|
+
const durationMin = tracker.getDurationMinutes();
|
|
2891
2239
|
return {
|
|
2892
2240
|
handled: true,
|
|
2893
2241
|
text: JSON.stringify({
|
|
@@ -2914,8 +2262,8 @@ async function handleContextTool(name, args, _ctx) {
|
|
|
2914
2262
|
const modifiedFiles = args.modifiedFiles || [];
|
|
2915
2263
|
const symbolsTouched = args.symbolsTouched || [];
|
|
2916
2264
|
const openQuestions = args.openQuestions || [];
|
|
2917
|
-
const stats =
|
|
2918
|
-
const breakdown =
|
|
2265
|
+
const stats = tracker.getStats();
|
|
2266
|
+
const breakdown = tracker.getCostBreakdown();
|
|
2919
2267
|
const handoffId = `h${Date.now().toString(36)}`;
|
|
2920
2268
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2921
2269
|
const handoffPayload = {
|
|
@@ -2929,7 +2277,7 @@ async function handleContextTool(name, args, _ctx) {
|
|
|
2929
2277
|
symbolsTouched,
|
|
2930
2278
|
openQuestions,
|
|
2931
2279
|
sessionStats: {
|
|
2932
|
-
duration:
|
|
2280
|
+
duration: tracker.getDurationMinutes(),
|
|
2933
2281
|
mcpCalls: stats.totals.toolCallCount + stats.totals.resourceReadCount,
|
|
2934
2282
|
estimatedTokens: stats.totals.totalTokens,
|
|
2935
2283
|
estimatedCostUsd: breakdown.total.costUsd,
|
|
@@ -2966,9 +2314,9 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
|
|
|
2966
2314
|
};
|
|
2967
2315
|
}
|
|
2968
2316
|
if (name === "paradigm_session_stats") {
|
|
2969
|
-
const stats =
|
|
2970
|
-
const breakdown =
|
|
2971
|
-
const durationMin =
|
|
2317
|
+
const stats = tracker.getStats();
|
|
2318
|
+
const breakdown = tracker.getCostBreakdown();
|
|
2319
|
+
const durationMin = tracker.getDurationMinutes();
|
|
2972
2320
|
return {
|
|
2973
2321
|
handled: true,
|
|
2974
2322
|
text: JSON.stringify({
|
|
@@ -3013,9 +2361,9 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
|
|
|
3013
2361
|
};
|
|
3014
2362
|
}
|
|
3015
2363
|
if (name === "paradigm_session_recover") {
|
|
3016
|
-
|
|
3017
|
-
const previousSession =
|
|
3018
|
-
const checkpoint =
|
|
2364
|
+
tracker.setRootDir(_ctx.rootDir);
|
|
2365
|
+
const previousSession = tracker.loadPreviousSession();
|
|
2366
|
+
const checkpoint = tracker.loadCheckpoint();
|
|
3019
2367
|
let pendingHandoffs = [];
|
|
3020
2368
|
try {
|
|
3021
2369
|
pendingHandoffs = loadPendingHandoffs(_ctx.rootDir);
|
|
@@ -3121,14 +2469,14 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
|
|
|
3121
2469
|
}
|
|
3122
2470
|
result.suggestion = suggestion;
|
|
3123
2471
|
result.agentInstruction = "Present a brief summary of the previous session, then ask the user what they would like to do: (1) Continue \u2014 pick up where the last session left off, (2) Discard \u2014 ignore the previous session and start fresh, or (3) let them describe what they want to work on instead. Do NOT automatically continue without asking.";
|
|
3124
|
-
|
|
2472
|
+
tracker.markRecovered();
|
|
3125
2473
|
return {
|
|
3126
2474
|
handled: true,
|
|
3127
2475
|
text: JSON.stringify(result, null, 2)
|
|
3128
2476
|
};
|
|
3129
2477
|
}
|
|
3130
2478
|
if (name === "paradigm_session_checkpoint") {
|
|
3131
|
-
|
|
2479
|
+
tracker.setRootDir(_ctx.rootDir);
|
|
3132
2480
|
const phase = args.phase;
|
|
3133
2481
|
const context = args.context;
|
|
3134
2482
|
const externalId = args.externalId;
|
|
@@ -3136,7 +2484,7 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
|
|
|
3136
2484
|
const modifiedFiles = args.modifiedFiles;
|
|
3137
2485
|
const symbolsTouched = args.symbolsTouched;
|
|
3138
2486
|
const decisions = args.decisions;
|
|
3139
|
-
const { checkpoint, persisted } =
|
|
2487
|
+
const { checkpoint, persisted } = tracker.saveCheckpoint({
|
|
3140
2488
|
phase,
|
|
3141
2489
|
context,
|
|
3142
2490
|
externalId,
|
|
@@ -3169,9 +2517,9 @@ ${nextSteps.map((step, i) => `${i + 1}. ${step}`).join("\n") || "(none specified
|
|
|
3169
2517
|
return { handled: false, text: "" };
|
|
3170
2518
|
}
|
|
3171
2519
|
async function buildRecoveryPreamble(rootDir) {
|
|
3172
|
-
const
|
|
3173
|
-
|
|
3174
|
-
const checkpoint =
|
|
2520
|
+
const tracker = getSessionTracker();
|
|
2521
|
+
tracker.setRootDir(rootDir);
|
|
2522
|
+
const checkpoint = tracker.loadCheckpoint();
|
|
3175
2523
|
let pendingHandoffs = [];
|
|
3176
2524
|
try {
|
|
3177
2525
|
pendingHandoffs = loadPendingHandoffs(rootDir);
|
|
@@ -3244,7 +2592,7 @@ async function buildRecoveryPreamble(rootDir) {
|
|
|
3244
2592
|
} catch {
|
|
3245
2593
|
}
|
|
3246
2594
|
try {
|
|
3247
|
-
const { loadNominations } = await import("./nomination-engine-
|
|
2595
|
+
const { loadNominations } = await import("./nomination-engine-RV5CNO5B.js");
|
|
3248
2596
|
const urgent = loadNominations(rootDir, { pending_only: true }).filter((n) => n.urgency === "critical" || n.urgency === "high");
|
|
3249
2597
|
if (urgent.length > 0) {
|
|
3250
2598
|
lines.push("");
|
|
@@ -3315,9 +2663,9 @@ var ToolCache = class {
|
|
|
3315
2663
|
var toolCache = new ToolCache(3e4);
|
|
3316
2664
|
|
|
3317
2665
|
// ../paradigm-mcp/src/utils/aspect-graph.ts
|
|
3318
|
-
import * as
|
|
3319
|
-
import * as
|
|
3320
|
-
import * as
|
|
2666
|
+
import * as fs4 from "fs";
|
|
2667
|
+
import * as path5 from "path";
|
|
2668
|
+
import * as crypto2 from "crypto";
|
|
3321
2669
|
import { execSync as execSync2 } from "child_process";
|
|
3322
2670
|
import initSqlJs from "sql.js";
|
|
3323
2671
|
var cachedSQL = null;
|
|
@@ -3438,15 +2786,15 @@ function queryOne(db, sql, params) {
|
|
|
3438
2786
|
}
|
|
3439
2787
|
async function openAspectGraph(rootDir) {
|
|
3440
2788
|
const SQL = await getSqlJs();
|
|
3441
|
-
const dbDir =
|
|
3442
|
-
const dbPath =
|
|
2789
|
+
const dbDir = path5.join(rootDir, ".paradigm");
|
|
2790
|
+
const dbPath = path5.join(dbDir, "aspect-graph.db");
|
|
3443
2791
|
let db;
|
|
3444
|
-
if (
|
|
3445
|
-
const buffer =
|
|
2792
|
+
if (fs4.existsSync(dbPath)) {
|
|
2793
|
+
const buffer = fs4.readFileSync(dbPath);
|
|
3446
2794
|
db = new SQL.Database(buffer);
|
|
3447
2795
|
} else {
|
|
3448
|
-
if (!
|
|
3449
|
-
|
|
2796
|
+
if (!fs4.existsSync(dbDir)) {
|
|
2797
|
+
fs4.mkdirSync(dbDir, { recursive: true });
|
|
3450
2798
|
}
|
|
3451
2799
|
db = new SQL.Database();
|
|
3452
2800
|
}
|
|
@@ -3467,13 +2815,13 @@ async function openAspectGraph(rootDir) {
|
|
|
3467
2815
|
}
|
|
3468
2816
|
function closeAspectGraph(db, rootDir) {
|
|
3469
2817
|
if (rootDir) {
|
|
3470
|
-
const dbDir =
|
|
3471
|
-
if (!
|
|
3472
|
-
|
|
2818
|
+
const dbDir = path5.join(rootDir, ".paradigm");
|
|
2819
|
+
if (!fs4.existsSync(dbDir)) {
|
|
2820
|
+
fs4.mkdirSync(dbDir, { recursive: true });
|
|
3473
2821
|
}
|
|
3474
|
-
const dbPath =
|
|
2822
|
+
const dbPath = path5.join(dbDir, "aspect-graph.db");
|
|
3475
2823
|
const data = db.export();
|
|
3476
|
-
|
|
2824
|
+
fs4.writeFileSync(dbPath, Buffer.from(data));
|
|
3477
2825
|
}
|
|
3478
2826
|
db.close();
|
|
3479
2827
|
}
|
|
@@ -3609,8 +2957,8 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
|
|
|
3609
2957
|
const anchorRows = aspectId ? queryRows(db, "SELECT * FROM anchors WHERE aspect_id = ?", [aspectId]) : queryRows(db, "SELECT * FROM anchors");
|
|
3610
2958
|
const results = [];
|
|
3611
2959
|
for (const anchor of anchorRows) {
|
|
3612
|
-
const absolutePath =
|
|
3613
|
-
if (!
|
|
2960
|
+
const absolutePath = path5.isAbsolute(anchor.file_path) ? anchor.file_path : path5.join(rootDir, anchor.file_path);
|
|
2961
|
+
if (!fs4.existsSync(absolutePath)) {
|
|
3614
2962
|
results.push({
|
|
3615
2963
|
aspectId: anchor.aspect_id,
|
|
3616
2964
|
path: anchor.file_path,
|
|
@@ -3624,12 +2972,12 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
|
|
|
3624
2972
|
continue;
|
|
3625
2973
|
}
|
|
3626
2974
|
try {
|
|
3627
|
-
const fileContent =
|
|
2975
|
+
const fileContent = fs4.readFileSync(absolutePath, "utf8");
|
|
3628
2976
|
const lines = fileContent.split("\n");
|
|
3629
2977
|
const startIdx = Math.max(0, anchor.start_line - 1);
|
|
3630
2978
|
const endIdx = Math.min(lines.length, anchor.end_line);
|
|
3631
2979
|
const sliceContent = lines.slice(startIdx, endIdx).join("\n");
|
|
3632
|
-
const currentExactHash =
|
|
2980
|
+
const currentExactHash = crypto2.createHash("sha256").update(sliceContent).digest("hex");
|
|
3633
2981
|
if (anchor.content_hash != null && currentExactHash === anchor.content_hash) {
|
|
3634
2982
|
results.push({
|
|
3635
2983
|
aspectId: anchor.aspect_id,
|
|
@@ -3646,7 +2994,7 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
|
|
|
3646
2994
|
}
|
|
3647
2995
|
continue;
|
|
3648
2996
|
}
|
|
3649
|
-
const currentNormalizedHash =
|
|
2997
|
+
const currentNormalizedHash = crypto2.createHash("sha256").update(normalizeForHash(sliceContent)).digest("hex");
|
|
3650
2998
|
if (anchor.normalized_hash != null && currentNormalizedHash === anchor.normalized_hash) {
|
|
3651
2999
|
db.run("UPDATE anchors SET content_hash = ?, drifted = 0 WHERE id = ?", [currentExactHash, anchor.id]);
|
|
3652
3000
|
results.push({
|
|
@@ -3691,7 +3039,7 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
|
|
|
3691
3039
|
const shiftedStartIdx = Math.max(0, mapping.currentStart - 1);
|
|
3692
3040
|
const shiftedEndIdx = Math.min(lines.length, mapping.currentEnd);
|
|
3693
3041
|
const shiftedContent = lines.slice(shiftedStartIdx, shiftedEndIdx).join("\n");
|
|
3694
|
-
const shiftedExactHash =
|
|
3042
|
+
const shiftedExactHash = crypto2.createHash("sha256").update(shiftedContent).digest("hex");
|
|
3695
3043
|
if (anchor.content_hash != null && shiftedExactHash === anchor.content_hash) {
|
|
3696
3044
|
const healed = autoHeal;
|
|
3697
3045
|
if (healed) {
|
|
@@ -3731,10 +3079,10 @@ function checkDrift(db, rootDir, aspectId, autoHeal = true) {
|
|
|
3731
3079
|
});
|
|
3732
3080
|
resolvedByGit = true;
|
|
3733
3081
|
} else {
|
|
3734
|
-
const shiftedNormalized =
|
|
3082
|
+
const shiftedNormalized = crypto2.createHash("sha256").update(normalizeForHash(shiftedContent)).digest("hex");
|
|
3735
3083
|
if (anchor.normalized_hash != null && shiftedNormalized === anchor.normalized_hash) {
|
|
3736
3084
|
if (autoHeal) {
|
|
3737
|
-
const shiftedNewHash =
|
|
3085
|
+
const shiftedNewHash = crypto2.createHash("sha256").update(shiftedContent).digest("hex");
|
|
3738
3086
|
db.run(
|
|
3739
3087
|
"UPDATE anchors SET start_line = ?, end_line = ?, content_hash = ?, drifted = 0 WHERE id = ?",
|
|
3740
3088
|
[mapping.currentStart, mapping.currentEnd, shiftedNewHash, anchor.id]
|
|
@@ -3934,15 +3282,15 @@ function computeLineShift(rootDir, filePath, fromCommit, originalStart, original
|
|
|
3934
3282
|
};
|
|
3935
3283
|
}
|
|
3936
3284
|
function healAnchorInPurposeFile(rootDir, purposeFilePath, anchorFilePath, oldStart, oldEnd, newStart, newEnd) {
|
|
3937
|
-
const absolutePurpose =
|
|
3938
|
-
if (!
|
|
3285
|
+
const absolutePurpose = path5.isAbsolute(purposeFilePath) ? purposeFilePath : path5.join(rootDir, purposeFilePath);
|
|
3286
|
+
if (!fs4.existsSync(absolutePurpose)) return false;
|
|
3939
3287
|
try {
|
|
3940
|
-
const content =
|
|
3288
|
+
const content = fs4.readFileSync(absolutePurpose, "utf8");
|
|
3941
3289
|
const oldAnchor = oldStart === oldEnd ? `${anchorFilePath}:${oldStart}` : `${anchorFilePath}:${oldStart}-${oldEnd}`;
|
|
3942
3290
|
const newAnchor = newStart === newEnd ? `${anchorFilePath}:${newStart}` : `${anchorFilePath}:${newStart}-${newEnd}`;
|
|
3943
3291
|
if (!content.includes(oldAnchor)) return false;
|
|
3944
3292
|
const updated = content.replace(oldAnchor, newAnchor);
|
|
3945
|
-
|
|
3293
|
+
fs4.writeFileSync(absolutePurpose, updated, "utf8");
|
|
3946
3294
|
return true;
|
|
3947
3295
|
} catch {
|
|
3948
3296
|
return false;
|
|
@@ -3953,18 +3301,18 @@ function normalizeForHash(content) {
|
|
|
3953
3301
|
}
|
|
3954
3302
|
function computeAnchorHash(anchor, rootDir) {
|
|
3955
3303
|
if (!rootDir) return { exact: null, normalized: null, normalizedContent: null };
|
|
3956
|
-
const absolutePath =
|
|
3957
|
-
if (!
|
|
3304
|
+
const absolutePath = path5.isAbsolute(anchor.path) ? anchor.path : path5.join(rootDir, anchor.path);
|
|
3305
|
+
if (!fs4.existsSync(absolutePath)) return { exact: null, normalized: null, normalizedContent: null };
|
|
3958
3306
|
try {
|
|
3959
|
-
const fileContent =
|
|
3307
|
+
const fileContent = fs4.readFileSync(absolutePath, "utf8");
|
|
3960
3308
|
const lines = fileContent.split("\n");
|
|
3961
3309
|
const { startLine, endLine } = resolveAnchorLines(anchor);
|
|
3962
3310
|
const startIdx = Math.max(0, startLine - 1);
|
|
3963
3311
|
const endIdx = Math.min(lines.length, endLine);
|
|
3964
3312
|
const sliceContent = lines.slice(startIdx, endIdx).join("\n");
|
|
3965
3313
|
const normalizedContent = normalizeForHash(sliceContent);
|
|
3966
|
-
const exact =
|
|
3967
|
-
const normalized =
|
|
3314
|
+
const exact = crypto2.createHash("sha256").update(sliceContent).digest("hex");
|
|
3315
|
+
const normalized = crypto2.createHash("sha256").update(normalizedContent).digest("hex");
|
|
3968
3316
|
return { exact, normalized, normalizedContent };
|
|
3969
3317
|
} catch {
|
|
3970
3318
|
return { exact: null, normalized: null, normalizedContent: null };
|
|
@@ -4184,20 +3532,20 @@ function toLoreSummary(entry) {
|
|
|
4184
3532
|
}
|
|
4185
3533
|
|
|
4186
3534
|
// ../paradigm-mcp/src/utils/personas-loader.ts
|
|
4187
|
-
import * as
|
|
4188
|
-
import * as
|
|
4189
|
-
import * as
|
|
3535
|
+
import * as fs5 from "fs";
|
|
3536
|
+
import * as path6 from "path";
|
|
3537
|
+
import * as yaml4 from "js-yaml";
|
|
4190
3538
|
var PERSONAS_ROOT = ".paradigm/personas";
|
|
4191
3539
|
var INDEX_FILE = "index.yaml";
|
|
4192
3540
|
async function loadPersonas(rootDir, filter) {
|
|
4193
|
-
const personasDir =
|
|
4194
|
-
if (!
|
|
4195
|
-
const files =
|
|
3541
|
+
const personasDir = path6.join(rootDir, PERSONAS_ROOT);
|
|
3542
|
+
if (!fs5.existsSync(personasDir)) return [];
|
|
3543
|
+
const files = fs5.readdirSync(personasDir).filter((f) => f.endsWith(".persona"));
|
|
4196
3544
|
const personas = [];
|
|
4197
3545
|
for (const file of files) {
|
|
4198
3546
|
try {
|
|
4199
|
-
const content =
|
|
4200
|
-
const persona =
|
|
3547
|
+
const content = fs5.readFileSync(path6.join(personasDir, file), "utf8");
|
|
3548
|
+
const persona = yaml4.load(content);
|
|
4201
3549
|
if (persona && persona.id) {
|
|
4202
3550
|
personas.push(persona);
|
|
4203
3551
|
}
|
|
@@ -4207,10 +3555,10 @@ async function loadPersonas(rootDir, filter) {
|
|
|
4207
3555
|
return applyFilter(personas, filter);
|
|
4208
3556
|
}
|
|
4209
3557
|
async function loadPersona(rootDir, id) {
|
|
4210
|
-
const filePath =
|
|
4211
|
-
if (!
|
|
3558
|
+
const filePath = path6.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
|
|
3559
|
+
if (!fs5.existsSync(filePath)) return null;
|
|
4212
3560
|
try {
|
|
4213
|
-
return
|
|
3561
|
+
return yaml4.load(fs5.readFileSync(filePath, "utf8"));
|
|
4214
3562
|
} catch {
|
|
4215
3563
|
return null;
|
|
4216
3564
|
}
|
|
@@ -4240,10 +3588,10 @@ function applyFilter(personas, filter) {
|
|
|
4240
3588
|
return result;
|
|
4241
3589
|
}
|
|
4242
3590
|
async function createPersona(rootDir, data) {
|
|
4243
|
-
const personasDir =
|
|
4244
|
-
|
|
4245
|
-
const filePath =
|
|
4246
|
-
if (
|
|
3591
|
+
const personasDir = path6.join(rootDir, PERSONAS_ROOT);
|
|
3592
|
+
fs5.mkdirSync(personasDir, { recursive: true });
|
|
3593
|
+
const filePath = path6.join(personasDir, `${data.id}.persona`);
|
|
3594
|
+
if (fs5.existsSync(filePath)) {
|
|
4247
3595
|
throw new Error(`Persona ${data.id} already exists`);
|
|
4248
3596
|
}
|
|
4249
3597
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4260,14 +3608,14 @@ async function createPersona(rootDir, data) {
|
|
|
4260
3608
|
created: now,
|
|
4261
3609
|
updated: now
|
|
4262
3610
|
};
|
|
4263
|
-
|
|
3611
|
+
fs5.writeFileSync(filePath, yaml4.dump(persona, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }));
|
|
4264
3612
|
await rebuildPersonaIndex(rootDir);
|
|
4265
3613
|
return data.id;
|
|
4266
3614
|
}
|
|
4267
3615
|
async function updatePersona(rootDir, id, partial) {
|
|
4268
3616
|
const persona = await loadPersona(rootDir, id);
|
|
4269
3617
|
if (!persona) return false;
|
|
4270
|
-
const filePath =
|
|
3618
|
+
const filePath = path6.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
|
|
4271
3619
|
const updated = {
|
|
4272
3620
|
...persona,
|
|
4273
3621
|
...partial,
|
|
@@ -4279,13 +3627,13 @@ async function updatePersona(rootDir, id, partial) {
|
|
|
4279
3627
|
// immutable
|
|
4280
3628
|
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
4281
3629
|
};
|
|
4282
|
-
|
|
3630
|
+
fs5.writeFileSync(filePath, yaml4.dump(updated, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }));
|
|
4283
3631
|
await rebuildPersonaIndex(rootDir);
|
|
4284
3632
|
return true;
|
|
4285
3633
|
}
|
|
4286
3634
|
async function deletePersona(rootDir, id) {
|
|
4287
|
-
const filePath =
|
|
4288
|
-
if (!
|
|
3635
|
+
const filePath = path6.join(rootDir, PERSONAS_ROOT, `${id}.persona`);
|
|
3636
|
+
if (!fs5.existsSync(filePath)) {
|
|
4289
3637
|
return { deleted: false, warnings: [] };
|
|
4290
3638
|
}
|
|
4291
3639
|
const warnings = [];
|
|
@@ -4301,7 +3649,7 @@ async function deletePersona(rootDir, id) {
|
|
|
4301
3649
|
}
|
|
4302
3650
|
}
|
|
4303
3651
|
}
|
|
4304
|
-
|
|
3652
|
+
fs5.unlinkSync(filePath);
|
|
4305
3653
|
await rebuildPersonaIndex(rootDir);
|
|
4306
3654
|
return { deleted: true, warnings };
|
|
4307
3655
|
}
|
|
@@ -4403,12 +3751,12 @@ async function validatePersona(rootDir, persona, deep = false) {
|
|
|
4403
3751
|
}
|
|
4404
3752
|
}
|
|
4405
3753
|
if (deep) {
|
|
4406
|
-
const portalPath =
|
|
3754
|
+
const portalPath = path6.join(rootDir, "portal.yaml");
|
|
4407
3755
|
let portalGates = [];
|
|
4408
3756
|
let portalRoutes = [];
|
|
4409
|
-
if (
|
|
3757
|
+
if (fs5.existsSync(portalPath)) {
|
|
4410
3758
|
try {
|
|
4411
|
-
const portal =
|
|
3759
|
+
const portal = yaml4.load(fs5.readFileSync(portalPath, "utf8"));
|
|
4412
3760
|
if (portal.gates && typeof portal.gates === "object") {
|
|
4413
3761
|
portalGates = Object.keys(portal.gates);
|
|
4414
3762
|
}
|
|
@@ -4459,10 +3807,10 @@ async function validatePersona(rootDir, persona, deep = false) {
|
|
|
4459
3807
|
}
|
|
4460
3808
|
}
|
|
4461
3809
|
let allFlows = [];
|
|
4462
|
-
const flowIndexPath =
|
|
4463
|
-
if (
|
|
3810
|
+
const flowIndexPath = path6.join(rootDir, ".paradigm", "flow-index.json");
|
|
3811
|
+
if (fs5.existsSync(flowIndexPath)) {
|
|
4464
3812
|
try {
|
|
4465
|
-
const flowIndex = JSON.parse(
|
|
3813
|
+
const flowIndex = JSON.parse(fs5.readFileSync(flowIndexPath, "utf8"));
|
|
4466
3814
|
allFlows = Object.keys(flowIndex.flows || {});
|
|
4467
3815
|
} catch {
|
|
4468
3816
|
}
|
|
@@ -4539,8 +3887,8 @@ async function detectSpawnCycle(rootDir, startId) {
|
|
|
4539
3887
|
return visit(startId);
|
|
4540
3888
|
}
|
|
4541
3889
|
async function rebuildPersonaIndex(rootDir) {
|
|
4542
|
-
const personasDir =
|
|
4543
|
-
|
|
3890
|
+
const personasDir = path6.join(rootDir, PERSONAS_ROOT);
|
|
3891
|
+
fs5.mkdirSync(personasDir, { recursive: true });
|
|
4544
3892
|
const personas = await loadPersonas(rootDir);
|
|
4545
3893
|
const entries = {};
|
|
4546
3894
|
const gateCoverage = {};
|
|
@@ -4579,10 +3927,10 @@ async function rebuildPersonaIndex(rootDir) {
|
|
|
4579
3927
|
}
|
|
4580
3928
|
}
|
|
4581
3929
|
let uncoveredRoutes = [];
|
|
4582
|
-
const portalPath =
|
|
4583
|
-
if (
|
|
3930
|
+
const portalPath = path6.join(rootDir, "portal.yaml");
|
|
3931
|
+
if (fs5.existsSync(portalPath)) {
|
|
4584
3932
|
try {
|
|
4585
|
-
const portal =
|
|
3933
|
+
const portal = yaml4.load(fs5.readFileSync(portalPath, "utf8"));
|
|
4586
3934
|
if (portal.routes && typeof portal.routes === "object") {
|
|
4587
3935
|
const portalRoutes = Object.keys(portal.routes);
|
|
4588
3936
|
uncoveredRoutes = portalRoutes.filter((pr) => {
|
|
@@ -4593,13 +3941,13 @@ async function rebuildPersonaIndex(rootDir) {
|
|
|
4593
3941
|
}
|
|
4594
3942
|
}
|
|
4595
3943
|
const chains = {};
|
|
4596
|
-
const chainsDir =
|
|
4597
|
-
if (
|
|
4598
|
-
const chainFiles =
|
|
3944
|
+
const chainsDir = path6.join(personasDir, "chains");
|
|
3945
|
+
if (fs5.existsSync(chainsDir)) {
|
|
3946
|
+
const chainFiles = fs5.readdirSync(chainsDir).filter((f) => f.endsWith(".yaml"));
|
|
4599
3947
|
for (const file of chainFiles) {
|
|
4600
3948
|
try {
|
|
4601
|
-
const content =
|
|
4602
|
-
const chain =
|
|
3949
|
+
const content = fs5.readFileSync(path6.join(chainsDir, file), "utf8");
|
|
3950
|
+
const chain = yaml4.load(content);
|
|
4603
3951
|
if (chain && chain.id) {
|
|
4604
3952
|
const orderIds = chain.order.map((o) => o.persona);
|
|
4605
3953
|
let totalSteps = 0;
|
|
@@ -4631,9 +3979,9 @@ async function rebuildPersonaIndex(rootDir) {
|
|
|
4631
3979
|
route_coverage: routeCoverage,
|
|
4632
3980
|
uncovered_routes: uncoveredRoutes
|
|
4633
3981
|
};
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
3982
|
+
fs5.writeFileSync(
|
|
3983
|
+
path6.join(personasDir, INDEX_FILE),
|
|
3984
|
+
yaml4.dump(index, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false })
|
|
4637
3985
|
);
|
|
4638
3986
|
return index;
|
|
4639
3987
|
}
|
|
@@ -4651,20 +3999,20 @@ async function getPersonaCoverage(rootDir) {
|
|
|
4651
3999
|
}
|
|
4652
4000
|
let portalGates = [];
|
|
4653
4001
|
let portalRoutes = [];
|
|
4654
|
-
const portalPath =
|
|
4655
|
-
if (
|
|
4002
|
+
const portalPath = path6.join(rootDir, "portal.yaml");
|
|
4003
|
+
if (fs5.existsSync(portalPath)) {
|
|
4656
4004
|
try {
|
|
4657
|
-
const portal =
|
|
4005
|
+
const portal = yaml4.load(fs5.readFileSync(portalPath, "utf8"));
|
|
4658
4006
|
if (portal.gates && typeof portal.gates === "object") portalGates = Object.keys(portal.gates);
|
|
4659
4007
|
if (portal.routes && typeof portal.routes === "object") portalRoutes = Object.keys(portal.routes);
|
|
4660
4008
|
} catch {
|
|
4661
4009
|
}
|
|
4662
4010
|
}
|
|
4663
4011
|
let allFlows = [];
|
|
4664
|
-
const flowIndexPath =
|
|
4665
|
-
if (
|
|
4012
|
+
const flowIndexPath = path6.join(rootDir, ".paradigm", "flow-index.json");
|
|
4013
|
+
if (fs5.existsSync(flowIndexPath)) {
|
|
4666
4014
|
try {
|
|
4667
|
-
const flowIndex = JSON.parse(
|
|
4015
|
+
const flowIndex = JSON.parse(fs5.readFileSync(flowIndexPath, "utf8"));
|
|
4668
4016
|
allFlows = Object.keys(flowIndex.flows || {});
|
|
4669
4017
|
} catch {
|
|
4670
4018
|
}
|
|
@@ -4713,8 +4061,8 @@ async function getAffectedPersonas(rootDir, symbol) {
|
|
|
4713
4061
|
}
|
|
4714
4062
|
return results;
|
|
4715
4063
|
}
|
|
4716
|
-
function deepGet(obj,
|
|
4717
|
-
const parts =
|
|
4064
|
+
function deepGet(obj, path10) {
|
|
4065
|
+
const parts = path10.split(/[.\[\]]+/).filter(Boolean);
|
|
4718
4066
|
let current = obj;
|
|
4719
4067
|
for (const part of parts) {
|
|
4720
4068
|
if (current == null || typeof current !== "object") return void 0;
|
|
@@ -4883,22 +4231,22 @@ async function validateAgainstSentinel(persona, options = {}) {
|
|
|
4883
4231
|
}
|
|
4884
4232
|
|
|
4885
4233
|
// ../paradigm-mcp/src/utils/protocol-loader.ts
|
|
4886
|
-
import * as
|
|
4887
|
-
import * as
|
|
4888
|
-
import * as
|
|
4234
|
+
import * as fs6 from "fs";
|
|
4235
|
+
import * as path7 from "path";
|
|
4236
|
+
import * as yaml5 from "js-yaml";
|
|
4889
4237
|
var PROTOCOLS_DIR = ".paradigm/protocols";
|
|
4890
4238
|
var INDEX_FILE2 = "index.yaml";
|
|
4891
4239
|
async function loadProtocols(rootDir) {
|
|
4892
|
-
const protocolsDir =
|
|
4893
|
-
if (!
|
|
4240
|
+
const protocolsDir = path7.join(rootDir, PROTOCOLS_DIR);
|
|
4241
|
+
if (!fs6.existsSync(protocolsDir)) {
|
|
4894
4242
|
return [];
|
|
4895
4243
|
}
|
|
4896
|
-
const files =
|
|
4244
|
+
const files = fs6.readdirSync(protocolsDir).filter((f) => f.endsWith(".protocol")).sort();
|
|
4897
4245
|
const protocols = [];
|
|
4898
4246
|
for (const file of files) {
|
|
4899
4247
|
try {
|
|
4900
|
-
const content =
|
|
4901
|
-
const protocol =
|
|
4248
|
+
const content = fs6.readFileSync(path7.join(protocolsDir, file), "utf8");
|
|
4249
|
+
const protocol = yaml5.load(content);
|
|
4902
4250
|
if (protocol?.id && protocol?.name) {
|
|
4903
4251
|
protocols.push(protocol);
|
|
4904
4252
|
}
|
|
@@ -4909,11 +4257,11 @@ async function loadProtocols(rootDir) {
|
|
|
4909
4257
|
}
|
|
4910
4258
|
async function loadProtocol(rootDir, id) {
|
|
4911
4259
|
const slug = id.replace(/^P-/, "");
|
|
4912
|
-
const filePath =
|
|
4913
|
-
if (
|
|
4260
|
+
const filePath = path7.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
|
|
4261
|
+
if (fs6.existsSync(filePath)) {
|
|
4914
4262
|
try {
|
|
4915
|
-
const content =
|
|
4916
|
-
return
|
|
4263
|
+
const content = fs6.readFileSync(filePath, "utf8");
|
|
4264
|
+
return yaml5.load(content);
|
|
4917
4265
|
} catch {
|
|
4918
4266
|
return null;
|
|
4919
4267
|
}
|
|
@@ -4922,13 +4270,13 @@ async function loadProtocol(rootDir, id) {
|
|
|
4922
4270
|
return protocols.find((p) => p.id === id) || null;
|
|
4923
4271
|
}
|
|
4924
4272
|
async function loadProtocolIndex(rootDir) {
|
|
4925
|
-
const indexPath =
|
|
4926
|
-
if (!
|
|
4273
|
+
const indexPath = path7.join(rootDir, PROTOCOLS_DIR, INDEX_FILE2);
|
|
4274
|
+
if (!fs6.existsSync(indexPath)) {
|
|
4927
4275
|
return null;
|
|
4928
4276
|
}
|
|
4929
4277
|
try {
|
|
4930
|
-
const content =
|
|
4931
|
-
return
|
|
4278
|
+
const content = fs6.readFileSync(indexPath, "utf8");
|
|
4279
|
+
return yaml5.load(content);
|
|
4932
4280
|
} catch {
|
|
4933
4281
|
return null;
|
|
4934
4282
|
}
|
|
@@ -4979,9 +4327,9 @@ async function searchProtocols(rootDir, task, limit = 3) {
|
|
|
4979
4327
|
return scored.slice(0, limit);
|
|
4980
4328
|
}
|
|
4981
4329
|
async function recordProtocol(rootDir, protocol) {
|
|
4982
|
-
const protocolsDir =
|
|
4983
|
-
if (!
|
|
4984
|
-
|
|
4330
|
+
const protocolsDir = path7.join(rootDir, PROTOCOLS_DIR);
|
|
4331
|
+
if (!fs6.existsSync(protocolsDir)) {
|
|
4332
|
+
fs6.mkdirSync(protocolsDir, { recursive: true });
|
|
4985
4333
|
}
|
|
4986
4334
|
const slug = slugify(protocol.name);
|
|
4987
4335
|
const id = `P-${slug}`;
|
|
@@ -5001,8 +4349,8 @@ async function recordProtocol(rootDir, protocol) {
|
|
|
5001
4349
|
verified_by: protocol.verified_by || "claude-opus-4-6",
|
|
5002
4350
|
status: "current"
|
|
5003
4351
|
};
|
|
5004
|
-
const filePath =
|
|
5005
|
-
|
|
4352
|
+
const filePath = path7.join(protocolsDir, `${slug}.protocol`);
|
|
4353
|
+
fs6.writeFileSync(filePath, yaml5.dump(full, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5006
4354
|
return id;
|
|
5007
4355
|
}
|
|
5008
4356
|
async function updateProtocol(rootDir, id, partial, refresh = false) {
|
|
@@ -5022,20 +4370,20 @@ async function updateProtocol(rootDir, id, partial, refresh = false) {
|
|
|
5022
4370
|
protocol.verified_by = partial.verified_by || "claude-opus-4-6";
|
|
5023
4371
|
}
|
|
5024
4372
|
const slug = id.replace(/^P-/, "");
|
|
5025
|
-
const filePath =
|
|
5026
|
-
|
|
4373
|
+
const filePath = path7.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
|
|
4374
|
+
fs6.writeFileSync(filePath, yaml5.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5027
4375
|
return true;
|
|
5028
4376
|
}
|
|
5029
4377
|
function validateProtocol(rootDir, protocol) {
|
|
5030
4378
|
const issues = [];
|
|
5031
4379
|
let status = "current";
|
|
5032
4380
|
if (protocol.exemplar) {
|
|
5033
|
-
const exemplarPath =
|
|
5034
|
-
if (!
|
|
4381
|
+
const exemplarPath = path7.join(rootDir, protocol.exemplar);
|
|
4382
|
+
if (!fs6.existsSync(exemplarPath)) {
|
|
5035
4383
|
issues.push(`Exemplar missing: ${protocol.exemplar}`);
|
|
5036
4384
|
status = "broken";
|
|
5037
4385
|
} else {
|
|
5038
|
-
const stat =
|
|
4386
|
+
const stat = fs6.statSync(exemplarPath);
|
|
5039
4387
|
if (stat.mtime.toISOString() > protocol.last_verified) {
|
|
5040
4388
|
issues.push(`Exemplar modified since last verified: ${protocol.exemplar}`);
|
|
5041
4389
|
if (status !== "broken") status = "stale";
|
|
@@ -5044,15 +4392,15 @@ function validateProtocol(rootDir, protocol) {
|
|
|
5044
4392
|
}
|
|
5045
4393
|
for (const step of protocol.steps) {
|
|
5046
4394
|
if (step.template_from) {
|
|
5047
|
-
const templatePath =
|
|
5048
|
-
if (!
|
|
4395
|
+
const templatePath = path7.join(rootDir, step.template_from);
|
|
4396
|
+
if (!fs6.existsSync(templatePath)) {
|
|
5049
4397
|
issues.push(`Template file missing: ${step.template_from}`);
|
|
5050
4398
|
status = "broken";
|
|
5051
4399
|
}
|
|
5052
4400
|
}
|
|
5053
4401
|
if (step.action === "modify" && step.target) {
|
|
5054
|
-
const targetPath =
|
|
5055
|
-
if (!step.target.includes("{") && !
|
|
4402
|
+
const targetPath = path7.join(rootDir, step.target);
|
|
4403
|
+
if (!step.target.includes("{") && !fs6.existsSync(targetPath)) {
|
|
5056
4404
|
issues.push(`Modify target missing: ${step.target}`);
|
|
5057
4405
|
status = "broken";
|
|
5058
4406
|
}
|
|
@@ -5071,9 +4419,9 @@ async function rebuildProtocolIndex(rootDir) {
|
|
|
5071
4419
|
if (protocol.status !== validation.status) {
|
|
5072
4420
|
protocol.status = validation.status;
|
|
5073
4421
|
const slug = protocol.id.replace(/^P-/, "");
|
|
5074
|
-
const filePath =
|
|
5075
|
-
if (
|
|
5076
|
-
|
|
4422
|
+
const filePath = path7.join(rootDir, PROTOCOLS_DIR, `${slug}.protocol`);
|
|
4423
|
+
if (fs6.existsSync(filePath)) {
|
|
4424
|
+
fs6.writeFileSync(filePath, yaml5.dump(protocol, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5077
4425
|
}
|
|
5078
4426
|
}
|
|
5079
4427
|
switch (validation.status) {
|
|
@@ -5107,13 +4455,13 @@ async function rebuildProtocolIndex(rootDir) {
|
|
|
5107
4455
|
broken
|
|
5108
4456
|
}
|
|
5109
4457
|
};
|
|
5110
|
-
const protocolsDir =
|
|
4458
|
+
const protocolsDir = path7.join(rootDir, PROTOCOLS_DIR);
|
|
5111
4459
|
if (protocols.length > 0) {
|
|
5112
|
-
if (!
|
|
5113
|
-
|
|
4460
|
+
if (!fs6.existsSync(protocolsDir)) {
|
|
4461
|
+
fs6.mkdirSync(protocolsDir, { recursive: true });
|
|
5114
4462
|
}
|
|
5115
|
-
const indexPath =
|
|
5116
|
-
|
|
4463
|
+
const indexPath = path7.join(protocolsDir, INDEX_FILE2);
|
|
4464
|
+
fs6.writeFileSync(indexPath, yaml5.dump(index, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5117
4465
|
}
|
|
5118
4466
|
return index;
|
|
5119
4467
|
}
|
|
@@ -5121,21 +4469,21 @@ function detectProtocolSuggestion(rootDir, filesCreated, filesModified) {
|
|
|
5121
4469
|
if (!filesCreated || filesCreated.length < 2) return null;
|
|
5122
4470
|
const dirGroups = {};
|
|
5123
4471
|
for (const file of filesCreated) {
|
|
5124
|
-
const dir =
|
|
4472
|
+
const dir = path7.dirname(file);
|
|
5125
4473
|
if (!dirGroups[dir]) dirGroups[dir] = [];
|
|
5126
4474
|
dirGroups[dir].push(file);
|
|
5127
4475
|
}
|
|
5128
4476
|
for (const [dir, created] of Object.entries(dirGroups)) {
|
|
5129
4477
|
if (created.length < 2) continue;
|
|
5130
|
-
const absDir =
|
|
5131
|
-
if (!
|
|
5132
|
-
const existing =
|
|
5133
|
-
const ext =
|
|
4478
|
+
const absDir = path7.join(rootDir, dir);
|
|
4479
|
+
if (!fs6.existsSync(absDir)) continue;
|
|
4480
|
+
const existing = fs6.readdirSync(absDir).filter((f) => {
|
|
4481
|
+
const ext = path7.extname(f);
|
|
5134
4482
|
return [".ts", ".tsx", ".js", ".jsx", ".rs", ".py"].includes(ext);
|
|
5135
4483
|
});
|
|
5136
|
-
const preExisting = existing.filter((f) => !created.some((c) =>
|
|
4484
|
+
const preExisting = existing.filter((f) => !created.some((c) => path7.basename(c) === f));
|
|
5137
4485
|
if (preExisting.length > 0) {
|
|
5138
|
-
const exemplar =
|
|
4486
|
+
const exemplar = path7.join(dir, preExisting[0]);
|
|
5139
4487
|
const steps = [
|
|
5140
4488
|
...created.map((f) => ({ action: "create", target: f })),
|
|
5141
4489
|
...filesModified.map((f) => ({ action: "modify", target: f }))
|
|
@@ -5143,7 +4491,7 @@ function detectProtocolSuggestion(rootDir, filesCreated, filesModified) {
|
|
|
5143
4491
|
return {
|
|
5144
4492
|
hint: `This session created ${created.length} new files in ${dir}/ following existing patterns. Consider recording a protocol.`,
|
|
5145
4493
|
draft: {
|
|
5146
|
-
name: `Add a ${
|
|
4494
|
+
name: `Add a ${path7.basename(dir).replace(/s$/, "")}`,
|
|
5147
4495
|
exemplar,
|
|
5148
4496
|
steps
|
|
5149
4497
|
}
|
|
@@ -5218,9 +4566,9 @@ function slugify(name) {
|
|
|
5218
4566
|
}
|
|
5219
4567
|
|
|
5220
4568
|
// ../paradigm-mcp/src/utils/university-loader.ts
|
|
5221
|
-
import * as
|
|
5222
|
-
import * as
|
|
5223
|
-
import * as
|
|
4569
|
+
import * as fs7 from "fs";
|
|
4570
|
+
import * as path8 from "path";
|
|
4571
|
+
import * as yaml6 from "js-yaml";
|
|
5224
4572
|
var UNIVERSITY_DIR = ".paradigm/university";
|
|
5225
4573
|
var CONTENT_DIR = "content";
|
|
5226
4574
|
var NOTES_DIR = "notes";
|
|
@@ -5261,13 +4609,13 @@ var DEFAULT_CONFIG = {
|
|
|
5261
4609
|
}
|
|
5262
4610
|
};
|
|
5263
4611
|
function loadUniversityConfig(rootDir) {
|
|
5264
|
-
const configPath =
|
|
5265
|
-
if (!
|
|
4612
|
+
const configPath = path8.join(rootDir, UNIVERSITY_DIR, CONFIG_FILE);
|
|
4613
|
+
if (!fs7.existsSync(configPath)) {
|
|
5266
4614
|
return { ...DEFAULT_CONFIG };
|
|
5267
4615
|
}
|
|
5268
4616
|
try {
|
|
5269
|
-
const raw =
|
|
5270
|
-
const data =
|
|
4617
|
+
const raw = fs7.readFileSync(configPath, "utf8");
|
|
4618
|
+
const data = yaml6.load(raw);
|
|
5271
4619
|
if (!data) return { ...DEFAULT_CONFIG };
|
|
5272
4620
|
return {
|
|
5273
4621
|
branding: { ...DEFAULT_BRANDING, ...data.branding || {} },
|
|
@@ -5288,11 +4636,11 @@ function loadUniversityConfig(rootDir) {
|
|
|
5288
4636
|
}
|
|
5289
4637
|
}
|
|
5290
4638
|
function loadUniversityIndex(rootDir) {
|
|
5291
|
-
const indexPath =
|
|
5292
|
-
if (!
|
|
4639
|
+
const indexPath = path8.join(rootDir, UNIVERSITY_DIR, INDEX_FILE3);
|
|
4640
|
+
if (!fs7.existsSync(indexPath)) return null;
|
|
5293
4641
|
try {
|
|
5294
|
-
const raw =
|
|
5295
|
-
return
|
|
4642
|
+
const raw = fs7.readFileSync(indexPath, "utf8");
|
|
4643
|
+
return yaml6.load(raw);
|
|
5296
4644
|
} catch {
|
|
5297
4645
|
return null;
|
|
5298
4646
|
}
|
|
@@ -5301,14 +4649,14 @@ function parseFrontmatter(content) {
|
|
|
5301
4649
|
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
5302
4650
|
if (!match) return null;
|
|
5303
4651
|
try {
|
|
5304
|
-
const frontmatter =
|
|
4652
|
+
const frontmatter = yaml6.load(match[1]);
|
|
5305
4653
|
return { frontmatter, body: match[2].trim() };
|
|
5306
4654
|
} catch {
|
|
5307
4655
|
return null;
|
|
5308
4656
|
}
|
|
5309
4657
|
}
|
|
5310
4658
|
function serializeFrontmatter(frontmatter, body) {
|
|
5311
|
-
const fm =
|
|
4659
|
+
const fm = yaml6.dump(frontmatter, { lineWidth: -1, noRefs: true, sortKeys: false });
|
|
5312
4660
|
return `---
|
|
5313
4661
|
${fm}---
|
|
5314
4662
|
|
|
@@ -5319,7 +4667,7 @@ function loadNote(rootDir, id) {
|
|
|
5319
4667
|
const filePath = resolveContentFile(rootDir, id, ".md");
|
|
5320
4668
|
if (!filePath) return null;
|
|
5321
4669
|
try {
|
|
5322
|
-
const raw =
|
|
4670
|
+
const raw = fs7.readFileSync(filePath, "utf8");
|
|
5323
4671
|
const parsed = parseFrontmatter(raw);
|
|
5324
4672
|
if (!parsed) return null;
|
|
5325
4673
|
const fm = parsed.frontmatter;
|
|
@@ -5330,19 +4678,19 @@ function loadNote(rootDir, id) {
|
|
|
5330
4678
|
}
|
|
5331
4679
|
function saveNote(rootDir, frontmatter, body) {
|
|
5332
4680
|
const subdir = frontmatter.type === "policy" ? POLICIES_DIR : NOTES_DIR;
|
|
5333
|
-
const dir =
|
|
5334
|
-
|
|
5335
|
-
const filePath =
|
|
4681
|
+
const dir = path8.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, subdir);
|
|
4682
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
4683
|
+
const filePath = path8.join(dir, `${frontmatter.id}.md`);
|
|
5336
4684
|
const content = serializeFrontmatter(frontmatter, body);
|
|
5337
|
-
|
|
4685
|
+
fs7.writeFileSync(filePath, content, "utf8");
|
|
5338
4686
|
return filePath;
|
|
5339
4687
|
}
|
|
5340
4688
|
function loadQuiz(rootDir, id) {
|
|
5341
4689
|
const filePath = resolveContentFile(rootDir, id, ".yaml");
|
|
5342
4690
|
if (!filePath) return null;
|
|
5343
4691
|
try {
|
|
5344
|
-
const raw =
|
|
5345
|
-
const data =
|
|
4692
|
+
const raw = fs7.readFileSync(filePath, "utf8");
|
|
4693
|
+
const data = yaml6.load(raw);
|
|
5346
4694
|
if (!data || !data.id) return null;
|
|
5347
4695
|
return normalizeQuiz(data);
|
|
5348
4696
|
} catch {
|
|
@@ -5350,18 +4698,18 @@ function loadQuiz(rootDir, id) {
|
|
|
5350
4698
|
}
|
|
5351
4699
|
}
|
|
5352
4700
|
function saveQuiz(rootDir, quiz) {
|
|
5353
|
-
const dir =
|
|
5354
|
-
|
|
5355
|
-
const filePath =
|
|
5356
|
-
|
|
4701
|
+
const dir = path8.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, QUIZZES_DIR);
|
|
4702
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
4703
|
+
const filePath = path8.join(dir, `${quiz.id}.yaml`);
|
|
4704
|
+
fs7.writeFileSync(filePath, yaml6.dump(quiz, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5357
4705
|
return filePath;
|
|
5358
4706
|
}
|
|
5359
4707
|
function loadPath(rootDir, id) {
|
|
5360
4708
|
const filePath = resolveContentFile(rootDir, id, ".yaml");
|
|
5361
4709
|
if (!filePath) return null;
|
|
5362
4710
|
try {
|
|
5363
|
-
const raw =
|
|
5364
|
-
const data =
|
|
4711
|
+
const raw = fs7.readFileSync(filePath, "utf8");
|
|
4712
|
+
const data = yaml6.load(raw);
|
|
5365
4713
|
if (!data || !data.id) return null;
|
|
5366
4714
|
return data;
|
|
5367
4715
|
} catch {
|
|
@@ -5369,22 +4717,22 @@ function loadPath(rootDir, id) {
|
|
|
5369
4717
|
}
|
|
5370
4718
|
}
|
|
5371
4719
|
function savePath(rootDir, lp) {
|
|
5372
|
-
const dir =
|
|
5373
|
-
|
|
5374
|
-
const filePath =
|
|
5375
|
-
|
|
4720
|
+
const dir = path8.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR, PATHS_DIR);
|
|
4721
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
4722
|
+
const filePath = path8.join(dir, `${lp.id}.yaml`);
|
|
4723
|
+
fs7.writeFileSync(filePath, yaml6.dump(lp, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5376
4724
|
return filePath;
|
|
5377
4725
|
}
|
|
5378
4726
|
function loadDiplomas(rootDir, filter) {
|
|
5379
|
-
const dir =
|
|
5380
|
-
if (!
|
|
4727
|
+
const dir = path8.join(rootDir, UNIVERSITY_DIR, DIPLOMAS_DIR);
|
|
4728
|
+
if (!fs7.existsSync(dir)) return [];
|
|
5381
4729
|
const results = [];
|
|
5382
4730
|
try {
|
|
5383
|
-
const files =
|
|
4731
|
+
const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".yaml"));
|
|
5384
4732
|
for (const file of files) {
|
|
5385
4733
|
try {
|
|
5386
|
-
const raw =
|
|
5387
|
-
const diploma =
|
|
4734
|
+
const raw = fs7.readFileSync(path8.join(dir, file), "utf8");
|
|
4735
|
+
const diploma = yaml6.load(raw);
|
|
5388
4736
|
if (!diploma || !diploma.id) continue;
|
|
5389
4737
|
if (filter?.student && diploma.student !== filter.student) continue;
|
|
5390
4738
|
if (filter?.type && diploma.type !== filter.type) continue;
|
|
@@ -5397,10 +4745,10 @@ function loadDiplomas(rootDir, filter) {
|
|
|
5397
4745
|
return results.sort((a, b) => b.earnedAt.localeCompare(a.earnedAt));
|
|
5398
4746
|
}
|
|
5399
4747
|
function saveDiploma(rootDir, diploma) {
|
|
5400
|
-
const dir =
|
|
5401
|
-
|
|
5402
|
-
const filePath =
|
|
5403
|
-
|
|
4748
|
+
const dir = path8.join(rootDir, UNIVERSITY_DIR, DIPLOMAS_DIR);
|
|
4749
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
4750
|
+
const filePath = path8.join(dir, `${diploma.id}.yaml`);
|
|
4751
|
+
fs7.writeFileSync(filePath, yaml6.dump(diploma, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5404
4752
|
return filePath;
|
|
5405
4753
|
}
|
|
5406
4754
|
function searchContent(rootDir, filter) {
|
|
@@ -5446,16 +4794,16 @@ function searchContent(rootDir, filter) {
|
|
|
5446
4794
|
return results.slice(0, limit);
|
|
5447
4795
|
}
|
|
5448
4796
|
function rebuildUniversityIndex(rootDir) {
|
|
5449
|
-
const uniDir =
|
|
5450
|
-
const contentDir =
|
|
4797
|
+
const uniDir = path8.join(rootDir, UNIVERSITY_DIR);
|
|
4798
|
+
const contentDir = path8.join(uniDir, CONTENT_DIR);
|
|
5451
4799
|
const entries = [];
|
|
5452
4800
|
for (const subdir of [NOTES_DIR, POLICIES_DIR]) {
|
|
5453
|
-
const dir =
|
|
5454
|
-
if (!
|
|
4801
|
+
const dir = path8.join(contentDir, subdir);
|
|
4802
|
+
if (!fs7.existsSync(dir)) continue;
|
|
5455
4803
|
try {
|
|
5456
|
-
for (const file of
|
|
4804
|
+
for (const file of fs7.readdirSync(dir).filter((f) => f.endsWith(".md"))) {
|
|
5457
4805
|
try {
|
|
5458
|
-
const raw =
|
|
4806
|
+
const raw = fs7.readFileSync(path8.join(dir, file), "utf8");
|
|
5459
4807
|
const parsed = parseFrontmatter(raw);
|
|
5460
4808
|
if (!parsed) continue;
|
|
5461
4809
|
const fm = parsed.frontmatter;
|
|
@@ -5478,13 +4826,13 @@ function rebuildUniversityIndex(rootDir) {
|
|
|
5478
4826
|
} catch {
|
|
5479
4827
|
}
|
|
5480
4828
|
}
|
|
5481
|
-
const quizDir =
|
|
5482
|
-
if (
|
|
4829
|
+
const quizDir = path8.join(contentDir, QUIZZES_DIR);
|
|
4830
|
+
if (fs7.existsSync(quizDir)) {
|
|
5483
4831
|
try {
|
|
5484
|
-
for (const file of
|
|
4832
|
+
for (const file of fs7.readdirSync(quizDir).filter((f) => f.endsWith(".yaml"))) {
|
|
5485
4833
|
try {
|
|
5486
|
-
const raw =
|
|
5487
|
-
const quiz =
|
|
4834
|
+
const raw = fs7.readFileSync(path8.join(quizDir, file), "utf8");
|
|
4835
|
+
const quiz = yaml6.load(raw);
|
|
5488
4836
|
if (!quiz || !quiz.id) continue;
|
|
5489
4837
|
entries.push({
|
|
5490
4838
|
id: quiz.id,
|
|
@@ -5505,13 +4853,13 @@ function rebuildUniversityIndex(rootDir) {
|
|
|
5505
4853
|
} catch {
|
|
5506
4854
|
}
|
|
5507
4855
|
}
|
|
5508
|
-
const pathDir =
|
|
5509
|
-
if (
|
|
4856
|
+
const pathDir = path8.join(contentDir, PATHS_DIR);
|
|
4857
|
+
if (fs7.existsSync(pathDir)) {
|
|
5510
4858
|
try {
|
|
5511
|
-
for (const file of
|
|
4859
|
+
for (const file of fs7.readdirSync(pathDir).filter((f) => f.endsWith(".yaml"))) {
|
|
5512
4860
|
try {
|
|
5513
|
-
const raw =
|
|
5514
|
-
const lp =
|
|
4861
|
+
const raw = fs7.readFileSync(path8.join(pathDir, file), "utf8");
|
|
4862
|
+
const lp = yaml6.load(raw);
|
|
5515
4863
|
if (!lp || !lp.id) continue;
|
|
5516
4864
|
entries.push({
|
|
5517
4865
|
id: lp.id,
|
|
@@ -5532,10 +4880,10 @@ function rebuildUniversityIndex(rootDir) {
|
|
|
5532
4880
|
}
|
|
5533
4881
|
}
|
|
5534
4882
|
let diplomaCount = 0;
|
|
5535
|
-
const diplomaDir =
|
|
5536
|
-
if (
|
|
4883
|
+
const diplomaDir = path8.join(uniDir, DIPLOMAS_DIR);
|
|
4884
|
+
if (fs7.existsSync(diplomaDir)) {
|
|
5537
4885
|
try {
|
|
5538
|
-
diplomaCount =
|
|
4886
|
+
diplomaCount = fs7.readdirSync(diplomaDir).filter((f) => f.endsWith(".yaml")).length;
|
|
5539
4887
|
} catch {
|
|
5540
4888
|
}
|
|
5541
4889
|
}
|
|
@@ -5546,9 +4894,9 @@ function rebuildUniversityIndex(rootDir) {
|
|
|
5546
4894
|
entries,
|
|
5547
4895
|
diplomaCount
|
|
5548
4896
|
};
|
|
5549
|
-
|
|
5550
|
-
const indexPath =
|
|
5551
|
-
|
|
4897
|
+
fs7.mkdirSync(uniDir, { recursive: true });
|
|
4898
|
+
const indexPath = path8.join(uniDir, INDEX_FILE3);
|
|
4899
|
+
fs7.writeFileSync(indexPath, yaml6.dump(index, { lineWidth: -1, noRefs: true }), "utf8");
|
|
5552
4900
|
return index;
|
|
5553
4901
|
}
|
|
5554
4902
|
function validateUniversityContent(rootDir, options) {
|
|
@@ -5740,10 +5088,10 @@ function getOnboardingSequence(rootDir, student) {
|
|
|
5740
5088
|
};
|
|
5741
5089
|
}
|
|
5742
5090
|
function resolveContentFile(rootDir, id, ext) {
|
|
5743
|
-
const contentDir =
|
|
5091
|
+
const contentDir = path8.join(rootDir, UNIVERSITY_DIR, CONTENT_DIR);
|
|
5744
5092
|
for (const subdir of [NOTES_DIR, POLICIES_DIR, QUIZZES_DIR, PATHS_DIR]) {
|
|
5745
|
-
const filePath =
|
|
5746
|
-
if (
|
|
5093
|
+
const filePath = path8.join(contentDir, subdir, `${id}${ext}`);
|
|
5094
|
+
if (fs7.existsSync(filePath)) return filePath;
|
|
5747
5095
|
}
|
|
5748
5096
|
return null;
|
|
5749
5097
|
}
|
|
@@ -5775,10 +5123,10 @@ function normalizeQuiz(quiz) {
|
|
|
5775
5123
|
}
|
|
5776
5124
|
function loadKnownSymbols(rootDir) {
|
|
5777
5125
|
const symbols = /* @__PURE__ */ new Set();
|
|
5778
|
-
const scanIndexPath =
|
|
5779
|
-
if (!
|
|
5126
|
+
const scanIndexPath = path8.join(rootDir, ".paradigm", "scan-index.json");
|
|
5127
|
+
if (!fs7.existsSync(scanIndexPath)) return symbols;
|
|
5780
5128
|
try {
|
|
5781
|
-
const raw =
|
|
5129
|
+
const raw = fs7.readFileSync(scanIndexPath, "utf8");
|
|
5782
5130
|
const index = JSON.parse(raw);
|
|
5783
5131
|
if (index.symbols && Array.isArray(index.symbols)) {
|
|
5784
5132
|
for (const sym of index.symbols) {
|
|
@@ -5810,17 +5158,17 @@ function isContentStale(rootDir, entry, _symbol) {
|
|
|
5810
5158
|
if (!entry.updated) return false;
|
|
5811
5159
|
const contentUpdated = new Date(entry.updated).getTime();
|
|
5812
5160
|
if (isNaN(contentUpdated)) return false;
|
|
5813
|
-
const scanIndexPath =
|
|
5814
|
-
if (!
|
|
5161
|
+
const scanIndexPath = path8.join(rootDir, ".paradigm", "scan-index.json");
|
|
5162
|
+
if (!fs7.existsSync(scanIndexPath)) return false;
|
|
5815
5163
|
try {
|
|
5816
|
-
const raw =
|
|
5164
|
+
const raw = fs7.readFileSync(scanIndexPath, "utf8");
|
|
5817
5165
|
const index = JSON.parse(raw);
|
|
5818
5166
|
if (index.symbols && Array.isArray(index.symbols)) {
|
|
5819
5167
|
for (const sym of index.symbols) {
|
|
5820
5168
|
if (sym.symbol === _symbol && sym.filePath) {
|
|
5821
|
-
const purposePath =
|
|
5822
|
-
if (
|
|
5823
|
-
const stat =
|
|
5169
|
+
const purposePath = path8.join(rootDir, sym.filePath);
|
|
5170
|
+
if (fs7.existsSync(purposePath)) {
|
|
5171
|
+
const stat = fs7.statSync(purposePath);
|
|
5824
5172
|
if (stat.mtime.getTime() > contentUpdated) {
|
|
5825
5173
|
return true;
|
|
5826
5174
|
}
|
|
@@ -5935,10 +5283,10 @@ async function rebuildStaticFiles(rootDir, ctx) {
|
|
|
5935
5283
|
} else {
|
|
5936
5284
|
aggregation = await aggregateFromDirectory(rootDir);
|
|
5937
5285
|
}
|
|
5938
|
-
const projectName = ctx?.projectName ||
|
|
5939
|
-
const paradigmDir =
|
|
5940
|
-
if (!
|
|
5941
|
-
|
|
5286
|
+
const projectName = ctx?.projectName || path9.basename(rootDir);
|
|
5287
|
+
const paradigmDir = path9.join(rootDir, ".paradigm");
|
|
5288
|
+
if (!fs8.existsSync(paradigmDir)) {
|
|
5289
|
+
fs8.mkdirSync(paradigmDir, { recursive: true });
|
|
5942
5290
|
}
|
|
5943
5291
|
const scanIndex = generateScanIndex(
|
|
5944
5292
|
{
|
|
@@ -5948,22 +5296,22 @@ async function rebuildStaticFiles(rootDir, ctx) {
|
|
|
5948
5296
|
},
|
|
5949
5297
|
{ projectName }
|
|
5950
5298
|
);
|
|
5951
|
-
const scanIndexPath =
|
|
5952
|
-
|
|
5299
|
+
const scanIndexPath = path9.join(paradigmDir, "scan-index.json");
|
|
5300
|
+
fs8.writeFileSync(scanIndexPath, serializeScanIndex(scanIndex), "utf8");
|
|
5953
5301
|
filesWritten.push(".paradigm/scan-index.json");
|
|
5954
5302
|
const navigatorData = buildNavigatorData(rootDir, aggregation);
|
|
5955
|
-
const navigatorPath =
|
|
5956
|
-
|
|
5303
|
+
const navigatorPath = path9.join(paradigmDir, "navigator.yaml");
|
|
5304
|
+
fs8.writeFileSync(
|
|
5957
5305
|
navigatorPath,
|
|
5958
|
-
|
|
5306
|
+
yaml7.dump(navigatorData, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }),
|
|
5959
5307
|
"utf8"
|
|
5960
5308
|
);
|
|
5961
5309
|
filesWritten.push(".paradigm/navigator.yaml");
|
|
5962
5310
|
const flowIndex = generateFlowIndex(rootDir, aggregation.purposeFiles);
|
|
5963
5311
|
let flowCount = 0;
|
|
5964
5312
|
if (flowIndex && Object.keys(flowIndex.flows).length > 0) {
|
|
5965
|
-
const flowIndexPath =
|
|
5966
|
-
|
|
5313
|
+
const flowIndexPath = path9.join(paradigmDir, "flow-index.json");
|
|
5314
|
+
fs8.writeFileSync(flowIndexPath, JSON.stringify(flowIndex, null, 2), "utf8");
|
|
5967
5315
|
filesWritten.push(".paradigm/flow-index.json");
|
|
5968
5316
|
flowCount = Object.keys(flowIndex.flows).length;
|
|
5969
5317
|
}
|
|
@@ -6006,8 +5354,8 @@ async function rebuildStaticFiles(rootDir, ctx) {
|
|
|
6006
5354
|
}
|
|
6007
5355
|
let universityStats;
|
|
6008
5356
|
try {
|
|
6009
|
-
const uniDir =
|
|
6010
|
-
if (
|
|
5357
|
+
const uniDir = path9.join(rootDir, ".paradigm", "university");
|
|
5358
|
+
if (fs8.existsSync(uniDir)) {
|
|
6011
5359
|
const uniIndex = rebuildUniversityIndex(rootDir);
|
|
6012
5360
|
if (uniIndex.totalContent > 0 || uniIndex.diplomaCount > 0) {
|
|
6013
5361
|
universityStats = {
|
|
@@ -6105,7 +5453,7 @@ function buildNavigatorData(rootDir, aggregation) {
|
|
|
6105
5453
|
function buildStructure(rootDir) {
|
|
6106
5454
|
const structure = {};
|
|
6107
5455
|
for (const [category, patterns] of Object.entries(DIRECTORY_PATTERNS)) {
|
|
6108
|
-
const existingPaths = patterns.filter((p) =>
|
|
5456
|
+
const existingPaths = patterns.filter((p) => fs8.existsSync(path9.join(rootDir, p)));
|
|
6109
5457
|
if (existingPaths.length > 0) {
|
|
6110
5458
|
const symbolInfo = Object.values(SYMBOL_CATEGORIES).find((s) => s.category === category);
|
|
6111
5459
|
structure[category] = { paths: existingPaths, symbol: symbolInfo?.prefix || "@" };
|
|
@@ -6116,7 +5464,7 @@ function buildStructure(rootDir) {
|
|
|
6116
5464
|
function buildKeyFiles(rootDir) {
|
|
6117
5465
|
const keyFiles = {};
|
|
6118
5466
|
for (const [category, patterns] of Object.entries(KEY_FILE_PATTERNS)) {
|
|
6119
|
-
const existingPaths = patterns.filter((p) =>
|
|
5467
|
+
const existingPaths = patterns.filter((p) => fs8.existsSync(path9.join(rootDir, p)));
|
|
6120
5468
|
if (existingPaths.length > 0) {
|
|
6121
5469
|
keyFiles[category] = existingPaths;
|
|
6122
5470
|
}
|
|
@@ -6132,10 +5480,10 @@ function buildSkipPatterns(rootDir) {
|
|
|
6132
5480
|
unless_testing: [...DEFAULT_SKIP_PATTERNS.unless_testing],
|
|
6133
5481
|
unless_docs: [...DEFAULT_SKIP_PATTERNS.unless_docs]
|
|
6134
5482
|
};
|
|
6135
|
-
const gitignorePath =
|
|
6136
|
-
if (
|
|
5483
|
+
const gitignorePath = path9.join(rootDir, ".gitignore");
|
|
5484
|
+
if (fs8.existsSync(gitignorePath)) {
|
|
6137
5485
|
try {
|
|
6138
|
-
const content =
|
|
5486
|
+
const content = fs8.readFileSync(gitignorePath, "utf8");
|
|
6139
5487
|
const gitignorePatterns = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).filter(
|
|
6140
5488
|
(line) => line.endsWith("/") || line.includes("*") || ["node_modules", "dist", "build", ".cache"].some((p) => line.includes(p))
|
|
6141
5489
|
).slice(0, 20);
|
|
@@ -6184,11 +5532,11 @@ function buildSymbolMap(symbols, purposeFiles, _rootDir) {
|
|
|
6184
5532
|
symbolMap[symbolId] = symbol.filePath;
|
|
6185
5533
|
} else {
|
|
6186
5534
|
const matchingPurpose = purposeFiles.find((pf) => {
|
|
6187
|
-
const dir =
|
|
5535
|
+
const dir = path9.dirname(pf);
|
|
6188
5536
|
return dir.toLowerCase().includes(symbol.id.toLowerCase());
|
|
6189
5537
|
});
|
|
6190
5538
|
if (matchingPurpose) {
|
|
6191
|
-
symbolMap[symbolId] =
|
|
5539
|
+
symbolMap[symbolId] = path9.dirname(matchingPurpose) + "/";
|
|
6192
5540
|
}
|
|
6193
5541
|
}
|
|
6194
5542
|
}
|
|
@@ -6199,8 +5547,8 @@ function generateFlowIndex(rootDir, purposeFiles) {
|
|
|
6199
5547
|
const symbolToFlows = {};
|
|
6200
5548
|
for (const filePath of purposeFiles) {
|
|
6201
5549
|
try {
|
|
6202
|
-
const content =
|
|
6203
|
-
const data =
|
|
5550
|
+
const content = fs8.readFileSync(filePath, "utf8");
|
|
5551
|
+
const data = yaml7.load(content);
|
|
6204
5552
|
if (!data?.flows) continue;
|
|
6205
5553
|
if (Array.isArray(data.flows)) {
|
|
6206
5554
|
for (const flowItem of data.flows) {
|
|
@@ -6213,7 +5561,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
|
|
|
6213
5561
|
id: flowId,
|
|
6214
5562
|
description: flow.description || "",
|
|
6215
5563
|
steps,
|
|
6216
|
-
definedIn:
|
|
5564
|
+
definedIn: path9.relative(rootDir, filePath)
|
|
6217
5565
|
};
|
|
6218
5566
|
indexFlowSymbols(flowId, steps, symbolToFlows);
|
|
6219
5567
|
}
|
|
@@ -6229,7 +5577,7 @@ function generateFlowIndex(rootDir, purposeFiles) {
|
|
|
6229
5577
|
trigger: flowDef.trigger,
|
|
6230
5578
|
steps,
|
|
6231
5579
|
validation: flowDef.validation,
|
|
6232
|
-
definedIn:
|
|
5580
|
+
definedIn: path9.relative(rootDir, filePath)
|
|
6233
5581
|
};
|
|
6234
5582
|
indexFlowSymbols(flowId, steps, symbolToFlows);
|
|
6235
5583
|
}
|
|
@@ -6296,12 +5644,6 @@ export {
|
|
|
6296
5644
|
getReferencesFrom,
|
|
6297
5645
|
getSymbolCounts,
|
|
6298
5646
|
getAllSymbols,
|
|
6299
|
-
loadGlobalAntipatterns,
|
|
6300
|
-
loadGlobalDecisions,
|
|
6301
|
-
loadGlobalPreferences,
|
|
6302
|
-
recordGlobalAntipattern,
|
|
6303
|
-
recordGlobalDecision,
|
|
6304
|
-
getSessionTracker,
|
|
6305
5647
|
trackToolCall,
|
|
6306
5648
|
trackResourceRead,
|
|
6307
5649
|
addToolBreadcrumb,
|