@higherdev/cli 0.29.0 → 0.30.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 +1 -1
- package/dist/index.js +8 -7
- package/dist/out.js +1 -1
- package/dist/roadmap.js +8 -3
- package/dist/tui/App.js +4 -3
- package/dist/tui/Dashboard.js +1 -1
- package/dist/tui/Help.js +1 -1
- package/dist/tui/Panels.js +12 -2
- package/dist/tui/Roadmap.js +1 -1
- package/dist/tui/agent-rows.js +4 -0
- package/dist/tui/data.js +2 -2
- package/dist/tui/parse.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
30
30
|
| `hd ticket new --title TITLE [--acceptance TEXT] [options]` | Create a ticket non-interactively |
|
|
31
31
|
| `hd ticket queue KEY` | Queue a complete ticket now |
|
|
32
32
|
| `hd ticket cancel KEY` | Cancel a ticket |
|
|
33
|
-
| `hd epic new PATH [--title TITLE]` | Create an epic
|
|
33
|
+
| `hd epic new PATH [--title TITLE] [--draft]` | Create an approved epic, or keep it as a draft |
|
|
34
34
|
| `hd epic list` | List epics and ticket progress |
|
|
35
35
|
| `hd epic approve ID` | Open a draft epic for orchestrator decomposition |
|
|
36
36
|
| `hd epic rm ID` | Remove a draft epic |
|
package/dist/index.js
CHANGED
|
@@ -186,18 +186,19 @@ async function cmdEpic(argv) {
|
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
188
188
|
console.log(table(["KEY", "STATUS", "PROGRESS", "TITLE"], rows.map((epic) => [
|
|
189
|
-
epic.id,
|
|
189
|
+
epic.id, epic.status === "draft" ? c.yellow(`needs your approval; hd epic approve ${epic.id}`)
|
|
190
|
+
: statusChip(epic.status), `${epic.merged}/${epic.total}`, epic.title,
|
|
190
191
|
])));
|
|
191
192
|
return;
|
|
192
193
|
}
|
|
193
194
|
if (action === "new") {
|
|
194
|
-
const { rest: paths, opts } = flags(rest);
|
|
195
|
+
const { rest: paths, opts, bools } = flags(rest);
|
|
195
196
|
const path = paths.join(" ");
|
|
196
|
-
if (!path)
|
|
197
|
-
fail("usage: hd epic new PATH [--title TITLE]");
|
|
197
|
+
if (!path || [...bools].some((name) => name !== "draft"))
|
|
198
|
+
fail("usage: hd epic new PATH [--title TITLE] [--draft]");
|
|
198
199
|
const input = await readEpicSpec(path, opts.title);
|
|
199
|
-
const { epic } = await createEpic(input);
|
|
200
|
-
console.log(`${c.bold(epic.id)} ${
|
|
200
|
+
const { epic, message } = await createEpic({ ...input, draft: bools.has("draft") });
|
|
201
|
+
console.log(`${c.bold(epic.id)} ${message}`);
|
|
201
202
|
return;
|
|
202
203
|
}
|
|
203
204
|
if (action === "approve" || action === "rm") {
|
|
@@ -233,7 +234,7 @@ async function cmdEpic(argv) {
|
|
|
233
234
|
console.log(`${c.bold(epic.id)} position ${epic.position}`);
|
|
234
235
|
return;
|
|
235
236
|
}
|
|
236
|
-
fail("usage: hd epic new PATH [--title TITLE] | list | approve ID | rm ID | set ID --position N");
|
|
237
|
+
fail("usage: hd epic new PATH [--title TITLE] [--draft] | list | approve ID | rm ID | set ID --position N");
|
|
237
238
|
}
|
|
238
239
|
async function cmdRoadmap(argv) {
|
|
239
240
|
const parsed = flags(argv);
|
package/dist/out.js
CHANGED
|
@@ -66,7 +66,7 @@ export function usage() {
|
|
|
66
66
|
` ${c.blue("hd status")} workspace overview`,
|
|
67
67
|
` ${c.blue("hd ticket list | show KEY [--json] | new [PATH] | queue | cancel | merge")} ticket operations`,
|
|
68
68
|
` ${c.blue("hd runs | hd run cancel ID")} inspect or cancel live runs`,
|
|
69
|
-
` ${c.blue("hd epic new PATH | list | approve | rm")} epic operations`,
|
|
69
|
+
` ${c.blue("hd epic new PATH [--draft] | list | approve | rm")} epic operations`,
|
|
70
70
|
` ${c.blue("hd roadmap [--json]")} ordered workspace roadmap`,
|
|
71
71
|
` ${c.blue("hd plan")} use /architect in the TUI`,
|
|
72
72
|
` ${c.blue("hd workspace ls | use | new | set | rotate-key | grant-runner-access")} workspace operations`,
|
package/dist/roadmap.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export function currentRoadmapEpic(epics) {
|
|
2
2
|
const statuses = new Map(epics.map((epic) => [epic.id, epic.status]));
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const next = [...epics].sort((a, b) => a.position - b.position).find((epic) => epic.status !== "done" && epic.depends_on.every((id) => statuses.get(id) === "done"));
|
|
4
|
+
return next?.status === "draft" ? null : next ?? null;
|
|
5
|
+
}
|
|
6
|
+
export function orchestratorIdleReason(epics) {
|
|
7
|
+
const statuses = new Map(epics.map((epic) => [epic.id, epic.status]));
|
|
8
|
+
const next = [...epics].sort((a, b) => a.position - b.position).find((epic) => epic.status !== "done" && epic.depends_on.every((id) => statuses.get(id) === "done"));
|
|
9
|
+
return next?.status === "draft" ? `idle because ${next.title} is a draft and needs your approval` : null;
|
|
5
10
|
}
|
|
6
11
|
export function progressBar(merged, total, width = 10) {
|
|
7
12
|
const complete = total > 0 ? Math.round((Math.max(0, Math.min(merged, total)) / total) * width) : 0;
|
|
@@ -12,7 +17,7 @@ export function roadmapText(epics) {
|
|
|
12
17
|
const current = currentRoadmapEpic(epics);
|
|
13
18
|
return [...epics].sort((a, b) => a.position - b.position).flatMap((epic) => {
|
|
14
19
|
const dependencies = epic.depends_on.map((id) => names.get(id) ?? id);
|
|
15
|
-
const status = epic.status === "draft" ?
|
|
20
|
+
const status = epic.status === "draft" ? `needs your approval · /epic approve ${epic.id}` : epic.status;
|
|
16
21
|
return [
|
|
17
22
|
`${epic.id === current?.id ? "▶" : " "} ${epic.position}. ${epic.title} [${status}]`,
|
|
18
23
|
` Outcome: ${epic.outcome_md || "Not described"}`,
|
package/dist/tui/App.js
CHANGED
|
@@ -683,8 +683,8 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
|
|
|
683
683
|
case "epic-new":
|
|
684
684
|
setBusy(true);
|
|
685
685
|
try {
|
|
686
|
-
const { epic } = await createEpicFromFile(config, action.path);
|
|
687
|
-
say("system", `Created epic ${epic.id}: ${
|
|
686
|
+
const { epic, message } = await createEpicFromFile(config, action.path, action.draft);
|
|
687
|
+
say("system", `Created epic ${epic.id}: ${message}`);
|
|
688
688
|
await refresh();
|
|
689
689
|
}
|
|
690
690
|
catch (error) {
|
|
@@ -725,7 +725,8 @@ export function App({ initial, availableUpdate: initialUpdate = null, updateDeps
|
|
|
725
725
|
case "epics": {
|
|
726
726
|
const rows = epicProgressRows(board.epics, board.tickets);
|
|
727
727
|
say("system", rows.length
|
|
728
|
-
? rows.map((epic) => `${epic.id} ${epic.status
|
|
728
|
+
? rows.map((epic) => `${epic.id} ${epic.status === "draft"
|
|
729
|
+
? `needs your approval · /epic approve ${epic.id}` : epic.status} ${epic.merged}/${epic.total} ${epic.title}`).join("\n")
|
|
729
730
|
: "No epics.");
|
|
730
731
|
return;
|
|
731
732
|
}
|
package/dist/tui/Dashboard.js
CHANGED
|
@@ -122,7 +122,7 @@ export function AgentsColumn({ board, width, rows, }) {
|
|
|
122
122
|
? "warning" : "muted";
|
|
123
123
|
return (_jsxs(Box, { flexWrap: "nowrap", children: [_jsxs(Text, { color: inkColor(tone), children: [DOT, " "] }), _jsxs(Text, { color: UI.text, wrap: "truncate", children: [row.name, row.state === "draining" ? null : (_jsx(Text, { color: UI.dim, children: row.run
|
|
124
124
|
? ` · ${row.ticket?.key ?? row.run.kind} ${elapsed(row.run.started_at ?? row.run.created_at)}`
|
|
125
|
-
: ` · ${row.limitedUntil ? `limited until ${row.limitedUntil}` : row.state}` }))] })] }, row.key));
|
|
125
|
+
: ` · ${row.limitedUntil ? `limited until ${row.limitedUntil}` : row.detail ?? row.state}` }))] })] }, row.key));
|
|
126
126
|
}),
|
|
127
127
|
_jsx(More, { count: displayRows.length - shown.length }, "more"),
|
|
128
128
|
] }));
|
package/dist/tui/Help.js
CHANGED
|
@@ -15,7 +15,7 @@ export const COMMANDS = [
|
|
|
15
15
|
{ name: "/msg", args: "HD-12 TEXT", help: "message a ticket's builder" },
|
|
16
16
|
{ name: "/attach", args: "PATH [HD-12]", help: "attach a file to the open or named ticket" },
|
|
17
17
|
{ name: "/logs", args: "[raw] [HD-12]", help: "filter activity; raw reveals event JSON" },
|
|
18
|
-
{ name: "/epic", args: "new PATH | approve ID | rm ID", help: "create, approve, or remove
|
|
18
|
+
{ name: "/epic", args: "new PATH [--draft] | approve ID | rm ID", help: "create, approve, or remove an epic" },
|
|
19
19
|
{ name: "/epics", help: "list epics and ticket progress" },
|
|
20
20
|
{ name: "/architect", help: "open a chat with the architect" },
|
|
21
21
|
{ name: "/plan", help: "alias for /architect" },
|
package/dist/tui/Panels.js
CHANGED
|
@@ -93,14 +93,15 @@ export function InboxPanel({ board, width = 80, rows = 12, focus = 0, selectedId
|
|
|
93
93
|
const entries = inboxEntries(board, width, answeringId);
|
|
94
94
|
const unread = board.messages ?? [];
|
|
95
95
|
const earlier = board.earlier ?? [];
|
|
96
|
+
const drafts = board.epics.filter((epic) => epic.status === "draft").length;
|
|
96
97
|
const selected = selectedId ?? decisionIdAt(entries, focus);
|
|
97
98
|
const inner = Math.max(0, rows - 1);
|
|
98
99
|
const window = scrollWindow(entries.length, inner, focus);
|
|
99
100
|
const hiddenAbove = window.start;
|
|
100
101
|
const hiddenBelow = entries.length - window.end;
|
|
101
|
-
const note = `${board.decisions.length}d · ${unread.length} unread`
|
|
102
|
+
const note = `${drafts ? `${drafts} epic${drafts === 1 ? "" : "s"} · ` : ""}${board.decisions.length}d · ${unread.length} unread`
|
|
102
103
|
+ `${hiddenAbove ? ` ${hiddenAbove}↑` : ""}${hiddenBelow ? ` ${hiddenBelow}↓` : ""}`;
|
|
103
|
-
const empty = board.decisions.length === 0 && unread.length === 0 && earlier.length === 0;
|
|
104
|
+
const empty = drafts === 0 && board.decisions.length === 0 && unread.length === 0 && earlier.length === 0;
|
|
104
105
|
return (_jsx(Panel, { width: width, rows: rows, children: [
|
|
105
106
|
_jsx(Heading, { text: "Inbox", note: note }, "h"),
|
|
106
107
|
...(empty
|
|
@@ -122,6 +123,15 @@ export function inboxEntries(board, width, answeringId) {
|
|
|
122
123
|
const bodyWidth = Math.max(12, width - 2);
|
|
123
124
|
const unread = board.messages ?? [];
|
|
124
125
|
const earlier = board.earlier ?? [];
|
|
126
|
+
const drafts = board.epics.filter((epic) => epic.status === "draft");
|
|
127
|
+
if (drafts.length) {
|
|
128
|
+
entries.push({ key: "drafts:section", kind: "section", text: "Draft epics" });
|
|
129
|
+
for (const epic of drafts) {
|
|
130
|
+
entries.push({ key: `draft:${epic.id}`, kind: "header", text: epic.title });
|
|
131
|
+
entries.push({ key: `draft:${epic.id}:action`, kind: "body",
|
|
132
|
+
text: `Needs your approval · /epic approve ${epic.id}` });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
125
135
|
board.decisions.forEach((decision, index) => {
|
|
126
136
|
const ticket = board.tickets.find((row) => row.id === decision.ticket_id);
|
|
127
137
|
entries.push({
|
package/dist/tui/Roadmap.js
CHANGED
|
@@ -30,7 +30,7 @@ export function RoadmapPanel({ board, width = 80, rows = 12, offset = 0 }) {
|
|
|
30
30
|
_jsx(Heading, { text: "Roadmap", note: note }, "h"),
|
|
31
31
|
...shown.map((line, index) => {
|
|
32
32
|
const current = line.startsWith("▶");
|
|
33
|
-
const draft = line.includes("
|
|
33
|
+
const draft = line.includes("needs your approval");
|
|
34
34
|
return _jsx(Text, { color: current ? UI.accent
|
|
35
35
|
: draft ? inkColor("warning") : line === "Vision" ? UI.text : UI.dim, bold: current || line === "Vision", inverse: current, wrap: "truncate", children: line || " " }, `${start + index}:${line}`);
|
|
36
36
|
}),
|
package/dist/tui/agent-rows.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatDrainStatus } from "../host.js";
|
|
2
|
+
import { orchestratorIdleReason } from "../roadmap.js";
|
|
2
3
|
const LIMITED_UNTIL = /^(?:Waiting on )?(\w+) (?:limited )?until (\d{1,2}:\d{2})\.?$/;
|
|
3
4
|
export function limitedUntilByProvider(tickets) {
|
|
4
5
|
const limited = new Map();
|
|
@@ -33,6 +34,7 @@ export function agentDisplayRows(board, now = Date.now()) {
|
|
|
33
34
|
ticket: null,
|
|
34
35
|
state: "draining",
|
|
35
36
|
limitedUntil: null,
|
|
37
|
+
detail: null,
|
|
36
38
|
}]
|
|
37
39
|
: [];
|
|
38
40
|
const activeAgents = new Set();
|
|
@@ -61,6 +63,7 @@ export function agentDisplayRows(board, now = Date.now()) {
|
|
|
61
63
|
ticket,
|
|
62
64
|
state: run.status,
|
|
63
65
|
limitedUntil: null,
|
|
66
|
+
detail: null,
|
|
64
67
|
};
|
|
65
68
|
});
|
|
66
69
|
const limited = limitedUntilByProvider(board.tickets);
|
|
@@ -81,6 +84,7 @@ export function agentDisplayRows(board, now = Date.now()) {
|
|
|
81
84
|
? "offline"
|
|
82
85
|
: "idle",
|
|
83
86
|
limitedUntil: until ?? null,
|
|
87
|
+
detail: agent.role === "orchestrator" ? orchestratorIdleReason(board.epics) : null,
|
|
84
88
|
};
|
|
85
89
|
});
|
|
86
90
|
return [...drain, ...live, ...idle];
|
package/dist/tui/data.js
CHANGED
|
@@ -177,8 +177,8 @@ export async function loadTicketDetail(config, key) {
|
|
|
177
177
|
},
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
|
-
export async function createEpicFromFile(config, path) {
|
|
181
|
-
return postEpic(await readEpicSpec(path), config);
|
|
180
|
+
export async function createEpicFromFile(config, path, draft = false) {
|
|
181
|
+
return postEpic({ ...await readEpicSpec(path), draft }, config);
|
|
182
182
|
}
|
|
183
183
|
export async function approveEpic(config, id) {
|
|
184
184
|
return approveEpicNow(id, config);
|
package/dist/tui/parse.js
CHANGED
|
@@ -61,7 +61,10 @@ export function parseLine(raw) {
|
|
|
61
61
|
: { kind: "unknown", command: "ticket needs a key" };
|
|
62
62
|
case "epic":
|
|
63
63
|
if (rest[0]?.toLowerCase() === "new" && rest.length > 1) {
|
|
64
|
-
|
|
64
|
+
const draft = rest.includes("--draft");
|
|
65
|
+
const path = rest.slice(1).filter((part) => part !== "--draft").join(" ");
|
|
66
|
+
return path ? { kind: "epic-new", path, draft }
|
|
67
|
+
: { kind: "unknown", command: "epic needs new PATH, approve ID, or rm ID" };
|
|
65
68
|
}
|
|
66
69
|
return rest[0]?.toLowerCase() === "approve" && rest.length === 2
|
|
67
70
|
? { kind: "epic-approve", id: rest[1] }
|