total-recall-brain 3.6.8 → 3.6.10

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.
@@ -79,6 +79,7 @@ const COMMANDS = {
79
79
  skill: 'skill.mjs',
80
80
  help: 'help.mjs',
81
81
  collab: 'collab.mjs',
82
+ command: 'command.mjs',
82
83
  };
83
84
  function printHelp() {
84
85
  console.log(`
@@ -128,6 +129,7 @@ function printHelp() {
128
129
  uninstall Completely uninstall all services, launchd agents, VFS, and shims
129
130
  help <topic> Interactive offline documentation, SSSS reference, and system help
130
131
  collab Start Express/WebSockets backend & React collab sandbox dev server
132
+ command <create|read|update|remove|list> Manage custom project-local CLI commands
131
133
 
132
134
 
133
135
 
@@ -166,8 +168,29 @@ async function main() {
166
168
  if (!handlerFile) {
167
169
  // Check if it is a dynamic integration command configured in the VFS
168
170
  try {
169
- const { agentDir } = await import('../src/core/config.mjs');
171
+ const { agentDir, resolveBrainLayer } = await import('../src/core/config.mjs');
170
172
  const fs = await import('node:fs');
173
+
174
+ // Check for custom project-level commands first
175
+ let projectAgentDir = null;
176
+ try {
177
+ const pBrain = resolveBrainLayer('project');
178
+ projectAgentDir = pBrain.agentDir;
179
+ } catch (e) { /* ignore */ }
180
+
181
+ if (projectAgentDir) {
182
+ const customCmdPath = path.join(projectAgentDir, 'commands', `${command}.mjs`);
183
+ if (fs.existsSync(customCmdPath)) {
184
+ const handler = await import(customCmdPath);
185
+ if (handler.run) {
186
+ await handler.run(process.argv);
187
+ } else {
188
+ await handler.default(process.argv.slice(3));
189
+ }
190
+ process.exit(0);
191
+ }
192
+ }
193
+
171
194
  const integrationPath = path.join(agentDir, 'skills', 'total-recall', 'integrations', `${command}.md`);
172
195
 
173
196
  if (fs.existsSync(integrationPath)) {
@@ -142,6 +142,7 @@ export default function LoginPage({ onAuthenticated }: Props) {
142
142
 
143
143
  {/* Form */}
144
144
  <form onSubmit={handleSubmit} style={{ display: "flex", flexDirection: "column", gap: 16 }}>
145
+ <input type="text" name="username" autoComplete="username" style={{ display: "none" }} />
145
146
  <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
146
147
  <label htmlFor="login-password" style={{ fontSize: 12, fontWeight: 600, color: "var(--text-secondary)", textTransform: "uppercase", letterSpacing: "0.05em" }}>
147
148
  {(needsReset || isFirstTime) ? "New Password" : "Password"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.6.8",
3
+ "version": "3.6.10",
4
4
  "description": "Sovereign AI system — database-free, SSSS-powered personal intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,7 +54,24 @@ Total Recall operates **entirely database-free**. The local filesystem is the so
54
54
  ### 2. Precise Tool & API Selection Heuristics
55
55
  When interacting with the system, choose your interfaces based on the following:
56
56
  * **Local Skills**: Use local skill packages (located in `.agent/skills/`) for complex workspace tasks, testing, and formatting. If a local skill has custom helper scripts, ALWAYS invoke them via your shell command tool instead of running raw standard commands.
57
- * **Total Recall Brain (MANDATORY)**: For all semantic searches, memory retrieval, memory writes, and derived index recompilation, **use either the local CLI suite (`npx total-recall`) or shell `curl` commands** targeting the REST API endpoints documented below.
57
+ * **Total Recall CLI (PRIMARY — use this)**: For all memory operations — saving, recalling, forgetting, and compiling — use the `npx total-recall` CLI commands. This is the correct and preferred interface.
58
+ ```bash
59
+ npx total-recall recall "your query here" --top-k 5
60
+ npx total-recall remember invariant "rule text" --importance 5 --priority absolute
61
+ npx total-recall remember fact "fact text" --project
62
+ npx total-recall forget <slug>
63
+ npx total-recall compile
64
+ ```
65
+ * **Custom Local Commands**: The Total Recall CLI supports managing project-specific CLI commands dynamically. Use the `command` subcommand to manage these.
66
+ ```bash
67
+ npx total-recall command create hello "console.log('Hello from the brain!');"
68
+ npx total-recall command list
69
+ npx total-recall command read hello
70
+ npx total-recall command update hello "console.log('Updated hello command');"
71
+ npx total-recall command remove hello
72
+ ```
73
+ Custom commands are auto-generated and stored in the workspace at `.agent/commands/<name>.mjs`.
74
+ * **REST API via curl (SECONDARY — fallback only)**: Use raw `curl` commands to the REST API endpoints only when the CLI is unavailable or for advanced programmatic use cases not covered by the CLI.
58
75
 
59
76
  ### 3. Non-Destructive Code Modifications
60
77
  When modifying existing rule surfaces or IDE shims (such as `.cursorrules`, `CLAUDE.md`, `GEMINI.md`, etc.), **NEVER** overwrite the developer's custom pre-existing rules. Instead, write compiled absolute invariants inside the managed comment block:
@@ -1,46 +1,196 @@
1
1
  ---
2
2
  type: skill
3
3
  name: ssss
4
- description: "Use this skill to inspect, validate, write, and manage SSSS structured semantic memory nodes and VFS specifications. Do NOT use for raw non-schema memory files."
4
+ description: "Use this skill to inspect, validate, write, and manage SSSS primitives, memory nodes, operation envelopes, scope overlays, projections, and VFS specifications in the Total Recall reference kernel. MANDATORY: Read this file before editing SSSS files or code."
5
5
  schema_version: 2
6
6
  ---
7
7
 
8
8
  # SSSS — Structured Semantic Syntax System Manager
9
9
 
10
- Structured Semantic Syntax System (ssss) is the core operational substrate of the Total Recall Virtual File System (VFS). This skill guides agents in inspecting, reading, and constructing SSSS-compliant memory nodes, conflict models, and derived indices.
10
+ SSSS is the Markdown-first state contract for Total Recall and compatible VFS
11
+ hosts. Use this skill when touching memory nodes, skills, assistants, workflows,
12
+ models, conversations, runs, projection metadata, operation envelopes, or SSSS
13
+ schema/reference files.
11
14
 
12
- ## Spec Version
15
+ ## Ground Truth
13
16
 
14
- Current specification: **v0.2 — Draft** (see `references/ssss-spec.md`)
17
+ The vendor-neutral contract is `references/ssss-spec.md`. If this skill conflicts
18
+ with the spec, correct this skill unless the implementation has intentionally
19
+ advanced beyond the vendored spec and the difference is documented here.
15
20
 
16
- ### v0.2 Changes (2026-05-30)
17
- - Expanded `modality` enum: `must|must_not|should|should_not|descriptive|preference`
18
- - Expanded `priority` enum: `absolute|high|normal|low` (OPTIONAL, defaults to `normal`)
19
- - Added §10.1: Projection Manifest requirement with vault-hash staleness detection
20
- - Added §11.3: Single canonical embedding implementation (no dual systems)
21
+ Current local implementation entry points:
21
22
 
22
- ## Core Directives
23
+ - `src/core/schema.mjs` — implemented SSSS primitive registry (`SSSS_SCHEMAS`).
24
+ - `src/core/operation-validator.mjs` — §6 operation pipeline.
25
+ - `src/core/validated-write.mjs` — memory write adapter for the operation
26
+ pipeline.
27
+ - `src/core/total-recall-memory-validator.mjs` — schema v2 memory validator.
28
+ - `src/core/vault-cache.mjs` — cached vault reads and watcher invalidation.
29
+ - `src/core/vector-store.mjs` and `src/core/semantic-index.mjs` — canonical
30
+ search/index path.
23
31
 
24
- 1. **Schema Integrity:** Every SSSS node must strictly adhere to the frontmatter schema definitions (Modality, Category, Sentiment).
25
- 2. **Zero-Database Persistence:** Do not look for Postgres or SQLite. Everything lives as a git-versioned Markdown file.
26
- 3. **Decay and Drift Monitoring:** Use decay indicators to manage node half-lives dynamically in background ticks.
27
- 4. **Cached Reads:** All vault reads MUST go through `vault-cache.mjs` (`getNodes()`) — never call `loadNodes()` directly. The cache supports multi-directory (global + project) with per-directory `fs.watch` invalidation.
28
- 5. **Operation Contract:** All agent-generated mutations MUST flow through `processOperation()` (§6). Direct `writeNode()` calls bypass validation, idempotency, authorization, lease protection, and audit logging.
32
+ ## Current Contract
29
33
 
30
- ## Progressive Memory Disclosure
34
+ All agent-generated mutations must flow through the SSSS operation pipeline:
31
35
 
32
- Memory flows through a 3-tier progressive hierarchy:
33
- * **Tier 1 (Hot):** INSTRUCTIONS.md shims (Invariants only).
34
- * **Tier 2 (Warm):** SKILL.md injects (Category-routed warm capsules).
35
- * **Tier 3 (Cold):** Full memory vault (Search index files on demand).
36
+ ```text
37
+ processOperation(envelope, vaultRoot, options)
38
+ -> envelope validation
39
+ -> idempotency check
40
+ -> authorization
41
+ -> lease validation
42
+ -> deterministic schema validation
43
+ -> atomic commit
44
+ -> audit event
45
+ ```
36
46
 
37
- ## Performance Invariants
47
+ Use `writeNodeValidated()` for Total Recall memory writes. Do not call raw
48
+ `writeNode()` or write files directly when an agent is mutating an SSSS vault.
38
49
 
39
- * `vault-cache.mjs` is the **single import point** for all node reads. The cache uses `fs.watch` auto-invalidation and supports multiple vault directories simultaneously.
40
- * `walkMd()` uses `{ withFileTypes: true }` to eliminate per-file `stat()` syscalls.
41
- * `deleteNode()` scans category directories directly — O(categories) not O(nodes).
42
- * Surface recompilation (`triggerMutation()`) is debounced with a 2-second quiet period for batch writes.
50
+ Total Recall currently implements these envelope types in
51
+ `processOperation()`:
52
+
53
+ - `operation` — full file create/replace.
54
+ - `patch` — partial frontmatter/body merge.
55
+ - `event` — append-only JSON event payload.
56
+
57
+ The UltraChat host also models a `delete` envelope. Do not claim or use `delete`
58
+ inside the Total Recall kernel until `src/core/schema.mjs` and
59
+ `src/core/operation-validator.mjs` implement and test it. For memory removal, use
60
+ the CLI/API forget/archive flow.
61
+
62
+ Use `dry_run: true` for preflight validation when constructing writes from model
63
+ output.
64
+
65
+ ## Canonical State
66
+
67
+ The vault is source-of-truth. Derived files, embeddings, search indexes,
68
+ instruction shims, UI caches, and projection manifests are disposable and must be
69
+ rebuildable from Markdown vault state plus append-only events.
70
+
71
+ Total Recall is database-free. Do not look for Postgres or SQLite as the record of
72
+ truth. The internal vector store is a derived index, not canonical state.
73
+
74
+ ## Scope Layers
75
+
76
+ Total Recall memory has layered vault scope:
77
+
78
+ - `global` — machine/user-level brain shared across registered projects.
79
+ - `project` — repository-local brain and session context.
80
+
81
+ Host applications can add product runtime scopes such as `system`, `account`, and
82
+ `workspace`. The UltraChat implementation proved the rule that shared system
83
+ skills must be inherited through an overlay, not copied into every workspace.
84
+ When authoring portable SSSS guidance, preserve this separation:
85
+
86
+ - system/platform primitives are hidden shared defaults;
87
+ - account-local primitives must not leak into workspace runtimes unless forked or
88
+ explicitly installed;
89
+ - workspace primitives can shadow shared system defaults;
90
+ - legacy copied system artifacts should be treated as derived cleanup targets only
91
+ when metadata proves they are seeded copies.
92
+
93
+ Do not flatten these layers into one directory or one effective identity in docs,
94
+ validators, or examples.
95
+
96
+ ## Primitive Registry
97
+
98
+ `src/core/schema.mjs` is the implementation registry. `references/ssss-spec.md`
99
+ is the vendor-neutral baseline. When adding or changing a primitive:
100
+
101
+ 1. Update `SSSS_SCHEMAS` and the relevant Zod schema.
102
+ 2. Update the spec or this skill if the primitive is standard or host-specific
103
+ respectively.
104
+ 3. Add/adjust fixtures or tests for the required fields and invalid cases.
105
+ 4. Confirm projection behavior is declared as source, projection, or external
106
+ state.
107
+
108
+ The current implementation includes the core spec primitives plus host/product
109
+ extensions such as `personalization_profile`, `account_assistant`,
110
+ `account_memory`, `account_workflow`, `workspace_transfer`, CRM/CMS/commerce
111
+ types, telephony types, and workspace/package types. Treat those as implemented
112
+ host extensions unless the spec explicitly standardizes them.
113
+
114
+ ## Memory Nodes
115
+
116
+ Memory files under `.agent/memory-vault/**` are `type: memory` and must satisfy
117
+ SSSS schema v2. Required schema v2 knowledge-graph fields are:
118
+
119
+ - `confidence`
120
+ - `importance`
121
+ - `modality`
122
+ - `subject`
123
+ - `predicate`
124
+ - `object`
125
+ - `sentiment_polarity`
126
+ - `sentiment_target`
127
+
128
+ Invariants additionally use `priority: absolute` and `immutable: true`.
129
+
130
+ Memory validation is owned by `TotalRecallMemoryValidator`, not by generic loose
131
+ frontmatter checks. No memory path may be committed with zero validation.
132
+
133
+ ## Reads And Writes
134
+
135
+ - Use `vault-cache.mjs` `getNodes()` for vault reads. Do not call `loadNodes()`
136
+ directly in new code.
137
+ - Use `writeNodeValidated()` or `processOperation()` for writes.
138
+ - Invalidate the vault cache after successful writes.
139
+ - Keep append-type documents append-only. `conversation` and `run` may append
140
+ body records but must not rewrite prior records.
141
+ - Events are immutable. Do not update or delete event records.
142
+ - Leases must be time-bound, actor-owned, operation-linked, and recoverable.
143
+
144
+ ## Projections And Indexes
145
+
146
+ Projection manifests must mark derived artifacts as disposable and record enough
147
+ provenance to detect drift. A projection must have a rebuild path from vault state
148
+ and events or be documented as external non-projection state.
149
+
150
+ The canonical search/index stack is the Total Recall semantic index/vector store.
151
+ Do not introduce a second embedding or indexing path for SSSS memory unless the
152
+ spec and tests are updated together.
153
+
154
+ ## Validation
155
+
156
+ Validate an individual SSSS Markdown file with:
157
+
158
+ ```bash
159
+ node .agent/skills/ssss/scripts/validate-schema.mjs <path-to-node.md>
160
+ ```
161
+
162
+ Run focused kernel tests after changing the operation contract, schema registry,
163
+ validator, or vault cache:
164
+
165
+ ```bash
166
+ npx vitest run \
167
+ src/core/schema.spec.mjs \
168
+ src/core/operation-validator.spec.mjs \
169
+ src/core/total-recall-memory-validator.spec.mjs \
170
+ src/core/vault-cache.spec.mjs
171
+ ```
172
+
173
+ ## Do Not
174
+
175
+ - Do not write agent mutations directly to vault files.
176
+ - Do not make derived indexes or projections canonical.
177
+ - Do not bypass deterministic validation for model-generated changes.
178
+ - Do not collapse `global`/`project` or `system`/`account`/`workspace` scope
179
+ boundaries.
180
+ - Do not represent copied seeded skills as canonical system primitives.
181
+ - Do not claim Total Recall supports `delete` envelopes until the implementation
182
+ supports them.
183
+ - Do not add host-specific required fields to the vendor-neutral spec unless they
184
+ are being standardized.
43
185
 
44
186
  ## References
45
187
 
46
- * Core SSSS specification: `references/ssss-spec.md`
188
+ | For | Read |
189
+ | --- | --- |
190
+ | SSSS standard | `references/ssss-spec.md` |
191
+ | Implemented schemas | `src/core/schema.mjs` |
192
+ | Operation pipeline | `src/core/operation-validator.mjs` |
193
+ | Validated memory writes | `src/core/validated-write.mjs` |
194
+ | Memory validator | `src/core/total-recall-memory-validator.mjs` |
195
+ | Vault cache | `src/core/vault-cache.mjs` |
196
+ | Total Recall SSSS v2 reference | `.agent/skills/total-recall/references/ssss-reference.md` |
@@ -0,0 +1,9 @@
1
+ # SSSS Changelog
2
+
3
+ ## [v0.2.0] - 2026-06
4
+ - **Changed**: Stripped product-specific primitives (`meeting`, `event`, `crm`, etc.) from the core specification to enforce vendor-neutrality. Product primitives must now live strictly in host-level registries.
5
+ - **Added**: Upgraded Total Recall, UltraChat, and MUSE to v0.2.0 alignment.
6
+
7
+ ## [v0.1.0] - 2026-05
8
+ - Initial Draft Specification.
9
+ - Defined VFS primitives, Operation Contract, and Append-Only Event Graph.
@@ -1,54 +1,232 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
3
4
  import matter from 'gray-matter';
4
5
 
6
+ const APPEND_TYPES = new Set(['conversation', 'run']);
7
+ const CONTRACT_ONLY_TYPES = new Set(['operation', 'patch', 'lease']);
8
+ const V2_MEMORY_REQUIRED = [
9
+ 'confidence',
10
+ 'importance',
11
+ 'modality',
12
+ 'subject',
13
+ 'predicate',
14
+ 'object',
15
+ 'sentiment_polarity',
16
+ 'sentiment_target',
17
+ ];
18
+
19
+ const FALLBACK_REQUIRED_BY_TYPE = {
20
+ memory: ['slug', 'category', 'title', 'status', 'schema_version'],
21
+ skill: ['name', 'description'],
22
+ task: ['priority', 'category', 'status'],
23
+ assistant: ['name'],
24
+ workflow: ['name'],
25
+ rule: ['name'],
26
+ model: ['model_id', 'provider'],
27
+ conversation: ['thread_id'],
28
+ run: ['run_id', 'workflow_id'],
29
+ conflict: ['conflict_id', 'status', 'new_slug', 'existing_slug', 'detected_at'],
30
+ page: ['slug', 'name', 'sandbox_entry'],
31
+ migration: ['migration_id', 'from_version', 'to_version', 'status', 'description'],
32
+ release: ['release_id', 'version', 'schema_version', 'summary', 'released_at'],
33
+ };
34
+
35
+ let kernelPromise;
36
+
37
+ function findRepoRoot(startDir) {
38
+ let dir = path.resolve(startDir);
39
+ while (true) {
40
+ if (
41
+ fs.existsSync(path.join(dir, 'package.json')) &&
42
+ fs.existsSync(path.join(dir, 'src/core/schema.mjs'))
43
+ ) {
44
+ return dir;
45
+ }
46
+ const parent = path.dirname(dir);
47
+ if (parent === dir) return null;
48
+ dir = parent;
49
+ }
50
+ }
51
+
52
+ async function loadKernel() {
53
+ if (kernelPromise) return kernelPromise;
54
+
55
+ kernelPromise = (async () => {
56
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
57
+ const roots = [process.cwd(), scriptDir]
58
+ .map(findRepoRoot)
59
+ .filter(Boolean);
60
+ const repoRoot = roots[0];
61
+ if (!repoRoot) return { repoRoot: null, schemas: null, validateMemoryNode: null };
62
+
63
+ try {
64
+ const schemaUrl = pathToFileURL(path.join(repoRoot, 'src/core/schema.mjs')).href;
65
+ const validatorUrl = pathToFileURL(
66
+ path.join(repoRoot, 'src/core/total-recall-memory-validator.mjs')
67
+ ).href;
68
+ const [{ SSSS_SCHEMAS }, { validateMemoryNode }] = await Promise.all([
69
+ import(schemaUrl),
70
+ import(validatorUrl),
71
+ ]);
72
+ return { repoRoot, schemas: SSSS_SCHEMAS, validateMemoryNode };
73
+ } catch {
74
+ return { repoRoot, schemas: null, validateMemoryNode: null };
75
+ }
76
+ })();
77
+
78
+ return kernelPromise;
79
+ }
80
+
81
+ function formatZodIssues(error) {
82
+ return (error?.issues || []).map((issue) => {
83
+ const field = issue.path?.length ? issue.path.join('.') : '(root)';
84
+ return `${field}: ${issue.message}`;
85
+ });
86
+ }
87
+
88
+ function addMissingFieldErrors(errors, data, fields) {
89
+ for (const field of fields) {
90
+ if (data[field] === undefined || data[field] === null || data[field] === '') {
91
+ errors.push(`Missing required field '${field}'.`);
92
+ }
93
+ }
94
+ }
95
+
96
+ function validateFallback(data, errors, warnings) {
97
+ if (!data.type) {
98
+ errors.push('Missing required frontmatter field: type.');
99
+ return;
100
+ }
101
+
102
+ if (typeof data.type !== 'string') {
103
+ errors.push('Frontmatter field type must be a string.');
104
+ return;
105
+ }
106
+
107
+ if (CONTRACT_ONLY_TYPES.has(data.type)) {
108
+ errors.push(
109
+ `Type '${data.type}' is a contract envelope type, not a Markdown document primitive.`
110
+ );
111
+ return;
112
+ }
113
+
114
+ const requiredFields = FALLBACK_REQUIRED_BY_TYPE[data.type];
115
+ if (!requiredFields) {
116
+ warnings.push(
117
+ `No local fallback schema for type '${data.type}'. Implementation registry was unavailable.`
118
+ );
119
+ return;
120
+ }
121
+
122
+ addMissingFieldErrors(errors, data, requiredFields);
123
+ }
124
+
125
+ function validateMemoryV2(data, errors) {
126
+ if (data.schema_version !== 2) {
127
+ errors.push(`Expected memory schema_version: 2, got: ${data.schema_version}`);
128
+ return;
129
+ }
130
+ addMissingFieldErrors(errors, data, V2_MEMORY_REQUIRED);
131
+ }
132
+
133
+ function validateMemoryPath(filePath, data, warnings) {
134
+ const normalized = filePath.split(path.sep).join('/');
135
+ if (!normalized.includes('/memory-vault/')) return;
136
+
137
+ const parent = path.basename(path.dirname(filePath));
138
+ if (data.category && parent !== data.category) {
139
+ warnings.push(
140
+ `Memory category '${data.category}' does not match parent directory '${parent}'.`
141
+ );
142
+ }
143
+ }
144
+
5
145
  /**
6
- * Validates a single SSSS Markdown file against schema rules
146
+ * Validates a single SSSS Markdown file against the Total Recall implementation
147
+ * schema when available, with a scaffold-safe fallback for standalone installs.
7
148
  */
8
- export function validateNode(filePath) {
149
+ export async function validateNode(filePath) {
150
+ const target = path.resolve(filePath);
151
+
9
152
  try {
10
- const raw = fs.readFileSync(filePath, 'utf8');
11
- const { data } = matter(raw);
153
+ const raw = fs.readFileSync(target, 'utf8');
154
+ const parsed = matter(raw);
155
+ const data = parsed.data || {};
12
156
  const errors = [];
157
+ const warnings = [];
158
+ const kernel = await loadKernel();
159
+
160
+ if (!raw.trimStart().startsWith('---')) {
161
+ errors.push('Missing YAML frontmatter block.');
162
+ }
163
+
164
+ if (data.type && CONTRACT_ONLY_TYPES.has(data.type)) {
165
+ errors.push(
166
+ `Type '${data.type}' is a contract envelope type, not a Markdown document primitive.`
167
+ );
168
+ } else if (data.type === 'memory' && kernel.validateMemoryNode) {
169
+ const result = kernel.validateMemoryNode(data);
170
+ if (!result.success) errors.push(...result.errors);
171
+ } else if (data.type && kernel.schemas?.[data.type]) {
172
+ const result = kernel.schemas[data.type].safeParse(data);
173
+ if (!result.success) errors.push(...formatZodIssues(result.error));
174
+ } else {
175
+ validateFallback(data, errors, warnings);
176
+ if (data.type === 'memory') validateMemoryV2(data, errors);
177
+ }
13
178
 
14
- if (!data.type) errors.push('Missing "type" property.');
15
- if (!data.slug) errors.push('Missing "slug" property.');
16
- if (!data.category) errors.push('Missing "category" property.');
17
- if (!data.title) errors.push('Missing "title" property.');
18
-
19
- if (data.schema_version !== 2) {
20
- errors.push(`Expected schema_version: 2, got: ${data.schema_version}`);
179
+ if (APPEND_TYPES.has(data.type) && parsed.content.trim().length === 0) {
180
+ warnings.push(`Append-type '${data.type}' has an empty body.`);
21
181
  }
22
182
 
183
+ if (data.type === 'memory') validateMemoryPath(target, data, warnings);
184
+
23
185
  return {
24
186
  valid: errors.length === 0,
25
187
  errors,
26
- file: filePath
188
+ warnings,
189
+ file: target,
190
+ type: data.type || null,
27
191
  };
28
192
  } catch (err) {
29
193
  return {
30
194
  valid: false,
31
195
  errors: [err.message],
32
- file: filePath
196
+ warnings: [],
197
+ file: target,
198
+ type: null,
33
199
  };
34
200
  }
35
201
  }
36
202
 
37
- if (import.meta.url.startsWith('file:')) {
38
- const runDirectly = process.argv[1] && process.argv[1].endsWith('validate-schema.mjs');
39
- if (runDirectly) {
40
- const target = process.argv[2];
41
- if (!target) {
42
- console.log('No file specified to validate.');
43
- process.exit(0);
44
- }
45
- const res = validateNode(path.resolve(target));
46
- if (res.valid) {
47
- console.log(`✅ SSSS node "${path.basename(target)}" is fully valid!`);
48
- process.exit(0);
49
- } else {
50
- console.error(`❌ Validation failed for "${path.basename(target)}":\n${res.errors.join('\n')}`);
51
- process.exit(1);
52
- }
203
+ function realPathOrPath(value) {
204
+ try {
205
+ return fs.realpathSync(value);
206
+ } catch {
207
+ return value;
53
208
  }
54
209
  }
210
+
211
+ const invokedPath = process.argv[1] ? realPathOrPath(path.resolve(process.argv[1])) : null;
212
+ const modulePath = realPathOrPath(fileURLToPath(import.meta.url));
213
+ if (invokedPath === modulePath) {
214
+ const target = process.argv[2];
215
+ if (!target) {
216
+ console.log('Usage: node validate-schema.mjs <path-to-ssss-node.md>');
217
+ process.exit(0);
218
+ }
219
+
220
+ const result = await validateNode(target);
221
+ if (result.warnings.length > 0) {
222
+ console.warn(`Warnings for ${path.basename(result.file)}:\n${result.warnings.join('\n')}`);
223
+ }
224
+
225
+ if (result.valid) {
226
+ console.log(`OK: SSSS node "${path.basename(result.file)}" is valid.`);
227
+ process.exit(0);
228
+ }
229
+
230
+ console.error(`FAIL: Validation failed for "${path.basename(result.file)}":\n${result.errors.join('\n')}`);
231
+ process.exit(1);
232
+ }
@@ -0,0 +1,134 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveBrainLayer } from '../core/config.mjs';
4
+
5
+ function printHelp() {
6
+ console.log(`
7
+ total-recall command — Manage project-local custom CLI commands
8
+
9
+ Usage:
10
+ total-recall command create <name> "<code>" Create a new custom CLI command
11
+ total-recall command read <name> Read the code of a custom CLI command
12
+ total-recall command update <name> "<code>" Update an existing custom CLI command
13
+ total-recall command remove <name> Remove a custom CLI command
14
+ total-recall command list List all custom CLI commands
15
+
16
+ Examples:
17
+ npx total-recall command create hello "console.log('Hello from the brain!');"
18
+ npx total-recall command read hello
19
+ npx total-recall command list
20
+ `);
21
+ }
22
+
23
+ export default async function commandCmd(args) {
24
+ const action = args[0];
25
+ const name = args[1];
26
+
27
+ if (!action || action === '--help' || action === '-h') {
28
+ printHelp();
29
+ return;
30
+ }
31
+
32
+ if (!name && action !== 'list') {
33
+ console.error(`Error: Missing command name.`);
34
+ console.error(`Usage: total-recall command ${action} <name>`);
35
+ process.exit(1);
36
+ }
37
+
38
+ // Ensure this is run in a project
39
+ let projectBrain;
40
+ try {
41
+ projectBrain = resolveBrainLayer('project');
42
+ } catch (err) {
43
+ console.error(`Error: You must be inside a Total Recall project to manage custom commands.`);
44
+ console.error(err.message);
45
+ process.exit(1);
46
+ }
47
+
48
+ const commandsDir = path.join(projectBrain.agentDir, 'commands');
49
+
50
+ if (action === 'create') {
51
+ const code = args[2];
52
+ if (!code) {
53
+ console.error(`Error: Missing code snippet for the command.`);
54
+ console.error(`Usage: total-recall command create <name> "<code>"`);
55
+ process.exit(1);
56
+ }
57
+
58
+ if (!fs.existsSync(commandsDir)) {
59
+ fs.mkdirSync(commandsDir, { recursive: true });
60
+ }
61
+
62
+ const commandPath = path.join(commandsDir, `${name}.mjs`);
63
+
64
+ // Wrap code in a default exported function for CLI execution
65
+ const fileContent = `// Auto-generated custom CLI command: ${name}
66
+ export default async function run(args) {
67
+ ${code}
68
+ }
69
+ `;
70
+
71
+ fs.writeFileSync(commandPath, fileContent, 'utf8');
72
+ console.log(`\x1b[32m✔ Successfully created custom command: \x1b[1mnpx total-recall ${name}\x1b[0m`);
73
+ console.log(` Saved to: ${commandPath}`);
74
+ } else if (action === 'read') {
75
+ const commandPath = path.join(commandsDir, `${name}.mjs`);
76
+ if (fs.existsSync(commandPath)) {
77
+ console.log(fs.readFileSync(commandPath, 'utf8'));
78
+ } else {
79
+ console.error(`Error: Custom command '${name}' does not exist.`);
80
+ process.exit(1);
81
+ }
82
+ } else if (action === 'update') {
83
+ const code = args[2];
84
+ if (!code) {
85
+ console.error(`Error: Missing code snippet for the command.`);
86
+ console.error(`Usage: total-recall command update <name> "<code>"`);
87
+ process.exit(1);
88
+ }
89
+
90
+ const commandPath = path.join(commandsDir, `${name}.mjs`);
91
+ if (!fs.existsSync(commandPath)) {
92
+ console.error(`Error: Custom command '${name}' does not exist.`);
93
+ process.exit(1);
94
+ }
95
+
96
+ // Wrap code in a default exported function for CLI execution
97
+ const fileContent = `// Auto-generated custom CLI command: ${name}
98
+ export default async function run(args) {
99
+ ${code}
100
+ }
101
+ `;
102
+
103
+ fs.writeFileSync(commandPath, fileContent, 'utf8');
104
+ console.log(`\x1b[32m✔ Successfully updated custom command: \x1b[1m${name}\x1b[0m`);
105
+ console.log(` Saved to: ${commandPath}`);
106
+ } else if (action === 'list') {
107
+ if (!fs.existsSync(commandsDir)) {
108
+ console.log('No custom commands found.');
109
+ return;
110
+ }
111
+ const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.mjs'));
112
+ if (files.length === 0) {
113
+ console.log('No custom commands found.');
114
+ } else {
115
+ console.log(`\x1b[1mCustom CLI commands in ${commandsDir}:\x1b[0m`);
116
+ files.forEach(f => {
117
+ console.log(` - ${f.replace('.mjs', '')}`);
118
+ });
119
+ }
120
+ } else if (action === 'remove') {
121
+ const commandPath = path.join(commandsDir, `${name}.mjs`);
122
+ if (fs.existsSync(commandPath)) {
123
+ fs.unlinkSync(commandPath);
124
+ console.log(`\x1b[32m✔ Successfully removed custom command: \x1b[1m${name}\x1b[0m`);
125
+ } else {
126
+ console.error(`Error: Custom command '${name}' does not exist.`);
127
+ process.exit(1);
128
+ }
129
+ } else {
130
+ console.error(`Unknown action: ${action}`);
131
+ printHelp();
132
+ process.exit(1);
133
+ }
134
+ }
@@ -10,6 +10,7 @@ import {
10
10
  } from './schema.mjs';
11
11
  import { atomicWrite, safeStringify } from './vault.mjs';
12
12
  import { logger } from './logger.mjs';
13
+ import { validateMemoryNode } from './total-recall-memory-validator.mjs';
13
14
 
14
15
  const APPEND_TYPES = new Set(['conversation', 'run']);
15
16
  const PROTOCOL_PATHS = [
@@ -158,8 +159,14 @@ export function processOperation(envelope, vaultRoot, options = {}) {
158
159
  if (!resolvedType) errors.push('Missing required frontmatter field: type');
159
160
  else if (!SSSS_SCHEMAS[resolvedType]) errors.push(`Unknown SSSS type: '${resolvedType}'.`);
160
161
  else {
161
- const r = SSSS_SCHEMAS[resolvedType].safeParse(fmData);
162
- if (!r.success) r.error.issues.forEach(i => errors.push(`${i.path.join('.')}: ${i.message}`));
162
+ if (resolvedType === 'memory' && envelope.path.includes('.agent/memory-vault/')) {
163
+ const vResult = validateMemoryNode(fmData);
164
+ if (!vResult.success) vResult.errors.forEach(e => errors.push(e));
165
+ } else {
166
+ const r = SSSS_SCHEMAS[resolvedType].safeParse(fmData);
167
+ if (!r.success) r.error.issues.forEach(i => errors.push(`${i.path.join('.')}: ${i.message}`));
168
+ }
169
+
163
170
  if (envelope.type === 'operation' && APPEND_TYPES.has(resolvedType)) {
164
171
  const ap = resolveVfsPath(vaultRoot, envelope.path);
165
172
  if (fs.existsSync(ap)) {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Promotes a feedback-derived memory node to a wider scope after applying
3
+ * strict provenance stripping and privacy guarantees.
4
+ *
5
+ * Flow: workspace -> system_candidate -> system_promoted
6
+ *
7
+ * @param {object} memoryNode - The memory node data (frontmatter + __body__).
8
+ * @param {string} targetScope - 'system_candidate' or 'system_promoted'
9
+ * @returns {object} { success: boolean, node: object, errors: string[] }
10
+ */
11
+ export function promoteFeedback(memoryNode, targetScope) {
12
+ const currentScope = memoryNode.feedback_scope || 'workspace';
13
+
14
+ if (targetScope === 'system_candidate') {
15
+ if (currentScope !== 'workspace' && currentScope !== 'local_thread') {
16
+ return { success: false, node: null, errors: [`Cannot promote to system_candidate from ${currentScope}`] };
17
+ }
18
+
19
+ // Provenance stripping
20
+ const strippedNode = JSON.parse(JSON.stringify(memoryNode));
21
+ strippedNode.feedback_scope = 'system_candidate';
22
+
23
+ // Remove PII / workspace leaking fields
24
+ delete strippedNode.workspace_id;
25
+ delete strippedNode.user_id;
26
+ if (strippedNode.source) {
27
+ delete strippedNode.source.session_id;
28
+ delete strippedNode.source.agent;
29
+ }
30
+ delete strippedNode.x_browser_context;
31
+ delete strippedNode.x_location;
32
+ delete strippedNode.x_citations; // Citations might contain private workspace doc links
33
+
34
+ // Scrub body for UUIDs
35
+ if (strippedNode.__body__) {
36
+ strippedNode.__body__ = strippedNode.__body__.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ig, '[REDACTED_UUID]');
37
+ }
38
+
39
+ return { success: true, node: strippedNode, errors: [] };
40
+ }
41
+
42
+ if (targetScope === 'system_promoted') {
43
+ if (currentScope !== 'system_candidate') {
44
+ return { success: false, node: null, errors: [`Cannot promote to system_promoted directly from ${currentScope}. Must be system_candidate first.`] };
45
+ }
46
+
47
+ const promotedNode = JSON.parse(JSON.stringify(memoryNode));
48
+ promotedNode.feedback_scope = 'system_promoted';
49
+
50
+ return { success: true, node: promotedNode, errors: [] };
51
+ }
52
+
53
+ return { success: false, node: null, errors: [`Unknown target scope: ${targetScope}`] };
54
+ }
@@ -317,10 +317,7 @@ export async function callLocalRuntime(prompt, system, config) {
317
317
  GITHUB_TOKEN: process.env.GITHUB_TOKEN || dynamicSecrets.github_token || githubToken,
318
318
  };
319
319
 
320
- if (agent.name === 'antigravity' || agent.name === 'gemini') {
321
- delete spawnEnv.GOOGLE_API_KEY;
322
- }
323
-
320
+ logger.info('api', 'SPAWN_ENV GOOGLE_API_KEY length: ' + (spawnEnv.GOOGLE_API_KEY ? spawnEnv.GOOGLE_API_KEY.length : 0));
324
321
  const result = spawnSync('sh', ['-c', cmd], {
325
322
  encoding: 'utf8',
326
323
  timeout,
@@ -24,8 +24,8 @@ export const MemoryNodeSchema = z.object({
24
24
  category: z.string(),
25
25
  title: z.string(),
26
26
  status: z.enum(['active', 'superseded', 'deprecated', 'draft']),
27
- confidence: z.number().min(0).max(1),
28
- importance: z.number().int().min(1).max(5),
27
+ confidence: z.number().min(0).max(1).optional(),
28
+ importance: z.number().int().min(1).max(5).optional(),
29
29
  created: ssssDatetime(),
30
30
  updated: ssssDatetime(),
31
31
  last_accessed: ssssDatetime(),
@@ -41,12 +41,12 @@ export const MemoryNodeSchema = z.object({
41
41
  tags: z.array(z.string()),
42
42
  related: z.array(z.string()),
43
43
  routes_to_skills: z.array(z.string()),
44
- sentiment_polarity: z.enum(['directive_must', 'directive_must_not', 'descriptive', 'preference']),
45
- sentiment_target: z.string(),
46
- modality: z.enum(['must', 'must_not', 'should', 'should_not', 'descriptive', 'preference']),
47
- subject: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/),
48
- predicate: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/),
49
- object: z.string(),
44
+ sentiment_polarity: z.enum(['directive_must', 'directive_must_not', 'descriptive', 'preference']).optional(),
45
+ sentiment_target: z.string().optional(),
46
+ modality: z.enum(['must', 'must_not', 'should', 'should_not', 'descriptive', 'preference']).optional(),
47
+ subject: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/).optional(),
48
+ predicate: z.string().regex(/^[a-zA-Z0-9_\s.-]+$/).optional(),
49
+ object: z.string().optional(),
50
50
  decay: z.object({
51
51
  half_life_days: z.number(),
52
52
  access_count: z.number().int(),
@@ -87,7 +87,8 @@ export const MemoryNodeSchema = z.object({
87
87
  expires_at: ssssDatetimeNullable().optional(),
88
88
  // Absolute Invariant Extensions
89
89
  priority: z.enum(['absolute', 'high', 'normal', 'low']).optional(),
90
- immutable: z.boolean().optional()
90
+ immutable: z.boolean().optional(),
91
+ feedback_scope: z.enum(['local_thread', 'workspace', 'account', 'system_candidate', 'system_promoted']).optional()
91
92
  });
92
93
 
93
94
  export const ConflictRecordSchema = z.object({
@@ -464,6 +465,7 @@ export const EventSchema = z.object({
464
465
  comment: z.string().optional().nullable(),
465
466
  comment_lang: z.string().optional().nullable(),
466
467
  timestamp: z.string().optional().nullable(),
468
+ feedback_scope: z.enum(['local_thread', 'workspace', 'account', 'system_candidate', 'system_promoted']).optional().nullable(),
467
469
  });
468
470
 
469
471
  export const LanguageConventionSchema = z.object({
@@ -424,7 +424,7 @@ async function writeShim(shimPath, skillsDir, nodes = [], { vaultDir, derivedDir
424
424
  if (stat.isSymbolicLink()) {
425
425
  // If it's a symlink, DO NOT destroy it. It likely points to INSTRUCTIONS.md natively,
426
426
  // and its content will update automatically when the target updates.
427
- return;
427
+ return false;
428
428
  } else {
429
429
  const raw = fs.readFileSync(shimPath, 'utf8');
430
430
  let cleaned = raw;
@@ -435,15 +435,18 @@ async function writeShim(shimPath, skillsDir, nodes = [], { vaultDir, derivedDir
435
435
 
436
436
  const updated = injectDirectives(cleaned, rulesBlock);
437
437
  atomicWrite(shimPath, updated);
438
+ return true;
438
439
  }
439
440
  } else {
440
441
  if (!fs.existsSync(shimDir)) {
441
442
  fs.mkdirSync(shimDir, { recursive: true });
442
443
  }
443
444
  atomicWrite(shimPath, fullContent);
445
+ return true;
444
446
  }
445
447
  } catch (err) {
446
448
  // Ignore permission errors
449
+ return false;
447
450
  }
448
451
  }
449
452
 
@@ -453,8 +456,8 @@ async function writeShim(shimPath, skillsDir, nodes = [], { vaultDir, derivedDir
453
456
  const CLIENT_SHIMS = {
454
457
  cursor: ['.cursorrules'],
455
458
  'claude-code': ['.clauderules'],
456
- antigravity: [],
457
- gemini: [],
459
+ antigravity: ['AGENTS.md'],
460
+ gemini: ['GEMINI.md'],
458
461
  codex: ['.codexrules'],
459
462
  vscode: ['.github/copilot-instructions.md', '.vscode/copilot-instructions.md'],
460
463
  pi: [],
@@ -484,32 +487,31 @@ function readConnectedClients(clientsPath) {
484
487
  async function compilePointers(instructionsFile, skillsDir, nodes = [], { vaultDir, derivedDir } = {}) {
485
488
  const agentDir = path.dirname(instructionsFile);
486
489
  const baseDir = path.basename(agentDir) === '.agent' ? path.dirname(agentDir) : agentDir;
490
+ let injectedCount = 0;
487
491
 
488
492
  // Always write the canonical INSTRUCTIONS.md
489
- await writeShim(path.join(baseDir, 'INSTRUCTIONS.md'), skillsDir, nodes, { vaultDir, derivedDir });
493
+ if (await writeShim(path.join(baseDir, 'INSTRUCTIONS.md'), skillsDir, nodes, { vaultDir, derivedDir })) {
494
+ injectedCount++;
495
+ }
490
496
 
491
497
  // Determine which client shims to write
492
498
  const clientsPath = path.join(baseDir, '.agent', 'config', 'clients.json');
493
499
  const connectedClients = readConnectedClients(clientsPath);
494
500
 
495
- if (connectedClients === null) {
496
- // Write out-of-the-box dotfiles to support IDEs automatically,
497
- // avoiding duplicate .md files to prevent Antigravity context bloat.
498
- for (const files of Object.values(CLIENT_SHIMS)) {
499
- for (const file of files) {
500
- await writeShim(path.join(baseDir, file), skillsDir, nodes, { vaultDir, derivedDir });
501
- }
502
- }
503
- } else {
501
+ if (connectedClients !== null) {
504
502
  // Only write shims for connected clients
505
503
  for (const client of connectedClients) {
506
504
  const files = CLIENT_SHIMS[client];
507
505
  if (!files) continue;
508
506
  for (const file of files) {
509
- await writeShim(path.join(baseDir, file), skillsDir, nodes, { vaultDir, derivedDir });
507
+ if (await writeShim(path.join(baseDir, file), skillsDir, nodes, { vaultDir, derivedDir })) {
508
+ injectedCount++;
509
+ }
510
510
  }
511
511
  }
512
512
  }
513
+
514
+ return injectedCount;
513
515
  }
514
516
 
515
517
  /**
@@ -537,7 +539,7 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
537
539
  }
538
540
 
539
541
  // 1. Write pointer and active rules to all instruction shims
540
- await compilePointers(instructionsFile, skillsDir, nodes, { vaultDir, derivedDir });
542
+ const skillsInjected = await compilePointers(instructionsFile, skillsDir, nodes, { vaultDir, derivedDir });
541
543
 
542
544
  // 2. Build derived indexes (powers semantic search API)
543
545
  if (!fs.existsSync(derivedDir)) {
@@ -577,7 +579,7 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
577
579
 
578
580
  return {
579
581
  nodesProcessed: nodes.length,
580
- skillsInjected: 0,
582
+ skillsInjected,
581
583
  semanticIndexed: semanticResult.indexed,
582
584
  semanticUnavailable: semanticResult.unavailable
583
585
  };
@@ -0,0 +1,44 @@
1
+ import { SSSS_SCHEMAS } from './schema.mjs';
2
+
3
+ /**
4
+ * Validates a memory node against the SSSS standard, enforcing
5
+ * Schema V2 conditional requirements for Total Recall.
6
+ *
7
+ * @param {object} fmData - The parsed YAML frontmatter of the memory node.
8
+ * @returns {object} { success: boolean, errors: string[] }
9
+ */
10
+ export function validateMemoryNode(fmData) {
11
+ const errors = [];
12
+
13
+ // 1. Base schema validation (types, bounds, basic required fields)
14
+ const r = SSSS_SCHEMAS['memory'].safeParse(fmData);
15
+ if (!r.success) {
16
+ r.error.issues.forEach(i => errors.push(`${i.path.join('.')}: ${i.message}`));
17
+ }
18
+
19
+ // 2. Schema V2 Conditional Validation
20
+ // If schema_version === 2, the following fields are strictly required:
21
+ if (fmData.schema_version === 2) {
22
+ const v2Required = [
23
+ 'confidence',
24
+ 'importance',
25
+ 'modality',
26
+ 'subject',
27
+ 'predicate',
28
+ 'object',
29
+ 'sentiment_polarity',
30
+ 'sentiment_target'
31
+ ];
32
+
33
+ v2Required.forEach(field => {
34
+ if (fmData[field] === undefined || fmData[field] === null) {
35
+ errors.push(`V2 Schema Requirement: Missing required field '${field}'`);
36
+ }
37
+ });
38
+ }
39
+
40
+ return {
41
+ success: errors.length === 0,
42
+ errors
43
+ };
44
+ }