@workser/cli 0.6.1 → 0.6.3
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 +614 -128
- 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
|
{
|
|
@@ -5316,7 +5386,7 @@ function buildContext(opts) {
|
|
|
5316
5386
|
const endpoint = endpointRaw.replace(/\/+$/, "");
|
|
5317
5387
|
const mode2 = socketPath ? "daemon" : /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:|\/|$)/i.test(endpoint) ? "daemon" : "cloud";
|
|
5318
5388
|
const folder = readFolderIdentity(cwd);
|
|
5319
|
-
const projectId = opts.project || process.env.WORKSER_PROJECT_ID ||
|
|
5389
|
+
const projectId = opts.project || folder?.projectId || process.env.WORKSER_PROJECT_ID || session.defaultProjectId;
|
|
5320
5390
|
const inThisProject = folder?.projectId === projectId;
|
|
5321
5391
|
const runId = process.env.WORKSER_RUN_ID || void 0;
|
|
5322
5392
|
const conversationId = process.env.WORKSER_CONVERSATION_ID || void 0;
|
|
@@ -5334,6 +5404,15 @@ function buildContext(opts) {
|
|
|
5334
5404
|
mode: mode2,
|
|
5335
5405
|
cwd,
|
|
5336
5406
|
projectId,
|
|
5407
|
+
/**
|
|
5408
|
+
* The ORG the cwd belongs to, from the same marker.
|
|
5409
|
+
*
|
|
5410
|
+
* Read here rather than from `WORKSER_ORGANIZATION_ID` for the same reason
|
|
5411
|
+
* as the project above: a user belongs to several, and the env is a
|
|
5412
|
+
* snapshot of the run that spawned this process, not of the folder the
|
|
5413
|
+
* command is standing in.
|
|
5414
|
+
*/
|
|
5415
|
+
orgId: inThisProject ? folder?.orgId : void 0,
|
|
5337
5416
|
projectRoot: inThisProject ? folder?.projectRoot : void 0,
|
|
5338
5417
|
appId: inThisProject ? folder?.appId : void 0,
|
|
5339
5418
|
appName: inThisProject ? folder?.appName : void 0,
|
|
@@ -5686,13 +5765,61 @@ function registerProject(program3) {
|
|
|
5686
5765
|
if (!merged.localPath) {
|
|
5687
5766
|
line(
|
|
5688
5767
|
import_picocolors4.default.dim(
|
|
5689
|
-
|
|
5768
|
+
` no code on this computer yet \u2014 run \`workser project sync ${merged.id ?? id}\``
|
|
5690
5769
|
)
|
|
5691
5770
|
);
|
|
5692
5771
|
}
|
|
5693
5772
|
});
|
|
5694
5773
|
})
|
|
5695
5774
|
);
|
|
5775
|
+
project.command("sync [id]").description(
|
|
5776
|
+
"Bring an app's code down to this computer \u2014 safe to run again; never overwrites local work"
|
|
5777
|
+
).action(
|
|
5778
|
+
action(async ({ ctx, args }) => {
|
|
5779
|
+
const projectId = requireProject(ctx);
|
|
5780
|
+
const appId = args[0] ?? ctx.appId;
|
|
5781
|
+
if (!appId) {
|
|
5782
|
+
throw new Error(
|
|
5783
|
+
"Which app? Pass its id, or run this from inside the app's folder."
|
|
5784
|
+
);
|
|
5785
|
+
}
|
|
5786
|
+
const prepared = await api(
|
|
5787
|
+
ctx,
|
|
5788
|
+
`/v1/app-folders/prepare`,
|
|
5789
|
+
{
|
|
5790
|
+
method: "POST",
|
|
5791
|
+
body: { projectId, webAppId: appId, requireSource: true }
|
|
5792
|
+
}
|
|
5793
|
+
);
|
|
5794
|
+
const localPath = prepared?.localPath;
|
|
5795
|
+
if (!localPath) {
|
|
5796
|
+
throw new Error(
|
|
5797
|
+
"Couldn't make a folder for this app on this computer."
|
|
5798
|
+
);
|
|
5799
|
+
}
|
|
5800
|
+
const seeded = await api(
|
|
5801
|
+
ctx,
|
|
5802
|
+
`/v1/app-folders/seed`,
|
|
5803
|
+
{
|
|
5804
|
+
method: "POST",
|
|
5805
|
+
body: {
|
|
5806
|
+
projectId,
|
|
5807
|
+
webAppId: appId,
|
|
5808
|
+
localPath,
|
|
5809
|
+
requireSource: true
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
);
|
|
5813
|
+
ok({ appId, localPath, ...seeded }, () => {
|
|
5814
|
+
if (seeded?.ok === false) {
|
|
5815
|
+
line(import_picocolors4.default.yellow(seeded.message ?? "The code did not arrive."));
|
|
5816
|
+
line(import_picocolors4.default.dim(` folder ${localPath}`));
|
|
5817
|
+
return;
|
|
5818
|
+
}
|
|
5819
|
+
line(`${import_picocolors4.default.green("synced")} ${localPath}`);
|
|
5820
|
+
});
|
|
5821
|
+
})
|
|
5822
|
+
);
|
|
5696
5823
|
project.command("list").description("List the workspace's projects").action(
|
|
5697
5824
|
action(async ({ ctx }) => {
|
|
5698
5825
|
const items = await api(ctx, `/v1/projects`);
|
|
@@ -7858,7 +7985,22 @@ var TYPES = [
|
|
|
7858
7985
|
"approval",
|
|
7859
7986
|
"confirmation",
|
|
7860
7987
|
"file_upload",
|
|
7861
|
-
"information"
|
|
7988
|
+
"information",
|
|
7989
|
+
/**
|
|
7990
|
+
* "Add an app to this project, and do it for me."
|
|
7991
|
+
*
|
|
7992
|
+
* NOT an approval with a well-worded message. What the user is agreeing to
|
|
7993
|
+
* is real infrastructure — a repository, hosting, a folder on their machine,
|
|
7994
|
+
* sometimes a database — and they are entitled to see WHICH app before they
|
|
7995
|
+
* agree, which a sentence and a Yes button cannot show. Needs `--app-type`;
|
|
7996
|
+
* `--app-name` is what it will be called.
|
|
7997
|
+
*
|
|
7998
|
+
* You cannot create an app yourself, and that is deliberate: an agent must
|
|
7999
|
+
* not be able to provision things on somebody's account by deciding to. This
|
|
8000
|
+
* asks. Their click is what creates it, and the answer tells you the new
|
|
8001
|
+
* app's id so the rest of your plan can be scoped to it.
|
|
8002
|
+
*/
|
|
8003
|
+
"create_app"
|
|
7862
8004
|
];
|
|
7863
8005
|
function registerAsk(program3) {
|
|
7864
8006
|
program3.command("ask <message>").description("Ask the user a question and wait for their answer").option(
|
|
@@ -7870,6 +8012,12 @@ function registerAsk(program3) {
|
|
|
7870
8012
|
"a choice (repeat for each; implies --type choice)",
|
|
7871
8013
|
collect,
|
|
7872
8014
|
[]
|
|
8015
|
+
).option(
|
|
8016
|
+
"--app-type <type>",
|
|
8017
|
+
"create_app only: web | mobile | api | worker | cron | python"
|
|
8018
|
+
).option(
|
|
8019
|
+
"--app-name <name>",
|
|
8020
|
+
"create_app only: what the app should be called"
|
|
7873
8021
|
).option("--priority <level>", "low | normal | high | urgent", "normal").option(
|
|
7874
8022
|
"--timeout <seconds>",
|
|
7875
8023
|
"how long to wait before giving up (default: 600, max: 3600)",
|
|
@@ -7896,6 +8044,12 @@ function registerAsk(program3) {
|
|
|
7896
8044
|
{ code: "bad_request" }
|
|
7897
8045
|
);
|
|
7898
8046
|
}
|
|
8047
|
+
if (type === "create_app" && !opts.appType) {
|
|
8048
|
+
throw new WorkserError(
|
|
8049
|
+
"Say which kind of app: `--app-type mobile`.",
|
|
8050
|
+
{ code: "bad_request" }
|
|
8051
|
+
);
|
|
8052
|
+
}
|
|
7899
8053
|
const timeoutSeconds = Number(opts.timeout);
|
|
7900
8054
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
7901
8055
|
throw new WorkserError("--timeout must be a positive number.", {
|
|
@@ -7909,6 +8063,8 @@ function registerAsk(program3) {
|
|
|
7909
8063
|
title: opts.title || deriveTitle(message),
|
|
7910
8064
|
message,
|
|
7911
8065
|
options: options.length ? options : void 0,
|
|
8066
|
+
appType: opts.appType,
|
|
8067
|
+
appName: opts.appName,
|
|
7912
8068
|
priority: opts.priority,
|
|
7913
8069
|
timeoutSeconds
|
|
7914
8070
|
}
|
|
@@ -8203,10 +8359,18 @@ function registerTask(program3) {
|
|
|
8203
8359
|
action(async ({ ctx, args }) => {
|
|
8204
8360
|
const id = args[0] || ctx.parentTaskId || resolveTaskId(ctx);
|
|
8205
8361
|
const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
|
|
8206
|
-
|
|
8362
|
+
const goalId = row.goal_id;
|
|
8363
|
+
const goal = goalId ? await api(
|
|
8364
|
+
ctx,
|
|
8365
|
+
`/v1/project-goals/${encodeURIComponent(goalId)}`
|
|
8366
|
+
).catch(() => null) : null;
|
|
8367
|
+
ok({ ...row, goal }, () => printTask(row, goal));
|
|
8207
8368
|
})
|
|
8208
8369
|
);
|
|
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").
|
|
8370
|
+
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(
|
|
8371
|
+
"--phase <name>",
|
|
8372
|
+
"which slice of that goal \u2014 must match one of its phase names"
|
|
8373
|
+
).action(
|
|
8210
8374
|
action(async ({ ctx, args, opts }) => {
|
|
8211
8375
|
requireProject(ctx);
|
|
8212
8376
|
if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
|
|
@@ -8219,6 +8383,10 @@ function registerTask(program3) {
|
|
|
8219
8383
|
summary: opts.note,
|
|
8220
8384
|
category: opts.kind,
|
|
8221
8385
|
labels: opts.label,
|
|
8386
|
+
// The level above this task, when it has one. Most tasks do not —
|
|
8387
|
+
// a goal has to be argued for, never assumed.
|
|
8388
|
+
goalId: opts.goal,
|
|
8389
|
+
phase: opts.phase,
|
|
8222
8390
|
targets: [
|
|
8223
8391
|
...(opts.app ?? []).map((appId) => ({ kind: "app", appId })),
|
|
8224
8392
|
...(opts.infra ?? []).map((ref) => ({ kind: "infra", ref }))
|
|
@@ -8271,13 +8439,13 @@ function registerTask(program3) {
|
|
|
8271
8439
|
});
|
|
8272
8440
|
})
|
|
8273
8441
|
);
|
|
8274
|
-
const subtask = task.command("subtask").description("The
|
|
8442
|
+
const subtask = task.command("subtask").description("The subtasks a task is broken into");
|
|
8275
8443
|
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
|
|
8444
|
+
'Add one subtask, e.g. `workser task subtask add "Build the upload screen" --role web`'
|
|
8445
|
+
).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
8446
|
"--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
|
|
8447
|
+
"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"
|
|
8448
|
+
).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
8449
|
action(async ({ ctx, args, opts }) => {
|
|
8282
8450
|
const parent = resolveTaskId(ctx, opts.task);
|
|
8283
8451
|
if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
|
|
@@ -8304,21 +8472,21 @@ function registerTask(program3) {
|
|
|
8304
8472
|
});
|
|
8305
8473
|
})
|
|
8306
8474
|
);
|
|
8307
|
-
subtask.command("list [taskId]").description("The
|
|
8475
|
+
subtask.command("list [taskId]").description("The subtasks of a task, in order").action(
|
|
8308
8476
|
action(async ({ ctx, args }) => {
|
|
8309
8477
|
const id = resolveTaskId(ctx, args[0]);
|
|
8310
8478
|
const row = await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`);
|
|
8311
8479
|
const rows = row.subtasks ?? [];
|
|
8312
8480
|
ok(rows, () => {
|
|
8313
8481
|
if (!rows.length) {
|
|
8314
|
-
line(import_picocolors28.default.dim("No
|
|
8482
|
+
line(import_picocolors28.default.dim("No subtasks yet."));
|
|
8315
8483
|
return;
|
|
8316
8484
|
}
|
|
8317
8485
|
rows.forEach((r, i) => line(formatSubtask(r, i + 1)));
|
|
8318
8486
|
});
|
|
8319
8487
|
})
|
|
8320
8488
|
);
|
|
8321
|
-
subtask.command("update <id>").description("Change a
|
|
8489
|
+
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
8490
|
action(async ({ ctx, args, opts }) => {
|
|
8323
8491
|
if (opts.role !== void 0) assertOneOf("--role", opts.role, ROLES);
|
|
8324
8492
|
if (opts.kind !== void 0) assertOneOf("--kind", opts.kind, KINDS2);
|
|
@@ -8339,7 +8507,7 @@ function registerTask(program3) {
|
|
|
8339
8507
|
ok(row, () => line(`${import_picocolors28.default.green("updated")} ${import_picocolors28.default.bold(row.title)}`));
|
|
8340
8508
|
})
|
|
8341
8509
|
);
|
|
8342
|
-
subtask.command("remove <id>").description("
|
|
8510
|
+
subtask.command("remove <id>").description("Remove a subtask (only before the work starts)").action(
|
|
8343
8511
|
action(async ({ ctx, args }) => {
|
|
8344
8512
|
await api(ctx, `/v1/project-tasks/${encodeURIComponent(args[0])}`, {
|
|
8345
8513
|
method: "DELETE"
|
|
@@ -8359,7 +8527,7 @@ function registerTask(program3) {
|
|
|
8359
8527
|
})
|
|
8360
8528
|
);
|
|
8361
8529
|
task.command("resume [id]").description(
|
|
8362
|
-
"Put a
|
|
8530
|
+
"Put a task the owner stopped back in the queue \u2014 only when they ask you to carry on"
|
|
8363
8531
|
).action(
|
|
8364
8532
|
action(async ({ ctx, args }) => {
|
|
8365
8533
|
const id = resolveTaskId(ctx, args[0]);
|
|
@@ -8368,12 +8536,22 @@ function registerTask(program3) {
|
|
|
8368
8536
|
`/v1/project-tasks/${encodeURIComponent(id)}/retry`,
|
|
8369
8537
|
{ method: "POST" }
|
|
8370
8538
|
);
|
|
8371
|
-
ok(
|
|
8372
|
-
row
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8539
|
+
ok(row, () => {
|
|
8540
|
+
if (!row?.queued) {
|
|
8541
|
+
line(import_picocolors28.default.yellow("could not resume it \u2014 check the step id"));
|
|
8542
|
+
return;
|
|
8543
|
+
}
|
|
8544
|
+
const started = row.started ?? 0;
|
|
8545
|
+
if (started > 0) {
|
|
8546
|
+
line(
|
|
8547
|
+
`${import_picocolors28.default.green("resumed")} \u2014 ${started} step${started === 1 ? "" : "s"} started`
|
|
8548
|
+
);
|
|
8549
|
+
return;
|
|
8550
|
+
}
|
|
8551
|
+
line(
|
|
8552
|
+
`${import_picocolors28.default.green("resumed")} \u2014 it is queued, but nothing started yet. Check the plan is approved and that no step is blocking the rest.`
|
|
8553
|
+
);
|
|
8554
|
+
});
|
|
8377
8555
|
})
|
|
8378
8556
|
);
|
|
8379
8557
|
subtask.command("send-back <id>").description("Send a finished step back to be done again, with the reason").requiredOption("--note <text>", "what was wrong with it").action(
|
|
@@ -8407,6 +8585,30 @@ function registerTask(program3) {
|
|
|
8407
8585
|
ok(row, () => line(import_picocolors28.default.green("approved \u2014 you may start")));
|
|
8408
8586
|
})
|
|
8409
8587
|
);
|
|
8588
|
+
task.command("start [id]").description(
|
|
8589
|
+
"Start the steps this task is ready to run \u2014 only works once the owner has approved the plan"
|
|
8590
|
+
).action(
|
|
8591
|
+
action(async ({ ctx, args }) => {
|
|
8592
|
+
const id = resolveTaskId(ctx, args[0]);
|
|
8593
|
+
const row = await api(
|
|
8594
|
+
ctx,
|
|
8595
|
+
`/v1/project-tasks/${encodeURIComponent(id)}/start`,
|
|
8596
|
+
{ method: "POST" }
|
|
8597
|
+
);
|
|
8598
|
+
ok(row, () => {
|
|
8599
|
+
const started = row?.started ?? null;
|
|
8600
|
+
if (started && started > 0) {
|
|
8601
|
+
line(
|
|
8602
|
+
import_picocolors28.default.green(
|
|
8603
|
+
`started ${started} step${started === 1 ? "" : "s"} \u2014 they are running now`
|
|
8604
|
+
)
|
|
8605
|
+
);
|
|
8606
|
+
return;
|
|
8607
|
+
}
|
|
8608
|
+
line(import_picocolors28.default.yellow(row?.note ?? "Nothing started."));
|
|
8609
|
+
});
|
|
8610
|
+
})
|
|
8611
|
+
);
|
|
8410
8612
|
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(
|
|
8411
8613
|
action(async ({ ctx, args, opts }) => {
|
|
8412
8614
|
const id = resolveTaskId(ctx, opts.task);
|
|
@@ -8442,7 +8644,7 @@ function registerTask(program3) {
|
|
|
8442
8644
|
ok(row, () => line(`${import_picocolors28.default.green(row.approval_state)} ${import_picocolors28.default.bold(row.title)}`));
|
|
8443
8645
|
})
|
|
8444
8646
|
);
|
|
8445
|
-
task.command("done [id]").description("Record what a
|
|
8647
|
+
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
8648
|
action(async ({ ctx, args, opts }) => {
|
|
8447
8649
|
const id = resolveTaskId(ctx, args[0]);
|
|
8448
8650
|
await api(ctx, `/v1/project-tasks/${encodeURIComponent(id)}`, {
|
|
@@ -8483,7 +8685,7 @@ async function resolveDeps(ctx, parentId, given) {
|
|
|
8483
8685
|
const id = byKey.get(g);
|
|
8484
8686
|
if (!id) {
|
|
8485
8687
|
throw new WorkserError(
|
|
8486
|
-
`"${g}" is not a
|
|
8688
|
+
`"${g}" is not a subtask of this task. Run \`workser task subtask list\` to see them.`,
|
|
8487
8689
|
{ code: "bad_request" }
|
|
8488
8690
|
);
|
|
8489
8691
|
}
|
|
@@ -8515,11 +8717,12 @@ function formatSubtask(r, index) {
|
|
|
8515
8717
|
return `${head}
|
|
8516
8718
|
${import_picocolors28.default.dim(wrapped)}`;
|
|
8517
8719
|
}
|
|
8518
|
-
function printTask(row) {
|
|
8720
|
+
function printTask(row, goal) {
|
|
8519
8721
|
line(`${import_picocolors28.default.bold(row.title)} ${import_picocolors28.default.dim(row.key ?? row.id)}`);
|
|
8520
8722
|
line(`${statusTag2(row.status)} approval: ${row.approval_state}`);
|
|
8521
8723
|
if (row.summary) line(`
|
|
8522
8724
|
${row.summary}`);
|
|
8725
|
+
if (goal) printGoalContext(row, goal);
|
|
8523
8726
|
if (row.targets?.length) {
|
|
8524
8727
|
line(
|
|
8525
8728
|
`
|
|
@@ -8528,22 +8731,52 @@ touches: ${row.targets.map((t) => t.appName ?? t.ref ?? t.kind).join(", ")}`
|
|
|
8528
8731
|
}
|
|
8529
8732
|
if (row.subtasks?.length) {
|
|
8530
8733
|
line(`
|
|
8531
|
-
${import_picocolors28.default.bold("
|
|
8734
|
+
${import_picocolors28.default.bold("subtasks")}`);
|
|
8532
8735
|
row.subtasks.forEach((s, i) => line(formatSubtask(s, i + 1)));
|
|
8533
8736
|
line(
|
|
8534
8737
|
import_picocolors28.default.dim(
|
|
8535
8738
|
`
|
|
8536
|
-
Run \`workser artifact list\` to see what these
|
|
8739
|
+
Run \`workser artifact list\` to see what these subtasks produced,`
|
|
8537
8740
|
)
|
|
8538
8741
|
);
|
|
8539
8742
|
line(
|
|
8540
8743
|
import_picocolors28.default.dim(
|
|
8541
|
-
`or \`workser artifact list --step <id>\` for one
|
|
8744
|
+
`or \`workser artifact list --step <id>\` for one subtask's output alone.`
|
|
8542
8745
|
)
|
|
8543
8746
|
);
|
|
8544
8747
|
} else {
|
|
8545
|
-
line(import_picocolors28.default.dim("\nNo
|
|
8748
|
+
line(import_picocolors28.default.dim("\nNo subtasks yet."));
|
|
8749
|
+
}
|
|
8750
|
+
}
|
|
8751
|
+
function printGoalContext(row, goal) {
|
|
8752
|
+
const mine = row.phase ?? null;
|
|
8753
|
+
line(`
|
|
8754
|
+
${import_picocolors28.default.bold("part of")}: ${goal.title}`);
|
|
8755
|
+
if (goal.outcome) line(import_picocolors28.default.dim(`done means: ${goal.outcome}`));
|
|
8756
|
+
const progress = goal.progress ?? [];
|
|
8757
|
+
for (const p of progress) {
|
|
8758
|
+
const where = p.total === 0 ? "not started" : `${p.done} of ${p.total} done`;
|
|
8759
|
+
const here = mine && p.name === mine ? import_picocolors28.default.cyan(" <- this task") : "";
|
|
8760
|
+
const mark = p.state === "done" ? import_picocolors28.default.green("*") : import_picocolors28.default.dim("-");
|
|
8761
|
+
line(` ${mark} ${p.name} \u2014 ${where}${here}`);
|
|
8762
|
+
}
|
|
8763
|
+
const next = progress.find((p) => p.state !== "done");
|
|
8764
|
+
const running = progress.some((p) => p.state === "working");
|
|
8765
|
+
if (next && !running) {
|
|
8766
|
+
line(
|
|
8767
|
+
import_picocolors28.default.dim(
|
|
8768
|
+
`
|
|
8769
|
+
Nothing is running. The next part waiting is "${next.name}" \u2014 offer it to them in a sentence rather than starting it unasked.`
|
|
8770
|
+
)
|
|
8771
|
+
);
|
|
8772
|
+
} else if (!next) {
|
|
8773
|
+
line(import_picocolors28.default.dim("\nEvery part of this plan is done. Say so, and offer to wrap it up."));
|
|
8546
8774
|
}
|
|
8775
|
+
line(
|
|
8776
|
+
import_picocolors28.default.dim(
|
|
8777
|
+
"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."
|
|
8778
|
+
)
|
|
8779
|
+
);
|
|
8547
8780
|
}
|
|
8548
8781
|
function statusTag2(status) {
|
|
8549
8782
|
switch (status) {
|
|
@@ -8562,6 +8795,219 @@ function statusTag2(status) {
|
|
|
8562
8795
|
}
|
|
8563
8796
|
}
|
|
8564
8797
|
|
|
8798
|
+
// src/commands/goal.ts
|
|
8799
|
+
var import_picocolors29 = __toESM(require_picocolors(), 1);
|
|
8800
|
+
var STATUSES3 = ["proposed", "agreed", "working", "delivered", "abandoned"];
|
|
8801
|
+
function registerGoal(program3) {
|
|
8802
|
+
const goal = program3.command("goal").description("Business goals and the phases that deliver them");
|
|
8803
|
+
goal.command("list").description("What this project is working towards").action(
|
|
8804
|
+
action(async ({ ctx }) => {
|
|
8805
|
+
requireProject(ctx);
|
|
8806
|
+
const rows = await api(ctx, "/v1/project-goals", {
|
|
8807
|
+
query: { projectId: ctx.projectId }
|
|
8808
|
+
}) ?? [];
|
|
8809
|
+
ok(rows, () => {
|
|
8810
|
+
if (!rows.length) {
|
|
8811
|
+
line(import_picocolors29.default.dim("No goals yet \u2014 every task here stands on its own."));
|
|
8812
|
+
return;
|
|
8813
|
+
}
|
|
8814
|
+
for (const g of rows) line(formatGoal(g));
|
|
8815
|
+
});
|
|
8816
|
+
})
|
|
8817
|
+
);
|
|
8818
|
+
goal.command("show <id>").description("One goal: its phases, and how far each has got").action(
|
|
8819
|
+
action(async ({ ctx, args }) => {
|
|
8820
|
+
const row = await api(
|
|
8821
|
+
ctx,
|
|
8822
|
+
`/v1/project-goals/${encodeURIComponent(String(args[0]))}`
|
|
8823
|
+
);
|
|
8824
|
+
ok(row, () => printGoal(row));
|
|
8825
|
+
})
|
|
8826
|
+
);
|
|
8827
|
+
goal.command("create <title>").description(
|
|
8828
|
+
"Propose a business goal and the phases that deliver it \u2014 the owner agrees the shape before any task exists"
|
|
8829
|
+
).option(
|
|
8830
|
+
"--outcome <text>",
|
|
8831
|
+
"what 'done' means for the whole thing, in the owner's words"
|
|
8832
|
+
).option(
|
|
8833
|
+
"--phase <name...>",
|
|
8834
|
+
"ordered phase names \u2014 each must deliver something the owner would notice"
|
|
8835
|
+
).option(
|
|
8836
|
+
"--criteria <json>",
|
|
8837
|
+
`what "done" means per phase, in the OWNER's words: '{"Checkout":["A customer can pay by card and gets a receipt"]}'`
|
|
8838
|
+
).action(
|
|
8839
|
+
action(async ({ ctx, args, opts }) => {
|
|
8840
|
+
requireProject(ctx);
|
|
8841
|
+
const phases = opts.phase ?? [];
|
|
8842
|
+
if (phases.length < 2) {
|
|
8843
|
+
throw new WorkserError(
|
|
8844
|
+
"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`."
|
|
8845
|
+
);
|
|
8846
|
+
}
|
|
8847
|
+
if (phases.length > 6) {
|
|
8848
|
+
throw new WorkserError(
|
|
8849
|
+
"More than six phases is a conversation, not a plan. Propose the first few and agree the rest as you go."
|
|
8850
|
+
);
|
|
8851
|
+
}
|
|
8852
|
+
let criteria = {};
|
|
8853
|
+
if (opts.criteria) {
|
|
8854
|
+
try {
|
|
8855
|
+
criteria = JSON.parse(opts.criteria);
|
|
8856
|
+
} catch {
|
|
8857
|
+
throw new WorkserError(
|
|
8858
|
+
`--criteria must be JSON mapping each phase name to a list of sentences, e.g. '{"Checkout":["A customer can pay by card"]}'`
|
|
8859
|
+
);
|
|
8860
|
+
}
|
|
8861
|
+
const unknown = Object.keys(criteria).filter(
|
|
8862
|
+
(name) => !phases.includes(name)
|
|
8863
|
+
);
|
|
8864
|
+
if (unknown.length) {
|
|
8865
|
+
throw new WorkserError(
|
|
8866
|
+
`--criteria names phases that aren't in this goal: ${unknown.join(", ")}. Phases are: ${phases.join(", ")}.`
|
|
8867
|
+
);
|
|
8868
|
+
}
|
|
8869
|
+
}
|
|
8870
|
+
const row = await api(ctx, "/v1/project-goals", {
|
|
8871
|
+
body: {
|
|
8872
|
+
projectId: ctx.projectId,
|
|
8873
|
+
title: args[0],
|
|
8874
|
+
outcome: opts.outcome,
|
|
8875
|
+
phases: phases.map((name) => ({
|
|
8876
|
+
name,
|
|
8877
|
+
criteria: (criteria[name] ?? []).map((text, i) => ({
|
|
8878
|
+
id: `c${i + 1}`,
|
|
8879
|
+
text,
|
|
8880
|
+
met: null
|
|
8881
|
+
}))
|
|
8882
|
+
})),
|
|
8883
|
+
channelId: ctx.projectChannelId,
|
|
8884
|
+
sourceMessageId: ctx.projectChannelMessageId
|
|
8885
|
+
}
|
|
8886
|
+
});
|
|
8887
|
+
ok(row, () => {
|
|
8888
|
+
success(`Proposed ${import_picocolors29.default.bold(row?.title ?? "goal")}`);
|
|
8889
|
+
printGoal(row);
|
|
8890
|
+
line(
|
|
8891
|
+
import_picocolors29.default.dim(
|
|
8892
|
+
"\nNothing has been created yet. The owner agrees the shape first."
|
|
8893
|
+
)
|
|
8894
|
+
);
|
|
8895
|
+
});
|
|
8896
|
+
})
|
|
8897
|
+
);
|
|
8898
|
+
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(
|
|
8899
|
+
action(async ({ ctx, args, opts }) => {
|
|
8900
|
+
const chosen = [opts.pass, opts.fail, opts.reset].filter(Boolean);
|
|
8901
|
+
if (chosen.length !== 1) {
|
|
8902
|
+
throw new WorkserError(
|
|
8903
|
+
"Pass exactly one of --pass, --fail or --reset."
|
|
8904
|
+
);
|
|
8905
|
+
}
|
|
8906
|
+
if (opts.pass && !opts.note) {
|
|
8907
|
+
throw new WorkserError(
|
|
8908
|
+
"--note is required on a pass: say what you checked. A tick the owner cannot verify is worse than no tick."
|
|
8909
|
+
);
|
|
8910
|
+
}
|
|
8911
|
+
const met = opts.pass ? true : opts.fail ? false : null;
|
|
8912
|
+
const row = await api(
|
|
8913
|
+
ctx,
|
|
8914
|
+
`/v1/project-goals/${encodeURIComponent(String(args[0]))}/criteria/${encodeURIComponent(String(args[1]))}`,
|
|
8915
|
+
{ method: "POST", body: { phase: opts.phase, met, note: opts.note } }
|
|
8916
|
+
);
|
|
8917
|
+
ok(
|
|
8918
|
+
row,
|
|
8919
|
+
() => line(
|
|
8920
|
+
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(
|
|
8921
|
+
"couldn't record it \u2014 check the goal id, the phase name and the criterion id"
|
|
8922
|
+
)
|
|
8923
|
+
)
|
|
8924
|
+
);
|
|
8925
|
+
})
|
|
8926
|
+
);
|
|
8927
|
+
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(
|
|
8928
|
+
action(async ({ ctx, args, opts }) => {
|
|
8929
|
+
if (opts.status && !STATUSES3.includes(opts.status)) {
|
|
8930
|
+
throw new WorkserError(
|
|
8931
|
+
`Unknown status "${opts.status}". Use one of: ${STATUSES3.join(", ")}.`
|
|
8932
|
+
);
|
|
8933
|
+
}
|
|
8934
|
+
const row = await api(
|
|
8935
|
+
ctx,
|
|
8936
|
+
`/v1/project-goals/${encodeURIComponent(String(args[0]))}`,
|
|
8937
|
+
{
|
|
8938
|
+
method: "PATCH",
|
|
8939
|
+
body: {
|
|
8940
|
+
title: opts.title,
|
|
8941
|
+
outcome: opts.outcome,
|
|
8942
|
+
phases: opts.phase,
|
|
8943
|
+
status: opts.status
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
);
|
|
8947
|
+
ok(row, () => printGoal(row));
|
|
8948
|
+
})
|
|
8949
|
+
);
|
|
8950
|
+
}
|
|
8951
|
+
function appScope(g) {
|
|
8952
|
+
const n = g.appIds?.length ?? 0;
|
|
8953
|
+
if (!n) return "";
|
|
8954
|
+
return import_picocolors29.default.dim(` ${n} part${n === 1 ? "" : "s"} of the system`);
|
|
8955
|
+
}
|
|
8956
|
+
function formatGoal(g) {
|
|
8957
|
+
const where = g.currentPhase && g.status !== "delivered" ? import_picocolors29.default.dim(` now: ${g.currentPhase}`) : "";
|
|
8958
|
+
const count = g.taskTotal != null ? import_picocolors29.default.dim(` ${g.taskDone}/${g.taskTotal} done`) : "";
|
|
8959
|
+
return `${statusTag3(g.status)} ${g.title}${appScope(g)}${where}${count} ${import_picocolors29.default.dim(g.id)}`;
|
|
8960
|
+
}
|
|
8961
|
+
function printGoal(g) {
|
|
8962
|
+
if (!g) return;
|
|
8963
|
+
line(`${import_picocolors29.default.bold(g.title)} ${import_picocolors29.default.dim(g.id)}`);
|
|
8964
|
+
line(statusTag3(g.status));
|
|
8965
|
+
if (g.outcome) line(`
|
|
8966
|
+
done means: ${g.outcome}`);
|
|
8967
|
+
const progress = g.progress ?? [];
|
|
8968
|
+
if (!progress.length) {
|
|
8969
|
+
line(import_picocolors29.default.dim("\nNo phases agreed yet."));
|
|
8970
|
+
return;
|
|
8971
|
+
}
|
|
8972
|
+
line(`
|
|
8973
|
+
${import_picocolors29.default.bold("phases")}`);
|
|
8974
|
+
for (const p of progress) {
|
|
8975
|
+
const bar2 = p.total > 0 ? `${p.done}/${p.total} done` : import_picocolors29.default.dim("nothing filed yet");
|
|
8976
|
+
const mark = p.state === "done" ? import_picocolors29.default.green("done") : p.state === "working" ? import_picocolors29.default.blue("working") : import_picocolors29.default.dim("waiting");
|
|
8977
|
+
line(
|
|
8978
|
+
` ${import_picocolors29.default.dim(String(p.index).padStart(2, "0"))} ${p.name} ${mark} ${import_picocolors29.default.dim(bar2)}`
|
|
8979
|
+
);
|
|
8980
|
+
for (const c of p.criteria ?? []) {
|
|
8981
|
+
const tick = c.met === true ? import_picocolors29.default.green("\u2713") : c.met === false ? import_picocolors29.default.red("\u2717") : import_picocolors29.default.dim("\xB7");
|
|
8982
|
+
line(` ${tick} ${c.text} ${import_picocolors29.default.dim(c.id)}`);
|
|
8983
|
+
if (c.note) line(import_picocolors29.default.dim(` ${c.note}`));
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
const current = progress.find((p) => p.name === g.currentPhase);
|
|
8987
|
+
if (current) {
|
|
8988
|
+
line(
|
|
8989
|
+
import_picocolors29.default.dim(
|
|
8990
|
+
`
|
|
8991
|
+
${current.name} \u2014 ${current.done} of ${current.total} done (phase ${current.index} of ${progress.length}).`
|
|
8992
|
+
)
|
|
8993
|
+
);
|
|
8994
|
+
}
|
|
8995
|
+
}
|
|
8996
|
+
function statusTag3(status) {
|
|
8997
|
+
switch (status) {
|
|
8998
|
+
case "agreed":
|
|
8999
|
+
return import_picocolors29.default.cyan("[agreed]");
|
|
9000
|
+
case "working":
|
|
9001
|
+
return import_picocolors29.default.blue("[working]");
|
|
9002
|
+
case "delivered":
|
|
9003
|
+
return import_picocolors29.default.green("[delivered]");
|
|
9004
|
+
case "abandoned":
|
|
9005
|
+
return import_picocolors29.default.dim("[abandoned]");
|
|
9006
|
+
default:
|
|
9007
|
+
return import_picocolors29.default.yellow("[proposed]");
|
|
9008
|
+
}
|
|
9009
|
+
}
|
|
9010
|
+
|
|
8565
9011
|
// src/role-guard.ts
|
|
8566
9012
|
var READS = [
|
|
8567
9013
|
// `requirement` sits beside `doc` and `decision` because it is the same kind
|
|
@@ -8599,7 +9045,19 @@ var READS = [
|
|
|
8599
9045
|
// Reading the plan and what is used against it. An agent proposing "add
|
|
8600
9046
|
// another project" can only sensibly propose it if it can find out the plan
|
|
8601
9047
|
// allows two and two already exist.
|
|
8602
|
-
"usage"
|
|
9048
|
+
"usage",
|
|
9049
|
+
// RECORDING WHAT YOU PRODUCED IS NOT CHANGING THE PROJECT.
|
|
9050
|
+
// Every dispatched role is told, in its own preamble, to run `workser
|
|
9051
|
+
// artifact list` before non-trivial work and `workser artifact add` when it
|
|
9052
|
+
// finishes something the owner should get. Five roles — pm, qa, security,
|
|
9053
|
+
// analyst, sre — were then refused the verb here, so the instruction and the
|
|
9054
|
+
// guard contradicted each other: the agent kept trying to obey an order this
|
|
9055
|
+
// file would not let it obey, and burned its run doing it. It belongs beside
|
|
9056
|
+
// `doc`, `decision` and `board`, which are already in this list and also
|
|
9057
|
+
// have `create` subcommands — these write the project's RECORD, not the
|
|
9058
|
+
// project. What stops a reviewer editing code is its filesystem mode, and
|
|
9059
|
+
// that is untouched.
|
|
9060
|
+
"artifact"
|
|
8603
9061
|
];
|
|
8604
9062
|
var BUILDS = [
|
|
8605
9063
|
...READS,
|
|
@@ -8608,7 +9066,6 @@ var BUILDS = [
|
|
|
8608
9066
|
"db",
|
|
8609
9067
|
"storage",
|
|
8610
9068
|
"checkpoint",
|
|
8611
|
-
"artifact",
|
|
8612
9069
|
"image",
|
|
8613
9070
|
"design",
|
|
8614
9071
|
"ask",
|
|
@@ -8644,6 +9101,33 @@ var NEVER = {
|
|
|
8644
9101
|
// proposed has removed the only gate this product has.
|
|
8645
9102
|
"task approval": "Only the owner can approve a plan."
|
|
8646
9103
|
};
|
|
9104
|
+
var READ_PAIRS = /* @__PURE__ */ new Set([
|
|
9105
|
+
// The project's own brand and design notes.
|
|
9106
|
+
"design show",
|
|
9107
|
+
// What exists, and what it is wired to.
|
|
9108
|
+
"app list",
|
|
9109
|
+
"app tools",
|
|
9110
|
+
// Shape, never contents-by-arbitrary-SQL.
|
|
9111
|
+
"db list",
|
|
9112
|
+
"db tables",
|
|
9113
|
+
"db schema",
|
|
9114
|
+
// What is stored, not what is in it.
|
|
9115
|
+
"storage list",
|
|
9116
|
+
"storage ls",
|
|
9117
|
+
"checkpoint list",
|
|
9118
|
+
"workflow list",
|
|
9119
|
+
"workflow get",
|
|
9120
|
+
"workflow runs",
|
|
9121
|
+
"workflow nodes",
|
|
9122
|
+
"tool list",
|
|
9123
|
+
// The business data an analyst exists to read.
|
|
9124
|
+
"business resources",
|
|
9125
|
+
"business list",
|
|
9126
|
+
"business get",
|
|
9127
|
+
// The database service's own inventory.
|
|
9128
|
+
"neon status",
|
|
9129
|
+
"neon list"
|
|
9130
|
+
]);
|
|
8647
9131
|
function assertRoleMayRun(argv) {
|
|
8648
9132
|
const role = (process.env.WORKSER_ROLE ?? "").trim();
|
|
8649
9133
|
if (!role) return;
|
|
@@ -8654,6 +9138,7 @@ function assertRoleMayRun(argv) {
|
|
|
8654
9138
|
if (NEVER[pair] && !(pair === "task approval" && (commandArgv[2] === "request" || !commandArgv[2]))) {
|
|
8655
9139
|
throw new WorkserError(NEVER[pair], { code: "role_forbidden" });
|
|
8656
9140
|
}
|
|
9141
|
+
if (READ_PAIRS.has(pair)) return;
|
|
8657
9142
|
const allowed = ROLE_VERBS[role];
|
|
8658
9143
|
const list = allowed ?? READS;
|
|
8659
9144
|
if (!list.includes(verb)) {
|
|
@@ -8694,7 +9179,7 @@ function stripLeadingGlobalOptions(argv) {
|
|
|
8694
9179
|
}
|
|
8695
9180
|
|
|
8696
9181
|
// src/commands/decision.ts
|
|
8697
|
-
var
|
|
9182
|
+
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
8698
9183
|
function registerDecision(program3) {
|
|
8699
9184
|
const decision = program3.command("decision").description("Read and record the project's architecture decisions");
|
|
8700
9185
|
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 +9192,11 @@ function registerDecision(program3) {
|
|
|
8707
9192
|
rows = applyLimit(rows, opts.limit);
|
|
8708
9193
|
ok(rows, () => {
|
|
8709
9194
|
if (!rows.length) {
|
|
8710
|
-
line(
|
|
9195
|
+
line(import_picocolors30.default.dim("No decisions recorded yet."));
|
|
8711
9196
|
return;
|
|
8712
9197
|
}
|
|
8713
9198
|
for (const r of rows) {
|
|
8714
|
-
line(`${
|
|
9199
|
+
line(`${import_picocolors30.default.dim(r.id)} ${import_picocolors30.default.dim(shortDate(r.createdAt))} ${r.title}`);
|
|
8715
9200
|
line(` ${truncate(r.decision, 100)}`);
|
|
8716
9201
|
}
|
|
8717
9202
|
});
|
|
@@ -8725,16 +9210,16 @@ function registerDecision(program3) {
|
|
|
8725
9210
|
`/v1/projects/${projectId}/architecture-decisions/${args[0]}`
|
|
8726
9211
|
);
|
|
8727
9212
|
ok(row, () => {
|
|
8728
|
-
line(`${
|
|
8729
|
-
line(
|
|
9213
|
+
line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
|
|
9214
|
+
line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
|
|
8730
9215
|
line(`
|
|
8731
|
-
${
|
|
9216
|
+
${import_picocolors30.default.bold("Context")}
|
|
8732
9217
|
${row.context}`);
|
|
8733
9218
|
line(`
|
|
8734
|
-
${
|
|
9219
|
+
${import_picocolors30.default.bold("Decision")}
|
|
8735
9220
|
${row.decision}`);
|
|
8736
9221
|
if (row.consequences) line(`
|
|
8737
|
-
${
|
|
9222
|
+
${import_picocolors30.default.bold("Consequences")}
|
|
8738
9223
|
${row.consequences}`);
|
|
8739
9224
|
});
|
|
8740
9225
|
})
|
|
@@ -8763,7 +9248,7 @@ ${row.consequences}`);
|
|
|
8763
9248
|
refId: row?.id,
|
|
8764
9249
|
output: { decision: row }
|
|
8765
9250
|
});
|
|
8766
|
-
ok(row, () => line(`Recorded decision ${
|
|
9251
|
+
ok(row, () => line(`Recorded decision ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
8767
9252
|
})
|
|
8768
9253
|
);
|
|
8769
9254
|
const requirement = program3.command("requirement").description("Read and record the project's requirements");
|
|
@@ -8775,11 +9260,11 @@ ${row.consequences}`);
|
|
|
8775
9260
|
rows = applyLimit(rows, opts.limit);
|
|
8776
9261
|
ok(rows, () => {
|
|
8777
9262
|
if (!rows.length) {
|
|
8778
|
-
line(
|
|
9263
|
+
line(import_picocolors30.default.dim("No requirements recorded yet."));
|
|
8779
9264
|
return;
|
|
8780
9265
|
}
|
|
8781
9266
|
for (const r of rows) {
|
|
8782
|
-
line(`${
|
|
9267
|
+
line(`${import_picocolors30.default.dim(r.id)} ${r.status.padEnd(9)} ${r.title}`);
|
|
8783
9268
|
}
|
|
8784
9269
|
});
|
|
8785
9270
|
})
|
|
@@ -8792,8 +9277,8 @@ ${row.consequences}`);
|
|
|
8792
9277
|
`/v1/projects/${projectId}/requirements/${args[0]}`
|
|
8793
9278
|
);
|
|
8794
9279
|
ok(row, () => {
|
|
8795
|
-
line(`${
|
|
8796
|
-
line(
|
|
9280
|
+
line(`${import_picocolors30.default.bold(row.title)} ${import_picocolors30.default.dim(row.id)}`);
|
|
9281
|
+
line(import_picocolors30.default.dim(`${row.status} \xB7 ${shortDate(row.createdAt)}`));
|
|
8797
9282
|
line(`
|
|
8798
9283
|
${row.body}`);
|
|
8799
9284
|
});
|
|
@@ -8819,7 +9304,7 @@ ${row.body}`);
|
|
|
8819
9304
|
refId: row?.id,
|
|
8820
9305
|
output: { requirement: row }
|
|
8821
9306
|
});
|
|
8822
|
-
ok(row, () => line(`Recorded requirement ${
|
|
9307
|
+
ok(row, () => line(`Recorded requirement ${import_picocolors30.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
8823
9308
|
})
|
|
8824
9309
|
);
|
|
8825
9310
|
requirement.command("update <id>").description(
|
|
@@ -8848,7 +9333,7 @@ ${row.body}`);
|
|
|
8848
9333
|
code: "bad_request"
|
|
8849
9334
|
});
|
|
8850
9335
|
}
|
|
8851
|
-
ok(row, () => line(`Updated requirement ${
|
|
9336
|
+
ok(row, () => line(`Updated requirement ${import_picocolors30.default.bold(row.id)} \u2014 ${row.title} (${row.status})`));
|
|
8852
9337
|
})
|
|
8853
9338
|
);
|
|
8854
9339
|
}
|
|
@@ -8871,7 +9356,7 @@ function shortDate(iso) {
|
|
|
8871
9356
|
}
|
|
8872
9357
|
|
|
8873
9358
|
// src/commands/doc.ts
|
|
8874
|
-
var
|
|
9359
|
+
var import_picocolors31 = __toESM(require_picocolors(), 1);
|
|
8875
9360
|
|
|
8876
9361
|
// src/mermaid-fences.ts
|
|
8877
9362
|
function hasDiagram(code) {
|
|
@@ -8916,13 +9401,13 @@ function registerDoc(program3) {
|
|
|
8916
9401
|
}) ?? [];
|
|
8917
9402
|
ok(rows, () => {
|
|
8918
9403
|
if (!rows.length) {
|
|
8919
|
-
line(
|
|
9404
|
+
line(import_picocolors31.default.dim("No documents yet."));
|
|
8920
9405
|
return;
|
|
8921
9406
|
}
|
|
8922
9407
|
for (const r of rows) {
|
|
8923
|
-
const link = r.workItemId ?
|
|
8924
|
-
const file = r.filePath ?
|
|
8925
|
-
line(`${
|
|
9408
|
+
const link = r.workItemId ? import_picocolors31.default.dim(` \u21B3 ${r.workItemId}`) : "";
|
|
9409
|
+
const file = r.filePath ? import_picocolors31.default.dim(` ${r.filePath}`) : "";
|
|
9410
|
+
line(`${import_picocolors31.default.dim(r.id)} ${r.title}${link}${file}`);
|
|
8926
9411
|
}
|
|
8927
9412
|
});
|
|
8928
9413
|
})
|
|
@@ -8936,17 +9421,17 @@ function registerDoc(program3) {
|
|
|
8936
9421
|
);
|
|
8937
9422
|
if (opts.markdown) {
|
|
8938
9423
|
ok({ id: row.id, title: row.title, filePath: row.filePath }, () => {
|
|
8939
|
-
line(`${
|
|
9424
|
+
line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
|
|
8940
9425
|
line(
|
|
8941
|
-
row.filePath ? `Read it at ${
|
|
9426
|
+
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
9427
|
);
|
|
8943
9428
|
});
|
|
8944
9429
|
return;
|
|
8945
9430
|
}
|
|
8946
9431
|
ok(row, () => {
|
|
8947
|
-
line(`${
|
|
8948
|
-
if (row.workItemId) line(
|
|
8949
|
-
if (row.filePath) line(
|
|
9432
|
+
line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
|
|
9433
|
+
if (row.workItemId) line(import_picocolors31.default.dim(`linked to work item ${row.workItemId}`));
|
|
9434
|
+
if (row.filePath) line(import_picocolors31.default.dim(`markdown mirror: ${row.filePath}`));
|
|
8950
9435
|
line("");
|
|
8951
9436
|
line(row.contentJson);
|
|
8952
9437
|
});
|
|
@@ -8975,7 +9460,7 @@ function registerDoc(program3) {
|
|
|
8975
9460
|
refId: row?.id,
|
|
8976
9461
|
output: { document: row }
|
|
8977
9462
|
});
|
|
8978
|
-
ok(row, () => line(`Created document ${
|
|
9463
|
+
ok(row, () => line(`Created document ${import_picocolors31.default.bold(row?.id ?? "")} \u2014 ${title}`));
|
|
8979
9464
|
})
|
|
8980
9465
|
);
|
|
8981
9466
|
doc.command("diagram <id>").description(
|
|
@@ -9001,19 +9486,19 @@ function registerDoc(program3) {
|
|
|
9001
9486
|
diagrams: diagrams.map((code) => ({ kind: diagramKind(code), code }))
|
|
9002
9487
|
};
|
|
9003
9488
|
ok(payload, () => {
|
|
9004
|
-
line(`${
|
|
9489
|
+
line(`${import_picocolors31.default.bold(row.title)} ${import_picocolors31.default.dim(row.id)}`);
|
|
9005
9490
|
if (markdown === null) {
|
|
9006
9491
|
line(
|
|
9007
|
-
|
|
9492
|
+
import_picocolors31.default.dim(
|
|
9008
9493
|
row.filePath ? `Could not read ${row.filePath} from this folder.` : "This document has no markdown mirror on disk yet."
|
|
9009
9494
|
)
|
|
9010
9495
|
);
|
|
9011
9496
|
} else if (!diagrams.length) {
|
|
9012
|
-
line(
|
|
9497
|
+
line(import_picocolors31.default.dim("No diagrams in this document."));
|
|
9013
9498
|
} else {
|
|
9014
9499
|
for (const [i, code] of diagrams.entries()) {
|
|
9015
9500
|
const kind = diagramKind(code) ?? "diagram";
|
|
9016
|
-
line(`${
|
|
9501
|
+
line(`${import_picocolors31.default.dim(String(i + 1))} ${kind} ${import_picocolors31.default.dim(`${code.split("\n").length} lines`)}`);
|
|
9017
9502
|
}
|
|
9018
9503
|
}
|
|
9019
9504
|
});
|
|
@@ -9049,7 +9534,7 @@ function registerDoc(program3) {
|
|
|
9049
9534
|
code: "bad_request"
|
|
9050
9535
|
});
|
|
9051
9536
|
}
|
|
9052
|
-
ok(row, () => line(`Updated document ${
|
|
9537
|
+
ok(row, () => line(`Updated document ${import_picocolors31.default.bold(row.id)} \u2014 ${row.title}`));
|
|
9053
9538
|
})
|
|
9054
9539
|
);
|
|
9055
9540
|
}
|
|
@@ -9063,7 +9548,7 @@ function readMirror(cwd, filePath) {
|
|
|
9063
9548
|
}
|
|
9064
9549
|
|
|
9065
9550
|
// src/commands/design.ts
|
|
9066
|
-
var
|
|
9551
|
+
var import_picocolors32 = __toESM(require_picocolors(), 1);
|
|
9067
9552
|
function registerDesign(program3) {
|
|
9068
9553
|
const design = program3.command("design").description("Read the project's brand (colours, fonts, logo)");
|
|
9069
9554
|
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 +9562,11 @@ function registerDesign(program3) {
|
|
|
9077
9562
|
if (opts.raw) {
|
|
9078
9563
|
ok(files, () => {
|
|
9079
9564
|
if (!files.length) {
|
|
9080
|
-
line(
|
|
9565
|
+
line(import_picocolors32.default.dim("No brand set for this project."));
|
|
9081
9566
|
return;
|
|
9082
9567
|
}
|
|
9083
9568
|
for (const f of files) {
|
|
9084
|
-
line(
|
|
9569
|
+
line(import_picocolors32.default.bold(f.path));
|
|
9085
9570
|
line(f.contents);
|
|
9086
9571
|
line("");
|
|
9087
9572
|
}
|
|
@@ -9098,21 +9583,21 @@ function registerDesign(program3) {
|
|
|
9098
9583
|
} : { hasBrand: false, colors: {}, fonts: {}, brand: {}, files: [] };
|
|
9099
9584
|
ok(summary, () => {
|
|
9100
9585
|
if (!tokens) {
|
|
9101
|
-
line(
|
|
9586
|
+
line(import_picocolors32.default.dim("No brand set for this project \u2014 choose sensible styling yourself."));
|
|
9102
9587
|
return;
|
|
9103
9588
|
}
|
|
9104
9589
|
for (const [name, value] of Object.entries(tokens.brand)) {
|
|
9105
|
-
line(`${
|
|
9590
|
+
line(`${import_picocolors32.default.dim(name.padEnd(12))} ${value}`);
|
|
9106
9591
|
}
|
|
9107
9592
|
for (const [name, value] of Object.entries(tokens.color)) {
|
|
9108
|
-
line(`${
|
|
9593
|
+
line(`${import_picocolors32.default.dim(`color.${name}`.padEnd(12))} ${value}`);
|
|
9109
9594
|
}
|
|
9110
9595
|
for (const [name, value] of Object.entries(tokens.font)) {
|
|
9111
|
-
line(`${
|
|
9596
|
+
line(`${import_picocolors32.default.dim(`font.${name}`.padEnd(12))} ${value}`);
|
|
9112
9597
|
}
|
|
9113
9598
|
line("");
|
|
9114
9599
|
line(
|
|
9115
|
-
|
|
9600
|
+
import_picocolors32.default.dim(
|
|
9116
9601
|
`Generated into the working tree as ${files.map((f) => f.path).join(", ")} \u2014 wire those in, never edit them.`
|
|
9117
9602
|
)
|
|
9118
9603
|
);
|
|
@@ -9143,7 +9628,7 @@ function unwrap(group) {
|
|
|
9143
9628
|
}
|
|
9144
9629
|
|
|
9145
9630
|
// src/commands/api.ts
|
|
9146
|
-
var
|
|
9631
|
+
var import_picocolors33 = __toESM(require_picocolors(), 1);
|
|
9147
9632
|
import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
|
|
9148
9633
|
import { join as join3, relative, sep } from "path";
|
|
9149
9634
|
|
|
@@ -9257,10 +9742,10 @@ function registerApi(program3) {
|
|
|
9257
9742
|
const appId = requireApp(opts.app);
|
|
9258
9743
|
const res = await api(ctx, `/v1/apps/${encodeURIComponent(appId)}/api/requests`);
|
|
9259
9744
|
ok(res, () => {
|
|
9260
|
-
for (const note of res?.notes ?? []) line(
|
|
9745
|
+
for (const note of res?.notes ?? []) line(import_picocolors33.default.dim(note));
|
|
9261
9746
|
for (const r of res?.requests ?? []) {
|
|
9262
9747
|
line(
|
|
9263
|
-
`${
|
|
9748
|
+
`${import_picocolors33.default.dim(r.method.padEnd(6))}${r.path}${r.note ? import_picocolors33.default.dim(` ${r.note}`) : ""}`
|
|
9264
9749
|
);
|
|
9265
9750
|
}
|
|
9266
9751
|
});
|
|
@@ -9294,14 +9779,14 @@ function registerApi(program3) {
|
|
|
9294
9779
|
);
|
|
9295
9780
|
ok(res, () => {
|
|
9296
9781
|
if (!res?.ok) {
|
|
9297
|
-
line(
|
|
9782
|
+
line(import_picocolors33.default.red(res?.error ?? "The service did not answer."));
|
|
9298
9783
|
return;
|
|
9299
9784
|
}
|
|
9300
9785
|
const code = `${res.status}${res.statusText ? ` ${res.statusText}` : ""}`;
|
|
9301
|
-
const colour2 = res.status && res.status < 300 ?
|
|
9302
|
-
line(`${colour2(code)} ${
|
|
9786
|
+
const colour2 = res.status && res.status < 300 ? import_picocolors33.default.green : res.status && res.status < 500 ? import_picocolors33.default.yellow : import_picocolors33.default.red;
|
|
9787
|
+
line(`${colour2(code)} ${import_picocolors33.default.dim(`${res.durationMs}ms ${res.url}`)}`);
|
|
9303
9788
|
if (res.body) line(res.body);
|
|
9304
|
-
if (res.truncated) line(
|
|
9789
|
+
if (res.truncated) line(import_picocolors33.default.dim("(answer truncated)"));
|
|
9305
9790
|
});
|
|
9306
9791
|
if (!res?.ok) process.exitCode = 1;
|
|
9307
9792
|
})
|
|
@@ -9321,15 +9806,15 @@ function registerApi(program3) {
|
|
|
9321
9806
|
for (const r of report.routes) {
|
|
9322
9807
|
const known = report.missing.some((m) => m.path === r.path);
|
|
9323
9808
|
line(
|
|
9324
|
-
`${known ?
|
|
9809
|
+
`${known ? import_picocolors33.default.yellow("undocumented") : import_picocolors33.default.green("documented ")} ${r.path}${import_picocolors33.default.dim(` ${r.file}`)}`
|
|
9325
9810
|
);
|
|
9326
9811
|
}
|
|
9327
9812
|
for (const p of report.stale) {
|
|
9328
|
-
line(`${
|
|
9813
|
+
line(`${import_picocolors33.default.dim("in spec only ")} ${p}`);
|
|
9329
9814
|
}
|
|
9330
9815
|
line("");
|
|
9331
9816
|
if (report.ok && specFile) success(summary);
|
|
9332
|
-
else line(
|
|
9817
|
+
else line(import_picocolors33.default.yellow(summary));
|
|
9333
9818
|
});
|
|
9334
9819
|
if (opts.check && !report.ok) {
|
|
9335
9820
|
throw new WorkserError(summary, { code: "bad_request" });
|
|
@@ -9400,7 +9885,7 @@ function listRepoFiles(root, maxDepth = 8) {
|
|
|
9400
9885
|
}
|
|
9401
9886
|
|
|
9402
9887
|
// src/commands/analysis.ts
|
|
9403
|
-
var
|
|
9888
|
+
var import_picocolors34 = __toESM(require_picocolors(), 1);
|
|
9404
9889
|
import { readFileSync as readFileSync4 } from "fs";
|
|
9405
9890
|
function registerAnalysis(program3) {
|
|
9406
9891
|
const cmd = program3.command("analysis").description("Run Python analysis locally, recorded in the task");
|
|
@@ -9410,14 +9895,14 @@ function registerAnalysis(program3) {
|
|
|
9410
9895
|
const res = await api(ctx, path);
|
|
9411
9896
|
ok(res, () => {
|
|
9412
9897
|
line(
|
|
9413
|
-
`${res?.available ?
|
|
9898
|
+
`${res?.available ? import_picocolors34.default.green("python") : import_picocolors34.default.red("python")} ${res?.version ?? "not found"} ${import_picocolors34.default.dim(res?.python ?? "")}`
|
|
9414
9899
|
);
|
|
9415
9900
|
for (const lib of res?.libraries ?? []) {
|
|
9416
9901
|
line(
|
|
9417
|
-
`${lib.present ?
|
|
9902
|
+
`${lib.present ? import_picocolors34.default.green(lib.name) : import_picocolors34.default.yellow(lib.name)}${import_picocolors34.default.dim(lib.present ? "" : " missing")}`
|
|
9418
9903
|
);
|
|
9419
9904
|
}
|
|
9420
|
-
for (const note of res?.notes ?? []) line(
|
|
9905
|
+
for (const note of res?.notes ?? []) line(import_picocolors34.default.dim(note));
|
|
9421
9906
|
});
|
|
9422
9907
|
if (!res?.available) process.exitCode = 1;
|
|
9423
9908
|
})
|
|
@@ -9439,15 +9924,15 @@ function registerAnalysis(program3) {
|
|
|
9439
9924
|
);
|
|
9440
9925
|
ok(res, () => {
|
|
9441
9926
|
if (res?.stdout) line(res.stdout.replace(/\n$/, ""));
|
|
9442
|
-
if (res?.stderr) line(
|
|
9443
|
-
if (res?.truncated) line(
|
|
9927
|
+
if (res?.stderr) line(import_picocolors34.default.dim(res.stderr.replace(/\n$/, "")));
|
|
9928
|
+
if (res?.truncated) line(import_picocolors34.default.dim("(output truncated)"));
|
|
9444
9929
|
const took = `${Math.round((res?.durationMs ?? 0) / 100) / 10}s`;
|
|
9445
9930
|
line(
|
|
9446
|
-
res?.ok ?
|
|
9931
|
+
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
9932
|
);
|
|
9448
9933
|
if (res && !res.sandboxed) {
|
|
9449
9934
|
line(
|
|
9450
|
-
|
|
9935
|
+
import_picocolors34.default.dim(
|
|
9451
9936
|
"This platform has no OS sandbox, so the script ran with your own file access."
|
|
9452
9937
|
)
|
|
9453
9938
|
);
|
|
@@ -9475,7 +9960,7 @@ function readCode(file, inline) {
|
|
|
9475
9960
|
}
|
|
9476
9961
|
|
|
9477
9962
|
// src/commands/scan.ts
|
|
9478
|
-
var
|
|
9963
|
+
var import_picocolors35 = __toESM(require_picocolors(), 1);
|
|
9479
9964
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
9480
9965
|
import { existsSync as existsSync3, readFileSync as readFileSync5, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
9481
9966
|
import { join as join4, relative as relative2, sep as sep2 } from "path";
|
|
@@ -9759,22 +10244,22 @@ function runPermissions(cwd, findings, checked, skipped) {
|
|
|
9759
10244
|
}
|
|
9760
10245
|
function print2(report, summary) {
|
|
9761
10246
|
for (const s of report.skipped) {
|
|
9762
|
-
line(`${
|
|
10247
|
+
line(`${import_picocolors35.default.yellow("not checked")} ${s.check}${import_picocolors35.default.dim(` \u2014 ${s.reason}`)}`);
|
|
9763
10248
|
}
|
|
9764
10249
|
for (const f of report.findings) {
|
|
9765
|
-
const where = f.file ?
|
|
10250
|
+
const where = f.file ? import_picocolors35.default.dim(` ${f.file}${f.line ? `:${f.line}` : ""}`) : "";
|
|
9766
10251
|
line(`${severityTag(f.severity)} ${f.title}${where}`);
|
|
9767
|
-
line(` ${
|
|
10252
|
+
line(` ${import_picocolors35.default.dim(f.fix)}`);
|
|
9768
10253
|
}
|
|
9769
10254
|
if (report.findings.length || report.skipped.length) line("");
|
|
9770
10255
|
if (report.ok && !report.skipped.length) success(summary);
|
|
9771
|
-
else if (report.ok) line(
|
|
9772
|
-
else line(
|
|
10256
|
+
else if (report.ok) line(import_picocolors35.default.yellow(summary));
|
|
10257
|
+
else line(import_picocolors35.default.red(summary));
|
|
9773
10258
|
}
|
|
9774
10259
|
function severityTag(severity) {
|
|
9775
|
-
if (severity === "high") return
|
|
9776
|
-
if (severity === "medium") return
|
|
9777
|
-
return
|
|
10260
|
+
if (severity === "high") return import_picocolors35.default.red("serious ");
|
|
10261
|
+
if (severity === "medium") return import_picocolors35.default.yellow("worth fixing");
|
|
10262
|
+
return import_picocolors35.default.dim("minor ");
|
|
9778
10263
|
}
|
|
9779
10264
|
function git(cwd, args) {
|
|
9780
10265
|
try {
|
|
@@ -9835,7 +10320,7 @@ function listRepoFiles2(root, maxDepth = 8) {
|
|
|
9835
10320
|
}
|
|
9836
10321
|
|
|
9837
10322
|
// src/commands/health.ts
|
|
9838
|
-
var
|
|
10323
|
+
var import_picocolors36 = __toESM(require_picocolors(), 1);
|
|
9839
10324
|
function registerHealth(program3) {
|
|
9840
10325
|
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
10326
|
action(async ({ ctx, opts }) => {
|
|
@@ -9849,16 +10334,16 @@ function registerHealth(program3) {
|
|
|
9849
10334
|
}
|
|
9850
10335
|
function print3(res) {
|
|
9851
10336
|
if (!res?.checks?.length) {
|
|
9852
|
-
line(
|
|
10337
|
+
line(import_picocolors36.default.dim(res?.note ?? "Nothing to check."));
|
|
9853
10338
|
return;
|
|
9854
10339
|
}
|
|
9855
10340
|
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} ${
|
|
10341
|
+
const mark = c.ok ? import_picocolors36.default.green("up ") : import_picocolors36.default.red("down");
|
|
10342
|
+
const timing = import_picocolors36.default.dim(`${c.ms}ms`);
|
|
10343
|
+
const detail = c.ok ? timing : import_picocolors36.default.dim(`${c.error ?? "no answer"}${c.failures > 1 ? ` \xB7 ${c.failures} in a row` : ""}`);
|
|
10344
|
+
line(` ${mark} ${c.appName} ${import_picocolors36.default.dim(`(${c.environment})`)} ${c.url} ${detail}`);
|
|
9860
10345
|
if (c.incidentOpened) {
|
|
9861
|
-
line(
|
|
10346
|
+
line(import_picocolors36.default.yellow(` An incident has been opened on the board for this.`));
|
|
9862
10347
|
}
|
|
9863
10348
|
}
|
|
9864
10349
|
const down = res.checks.filter((c) => !c.ok);
|
|
@@ -9871,14 +10356,14 @@ function print3(res) {
|
|
|
9871
10356
|
}
|
|
9872
10357
|
const production = down.filter((c) => c.environment === "production").length;
|
|
9873
10358
|
line(
|
|
9874
|
-
|
|
10359
|
+
import_picocolors36.default.red(
|
|
9875
10360
|
`${down.length} of ${res.checks.length} not answering` + (production ? ` \u2014 ${production} customer-facing.` : " (preview only).")
|
|
9876
10361
|
)
|
|
9877
10362
|
);
|
|
9878
10363
|
}
|
|
9879
10364
|
|
|
9880
10365
|
// src/commands/urls.ts
|
|
9881
|
-
var
|
|
10366
|
+
var import_picocolors37 = __toESM(require_picocolors(), 1);
|
|
9882
10367
|
function registerUrls(program3) {
|
|
9883
10368
|
program3.command("urls").description("The stable preview and production addresses of every app in this project").option("--app <webAppId>", "just one app").action(
|
|
9884
10369
|
action(async ({ ctx, opts }) => {
|
|
@@ -9892,20 +10377,20 @@ function registerUrls(program3) {
|
|
|
9892
10377
|
const summary = urlsSummary(rows);
|
|
9893
10378
|
ok({ rows, summary }, () => {
|
|
9894
10379
|
for (const row of rows) {
|
|
9895
|
-
const label =
|
|
9896
|
-
const value = row.url ?
|
|
10380
|
+
const label = import_picocolors37.default.dim(row.environment.padEnd(10));
|
|
10381
|
+
const value = row.url ? import_picocolors37.default.cyan(row.url) : import_picocolors37.default.dim(row.note ?? "not published");
|
|
9897
10382
|
line(` ${row.appName.padEnd(22)} ${label} ${value}`);
|
|
9898
10383
|
}
|
|
9899
10384
|
line("");
|
|
9900
10385
|
if (rows.some((r) => r.url)) success(summary);
|
|
9901
|
-
else line(
|
|
10386
|
+
else line(import_picocolors37.default.yellow(summary));
|
|
9902
10387
|
});
|
|
9903
10388
|
})
|
|
9904
10389
|
);
|
|
9905
10390
|
}
|
|
9906
10391
|
|
|
9907
10392
|
// src/commands/deployments.ts
|
|
9908
|
-
var
|
|
10393
|
+
var import_picocolors38 = __toESM(require_picocolors(), 1);
|
|
9909
10394
|
function registerDeployments(program3) {
|
|
9910
10395
|
const cmd = program3.command("deployments").description("Deployment history, and putting a build in front of customers");
|
|
9911
10396
|
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 +10408,7 @@ function registerDeployments(program3) {
|
|
|
9923
10408
|
ok(res, () => {
|
|
9924
10409
|
if (!items.length) {
|
|
9925
10410
|
return line(
|
|
9926
|
-
|
|
10411
|
+
import_picocolors38.default.dim(
|
|
9927
10412
|
environment ? `Nothing has been deployed to ${environment} yet.` : "Nothing has been deployed yet. `workser deploy` builds the first one."
|
|
9928
10413
|
)
|
|
9929
10414
|
);
|
|
@@ -9943,13 +10428,13 @@ function registerDeployments(program3) {
|
|
|
9943
10428
|
).catch(() => null) : null;
|
|
9944
10429
|
ok({ ...dep, logs }, () => {
|
|
9945
10430
|
line(formatDeployment(dep));
|
|
9946
|
-
if (dep?.error_message) line(
|
|
10431
|
+
if (dep?.error_message) line(import_picocolors38.default.red(` ${dep.error_message}`));
|
|
9947
10432
|
const events = logs?.events ?? [];
|
|
9948
10433
|
for (const e of events) {
|
|
9949
|
-
line(` ${
|
|
10434
|
+
line(` ${import_picocolors38.default.dim(String(e.type ?? "log"))} ${e.text ?? ""}`);
|
|
9950
10435
|
}
|
|
9951
10436
|
if (opts.logs && !events.length) {
|
|
9952
|
-
line(
|
|
10437
|
+
line(import_picocolors38.default.dim(" That build produced no output."));
|
|
9953
10438
|
}
|
|
9954
10439
|
});
|
|
9955
10440
|
})
|
|
@@ -9993,16 +10478,16 @@ function printPromoted(res, version) {
|
|
|
9993
10478
|
const what = version === null ? "the latest build" : `version ${version}`;
|
|
9994
10479
|
const url = res.url ?? res.vercel_url;
|
|
9995
10480
|
success(`Production is being rebuilt from ${what}.`);
|
|
9996
|
-
if (url) line(
|
|
9997
|
-
line(
|
|
10481
|
+
if (url) line(import_picocolors38.default.dim(`It will be at ${url}`));
|
|
10482
|
+
line(import_picocolors38.default.dim("`workser deploy status` follows it."));
|
|
9998
10483
|
}
|
|
9999
10484
|
function formatDeployment(d) {
|
|
10000
10485
|
if (!d) return "";
|
|
10001
|
-
const version = d.version !== void 0 ?
|
|
10002
|
-
const env =
|
|
10486
|
+
const version = d.version !== void 0 ? import_picocolors38.default.yellow(`v${d.version}`) : import_picocolors38.default.dim("v?");
|
|
10487
|
+
const env = import_picocolors38.default.dim((d.environment ?? "?").padEnd(10));
|
|
10003
10488
|
const app = d.webAppName ? `${d.webAppName} ` : "";
|
|
10004
|
-
const when =
|
|
10005
|
-
const url = d.url ? " " +
|
|
10489
|
+
const when = import_picocolors38.default.dim(formatTime2(d.created_at));
|
|
10490
|
+
const url = d.url ? " " + import_picocolors38.default.cyan(d.url) : "";
|
|
10006
10491
|
return `${version} ${env} ${colorStatus(d.status ?? "")} ${app}${when}${url}`;
|
|
10007
10492
|
}
|
|
10008
10493
|
function formatTime2(t) {
|
|
@@ -10012,7 +10497,7 @@ function formatTime2(t) {
|
|
|
10012
10497
|
}
|
|
10013
10498
|
|
|
10014
10499
|
// src/commands/usage.ts
|
|
10015
|
-
var
|
|
10500
|
+
var import_picocolors39 = __toESM(require_picocolors(), 1);
|
|
10016
10501
|
|
|
10017
10502
|
// src/usage.ts
|
|
10018
10503
|
var NEAR_LIMIT_FRACTION = 0.8;
|
|
@@ -10107,7 +10592,7 @@ function registerUsage(program3) {
|
|
|
10107
10592
|
function print4(report) {
|
|
10108
10593
|
const dims = report.dimensions ?? [];
|
|
10109
10594
|
if (!dims.length) {
|
|
10110
|
-
return line(
|
|
10595
|
+
return line(import_picocolors39.default.dim("Nothing to measure for this project yet."));
|
|
10111
10596
|
}
|
|
10112
10597
|
const width = Math.max(...dims.map((d) => d.label.length));
|
|
10113
10598
|
for (const d of dims) {
|
|
@@ -10116,23 +10601,23 @@ function print4(report) {
|
|
|
10116
10601
|
line("");
|
|
10117
10602
|
const summary = usageSummary(report);
|
|
10118
10603
|
const worst = dims.map(usageState);
|
|
10119
|
-
if (worst.includes("over")) line(
|
|
10604
|
+
if (worst.includes("over")) line(import_picocolors39.default.red(summary));
|
|
10120
10605
|
else if (worst.includes("near") || worst.includes("unknown"))
|
|
10121
|
-
line(
|
|
10606
|
+
line(import_picocolors39.default.yellow(summary));
|
|
10122
10607
|
else success(summary);
|
|
10123
10608
|
}
|
|
10124
10609
|
function gauge(d, _labelWidth) {
|
|
10125
10610
|
const drawn = bar(d);
|
|
10126
|
-
return drawn ? ` ${
|
|
10611
|
+
return drawn ? ` ${import_picocolors39.default.dim(drawn)}` : "";
|
|
10127
10612
|
}
|
|
10128
10613
|
function colour(d) {
|
|
10129
10614
|
switch (usageState(d)) {
|
|
10130
10615
|
case "over":
|
|
10131
|
-
return d.kind === "hard" ?
|
|
10616
|
+
return d.kind === "hard" ? import_picocolors39.default.red : import_picocolors39.default.yellow;
|
|
10132
10617
|
case "near":
|
|
10133
|
-
return
|
|
10618
|
+
return import_picocolors39.default.yellow;
|
|
10134
10619
|
case "unknown":
|
|
10135
|
-
return
|
|
10620
|
+
return import_picocolors39.default.dim;
|
|
10136
10621
|
default:
|
|
10137
10622
|
return (s) => s;
|
|
10138
10623
|
}
|
|
@@ -10140,7 +10625,7 @@ function colour(d) {
|
|
|
10140
10625
|
|
|
10141
10626
|
// src/index.ts
|
|
10142
10627
|
var pkg = {
|
|
10143
|
-
version: true ? "0.6.
|
|
10628
|
+
version: true ? "0.6.3" : "0.0.0-dev"
|
|
10144
10629
|
};
|
|
10145
10630
|
var program2 = new Command();
|
|
10146
10631
|
program2.name("workser").description(
|
|
@@ -10193,6 +10678,7 @@ registerAsk(program2);
|
|
|
10193
10678
|
registerSearch(program2);
|
|
10194
10679
|
registerBoard(program2);
|
|
10195
10680
|
registerTask(program2);
|
|
10681
|
+
registerGoal(program2);
|
|
10196
10682
|
registerDecision(program2);
|
|
10197
10683
|
registerDoc(program2);
|
|
10198
10684
|
registerDesign(program2);
|