@pcircle/memesh 4.5.1 → 4.6.1
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 +5 -3
- package/.claude-plugin/plugin.json +6 -4
- package/AGENTS.md +116 -0
- package/README.de.md +141 -48
- package/README.md +173 -48
- package/README.zh-TW.md +142 -48
- package/dashboard/dist/index.html +15 -14
- package/dist/cli/view-live.js +3 -3
- package/dist/core/analytics.d.ts +9 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +36 -18
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js +4 -9
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/briefing.d.ts +8 -0
- package/dist/core/briefing.d.ts.map +1 -0
- package/dist/core/briefing.js +92 -0
- package/dist/core/briefing.js.map +1 -0
- package/dist/core/capture-flag.d.ts +5 -0
- package/dist/core/capture-flag.d.ts.map +1 -0
- package/dist/core/capture-flag.js +10 -0
- package/dist/core/capture-flag.js.map +1 -0
- package/dist/core/config.d.ts +0 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/conflict-candidates.d.ts +20 -0
- package/dist/core/conflict-candidates.d.ts.map +1 -0
- package/dist/core/conflict-candidates.js +79 -0
- package/dist/core/conflict-candidates.js.map +1 -0
- package/dist/core/conflict-judge.d.ts +47 -0
- package/dist/core/conflict-judge.d.ts.map +1 -0
- package/dist/core/conflict-judge.js +189 -0
- package/dist/core/conflict-judge.js.map +1 -0
- package/dist/core/demo.d.ts.map +1 -1
- package/dist/core/demo.js +1 -1
- package/dist/core/demo.js.map +1 -1
- package/dist/core/digest-validator.d.ts.map +1 -1
- package/dist/core/digest-validator.js +3 -5
- package/dist/core/digest-validator.js.map +1 -1
- package/dist/core/doctor.d.ts +2 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +59 -62
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +5 -2
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +329 -25
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +8 -4
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +82 -24
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +7 -12
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/graph.d.ts +12 -0
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js +56 -1
- package/dist/core/graph.js.map +1 -1
- package/dist/core/guards.d.ts +20 -0
- package/dist/core/guards.d.ts.map +1 -0
- package/dist/core/guards.js +103 -0
- package/dist/core/guards.js.map +1 -0
- package/dist/core/install-channel.d.ts +1 -1
- package/dist/core/install-channel.d.ts.map +1 -1
- package/dist/core/install-channel.js +16 -5
- package/dist/core/install-channel.js.map +1 -1
- package/dist/core/install-hooks.d.ts +5 -0
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +0 -0
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/json-utils.d.ts +1 -0
- package/dist/core/json-utils.d.ts.map +1 -1
- package/dist/core/json-utils.js +19 -10
- package/dist/core/json-utils.js.map +1 -1
- package/dist/core/kg-backfill.d.ts +5 -2
- package/dist/core/kg-backfill.d.ts.map +1 -1
- package/dist/core/kg-backfill.js +155 -5
- package/dist/core/kg-backfill.js.map +1 -1
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +14 -21
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/memory-tool.d.ts.map +1 -1
- package/dist/core/memory-tool.js +4 -4
- package/dist/core/memory-tool.js.map +1 -1
- package/dist/core/operations.d.ts +13 -2
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +115 -28
- package/dist/core/operations.js.map +1 -1
- package/dist/core/prompt-safety.d.ts +1 -0
- package/dist/core/prompt-safety.d.ts.map +1 -1
- package/dist/core/prompt-safety.js +7 -0
- package/dist/core/prompt-safety.js.map +1 -1
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +31 -0
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/serializer.d.ts.map +1 -1
- package/dist/core/serializer.js +8 -0
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/setup.d.ts +29 -0
- package/dist/core/setup.d.ts.map +1 -0
- package/dist/core/setup.js +127 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/core/task-state-store.d.ts +17 -0
- package/dist/core/task-state-store.d.ts.map +1 -0
- package/dist/core/task-state-store.js +45 -0
- package/dist/core/task-state-store.js.map +1 -0
- package/dist/core/task-state.d.ts +19 -0
- package/dist/core/task-state.d.ts.map +1 -0
- package/dist/core/task-state.js +91 -0
- package/dist/core/task-state.js.map +1 -0
- package/dist/core/time-utils.d.ts +2 -0
- package/dist/core/time-utils.d.ts.map +1 -0
- package/dist/core/time-utils.js +14 -0
- package/dist/core/time-utils.js.map +1 -0
- package/dist/core/title.d.ts +5 -0
- package/dist/core/title.d.ts.map +1 -0
- package/dist/core/title.js +14 -0
- package/dist/core/title.js.map +1 -0
- package/dist/core/transcript-source.d.ts.map +1 -1
- package/dist/core/transcript-source.js +2 -3
- package/dist/core/transcript-source.js.map +1 -1
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/why.d.ts +54 -0
- package/dist/core/why.d.ts.map +1 -0
- package/dist/core/why.js +168 -0
- package/dist/core/why.js.map +1 -0
- package/dist/core/work-topology.d.ts +36 -0
- package/dist/core/work-topology.d.ts.map +1 -0
- package/dist/core/work-topology.js +192 -0
- package/dist/core/work-topology.js.map +1 -0
- package/dist/db.d.ts +33 -11
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +307 -315
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +1 -0
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +50 -40
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/skills-manifest.json +62 -22
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js +2 -7
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/storage/fts-index.d.ts +4 -2
- package/dist/storage/fts-index.d.ts.map +1 -1
- package/dist/storage/fts-index.js +16 -4
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/storage/schema.d.ts +20 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +274 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +1 -1
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/transports/cli/cli.d.ts +1 -4
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +579 -66
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +242 -303
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +46 -0
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +59 -4
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +29 -10
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +33 -8
- package/dist/transports/schemas.js.map +1 -1
- package/hooks/hooks.json +10 -0
- package/llms-install.md +138 -0
- package/package.json +14 -9
- package/scripts/hooks/_generated/capture-flag.js +17 -0
- package/scripts/hooks/_generated/fts-index.js +16 -4
- package/scripts/hooks/_generated/guards.js +110 -0
- package/scripts/hooks/_generated/schema.js +281 -0
- package/scripts/hooks/_generated/sqlite.js +1 -1
- package/scripts/hooks/_generated/task-state.js +98 -0
- package/scripts/hooks/_generated/time-utils.js +21 -0
- package/scripts/hooks/_generated/title.js +21 -0
- package/scripts/hooks/_generated/work-topology.js +199 -0
- package/scripts/hooks/_shared.js +197 -480
- package/scripts/hooks/guard-check.js +76 -0
- package/scripts/hooks/post-commit.js +31 -1
- package/scripts/hooks/pre-compact.js +13 -1
- package/scripts/hooks/pre-edit-recall.js +158 -120
- package/scripts/hooks/session-start.js +169 -82
- package/scripts/hooks/session-summary.js +78 -90
- package/skills/memesh/SKILL.md +108 -76
- package/README.es.md +0 -467
- package/README.fr.md +0 -459
- package/README.ja.md +0 -467
- package/README.ko.md +0 -467
- package/README.pt.md +0 -459
- package/README.th.md +0 -460
- package/README.vi.md +0 -459
- package/README.zh-CN.md +0 -466
- package/dist/cli/view.d.ts +0 -3
- package/dist/cli/view.d.ts.map +0 -1
- package/dist/cli/view.js +0 -523
- package/dist/cli/view.js.map +0 -1
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
|
-
import { homedir } from 'os';
|
|
6
5
|
import { join } from 'path';
|
|
7
6
|
import { pathToFileURL } from 'url';
|
|
8
7
|
import { existsSync, readFileSync, unlinkSync, mkdirSync, accessSync, constants as fsConstants } from 'fs';
|
|
@@ -13,10 +12,22 @@ import {
|
|
|
13
12
|
getMemeshDirFromDbPath,
|
|
14
13
|
getProjectName,
|
|
15
14
|
importFromPluginRoot,
|
|
15
|
+
assembleTopologyBlock,
|
|
16
|
+
DEFAULT_TOPOLOGY_BUDGET,
|
|
17
|
+
SNIPPET_FETCH_CHARS,
|
|
18
|
+
TOPOLOGY_CANDIDATE_CAP,
|
|
16
19
|
isTrustedForAutoContext,
|
|
20
|
+
parseEntityMetadata,
|
|
21
|
+
// Aliased: this file already has a local `const memeshDir` (a resolved
|
|
22
|
+
// db-path-derived directory string) — the helper here is the MEMESH_DIR/
|
|
23
|
+
// home resolver the update-check cache itself uses.
|
|
24
|
+
memeshDir as memeshHomeDir,
|
|
25
|
+
parseTaskState,
|
|
17
26
|
readUpdateCheckCache,
|
|
18
27
|
resolvePluginRoot,
|
|
19
28
|
resolveSessionLimit,
|
|
29
|
+
taskStateLines,
|
|
30
|
+
taskStateName,
|
|
20
31
|
writePrivateJson,
|
|
21
32
|
} from './_shared.js';
|
|
22
33
|
import { MemeshDatabase } from './_generated/sqlite.js';
|
|
@@ -192,7 +203,7 @@ function isStrictlyOlder(a, b) {
|
|
|
192
203
|
return pa.tail < pb.tail;
|
|
193
204
|
}
|
|
194
205
|
|
|
195
|
-
function buildUpdateAvailableBanner(currentVersion, cache,
|
|
206
|
+
function buildUpdateAvailableBanner(currentVersion, cache, getChannel) {
|
|
196
207
|
if (!cache || cache.currentVersion !== currentVersion) return [];
|
|
197
208
|
// Deprecation banner takes precedence — when set, it owns the
|
|
198
209
|
// session-start real estate. Skip the soft banner so the user sees
|
|
@@ -215,7 +226,10 @@ function buildUpdateAvailableBanner(currentVersion, cache, channel) {
|
|
|
215
226
|
// this user a banner today?"
|
|
216
227
|
try {
|
|
217
228
|
const fs = require('fs');
|
|
218
|
-
|
|
229
|
+
// memeshHomeDir(), not join(homedir(), '.memesh'): the throttle marker
|
|
230
|
+
// must sit next to the update-check cache it throttles when MEMESH_DIR
|
|
231
|
+
// is set (readUpdateCheckCache resolves its path with this same helper).
|
|
232
|
+
const dir = memeshHomeDir();
|
|
219
233
|
try { ensurePrivateDir(dir); } catch { /* best-effort */ }
|
|
220
234
|
const versionTag = /^[0-9A-Za-z.+-]+$/.test(currentVersion) ? currentVersion : 'unknown';
|
|
221
235
|
const markerPath = join(dir, `last-update-banner.${versionTag}.lock`);
|
|
@@ -234,6 +248,13 @@ function buildUpdateAvailableBanner(currentVersion, cache, channel) {
|
|
|
234
248
|
} catch { /* best-effort */ }
|
|
235
249
|
} catch { /* best-effort */ }
|
|
236
250
|
|
|
251
|
+
// Channel detection spawns `npm root -g` (50-200ms) — resolve it only
|
|
252
|
+
// here, after every early return above has had its chance to suppress
|
|
253
|
+
// the banner. The guards fire on ~every session; the banner at most
|
|
254
|
+
// once per 24h.
|
|
255
|
+
let channel = 'unknown';
|
|
256
|
+
try { channel = getChannel(); } catch { /* best-effort */ }
|
|
257
|
+
|
|
237
258
|
const lines = [
|
|
238
259
|
'',
|
|
239
260
|
`ℹ️ MeMesh update available: ${cache.latestVersion} (you're on ${currentVersion}).`,
|
|
@@ -241,7 +262,7 @@ function buildUpdateAvailableBanner(currentVersion, cache, channel) {
|
|
|
241
262
|
if (channel === 'npm-global') {
|
|
242
263
|
lines.push(` Run: memesh update`);
|
|
243
264
|
} else if (channel === 'plugin-marketplace') {
|
|
244
|
-
lines.push(` Run:
|
|
265
|
+
lines.push(` Run: memesh upgrade-plugin (no CLI? npx @pcircle/memesh upgrade-plugin — or reinstall from /plugin UI)`);
|
|
245
266
|
} else if (channel === 'source-checkout') {
|
|
246
267
|
lines.push(` Source checkout: \`git pull && npm install && npm run build\`.`);
|
|
247
268
|
} else if (channel === 'npm-local') {
|
|
@@ -309,7 +330,9 @@ function spawnFreshUpdateCheck(installedVersion) {
|
|
|
309
330
|
const cliPath = join(pluginRoot, 'dist/transports/cli/cli.js');
|
|
310
331
|
if (!existsSync(cliPath)) return false;
|
|
311
332
|
const fs = require('fs');
|
|
312
|
-
|
|
333
|
+
// memeshHomeDir(), not join(homedir(), '.memesh') — same reasoning as
|
|
334
|
+
// the banner marker above: marker and cache must share a directory.
|
|
335
|
+
const dir = memeshHomeDir();
|
|
313
336
|
try { ensurePrivateDir(dir); } catch { /* best-effort */ }
|
|
314
337
|
// Codex round 37: scope the throttle marker to the installed
|
|
315
338
|
// version. The marker was machine-global, so a refresh started
|
|
@@ -491,8 +514,8 @@ function combineWithBanner(baseMessage) {
|
|
|
491
514
|
if (deprecation.length > 0) {
|
|
492
515
|
lines = deprecation;
|
|
493
516
|
} else {
|
|
494
|
-
|
|
495
|
-
|
|
517
|
+
lines = buildUpdateAvailableBanner(
|
|
518
|
+
installedVersion, cache, () => detectInstallChannelHook(pluginRoot));
|
|
496
519
|
}
|
|
497
520
|
}
|
|
498
521
|
} catch {
|
|
@@ -571,6 +594,10 @@ process.stdin.on('end', async () => {
|
|
|
571
594
|
// property of the database file that the writing side already set, and a
|
|
572
595
|
// reader opens a WAL database perfectly well without asking for it.
|
|
573
596
|
const db = new MemeshDatabase(dbPath, { readOnly: true });
|
|
597
|
+
// Whether the noise-compression epilogue below should run at all —
|
|
598
|
+
// pre-read from this readonly handle before it closes. Defaults to
|
|
599
|
+
// true so any early exit still lets the epilogue's own throttle decide.
|
|
600
|
+
let noiseCompressDue = true;
|
|
574
601
|
try {
|
|
575
602
|
// Check if tables exist (db may exist but be empty)
|
|
576
603
|
const tableCheck = db.prepare(
|
|
@@ -633,7 +660,12 @@ process.stdin.on('end', async () => {
|
|
|
633
660
|
// doesn't have confidence/access_count/last_accessed_at, so the
|
|
634
661
|
// SELECT can't reference them. Build the column list to match
|
|
635
662
|
// what the schema actually supports.
|
|
636
|
-
|
|
663
|
+
// `title` is an ALTER-added column (UX-1) and gets the same
|
|
664
|
+
// legacy-schema guard as the scoring columns: a database that predates
|
|
665
|
+
// it must still produce an injection, falling back to the observation
|
|
666
|
+
// snippet for its display text.
|
|
667
|
+
const hasTitle = colNames.has('title');
|
|
668
|
+
const baseCols = `e.id, e.name, e.type,${hasTitle ? ' e.title,' : ''} e.created_at, e.metadata`;
|
|
637
669
|
const scoringCols = hasScoringCols
|
|
638
670
|
? `, e.confidence, e.access_count, e.last_accessed_at`
|
|
639
671
|
: '';
|
|
@@ -650,7 +682,7 @@ process.stdin.on('end', async () => {
|
|
|
650
682
|
pool_stats AS (
|
|
651
683
|
SELECT COALESCE(MAX(access_count), 0) AS max_access FROM pool
|
|
652
684
|
)
|
|
653
|
-
SELECT p.id, p.name, p.type, p.created_at, p.metadata
|
|
685
|
+
SELECT p.id, p.name, p.type,${hasTitle ? ' p.title,' : ''} p.created_at, p.metadata
|
|
654
686
|
FROM pool p, pool_stats s
|
|
655
687
|
ORDER BY
|
|
656
688
|
COALESCE(p.confidence, 1.0) * 0.2833
|
|
@@ -679,7 +711,19 @@ process.stdin.on('end', async () => {
|
|
|
679
711
|
`JOIN tags t ON t.entity_id = e.id`,
|
|
680
712
|
`WHERE t.tag = ? ${statusFilter}`,
|
|
681
713
|
);
|
|
682
|
-
|
|
714
|
+
// Over-fetch WIDE, then filter. The window used to be `sessionLimit * 3`
|
|
715
|
+
// and the trust filter ran after it — so a class of entity that ranks
|
|
716
|
+
// high can consume the entire window and leave nothing. That was not
|
|
717
|
+
// hypothetical: measured on a real graph, all 30 top-ranked rows were
|
|
718
|
+
// filtered out and the "project memory" section rendered empty while 92
|
|
719
|
+
// eligible entities sat below the cut. The filter is a JS predicate with
|
|
720
|
+
// one owner (`isTrustedForAutoContext`); rather than restate it as SQL
|
|
721
|
+
// and own it twice, the window is made wide enough that the filtered
|
|
722
|
+
// class cannot fill it. CANDIDATE_CAP bounds the work for a large graph.
|
|
723
|
+
// Shared with the briefing surface via the leaf, so the two sides'
|
|
724
|
+
// candidate windows cannot drift apart.
|
|
725
|
+
const CANDIDATE_CAP = TOPOLOGY_CANDIDATE_CAP;
|
|
726
|
+
const projectEntities = db.prepare(projectQuery).all(projectTag, CANDIDATE_CAP)
|
|
683
727
|
.filter(entity => isTrustedForAutoContext(entity.metadata))
|
|
684
728
|
.slice(0, sessionLimit);
|
|
685
729
|
|
|
@@ -688,7 +732,7 @@ process.stdin.on('end', async () => {
|
|
|
688
732
|
// so rewrite to e.status for consistency.
|
|
689
733
|
const recentWhere = hasStatus ? "WHERE e.status = 'active'" : '';
|
|
690
734
|
const recentQuery = buildScoringQuery('', recentWhere);
|
|
691
|
-
const recentEntities = db.prepare(recentQuery).all(
|
|
735
|
+
const recentEntities = db.prepare(recentQuery).all(CANDIDATE_CAP)
|
|
692
736
|
.filter(entity => isTrustedForAutoContext(entity.metadata))
|
|
693
737
|
.slice(0, 5);
|
|
694
738
|
|
|
@@ -701,7 +745,7 @@ process.stdin.on('end', async () => {
|
|
|
701
745
|
let lessonEntities = [];
|
|
702
746
|
try {
|
|
703
747
|
const lessonRows = db.prepare(`
|
|
704
|
-
SELECT DISTINCT e.id, e.name, e.type, e.metadata
|
|
748
|
+
SELECT DISTINCT e.id, e.name, e.type,${hasTitle ? ' e.title,' : ''} e.metadata
|
|
705
749
|
FROM entities e
|
|
706
750
|
JOIN tags t ON t.entity_id = e.id
|
|
707
751
|
WHERE e.type = 'lesson_learned'
|
|
@@ -754,17 +798,20 @@ process.stdin.on('end', async () => {
|
|
|
754
798
|
// stay far under that on purpose — session start should prime the
|
|
755
799
|
// model, not consume its working context. Snippets are truncated per
|
|
756
800
|
// observation and the whole block is hard-capped.
|
|
757
|
-
|
|
758
|
-
|
|
801
|
+
// The budget itself comes from the leaf (DEFAULT_TOPOLOGY_BUDGET) at
|
|
802
|
+
// the assembleTopologyBlock call — "the same block" depends on the two
|
|
803
|
+
// surfaces agreeing, so neither side restates the numbers.
|
|
804
|
+
|
|
805
|
+
// Only the entities we will actually render — the lesson query pulls
|
|
806
|
+
// up to 50 rows for the banner count, but at most 5 are injected, and
|
|
807
|
+
// this runs before the user's first turn. Bounded well under SQLite's
|
|
808
|
+
// 999-variable limit by construction (5 lessons + sessionLimit
|
|
809
|
+
// project + 5 recent). Declared out here because the injected-set
|
|
810
|
+
// record below must list the SAME lessons the block renders.
|
|
811
|
+
const topLessons = lessonEntities.slice(0, 5);
|
|
759
812
|
|
|
760
813
|
const memoryLines = [];
|
|
761
814
|
try {
|
|
762
|
-
// Only the entities we will actually render — the lesson query pulls
|
|
763
|
-
// up to 50 rows for the banner count, but at most 5 are injected, and
|
|
764
|
-
// this runs before the user's first turn. Bounded well under SQLite's
|
|
765
|
-
// 999-variable limit by construction (5 lessons + sessionLimit
|
|
766
|
-
// project + 5 recent).
|
|
767
|
-
const topLessons = lessonEntities.slice(0, 5);
|
|
768
815
|
const rankedIds = [
|
|
769
816
|
...topLessons.map(e => e.id),
|
|
770
817
|
...projectEntities.map(e => e.id),
|
|
@@ -788,33 +835,63 @@ process.stdin.on('end', async () => {
|
|
|
788
835
|
// later ones are refinements.
|
|
789
836
|
if (snippets.has(row.entity_id)) continue;
|
|
790
837
|
const text = String(row.content ?? '').replace(/\s+/g, ' ').trim();
|
|
791
|
-
|
|
838
|
+
// A few line-widths, not the exact line cap: the final cut is
|
|
839
|
+
// clip()'s, on a word boundary — a hard slice at the line cap
|
|
840
|
+
// would hand it a string with nothing left to trim and ship
|
|
841
|
+
// mid-word fragments again.
|
|
842
|
+
if (text) snippets.set(row.entity_id, text.slice(0, SNIPPET_FETCH_CHARS));
|
|
792
843
|
}
|
|
793
844
|
}
|
|
794
845
|
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
}
|
|
812
|
-
memoryLines.push('');
|
|
813
|
-
};
|
|
846
|
+
// "Where we left off" leads the block. It is the one memory a new
|
|
847
|
+
// session needs before any other: everything below is context for
|
|
848
|
+
// work, this IS the work. It is also the only line here a human
|
|
849
|
+
// (or an agent acting for one) stated on purpose — the rest is
|
|
850
|
+
// ranked, and ranking cannot know what you meant to do next.
|
|
851
|
+
//
|
|
852
|
+
// Read from metadata, not from the observation trail: observations
|
|
853
|
+
// are the CHANGE history, and picking "the current goal" out of them
|
|
854
|
+
// means guessing which line is newest. Metadata holds one answer.
|
|
855
|
+
const taskRow = db
|
|
856
|
+
.prepare('SELECT metadata FROM entities WHERE name = ?')
|
|
857
|
+
.get(taskStateName(projectName));
|
|
858
|
+
const stateLines = taskStateLines(
|
|
859
|
+
parseTaskState(parseEntityMetadata(taskRow?.metadata)),
|
|
860
|
+
projectName,
|
|
861
|
+
);
|
|
814
862
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
863
|
+
// The pools overlap by construction (a lesson tagged to this project
|
|
864
|
+
// is in lessonEntities AND projectEntities); the shared assembler
|
|
865
|
+
// dedupes across them in claim order, so a project-scoped row is
|
|
866
|
+
// never marked foreign by the cross-project recent pool, and the
|
|
867
|
+
// topology grouping decides where each one belongs. This mapping —
|
|
868
|
+
// raw row → TopologyEntity — is the only part this hook owns; the
|
|
869
|
+
// assembly order, the spacer discipline, the budget and the
|
|
870
|
+
// task-state exclusion live in the leaf, shared with `briefing`.
|
|
871
|
+
const toEntity = (e) => {
|
|
872
|
+
const meta = parseEntityMetadata(e.metadata);
|
|
873
|
+
return {
|
|
874
|
+
name: e.name,
|
|
875
|
+
type: e.type || 'memory',
|
|
876
|
+
// The citation handle: topologyLine prints `[mem:<id>]` so the
|
|
877
|
+
// agent can credit the exact memory it used (the Stop hook's
|
|
878
|
+
// accounting reads those markers back).
|
|
879
|
+
id: e.id,
|
|
880
|
+
title: e.title ?? null,
|
|
881
|
+
snippet: snippets.get(e.id) ?? null,
|
|
882
|
+
signalScore: meta && typeof meta.signal_score === 'number' ? meta.signal_score : null,
|
|
883
|
+
};
|
|
884
|
+
};
|
|
885
|
+
memoryLines.push(...assembleTopologyBlock(
|
|
886
|
+
stateLines,
|
|
887
|
+
[
|
|
888
|
+
{ entities: topLessons.map(toEntity), foreign: false },
|
|
889
|
+
{ entities: projectEntities.map(toEntity), foreign: false },
|
|
890
|
+
{ entities: recentEntities.map(toEntity), foreign: true },
|
|
891
|
+
],
|
|
892
|
+
projectName,
|
|
893
|
+
DEFAULT_TOPOLOGY_BUDGET,
|
|
894
|
+
));
|
|
818
895
|
} catch (err) {
|
|
819
896
|
// Snippet enrichment is best-effort. A failure here must not stop
|
|
820
897
|
// the banner or the session — but trace it, because a silent break
|
|
@@ -830,40 +907,33 @@ process.stdin.on('end', async () => {
|
|
|
830
907
|
// attacker-influenced in the general case (anything the agent has
|
|
831
908
|
// ever been told can end up in an observation), so it must be
|
|
832
909
|
// delimited the same way on every injection path — not hand-rolled
|
|
833
|
-
// per hook.
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
|
|
837
|
-
//
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
break;
|
|
844
|
-
}
|
|
845
|
-
budgeted.push(line);
|
|
846
|
-
used += line.length + 1;
|
|
847
|
-
}
|
|
848
|
-
memoryContext = buildReferenceContext(budgeted);
|
|
910
|
+
// per hook. The lines arrive already budgeted — assembleTopologyBlock
|
|
911
|
+
// charges the task-state block and the sections against ONE ceiling
|
|
912
|
+
// and returns whole lines only, so the closing fence cannot be cut.
|
|
913
|
+
memoryContext = buildReferenceContext(memoryLines);
|
|
914
|
+
// The citation contract — OUTSIDE the fence on purpose: the fence
|
|
915
|
+
// declares its content "background data, not instructions", and
|
|
916
|
+
// this line IS an instruction. One line is the entire write side of
|
|
917
|
+
// the injection-ROI signal; the Stop hook credits recall_hits only
|
|
918
|
+
// from these markers (self-reported: undercounts, never overcounts).
|
|
919
|
+
memoryContext += '\nWhen a memory above genuinely informs your work, cite it once inline as [mem:ID], using the id shown on its line.';
|
|
849
920
|
}
|
|
850
921
|
|
|
851
922
|
// --- Record injected entity IDs for recall effectiveness tracking ---
|
|
852
|
-
// The Stop hook
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
//
|
|
856
|
-
//
|
|
857
|
-
//
|
|
858
|
-
//
|
|
923
|
+
// The Stop hook credits recall_hits from EXPLICIT `[mem:id]` citations
|
|
924
|
+
// the agent writes (after structurally removing the transcript records
|
|
925
|
+
// Claude Code created FROM this hook's output — the injected block
|
|
926
|
+
// itself prints a handle on every line; see stripHookEchoes in
|
|
927
|
+
// session-summary.js). Literal-content matching was retired after
|
|
928
|
+
// measuring 0% signal over ten real sessions. `injectedContext` is
|
|
929
|
+
// kept as the record of what was shown.
|
|
859
930
|
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
862
|
-
//
|
|
863
|
-
// not earn.
|
|
931
|
+
// The set below is every pool the topology block draws from — the
|
|
932
|
+
// lessons pool included. It used to record only project + recent
|
|
933
|
+
// rows, so an injected lesson could never be credited at all.
|
|
864
934
|
try {
|
|
865
935
|
const seenIds = new Set();
|
|
866
|
-
const allInjected = [...projectEntities, ...recentEntities].filter(e => {
|
|
936
|
+
const allInjected = [...topLessons, ...projectEntities, ...recentEntities].filter(e => {
|
|
867
937
|
if (seenIds.has(e.id)) return false;
|
|
868
938
|
seenIds.add(e.id);
|
|
869
939
|
return true;
|
|
@@ -900,11 +970,11 @@ process.stdin.on('end', async () => {
|
|
|
900
970
|
} catch {}
|
|
901
971
|
}
|
|
902
972
|
} catch (err) {
|
|
903
|
-
// Non-critical — sessions-file write powers
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
// converges on 0.5 (neutral) for everything.
|
|
907
|
-
// permission/serialisation regression is visible.
|
|
973
|
+
// Non-critical — sessions-file write powers citation accounting
|
|
974
|
+
// (recall_hits credited from `[mem:id]` markers). If it silently
|
|
975
|
+
// breaks, no hit can ever be credited and the impact-score factor
|
|
976
|
+
// in core/scoring.ts converges on 0.5 (neutral) for everything.
|
|
977
|
+
// Stderr trace so a permission/serialisation regression is visible.
|
|
908
978
|
try { process.stderr.write(`[memesh session-start] sessions-write: ${err?.message || err}\n`); } catch {}
|
|
909
979
|
}
|
|
910
980
|
|
|
@@ -925,12 +995,8 @@ process.stdin.on('end', async () => {
|
|
|
925
995
|
if (deprecation.length > 0) {
|
|
926
996
|
bannerLines = deprecation;
|
|
927
997
|
} else {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
931
|
-
channel = detectInstallChannelHook(pluginRoot);
|
|
932
|
-
} catch { /* best-effort */ }
|
|
933
|
-
bannerLines = buildUpdateAvailableBanner(installedVersion, updateCache, channel);
|
|
998
|
+
bannerLines = buildUpdateAvailableBanner(installedVersion, updateCache,
|
|
999
|
+
() => detectInstallChannelHook(resolvePluginRoot(import.meta.url)));
|
|
934
1000
|
}
|
|
935
1001
|
}
|
|
936
1002
|
const finalMessage = bannerLines.length > 0
|
|
@@ -938,6 +1004,25 @@ process.stdin.on('end', async () => {
|
|
|
938
1004
|
: summary;
|
|
939
1005
|
|
|
940
1006
|
output(withCaptureWarning(finalMessage), memoryContext);
|
|
1007
|
+
|
|
1008
|
+
// Pre-read the noise-compression throttle on the handle we already
|
|
1009
|
+
// hold. compressWeeklyNoise() re-checks under its own connection, but
|
|
1010
|
+
// ~364/365 sessions are inside the 24h window — and the full path
|
|
1011
|
+
// costs two dist module-graph imports plus a write-capable
|
|
1012
|
+
// migration-chain open (WAL writer lock) that must stay off the
|
|
1013
|
+
// SessionStart hot path. Missing table / any error ⇒ due (the full
|
|
1014
|
+
// path owns schema creation).
|
|
1015
|
+
noiseCompressDue = (() => {
|
|
1016
|
+
try {
|
|
1017
|
+
const row = db.prepare(
|
|
1018
|
+
"SELECT value FROM memesh_metadata WHERE key = 'last_noise_compress_at'"
|
|
1019
|
+
).get();
|
|
1020
|
+
if (!row) return true;
|
|
1021
|
+
return Date.now() - new Date(row.value).getTime() >= 24 * 60 * 60 * 1000;
|
|
1022
|
+
} catch {
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
})();
|
|
941
1026
|
} finally {
|
|
942
1027
|
db.close();
|
|
943
1028
|
}
|
|
@@ -945,6 +1030,7 @@ process.stdin.on('end', async () => {
|
|
|
945
1030
|
// Opens a separate read-write connection via the core module.
|
|
946
1031
|
// Throttled to once per 24h inside compressWeeklyNoise().
|
|
947
1032
|
try {
|
|
1033
|
+
if (noiseCompressDue) {
|
|
948
1034
|
// F5: derive pluginRoot strictly from this file's location.
|
|
949
1035
|
// See `resolvePluginRoot` for the full reasoning.
|
|
950
1036
|
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
@@ -956,6 +1042,7 @@ process.stdin.on('end', async () => {
|
|
|
956
1042
|
} finally {
|
|
957
1043
|
dbMod.closeDatabase();
|
|
958
1044
|
}
|
|
1045
|
+
}
|
|
959
1046
|
} catch (err) {
|
|
960
1047
|
// Non-critical — noise compression failed, will retry next session.
|
|
961
1048
|
// Trace because this catch previously hid an off-by-one regression
|