@workser/cli 0.2.7 → 0.3.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/dist/index.js +402 -37
- package/package.json +1 -1
- package/skills/workser/reference/tasks.md +82 -0
package/dist/index.js
CHANGED
|
@@ -4404,6 +4404,89 @@ workser storage get <key> [dest] # download an object (or print its URL)
|
|
|
4404
4404
|
infrastructure on the project's own Neon branch, available only on dedicated tenancy
|
|
4405
4405
|
in a supported region. They are different stores \u2014 a file put in one is not visible
|
|
4406
4406
|
in the other. See \`reference/neon-backend.md\`.
|
|
4407
|
+
`
|
|
4408
|
+
},
|
|
4409
|
+
{
|
|
4410
|
+
topic: "tasks",
|
|
4411
|
+
title: "Project tasks & subtasks",
|
|
4412
|
+
summary: "The ticket you are working inside: read it, break it into steps, and ask before starting.",
|
|
4413
|
+
commands: ["task"],
|
|
4414
|
+
source: "skills/workser/reference/tasks.md",
|
|
4415
|
+
body: `# Project tasks & subtasks
|
|
4416
|
+
|
|
4417
|
+
A **project task** is a ticket the owner filed. You are usually running inside
|
|
4418
|
+
one \u2014 Orbit sets \`WORKSER_PROJECT_TASK_ID\` on your process, so every command
|
|
4419
|
+
below defaults to it and you rarely pass an id at all.
|
|
4420
|
+
|
|
4421
|
+
\`\`\`
|
|
4422
|
+
workser task list [--status <value>] [--label <value>] [--limit <n>]
|
|
4423
|
+
workser task show [id] # the task you are in, with its steps
|
|
4424
|
+
|
|
4425
|
+
workser task subtask add <title> [--role <value>] [--kind <value>]
|
|
4426
|
+
[--note <text>] [--app <id...>]
|
|
4427
|
+
[--infra <ref...>] [--scope <path...>]
|
|
4428
|
+
[--depends-on <key...>]
|
|
4429
|
+
workser task subtask list [taskId]
|
|
4430
|
+
workser task subtask update <id> [--title|--note|--role|--kind|--scope]
|
|
4431
|
+
workser task subtask remove <id>
|
|
4432
|
+
|
|
4433
|
+
workser task can-start [id] # may work begin? refuses until approved
|
|
4434
|
+
workser task approval request # tell the owner the plan is ready
|
|
4435
|
+
workser task move <id> <status>
|
|
4436
|
+
workser task done [id] --summary <text>
|
|
4437
|
+
\`\`\`
|
|
4438
|
+
|
|
4439
|
+
## This is not \`workser board\`
|
|
4440
|
+
|
|
4441
|
+
\`board\` is the Orbit Board \u2014 a human's list of work items. \`task\` is the AI Tech
|
|
4442
|
+
Team's own table. Filing your plan on the Board puts it somewhere the owner's
|
|
4443
|
+
task page never reads: they see "created work item" and an empty plan. Use
|
|
4444
|
+
\`task subtask add\`.
|
|
4445
|
+
|
|
4446
|
+
## Planning a task
|
|
4447
|
+
|
|
4448
|
+
Read the project first, then propose. One \`subtask add\` per step:
|
|
4449
|
+
|
|
4450
|
+
\`\`\`
|
|
4451
|
+
workser task subtask add "Build the upload endpoint" \\
|
|
4452
|
+
--role api --kind service \\
|
|
4453
|
+
--note "Accept a file, work out its type, hand it to the right analyzer." \\
|
|
4454
|
+
--scope src/app/api/analyze/route.ts
|
|
4455
|
+
|
|
4456
|
+
workser task subtask add "Check every supported file type end to end" \\
|
|
4457
|
+
--role qa --depends-on RIZZ-15
|
|
4458
|
+
\`\`\`
|
|
4459
|
+
|
|
4460
|
+
\`--role\` is one of: pm, architect, web, api, automation, qa.
|
|
4461
|
+
\`--kind\` is what the step produces: data_reports, web, mobile, service,
|
|
4462
|
+
automation, docs. It is not the same fact as the role \u2014 the same engineer
|
|
4463
|
+
writing a screen, the docs for it and the service behind it is three kinds of
|
|
4464
|
+
work.
|
|
4465
|
+
|
|
4466
|
+
\`--scope\` is what that step OWNS. Two steps naming the same file cannot run at
|
|
4467
|
+
the same time, so keeping scopes apart is what lets the team work in parallel.
|
|
4468
|
+
\`--depends-on\` takes the keys you read off \`task show\`.
|
|
4469
|
+
|
|
4470
|
+
Between three and six steps. If it needs more, say the task is too big instead.
|
|
4471
|
+
The step that CHECKS work must not be the same role as the one that built it.
|
|
4472
|
+
|
|
4473
|
+
## Nothing runs until the owner approves
|
|
4474
|
+
|
|
4475
|
+
\`\`\`
|
|
4476
|
+
workser task can-start
|
|
4477
|
+
\`\`\`
|
|
4478
|
+
|
|
4479
|
+
This exits non-zero, with the reason, until they have approved the plan \u2014 that
|
|
4480
|
+
refusal is the product working, not an error to route around. Ask with
|
|
4481
|
+
\`workser task approval request\`; only a person can answer.
|
|
4482
|
+
|
|
4483
|
+
## Finishing a step
|
|
4484
|
+
|
|
4485
|
+
\`\`\`
|
|
4486
|
+
workser task done --summary "The report now shows cost per KOL, with six months of history."
|
|
4487
|
+
\`\`\`
|
|
4488
|
+
|
|
4489
|
+
Write the summary for someone who runs a business and does not read code.
|
|
4407
4490
|
`
|
|
4408
4491
|
},
|
|
4409
4492
|
{
|
|
@@ -4632,7 +4715,18 @@ function buildContext(opts) {
|
|
|
4632
4715
|
const projectId = opts.project || process.env.WORKSER_PROJECT_ID || link?.projectId || session.defaultProjectId;
|
|
4633
4716
|
const runId = process.env.WORKSER_RUN_ID || void 0;
|
|
4634
4717
|
const conversationId = process.env.WORKSER_CONVERSATION_ID || void 0;
|
|
4635
|
-
|
|
4718
|
+
const projectTaskId = process.env.WORKSER_PROJECT_TASK_ID || void 0;
|
|
4719
|
+
return {
|
|
4720
|
+
endpoint,
|
|
4721
|
+
socketPath,
|
|
4722
|
+
token,
|
|
4723
|
+
mode: mode2,
|
|
4724
|
+
cwd,
|
|
4725
|
+
projectId,
|
|
4726
|
+
runId,
|
|
4727
|
+
conversationId,
|
|
4728
|
+
projectTaskId
|
|
4729
|
+
};
|
|
4636
4730
|
}
|
|
4637
4731
|
function runTarget(ctx) {
|
|
4638
4732
|
return ctx.runId || "current";
|
|
@@ -6941,8 +7035,278 @@ function collect2(value, previous) {
|
|
|
6941
7035
|
return [...previous, value];
|
|
6942
7036
|
}
|
|
6943
7037
|
|
|
6944
|
-
// src/commands/
|
|
7038
|
+
// src/commands/task.ts
|
|
6945
7039
|
var import_picocolors27 = __toESM(require_picocolors(), 1);
|
|
7040
|
+
var STATUSES2 = ["todo", "working", "checking", "ready", "accepted", "archived"];
|
|
7041
|
+
var ROLES = ["pm", "architect", "web", "api", "automation", "qa"];
|
|
7042
|
+
var KINDS2 = ["data_reports", "web", "mobile", "service", "automation", "docs"];
|
|
7043
|
+
function registerTask(program3) {
|
|
7044
|
+
const task = program3.command("task").description("The project's tasks and their subtasks (AI Tech Team)");
|
|
7045
|
+
task.command("list").description("List the board's tasks \u2014 run this before starting anything").option("--status <value>", `only tasks in this status (${STATUSES2.join(" | ")})`).option("--label <value>", "only tasks carrying this label").option("--limit <n>", "cap the number of tasks returned").action(
|
|
7046
|
+
action(async ({ ctx, opts }) => {
|
|
7047
|
+
requireProject(ctx);
|
|
7048
|
+
if (opts.status !== void 0) assertOneOf("--status", opts.status, STATUSES2);
|
|
7049
|
+
const rows = await api(ctx, "/v1/project-tasks", {
|
|
7050
|
+
query: {
|
|
7051
|
+
status: opts.status,
|
|
7052
|
+
label: opts.label,
|
|
7053
|
+
limit: opts.limit
|
|
7054
|
+
}
|
|
7055
|
+
}) ?? [];
|
|
7056
|
+
ok(rows, () => {
|
|
7057
|
+
if (!rows.length) {
|
|
7058
|
+
line(import_picocolors27.default.dim("No tasks on the board yet."));
|
|
7059
|
+
return;
|
|
7060
|
+
}
|
|
7061
|
+
for (const r of rows) line(formatRow2(r));
|
|
7062
|
+
});
|
|
7063
|
+
})
|
|
7064
|
+
);
|
|
7065
|
+
task.command("show [id]").description(
|
|
7066
|
+
"Show one task with its subtasks. Defaults to the task this run is inside."
|
|
7067
|
+
).action(
|
|
7068
|
+
action(async ({ ctx, args }) => {
|
|
7069
|
+
const id = resolveTaskId(ctx, args[0]);
|
|
7070
|
+
const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
|
|
7071
|
+
ok(row, () => printTask(row));
|
|
7072
|
+
})
|
|
7073
|
+
);
|
|
7074
|
+
const subtask = task.command("subtask").description("The steps a task is broken into");
|
|
7075
|
+
subtask.command("add <title>").description(
|
|
7076
|
+
'Add one step, e.g. `workser task subtask add "Build the upload screen" --role web`'
|
|
7077
|
+
).option("--task <id>", "the parent task (defaults to the task this run is inside)").option("--role <value>", `who does it (${ROLES.join(" | ")})`).option("--kind <value>", `what it produces (${KINDS2.join(" | ")})`).option("--note <text>", "one sentence on what this step does").option("--app <id...>", "app ids this step touches").option("--infra <ref...>", "shared setup it touches (database | storage | auth | hosting | jobs)").option("--scope <path...>", "files or folders THIS step owns").option("--depends-on <id...>", "steps that must finish first (key or id)").action(
|
|
7078
|
+
action(async ({ ctx, args, opts }) => {
|
|
7079
|
+
const parent = resolveTaskId(ctx, opts.task);
|
|
7080
|
+
if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
|
|
7081
|
+
if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
|
|
7082
|
+
const dependsOn = await resolveDeps(ctx, parent, opts.dependsOn ?? []);
|
|
7083
|
+
const row = await api(ctx, "/v1/project-tasks", {
|
|
7084
|
+
body: {
|
|
7085
|
+
parentTaskId: parent,
|
|
7086
|
+
title: args[0],
|
|
7087
|
+
summary: opts.note,
|
|
7088
|
+
role: opts.role,
|
|
7089
|
+
category: opts.kind,
|
|
7090
|
+
scopePaths: opts.scope,
|
|
7091
|
+
dependsOn,
|
|
7092
|
+
targets: [
|
|
7093
|
+
...(opts.app ?? []).map((appId) => ({ kind: "app", appId })),
|
|
7094
|
+
...(opts.infra ?? []).map((ref) => ({ kind: "infra", ref }))
|
|
7095
|
+
]
|
|
7096
|
+
}
|
|
7097
|
+
});
|
|
7098
|
+
ok(row, () => {
|
|
7099
|
+
line(`${import_picocolors27.default.green("added")} ${import_picocolors27.default.bold(row.title)} ${import_picocolors27.default.dim(row.key ?? row.id)}`);
|
|
7100
|
+
if (row.role) line(import_picocolors27.default.dim(`role: ${row.role}`));
|
|
7101
|
+
});
|
|
7102
|
+
})
|
|
7103
|
+
);
|
|
7104
|
+
subtask.command("list [taskId]").description("The steps of a task, in order").action(
|
|
7105
|
+
action(async ({ ctx, args }) => {
|
|
7106
|
+
const id = resolveTaskId(ctx, args[0]);
|
|
7107
|
+
const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
|
|
7108
|
+
const rows = row.subtasks ?? [];
|
|
7109
|
+
ok(rows, () => {
|
|
7110
|
+
if (!rows.length) {
|
|
7111
|
+
line(import_picocolors27.default.dim("No steps yet."));
|
|
7112
|
+
return;
|
|
7113
|
+
}
|
|
7114
|
+
rows.forEach((r, i) => line(formatSubtask(r, i + 1)));
|
|
7115
|
+
});
|
|
7116
|
+
})
|
|
7117
|
+
);
|
|
7118
|
+
subtask.command("update <id>").description("Change a step \u2014 its title, its role, what it touches").option("--title <text>").option("--note <text>").option("--role <value>", ROLES.join(" | ")).option("--kind <value>", KINDS2.join(" | ")).option("--scope <path...>", "replaces the step's scope entirely").action(
|
|
7119
|
+
action(async ({ ctx, args, opts }) => {
|
|
7120
|
+
if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
|
|
7121
|
+
if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
|
|
7122
|
+
const row = await api(
|
|
7123
|
+
ctx,
|
|
7124
|
+
`/v1/project-tasks/${encodeURIComponent(args[0])}`,
|
|
7125
|
+
{
|
|
7126
|
+
method: "PATCH",
|
|
7127
|
+
body: {
|
|
7128
|
+
title: opts.title,
|
|
7129
|
+
summary: opts.note,
|
|
7130
|
+
role: opts.role,
|
|
7131
|
+
category: opts.kind,
|
|
7132
|
+
scopePaths: opts.scope
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
);
|
|
7136
|
+
ok(row, () => line(`${import_picocolors27.default.green("updated")} ${import_picocolors27.default.bold(row.title)}`));
|
|
7137
|
+
})
|
|
7138
|
+
);
|
|
7139
|
+
subtask.command("remove <id>").description("Drop a step from the plan (only before the work starts)").action(
|
|
7140
|
+
action(async ({ ctx, args }) => {
|
|
7141
|
+
await api(ctx, `/v1/project-tasks/${encodeURIComponent(args[0])}`, {
|
|
7142
|
+
method: "DELETE"
|
|
7143
|
+
});
|
|
7144
|
+
ok({ removed: args[0] }, () => line(import_picocolors27.default.green("removed")));
|
|
7145
|
+
})
|
|
7146
|
+
);
|
|
7147
|
+
task.command("move <id> <status>").description(`Move a task or step along the board (${STATUSES2.join(" | ")})`).action(
|
|
7148
|
+
action(async ({ ctx, args }) => {
|
|
7149
|
+
assertOneOf("<status>", args[1], STATUSES2);
|
|
7150
|
+
const row = await api(
|
|
7151
|
+
ctx,
|
|
7152
|
+
`/v1/project-tasks/${encodeURIComponent(args[0])}/move`,
|
|
7153
|
+
{ body: { status: args[1] } }
|
|
7154
|
+
);
|
|
7155
|
+
ok(row, () => line(`${import_picocolors27.default.green("moved")} ${import_picocolors27.default.bold(row.title)} \u2192 ${args[1]}`));
|
|
7156
|
+
})
|
|
7157
|
+
);
|
|
7158
|
+
task.command("can-start [id]").description("Ask whether work on this task may begin. Refuses until the owner approves.").action(
|
|
7159
|
+
action(async ({ ctx, args }) => {
|
|
7160
|
+
const id = resolveTaskId(ctx, args[0]);
|
|
7161
|
+
const row = await api(
|
|
7162
|
+
ctx,
|
|
7163
|
+
`/v1/project-tasks/${encodeURIComponent(id)}/dispatch-check`,
|
|
7164
|
+
{ method: "POST" }
|
|
7165
|
+
);
|
|
7166
|
+
ok(row, () => line(import_picocolors27.default.green("approved \u2014 you may start")));
|
|
7167
|
+
})
|
|
7168
|
+
);
|
|
7169
|
+
task.command("approval").description("Ask the owner to approve the plan, or record their decision").argument("<request|approve|decline>").option("--task <id>", "defaults to the task this run is inside").option("--note <text>", "why").action(
|
|
7170
|
+
action(async ({ ctx, args, opts }) => {
|
|
7171
|
+
const id = resolveTaskId(ctx, opts.task);
|
|
7172
|
+
const what = args[0];
|
|
7173
|
+
if (what === "request") {
|
|
7174
|
+
const row2 = await api(
|
|
7175
|
+
ctx,
|
|
7176
|
+
`/v1/project-tasks/${encodeURIComponent(id)}`
|
|
7177
|
+
);
|
|
7178
|
+
ok({ awaiting: row2.approval_state === "awaiting", task: row2 }, () => {
|
|
7179
|
+
line(
|
|
7180
|
+
row2.approval_state === "awaiting" ? import_picocolors27.default.yellow("The plan is waiting on the owner. They see it in the task.") : `Already ${row2.approval_state}.`
|
|
7181
|
+
);
|
|
7182
|
+
});
|
|
7183
|
+
return;
|
|
7184
|
+
}
|
|
7185
|
+
if (what !== "approve" && what !== "decline") {
|
|
7186
|
+
throw new WorkserError(
|
|
7187
|
+
`Expected "request", "approve" or "decline", got "${what}".`,
|
|
7188
|
+
{ code: "bad_request" }
|
|
7189
|
+
);
|
|
7190
|
+
}
|
|
7191
|
+
const row = await api(
|
|
7192
|
+
ctx,
|
|
7193
|
+
`/v1/project-tasks/${encodeURIComponent(id)}/approval`,
|
|
7194
|
+
{
|
|
7195
|
+
body: {
|
|
7196
|
+
decision: what === "approve" ? "approved" : "declined",
|
|
7197
|
+
note: opts.note
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
);
|
|
7201
|
+
ok(row, () => line(`${import_picocolors27.default.green(row.approval_state)} ${import_picocolors27.default.bold(row.title)}`));
|
|
7202
|
+
})
|
|
7203
|
+
);
|
|
7204
|
+
task.command("done [id]").description("Record what a step produced, and move it to ready").option("--summary <text>", "what changed, in the owner's words").action(
|
|
7205
|
+
action(async ({ ctx, args, opts }) => {
|
|
7206
|
+
const id = resolveTaskId(ctx, args[0]);
|
|
7207
|
+
await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`, {
|
|
7208
|
+
method: "PATCH",
|
|
7209
|
+
body: { resultSummary: opts.summary }
|
|
7210
|
+
});
|
|
7211
|
+
const row = await api(
|
|
7212
|
+
ctx,
|
|
7213
|
+
`/v1/project-tasks/${encodeURIComponent(id)}/move`,
|
|
7214
|
+
{ body: { status: "ready" } }
|
|
7215
|
+
);
|
|
7216
|
+
ok(row, () => line(`${import_picocolors27.default.green("ready")} ${import_picocolors27.default.bold(row.title)}`));
|
|
7217
|
+
})
|
|
7218
|
+
);
|
|
7219
|
+
}
|
|
7220
|
+
function resolveTaskId(ctx, given) {
|
|
7221
|
+
const id = given || ctx.projectTaskId;
|
|
7222
|
+
if (!id) {
|
|
7223
|
+
throw new WorkserError(
|
|
7224
|
+
"No task. This run isn't inside one (WORKSER_PROJECT_TASK_ID is unset), so pass --task <id or key>.",
|
|
7225
|
+
{ code: "no_task" }
|
|
7226
|
+
);
|
|
7227
|
+
}
|
|
7228
|
+
return id;
|
|
7229
|
+
}
|
|
7230
|
+
async function resolveDeps(ctx, parentId, given) {
|
|
7231
|
+
if (!given.length) return void 0;
|
|
7232
|
+
const parent = await api(
|
|
7233
|
+
ctx,
|
|
7234
|
+
`/v1/project-tasks/${encodeURIComponent(parentId)}`
|
|
7235
|
+
);
|
|
7236
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
7237
|
+
for (const s of parent.subtasks ?? []) {
|
|
7238
|
+
byKey.set(s.id, s.id);
|
|
7239
|
+
if (s.key) byKey.set(s.key, s.id);
|
|
7240
|
+
}
|
|
7241
|
+
return given.map((g) => {
|
|
7242
|
+
const id = byKey.get(g);
|
|
7243
|
+
if (!id) {
|
|
7244
|
+
throw new WorkserError(
|
|
7245
|
+
`"${g}" is not a step of this task. Run \`workser task subtask list\` to see them.`,
|
|
7246
|
+
{ code: "bad_request" }
|
|
7247
|
+
);
|
|
7248
|
+
}
|
|
7249
|
+
return id;
|
|
7250
|
+
});
|
|
7251
|
+
}
|
|
7252
|
+
function assertOneOf(flag, value, allowed) {
|
|
7253
|
+
if (!allowed.includes(value)) {
|
|
7254
|
+
throw new WorkserError(
|
|
7255
|
+
`${flag} must be one of: ${allowed.join(", ")} \u2014 got "${value}".`,
|
|
7256
|
+
{ code: "bad_request" }
|
|
7257
|
+
);
|
|
7258
|
+
}
|
|
7259
|
+
}
|
|
7260
|
+
function formatRow2(r) {
|
|
7261
|
+
const key = import_picocolors27.default.dim((r.key ?? r.id.slice(0, 8)).padEnd(10));
|
|
7262
|
+
const steps = r.subtaskTotal ? import_picocolors27.default.dim(` ${r.subtaskDone}/${r.subtaskTotal}`) : "";
|
|
7263
|
+
const gate = r.approval_state === "awaiting" ? import_picocolors27.default.yellow(" awaiting approval") : "";
|
|
7264
|
+
return `${key} ${statusTag2(r.status)} ${r.title}${steps}${gate}`;
|
|
7265
|
+
}
|
|
7266
|
+
function formatSubtask(r, index) {
|
|
7267
|
+
const n = import_picocolors27.default.dim(String(index).padStart(2, "0"));
|
|
7268
|
+
const role = r.role ? import_picocolors27.default.dim(` [${r.role}]`) : "";
|
|
7269
|
+
const scope = r.scope_paths?.length ? import_picocolors27.default.dim(` owns: ${r.scope_paths.join(", ")}`) : "";
|
|
7270
|
+
return `${n} ${statusTag2(r.status)} ${r.title}${role}${scope}`;
|
|
7271
|
+
}
|
|
7272
|
+
function printTask(row) {
|
|
7273
|
+
line(`${import_picocolors27.default.bold(row.title)} ${import_picocolors27.default.dim(row.key ?? row.id)}`);
|
|
7274
|
+
line(`${statusTag2(row.status)} approval: ${row.approval_state}`);
|
|
7275
|
+
if (row.summary) line(`
|
|
7276
|
+
${row.summary}`);
|
|
7277
|
+
if (row.targets?.length) {
|
|
7278
|
+
line(
|
|
7279
|
+
`
|
|
7280
|
+
touches: ${row.targets.map((t) => t.appName ?? t.ref ?? t.kind).join(", ")}`
|
|
7281
|
+
);
|
|
7282
|
+
}
|
|
7283
|
+
if (row.subtasks?.length) {
|
|
7284
|
+
line(`
|
|
7285
|
+
${import_picocolors27.default.bold("steps")}`);
|
|
7286
|
+
row.subtasks.forEach((s, i) => line(formatSubtask(s, i + 1)));
|
|
7287
|
+
} else {
|
|
7288
|
+
line(import_picocolors27.default.dim("\nNo steps yet."));
|
|
7289
|
+
}
|
|
7290
|
+
}
|
|
7291
|
+
function statusTag2(status) {
|
|
7292
|
+
switch (status) {
|
|
7293
|
+
case "ready":
|
|
7294
|
+
return import_picocolors27.default.green("[ready]");
|
|
7295
|
+
case "working":
|
|
7296
|
+
return import_picocolors27.default.blue("[working]");
|
|
7297
|
+
case "checking":
|
|
7298
|
+
return import_picocolors27.default.cyan("[checking]");
|
|
7299
|
+
case "accepted":
|
|
7300
|
+
return import_picocolors27.default.green("[accepted]");
|
|
7301
|
+
case "archived":
|
|
7302
|
+
return import_picocolors27.default.dim("[archived]");
|
|
7303
|
+
default:
|
|
7304
|
+
return import_picocolors27.default.dim("[todo]");
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
|
|
7308
|
+
// src/commands/decision.ts
|
|
7309
|
+
var import_picocolors28 = __toESM(require_picocolors(), 1);
|
|
6946
7310
|
function registerDecision(program3) {
|
|
6947
7311
|
const decision = program3.command("decision").description("Read and record the project's architecture decisions");
|
|
6948
7312
|
decision.command("list").description("Every decision on record \u2014 read this before changing how something works").option("--limit <n>", "cap the number returned (newest first)").action(
|
|
@@ -6955,11 +7319,11 @@ function registerDecision(program3) {
|
|
|
6955
7319
|
rows = applyLimit(rows, opts.limit);
|
|
6956
7320
|
ok(rows, () => {
|
|
6957
7321
|
if (!rows.length) {
|
|
6958
|
-
line(
|
|
7322
|
+
line(import_picocolors28.default.dim("No decisions recorded yet."));
|
|
6959
7323
|
return;
|
|
6960
7324
|
}
|
|
6961
7325
|
for (const r of rows) {
|
|
6962
|
-
line(`${
|
|
7326
|
+
line(`${import_picocolors28.default.dim(r.id)} ${import_picocolors28.default.dim(shortDate(r.createdAt))} ${r.title}`);
|
|
6963
7327
|
line(` ${truncate(r.decision, 100)}`);
|
|
6964
7328
|
}
|
|
6965
7329
|
});
|
|
@@ -6973,16 +7337,16 @@ function registerDecision(program3) {
|
|
|
6973
7337
|
`/v1/projects/${projectId}/architecture-decisions/${args[0]}`
|
|
6974
7338
|
);
|
|
6975
7339
|
ok(row, () => {
|
|
6976
|
-
line(`${
|
|
6977
|
-
line(
|
|
7340
|
+
line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.id)}`);
|
|
7341
|
+
line(import_picocolors28.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
|
|
6978
7342
|
line(`
|
|
6979
|
-
${
|
|
7343
|
+
${import_picocolors28.default.bold("Context")}
|
|
6980
7344
|
${row.context}`);
|
|
6981
7345
|
line(`
|
|
6982
|
-
${
|
|
7346
|
+
${import_picocolors28.default.bold("Decision")}
|
|
6983
7347
|
${row.decision}`);
|
|
6984
7348
|
if (row.consequences) line(`
|
|
6985
|
-
${
|
|
7349
|
+
${import_picocolors28.default.bold("Consequences")}
|
|
6986
7350
|
${row.consequences}`);
|
|
6987
7351
|
});
|
|
6988
7352
|
})
|
|
@@ -7011,7 +7375,7 @@ ${row.consequences}`);
|
|
|
7011
7375
|
refId: row?.id,
|
|
7012
7376
|
output: { decision: row }
|
|
7013
7377
|
});
|
|
7014
|
-
ok(row, () => line(`Recorded decision ${
|
|
7378
|
+
ok(row, () => line(`Recorded decision ${import_picocolors28.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
7015
7379
|
})
|
|
7016
7380
|
);
|
|
7017
7381
|
const requirement = program3.command("requirement").description("Read and record the project's requirements");
|
|
@@ -7023,11 +7387,11 @@ ${row.consequences}`);
|
|
|
7023
7387
|
rows = applyLimit(rows, opts.limit);
|
|
7024
7388
|
ok(rows, () => {
|
|
7025
7389
|
if (!rows.length) {
|
|
7026
|
-
line(
|
|
7390
|
+
line(import_picocolors28.default.dim("No requirements recorded yet."));
|
|
7027
7391
|
return;
|
|
7028
7392
|
}
|
|
7029
7393
|
for (const r of rows) {
|
|
7030
|
-
line(`${
|
|
7394
|
+
line(`${import_picocolors28.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
|
|
7031
7395
|
}
|
|
7032
7396
|
});
|
|
7033
7397
|
})
|
|
@@ -7040,8 +7404,8 @@ ${row.consequences}`);
|
|
|
7040
7404
|
`/v1/projects/${projectId}/requirements/${args[0]}`
|
|
7041
7405
|
);
|
|
7042
7406
|
ok(row, () => {
|
|
7043
|
-
line(`${
|
|
7044
|
-
line(
|
|
7407
|
+
line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.id)}`);
|
|
7408
|
+
line(import_picocolors28.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
|
|
7045
7409
|
line(`
|
|
7046
7410
|
${row.body}`);
|
|
7047
7411
|
});
|
|
@@ -7067,7 +7431,7 @@ ${row.body}`);
|
|
|
7067
7431
|
refId: row?.id,
|
|
7068
7432
|
output: { requirement: row }
|
|
7069
7433
|
});
|
|
7070
|
-
ok(row, () => line(`Recorded requirement ${
|
|
7434
|
+
ok(row, () => line(`Recorded requirement ${import_picocolors28.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
7071
7435
|
})
|
|
7072
7436
|
);
|
|
7073
7437
|
requirement.command("update <id>").description(
|
|
@@ -7096,7 +7460,7 @@ ${row.body}`);
|
|
|
7096
7460
|
code: "bad_request"
|
|
7097
7461
|
});
|
|
7098
7462
|
}
|
|
7099
|
-
ok(row, () => line(`Updated requirement ${
|
|
7463
|
+
ok(row, () => line(`Updated requirement ${import_picocolors28.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
|
|
7100
7464
|
})
|
|
7101
7465
|
);
|
|
7102
7466
|
}
|
|
@@ -7119,7 +7483,7 @@ function shortDate(iso) {
|
|
|
7119
7483
|
}
|
|
7120
7484
|
|
|
7121
7485
|
// src/commands/doc.ts
|
|
7122
|
-
var
|
|
7486
|
+
var import_picocolors29 = __toESM(require_picocolors(), 1);
|
|
7123
7487
|
function registerDoc(program3) {
|
|
7124
7488
|
const doc = program3.command("doc").description("Read and write project documents");
|
|
7125
7489
|
doc.command("list").description("List the project's documents \u2014 check here before writing a new one").option("--work-item <id>", "the document linked to this card, if there is one").action(
|
|
@@ -7130,13 +7494,13 @@ function registerDoc(program3) {
|
|
|
7130
7494
|
}) ?? [];
|
|
7131
7495
|
ok(rows, () => {
|
|
7132
7496
|
if (!rows.length) {
|
|
7133
|
-
line(
|
|
7497
|
+
line(import_picocolors29.default.dim("No documents yet."));
|
|
7134
7498
|
return;
|
|
7135
7499
|
}
|
|
7136
7500
|
for (const r of rows) {
|
|
7137
|
-
const link = r.workItemId ?
|
|
7138
|
-
const file = r.filePath ?
|
|
7139
|
-
line(`${
|
|
7501
|
+
const link = r.workItemId ? import_picocolors29.default.dim(` \u21B3 ${r.workItemId}`) : "";
|
|
7502
|
+
const file = r.filePath ? import_picocolors29.default.dim(` ${r.filePath}`) : "";
|
|
7503
|
+
line(`${import_picocolors29.default.dim(r.id)} ${r.title}${link}${file}`);
|
|
7140
7504
|
}
|
|
7141
7505
|
});
|
|
7142
7506
|
})
|
|
@@ -7150,17 +7514,17 @@ function registerDoc(program3) {
|
|
|
7150
7514
|
);
|
|
7151
7515
|
if (opts.markdown) {
|
|
7152
7516
|
ok({ id: row.id, title: row.title, filePath: row.filePath }, () => {
|
|
7153
|
-
line(`${
|
|
7517
|
+
line(`${import_picocolors29.default.bold(row.title)} ${import_picocolors29.default.dim(row.id)}`);
|
|
7154
7518
|
line(
|
|
7155
|
-
row.filePath ? `Read it at ${
|
|
7519
|
+
row.filePath ? `Read it at ${import_picocolors29.default.bold(row.filePath)} (relative to the project folder).` : import_picocolors29.default.dim("This document has no markdown mirror on disk yet.")
|
|
7156
7520
|
);
|
|
7157
7521
|
});
|
|
7158
7522
|
return;
|
|
7159
7523
|
}
|
|
7160
7524
|
ok(row, () => {
|
|
7161
|
-
line(`${
|
|
7162
|
-
if (row.workItemId) line(
|
|
7163
|
-
if (row.filePath) line(
|
|
7525
|
+
line(`${import_picocolors29.default.bold(row.title)} ${import_picocolors29.default.dim(row.id)}`);
|
|
7526
|
+
if (row.workItemId) line(import_picocolors29.default.dim(`linked to work item ${row.workItemId}`));
|
|
7527
|
+
if (row.filePath) line(import_picocolors29.default.dim(`markdown mirror: ${row.filePath}`));
|
|
7164
7528
|
line("");
|
|
7165
7529
|
line(row.contentJson);
|
|
7166
7530
|
});
|
|
@@ -7189,7 +7553,7 @@ function registerDoc(program3) {
|
|
|
7189
7553
|
refId: row?.id,
|
|
7190
7554
|
output: { document: row }
|
|
7191
7555
|
});
|
|
7192
|
-
ok(row, () => line(`Created document ${
|
|
7556
|
+
ok(row, () => line(`Created document ${import_picocolors29.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
7193
7557
|
})
|
|
7194
7558
|
);
|
|
7195
7559
|
doc.command("update <id>").description("Revise an existing document rather than creating a second copy of it").option("--title <text>", "new title").option("--markdown <text>", "replace the body with this markdown").option("--content-json <json>", "replace the body with this rich-text content JSON").action(
|
|
@@ -7216,13 +7580,13 @@ function registerDoc(program3) {
|
|
|
7216
7580
|
code: "bad_request"
|
|
7217
7581
|
});
|
|
7218
7582
|
}
|
|
7219
|
-
ok(row, () => line(`Updated document ${
|
|
7583
|
+
ok(row, () => line(`Updated document ${import_picocolors29.default.bold(row.id)} \u2014 ${row.title}`));
|
|
7220
7584
|
})
|
|
7221
7585
|
);
|
|
7222
7586
|
}
|
|
7223
7587
|
|
|
7224
7588
|
// src/commands/design.ts
|
|
7225
|
-
var
|
|
7589
|
+
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
7226
7590
|
function registerDesign(program3) {
|
|
7227
7591
|
const design = program3.command("design").description("Read the project's brand (colours, fonts, logo)");
|
|
7228
7592
|
design.command("show").description("Show this project's brand \u2014 read it before writing any UI").option("--raw", "print the generated token files verbatim instead of a summary").action(
|
|
@@ -7236,11 +7600,11 @@ function registerDesign(program3) {
|
|
|
7236
7600
|
if (opts.raw) {
|
|
7237
7601
|
ok(files, () => {
|
|
7238
7602
|
if (!files.length) {
|
|
7239
|
-
line(
|
|
7603
|
+
line(import_picocolors30.default.dim("No brand set for this project."));
|
|
7240
7604
|
return;
|
|
7241
7605
|
}
|
|
7242
7606
|
for (const f of files) {
|
|
7243
|
-
line(
|
|
7607
|
+
line(import_picocolors30.default.bold(f.path));
|
|
7244
7608
|
line(f.contents);
|
|
7245
7609
|
line("");
|
|
7246
7610
|
}
|
|
@@ -7257,21 +7621,21 @@ function registerDesign(program3) {
|
|
|
7257
7621
|
} : { hasBrand: false, colors: {}, fonts: {}, brand: {}, files: [] };
|
|
7258
7622
|
ok(summary, () => {
|
|
7259
7623
|
if (!tokens) {
|
|
7260
|
-
line(
|
|
7624
|
+
line(import_picocolors30.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
|
|
7261
7625
|
return;
|
|
7262
7626
|
}
|
|
7263
7627
|
for (const [name, value] of Object.entries(tokens.brand)) {
|
|
7264
|
-
line(`${
|
|
7628
|
+
line(`${import_picocolors30.default.dim(name.padEnd(12))} ${value}`);
|
|
7265
7629
|
}
|
|
7266
7630
|
for (const [name, value] of Object.entries(tokens.color)) {
|
|
7267
|
-
line(`${
|
|
7631
|
+
line(`${import_picocolors30.default.dim(`color.${name}`.padEnd(12))} ${value}`);
|
|
7268
7632
|
}
|
|
7269
7633
|
for (const [name, value] of Object.entries(tokens.font)) {
|
|
7270
|
-
line(`${
|
|
7634
|
+
line(`${import_picocolors30.default.dim(`font.${name}`.padEnd(12))} ${value}`);
|
|
7271
7635
|
}
|
|
7272
7636
|
line("");
|
|
7273
7637
|
line(
|
|
7274
|
-
|
|
7638
|
+
import_picocolors30.default.dim(
|
|
7275
7639
|
`Generated into the working tree as ${files.map((f) => f.path).join(", ")} \u2014 wire those in, never edit them.`
|
|
7276
7640
|
)
|
|
7277
7641
|
);
|
|
@@ -7303,7 +7667,7 @@ function unwrap(group) {
|
|
|
7303
7667
|
|
|
7304
7668
|
// src/index.ts
|
|
7305
7669
|
var pkg = {
|
|
7306
|
-
version: true ? "0.
|
|
7670
|
+
version: true ? "0.3.0" : "0.0.0-dev"
|
|
7307
7671
|
};
|
|
7308
7672
|
var program2 = new Command();
|
|
7309
7673
|
program2.name("workser").description(
|
|
@@ -7348,6 +7712,7 @@ registerImage(program2);
|
|
|
7348
7712
|
registerAsk(program2);
|
|
7349
7713
|
registerSearch(program2);
|
|
7350
7714
|
registerBoard(program2);
|
|
7715
|
+
registerTask(program2);
|
|
7351
7716
|
registerDecision(program2);
|
|
7352
7717
|
registerDoc(program2);
|
|
7353
7718
|
registerDesign(program2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workser/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Workser CLI — give your local AI agent native DevOps & infrastructure on Workser. The agent runs `workser …` to provision, deploy, and manage real apps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: tasks
|
|
3
|
+
title: Project tasks & subtasks
|
|
4
|
+
summary: The ticket you are working inside: read it, break it into steps, and ask before starting.
|
|
5
|
+
commands: [task]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Project tasks & subtasks
|
|
9
|
+
|
|
10
|
+
A **project task** is a ticket the owner filed. You are usually running inside
|
|
11
|
+
one — Orbit sets `WORKSER_PROJECT_TASK_ID` on your process, so every command
|
|
12
|
+
below defaults to it and you rarely pass an id at all.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
workser task list [--status <value>] [--label <value>] [--limit <n>]
|
|
16
|
+
workser task show [id] # the task you are in, with its steps
|
|
17
|
+
|
|
18
|
+
workser task subtask add <title> [--role <value>] [--kind <value>]
|
|
19
|
+
[--note <text>] [--app <id...>]
|
|
20
|
+
[--infra <ref...>] [--scope <path...>]
|
|
21
|
+
[--depends-on <key...>]
|
|
22
|
+
workser task subtask list [taskId]
|
|
23
|
+
workser task subtask update <id> [--title|--note|--role|--kind|--scope]
|
|
24
|
+
workser task subtask remove <id>
|
|
25
|
+
|
|
26
|
+
workser task can-start [id] # may work begin? refuses until approved
|
|
27
|
+
workser task approval request # tell the owner the plan is ready
|
|
28
|
+
workser task move <id> <status>
|
|
29
|
+
workser task done [id] --summary <text>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## This is not `workser board`
|
|
33
|
+
|
|
34
|
+
`board` is the Orbit Board — a human's list of work items. `task` is the AI Tech
|
|
35
|
+
Team's own table. Filing your plan on the Board puts it somewhere the owner's
|
|
36
|
+
task page never reads: they see "created work item" and an empty plan. Use
|
|
37
|
+
`task subtask add`.
|
|
38
|
+
|
|
39
|
+
## Planning a task
|
|
40
|
+
|
|
41
|
+
Read the project first, then propose. One `subtask add` per step:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
workser task subtask add "Build the upload endpoint" \
|
|
45
|
+
--role api --kind service \
|
|
46
|
+
--note "Accept a file, work out its type, hand it to the right analyzer." \
|
|
47
|
+
--scope src/app/api/analyze/route.ts
|
|
48
|
+
|
|
49
|
+
workser task subtask add "Check every supported file type end to end" \
|
|
50
|
+
--role qa --depends-on RIZZ-15
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`--role` is one of: pm, architect, web, api, automation, qa.
|
|
54
|
+
`--kind` is what the step produces: data_reports, web, mobile, service,
|
|
55
|
+
automation, docs. It is not the same fact as the role — the same engineer
|
|
56
|
+
writing a screen, the docs for it and the service behind it is three kinds of
|
|
57
|
+
work.
|
|
58
|
+
|
|
59
|
+
`--scope` is what that step OWNS. Two steps naming the same file cannot run at
|
|
60
|
+
the same time, so keeping scopes apart is what lets the team work in parallel.
|
|
61
|
+
`--depends-on` takes the keys you read off `task show`.
|
|
62
|
+
|
|
63
|
+
Between three and six steps. If it needs more, say the task is too big instead.
|
|
64
|
+
The step that CHECKS work must not be the same role as the one that built it.
|
|
65
|
+
|
|
66
|
+
## Nothing runs until the owner approves
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
workser task can-start
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This exits non-zero, with the reason, until they have approved the plan — that
|
|
73
|
+
refusal is the product working, not an error to route around. Ask with
|
|
74
|
+
`workser task approval request`; only a person can answer.
|
|
75
|
+
|
|
76
|
+
## Finishing a step
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
workser task done --summary "The report now shows cost per KOL, with six months of history."
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Write the summary for someone who runs a business and does not read code.
|