total-recall-brain 3.6.9 → 3.6.11
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.mjs +24 -1
- package/frontend/src/pages/LoginPage.tsx +1 -0
- package/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/SKILL.md +18 -1
- package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +176 -26
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/CHANGELOG.md +9 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/scripts/validate-schema.mjs +207 -29
- package/src/cli/command.mjs +134 -0
- package/src/cli/connect.mjs +2 -2
- package/src/core/runtime.mjs +1 -4
- package/src/core/surface.mjs +31 -9
package/bin/total-recall.mjs
CHANGED
|
@@ -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
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
15
|
+
## Ground Truth
|
|
13
16
|
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
+
All agent-generated mutations must flow through the SSSS operation pipeline:
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
11
|
-
const
|
|
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 (
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
+
warnings: [],
|
|
197
|
+
file: target,
|
|
198
|
+
type: null,
|
|
33
199
|
};
|
|
34
200
|
}
|
|
35
201
|
}
|
|
36
202
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
}
|
package/src/cli/connect.mjs
CHANGED
|
@@ -62,13 +62,13 @@ const CLIENTS = {
|
|
|
62
62
|
antigravity: {
|
|
63
63
|
label: 'Antigravity',
|
|
64
64
|
mode: 'symlink',
|
|
65
|
-
target: 'AGENTS.md',
|
|
65
|
+
target: '.agents/rules/AGENTS.md',
|
|
66
66
|
writeSlashCommands: true
|
|
67
67
|
},
|
|
68
68
|
gemini: {
|
|
69
69
|
label: 'Gemini',
|
|
70
70
|
mode: 'symlink',
|
|
71
|
-
target: 'GEMINI.md',
|
|
71
|
+
target: '.agents/rules/GEMINI.md',
|
|
72
72
|
writeSlashCommands: true
|
|
73
73
|
},
|
|
74
74
|
aider: {
|
package/src/core/runtime.mjs
CHANGED
|
@@ -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
|
-
|
|
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,
|
package/src/core/surface.mjs
CHANGED
|
@@ -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', '.agents/rules/AGENTS.md', '.agent/rules/AGENTS.md'],
|
|
460
|
+
gemini: ['GEMINI.md', '.agents/rules/GEMINI.md', '.agent/rules/GEMINI.md'],
|
|
458
461
|
codex: ['.codexrules'],
|
|
459
462
|
vscode: ['.github/copilot-instructions.md', '.vscode/copilot-instructions.md'],
|
|
460
463
|
pi: [],
|
|
@@ -471,8 +474,20 @@ function readConnectedClients(clientsPath) {
|
|
|
471
474
|
const raw = fs.readFileSync(clientsPath, 'utf8').trim();
|
|
472
475
|
if (!raw) return null;
|
|
473
476
|
const parsed = JSON.parse(raw);
|
|
474
|
-
|
|
475
|
-
|
|
477
|
+
|
|
478
|
+
// Support object format: { clients: { gemini: {...} } }
|
|
479
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && parsed.clients) {
|
|
480
|
+
const keys = Object.keys(parsed.clients);
|
|
481
|
+
if (keys.length === 0) return null;
|
|
482
|
+
return new Set(keys);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Support legacy array format just in case
|
|
486
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
487
|
+
return new Set(parsed.map(String));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return null;
|
|
476
491
|
} catch {
|
|
477
492
|
return null;
|
|
478
493
|
}
|
|
@@ -484,9 +499,12 @@ function readConnectedClients(clientsPath) {
|
|
|
484
499
|
async function compilePointers(instructionsFile, skillsDir, nodes = [], { vaultDir, derivedDir } = {}) {
|
|
485
500
|
const agentDir = path.dirname(instructionsFile);
|
|
486
501
|
const baseDir = path.basename(agentDir) === '.agent' ? path.dirname(agentDir) : agentDir;
|
|
502
|
+
let injectedCount = 0;
|
|
487
503
|
|
|
488
504
|
// Always write the canonical INSTRUCTIONS.md
|
|
489
|
-
await writeShim(path.join(baseDir, 'INSTRUCTIONS.md'), skillsDir, nodes, { vaultDir, derivedDir })
|
|
505
|
+
if (await writeShim(path.join(baseDir, 'INSTRUCTIONS.md'), skillsDir, nodes, { vaultDir, derivedDir })) {
|
|
506
|
+
injectedCount++;
|
|
507
|
+
}
|
|
490
508
|
|
|
491
509
|
// Determine which client shims to write
|
|
492
510
|
const clientsPath = path.join(baseDir, '.agent', 'config', 'clients.json');
|
|
@@ -498,10 +516,14 @@ async function compilePointers(instructionsFile, skillsDir, nodes = [], { vaultD
|
|
|
498
516
|
const files = CLIENT_SHIMS[client];
|
|
499
517
|
if (!files) continue;
|
|
500
518
|
for (const file of files) {
|
|
501
|
-
await writeShim(path.join(baseDir, file), skillsDir, nodes, { vaultDir, derivedDir })
|
|
519
|
+
if (await writeShim(path.join(baseDir, file), skillsDir, nodes, { vaultDir, derivedDir })) {
|
|
520
|
+
injectedCount++;
|
|
521
|
+
}
|
|
502
522
|
}
|
|
503
523
|
}
|
|
504
524
|
}
|
|
525
|
+
|
|
526
|
+
return injectedCount;
|
|
505
527
|
}
|
|
506
528
|
|
|
507
529
|
/**
|
|
@@ -529,7 +551,7 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
|
|
|
529
551
|
}
|
|
530
552
|
|
|
531
553
|
// 1. Write pointer and active rules to all instruction shims
|
|
532
|
-
await compilePointers(instructionsFile, skillsDir, nodes, { vaultDir, derivedDir });
|
|
554
|
+
const skillsInjected = await compilePointers(instructionsFile, skillsDir, nodes, { vaultDir, derivedDir });
|
|
533
555
|
|
|
534
556
|
// 2. Build derived indexes (powers semantic search API)
|
|
535
557
|
if (!fs.existsSync(derivedDir)) {
|
|
@@ -569,7 +591,7 @@ export async function compileSurface({ vaultDir, skillsDir, derivedDir, instruct
|
|
|
569
591
|
|
|
570
592
|
return {
|
|
571
593
|
nodesProcessed: nodes.length,
|
|
572
|
-
skillsInjected
|
|
594
|
+
skillsInjected,
|
|
573
595
|
semanticIndexed: semanticResult.indexed,
|
|
574
596
|
semanticUnavailable: semanticResult.unavailable
|
|
575
597
|
};
|