open-xmen 0.1.0 → 0.1.2

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 (89) hide show
  1. package/README.md +6 -2
  2. package/dist/agents/cerebro.d.ts +12 -0
  3. package/dist/agents/cerebro.js +112 -0
  4. package/dist/agents/index.d.ts +6 -0
  5. package/dist/agents/index.js +18 -0
  6. package/dist/agents/markdown.d.ts +6 -0
  7. package/dist/agents/markdown.js +27 -0
  8. package/dist/agents/team.d.ts +45 -0
  9. package/dist/agents/team.js +255 -0
  10. package/dist/agents/types.d.ts +30 -0
  11. package/dist/agents/types.js +12 -0
  12. package/dist/cli/config.d.ts +6 -0
  13. package/dist/cli/config.js +228 -0
  14. package/dist/cli/doctor.d.ts +8 -0
  15. package/dist/cli/doctor.js +48 -0
  16. package/dist/cli/runtime.d.ts +7 -0
  17. package/dist/cli/runtime.js +94 -0
  18. package/dist/cli.js +72 -430
  19. package/dist/commands/definitions.d.ts +11 -0
  20. package/dist/commands/definitions.js +123 -0
  21. package/dist/commands/index.d.ts +2 -0
  22. package/dist/commands/index.js +1 -0
  23. package/dist/config/models.d.ts +8 -0
  24. package/dist/config/models.js +8 -0
  25. package/dist/council/index.d.ts +2 -0
  26. package/dist/council/index.js +2 -0
  27. package/dist/index.js +41 -64
  28. package/dist/runtime/definitions.d.ts +6 -0
  29. package/dist/runtime/definitions.js +5 -0
  30. package/dist/runtime/generated-assets.d.ts +5 -0
  31. package/dist/runtime/generated-assets.js +2425 -0
  32. package/dist/runtime/index.d.ts +9 -0
  33. package/dist/runtime/index.js +12 -0
  34. package/dist/utils/session.d.ts +2 -0
  35. package/dist/utils/session.js +5 -0
  36. package/package.json +4 -14
  37. package/.cerebro/.gitignore +0 -27
  38. package/.cerebro/cerebro-identity.md +0 -76
  39. package/.cerebro/docs/agent-mapping.md +0 -54
  40. package/.cerebro/docs/cerebro-workflow.md +0 -115
  41. package/.cerebro/docs/orchestration.md +0 -28
  42. package/.cerebro/docs/overview.md +0 -44
  43. package/.cerebro/docs/skill-policy.md +0 -25
  44. package/.cerebro/integrations/semble.md +0 -30
  45. package/.cerebro/opencode/model-routing.md +0 -37
  46. package/.cerebro/schemas/boulder.schema.json +0 -143
  47. package/.cerebro/schemas/team-run.schema.json +0 -234
  48. package/.cerebro/schemas/upgrade-manifest.schema.json +0 -45
  49. package/.cerebro/schemas/upgrade-state.schema.json +0 -38
  50. package/.cerebro/scripts/check-agent-teams-enabled.py +0 -24
  51. package/.cerebro/scripts/ensure-upgrade-cache-gitignored.py +0 -27
  52. package/.cerebro/scripts/fetch-upstream-ref.py +0 -67
  53. package/.cerebro/scripts/reset-runtime.py +0 -125
  54. package/.cerebro/scripts/setup-status.py +0 -101
  55. package/.cerebro/scripts/test-stop-hook.py +0 -60
  56. package/.cerebro/scripts/upgrade-latest-tag.py +0 -34
  57. package/.cerebro/scripts/validate-agent-frontmatter.py +0 -87
  58. package/.cerebro/scripts/validate-boulder.py +0 -105
  59. package/.cerebro/scripts/validate-opencode-runtime.py +0 -94
  60. package/.cerebro/scripts/validate-team-runs.py +0 -310
  61. package/.cerebro/scripts/validate-upgrade-metadata.py +0 -104
  62. package/.cerebro/scripts/write-upgrade-state.py +0 -93
  63. package/.cerebro/templates/customer-vision.md +0 -58
  64. package/.cerebro/templates/plan.md +0 -35
  65. package/.cerebro/templates/product-brief.md +0 -110
  66. package/.cerebro/templates/project-context.md +0 -64
  67. package/.cerebro/templates/requirements-brief.md +0 -67
  68. package/.cerebro/templates/team-run.json +0 -22
  69. package/.cerebro/upgrade-manifest.json +0 -160
  70. package/.opencode/.gitignore +0 -5
  71. package/.opencode/agents/beast.md +0 -38
  72. package/.opencode/agents/cerebro.md +0 -22
  73. package/.opencode/agents/cyclops.md +0 -22
  74. package/.opencode/agents/cypher.md +0 -46
  75. package/.opencode/agents/emma-frost.md +0 -38
  76. package/.opencode/agents/forge.md +0 -22
  77. package/.opencode/agents/legion.md +0 -45
  78. package/.opencode/agents/nightcrawler.md +0 -22
  79. package/.opencode/agents/professor-x.md +0 -39
  80. package/.opencode/agents/sage.md +0 -22
  81. package/.opencode/agents/storm.md +0 -49
  82. package/.opencode/agents/wolverine.md +0 -22
  83. package/.opencode/commands/cerebro-doctor.md +0 -19
  84. package/.opencode/commands/cerebro-index.md +0 -22
  85. package/.opencode/commands/cerebro-plan.md +0 -21
  86. package/.opencode/commands/cerebro-reset.md +0 -20
  87. package/.opencode/commands/cerebro-start-work.md +0 -20
  88. package/.opencode/commands/cerebro-upgrade.md +0 -19
  89. package/.opencode/commands/to-me-my-x-men.md +0 -27
