opencode-manifold 0.2.0 → 0.4.1

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/README.md CHANGED
@@ -43,7 +43,8 @@ Open Manifold requires:
43
43
  }
44
44
  ```
45
45
 
46
- 2. Run opencode. The plugin auto-installs and generates all required files on first session.
46
+ 2. Run opencode. The plugin auto-installs and generates the global template source.
47
+ 3. Run `/manifold-init` in the TUI to set up the project.
47
48
 
48
49
  ### Option B — Local plugin files
49
50
 
@@ -61,15 +62,17 @@ Open Manifold requires:
61
62
  "plugin": ["/path/to/.opencode/plugins/opencode-manifold"]
62
63
  }
63
64
  ```
64
- 4. Run opencode. The plugin generates all required files on first session.
65
+ 4. Run opencode. The plugin generates the global template source.
66
+ 5. Run `/manifold-init` in the TUI to set up the project.
65
67
 
66
68
  ---
67
69
 
68
70
  ## Quick Start
69
71
 
70
72
  1. **Install** using one of the methods above
71
- 2. **Create a plan** any format (markdown, TODO list, email, meeting notes)
72
- 3. **Point the Lead Dev agent** at your plan file and tell it to execute
73
+ 2. **Run `/manifold-init`** in the opencode TUI to set up agents, skills, and the Manifold directory
74
+ 3. **Create a plan** any format (markdown, TODO list, email, meeting notes)
75
+ 4. **Point the Lead Dev agent** at your plan file and tell it to execute
73
76
 
74
77
  The Lead Dev will:
75
78
  - Extract tasks from your plan
@@ -126,6 +129,76 @@ The sr/jr/debug loop enables cost-efficient workflows: a strong sr-dev paired wi
126
129
 
127
130
  ---
128
131
 
