zmemory 0.1.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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/adapters/claude-code-adapter.sh +11 -0
  4. package/adapters/codex-adapter.sh +12 -0
  5. package/adapters/cursor-adapter.sh +14 -0
  6. package/adapters/opencode-adapter.sh +9 -0
  7. package/bin/zmemory.js +426 -0
  8. package/package.json +42 -0
  9. package/src/bus/server.js +51 -0
  10. package/src/commands/agent.js +42 -0
  11. package/src/commands/archive.js +32 -0
  12. package/src/commands/bootstrap.js +7 -0
  13. package/src/commands/bus.js +6 -0
  14. package/src/commands/claim.js +66 -0
  15. package/src/commands/config.js +29 -0
  16. package/src/commands/context.js +269 -0
  17. package/src/commands/daemon.js +6 -0
  18. package/src/commands/decision.js +12 -0
  19. package/src/commands/doctor.js +100 -0
  20. package/src/commands/event.js +149 -0
  21. package/src/commands/exec.js +25 -0
  22. package/src/commands/explain.js +68 -0
  23. package/src/commands/failure.js +12 -0
  24. package/src/commands/gitHook.js +22 -0
  25. package/src/commands/handoff.js +101 -0
  26. package/src/commands/health.js +18 -0
  27. package/src/commands/help.js +33 -0
  28. package/src/commands/index.js +21 -0
  29. package/src/commands/init.js +38 -0
  30. package/src/commands/install.js +28 -0
  31. package/src/commands/locks.js +9 -0
  32. package/src/commands/next.js +59 -0
  33. package/src/commands/orchestrator.js +6 -0
  34. package/src/commands/plan.js +30 -0
  35. package/src/commands/resume.js +79 -0
  36. package/src/commands/route.js +67 -0
  37. package/src/commands/runs.js +47 -0
  38. package/src/commands/search.js +159 -0
  39. package/src/commands/setup.js +148 -0
  40. package/src/commands/skill.js +103 -0
  41. package/src/commands/startRun.js +82 -0
  42. package/src/commands/state.js +68 -0
  43. package/src/commands/status.js +51 -0
  44. package/src/commands/stream.js +27 -0
  45. package/src/commands/summary.js +65 -0
  46. package/src/commands/sync.js +20 -0
  47. package/src/commands/tasks.js +89 -0
  48. package/src/commands/timeline.js +75 -0
  49. package/src/commands/upgrade.js +19 -0
  50. package/src/commands/version.js +12 -0
  51. package/src/commands/who.js +26 -0
  52. package/src/commands/worker.js +71 -0
  53. package/src/commands/workspaces.js +25 -0
  54. package/src/daemon/orchestrator.js +34 -0
  55. package/src/daemon/zmemoryd.js +17 -0
  56. package/src/dashboard/server.js +40 -0
  57. package/src/integrations/agent-hooks.md +39 -0
  58. package/src/integrations/auto-bootstrap.js +90 -0
  59. package/src/integrations/harness-adapter.md +39 -0
  60. package/src/lib/agents.js +36 -0
  61. package/src/lib/config.js +15 -0
  62. package/src/lib/coordination/agents.js +49 -0
  63. package/src/lib/coordination/claims.js +127 -0
  64. package/src/lib/coordination/who.js +49 -0
  65. package/src/lib/fileIndex.js +36 -0
  66. package/src/lib/fileLocks.js +44 -0
  67. package/src/lib/fs.js +140 -0
  68. package/src/lib/lock.js +103 -0
  69. package/src/lib/repoScan.js +19 -0
  70. package/src/lib/run.js +63 -0
  71. package/src/lib/sqlite.js +7 -0
  72. package/src/lib/tasks.js +77 -0
  73. package/src/lib/teamSync.js +36 -0
  74. package/src/lib/workspaces.js +29 -0
  75. package/src/mcp/autostart.js +12 -0
  76. package/src/mcp/server.js +420 -0
  77. package/src/mcp/tools.json +28 -0