@@ -0,0 +1,228 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ const PACKAGE_NAME = "open-xmen";
5
+ const OPENCODE_INSTRUCTIONS = ["AGENTS.md", ".cerebro/cerebro-identity.md", ".cerebro/opencode/model-routing.md"];
6
+ export function updateOpencodeConfig(target, opts) {
7
+ const destination = path.join(target, "opencode.jsonc");
8
+ const parsed = existsSync(destination) ? parseJsonc(readFileSync(destination, "utf8")) : {};
9
+ const config = isRecord(parsed) ? parsed : {};
10
+ config.$schema ||= "https://opencode.ai/config.json";
11
+ config.plugin = replaceOpenXmenPluginEntries(asArray(config.plugin), getPluginEntry());
12
+ config.instructions = appendUnique(asArray(config.instructions), ...OPENCODE_INSTRUCTIONS);
13
+ config.share ??= "disabled";
14
+ config.permission ??= { edit: "ask", bash: "ask", webfetch: "ask" };
15
+ const content = `${JSON.stringify(config, null, 2)}\n`;
16
+ if (opts.dryRun) {
17
+ if (existsSync(destination))
18
+ opts.planned.push(`atomically update ${destination} (backup ${destination}.bak)`);
19
+ else
20
+ opts.planned.push(`create ${destination}`);
21
+ return;
22
+ }
23
+ writeAtomicConfig(destination, content);
24
+ }
25
+ export function warmOpenCodePluginCache(packageRoot) {
26
+ if (!isPackageManagerInstall(packageRoot)) {
27
+ console.log("Local development install - cache warm-up not required");
28
+ return;
29
+ }
30
+ const home = process.env.HOME;
31
+ if (!home)
32
+ return;
33
+ let version = "latest";
34
+ const packageJson = readJsonFile(path.join(packageRoot, "package.json"));
35
+ if (isRecord(packageJson) && typeof packageJson.version === "string")
36
+ version = packageJson.version;
37
+ const cacheRoot = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
38
+ const cacheDir = path.join(cacheRoot, "opencode", "packages", `${PACKAGE_NAME}@${version}`);
39
+ mkdirSync(cacheDir, { recursive: true });
40
+ writeFileSync(path.join(cacheDir, "package.json"), `${JSON.stringify({ name: `${PACKAGE_NAME}-cache`, private: true, dependencies: { [PACKAGE_NAME]: version } }, null, 2)}\n`, "utf8");
41
+ const bun = spawnSync("bun", ["install", "--ignore-scripts"], { cwd: cacheDir, stdio: "inherit" });
42
+ if (bun.status !== 0)
43
+ console.warn("open-xmen install: skipped OpenCode plugin cache warm-up; bun install failed");
44
+ else
45
+ console.log(`OpenCode plugin cache warmed: ${cacheDir}`);
46
+ }
47
+ export function opencodeConfigHasOpenXmenPlugin(cwd = process.cwd()) {
48
+ const configPath = path.join(cwd, "opencode.jsonc");
49
+ if (!existsSync(configPath))
50
+ return false;
51
+ try {
52
+ const config = parseJsonc(readFileSync(configPath, "utf8"));
53
+ return asArray(config.plugin).some((entry) => {
54
+ const spec = Array.isArray(entry) ? entry[0] : entry;
55
+ return typeof spec === "string" && isOpenXmenPluginEntry(spec);
56
+ });
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ function writeAtomicConfig(destination, content) {
63
+ mkdirSync(path.dirname(destination), { recursive: true });
64
+ if (existsSync(destination)) {
65
+ const current = readFileSync(destination, "utf8");
66
+ if (current === content)
67
+ return;
68
+ copyFileSync(destination, `${destination}.bak`);
69
+ }
70
+ const temp = `${destination}.tmp`;
71
+ writeFileSync(temp, content, "utf8");
72
+ renameSync(temp, destination);
73
+ }
74
+ function getPluginEntry() {
75
+ const cliEntryPath = process.argv[1];
76
+ if (!cliEntryPath)
77
+ return PACKAGE_NAME;
78
+ const packageRoot = findPackageRoot(cliEntryPath);
79
+ if (!packageRoot || isPackageManagerInstall(packageRoot))
80
+ return PACKAGE_NAME;
81
+ return packageRoot;
82
+ }
83
+ function replaceOpenXmenPluginEntries(items, pluginEntry) {
84
+ return [...items.filter((entry) => {
85
+ const spec = Array.isArray(entry) ? entry[0] : entry;
86
+ return !(typeof spec === "string" && isOpenXmenPluginEntry(spec));
87
+ }), pluginEntry];
88
+ }
89
+ function isOpenXmenPluginEntry(entry) {
90
+ return entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`) || entry.endsWith(".opencode/plugins/open-xmen.ts") || isLocalPackageRootEntry(entry);
91
+ }
92
+ function findPackageRoot(startPath) {
93
+ let current = path.dirname(startPath);
94
+ while (true) {
95
+ const packageJsonPath = path.join(current, "package.json");
96
+ if (existsSync(packageJsonPath)) {
97
+ const packageJson = readJsonFile(packageJsonPath);
98
+ if (isRecord(packageJson) && packageJson.name === PACKAGE_NAME)
99
+ return current;
100
+ }
101
+ const parent = path.dirname(current);
102
+ if (parent === current)
103
+ return null;
104
+ current = parent;
105
+ }
106
+ }
107
+ function isLocalPackageRootEntry(entry) {
108
+ if (!entry || entry.startsWith("file://"))
109
+ return false;
110
+ const packageJsonPath = path.join(entry, "package.json");
111
+ if (!existsSync(packageJsonPath))
112
+ return false;
113
+ const packageJson = readJsonFile(packageJsonPath);
114
+ return isRecord(packageJson) && packageJson.name === PACKAGE_NAME;
115
+ }
116
+ function isPackageManagerInstall(packageRoot) {
117
+ return packageRoot.replaceAll("\\", "/").includes(`/node_modules/${PACKAGE_NAME}`);
118
+ }
119
+ function readJsonFile(file) {
120
+ if (!existsSync(file))
121
+ return undefined;
122
+ try {
123
+ return JSON.parse(readFileSync(file, "utf8"));
124
+ }
125
+ catch (error) {
126
+ if (error instanceof SyntaxError)
127
+ return undefined;
128
+ throw error;
129
+ }
130
+ }
131
+ function isRecord(value) {
132
+ return typeof value === "object" && value !== null && !Array.isArray(value);
133
+ }
134
+ function parseJsonc(text) {
135
+ const stripped = removeTrailingCommas(stripJsonComments(text));
136
+ return stripped.trim() ? JSON.parse(stripped) : {};
137
+ }
138
+ function stripJsonComments(text) {
139
+ let out = "";
140
+ let inString = false;
141
+ let escape = false;
142
+ for (let i = 0; i < text.length; i++) {
143
+ const ch = text[i];
144
+ const next = text[i + 1];
145
+ if (inString) {
146
+ out += ch;
147
+ if (escape)
148
+ escape = false;
149
+ else if (ch === "\\")
150
+ escape = true;
151
+ else if (ch === '"')
152
+ inString = false;
153
+ continue;
154
+ }
155
+ if (ch === '"') {
156
+ inString = true;
157
+ out += ch;
158
+ continue;
159
+ }
160
+ if (ch === "/" && next === "/") {
161
+ while (i < text.length && text[i] !== "\n")
162
+ i++;
163
+ out += "\n";
164
+ continue;
165
+ }
166
+ if (ch === "/" && next === "*") {
167
+ i += 2;
168
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
169
+ i++;
170
+ i++;
171
+ continue;
172
+ }
173
+ out += ch;
174
+ }
175
+ return out;
176
+ }
177
+ function removeTrailingCommas(text) {
178
+ let out = "";
179
+ let inString = false;
180
+ let escape = false;
181
+ for (let i = 0; i < text.length; i++) {
182
+ const ch = text[i];
183
+ if (inString) {
184
+ out += ch;
185
+ if (escape)
186
+ escape = false;
187
+ else if (ch === "\\")
188
+ escape = true;
189
+ else if (ch === '"')
190
+ inString = false;
191
+ continue;
192
+ }
193
+ if (ch === '"') {
194
+ inString = true;
195
+ out += ch;
196
+ continue;
197
+ }
198
+ if (ch === ",") {
199
+ let j = i + 1;
200
+ while (/\s/.test(text[j] || ""))
201
+ j++;
202
+ if (text[j] === "}" || text[j] === "]")
203
+ continue;
204
+ }
205
+ out += ch;
206
+ }
207
+ return out;
208
+ }
209
+ function appendUnique(items, ...values) {
210
+ const result = [...items];
211
+ for (const value of values) {
212
+ if (!result.includes(value))
213
+ result.push(value);
214
+ }
215
+ return result;
216
+ }
217
+ function asArray(value) {
218
+ return Array.isArray(value) ? value.filter(isJsonValue) : [];
219
+ }
220
+ function isJsonValue(value) {
221
+ if (value === null)
222
+ return true;
223
+ if (["boolean", "number", "string"].includes(typeof value))
224
+ return true;
225
+ if (Array.isArray(value))
226
+ return value.every(isJsonValue);
227
+ return isRecord(value) && Object.values(value).every(isJsonValue);
228
+ }
@@ -0,0 +1,8 @@
1
+ export type DoctorResult = {
2
+ ok: boolean;
3
+ cwd: string;
4
+ errors: string[];
5
+ agents: number;
6
+ commands: number;
7
+ };
8
+ export declare function runOpenCodeDoctor(cwd: string): DoctorResult;
@@ -0,0 +1,48 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import { CEREBRO_AGENTS, CEREBRO_COMMANDS } from "../runtime/index.js";
5
+ import { opencodeConfigHasOpenXmenPlugin } from "./config.js";
6
+ const REQUIRED_AGENTS = [...CEREBRO_AGENTS];
7
+ const REQUIRED_COMMANDS = CEREBRO_COMMANDS.map((command) => command.slice(1));
8
+ export function runOpenCodeDoctor(cwd) {
9
+ const errors = [];
10
+ const exists = (file) => existsSync(path.join(cwd, file));
11
+ const read = (file) => readFileSync(path.join(cwd, file), "utf8");
12
+ for (const file of ["opencode.jsonc", "AGENTS.md", ".cerebro/cerebro-identity.md"]) {
13
+ if (!exists(file))
14
+ errors.push(`missing ${file}`);
15
+ }
16
+ if (!opencodeConfigHasOpenXmenPlugin(cwd)) {
17
+ errors.push("opencode.jsonc does not include the open-xmen plugin entry");
18
+ }
19
+ for (const name of REQUIRED_AGENTS) {
20
+ const file = `.opencode/agents/${name}.md`;
21
+ if (!exists(file))
22
+ errors.push(`missing ${file}`);
23
+ else if (!read(file).startsWith("---\n"))
24
+ errors.push(`${file} missing frontmatter`);
25
+ }
26
+ for (const name of REQUIRED_COMMANDS) {
27
+ const file = `.opencode/commands/${name}.md`;
28
+ if (!exists(file))
29
+ errors.push(`missing ${file}`);
30
+ else if (!read(file).includes("agent: cerebro"))
31
+ errors.push(`${file} should run with agent: cerebro`);
32
+ }
33
+ for (const dir of [".cerebro/plans", ".cerebro/notepads", ".cerebro/team-runs"]) {
34
+ if (!exists(dir))
35
+ errors.push(`missing ${dir}`);
36
+ }
37
+ const opencode = spawnSync("opencode", ["--version"], { encoding: "utf8" });
38
+ if (opencode.status !== 0)
39
+ errors.push("opencode executable not found or not working");
40
+ const agents = countMarkdownFiles(path.join(cwd, ".opencode/agents"));
41
+ const commands = countMarkdownFiles(path.join(cwd, ".opencode/commands"));
42
+ return { ok: errors.length === 0, cwd, errors, agents, commands };
43
+ }
44
+ function countMarkdownFiles(directory) {
45
+ if (!existsSync(directory))
46
+ return 0;
47
+ return readdirSync(directory).filter((file) => file.endsWith(".md")).length;
48
+ }
@@ -0,0 +1,7 @@
1
+ export type ManagedWriteOptions = {
2
+ dryRun: boolean;
3
+ overwrite: boolean;
4
+ planned: string[];
5
+ };
6
+ export declare function installManagedRuntime(target: string, opts: ManagedWriteOptions): void;
7
+ export declare function installManagedAgentInstructions(target: string, opts: ManagedWriteOptions): void;
@@ -0,0 +1,94 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { runtimeAssetMap, runtimeAssetsByPrefix } from "../runtime/index.js";
4
+ const OPEN_XMEN_AGENT_BLOCK_START = "<!-- OPEN-XMEN:START -->";
5
+ const OPEN_XMEN_AGENT_BLOCK_END = "<!-- OPEN-XMEN:END -->";
6
+ const RUNTIME_ASSET_MAP = runtimeAssetMap();
7
+ export function installManagedRuntime(target, opts) {
8
+ for (const asset of runtimeAssetsByPrefix(".opencode/").concat(runtimeAssetsByPrefix(".cerebro/"))) {
9
+ writeManagedFile(path.join(target, asset.path), asset.content, opts);
10
+ }
11
+ for (const dir of ["plans", "notepads", "team-runs", "pending-todos"]) {
12
+ const targetDir = path.join(target, ".cerebro", dir);
13
+ if (opts.dryRun)
14
+ opts.planned.push(`ensure directory ${targetDir}`);
15
+ else
16
+ mkdirSync(targetDir, { recursive: true });
17
+ }
18
+ appendGitignoreEntry(target, ".cerebro/", opts);
19
+ }
20
+ export function installManagedAgentInstructions(target, opts) {
21
+ const source = requiredRuntimeAsset("AGENTS.md").trim();
22
+ const block = `${OPEN_XMEN_AGENT_BLOCK_START}\n${source}\n${OPEN_XMEN_AGENT_BLOCK_END}`;
23
+ const destination = path.join(target, "AGENTS.md");
24
+ if (!existsSync(destination)) {
25
+ if (opts.dryRun)
26
+ opts.planned.push(`create ${destination}`);
27
+ else
28
+ writeFileSync(destination, `${block}\n`, "utf8");
29
+ return;
30
+ }
31
+ const current = readFileSync(destination, "utf8");
32
+ const start = current.indexOf(OPEN_XMEN_AGENT_BLOCK_START);
33
+ const end = current.indexOf(OPEN_XMEN_AGENT_BLOCK_END);
34
+ if (start !== -1 && end !== -1 && end > start) {
35
+ if (!opts.overwrite) {
36
+ opts.planned.push(`skip existing Open X-Men AGENTS.md block ${destination}`);
37
+ return;
38
+ }
39
+ const next = `${current.slice(0, start)}${block}${current.slice(end + OPEN_XMEN_AGENT_BLOCK_END.length)}`;
40
+ if (next === current || `${next}\n` === current) {
41
+ opts.planned.push(`unchanged ${destination}`);
42
+ return;
43
+ }
44
+ if (opts.dryRun)
45
+ opts.planned.push(`refresh Open X-Men AGENTS.md block ${destination}`);
46
+ else
47
+ writeFileSync(destination, next.endsWith("\n") ? next : `${next}\n`, "utf8");
48
+ return;
49
+ }
50
+ if (opts.dryRun)
51
+ opts.planned.push(`append Open X-Men AGENTS.md block ${destination}`);
52
+ else
53
+ writeFileSync(destination, `${current.trimEnd()}\n\n${block}\n`, "utf8");
54
+ }
55
+ function appendGitignoreEntry(target, entry, opts) {
56
+ const destination = path.join(target, ".gitignore");
57
+ const normalized = entry.endsWith("\n") ? entry : `${entry}\n`;
58
+ if (existsSync(destination)) {
59
+ const current = readFileSync(destination, "utf8");
60
+ const lines = current.split("\n");
61
+ if (lines.some((line) => line.trim() === entry.trim()))
62
+ return;
63
+ if (opts.dryRun) {
64
+ opts.planned.push(`append ${entry.trim()} to ${destination}`);
65
+ return;
66
+ }
67
+ writeFileSync(destination, `${current.trimEnd()}\n${normalized}`, "utf8");
68
+ }
69
+ else {
70
+ if (opts.dryRun) {
71
+ opts.planned.push(`create ${destination} with ${entry.trim()}`);
72
+ return;
73
+ }
74
+ writeFileSync(destination, normalized, "utf8");
75
+ }
76
+ }
77
+ function requiredRuntimeAsset(assetPath) {
78
+ const content = RUNTIME_ASSET_MAP.get(assetPath);
79
+ if (typeof content !== "string")
80
+ throw new Error(`Missing generated runtime asset: ${assetPath}`);
81
+ return content;
82
+ }
83
+ function writeManagedFile(destination, content, opts) {
84
+ if (existsSync(destination) && !opts.overwrite) {
85
+ opts.planned.push(`skip existing ${destination}`);
86
+ return;
87
+ }
88
+ if (opts.dryRun) {
89
+ opts.planned.push(`${existsSync(destination) ? "overwrite" : "write"} ${destination}`);
90
+ return;
91
+ }
92
+ mkdirSync(path.dirname(destination), { recursive: true });
93
+ writeFileSync(destination, content, "utf8");
94
+ }