@cruxy/cli 0.17.0 → 0.18.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/dist/agent/loop.d.ts +3 -0
- package/dist/agent/loop.js +1 -0
- package/dist/agent/prompts.d.ts +7 -0
- package/dist/agent/prompts.js +6 -0
- package/dist/agent/session.d.ts +8 -0
- package/dist/agent/session.js +1 -0
- package/dist/cli/commands/memory.d.ts +8 -0
- package/dist/cli/commands/memory.js +98 -0
- package/dist/cli/program.js +2 -0
- package/dist/cli/session-factory.js +22 -1
- package/dist/config/schema.d.ts +56 -12
- package/dist/config/schema.js +18 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +11 -0
- package/dist/errors/constructors.d.ts +14 -0
- package/dist/errors/constructors.js +45 -0
- package/dist/errors/types.d.ts +9 -0
- package/dist/errors/types.js +16 -0
- package/dist/hooks/types.d.ts +1 -1
- package/dist/memory/index.d.ts +7 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/recall.d.ts +32 -0
- package/dist/memory/recall.js +73 -0
- package/dist/memory/remember-tool.d.ts +25 -0
- package/dist/memory/remember-tool.js +56 -0
- package/dist/memory/secrets.d.ts +29 -0
- package/dist/memory/secrets.js +61 -0
- package/dist/memory/service.d.ts +92 -0
- package/dist/memory/service.js +164 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.js +100 -0
- package/dist/memory/trust.d.ts +52 -0
- package/dist/memory/trust.js +106 -0
- package/dist/memory/types.d.ts +101 -0
- package/dist/memory/types.js +58 -0
- package/dist/plan/service.d.ts +3 -0
- package/dist/plan/service.js +2 -0
- package/package.json +1 -1
package/dist/agent/loop.d.ts
CHANGED
|
@@ -40,6 +40,9 @@ export interface RunAgentArgs {
|
|
|
40
40
|
} | null;
|
|
41
41
|
/** Project instructions (e.g. from CRUXY.md) folded into the system prompt. */
|
|
42
42
|
projectInstructions?: string | null;
|
|
43
|
+
/** Persistent memory (C.29): the pre-rendered recall block for the system
|
|
44
|
+
* prompt, or null. Reference data only — never influences the U.3 gate. */
|
|
45
|
+
recalledMemory?: string | null;
|
|
43
46
|
/** Plan mode's propose phase (C.31): inject the plan-first system directive. */
|
|
44
47
|
planMode?: boolean;
|
|
45
48
|
/** Subagent runs (C.14): inject the bounded-subtask system directive. */
|
package/dist/agent/loop.js
CHANGED
|
@@ -56,6 +56,7 @@ async function driveLoop(args, renderer, routed) {
|
|
|
56
56
|
.map((tool) => ({ name: tool.name, description: tool.description })),
|
|
57
57
|
git: args.git ?? null,
|
|
58
58
|
projectInstructions: args.projectInstructions ?? null,
|
|
59
|
+
recalledMemory: args.recalledMemory ?? null,
|
|
59
60
|
planMode: args.planMode ?? false,
|
|
60
61
|
subagent: args.subagent ?? false,
|
|
61
62
|
});
|
package/dist/agent/prompts.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface PromptContext {
|
|
|
28
28
|
} | null;
|
|
29
29
|
/** Optional extra instructions (e.g. from a project CRUXY.md). */
|
|
30
30
|
projectInstructions?: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Persistent memory (C.29): the pre-rendered, demarcated recall block injected
|
|
33
|
+
* at session start. It is REFERENCE DATA, not instructions — the block carries
|
|
34
|
+
* its own data-only framing (see memory/recall.ts) and is appended as an
|
|
35
|
+
* ordinary section; the U.3 gate never reads it. Null when nothing is recalled.
|
|
36
|
+
*/
|
|
37
|
+
recalledMemory?: string | null;
|
|
31
38
|
/** Plan mode's propose phase (C.31): inject the plan-first directive. */
|
|
32
39
|
planMode?: boolean;
|
|
33
40
|
/** Subagent run (C.14): inject the bounded-subtask directive. */
|
package/dist/agent/prompts.js
CHANGED
|
@@ -91,6 +91,12 @@ export function buildSystemPrompt(ctx) {
|
|
|
91
91
|
if (ctx.projectInstructions?.trim()) {
|
|
92
92
|
sections.push(`## Project instructions\nThe following came from this project's configuration; honor it unless it conflicts with the rules above:\n\n${ctx.projectInstructions.trim()}`);
|
|
93
93
|
}
|
|
94
|
+
// Persistent memory (C.29). Appended last, as reference DATA — the block is
|
|
95
|
+
// pre-rendered with its own un-spoofable data-only demarcation, so it is added
|
|
96
|
+
// verbatim (never re-wrapped as an instruction). Absent when nothing recalled.
|
|
97
|
+
if (ctx.recalledMemory?.trim()) {
|
|
98
|
+
sections.push(ctx.recalledMemory.trim());
|
|
99
|
+
}
|
|
94
100
|
return sections.join("\n\n");
|
|
95
101
|
}
|
|
96
102
|
/**
|
package/dist/agent/session.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { type AgentResult, type LifecycleHookRunner } from "./loop.js";
|
|
|
14
14
|
export type PlanRunner = (args: {
|
|
15
15
|
messages: Message[];
|
|
16
16
|
projectInstructions: string | null;
|
|
17
|
+
recalledMemory: string | null;
|
|
17
18
|
renderer?: StreamRenderer;
|
|
18
19
|
}) => Promise<AgentResult>;
|
|
19
20
|
export interface SessionArgs {
|
|
@@ -32,6 +33,13 @@ export interface SessionArgs {
|
|
|
32
33
|
} | null;
|
|
33
34
|
/** Project instructions (e.g. CRUXY.md) folded into every turn's system prompt. */
|
|
34
35
|
projectInstructions?: string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Persistent memory (C.29): the pre-rendered recall block, injected into every
|
|
38
|
+
* turn's system prompt as reference data. Built once at session start (user
|
|
39
|
+
* memory + trusted project memory); null when memory is off or empty. Fixed
|
|
40
|
+
* for the session — it does not re-read mid-session.
|
|
41
|
+
*/
|
|
42
|
+
recalledMemory?: string | null;
|
|
35
43
|
/** Start in plan mode (C.31). Toggleable at runtime via `setPlanMode`. */
|
|
36
44
|
planMode?: boolean;
|
|
37
45
|
/** The plan-mode turn runner; required for plan mode to actually engage. */
|
package/dist/agent/session.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* `cruxy memory` — inspect and control persistent memory (C.29). `list` shows
|
|
4
|
+
* user + project entries and whether project memory is trusted (recalled);
|
|
5
|
+
* `forget`/`clear` remove entries; `trust` records the explicit decision to
|
|
6
|
+
* recall a repo's project memory, bound to its current fingerprint.
|
|
7
|
+
*/
|
|
8
|
+
export declare function memoryCommand(): Command;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { loadConfig } from "../../config/index.js";
|
|
4
|
+
import { shouldUseColor } from "../../errors/index.js";
|
|
5
|
+
import { themeForColor } from "../../theme/index.js";
|
|
6
|
+
import { MemoryService } from "../../memory/index.js";
|
|
7
|
+
import { logger } from "../../utils/logger.js";
|
|
8
|
+
/**
|
|
9
|
+
* `cruxy memory` — inspect and control persistent memory (C.29). `list` shows
|
|
10
|
+
* user + project entries and whether project memory is trusted (recalled);
|
|
11
|
+
* `forget`/`clear` remove entries; `trust` records the explicit decision to
|
|
12
|
+
* recall a repo's project memory, bound to its current fingerprint.
|
|
13
|
+
*/
|
|
14
|
+
export function memoryCommand() {
|
|
15
|
+
const cmd = new Command("memory").description("inspect and control persistent memory recalled across sessions");
|
|
16
|
+
const service = () => {
|
|
17
|
+
const { config } = loadConfig();
|
|
18
|
+
return new MemoryService({ cwd: process.cwd(), config: config.memory });
|
|
19
|
+
};
|
|
20
|
+
cmd
|
|
21
|
+
.command("list", { isDefault: true })
|
|
22
|
+
.description("list saved memory entries and project-memory trust status")
|
|
23
|
+
.action(() => {
|
|
24
|
+
const t = themeForColor(shouldUseColor(process.stdout));
|
|
25
|
+
const { config } = loadConfig();
|
|
26
|
+
const status = service().status();
|
|
27
|
+
logger.print(`${t.strong("memory:")} ${config.memory.enabled ? t.success("enabled") : t.warning("disabled (memory.enabled = false)")}`);
|
|
28
|
+
logger.print(`\n${t.heading("user memory")} ${t.muted("(trusted)")}`);
|
|
29
|
+
printEntries(status.user, t);
|
|
30
|
+
const trustLabel = status.projectTrusted
|
|
31
|
+
? t.success("trusted — recalled")
|
|
32
|
+
: t.danger("NOT trusted — not recalled; run `cruxy memory trust .`");
|
|
33
|
+
logger.print(`\n${t.heading("project memory")} ${trustLabel}`);
|
|
34
|
+
printEntries(status.project, t);
|
|
35
|
+
if (status.errors.length > 0) {
|
|
36
|
+
logger.print(`\n${t.danger(t.heading("excluded (invalid or secret — never recalled):"))}`);
|
|
37
|
+
for (const e of status.errors) {
|
|
38
|
+
logger.print(` ${t.muted(`[${e.scope}]`)} ${t.strong(e.id)} — ${e.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
cmd
|
|
43
|
+
.command("forget <id>")
|
|
44
|
+
.description("forget one saved memory entry by id")
|
|
45
|
+
.action((id) => {
|
|
46
|
+
const t = themeForColor(shouldUseColor(process.stdout));
|
|
47
|
+
const removed = service().forget(id);
|
|
48
|
+
logger.print(removed
|
|
49
|
+
? `${t.success("forgotten")} — entry ${t.strong(id)} removed`
|
|
50
|
+
: t.warning(`no memory entry with id ${id}`));
|
|
51
|
+
});
|
|
52
|
+
cmd
|
|
53
|
+
.command("clear")
|
|
54
|
+
.description("clear saved memory entries in a scope")
|
|
55
|
+
.option("--scope <scope>", "which scope to clear: user | project | all", "all")
|
|
56
|
+
.action((opts) => {
|
|
57
|
+
const t = themeForColor(shouldUseColor(process.stdout));
|
|
58
|
+
const scope = opts.scope;
|
|
59
|
+
if (scope !== "user" && scope !== "project" && scope !== "all") {
|
|
60
|
+
logger.print(t.danger(`invalid --scope ${scope} (use user | project | all)`));
|
|
61
|
+
process.exitCode = 2;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const removed = service().clear(scope);
|
|
65
|
+
const where = scope === "all" ? "across all scopes" : `from ${scope} memory`;
|
|
66
|
+
logger.print(`${t.success("cleared")} — removed ${removed} ` +
|
|
67
|
+
`entr${removed === 1 ? "y" : "ies"} ${where}`);
|
|
68
|
+
});
|
|
69
|
+
cmd
|
|
70
|
+
.command("trust [path]")
|
|
71
|
+
.description("trust this repo's project memory after reviewing it (records the decision)")
|
|
72
|
+
.action((target) => {
|
|
73
|
+
const t = themeForColor(shouldUseColor(process.stdout));
|
|
74
|
+
const { config } = loadConfig();
|
|
75
|
+
const root = path.resolve(target ?? process.cwd());
|
|
76
|
+
const svc = new MemoryService({ cwd: root, config: config.memory });
|
|
77
|
+
const status = svc.status();
|
|
78
|
+
if (status.project.length === 0) {
|
|
79
|
+
logger.print(t.muted(`no project memory found under ${root}`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
logger.print(t.strong(`trusting ${status.project.length} project entr${status.project.length === 1 ? "y" : "ies"}:`));
|
|
83
|
+
printEntries(status.project, t);
|
|
84
|
+
const count = svc.trustProject();
|
|
85
|
+
logger.print(`${t.success("trusted")} — ${count} project entr${count === 1 ? "y" : "ies"} will now be recalled for ${root}. ` +
|
|
86
|
+
t.muted("changing them will require re-trusting."));
|
|
87
|
+
});
|
|
88
|
+
return cmd;
|
|
89
|
+
}
|
|
90
|
+
function printEntries(entries, t) {
|
|
91
|
+
if (entries.length === 0) {
|
|
92
|
+
logger.print(t.muted(" (none)"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const e of entries) {
|
|
96
|
+
logger.print(` ${t.muted(e.id.slice(0, 8))} ${t.strong(`(${e.kind})`)} ${e.content}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
package/dist/cli/program.js
CHANGED
|
@@ -15,6 +15,7 @@ import { checkpointCommand } from "./commands/checkpoint.js";
|
|
|
15
15
|
import { rollbackCommand } from "./commands/rollback.js";
|
|
16
16
|
import { testCommand } from "./commands/test.js";
|
|
17
17
|
import { hooksCommand } from "./commands/hooks.js";
|
|
18
|
+
import { memoryCommand } from "./commands/memory.js";
|
|
18
19
|
import { loadConfig } from "../config/index.js";
|
|
19
20
|
import { maybeRunOnboarding } from "./onboard.js";
|
|
20
21
|
export function buildProgram() {
|
|
@@ -45,6 +46,7 @@ export function buildProgram() {
|
|
|
45
46
|
program.addCommand(rollbackCommand());
|
|
46
47
|
program.addCommand(testCommand());
|
|
47
48
|
program.addCommand(hooksCommand());
|
|
49
|
+
program.addCommand(memoryCommand());
|
|
48
50
|
// Default action: bare `cruxy` -> entrypoint. An unrecognized first operand
|
|
49
51
|
// means an unknown command (Commander runs the default action with it as an
|
|
50
52
|
// operand rather than erroring), so reject it as a usage error.
|
|
@@ -8,6 +8,7 @@ import { buildDefaultRegistry } from "../tools/index.js";
|
|
|
8
8
|
import { Session, } from "../agent/index.js";
|
|
9
9
|
import { PlanExecutionPolicy, runPlanSession } from "../plan/index.js";
|
|
10
10
|
import { routerForConfig } from "../routing/index.js";
|
|
11
|
+
import { MemoryService, rememberTool } from "../memory/index.js";
|
|
11
12
|
import { SubagentOrchestrator, makeSpawnSubagentTool, } from "../subagent/index.js";
|
|
12
13
|
/**
|
|
13
14
|
* Wrap a PromptIO so the live region yields before any prompt text lands
|
|
@@ -104,6 +105,23 @@ export function buildAgentSession(config, apiKey, cwd, ttyInteractive, planMode
|
|
|
104
105
|
const execRegistry = buildDefaultRegistry();
|
|
105
106
|
const git = getGitInfo(cwd);
|
|
106
107
|
const projectInstructions = loadProjectInstructions(cwd);
|
|
108
|
+
// Persistent memory (C.29): register the write tool and build the recall block
|
|
109
|
+
// ONCE at session start. User memory is always eligible; project memory is
|
|
110
|
+
// recalled only when trusted (a cloned repo's notes never inject silently).
|
|
111
|
+
// Fully inert when disabled. Recall is best-effort — a load problem degrades to
|
|
112
|
+
// "no memory", never a hard failure at session start.
|
|
113
|
+
let recalledMemory = null;
|
|
114
|
+
if (config.memory.enabled) {
|
|
115
|
+
execRegistry.register(rememberTool);
|
|
116
|
+
const recall = new MemoryService({ cwd, config: config.memory }).recall();
|
|
117
|
+
recalledMemory = recall.block;
|
|
118
|
+
if (recall.projectPresentButUntrusted) {
|
|
119
|
+
logger.info("project memory found but not trusted — run `cruxy memory trust .` to recall it");
|
|
120
|
+
}
|
|
121
|
+
for (const e of recall.errors) {
|
|
122
|
+
logger.warn(`memory: excluded ${e.scope} entry ${e.id} — ${e.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
107
125
|
// One io shared by every prompt in the session (plan approval, the U.3 gate,
|
|
108
126
|
// and any gate inside a subagent), so they all coordinate with the same live
|
|
109
127
|
// region. The full wrapper stack around an ApprovalService is factored here
|
|
@@ -150,7 +168,7 @@ export function buildAgentSession(config, apiKey, cwd, ttyInteractive, planMode
|
|
|
150
168
|
requestApproval: gate(approval),
|
|
151
169
|
sandbox,
|
|
152
170
|
};
|
|
153
|
-
const planRunner = ({ messages, projectInstructions, renderer: turnRenderer, }) => runPlanSession({
|
|
171
|
+
const planRunner = ({ messages, projectInstructions, recalledMemory: turnMemory, renderer: turnRenderer, }) => runPlanSession({
|
|
154
172
|
provider,
|
|
155
173
|
config,
|
|
156
174
|
ctx,
|
|
@@ -161,6 +179,7 @@ export function buildAgentSession(config, apiKey, cwd, ttyInteractive, planMode
|
|
|
161
179
|
messages,
|
|
162
180
|
git,
|
|
163
181
|
projectInstructions,
|
|
182
|
+
recalledMemory: turnMemory,
|
|
164
183
|
renderer: turnRenderer,
|
|
165
184
|
router,
|
|
166
185
|
});
|
|
@@ -171,6 +190,7 @@ export function buildAgentSession(config, apiKey, cwd, ttyInteractive, planMode
|
|
|
171
190
|
ctx,
|
|
172
191
|
git,
|
|
173
192
|
projectInstructions,
|
|
193
|
+
recalledMemory,
|
|
174
194
|
planMode: true,
|
|
175
195
|
planRunner,
|
|
176
196
|
hooks,
|
|
@@ -190,6 +210,7 @@ export function buildAgentSession(config, apiKey, cwd, ttyInteractive, planMode
|
|
|
190
210
|
ctx,
|
|
191
211
|
git,
|
|
192
212
|
projectInstructions,
|
|
213
|
+
recalledMemory,
|
|
193
214
|
hooks,
|
|
194
215
|
router,
|
|
195
216
|
});
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -182,7 +182,7 @@ export declare const IndexConfigSchema: z.ZodObject<{
|
|
|
182
182
|
};
|
|
183
183
|
enabled: boolean;
|
|
184
184
|
embedder: "fastembed";
|
|
185
|
-
store: "
|
|
185
|
+
store: "memory" | "auto" | "sqlite";
|
|
186
186
|
maxFileBytes: number;
|
|
187
187
|
chunk: {
|
|
188
188
|
windowLines: number;
|
|
@@ -196,7 +196,7 @@ export declare const IndexConfigSchema: z.ZodObject<{
|
|
|
196
196
|
} | undefined;
|
|
197
197
|
enabled?: boolean | undefined;
|
|
198
198
|
embedder?: "fastembed" | undefined;
|
|
199
|
-
store?: "
|
|
199
|
+
store?: "memory" | "auto" | "sqlite" | undefined;
|
|
200
200
|
maxFileBytes?: number | undefined;
|
|
201
201
|
chunk?: {
|
|
202
202
|
windowLines?: number | undefined;
|
|
@@ -320,18 +320,18 @@ export declare const SandboxConfigSchema: z.ZodObject<{
|
|
|
320
320
|
*/
|
|
321
321
|
mounts: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
322
322
|
}, "strict", z.ZodTypeAny, {
|
|
323
|
+
memory: string;
|
|
323
324
|
image: string;
|
|
324
325
|
enabled: boolean;
|
|
325
|
-
memory: string;
|
|
326
326
|
network: "none" | "host-loopback" | "full";
|
|
327
327
|
pids: number;
|
|
328
328
|
cpus: number;
|
|
329
329
|
mounts: string[];
|
|
330
330
|
timeout?: number | undefined;
|
|
331
331
|
}, {
|
|
332
|
+
memory?: string | undefined;
|
|
332
333
|
image?: string | undefined;
|
|
333
334
|
enabled?: boolean | undefined;
|
|
334
|
-
memory?: string | undefined;
|
|
335
335
|
network?: "none" | "host-loopback" | "full" | undefined;
|
|
336
336
|
pids?: number | undefined;
|
|
337
337
|
cpus?: number | undefined;
|
|
@@ -386,6 +386,28 @@ export declare const RoutingConfigSchema: z.ZodObject<{
|
|
|
386
386
|
map?: Partial<Record<"main-turn" | "subagent" | "plan" | "commit-msg" | "classify" | "summarize", "kavi" | "vaani" | "mira">> | undefined;
|
|
387
387
|
default?: "kavi" | "vaani" | "mira" | undefined;
|
|
388
388
|
}>;
|
|
389
|
+
/**
|
|
390
|
+
* Persistent memory (C.29): structured notes recalled across sessions. ON by
|
|
391
|
+
* default and safe to be so — user memory is self-authored (nothing you didn't
|
|
392
|
+
* write is injected) and project memory still requires an explicit
|
|
393
|
+
* `cruxy memory trust`, so a cloned repo never auto-injects. Recall is bounded
|
|
394
|
+
* by a token budget; the `remember` tool refuses to persist secrets.
|
|
395
|
+
*/
|
|
396
|
+
export declare const MemoryConfigSchema: z.ZodObject<{
|
|
397
|
+
/** Master switch. When false, nothing is recalled and the `remember` tool
|
|
398
|
+
* is not registered (memory stays fully inert). */
|
|
399
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
400
|
+
/** Token budget for the recalled block injected at session start; entries
|
|
401
|
+
* beyond it are pruned oldest-first (the omission is stated, never silent). */
|
|
402
|
+
maxRecallTokens: z.ZodDefault<z.ZodNumber>;
|
|
403
|
+
}, "strict", z.ZodTypeAny, {
|
|
404
|
+
enabled: boolean;
|
|
405
|
+
maxRecallTokens: number;
|
|
406
|
+
}, {
|
|
407
|
+
enabled?: boolean | undefined;
|
|
408
|
+
maxRecallTokens?: number | undefined;
|
|
409
|
+
}>;
|
|
410
|
+
export type MemoryConfig = z.infer<typeof MemoryConfigSchema>;
|
|
389
411
|
/** MCP server entry — stdio or URL transport (wired up in a later phase). */
|
|
390
412
|
export declare const McpServerSchema: z.ZodObject<{
|
|
391
413
|
command: z.ZodOptional<z.ZodString>;
|
|
@@ -567,7 +589,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
567
589
|
};
|
|
568
590
|
enabled: boolean;
|
|
569
591
|
embedder: "fastembed";
|
|
570
|
-
store: "
|
|
592
|
+
store: "memory" | "auto" | "sqlite";
|
|
571
593
|
maxFileBytes: number;
|
|
572
594
|
chunk: {
|
|
573
595
|
windowLines: number;
|
|
@@ -581,7 +603,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
581
603
|
} | undefined;
|
|
582
604
|
enabled?: boolean | undefined;
|
|
583
605
|
embedder?: "fastembed" | undefined;
|
|
584
|
-
store?: "
|
|
606
|
+
store?: "memory" | "auto" | "sqlite" | undefined;
|
|
585
607
|
maxFileBytes?: number | undefined;
|
|
586
608
|
chunk?: {
|
|
587
609
|
windowLines?: number | undefined;
|
|
@@ -683,18 +705,18 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
683
705
|
*/
|
|
684
706
|
mounts: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
685
707
|
}, "strict", z.ZodTypeAny, {
|
|
708
|
+
memory: string;
|
|
686
709
|
image: string;
|
|
687
710
|
enabled: boolean;
|
|
688
|
-
memory: string;
|
|
689
711
|
network: "none" | "host-loopback" | "full";
|
|
690
712
|
pids: number;
|
|
691
713
|
cpus: number;
|
|
692
714
|
mounts: string[];
|
|
693
715
|
timeout?: number | undefined;
|
|
694
716
|
}, {
|
|
717
|
+
memory?: string | undefined;
|
|
695
718
|
image?: string | undefined;
|
|
696
719
|
enabled?: boolean | undefined;
|
|
697
|
-
memory?: string | undefined;
|
|
698
720
|
network?: "none" | "host-loopback" | "full" | undefined;
|
|
699
721
|
pids?: number | undefined;
|
|
700
722
|
cpus?: number | undefined;
|
|
@@ -731,6 +753,20 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
731
753
|
map?: Partial<Record<"main-turn" | "subagent" | "plan" | "commit-msg" | "classify" | "summarize", "kavi" | "vaani" | "mira">> | undefined;
|
|
732
754
|
default?: "kavi" | "vaani" | "mira" | undefined;
|
|
733
755
|
}>>;
|
|
756
|
+
memory: z.ZodDefault<z.ZodObject<{
|
|
757
|
+
/** Master switch. When false, nothing is recalled and the `remember` tool
|
|
758
|
+
* is not registered (memory stays fully inert). */
|
|
759
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
760
|
+
/** Token budget for the recalled block injected at session start; entries
|
|
761
|
+
* beyond it are pruned oldest-first (the omission is stated, never silent). */
|
|
762
|
+
maxRecallTokens: z.ZodDefault<z.ZodNumber>;
|
|
763
|
+
}, "strict", z.ZodTypeAny, {
|
|
764
|
+
enabled: boolean;
|
|
765
|
+
maxRecallTokens: number;
|
|
766
|
+
}, {
|
|
767
|
+
enabled?: boolean | undefined;
|
|
768
|
+
maxRecallTokens?: number | undefined;
|
|
769
|
+
}>>;
|
|
734
770
|
mcpServers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
735
771
|
command: z.ZodOptional<z.ZodString>;
|
|
736
772
|
args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -749,14 +785,18 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
749
785
|
cruxy: {
|
|
750
786
|
gatewayUrl: string;
|
|
751
787
|
};
|
|
788
|
+
memory: {
|
|
789
|
+
enabled: boolean;
|
|
790
|
+
maxRecallTokens: number;
|
|
791
|
+
};
|
|
752
792
|
checkpoint: {
|
|
753
793
|
enabled: boolean;
|
|
754
794
|
retention: number;
|
|
755
795
|
};
|
|
756
796
|
sandbox: {
|
|
797
|
+
memory: string;
|
|
757
798
|
image: string;
|
|
758
799
|
enabled: boolean;
|
|
759
|
-
memory: string;
|
|
760
800
|
network: "none" | "host-loopback" | "full";
|
|
761
801
|
pids: number;
|
|
762
802
|
cpus: number;
|
|
@@ -812,7 +852,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
812
852
|
};
|
|
813
853
|
enabled: boolean;
|
|
814
854
|
embedder: "fastembed";
|
|
815
|
-
store: "
|
|
855
|
+
store: "memory" | "auto" | "sqlite";
|
|
816
856
|
maxFileBytes: number;
|
|
817
857
|
chunk: {
|
|
818
858
|
windowLines: number;
|
|
@@ -842,14 +882,18 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
842
882
|
cruxy?: {
|
|
843
883
|
gatewayUrl?: string | undefined;
|
|
844
884
|
} | undefined;
|
|
885
|
+
memory?: {
|
|
886
|
+
enabled?: boolean | undefined;
|
|
887
|
+
maxRecallTokens?: number | undefined;
|
|
888
|
+
} | undefined;
|
|
845
889
|
checkpoint?: {
|
|
846
890
|
enabled?: boolean | undefined;
|
|
847
891
|
retention?: number | undefined;
|
|
848
892
|
} | undefined;
|
|
849
893
|
sandbox?: {
|
|
894
|
+
memory?: string | undefined;
|
|
850
895
|
image?: string | undefined;
|
|
851
896
|
enabled?: boolean | undefined;
|
|
852
|
-
memory?: string | undefined;
|
|
853
897
|
network?: "none" | "host-loopback" | "full" | undefined;
|
|
854
898
|
pids?: number | undefined;
|
|
855
899
|
cpus?: number | undefined;
|
|
@@ -905,7 +949,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
|
|
|
905
949
|
} | undefined;
|
|
906
950
|
enabled?: boolean | undefined;
|
|
907
951
|
embedder?: "fastembed" | undefined;
|
|
908
|
-
store?: "
|
|
952
|
+
store?: "memory" | "auto" | "sqlite" | undefined;
|
|
909
953
|
maxFileBytes?: number | undefined;
|
|
910
954
|
chunk?: {
|
|
911
955
|
windowLines?: number | undefined;
|
package/dist/config/schema.js
CHANGED
|
@@ -273,6 +273,23 @@ export const RoutingConfigSchema = z
|
|
|
273
273
|
map: z.record(z.enum(TASK_CLASSES), z.enum(MODEL_TIERS)).default({}),
|
|
274
274
|
})
|
|
275
275
|
.strict();
|
|
276
|
+
/**
|
|
277
|
+
* Persistent memory (C.29): structured notes recalled across sessions. ON by
|
|
278
|
+
* default and safe to be so — user memory is self-authored (nothing you didn't
|
|
279
|
+
* write is injected) and project memory still requires an explicit
|
|
280
|
+
* `cruxy memory trust`, so a cloned repo never auto-injects. Recall is bounded
|
|
281
|
+
* by a token budget; the `remember` tool refuses to persist secrets.
|
|
282
|
+
*/
|
|
283
|
+
export const MemoryConfigSchema = z
|
|
284
|
+
.object({
|
|
285
|
+
/** Master switch. When false, nothing is recalled and the `remember` tool
|
|
286
|
+
* is not registered (memory stays fully inert). */
|
|
287
|
+
enabled: z.boolean().default(true),
|
|
288
|
+
/** Token budget for the recalled block injected at session start; entries
|
|
289
|
+
* beyond it are pruned oldest-first (the omission is stated, never silent). */
|
|
290
|
+
maxRecallTokens: z.number().int().positive().default(1000),
|
|
291
|
+
})
|
|
292
|
+
.strict();
|
|
276
293
|
/** MCP server entry — stdio or URL transport (wired up in a later phase). */
|
|
277
294
|
export const McpServerSchema = z
|
|
278
295
|
.object({
|
|
@@ -298,6 +315,7 @@ export const CruxyConfigSchema = z
|
|
|
298
315
|
sandbox: SandboxConfigSchema.default({}),
|
|
299
316
|
hooks: HooksConfigSchema.default({}),
|
|
300
317
|
routing: RoutingConfigSchema.default({}),
|
|
318
|
+
memory: MemoryConfigSchema.default({}),
|
|
301
319
|
mcpServers: z.record(z.string(), McpServerSchema).default({}),
|
|
302
320
|
logLevel: z.enum(LOG_LEVELS).default("info"),
|
|
303
321
|
})
|
package/dist/constants.d.ts
CHANGED
|
@@ -30,6 +30,17 @@ export declare const COMMANDS_DIR_NAME = "commands";
|
|
|
30
30
|
/** Per-repo hook-trust record, in the GLOBAL dir only (`~/.cruxy/trust.json`) —
|
|
31
31
|
* never in a repo, so cloning carries no trust (C.19 supply-chain safety). */
|
|
32
32
|
export declare const TRUST_FILE_NAME = "trust.json";
|
|
33
|
+
/**
|
|
34
|
+
* Persistent memory (C.29). Structured entries live under the memory subdir of
|
|
35
|
+
* the project dir (`<cwd>/.cruxy/memory`) and the global dir (`~/.cruxy/memory`),
|
|
36
|
+
* one JSON file per scope — pure data, validated, never eval'd.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MEMORY_DIR_NAME = "memory";
|
|
39
|
+
export declare const MEMORY_FILE_NAME = "entries.json";
|
|
40
|
+
/** Per-repo PROJECT-memory trust record, in the GLOBAL dir only
|
|
41
|
+
* (`~/.cruxy/memory-trust.json`) — its own file, independent of hook trust, so
|
|
42
|
+
* cloning a repo carries zero memory trust (C.29 supply-chain safety). */
|
|
43
|
+
export declare const MEMORY_TRUST_FILE_NAME = "memory-trust.json";
|
|
33
44
|
/**
|
|
34
45
|
* Absolute path of the shipped builtin skills directory (`<pkg>/skills`).
|
|
35
46
|
* Anchored the same way as the package.json lookup above: both `dist/` and
|
package/dist/constants.js
CHANGED
|
@@ -50,6 +50,17 @@ export const COMMANDS_DIR_NAME = "commands";
|
|
|
50
50
|
/** Per-repo hook-trust record, in the GLOBAL dir only (`~/.cruxy/trust.json`) —
|
|
51
51
|
* never in a repo, so cloning carries no trust (C.19 supply-chain safety). */
|
|
52
52
|
export const TRUST_FILE_NAME = "trust.json";
|
|
53
|
+
/**
|
|
54
|
+
* Persistent memory (C.29). Structured entries live under the memory subdir of
|
|
55
|
+
* the project dir (`<cwd>/.cruxy/memory`) and the global dir (`~/.cruxy/memory`),
|
|
56
|
+
* one JSON file per scope — pure data, validated, never eval'd.
|
|
57
|
+
*/
|
|
58
|
+
export const MEMORY_DIR_NAME = "memory";
|
|
59
|
+
export const MEMORY_FILE_NAME = "entries.json";
|
|
60
|
+
/** Per-repo PROJECT-memory trust record, in the GLOBAL dir only
|
|
61
|
+
* (`~/.cruxy/memory-trust.json`) — its own file, independent of hook trust, so
|
|
62
|
+
* cloning a repo carries zero memory trust (C.29 supply-chain safety). */
|
|
63
|
+
export const MEMORY_TRUST_FILE_NAME = "memory-trust.json";
|
|
53
64
|
/**
|
|
54
65
|
* Absolute path of the shipped builtin skills directory (`<pkg>/skills`).
|
|
55
66
|
* Anchored the same way as the package.json lookup above: both `dist/` and
|
|
@@ -136,6 +136,20 @@ export declare function subagentFailed(underlying?: unknown): CruxyError;
|
|
|
136
136
|
* invents a test command — the fix is always to declare one.
|
|
137
137
|
*/
|
|
138
138
|
export declare function testCommandNotFound(): CruxyError;
|
|
139
|
+
/**
|
|
140
|
+
* A memory write was refused because the content matched a known secret shape
|
|
141
|
+
* (C.29). Secrets are NEVER persisted to memory — the fix is to remember a
|
|
142
|
+
* non-secret description, not the secret itself.
|
|
143
|
+
*/
|
|
144
|
+
export declare function memorySecretRefused(kind: string): CruxyError;
|
|
145
|
+
/**
|
|
146
|
+
* A repo's project memory is present but has not been trusted on this machine
|
|
147
|
+
* (C.29). It is never recalled into the model's context silently — a cloned repo
|
|
148
|
+
* cannot inject notes until you review and trust them.
|
|
149
|
+
*/
|
|
150
|
+
export declare function memoryUntrusted(root: string): CruxyError;
|
|
151
|
+
/** A malformed memory entry was rejected (C.29) — excluded, never eval'd. */
|
|
152
|
+
export declare function memoryInvalid(detail: string): CruxyError;
|
|
139
153
|
export declare function internal(underlying?: unknown): CruxyError;
|
|
140
154
|
/**
|
|
141
155
|
* Map a known provider/transport error (from `@cruxy/sdk`) to a typed
|
|
@@ -602,6 +602,51 @@ export function testCommandNotFound() {
|
|
|
602
602
|
],
|
|
603
603
|
});
|
|
604
604
|
}
|
|
605
|
+
// ── persistent memory (exit 14) — C.29 ────────────────────────────────────────
|
|
606
|
+
/**
|
|
607
|
+
* A memory write was refused because the content matched a known secret shape
|
|
608
|
+
* (C.29). Secrets are NEVER persisted to memory — the fix is to remember a
|
|
609
|
+
* non-secret description, not the secret itself.
|
|
610
|
+
*/
|
|
611
|
+
export function memorySecretRefused(kind) {
|
|
612
|
+
return new CruxyError({
|
|
613
|
+
code: ErrorCode.MemorySecret,
|
|
614
|
+
title: "refused to save that note — it looks like it contains a secret",
|
|
615
|
+
cause: `the content matched a ${kind}; secrets are never written to memory`,
|
|
616
|
+
nextSteps: [
|
|
617
|
+
"remember a non-secret description instead (e.g. “the deploy key lives in 1Password”, not the key)",
|
|
618
|
+
],
|
|
619
|
+
meta: { kind },
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* A repo's project memory is present but has not been trusted on this machine
|
|
624
|
+
* (C.29). It is never recalled into the model's context silently — a cloned repo
|
|
625
|
+
* cannot inject notes until you review and trust them.
|
|
626
|
+
*/
|
|
627
|
+
export function memoryUntrusted(root) {
|
|
628
|
+
return new CruxyError({
|
|
629
|
+
code: ErrorCode.MemoryUntrusted,
|
|
630
|
+
title: "this project's memory has not been trusted",
|
|
631
|
+
cause: "project memory can arrive with a cloned repo (another author), so it is not recalled until you trust it",
|
|
632
|
+
nextSteps: [
|
|
633
|
+
"review the entries with `cruxy memory list`",
|
|
634
|
+
"then trust them with `cruxy memory trust .` (re-trust is required if they change)",
|
|
635
|
+
],
|
|
636
|
+
meta: { root },
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
/** A malformed memory entry was rejected (C.29) — excluded, never eval'd. */
|
|
640
|
+
export function memoryInvalid(detail) {
|
|
641
|
+
return new CruxyError({
|
|
642
|
+
code: ErrorCode.MemoryInvalid,
|
|
643
|
+
title: "a memory entry is malformed and was excluded",
|
|
644
|
+
cause: detail,
|
|
645
|
+
nextSteps: [
|
|
646
|
+
"inspect the file with `cruxy memory list`, or clear it with `cruxy memory clear`",
|
|
647
|
+
],
|
|
648
|
+
});
|
|
649
|
+
}
|
|
605
650
|
// ── internal (exit 1) ─────────────────────────────────────────────────────────
|
|
606
651
|
export function internal(underlying) {
|
|
607
652
|
return new CruxyError({
|
package/dist/errors/types.d.ts
CHANGED
|
@@ -71,6 +71,15 @@ export declare const ErrorCode: {
|
|
|
71
71
|
readonly HookInvalid: "CRUXY_E_HOOK_INVALID";
|
|
72
72
|
/** A malformed custom slash-command definition — excluded and surfaced. */
|
|
73
73
|
readonly SlashInvalid: "CRUXY_E_SLASH_INVALID";
|
|
74
|
+
/** A malformed memory entry — excluded from recall/writes and surfaced,
|
|
75
|
+
* never eval'd. */
|
|
76
|
+
readonly MemoryInvalid: "CRUXY_E_MEMORY_INVALID";
|
|
77
|
+
/** A repo's project memory has not been trusted — it is never recalled into
|
|
78
|
+
* context silently (supply-chain safety). */
|
|
79
|
+
readonly MemoryUntrusted: "CRUXY_E_MEMORY_UNTRUSTED";
|
|
80
|
+
/** A memory write was refused because the content matched a secret shape —
|
|
81
|
+
* secrets are never persisted (defense in depth over C.17). */
|
|
82
|
+
readonly MemorySecret: "CRUXY_E_MEMORY_SECRET";
|
|
74
83
|
};
|
|
75
84
|
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
76
85
|
/** The process exit code for an error code (defaults to 1 for safety). */
|
package/dist/errors/types.js
CHANGED
|
@@ -85,6 +85,16 @@ export const ErrorCode = {
|
|
|
85
85
|
HookInvalid: "CRUXY_E_HOOK_INVALID",
|
|
86
86
|
/** A malformed custom slash-command definition — excluded and surfaced. */
|
|
87
87
|
SlashInvalid: "CRUXY_E_SLASH_INVALID",
|
|
88
|
+
// persistent memory (exit 14) — C.29
|
|
89
|
+
/** A malformed memory entry — excluded from recall/writes and surfaced,
|
|
90
|
+
* never eval'd. */
|
|
91
|
+
MemoryInvalid: "CRUXY_E_MEMORY_INVALID",
|
|
92
|
+
/** A repo's project memory has not been trusted — it is never recalled into
|
|
93
|
+
* context silently (supply-chain safety). */
|
|
94
|
+
MemoryUntrusted: "CRUXY_E_MEMORY_UNTRUSTED",
|
|
95
|
+
/** A memory write was refused because the content matched a secret shape —
|
|
96
|
+
* secrets are never persisted (defense in depth over C.17). */
|
|
97
|
+
MemorySecret: "CRUXY_E_MEMORY_SECRET",
|
|
88
98
|
};
|
|
89
99
|
/**
|
|
90
100
|
* Category exit codes. Distinct per category so a caller (CI, a script) can
|
|
@@ -146,6 +156,12 @@ const EXIT_CODES = {
|
|
|
146
156
|
[ErrorCode.HookUntrusted]: 13,
|
|
147
157
|
[ErrorCode.HookInvalid]: 13,
|
|
148
158
|
[ErrorCode.SlashInvalid]: 13,
|
|
159
|
+
// Persistent memory (C.29). A malformed entry and an untrusted project are
|
|
160
|
+
// data-safety problems; a refused secret write is a security stop — grouped
|
|
161
|
+
// for a greppable exit code.
|
|
162
|
+
[ErrorCode.MemoryInvalid]: 14,
|
|
163
|
+
[ErrorCode.MemoryUntrusted]: 14,
|
|
164
|
+
[ErrorCode.MemorySecret]: 14,
|
|
149
165
|
};
|
|
150
166
|
/** The process exit code for an error code (defaults to 1 for safety). */
|
|
151
167
|
export function exitCodeFor(code) {
|
package/dist/hooks/types.d.ts
CHANGED
|
@@ -103,8 +103,8 @@ export declare const SlashFrontmatterSchema: z.ZodObject<{
|
|
|
103
103
|
command?: string | undefined;
|
|
104
104
|
}, {
|
|
105
105
|
description: string;
|
|
106
|
-
command?: string | undefined;
|
|
107
106
|
kind?: "shell" | "prompt" | undefined;
|
|
107
|
+
command?: string | undefined;
|
|
108
108
|
}>;
|
|
109
109
|
export type SlashFrontmatter = z.infer<typeof SlashFrontmatterSchema>;
|
|
110
110
|
/** A validated custom slash command, tagged with source. */
|