@@ -0,0 +1,79 @@
1
+ import fs from "fs";
2
+ import { generateContext } from "./context.js";
3
+ import { isValidRunId } from "../lib/run.js";
4
+ import { listAgents } from "../lib/coordination/agents.js";
5
+ import { listClaims } from "../lib/coordination/claims.js";
6
+
7
+ export async function resumeRun() {
8
+ const base = ".zmemory/runs/active";
9
+
10
+ if (!fs.existsSync(base)) {
11
+ console.log("no active runs");
12
+ return;
13
+ }
14
+
15
+ const runs = fs.readdirSync(base).filter((r) => {
16
+ if (!isValidRunId(r)) return false;
17
+ try {
18
+ return fs.statSync(`${base}/${r}`).isDirectory();
19
+ } catch {
20
+ return false;
21
+ }
22
+ });
23
+
24
+ if (!runs.length) {
25
+ console.log("no active runs");
26
+ return;
27
+ }
28
+
29
+ const latest = runs.sort().reverse()[0];
30
+
31
+ console.log("# ZMemory Resume\n");
32
+
33
+ // Active agents
34
+ const agents = listAgents();
35
+ console.log("Active agents");
36
+ if (!agents.length) console.log(" none");
37
+ for (const a of agents) {
38
+ console.log(` ${a.session}`);
39
+ if (a.task) console.log(` task: ${a.task}`);
40
+ }
41
+ console.log();
42
+
43
+ // Claims
44
+ const claims = listClaims();
45
+ console.log("Open claims");
46
+ if (!claims.length) console.log(" none");
47
+ for (const c of claims) {
48
+ console.log(` ${c.pattern}`);
49
+ for (const ag of c.agents || []) {
50
+ console.log(` ${ag.session} ${ag.task || ""}`);
51
+ }
52
+ }
53
+ console.log();
54
+
55
+ // Recent events
56
+ const eventsFile = ".zmemory/logs/events.jsonl";
57
+ console.log("Recent events");
58
+ if (fs.existsSync(eventsFile)) {
59
+ const lines = fs
60
+ .readFileSync(eventsFile, "utf8")
61
+ .split("\n")
62
+ .filter(Boolean)
63
+ .slice(-5);
64
+
65
+ for (const l of lines) {
66
+ try {
67
+ const e = JSON.parse(l);
68
+ console.log(` ${e.role || "agent"}: ${e.summary || ""}`);
69
+ } catch {}
70
+ }
71
+ } else {
72
+ console.log(" none");
73
+ }
74
+ console.log();
75
+
76
+ console.log("Resuming run:", latest, "\n");
77
+
78
+ await generateContext(["--run", latest, "--role", "executor"]);
79
+ }
@@ -0,0 +1,67 @@
1
+ import fs from "fs";
2
+ import { getRunFile, isValidRunId, ensureRunExists } from "../lib/run.js";
3
+ import { listAgents } from "../lib/agents.js";
4
+
5
+ export async function routeRun(args) {
6
+ const i = args.indexOf("--run");
7
+ const run = i !== -1 ? args[i + 1] : null;
8
+
9
+ if (!run) {
10
+ console.log("missing --run");
11
+ return;
12
+ }
13
+
14
+ if (!isValidRunId(run)) {
15
+ console.log("invalid run id");
16
+ return;
17
+ }
18
+
19
+ try {
20
+ ensureRunExists(run);
21
+ } catch {
22
+ console.log("run not found");
23
+ return;
24
+ }
25
+
26
+ let state;
27
+ try {
28
+ state = JSON.parse(fs.readFileSync(getRunFile(run, "state.json"), "utf8"));
29
+ } catch {
30
+ console.log("run not found");
31
+ return;
32
+ }
33
+
34
+ const attempts = state.attempts || {};
35
+ const repeated = attempts.repeated_count || 0;
36
+ const blockers = Array.isArray(state.blockers) ? state.blockers : [];
37
+
38
+ let role = "executor";
39
+ let reason = "continue execution";
40
+
41
+ const agents = listAgents();
42
+ const activeRoles = Object.values(agents).map((a) => a.role);
43
+
44
+ if (blockers.length) {
45
+ role = "brain";
46
+ reason = "blockers present";
47
+ } else if (repeated >= 3) {
48
+ role = "brain";
49
+ reason = "repeated failures >=3";
50
+ } else if (repeated >= 2) {
51
+ role = "reviewer";
52
+ reason = "repeated failures >=2";
53
+ }
54
+
55
+ if (role === "executor" && activeRoles.includes("executor")) {
56
+ reason = "executor already active, waiting";
57
+ }
58
+
59
+ const result = `# ZMemory Router\n\nRun: ${run}\nNext Role: ${role}\nReason: ${reason}\nRepeated Failures: ${repeated}\nBlockers: ${blockers.join(", ") || "none"}\n\nSuggested Command:\nzmemory context --run ${run} --role ${role}\n`;
60
+
61
+ try {
62
+ const p = getRunFile(run, "router.md");
63
+ fs.writeFileSync(p, result);
64
+ } catch {}
65
+
66
+ console.log(result);
67
+ }
@@ -0,0 +1,47 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ export async function listRuns() {
5
+ const base = ".zmemory/runs/active";
6
+
7
+ if (!fs.existsSync(base)) {
8
+ console.log("No runs directory. Run `zmemory start-run` first.");
9
+ return;
10
+ }
11
+
12
+ const runs = fs.readdirSync(base).filter((r) => r.startsWith("run_"));
13
+
14
+ if (!runs.length) {
15
+ console.log("No active runs.");
16
+ return;
17
+ }
18
+
19
+ console.log("Active Runs");
20
+ console.log("-----------");
21
+
22
+ runs.forEach((runId) => {
23
+ const runDir = path.join(base, runId);
24
+ const statePath = path.join(runDir, "state.json");
25
+ const taskPath = path.join(runDir, "task.md");
26
+
27
+ let task = "(unknown task)";
28
+ let status = "unknown";
29
+
30
+ if (fs.existsSync(taskPath)) {
31
+ const txt = fs.readFileSync(taskPath, "utf8");
32
+ const line = txt.split("\n").slice(1).join(" ").trim();
33
+ if (line) task = line;
34
+ }
35
+
36
+ if (fs.existsSync(statePath)) {
37
+ try {
38
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
39
+ status = state.status || "unknown";
40
+ } catch {}
41
+ }
42
+
43
+ console.log(`${runId} ${task}`);
44
+ console.log(` status: ${status}`);
45
+ console.log();
46
+ });
47
+ }
@@ -0,0 +1,159 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const INDEX_PATH = ".zmemory/index.json";
5
+
6
+ function loadIgnore() {
7
+ const p = ".zmemoryignore";
8
+ if (!fs.existsSync(p)) return [];
9
+ return fs.readFileSync(p, "utf8").split("\n").filter(Boolean);
10
+ }
11
+
12
+ function buildIndex(base, ignore = []) {
13
+ const index = [];
14
+
15
+ function walk(dir) {
16
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
17
+
18
+ for (const e of entries) {
19
+ const p = path.join(dir, e.name);
20
+ if (ignore.some((i) => p.includes(i))) continue;
21
+
22
+ if (e.isDirectory()) {
23
+ walk(p);
24
+ continue;
25
+ }
26
+
27
+ if (!/\.(md|json|jsonl|txt)$/i.test(e.name)) continue;
28
+
29
+ try {
30
+ const content = fs.readFileSync(p, "utf8");
31
+ index.push({ path: p, content: content.toLowerCase() });
32
+ } catch {}
33
+ }
34
+ }
35
+
36
+ walk(base);
37
+
38
+ fs.mkdirSync(path.dirname(INDEX_PATH), { recursive: true });
39
+ fs.writeFileSync(INDEX_PATH, JSON.stringify(index));
40
+
41
+ return index;
42
+ }
43
+
44
+ function loadIndex(base, ignore) {
45
+ if (!fs.existsSync(INDEX_PATH)) {
46
+ return buildIndex(base, ignore);
47
+ }
48
+
49
+ try {
50
+ const raw = JSON.parse(fs.readFileSync(INDEX_PATH, "utf8"));
51
+ if (Array.isArray(raw)) return raw;
52
+ } catch {}
53
+
54
+ return buildIndex(base, ignore);
55
+ }
56
+
57
+ function scan(dir, query, results = [], ignore = []) {
58
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
59
+
60
+ for (const e of entries) {
61
+ const p = path.join(dir, e.name);
62
+ if (ignore.some((i) => p.includes(i))) continue;
63
+
64
+ if (e.isDirectory()) {
65
+ scan(p, query, results, ignore);
66
+ continue;
67
+ }
68
+
69
+ // Only search typical text files in zmemory
70
+ if (!/\.(md|json|jsonl|txt)$/i.test(e.name)) continue;
71
+
72
+ let content;
73
+ try {
74
+ content = fs.readFileSync(p, "utf8");
75
+ } catch {
76
+ continue; // skip unreadable files
77
+ }
78
+
79
+ const lower = content.toLowerCase();
80
+ const q = query.toLowerCase();
81
+
82
+ if (!lower.includes(q)) continue;
83
+
84
+ // Extract short preview around match
85
+ const idx = lower.indexOf(q);
86
+ const start = Math.max(0, idx - 60);
87
+ const end = Math.min(content.length, idx + 60);
88
+ const snippet = content.slice(start, end).replace(/\n/g, " ");
89
+
90
+ results.push({ path: p, snippet });
91
+ }
92
+
93
+ return results;
94
+ }
95
+
96
+ export async function searchMemory(args) {
97
+ const scopeIndex = args.indexOf("--scope");
98
+ const scope = scopeIndex !== -1 ? args[scopeIndex + 1] : "all";
99
+
100
+ const query = args
101
+ .filter((a, i) => a !== "--scope" && args[i - 1] !== "--scope")
102
+ .join(" ");
103
+ if (!query) {
104
+ console.log("missing query");
105
+ return;
106
+ }
107
+
108
+ let base = ".zmemory";
109
+
110
+ if (scope === "runs") base = ".zmemory/runs";
111
+ if (scope === "decisions") base = ".zmemory/decisions.md";
112
+ if (scope === "failures") base = ".zmemory/failures.md";
113
+ if (!fs.existsSync(base)) {
114
+ console.log("zmemory not initialized");
115
+ return;
116
+ }
117
+
118
+ let results = [];
119
+ const ignore = loadIgnore();
120
+
121
+ if (fs.existsSync(base) && fs.statSync(base).isDirectory()) {
122
+ const index = loadIndex(base, ignore);
123
+ const q = query.toLowerCase();
124
+
125
+ for (const entry of index) {
126
+ if (!entry.content.includes(q)) continue;
127
+
128
+ const idx = entry.content.indexOf(q);
129
+ let snippet = "";
130
+
131
+ try {
132
+ const original = fs.readFileSync(entry.path, "utf8");
133
+ const start = Math.max(0, idx - 60);
134
+ const end = Math.min(original.length, idx + 60);
135
+ snippet = original.slice(start, end).replace(/\n/g, " ");
136
+ } catch {}
137
+
138
+ results.push({ path: entry.path, snippet });
139
+ }
140
+ } else if (fs.existsSync(base)) {
141
+ const content = fs.readFileSync(base, "utf8");
142
+ if (content.toLowerCase().includes(query.toLowerCase())) {
143
+ const idx = content.toLowerCase().indexOf(query.toLowerCase());
144
+ const snippet = content.slice(Math.max(0, idx - 60), idx + 60).replace(/\n/g, " ");
145
+ results = [{ path: base, snippet }];
146
+ }
147
+ }
148
+
149
+ if (!results.length) {
150
+ console.log("no results");
151
+ return;
152
+ }
153
+
154
+ results.forEach((r) => {
155
+ console.log(r.path);
156
+ if (r.snippet) console.log(" ", r.snippet.trim());
157
+ console.log();
158
+ });
159
+ }
@@ -0,0 +1,148 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { multiselect, confirm, intro, outro } from "@clack/prompts";
5
+
6
+ function ensureDir(p) {
7
+ fs.mkdirSync(path.dirname(p), { recursive: true });
8
+ }
9
+
10
+ function writeJSONMerge(file, patch) {
11
+ let data = {};
12
+ try {
13
+ if (fs.existsSync(file)) data = JSON.parse(fs.readFileSync(file, "utf8"));
14
+ } catch {}
15
+ const merged = { ...data, ...patch };
16
+ ensureDir(file);
17
+ fs.writeFileSync(file, JSON.stringify(merged, null, 2));
18
+ }
19
+
20
+ function installClaude() {
21
+ const file = path.join(os.homedir(), ".claude", "settings.json");
22
+ let data = {};
23
+ try {
24
+ if (fs.existsSync(file)) data = JSON.parse(fs.readFileSync(file, "utf8"));
25
+ } catch {}
26
+
27
+ data.mcpServers = data.mcpServers || {};
28
+ data.mcpServers.zmemory = { command: "zmemory mcp" };
29
+
30
+ ensureDir(file);
31
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
32
+ }
33
+
34
+ function installCodex() {
35
+ const file = path.join(os.homedir(), ".codex", "config.toml");
36
+ let content = "";
37
+ try {
38
+ if (fs.existsSync(file)) content = fs.readFileSync(file, "utf8");
39
+ } catch {}
40
+
41
+ if (!content.includes("[mcp_servers.zmemory]")) {
42
+ content += `\n[mcp_servers.zmemory]\ncommand = "zmemory mcp"\n`;
43
+ }
44
+
45
+ ensureDir(file);
46
+ fs.writeFileSync(file, content);
47
+ }
48
+
49
+ function installCursor() {
50
+ const file = path.join(process.cwd(), ".cursor", "mcp.json");
51
+ let data = { mcpServers: {} };
52
+ try {
53
+ if (fs.existsSync(file)) data = JSON.parse(fs.readFileSync(file, "utf8"));
54
+ } catch {}
55
+
56
+ data.mcpServers = data.mcpServers || {};
57
+ data.mcpServers.zmemory = { command: "zmemory mcp" };
58
+
59
+ ensureDir(file);
60
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
61
+ }
62
+
63
+ function installOpenCode() {
64
+ const file = path.join(process.cwd(), "opencode.json");
65
+ let data = {};
66
+ try {
67
+ if (fs.existsSync(file)) data = JSON.parse(fs.readFileSync(file, "utf8"));
68
+ } catch {}
69
+
70
+ data.plugin = data.plugin || [];
71
+ if (!data.plugin.includes("zmemory")) data.plugin.push("zmemory");
72
+
73
+ ensureDir(file);
74
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
75
+ }
76
+
77
+ function installKilo() {
78
+ const file = path.join(process.cwd(), "kilo.json");
79
+ let data = {};
80
+ try {
81
+ if (fs.existsSync(file)) data = JSON.parse(fs.readFileSync(file, "utf8"));
82
+ } catch {}
83
+
84
+ data.plugin = data.plugin || [];
85
+ if (!data.plugin.includes("zmemory")) data.plugin.push("zmemory");
86
+
87
+ ensureDir(file);
88
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
89
+ }
90
+
91
+ function installAGY() {
92
+ const file = path.join(os.homedir(), ".copilot", "mcp-config.json");
93
+ writeJSONMerge(file, {
94
+ mcpServers: { zmemory: { command: "zmemory mcp" } }
95
+ });
96
+ }
97
+
98
+ function installKimi() {
99
+ const file = path.join(os.homedir(), ".kimi-code", "mcp.json");
100
+ writeJSONMerge(file, {
101
+ mcpServers: { zmemory: { command: "zmemory mcp" } }
102
+ });
103
+ }
104
+
105
+ export async function setupCommand() {
106
+ intro("ZMemory Setup");
107
+
108
+ const selected = await multiselect({
109
+ message: "Select platforms to configure",
110
+ options: [
111
+ { value: "opencode", label: "OpenCode" },
112
+ { value: "codex", label: "Codex CLI" },
113
+ { value: "claude", label: "Claude Code" },
114
+ { value: "agy", label: "Antigravity CLI (agy)" },
115
+ { value: "kilo", label: "Kilo Code" },
116
+ { value: "cursor", label: "Cursor" },
117
+ { value: "kimi", label: "Kimi Code" }
118
+ ]
119
+ });
120
+
121
+ if (!selected || !selected.length) {
122
+ outro("No platforms selected.");
123
+ return;
124
+ }
125
+
126
+ if (selected.includes("claude")) installClaude();
127
+ if (selected.includes("codex")) installCodex();
128
+ if (selected.includes("cursor")) installCursor();
129
+ if (selected.includes("opencode")) installOpenCode();
130
+ if (selected.includes("kilo")) installKilo();
131
+ if (selected.includes("agy")) installAGY();
132
+ if (selected.includes("kimi")) installKimi();
133
+
134
+ const initRepo = await confirm({ message: "Initialize .zmemory in this repo?" });
135
+ if (initRepo) {
136
+ const { initProject } = await import("./init.js");
137
+ await initProject();
138
+ }
139
+
140
+ // Run diagnostics automatically after setup
141
+ try {
142
+ const { doctor } = await import("./doctor.js");
143
+ console.log("\nRunning diagnostics...\n");
144
+ await doctor();
145
+ } catch {}
146
+
147
+ outro("ZMemory setup complete.");
148
+ }
@@ -0,0 +1,103 @@
1
+ import fs from "fs";
2
+
3
+ function baseInstructions() {
4
+ return `ZMemory Integration Protocol
5
+
6
+ Goal:
7
+ Maintain shared operational memory between coding agents and sessions.
8
+
9
+ Standard workflow:
10
+
11
+ 1. Read project status
12
+ zmemory status
13
+
14
+ 2. Fetch role context
15
+ zmemory context --role <role> --run <run_id>
16
+
17
+ 3. Execute task
18
+
19
+ 4. Record event
20
+ zmemory event --run <run_id> --role <role> --type <type> --summary "<summary>"
21
+
22
+ 5. Update state if needed
23
+ zmemory state update --run <run_id> --next-action "<next step>"
24
+
25
+ 6. Before stopping
26
+ zmemory handoff --run <run_id>
27
+ `;
28
+ }
29
+
30
+ function codexSkill() {
31
+ return `# ZMemory Skill (Codex)
32
+
33
+ Codex agents should treat ZMemory as the shared run-state layer.
34
+
35
+ ${baseInstructions()}
36
+
37
+ Codex guidelines:
38
+
39
+ - Always read context before modifying code
40
+ - Record file changes with zmemory event
41
+ - Avoid repeating failures recorded in events
42
+ - Generate handoff before ending session
43
+ `;
44
+ }
45
+
46
+ function claudeSkill() {
47
+ return `# ZMemory Skill (Claude Code)
48
+
49
+ Claude Code should use ZMemory to coordinate tasks between sessions.
50
+
51
+ ${baseInstructions()}
52
+
53
+ Claude Code guidelines:
54
+
55
+ - Read executor context before implementing
56
+ - Write reviewer verdicts using events
57
+ - Escalate repeated failures to brain role
58
+ `;
59
+ }
60
+
61
+ function opencodeSkill() {
62
+ return `# ZMemory Skill (OpenCode)
63
+
64
+ OpenCode agents integrate with ZMemory for shared memory and handoffs.
65
+
66
+ ${baseInstructions()}
67
+
68
+ OpenCode guidelines:
69
+
70
+ - Use context packets to reduce prompt size
71
+ - Log important actions as events
72
+ - Generate handoff packets when switching models
73
+ `;
74
+ }
75
+
76
+ export async function generateSkill(args) {
77
+ const i = args.indexOf("--target");
78
+ const target = i !== -1 ? args[i + 1] : "generic";
79
+
80
+ let content;
81
+
82
+ switch (target) {
83
+ case "codex":
84
+ content = codexSkill();
85
+ break;
86
+ case "claude-code":
87
+ content = claudeSkill();
88
+ break;
89
+ case "opencode":
90
+ content = opencodeSkill();
91
+ break;
92
+ default:
93
+ content = `# ZMemory Skill (Generic)
94
+
95
+ ${baseInstructions()}`;
96
+ }
97
+
98
+ const path = `.zmemory/skills/zmemory-${target}.md`;
99
+ fs.mkdirSync(`.zmemory/skills`, { recursive: true });
100
+ fs.writeFileSync(path, content);
101
+
102
+ console.log(`skill generated: ${path}`);
103
+ }
@@ -0,0 +1,82 @@
1
+ import { ensureDir, writeFile, writeJSON, now } from "../lib/fs.js";
2
+ import path from "path";
3
+ import fs from "fs";
4
+
5
+ const ACTIVE_ROOT = ".zmemory/runs/active";
6
+ const ARCHIVE_ROOT = ".zmemory/runs/archived";
7
+
8
+ function ensureRunRoots() {
9
+ ensureDir(ACTIVE_ROOT);
10
+ ensureDir(ARCHIVE_ROOT);
11
+ }
12
+
13
+ function todayPrefix() {
14
+ const d = new Date();
15
+ const y = d.getFullYear();
16
+ const m = d.getMonth() + 1;
17
+ const day = d.getDate();
18
+ return `run_${y}_${m}_${day}`;
19
+ }
20
+
21
+ function nextIndex(prefix) {
22
+ let max = 0;
23
+
24
+ for (const root of [ACTIVE_ROOT, ARCHIVE_ROOT]) {
25
+ if (!fs.existsSync(root)) continue;
26
+ for (const e of fs.readdirSync(root)) {
27
+ if (!e.startsWith(prefix + "_")) continue;
28
+ const parts = e.split("_");
29
+ const n = Number(parts[parts.length - 1]);
30
+ if (!Number.isNaN(n)) max = Math.max(max, n);
31
+ }
32
+ }
33
+
34
+ return max + 1;
35
+ }
36
+
37
+ function generateRunId() {
38
+ const prefix = todayPrefix();
39
+ const idx = nextIndex(prefix);
40
+ return `${prefix}_${String(idx).padStart(3, "0")}`;
41
+ }
42
+
43
+ export async function startRun(task) {
44
+ ensureRunRoots();
45
+
46
+ const id = generateRunId();
47
+ const base = path.join(ACTIVE_ROOT, id);
48
+
49
+ ensureDir(base);
50
+
51
+ writeFile(path.join(base, "task.md"), `# Task\n${task}\n`);
52
+ writeFile(path.join(base, "plan.md"), "# Plan\n");
53
+ writeFile(path.join(base, "events.jsonl"), "");
54
+
55
+ writeJSON(path.join(base, "state.json"), {
56
+ run_id: id,
57
+ project: path.basename(process.cwd()),
58
+ task,
59
+ status: "created",
60
+ current_step: 0,
61
+ completed: [],
62
+ blockers: [],
63
+ next_action: null,
64
+ attempts: { current_error_signature: null, repeated_count: 0 },
65
+ allowed_files: [],
66
+ forbidden_files: [],
67
+ updated_at: now()
68
+ });
69
+
70
+ // initial event
71
+ const evt = JSON.stringify({
72
+ ts: now(),
73
+ run_id: id,
74
+ role: "system",
75
+ type: "run_created",
76
+ summary: "Run created"
77
+ }) + "\n";
78
+
79
+ fs.appendFileSync(path.join(base, "events.jsonl"), evt);
80
+
81
+ console.log(id);
82
+ }