@harborppm/cli 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/app.mjs +20 -1
- package/lib/cli.mjs +33 -6
- package/lib/format.mjs +9 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,9 +155,11 @@ harbor risk "TITLE" --project P|--program P [--impact 1-5] [--probability 1-5]
|
|
|
155
155
|
harbor issue "TITLE" --project P|--program P [--impact 1-5] [--owner NAME]
|
|
156
156
|
[--due DATE]
|
|
157
157
|
harbor decision "TITLE" --project P|--program P [--owner NAME] [--due DATE]
|
|
158
|
+
[--turned-down T] [--look-again DATE] [--supersedes "OLD"]
|
|
158
159
|
harbor risks [words] [--kind risk,issue,decision] [--project P] [--closed]
|
|
159
160
|
harbor raid "TITLE" [--status S] [--owner me|none|NAME] [--impact 1-5] [--probability 1-5]
|
|
160
|
-
[--due DATE|none] [--update TEXT] [--mitigation T] [--resolution T]
|
|
161
|
+
[--due DATE|none] [--update TEXT] [--mitigation T] [--resolution T]
|
|
162
|
+
[--turned-down T] [--look-again DATE|none] …
|
|
161
163
|
one risk, issue or decision in full, or change it
|
|
162
164
|
harbor review [WORDS] [--mine|--owner NAME] [--portfolio P] [--done]
|
|
163
165
|
the review: programs worst first, with next moves
|
package/dist/app.mjs
CHANGED
|
@@ -2069,7 +2069,8 @@ function normalizeRaidItem(r) {
|
|
|
2069
2069
|
updates: Array.isArray(r.updates) ? r.updates : [],
|
|
2070
2070
|
scoreHistory: Array.isArray(r.scoreHistory) && r.scoreHistory.length ? r.scoreHistory : void 0,
|
|
2071
2071
|
...kind === "risk" && typeof r.happened === "boolean" ? { happened: r.happened } : {},
|
|
2072
|
-
...kind === "decision" ? decisionFields(r) : {}
|
|
2072
|
+
...kind === "decision" ? decisionFields(r) : {},
|
|
2073
|
+
...ids$1(r.followerIds).length ? { followerIds: ids$1(r.followerIds) } : {}
|
|
2073
2074
|
};
|
|
2074
2075
|
}
|
|
2075
2076
|
/** A revisit condition that still says something, or null. */
|
|
@@ -14719,6 +14720,24 @@ var init_assignment = __esmMin((() => {}));
|
|
|
14719
14720
|
//#region src/lib/slices/tasks.ts
|
|
14720
14721
|
function tasksSlice({ set, get, write }) {
|
|
14721
14722
|
return {
|
|
14723
|
+
followWork: (personId, taskIds, raidIds = []) => {
|
|
14724
|
+
const tasks = new Set(taskIds);
|
|
14725
|
+
const raid = new Set(raidIds);
|
|
14726
|
+
const add = (ids) => ids?.includes(personId) ? ids : [...ids ?? [], personId];
|
|
14727
|
+
write({
|
|
14728
|
+
tasks: get().tasks.map((t) => tasks.has(t.id) && t.assigneeId !== personId ? {
|
|
14729
|
+
...t,
|
|
14730
|
+
followerIds: add(t.followerIds)
|
|
14731
|
+
} : t),
|
|
14732
|
+
raidItems: get().raidItems.map((r) => raid.has(r.id) && r.ownerId !== personId ? {
|
|
14733
|
+
...r,
|
|
14734
|
+
followerIds: add(r.followerIds),
|
|
14735
|
+
updatedAt: stamp()
|
|
14736
|
+
} : r)
|
|
14737
|
+
});
|
|
14738
|
+
const who = get().people.find((p) => p.id === personId);
|
|
14739
|
+
get().log(`added ${who?.name ?? "someone"} as a second pair of hands on ${taskIds.length + raidIds.length} item${taskIds.length + raidIds.length === 1 ? "" : "s"}`, "/people");
|
|
14740
|
+
},
|
|
14722
14741
|
addTask: (t, opts) => {
|
|
14723
14742
|
if (!opts?.allowOverCap) {
|
|
14724
14743
|
if (columnWip(get(), t.projectId, t.status, 1)) return "";
|
package/lib/cli.mjs
CHANGED
|
@@ -142,6 +142,8 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
142
142
|
harbor issue "TITLE" --project P|--program P [--impact 1-5] [--owner NAME]
|
|
143
143
|
[--due DATE]
|
|
144
144
|
harbor decision "TITLE" --project P|--program P [--owner NAME] [--due DATE]
|
|
145
|
+
[--turned-down T] [--look-again DATE]
|
|
146
|
+
[--supersedes "OLD DECISION"]
|
|
145
147
|
harbor risks [words] [--kind risk,issue,decision] [--project P] [--closed]
|
|
146
148
|
the register, worst first
|
|
147
149
|
(--closed adds closed ones)
|
|
@@ -149,7 +151,7 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
149
151
|
[--probability 1-5] [--due DATE|none] [--update TEXT]
|
|
150
152
|
[--title T] [--description T] [--mitigation T]
|
|
151
153
|
[--contingency T] [--resolution T] [--rationale T]
|
|
152
|
-
[--notes T]
|
|
154
|
+
[--notes T] [--turned-down T] [--look-again DATE|none]
|
|
153
155
|
one risk, issue or decision in
|
|
154
156
|
full, or change it
|
|
155
157
|
harbor review [WORDS] [--mine|--owner NAME] [--portfolio P] [--done]
|
|
@@ -743,7 +745,8 @@ const DETAIL = {
|
|
|
743
745
|
" Also --owner me|none|NAME, --impact 1-5, --probability 1-5 (risks),",
|
|
744
746
|
" --due DATE|none (review by, or decide by), --title, --description,",
|
|
745
747
|
" --mitigation and --contingency (risks), --resolution (issues and",
|
|
746
|
-
" decisions), --rationale
|
|
748
|
+
" decisions), --rationale, --turned-down and --look-again DATE|none",
|
|
749
|
+
" (decisions) and --notes. TITLE is its title or",
|
|
747
750
|
" enough of it to pick one out; harbor risks lists them. Changes several",
|
|
748
751
|
" at once, as one change.",
|
|
749
752
|
],
|
|
@@ -794,6 +797,12 @@ const DETAIL = {
|
|
|
794
797
|
' harbor decision "TITLE" --project P writes: logs a decision to make',
|
|
795
798
|
' harbor decision "TITLE" --program P writes: logs one on the program',
|
|
796
799
|
" [--owner NAME] [--due DATE] (the date to decide by)",
|
|
800
|
+
" [--turned-down T] (what was turned down, and why not)",
|
|
801
|
+
" [--look-again DATE] (when it comes back to the top of the brief)",
|
|
802
|
+
' harbor decision "TITLE" --supersedes "OLD"',
|
|
803
|
+
" writes: a decision that replaces an",
|
|
804
|
+
" earlier one, which reads as",
|
|
805
|
+
" superseded",
|
|
797
806
|
],
|
|
798
807
|
};
|
|
799
808
|
|
|
@@ -850,6 +859,9 @@ function distance(a, b) {
|
|
|
850
859
|
/** A mistyped command, with the one it was probably meant to be. */
|
|
851
860
|
export function noCommand(name) {
|
|
852
861
|
if (name.startsWith("-")) return `${name} goes after a command, like harbor mine ${name}.`;
|
|
862
|
+
// A plural kind reads the register, not the command that writes one.
|
|
863
|
+
if (["decisions", "issues"].includes(name))
|
|
864
|
+
return `No command "${name}". harbor risks --kind ${name.slice(0, -1)} lists them.`;
|
|
853
865
|
const near = COMMANDS.filter((c) => distance(name, c) <= (c.length > 4 ? 2 : 1));
|
|
854
866
|
const guess = near.length === 1 ? ` Did you mean harbor ${near[0]}?` : "";
|
|
855
867
|
return `No command "${name}".${guess} Run harbor help.`;
|
|
@@ -1963,7 +1975,7 @@ export async function run(argv, io = {}) {
|
|
|
1963
1975
|
includeClosed: Boolean(values.closed),
|
|
1964
1976
|
limit: 50,
|
|
1965
1977
|
},
|
|
1966
|
-
(r) => raidList(r, positionals.join(" ")),
|
|
1978
|
+
(r) => raidList(r, positionals.join(" "), kind),
|
|
1967
1979
|
);
|
|
1968
1980
|
}
|
|
1969
1981
|
case "report": {
|
|
@@ -2292,6 +2304,8 @@ export async function run(argv, io = {}) {
|
|
|
2292
2304
|
"update",
|
|
2293
2305
|
"decided-by",
|
|
2294
2306
|
"decided-on",
|
|
2307
|
+
"turned-down",
|
|
2308
|
+
"look-again",
|
|
2295
2309
|
...fields,
|
|
2296
2310
|
],
|
|
2297
2311
|
),
|
|
@@ -2309,6 +2323,9 @@ export async function run(argv, io = {}) {
|
|
|
2309
2323
|
decidedBy: values["decided-by"],
|
|
2310
2324
|
decidedOn:
|
|
2311
2325
|
values["decided-on"] === undefined ? undefined : nullable(dueDate(values["decided-on"])),
|
|
2326
|
+
turnedDown: values["turned-down"],
|
|
2327
|
+
revisitOn:
|
|
2328
|
+
values["look-again"] === undefined ? undefined : nullable(dueDate(values["look-again"])),
|
|
2312
2329
|
update: values.update,
|
|
2313
2330
|
...Object.fromEntries(fields.map((f) => [f, values[f]])),
|
|
2314
2331
|
};
|
|
@@ -2370,11 +2387,15 @@ export async function run(argv, io = {}) {
|
|
|
2370
2387
|
spec(
|
|
2371
2388
|
[],
|
|
2372
2389
|
["project", "program", "owner", "due"].concat(
|
|
2373
|
-
command === "risk"
|
|
2390
|
+
command === "risk"
|
|
2391
|
+
? ["impact", "probability"]
|
|
2392
|
+
: command === "issue"
|
|
2393
|
+
? ["impact"]
|
|
2394
|
+
: ["supersedes", "turned-down", "look-again"],
|
|
2374
2395
|
),
|
|
2375
2396
|
),
|
|
2376
2397
|
);
|
|
2377
|
-
if (!values.project && !values.program) {
|
|
2398
|
+
if (!values.project && !values.program && !values.supersedes) {
|
|
2378
2399
|
throw new HarborError("Say where it belongs: --project NAME or --program NAME.");
|
|
2379
2400
|
}
|
|
2380
2401
|
return call(
|
|
@@ -2389,8 +2410,14 @@ export async function run(argv, io = {}) {
|
|
|
2389
2410
|
impact: level(values.impact, "impact"),
|
|
2390
2411
|
probability: level(values.probability, "probability"),
|
|
2391
2412
|
due: dueDate(values.due),
|
|
2413
|
+
supersedes: values.supersedes,
|
|
2414
|
+
turnedDown: values["turned-down"],
|
|
2415
|
+
revisitOn: values["look-again"] === undefined ? undefined : dueDate(values["look-again"]),
|
|
2392
2416
|
},
|
|
2393
|
-
(r) =>
|
|
2417
|
+
(r) =>
|
|
2418
|
+
r.replaces
|
|
2419
|
+
? `Logged the decision “${r.raised.title}”; it replaces “${r.replaces.title}”.`
|
|
2420
|
+
: `Logged the ${command} “${r.raised.title}”.`,
|
|
2394
2421
|
);
|
|
2395
2422
|
}
|
|
2396
2423
|
default:
|
package/lib/format.mjs
CHANGED
|
@@ -246,12 +246,19 @@ export function todayLocal(d = new Date()) {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
/** The register, worst first: kind, title, then status, scores, owner and where it sits. */
|
|
249
|
-
export function raidList(r, words = "") {
|
|
249
|
+
export function raidList(r, words = "", kinds = []) {
|
|
250
250
|
const want = words.trim().toLowerCase();
|
|
251
251
|
const items = (r.items ?? []).filter(
|
|
252
252
|
(x) => !want || `${x.title} ${x.description ?? ""}`.toLowerCase().includes(want),
|
|
253
253
|
);
|
|
254
|
-
if (!items.length)
|
|
254
|
+
if (!items.length) {
|
|
255
|
+
if (want) return "Nothing in the register matches.";
|
|
256
|
+
const only =
|
|
257
|
+
kinds?.length === 1
|
|
258
|
+
? { risk: "risks", issue: "issues", decision: "decisions" }[kinds[0]]
|
|
259
|
+
: null;
|
|
260
|
+
return only ? `No ${only} in the register.` : "The register is clear.";
|
|
261
|
+
}
|
|
255
262
|
const lines = items.map((x) => {
|
|
256
263
|
const bits = [
|
|
257
264
|
x.status?.toLowerCase(),
|