@mu-crew/mu 3.3.0 → 3.4.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 -31
- package/dist/cli.js +186 -20
- package/dist/cli.js.map +1 -1
- package/dist/extension/mu-pi.js +103 -74
- package/dist/extension/mu-pi.js.map +1 -1
- package/dist/index.d.ts +33 -4
- package/dist/index.js +70 -4
- package/dist/index.js.map +1 -1
- package/docs/VISION.md +3 -1
- package/docs/VOCABULARY.md +4 -1
- package/docs/architecture/dag.md +3 -3
- package/docs/guide/delegate.md +30 -3
- package/docs/reference/env.md +1 -0
- package/docs/reference/naming.md +18 -0
- package/docs/specs/2026-09-29-task-substates.md +1 -1
- package/package.json +1 -1
- package/prompts/mu-debug.md +8 -0
- package/prompts/mu-sweep.md +8 -0
- package/prompts/mu-until.md +8 -0
- package/prompts/ultrathink.md +2 -1
- package/skills/mu/SKILL.md +70 -31
- package/skills/mu/recipes/adversarial-review.md +27 -19
- package/skills/mu/recipes/{triage.md → backlog-triage.md} +9 -4
- package/skills/mu/recipes/codemode-driver.md +68 -20
- package/skills/mu/recipes/deep-research.md +23 -8
- package/skills/mu/recipes/fan-out.md +15 -11
- package/skills/mu/recipes/findings.md +103 -0
- package/skills/mu/recipes/hypothesis-panel.md +18 -13
- package/skills/mu/recipes/long-run.md +4 -4
- package/skills/mu/recipes/loop-until-done.md +7 -3
- package/skills/mu/recipes/orchestrator-loop.md +38 -27
- package/skills/mu/recipes/plan.md +8 -3
- package/skills/mu/recipes/recovery.md +2 -0
- package/skills/mu/recipes/refute.md +38 -36
- package/skills/mu/recipes/remote-workers.md +7 -2
- package/skills/mu/recipes/review-panel.md +36 -31
- package/skills/mu/recipes/rules-audit.md +30 -17
- package/skills/mu/recipes/tasks-or-calls.md +67 -0
- package/skills/mu/recipes/tournament.md +13 -10
- package/skills/mu/recipes/ultrathink.md +20 -11
- package/skills/mu/recipes/watcher.md +4 -1
- package/skills/mu/recipes/waves.md +6 -5
- package/skills/mu/recipes/worker.md +3 -1
package/README.md
CHANGED
|
@@ -20,9 +20,9 @@ in-progress, and blocked tasks, the log tail, workspaces, and doctor.*
|
|
|
20
20
|
socket inside each agent, so send, state, wait, and abort are exact,
|
|
21
21
|
locally and over ssh. The pane stays pi's normal TUI.
|
|
22
22
|
|
|
23
|
-
mu persists state and coordinates handoffs.
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
mu persists state and coordinates handoffs. Every read path has
|
|
24
|
+
`--json`, and every change goes through a typed CLI verb. See
|
|
25
|
+
[What mu is not](#what-mu-is-not).
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
@@ -71,24 +71,6 @@ For one helper, ask pi to delegate (`mu_delegate` ships with `mu link
|
|
|
71
71
|
pi`), or spawn into the reserved `scratch` workstream:
|
|
72
72
|
`mu agent spawn scout-1 -w scratch`.
|
|
73
73
|
|
|
74
|
-
## mu delegates vs hidden subagents
|
|
75
|
-
|
|
76
|
-
Claude Code, Codex, and pi-subagents delegate to a hidden child process
|
|
77
|
-
and return only its result. A mu delegate is an ordinary agent in a
|
|
78
|
-
pane.
|
|
79
|
-
|
|
80
|
-
| | Hidden subagent | mu delegate |
|
|
81
|
-
| ----------------------- | ------------------------ | ----------- |
|
|
82
|
-
| Visibility | none while it runs | a pane; attach and watch |
|
|
83
|
-
| Steer mid-run | no | `mu agent send`; stop with `mu agent abort` |
|
|
84
|
-
| Keep talking after it answers | no | yes, with `keep: true` |
|
|
85
|
-
| Transcript | collapses into a result | a normal pi session log |
|
|
86
|
-
| If the work grows | re-brief a new agent | same task DAG and workspaces |
|
|
87
|
-
| Cost | light | one pane and one pi process |
|
|
88
|
-
|
|
89
|
-
For many tiny calls, a hidden subagent is lighter. mu bets that seeing
|
|
90
|
-
and steering agent work is worth a pane.
|
|
91
|
-
|
|
92
74
|
## Ultrathink, the mu way
|
|
93
75
|
|
|
94
76
|
Long agent runs fail in two ways: the agent stops at 35 of 50 items and
|
|
@@ -98,19 +80,28 @@ agents, and have other agents try to refute each result before it
|
|
|
98
80
|
counts. Claude Code's ultracode does this with a hidden script. mu does
|
|
99
81
|
it in the open:
|
|
100
82
|
|
|
101
|
-
- **The plan is the DAG.** Every unit,
|
|
83
|
+
- **The plan is the DAG.** Every unit, review gate, finding and round
|
|
102
84
|
is a task before an agent spawns. `mu state` shows 35 of 50 done, so
|
|
103
85
|
nobody can declare victory early.
|
|
104
|
-
- **
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
86
|
+
- **Findings are tasks.** A reviewer records each problem as a task in
|
|
87
|
+
triage ([findings](skills/mu/recipes/findings.md)), and it blocks the
|
|
88
|
+
review until someone accepts it as work or closes it rejected or
|
|
89
|
+
duplicate. The graph holds the whole review.
|
|
90
|
+
- **Checks are calls.** Refuters, claim checkers and judges are
|
|
91
|
+
[delegate calls](skills/mu/recipes/tasks-or-calls.md): fresh agents,
|
|
92
|
+
ideally on another model, whose verdicts land on the task they
|
|
93
|
+
judged. A 50-unit run adds about 50 units, 50 review gates and the
|
|
94
|
+
findings that survive, not a task per check.
|
|
108
95
|
- **Every agent is a pane.** Attach to any worker, reviewer, or judge
|
|
109
96
|
mid-run and steer it.
|
|
110
97
|
- **Nothing lives only in a context window.** Verdicts, evidence, and
|
|
111
98
|
stop rules are task notes. The run survives compaction, crashes, and
|
|
112
99
|
a new orchestrator.
|
|
113
100
|
|
|
101
|
+
Not every review needs a record. Reviewing someone's PR or a doc runs
|
|
102
|
+
as delegates only: findings come back in their answers, and nothing
|
|
103
|
+
lands in the ops log.
|
|
104
|
+
|
|
114
105
|
Ask your orchestrator to *ultrathink* a job, and it follows
|
|
115
106
|
[skills/mu/recipes/ultrathink.md](skills/mu/recipes/ultrathink.md).
|
|
116
107
|
That recipe composes smaller ones you can also use on their own:
|
|
@@ -118,23 +109,46 @@ That recipe composes smaller ones you can also use on their own:
|
|
|
118
109
|
| Recipe | Shape |
|
|
119
110
|
| ------ | ----- |
|
|
120
111
|
| [fan-out](skills/mu/recipes/fan-out.md) | one task per unit, merged as each closes |
|
|
121
|
-
| [adversarial-review](skills/mu/recipes/adversarial-review.md) | a fresh agent tries to reject
|
|
122
|
-
| [refute](skills/mu/recipes/refute.md) | finders,
|
|
112
|
+
| [adversarial-review](skills/mu/recipes/adversarial-review.md) | a review gate per unit: a fresh agent tries to reject it before it ships |
|
|
113
|
+
| [refute](skills/mu/recipes/refute.md) | finders record findings, one refuter call per finding, report from the graph |
|
|
123
114
|
| [hypothesis-panel](skills/mu/recipes/hypothesis-panel.md) | independent theories from separate evidence, each attacked |
|
|
124
115
|
| [tournament](skills/mu/recipes/tournament.md) | competing attempts, judged in pairs |
|
|
125
116
|
| [loop-until-done](skills/mu/recipes/loop-until-done.md) | rounds until a stop rule written up front holds |
|
|
126
|
-
| [triage](skills/mu/recipes/triage.md) | quarantined readers classify, a trusted actor acts |
|
|
117
|
+
| [backlog-triage](skills/mu/recipes/backlog-triage.md) | quarantined readers classify, a trusted actor acts |
|
|
127
118
|
| [deep-research](skills/mu/recipes/deep-research.md) | searchers by angle, one checker per claim, a cited report |
|
|
128
|
-
| [review-panel](skills/mu/recipes/review-panel.md) | one reviewer per angle on a diff
|
|
119
|
+
| [review-panel](skills/mu/recipes/review-panel.md) | one reviewer per angle on a diff; delegates by default, tasks when fixes follow |
|
|
129
120
|
| [rules-audit](skills/mu/recipes/rules-audit.md) | one checker per AGENTS.md rule plus a skeptic; or mine repeated corrections into new rules |
|
|
130
121
|
|
|
131
122
|
In pi, `mu link pi` adds them as commands: `/ultrathink <job>`,
|
|
132
123
|
`/mu-research <question>`, `/mu-review [target]`, `/mu-refute <scope>`,
|
|
133
|
-
`/mu-tournament <task>`,
|
|
124
|
+
`/mu-tournament <task>`, `/mu-rules-audit [check <target> | mine]`,
|
|
125
|
+
`/mu-debug <symptom>`, `/mu-sweep <change>`, and `/mu-until <stop rule>`.
|
|
134
126
|
|
|
135
127
|
All recipes, including the orchestrator loop and remote workers, are
|
|
136
128
|
in [skills/mu/recipes/](skills/mu/recipes/).
|
|
137
129
|
|
|
130
|
+
## What mu is not
|
|
131
|
+
|
|
132
|
+
- **Not a hidden subagent.** Every agent, including a one-off
|
|
133
|
+
`mu_delegate` helper, is an ordinary agent in a pane: attach, steer,
|
|
134
|
+
abort, or keep talking to it. That costs a pane and a pi process per
|
|
135
|
+
agent; for many tiny calls a hidden subagent is lighter. See
|
|
136
|
+
[the comparison](docs/guide/delegate.md#delegates-vs-hidden-subagents).
|
|
137
|
+
- **Not a workflow engine.** No DSL, no hidden plan, no script runner.
|
|
138
|
+
The plan is the task DAG; recipes are instructions an agent follows,
|
|
139
|
+
and every step is a CLI verb.
|
|
140
|
+
- **Not a model router.** mu does not pick models, thinking levels, or
|
|
141
|
+
tools. Pass them in the spawn command (`--cli pi_big`).
|
|
142
|
+
- **Not a chat bus.** Agents coordinate through task notes, claims, and
|
|
143
|
+
the ops log, not messages to each other.
|
|
144
|
+
- **Not a daemon or a service.** No background process, config file, web
|
|
145
|
+
UI, or RPC. State is one SQLite file; sync rides on ordinary commands.
|
|
146
|
+
- **Not a test runner or a judge.** `--evidence` records what an agent
|
|
147
|
+
observed; checking it is the orchestrator's job, and the recipes say how.
|
|
148
|
+
|
|
149
|
+
The full list, with reasons: [VISION § What it is not](docs/VISION.md#what-it-is-not)
|
|
150
|
+
and [ROADMAP § Anti-feature pledges](docs/ROADMAP.md#anti-feature-pledges).
|
|
151
|
+
|
|
138
152
|
## Documentation
|
|
139
153
|
|
|
140
154
|
- [Getting started](docs/guide/getting-started.md): the first
|
package/dist/cli.js
CHANGED
|
@@ -393,7 +393,7 @@ var init_status = __esm({
|
|
|
393
393
|
TASK_STATUSES = ["OPEN", "IN_PROGRESS", "CLOSED"];
|
|
394
394
|
TASK_STATUS_LIST = TASK_STATUSES.join(" | ");
|
|
395
395
|
TASK_SUBSTATES = {
|
|
396
|
-
OPEN: ["todo", "parked"],
|
|
396
|
+
OPEN: ["todo", "parked", "triage"],
|
|
397
397
|
IN_PROGRESS: ["active"],
|
|
398
398
|
CLOSED: ["done", "rejected", "wontfix", "duplicate", "superseded"]
|
|
399
399
|
};
|
|
@@ -607,7 +607,7 @@ CREATE VIEW ready AS
|
|
|
607
607
|
SELECT t.*
|
|
608
608
|
FROM tasks t
|
|
609
609
|
WHERE t.status = 'OPEN'
|
|
610
|
-
AND t.substate
|
|
610
|
+
AND t.substate NOT IN ('parked', 'triage')
|
|
611
611
|
AND NOT EXISTS (
|
|
612
612
|
SELECT 1
|
|
613
613
|
FROM task_edges e
|
|
@@ -5850,7 +5850,7 @@ var init_id = __esm({
|
|
|
5850
5850
|
});
|
|
5851
5851
|
|
|
5852
5852
|
// src/tasks/errors.ts
|
|
5853
|
-
var TaskNotFoundError, TaskIdInvalidError, TaskExistsError, TaskNotInWorkstreamError, TaskAlreadyOwnedError, ClaimerNotRegisteredError, TaskClaimStaleWorkspaceError, CycleError, ReaperDetectedDuringWaitError, StallDetectedDuringWaitError, CrossWorkstreamEdgeError, SubstateReasonRequiredError, TaskParkStateError, TaskParkedError, InvalidSubstateError;
|
|
5853
|
+
var TaskNotFoundError, TaskIdInvalidError, TaskExistsError, TaskNotInWorkstreamError, TaskAlreadyOwnedError, ClaimerNotRegisteredError, TaskClaimStaleWorkspaceError, CycleError, ReaperDetectedDuringWaitError, StallDetectedDuringWaitError, CrossWorkstreamEdgeError, SubstateReasonRequiredError, TaskParkStateError, TaskInTriageError, TaskParkedError, InvalidSubstateError;
|
|
5854
5854
|
var init_errors2 = __esm({
|
|
5855
5855
|
"src/tasks/errors.ts"() {
|
|
5856
5856
|
"use strict";
|
|
@@ -6198,6 +6198,30 @@ var init_errors2 = __esm({
|
|
|
6198
6198
|
];
|
|
6199
6199
|
}
|
|
6200
6200
|
};
|
|
6201
|
+
TaskInTriageError = class extends Error {
|
|
6202
|
+
constructor(taskId, workstream) {
|
|
6203
|
+
super(`task ${taskId} is in triage (OPEN/triage); accept it or claim with --force`);
|
|
6204
|
+
this.taskId = taskId;
|
|
6205
|
+
this.workstream = workstream;
|
|
6206
|
+
}
|
|
6207
|
+
taskId;
|
|
6208
|
+
workstream;
|
|
6209
|
+
name = "TaskInTriageError";
|
|
6210
|
+
errorNextSteps() {
|
|
6211
|
+
const ws = this.workstream;
|
|
6212
|
+
return [
|
|
6213
|
+
{
|
|
6214
|
+
intent: "Accept it as work, then claim",
|
|
6215
|
+
command: `mu task accept ${this.taskId} -w ${ws}`
|
|
6216
|
+
},
|
|
6217
|
+
{
|
|
6218
|
+
intent: "Decline it",
|
|
6219
|
+
command: `mu task close ${this.taskId} --as rejected --why "<reason>" -w ${ws}`
|
|
6220
|
+
},
|
|
6221
|
+
{ intent: "Claim it anyway", command: `mu task claim ${this.taskId} --force -w ${ws}` }
|
|
6222
|
+
];
|
|
6223
|
+
}
|
|
6224
|
+
};
|
|
6201
6225
|
TaskParkedError = class extends Error {
|
|
6202
6226
|
constructor(taskId, workstream) {
|
|
6203
6227
|
super(`task ${taskId} is parked (OPEN/parked); unpark it or claim with --force`);
|
|
@@ -6760,8 +6784,17 @@ function addTaskImpl(db, opts) {
|
|
|
6760
6784
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6761
6785
|
const insertResult = db.prepare(
|
|
6762
6786
|
`INSERT INTO tasks (workstream_id, local_id, title, status, substate, impact, effort_days, created_at, updated_at)
|
|
6763
|
-
VALUES (?, ?, ?, 'OPEN',
|
|
6764
|
-
).run(
|
|
6787
|
+
VALUES (?, ?, ?, 'OPEN', ?, ?, ?, ?, ?)`
|
|
6788
|
+
).run(
|
|
6789
|
+
wsId,
|
|
6790
|
+
opts.localId,
|
|
6791
|
+
opts.title,
|
|
6792
|
+
opts.triage === true ? "triage" : "todo",
|
|
6793
|
+
opts.impact,
|
|
6794
|
+
opts.effortDays,
|
|
6795
|
+
now,
|
|
6796
|
+
now
|
|
6797
|
+
);
|
|
6765
6798
|
const newTaskId = Number(insertResult.lastInsertRowid);
|
|
6766
6799
|
if (opts.blockedBy && opts.blockedBy.length > 0) {
|
|
6767
6800
|
const blockerLookupSameWs = db.prepare(
|
|
@@ -7049,6 +7082,28 @@ function parkTask(db, localId, opts) {
|
|
|
7049
7082
|
})()
|
|
7050
7083
|
);
|
|
7051
7084
|
}
|
|
7085
|
+
function acceptTask(db, localId, opts) {
|
|
7086
|
+
return withOpContext(
|
|
7087
|
+
db,
|
|
7088
|
+
{ intent: "task.accept", group: "new" },
|
|
7089
|
+
() => db.transaction(() => {
|
|
7090
|
+
const before = getTask(db, localId, opts.workstream);
|
|
7091
|
+
if (!before) throw new TaskNotFoundError(localId);
|
|
7092
|
+
if (before.status !== "OPEN" || before.substate !== "triage") {
|
|
7093
|
+
return {
|
|
7094
|
+
previousStatus: before.status,
|
|
7095
|
+
status: before.status,
|
|
7096
|
+
previousSubstate: before.substate,
|
|
7097
|
+
substate: before.substate,
|
|
7098
|
+
changed: false
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
const r = setTaskStatus(db, localId, "OPEN", { workstream: opts.workstream });
|
|
7102
|
+
recordEvidenceNote(db, localId, before.workstreamName, "ACCEPT", opts);
|
|
7103
|
+
return r;
|
|
7104
|
+
})()
|
|
7105
|
+
);
|
|
7106
|
+
}
|
|
7052
7107
|
function unparkTask(db, localId, opts) {
|
|
7053
7108
|
return withOpContext(
|
|
7054
7109
|
db,
|
|
@@ -7191,6 +7246,9 @@ function assertNotParked(task, opts) {
|
|
|
7191
7246
|
if (task.status === "OPEN" && task.substate === "parked" && opts.force !== true) {
|
|
7192
7247
|
throw new TaskParkedError(task.name, task.workstreamName);
|
|
7193
7248
|
}
|
|
7249
|
+
if (task.status === "OPEN" && task.substate === "triage" && opts.force !== true) {
|
|
7250
|
+
throw new TaskInTriageError(task.name, task.workstreamName);
|
|
7251
|
+
}
|
|
7194
7252
|
}
|
|
7195
7253
|
async function resolveActorIdentity() {
|
|
7196
7254
|
const worker = await resolveWorkerIdentity();
|
|
@@ -10083,6 +10141,10 @@ async function loadWorkstreamSnapshotFast(db, workstream, opts = {}) {
|
|
|
10083
10141
|
blocked: listBlocked(db, workstream),
|
|
10084
10142
|
recentClosed: listRecentClosed(db, workstream),
|
|
10085
10143
|
parkedCount: listTasks(db, workstream, { status: "OPEN", substate: "parked" }).length,
|
|
10144
|
+
triage: listTasks(db, workstream, { status: "OPEN", substate: "triage" }),
|
|
10145
|
+
taskCount: db.prepare(
|
|
10146
|
+
"SELECT count(*) AS n FROM tasks t JOIN workstreams w ON w.id = t.workstream_id WHERE w.name = ?"
|
|
10147
|
+
).get(workstream).n,
|
|
10086
10148
|
allTasks: opts.withAllTasks === true ? listTasks(db, workstream) : [],
|
|
10087
10149
|
workspaces: listWorkspaces2(db, workstream),
|
|
10088
10150
|
workspaceOrphans: listWorkspaceOrphans(db, workstream),
|
|
@@ -10156,6 +10218,8 @@ function minimalSnapshot(workstream) {
|
|
|
10156
10218
|
blocked: [],
|
|
10157
10219
|
recentClosed: [],
|
|
10158
10220
|
parkedCount: 0,
|
|
10221
|
+
triage: [],
|
|
10222
|
+
taskCount: 0,
|
|
10159
10223
|
allTasks: [],
|
|
10160
10224
|
workspaces: [],
|
|
10161
10225
|
workspaceOrphans: [],
|
|
@@ -11324,7 +11388,7 @@ function colorPair(pair) {
|
|
|
11324
11388
|
const text2 = formatPair(pair);
|
|
11325
11389
|
switch (pair.status) {
|
|
11326
11390
|
case "OPEN":
|
|
11327
|
-
return pair.substate === "parked" ? pc.dim(text2) : pc.cyan(text2);
|
|
11391
|
+
return pair.substate === "parked" || pair.substate === "triage" ? pc.dim(text2) : pc.cyan(text2);
|
|
11328
11392
|
case "IN_PROGRESS":
|
|
11329
11393
|
return pc.yellow(text2);
|
|
11330
11394
|
case "CLOSED":
|
|
@@ -11344,7 +11408,7 @@ function colorStatus(status) {
|
|
|
11344
11408
|
function inkColorForPair(pair) {
|
|
11345
11409
|
switch (pair.status) {
|
|
11346
11410
|
case "OPEN":
|
|
11347
|
-
return pair.substate === "parked" ? "gray" : "cyan";
|
|
11411
|
+
return pair.substate === "parked" || pair.substate === "triage" ? "gray" : "cyan";
|
|
11348
11412
|
case "IN_PROGRESS":
|
|
11349
11413
|
return "yellow";
|
|
11350
11414
|
case "CLOSED":
|
|
@@ -16537,6 +16601,7 @@ function yankCommandForTask2(t, ws) {
|
|
|
16537
16601
|
switch (t.status) {
|
|
16538
16602
|
case "OPEN":
|
|
16539
16603
|
if (t.substate === "parked") return `mu task unpark ${t.name} -w ${ws}`;
|
|
16604
|
+
if (t.substate === "triage") return `mu task accept ${t.name} -w ${ws}`;
|
|
16540
16605
|
return t.ownerName === null ? `mu task claim ${t.name} -w ${ws}` : `mu task release ${t.name} -w ${ws}`;
|
|
16541
16606
|
case "IN_PROGRESS":
|
|
16542
16607
|
return `mu task close ${t.name} -w ${ws} --evidence "..."`;
|
|
@@ -17596,6 +17661,7 @@ function snapshotKey(s) {
|
|
|
17596
17661
|
blocked: s.blocked.map(taskKey2),
|
|
17597
17662
|
recentClosed: s.recentClosed.map(taskKey2),
|
|
17598
17663
|
parkedCount: s.parkedCount,
|
|
17664
|
+
triage: s.triage.map(taskKey2),
|
|
17599
17665
|
allTasks: s.allTasks.map(taskKey2),
|
|
17600
17666
|
commitsBackend: s.commitsBackend ?? null,
|
|
17601
17667
|
recentCommits: s.recentCommits.map((c) => [c.sha, c.subject, c.author, c.relTime]),
|
|
@@ -18769,7 +18835,8 @@ async function cmdTaskAdd(db, localId, opts) {
|
|
|
18769
18835
|
title: opts.title,
|
|
18770
18836
|
impact: opts.impact,
|
|
18771
18837
|
effortDays: opts.effortDays,
|
|
18772
|
-
...hasBlockers ? { blockedBy } : {}
|
|
18838
|
+
...hasBlockers ? { blockedBy } : {},
|
|
18839
|
+
...opts.triage === true ? { triage: true } : {}
|
|
18773
18840
|
});
|
|
18774
18841
|
const createdNote = opts.note !== void 0 ? addNote(db, createdTask.name, unescapeNoteText(opts.note), {
|
|
18775
18842
|
author: initialNoteAuthor,
|
|
@@ -18793,7 +18860,7 @@ async function cmdTaskAdd(db, localId, opts) {
|
|
|
18793
18860
|
intent: "Add a blocker",
|
|
18794
18861
|
command: `mu task block ${task.name} --by <other-id> -w ${workstream}`
|
|
18795
18862
|
},
|
|
18796
|
-
{
|
|
18863
|
+
opts.triage === true ? { intent: "Accept it as work", command: `mu task accept ${task.name} -w ${workstream}` } : {
|
|
18797
18864
|
intent: "Claim and start",
|
|
18798
18865
|
command: `mu task claim ${task.name} -w ${workstream} --self (or --for <worker>)`
|
|
18799
18866
|
}
|
|
@@ -18916,6 +18983,12 @@ async function cmdTaskShow(db, rawId, opts = {}) {
|
|
|
18916
18983
|
if (task.substate === "parked") {
|
|
18917
18984
|
console.log("");
|
|
18918
18985
|
printNextSteps([{ intent: "Return it to next", command: `mu task unpark ${ref}` }]);
|
|
18986
|
+
} else if (task.substate === "triage") {
|
|
18987
|
+
console.log("");
|
|
18988
|
+
printNextSteps([
|
|
18989
|
+
{ intent: "Accept it as work", command: `mu task accept ${ref}` },
|
|
18990
|
+
{ intent: "Decline it", command: `mu task close ${ref} --as rejected --why "<reason>"` }
|
|
18991
|
+
]);
|
|
18919
18992
|
} else if (task.status === "CLOSED" && task.substate !== "done") {
|
|
18920
18993
|
console.log("");
|
|
18921
18994
|
printNextSteps([{ intent: "Reopen it", command: `mu task open ${ref}` }]);
|
|
@@ -19040,6 +19113,17 @@ async function cmdTaskList(db, opts) {
|
|
|
19040
19113
|
const basis = relTimeBasisForSort(sortKey);
|
|
19041
19114
|
if (basis !== null) tableOpts.relTimeBasis = basis;
|
|
19042
19115
|
console.log(formatTaskListTable(tasks, tableOpts));
|
|
19116
|
+
const first = tasks.find((t) => t.status === "OPEN" && t.substate === "triage");
|
|
19117
|
+
if (first !== void 0) {
|
|
19118
|
+
printNextSteps([
|
|
19119
|
+
{ intent: "Accept as work", command: `mu task accept ${first.name} -w ${workstream}` },
|
|
19120
|
+
{
|
|
19121
|
+
intent: "Decline",
|
|
19122
|
+
command: `mu task close ${first.name} --as rejected|duplicate --why "<reason>" -w ${workstream}`
|
|
19123
|
+
},
|
|
19124
|
+
{ intent: "Read the evidence", command: `mu task notes ${first.name} -w ${workstream}` }
|
|
19125
|
+
]);
|
|
19126
|
+
}
|
|
19043
19127
|
}
|
|
19044
19128
|
async function cmdTaskNext(db, opts) {
|
|
19045
19129
|
const workstream = await resolveWorkstream(opts.workstream);
|
|
@@ -19060,16 +19144,26 @@ async function cmdTaskNext(db, opts) {
|
|
|
19060
19144
|
}
|
|
19061
19145
|
if (tasks.length === 0) {
|
|
19062
19146
|
const parked = listTasks(db, workstream, { substate: "parked" }).length;
|
|
19063
|
-
|
|
19147
|
+
const triage = listTasks(db, workstream, { substate: "triage" }).length;
|
|
19148
|
+
if (parked === 0 && triage === 0) {
|
|
19064
19149
|
console.log(pc.dim("(no ready tasks)"));
|
|
19065
19150
|
return;
|
|
19066
19151
|
}
|
|
19067
|
-
|
|
19152
|
+
const held = [parked > 0 ? `${parked} parked` : "", triage > 0 ? `${triage} in triage` : ""].filter(Boolean).join(", ");
|
|
19153
|
+
console.log(pc.dim(`(no ready tasks; ${held})`));
|
|
19068
19154
|
printNextSteps([
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19072
|
-
|
|
19155
|
+
...triage > 0 ? [
|
|
19156
|
+
{
|
|
19157
|
+
intent: "Triage findings",
|
|
19158
|
+
command: `mu task list --substate triage -w ${workstream}`
|
|
19159
|
+
}
|
|
19160
|
+
] : [],
|
|
19161
|
+
...parked > 0 ? [
|
|
19162
|
+
{
|
|
19163
|
+
intent: "List parked tasks",
|
|
19164
|
+
command: `mu task list --substate parked -w ${workstream}`
|
|
19165
|
+
}
|
|
19166
|
+
] : []
|
|
19073
19167
|
]);
|
|
19074
19168
|
return;
|
|
19075
19169
|
}
|
|
@@ -19638,7 +19732,14 @@ async function cmdTaskClose(db, rawId, opts = {}) {
|
|
|
19638
19732
|
const taskRow = getTask(db, localId, ws);
|
|
19639
19733
|
const r = closeTask(db, localId, sdkOpts);
|
|
19640
19734
|
if ("skipped" in r) {
|
|
19735
|
+
const inTriage = r.blockingIds.filter((id) => getTask(db, id, ws)?.substate === "triage");
|
|
19641
19736
|
const blockingNextSteps = [
|
|
19737
|
+
...inTriage.length > 0 ? [
|
|
19738
|
+
{
|
|
19739
|
+
intent: `Triage ${inTriage.length} blocking finding(s) (accept, or close --as rejected|duplicate)`,
|
|
19740
|
+
command: `mu task list --substate triage -w ${ws}`
|
|
19741
|
+
}
|
|
19742
|
+
] : [],
|
|
19642
19743
|
{
|
|
19643
19744
|
intent: "Watch the remaining blockers (returns when one closes)",
|
|
19644
19745
|
command: `mu task wait ${r.blockingIds.join(" ")} -w ${ws} --first --any --on-stall exit`
|
|
@@ -19771,6 +19872,32 @@ async function cmdTaskPark(db, rawId, opts) {
|
|
|
19771
19872
|
}
|
|
19772
19873
|
printNextSteps(nextSteps3);
|
|
19773
19874
|
}
|
|
19875
|
+
async function cmdTaskAccept(db, rawId, opts = {}) {
|
|
19876
|
+
const { name: localId } = await resolveEntityRef(db, rawId, opts, "task");
|
|
19877
|
+
assertTaskInWorkstream(db, localId, opts.workstream);
|
|
19878
|
+
const ws = await resolveWorkstream(opts.workstream);
|
|
19879
|
+
const sdkOpts = { workstream: ws };
|
|
19880
|
+
if (opts.evidence !== void 0) sdkOpts.evidence = opts.evidence;
|
|
19881
|
+
const r = acceptTask(db, localId, sdkOpts);
|
|
19882
|
+
const nextSteps3 = [
|
|
19883
|
+
{
|
|
19884
|
+
intent: "Claim it",
|
|
19885
|
+
command: `mu task claim ${localId} -w ${ws} (--self / --for <worker>)`
|
|
19886
|
+
},
|
|
19887
|
+
{ intent: "Triage the next one", command: `mu task list --substate triage -w ${ws}` }
|
|
19888
|
+
];
|
|
19889
|
+
if (opts.json) {
|
|
19890
|
+
emitJson({ taskName: localId, ...r, nextSteps: nextSteps3 });
|
|
19891
|
+
return;
|
|
19892
|
+
}
|
|
19893
|
+
if (!r.changed) {
|
|
19894
|
+
const pair = formatPair({ status: r.status, substate: r.substate });
|
|
19895
|
+
console.log(pc.dim(`${localId} is ${pair}, not in triage (no-op)`));
|
|
19896
|
+
} else {
|
|
19897
|
+
console.log(`Accepted ${pc.bold(localId)} ${pc.dim(`(${transition(r)})`)}`);
|
|
19898
|
+
}
|
|
19899
|
+
printNextSteps(nextSteps3);
|
|
19900
|
+
}
|
|
19774
19901
|
async function cmdTaskUnpark(db, rawId, opts = {}) {
|
|
19775
19902
|
const { name: localId } = await resolveEntityRef(db, rawId, opts, "task");
|
|
19776
19903
|
assertTaskInWorkstream(db, localId, opts.workstream);
|
|
@@ -19849,7 +19976,10 @@ function wireTaskCommands(program) {
|
|
|
19849
19976
|
).requiredOption("-t, --title <title>", "task title").requiredOption("-i, --impact <n>", "impact 1..100", parseImpact).requiredOption("-e, --effort-days <days>", "effort in days (>0)", parsePositiveNumber).option(
|
|
19850
19977
|
"-b, --blocked-by <ids...>",
|
|
19851
19978
|
"task ids that block this one (repeat or comma-separate; or both)"
|
|
19852
|
-
).option("--note <text>", "append an initial note after creating the task").option("--note-author <name>", "author label for --note (default: current actor)").option(
|
|
19979
|
+
).option("--note <text>", "append an initial note after creating the task").option("--note-author <name>", "author label for --note (default: current actor)").option(
|
|
19980
|
+
"--triage",
|
|
19981
|
+
"start as OPEN/triage: a proposal (e.g. a review finding) kept out of next/claim until `mu task accept`"
|
|
19982
|
+
).option(...WORKSTREAM_OPT).option(...JSON_OPT).action(function(id) {
|
|
19853
19983
|
const opts = this.opts();
|
|
19854
19984
|
return handle((db) => cmdTaskAdd(db, id, opts), this)();
|
|
19855
19985
|
});
|
|
@@ -19954,6 +20084,12 @@ function wireTaskCommands(program) {
|
|
|
19954
20084
|
const opts = this.opts();
|
|
19955
20085
|
return handle((db) => cmdTaskPark(db, id, opts), this)();
|
|
19956
20086
|
});
|
|
20087
|
+
task.command("accept <id>").description(
|
|
20088
|
+
"Accept an OPEN/triage task (a proposed finding) as work: OPEN/todo, back in `next`. No-op on any other state. Decline with `close --as rejected|duplicate --why`."
|
|
20089
|
+
).option(...WORKSTREAM_OPT).option(...EVIDENCE_OPT).option(...JSON_OPT).action(function(id) {
|
|
20090
|
+
const opts = this.opts();
|
|
20091
|
+
return handle((db) => cmdTaskAccept(db, id, opts), this)();
|
|
20092
|
+
});
|
|
19957
20093
|
task.command("unpark <id>").description("Return a parked task to OPEN (back in `next`). No-op on any other state.").option(...WORKSTREAM_OPT).option(...EVIDENCE_OPT).option(...JSON_OPT).action(function(id) {
|
|
19958
20094
|
const opts = this.opts();
|
|
19959
20095
|
return handle((db) => cmdTaskUnpark(db, id, opts), this)();
|
|
@@ -19978,7 +20114,7 @@ function wireTaskCommands(program) {
|
|
|
19978
20114
|
).option(
|
|
19979
20115
|
"--actor <name>",
|
|
19980
20116
|
"override the actor name used for the log (only valid with --self; defaults to pane title or $USER)"
|
|
19981
|
-
).option("--force", "claim even when the task is parked (OPEN/parked)").option(
|
|
20117
|
+
).option("--force", "claim even when the task is parked or in triage (OPEN/parked, OPEN/triage)").option(
|
|
19982
20118
|
"--strict-staleness",
|
|
19983
20119
|
"refuse --for dispatch when the target agent's workspace is stale (default: warn and proceed)"
|
|
19984
20120
|
).option(...WORKSTREAM_OPT).option(...EVIDENCE_OPT).option(...JSON_OPT).action(function(taskId) {
|
|
@@ -21395,7 +21531,7 @@ function classifyError(err) {
|
|
|
21395
21531
|
}
|
|
21396
21532
|
if (err instanceof NameAmbiguousError || err instanceof AgentExistsError || err instanceof TaskExistsError || err instanceof TaskAlreadyOwnedError || // Substate refusals (ts_4): a missing --why, a park from the wrong
|
|
21397
21533
|
// phase, a claim on a parked task, a pair outside TASK_SUBSTATES.
|
|
21398
|
-
err instanceof SubstateReasonRequiredError || err instanceof TaskParkStateError || err instanceof TaskParkedError || err instanceof InvalidSubstateError || err instanceof TaskClaimStaleWorkspaceError || err instanceof TaskNotInWorkstreamError || err instanceof AgentNotInWorkstreamError || err instanceof CycleError || err instanceof CrossWorkstreamEdgeError || err instanceof WorkspaceExistsError || err instanceof WorkspacePathNotEmptyError || err instanceof WorkspacePreservedError || err instanceof HomeDirAsProjectRootError || err instanceof WorkspaceVcsRequiredError || err instanceof WorkspaceDirtyError || err instanceof ClaimerNotRegisteredError || err instanceof SchemaTooOldError || err instanceof SchemaTooNewError || err instanceof TaskIdInvalidError || err instanceof WorkstreamExistsError || // Rebuild refuses to overwrite an existing target or to write onto
|
|
21534
|
+
err instanceof SubstateReasonRequiredError || err instanceof TaskParkStateError || err instanceof TaskParkedError || err instanceof TaskInTriageError || err instanceof InvalidSubstateError || err instanceof TaskClaimStaleWorkspaceError || err instanceof TaskNotInWorkstreamError || err instanceof AgentNotInWorkstreamError || err instanceof CycleError || err instanceof CrossWorkstreamEdgeError || err instanceof WorkspaceExistsError || err instanceof WorkspacePathNotEmptyError || err instanceof WorkspacePreservedError || err instanceof HomeDirAsProjectRootError || err instanceof WorkspaceVcsRequiredError || err instanceof WorkspaceDirtyError || err instanceof ClaimerNotRegisteredError || err instanceof SchemaTooOldError || err instanceof SchemaTooNewError || err instanceof TaskIdInvalidError || err instanceof WorkstreamExistsError || // Rebuild refuses to overwrite an existing target or to write onto
|
|
21399
21535
|
// the source DB: both are name/state collisions, not usage errors.
|
|
21400
21536
|
err instanceof RebuildTargetExistsError || err instanceof RebuildTargetIsSourceError || // An abbreviated group id matching several groups: the operator must
|
|
21401
21537
|
// disambiguate. Guessing would be catastrophic for `mu undo`.
|
|
@@ -23020,6 +23156,7 @@ function fullJsonShape(d) {
|
|
|
23020
23156
|
orphans: d.view.orphans,
|
|
23021
23157
|
tracks: d.tracks,
|
|
23022
23158
|
ready: withRoiAll(d.ready),
|
|
23159
|
+
triage: withRoiAll(d.triage),
|
|
23023
23160
|
inProgress: withRoiAll(d.inProgress),
|
|
23024
23161
|
blocked: withRoiAll(d.blocked),
|
|
23025
23162
|
recentClosed: withRoiAll(d.recentClosed),
|
|
@@ -23092,6 +23229,7 @@ async function cmdState(db, opts) {
|
|
|
23092
23229
|
}
|
|
23093
23230
|
renderFullMode(perWs);
|
|
23094
23231
|
}
|
|
23232
|
+
var BIG_WORKSTREAM_TASKS = 300;
|
|
23095
23233
|
function renderFullMode(perWs) {
|
|
23096
23234
|
perWs.forEach((d, i) => {
|
|
23097
23235
|
if (i > 0) console.log("");
|
|
@@ -23124,6 +23262,15 @@ function renderFullCard(d) {
|
|
|
23124
23262
|
);
|
|
23125
23263
|
}
|
|
23126
23264
|
}
|
|
23265
|
+
const total = d.taskCount;
|
|
23266
|
+
const open = ready.length + inProgress.length + blocked.length + d.triage.length;
|
|
23267
|
+
if (total >= BIG_WORKSTREAM_TASKS && open * 10 < total) {
|
|
23268
|
+
console.log(
|
|
23269
|
+
pc.dim(
|
|
23270
|
+
`hint: ${total} tasks, ${open} open; start the next effort in a new <project>-<purpose> workstream`
|
|
23271
|
+
)
|
|
23272
|
+
);
|
|
23273
|
+
}
|
|
23127
23274
|
console.log("");
|
|
23128
23275
|
console.log(pc.bold(`Tracks (${tracks.length})`));
|
|
23129
23276
|
console.log(formatTracks(tracks));
|
|
@@ -23131,6 +23278,13 @@ function renderFullCard(d) {
|
|
|
23131
23278
|
console.log(pc.bold(`Ready (${ready.length})`));
|
|
23132
23279
|
console.log(ready.length === 0 ? pc.dim(" (none)") : formatTaskListTable(ready));
|
|
23133
23280
|
console.log("");
|
|
23281
|
+
if (d.triage.length > 0) {
|
|
23282
|
+
console.log(
|
|
23283
|
+
`${pc.bold(`Triage (${d.triage.length})`)} ${pc.dim(`accept: mu task accept <id> -w ${workstreamName} \xB7 decline: mu task close <id> --as rejected|duplicate --why ...`)}`
|
|
23284
|
+
);
|
|
23285
|
+
console.log(formatTaskListTable(d.triage));
|
|
23286
|
+
console.log("");
|
|
23287
|
+
}
|
|
23134
23288
|
console.log(pc.bold(`In progress (${inProgress.length})`));
|
|
23135
23289
|
console.log(inProgress.length === 0 ? pc.dim(" (none)") : formatTaskListTable(inProgress));
|
|
23136
23290
|
console.log("");
|
|
@@ -23736,6 +23890,7 @@ function wireWorkspaceCommands(program) {
|
|
|
23736
23890
|
}
|
|
23737
23891
|
|
|
23738
23892
|
// src/cli/workstream.ts
|
|
23893
|
+
import { basename as basename2 } from "path";
|
|
23739
23894
|
init_mux2();
|
|
23740
23895
|
init_output();
|
|
23741
23896
|
init_workstream();
|
|
@@ -23764,6 +23919,7 @@ async function cmdInit(db, name, opts = {}) {
|
|
|
23764
23919
|
await mux.enableMuPaneBordersForSession(sessionName).catch(() => {
|
|
23765
23920
|
});
|
|
23766
23921
|
const created = !sessionAlready || dbCreated;
|
|
23922
|
+
const nameHint = name.includes("-") ? void 0 : `name workstreams <project>-<purpose> (e.g. ${projectGuess() ?? "repo"}-${name}); one per effort`;
|
|
23767
23923
|
const nextSteps3 = [
|
|
23768
23924
|
{ intent: "Attach the session", command: mux.attachHint({ session: sessionName }) },
|
|
23769
23925
|
{
|
|
@@ -23781,6 +23937,7 @@ async function cmdInit(db, name, opts = {}) {
|
|
|
23781
23937
|
muxSessionAlreadyExisted: sessionAlready,
|
|
23782
23938
|
dbRowAlreadyExisted: !dbCreated,
|
|
23783
23939
|
muWindowRepaired,
|
|
23940
|
+
...nameHint && created ? { hint: nameHint } : {},
|
|
23784
23941
|
nextSteps: nextSteps3
|
|
23785
23942
|
});
|
|
23786
23943
|
return;
|
|
@@ -23796,8 +23953,13 @@ async function cmdInit(db, name, opts = {}) {
|
|
|
23796
23953
|
return;
|
|
23797
23954
|
}
|
|
23798
23955
|
console.log(`Created workstream ${pc.bold(name)} (mux session ${pc.bold(sessionName)})`);
|
|
23956
|
+
if (nameHint && created) console.log(pc.dim(`hint: ${nameHint}`));
|
|
23799
23957
|
printNextSteps(nextSteps3);
|
|
23800
23958
|
}
|
|
23959
|
+
function projectGuess() {
|
|
23960
|
+
const base = basename2(process.cwd()).toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "");
|
|
23961
|
+
return base === "" ? void 0 : base;
|
|
23962
|
+
}
|
|
23801
23963
|
async function cmdWorkstreamList(db, opts = {}) {
|
|
23802
23964
|
if (opts.tornDown === true) {
|
|
23803
23965
|
const gone = listTornDownWorkstreams(db);
|
|
@@ -24053,8 +24215,12 @@ function withPositionalWorkstream(opts, name) {
|
|
|
24053
24215
|
return { ...opts, workstream: name };
|
|
24054
24216
|
}
|
|
24055
24217
|
function wireWorkstreamCommands(program) {
|
|
24056
|
-
const workstream = program.command("workstream").description(
|
|
24057
|
-
|
|
24218
|
+
const workstream = program.command("workstream").description(
|
|
24219
|
+
"Workstream-level commands. Name them <project>-<purpose> (hail-auth), one per effort; tear down when it ships. Reviews nobody will track belong in scratch delegates, not a workstream."
|
|
24220
|
+
);
|
|
24221
|
+
workstream.command("init <name>").description(
|
|
24222
|
+
"Create the workstream's mux session and register it in the DB. Name it <project>-<purpose>."
|
|
24223
|
+
).option(...JSON_OPT).action(function(name) {
|
|
24058
24224
|
const opts = this.opts();
|
|
24059
24225
|
return handle((db) => cmdInit(db, name, opts), this)();
|
|
24060
24226
|
});
|