total-recall-brain 3.18.3 → 3.19.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/frontend/dist/assets/{index-CGqvdwHT.js → index-D0XeeXhG.js} +119 -119
- 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/frontend/src/pages/SkillsPage.tsx +40 -13
- 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 +52 -7
- 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/source-adapters.mjs +1 -1
- package/src/core/surface.mjs +4 -4
- package/src/core/throttled-fetch.mjs +59 -24
- 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/rules.mjs +11 -27
- package/src/server/routes/share.mjs +2 -1
- package/src/server/routes/skills.mjs +3 -0
- 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-D0XeeXhG.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({
|
|
@@ -189,6 +189,22 @@ export default function SkillsPage({ activeBrainId }: { activeBrainId?: string }
|
|
|
189
189
|
const [installingPkg, setInstallingPkg] = useState<string | null>(null);
|
|
190
190
|
const [quarantineAlert, setQuarantineAlert] = useState<{ pkg: string; reason: string } | null>(null);
|
|
191
191
|
|
|
192
|
+
// ─── Active brain → single deployable repo target ──────────────────────────
|
|
193
|
+
// activeBrainId is 'global', 'project:<name>', or a comma-joined multi-select
|
|
194
|
+
// (see BrainSelector). Only a single, non-global selection maps to one concrete
|
|
195
|
+
// project repo we can deploy a global skill into.
|
|
196
|
+
const activeRepoName = useMemo(() => {
|
|
197
|
+
if (!activeBrainId) return null;
|
|
198
|
+
const ids = activeBrainId.split(',').map((s) => s.trim()).filter(Boolean);
|
|
199
|
+
if (ids.length !== 1 || ids[0] === 'global') return null;
|
|
200
|
+
const id = ids[0];
|
|
201
|
+
return id.startsWith('project:') ? id.slice('project:'.length) : id;
|
|
202
|
+
}, [activeBrainId]);
|
|
203
|
+
const activeRepoGroup = useMemo(
|
|
204
|
+
() => (activeRepoName ? repoGroups.find((g) => g.repo === activeRepoName) || null : null),
|
|
205
|
+
[activeRepoName, repoGroups],
|
|
206
|
+
);
|
|
207
|
+
|
|
192
208
|
// ─── Lifecycle tab state ────────────────────────────────────────────────────
|
|
193
209
|
const lifecycleVersion = useMemo(() => selectedSkill && skillContent ? extractVersion(skillContent) : '0.0.0', [selectedSkill, skillContent]);
|
|
194
210
|
const changelogEntries = useMemo(() => selectedSkill && skillContent ? parseChangelog(skillContent) : [], [selectedSkill, skillContent]);
|
|
@@ -269,14 +285,23 @@ export default function SkillsPage({ activeBrainId }: { activeBrainId?: string }
|
|
|
269
285
|
groups = (data.repos || []) as RepoSkillsGroup[];
|
|
270
286
|
}
|
|
271
287
|
|
|
272
|
-
|
|
273
|
-
|
|
288
|
+
// activeBrainId may be a comma-joined multi-select (see BrainSelector) — split it
|
|
289
|
+
// rather than treating the whole string as a single repo id, else selecting more
|
|
290
|
+
// than one brain collapsed the list down to only the "Global" group.
|
|
291
|
+
const selectedBrainIds = (activeBrainId || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
292
|
+
if (selectedBrainIds.length > 0 && !selectedBrainIds.includes('global')) {
|
|
293
|
+
const targetRepoIds = selectedBrainIds.map((id) =>
|
|
294
|
+
id.startsWith('project:') ? id.slice('project:'.length) : id,
|
|
295
|
+
);
|
|
274
296
|
groups = groups.filter(
|
|
275
|
-
g => g.repo === 'Global' ||
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
g => g.repo === 'Global' ||
|
|
298
|
+
targetRepoIds.some(
|
|
299
|
+
(targetRepoId) =>
|
|
300
|
+
g.repo === targetRepoId ||
|
|
301
|
+
targetRepoId.endsWith(`/${g.repo}`) ||
|
|
302
|
+
g.repo.endsWith(`/${targetRepoId}`),
|
|
303
|
+
) ||
|
|
304
|
+
selectedBrainIds.includes(`project:${g.repo}`)
|
|
280
305
|
);
|
|
281
306
|
list = groups.flatMap(g => g.skills || []);
|
|
282
307
|
}
|
|
@@ -713,24 +738,26 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
713
738
|
{skill.name}
|
|
714
739
|
</span>
|
|
715
740
|
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
|
|
716
|
-
{
|
|
741
|
+
{activeRepoGroup?.path && group.repo === 'Global' && (
|
|
717
742
|
<input
|
|
718
743
|
type="checkbox"
|
|
719
|
-
checked={!!
|
|
744
|
+
checked={!!activeRepoGroup?.skills?.some(s => s.name === skill.name)}
|
|
720
745
|
onClick={(e) => e.stopPropagation()}
|
|
721
746
|
onChange={async (e) => {
|
|
722
747
|
e.stopPropagation();
|
|
723
748
|
const checked = e.target.checked;
|
|
749
|
+
const targetRepoPath = activeRepoGroup?.path;
|
|
750
|
+
if (!targetRepoPath) return;
|
|
724
751
|
if (checked) {
|
|
725
752
|
// Open preview modal instead of instantly deploying
|
|
726
753
|
setPreviewModalOpen(true);
|
|
727
754
|
setPreviewLoading(true);
|
|
728
755
|
setPreviewData(null);
|
|
729
756
|
try {
|
|
730
|
-
const res = await previewSkillRepo(skill.name,
|
|
757
|
+
const res = await previewSkillRepo(skill.name, targetRepoPath);
|
|
731
758
|
setPreviewData({
|
|
732
759
|
skillName: skill.name,
|
|
733
|
-
targetRepo:
|
|
760
|
+
targetRepo: targetRepoPath,
|
|
734
761
|
original: res.original,
|
|
735
762
|
preview: res.preview,
|
|
736
763
|
signals: res.signals
|
|
@@ -744,7 +771,7 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
744
771
|
} else {
|
|
745
772
|
// Uncheck (disable) happens immediately
|
|
746
773
|
try {
|
|
747
|
-
await toggleSkillRepo(skill.name,
|
|
774
|
+
await toggleSkillRepo(skill.name, targetRepoPath, false);
|
|
748
775
|
void fetchSkillsList();
|
|
749
776
|
} catch (err) {
|
|
750
777
|
setError((err as Error).message);
|
|
@@ -831,7 +858,7 @@ Configure triggers, options, and prompts inside this rules sheet to hot-recompil
|
|
|
831
858
|
<h2 style={{ marginTop: 0, marginBottom: 12 }}>Skill Adaptation Preview</h2>
|
|
832
859
|
{previewLoading ? (
|
|
833
860
|
<div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
|
834
|
-
<p>Generating adaptive preview for {activeBrainId}...</p>
|
|
861
|
+
<p>Generating adaptive preview for {activeRepoName || activeBrainId}...</p>
|
|
835
862
|
</div>
|
|
836
863
|
) : previewData ? (
|
|
837
864
|
<>
|
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')}`;
|