@pcircle/memesh 4.5.1 → 4.6.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/.claude-plugin/marketplace.json +5 -3
- package/.claude-plugin/plugin.json +6 -4
- package/AGENTS.md +95 -0
- package/README.de.md +129 -35
- package/README.md +161 -34
- package/README.zh-TW.md +130 -35
- package/dashboard/dist/index.html +10 -10
- package/dist/cli/view-live.js +3 -3
- 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 +91 -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/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/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 +34 -56
- 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 +108 -25
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +5 -4
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +16 -8
- 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/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 +0 -1
- package/dist/core/kg-backfill.d.ts.map +1 -1
- package/dist/core/kg-backfill.js +0 -3
- 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.map +1 -1
- package/dist/core/operations.js +22 -13
- 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/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 +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/work-topology.d.ts +33 -0
- package/dist/core/work-topology.d.ts.map +1 -0
- package/dist/core/work-topology.js +183 -0
- package/dist/core/work-topology.js.map +1 -0
- package/dist/db.d.ts +2 -7
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +144 -284
- 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 +48 -18
- 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/transports/cli/cli.d.ts +1 -4
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +382 -6
- 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 +208 -307
- 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 +57 -2
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +21 -10
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +26 -8
- package/dist/transports/schemas.js.map +1 -1
- 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/schema.js +281 -0
- 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 +190 -0
- package/scripts/hooks/_shared.js +122 -478
- package/scripts/hooks/post-commit.js +4 -1
- package/scripts/hooks/pre-compact.js +13 -1
- package/scripts/hooks/pre-edit-recall.js +5 -3
- package/scripts/hooks/session-start.js +135 -59
- package/scripts/hooks/session-summary.js +59 -24
- package/skills/memesh/SKILL.md +97 -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
|
@@ -8,9 +8,14 @@ import { openDatabase, closeDatabase, getDatabase, reindexFts, allowVectorIndexR
|
|
|
8
8
|
import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn, reindex, setPinned } from '../../core/operations.js';
|
|
9
9
|
import { readConfig, writeConfig, maskApiKey, detectCapabilities } from '../../core/config.js';
|
|
10
10
|
import { MAX_LANGUAGE_LENGTH, languageValueError } from '../../core/output-language.js';
|
|
11
|
-
import { getDbPath, redactSecrets, redactUserPaths } from '../../core/paths.js';
|
|
11
|
+
import { getDbPath, homeDir, redactSecrets, redactUserPaths } from '../../core/paths.js';
|
|
12
12
|
import { flushPendingEmbeddings, canRefillVectorIndex } from '../../core/embedder.js';
|
|
13
13
|
import { NAMESPACES } from '../../core/types.js';
|
|
14
|
+
import { assembleBriefing } from '../../core/briefing.js';
|
|
15
|
+
import { inspectHosts, allWired } from '../../core/setup.js';
|
|
16
|
+
import { installHooks } from '../../core/install-hooks.js';
|
|
17
|
+
import { getTaskState, setTaskState } from '../../core/task-state-store.js';
|
|
18
|
+
import { TASK_STATE_FIELDS, taskStateLines } from '../../core/task-state.js';
|
|
14
19
|
async function withDatabase(fn) {
|
|
15
20
|
openDatabase();
|
|
16
21
|
try {
|
|
@@ -26,13 +31,34 @@ function requireOneOf(value, allowed, flag) {
|
|
|
26
31
|
console.error(`Error: ${flag} "${value}" is not valid. Use one of: ${allowed.join(', ')}.`);
|
|
27
32
|
process.exit(1);
|
|
28
33
|
}
|
|
34
|
+
function isOnPath(tool) {
|
|
35
|
+
const exts = process.platform === 'win32'
|
|
36
|
+
? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean)
|
|
37
|
+
: [''];
|
|
38
|
+
for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {
|
|
39
|
+
if (!dir)
|
|
40
|
+
continue;
|
|
41
|
+
for (const ext of exts) {
|
|
42
|
+
try {
|
|
43
|
+
fs.accessSync(path.join(dir, tool + ext), fs.constants.X_OK);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
function wireUserHooks() {
|
|
52
|
+
const r = installHooks({ pluginRoot: packageRoot, pluginVersion: pkg.version, scope: 'user' });
|
|
53
|
+
return `hooks: added ${r.added}, skipped ${r.skipped} already-installed${r.backupPath ? ` (backup: ${r.backupPath})` : ''}`;
|
|
54
|
+
}
|
|
29
55
|
const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../package.json');
|
|
30
56
|
const packageRoot = path.dirname(packageJsonPath);
|
|
31
57
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
32
58
|
const program = new Command();
|
|
33
59
|
program
|
|
34
60
|
.name('memesh')
|
|
35
|
-
.description('MeMesh —
|
|
61
|
+
.description('MeMesh — Agentic memory for coding agents')
|
|
36
62
|
.version(pkg.version)
|
|
37
63
|
.allowExcessArguments(true)
|
|
38
64
|
.showSuggestionAfterError(true);
|
|
@@ -42,6 +68,7 @@ program
|
|
|
42
68
|
.description('Store knowledge as an entity (use flags for explicit form, or positional text for quick capture)')
|
|
43
69
|
.option('--name <name>', 'Entity name')
|
|
44
70
|
.option('--type <type>', 'Entity type')
|
|
71
|
+
.option('--title <title>', 'Short human-readable label shown as the headline (name stays the stable machine key)')
|
|
45
72
|
.option('--obs <observations...>', 'Observations (space-separated)')
|
|
46
73
|
.option('--tags <tags...>', 'Tags (space-separated)')
|
|
47
74
|
.option('--namespace <namespace>', 'Namespace: personal, team, or global. On a NEW memory this places it (default personal); on one that already exists it MOVES it out of the scope it is in — omit the flag to leave it alone.')
|
|
@@ -60,6 +87,8 @@ program
|
|
|
60
87
|
const suffix = randomBytes(3).toString('hex');
|
|
61
88
|
opts.name = `quick-${date}-${slug || 'note'}-${suffix}`;
|
|
62
89
|
opts.type = 'note';
|
|
90
|
+
if (!opts.title)
|
|
91
|
+
opts.title = String(text).slice(0, 200);
|
|
63
92
|
if (!opts.obs || opts.obs.length === 0)
|
|
64
93
|
opts.obs = [String(text)];
|
|
65
94
|
else
|
|
@@ -85,6 +114,7 @@ program
|
|
|
85
114
|
const result = remember({
|
|
86
115
|
name: opts.name,
|
|
87
116
|
type: opts.type,
|
|
117
|
+
title: opts.title,
|
|
88
118
|
observations: opts.obs,
|
|
89
119
|
tags: opts.tags,
|
|
90
120
|
namespace: opts.namespace,
|
|
@@ -400,6 +430,165 @@ program
|
|
|
400
430
|
}
|
|
401
431
|
});
|
|
402
432
|
});
|
|
433
|
+
program
|
|
434
|
+
.command('briefing')
|
|
435
|
+
.description('The assembled work topology for a project — task state, decisions, lessons, knowledge, recent activity')
|
|
436
|
+
.option('--project <name>', 'Project name (default: the current directory’s project)')
|
|
437
|
+
.option('--json', 'Output as JSON')
|
|
438
|
+
.action(async (opts) => {
|
|
439
|
+
await withDatabase(() => {
|
|
440
|
+
const result = assembleBriefing(opts.project);
|
|
441
|
+
if (opts.json) {
|
|
442
|
+
console.log(JSON.stringify(result));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (!result.text) {
|
|
446
|
+
console.log(`No memories for "${result.project}" yet.\n` +
|
|
447
|
+
`Capture happens automatically as you work; or set the task state: memesh task --goal "…"`);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
console.log(result.text);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
program
|
|
454
|
+
.command('setup')
|
|
455
|
+
.description('Detect Claude Code / Codex / Gemini on this machine, wire memesh into each, and verify')
|
|
456
|
+
.option('--check', 'Only report wiring status per host; change nothing (exit 1 if a present host is unwired)')
|
|
457
|
+
.option('--yes', 'Apply every wiring action without asking')
|
|
458
|
+
.action(async (opts) => {
|
|
459
|
+
const { spawnSync, execFileSync } = await import('child_process');
|
|
460
|
+
const runSeam = (cmd, args) => {
|
|
461
|
+
try {
|
|
462
|
+
let target = cmd;
|
|
463
|
+
let useShell = false;
|
|
464
|
+
if (process.platform === 'win32') {
|
|
465
|
+
const resolved = execFileSync('where', [cmd], { encoding: 'utf8' }).split(/\r?\n/)[0]?.trim();
|
|
466
|
+
if (resolved) {
|
|
467
|
+
target = resolved;
|
|
468
|
+
useShell = /\.(cmd|bat)$/i.test(resolved);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const r = spawnSync(target, args, { encoding: 'utf8', shell: useShell });
|
|
472
|
+
return { status: r.status, stderr: r.stderr ?? '' };
|
|
473
|
+
}
|
|
474
|
+
catch (err) {
|
|
475
|
+
return { status: null, stderr: err instanceof Error ? err.message : String(err) };
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
const seams = { home: () => homeDir(), isOnPath, run: runSeam };
|
|
479
|
+
const render = (statuses) => {
|
|
480
|
+
for (const st of statuses) {
|
|
481
|
+
if (!st.present) {
|
|
482
|
+
console.log(` ${st.title}: not found (looked for ${st.presenceDetail}) — skipped`);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const mark = st.wired === true ? '✅' : st.wired === false ? '❌' : '❓';
|
|
486
|
+
console.log(`${mark} ${st.title}: ${st.wiredDetail}`);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
let statuses = inspectHosts(seams);
|
|
490
|
+
if (opts.check) {
|
|
491
|
+
render(statuses);
|
|
492
|
+
process.exit(allWired(statuses) ? 0 : 1);
|
|
493
|
+
}
|
|
494
|
+
const pending = statuses.filter((st) => st.present && st.actions.length > 0);
|
|
495
|
+
if (pending.length === 0) {
|
|
496
|
+
render(statuses);
|
|
497
|
+
console.log(allWired(statuses)
|
|
498
|
+
? '\nEverything present is wired. Nothing to do.'
|
|
499
|
+
: '\nNothing to wire automatically — see the lines above.');
|
|
500
|
+
process.exit(allWired(statuses) ? 0 : 1);
|
|
501
|
+
}
|
|
502
|
+
render(statuses);
|
|
503
|
+
console.log('\nPlanned actions:');
|
|
504
|
+
for (const st of pending)
|
|
505
|
+
for (const a of st.actions) {
|
|
506
|
+
console.log(` • [${st.title}] ${a.label}${a.cmd ? `\n ${a.cmd} ${(a.args ?? []).join(' ')}` : ''}`);
|
|
507
|
+
}
|
|
508
|
+
if (!opts.yes && !process.stdin.isTTY) {
|
|
509
|
+
console.error('\nNot a terminal and --yes not given — nothing was changed. Re-run with: memesh setup --yes');
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
const confirmAll = Boolean(opts.yes);
|
|
513
|
+
let rl = null;
|
|
514
|
+
if (!confirmAll) {
|
|
515
|
+
const { createInterface } = await import('node:readline/promises');
|
|
516
|
+
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
517
|
+
}
|
|
518
|
+
let failed = false;
|
|
519
|
+
for (const st of pending) {
|
|
520
|
+
for (const action of st.actions) {
|
|
521
|
+
if (!confirmAll && rl) {
|
|
522
|
+
const answer = (await rl.question(`\n[${st.title}] ${action.label} — proceed? [y/N] `)).trim().toLowerCase();
|
|
523
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
524
|
+
console.log(' skipped');
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (action.kind === 'install-hooks') {
|
|
529
|
+
console.log(` ✅ ${wireUserHooks()}`);
|
|
530
|
+
}
|
|
531
|
+
else if (action.cmd) {
|
|
532
|
+
const r = runSeam(action.cmd, action.args ?? []);
|
|
533
|
+
if (r.status === 0)
|
|
534
|
+
console.log(` ✅ done (${action.cmd} ${(action.args ?? []).join(' ')})`);
|
|
535
|
+
else {
|
|
536
|
+
console.error(` ❌ ${action.cmd} exited ${r.status ?? 'without running'}${r.stderr ? `: ${r.stderr.trim()}` : ''}`);
|
|
537
|
+
failed = true;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
rl?.close();
|
|
543
|
+
console.log('\nAfter wiring:');
|
|
544
|
+
statuses = inspectHosts(seams);
|
|
545
|
+
render(statuses);
|
|
546
|
+
process.exit(failed || !allWired(statuses) ? 1 : 0);
|
|
547
|
+
});
|
|
548
|
+
program
|
|
549
|
+
.command('task')
|
|
550
|
+
.description('Show or update where the work stands on this project')
|
|
551
|
+
.option('--project <name>', 'Project name (default: the current directory’s project)')
|
|
552
|
+
.option('--goal <text>', 'What this work is FOR — the outcome being aimed at')
|
|
553
|
+
.option('--next <text>', 'The next concrete step')
|
|
554
|
+
.option('--blocked <text>', 'What is standing in the way (pass "" to clear it once resolved)')
|
|
555
|
+
.option('--done <text>', 'What was just finished')
|
|
556
|
+
.option('--json', 'Output as JSON')
|
|
557
|
+
.action(async (opts) => {
|
|
558
|
+
await withDatabase(() => {
|
|
559
|
+
const patch = {};
|
|
560
|
+
for (const field of TASK_STATE_FIELDS) {
|
|
561
|
+
if (opts[field] !== undefined)
|
|
562
|
+
patch[field] = opts[field];
|
|
563
|
+
}
|
|
564
|
+
if (Object.keys(patch).length === 0) {
|
|
565
|
+
const { project, state } = getTaskState(opts.project);
|
|
566
|
+
if (opts.json) {
|
|
567
|
+
console.log(JSON.stringify({ project, state }));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const lines = taskStateLines(state, project);
|
|
571
|
+
if (lines.length === 0) {
|
|
572
|
+
console.log(`Nothing recorded for "${project}" yet.\n` +
|
|
573
|
+
`Set it with: memesh task --goal "…" --next "…"`);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
console.log(lines.join('\n'));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const result = setTaskState({ project: opts.project, patch, sourceHost: 'cli' });
|
|
580
|
+
if (opts.json) {
|
|
581
|
+
console.log(JSON.stringify(result));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (result.changed.length === 0) {
|
|
585
|
+
console.log(`No change — "${result.project}" already said exactly that.`);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
console.log(`Updated ${result.changed.join(', ')} for "${result.project}".`);
|
|
589
|
+
console.log(taskStateLines(result.state, result.project).join('\n'));
|
|
590
|
+
});
|
|
591
|
+
});
|
|
403
592
|
const configCmd = program.command('config').description('Manage configuration');
|
|
404
593
|
configCmd
|
|
405
594
|
.command('list')
|
|
@@ -432,6 +621,7 @@ const ALLOWED_KEYS = new Set([
|
|
|
432
621
|
'autoCapture',
|
|
433
622
|
'llmFallbacks',
|
|
434
623
|
'language',
|
|
624
|
+
'transcriptMining',
|
|
435
625
|
]);
|
|
436
626
|
const KEY_VALIDATORS = {
|
|
437
627
|
'llm.provider': (v) => ['anthropic', 'openai', 'ollama'].includes(v) ? null : `must be one of: anthropic, openai, ollama`,
|
|
@@ -498,7 +688,7 @@ function formatConfigValue(key, raw) {
|
|
|
498
688
|
return JSON.stringify(raw);
|
|
499
689
|
return String(raw);
|
|
500
690
|
}
|
|
501
|
-
|
|
691
|
+
function buildConfigListing(config) {
|
|
502
692
|
const rows = [];
|
|
503
693
|
for (const key of Array.from(ALLOWED_KEYS).sort()) {
|
|
504
694
|
const raw = getNested(config, key.split('.'));
|
|
@@ -555,6 +745,9 @@ configCmd
|
|
|
555
745
|
if (canonical === 'autoCapture') {
|
|
556
746
|
coerced = value === 'true' || value === '1';
|
|
557
747
|
}
|
|
748
|
+
if (canonical === 'transcriptMining') {
|
|
749
|
+
coerced = value === 'true' || value === '1';
|
|
750
|
+
}
|
|
558
751
|
const config = readConfig();
|
|
559
752
|
setNested(config, canonical.split('.'), coerced);
|
|
560
753
|
writeConfig(config);
|
|
@@ -680,6 +873,53 @@ program
|
|
|
680
873
|
process.exit(1);
|
|
681
874
|
}
|
|
682
875
|
});
|
|
876
|
+
program
|
|
877
|
+
.command('upgrade-plugin')
|
|
878
|
+
.description('Upgrade the Claude Code plugin install (finds and runs its bundled upgrade script)')
|
|
879
|
+
.action(async () => {
|
|
880
|
+
const { spawnSync } = await import('child_process');
|
|
881
|
+
const cacheRoot = path.join(homeDir(), '.claude', 'plugins', 'cache', 'pcircle-memesh', 'memesh');
|
|
882
|
+
let versions = [];
|
|
883
|
+
try {
|
|
884
|
+
versions = fs.readdirSync(cacheRoot, { withFileTypes: true })
|
|
885
|
+
.filter((entry) => entry.isDirectory() && /^\d+\.\d+\.\d+/.test(entry.name))
|
|
886
|
+
.map((entry) => entry.name);
|
|
887
|
+
}
|
|
888
|
+
catch { }
|
|
889
|
+
if (versions.length === 0) {
|
|
890
|
+
console.error('No Claude Code plugin install found (looked in ~/.claude/plugins/cache/pcircle-memesh).');
|
|
891
|
+
console.error('If you installed via npm, upgrade with: memesh update');
|
|
892
|
+
process.exit(1);
|
|
893
|
+
}
|
|
894
|
+
versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
|
895
|
+
const newest = versions[versions.length - 1];
|
|
896
|
+
const script = path.join(cacheRoot, newest, 'scripts', 'upgrade-plugin.sh');
|
|
897
|
+
if (!fs.existsSync(script)) {
|
|
898
|
+
console.error(`Plugin install found (v${newest}), but it has no scripts/upgrade-plugin.sh — plugin versions before 4.2.5 shipped without it.`);
|
|
899
|
+
console.error('Reinstall once from the Claude Code /plugin UI, or run the npm-global copy directly:');
|
|
900
|
+
console.error(' bash "$(npm prefix -g)/lib/node_modules/@pcircle/memesh/scripts/upgrade-plugin.sh"');
|
|
901
|
+
process.exit(1);
|
|
902
|
+
}
|
|
903
|
+
const installHints = {
|
|
904
|
+
node: 'node is required by the upgrade script. Install Node.js from https://nodejs.org',
|
|
905
|
+
npm: 'npm is required by the upgrade script. It ships with Node.js — reinstall from https://nodejs.org',
|
|
906
|
+
rsync: 'rsync is required by the upgrade script. macOS: already installed; Debian/Ubuntu: sudo apt install rsync',
|
|
907
|
+
};
|
|
908
|
+
const missing = Object.keys(installHints).filter((tool) => !isOnPath(tool));
|
|
909
|
+
if (missing.length > 0) {
|
|
910
|
+
for (const tool of missing)
|
|
911
|
+
console.error(installHints[tool]);
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
const run = spawnSync('bash', [script], { stdio: 'inherit' });
|
|
915
|
+
if (run.error) {
|
|
916
|
+
console.error(`Could not run the upgrade script: ${run.error.message}`);
|
|
917
|
+
console.error('bash is required to run it. If bash is available under another name, run it yourself:');
|
|
918
|
+
console.error(` bash ${script}`);
|
|
919
|
+
process.exit(1);
|
|
920
|
+
}
|
|
921
|
+
process.exit(run.status ?? 1);
|
|
922
|
+
});
|
|
683
923
|
program
|
|
684
924
|
.command('telemetry')
|
|
685
925
|
.description('Show LLM call telemetry (per-flow scorecard for the last N days)')
|
|
@@ -908,15 +1148,82 @@ program
|
|
|
908
1148
|
.option('--probe-http', 'Also probe the local HTTP server health endpoint')
|
|
909
1149
|
.option('--probe', 'Make one small live call to the configured LLM to confirm it actually answers')
|
|
910
1150
|
.option('--url <url>', 'Base URL for --probe-http', 'http://127.0.0.1:3737')
|
|
1151
|
+
.option('--fix', 'Apply the whitelisted fixes doctor prescribes (asks per fix; --yes skips asking)')
|
|
1152
|
+
.option('--yes', 'With --fix: apply without asking')
|
|
911
1153
|
.action(async (opts) => {
|
|
912
1154
|
const { formatDoctorReport, runDoctor } = await import('../../core/doctor.js');
|
|
913
|
-
|
|
1155
|
+
let result = await runDoctor({
|
|
914
1156
|
packageRoot,
|
|
915
1157
|
packageVersion: pkg.version,
|
|
916
1158
|
probeHttp: opts.probeHttp,
|
|
917
1159
|
probeCapabilities: opts.probe,
|
|
918
1160
|
httpBaseUrl: opts.url,
|
|
919
1161
|
});
|
|
1162
|
+
if (opts.fix) {
|
|
1163
|
+
const FIX_ACTIONS = {
|
|
1164
|
+
'install-hooks': wireUserHooks,
|
|
1165
|
+
'fts-rebuild': () => {
|
|
1166
|
+
openDatabase();
|
|
1167
|
+
try {
|
|
1168
|
+
return `keyword index rebuilt (${reindexFts().entities} entities)`;
|
|
1169
|
+
}
|
|
1170
|
+
finally {
|
|
1171
|
+
closeDatabase();
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
'chmod-db': () => {
|
|
1175
|
+
fs.chmodSync(getDbPath(), 0o600);
|
|
1176
|
+
return `permissions restored: chmod 600 ${getDbPath()}`;
|
|
1177
|
+
},
|
|
1178
|
+
};
|
|
1179
|
+
const fixable = result.checks.filter((c) => c.fixId && (c.status === 'warn' || c.status === 'fail'));
|
|
1180
|
+
if (fixable.length === 0) {
|
|
1181
|
+
console.log('Nothing on the --fix whitelist to apply.');
|
|
1182
|
+
}
|
|
1183
|
+
else {
|
|
1184
|
+
if (!opts.yes && !process.stdin.isTTY) {
|
|
1185
|
+
console.error('Not a terminal and --yes not given — nothing was changed. Re-run with: memesh doctor --fix --yes');
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
}
|
|
1188
|
+
let rl = null;
|
|
1189
|
+
if (!opts.yes) {
|
|
1190
|
+
const { createInterface } = await import('node:readline/promises');
|
|
1191
|
+
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1192
|
+
}
|
|
1193
|
+
for (const check of fixable) {
|
|
1194
|
+
console.log(`\n${check.label}: ${check.summary}`);
|
|
1195
|
+
if (rl) {
|
|
1196
|
+
const answer = (await rl.question(`Apply fix (${check.fixId})? [y/N] `)).trim().toLowerCase();
|
|
1197
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
1198
|
+
console.log(' skipped');
|
|
1199
|
+
continue;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
try {
|
|
1203
|
+
console.log(` ✅ ${FIX_ACTIONS[check.fixId]()}`);
|
|
1204
|
+
}
|
|
1205
|
+
catch (err) {
|
|
1206
|
+
console.error(` ❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
rl?.close();
|
|
1210
|
+
console.log('\nAfter fixes:');
|
|
1211
|
+
const before = new Map(result.checks.map((c) => [c.id, c.status]));
|
|
1212
|
+
const fixedIds = new Set(fixable.map((c) => c.id));
|
|
1213
|
+
result = await runDoctor({
|
|
1214
|
+
packageRoot,
|
|
1215
|
+
packageVersion: pkg.version,
|
|
1216
|
+
probeHttp: false,
|
|
1217
|
+
probeCapabilities: false,
|
|
1218
|
+
httpBaseUrl: opts.url,
|
|
1219
|
+
});
|
|
1220
|
+
for (const c of result.checks) {
|
|
1221
|
+
const was = before.get(c.id);
|
|
1222
|
+
if (fixedIds.has(c.id) && was && was !== c.status)
|
|
1223
|
+
console.log(` ${c.label}: ${was} → ${c.status}`);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
920
1227
|
if (opts.json) {
|
|
921
1228
|
console.log(JSON.stringify(result, null, 2));
|
|
922
1229
|
}
|
|
@@ -1151,7 +1458,8 @@ dreamCmd
|
|
|
1151
1458
|
console.log(`${proposals.length} ${opts.status} proposal(s):`);
|
|
1152
1459
|
console.log('');
|
|
1153
1460
|
for (const p of proposals) {
|
|
1154
|
-
const srcLabel = p.
|
|
1461
|
+
const srcLabel = p.kind === 'relation' ? ' (conflict)'
|
|
1462
|
+
: p.source_kind === 'transcript' ? ' (transcript)' : '';
|
|
1155
1463
|
console.log(` #${p.id} [${p.project}/${p.cluster_key}]${srcLabel} ${p.source_count} source(s) → "${p.digest_name}"`);
|
|
1156
1464
|
if (p.digest_observations_preview !== null) {
|
|
1157
1465
|
console.log(` ${p.digest_observations_preview}`);
|
|
@@ -1162,6 +1470,49 @@ dreamCmd
|
|
|
1162
1470
|
console.log(`Inspect: memesh dream show <id> | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>`);
|
|
1163
1471
|
});
|
|
1164
1472
|
});
|
|
1473
|
+
dreamCmd
|
|
1474
|
+
.command('conflicts')
|
|
1475
|
+
.description('Judge semantically-close memory pairs for contradiction / supersession / duplication (LLM) and stage relation proposals for review')
|
|
1476
|
+
.option('--max-pairs <n>', 'Judge at most N of the tightest candidate pairs this run (default 20)', (v) => parseInt(v, 10))
|
|
1477
|
+
.option('--dry-run', 'Show how many candidates are queued without calling an LLM or writing anything')
|
|
1478
|
+
.action(async (opts) => {
|
|
1479
|
+
await withDatabase(async () => {
|
|
1480
|
+
const cfg = readConfig();
|
|
1481
|
+
const llm = detectCapabilities().llm;
|
|
1482
|
+
if (!llm) {
|
|
1483
|
+
console.error('No LLM configured. Run `memesh config set llm.provider <anthropic|openai|ollama>` first (or set ANTHROPIC_API_KEY / OPENAI_API_KEY).');
|
|
1484
|
+
console.error('LLM is required for `memesh dream conflicts` because "do these two entries disagree" is a semantic judgement, not a rule.');
|
|
1485
|
+
process.exit(1);
|
|
1486
|
+
}
|
|
1487
|
+
const { judgeConflicts, CONFLICT_JUDGE_MAX_PAIRS } = await import('../../core/conflict-judge.js');
|
|
1488
|
+
const { getDatabase } = await import('../../db.js');
|
|
1489
|
+
const result = await judgeConflicts(getDatabase(), llm, {
|
|
1490
|
+
maxPairs: typeof opts.maxPairs === 'number' && !Number.isNaN(opts.maxPairs) ? opts.maxPairs : CONFLICT_JUDGE_MAX_PAIRS,
|
|
1491
|
+
dryRun: !!opts.dryRun,
|
|
1492
|
+
fallbacks: cfg.llmFallbacks,
|
|
1493
|
+
});
|
|
1494
|
+
console.log(`${opts.dryRun ? '[dry-run] ' : ''}Conflict pass complete in ${result.durationMs}ms`);
|
|
1495
|
+
console.log(` candidate pairs available: ${result.candidatesAvailable}`);
|
|
1496
|
+
if (opts.dryRun) {
|
|
1497
|
+
console.log(' (dry run — no LLM called, nothing judged or written)');
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
console.log(` LLM calls: ${result.llmCalls}`);
|
|
1501
|
+
console.log(` judged: ${result.judged} (${result.unrelated} unrelated, remembered so they are never re-bought)`);
|
|
1502
|
+
console.log(` staged: ${result.staged} relation proposal(s)`);
|
|
1503
|
+
if (result.llmFailures > 0) {
|
|
1504
|
+
console.log(` failed: ${result.llmFailures} (unparseable or errored LLM responses; those pairs stay at the head of the candidate list and are retried next run)`);
|
|
1505
|
+
}
|
|
1506
|
+
if (result.aborted) {
|
|
1507
|
+
console.error(` ABORTED after the work above: ${result.aborted}`);
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
}
|
|
1510
|
+
if (result.staged > 0) {
|
|
1511
|
+
console.log('');
|
|
1512
|
+
console.log('Review: memesh dream list | Apply: memesh dream accept <id> | Reject: memesh dream reject <id>');
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
});
|
|
1165
1516
|
dreamCmd
|
|
1166
1517
|
.command('show <id>')
|
|
1167
1518
|
.description('Show a proposal in full — name, type, ALL observations, tags, source — so you can review the whole thing before accepting')
|
|
@@ -1183,6 +1534,31 @@ dreamCmd
|
|
|
1183
1534
|
console.log(`Proposal #${detail.id} [${detail.project}/${detail.cluster_key}] source: ${detail.source_kind} status: ${detail.status}`);
|
|
1184
1535
|
console.log(`created: ${detail.created_at}`);
|
|
1185
1536
|
console.log('');
|
|
1537
|
+
if (detail.kind === 'relation') {
|
|
1538
|
+
const rel = detail.relation;
|
|
1539
|
+
if (!rel) {
|
|
1540
|
+
console.error('relation payload is corrupt — reject this proposal');
|
|
1541
|
+
process.exit(1);
|
|
1542
|
+
}
|
|
1543
|
+
const [fromE, toE] = rel.relation_type === 'supersedes' && rel.direction === 'b_supersedes_a' ? [rel.b, rel.a] : [rel.a, rel.b];
|
|
1544
|
+
console.log(`verdict: ${rel.verdict} (severity: ${rel.severity ?? 'unknown'})`);
|
|
1545
|
+
console.log(`accepting creates: ${fromE?.name} —${rel.relation_type}→ ${toE?.name}`);
|
|
1546
|
+
if (rel.relation_type === 'supersedes') {
|
|
1547
|
+
console.log(` survivor: ${fromE?.name} (the arrow points from the surviving claim to the obsolete one)`);
|
|
1548
|
+
}
|
|
1549
|
+
console.log(`rationale: ${rel.rationale ?? '(none given)'}`);
|
|
1550
|
+
if (rel.recommended_action)
|
|
1551
|
+
console.log(`recommended action: ${rel.recommended_action}`);
|
|
1552
|
+
if (rel.excerpts?.a || rel.excerpts?.b) {
|
|
1553
|
+
console.log(`excerpt A (${rel.a?.name}): ${rel.excerpts?.a ?? ''}`);
|
|
1554
|
+
console.log(`excerpt B (${rel.b?.name}): ${rel.excerpts?.b ?? ''}`);
|
|
1555
|
+
}
|
|
1556
|
+
if (typeof rel.cosine_distance === 'number')
|
|
1557
|
+
console.log(`cosine distance: ${rel.cosine_distance.toFixed(3)}`);
|
|
1558
|
+
console.log('');
|
|
1559
|
+
console.log(`Accept: memesh dream accept ${detail.id} | Reject: memesh dream reject ${detail.id}`);
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1186
1562
|
console.log(`name: ${detail.digest.name}`);
|
|
1187
1563
|
console.log(`type: ${detail.digest.type}`);
|
|
1188
1564
|
console.log(`observations (${detail.digest.observations.length}):`);
|
|
@@ -1350,7 +1726,7 @@ program
|
|
|
1350
1726
|
}
|
|
1351
1727
|
}
|
|
1352
1728
|
body = redactUserPaths(redactSecrets(body));
|
|
1353
|
-
const url = `https://github.com/PCIRCLE-AI/memesh
|
|
1729
|
+
const url = `https://github.com/PCIRCLE-AI/memesh/issues/new?title=${encodeURIComponent(`[${typeLabel}] `)}&body=${encodeURIComponent(body)}&labels=${encodeURIComponent(labels)}`;
|
|
1354
1730
|
if (opts.open === false) {
|
|
1355
1731
|
console.log(url);
|
|
1356
1732
|
return;
|