total-recall-brain 3.18.3 → 3.19.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/frontend/dist/assets/{index-CGqvdwHT.js → index-B6AfuKv9.js} +115 -115
- package/frontend/dist/index.html +1 -1
- package/frontend/src/api/update.ts +29 -4
- package/frontend/src/pages/HealthPage.tsx +12 -16
- package/frontend/src/pages/SettingsPage.tsx +22 -1
- package/package.json +1 -1
- package/src/cli/agent-dir.mjs +15 -1
- package/src/cli/chat.mjs +1 -1
- package/src/cli/daemon.mjs +2 -1
- package/src/cli/dream.mjs +14 -10
- package/src/cli/forget.mjs +14 -2
- package/src/cli/friction.mjs +1 -1
- package/src/cli/import-rules.mjs +8 -3
- package/src/cli/ingest.mjs +3 -2
- package/src/cli/lint.mjs +9 -6
- package/src/cli/rebuild.mjs +9 -4
- package/src/cli/remember.mjs +21 -15
- package/src/cli/secret.mjs +4 -1
- package/src/cli/share.mjs +5 -3
- package/src/cli/skill-projection.mjs +21 -2
- package/src/cli/snapshot.mjs +10 -8
- package/src/cli/update.mjs +2 -1
- package/src/core/clarity-rewriter.mjs +31 -12
- package/src/core/config.mjs +10 -4
- package/src/core/daemon-loop.mjs +2 -1
- package/src/core/embeddings.mjs +51 -3
- package/src/core/memory-title.mjs +70 -0
- package/src/core/runtime.mjs +11 -1
- package/src/core/secrets-store.mjs +7 -1
- package/src/core/skills-registry.mjs +12 -0
- package/src/core/surface.mjs +4 -4
- package/src/core/throttled-fetch.mjs +5 -0
- package/src/core/vault.mjs +7 -1
- package/src/server/index.mjs +56 -0
- package/src/server/route-manifest.json +16 -0
- package/src/server/routes/_shared.mjs +33 -1
- package/src/server/routes/context.mjs +14 -9
- package/src/server/routes/dashboard.mjs +39 -18
- package/src/server/routes/docs.mjs +27 -13
- package/src/server/routes/dream.mjs +10 -11
- package/src/server/routes/export.mjs +43 -12
- package/src/server/routes/field.mjs +11 -9
- package/src/server/routes/graph.mjs +31 -13
- package/src/server/routes/import.mjs +35 -16
- package/src/server/routes/instructions.mjs +41 -5
- package/src/server/routes/memory.mjs +61 -21
- package/src/server/routes/share.mjs +2 -1
- package/src/server/routes/ssss.mjs +11 -3
- package/src/server/routes/update.mjs +22 -3
- package/src/server/routes/vault.mjs +53 -28
package/frontend/dist/index.html
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
18
18
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
19
19
|
<title>Total Recall — Portable Memory</title>
|
|
20
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
+
<script type="module" crossorigin src="/assets/index-B6AfuKv9.js"></script>
|
|
21
21
|
<link rel="stylesheet" crossorigin href="/assets/index-B4RASWbV.css">
|
|
22
22
|
</head>
|
|
23
23
|
<body>
|
|
@@ -25,8 +25,33 @@ export async function checkUpdate(): Promise<UpdateCheckResult> {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export async function runUpdate(): Promise<{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
export async function runUpdate(): Promise<{
|
|
29
|
+
success: boolean
|
|
30
|
+
message: string
|
|
31
|
+
summary?: {
|
|
32
|
+
latest?: string
|
|
33
|
+
updated?: number
|
|
34
|
+
failed?: number
|
|
35
|
+
up_to_date?: number
|
|
36
|
+
results?: { name: string; status: string; error?: string | null }[]
|
|
37
|
+
}
|
|
38
|
+
}> {
|
|
39
|
+
const res = await apiFetch(`${API_BASE}/api/update/run`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ force: true }),
|
|
43
|
+
})
|
|
44
|
+
const data = await res.json().catch(() => ({}))
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(data.error || data.message || `Run update API error: ${res.status}`)
|
|
47
|
+
}
|
|
48
|
+
// API historically omitted success — treat finished-with-0-failures as success
|
|
49
|
+
const failed = Number(data.summary?.failed ?? 0)
|
|
50
|
+
const success =
|
|
51
|
+
typeof data.success === 'boolean' ? data.success : failed === 0 && data.summary?.skipped !== true
|
|
52
|
+
return {
|
|
53
|
+
success,
|
|
54
|
+
message: data.message || (success ? 'Package auto-update finished' : 'Package auto-update failed'),
|
|
55
|
+
summary: data.summary,
|
|
56
|
+
}
|
|
32
57
|
}
|
|
@@ -85,23 +85,19 @@ export default function HealthPage() {
|
|
|
85
85
|
setUpdateError('')
|
|
86
86
|
|
|
87
87
|
try {
|
|
88
|
-
await runUpdate()
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const res = await fetchHealth()
|
|
95
|
-
if (serverDown && res.status === 'healthy') {
|
|
96
|
-
clearInterval(pollInterval)
|
|
97
|
-
window.location.reload()
|
|
98
|
-
}
|
|
99
|
-
} catch {
|
|
100
|
-
// Connection failed = server is offline / restarting
|
|
101
|
-
serverDown = true
|
|
102
|
-
}
|
|
103
|
-
}, 2000)
|
|
88
|
+
const result = await runUpdate()
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
setUpdateError(result.message || 'Package auto-update reported failures')
|
|
91
|
+
setIsUpdating(false)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
104
94
|
|
|
95
|
+
// Multi-repo npm update does not reboot this host — show success, optional reload
|
|
96
|
+
setIsUpdating(false)
|
|
97
|
+
setCheckMessage(
|
|
98
|
+
result.message ||
|
|
99
|
+
'Registered projects updated. Restart local daemon/server if you need new code in this process.',
|
|
100
|
+
)
|
|
105
101
|
} catch (e) {
|
|
106
102
|
setUpdateError((e as Error).message)
|
|
107
103
|
setIsUpdating(false)
|
|
@@ -117,7 +117,28 @@ export default function SettingsPage({ activeBrainId }: { activeBrainId?: string
|
|
|
117
117
|
setUpdateMessage(null);
|
|
118
118
|
try {
|
|
119
119
|
const res = await runUpdate();
|
|
120
|
-
|
|
120
|
+
if (res.success) {
|
|
121
|
+
const s = res.summary;
|
|
122
|
+
const bits = [
|
|
123
|
+
s?.updated != null ? `${s.updated} updated` : null,
|
|
124
|
+
s?.up_to_date != null ? `${s.up_to_date} current` : null,
|
|
125
|
+
s?.latest ? `latest ${s.latest}` : null,
|
|
126
|
+
].filter(Boolean);
|
|
127
|
+
setUpdateMessage(
|
|
128
|
+
bits.length
|
|
129
|
+
? `Update complete — ${bits.join(' · ')}. Restart daemon/server if this host still runs old code.`
|
|
130
|
+
: res.message || 'Update complete.',
|
|
131
|
+
);
|
|
132
|
+
} else {
|
|
133
|
+
const failDetail = (res.summary?.results || [])
|
|
134
|
+
.filter((r) => r.status === 'failed')
|
|
135
|
+
.map((r) => `${r.name}: ${r.error || 'failed'}`)
|
|
136
|
+
.slice(0, 3)
|
|
137
|
+
.join('; ');
|
|
138
|
+
setUpdateMessage(
|
|
139
|
+
`Update failed: ${res.message}${failDetail ? ` (${failDetail})` : ''}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
121
142
|
const refreshed = await checkUpdate().catch(() => null);
|
|
122
143
|
if (refreshed) {
|
|
123
144
|
setUpdateInfo({
|
package/package.json
CHANGED
package/src/cli/agent-dir.mjs
CHANGED
|
@@ -91,7 +91,21 @@ export function resolveAgentDir(layer = 'auto', targetPath = process.cwd()) {
|
|
|
91
91
|
* @param {'global' | 'project' | 'auto'} [layer='auto']
|
|
92
92
|
*/
|
|
93
93
|
export function resolveBrainDir(layer = 'auto', targetPath = process.cwd()) {
|
|
94
|
-
|
|
94
|
+
// Match resolveAgentDir: AGENT_DIR override pins the agent root → brain under it.
|
|
95
|
+
if (process.env.AGENT_DIR) {
|
|
96
|
+
return path.join(process.env.AGENT_DIR, 'skills', 'total-recall');
|
|
97
|
+
}
|
|
98
|
+
if (layer === 'global') return getGlobalBrainDir();
|
|
99
|
+
if (layer === 'project') {
|
|
100
|
+
const project = detectProjectBrain(targetPath);
|
|
101
|
+
if (!project) {
|
|
102
|
+
throw new Error('No project brain found. Run `npx total-recall init --project` to create one.');
|
|
103
|
+
}
|
|
104
|
+
return project.brainDir;
|
|
105
|
+
}
|
|
106
|
+
// Auto: project-local wins if a project brain exists
|
|
107
|
+
const project = detectProjectBrain(targetPath);
|
|
108
|
+
return project ? project.brainDir : getGlobalBrainDir();
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
/**
|
package/src/cli/chat.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { callLocalRuntime, loadRuntimeConfig, checkRuntimeHealth } from '../core
|
|
|
10
10
|
export default async function runChat(args) {
|
|
11
11
|
console.log('🤖 Total Recall CLI Chat initialized. Type "exit" to quit.\n');
|
|
12
12
|
|
|
13
|
-
const configDir = path.join(resolveBrainDir(), 'config');
|
|
13
|
+
const configDir = path.join(resolveBrainDir('global'), 'config');
|
|
14
14
|
|
|
15
15
|
let config;
|
|
16
16
|
let caller;
|
package/src/cli/daemon.mjs
CHANGED
|
@@ -22,7 +22,8 @@ import { startDaemon, stopDaemon, readPid } from '../core/daemon-control.mjs';
|
|
|
22
22
|
|
|
23
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
24
|
const ROOT = path.join(__dirname, '..', '..');
|
|
25
|
-
|
|
25
|
+
// System daemon always targets the global brain — never the cwd project vault.
|
|
26
|
+
const BRAIN_DIR = resolveBrainDir('global');
|
|
26
27
|
const LOG_FILE = path.join(BRAIN_DIR, 'logs', 'daemon.log');
|
|
27
28
|
|
|
28
29
|
function commandExists(cmd) {
|
package/src/cli/dream.mjs
CHANGED
|
@@ -13,11 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import { resolveAgentDir, resolveBrainDir } from './agent-dir.mjs';
|
|
16
|
+
import { resolveAgentDir, resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
17
17
|
|
|
18
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const AGENT_DIR = resolveAgentDir();
|
|
20
|
-
const BRAIN_DIR = resolveBrainDir();
|
|
21
19
|
|
|
22
20
|
function parseArgs(args) {
|
|
23
21
|
const opts = { vault: null, help: false };
|
|
@@ -42,20 +40,26 @@ function printHelp() {
|
|
|
42
40
|
Usage: total-recall dream [options]
|
|
43
41
|
|
|
44
42
|
Options:
|
|
45
|
-
--vault <path> Override vault directory
|
|
43
|
+
--vault <path> Override vault directory
|
|
44
|
+
--global Dream the global brain (default)
|
|
45
|
+
--project Dream the project brain for the current repo
|
|
46
46
|
--help, -h Show this help
|
|
47
47
|
`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export default async function dream(args) {
|
|
51
|
-
const
|
|
51
|
+
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
52
|
+
const effectiveLayer = layer === 'auto' ? 'global' : layer;
|
|
53
|
+
const opts = parseArgs(remainingArgs);
|
|
52
54
|
if (opts.help) { printHelp(); return; }
|
|
53
55
|
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
56
|
+
const agentDir = resolveAgentDir(effectiveLayer);
|
|
57
|
+
const brainDir = resolveBrainDir(effectiveLayer);
|
|
58
|
+
const vaultDir = opts.vault || path.join(brainDir, 'memory-vault');
|
|
59
|
+
const skillsDir = path.join(agentDir, 'skills');
|
|
60
|
+
const derivedDir = path.join(brainDir, 'memory-derived');
|
|
61
|
+
const conflictsDir = path.join(brainDir, 'memory-inbox', 'conflicts');
|
|
62
|
+
const instructionsFile = path.join(agentDir, 'INSTRUCTIONS.md');
|
|
59
63
|
|
|
60
64
|
console.error('\n 🌙 Starting dream cycle...\n');
|
|
61
65
|
const startTime = Date.now();
|
package/src/cli/forget.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { resolveAgentDir, resolveBrainDir, parseLayerFlag, getBothBrains } from './agent-dir.mjs';
|
|
4
4
|
import { compileSurface } from '../core/surface.mjs';
|
|
5
5
|
import { deleteNode } from '../core/vault.mjs';
|
|
6
|
+
import { invalidate } from '../core/vault-cache.mjs';
|
|
6
7
|
|
|
7
8
|
function printHelp() {
|
|
8
9
|
console.log(`
|
|
@@ -65,12 +66,16 @@ export default async function forget(args) {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
console.log(` ✅ Soft-deleted ${count} nodes for project "${projectName}" (moved to .trash/)`);
|
|
69
|
+
|
|
70
|
+
// Files moved under .trash still invalidate the vault cache (watcher may lag)
|
|
71
|
+
invalidate(globalVaultDir);
|
|
68
72
|
|
|
69
73
|
if (!remainingArgs.includes('--no-compile')) {
|
|
70
74
|
console.log(' ⏳ Recompiling active memory surfaces and indexes in the background...');
|
|
71
75
|
try {
|
|
72
76
|
const { spawn } = await import('node:child_process');
|
|
73
|
-
|
|
77
|
+
// Soft-delete from global vault tags — recompile global surfaces
|
|
78
|
+
const child = spawn(process.argv[0], [process.argv[1], 'compile', '--global'], {
|
|
74
79
|
detached: true,
|
|
75
80
|
stdio: 'ignore'
|
|
76
81
|
});
|
|
@@ -130,6 +135,9 @@ export default async function forget(args) {
|
|
|
130
135
|
process.exit(1);
|
|
131
136
|
}
|
|
132
137
|
|
|
138
|
+
// Drop cached nodes immediately (fs.watch may lag or miss same-process deletes)
|
|
139
|
+
invalidate(vaultDir);
|
|
140
|
+
|
|
133
141
|
console.log(` ✅ Deleted memory node "${slug}" from ${layerLabel} vault.`);
|
|
134
142
|
|
|
135
143
|
// Recompile unless --no-compile
|
|
@@ -137,7 +145,11 @@ export default async function forget(args) {
|
|
|
137
145
|
console.log(' ⏳ Recompiling active memory surfaces and indexes in the background...');
|
|
138
146
|
try {
|
|
139
147
|
const { spawn } = await import('node:child_process');
|
|
140
|
-
|
|
148
|
+
// Pass layer so project forget recompiles the project brain, not auto/wrong vault
|
|
149
|
+
const compileArgs = [process.argv[1], 'compile'];
|
|
150
|
+
if (layer === 'project') compileArgs.push('--project');
|
|
151
|
+
else if (layer === 'global') compileArgs.push('--global');
|
|
152
|
+
const child = spawn(process.argv[0], compileArgs, {
|
|
141
153
|
detached: true,
|
|
142
154
|
stdio: 'ignore'
|
|
143
155
|
});
|
package/src/cli/friction.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export default async function runFriction(args) {
|
|
|
9
9
|
console.log(`[Friction] 🔍 Analyzing logs for workflow bottlenecks...`);
|
|
10
10
|
|
|
11
11
|
// Default path for watchdog logs
|
|
12
|
-
const logPath = args[0] || path.join(resolveBrainDir(), 'logs', 'watchdog.jsonl');
|
|
12
|
+
const logPath = args[0] || path.join(resolveBrainDir('global'), 'logs', 'watchdog.jsonl');
|
|
13
13
|
|
|
14
14
|
const result = await detectFriction(logPath);
|
|
15
15
|
if (!result) {
|
package/src/cli/import-rules.mjs
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { detectAndImport } from '../core/import-rules.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import { resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
12
12
|
|
|
13
13
|
export async function run(argv = []) {
|
|
14
|
-
const
|
|
14
|
+
const rawArgs = argv.slice(3); // strip node, script, 'import'
|
|
15
|
+
const { layer, remainingArgs: args } = parseLayerFlag(rawArgs);
|
|
15
16
|
|
|
16
17
|
const dirs = [];
|
|
17
18
|
let force = false;
|
|
@@ -33,7 +34,8 @@ export async function run(argv = []) {
|
|
|
33
34
|
// Default: search cwd
|
|
34
35
|
if (dirs.length === 0) dirs.push(process.cwd());
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
// auto → project vault when present (import rules for this repo)
|
|
38
|
+
const brainDir = resolveBrainDir(layer);
|
|
37
39
|
const vaultDir = path.join(brainDir, 'memory-vault');
|
|
38
40
|
|
|
39
41
|
console.log(`\n🔍 Scanning for rule files in: ${dirs.join(', ')}\n`);
|
|
@@ -90,12 +92,15 @@ all future AI sessions.
|
|
|
90
92
|
|
|
91
93
|
Options:
|
|
92
94
|
--dir <path> Directory to search (default: current directory)
|
|
95
|
+
--global Import into the global vault
|
|
96
|
+
--project Import into the project vault (default: auto when present)
|
|
93
97
|
--force Re-import files that were previously imported
|
|
94
98
|
--dry-run Show what would be imported without writing anything
|
|
95
99
|
--help Show this help
|
|
96
100
|
|
|
97
101
|
Examples:
|
|
98
102
|
npx total-recall import
|
|
103
|
+
npx total-recall import --project
|
|
99
104
|
npx total-recall import --dir ~/my-project
|
|
100
105
|
npx total-recall import --force --dry-run
|
|
101
106
|
`);
|
package/src/cli/ingest.mjs
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
import path from 'node:path';
|
|
17
17
|
import { resolveAgentDir, resolveBrainDir } from './agent-dir.mjs';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const
|
|
19
|
+
// Session ingest lands in the global brain by default.
|
|
20
|
+
const AGENT_DIR = resolveAgentDir('global');
|
|
21
|
+
const BRAIN_DIR = resolveBrainDir('global');
|
|
21
22
|
|
|
22
23
|
function parseArgs(args) {
|
|
23
24
|
const opts = { sources: null, watch: false, help: false };
|
package/src/cli/lint.mjs
CHANGED
|
@@ -17,11 +17,9 @@
|
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
|
-
import {
|
|
20
|
+
import { resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
21
21
|
|
|
22
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
-
const AGENT_DIR = resolveAgentDir();
|
|
24
|
-
const BRAIN_DIR = resolveBrainDir();
|
|
25
23
|
|
|
26
24
|
function parseArgs(args) {
|
|
27
25
|
const opts = { vault: null, strict: false, json: false, help: false, okf: false };
|
|
@@ -47,7 +45,9 @@ function printHelp() {
|
|
|
47
45
|
Usage: total-recall lint [options]
|
|
48
46
|
|
|
49
47
|
Options:
|
|
50
|
-
--vault <path> Override vault directory (default:
|
|
48
|
+
--vault <path> Override vault directory (default: global brain vault)
|
|
49
|
+
--global Lint the global vault (default)
|
|
50
|
+
--project Lint the project vault for the current repo
|
|
51
51
|
--strict Treat warnings as errors (exit 1 on any issue)
|
|
52
52
|
--okf Verify OKF v0.1 draft metadata compliance
|
|
53
53
|
--json Output results as JSONL
|
|
@@ -60,10 +60,13 @@ function printHelp() {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export default async function lint(args) {
|
|
63
|
-
const
|
|
63
|
+
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
64
|
+
// Default layer is global when no --project/--global (matches help text)
|
|
65
|
+
const effectiveLayer = layer === 'auto' ? 'global' : layer;
|
|
66
|
+
const opts = parseArgs(remainingArgs);
|
|
64
67
|
if (opts.help) { printHelp(); return; }
|
|
65
68
|
|
|
66
|
-
const vaultDir = opts.vault || path.join(
|
|
69
|
+
const vaultDir = opts.vault || path.join(resolveBrainDir(effectiveLayer), 'memory-vault');
|
|
67
70
|
|
|
68
71
|
if (opts.okf) {
|
|
69
72
|
const { lintOkfCompliance } = await import('../core/okf-adapter.mjs');
|
package/src/cli/rebuild.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { compileSurface } from '../core/surface.mjs';
|
|
4
4
|
import { detectIndexDrift } from '../core/drift-detector.mjs';
|
|
5
|
-
import { resolveAgentDir, resolveBrainDir } from './agent-dir.mjs';
|
|
5
|
+
import { resolveAgentDir, resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
6
6
|
import { logger } from '../core/logger.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -14,8 +14,9 @@ import { logger } from '../core/logger.mjs';
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
export async function runRebuild(options = {}) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const layer = options.layer || 'auto';
|
|
18
|
+
const agentDir = resolveAgentDir(layer);
|
|
19
|
+
const brainDir = resolveBrainDir(layer);
|
|
19
20
|
const vaultDir = path.join(brainDir, 'memory-vault');
|
|
20
21
|
const skillsDir = path.join(agentDir, 'skills');
|
|
21
22
|
const derivedDir = path.join(brainDir, 'memory-derived');
|
|
@@ -82,7 +83,11 @@ export async function runRebuild(options = {}) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export default async function cli(args) {
|
|
85
|
-
const
|
|
86
|
+
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
87
|
+
const options = {
|
|
88
|
+
check: remainingArgs.includes('--check'),
|
|
89
|
+
layer,
|
|
90
|
+
};
|
|
86
91
|
const code = await runRebuild(options);
|
|
87
92
|
process.exit(code);
|
|
88
93
|
}
|
package/src/cli/remember.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import matter from 'gray-matter';
|
|
|
5
5
|
import { resolveAgentDir, resolveBrainDir, parseLayerFlag, getBothBrains, defaultLayerForCategory } from './agent-dir.mjs';
|
|
6
6
|
import { compileSurface } from '../core/surface.mjs';
|
|
7
7
|
import { writeNodeValidatedAsync } from '../core/validated-write.mjs';
|
|
8
|
+
import { defaultTitleFromBody } from '../core/memory-title.mjs';
|
|
8
9
|
|
|
9
10
|
function printHelp() {
|
|
10
11
|
console.log(`
|
|
@@ -243,8 +244,12 @@ export default async function remember(args) {
|
|
|
243
244
|
// Create individual SSSS v2 node in the vault
|
|
244
245
|
const contentHash = crypto.createHash('md5').update(bodyContent).digest('hex').slice(0, 8);
|
|
245
246
|
const finalSlug = slug || `${category}-${contentHash}`;
|
|
246
|
-
|
|
247
|
-
const finalTitle = title ||
|
|
247
|
+
// Provenance lives in source.type + tags — not a title prefix
|
|
248
|
+
const finalTitle = title || defaultTitleFromBody(bodyContent);
|
|
249
|
+
const finalTags = Array.isArray(tags) ? [...tags] : [];
|
|
250
|
+
if (!finalTags.includes('self-captured') && !title) {
|
|
251
|
+
finalTags.push('self-captured');
|
|
252
|
+
}
|
|
248
253
|
|
|
249
254
|
const now = new Date().toISOString();
|
|
250
255
|
|
|
@@ -264,12 +269,13 @@ export default async function remember(args) {
|
|
|
264
269
|
source: {
|
|
265
270
|
type: 'remember-cli',
|
|
266
271
|
session_id: process.env.TR_SESSION_ID || 'remember-session',
|
|
267
|
-
evidence_count: 1
|
|
272
|
+
evidence_count: 1,
|
|
273
|
+
capture: 'self',
|
|
268
274
|
},
|
|
269
275
|
supersedes: [],
|
|
270
276
|
superseded_by: null,
|
|
271
277
|
contradicts: [],
|
|
272
|
-
tags,
|
|
278
|
+
tags: finalTags,
|
|
273
279
|
related,
|
|
274
280
|
routes_to_skills: [],
|
|
275
281
|
sentiment_polarity: type.toLowerCase() === 'preference' ? 'preference' : 'descriptive',
|
|
@@ -309,8 +315,9 @@ export default async function remember(args) {
|
|
|
309
315
|
const oldTitle = (data.title || '').trim().toLowerCase();
|
|
310
316
|
const newTitle = finalTitle.trim().toLowerCase();
|
|
311
317
|
|
|
312
|
-
|
|
313
|
-
const
|
|
318
|
+
// Word tokens for Jaccard similarity (must be real \b/\w, not double-escaped literals)
|
|
319
|
+
const words1 = new Set(bodyContent.toLowerCase().match(/\b\w+\b/g) || []);
|
|
320
|
+
const words2 = new Set((oldContent || '').toLowerCase().match(/\b\w+\b/g) || []);
|
|
314
321
|
const intersection = new Set([...words1].filter(x => words2.has(x)));
|
|
315
322
|
const union = new Set([...words1, ...words2]);
|
|
316
323
|
const similarity = union.size === 0 ? 0 : intersection.size / union.size;
|
|
@@ -325,14 +332,10 @@ export default async function remember(args) {
|
|
|
325
332
|
data.superseded_by = finalSlug;
|
|
326
333
|
data.updated = now;
|
|
327
334
|
node.supersedes = [...(node.supersedes || []), data.slug];
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
lines.push('---');
|
|
334
|
-
lines.push(oldContent);
|
|
335
|
-
fs.writeFileSync(p, lines.join('\n'));
|
|
335
|
+
|
|
336
|
+
// Use gray-matter so nested objects/arrays stay valid YAML
|
|
337
|
+
const bodyOut = (oldContent || '').trim();
|
|
338
|
+
fs.writeFileSync(p, matter.stringify(bodyOut ? `${bodyOut}\n` : '', data), 'utf8');
|
|
336
339
|
}
|
|
337
340
|
}
|
|
338
341
|
}
|
|
@@ -360,7 +363,10 @@ export default async function remember(args) {
|
|
|
360
363
|
console.log(' ⏳ Recompiling active memory surfaces and indexes in the background...');
|
|
361
364
|
try {
|
|
362
365
|
const { spawn } = await import('node:child_process');
|
|
363
|
-
const
|
|
366
|
+
const compileArgs = [process.argv[1], 'compile'];
|
|
367
|
+
if (layer === 'project') compileArgs.push('--project');
|
|
368
|
+
else if (layer === 'global') compileArgs.push('--global');
|
|
369
|
+
const child = spawn(process.argv[0], compileArgs, {
|
|
364
370
|
detached: true,
|
|
365
371
|
stdio: 'ignore'
|
|
366
372
|
});
|
package/src/cli/secret.mjs
CHANGED
|
@@ -985,9 +985,12 @@ export default async function secretCli(argv) {
|
|
|
985
985
|
|
|
986
986
|
if (opts.command === 'check-surfaces') {
|
|
987
987
|
const texts = [];
|
|
988
|
+
const agentRoot = path.dirname(path.dirname(brainDir)); // …/skills/total-recall → …/.agent (or similar)
|
|
988
989
|
const candidates = [
|
|
989
|
-
path.join(brainDir, 'INSTRUCTIONS.md'),
|
|
990
990
|
path.join(process.cwd(), 'INSTRUCTIONS.md'),
|
|
991
|
+
path.join(process.cwd(), 'AGENTS.md'),
|
|
992
|
+
path.join(agentRoot, 'INSTRUCTIONS.md'),
|
|
993
|
+
path.join(brainDir, 'INSTRUCTIONS.md'), // legacy
|
|
991
994
|
path.join(brainDir, 'openwiki'),
|
|
992
995
|
];
|
|
993
996
|
for (const c of candidates) {
|
package/src/cli/share.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import { parseLayerFlag, resolveBrainDir, getBothBrains, defaultLayerForCategory } from './agent-dir.mjs';
|
|
2
|
+
import { parseLayerFlag, resolveAgentDir, resolveBrainDir, getBothBrains, defaultLayerForCategory } from './agent-dir.mjs';
|
|
3
3
|
import { createMemoryNode } from '../core/vault.mjs';
|
|
4
4
|
import { writeNodeValidatedAsync } from '../core/validated-write.mjs';
|
|
5
5
|
import { addToQueue } from '../core/research-queue.mjs';
|
|
@@ -128,10 +128,12 @@ export default async function share(args) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const resolvedBrainDir = resolveBrainDir(layer);
|
|
131
|
+
const resolvedAgentDir = resolveAgentDir(layer);
|
|
131
132
|
const vaultDir = path.join(resolvedBrainDir, 'memory-vault');
|
|
132
|
-
|
|
133
|
+
// skillsDir must be <agent>/.agent/skills (or ~/.agent/skills), not the agent root
|
|
134
|
+
const skillsDir = path.join(resolvedAgentDir, 'skills');
|
|
133
135
|
const derivedDir = path.join(resolvedBrainDir, 'memory-derived');
|
|
134
|
-
const instructionsFile = path.join(
|
|
136
|
+
const instructionsFile = path.join(resolvedAgentDir, 'INSTRUCTIONS.md');
|
|
135
137
|
|
|
136
138
|
const slugBase = category || 'facts';
|
|
137
139
|
const slug = `${slugBase}-${crypto.randomBytes(4).toString('hex')}`;
|
|
@@ -70,6 +70,22 @@ function pathExists(filePath) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// Marker written by tools like repo-expert-generate.mjs into any SKILL.md
|
|
74
|
+
// they produce. A real (non-symlink) directory carrying this marker is
|
|
75
|
+
// leftover tool output, not a user's hand-authored skill — it is always safe
|
|
76
|
+
// to heal back onto the canonical source, even without --force.
|
|
77
|
+
const AUTO_GENERATED_MARKER = '**Auto-generated** by `npx total-recall';
|
|
78
|
+
|
|
79
|
+
function isStaleGeneratedDir(entryPath) {
|
|
80
|
+
try {
|
|
81
|
+
if (!fs.statSync(entryPath).isDirectory()) return false;
|
|
82
|
+
const content = fs.readFileSync(path.join(entryPath, 'SKILL.md'), 'utf8');
|
|
83
|
+
return content.includes(AUTO_GENERATED_MARKER);
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
/**
|
|
74
90
|
* Skill source roots, in priority order. The canonical Total Recall location
|
|
75
91
|
* (.agent/skills, singular) wins, but we also scan the Antigravity-native
|
|
@@ -115,7 +131,10 @@ export function discoverRepoSkills(agentDir) {
|
|
|
115
131
|
* Idempotent, self-healing, and strictly additive: a symlink that is broken or
|
|
116
132
|
* stale is refreshed even without --force; a *real* (non-symlink) entry is
|
|
117
133
|
* never clobbered unless --force (so a user's own skill always wins a name
|
|
118
|
-
* collision)
|
|
134
|
+
* collision) — UNLESS that real entry is itself stale tool output (carries
|
|
135
|
+
* the auto-generated marker), in which case it is healed back onto the
|
|
136
|
+
* canonical source; a skill whose source already *is* the destination is
|
|
137
|
+
* left alone.
|
|
119
138
|
*/
|
|
120
139
|
export function projectSkillsAsCommands(destDir, skills, opts = {}) {
|
|
121
140
|
fs.mkdirSync(destDir, { recursive: true });
|
|
@@ -138,7 +157,7 @@ export function projectSkillsAsCommands(destDir, skills, opts = {}) {
|
|
|
138
157
|
continue;
|
|
139
158
|
}
|
|
140
159
|
fs.unlinkSync(linkPath); // stale, broken, or --force → refresh
|
|
141
|
-
} else if (!opts.force) {
|
|
160
|
+
} else if (!opts.force && !isStaleGeneratedDir(linkPath)) {
|
|
142
161
|
written.push({ name: skill.name, action: 'skipped' }); // user's real file/dir wins
|
|
143
162
|
continue;
|
|
144
163
|
} else {
|
package/src/cli/snapshot.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createSnapshot, listSnapshots, rollbackVault } from '../core/snapshot.mjs';
|
|
2
|
-
import { resolveAgentDir, resolveBrainDir } from './agent-dir.mjs';
|
|
2
|
+
import { resolveAgentDir, resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
3
3
|
import { compileSurface } from '../core/surface.mjs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
|
|
@@ -24,20 +24,22 @@ function printHelp() {
|
|
|
24
24
|
rollback <id> Restore the vault to a specific snapshot ID
|
|
25
25
|
|
|
26
26
|
Options:
|
|
27
|
-
--
|
|
27
|
+
--global / --project Target brain layer (default: auto — project if present)
|
|
28
|
+
--help, -h Show this help
|
|
28
29
|
`);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export default async function snapshotCli(args) {
|
|
32
|
-
|
|
33
|
+
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
34
|
+
if (remainingArgs.length === 0 || remainingArgs.includes('--help') || remainingArgs.includes('-h')) {
|
|
33
35
|
printHelp();
|
|
34
36
|
return;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
const command =
|
|
39
|
+
const command = remainingArgs[0];
|
|
38
40
|
|
|
39
41
|
if (command === 'create') {
|
|
40
|
-
const reason =
|
|
42
|
+
const reason = remainingArgs.slice(1).join(' ') || 'manual';
|
|
41
43
|
console.log('📸 Creating snapshot...');
|
|
42
44
|
const result = createSnapshot(reason);
|
|
43
45
|
if (result.success) {
|
|
@@ -69,7 +71,7 @@ export default async function snapshotCli(args) {
|
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
else if (command === 'rollback') {
|
|
72
|
-
const id =
|
|
74
|
+
const id = remainingArgs[1];
|
|
73
75
|
if (!id) {
|
|
74
76
|
console.error('❌ You must provide a snapshot ID to rollback to.');
|
|
75
77
|
console.error('Usage: total-recall snapshot rollback <id>');
|
|
@@ -84,8 +86,8 @@ export default async function snapshotCli(args) {
|
|
|
84
86
|
console.log(`✅ Rollback successful.`);
|
|
85
87
|
console.log(`🔄 Recompiling derived indexes from restored vault...`);
|
|
86
88
|
|
|
87
|
-
const agentDir = resolveAgentDir();
|
|
88
|
-
const brainDir = resolveBrainDir();
|
|
89
|
+
const agentDir = resolveAgentDir(layer);
|
|
90
|
+
const brainDir = resolveBrainDir(layer);
|
|
89
91
|
await compileSurface({
|
|
90
92
|
vaultDir: path.join(brainDir, 'memory-vault'),
|
|
91
93
|
skillsDir: path.join(agentDir, 'skills'),
|
package/src/cli/update.mjs
CHANGED
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
} from '../core/package-auto-update.mjs';
|
|
17
17
|
import { brainDir } from '../core/config.mjs';
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/** @param {string[]} args */
|
|
20
|
+
export function parseArgs(args) {
|
|
20
21
|
const opts = {
|
|
21
22
|
apply: false,
|
|
22
23
|
force: false,
|