total-recall-brain 3.40.6 → 3.41.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/bin/total-recall-memory.mjs +0 -0
- package/bin/total-recall.mjs +9 -40
- package/docs/reference/cli-reference.md +65 -1
- package/frontend/dist/assets/{index-AGUes-VG.css → index-BG9NkVUy.css} +1 -1
- package/frontend/dist/assets/{index-Bt5iVPZj.js → index-BtuiZqwP.js} +20 -7
- package/frontend/dist/index.html +2 -2
- package/frontend/src/components/plugins/PluginPanel.tsx +31 -5
- package/frontend/src/components/plugins/card/CardEngine.tsx +15 -23
- package/frontend/src/components/plugins/card/CardScenes.tsx +52 -23
- package/frontend/src/components/plugins/card/card.css +2 -5
- package/frontend/src/components/plugins/card/contract.ts +13 -19
- package/frontend/src/components/plugins/card/format.ts +8 -1
- package/frontend/src/components/plugins/card/widgets.tsx +4 -35
- package/frontend/src/components/plugins/plugin-frame.ts +30 -3
- package/frontend/src/pages/PluginsPage.tsx +3 -2
- package/package.json +1 -1
- package/scaffold/.agent/skills/plugin/SKILL.md +110 -0
- package/scaffold/.agent/skills/plugin/evals/README.md +53 -0
- package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +42 -0
- package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +39 -0
- package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +32 -0
- package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +46 -0
- package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +55 -0
- package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +92 -0
- package/scaffold/.agent/skills/plugin/evals/run.mjs +118 -0
- package/scaffold/.agent/skills/plugin/prompts/01-function.md +29 -0
- package/scaffold/.agent/skills/plugin/prompts/02-surfaces.md +43 -0
- package/scaffold/.agent/skills/plugin/prompts/03-scaffold.md +31 -0
- package/scaffold/.agent/skills/plugin/prompts/04-cli.md +49 -0
- package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +41 -0
- package/scaffold/.agent/skills/plugin/prompts/06-card.md +46 -0
- package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +52 -0
- package/scaffold/.agent/skills/plugin/prompts/08-verify.md +43 -0
- package/scaffold/.agent/skills/plugin/references/anti-patterns.md +123 -0
- package/scaffold/.agent/skills/plugin/references/card.md +90 -0
- package/scaffold/.agent/skills/plugin/references/scaffold.md +120 -0
- package/scaffold/.agent/skills/plugin/references/surfaces.md +206 -0
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +360 -0
- package/scaffold/.agent/skills/plugin/subagents/card-designer.md +60 -0
- package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +55 -0
- package/scaffold/.agent/skills/plugin/subagents/scaffold-extractor.md +71 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +71 -1441
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +7 -5
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +3 -44
- package/scaffold/.agent/skills/total-recall/plugins/auth/scripts/agent-login-pty.py +185 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/host-secrets-store.mjs +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +6 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -24
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +0 -21
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +1 -28
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +95 -13
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +553 -340
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +0 -36
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/card.js +383 -400
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +0 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +0 -28
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +0 -32
- package/scaffold/.agent/skills/total-recall/references/auth.md +3 -1
- package/scaffold/.agent/skills/total-recall/references/memory.md +4 -0
- package/scaffold/.agent/skills/total-recall/references/openwiki.md +2 -0
- package/scaffold/.agent/skills/total-recall/references/operations.md +12 -0
- package/src/cli/brain.mjs +4 -2
- package/src/cli/context-stats.mjs +104 -0
- package/src/cli/context.mjs +8 -0
- package/src/cli/daemon.mjs +10 -1
- package/src/cli/export.mjs +3 -2
- package/src/cli/ingest-okf.mjs +12 -15
- package/src/cli/init.mjs +45 -85
- package/src/cli/lint.mjs +3 -2
- package/src/cli/plugin/copies.mjs +68 -0
- package/src/cli/plugin/create.mjs +22 -24
- package/src/cli/plugin/index.mjs +49 -3
- package/src/cli/plugin/search.mjs +19 -2
- package/src/cli/rebuild.mjs +2 -0
- package/src/cli/remember.mjs +23 -0
- package/src/cli/status.mjs +10 -3
- package/src/cli/update.mjs +3 -2
- package/src/core/action-predictor.mjs +161 -0
- package/src/core/app-deploy/runtime-cli/node.mjs.tmpl +20 -3
- package/src/core/app-deploy/source.mjs +16 -9
- package/src/core/automatic-brain-updates.mjs +6 -3
- package/src/core/brain-registry.mjs +19 -2
- package/src/core/brain-state.mjs +23 -0
- package/src/core/cli-commands.mjs +1 -0
- package/src/core/cli-maintenance.mjs +1 -1
- package/src/core/cli-semantic-index.mjs +1 -1
- package/src/core/command-surface.mjs +3 -3
- package/src/core/composed-command-resolution.mjs +21 -0
- package/src/core/config.mjs +84 -0
- package/src/core/confined-memory.mjs +93 -0
- package/src/core/context-telemetry.mjs +214 -0
- package/src/core/daemon-control.mjs +14 -43
- package/src/core/fast-recall.mjs +1 -1
- package/src/core/memory-brain.mjs +2 -0
- package/src/core/plugin-activation.mjs +38 -6
- package/src/core/plugin-bundle.mjs +54 -0
- package/src/core/plugin-catalog.mjs +1 -1
- package/src/core/plugin-context.mjs +1 -1
- package/src/core/plugin-contracts/card.mjs +69 -37
- package/src/core/plugin-loader.mjs +5 -6
- package/src/core/plugin-runner.mjs +13 -6
- package/src/core/plugin-runtime.mjs +5 -4
- package/src/core/plugin-setup.mjs +5 -3
- package/src/core/plugin-state-store.mjs +12 -1
- package/src/core/plugin-state.mjs +46 -5
- package/src/core/plugin-store.mjs +78 -3
- package/src/core/plugin-tasks.mjs +3 -1
- package/src/core/plugin-updates.mjs +117 -0
- package/src/core/plugin-vendor.mjs +10 -4
- package/src/core/project-brain.mjs +14 -41
- package/src/core/project-id.mjs +4 -1
- package/src/core/required-plugins.mjs +151 -13
- package/src/core/skills-registry.mjs +27 -3
- package/src/core/ssss-kernel-bridge.mjs +3 -1
- package/src/core/startup-health.mjs +66 -16
- package/src/core/surface.mjs +51 -44
- package/src/core/testing/plugin-fixture.mjs +69 -0
- package/src/core/vault-files.mjs +13 -0
- package/src/core/vault.mjs +4 -30
- package/src/server/index.mjs +13 -1
- package/src/server/invoke-decision-child.mjs +49 -0
- package/src/server/invoke-execution.mjs +87 -0
- package/src/server/invoke-policy.mjs +64 -0
- package/src/server/invoke-read-child.mjs +26 -0
- package/src/server/invoke-targets.mjs +49 -0
- package/src/server/invoke-write-child.mjs +19 -0
- package/src/server/routes/invoke.mjs +314 -0
- package/src/server/routes/plugins.mjs +1 -1
- package/scaffold/.agent/skills/plugins/SKILL.md +0 -57
- package/scaffold/.agent/skills/plugins/references/card-design.md +0 -55
- package/scaffold/.agent/skills/plugins/references/distribution.md +0 -23
- package/scaffold/.agent/skills/plugins/references/manifest-contract.md +0 -90
- package/scaffold/.agent/skills/plugins/references/optimized/index.md +0 -9
- package/scaffold/.agent/skills/plugins/references/optimized/manifest.json +0 -35
- package/scaffold/.agent/skills/plugins/references/optimized/original.md +0 -58
- package/scaffold/.agent/skills/plugins/references/optimized/section-01.md +0 -5
- package/scaffold/.agent/skills/plugins/references/optimized/section-02.md +0 -25
- package/scaffold/.agent/skills/plugins/references/optimized/section-03.md +0 -12
- package/scaffold/.agent/skills/plugins/references/optimized/section-04.md +0 -7
- package/scaffold/.agent/skills/plugins/references/optimized/section-05.md +0 -5
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +0 -10
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +0 -5
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +0 -10
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +0 -11
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +0 -7
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +0 -7
- package/scaffold/openwiki/README.md +0 -10
- package/scaffold/openwiki/architecture.md +0 -5
- package/scaffold/openwiki/ide.md +0 -10
- package/scaffold/openwiki/memory.md +0 -11
- package/scaffold/openwiki/secrets.md +0 -7
- package/scaffold/openwiki/skills.md +0 -7
- package/src/core/okf-adapter.mjs +0 -559
- package/templates/openwiki/README.md +0 -10
- package/templates/openwiki/architecture.md +0 -5
- package/templates/openwiki/ide.md +0 -10
- package/templates/openwiki/memory.md +0 -11
- package/templates/openwiki/secrets.md +0 -7
- package/templates/openwiki/skills.md +0 -7
package/src/core/okf-adapter.mjs
DELETED
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import crypto from 'crypto';
|
|
4
|
-
import matter from './frontmatter.mjs';
|
|
5
|
-
import { safeStringify } from './vault.mjs';
|
|
6
|
-
import { writeNodeValidatedAsync } from './validated-write.mjs';
|
|
7
|
-
import { getNodes } from './vault-cache.mjs';
|
|
8
|
-
import { validateMemoryNode } from './total-recall-memory-validator.mjs';
|
|
9
|
-
|
|
10
|
-
// ─── DEFAULT OKF TYPE MAP ──────────────────────────────────────────────────
|
|
11
|
-
export const DEFAULT_OKF_TYPE_MAP = {
|
|
12
|
-
'BigQuery Table': 'facts',
|
|
13
|
-
'BigQuery Dataset': 'facts',
|
|
14
|
-
'API Endpoint': 'facts',
|
|
15
|
-
'Metric': 'concepts',
|
|
16
|
-
'Playbook': 'patterns',
|
|
17
|
-
'Runbook': 'patterns',
|
|
18
|
-
'Reference': 'facts',
|
|
19
|
-
'Architecture': 'concepts',
|
|
20
|
-
'Decision': 'decisions',
|
|
21
|
-
'Policy': 'invariants',
|
|
22
|
-
'Best Practice': 'patterns',
|
|
23
|
-
'Anti-Pattern': 'anti-patterns',
|
|
24
|
-
'*': 'facts',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// ─── Pure Mapping Functions ────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Derives a valid SSSS subject from a title.
|
|
31
|
-
*/
|
|
32
|
-
function deriveSubject(title) {
|
|
33
|
-
let subject = 'okf.concept';
|
|
34
|
-
if (title) {
|
|
35
|
-
const cleaned = title.replace(/[^a-zA-Z0-9_\s.-]/g, '').trim();
|
|
36
|
-
if (cleaned) {
|
|
37
|
-
const words = cleaned.split(/\s+/);
|
|
38
|
-
if (words.length > 0 && words[0]) {
|
|
39
|
-
subject = words[0].toLowerCase();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return /^[a-zA-Z0-9_\s.-]+$/.test(subject) ? subject : 'okf.concept';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Derives a description from a Markdown body if absent.
|
|
48
|
-
*/
|
|
49
|
-
function deriveDescription(body) {
|
|
50
|
-
if (!body) return undefined;
|
|
51
|
-
const match = body.trim().match(/^[^.!?\n]+[.!?]/);
|
|
52
|
-
return match ? match[0].trim() : body.trim().split('\n')[0].trim();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Capitalizes SSSS category to OKF type.
|
|
57
|
-
*/
|
|
58
|
-
function categoryToOkfType(category) {
|
|
59
|
-
const reverseMap = {
|
|
60
|
-
'facts': 'Fact',
|
|
61
|
-
'concepts': 'Concept',
|
|
62
|
-
'patterns': 'Pattern',
|
|
63
|
-
'invariants': 'Invariant',
|
|
64
|
-
'decisions': 'Decision',
|
|
65
|
-
'anti-patterns': 'Anti-Pattern',
|
|
66
|
-
'preferences': 'Preference',
|
|
67
|
-
'corrections': 'Correction',
|
|
68
|
-
'lore': 'Lore'
|
|
69
|
-
};
|
|
70
|
-
return reverseMap[category] || (category.charAt(0).toUpperCase() + category.slice(1));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Convert an OKF concept (frontmatter + body) into an SSSS memory node.
|
|
75
|
-
*/
|
|
76
|
-
export function okfConceptToSsssNode(okfFrontmatter, okfBody, conceptId, options = {}) {
|
|
77
|
-
if (!okfFrontmatter || typeof okfFrontmatter !== 'object') {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const actualTypeMap = { ...DEFAULT_OKF_TYPE_MAP, ...options.typeMap };
|
|
82
|
-
const okfType = okfFrontmatter.type;
|
|
83
|
-
|
|
84
|
-
let category = options.category;
|
|
85
|
-
if (!category) {
|
|
86
|
-
category = actualTypeMap[okfType] || actualTypeMap['*'] || 'facts';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Derive slug
|
|
90
|
-
let slug = conceptId || 'unnamed-concept';
|
|
91
|
-
if (slug.endsWith('.md')) {
|
|
92
|
-
slug = slug.slice(0, -3);
|
|
93
|
-
}
|
|
94
|
-
slug = slug.replace(/^(\.\.?[\/\\])+/, '').replace(/^[\/\\]+/, '');
|
|
95
|
-
slug = slug.replace(/[\/\\]/g, '-').toLowerCase();
|
|
96
|
-
|
|
97
|
-
const title = okfFrontmatter.title || conceptId || 'Untitled Concept';
|
|
98
|
-
const subject = okfFrontmatter.title ? deriveSubject(okfFrontmatter.title) : 'okf.concept';
|
|
99
|
-
const object = okfFrontmatter.type || 'concept';
|
|
100
|
-
|
|
101
|
-
// Parse ISO 8601 timestamp
|
|
102
|
-
let updatedTimestamp = new Date().toISOString();
|
|
103
|
-
if (okfFrontmatter.updated) {
|
|
104
|
-
if (okfFrontmatter.updated instanceof Date) {
|
|
105
|
-
updatedTimestamp = okfFrontmatter.updated.toISOString();
|
|
106
|
-
} else if (typeof okfFrontmatter.updated === 'string') {
|
|
107
|
-
updatedTimestamp = okfFrontmatter.updated;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const importance = (options.importance !== undefined && options.importance !== null)
|
|
112
|
-
? options.importance
|
|
113
|
-
: ((options.defaultImportance !== undefined && options.defaultImportance !== null)
|
|
114
|
-
? options.defaultImportance
|
|
115
|
-
: 3);
|
|
116
|
-
|
|
117
|
-
const node = {
|
|
118
|
-
type: 'memory',
|
|
119
|
-
slug,
|
|
120
|
-
category,
|
|
121
|
-
title,
|
|
122
|
-
description: okfFrontmatter.description || undefined,
|
|
123
|
-
resource: okfFrontmatter.resource || undefined,
|
|
124
|
-
status: 'active',
|
|
125
|
-
confidence: 0.8,
|
|
126
|
-
importance,
|
|
127
|
-
created: updatedTimestamp,
|
|
128
|
-
updated: updatedTimestamp,
|
|
129
|
-
last_accessed: new Date().toISOString(),
|
|
130
|
-
source: {
|
|
131
|
-
type: 'okf-import',
|
|
132
|
-
session_id: crypto.randomUUID(),
|
|
133
|
-
agent: 'total-recall-cli',
|
|
134
|
-
evidence_count: 1,
|
|
135
|
-
},
|
|
136
|
-
supersedes: [],
|
|
137
|
-
superseded_by: null,
|
|
138
|
-
contradicts: [],
|
|
139
|
-
tags: Array.isArray(okfFrontmatter.tags) ? okfFrontmatter.tags : (okfFrontmatter.tags ? [okfFrontmatter.tags] : []),
|
|
140
|
-
related: [],
|
|
141
|
-
routes_to_skills: [],
|
|
142
|
-
sentiment_polarity: 'descriptive',
|
|
143
|
-
sentiment_target: subject,
|
|
144
|
-
modality: 'descriptive',
|
|
145
|
-
subject,
|
|
146
|
-
predicate: 'describes',
|
|
147
|
-
object,
|
|
148
|
-
decay: {
|
|
149
|
-
half_life_days: 30,
|
|
150
|
-
access_count: 0,
|
|
151
|
-
},
|
|
152
|
-
schema_version: 2,
|
|
153
|
-
body: okfBody || '',
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return node;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Convert an SSSS memory node into an OKF-compliant concept.
|
|
161
|
-
*/
|
|
162
|
-
export function ssssNodeToOkfConcept(ssssNode) {
|
|
163
|
-
if (!ssssNode || typeof ssssNode !== 'object') {
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const desc = ssssNode.description || deriveDescription(ssssNode.body);
|
|
168
|
-
|
|
169
|
-
const frontmatter = {
|
|
170
|
-
...ssssNode,
|
|
171
|
-
type: categoryToOkfType(ssssNode.category),
|
|
172
|
-
title: ssssNode.title,
|
|
173
|
-
description: desc || undefined,
|
|
174
|
-
resource: ssssNode.resource || undefined,
|
|
175
|
-
tags: ssssNode.tags || [],
|
|
176
|
-
updated: ssssNode.updated || ssssNode.created || new Date().toISOString(),
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Clean up SSSS node-specific keys we don't want as top-level frontmatter if they match the body/filepath fields
|
|
180
|
-
delete frontmatter.body;
|
|
181
|
-
delete frontmatter._filePath;
|
|
182
|
-
delete frontmatter._filepath;
|
|
183
|
-
delete frontmatter._file_path;
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
frontmatter,
|
|
187
|
-
body: ssssNode.body || '',
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ─── Bundle Operations (Placeholders to be fully implemented) ────────────────
|
|
192
|
-
|
|
193
|
-
export async function importBundle(bundlePath, vaultDir, options = {}) {
|
|
194
|
-
const imported = [];
|
|
195
|
-
const skipped = [];
|
|
196
|
-
const errors = [];
|
|
197
|
-
|
|
198
|
-
if (!fs.existsSync(bundlePath)) {
|
|
199
|
-
errors.push({ file: bundlePath, error: 'Bundle directory does not exist' });
|
|
200
|
-
return { imported, skipped, errors };
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Helper to walk directory recursively, skipping index.md and log.md
|
|
204
|
-
function walk(dir) {
|
|
205
|
-
let files = [];
|
|
206
|
-
const list = fs.readdirSync(dir, { withFileTypes: true });
|
|
207
|
-
for (const entry of list) {
|
|
208
|
-
const name = entry.name;
|
|
209
|
-
// Skip reserved filenames at any directory level
|
|
210
|
-
if (name === 'index.md' || name === 'log.md') {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
const fullPath = path.join(dir, name);
|
|
214
|
-
if (entry.isDirectory()) {
|
|
215
|
-
files = files.concat(walk(fullPath));
|
|
216
|
-
} else if (name.endsWith('.md')) {
|
|
217
|
-
files.push(fullPath);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return files;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
let mdFiles = [];
|
|
224
|
-
try {
|
|
225
|
-
mdFiles = walk(bundlePath);
|
|
226
|
-
} catch (err) {
|
|
227
|
-
errors.push({ file: bundlePath, error: err.message });
|
|
228
|
-
return { imported, skipped, errors };
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Get existing slugs to handle conflicts
|
|
232
|
-
let existingSlugs = new Set();
|
|
233
|
-
try {
|
|
234
|
-
const existing = getNodes(vaultDir);
|
|
235
|
-
existingSlugs = new Set(existing.map(n => n.slug));
|
|
236
|
-
} catch (err) {
|
|
237
|
-
// If vault dir doesn't exist yet, it's fine, we'll create it during writes
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const onConflict = options.onConflict || 'warn';
|
|
241
|
-
|
|
242
|
-
for (const filePath of mdFiles) {
|
|
243
|
-
const relPath = path.relative(bundlePath, filePath);
|
|
244
|
-
let raw;
|
|
245
|
-
try {
|
|
246
|
-
raw = fs.readFileSync(filePath, 'utf8');
|
|
247
|
-
} catch (err) {
|
|
248
|
-
errors.push({ file: relPath, error: `Failed to read file: ${err.message}` });
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
let parsed;
|
|
253
|
-
try {
|
|
254
|
-
parsed = matter(raw);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
errors.push({ file: relPath, error: `Failed to parse YAML frontmatter: ${err.message}` });
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Skip if there's no frontmatter data or if the required OKF 'type' field is missing
|
|
261
|
-
if (!parsed || !parsed.data || Object.keys(parsed.data).length === 0 || !parsed.data.type) {
|
|
262
|
-
skipped.push({ file: relPath, reason: 'missing frontmatter or type field' });
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const node = okfConceptToSsssNode(parsed.data, parsed.content, relPath, options);
|
|
267
|
-
if (!node) {
|
|
268
|
-
skipped.push({ file: relPath, reason: 'failed to map to SSSS node' });
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Handle slug conflict
|
|
273
|
-
if (existingSlugs.has(node.slug)) {
|
|
274
|
-
if (onConflict === 'skip') {
|
|
275
|
-
skipped.push({ file: relPath, reason: `conflict (slug ${node.slug} exists)` });
|
|
276
|
-
continue;
|
|
277
|
-
} else if (onConflict === 'warn') {
|
|
278
|
-
console.warn(`[OKF Import Warning] Slug collision: '${node.slug}' already exists. Overwriting.`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
try {
|
|
283
|
-
const writeResult = await writeNodeValidatedAsync(node, vaultDir, {
|
|
284
|
-
dryRun: options.dryRun || false,
|
|
285
|
-
agentRole: options.agentRole || 'admin'
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
if (!writeResult.success) {
|
|
289
|
-
let errMsg = writeResult.error || 'Validation failed';
|
|
290
|
-
if (writeResult.repair?.field_errors) {
|
|
291
|
-
const details = writeResult.repair.field_errors.map(fe => `${fe.field}: ${fe.issue}`).join(', ');
|
|
292
|
-
errMsg += ` (${details})`;
|
|
293
|
-
}
|
|
294
|
-
errors.push({ file: relPath, error: errMsg });
|
|
295
|
-
} else {
|
|
296
|
-
imported.push({ slug: node.slug, file: relPath });
|
|
297
|
-
if (!options.dryRun) {
|
|
298
|
-
existingSlugs.add(node.slug);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
} catch (err) {
|
|
302
|
-
errors.push({ file: relPath, error: `Write error: ${err.message}` });
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return { imported, skipped, errors };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export async function exportBundle(vaultDir, outputDir, options = {}) {
|
|
310
|
-
const { execSync } = await import('node:child_process');
|
|
311
|
-
const os = await import('node:os');
|
|
312
|
-
|
|
313
|
-
const format = options.format || 'dir';
|
|
314
|
-
const stripSsss = options.stripSsss || false;
|
|
315
|
-
|
|
316
|
-
let targetDir = outputDir;
|
|
317
|
-
let isTar = format === 'tar.gz';
|
|
318
|
-
let tempDir = null;
|
|
319
|
-
|
|
320
|
-
if (isTar) {
|
|
321
|
-
tempDir = path.join(os.tmpdir(), `okf-export-${crypto.randomUUID()}`);
|
|
322
|
-
targetDir = tempDir;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
326
|
-
|
|
327
|
-
const nodes = getNodes(vaultDir);
|
|
328
|
-
const exported = [];
|
|
329
|
-
|
|
330
|
-
const conceptsByCategory = {};
|
|
331
|
-
|
|
332
|
-
for (const node of nodes) {
|
|
333
|
-
const concept = ssssNodeToOkfConcept(node);
|
|
334
|
-
if (!concept) continue;
|
|
335
|
-
|
|
336
|
-
if (stripSsss) {
|
|
337
|
-
const allowed = ['type', 'title', 'description', 'resource', 'tags', 'updated'];
|
|
338
|
-
const stripped = {};
|
|
339
|
-
for (const key of allowed) {
|
|
340
|
-
if (concept.frontmatter[key] !== undefined) {
|
|
341
|
-
stripped[key] = concept.frontmatter[key];
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
concept.frontmatter = stripped;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const category = node.category || 'facts';
|
|
348
|
-
const catDir = path.join(targetDir, category);
|
|
349
|
-
fs.mkdirSync(catDir, { recursive: true });
|
|
350
|
-
|
|
351
|
-
const filename = `${node.slug}.md`;
|
|
352
|
-
const destPath = path.join(catDir, filename);
|
|
353
|
-
|
|
354
|
-
const fileContent = safeStringify(concept.body, concept.frontmatter);
|
|
355
|
-
fs.writeFileSync(destPath, fileContent, 'utf8');
|
|
356
|
-
|
|
357
|
-
exported.push({ slug: node.slug, file: path.join(category, filename) });
|
|
358
|
-
|
|
359
|
-
if (!conceptsByCategory[category]) {
|
|
360
|
-
conceptsByCategory[category] = [];
|
|
361
|
-
}
|
|
362
|
-
conceptsByCategory[category].push({
|
|
363
|
-
title: node.title,
|
|
364
|
-
relPath: `./${category}/${filename}`,
|
|
365
|
-
description: concept.frontmatter.description || ''
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Generate index.md
|
|
370
|
-
let indexContent = '# Knowledge Bundle Index\n\n';
|
|
371
|
-
for (const [category, list] of Object.entries(conceptsByCategory)) {
|
|
372
|
-
const catHeader = category.charAt(0).toUpperCase() + category.slice(1);
|
|
373
|
-
indexContent += `## ${catHeader}\n`;
|
|
374
|
-
for (const item of list) {
|
|
375
|
-
const descSuffix = item.description ? ` - ${item.description}` : '';
|
|
376
|
-
indexContent += `- [${item.title}](${item.relPath})${descSuffix}\n`;
|
|
377
|
-
}
|
|
378
|
-
indexContent += '\n';
|
|
379
|
-
}
|
|
380
|
-
fs.writeFileSync(path.join(targetDir, 'index.md'), indexContent, 'utf8');
|
|
381
|
-
|
|
382
|
-
// Generate log.md
|
|
383
|
-
let logContent = '# Bundle Update History\n\n';
|
|
384
|
-
const auditPath = path.join(vaultDir, '.events', 'audit.jsonl');
|
|
385
|
-
if (fs.existsSync(auditPath)) {
|
|
386
|
-
try {
|
|
387
|
-
const lines = fs.readFileSync(auditPath, 'utf8').trim().split('\n');
|
|
388
|
-
for (const line of lines) {
|
|
389
|
-
if (!line) continue;
|
|
390
|
-
const event = JSON.parse(line);
|
|
391
|
-
const date = event.ts ? new Date(event.ts).toISOString().replace(/\.\d+Z$/, 'Z') : 'Unknown Date';
|
|
392
|
-
const operation = event.payload?.resolved_type || 'mutation';
|
|
393
|
-
logContent += `- **${date}**: Operation '${operation}' committed at \`${event.subject || event.path || ''}\`\n`;
|
|
394
|
-
}
|
|
395
|
-
} catch (err) {
|
|
396
|
-
logContent += `Failed to parse audit history: ${err.message}\n`;
|
|
397
|
-
}
|
|
398
|
-
} else {
|
|
399
|
-
logContent += 'No update history available.\n';
|
|
400
|
-
}
|
|
401
|
-
fs.writeFileSync(path.join(targetDir, 'log.md'), logContent, 'utf8');
|
|
402
|
-
|
|
403
|
-
if (isTar) {
|
|
404
|
-
const tarFile = outputDir.endsWith('.tar.gz') ? outputDir : `${outputDir}.tar.gz`;
|
|
405
|
-
fs.mkdirSync(path.dirname(tarFile), { recursive: true });
|
|
406
|
-
execSync(`tar -czf "${tarFile}" -C "${targetDir}" .`);
|
|
407
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return {
|
|
411
|
-
exported,
|
|
412
|
-
indexGenerated: true,
|
|
413
|
-
logGenerated: true
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
export function lintOkfCompliance(vaultDir, options = {}) {
|
|
418
|
-
const strict = options.strict || false;
|
|
419
|
-
const nodes = getNodes(vaultDir);
|
|
420
|
-
|
|
421
|
-
const warnings = [];
|
|
422
|
-
const errors = [];
|
|
423
|
-
|
|
424
|
-
for (const node of nodes) {
|
|
425
|
-
const relPath = `${node.category}/${node.slug}.md`;
|
|
426
|
-
|
|
427
|
-
// Check title
|
|
428
|
-
if (!node.title) {
|
|
429
|
-
const msg = `Missing title in ${relPath}`;
|
|
430
|
-
if (strict) errors.push({ file: relPath, message: msg });
|
|
431
|
-
else warnings.push({ file: relPath, message: msg });
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Check description
|
|
435
|
-
if (!node.description) {
|
|
436
|
-
const msg = `Missing description in ${relPath}`;
|
|
437
|
-
if (strict) errors.push({ file: relPath, message: msg });
|
|
438
|
-
else warnings.push({ file: relPath, message: msg });
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Check tags
|
|
442
|
-
if (!node.tags || !Array.isArray(node.tags) || node.tags.length === 0) {
|
|
443
|
-
const msg = `Missing or empty tags in ${relPath}`;
|
|
444
|
-
if (strict) errors.push({ file: relPath, message: msg });
|
|
445
|
-
else warnings.push({ file: relPath, message: msg });
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Check updated/created timestamp
|
|
449
|
-
if (!node.updated && !node.created) {
|
|
450
|
-
const msg = `Missing updated/created timestamp in ${relPath}`;
|
|
451
|
-
if (strict) errors.push({ file: relPath, message: msg });
|
|
452
|
-
else warnings.push({ file: relPath, message: msg });
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const pass = errors.length === 0;
|
|
457
|
-
|
|
458
|
-
return {
|
|
459
|
-
total: nodes.length,
|
|
460
|
-
pass,
|
|
461
|
-
warnings,
|
|
462
|
-
errors
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Writes a generated file only when its content changed. Rewriting identical
|
|
468
|
-
* bytes still bumps mtime and fires every filesystem watcher.
|
|
469
|
-
*/
|
|
470
|
-
function writeIfChanged(file, content) {
|
|
471
|
-
try {
|
|
472
|
-
if (fs.readFileSync(file, 'utf8') === content) return false;
|
|
473
|
-
} catch {
|
|
474
|
-
// Missing or unreadable: write it.
|
|
475
|
-
}
|
|
476
|
-
fs.writeFileSync(file, content, 'utf8');
|
|
477
|
-
return true;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
export function generateLiveIndex(vaultDir) {
|
|
481
|
-
const nodes = getNodes(vaultDir);
|
|
482
|
-
if (nodes.length === 0) return;
|
|
483
|
-
|
|
484
|
-
const conceptsByType = {};
|
|
485
|
-
|
|
486
|
-
for (const node of nodes) {
|
|
487
|
-
const concept = ssssNodeToOkfConcept(node);
|
|
488
|
-
if (!concept) continue;
|
|
489
|
-
|
|
490
|
-
const type = concept.frontmatter.type || 'Generic Concept';
|
|
491
|
-
const category = node.category || 'facts';
|
|
492
|
-
const filename = `${node.slug}.md`;
|
|
493
|
-
|
|
494
|
-
if (!conceptsByType[type]) {
|
|
495
|
-
conceptsByType[type] = [];
|
|
496
|
-
}
|
|
497
|
-
conceptsByType[type].push({
|
|
498
|
-
title: node.title || node.slug,
|
|
499
|
-
relPath: `./${category}/${filename}`,
|
|
500
|
-
description: concept.frontmatter.description || ''
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
let indexContent = '# Knowledge Bundle Index\n\n';
|
|
505
|
-
const sortedTypes = Object.keys(conceptsByType).sort();
|
|
506
|
-
for (const type of sortedTypes) {
|
|
507
|
-
indexContent += `# ${type}\n\n`;
|
|
508
|
-
const list = conceptsByType[type].sort((a, b) => a.title.localeCompare(b.title));
|
|
509
|
-
for (const item of list) {
|
|
510
|
-
const descSuffix = item.description ? ` - ${item.description}` : '';
|
|
511
|
-
indexContent += `* [${item.title}](${item.relPath})${descSuffix}\n`;
|
|
512
|
-
}
|
|
513
|
-
indexContent += '\n';
|
|
514
|
-
}
|
|
515
|
-
// ssss-raw-write: OKF bundle manifest, not a memory node (excluded from node walks).
|
|
516
|
-
writeIfChanged(path.join(vaultDir, 'index.md'), indexContent);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
export function generateLiveLog(vaultDir) {
|
|
520
|
-
let logContent = '# Bundle Update History\n\n';
|
|
521
|
-
const auditPath = path.join(vaultDir, '.events', 'audit.jsonl');
|
|
522
|
-
if (fs.existsSync(auditPath)) {
|
|
523
|
-
try {
|
|
524
|
-
const lines = fs.readFileSync(auditPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
525
|
-
const eventsByDate = {};
|
|
526
|
-
|
|
527
|
-
for (const line of lines) {
|
|
528
|
-
const event = JSON.parse(line);
|
|
529
|
-
if (!event.ts) continue;
|
|
530
|
-
const dateStr = new Date(event.ts).toISOString().split('T')[0];
|
|
531
|
-
const operation = event.payload?.resolved_type || 'mutation';
|
|
532
|
-
const formattedOp = operation.charAt(0).toUpperCase() + operation.slice(1);
|
|
533
|
-
const subject = event.subject || event.path || 'unknown';
|
|
534
|
-
|
|
535
|
-
if (!eventsByDate[dateStr]) {
|
|
536
|
-
eventsByDate[dateStr] = [];
|
|
537
|
-
}
|
|
538
|
-
eventsByDate[dateStr].push(`* **${formattedOp}**: Committed at \`${subject}\``);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const sortedDates = Object.keys(eventsByDate).sort().reverse();
|
|
542
|
-
for (const date of sortedDates) {
|
|
543
|
-
logContent += `## ${date}\n`;
|
|
544
|
-
const logs = eventsByDate[date].reverse();
|
|
545
|
-
for (const log of logs) {
|
|
546
|
-
logContent += `${log}\n`;
|
|
547
|
-
}
|
|
548
|
-
logContent += '\n';
|
|
549
|
-
}
|
|
550
|
-
} catch (err) {
|
|
551
|
-
logContent += `Failed to parse audit history: ${err.message}\n`;
|
|
552
|
-
}
|
|
553
|
-
} else {
|
|
554
|
-
logContent += 'No update history available.\n';
|
|
555
|
-
}
|
|
556
|
-
// ssss-raw-write: OKF bundle changelog, not a memory node (excluded from node walks).
|
|
557
|
-
writeIfChanged(path.join(vaultDir, 'log.md'), logContent);
|
|
558
|
-
}
|
|
559
|
-
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# OpenWiki
|
|
2
|
-
|
|
3
|
-
Portable knowledge documentation for this Total Recall brain.
|
|
4
|
-
|
|
5
|
-
| Layer | Purpose |
|
|
6
|
-
|-------|---------|
|
|
7
|
-
| **Memory vault** | SSSS nodes (rules, prefs, facts) — machine-enforceable |
|
|
8
|
-
| **OpenWiki** | Human/agent docs (architecture, how-tos, stack notes) |
|
|
9
|
-
|
|
10
|
-
OpenWiki ships with Total Recall. Edit freely; do not put secrets here.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# IDE personalization
|
|
2
|
-
|
|
3
|
-
```bash
|
|
4
|
-
npx total-recall connect claude-code # CLAUDE.md
|
|
5
|
-
npx total-recall connect codex # AGENTS.md
|
|
6
|
-
npx total-recall connect cursor # .cursor/rules/
|
|
7
|
-
npx total-recall connect gemini # GEMINI.md
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
Total Recall only writes inside managed inject markers. Your local rules stay intact.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Memory
|
|
2
|
-
|
|
3
|
-
Use the Total Recall CLI for durable memory:
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npx total-recall remember invariant "Never commit secrets." --importance 5
|
|
7
|
-
npx total-recall recall "secrets"
|
|
8
|
-
npx total-recall compile
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Categories: invariants, preferences, facts, decisions, patterns, anti-patterns, concepts, lore.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Secrets & API keys
|
|
2
|
-
|
|
3
|
-
Store credentials in Total Recall's secrets store (`config/secrets.enc` / future CLI).
|
|
4
|
-
|
|
5
|
-
**Never** put API keys in openwiki pages or compiled IDE instruction files.
|
|
6
|
-
|
|
7
|
-
Planned CLI surface (TR_CORE_FOCUS Phase 4): `secret set|list|rotate|audit` and usage/cost meters.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Skills
|
|
2
|
-
|
|
3
|
-
- **Total Recall skill** (only nested agent skill): `.agent/skills/total-recall/SKILL.md`
|
|
4
|
-
- **User skills**: `.agent/skills/<name>/SKILL.md` — project or global
|
|
5
|
-
- **TR modules** (not skills): `.agent/skills/total-recall/modules/` — internal docs/scripts
|
|
6
|
-
|
|
7
|
-
TR will track skill installs across repos and support adaptive deploy (see TR_CORE_FOCUS).
|