@harborppm/cli 0.95.0 → 0.97.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cli.mjs CHANGED
@@ -54,6 +54,8 @@ import {
54
54
  catchupText,
55
55
  handoverText,
56
56
  fogText,
57
+ scuttleText,
58
+ scuttleCallText,
57
59
  agentWorkText,
58
60
  raidDetail,
59
61
  raidList,
@@ -295,6 +297,13 @@ export const HELP = `harbor: Harbor from the terminal
295
297
  harbor fog --sweep [--project P] one at a time: still needed, a
296
298
  new date, or drop it
297
299
  harbor fog keep TASK it's still needed: out of the fog
300
+ harbor scuttle [--program P|--portfolio P] projects that look adrift: still
301
+ for weeks, slipping, long late
302
+ harbor scuttle sail|refit PROJECT [--end DATE] [--note TEXT]
303
+ keep it, or refit it, for three
304
+ months out of the line-up
305
+ harbor scuttle PROJECT --why "TEXT" stop it on purpose: Done, on the
306
+ register, its tasks set free
298
307
  harbor watch [--project P] [--bell] a live ticker of the harbor's
299
308
  changes; yours bright, q stops
300
309
  harbor watch --once [--since TIME] [--json] the latest changes (or all since
@@ -1149,6 +1158,24 @@ const DETAIL = {
1149
1158
  " The sweep needs a terminal and full access (harbor login without",
1150
1159
  " --read-only), since dropping a task deletes it.",
1151
1160
  ],
1161
+ scuttle: [
1162
+ " harbor scuttle [--program P] reads: running projects that look",
1163
+ " [--portfolio P] adrift (nothing moved for four",
1164
+ " weeks, slipping again and again, long",
1165
+ " past the end, paused for a month),",
1166
+ " and what stopping each would free",
1167
+ " harbor scuttle sail PROJECT writes: keep it sailing; it leaves",
1168
+ " the line-up for three months",
1169
+ " harbor scuttle refit PROJECT writes: a new end date and/or a line",
1170
+ ' [--end DATE] [--note "TEXT"] on what changes',
1171
+ ' harbor scuttle PROJECT --why "TEXT" writes: stop it. It closes as Done',
1172
+ " with the why as its note, a decision",
1173
+ " goes on the register, and its open",
1174
+ " tasks come off people's lists",
1175
+ "",
1176
+ " Nothing is deleted: harbor health auto --project P raises a scuttled project",
1177
+ " again and gives its tasks back.",
1178
+ ],
1152
1179
  watch: [
1153
1180
  " harbor watch reads, live: each change to the harbor",
1154
1181
  " as it lands, one line with its time",
@@ -3579,6 +3606,49 @@ export async function run(argv, io = {}) {
3579
3606
  }
3580
3607
  return call("fog", { harbor, project: values.project, limit: 100 }, (r) => fogText(r));
3581
3608
  }
3609
+ case "scuttle": {
3610
+ const { positionals, values, harbor } = parse(
3611
+ spec(["list"], ["program", "portfolio", "end", "note", "why"]),
3612
+ );
3613
+ const [first, ...rest] = positionals;
3614
+ if (first === "sail" || first === "refit") {
3615
+ if (values.why !== undefined)
3616
+ throw new HarborError(`--why goes with scuttling: harbor scuttle PROJECT --why "TEXT".`);
3617
+ const project = one(rest, "project");
3618
+ if (first === "sail" && (values.end !== undefined || values.note !== undefined))
3619
+ throw new HarborError("--end and --note go with a refit: harbor scuttle refit PROJECT --end DATE.");
3620
+ if (first === "refit" && values.end === undefined && values.note === undefined)
3621
+ throw new HarborError('A refit needs --end DATE or --note "what changes".');
3622
+ return call(
3623
+ "answer_scuttle_or_sail",
3624
+ {
3625
+ harbor,
3626
+ project,
3627
+ call: first,
3628
+ ...(values.end !== undefined ? { endDate: dueDate(values.end) } : {}),
3629
+ ...(values.note !== undefined ? { note: values.note } : {}),
3630
+ },
3631
+ scuttleCallText,
3632
+ );
3633
+ }
3634
+ if (first !== undefined) {
3635
+ const project = one(positionals, "project");
3636
+ if (values.why === undefined || !values.why.trim())
3637
+ throw new HarborError(
3638
+ `Say why it stops: harbor scuttle ${project} --why "TEXT". To keep it: harbor scuttle sail ${project}.`,
3639
+ );
3640
+ return call(
3641
+ "answer_scuttle_or_sail",
3642
+ { harbor, project, call: "scuttle", why: values.why },
3643
+ scuttleCallText,
3644
+ );
3645
+ }
3646
+ return call(
3647
+ "scuttle_or_sail",
3648
+ { harbor, program: values.program, portfolio: values.portfolio, limit: 100 },
3649
+ scuttleText,
3650
+ );
3651
+ }
3582
3652
  case "bridge": {
3583
3653
  const { positionals, values, harbor } = parse(spec(["list"], ["program"]));
3584
3654
  none(positionals);
package/lib/format.mjs CHANGED
@@ -1124,6 +1124,45 @@ export function fogText(r, { sweepHint = true } = {}) {
1124
1124
  return lines.join("\n");
1125
1125
  }
1126
1126
 
1127
+ /** harbor scuttle: projects that look adrift, with what stopping each would free. */
1128
+ export function scuttleText(r) {
1129
+ const where = r.program ? ` in ${r.program}` : r.portfolio ? ` in ${r.portfolio}` : "";
1130
+ if (!r.adrift)
1131
+ return `Nothing looks adrift${where}: every running project has moved lately, and none keeps slipping.`;
1132
+ const lines = [
1133
+ `${r.adrift} ${r.adrift === 1 ? "project looks" : "projects look"} adrift${where}. Keep each sailing, refit it, or stop it on purpose.`,
1134
+ "",
1135
+ ];
1136
+ for (const p of r.projects) {
1137
+ lines.push(
1138
+ ` ${(p.key ?? "").padEnd(5)} ${p.project}${p.owner ? ` · ${p.owner}` : ""} · ends ${p.endDate}`,
1139
+ );
1140
+ for (const s of p.signs) lines.push(` ${s.text}`);
1141
+ const frees = [
1142
+ `${p.openTasks} open task${p.openTasks === 1 ? "" : "s"}`,
1143
+ p.crew.length ? `with ${p.crew.map((c) => c.person).filter(Boolean).join(", ")}` : null,
1144
+ p.hoursPerWeek ? `${p.hoursPerWeek}h a week logged` : null,
1145
+ p.spendPerWeek ? `$${p.spendPerWeek.toLocaleString("en-US")} a week of spend` : null,
1146
+ ];
1147
+ lines.push(` holds ${frees.filter(Boolean).join(" · ")}`);
1148
+ }
1149
+ if (r.more) lines.push("", `And ${r.adrift - r.projects.length} more.`);
1150
+ lines.push(
1151
+ "",
1152
+ 'harbor scuttle sail PROJECT keeps one, harbor scuttle refit PROJECT --end DATE refits it, harbor scuttle PROJECT --why "TEXT" stops it.',
1153
+ );
1154
+ return lines.join("\n");
1155
+ }
1156
+
1157
+ /** What harbor scuttle sail, refit or PROJECT --why did. */
1158
+ export function scuttleCallText(r) {
1159
+ if (r.call === "scuttle")
1160
+ return `Scuttled ${r.project}. ${r.sendOff} The why is on the register as a decision; harbor health auto --project "${r.project}" raises it again.`;
1161
+ if (r.call === "refit")
1162
+ return `Refitted ${r.project}: ${r.note ?? `ends ${r.endDate}`}. It's out of the line-up until ${r.quietUntil}.`;
1163
+ return `${r.project} sails on. It's out of the line-up until ${r.quietUntil}.`;
1164
+ }
1165
+
1127
1166
  /** harbor lifeboats: work only one person knows, numbered for harbor lifeboats N. */
1128
1167
  export function lifeboatsText(r) {
1129
1168
  const where = r.program ? ` in ${r.program}` : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborppm/cli",
3
- "version": "0.95.0",
3
+ "version": "0.97.0",
4
4
  "description": "Harbor from the terminal: a full-screen app with everything, plus commands for scripts and agents.",
5
5
  "keywords": [
6
6
  "harbor",