132
+ ## Customizing Agents
133
+
134
+ Agents are markdown files with [opencode frontmatter](https://opencode.ai/docs/agents/) for configuration (description, mode, permissions, model) and a body that becomes the agent's system prompt.
135
+
136
+ Manifold uses a three-tier template system:
137
+
138
+ ```
139
+ Bundled (inside npm package)
140
+ └── Never edit directly — overwritten on updates
141
+
142
+ ▼ Plugin load (one-time, only copies missing files)
143
+ ~/.config/opencode/manifold/ ← Global templates
144
+ ├── agents/ (clerk.md, senior-dev.md, ...)
145
+ ├── skills/ (manifold-workflow/, clerk-orchestration/, ...)
146
+ ├── manifold/ (settings.json, schema.md, ...)
147
+ └── config/ (opencode.json)
148
+
149
+ ▼ /manifold-init (only copies missing files)
150
+ Project root
151
+ ├── .opencode/agents/ ← Per-project (editable)
152
+ ├── .opencode/skills/
153
+ ├── Manifold/
154
+ └── opencode.json
155
+ ```
156
+
157
+ ### Per-project customization
158
+
159
+ Edit files directly in `.opencode/agents/` or `.opencode/skills/`. These are never overwritten by `/manifold-init` — only files that don't exist yet are copied.
160
+
161
+ ### Global customization
162
+
163
+ Edit files in `~/.config/opencode/manifold/`. All new projects initialized with `/manifold-init` will inherit your customized versions.
164
+
165
+ ### Resetting
166
+
167
+ To reset a specific agent to its default, delete just that file and run `/manifold-init` again:
168
+
169
+ ```bash
170
+ rm .opencode/agents/senior-dev.md
171
+ # Then run /manifold-init
172
+ ```
173
+
174
+ To reset all Manifold files, delete the directories and re-run:
175
+
176
+ ```bash
177
+ rm -rf .opencode/agents/ .opencode/skills/ Manifold/
178
+ # Then run /manifold-init
179
+ ```
180
+
181
+ ### Assigning models to agents
182
+
183
+ Add a `model` field to the agent's frontmatter or your `opencode.json`:
184
+
185
+ ```json
186
+ {
187
+ "agent": {
188
+ "senior-dev": {
189
+ "model": "anthropic/claude-sonnet-4-20250514"
190
+ },
191
+ "junior-dev": {
192
+ "model": "anthropic/claude-haiku-4-20250514"
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ See the [Example Configurations](#example-configurations) section for model pairing suggestions.
199
+
200
+ ---
201
+
129
202
  ## Example Configurations
130
203
 
131
204
  | Budget | Senior Dev | Junior Dev | Debug |
@@ -190,10 +263,10 @@ No mid-loop state recovery needed — restart the task, the Clerk's research pha
190
263
  ## FAQ
191
264
 
192
265
  **Q: Do I need to configure agents?**
193
- A: No. On first run, the plugin auto-generates agent definitions, skills, and configuration from built-in templates.
266
+ A: No. Run `/manifold-init` once per project and it sets up agent definitions, skills, and configuration from templates.
194
267
 
195
268
  **Q: Can I customize agents and skills?**
196
- A: Yes. Edit the files in `.opencode/agents/` and `.opencode/skills/` after they're generated.
269
+ A: Yes. See the [Customizing Agents](#customizing-agents) section for the full three-tier template system.
197
270
 
198
271
  **Q: What is the Clerk's role?**
199
272
  A: The Clerk has full project sight. It researches context (codebase-index, wiki, graph), composes scoped prompts for workers, and maintains the wiki.
@@ -215,6 +288,26 @@ A: The system is designed around the Clerk's ability to research context via sem
215
288
 
216
289
  ---
217
290
 
291
+ ## Uninstall
292
+
293
+ To remove Open Manifold from a project:
294
+
295
+ ```bash
296
+ rm -rf .opencode/agents/ .opencode/skills/ Manifold/
297
+ ```
298
+
299
+ Then remove the manifold-specific keys from `opencode.json` (`agent.manifold`, `permission.clerk`, `permission.senior-dev`, `permission.junior-dev`, `permission.debug`).
300
+
301
+ To remove global templates:
302
+
303
+ ```bash
304
+ rm -rf ~/.config/opencode/manifold/ ~/.config/opencode/commands/manifold-init.md
305
+ ```
306
+
307
+ To fully uninstall the plugin, also remove `"opencode-manifold"` from the `plugin` array in your global or project `opencode.json`.
308
+
309
+ ---
310
+
218
311
  ## License
219
312
 
220
313
  GPL 3+. See LICENSE file.
package/dist/index.js CHANGED
@@ -1,198 +1,149 @@
1
- // src/setup.ts
2
- import { readFile, writeFile, mkdir, readdir, stat } from "fs/promises";
1
+ // src/init.ts
2
+ import { readFile, writeFile, mkdir, readdir } from "fs/promises";
3
3
  import { existsSync } from "fs";
4
4
  import { join, dirname } from "path";
5
- var __dirname = "/Users/don/gitthings/Open_Manifold_System/src";
6
- async function fileExists(path) {
5
+ import { fileURLToPath } from "url";
6
+ import { homedir } from "os";
7
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
8
+ var bundledTemplatesDir = join(__dirname2, "..", "src", "templates");
9
+ var globalTemplatesDir = join(homedir(), ".config", "opencode", "manifold");
10
+ async function dirHasContent(dirPath) {
11
+ if (!existsSync(dirPath))
12
+ return false;
7
13
  try {
8
- await stat(path);
9
- return true;
14
+ const entries = await readdir(dirPath);
15
+ return entries.length > 0;
10
16
  } catch {
11
17
  return false;
12
18
  }
13
19
  }
14
- async function copyFile(src, dest) {
15
- const destDir = dirname(dest);
16
- if (!existsSync(destDir)) {
17
- await mkdir(destDir, { recursive: true });
18
- }
19
- const content = await readFile(src);
20
- await writeFile(dest, content);
21
- }
22
- async function copyDirectory(src, dest) {
23
- if (!existsSync(dest)) {
24
- await mkdir(dest, { recursive: true });
25
- }
20
+ async function copyMissingFiles(src, dest) {
21
+ if (!existsSync(src))
22
+ return [];
23
+ await mkdir(dest, { recursive: true });
24
+ const copied = [];
26
25
  const entries = await readdir(src, { withFileTypes: true });
27
26
  for (const entry of entries) {
28
27
  const srcPath = join(src, entry.name);
29
28
  const destPath = join(dest, entry.name);
30
29
  if (entry.isDirectory()) {
31
- await copyDirectory(srcPath, destPath);
32
- } else {
33
- await copyFile(srcPath, destPath);
30
+ const subCopied = await copyMissingFiles(srcPath, destPath);
31
+ if (subCopied.length > 0) {
32
+ copied.push(entry.name);
33
+ }
34
+ } else if (!existsSync(destPath)) {
35
+ await writeFile(destPath, await readFile(srcPath));
36
+ copied.push(entry.name);
34
37
  }
35
38
  }
39
+ return copied;
36
40
  }
37
- async function setupProject(directory, client) {
38
- await client.app.log({
41
+ async function mergeOpencodeConfig(projectDir) {
42
+ const configPath = join(projectDir, "opencode.json");
43
+ const templateConfigPath = join(globalTemplatesDir, "config", "opencode.json");
44
+ if (!existsSync(templateConfigPath))
45
+ return;
46
+ const templateConfig = JSON.parse(await readFile(templateConfigPath, "utf-8"));
47
+ if (!existsSync(configPath)) {
48
+ await writeFile(configPath, JSON.stringify(templateConfig, null, 2));
49
+ return;
50
+ }
51
+ const existing = JSON.parse(await readFile(configPath, "utf-8"));
52
+ let changed = false;
53
+ if (templateConfig.agent?.manifold && !existing.agent?.manifold) {
54
+ existing.agent = existing.agent || {};
55
+ existing.agent.manifold = templateConfig.agent.manifold;
56
+ changed = true;
57
+ }
58
+ if (templateConfig.permission) {
59
+ for (const [key, value] of Object.entries(templateConfig.permission)) {
60
+ if (!existing.permission?.[key]) {
61
+ existing.permission = existing.permission || {};
62
+ existing.permission[key] = value;
63
+ changed = true;
64
+ }
65
+ }
66
+ }
67
+ if (changed) {
68
+ await writeFile(configPath, JSON.stringify(existing, null, 2));
69
+ }
70
+ }
71
+ async function ensureGlobalTemplates(ctx) {
72
+ await ctx.client.app.log({
39
73
  body: {
40
74
  service: "opencode-manifold",
41
75
  level: "info",
42
- message: `Checking Open Manifold setup in ${directory}`
76
+ message: "Checking global templates..."
43
77
  }
44
78
  });
45
- const manifoldPath = join(directory, "Manifold");
46
- const opencodeAgentsPath = join(directory, ".opencode", "agents");
47
- const opencodeSkillsPath = join(directory, ".opencode", "skills");
48
- const opencodeJsonPath = join(directory, "opencode.json");
49
- const pluginSourceDir = join(directory, "..", "node_modules", "opencode-manifold", "src", "templates");
50
- const manifestPath = join(pluginSourceDir, "..", "..");
51
- const templateSourceDir = existsSync(manifestPath) ? manifestPath : join(__dirname, "..", "templates");
52
- const manifoldExists = await fileExists(manifoldPath);
53
- if (!manifoldExists) {
54
- await client.app.log({
79
+ if (!existsSync(bundledTemplatesDir)) {
80
+ await ctx.client.app.log({
55
81
  body: {
56
82
  service: "opencode-manifold",
57
- level: "info",
58
- message: "Manifold directory not found, setting up..."
59
- }
60
- });
61
- await mkdir(manifoldPath, { recursive: true });
62
- await mkdir(join(manifoldPath, ".obsidian"), { recursive: true });
63
- await mkdir(join(manifoldPath, "tasks"), { recursive: true });
64
- await mkdir(join(manifoldPath, "graph"), { recursive: true });
65
- const templateManifold = join(templateSourceDir, "manifold");
66
- if (existsSync(templateManifold)) {
67
- const files = [
68
- "settings.json",
69
- "plans.json",
70
- "state.json",
71
- "index.md",
72
- "log.md",
73
- "schema.md"
74
- ];
75
- for (const file of files) {
76
- const src = join(templateManifold, file);
77
- if (existsSync(src)) {
78
- await copyFile(src, join(manifoldPath, file));
79
- }
80
- }
81
- }
82
- await client.app.log({
83
- body: {
84
- service: "opencode-manifold",
85
- level: "info",
86
- message: "Manifold directory created"
83
+ level: "error",
84
+ message: `Bundled templates not found at ${bundledTemplatesDir}`
87
85
  }
88
86
  });
87
+ return;
89
88
  }
90
- const agentsExist = await fileExists(opencodeAgentsPath);
91
- if (!agentsExist) {
92
- await client.app.log({
93
- body: {
94
- service: "opencode-manifold",
95
- level: "info",
96
- message: "Setting up agent definitions..."
97
- }
98
- });
99
- await mkdir(opencodeAgentsPath, { recursive: true });
100
- const templateAgents = join(templateSourceDir, "agents");
101
- if (existsSync(templateAgents)) {
102
- const agents = [
103
- "manifold.md",
104
- "clerk.md",
105
- "senior-dev.md",
106
- "junior-dev.md",
107
- "debug.md"
108
- ];
109
- for (const agent of agents) {
110
- const src = join(templateAgents, agent);
111
- if (existsSync(src)) {
112
- await copyFile(src, join(opencodeAgentsPath, agent));
113
- }
114
- }
115
- }
116
- await client.app.log({
117
- body: {
118
- service: "opencode-manifold",
119
- level: "info",
120
- message: "Agent definitions created"
121
- }
122
- });
89
+ await copyMissingFiles(bundledTemplatesDir, globalTemplatesDir);
90
+ const globalCommandsDir = join(homedir(), ".config", "opencode", "commands");
91
+ const bundledCommandsDir = join(bundledTemplatesDir, "commands");
92
+ if (existsSync(bundledCommandsDir)) {
93
+ await copyMissingFiles(bundledCommandsDir, globalCommandsDir);
123
94
  }
124
- const skillsExist = await fileExists(opencodeSkillsPath);
125
- if (!skillsExist) {
126
- await client.app.log({
127
- body: {
128
- service: "opencode-manifold",
129
- level: "info",
130
- message: "Setting up skills..."
131
- }
132
- });
133
- await mkdir(opencodeSkillsPath, { recursive: true });
134
- const templateSkills = join(templateSourceDir, "skills");
135
- if (existsSync(templateSkills)) {
136
- const skills = [
137
- "clerk-orchestration",
138
- "manifold-workflow",
139
- "wiki-ingest",
140
- "wiki-query"
141
- ];
142
- for (const skill of skills) {
143
- const skillDir = join(templateSkills, skill);
144
- if (existsSync(skillDir)) {
145
- await copyDirectory(skillDir, join(opencodeSkillsPath, skill));
146
- }
147
- }
95
+ await ctx.client.app.log({
96
+ body: {
97
+ service: "opencode-manifold",
98
+ level: "info",
99
+ message: "Global templates ready"
148
100
  }
149
- await client.app.log({
150
- body: {
151
- service: "opencode-manifold",
152
- level: "info",
153
- message: "Skills created"
154
- }
155
- });
156
- }
157
- const opencodeJsonExists = await fileExists(opencodeJsonPath);
158
- if (!opencodeJsonExists) {
159
- await client.app.log({
160
- body: {
161
- service: "opencode-manifold",
162
- level: "info",
163
- message: "Creating opencode.json configuration..."
164
- }
165
- });
166
- const templateConfig = join(templateSourceDir, "config", "opencode.json");
167
- if (existsSync(templateConfig)) {
168
- await copyFile(templateConfig, opencodeJsonPath);
169
- } else {
170
- const defaultConfig = {
171
- $schema: "https://opencode.ai/config.json",
172
- plugin: ["opencode-manifold", "opencode-codebase-index"],
173
- agent: {
174
- manifold: {
175
- skill: ["manifold-workflow"]
176
- }
177
- }
178
- };
179
- await writeFile(opencodeJsonPath, JSON.stringify(defaultConfig, null, 2));
101
+ });
102
+ }
103
+ async function initProject(directory, client) {
104
+ const initialized = [];
105
+ await client.app.log({
106
+ body: {
107
+ service: "opencode-manifold",
108
+ level: "info",
109
+ message: `Running /manifold-init in ${directory}`
180
110
  }
111
+ });
112
+ if (!await dirHasContent(globalTemplatesDir)) {
181
113
  await client.app.log({
182
114
  body: {
183
115
  service: "opencode-manifold",
184
- level: "info",
185
- message: "opencode.json created"
116
+ level: "error",
117
+ message: `Global templates not found at ${globalTemplatesDir}. Plugin may not have loaded correctly.`
186
118
  }
187
119
  });
120
+ return initialized;
121
+ }
122
+ const agentsCopied = await copyMissingFiles(join(globalTemplatesDir, "agents"), join(directory, ".opencode", "agents"));
123
+ if (agentsCopied.length > 0) {
124
+ initialized.push(`agents (${agentsCopied.join(", ")})`);
125
+ }
126
+ const skillsCopied = await copyMissingFiles(join(globalTemplatesDir, "skills"), join(directory, ".opencode", "skills"));
127
+ if (skillsCopied.length > 0) {
128
+ initialized.push(`skills (${skillsCopied.join(", ")})`);
129
+ }
130
+ const manifoldCopied = await copyMissingFiles(join(globalTemplatesDir, "manifold"), join(directory, "Manifold"));
131
+ if (manifoldCopied.length > 0) {
132
+ initialized.push(`Manifold/ (${manifoldCopied.join(", ")})`);
133
+ }
134
+ const configPath = join(directory, "opencode.json");
135
+ if (!existsSync(configPath) || !JSON.parse(await readFile(configPath, "utf-8")).agent?.manifold) {
136
+ await mergeOpencodeConfig(directory);
137
+ initialized.push("opencode.json");
188
138
  }
189
139
  await client.app.log({
190
140
  body: {
191
141
  service: "opencode-manifold",
192
142
  level: "info",
193
- message: "Open Manifold initialized successfully"
143
+ message: `/manifold-init complete: ${initialized.join(", ") || "already initialized"}`
194
144
  }
195
145
  });
146
+ return initialized;
196
147
  }
197
148
 
198
149
  // src/tools/dispatch-task.ts
@@ -1341,7 +1292,7 @@ var dispatchTaskTool = tool({
1341
1292
  description: tool.schema.string().describe("One-line task description"),
1342
1293
  plan_file: tool.schema.string().describe("Path to the plan document")
1343
1294
  },
1344
- async execute(args) {
1295
+ async execute(args, context) {
1345
1296
  const { task_number, description, plan_file } = args;
1346
1297
  const client = getClient();
1347
1298
  await client.app.log({
@@ -1351,7 +1302,7 @@ var dispatchTaskTool = tool({
1351
1302
  message: `dispatchTask called: task ${task_number} - ${description}`
1352
1303
  }
1353
1304
  });
1354
- const directory = process.cwd();
1305
+ const directory = context.directory;
1355
1306
  const settings = await readSettings(directory);
1356
1307
  await updatePlansRegistry(directory, plan_file);
1357
1308
  await client.app.log({
@@ -1424,6 +1375,7 @@ ${testResult.result.output}`,
1424
1375
  // src/index.ts
1425
1376
  var ManifoldPlugin = async (ctx) => {
1426
1377
  setPluginContext(ctx.client);
1378
+ await ensureGlobalTemplates(ctx);
1427
1379
  await ctx.client.app.log({
1428
1380
  body: {
1429
1381
  service: "opencode-manifold",
@@ -1435,16 +1387,24 @@ var ManifoldPlugin = async (ctx) => {
1435
1387
  tool: {
1436
1388
  dispatchTask: dispatchTaskTool
1437
1389
  },
1438
- event: async ({ event }) => {
1439
- if (event.type === "session.created") {
1440
- await ctx.client.app.log({
1441
- body: {
1442
- service: "opencode-manifold",
1443
- level: "info",
1444
- message: "Session created, checking for Open Manifold setup"
1445
- }
1446
- });
1447
- await setupProject(ctx.directory, ctx.client);
1390
+ "command.execute.before": async (input, output) => {
1391
+ if (input.command === "manifold-init") {
1392
+ const initialized = await initProject(ctx.directory, ctx.client);
1393
+ if (initialized.length > 0) {
1394
+ output.parts = [
1395
+ {
1396
+ type: "text",
1397
+ text: `Manifold initialized: ${initialized.join(", ")}`
1398
+ }
1399
+ ];
1400
+ } else {
1401
+ output.parts = [
1402
+ {
1403
+ type: "text",
1404
+ text: "All Manifold files already present. To reset a specific component, delete the corresponding file(s) from `.opencode/agents/`, `.opencode/skills/`, or `Manifold/`, then run `/manifold-init` again."
1405
+ }
1406
+ ];
1407
+ }
1448
1408
  }
1449
1409
  }
1450
1410
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-manifold",
3
- "version": "0.2.0",
3
+ "version": "0.4.1",
4
4
  "description": "Multi-agent development system for opencode with persistent knowledge",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Initialize Open Manifold multi-agent system for this project
3
+ agent: build
4
+ ---
5
+ Initialize the Open Manifold multi-agent system for this project. Run this once per project to set up agents, skills, and the Manifold knowledge directory.
@@ -1,9 +1,5 @@
1
1
  {
2
2
  "$schema": "https://opencode.ai/config.json",
3
- "plugin": [
4
- "opencode-manifold",
5
- "opencode-codebase-index"
6
- ],
7
3
  "agent": {
8
4
  "manifold": {
9
5
  "skill": ["manifold-workflow"]
@@ -31,4 +27,4 @@
31
27
  }
32
28
  }
33
29
  }
34
- }
30
+ }