@femtomc/mu 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.
- package/README.md +45 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +10 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1132 -0
- package/dist/templates.d.ts +4 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +117 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @femtomc/mu
|
|
2
|
+
|
|
3
|
+
Node CLI (and programmatic wrapper) for the mu `.mu/` issue DAG + forum store.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
After publishing:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @femtomc/mu
|
|
11
|
+
# or: bun add -g @femtomc/mu
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
From this repo:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd mu
|
|
18
|
+
bun install
|
|
19
|
+
bun run build
|
|
20
|
+
packages/cli/dist/cli.js --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { run } from "@femtomc/mu";
|
|
27
|
+
|
|
28
|
+
const r = await run(["status", "--json"]);
|
|
29
|
+
if (r.exitCode !== 0) throw new Error(r.stdout);
|
|
30
|
+
console.log(r.stdout);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Tests / Typecheck
|
|
34
|
+
|
|
35
|
+
From the `mu/` repo root:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bun test packages/cli
|
|
39
|
+
bun run typecheck
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Runtime
|
|
43
|
+
|
|
44
|
+
- **Node-only** (ESM).
|
|
45
|
+
- Reads/writes a `.mu/` store at the git repo root (use `mu init` to create it).
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { run } from "./index.js";
|
|
3
|
+
const result = await run(process.argv.slice(2));
|
|
4
|
+
if (result.stdout.length > 0) {
|
|
5
|
+
process.stdout.write(result.stdout);
|
|
6
|
+
}
|
|
7
|
+
if (result.stderr.length > 0) {
|
|
8
|
+
process.stderr.write(result.stderr);
|
|
9
|
+
}
|
|
10
|
+
process.exitCode = result.exitCode;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AA4PF,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAoCzF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { FsJsonlStore, fsEventLog, getStorePaths, newRunId, runContext } from "@femtomc/mu-core/node";
|
|
5
|
+
import { ForumStore } from "@femtomc/mu-forum";
|
|
6
|
+
import { IssueStore } from "@femtomc/mu-issue";
|
|
7
|
+
import { DagRunner, extractDescription, splitFrontmatter } from "@femtomc/mu-orchestrator";
|
|
8
|
+
import { DEFAULT_ORCHESTRATOR_MD, DEFAULT_REVIEWER_ROLE_MD, DEFAULT_WORKER_ROLE_MD } from "./templates.js";
|
|
9
|
+
function ok(stdout = "", exitCode = 0) {
|
|
10
|
+
return { stdout, stderr: "", exitCode };
|
|
11
|
+
}
|
|
12
|
+
function jsonText(data, pretty) {
|
|
13
|
+
return `${JSON.stringify(data, null, pretty ? 2 : 0)}\n`;
|
|
14
|
+
}
|
|
15
|
+
function formatRecovery(recovery) {
|
|
16
|
+
if (!recovery || recovery.length === 0) {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
return ` Recovery: ${recovery.join(" | ")}`;
|
|
20
|
+
}
|
|
21
|
+
function jsonError(msg, opts = {}) {
|
|
22
|
+
const pretty = opts.pretty ?? false;
|
|
23
|
+
return { stdout: jsonText({ error: `${msg}${formatRecovery(opts.recovery)}` }, pretty), stderr: "", exitCode: 1 };
|
|
24
|
+
}
|
|
25
|
+
function hasHelpFlag(argv) {
|
|
26
|
+
return argv.includes("--help") || argv.includes("-h");
|
|
27
|
+
}
|
|
28
|
+
function popFlag(argv, name) {
|
|
29
|
+
let present = false;
|
|
30
|
+
const rest = [];
|
|
31
|
+
for (const a of argv) {
|
|
32
|
+
if (a === name) {
|
|
33
|
+
present = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
rest.push(a);
|
|
37
|
+
}
|
|
38
|
+
return { present, rest };
|
|
39
|
+
}
|
|
40
|
+
function getFlagValue(argv, name) {
|
|
41
|
+
const rest = [];
|
|
42
|
+
let value = null;
|
|
43
|
+
for (let i = 0; i < argv.length; i++) {
|
|
44
|
+
const a = argv[i];
|
|
45
|
+
if (a === name) {
|
|
46
|
+
const next = argv[i + 1];
|
|
47
|
+
if (next == null) {
|
|
48
|
+
value = "";
|
|
49
|
+
i += 0;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
value = next;
|
|
53
|
+
i += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (a.startsWith(`${name}=`)) {
|
|
57
|
+
value = a.slice(`${name}=`.length);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
rest.push(a);
|
|
61
|
+
}
|
|
62
|
+
return { value, rest };
|
|
63
|
+
}
|
|
64
|
+
function getRepeatFlagValues(argv, names) {
|
|
65
|
+
const nameSet = new Set(names);
|
|
66
|
+
const values = [];
|
|
67
|
+
const rest = [];
|
|
68
|
+
for (let i = 0; i < argv.length; i++) {
|
|
69
|
+
const a = argv[i];
|
|
70
|
+
if (nameSet.has(a)) {
|
|
71
|
+
const next = argv[i + 1];
|
|
72
|
+
if (next != null) {
|
|
73
|
+
values.push(next);
|
|
74
|
+
i += 1;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let matched = false;
|
|
79
|
+
for (const name of names) {
|
|
80
|
+
if (a.startsWith(`${name}=`)) {
|
|
81
|
+
values.push(a.slice(`${name}=`.length));
|
|
82
|
+
matched = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (matched) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
rest.push(a);
|
|
90
|
+
}
|
|
91
|
+
return { values, rest };
|
|
92
|
+
}
|
|
93
|
+
function ensureInt(value, opts) {
|
|
94
|
+
const n = Number.parseInt(value, 10);
|
|
95
|
+
if (!Number.isFinite(n)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
if (opts.min != null && n < opts.min) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
if (opts.max != null && n > opts.max) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return n;
|
|
105
|
+
}
|
|
106
|
+
async function ensureCtx(cwd) {
|
|
107
|
+
const repoRoot = findRepoRoot(cwd);
|
|
108
|
+
const paths = getStorePaths(repoRoot);
|
|
109
|
+
const events = fsEventLog(paths.eventsPath);
|
|
110
|
+
const store = new IssueStore(new FsJsonlStore(paths.issuesPath), { events });
|
|
111
|
+
const forum = new ForumStore(new FsJsonlStore(paths.forumPath), { events });
|
|
112
|
+
return { cwd, repoRoot, store, forum, events, paths };
|
|
113
|
+
}
|
|
114
|
+
function findRepoRoot(start) {
|
|
115
|
+
let current = resolve(start);
|
|
116
|
+
while (true) {
|
|
117
|
+
if (existsSync(join(current, ".git"))) {
|
|
118
|
+
return current;
|
|
119
|
+
}
|
|
120
|
+
const parent = dirname(current);
|
|
121
|
+
if (parent === current) {
|
|
122
|
+
return resolve(start);
|
|
123
|
+
}
|
|
124
|
+
current = parent;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function listRolesJson(repoRoot) {
|
|
128
|
+
const rolesDir = join(repoRoot, ".mu", "roles");
|
|
129
|
+
let entries;
|
|
130
|
+
try {
|
|
131
|
+
entries = await readdir(rolesDir);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const mdFiles = entries.filter((e) => e.endsWith(".md")).sort();
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const file of mdFiles) {
|
|
139
|
+
const abs = join(rolesDir, file);
|
|
140
|
+
const text = await readFile(abs, "utf8");
|
|
141
|
+
const { meta, body } = splitFrontmatter(text);
|
|
142
|
+
const { description, source } = extractDescription(meta, body);
|
|
143
|
+
const name = file.replace(/\.md$/, "");
|
|
144
|
+
const prompt_path = relative(repoRoot, abs).replaceAll("\\", "/");
|
|
145
|
+
out.push({
|
|
146
|
+
name,
|
|
147
|
+
prompt_path,
|
|
148
|
+
cli: typeof meta.cli === "string" ? meta.cli : "",
|
|
149
|
+
model: typeof meta.model === "string" ? meta.model : "",
|
|
150
|
+
reasoning: typeof meta.reasoning === "string" ? meta.reasoning : "",
|
|
151
|
+
description,
|
|
152
|
+
description_source: source,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
function issueJson(issue) {
|
|
158
|
+
return {
|
|
159
|
+
id: issue.id,
|
|
160
|
+
title: issue.title,
|
|
161
|
+
body: issue.body ?? "",
|
|
162
|
+
status: issue.status,
|
|
163
|
+
outcome: issue.outcome ?? null,
|
|
164
|
+
tags: issue.tags ?? [],
|
|
165
|
+
deps: issue.deps ?? [],
|
|
166
|
+
execution_spec: issue.execution_spec ?? null,
|
|
167
|
+
priority: issue.priority ?? 3,
|
|
168
|
+
created_at: issue.created_at ?? 0,
|
|
169
|
+
updated_at: issue.updated_at ?? 0,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function resolveIssueId(store, rawId) {
|
|
173
|
+
const direct = await store.get(rawId);
|
|
174
|
+
if (direct) {
|
|
175
|
+
return { issueId: direct.id, error: null };
|
|
176
|
+
}
|
|
177
|
+
const all = await store.list();
|
|
178
|
+
const matches = all.map((i) => i.id).filter((id) => id.startsWith(rawId));
|
|
179
|
+
if (matches.length === 0) {
|
|
180
|
+
return {
|
|
181
|
+
issueId: null,
|
|
182
|
+
error: `not found: ${rawId}` +
|
|
183
|
+
formatRecovery(["mu issues list --limit 20", "mu issues ready --root <root-id>", "mu status"]),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (matches.length > 1) {
|
|
187
|
+
const sample = matches.slice(0, 5).join(",");
|
|
188
|
+
const suffix = matches.length > 5 ? "..." : "";
|
|
189
|
+
return {
|
|
190
|
+
issueId: null,
|
|
191
|
+
error: `ambiguous id prefix: ${rawId} (${sample}${suffix})` +
|
|
192
|
+
formatRecovery(["use a longer id prefix", "mu issues list --limit 20"]),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { issueId: matches[0], error: null };
|
|
196
|
+
}
|
|
197
|
+
function mainHelp() {
|
|
198
|
+
return [
|
|
199
|
+
"mu - issue DAG + forum CLI",
|
|
200
|
+
"",
|
|
201
|
+
"Usage:",
|
|
202
|
+
" mu <command> [args...]",
|
|
203
|
+
"",
|
|
204
|
+
"Commands:",
|
|
205
|
+
" init [--force] Initialize .mu store + templates",
|
|
206
|
+
" status [--json] [--pretty] Show repo + DAG status",
|
|
207
|
+
" roles [--json|--table] List role templates",
|
|
208
|
+
" issues <subcmd> Issue DAG commands (JSON)",
|
|
209
|
+
" forum <subcmd> Forum commands (JSON)",
|
|
210
|
+
" run <prompt...> Create root + run DAG loop",
|
|
211
|
+
" resume <root-id> Resume a DAG loop",
|
|
212
|
+
" replay <id|path> [--backend pi] Replay a logged run (pi-only)",
|
|
213
|
+
"",
|
|
214
|
+
"Run `mu <command> --help` for details.",
|
|
215
|
+
].join("\n");
|
|
216
|
+
}
|
|
217
|
+
export async function run(argv, opts = {}) {
|
|
218
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
219
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
220
|
+
return ok(`${mainHelp()}\n`);
|
|
221
|
+
}
|
|
222
|
+
if (argv.includes("--version")) {
|
|
223
|
+
return ok("mu 0.0.0\n");
|
|
224
|
+
}
|
|
225
|
+
const cmd = argv[0];
|
|
226
|
+
const rest = argv.slice(1);
|
|
227
|
+
const ctx = await ensureCtx(cwd);
|
|
228
|
+
switch (cmd) {
|
|
229
|
+
case "init":
|
|
230
|
+
return await cmdInit(rest, ctx);
|
|
231
|
+
case "status":
|
|
232
|
+
return await cmdStatus(rest, ctx);
|
|
233
|
+
case "roles":
|
|
234
|
+
return await cmdRoles(rest, ctx);
|
|
235
|
+
case "issues":
|
|
236
|
+
return await cmdIssues(rest, ctx);
|
|
237
|
+
case "forum":
|
|
238
|
+
return await cmdForum(rest, ctx);
|
|
239
|
+
case "run":
|
|
240
|
+
return await cmdRun(rest, ctx);
|
|
241
|
+
case "resume":
|
|
242
|
+
return await cmdResume(rest, ctx);
|
|
243
|
+
case "replay":
|
|
244
|
+
return await cmdReplay(rest, ctx);
|
|
245
|
+
default:
|
|
246
|
+
return jsonError(`unknown command: ${cmd}`, {
|
|
247
|
+
recovery: ["mu --help"],
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function cmdInit(argv, ctx) {
|
|
252
|
+
if (hasHelpFlag(argv)) {
|
|
253
|
+
return ok(["mu init - initialize .mu state, templates, and logs", "", "Usage:", " mu init [--force]"].join("\n") + "\n");
|
|
254
|
+
}
|
|
255
|
+
const { present: force, rest } = popFlag(argv, "--force");
|
|
256
|
+
if (rest.length > 0) {
|
|
257
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { recovery: ["mu init --help"] });
|
|
258
|
+
}
|
|
259
|
+
await mkdir(ctx.paths.storeDir, { recursive: true });
|
|
260
|
+
await writeFile(ctx.paths.issuesPath, "", { encoding: "utf8", flag: "a" });
|
|
261
|
+
await writeFile(ctx.paths.forumPath, "", { encoding: "utf8", flag: "a" });
|
|
262
|
+
await writeFile(ctx.paths.eventsPath, "", { encoding: "utf8", flag: "a" });
|
|
263
|
+
await mkdir(ctx.paths.logsDir, { recursive: true });
|
|
264
|
+
if (force || !existsSync(ctx.paths.orchestratorPath)) {
|
|
265
|
+
await writeFile(ctx.paths.orchestratorPath, DEFAULT_ORCHESTRATOR_MD, "utf8");
|
|
266
|
+
}
|
|
267
|
+
const rolesDir = ctx.paths.rolesDir;
|
|
268
|
+
await mkdir(rolesDir, { recursive: true });
|
|
269
|
+
const workerPath = join(rolesDir, "worker.md");
|
|
270
|
+
if (force || !existsSync(workerPath)) {
|
|
271
|
+
await writeFile(workerPath, DEFAULT_WORKER_ROLE_MD, "utf8");
|
|
272
|
+
}
|
|
273
|
+
const reviewerPath = join(rolesDir, "reviewer.md");
|
|
274
|
+
if (force || !existsSync(reviewerPath)) {
|
|
275
|
+
await writeFile(reviewerPath, DEFAULT_REVIEWER_ROLE_MD, "utf8");
|
|
276
|
+
}
|
|
277
|
+
const verb = force ? "Reinitialized" : "Initialized";
|
|
278
|
+
return ok(`${verb} .mu/ in ${ctx.repoRoot}\n`);
|
|
279
|
+
}
|
|
280
|
+
async function cmdStatus(argv, ctx) {
|
|
281
|
+
if (hasHelpFlag(argv)) {
|
|
282
|
+
return ok(["mu status - show repo + DAG status", "", "Usage:", " mu status [--json] [--pretty]"].join("\n") + "\n");
|
|
283
|
+
}
|
|
284
|
+
const { present: pretty, rest: argv0 } = popFlag(argv, "--pretty");
|
|
285
|
+
const { present: jsonMode, rest } = popFlag(argv0, "--json");
|
|
286
|
+
if (rest.length > 0) {
|
|
287
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { recovery: ["mu status --help"] });
|
|
288
|
+
}
|
|
289
|
+
const roots = (await ctx.store.list({ tag: "node:root" })).map(issueJson);
|
|
290
|
+
const openIssues = await ctx.store.list({ status: "open" });
|
|
291
|
+
const ready = await ctx.store.ready(null, { tags: ["node:agent"] });
|
|
292
|
+
const topics = await ctx.forum.topics("issue:");
|
|
293
|
+
const roles = await listRolesJson(ctx.repoRoot);
|
|
294
|
+
const payload = {
|
|
295
|
+
repo_root: ctx.repoRoot,
|
|
296
|
+
roots,
|
|
297
|
+
open_count: openIssues.length,
|
|
298
|
+
ready_count: ready.length,
|
|
299
|
+
ready: ready.slice(0, 10).map(issueJson),
|
|
300
|
+
recent_topics: topics.slice(0, 10),
|
|
301
|
+
roles,
|
|
302
|
+
};
|
|
303
|
+
if (jsonMode) {
|
|
304
|
+
return ok(jsonText(payload, pretty));
|
|
305
|
+
}
|
|
306
|
+
let out = `Repo: ${ctx.repoRoot}\n`;
|
|
307
|
+
out += `Root issues: ${roots.length}\n`;
|
|
308
|
+
out += `Open issues: ${openIssues.length}\n`;
|
|
309
|
+
out += `Ready issues: ${ready.length}\n`;
|
|
310
|
+
out += `Roles: ${roles.length}\n`;
|
|
311
|
+
if (ready.length > 0) {
|
|
312
|
+
out += "\nReady:\n";
|
|
313
|
+
for (const issue of ready.slice(0, 10)) {
|
|
314
|
+
out += ` ${issue.id} [p=${issue.priority ?? 3}] ${String(issue.title ?? "").slice(0, 80)}\n`;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (topics.length > 0) {
|
|
318
|
+
out += "\nRecent issue topics:\n";
|
|
319
|
+
for (const topic of topics.slice(0, 10)) {
|
|
320
|
+
out += ` ${topic.topic} (${topic.messages}) last_at=${topic.last_at}\n`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return ok(out);
|
|
324
|
+
}
|
|
325
|
+
async function cmdRoles(argv, ctx) {
|
|
326
|
+
if (hasHelpFlag(argv)) {
|
|
327
|
+
return ok([
|
|
328
|
+
"mu roles - list role templates from .mu/roles/*.md",
|
|
329
|
+
"",
|
|
330
|
+
"Usage:",
|
|
331
|
+
" mu roles [--json] [--table] [--pretty]",
|
|
332
|
+
"",
|
|
333
|
+
"Defaults to JSON output for automation.",
|
|
334
|
+
].join("\n") + "\n");
|
|
335
|
+
}
|
|
336
|
+
const { present: pretty, rest: argv0 } = popFlag(argv, "--pretty");
|
|
337
|
+
const { present: tableMode, rest: argv1 } = popFlag(argv0, "--table");
|
|
338
|
+
const { present: jsonFlag, rest } = popFlag(argv1, "--json");
|
|
339
|
+
if (rest.length > 0) {
|
|
340
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { recovery: ["mu roles --help"] });
|
|
341
|
+
}
|
|
342
|
+
const jsonMode = jsonFlag || !tableMode;
|
|
343
|
+
const roles = await listRolesJson(ctx.repoRoot);
|
|
344
|
+
if (jsonMode) {
|
|
345
|
+
return ok(jsonText(roles, pretty));
|
|
346
|
+
}
|
|
347
|
+
// Simple ASCII table.
|
|
348
|
+
const lines = [];
|
|
349
|
+
lines.push("Name\tCLI\tModel\tReasoning\tPrompt");
|
|
350
|
+
for (const role of roles) {
|
|
351
|
+
lines.push([role.name, role.cli || "-", role.model || "-", role.reasoning || "-", role.prompt_path || "-"].join("\t"));
|
|
352
|
+
}
|
|
353
|
+
return ok(`${lines.join("\n")}\n`);
|
|
354
|
+
}
|
|
355
|
+
async function cmdIssues(argv, ctx) {
|
|
356
|
+
const { present: pretty, rest: argv0 } = popFlag(argv, "--pretty");
|
|
357
|
+
if (argv0.length === 0 || hasHelpFlag(argv0)) {
|
|
358
|
+
return ok([
|
|
359
|
+
"mu issues - issue DAG commands (JSON)",
|
|
360
|
+
"",
|
|
361
|
+
"Usage:",
|
|
362
|
+
" mu issues <command> [args...] [--pretty]",
|
|
363
|
+
"",
|
|
364
|
+
"Commands:",
|
|
365
|
+
" list/get/create/update/claim/open/close/dep/undep/children/ready/validate",
|
|
366
|
+
].join("\n") + "\n");
|
|
367
|
+
}
|
|
368
|
+
const sub = argv0[0];
|
|
369
|
+
const rest = argv0.slice(1);
|
|
370
|
+
switch (sub) {
|
|
371
|
+
case "list":
|
|
372
|
+
return await issuesList(rest, ctx, pretty);
|
|
373
|
+
case "get":
|
|
374
|
+
return await issuesGet(rest, ctx, pretty);
|
|
375
|
+
case "create":
|
|
376
|
+
return await issuesCreate(rest, ctx, pretty);
|
|
377
|
+
case "update":
|
|
378
|
+
return await issuesUpdate(rest, ctx, pretty);
|
|
379
|
+
case "claim":
|
|
380
|
+
return await issuesClaim(rest, ctx, pretty);
|
|
381
|
+
case "open":
|
|
382
|
+
return await issuesOpen(rest, ctx, pretty);
|
|
383
|
+
case "close":
|
|
384
|
+
return await issuesClose(rest, ctx, pretty);
|
|
385
|
+
case "dep":
|
|
386
|
+
return await issuesDep(rest, ctx, pretty);
|
|
387
|
+
case "undep":
|
|
388
|
+
return await issuesUndep(rest, ctx, pretty);
|
|
389
|
+
case "children":
|
|
390
|
+
return await issuesChildren(rest, ctx, pretty);
|
|
391
|
+
case "ready":
|
|
392
|
+
return await issuesReady(rest, ctx, pretty);
|
|
393
|
+
case "validate":
|
|
394
|
+
return await issuesValidate(rest, ctx, pretty);
|
|
395
|
+
default:
|
|
396
|
+
return jsonError(`unknown subcommand: ${sub}`, { pretty, recovery: ["mu issues --help"] });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async function issuesList(argv, ctx, pretty) {
|
|
400
|
+
if (hasHelpFlag(argv)) {
|
|
401
|
+
return ok([
|
|
402
|
+
"mu issues list - list issues with optional filters",
|
|
403
|
+
"",
|
|
404
|
+
"Usage:",
|
|
405
|
+
" mu issues list [--status STATUS] [--tag TAG] [--root ID] [--limit N] [--pretty]",
|
|
406
|
+
].join("\n") + "\n");
|
|
407
|
+
}
|
|
408
|
+
const { value: statusRaw, rest: argv0 } = getFlagValue(argv, "--status");
|
|
409
|
+
const { values: tags, rest: argv1 } = getRepeatFlagValues(argv0, ["--tag"]);
|
|
410
|
+
const { value: rootRaw, rest: argv2 } = getFlagValue(argv1, "--root");
|
|
411
|
+
const { value: limitRaw, rest } = getFlagValue(argv2, "--limit");
|
|
412
|
+
if (rest.length > 0) {
|
|
413
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { pretty, recovery: ["mu issues list --help"] });
|
|
414
|
+
}
|
|
415
|
+
const status = statusRaw && statusRaw.length > 0 ? statusRaw : null;
|
|
416
|
+
if (status != null && status !== "open" && status !== "in_progress" && status !== "closed") {
|
|
417
|
+
return jsonError(`invalid status: ${status}`, { pretty, recovery: ["mu issues list --help"] });
|
|
418
|
+
}
|
|
419
|
+
let issues = await ctx.store.list({ status: status ?? undefined });
|
|
420
|
+
if (tags.length > 0) {
|
|
421
|
+
issues = issues.filter((i) => tags.every((t) => i.tags.includes(t)));
|
|
422
|
+
}
|
|
423
|
+
if (rootRaw) {
|
|
424
|
+
const resolved = await resolveIssueId(ctx.store, rootRaw);
|
|
425
|
+
if (resolved.error) {
|
|
426
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
427
|
+
}
|
|
428
|
+
const subtree = new Set(await ctx.store.subtree_ids(resolved.issueId));
|
|
429
|
+
issues = issues.filter((i) => subtree.has(i.id));
|
|
430
|
+
}
|
|
431
|
+
let limit = 0;
|
|
432
|
+
if (limitRaw) {
|
|
433
|
+
const parsed = ensureInt(limitRaw, { name: "--limit", min: 0 });
|
|
434
|
+
if (parsed == null) {
|
|
435
|
+
return jsonError("limit must be an integer >= 0", { pretty, recovery: ["mu issues list --limit 20"] });
|
|
436
|
+
}
|
|
437
|
+
limit = parsed;
|
|
438
|
+
}
|
|
439
|
+
if (limit > 0) {
|
|
440
|
+
issues = issues.slice(-limit);
|
|
441
|
+
}
|
|
442
|
+
return ok(jsonText(issues.map(issueJson), pretty));
|
|
443
|
+
}
|
|
444
|
+
async function issuesGet(argv, ctx, pretty) {
|
|
445
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
446
|
+
return ok(["mu issues get - fetch a single issue", "", "Usage:", " mu issues get <id-or-prefix> [--pretty]"].join("\n") + "\n");
|
|
447
|
+
}
|
|
448
|
+
const resolved = await resolveIssueId(ctx.store, argv[0]);
|
|
449
|
+
if (resolved.error) {
|
|
450
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
451
|
+
}
|
|
452
|
+
const issue = await ctx.store.get(resolved.issueId);
|
|
453
|
+
if (!issue) {
|
|
454
|
+
return jsonError(`not found: ${argv[0]}`, { pretty, recovery: ["mu issues list --limit 20"] });
|
|
455
|
+
}
|
|
456
|
+
return ok(jsonText(issueJson(issue), pretty));
|
|
457
|
+
}
|
|
458
|
+
function buildExecutionSpec(fields) {
|
|
459
|
+
const spec = {};
|
|
460
|
+
if (fields.role)
|
|
461
|
+
spec.role = fields.role;
|
|
462
|
+
if (fields.cli)
|
|
463
|
+
spec.cli = fields.cli;
|
|
464
|
+
if (fields.model)
|
|
465
|
+
spec.model = fields.model;
|
|
466
|
+
if (fields.reasoning)
|
|
467
|
+
spec.reasoning = fields.reasoning;
|
|
468
|
+
if (fields.prompt_path)
|
|
469
|
+
spec.prompt_path = fields.prompt_path;
|
|
470
|
+
return Object.keys(spec).length > 0 ? spec : null;
|
|
471
|
+
}
|
|
472
|
+
async function issuesCreate(argv, ctx, pretty) {
|
|
473
|
+
if (hasHelpFlag(argv)) {
|
|
474
|
+
return ok([
|
|
475
|
+
"mu issues create - create a new issue (adds node:agent tag automatically)",
|
|
476
|
+
"",
|
|
477
|
+
"Usage:",
|
|
478
|
+
" mu issues create <title> [--body TEXT] [--parent ID] [--tag TAG] [--role ROLE] [--cli NAME] [--model NAME] [--reasoning LEVEL] [--prompt-path PATH] [--priority N] [--pretty]",
|
|
479
|
+
].join("\n") + "\n");
|
|
480
|
+
}
|
|
481
|
+
const title = argv[0];
|
|
482
|
+
if (!title || title.startsWith("-")) {
|
|
483
|
+
return jsonError("missing title", {
|
|
484
|
+
pretty,
|
|
485
|
+
recovery: ['mu issues create "Title" --body "Details"'],
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const { value: body, rest: argv0 } = getFlagValue(argv.slice(1), "--body");
|
|
489
|
+
const { value: bodyShort, rest: argv1 } = getFlagValue(argv0, "-b");
|
|
490
|
+
const resolvedBody = body ?? bodyShort ?? "";
|
|
491
|
+
const { value: parentRaw, rest: argv2 } = getFlagValue(argv1, "--parent");
|
|
492
|
+
const { values: tags0, rest: argv3 } = getRepeatFlagValues(argv2, ["--tag", "-t"]);
|
|
493
|
+
const { value: role, rest: argv4 } = getFlagValue(argv3, "--role");
|
|
494
|
+
const { value: roleShort, rest: argv5 } = getFlagValue(argv4, "-r");
|
|
495
|
+
const { value: cli, rest: argv6 } = getFlagValue(argv5, "--cli");
|
|
496
|
+
const { value: model, rest: argv7 } = getFlagValue(argv6, "--model");
|
|
497
|
+
const { value: reasoning, rest: argv8 } = getFlagValue(argv7, "--reasoning");
|
|
498
|
+
const { value: promptPath, rest: argv9 } = getFlagValue(argv8, "--prompt-path");
|
|
499
|
+
const { value: priorityRaw, rest } = getFlagValue(argv9, "--priority");
|
|
500
|
+
const { value: priorityShortRaw, rest: rest2 } = getFlagValue(rest, "-p");
|
|
501
|
+
const restFinal = rest2;
|
|
502
|
+
const priorityValue = priorityRaw ?? priorityShortRaw ?? "3";
|
|
503
|
+
if (restFinal.length > 0) {
|
|
504
|
+
return jsonError(`unknown args: ${restFinal.join(" ")}`, { pretty, recovery: ["mu issues create --help"] });
|
|
505
|
+
}
|
|
506
|
+
const priority = ensureInt(priorityValue, { name: "--priority", min: 1, max: 5 });
|
|
507
|
+
if (priority == null) {
|
|
508
|
+
return jsonError("priority must be in range 1-5", {
|
|
509
|
+
pretty,
|
|
510
|
+
recovery: ['mu issues create "Title" --priority 2'],
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
const tags = [...new Set(tags0)];
|
|
514
|
+
if (!tags.includes("node:agent")) {
|
|
515
|
+
tags.push("node:agent");
|
|
516
|
+
}
|
|
517
|
+
const execution_spec = buildExecutionSpec({
|
|
518
|
+
role: role ?? roleShort ?? null,
|
|
519
|
+
cli: cli ?? null,
|
|
520
|
+
model: model ?? null,
|
|
521
|
+
reasoning: reasoning ?? null,
|
|
522
|
+
prompt_path: promptPath ?? null,
|
|
523
|
+
});
|
|
524
|
+
let parentId = null;
|
|
525
|
+
if (parentRaw) {
|
|
526
|
+
const resolved = await resolveIssueId(ctx.store, parentRaw);
|
|
527
|
+
if (resolved.error) {
|
|
528
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
529
|
+
}
|
|
530
|
+
parentId = resolved.issueId;
|
|
531
|
+
}
|
|
532
|
+
let issue = await ctx.store.create(title, {
|
|
533
|
+
body: resolvedBody,
|
|
534
|
+
tags,
|
|
535
|
+
execution_spec,
|
|
536
|
+
priority,
|
|
537
|
+
});
|
|
538
|
+
if (parentId) {
|
|
539
|
+
await ctx.store.add_dep(issue.id, "parent", parentId);
|
|
540
|
+
issue = (await ctx.store.get(issue.id)) ?? issue;
|
|
541
|
+
}
|
|
542
|
+
return ok(jsonText(issueJson(issue), pretty));
|
|
543
|
+
}
|
|
544
|
+
async function issuesUpdate(argv, ctx, pretty) {
|
|
545
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
546
|
+
return ok([
|
|
547
|
+
"mu issues update - patch fields and routing metadata",
|
|
548
|
+
"",
|
|
549
|
+
"Usage:",
|
|
550
|
+
" mu issues update <id-or-prefix> [--title TEXT] [--body TEXT] [--status STATUS] [--outcome OUTCOME] [--priority N] [--add-tag TAG] [--remove-tag TAG] [--role ROLE] [--cli NAME] [--model NAME] [--reasoning LEVEL] [--prompt-path PATH] [--clear-execution-spec] [--pretty]",
|
|
551
|
+
].join("\n") + "\n");
|
|
552
|
+
}
|
|
553
|
+
const rawId = argv[0];
|
|
554
|
+
const resolvedId = await resolveIssueId(ctx.store, rawId);
|
|
555
|
+
if (resolvedId.error) {
|
|
556
|
+
return { stdout: jsonText({ error: resolvedId.error }, pretty), stderr: "", exitCode: 1 };
|
|
557
|
+
}
|
|
558
|
+
const issueId = resolvedId.issueId;
|
|
559
|
+
const issue = await ctx.store.get(issueId);
|
|
560
|
+
if (!issue) {
|
|
561
|
+
return jsonError(`not found: ${rawId}`, { pretty, recovery: ["mu issues list --limit 20"] });
|
|
562
|
+
}
|
|
563
|
+
const argvRest = argv.slice(1);
|
|
564
|
+
const { value: title, rest: argv0 } = getFlagValue(argvRest, "--title");
|
|
565
|
+
const { value: body, rest: argv1 } = getFlagValue(argv0, "--body");
|
|
566
|
+
const { value: status, rest: argv2 } = getFlagValue(argv1, "--status");
|
|
567
|
+
const { value: outcome, rest: argv3 } = getFlagValue(argv2, "--outcome");
|
|
568
|
+
const { value: priorityRaw, rest: argv4 } = getFlagValue(argv3, "--priority");
|
|
569
|
+
const { values: addTags, rest: argv5 } = getRepeatFlagValues(argv4, ["--add-tag"]);
|
|
570
|
+
const { values: removeTags, rest: argv6 } = getRepeatFlagValues(argv5, ["--remove-tag"]);
|
|
571
|
+
const { value: role, rest: argv7 } = getFlagValue(argv6, "--role");
|
|
572
|
+
const { value: cli, rest: argv8 } = getFlagValue(argv7, "--cli");
|
|
573
|
+
const { value: model, rest: argv9 } = getFlagValue(argv8, "--model");
|
|
574
|
+
const { value: reasoning, rest: argv10 } = getFlagValue(argv9, "--reasoning");
|
|
575
|
+
const { value: promptPath, rest: argv11 } = getFlagValue(argv10, "--prompt-path");
|
|
576
|
+
const { present: clearExecutionSpec, rest } = popFlag(argv11, "--clear-execution-spec");
|
|
577
|
+
if (rest.length > 0) {
|
|
578
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { pretty, recovery: ["mu issues update --help"] });
|
|
579
|
+
}
|
|
580
|
+
if (priorityRaw != null) {
|
|
581
|
+
const pr = ensureInt(priorityRaw, { name: "--priority", min: 1, max: 5 });
|
|
582
|
+
if (pr == null) {
|
|
583
|
+
return jsonError("priority must be in range 1-5", {
|
|
584
|
+
pretty,
|
|
585
|
+
recovery: [`mu issues update ${issueId} --priority 2`],
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const fields = {};
|
|
590
|
+
if (title != null)
|
|
591
|
+
fields.title = title;
|
|
592
|
+
if (body != null)
|
|
593
|
+
fields.body = body;
|
|
594
|
+
if (status != null)
|
|
595
|
+
fields.status = status;
|
|
596
|
+
if (outcome != null)
|
|
597
|
+
fields.outcome = outcome;
|
|
598
|
+
if (priorityRaw != null)
|
|
599
|
+
fields.priority = Number.parseInt(priorityRaw, 10);
|
|
600
|
+
if (addTags.length > 0 || removeTags.length > 0) {
|
|
601
|
+
let tags = [...(issue.tags ?? [])];
|
|
602
|
+
for (const tag of addTags) {
|
|
603
|
+
if (!tags.includes(tag)) {
|
|
604
|
+
tags.push(tag);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (removeTags.length > 0) {
|
|
608
|
+
tags = tags.filter((t) => !removeTags.includes(t));
|
|
609
|
+
}
|
|
610
|
+
fields.tags = tags;
|
|
611
|
+
}
|
|
612
|
+
const routingTouched = [role, cli, model, reasoning, promptPath].some((v) => v != null);
|
|
613
|
+
if (clearExecutionSpec) {
|
|
614
|
+
fields.execution_spec = null;
|
|
615
|
+
}
|
|
616
|
+
else if (routingTouched) {
|
|
617
|
+
const spec = { ...(issue.execution_spec ?? {}) };
|
|
618
|
+
if (role != null)
|
|
619
|
+
spec.role = role;
|
|
620
|
+
if (cli != null)
|
|
621
|
+
spec.cli = cli;
|
|
622
|
+
if (model != null)
|
|
623
|
+
spec.model = model;
|
|
624
|
+
if (reasoning != null)
|
|
625
|
+
spec.reasoning = reasoning;
|
|
626
|
+
if (promptPath != null)
|
|
627
|
+
spec.prompt_path = promptPath;
|
|
628
|
+
fields.execution_spec = Object.keys(spec).length > 0 ? spec : null;
|
|
629
|
+
}
|
|
630
|
+
if (Object.keys(fields).length === 0) {
|
|
631
|
+
return jsonError("no fields to update", {
|
|
632
|
+
pretty,
|
|
633
|
+
recovery: [`mu issues update ${issueId} --status in_progress`],
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
const updated = await ctx.store.update(issueId, fields);
|
|
637
|
+
return ok(jsonText(issueJson(updated), pretty));
|
|
638
|
+
}
|
|
639
|
+
async function issuesClaim(argv, ctx, pretty) {
|
|
640
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
641
|
+
return ok([
|
|
642
|
+
"mu issues claim - mark an open issue as in_progress",
|
|
643
|
+
"",
|
|
644
|
+
"Usage:",
|
|
645
|
+
" mu issues claim <id-or-prefix> [--pretty]",
|
|
646
|
+
].join("\n") + "\n");
|
|
647
|
+
}
|
|
648
|
+
const resolved = await resolveIssueId(ctx.store, argv[0]);
|
|
649
|
+
if (resolved.error) {
|
|
650
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
651
|
+
}
|
|
652
|
+
const issue = await ctx.store.get(resolved.issueId);
|
|
653
|
+
if (!issue) {
|
|
654
|
+
return jsonError(`not found: ${argv[0]}`, { pretty, recovery: ["mu issues list --status open --limit 20"] });
|
|
655
|
+
}
|
|
656
|
+
if (issue.status !== "open") {
|
|
657
|
+
return jsonError(`cannot claim issue in status=${issue.status}`, {
|
|
658
|
+
pretty,
|
|
659
|
+
recovery: [`mu issues get ${issue.id}`, `mu issues update ${issue.id} --status open`],
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
await ctx.store.claim(issue.id);
|
|
663
|
+
const claimed = (await ctx.store.get(issue.id)) ?? issue;
|
|
664
|
+
return ok(jsonText(issueJson(claimed), pretty));
|
|
665
|
+
}
|
|
666
|
+
async function issuesOpen(argv, ctx, pretty) {
|
|
667
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
668
|
+
return ok([
|
|
669
|
+
"mu issues open - reopen an issue and clear outcome",
|
|
670
|
+
"",
|
|
671
|
+
"Usage:",
|
|
672
|
+
" mu issues open <id-or-prefix> [--pretty]",
|
|
673
|
+
].join("\n") + "\n");
|
|
674
|
+
}
|
|
675
|
+
const resolved = await resolveIssueId(ctx.store, argv[0]);
|
|
676
|
+
if (resolved.error) {
|
|
677
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
678
|
+
}
|
|
679
|
+
const issue = await ctx.store.get(resolved.issueId);
|
|
680
|
+
if (!issue) {
|
|
681
|
+
return jsonError(`not found: ${argv[0]}`, { pretty, recovery: ["mu issues list --limit 20"] });
|
|
682
|
+
}
|
|
683
|
+
const reopened = await ctx.store.update(issue.id, { status: "open", outcome: null });
|
|
684
|
+
return ok(jsonText(issueJson(reopened), pretty));
|
|
685
|
+
}
|
|
686
|
+
async function issuesClose(argv, ctx, pretty) {
|
|
687
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
688
|
+
return ok([
|
|
689
|
+
"mu issues close - close an issue with an outcome",
|
|
690
|
+
"",
|
|
691
|
+
"Usage:",
|
|
692
|
+
" mu issues close <id-or-prefix> [--outcome OUTCOME] [--pretty]",
|
|
693
|
+
].join("\n") + "\n");
|
|
694
|
+
}
|
|
695
|
+
const issueRaw = argv[0];
|
|
696
|
+
const { value: outcome, rest } = getFlagValue(argv.slice(1), "--outcome");
|
|
697
|
+
if (rest.length > 0) {
|
|
698
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { pretty, recovery: ["mu issues close --help"] });
|
|
699
|
+
}
|
|
700
|
+
const resolved = await resolveIssueId(ctx.store, issueRaw);
|
|
701
|
+
if (resolved.error) {
|
|
702
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
703
|
+
}
|
|
704
|
+
const closed = await ctx.store.close(resolved.issueId, outcome ?? "success");
|
|
705
|
+
return ok(jsonText(issueJson(closed), pretty));
|
|
706
|
+
}
|
|
707
|
+
async function issuesDep(argv, ctx, pretty) {
|
|
708
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
709
|
+
return ok([
|
|
710
|
+
"mu issues dep - add dependency edge",
|
|
711
|
+
"",
|
|
712
|
+
"Usage:",
|
|
713
|
+
" mu issues dep <src-id> <blocks|parent> <dst-id> [--pretty]",
|
|
714
|
+
].join("\n") + "\n");
|
|
715
|
+
}
|
|
716
|
+
if (argv.length < 3) {
|
|
717
|
+
return jsonError("usage: mu issues dep <src> <type> <dst>", {
|
|
718
|
+
pretty,
|
|
719
|
+
recovery: ["mu issues dep <src-id> blocks <dst-id>"],
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
const [srcRaw, depType, dstRaw] = argv;
|
|
723
|
+
if (depType !== "blocks" && depType !== "parent") {
|
|
724
|
+
return jsonError(`invalid dep type: ${depType} (use 'blocks' or 'parent')`, {
|
|
725
|
+
pretty,
|
|
726
|
+
recovery: ["mu issues dep <src-id> blocks <dst-id>", "mu issues dep <child-id> parent <parent-id>"],
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
const src = await resolveIssueId(ctx.store, srcRaw);
|
|
730
|
+
if (src.error)
|
|
731
|
+
return { stdout: jsonText({ error: src.error }, pretty), stderr: "", exitCode: 1 };
|
|
732
|
+
const dst = await resolveIssueId(ctx.store, dstRaw);
|
|
733
|
+
if (dst.error)
|
|
734
|
+
return { stdout: jsonText({ error: dst.error }, pretty), stderr: "", exitCode: 1 };
|
|
735
|
+
if (src.issueId === dst.issueId) {
|
|
736
|
+
return jsonError("source and destination must be different", {
|
|
737
|
+
pretty,
|
|
738
|
+
recovery: ["mu issues dep <src-id> blocks <dst-id>"],
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
await ctx.store.add_dep(src.issueId, depType, dst.issueId);
|
|
742
|
+
return ok(jsonText({ ok: true, src: src.issueId, type: depType, dst: dst.issueId }, pretty));
|
|
743
|
+
}
|
|
744
|
+
async function issuesUndep(argv, ctx, pretty) {
|
|
745
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
746
|
+
return ok([
|
|
747
|
+
"mu issues undep - remove dependency edge",
|
|
748
|
+
"",
|
|
749
|
+
"Usage:",
|
|
750
|
+
" mu issues undep <src-id> <blocks|parent> <dst-id> [--pretty]",
|
|
751
|
+
].join("\n") + "\n");
|
|
752
|
+
}
|
|
753
|
+
if (argv.length < 3) {
|
|
754
|
+
return jsonError("usage: mu issues undep <src> <type> <dst>", {
|
|
755
|
+
pretty,
|
|
756
|
+
recovery: ["mu issues undep <src-id> blocks <dst-id>"],
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
const [srcRaw, depType, dstRaw] = argv;
|
|
760
|
+
if (depType !== "blocks" && depType !== "parent") {
|
|
761
|
+
return jsonError(`invalid dep type: ${depType} (use 'blocks' or 'parent')`, {
|
|
762
|
+
pretty,
|
|
763
|
+
recovery: ["mu issues undep <src-id> blocks <dst-id>"],
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
const src = await resolveIssueId(ctx.store, srcRaw);
|
|
767
|
+
if (src.error)
|
|
768
|
+
return { stdout: jsonText({ error: src.error }, pretty), stderr: "", exitCode: 1 };
|
|
769
|
+
const dst = await resolveIssueId(ctx.store, dstRaw);
|
|
770
|
+
if (dst.error)
|
|
771
|
+
return { stdout: jsonText({ error: dst.error }, pretty), stderr: "", exitCode: 1 };
|
|
772
|
+
const removed = await ctx.store.remove_dep(src.issueId, depType, dst.issueId);
|
|
773
|
+
return ok(jsonText({ ok: removed, src: src.issueId, type: depType, dst: dst.issueId }, pretty));
|
|
774
|
+
}
|
|
775
|
+
async function issuesChildren(argv, ctx, pretty) {
|
|
776
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
777
|
+
return ok([
|
|
778
|
+
"mu issues children - list direct child issues",
|
|
779
|
+
"",
|
|
780
|
+
"Usage:",
|
|
781
|
+
" mu issues children <id-or-prefix> [--pretty]",
|
|
782
|
+
].join("\n") + "\n");
|
|
783
|
+
}
|
|
784
|
+
const resolved = await resolveIssueId(ctx.store, argv[0]);
|
|
785
|
+
if (resolved.error) {
|
|
786
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
787
|
+
}
|
|
788
|
+
const children = await ctx.store.children(resolved.issueId);
|
|
789
|
+
children.sort((a, b) => (a.priority ?? 3) - (b.priority ?? 3));
|
|
790
|
+
return ok(jsonText(children.map(issueJson), pretty));
|
|
791
|
+
}
|
|
792
|
+
async function issuesReady(argv, ctx, pretty) {
|
|
793
|
+
if (hasHelpFlag(argv)) {
|
|
794
|
+
return ok([
|
|
795
|
+
"mu issues ready - list open, unblocked, leaf issues tagged node:agent",
|
|
796
|
+
"",
|
|
797
|
+
"Usage:",
|
|
798
|
+
" mu issues ready [--root ID] [--tag TAG] [--pretty]",
|
|
799
|
+
].join("\n") + "\n");
|
|
800
|
+
}
|
|
801
|
+
const { value: rootRaw, rest: argv0 } = getFlagValue(argv, "--root");
|
|
802
|
+
const { values: extraTags, rest } = getRepeatFlagValues(argv0, ["--tag"]);
|
|
803
|
+
if (rest.length > 0) {
|
|
804
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { pretty, recovery: ["mu issues ready --help"] });
|
|
805
|
+
}
|
|
806
|
+
let rootId = null;
|
|
807
|
+
if (rootRaw) {
|
|
808
|
+
const resolved = await resolveIssueId(ctx.store, rootRaw);
|
|
809
|
+
if (resolved.error) {
|
|
810
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
811
|
+
}
|
|
812
|
+
rootId = resolved.issueId;
|
|
813
|
+
}
|
|
814
|
+
const tags = ["node:agent", ...extraTags];
|
|
815
|
+
const issues = await ctx.store.ready(rootId, { tags });
|
|
816
|
+
return ok(jsonText(issues.map(issueJson), pretty));
|
|
817
|
+
}
|
|
818
|
+
async function issuesValidate(argv, ctx, pretty) {
|
|
819
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
820
|
+
return ok([
|
|
821
|
+
"mu issues validate - validate DAG completion state for a root",
|
|
822
|
+
"",
|
|
823
|
+
"Usage:",
|
|
824
|
+
" mu issues validate <root-id-or-prefix> [--pretty]",
|
|
825
|
+
].join("\n") + "\n");
|
|
826
|
+
}
|
|
827
|
+
const resolved = await resolveIssueId(ctx.store, argv[0]);
|
|
828
|
+
if (resolved.error) {
|
|
829
|
+
return { stdout: jsonText({ error: resolved.error }, pretty), stderr: "", exitCode: 1 };
|
|
830
|
+
}
|
|
831
|
+
const v = await ctx.store.validate(resolved.issueId);
|
|
832
|
+
return ok(jsonText({ root_id: resolved.issueId, is_final: v.is_final, reason: v.reason }, pretty));
|
|
833
|
+
}
|
|
834
|
+
async function cmdForum(argv, ctx) {
|
|
835
|
+
const { present: pretty, rest: argv0 } = popFlag(argv, "--pretty");
|
|
836
|
+
if (argv0.length === 0 || hasHelpFlag(argv0)) {
|
|
837
|
+
return ok([
|
|
838
|
+
"mu forum - forum messages for coordination (JSON)",
|
|
839
|
+
"",
|
|
840
|
+
"Usage:",
|
|
841
|
+
" mu forum <command> [args...] [--pretty]",
|
|
842
|
+
"",
|
|
843
|
+
"Commands:",
|
|
844
|
+
" post/read/topics",
|
|
845
|
+
].join("\n") + "\n");
|
|
846
|
+
}
|
|
847
|
+
const sub = argv0[0];
|
|
848
|
+
const rest = argv0.slice(1);
|
|
849
|
+
switch (sub) {
|
|
850
|
+
case "post":
|
|
851
|
+
return await forumPost(rest, ctx, pretty);
|
|
852
|
+
case "read":
|
|
853
|
+
return await forumRead(rest, ctx, pretty);
|
|
854
|
+
case "topics":
|
|
855
|
+
return await forumTopics(rest, ctx, pretty);
|
|
856
|
+
default:
|
|
857
|
+
return jsonError(`unknown subcommand: ${sub}`, { pretty, recovery: ["mu forum --help"] });
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async function forumPost(argv, ctx, pretty) {
|
|
861
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
862
|
+
return ok([
|
|
863
|
+
"mu forum post - post a message to a topic",
|
|
864
|
+
"",
|
|
865
|
+
"Usage:",
|
|
866
|
+
" mu forum post <topic> -m <message> [--author NAME] [--pretty]",
|
|
867
|
+
].join("\n") + "\n");
|
|
868
|
+
}
|
|
869
|
+
const topic = argv[0];
|
|
870
|
+
const { value: message, rest: argv0 } = getFlagValue(argv.slice(1), "--message");
|
|
871
|
+
const { value: messageShort, rest: argv1 } = getFlagValue(argv0, "-m");
|
|
872
|
+
const { value: author, rest } = getFlagValue(argv1, "--author");
|
|
873
|
+
const msgBody = message ?? messageShort;
|
|
874
|
+
if (!msgBody) {
|
|
875
|
+
return jsonError("missing message (-m/--message)", {
|
|
876
|
+
pretty,
|
|
877
|
+
recovery: [`mu forum post ${topic} -m "..." --author worker`],
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
if (rest.length > 0) {
|
|
881
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { pretty, recovery: ["mu forum post --help"] });
|
|
882
|
+
}
|
|
883
|
+
const msg = await ctx.forum.post(topic, msgBody, author ?? "system");
|
|
884
|
+
return ok(jsonText(msg, pretty));
|
|
885
|
+
}
|
|
886
|
+
async function forumRead(argv, ctx, pretty) {
|
|
887
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
888
|
+
return ok([
|
|
889
|
+
"mu forum read - read messages from a topic (chronological)",
|
|
890
|
+
"",
|
|
891
|
+
"Usage:",
|
|
892
|
+
" mu forum read <topic> [--limit N] [--pretty]",
|
|
893
|
+
].join("\n") + "\n");
|
|
894
|
+
}
|
|
895
|
+
const topic = argv[0];
|
|
896
|
+
const { value: limitRaw, rest } = getFlagValue(argv.slice(1), "--limit");
|
|
897
|
+
if (rest.length > 0) {
|
|
898
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { pretty, recovery: ["mu forum read --help"] });
|
|
899
|
+
}
|
|
900
|
+
const limit = limitRaw ? ensureInt(limitRaw, { name: "--limit", min: 1 }) : 50;
|
|
901
|
+
if (limit == null) {
|
|
902
|
+
return jsonError("limit must be >= 1", { pretty, recovery: [`mu forum read ${topic} --limit 20`] });
|
|
903
|
+
}
|
|
904
|
+
const msgs = await ctx.forum.read(topic, limit);
|
|
905
|
+
return ok(jsonText(msgs, pretty));
|
|
906
|
+
}
|
|
907
|
+
async function forumTopics(argv, ctx, pretty) {
|
|
908
|
+
if (hasHelpFlag(argv)) {
|
|
909
|
+
return ok([
|
|
910
|
+
"mu forum topics - list active topics sorted by most recent message",
|
|
911
|
+
"",
|
|
912
|
+
"Usage:",
|
|
913
|
+
" mu forum topics [--prefix PREFIX] [--limit N] [--pretty]",
|
|
914
|
+
].join("\n") + "\n");
|
|
915
|
+
}
|
|
916
|
+
const { value: prefix, rest: argv0 } = getFlagValue(argv, "--prefix");
|
|
917
|
+
const { value: limitRaw, rest } = getFlagValue(argv0, "--limit");
|
|
918
|
+
if (rest.length > 0) {
|
|
919
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { pretty, recovery: ["mu forum topics --help"] });
|
|
920
|
+
}
|
|
921
|
+
const limit = limitRaw ? ensureInt(limitRaw, { name: "--limit", min: 1 }) : 100;
|
|
922
|
+
if (limit == null) {
|
|
923
|
+
return jsonError("limit must be >= 1", { pretty, recovery: ["mu forum topics --limit 20"] });
|
|
924
|
+
}
|
|
925
|
+
let topics = await ctx.forum.topics(prefix ?? null);
|
|
926
|
+
if (limit > 0) {
|
|
927
|
+
topics = topics.slice(0, limit);
|
|
928
|
+
}
|
|
929
|
+
return ok(jsonText(topics, pretty));
|
|
930
|
+
}
|
|
931
|
+
async function cmdRun(argv, ctx) {
|
|
932
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
933
|
+
return ok([
|
|
934
|
+
"mu run - create a root issue and run the DAG loop",
|
|
935
|
+
"",
|
|
936
|
+
"Usage:",
|
|
937
|
+
" mu run <prompt...> [--max-steps N] [--review|--no-review] [--json]",
|
|
938
|
+
].join("\n") + "\n");
|
|
939
|
+
}
|
|
940
|
+
let maxSteps = 20;
|
|
941
|
+
let review = true;
|
|
942
|
+
let jsonMode = false;
|
|
943
|
+
const promptParts = [];
|
|
944
|
+
for (let i = 0; i < argv.length; i++) {
|
|
945
|
+
const a = argv[i];
|
|
946
|
+
if (a === "--json") {
|
|
947
|
+
jsonMode = true;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (a === "--review") {
|
|
951
|
+
review = true;
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (a === "--no-review") {
|
|
955
|
+
review = false;
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
if (a === "--max-steps") {
|
|
959
|
+
const next = argv[i + 1];
|
|
960
|
+
if (!next) {
|
|
961
|
+
return jsonError("missing value for --max-steps", { recovery: ['mu run --max-steps 20 "..."'] });
|
|
962
|
+
}
|
|
963
|
+
const n = ensureInt(next, { name: "--max-steps", min: 1 });
|
|
964
|
+
if (n == null) {
|
|
965
|
+
return jsonError("max-steps must be >= 1", { recovery: ['mu run --max-steps 20 "..."'] });
|
|
966
|
+
}
|
|
967
|
+
maxSteps = n;
|
|
968
|
+
i += 1;
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
if (a.startsWith("--max-steps=")) {
|
|
972
|
+
const n = ensureInt(a.slice("--max-steps=".length), { name: "--max-steps", min: 1 });
|
|
973
|
+
if (n == null) {
|
|
974
|
+
return jsonError("max-steps must be >= 1", { recovery: ['mu run --max-steps 20 "..."'] });
|
|
975
|
+
}
|
|
976
|
+
maxSteps = n;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
promptParts.push(a);
|
|
980
|
+
}
|
|
981
|
+
const promptText = promptParts.join(" ").trim();
|
|
982
|
+
if (!promptText) {
|
|
983
|
+
return jsonError("missing prompt", { recovery: ['mu run "Break down and execute this goal"'] });
|
|
984
|
+
}
|
|
985
|
+
const runId = newRunId();
|
|
986
|
+
const { rootIssue, result } = await runContext({ runId }, async () => {
|
|
987
|
+
const rootIssue = await ctx.store.create(promptText, { tags: ["node:agent", "node:root"] });
|
|
988
|
+
const runner = new DagRunner(ctx.store, ctx.forum, ctx.repoRoot);
|
|
989
|
+
const result = await runner.run(rootIssue.id, maxSteps, { review });
|
|
990
|
+
return { rootIssue, result };
|
|
991
|
+
});
|
|
992
|
+
if (jsonMode) {
|
|
993
|
+
return {
|
|
994
|
+
stdout: jsonText({ status: result.status, steps: result.steps, error: result.error, root_id: rootIssue.id }, true),
|
|
995
|
+
stderr: "",
|
|
996
|
+
exitCode: result.status === "root_final" ? 0 : 1,
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
let out = `Root: ${rootIssue.id} ${String(rootIssue.title ?? "").slice(0, 80)}\n`;
|
|
1000
|
+
out += `Runner status: ${result.status}\n`;
|
|
1001
|
+
if (result.error) {
|
|
1002
|
+
out += `Error: ${result.error}\n`;
|
|
1003
|
+
}
|
|
1004
|
+
return { stdout: out, stderr: "", exitCode: result.status === "root_final" ? 0 : 1 };
|
|
1005
|
+
}
|
|
1006
|
+
async function cmdResume(argv, ctx) {
|
|
1007
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
1008
|
+
return ok([
|
|
1009
|
+
"mu resume - resume an interrupted DAG loop",
|
|
1010
|
+
"",
|
|
1011
|
+
"Usage:",
|
|
1012
|
+
" mu resume <root-id> [--max-steps N] [--review|--no-review] [--json]",
|
|
1013
|
+
].join("\n") + "\n");
|
|
1014
|
+
}
|
|
1015
|
+
const rawId = argv[0];
|
|
1016
|
+
let maxSteps = 20;
|
|
1017
|
+
let review = true;
|
|
1018
|
+
let jsonMode = false;
|
|
1019
|
+
const rest = argv.slice(1);
|
|
1020
|
+
for (let i = 0; i < rest.length; i++) {
|
|
1021
|
+
const a = rest[i];
|
|
1022
|
+
if (a === "--json") {
|
|
1023
|
+
jsonMode = true;
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
if (a === "--review") {
|
|
1027
|
+
review = true;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
if (a === "--no-review") {
|
|
1031
|
+
review = false;
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (a === "--max-steps") {
|
|
1035
|
+
const next = rest[i + 1];
|
|
1036
|
+
if (!next) {
|
|
1037
|
+
return jsonError("missing value for --max-steps", { recovery: [`mu resume ${rawId} --max-steps 20`] });
|
|
1038
|
+
}
|
|
1039
|
+
const n = ensureInt(next, { name: "--max-steps", min: 1 });
|
|
1040
|
+
if (n == null) {
|
|
1041
|
+
return jsonError("max-steps must be >= 1", { recovery: [`mu resume ${rawId} --max-steps 20`] });
|
|
1042
|
+
}
|
|
1043
|
+
maxSteps = n;
|
|
1044
|
+
i += 1;
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (a.startsWith("--max-steps=")) {
|
|
1048
|
+
const n = ensureInt(a.slice("--max-steps=".length), { name: "--max-steps", min: 1 });
|
|
1049
|
+
if (n == null) {
|
|
1050
|
+
return jsonError("max-steps must be >= 1", { recovery: [`mu resume ${rawId} --max-steps 20`] });
|
|
1051
|
+
}
|
|
1052
|
+
maxSteps = n;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
return jsonError(`unknown arg: ${a}`, { recovery: ["mu resume --help"] });
|
|
1056
|
+
}
|
|
1057
|
+
const resolved = await resolveIssueId(ctx.store, rawId);
|
|
1058
|
+
if (resolved.error) {
|
|
1059
|
+
return { stdout: jsonText({ error: resolved.error }, false), stderr: "", exitCode: 1 };
|
|
1060
|
+
}
|
|
1061
|
+
const rootId = resolved.issueId;
|
|
1062
|
+
const reset = await ctx.store.reset_in_progress(rootId);
|
|
1063
|
+
const runId = newRunId();
|
|
1064
|
+
const result = await runContext({ runId }, async () => {
|
|
1065
|
+
const runner = new DagRunner(ctx.store, ctx.forum, ctx.repoRoot);
|
|
1066
|
+
return await runner.run(rootId, maxSteps, { review });
|
|
1067
|
+
});
|
|
1068
|
+
if (jsonMode) {
|
|
1069
|
+
return {
|
|
1070
|
+
stdout: jsonText({ status: result.status, steps: result.steps, error: result.error, root_id: rootId }, true),
|
|
1071
|
+
stderr: "",
|
|
1072
|
+
exitCode: result.status === "root_final" ? 0 : 1,
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
let out = "";
|
|
1076
|
+
if (reset.length > 0) {
|
|
1077
|
+
out += `Reset ${reset.length} stale issue(s) to open: ${reset.join(", ")}\n`;
|
|
1078
|
+
}
|
|
1079
|
+
out += `Resuming ${rootId}\n`;
|
|
1080
|
+
out += `Runner status: ${result.status}\n`;
|
|
1081
|
+
if (result.error) {
|
|
1082
|
+
out += `Error: ${result.error}\n`;
|
|
1083
|
+
}
|
|
1084
|
+
return { stdout: out, stderr: "", exitCode: result.status === "root_final" ? 0 : 1 };
|
|
1085
|
+
}
|
|
1086
|
+
async function cmdReplay(argv, ctx) {
|
|
1087
|
+
if (argv.length === 0 || hasHelpFlag(argv)) {
|
|
1088
|
+
return ok(["mu replay - replay a logged run (pi-only)", "", "Usage:", " mu replay <issue-id|path> [--backend pi]"].join("\n") + "\n");
|
|
1089
|
+
}
|
|
1090
|
+
const target = argv[0];
|
|
1091
|
+
const { value: backend, rest } = getFlagValue(argv.slice(1), "--backend");
|
|
1092
|
+
if (rest.length > 0) {
|
|
1093
|
+
return jsonError(`unknown args: ${rest.join(" ")}`, { recovery: ["mu replay --help"] });
|
|
1094
|
+
}
|
|
1095
|
+
if (backend && backend !== "pi") {
|
|
1096
|
+
return jsonError(`unsupported backend: ${backend} (only pi is supported)`, {
|
|
1097
|
+
recovery: ["mu replay --backend pi <id>"],
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
const logsDir = ctx.paths.logsDir;
|
|
1101
|
+
let path = resolve(ctx.cwd, target);
|
|
1102
|
+
if (!existsSync(path)) {
|
|
1103
|
+
const candidate = join(logsDir, `${target}.jsonl`);
|
|
1104
|
+
if (existsSync(candidate)) {
|
|
1105
|
+
path = candidate;
|
|
1106
|
+
}
|
|
1107
|
+
else {
|
|
1108
|
+
// Prefix match: <target>*.jsonl
|
|
1109
|
+
let entries;
|
|
1110
|
+
try {
|
|
1111
|
+
entries = await readdir(logsDir);
|
|
1112
|
+
}
|
|
1113
|
+
catch {
|
|
1114
|
+
entries = [];
|
|
1115
|
+
}
|
|
1116
|
+
const matches = entries.filter((e) => e.startsWith(target) && e.endsWith(".jsonl"));
|
|
1117
|
+
if (matches.length === 1) {
|
|
1118
|
+
path = join(logsDir, matches[0]);
|
|
1119
|
+
}
|
|
1120
|
+
else if (matches.length > 1) {
|
|
1121
|
+
return jsonError(`ambiguous prefix '${target}'`, {
|
|
1122
|
+
recovery: matches.slice(0, 10).map((m) => `mu replay ${m.replace(/\\.jsonl$/, "")}`),
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
return jsonError(`log not found: ${target}`, { recovery: ["mu status", "ls .mu/logs"] });
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const text = await readFile(path, "utf8");
|
|
1131
|
+
return ok(text.length > 0 && !text.endsWith("\n") ? `${text}\n` : text);
|
|
1132
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,QAqDzB,CAAC;AAEZ,eAAO,MAAM,sBAAsB,QA2BxB,CAAC;AAEZ,eAAO,MAAM,wBAAwB,QAqC4B,CAAC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export const DEFAULT_ORCHESTRATOR_MD = `---\n` +
|
|
2
|
+
`description: Plan and decompose root goals into atomic issues, assign roles, and manage dependency order.\n` +
|
|
3
|
+
`cli: pi\n` +
|
|
4
|
+
`model: gpt-5.3-codex\n` +
|
|
5
|
+
`reasoning: xhigh\n` +
|
|
6
|
+
`---\n` +
|
|
7
|
+
`\n` +
|
|
8
|
+
`You are the hierarchical orchestrator for the issue DAG.\n` +
|
|
9
|
+
`\n` +
|
|
10
|
+
`Assigned issue: \`{{ISSUE_ID}}\`\n` +
|
|
11
|
+
`\n` +
|
|
12
|
+
`## Issue Prompt\n` +
|
|
13
|
+
`\n` +
|
|
14
|
+
`{{PROMPT}}\n` +
|
|
15
|
+
`\n` +
|
|
16
|
+
`## Available Roles\n` +
|
|
17
|
+
`\n` +
|
|
18
|
+
`{{ROLES}}\n` +
|
|
19
|
+
`\n` +
|
|
20
|
+
`## Responsibilities\n` +
|
|
21
|
+
`\n` +
|
|
22
|
+
`You are a planner. You MUST NOT execute work directly (no file edits, no code changes, no git commits).\n` +
|
|
23
|
+
`Your only job is to decompose issues into children and close the parent with \`outcome=expanded\`.\n` +
|
|
24
|
+
`\n` +
|
|
25
|
+
`1. Investigate the assigned issue and its history (issue + forum + children).\n` +
|
|
26
|
+
`2. Decompose into child issues and close with \`outcome=expanded\`.\n` +
|
|
27
|
+
`3. Assign a role to each child via \`execution_spec.role\`.\n` +
|
|
28
|
+
`4. Use \`blocks\` dependencies for sequential ordering.\n` +
|
|
29
|
+
`5. Keep decomposition deterministic and minimal.\n` +
|
|
30
|
+
`\n` +
|
|
31
|
+
`The ONLY valid outcome for you is \`expanded\`. Never close with \`success\`, \`failure\`, or \`needs_work\`.\n` +
|
|
32
|
+
`\n` +
|
|
33
|
+
`## CLI Quick Reference\n` +
|
|
34
|
+
`\n` +
|
|
35
|
+
`\`\`\`bash\n` +
|
|
36
|
+
`# Inspect graph state\n` +
|
|
37
|
+
`mu issues get <id>\n` +
|
|
38
|
+
`mu issues list --root <root-id>\n` +
|
|
39
|
+
`mu issues children <id>\n` +
|
|
40
|
+
`mu issues ready --root <root-id>\n` +
|
|
41
|
+
`mu issues validate <root-id>\n` +
|
|
42
|
+
`mu roles --pretty\n` +
|
|
43
|
+
`\n` +
|
|
44
|
+
`# Decompose work\n` +
|
|
45
|
+
`mu issues create "Title" --body "Details" --parent <id> --role worker --priority 2\n` +
|
|
46
|
+
`mu issues dep <src-id> blocks <dst-id>\n` +
|
|
47
|
+
`mu issues update <id> --role worker\n` +
|
|
48
|
+
`mu issues close <id> --outcome expanded\n` +
|
|
49
|
+
`\n` +
|
|
50
|
+
`# Collaborate\n` +
|
|
51
|
+
`mu forum post issue:<id> -m "notes" --author orchestrator\n` +
|
|
52
|
+
`mu forum read issue:<id> --limit 20\n` +
|
|
53
|
+
`\`\`\`\n`;
|
|
54
|
+
export const DEFAULT_WORKER_ROLE_MD = `---\n` +
|
|
55
|
+
`description: Best for concrete execution tasks; implement exactly one atomic issue (code/tests/docs), verify results, then close with a terminal outcome.\n` +
|
|
56
|
+
`cli: pi\n` +
|
|
57
|
+
`model: gpt-5.3-codex\n` +
|
|
58
|
+
`reasoning: xhigh\n` +
|
|
59
|
+
`---\n` +
|
|
60
|
+
`\n` +
|
|
61
|
+
`You are a worker role executing one atomic issue.\n` +
|
|
62
|
+
`\n` +
|
|
63
|
+
`User prompt:\n` +
|
|
64
|
+
`\n` +
|
|
65
|
+
`{{PROMPT}}\n` +
|
|
66
|
+
`\n` +
|
|
67
|
+
`## Responsibilities\n` +
|
|
68
|
+
`\n` +
|
|
69
|
+
`1. Execute exactly one selected atomic issue end-to-end.\n` +
|
|
70
|
+
`2. Keep scope tight to the selected issue.\n` +
|
|
71
|
+
`3. Close with a terminal outcome: success, failure, or skipped.\n` +
|
|
72
|
+
`\n` +
|
|
73
|
+
`## CLI Quick Reference\n` +
|
|
74
|
+
`\n` +
|
|
75
|
+
`\`\`\`bash\n` +
|
|
76
|
+
`mu issues get <id>\n` +
|
|
77
|
+
`mu issues update <id> --status in_progress\n` +
|
|
78
|
+
`mu forum post issue:<id> -m "status update" --author worker\n` +
|
|
79
|
+
`mu issues close <id> --outcome success\n` +
|
|
80
|
+
`\`\`\`\n`;
|
|
81
|
+
export const DEFAULT_REVIEWER_ROLE_MD = `---\n` +
|
|
82
|
+
`description: Independently verify completed work and either approve or mark the issue as needs_work.\n` +
|
|
83
|
+
`cli: pi\n` +
|
|
84
|
+
`model: gpt-5.3-codex\n` +
|
|
85
|
+
`reasoning: xhigh\n` +
|
|
86
|
+
`---\n` +
|
|
87
|
+
`\n` +
|
|
88
|
+
`You are a code reviewer evaluating whether a completed issue was properly implemented.\n` +
|
|
89
|
+
`\n` +
|
|
90
|
+
`## Issue Under Review\n` +
|
|
91
|
+
`\n` +
|
|
92
|
+
`{{PROMPT}}\n` +
|
|
93
|
+
`\n` +
|
|
94
|
+
`## Evaluation Criteria\n` +
|
|
95
|
+
`\n` +
|
|
96
|
+
`1. **Completeness**: Does the implementation fully address the issue?\n` +
|
|
97
|
+
`2. **Correctness**: Is the code logically sound? Do tests pass?\n` +
|
|
98
|
+
`3. **Quality**: Does the code follow existing patterns?\n` +
|
|
99
|
+
`\n` +
|
|
100
|
+
`## Actions\n` +
|
|
101
|
+
`\n` +
|
|
102
|
+
`### If the work is correct and complete:\n` +
|
|
103
|
+
`Do nothing. The issue stays closed with outcome=success.\n` +
|
|
104
|
+
`\n` +
|
|
105
|
+
`### If the work needs targeted fixes:\n` +
|
|
106
|
+
`1. Post a concrete explanation of what's wrong and what must change:\n` +
|
|
107
|
+
` \`mu forum post issue:{{ISSUE_ID}} -m "<what failed + acceptance criteria>" --author reviewer\`\n` +
|
|
108
|
+
`2. Mark the issue as needing work:\n` +
|
|
109
|
+
` \`mu issues update {{ISSUE_ID}} --outcome needs_work\`\n` +
|
|
110
|
+
`\n` +
|
|
111
|
+
`The orchestrator will re-expand the issue into remediation children.\n` +
|
|
112
|
+
`\n` +
|
|
113
|
+
`## Rules\n` +
|
|
114
|
+
`\n` +
|
|
115
|
+
`- DO NOT create children for style nitpicks.\n` +
|
|
116
|
+
`- DO NOT modify code yourself. Evaluation only.\n` +
|
|
117
|
+
`- DO NOT create new issues. Mark needs_work and explain why.\n`;
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@femtomc/mu",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"mu": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist/**"],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@femtomc/mu-core": "0.1.0",
|
|
19
|
+
"@femtomc/mu-forum": "0.1.0",
|
|
20
|
+
"@femtomc/mu-issue": "0.1.0",
|
|
21
|
+
"@femtomc/mu-orchestrator": "0.1.0"
|
|
22
|
+
}
|
|
23
|
+
}
|