context-mode 1.0.93 → 1.0.95
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/build/cli.d.ts +2 -0
- package/build/cli.js +26 -6
- package/build/server.js +51 -12
- package/build/session/analytics.d.ts +11 -2
- package/build/session/analytics.js +38 -32
- package/cli.bundle.mjs +106 -106
- package/hooks/ensure-deps.mjs +5 -3
- package/insight/server.mjs +21 -0
- package/insight/src/routes/index.tsx +5 -11
- package/insight/src/routes/sessions.tsx +3 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +87 -87
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.95"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "context-mode",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Claude Code MCP plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
16
|
-
"version": "1.0.
|
|
16
|
+
"version": "1.0.95",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Mert Koseoğlu"
|
|
19
19
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.95",
|
|
4
4
|
"description": "MCP server that saves 98% of your context window with session continuity. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and automatic state restore across compactions.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Context Mode",
|
|
4
4
|
"kind": "tool",
|
|
5
5
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.95",
|
|
7
7
|
"sandbox": {
|
|
8
8
|
"mode": "permissive",
|
|
9
9
|
"filesystem_access": "full",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.95",
|
|
4
4
|
"description": "OpenClaw plugin that saves 98% of your context window. Sandboxed code execution in 11 languages, FTS5 knowledge base with BM25 ranking, and intent-driven search.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/build/cli.d.ts
CHANGED
|
@@ -13,3 +13,5 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/** Normalize Windows backslash paths to forward slashes for Bash (MSYS2) compatibility. */
|
|
15
15
|
export declare function toUnixPath(p: string): string;
|
|
16
|
+
export declare function npmExecFile(args: string[], opts?: Record<string, unknown>): void;
|
|
17
|
+
export declare function npmExec(command: string, opts?: Record<string, unknown>): void;
|
package/build/cli.js
CHANGED
|
@@ -120,6 +120,26 @@ else {
|
|
|
120
120
|
export function toUnixPath(p) {
|
|
121
121
|
return p.replace(/\\/g, "/");
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Windows-safe npm execution. On Windows:
|
|
125
|
+
* - "npm" → "npm.cmd" (Node won't resolve via PATHEXT in execFile)
|
|
126
|
+
* - shell: true required (Node v20+ CVE-2024-27980 mitigation)
|
|
127
|
+
* See: https://github.com/mksglu/context-mode/issues/344
|
|
128
|
+
*/
|
|
129
|
+
const isWin = process.platform === "win32";
|
|
130
|
+
export function npmExecFile(args, opts = {}) {
|
|
131
|
+
execFileSync(isWin ? "npm.cmd" : "npm", args, {
|
|
132
|
+
...opts,
|
|
133
|
+
...(isWin ? { shell: true } : {}),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
export function npmExec(command, opts = {}) {
|
|
137
|
+
const { execSync: es } = require("node:child_process");
|
|
138
|
+
es(isWin ? command.replace(/^npm /, "npm.cmd ") : command, {
|
|
139
|
+
...opts,
|
|
140
|
+
...(isWin ? { shell: true } : {}),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
123
143
|
function defaultPluginRoot() {
|
|
124
144
|
const __filename = fileURLToPath(import.meta.url);
|
|
125
145
|
const __dirname = dirname(__filename);
|
|
@@ -400,7 +420,7 @@ async function insight(port) {
|
|
|
400
420
|
if (!existsSync(join(cacheDir, "node_modules"))) {
|
|
401
421
|
console.log("Installing dependencies (first run)...");
|
|
402
422
|
try {
|
|
403
|
-
|
|
423
|
+
npmExec("npm install --production=false", { cwd: cacheDir, stdio: "inherit", timeout: 300000 });
|
|
404
424
|
}
|
|
405
425
|
catch {
|
|
406
426
|
// Clean up partial install so next run retries fresh
|
|
@@ -512,12 +532,12 @@ async function upgrade() {
|
|
|
512
532
|
}
|
|
513
533
|
// Step 2: Install dependencies + build
|
|
514
534
|
s.start("Installing dependencies & building");
|
|
515
|
-
|
|
535
|
+
npmExecFile(["install", "--no-audit", "--no-fund"], {
|
|
516
536
|
cwd: srcDir,
|
|
517
537
|
stdio: "pipe",
|
|
518
538
|
timeout: 120000,
|
|
519
539
|
});
|
|
520
|
-
|
|
540
|
+
npmExecFile(["run", "build"], {
|
|
521
541
|
cwd: srcDir,
|
|
522
542
|
stdio: "pipe",
|
|
523
543
|
timeout: 60000,
|
|
@@ -558,7 +578,7 @@ async function upgrade() {
|
|
|
558
578
|
p.log.info(color.dim(" Registry synced to " + pluginRoot));
|
|
559
579
|
// Install production deps
|
|
560
580
|
s.start("Installing production dependencies");
|
|
561
|
-
|
|
581
|
+
npmExecFile(["install", "--production", "--no-audit", "--no-fund"], {
|
|
562
582
|
cwd: pluginRoot,
|
|
563
583
|
stdio: "pipe",
|
|
564
584
|
timeout: 60000,
|
|
@@ -568,7 +588,7 @@ async function upgrade() {
|
|
|
568
588
|
// Rebuild native addons for current Node.js ABI (fixes #131)
|
|
569
589
|
s.start("Rebuilding native addons");
|
|
570
590
|
try {
|
|
571
|
-
|
|
591
|
+
npmExecFile(["rebuild", "better-sqlite3"], {
|
|
572
592
|
cwd: pluginRoot,
|
|
573
593
|
stdio: "pipe",
|
|
574
594
|
timeout: 60000,
|
|
@@ -587,7 +607,7 @@ async function upgrade() {
|
|
|
587
607
|
// Update global npm
|
|
588
608
|
s.start("Updating npm global package");
|
|
589
609
|
try {
|
|
590
|
-
|
|
610
|
+
npmExecFile(["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
|
|
591
611
|
stdio: "pipe",
|
|
592
612
|
timeout: 30000,
|
|
593
613
|
});
|
package/build/server.js
CHANGED
|
@@ -1700,6 +1700,25 @@ server.registerTool("ctx_upgrade", {
|
|
|
1700
1700
|
const pluginRoot = existsSync(resolve(__pkg_dir, "package.json")) ? __pkg_dir : dirname(__pkg_dir);
|
|
1701
1701
|
const bundlePath = resolve(pluginRoot, "cli.bundle.mjs");
|
|
1702
1702
|
const fallbackPath = resolve(pluginRoot, "build", "cli.js");
|
|
1703
|
+
// Clean up insight-cache on upgrade so next ctx_insight does fresh build
|
|
1704
|
+
try {
|
|
1705
|
+
const sessDir = getSessionDir();
|
|
1706
|
+
const insightCacheDir = join(dirname(sessDir), "insight-cache");
|
|
1707
|
+
if (existsSync(insightCacheDir)) {
|
|
1708
|
+
// Kill any running insight server first
|
|
1709
|
+
try {
|
|
1710
|
+
if (process.platform === "win32") {
|
|
1711
|
+
execSync('for /f "tokens=5" %a in (\'netstat -ano ^| findstr :4747\') do taskkill /F /PID %a', { stdio: "pipe" });
|
|
1712
|
+
}
|
|
1713
|
+
else {
|
|
1714
|
+
execSync("lsof -ti:4747 | xargs kill 2>/dev/null", { stdio: "pipe" });
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
catch { /* no process to kill */ }
|
|
1718
|
+
rmSync(insightCacheDir, { recursive: true, force: true });
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
catch { /* best effort — don't block upgrade */ }
|
|
1703
1722
|
let cmd;
|
|
1704
1723
|
if (existsSync(bundlePath)) {
|
|
1705
1724
|
cmd = `node "${bundlePath}" upgrade`;
|
|
@@ -1726,13 +1745,13 @@ server.registerTool("ctx_upgrade", {
|
|
|
1726
1745
|
`console.log("- [x] Starting inline upgrade (no CLI found)");`,
|
|
1727
1746
|
`execFileSync("git",["clone","--depth","1","${repoUrl}",T],{stdio:"inherit"});`,
|
|
1728
1747
|
`console.log("- [x] Cloned latest source");`,
|
|
1729
|
-
`execFileSync("npm",["install"],{cwd:T,stdio:"inherit"});`,
|
|
1730
|
-
`execFileSync("npm",["run","build"],{cwd:T,stdio:"inherit"});`,
|
|
1748
|
+
`execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});`,
|
|
1749
|
+
`execFileSync(process.platform==="win32"?"npm.cmd":"npm",["run","build"],{cwd:T,stdio:"inherit",shell:process.platform==="win32"});`,
|
|
1731
1750
|
`console.log("- [x] Built from source");`,
|
|
1732
1751
|
...copyDirs.map((d) => `if(existsSync(join(T,${JSON.stringify(d)})))cpSync(join(T,${JSON.stringify(d)}),join(P,${JSON.stringify(d)}),{recursive:true,force:true});`),
|
|
1733
1752
|
...copyFiles.map((f) => `if(existsSync(join(T,${JSON.stringify(f)})))cpSync(join(T,${JSON.stringify(f)}),join(P,${JSON.stringify(f)}),{force:true});`),
|
|
1734
1753
|
`console.log("- [x] Copied build artifacts");`,
|
|
1735
|
-
`execFileSync("npm",["install","--production"],{cwd:P,stdio:"inherit"});`,
|
|
1754
|
+
`execFileSync(process.platform==="win32"?"npm.cmd":"npm",["install","--production"],{cwd:P,stdio:"inherit",shell:process.platform==="win32"});`,
|
|
1736
1755
|
`console.log("- [x] Installed production dependencies");`,
|
|
1737
1756
|
`console.log("## context-mode upgrade complete");`,
|
|
1738
1757
|
`}catch(e){`,
|
|
@@ -1904,6 +1923,7 @@ server.registerTool("ctx_insight", {
|
|
|
1904
1923
|
}
|
|
1905
1924
|
try {
|
|
1906
1925
|
const steps = [];
|
|
1926
|
+
let sourceUpdated = false;
|
|
1907
1927
|
// Ensure cache dir
|
|
1908
1928
|
mkdirSync(cacheDir, { recursive: true });
|
|
1909
1929
|
// Copy source files if needed (check by comparing server.mjs mtime)
|
|
@@ -1914,13 +1934,14 @@ server.registerTool("ctx_insight", {
|
|
|
1914
1934
|
steps.push("Copying source files...");
|
|
1915
1935
|
cpSync(insightSource, cacheDir, { recursive: true, force: true });
|
|
1916
1936
|
steps.push("Source files copied.");
|
|
1937
|
+
sourceUpdated = true;
|
|
1917
1938
|
}
|
|
1918
|
-
// Install deps if needed
|
|
1939
|
+
// Install deps if needed (also reinstall when source updated and package.json may have changed)
|
|
1919
1940
|
const hasNodeModules = existsSync(join(cacheDir, "node_modules"));
|
|
1920
|
-
if (!hasNodeModules) {
|
|
1941
|
+
if (!hasNodeModules || sourceUpdated) {
|
|
1921
1942
|
steps.push("Installing dependencies (first run, ~30s)...");
|
|
1922
1943
|
try {
|
|
1923
|
-
execSync("npm install --production=false", {
|
|
1944
|
+
execSync(process.platform === "win32" ? "npm.cmd install --production=false" : "npm install --production=false", {
|
|
1924
1945
|
cwd: cacheDir,
|
|
1925
1946
|
stdio: "pipe",
|
|
1926
1947
|
timeout: 300000,
|
|
@@ -1949,7 +1970,8 @@ server.registerTool("ctx_insight", {
|
|
|
1949
1970
|
timeout: 60000,
|
|
1950
1971
|
});
|
|
1951
1972
|
steps.push("Build complete.");
|
|
1952
|
-
// Pre-check: is port already in use?
|
|
1973
|
+
// Pre-check: is port already in use?
|
|
1974
|
+
let portOccupied = false;
|
|
1953
1975
|
try {
|
|
1954
1976
|
const { request } = await import("node:http");
|
|
1955
1977
|
await new Promise((resolve, reject) => {
|
|
@@ -1961,9 +1983,29 @@ server.registerTool("ctx_insight", {
|
|
|
1961
1983
|
req.on("timeout", () => { req.destroy(); reject(); });
|
|
1962
1984
|
req.end();
|
|
1963
1985
|
});
|
|
1964
|
-
|
|
1986
|
+
portOccupied = true;
|
|
1987
|
+
}
|
|
1988
|
+
catch {
|
|
1989
|
+
// Port is free, proceed with spawn
|
|
1990
|
+
}
|
|
1991
|
+
if (portOccupied && sourceUpdated) {
|
|
1992
|
+
// Source was updated but stale server is running on port — kill it so fresh code runs
|
|
1993
|
+
steps.push("Killing stale dashboard server (source updated)...");
|
|
1994
|
+
try {
|
|
1995
|
+
if (process.platform === "win32") {
|
|
1996
|
+
execSync(`for /f "tokens=5" %a in ('netstat -ano ^| findstr :${port}') do taskkill /F /PID %a`, { stdio: "pipe" });
|
|
1997
|
+
}
|
|
1998
|
+
else {
|
|
1999
|
+
execSync(`lsof -ti:${port} | xargs kill 2>/dev/null`, { stdio: "pipe" });
|
|
2000
|
+
}
|
|
2001
|
+
await new Promise(r => setTimeout(r, 500)); // Wait for port to free
|
|
2002
|
+
}
|
|
2003
|
+
catch { /* no process to kill — proceed anyway */ }
|
|
2004
|
+
steps.push("Stale server killed.");
|
|
2005
|
+
}
|
|
2006
|
+
else if (portOccupied) {
|
|
2007
|
+
// Source unchanged, server is running fine — just open browser
|
|
1965
2008
|
steps.push("Dashboard already running.");
|
|
1966
|
-
// Open browser anyway
|
|
1967
2009
|
const url = `http://localhost:${port}`;
|
|
1968
2010
|
const platform = process.platform;
|
|
1969
2011
|
try {
|
|
@@ -1979,9 +2021,6 @@ server.registerTool("ctx_insight", {
|
|
|
1979
2021
|
content: [{ type: "text", text: `Dashboard already running at http://localhost:${port}` }],
|
|
1980
2022
|
});
|
|
1981
2023
|
}
|
|
1982
|
-
catch {
|
|
1983
|
-
// Port is free, proceed with spawn
|
|
1984
|
-
}
|
|
1985
2024
|
// Kill any previous insight child this MCP spawned (e.g. re-invocation).
|
|
1986
2025
|
if (_insightChild && _insightChild.pid && !_insightChild.killed) {
|
|
1987
2026
|
try {
|
|
@@ -96,6 +96,16 @@ export interface FullReport {
|
|
|
96
96
|
compact_count: number;
|
|
97
97
|
resume_ready: boolean;
|
|
98
98
|
};
|
|
99
|
+
/** Persistent project memory — all events across all sessions */
|
|
100
|
+
projectMemory: {
|
|
101
|
+
total_events: number;
|
|
102
|
+
session_count: number;
|
|
103
|
+
by_category: Array<{
|
|
104
|
+
category: string;
|
|
105
|
+
count: number;
|
|
106
|
+
label: string;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
99
109
|
}
|
|
100
110
|
/** Human-readable labels for event categories. */
|
|
101
111
|
export declare const categoryLabels: Record<string, string>;
|
|
@@ -155,8 +165,7 @@ export declare class AnalyticsEngine {
|
|
|
155
165
|
* - Before/After comparison bar is the HERO — one glance = "wow"
|
|
156
166
|
* - "tokens saved" is the number people share
|
|
157
167
|
* - Per-tool breakdown shows what each tool SAVED, sorted by impact
|
|
158
|
-
* -
|
|
168
|
+
* - Project memory: category bars showing persistent data across sessions
|
|
159
169
|
* - No: Pct column, category tables, tips, jargon
|
|
160
|
-
* - Under 22 lines for heavy sessions, under 10 for fresh
|
|
161
170
|
*/
|
|
162
171
|
export declare function formatReport(report: FullReport, version?: string, latestVersion?: string | null): string;
|
|
@@ -199,6 +199,16 @@ export class AnalyticsEngine {
|
|
|
199
199
|
: "",
|
|
200
200
|
why: categoryHints[row.category] || "Survives context resets",
|
|
201
201
|
}));
|
|
202
|
+
// ── Project-wide persistent memory (all sessions, no session_id filter) ──
|
|
203
|
+
const projectTotals = this.db.prepare("SELECT COUNT(*) as cnt, COUNT(DISTINCT session_id) as sessions FROM session_events").get();
|
|
204
|
+
const projectByCategory = this.db.prepare("SELECT category, COUNT(*) as cnt FROM session_events GROUP BY category ORDER BY cnt DESC").all();
|
|
205
|
+
const projectMemoryByCategory = projectByCategory
|
|
206
|
+
.filter((row) => row.cnt > 0)
|
|
207
|
+
.map((row) => ({
|
|
208
|
+
category: row.category,
|
|
209
|
+
count: row.cnt,
|
|
210
|
+
label: categoryLabels[row.category] || row.category,
|
|
211
|
+
}));
|
|
202
212
|
return {
|
|
203
213
|
savings: {
|
|
204
214
|
processed_kb: Math.round(totalProcessed / 1024 * 10) / 10,
|
|
@@ -223,6 +233,11 @@ export class AnalyticsEngine {
|
|
|
223
233
|
compact_count: compactCount,
|
|
224
234
|
resume_ready: resumeReady,
|
|
225
235
|
},
|
|
236
|
+
projectMemory: {
|
|
237
|
+
total_events: projectTotals.cnt,
|
|
238
|
+
session_count: projectTotals.sessions,
|
|
239
|
+
by_category: projectMemoryByCategory,
|
|
240
|
+
},
|
|
226
241
|
};
|
|
227
242
|
}
|
|
228
243
|
}
|
|
@@ -266,6 +281,24 @@ function dataBar(bytes, maxBytes, width = 40) {
|
|
|
266
281
|
const filled = Math.max(1, Math.round((bytes / maxBytes) * width));
|
|
267
282
|
return "█".repeat(Math.min(filled, width)) + "░".repeat(Math.max(0, width - filled));
|
|
268
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Render project memory section with category bars.
|
|
286
|
+
* Shows persistent event data across all sessions.
|
|
287
|
+
*/
|
|
288
|
+
function renderProjectMemory(pm) {
|
|
289
|
+
if (pm.total_events === 0)
|
|
290
|
+
return [];
|
|
291
|
+
const out = [];
|
|
292
|
+
out.push("");
|
|
293
|
+
const sessionLabel = pm.session_count === 1 ? "1 session" : `${pm.session_count} sessions`;
|
|
294
|
+
out.push(`${fmtNum(pm.total_events)} events remembered across ${sessionLabel} \u2014 searchable after compact & restart`);
|
|
295
|
+
out.push("");
|
|
296
|
+
const maxCount = pm.by_category.length > 0 ? pm.by_category[0].count : 1;
|
|
297
|
+
for (const cat of pm.by_category) {
|
|
298
|
+
out.push(` ${cat.label.padEnd(18)} ${String(cat.count).padStart(5)} ${dataBar(cat.count, maxCount, 30)}`);
|
|
299
|
+
}
|
|
300
|
+
return out;
|
|
301
|
+
}
|
|
269
302
|
/**
|
|
270
303
|
* Render a FullReport as a visual savings dashboard designed for screenshotting.
|
|
271
304
|
*
|
|
@@ -273,9 +306,8 @@ function dataBar(bytes, maxBytes, width = 40) {
|
|
|
273
306
|
* - Before/After comparison bar is the HERO — one glance = "wow"
|
|
274
307
|
* - "tokens saved" is the number people share
|
|
275
308
|
* - Per-tool breakdown shows what each tool SAVED, sorted by impact
|
|
276
|
-
* -
|
|
309
|
+
* - Project memory: category bars showing persistent data across sessions
|
|
277
310
|
* - No: Pct column, category tables, tips, jargon
|
|
278
|
-
* - Under 22 lines for heavy sessions, under 10 for fresh
|
|
279
311
|
*/
|
|
280
312
|
export function formatReport(report, version, latestVersion) {
|
|
281
313
|
const lines = [];
|
|
@@ -297,6 +329,8 @@ export function formatReport(report, version, latestVersion) {
|
|
|
297
329
|
else {
|
|
298
330
|
lines.push(`${kb(totalReturned)} entered context | 0 tokens saved`);
|
|
299
331
|
}
|
|
332
|
+
// Project memory
|
|
333
|
+
lines.push(...renderProjectMemory(report.projectMemory));
|
|
300
334
|
// Footer
|
|
301
335
|
lines.push("");
|
|
302
336
|
const versionStr = version ? `v${version}` : "context-mode";
|
|
@@ -342,36 +376,8 @@ export function formatReport(report, version, latestVersion) {
|
|
|
342
376
|
lines.push(` ${name.padEnd(22)} ${String(t.calls).padStart(4)} calls ${kb(t.estimatedSaved).padStart(8)} saved`);
|
|
343
377
|
}
|
|
344
378
|
}
|
|
345
|
-
// ──
|
|
346
|
-
|
|
347
|
-
lines.push("");
|
|
348
|
-
const cats = report.continuity.by_category;
|
|
349
|
-
// Pick the top 3-4 most impactful categories for a human-readable summary
|
|
350
|
-
const highlights = [];
|
|
351
|
-
const fileCount = cats.find(c => c.category === "file")?.count;
|
|
352
|
-
const gitCount = cats.find(c => c.category === "git")?.count;
|
|
353
|
-
const promptCount = cats.find(c => c.category === "prompt")?.count;
|
|
354
|
-
const errorCount = cats.find(c => c.category === "error")?.count;
|
|
355
|
-
const taskCount = cats.find(c => c.category === "task")?.count;
|
|
356
|
-
if (fileCount)
|
|
357
|
-
highlights.push(`${fileCount} files`);
|
|
358
|
-
if (gitCount)
|
|
359
|
-
highlights.push(`${gitCount} git ops`);
|
|
360
|
-
if (promptCount)
|
|
361
|
-
highlights.push(`${promptCount} prompts`);
|
|
362
|
-
if (errorCount)
|
|
363
|
-
highlights.push(`${errorCount} errors`);
|
|
364
|
-
if (taskCount)
|
|
365
|
-
highlights.push(`${taskCount} tasks`);
|
|
366
|
-
const summary = highlights.length > 0 ? ` · ${highlights.join(", ")}` : "";
|
|
367
|
-
if (report.continuity.compact_count > 0) {
|
|
368
|
-
lines.push(`${fmtNum(report.continuity.total_events)} events remembered across ${report.continuity.compact_count} compaction${report.continuity.compact_count !== 1 ? "s" : ""}${summary}`);
|
|
369
|
-
lines.push("Zero knowledge lost — picks up exactly where you left off.");
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
lines.push(`${fmtNum(report.continuity.total_events)} events tracked${summary}`);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
379
|
+
// ── Project memory — persistent across sessions ──
|
|
380
|
+
lines.push(...renderProjectMemory(report.projectMemory));
|
|
375
381
|
// ── Footer ──
|
|
376
382
|
lines.push("");
|
|
377
383
|
const versionStr = version ? `v${version}` : "context-mode";
|