@workser/cli 0.6.1 → 0.6.2
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 +515 -120
- package/package.json +1 -1
- package/skills/workser/SKILL.md +1 -0
- package/skills/workser/reference/goals.md +69 -0
package/dist/index.js
CHANGED
|
@@ -4485,6 +4485,76 @@ same rule.
|
|
|
4485
4485
|
- **Cloud and local are different environments.** \`env set\` configures the
|
|
4486
4486
|
cloud; the files in the app folder configure this computer. Don't hand-edit
|
|
4487
4487
|
one to change the other.
|
|
4488
|
+
`
|
|
4489
|
+
},
|
|
4490
|
+
{
|
|
4491
|
+
topic: "goals",
|
|
4492
|
+
title: "Business goals & phases",
|
|
4493
|
+
summary: "The level above a task \u2014 a business requirement too big for one sitting, and the ordered phases that deliver it.",
|
|
4494
|
+
commands: ["goal"],
|
|
4495
|
+
source: "skills/workser/reference/goals.md",
|
|
4496
|
+
body: `# Business goals & phases
|
|
4497
|
+
|
|
4498
|
+
A **goal** is a business requirement \u2014 "customers can buy from my site" \u2014 that
|
|
4499
|
+
does not fit in one task. It carries an ordered list of **phase names**; a task
|
|
4500
|
+
joins one by carrying that name (\`workser task create --goal <id> --phase <name>\`).
|
|
4501
|
+
A phase has no existence apart from the tasks inside it, so it cannot drift from
|
|
4502
|
+
the work it names.
|
|
4503
|
+
|
|
4504
|
+
\`\`\`
|
|
4505
|
+
workser goal list
|
|
4506
|
+
workser goal show <id>
|
|
4507
|
+
workser goal create <title> --phase <name...> [--outcome <text>]
|
|
4508
|
+
[--criteria <json>]
|
|
4509
|
+
workser goal update <id> [--title <text>] [--outcome <text>]
|
|
4510
|
+
[--phase <name...>] [--status <value>]
|
|
4511
|
+
workser goal check <id> <criterionId> --phase <name> (--pass|--fail|--reset) [--note <text>]
|
|
4512
|
+
\`\`\`
|
|
4513
|
+
|
|
4514
|
+
Status is one of: proposed, agreed, working, delivered, abandoned.
|
|
4515
|
+
|
|
4516
|
+
## A goal has to be argued for
|
|
4517
|
+
|
|
4518
|
+
Most requests are one task and should stay one task. Turning a two-hour job into
|
|
4519
|
+
four milestones buries the owner in ceremony before anything is built \u2014 use
|
|
4520
|
+
\`workser task create\` unless you can genuinely name two-plus slices that each
|
|
4521
|
+
deliver something the owner would notice.
|
|
4522
|
+
|
|
4523
|
+
\`\`\`
|
|
4524
|
+
workser goal create "Launch checkout" \\
|
|
4525
|
+
--phase "Cart" --phase "Payment" --phase "Receipts" \\
|
|
4526
|
+
--outcome "A customer can buy something and get a receipt"
|
|
4527
|
+
\`\`\`
|
|
4528
|
+
|
|
4529
|
+
\`--phase\` needs 2\u20136 names. Propose the shape only \u2014 nothing is created until the
|
|
4530
|
+
owner agrees it; planning phase four now is waste, since it will change once
|
|
4531
|
+
phase one is real.
|
|
4532
|
+
|
|
4533
|
+
## Acceptance criteria are agreed with the shape
|
|
4534
|
+
|
|
4535
|
+
\`--criteria\` maps each phase name to the owner's own sentences about what "done"
|
|
4536
|
+
means for it, e.g. \`'{"Payment":["A customer can pay by card and gets a receipt"]}'\`.
|
|
4537
|
+
Written afterwards they only describe what got built; written with the shape they
|
|
4538
|
+
can still change the plan.
|
|
4539
|
+
|
|
4540
|
+
Record whether one is met with \`workser goal check\`:
|
|
4541
|
+
|
|
4542
|
+
\`\`\`
|
|
4543
|
+
workser goal check g_123 c1 --phase Payment --pass \\
|
|
4544
|
+
--note "Tested a card payment end to end; receipt emailed."
|
|
4545
|
+
\`\`\`
|
|
4546
|
+
|
|
4547
|
+
\`--note\` is required on \`--pass\` \u2014 a tick the owner cannot verify is worse than no
|
|
4548
|
+
tick.
|
|
4549
|
+
|
|
4550
|
+
## Joining tasks to a goal
|
|
4551
|
+
|
|
4552
|
+
\`\`\`
|
|
4553
|
+
workser task create "Build the payment form" --goal g_123 --phase Payment
|
|
4554
|
+
\`\`\`
|
|
4555
|
+
|
|
4556
|
+
A goal's apps and progress are derived from the tasks that join it, not declared
|
|
4557
|
+
up front \u2014 most of the apps a goal will touch don't exist when it's proposed.
|
|
4488
4558
|
`
|
|
4489
4559
|
},
|
|
4490
4560
|
{
|
|
@@ -7858,7 +7928,22 @@ var TYPES = [
|
|
|
7858
7928
|
"approval",
|
|
7859
7929
|
"confirmation",
|
|
7860
7930
|
"file_upload",
|
|
7861
|
-
"information"
|
|
7931
|
+
"information",
|
|
7932
|
+
/**
|
|
7933
|
+
* "Add an app to this project, and do it for me."
|
|
7934
|
+
*
|
|
7935
|
+
* NOT an approval with a well-worded message. What the user is agreeing to
|
|
7936
|
+
* is real infrastructure — a repository, hosting, a folder on their machine,
|
|
7937
|
+
* sometimes a database — and they are entitled to see WHICH app before they
|
|
7938
|
+
* agree, which a sentence and a Yes button cannot show. Needs `--app-type`;
|
|
7939
|
+
* `--app-name` is what it will be called.
|
|
7940
|
+
*
|
|
7941
|
+
* You cannot create an app yourself, and that is deliberate: an agent must
|
|
7942
|
+
* not be able to provision things on somebody's account by deciding to. This
|
|
7943
|
+
* asks. Their click is what creates it, and the answer tells you the new
|
|
7944
|
+
* app's id so the rest of your plan can be scoped to it.
|
|
7945
|
+
*/
|
|
7946
|
+
"create_app"
|
|
7862
7947
|
];
|
|
7863
7948
|
function registerAsk(program3) {
|
|
7864
7949
|
program3.command("ask <message>").description("Ask the user a question and wait for their answer").option(
|
|
@@ -7870,6 +7955,12 @@ function registerAsk(program3) {
|
|
|
7870
7955
|
"a choice (repeat for each; implies --type choice)",
|
|
7871
7956
|
collect,
|
|
7872
7957
|
[]
|
|
7958
|
+
).option(
|
|
7959
|
+
"--app-type <type>",
|
|
7960
|
+
"create_app only: web | mobile | api | worker | cron | python"
|
|
7961
|
+
).option(
|
|
7962
|
+
"--app-name <name>",
|
|
7963
|
+
"create_app only: what the app should be called"
|
|
7873
7964
|
).option("--priority <level>", "low | normal | high | urgent", "normal").option(
|
|
7874
7965
|
"--timeout <seconds>",
|
|
7875
7966
|
"how long to wait before giving up (default: 600, max: 3600)",
|
|
@@ -7896,6 +7987,12 @@ function registerAsk(program3) {
|
|
|
7896
7987
|
{ code: "bad_request" }
|
|
7897
7988
|
);
|
|
7898
7989
|
}
|
|
7990
|
+
if (type === "create_app" && !opts.appType) {
|
|
7991
|
+
throw new WorkserError(
|
|
7992
|
+
"Say which kind of app: `--app-type mobile`.",
|
|
7993
|
+
{ code: "bad_request" }
|
|
7994
|
+
);
|
|
7995
|
+
}
|
|
7899
7996
|
const timeoutSeconds = Number(opts.timeout);
|
|
7900
7997
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
7901
7998
|
throw new WorkserError("--timeout must be a positive number.", {
|
|
@@ -7909,6 +8006,8 @@ function registerAsk(program3) {
|
|
|
7909
8006
|
title: opts.title || deriveTitle(message),
|
|
7910
8007
|
message,
|
|
7911
8008
|
options: options.length ? options : void 0,
|
|
8009
|
+
appType: opts.appType,
|
|
8010
|
+
appName: opts.appName,
|
|
7912
8011
|
priority: opts.priority,
|
|
7913
8012
|
timeoutSeconds
|
|
7914
8013
|
}
|
|
@@ -8203,10 +8302,18 @@ function registerTask(program3) {
|
|
|
8203
8302
|
action(async ({ ctx, args }) => {
|
|
8204
8303
|
const id = args[0] || ctx.parentTaskId || resolveTaskId(ctx);
|
|
8205
8304
|
const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
|
|
8206
|
-
|
|
8305
|
+
const goalId = row.goal_id;
|
|
8306
|
+
const goal = goalId ? await api(
|
|
8307
|
+
ctx,
|
|
8308
|
+
`/v1/project-goals/${encodeURIComponent(goalId)}`
|
|
8309
|
+
).catch(() => null) : null;
|
|
8310
|
+
ok({ ...row, goal }, () => printTask(row, goal));
|
|
8207
8311
|
})
|
|
8208
8312
|
);
|
|
8209
|
-
task.command("create <title>").description("Open a new root task for the project team").option("--note <text>", "context, constraints and expected outcome").option("--kind <value>", `what it produces (${KINDS2.join(" | ")})`).option("--label <value...>", "labels to put on the task").option("--app <id...>", "apps the task touches").option("--infra <ref...>", "shared setup it touches").
|
|
8313
|
+
task.command("create <title>").description("Open a new root task for the project team").option("--note <text>", "context, constraints and expected outcome").option("--kind <value>", `what it produces (${KINDS2.join(" | ")})`).option("--label <value...>", "labels to put on the task").option("--app <id...>", "apps the task touches").option("--infra <ref...>", "shared setup it touches").option("--goal <id>", "the business goal this delivers part of").option(
|
|
8314
|
+
"--phase <name>",
|
|
8315
|
+
"which slice of that goal \u2014 must match one of its phase names"
|
|
8316
|
+
).action(
|
|
8210
8317
|
action(async ({ ctx, args, opts }) => {
|
|
8211
8318
|
requireProject(ctx);
|
|
8212
8319
|
if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
|
|
@@ -8219,6 +8326,10 @@ function registerTask(program3) {
|
|
|
8219
8326
|
summary: opts.note,
|
|
8220
8327
|
category: opts.kind,
|
|
8221
8328
|
labels: opts.label,
|
|
8329
|
+
// The level above this task, when it has one. Most tasks do not —
|
|
8330
|
+
// a goal has to be argued for, never assumed.
|
|
8331
|
+
goalId: opts.goal,
|
|
8332
|
+
phase: opts.phase,
|
|
8222
8333
|
targets: [
|
|
8223
8334
|
...(opts.app ?? []).map((appId) => ({ kind: "app", appId })),
|
|
8224
8335
|
...(opts.infra ?? []).map((ref) => ({ kind: "infra", ref }))
|
|
@@ -8271,13 +8382,13 @@ function registerTask(program3) {
|
|
|
8271
8382
|
});
|
|
8272
8383
|
})
|
|
8273
8384
|
);
|
|
8274
|
-
const subtask = task.command("subtask").description("The
|
|
8385
|
+
const subtask = task.command("subtask").description("The subtasks a task is broken into");
|
|
8275
8386
|
subtask.command("add <title>").description(
|
|
8276
|
-
'Add one
|
|
8277
|
-
).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
|
|
8387
|
+
'Add one subtask, e.g. `workser task subtask add "Build the upload screen" --role web`'
|
|
8388
|
+
).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 subtask does").option(
|
|
8278
8389
|
"--app <id...>",
|
|
8279
|
-
"the app this
|
|
8280
|
-
).option("--infra <ref...>", "shared setup it touches (database | storage | auth | hosting | jobs)").option("--scope <path...>", "files or folders THIS
|
|
8390
|
+
"the app this subtask is for \u2014 one id runs it inside that app's folder; leave it off and it runs at the project, seeing every app"
|
|
8391
|
+
).option("--infra <ref...>", "shared setup it touches (database | storage | auth | hosting | jobs)").option("--scope <path...>", "files or folders THIS subtask owns").option("--depends-on <id...>", "subtasks that must finish first (key or id)").action(
|
|
8281
8392
|
action(async ({ ctx, args, opts }) => {
|
|
8282
8393
|
const parent = resolveTaskId(ctx, opts.task);
|
|
8283
8394
|
if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
|
|
@@ -8304,21 +8415,21 @@ function registerTask(program3) {
|
|
|
8304
8415
|
});
|
|
8305
8416
|
})
|
|
8306
8417
|
);
|
|
8307
|
-
subtask.command("list [taskId]").description("The
|
|
8418
|
+
subtask.command("list [taskId]").description("The subtasks of a task, in order").action(
|
|
8308
8419
|
action(async ({ ctx, args }) => {
|
|
8309
8420
|
const id = resolveTaskId(ctx, args[0]);
|
|
8310
8421
|
const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
|
|
8311
8422
|
const rows = row.subtasks ?? [];
|
|
8312
8423
|
ok(rows, () => {
|
|
8313
8424
|
if (!rows.length) {
|
|
8314
|
-
line(import_picocolors28.default.dim("No
|
|
8425
|
+
line(import_picocolors28.default.dim("No subtasks yet."));
|
|
8315
8426
|
return;
|
|
8316
8427
|
}
|
|
8317
8428
|
rows.forEach((r, i) => line(formatSubtask(r, i + 1)));
|
|
8318
8429
|
});
|
|
8319
8430
|
})
|
|
8320
8431
|
);
|
|
8321
|
-
subtask.command("update <id>").description("Change a
|
|
8432
|
+
subtask.command("update <id>").description("Change a subtask \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 subtask's scope entirely").action(
|
|
8322
8433
|
action(async ({ ctx, args, opts }) => {
|
|
8323
8434
|
if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
|
|
8324
8435
|
if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
|
|
@@ -8339,7 +8450,7 @@ function registerTask(program3) {
|
|
|
8339
8450
|
ok(row, () => line(`${import_picocolors28.default.green("updated")} ${import_picocolors28.default.bold(row.title)}`));
|
|
8340
8451
|
})
|
|
8341
8452
|
);
|
|
8342
|
-
subtask.command("remove <id>").description("
|
|
8453
|
+
subtask.command("remove <id>").description("Remove a subtask (only before the work starts)").action(
|
|
8343
8454
|
action(async ({ ctx, args }) => {
|
|
8344
8455
|
await api(ctx, `/v1/project-tasks/${encodeURIComponent(args[0])}`, {
|
|
8345
8456
|
method: "DELETE"
|
|
@@ -8359,7 +8470,7 @@ function registerTask(program3) {
|
|
|
8359
8470
|
})
|
|
8360
8471
|
);
|
|
8361
8472
|
task.command("resume [id]").description(
|
|
8362
|
-
"Put a
|
|
8473
|
+
"Put a task the owner stopped back in the queue \u2014 only when they ask you to carry on"
|
|
8363
8474
|
).action(
|
|
8364
8475
|
action(async ({ ctx, args }) => {
|
|
8365
8476
|
const id = resolveTaskId(ctx, args[0]);
|
|
@@ -8442,7 +8553,7 @@ function registerTask(program3) {
|
|
|
8442
8553
|
ok(row, () => line(`${import_picocolors28.default.green(row.approval_state)} ${import_picocolors28.default.bold(row.title)}`));
|
|
8443
8554
|
})
|
|
8444
8555
|
);
|
|
8445
|
-
task.command("done [id]").description("Record what a
|
|
8556
|
+
task.command("done [id]").description("Record what a subtask produced, and move it to ready").option("--summary <text>", "what changed, in the owner's words").action(
|
|
8446
8557
|
action(async ({ ctx, args, opts }) => {
|
|
8447
8558
|
const id = resolveTaskId(ctx, args[0]);
|
|
8448
8559
|
await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`, {
|
|
@@ -8483,7 +8594,7 @@ async function resolveDeps(ctx, parentId, given) {
|
|
|
8483
8594
|
const id = byKey.get(g);
|
|
8484
8595
|
if (!id) {
|
|
8485
8596
|
throw new WorkserError(
|
|
8486
|
-
`"${g}" is not a
|
|
8597
|
+
`"${g}" is not a subtask of this task. Run \`workser task subtask list\` to see them.`,
|
|
8487
8598
|
{ code: "bad_request" }
|
|
8488
8599
|
);
|
|
8489
8600
|
}
|
|
@@ -8515,11 +8626,12 @@ function formatSubtask(r, index) {
|
|
|
8515
8626
|
return `${head}
|
|
8516
8627
|
${import_picocolors28.default.dim(wrapped)}`;
|
|
8517
8628
|
}
|
|
8518
|
-
function printTask(row) {
|
|
8629
|
+
function printTask(row, goal) {
|
|
8519
8630
|
line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.key ?? row.id)}`);
|
|
8520
8631
|
line(`${statusTag2(row.status)} approval: ${row.approval_state}`);
|
|
8521
8632
|
if (row.summary) line(`
|
|
8522
8633
|
${row.summary}`);
|
|
8634
|
+
if (goal) printGoalContext(row, goal);
|
|
8523
8635
|
if (row.targets?.length) {
|
|
8524
8636
|
line(
|
|
8525
8637
|
`
|
|
@@ -8528,23 +8640,53 @@ touches: ${row.targets.map((t) => t.appName ?? t.ref ?? t.kind).join(", ")}`
|
|
|
8528
8640
|
}
|
|
8529
8641
|
if (row.subtasks?.length) {
|
|
8530
8642
|
line(`
|
|
8531
|
-
${import_picocolors28.default.bold("
|
|
8643
|
+
${import_picocolors28.default.bold("subtasks")}`);
|
|
8532
8644
|
row.subtasks.forEach((s, i) => line(formatSubtask(s, i + 1)));
|
|
8533
8645
|
line(
|
|
8534
8646
|
import_picocolors28.default.dim(
|
|
8535
8647
|
`
|
|
8536
|
-
Run \`workser artifact list\` to see what these
|
|
8648
|
+
Run \`workser artifact list\` to see what these subtasks produced,`
|
|
8537
8649
|
)
|
|
8538
8650
|
);
|
|
8539
8651
|
line(
|
|
8540
8652
|
import_picocolors28.default.dim(
|
|
8541
|
-
`or \`workser artifact list --step <id>\` for one
|
|
8653
|
+
`or \`workser artifact list --step <id>\` for one subtask's output alone.`
|
|
8542
8654
|
)
|
|
8543
8655
|
);
|
|
8544
8656
|
} else {
|
|
8545
|
-
line(import_picocolors28.default.dim("\nNo
|
|
8657
|
+
line(import_picocolors28.default.dim("\nNo subtasks yet."));
|
|
8546
8658
|
}
|
|
8547
8659
|
}
|
|
8660
|
+
function printGoalContext(row, goal) {
|
|
8661
|
+
const mine = row.phase ?? null;
|
|
8662
|
+
line(`
|
|
8663
|
+
${import_picocolors28.default.bold("part of")}: ${goal.title}`);
|
|
8664
|
+
if (goal.outcome) line(import_picocolors28.default.dim(`done means: ${goal.outcome}`));
|
|
8665
|
+
const progress = goal.progress ?? [];
|
|
8666
|
+
for (const p of progress) {
|
|
8667
|
+
const where = p.total === 0 ? "not started" : `${p.done} of ${p.total} done`;
|
|
8668
|
+
const here = mine && p.name === mine ? import_picocolors28.default.cyan(" <- this task") : "";
|
|
8669
|
+
const mark = p.state === "done" ? import_picocolors28.default.green("*") : import_picocolors28.default.dim("-");
|
|
8670
|
+
line(` ${mark} ${p.name} \u2014 ${where}${here}`);
|
|
8671
|
+
}
|
|
8672
|
+
const next = progress.find((p) => p.state !== "done");
|
|
8673
|
+
const running = progress.some((p) => p.state === "working");
|
|
8674
|
+
if (next && !running) {
|
|
8675
|
+
line(
|
|
8676
|
+
import_picocolors28.default.dim(
|
|
8677
|
+
`
|
|
8678
|
+
Nothing is running. The next part waiting is "${next.name}" \u2014 offer it to them in a sentence rather than starting it unasked.`
|
|
8679
|
+
)
|
|
8680
|
+
);
|
|
8681
|
+
} else if (!next) {
|
|
8682
|
+
line(import_picocolors28.default.dim("\nEvery part of this plan is done. Say so, and offer to wrap it up."));
|
|
8683
|
+
}
|
|
8684
|
+
line(
|
|
8685
|
+
import_picocolors28.default.dim(
|
|
8686
|
+
"The plan is a reference for what was agreed, not a rule about what may run. Work can start on any part at any time if they ask for it."
|
|
8687
|
+
)
|
|
8688
|
+
);
|
|
8689
|
+
}
|
|
8548
8690
|
function statusTag2(status) {
|
|
8549
8691
|
switch (status) {
|
|
8550
8692
|
case "ready":
|
|
@@ -8562,6 +8704,219 @@ function statusTag2(status) {
|
|
|
8562
8704
|
}
|
|
8563
8705
|
}
|
|
8564
8706
|
|
|
8707
|
+
// src/commands/goal.ts
|
|
8708
|
+
var import_picocolors29 = __toESM(require_picocolors(), 1);
|
|
8709
|
+
var STATUSES3 = ["proposed", "agreed", "working", "delivered", "abandoned"];
|
|
8710
|
+
function registerGoal(program3) {
|
|
8711
|
+
const goal = program3.command("goal").description("Business goals and the phases that deliver them");
|
|
8712
|
+
goal.command("list").description("What this project is working towards").action(
|
|
8713
|
+
action(async ({ ctx }) => {
|
|
8714
|
+
requireProject(ctx);
|
|
8715
|
+
const rows = await api(ctx, "/v1/project-goals", {
|
|
8716
|
+
query: { projectId: ctx.projectId }
|
|
8717
|
+
}) ?? [];
|
|
8718
|
+
ok(rows, () => {
|
|
8719
|
+
if (!rows.length) {
|
|
8720
|
+
line(import_picocolors29.default.dim("No goals yet \u2014 every task here stands on its own."));
|
|
8721
|
+
return;
|
|
8722
|
+
}
|
|
8723
|
+
for (const g of rows) line(formatGoal(g));
|
|
8724
|
+
});
|
|
8725
|
+
})
|
|
8726
|
+
);
|
|
8727
|
+
goal.command("show <id>").description("One goal: its phases, and how far each has got").action(
|
|
8728
|
+
action(async ({ ctx, args }) => {
|
|
8729
|
+
const row = await api(
|
|
8730
|
+
ctx,
|
|
8731
|
+
`/v1/project-goals/${encodeURIComponent(String(args[0]))}`
|
|
8732
|
+
);
|
|
8733
|
+
ok(row, () => printGoal(row));
|
|
8734
|
+
})
|
|
8735
|
+
);
|
|
8736
|
+
goal.command("create <title>").description(
|
|
8737
|
+
"Propose a business goal and the phases that deliver it \u2014 the owner agrees the shape before any task exists"
|
|
8738
|
+
).option(
|
|
8739
|
+
"--outcome <text>",
|
|
8740
|
+
"what 'done' means for the whole thing, in the owner's words"
|
|
8741
|
+
).option(
|
|
8742
|
+
"--phase <name...>",
|
|
8743
|
+
"ordered phase names \u2014 each must deliver something the owner would notice"
|
|
8744
|
+
).option(
|
|
8745
|
+
"--criteria <json>",
|
|
8746
|
+
`what "done" means per phase, in the OWNER's words: '{"Checkout":["A customer can pay by card and gets a receipt"]}'`
|
|
8747
|
+
).action(
|
|
8748
|
+
action(async ({ ctx, args, opts }) => {
|
|
8749
|
+
requireProject(ctx);
|
|
8750
|
+
const phases = opts.phase ?? [];
|
|
8751
|
+
if (phases.length < 2) {
|
|
8752
|
+
throw new WorkserError(
|
|
8753
|
+
"A goal needs at least two phases. If you can't name two slices that each deliver something the owner would notice, this is one task \u2014 use `workser task create`."
|
|
8754
|
+
);
|
|
8755
|
+
}
|
|
8756
|
+
if (phases.length > 6) {
|
|
8757
|
+
throw new WorkserError(
|
|
8758
|
+
"More than six phases is a conversation, not a plan. Propose the first few and agree the rest as you go."
|
|
8759
|
+
);
|
|
8760
|
+
}
|
|
8761
|
+
let criteria = {};
|
|
8762
|
+
if (opts.criteria) {
|
|
8763
|
+
try {
|
|
8764
|
+
criteria = JSON.parse(opts.criteria);
|
|
8765
|
+
} catch {
|
|
8766
|
+
throw new WorkserError(
|
|
8767
|
+
`--criteria must be JSON mapping each phase name to a list of sentences, e.g. '{"Checkout":["A customer can pay by card"]}'`
|
|
8768
|
+
);
|
|
8769
|
+
}
|
|
8770
|
+
const unknown = Object.keys(criteria).filter(
|
|
8771
|
+
(name) => !phases.includes(name)
|
|
8772
|
+
);
|
|
8773
|
+
if (unknown.length) {
|
|
8774
|
+
throw new WorkserError(
|
|
8775
|
+
`--criteria names phases that aren't in this goal: ${unknown.join(", ")}. Phases are: ${phases.join(", ")}.`
|
|
8776
|
+
);
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8779
|
+
const row = await api(ctx, "/v1/project-goals", {
|
|
8780
|
+
body: {
|
|
8781
|
+
projectId: ctx.projectId,
|
|
8782
|
+
title: args[0],
|
|
8783
|
+
outcome: opts.outcome,
|
|
8784
|
+
phases: phases.map((name) => ({
|
|
8785
|
+
name,
|
|
8786
|
+
criteria: (criteria[name] ?? []).map((text, i) => ({
|
|
8787
|
+
id: `c${i + 1}`,
|
|
8788
|
+
text,
|
|
8789
|
+
met: null
|
|
8790
|
+
}))
|
|
8791
|
+
})),
|
|
8792
|
+
channelId: ctx.projectChannelId,
|
|
8793
|
+
sourceMessageId: ctx.projectChannelMessageId
|
|
8794
|
+
}
|
|
8795
|
+
});
|
|
8796
|
+
ok(row, () => {
|
|
8797
|
+
success(`Proposed ${import_picocolors29.default.bold(row?.title ?? "goal")}`);
|
|
8798
|
+
printGoal(row);
|
|
8799
|
+
line(
|
|
8800
|
+
import_picocolors29.default.dim(
|
|
8801
|
+
"\nNothing has been created yet. The owner agrees the shape first."
|
|
8802
|
+
)
|
|
8803
|
+
);
|
|
8804
|
+
});
|
|
8805
|
+
})
|
|
8806
|
+
);
|
|
8807
|
+
goal.command("check <id> <criterionId>").description("Record whether an acceptance criterion is met").requiredOption("--phase <name>", "which part of the plan it belongs to").option("--pass", "it is met").option("--fail", "it is not met").option("--reset", "back to unchecked").option("--note <text>", "what you checked, and how").action(
|
|
8808
|
+
action(async ({ ctx, args, opts }) => {
|
|
8809
|
+
const chosen = [opts.pass, opts.fail, opts.reset].filter(Boolean);
|
|
8810
|
+
if (chosen.length !== 1) {
|
|
8811
|
+
throw new WorkserError(
|
|
8812
|
+
"Pass exactly one of --pass, --fail or --reset."
|
|
8813
|
+
);
|
|
8814
|
+
}
|
|
8815
|
+
if (opts.pass && !opts.note) {
|
|
8816
|
+
throw new WorkserError(
|
|
8817
|
+
"--note is required on a pass: say what you checked. A tick the owner cannot verify is worse than no tick."
|
|
8818
|
+
);
|
|
8819
|
+
}
|
|
8820
|
+
const met = opts.pass ? true : opts.fail ? false : null;
|
|
8821
|
+
const row = await api(
|
|
8822
|
+
ctx,
|
|
8823
|
+
`/v1/project-goals/${encodeURIComponent(String(args[0]))}/criteria/${encodeURIComponent(String(args[1]))}`,
|
|
8824
|
+
{ method: "POST", body: { phase: opts.phase, met, note: opts.note } }
|
|
8825
|
+
);
|
|
8826
|
+
ok(
|
|
8827
|
+
row,
|
|
8828
|
+
() => line(
|
|
8829
|
+
row?.recorded ? met === true ? import_picocolors29.default.green("recorded \u2014 met") : met === false ? import_picocolors29.default.yellow("recorded \u2014 not met") : import_picocolors29.default.dim("recorded \u2014 back to unchecked") : import_picocolors29.default.yellow(
|
|
8830
|
+
"couldn't record it \u2014 check the goal id, the phase name and the criterion id"
|
|
8831
|
+
)
|
|
8832
|
+
)
|
|
8833
|
+
);
|
|
8834
|
+
})
|
|
8835
|
+
);
|
|
8836
|
+
goal.command("update <id>").description("Change the shape, or move the goal along").option("--title <text>", "the owner's words for what they want").option("--outcome <text>", "what 'done' means").option("--phase <name...>", "replace the ordered phase list").option("--status <value>", `one of: ${STATUSES3.join(" | ")}`).action(
|
|
8837
|
+
action(async ({ ctx, args, opts }) => {
|
|
8838
|
+
if (opts.status && !STATUSES3.includes(opts.status)) {
|
|
8839
|
+
throw new WorkserError(
|
|
8840
|
+
`Unknown status "${opts.status}". Use one of: ${STATUSES3.join(", ")}.`
|
|
8841
|
+
);
|
|
8842
|
+
}
|
|
8843
|
+
const row = await api(
|
|
8844
|
+
ctx,
|
|
8845
|
+
`/v1/project-goals/${encodeURIComponent(String(args[0]))}`,
|
|
8846
|
+
{
|
|
8847
|
+
method: "PATCH",
|
|
8848
|
+
body: {
|
|
8849
|
+
title: opts.title,
|
|
8850
|
+
outcome: opts.outcome,
|
|
8851
|
+
phases: opts.phase,
|
|
8852
|
+
status: opts.status
|
|
8853
|
+
}
|
|
8854
|
+
}
|
|
8855
|
+
);
|
|
8856
|
+
ok(row, () => printGoal(row));
|
|
8857
|
+
})
|
|
8858
|
+
);
|
|
8859
|
+
}
|
|
8860
|
+
function appScope(g) {
|
|
8861
|
+
const n = g.appIds?.length ?? 0;
|
|
8862
|
+
if (!n) return "";
|
|
8863
|
+
return import_picocolors29.default.dim(` ${n} part${n === 1 ? "" : "s"} of the system`);
|
|
8864
|
+
}
|
|
8865
|
+
function formatGoal(g) {
|
|
8866
|
+
const where = g.currentPhase && g.status !== "delivered" ? import_picocolors29.default.dim(` now: ${g.currentPhase}`) : "";
|
|
8867
|
+
const count = g.taskTotal != null ? import_picocolors29.default.dim(` ${g.taskDone}/${g.taskTotal} done`) : "";
|
|
8868
|
+
return `${statusTag3(g.status)} ${g.title}${appScope(g)}${where}${count} ${import_picocolors29.default.dim(g.id)}`;
|
|
8869
|
+
}
|
|
8870
|
+
function printGoal(g) {
|
|
8871
|
+
if (!g) return;
|
|
8872
|
+
line(`${import_picocolors29.default.bold(g.title)} ${import_picocolors29.default.dim(g.id)}`);
|
|
8873
|
+
line(statusTag3(g.status));
|
|
8874
|
+
if (g.outcome) line(`
|
|
8875
|
+
done means: ${g.outcome}`);
|
|
8876
|
+
const progress = g.progress ?? [];
|
|
8877
|
+
if (!progress.length) {
|
|
8878
|
+
line(import_picocolors29.default.dim("\nNo phases agreed yet."));
|
|
8879
|
+
return;
|
|
8880
|
+
}
|
|
8881
|
+
line(`
|
|
8882
|
+
${import_picocolors29.default.bold("phases")}`);
|
|
8883
|
+
for (const p of progress) {
|
|
8884
|
+
const bar2 = p.total > 0 ? `${p.done}/${p.total} done` : import_picocolors29.default.dim("nothing filed yet");
|
|
8885
|
+
const mark = p.state === "done" ? import_picocolors29.default.green("done") : p.state === "working" ? import_picocolors29.default.blue("working") : import_picocolors29.default.dim("waiting");
|
|
8886
|
+
line(
|
|
8887
|
+
` ${import_picocolors29.default.dim(String(p.index).padStart(2, "0"))} ${p.name} ${mark} ${import_picocolors29.default.dim(bar2)}`
|
|
8888
|
+
);
|
|
8889
|
+
for (const c of p.criteria ?? []) {
|
|
8890
|
+
const tick = c.met === true ? import_picocolors29.default.green("\u2713") : c.met === false ? import_picocolors29.default.red("\u2717") : import_picocolors29.default.dim("\xB7");
|
|
8891
|
+
line(` ${tick} ${c.text} ${import_picocolors29.default.dim(c.id)}`);
|
|
8892
|
+
if (c.note) line(import_picocolors29.default.dim(` ${c.note}`));
|
|
8893
|
+
}
|
|
8894
|
+
}
|
|
8895
|
+
const current = progress.find((p) => p.name === g.currentPhase);
|
|
8896
|
+
if (current) {
|
|
8897
|
+
line(
|
|
8898
|
+
import_picocolors29.default.dim(
|
|
8899
|
+
`
|
|
8900
|
+
${current.name} \u2014 ${current.done} of ${current.total} done (phase ${current.index} of ${progress.length}).`
|
|
8901
|
+
)
|
|
8902
|
+
);
|
|
8903
|
+
}
|
|
8904
|
+
}
|
|
8905
|
+
function statusTag3(status) {
|
|
8906
|
+
switch (status) {
|
|
8907
|
+
case "agreed":
|
|
8908
|
+
return import_picocolors29.default.cyan("[agreed]");
|
|
8909
|
+
case "working":
|
|
8910
|
+
return import_picocolors29.default.blue("[working]");
|
|
8911
|
+
case "delivered":
|
|
8912
|
+
return import_picocolors29.default.green("[delivered]");
|
|
8913
|
+
case "abandoned":
|
|
8914
|
+
return import_picocolors29.default.dim("[abandoned]");
|
|
8915
|
+
default:
|
|
8916
|
+
return import_picocolors29.default.yellow("[proposed]");
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
|
|
8565
8920
|
// src/role-guard.ts
|
|
8566
8921
|
var READS = [
|
|
8567
8922
|
// `requirement` sits beside `doc` and `decision` because it is the same kind
|
|
@@ -8599,7 +8954,19 @@ var READS = [
|
|
|
8599
8954
|
// Reading the plan and what is used against it. An agent proposing "add
|
|
8600
8955
|
// another project" can only sensibly propose it if it can find out the plan
|
|
8601
8956
|
// allows two and two already exist.
|
|
8602
|
-
"usage"
|
|
8957
|
+
"usage",
|
|
8958
|
+
// RECORDING WHAT YOU PRODUCED IS NOT CHANGING THE PROJECT.
|
|
8959
|
+
// Every dispatched role is told, in its own preamble, to run `workser
|
|
8960
|
+
// artifact list` before non-trivial work and `workser artifact add` when it
|
|
8961
|
+
// finishes something the owner should get. Five roles — pm, qa, security,
|
|
8962
|
+
// analyst, sre — were then refused the verb here, so the instruction and the
|
|
8963
|
+
// guard contradicted each other: the agent kept trying to obey an order this
|
|
8964
|
+
// file would not let it obey, and burned its run doing it. It belongs beside
|
|
8965
|
+
// `doc`, `decision` and `board`, which are already in this list and also
|
|
8966
|
+
// have `create` subcommands — these write the project's RECORD, not the
|
|
8967
|
+
// project. What stops a reviewer editing code is its filesystem mode, and
|
|
8968
|
+
// that is untouched.
|
|
8969
|
+
"artifact"
|
|
8603
8970
|
];
|
|
8604
8971
|
var BUILDS = [
|
|
8605
8972
|
...READS,
|
|
@@ -8608,7 +8975,6 @@ var BUILDS = [
|
|
|
8608
8975
|
"db",
|
|
8609
8976
|
"storage",
|
|
8610
8977
|
"checkpoint",
|
|
8611
|
-
"artifact",
|
|
8612
8978
|
"image",
|
|
8613
8979
|
"design",
|
|
8614
8980
|
"ask",
|
|
@@ -8644,6 +9010,33 @@ var NEVER = {
|
|
|
8644
9010
|
// proposed has removed the only gate this product has.
|
|
8645
9011
|
"task approval": "Only the owner can approve a plan."
|
|
8646
9012
|
};
|
|
9013
|
+
var READ_PAIRS = /* @__PURE__ */ new Set([
|
|
9014
|
+
// The project's own brand and design notes.
|
|
9015
|
+
"design show",
|
|
9016
|
+
// What exists, and what it is wired to.
|
|
9017
|
+
"app list",
|
|
9018
|
+
"app tools",
|
|
9019
|
+
// Shape, never contents-by-arbitrary-SQL.
|
|
9020
|
+
"db list",
|
|
9021
|
+
"db tables",
|
|
9022
|
+
"db schema",
|
|
9023
|
+
// What is stored, not what is in it.
|
|
9024
|
+
"storage list",
|
|
9025
|
+
"storage ls",
|
|
9026
|
+
"checkpoint list",
|
|
9027
|
+
"workflow list",
|
|
9028
|
+
"workflow get",
|
|
9029
|
+
"workflow runs",
|
|
9030
|
+
"workflow nodes",
|
|
9031
|
+
"tool list",
|
|
9032
|
+
// The business data an analyst exists to read.
|
|
9033
|
+
"business resources",
|
|
9034
|
+
"business list",
|
|
9035
|
+
"business get",
|
|
9036
|
+
// The database service's own inventory.
|
|
9037
|
+
"neon status",
|
|
9038
|
+
"neon list"
|
|
9039
|
+
]);
|
|
8647
9040
|
function assertRoleMayRun(argv) {
|
|
8648
9041
|
const role = (process.env.WORKSER_ROLE ?? "").trim();
|
|
8649
9042
|
if (!role) return;
|
|
@@ -8654,6 +9047,7 @@ function assertRoleMayRun(argv) {
|
|
|
8654
9047
|
if (NEVER[pair] && !(pair === "task approval" && (commandArgv[2] === "request" || !commandArgv[2]))) {
|
|
8655
9048
|
throw new WorkserError(NEVER[pair], { code: "role_forbidden" });
|
|
8656
9049
|
}
|
|
9050
|
+
if (READ_PAIRS.has(pair)) return;
|
|
8657
9051
|
const allowed = ROLE_VERBS[role];
|
|
8658
9052
|
const list = allowed ?? READS;
|
|
8659
9053
|
if (!list.includes(verb)) {
|
|
@@ -8694,7 +9088,7 @@ function stripLeadingGlobalOptions(argv) {
|
|
|
8694
9088
|
}
|
|
8695
9089
|
|
|
8696
9090
|
// src/commands/decision.ts
|
|
8697
|
-
var
|
|
9091
|
+
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
8698
9092
|
function registerDecision(program3) {
|
|
8699
9093
|
const decision = program3.command("decision").description("Read and record the project's architecture decisions");
|
|
8700
9094
|
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(
|
|
@@ -8707,11 +9101,11 @@ function registerDecision(program3) {
|
|
|
8707
9101
|
rows = applyLimit(rows, opts.limit);
|
|
8708
9102
|
ok(rows, () => {
|
|
8709
9103
|
if (!rows.length) {
|
|
8710
|
-
line(
|
|
9104
|
+
line(import_picocolors30.default.dim("No decisions recorded yet."));
|
|
8711
9105
|
return;
|
|
8712
9106
|
}
|
|
8713
9107
|
for (const r of rows) {
|
|
8714
|
-
line(`${
|
|
9108
|
+
line(`${import_picocolors30.default.dim(r.id)} ${import_picocolors30.default.dim(shortDate(r.createdAt))} ${r.title}`);
|
|
8715
9109
|
line(` ${truncate(r.decision, 100)}`);
|
|
8716
9110
|
}
|
|
8717
9111
|
});
|
|
@@ -8725,16 +9119,16 @@ function registerDecision(program3) {
|
|
|
8725
9119
|
`/v1/projects/${projectId}/architecture-decisions/${args[0]}`
|
|
8726
9120
|
);
|
|
8727
9121
|
ok(row, () => {
|
|
8728
|
-
line(`${
|
|
8729
|
-
line(
|
|
9122
|
+
line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
|
|
9123
|
+
line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
|
|
8730
9124
|
line(`
|
|
8731
|
-
${
|
|
9125
|
+
${import_picocolors30.default.bold("Context")}
|
|
8732
9126
|
${row.context}`);
|
|
8733
9127
|
line(`
|
|
8734
|
-
${
|
|
9128
|
+
${import_picocolors30.default.bold("Decision")}
|
|
8735
9129
|
${row.decision}`);
|
|
8736
9130
|
if (row.consequences) line(`
|
|
8737
|
-
${
|
|
9131
|
+
${import_picocolors30.default.bold("Consequences")}
|
|
8738
9132
|
${row.consequences}`);
|
|
8739
9133
|
});
|
|
8740
9134
|
})
|
|
@@ -8763,7 +9157,7 @@ ${row.consequences}`);
|
|
|
8763
9157
|
refId: row?.id,
|
|
8764
9158
|
output: { decision: row }
|
|
8765
9159
|
});
|
|
8766
|
-
ok(row, () => line(`Recorded decision ${
|
|
9160
|
+
ok(row, () => line(`Recorded decision ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
8767
9161
|
})
|
|
8768
9162
|
);
|
|
8769
9163
|
const requirement = program3.command("requirement").description("Read and record the project's requirements");
|
|
@@ -8775,11 +9169,11 @@ ${row.consequences}`);
|
|
|
8775
9169
|
rows = applyLimit(rows, opts.limit);
|
|
8776
9170
|
ok(rows, () => {
|
|
8777
9171
|
if (!rows.length) {
|
|
8778
|
-
line(
|
|
9172
|
+
line(import_picocolors30.default.dim("No requirements recorded yet."));
|
|
8779
9173
|
return;
|
|
8780
9174
|
}
|
|
8781
9175
|
for (const r of rows) {
|
|
8782
|
-
line(`${
|
|
9176
|
+
line(`${import_picocolors30.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
|
|
8783
9177
|
}
|
|
8784
9178
|
});
|
|
8785
9179
|
})
|
|
@@ -8792,8 +9186,8 @@ ${row.consequences}`);
|
|
|
8792
9186
|
`/v1/projects/${projectId}/requirements/${args[0]}`
|
|
8793
9187
|
);
|
|
8794
9188
|
ok(row, () => {
|
|
8795
|
-
line(`${
|
|
8796
|
-
line(
|
|
9189
|
+
line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
|
|
9190
|
+
line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
|
|
8797
9191
|
line(`
|
|
8798
9192
|
${row.body}`);
|
|
8799
9193
|
});
|
|
@@ -8819,7 +9213,7 @@ ${row.body}`);
|
|
|
8819
9213
|
refId: row?.id,
|
|
8820
9214
|
output: { requirement: row }
|
|
8821
9215
|
});
|
|
8822
|
-
ok(row, () => line(`Recorded requirement ${
|
|
9216
|
+
ok(row, () => line(`Recorded requirement ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
8823
9217
|
})
|
|
8824
9218
|
);
|
|
8825
9219
|
requirement.command("update <id>").description(
|
|
@@ -8848,7 +9242,7 @@ ${row.body}`);
|
|
|
8848
9242
|
code: "bad_request"
|
|
8849
9243
|
});
|
|
8850
9244
|
}
|
|
8851
|
-
ok(row, () => line(`Updated requirement ${
|
|
9245
|
+
ok(row, () => line(`Updated requirement ${import_picocolors30.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
|
|
8852
9246
|
})
|
|
8853
9247
|
);
|
|
8854
9248
|
}
|
|
@@ -8871,7 +9265,7 @@ function shortDate(iso) {
|
|
|
8871
9265
|
}
|
|
8872
9266
|
|
|
8873
9267
|
// src/commands/doc.ts
|
|
8874
|
-
var
|
|
9268
|
+
var import_picocolors31 = __toESM(require_picocolors(), 1);
|
|
8875
9269
|
|
|
8876
9270
|
// src/mermaid-fences.ts
|
|
8877
9271
|
function hasDiagram(code) {
|
|
@@ -8916,13 +9310,13 @@ function registerDoc(program3) {
|
|
|
8916
9310
|
}) ?? [];
|
|
8917
9311
|
ok(rows, () => {
|
|
8918
9312
|
if (!rows.length) {
|
|
8919
|
-
line(
|
|
9313
|
+
line(import_picocolors31.default.dim("No documents yet."));
|
|
8920
9314
|
return;
|
|
8921
9315
|
}
|
|
8922
9316
|
for (const r of rows) {
|
|
8923
|
-
const link = r.workItemId ?
|
|
8924
|
-
const file = r.filePath ?
|
|
8925
|
-
line(`${
|
|
9317
|
+
const link = r.workItemId ? import_picocolors31.default.dim(` \u21B3 ${r.workItemId}`) : "";
|
|
9318
|
+
const file = r.filePath ? import_picocolors31.default.dim(` ${r.filePath}`) : "";
|
|
9319
|
+
line(`${import_picocolors31.default.dim(r.id)} ${r.title}${link}${file}`);
|
|
8926
9320
|
}
|
|
8927
9321
|
});
|
|
8928
9322
|
})
|
|
@@ -8936,17 +9330,17 @@ function registerDoc(program3) {
|
|
|
8936
9330
|
);
|
|
8937
9331
|
if (opts.markdown) {
|
|
8938
9332
|
ok({ id: row.id, title: row.title, filePath: row.filePath }, () => {
|
|
8939
|
-
line(`${
|
|
9333
|
+
line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
|
|
8940
9334
|
line(
|
|
8941
|
-
row.filePath ? `Read it at ${
|
|
9335
|
+
row.filePath ? `Read it at ${import_picocolors31.default.bold(row.filePath)} (relative to the project folder).` : import_picocolors31.default.dim("This document has no markdown mirror on disk yet.")
|
|
8942
9336
|
);
|
|
8943
9337
|
});
|
|
8944
9338
|
return;
|
|
8945
9339
|
}
|
|
8946
9340
|
ok(row, () => {
|
|
8947
|
-
line(`${
|
|
8948
|
-
if (row.workItemId) line(
|
|
8949
|
-
if (row.filePath) line(
|
|
9341
|
+
line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
|
|
9342
|
+
if (row.workItemId) line(import_picocolors31.default.dim(`linked to work item ${row.workItemId}`));
|
|
9343
|
+
if (row.filePath) line(import_picocolors31.default.dim(`markdown mirror: ${row.filePath}`));
|
|
8950
9344
|
line("");
|
|
8951
9345
|
line(row.contentJson);
|
|
8952
9346
|
});
|
|
@@ -8975,7 +9369,7 @@ function registerDoc(program3) {
|
|
|
8975
9369
|
refId: row?.id,
|
|
8976
9370
|
output: { document: row }
|
|
8977
9371
|
});
|
|
8978
|
-
ok(row, () => line(`Created document ${
|
|
9372
|
+
ok(row, () => line(`Created document ${import_picocolors31.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
8979
9373
|
})
|
|
8980
9374
|
);
|
|
8981
9375
|
doc.command("diagram <id>").description(
|
|
@@ -9001,19 +9395,19 @@ function registerDoc(program3) {
|
|
|
9001
9395
|
diagrams: diagrams.map((code) => ({ kind: diagramKind(code), code }))
|
|
9002
9396
|
};
|
|
9003
9397
|
ok(payload, () => {
|
|
9004
|
-
line(`${
|
|
9398
|
+
line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
|
|
9005
9399
|
if (markdown === null) {
|
|
9006
9400
|
line(
|
|
9007
|
-
|
|
9401
|
+
import_picocolors31.default.dim(
|
|
9008
9402
|
row.filePath ? `Could not read ${row.filePath} from this folder.` : "This document has no markdown mirror on disk yet."
|
|
9009
9403
|
)
|
|
9010
9404
|
);
|
|
9011
9405
|
} else if (!diagrams.length) {
|
|
9012
|
-
line(
|
|
9406
|
+
line(import_picocolors31.default.dim("No diagrams in this document."));
|
|
9013
9407
|
} else {
|
|
9014
9408
|
for (const [i, code] of diagrams.entries()) {
|
|
9015
9409
|
const kind = diagramKind(code) ?? "diagram";
|
|
9016
|
-
line(`${
|
|
9410
|
+
line(`${import_picocolors31.default.dim(String(i + 1))} ${kind} ${import_picocolors31.default.dim(`${code.split("\n").length} lines`)}`);
|
|
9017
9411
|
}
|
|
9018
9412
|
}
|
|
9019
9413
|
});
|
|
@@ -9049,7 +9443,7 @@ function registerDoc(program3) {
|
|
|
9049
9443
|
code: "bad_request"
|
|
9050
9444
|
});
|
|
9051
9445
|
}
|
|
9052
|
-
ok(row, () => line(`Updated document ${
|
|
9446
|
+
ok(row, () => line(`Updated document ${import_picocolors31.default.bold(row.id)} \u2014 ${row.title}`));
|
|
9053
9447
|
})
|
|
9054
9448
|
);
|
|
9055
9449
|
}
|
|
@@ -9063,7 +9457,7 @@ function readMirror(cwd, filePath) {
|
|
|
9063
9457
|
}
|
|
9064
9458
|
|
|
9065
9459
|
// src/commands/design.ts
|
|
9066
|
-
var
|
|
9460
|
+
var import_picocolors32 = __toESM(require_picocolors(), 1);
|
|
9067
9461
|
function registerDesign(program3) {
|
|
9068
9462
|
const design = program3.command("design").description("Read the project's brand (colours, fonts, logo)");
|
|
9069
9463
|
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(
|
|
@@ -9077,11 +9471,11 @@ function registerDesign(program3) {
|
|
|
9077
9471
|
if (opts.raw) {
|
|
9078
9472
|
ok(files, () => {
|
|
9079
9473
|
if (!files.length) {
|
|
9080
|
-
line(
|
|
9474
|
+
line(import_picocolors32.default.dim("No brand set for this project."));
|
|
9081
9475
|
return;
|
|
9082
9476
|
}
|
|
9083
9477
|
for (const f of files) {
|
|
9084
|
-
line(
|
|
9478
|
+
line(import_picocolors32.default.bold(f.path));
|
|
9085
9479
|
line(f.contents);
|
|
9086
9480
|
line("");
|
|
9087
9481
|
}
|
|
@@ -9098,21 +9492,21 @@ function registerDesign(program3) {
|
|
|
9098
9492
|
} : { hasBrand: false, colors: {}, fonts: {}, brand: {}, files: [] };
|
|
9099
9493
|
ok(summary, () => {
|
|
9100
9494
|
if (!tokens) {
|
|
9101
|
-
line(
|
|
9495
|
+
line(import_picocolors32.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
|
|
9102
9496
|
return;
|
|
9103
9497
|
}
|
|
9104
9498
|
for (const [name, value] of Object.entries(tokens.brand)) {
|
|
9105
|
-
line(`${
|
|
9499
|
+
line(`${import_picocolors32.default.dim(name.padEnd(12))} ${value}`);
|
|
9106
9500
|
}
|
|
9107
9501
|
for (const [name, value] of Object.entries(tokens.color)) {
|
|
9108
|
-
line(`${
|
|
9502
|
+
line(`${import_picocolors32.default.dim(`color.${name}`.padEnd(12))} ${value}`);
|
|
9109
9503
|
}
|
|
9110
9504
|
for (const [name, value] of Object.entries(tokens.font)) {
|
|
9111
|
-
line(`${
|
|
9505
|
+
line(`${import_picocolors32.default.dim(`font.${name}`.padEnd(12))} ${value}`);
|
|
9112
9506
|
}
|
|
9113
9507
|
line("");
|
|
9114
9508
|
line(
|
|
9115
|
-
|
|
9509
|
+
import_picocolors32.default.dim(
|
|
9116
9510
|
`Generated into the working tree as ${files.map((f) => f.path).join(", ")} \u2014 wire those in, never edit them.`
|
|
9117
9511
|
)
|
|
9118
9512
|
);
|
|
@@ -9143,7 +9537,7 @@ function unwrap(group) {
|
|
|
9143
9537
|
}
|
|
9144
9538
|
|
|
9145
9539
|
// src/commands/api.ts
|
|
9146
|
-
var
|
|
9540
|
+
var import_picocolors33 = __toESM(require_picocolors(), 1);
|
|
9147
9541
|
import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
9148
9542
|
import { join as join3, relative, sep } from "path";
|
|
9149
9543
|
|
|
@@ -9257,10 +9651,10 @@ function registerApi(program3) {
|
|
|
9257
9651
|
const appId = requireApp(opts.app);
|
|
9258
9652
|
const res = await api(ctx, `/v1/apps/${encodeURIComponent(appId)}/api/requests`);
|
|
9259
9653
|
ok(res, () => {
|
|
9260
|
-
for (const note of res?.notes ?? []) line(
|
|
9654
|
+
for (const note of res?.notes ?? []) line(import_picocolors33.default.dim(note));
|
|
9261
9655
|
for (const r of res?.requests ?? []) {
|
|
9262
9656
|
line(
|
|
9263
|
-
`${
|
|
9657
|
+
`${import_picocolors33.default.dim(r.method.padEnd(6))}${r.path}${r.note ? import_picocolors33.default.dim(` ${r.note}`) : ""}`
|
|
9264
9658
|
);
|
|
9265
9659
|
}
|
|
9266
9660
|
});
|
|
@@ -9294,14 +9688,14 @@ function registerApi(program3) {
|
|
|
9294
9688
|
);
|
|
9295
9689
|
ok(res, () => {
|
|
9296
9690
|
if (!res?.ok) {
|
|
9297
|
-
line(
|
|
9691
|
+
line(import_picocolors33.default.red(res?.error ?? "The service did not answer."));
|
|
9298
9692
|
return;
|
|
9299
9693
|
}
|
|
9300
9694
|
const code = `${res.status}${res.statusText ? ` ${res.statusText}` : ""}`;
|
|
9301
|
-
const colour2 = res.status && res.status < 300 ?
|
|
9302
|
-
line(`${colour2(code)} ${
|
|
9695
|
+
const colour2 = res.status && res.status < 300 ? import_picocolors33.default.green : res.status && res.status < 500 ? import_picocolors33.default.yellow : import_picocolors33.default.red;
|
|
9696
|
+
line(`${colour2(code)} ${import_picocolors33.default.dim(`${res.durationMs}ms ${res.url}`)}`);
|
|
9303
9697
|
if (res.body) line(res.body);
|
|
9304
|
-
if (res.truncated) line(
|
|
9698
|
+
if (res.truncated) line(import_picocolors33.default.dim("(answer truncated)"));
|
|
9305
9699
|
});
|
|
9306
9700
|
if (!res?.ok) process.exitCode = 1;
|
|
9307
9701
|
})
|
|
@@ -9321,15 +9715,15 @@ function registerApi(program3) {
|
|
|
9321
9715
|
for (const r of report.routes) {
|
|
9322
9716
|
const known = report.missing.some((m) => m.path === r.path);
|
|
9323
9717
|
line(
|
|
9324
|
-
`${known ?
|
|
9718
|
+
`${known ? import_picocolors33.default.yellow("undocumented") : import_picocolors33.default.green("documented ")} ${r.path}${import_picocolors33.default.dim(` ${r.file}`)}`
|
|
9325
9719
|
);
|
|
9326
9720
|
}
|
|
9327
9721
|
for (const p of report.stale) {
|
|
9328
|
-
line(`${
|
|
9722
|
+
line(`${import_picocolors33.default.dim("in spec only ")} ${p}`);
|
|
9329
9723
|
}
|
|
9330
9724
|
line("");
|
|
9331
9725
|
if (report.ok && specFile) success(summary);
|
|
9332
|
-
else line(
|
|
9726
|
+
else line(import_picocolors33.default.yellow(summary));
|
|
9333
9727
|
});
|
|
9334
9728
|
if (opts.check && !report.ok) {
|
|
9335
9729
|
throw new WorkserError(summary, { code: "bad_request" });
|
|
@@ -9400,7 +9794,7 @@ function listRepoFiles(root, maxDepth = 8) {
|
|
|
9400
9794
|
}
|
|
9401
9795
|
|
|
9402
9796
|
// src/commands/analysis.ts
|
|
9403
|
-
var
|
|
9797
|
+
var import_picocolors34 = __toESM(require_picocolors(), 1);
|
|
9404
9798
|
import { readFileSync as readFileSync4 } from "fs";
|
|
9405
9799
|
function registerAnalysis(program3) {
|
|
9406
9800
|
const cmd = program3.command("analysis").description("Run Python analysis locally, recorded in the task");
|
|
@@ -9410,14 +9804,14 @@ function registerAnalysis(program3) {
|
|
|
9410
9804
|
const res = await api(ctx, path);
|
|
9411
9805
|
ok(res, () => {
|
|
9412
9806
|
line(
|
|
9413
|
-
`${res?.available ?
|
|
9807
|
+
`${res?.available ? import_picocolors34.default.green("python") : import_picocolors34.default.red("python")} ${res?.version ?? "not found"} ${import_picocolors34.default.dim(res?.python ?? "")}`
|
|
9414
9808
|
);
|
|
9415
9809
|
for (const lib of res?.libraries ?? []) {
|
|
9416
9810
|
line(
|
|
9417
|
-
`${lib.present ?
|
|
9811
|
+
`${lib.present ? import_picocolors34.default.green(lib.name) : import_picocolors34.default.yellow(lib.name)}${import_picocolors34.default.dim(lib.present ? "" : " missing")}`
|
|
9418
9812
|
);
|
|
9419
9813
|
}
|
|
9420
|
-
for (const note of res?.notes ?? []) line(
|
|
9814
|
+
for (const note of res?.notes ?? []) line(import_picocolors34.default.dim(note));
|
|
9421
9815
|
});
|
|
9422
9816
|
if (!res?.available) process.exitCode = 1;
|
|
9423
9817
|
})
|
|
@@ -9439,15 +9833,15 @@ function registerAnalysis(program3) {
|
|
|
9439
9833
|
);
|
|
9440
9834
|
ok(res, () => {
|
|
9441
9835
|
if (res?.stdout) line(res.stdout.replace(/\n$/, ""));
|
|
9442
|
-
if (res?.stderr) line(
|
|
9443
|
-
if (res?.truncated) line(
|
|
9836
|
+
if (res?.stderr) line(import_picocolors34.default.dim(res.stderr.replace(/\n$/, "")));
|
|
9837
|
+
if (res?.truncated) line(import_picocolors34.default.dim("(output truncated)"));
|
|
9444
9838
|
const took = `${Math.round((res?.durationMs ?? 0) / 100) / 10}s`;
|
|
9445
9839
|
line(
|
|
9446
|
-
res?.ok ?
|
|
9840
|
+
res?.ok ? import_picocolors34.default.green(`\u2713 ${res.summary ?? "It finished."}`) + import_picocolors34.default.dim(` ${took}`) : import_picocolors34.default.yellow(res?.summary ?? "It did not finish.") + import_picocolors34.default.dim(` ${took}`)
|
|
9447
9841
|
);
|
|
9448
9842
|
if (res && !res.sandboxed) {
|
|
9449
9843
|
line(
|
|
9450
|
-
|
|
9844
|
+
import_picocolors34.default.dim(
|
|
9451
9845
|
"This platform has no OS sandbox, so the script ran with your own file access."
|
|
9452
9846
|
)
|
|
9453
9847
|
);
|
|
@@ -9475,7 +9869,7 @@ function readCode(file, inline) {
|
|
|
9475
9869
|
}
|
|
9476
9870
|
|
|
9477
9871
|
// src/commands/scan.ts
|
|
9478
|
-
var
|
|
9872
|
+
var import_picocolors35 = __toESM(require_picocolors(), 1);
|
|
9479
9873
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9480
9874
|
import { existsSync as existsSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
9481
9875
|
import { join as join4, relative as relative2, sep as sep2 } from "path";
|
|
@@ -9759,22 +10153,22 @@ function runPermissions(cwd, findings, checked, skipped) {
|
|
|
9759
10153
|
}
|
|
9760
10154
|
function print2(report, summary) {
|
|
9761
10155
|
for (const s of report.skipped) {
|
|
9762
|
-
line(`${
|
|
10156
|
+
line(`${import_picocolors35.default.yellow("not checked")} ${s.check}${import_picocolors35.default.dim(` \u2014 ${s.reason}`)}`);
|
|
9763
10157
|
}
|
|
9764
10158
|
for (const f of report.findings) {
|
|
9765
|
-
const where = f.file ?
|
|
10159
|
+
const where = f.file ? import_picocolors35.default.dim(` ${f.file}${f.line ? `:${f.line}` : ""}`) : "";
|
|
9766
10160
|
line(`${severityTag(f.severity)} ${f.title}${where}`);
|
|
9767
|
-
line(` ${
|
|
10161
|
+
line(` ${import_picocolors35.default.dim(f.fix)}`);
|
|
9768
10162
|
}
|
|
9769
10163
|
if (report.findings.length || report.skipped.length) line("");
|
|
9770
10164
|
if (report.ok && !report.skipped.length) success(summary);
|
|
9771
|
-
else if (report.ok) line(
|
|
9772
|
-
else line(
|
|
10165
|
+
else if (report.ok) line(import_picocolors35.default.yellow(summary));
|
|
10166
|
+
else line(import_picocolors35.default.red(summary));
|
|
9773
10167
|
}
|
|
9774
10168
|
function severityTag(severity) {
|
|
9775
|
-
if (severity === "high") return
|
|
9776
|
-
if (severity === "medium") return
|
|
9777
|
-
return
|
|
10169
|
+
if (severity === "high") return import_picocolors35.default.red("serious ");
|
|
10170
|
+
if (severity === "medium") return import_picocolors35.default.yellow("worth fixing");
|
|
10171
|
+
return import_picocolors35.default.dim("minor ");
|
|
9778
10172
|
}
|
|
9779
10173
|
function git(cwd, args) {
|
|
9780
10174
|
try {
|
|
@@ -9835,7 +10229,7 @@ function listRepoFiles2(root, maxDepth = 8) {
|
|
|
9835
10229
|
}
|
|
9836
10230
|
|
|
9837
10231
|
// src/commands/health.ts
|
|
9838
|
-
var
|
|
10232
|
+
var import_picocolors36 = __toESM(require_picocolors(), 1);
|
|
9839
10233
|
function registerHealth(program3) {
|
|
9840
10234
|
program3.command("health").description("Check that the published apps in this project are still answering").option("--app <webAppId>", "check one app rather than all of them").action(
|
|
9841
10235
|
action(async ({ ctx, opts }) => {
|
|
@@ -9849,16 +10243,16 @@ function registerHealth(program3) {
|
|
|
9849
10243
|
}
|
|
9850
10244
|
function print3(res) {
|
|
9851
10245
|
if (!res?.checks?.length) {
|
|
9852
|
-
line(
|
|
10246
|
+
line(import_picocolors36.default.dim(res?.note ?? "Nothing to check."));
|
|
9853
10247
|
return;
|
|
9854
10248
|
}
|
|
9855
10249
|
for (const c of res.checks) {
|
|
9856
|
-
const mark = c.ok ?
|
|
9857
|
-
const timing =
|
|
9858
|
-
const detail = c.ok ? timing :
|
|
9859
|
-
line(` ${mark} ${c.appName} ${
|
|
10250
|
+
const mark = c.ok ? import_picocolors36.default.green("up ") : import_picocolors36.default.red("down");
|
|
10251
|
+
const timing = import_picocolors36.default.dim(`${c.ms}ms`);
|
|
10252
|
+
const detail = c.ok ? timing : import_picocolors36.default.dim(`${c.error ?? "no answer"}${c.failures > 1 ? ` \xB7 ${c.failures} in a row` : ""}`);
|
|
10253
|
+
line(` ${mark} ${c.appName} ${import_picocolors36.default.dim(`(${c.environment})`)} ${c.url} ${detail}`);
|
|
9860
10254
|
if (c.incidentOpened) {
|
|
9861
|
-
line(
|
|
10255
|
+
line(import_picocolors36.default.yellow(` An incident has been opened on the board for this.`));
|
|
9862
10256
|
}
|
|
9863
10257
|
}
|
|
9864
10258
|
const down = res.checks.filter((c) => !c.ok);
|
|
@@ -9871,14 +10265,14 @@ function print3(res) {
|
|
|
9871
10265
|
}
|
|
9872
10266
|
const production = down.filter((c) => c.environment === "production").length;
|
|
9873
10267
|
line(
|
|
9874
|
-
|
|
10268
|
+
import_picocolors36.default.red(
|
|
9875
10269
|
`${down.length} of ${res.checks.length} not answering` + (production ? ` \u2014 ${production} customer-facing.` : " (preview only).")
|
|
9876
10270
|
)
|
|
9877
10271
|
);
|
|
9878
10272
|
}
|
|
9879
10273
|
|
|
9880
10274
|
// src/commands/urls.ts
|
|
9881
|
-
var
|
|
10275
|
+
var import_picocolors37 = __toESM(require_picocolors(), 1);
|
|
9882
10276
|
function registerUrls(program3) {
|
|
9883
10277
|
program3.command("urls").description("The stable preview and production addresses of every app in this project").option("--app <webAppId>", "just one app").action(
|
|
9884
10278
|
action(async ({ ctx, opts }) => {
|
|
@@ -9892,20 +10286,20 @@ function registerUrls(program3) {
|
|
|
9892
10286
|
const summary = urlsSummary(rows);
|
|
9893
10287
|
ok({ rows, summary }, () => {
|
|
9894
10288
|
for (const row of rows) {
|
|
9895
|
-
const label =
|
|
9896
|
-
const value = row.url ?
|
|
10289
|
+
const label = import_picocolors37.default.dim(row.environment.padEnd(10));
|
|
10290
|
+
const value = row.url ? import_picocolors37.default.cyan(row.url) : import_picocolors37.default.dim(row.note ?? "not published");
|
|
9897
10291
|
line(` ${row.appName.padEnd(22)} ${label} ${value}`);
|
|
9898
10292
|
}
|
|
9899
10293
|
line("");
|
|
9900
10294
|
if (rows.some((r) => r.url)) success(summary);
|
|
9901
|
-
else line(
|
|
10295
|
+
else line(import_picocolors37.default.yellow(summary));
|
|
9902
10296
|
});
|
|
9903
10297
|
})
|
|
9904
10298
|
);
|
|
9905
10299
|
}
|
|
9906
10300
|
|
|
9907
10301
|
// src/commands/deployments.ts
|
|
9908
|
-
var
|
|
10302
|
+
var import_picocolors38 = __toESM(require_picocolors(), 1);
|
|
9909
10303
|
function registerDeployments(program3) {
|
|
9910
10304
|
const cmd = program3.command("deployments").description("Deployment history, and putting a build in front of customers");
|
|
9911
10305
|
cmd.command("list").description("What has been built, newest first").option("--app <webAppId>", "just one app (default: every app in the project)").option("--env <environment>", "preview or production").option("--limit <n>", "how many to show", "20").action(
|
|
@@ -9923,7 +10317,7 @@ function registerDeployments(program3) {
|
|
|
9923
10317
|
ok(res, () => {
|
|
9924
10318
|
if (!items.length) {
|
|
9925
10319
|
return line(
|
|
9926
|
-
|
|
10320
|
+
import_picocolors38.default.dim(
|
|
9927
10321
|
environment ? `Nothing has been deployed to ${environment} yet.` : "Nothing has been deployed yet. `workser deploy` builds the first one."
|
|
9928
10322
|
)
|
|
9929
10323
|
);
|
|
@@ -9943,13 +10337,13 @@ function registerDeployments(program3) {
|
|
|
9943
10337
|
).catch(() => null) : null;
|
|
9944
10338
|
ok({ ...dep, logs }, () => {
|
|
9945
10339
|
line(formatDeployment(dep));
|
|
9946
|
-
if (dep?.error_message) line(
|
|
10340
|
+
if (dep?.error_message) line(import_picocolors38.default.red(` ${dep.error_message}`));
|
|
9947
10341
|
const events = logs?.events ?? [];
|
|
9948
10342
|
for (const e of events) {
|
|
9949
|
-
line(` ${
|
|
10343
|
+
line(` ${import_picocolors38.default.dim(String(e.type ?? "log"))} ${e.text ?? ""}`);
|
|
9950
10344
|
}
|
|
9951
10345
|
if (opts.logs && !events.length) {
|
|
9952
|
-
line(
|
|
10346
|
+
line(import_picocolors38.default.dim(" That build produced no output."));
|
|
9953
10347
|
}
|
|
9954
10348
|
});
|
|
9955
10349
|
})
|
|
@@ -9993,16 +10387,16 @@ function printPromoted(res, version) {
|
|
|
9993
10387
|
const what = version === null ? "the latest build" : `version ${version}`;
|
|
9994
10388
|
const url = res.url ?? res.vercel_url;
|
|
9995
10389
|
success(`Production is being rebuilt from ${what}.`);
|
|
9996
|
-
if (url) line(
|
|
9997
|
-
line(
|
|
10390
|
+
if (url) line(import_picocolors38.default.dim(`It will be at ${url}`));
|
|
10391
|
+
line(import_picocolors38.default.dim("`workser deploy status` follows it."));
|
|
9998
10392
|
}
|
|
9999
10393
|
function formatDeployment(d) {
|
|
10000
10394
|
if (!d) return "";
|
|
10001
|
-
const version = d.version !== void 0 ?
|
|
10002
|
-
const env =
|
|
10395
|
+
const version = d.version !== void 0 ? import_picocolors38.default.yellow(`v${d.version}`) : import_picocolors38.default.dim("v?");
|
|
10396
|
+
const env = import_picocolors38.default.dim((d.environment ?? "?").padEnd(10));
|
|
10003
10397
|
const app = d.webAppName ? `${d.webAppName} ` : "";
|
|
10004
|
-
const when =
|
|
10005
|
-
const url = d.url ? " " +
|
|
10398
|
+
const when = import_picocolors38.default.dim(formatTime2(d.created_at));
|
|
10399
|
+
const url = d.url ? " " + import_picocolors38.default.cyan(d.url) : "";
|
|
10006
10400
|
return `${version} ${env} ${colorStatus(d.status ?? "")} ${app}${when}${url}`;
|
|
10007
10401
|
}
|
|
10008
10402
|
function formatTime2(t) {
|
|
@@ -10012,7 +10406,7 @@ function formatTime2(t) {
|
|
|
10012
10406
|
}
|
|
10013
10407
|
|
|
10014
10408
|
// src/commands/usage.ts
|
|
10015
|
-
var
|
|
10409
|
+
var import_picocolors39 = __toESM(require_picocolors(), 1);
|
|
10016
10410
|
|
|
10017
10411
|
// src/usage.ts
|
|
10018
10412
|
var NEAR_LIMIT_FRACTION = 0.8;
|
|
@@ -10107,7 +10501,7 @@ function registerUsage(program3) {
|
|
|
10107
10501
|
function print4(report) {
|
|
10108
10502
|
const dims = report.dimensions ?? [];
|
|
10109
10503
|
if (!dims.length) {
|
|
10110
|
-
return line(
|
|
10504
|
+
return line(import_picocolors39.default.dim("Nothing to measure for this project yet."));
|
|
10111
10505
|
}
|
|
10112
10506
|
const width = Math.max(...dims.map((d) => d.label.length));
|
|
10113
10507
|
for (const d of dims) {
|
|
@@ -10116,23 +10510,23 @@ function print4(report) {
|
|
|
10116
10510
|
line("");
|
|
10117
10511
|
const summary = usageSummary(report);
|
|
10118
10512
|
const worst = dims.map(usageState);
|
|
10119
|
-
if (worst.includes("over")) line(
|
|
10513
|
+
if (worst.includes("over")) line(import_picocolors39.default.red(summary));
|
|
10120
10514
|
else if (worst.includes("near") || worst.includes("unknown"))
|
|
10121
|
-
line(
|
|
10515
|
+
line(import_picocolors39.default.yellow(summary));
|
|
10122
10516
|
else success(summary);
|
|
10123
10517
|
}
|
|
10124
10518
|
function gauge(d, _labelWidth) {
|
|
10125
10519
|
const drawn = bar(d);
|
|
10126
|
-
return drawn ? ` ${
|
|
10520
|
+
return drawn ? ` ${import_picocolors39.default.dim(drawn)}` : "";
|
|
10127
10521
|
}
|
|
10128
10522
|
function colour(d) {
|
|
10129
10523
|
switch (usageState(d)) {
|
|
10130
10524
|
case "over":
|
|
10131
|
-
return d.kind === "hard" ?
|
|
10525
|
+
return d.kind === "hard" ? import_picocolors39.default.red : import_picocolors39.default.yellow;
|
|
10132
10526
|
case "near":
|
|
10133
|
-
return
|
|
10527
|
+
return import_picocolors39.default.yellow;
|
|
10134
10528
|
case "unknown":
|
|
10135
|
-
return
|
|
10529
|
+
return import_picocolors39.default.dim;
|
|
10136
10530
|
default:
|
|
10137
10531
|
return (s) => s;
|
|
10138
10532
|
}
|
|
@@ -10140,7 +10534,7 @@ function colour(d) {
|
|
|
10140
10534
|
|
|
10141
10535
|
// src/index.ts
|
|
10142
10536
|
var pkg = {
|
|
10143
|
-
version: true ? "0.6.
|
|
10537
|
+
version: true ? "0.6.2" : "0.0.0-dev"
|
|
10144
10538
|
};
|
|
10145
10539
|
var program2 = new Command();
|
|
10146
10540
|
program2.name("workser").description(
|
|
@@ -10193,6 +10587,7 @@ registerAsk(program2);
|
|
|
10193
10587
|
registerSearch(program2);
|
|
10194
10588
|
registerBoard(program2);
|
|
10195
10589
|
registerTask(program2);
|
|
10590
|
+
registerGoal(program2);
|
|
10196
10591
|
registerDecision(program2);
|
|
10197
10592
|
registerDoc(program2);
|
|
10198
10593
|
registerDesign(program2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workser/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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",
|
package/skills/workser/SKILL.md
CHANGED
|
@@ -18,6 +18,7 @@ load.
|
|
|
18
18
|
|
|
19
19
|
| You need to… | Commands | Run |
|
|
20
20
|
| --- | --- | --- |
|
|
21
|
+
| Track a business requirement too big for one task, and its phases | `goal …` | `workser help goals` |
|
|
21
22
|
| See what was decided or written down for this project | `decision …`, `requirement …`, `doc …` | `workser help sdlc-entities` |
|
|
22
23
|
| Follow the project's brand — colours, fonts, logo | `design …` | `workser help brand` |
|
|
23
24
|
| Provision or query Postgres; list end users | `db …`, `auth …` | `workser help database` |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: goals
|
|
3
|
+
title: Business goals & phases
|
|
4
|
+
summary: The level above a task — a business requirement too big for one sitting, and the ordered phases that deliver it.
|
|
5
|
+
commands: [goal]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Business goals & phases
|
|
9
|
+
|
|
10
|
+
A **goal** is a business requirement — "customers can buy from my site" — that
|
|
11
|
+
does not fit in one task. It carries an ordered list of **phase names**; a task
|
|
12
|
+
joins one by carrying that name (`workser task create --goal <id> --phase <name>`).
|
|
13
|
+
A phase has no existence apart from the tasks inside it, so it cannot drift from
|
|
14
|
+
the work it names.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
workser goal list
|
|
18
|
+
workser goal show <id>
|
|
19
|
+
workser goal create <title> --phase <name...> [--outcome <text>]
|
|
20
|
+
[--criteria <json>]
|
|
21
|
+
workser goal update <id> [--title <text>] [--outcome <text>]
|
|
22
|
+
[--phase <name...>] [--status <value>]
|
|
23
|
+
workser goal check <id> <criterionId> --phase <name> (--pass|--fail|--reset) [--note <text>]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Status is one of: proposed, agreed, working, delivered, abandoned.
|
|
27
|
+
|
|
28
|
+
## A goal has to be argued for
|
|
29
|
+
|
|
30
|
+
Most requests are one task and should stay one task. Turning a two-hour job into
|
|
31
|
+
four milestones buries the owner in ceremony before anything is built — use
|
|
32
|
+
`workser task create` unless you can genuinely name two-plus slices that each
|
|
33
|
+
deliver something the owner would notice.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
workser goal create "Launch checkout" \
|
|
37
|
+
--phase "Cart" --phase "Payment" --phase "Receipts" \
|
|
38
|
+
--outcome "A customer can buy something and get a receipt"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`--phase` needs 2–6 names. Propose the shape only — nothing is created until the
|
|
42
|
+
owner agrees it; planning phase four now is waste, since it will change once
|
|
43
|
+
phase one is real.
|
|
44
|
+
|
|
45
|
+
## Acceptance criteria are agreed with the shape
|
|
46
|
+
|
|
47
|
+
`--criteria` maps each phase name to the owner's own sentences about what "done"
|
|
48
|
+
means for it, e.g. `'{"Payment":["A customer can pay by card and gets a receipt"]}'`.
|
|
49
|
+
Written afterwards they only describe what got built; written with the shape they
|
|
50
|
+
can still change the plan.
|
|
51
|
+
|
|
52
|
+
Record whether one is met with `workser goal check`:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
workser goal check g_123 c1 --phase Payment --pass \
|
|
56
|
+
--note "Tested a card payment end to end; receipt emailed."
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`--note` is required on `--pass` — a tick the owner cannot verify is worse than no
|
|
60
|
+
tick.
|
|
61
|
+
|
|
62
|
+
## Joining tasks to a goal
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
workser task create "Build the payment form" --goal g_123 --phase Payment
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
A goal's apps and progress are derived from the tasks that join it, not declared
|
|
69
|
+
up front — most of the apps a goal will touch don't exist when it's proposed.
|