@higherdev/cli 0.7.0 → 0.9.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 +7 -2
- package/dist/index.js +27 -7
- package/dist/out.js +1 -0
- package/dist/plan.js +94 -0
- package/dist/tui/App.js +18 -2
- package/dist/tui/Help.js +1 -0
- package/dist/tui/parse.js +2 -0
- package/dist/workspace-preflight.js +77 -0
- package/package.json +3 -2
- package/prompts/architect.md +23 -0
package/README.md
CHANGED
|
@@ -34,8 +34,9 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
34
34
|
| `hd ticket queue KEY` | Queue a complete ticket now |
|
|
35
35
|
| `hd epic new PATH [--title TITLE]` | Create an epic from a Markdown spec |
|
|
36
36
|
| `hd epic list` | List epics and ticket progress |
|
|
37
|
+
| `hd plan [--repo DIR]` | Hand the terminal to Codex to author an epic spec |
|
|
37
38
|
| `hd workspace ls` | List every workspace available to the configured key |
|
|
38
|
-
| `hd workspace new --name NAME --repo OWNER/NAME [options]` |
|
|
39
|
+
| `hd workspace new --name NAME --repo OWNER/NAME [options]` | Preflight GitHub, wire the runner, and create a paused workspace |
|
|
39
40
|
| `hd agents` | List agents |
|
|
40
41
|
| `hd agents set ROLE --provider P --model M [--effort E]` | Update an agent |
|
|
41
42
|
| `hd logs KEY [-f]` | Show or follow run events |
|
|
@@ -47,7 +48,11 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
47
48
|
| `hd init [options]` | Configure this host and runner service |
|
|
48
49
|
| `hd upgrade [options]` | Refresh this host configuration |
|
|
49
50
|
|
|
51
|
+
`hd workspace new` uses the operator's authenticated `gh`, defaults to the repository's real default
|
|
52
|
+
branch, bootstraps an empty repository unless `--no-bootstrap` is set, and invites `mel-ilotus` unless
|
|
53
|
+
`--runner-user USER` overrides it.
|
|
54
|
+
|
|
50
55
|
Inside the TUI, use `/board`, `/inbox`, `/ticket`, `/queue`, `/epic new`,
|
|
51
|
-
`/epics`, `/decide`, `/agents`, `/settings`, `/workspace`, `/feed`,
|
|
56
|
+
`/epics`, `/plan`, `/decide`, `/agents`, `/settings`, `/workspace`, `/feed`,
|
|
52
57
|
`/orchestrator`, `/refresh`, `/help`, or `/exit`. The display refreshes from
|
|
53
58
|
the HDX API every five seconds.
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { initHost, parseHostFlags } from "./host.js";
|
|
|
6
6
|
import { loadConfig, writeHdConfig } from "./config.js";
|
|
7
7
|
import { epicProgressRows, readEpicSpec } from "./epics.js";
|
|
8
8
|
import { banner, c, statusChip, table, truncate, usage } from "./out.js";
|
|
9
|
+
import { launchArchitect } from "./plan.js";
|
|
10
|
+
import { HDX_RUNNER_GH_USER, preflightWorkspace } from "./workspace-preflight.js";
|
|
9
11
|
function fail(message) {
|
|
10
12
|
console.error(message);
|
|
11
13
|
process.exit(1);
|
|
@@ -145,6 +147,14 @@ async function cmdEpic(argv) {
|
|
|
145
147
|
}
|
|
146
148
|
fail("usage: hd epic new PATH [--title TITLE] | list");
|
|
147
149
|
}
|
|
150
|
+
async function cmdPlan(argv) {
|
|
151
|
+
const { rest, opts, bools } = flags(argv);
|
|
152
|
+
if (rest.length || bools.size || Object.keys(opts).some((key) => key !== "repo")) {
|
|
153
|
+
fail("usage: hd plan [--repo DIR]");
|
|
154
|
+
}
|
|
155
|
+
const { workspace } = await getStatus();
|
|
156
|
+
await launchArchitect({ workspaceRepo: workspace.repo, repo: opts.repo });
|
|
157
|
+
}
|
|
148
158
|
async function cmdLogs(argv) {
|
|
149
159
|
const { rest, bools } = flags(argv);
|
|
150
160
|
const key = rest[0];
|
|
@@ -209,7 +219,7 @@ async function cmdDecide(argv) {
|
|
|
209
219
|
await answerDecision(id, opts.answer);
|
|
210
220
|
console.log("answered");
|
|
211
221
|
}
|
|
212
|
-
async function cmdWorkspace(argv) {
|
|
222
|
+
async function cmdWorkspace(argv, deps = {}) {
|
|
213
223
|
const [action, ...rest] = argv;
|
|
214
224
|
if (action === "ls") {
|
|
215
225
|
const current = loadConfig().slug;
|
|
@@ -223,14 +233,20 @@ async function cmdWorkspace(argv) {
|
|
|
223
233
|
])));
|
|
224
234
|
return;
|
|
225
235
|
}
|
|
236
|
+
const workspaceUsage = "usage: hd workspace ls | new --name NAME --repo OWNER/NAME [--slug S] [--branch B] [--host box] [--runner-user USER] [--no-bootstrap]";
|
|
226
237
|
if (action !== "new")
|
|
227
|
-
fail(
|
|
228
|
-
const { opts } = flags(rest);
|
|
238
|
+
fail(workspaceUsage);
|
|
239
|
+
const { opts, bools } = flags(rest);
|
|
229
240
|
if (!opts.name || !opts.repo) {
|
|
230
|
-
fail(
|
|
241
|
+
fail(workspaceUsage);
|
|
231
242
|
}
|
|
243
|
+
const preflight = await (deps.preflightWorkspace ?? preflightWorkspace)({ name: opts.name, repo: opts.repo,
|
|
244
|
+
branch: opts.branch, noBootstrap: bools.has("no-bootstrap"),
|
|
245
|
+
runnerUser: opts["runner-user"] ?? HDX_RUNNER_GH_USER });
|
|
246
|
+
if (preflight.invitationPending)
|
|
247
|
+
console.log(`invitation pending for ${opts["runner-user"] ?? HDX_RUNNER_GH_USER}`);
|
|
232
248
|
const result = await createWorkspace({ name: opts.name, repo: opts.repo, slug: opts.slug,
|
|
233
|
-
default_branch:
|
|
249
|
+
default_branch: preflight.branch, default_host: opts.host ?? "box" });
|
|
234
250
|
console.log(`workspace: ${result.workspace.slug}`);
|
|
235
251
|
writeHdConfig({ url: result.url, api_key: result.api_key, slug: result.workspace.slug });
|
|
236
252
|
console.log("saved and switched; configure another machine with:");
|
|
@@ -270,7 +286,7 @@ async function cmdAgents(argv) {
|
|
|
270
286
|
});
|
|
271
287
|
console.log(`${agent.role} ${agent.provider} ${agent.model} ${agent.effort}`);
|
|
272
288
|
}
|
|
273
|
-
export async function main(argv = process.argv.slice(2)) {
|
|
289
|
+
export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
274
290
|
const [cmd, ...rest] = argv;
|
|
275
291
|
try {
|
|
276
292
|
if (!cmd) {
|
|
@@ -303,6 +319,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
303
319
|
await cmdEpic(rest);
|
|
304
320
|
return;
|
|
305
321
|
}
|
|
322
|
+
if (cmd === "plan") {
|
|
323
|
+
await cmdPlan(rest);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
306
326
|
if (cmd === "logs") {
|
|
307
327
|
await cmdLogs(rest);
|
|
308
328
|
return;
|
|
@@ -316,7 +336,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
316
336
|
return;
|
|
317
337
|
}
|
|
318
338
|
if (cmd === "workspace") {
|
|
319
|
-
await cmdWorkspace(rest);
|
|
339
|
+
await cmdWorkspace(rest, deps);
|
|
320
340
|
return;
|
|
321
341
|
}
|
|
322
342
|
if (cmd === "agents") {
|
package/dist/out.js
CHANGED
|
@@ -66,6 +66,7 @@ export function usage() {
|
|
|
66
66
|
` ${c.blue("hd status")} workspace overview`,
|
|
67
67
|
` ${c.blue("hd ticket list | show | new | queue")} ticket operations`,
|
|
68
68
|
` ${c.blue("hd epic new PATH | list")} epic operations`,
|
|
69
|
+
` ${c.blue("hd plan [--repo DIR]")} author an epic with Codex`,
|
|
69
70
|
` ${c.blue("hd workspace ls | new")} list or create workspaces`,
|
|
70
71
|
` ${c.blue("hd agents [set]")} inspect or update agents`,
|
|
71
72
|
` ${c.blue("hd logs KEY [-f]")} run events`,
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
const ARCHITECT_PROMPT = new URL("../prompts/architect.md", import.meta.url);
|
|
7
|
+
function gitOutput(cwd, args) {
|
|
8
|
+
const result = spawnSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
9
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
10
|
+
}
|
|
11
|
+
export function remoteRepo(remote) {
|
|
12
|
+
const trimmed = remote.trim().replace(/\/$/, "").replace(/\.git$/, "");
|
|
13
|
+
const scp = trimmed.match(/^[^@]+@[^:]+:(.+)$/);
|
|
14
|
+
if (scp)
|
|
15
|
+
return scp[1] ?? null;
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(trimmed);
|
|
18
|
+
return url.pathname.replace(/^\//, "") || null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return trimmed.includes("/") ? trimmed : null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function expanded(path) {
|
|
25
|
+
const value = path.trim();
|
|
26
|
+
if (value === "~")
|
|
27
|
+
return homedir();
|
|
28
|
+
if (value.startsWith("~/"))
|
|
29
|
+
return join(homedir(), value.slice(2));
|
|
30
|
+
return isAbsolute(value) ? value : resolve(value);
|
|
31
|
+
}
|
|
32
|
+
export function matchingWorkspaceRepo(directory, workspaceRepo) {
|
|
33
|
+
const root = gitOutput(expanded(directory), ["rev-parse", "--show-toplevel"]);
|
|
34
|
+
if (!root)
|
|
35
|
+
return null;
|
|
36
|
+
const origin = gitOutput(root, ["remote", "get-url", "origin"]);
|
|
37
|
+
return origin && remoteRepo(origin)?.toLowerCase() === workspaceRepo.toLowerCase() ? root : null;
|
|
38
|
+
}
|
|
39
|
+
async function askForRepo(workspaceRepo) {
|
|
40
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
41
|
+
throw new Error(`Current directory is not ${workspaceRepo}. Pass --repo DIR.`);
|
|
42
|
+
}
|
|
43
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
44
|
+
try {
|
|
45
|
+
return (await prompt.question(`Repository directory for ${workspaceRepo}: `)).trim();
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
prompt.close();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function resolveWorkspaceRepo(workspaceRepo, requested) {
|
|
52
|
+
if (requested) {
|
|
53
|
+
const match = matchingWorkspaceRepo(requested, workspaceRepo);
|
|
54
|
+
if (!match)
|
|
55
|
+
throw new Error(`${requested} is not a checkout of ${workspaceRepo}.`);
|
|
56
|
+
return match;
|
|
57
|
+
}
|
|
58
|
+
const current = matchingWorkspaceRepo(process.cwd(), workspaceRepo);
|
|
59
|
+
if (current)
|
|
60
|
+
return current;
|
|
61
|
+
const answer = await askForRepo(workspaceRepo);
|
|
62
|
+
if (!answer)
|
|
63
|
+
throw new Error("A repository directory is required.");
|
|
64
|
+
const match = matchingWorkspaceRepo(answer, workspaceRepo);
|
|
65
|
+
if (!match)
|
|
66
|
+
throw new Error(`${answer} is not a checkout of ${workspaceRepo}.`);
|
|
67
|
+
return match;
|
|
68
|
+
}
|
|
69
|
+
export function verifyCodex(checkVersion = () => spawnSync("codex", ["--version"], { stdio: "ignore" })) {
|
|
70
|
+
const check = checkVersion();
|
|
71
|
+
if (check.error || check.status !== 0) {
|
|
72
|
+
throw new Error("Codex CLI is unavailable. Install it if needed, then run `codex login`.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function runCodex(cwd, prompt) {
|
|
76
|
+
return new Promise((resolveRun, rejectRun) => {
|
|
77
|
+
const child = spawn("codex", [prompt], { cwd, stdio: "inherit" });
|
|
78
|
+
child.once("error", rejectRun);
|
|
79
|
+
child.once("exit", (code, signal) => {
|
|
80
|
+
if (code === 0)
|
|
81
|
+
resolveRun();
|
|
82
|
+
else
|
|
83
|
+
rejectRun(new Error(`codex exited ${signal ? `with signal ${signal}` : `with status ${code ?? "unknown"}`}.`));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export async function launchArchitect(options, runtime = {}) {
|
|
88
|
+
(runtime.verifyCodex ?? verifyCodex)();
|
|
89
|
+
const [cwd, prompt] = await Promise.all([
|
|
90
|
+
resolveWorkspaceRepo(options.workspaceRepo, options.repo),
|
|
91
|
+
readFile(ARCHITECT_PROMPT, "utf8"),
|
|
92
|
+
]);
|
|
93
|
+
await (runtime.invokeCodex ?? runCodex)(cwd, prompt);
|
|
94
|
+
}
|
package/dist/tui/App.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Static, Text, useApp, useInput, useStdout } from "ink";
|
|
4
4
|
import { epicProgressRows } from "../epics.js";
|
|
5
|
+
import { launchArchitect } from "../plan.js";
|
|
5
6
|
import { Banner } from "./Banner.js";
|
|
6
7
|
import { Bubble } from "./Bubble.js";
|
|
7
8
|
import { Cockpit, StreamPanel, boardTicketIds, nextCursor } from "./Dashboard.js";
|
|
@@ -23,7 +24,7 @@ import { WorkspaceLoads } from "./workspace-load.js";
|
|
|
23
24
|
let messageSeq = 0;
|
|
24
25
|
const nextId = () => `m${messageSeq++}`;
|
|
25
26
|
export function App({ initial }) {
|
|
26
|
-
const { exit } = useApp();
|
|
27
|
+
const { exit, suspendTerminal } = useApp();
|
|
27
28
|
const { stdout } = useStdout();
|
|
28
29
|
const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
|
|
29
30
|
const rows = stdout?.rows && stdout.rows > 0 ? stdout.rows : 24;
|
|
@@ -347,6 +348,20 @@ export function App({ initial }) {
|
|
|
347
348
|
setBusy(false);
|
|
348
349
|
}
|
|
349
350
|
return;
|
|
351
|
+
case "plan":
|
|
352
|
+
setBusy(true);
|
|
353
|
+
try {
|
|
354
|
+
await suspendTerminal(() => launchArchitect({ workspaceRepo: workspace.repo }));
|
|
355
|
+
say("system", "Architect session ended. Review the spec, then run /epic new PATH.");
|
|
356
|
+
await refresh();
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
setNotice(error instanceof Error ? error.message : String(error));
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
setBusy(false);
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
350
365
|
case "decide": {
|
|
351
366
|
const decision = board.decisions[0];
|
|
352
367
|
if (!decision) {
|
|
@@ -393,7 +408,8 @@ export function App({ initial }) {
|
|
|
393
408
|
default:
|
|
394
409
|
return;
|
|
395
410
|
}
|
|
396
|
-
}, [view, settings, applyEdit, board, browsing, mode, say, askOrchestrator, order, settingsOrder, changeWorkspace,
|
|
411
|
+
}, [view, settings, applyEdit, board, browsing, mode, say, askOrchestrator, order, settingsOrder, changeWorkspace,
|
|
412
|
+
config, workspace.repo, refresh, suspendTerminal, exit]);
|
|
397
413
|
useInput((input, key) => {
|
|
398
414
|
if (key.ctrl && input === "c")
|
|
399
415
|
exit();
|
package/dist/tui/Help.js
CHANGED
|
@@ -11,6 +11,7 @@ export const COMMANDS = [
|
|
|
11
11
|
{ name: "/queue", args: "HD-12", help: "queue a complete ticket now" },
|
|
12
12
|
{ name: "/epic", args: "new PATH", help: "create an epic from a Markdown spec" },
|
|
13
13
|
{ name: "/epics", help: "list epics and ticket progress" },
|
|
14
|
+
{ name: "/plan", help: "author an epic with your Codex CLI" },
|
|
14
15
|
{ name: "/decide", args: "2 | text", help: "answer the decision on screen" },
|
|
15
16
|
{ name: "/agents", help: "every agent in full, and the live run stream" },
|
|
16
17
|
{ name: "/settings", help: "change provider caps and agent settings" },
|
package/dist/tui/parse.js
CHANGED
|
@@ -39,6 +39,8 @@ export function parseLine(raw) {
|
|
|
39
39
|
return argument
|
|
40
40
|
? { kind: "queue", key: argument.toUpperCase() }
|
|
41
41
|
: { kind: "unknown", command: "queue needs a key" };
|
|
42
|
+
case "plan":
|
|
43
|
+
return argument ? { kind: "unknown", command: "plan takes no arguments" } : { kind: "plan" };
|
|
42
44
|
case "decide": {
|
|
43
45
|
const dismiss = /(^|\s)--(skip|dismiss)(\s|$)/.test(argument);
|
|
44
46
|
return {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const exec = promisify(execFile);
|
|
4
|
+
export const HDX_RUNNER_GH_USER = "mel-ilotus";
|
|
5
|
+
export function preflightDecision(input) {
|
|
6
|
+
if (!input.found)
|
|
7
|
+
return { ok: false, error: "Repository was not found or is inaccessible." };
|
|
8
|
+
if (input.viewerPermission.toUpperCase() !== "ADMIN") {
|
|
9
|
+
return { ok: false, error: `Repository admin permission is required; viewer has ${input.viewerPermission || "none"}.` };
|
|
10
|
+
}
|
|
11
|
+
if (input.empty && input.noBootstrap) {
|
|
12
|
+
return { ok: false, error: "Repository is empty and --no-bootstrap was set." };
|
|
13
|
+
}
|
|
14
|
+
return { ok: true, bootstrap: input.empty };
|
|
15
|
+
}
|
|
16
|
+
function errorMessage(error) {
|
|
17
|
+
if (!error || typeof error !== "object")
|
|
18
|
+
return String(error);
|
|
19
|
+
const value = error;
|
|
20
|
+
return value.stderr?.trim() || value.message || String(error);
|
|
21
|
+
}
|
|
22
|
+
async function defaultGh(args) {
|
|
23
|
+
try {
|
|
24
|
+
return (await exec("gh", args, { encoding: "utf8", maxBuffer: 1024 * 1024 })).stdout;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new Error(errorMessage(error));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function repoView(repo, gh) {
|
|
31
|
+
return JSON.parse(await gh(["repo", "view", repo, "--json", "defaultBranchRef,isEmpty,viewerPermission"]));
|
|
32
|
+
}
|
|
33
|
+
function canPush(permission) {
|
|
34
|
+
return ["admin", "maintain", "write", "push"].includes(permission.toLowerCase());
|
|
35
|
+
}
|
|
36
|
+
export async function preflightWorkspace(input, gh = defaultGh) {
|
|
37
|
+
try {
|
|
38
|
+
await gh(["--version"]);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error("GitHub CLI is unavailable. Install it if needed, then run `gh auth login`.");
|
|
42
|
+
}
|
|
43
|
+
let view;
|
|
44
|
+
try {
|
|
45
|
+
view = await repoView(input.repo, gh);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new Error(`GitHub repository ${input.repo} was not found or is inaccessible: ${errorMessage(error)}`);
|
|
49
|
+
}
|
|
50
|
+
const decision = preflightDecision({ found: true, empty: view.isEmpty,
|
|
51
|
+
viewerPermission: view.viewerPermission, noBootstrap: input.noBootstrap });
|
|
52
|
+
if (!decision.ok)
|
|
53
|
+
throw new Error(decision.error);
|
|
54
|
+
if (decision.bootstrap) {
|
|
55
|
+
await gh(["api", "-X", "PUT", `repos/${input.repo}/contents/README.md`,
|
|
56
|
+
"-f", "message=Initial commit", "-f", `content=${Buffer.from(`# ${input.name}`).toString("base64")}`]);
|
|
57
|
+
view = await repoView(input.repo, gh);
|
|
58
|
+
}
|
|
59
|
+
const branch = input.branch || view.defaultBranchRef?.name;
|
|
60
|
+
if (!branch)
|
|
61
|
+
throw new Error(`GitHub repository ${input.repo} has no default branch.`);
|
|
62
|
+
const runnerUser = input.runnerUser || HDX_RUNNER_GH_USER;
|
|
63
|
+
let permission = "none";
|
|
64
|
+
try {
|
|
65
|
+
const result = JSON.parse(await gh(["api", `repos/${input.repo}/collaborators/${runnerUser}/permission`]));
|
|
66
|
+
permission = result.permission ?? "none";
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (!/404|not found/i.test(errorMessage(error)))
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const invitationPending = !canPush(permission);
|
|
73
|
+
if (invitationPending) {
|
|
74
|
+
await gh(["api", "-X", "PUT", `repos/${input.repo}/collaborators/${runnerUser}`, "-f", "permission=push"]);
|
|
75
|
+
}
|
|
76
|
+
return { branch, invitationPending };
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@higherdev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hd": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"prompts"
|
|
11
12
|
],
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsc",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
You are helping the operator author an HDX epic for this repository.
|
|
2
|
+
|
|
3
|
+
Start by interviewing the operator about the goal. Ask focused questions until the intended outcome, boundaries, and important tradeoffs are clear. Read the repository as needed so the plan reflects the code that actually exists.
|
|
4
|
+
|
|
5
|
+
Draft the epic in this exact structure:
|
|
6
|
+
|
|
7
|
+
# <title>
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
## Out of scope
|
|
14
|
+
|
|
15
|
+
## Acceptance
|
|
16
|
+
|
|
17
|
+
## Constraints
|
|
18
|
+
|
|
19
|
+
The operator is the author. Let them review and revise the draft before saving it. Do not submit the epic to HDX yourself.
|
|
20
|
+
|
|
21
|
+
Once the operator accepts the spec, write it to `docs/epics/<slug>.md`, where `<slug>` is a concise kebab-case name for the epic. End by printing the exact next command, with the real path substituted:
|
|
22
|
+
|
|
23
|
+
`hd epic new docs/epics/<slug>.md`
|