@harborppm/cli 0.49.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.mjs +34 -1
- package/lib/cli.mjs +34 -14
- package/package.json +1 -1
package/dist/app.mjs
CHANGED
|
@@ -25377,9 +25377,30 @@ function dockCompare(data, projectId, changes, now) {
|
|
|
25377
25377
|
pace: plan.pace,
|
|
25378
25378
|
shift: a && b ? dayNumber(b) - dayNumber(a) : null,
|
|
25379
25379
|
load,
|
|
25380
|
-
stale: plan.stale
|
|
25380
|
+
stale: plan.stale,
|
|
25381
|
+
notes: dockNotes(plan.project, realTasks, dockTide, now)
|
|
25381
25382
|
};
|
|
25382
25383
|
}
|
|
25384
|
+
/**
|
|
25385
|
+
* What to know before trusting the shift: the pace is read from weeks of
|
|
25386
|
+
* finished work, so a short or lumpy record (work marked done in one go) or
|
|
25387
|
+
* a due date that holds the finish can leave the dates where they were.
|
|
25388
|
+
*/
|
|
25389
|
+
function dockNotes(project, realTasks, docked, now) {
|
|
25390
|
+
if (docked.kind !== "forecast") return [];
|
|
25391
|
+
const notes = [];
|
|
25392
|
+
const weekly = weeklyFinished(realTasks, project.startDate, now);
|
|
25393
|
+
const finished = weekly.reduce((a, b) => a + b, 0);
|
|
25394
|
+
const busiest = Math.max(0, ...weekly);
|
|
25395
|
+
if (weekly.length < 2) notes.push("The pace comes from under two weeks of finished work, so treat the dates as rough.");
|
|
25396
|
+
else if (docked.rough) notes.push(`Only ${finished} task${finished === 1 ? "" : "s"} finished in the last ${weekly.length} weeks, so treat the dates as rough.`);
|
|
25397
|
+
if (weekly.length >= 2 && finished >= 4 && busiest * 2 > finished) notes.push(`${busiest} of the ${finished} tasks finished lately were marked done in one week, so the pace jumps from week to week and a few tasks more or less may not move the dates.`);
|
|
25398
|
+
if (docked.heldBy?.dueDate) {
|
|
25399
|
+
const name = taskKey(docked.heldBy, project) ?? docked.heldBy.title;
|
|
25400
|
+
notes.push(`${name} is due ${shortDate(docked.heldBy.dueDate)}, after the pace would finish, so its due date sets the finish and work done before then doesn't move it.`);
|
|
25401
|
+
}
|
|
25402
|
+
return notes;
|
|
25403
|
+
}
|
|
25383
25404
|
/** "11 days sooner", "3 days later", "Same day", or null without both forecasts. */
|
|
25384
25405
|
function shiftText(shift) {
|
|
25385
25406
|
if (shift == null) return null;
|
|
@@ -25573,6 +25594,10 @@ function tryLines(data, project, changes, cmp, today) {
|
|
|
25573
25594
|
const end = docked.project.endDate;
|
|
25574
25595
|
if (end >= today && real.tide.kind === "forecast" && docked.tide.kind === "forecast") out.push(row(`Making ${shortDate(end)}`, end === real.project.endDate ? chanceText(real.tide.chanceByEnd) : "-", chanceText(docked.tide.chanceByEnd)));
|
|
25575
25596
|
if (cmp.pace !== 1) out.push(row("Crew pace", "100%", `${Math.round(cmp.pace * 100)}%`));
|
|
25597
|
+
if (changes.length && cmp.notes.length) {
|
|
25598
|
+
out.push("");
|
|
25599
|
+
for (const n of cmp.notes) out.push(` Note: ${n}`);
|
|
25600
|
+
}
|
|
25576
25601
|
if (cmp.load.length) {
|
|
25577
25602
|
out.push("", " Due in the next six weeks:");
|
|
25578
25603
|
for (const l of cmp.load) {
|
|
@@ -25621,6 +25646,14 @@ async function tryHere(opts) {
|
|
|
25621
25646
|
lines
|
|
25622
25647
|
};
|
|
25623
25648
|
if ((opts.apply || opts.save) && changes.length === 0) throw new HarborError("There are no changes to apply or save. Add some, like --drop RL-4.");
|
|
25649
|
+
if (opts.apply && !opts.confirm) {
|
|
25650
|
+
const gone = changes.flatMap((c) => {
|
|
25651
|
+
if (c.kind !== "drop") return [];
|
|
25652
|
+
const t = data.tasks.find((x) => x.id === c.taskId);
|
|
25653
|
+
return t ? [`${taskKey(t, project) ?? t.id} ${t.title}`] : [];
|
|
25654
|
+
});
|
|
25655
|
+
if (gone.length) throw new HarborError(`--apply removes ${gone.length === 1 ? "this task" : `these ${gone.length} tasks`} from the harbor:\n${gone.map((g) => ` ${g}`).join("\n")}\nTo remove ${gone.length === 1 ? "it" : "them"}, run it again with --confirm. Nothing was changed.`);
|
|
25656
|
+
}
|
|
25624
25657
|
if (opts.apply) {
|
|
25625
25658
|
const s = store.getState();
|
|
25626
25659
|
result.applied = applyDock({
|
package/lib/cli.mjs
CHANGED
|
@@ -85,7 +85,7 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
85
85
|
harbor try PROJECT [--drop KEYS] [--add "TITLE" [--count N]]
|
|
86
86
|
[--due KEY=DATE] [--give KEY=NAME] [--end DATE]
|
|
87
87
|
[--borrow NAME[=DAYS]] [--release NAME]
|
|
88
|
-
[--saved] [--apply|--save|--discard]
|
|
88
|
+
[--saved] [--apply [--confirm]|--save|--discard]
|
|
89
89
|
dry dock: the forecast with plan
|
|
90
90
|
changes tried on a copy; nothing
|
|
91
91
|
changes without --apply
|
|
@@ -566,6 +566,8 @@ const DETAIL = {
|
|
|
566
566
|
" harbor try PROJECT [changes] reads the forecast with the changes",
|
|
567
567
|
" tried on a copy of the plan",
|
|
568
568
|
" harbor try PROJECT [changes] --apply writes: makes the changes for real",
|
|
569
|
+
" (--confirm too when it drops tasks:",
|
|
570
|
+
" they're removed from the harbor)",
|
|
569
571
|
" harbor try PROJECT [changes] --save writes: keeps them on the project to",
|
|
570
572
|
" show someone (Forecast tab, Open dry",
|
|
571
573
|
" dock)",
|
|
@@ -574,7 +576,8 @@ const DETAIL = {
|
|
|
574
576
|
" harbor try PROJECT --discard writes: throws the saved ones away",
|
|
575
577
|
"",
|
|
576
578
|
" Changes, each taking several with commas between:",
|
|
577
|
-
" --drop RL-4,RL-5 take tasks out of the plan (--apply
|
|
579
|
+
" --drop RL-4,RL-5 take tasks out of the plan (--apply --confirm",
|
|
580
|
+
" removes them)",
|
|
578
581
|
' --add "TITLE" --count N new work, N tasks called TITLE',
|
|
579
582
|
" --due RL-4=fri move a due date (none takes it off)",
|
|
580
583
|
" --give RL-4=Sam give a task to someone (none: nobody)",
|
|
@@ -588,6 +591,13 @@ const DETAIL = {
|
|
|
588
591
|
" date, the crew's pace, and the load of everyone the changes touch. A",
|
|
589
592
|
" borrowed person adds an average crew member's pace for each day a week",
|
|
590
593
|
" they give; a released one takes away their share of what was finished.",
|
|
594
|
+
" Notes under it say when the dates may not move: a short or lumpy record",
|
|
595
|
+
" of finished work, or a due date that sets the finish.",
|
|
596
|
+
"",
|
|
597
|
+
" --json gives each plan's endDate, open, likely, veryLikely, chanceByEnd",
|
|
598
|
+
" and tasksPerWeek (noForecast says why when there are no dates), the",
|
|
599
|
+
" shiftDays between them, crewPacePercent (100 is the crew as it is) and",
|
|
600
|
+
" the notes.",
|
|
591
601
|
],
|
|
592
602
|
report: [
|
|
593
603
|
" harbor report PROGRAM reads the program's status report",
|
|
@@ -1169,7 +1179,7 @@ export async function run(argv, io = {}) {
|
|
|
1169
1179
|
|
|
1170
1180
|
if (command === "try") {
|
|
1171
1181
|
const { values, positionals } = parseArgs(rest, {
|
|
1172
|
-
flags: ["apply", "save", "discard", "saved", "json"],
|
|
1182
|
+
flags: ["apply", "confirm", "save", "discard", "saved", "json"],
|
|
1173
1183
|
options: ["harbor", "drop", "add", "count", "due", "give", "end", "borrow", "release"],
|
|
1174
1184
|
});
|
|
1175
1185
|
if (positionals.length !== 1)
|
|
@@ -1180,6 +1190,8 @@ export async function run(argv, io = {}) {
|
|
|
1180
1190
|
);
|
|
1181
1191
|
if (["apply", "save", "discard"].filter((k) => values[k]).length > 1)
|
|
1182
1192
|
throw new HarborError("Use one of --apply, --save or --discard.");
|
|
1193
|
+
if (values.confirm && !values.apply)
|
|
1194
|
+
throw new HarborError("--confirm goes with --apply, to remove the tasks it drops.");
|
|
1183
1195
|
const creds = await readCredentials(env);
|
|
1184
1196
|
if (!hasFullAccess(creds))
|
|
1185
1197
|
throw new HarborError(
|
|
@@ -1198,6 +1210,7 @@ export async function run(argv, io = {}) {
|
|
|
1198
1210
|
project: positionals[0],
|
|
1199
1211
|
flags: values,
|
|
1200
1212
|
apply: Boolean(values.apply),
|
|
1213
|
+
confirm: Boolean(values.confirm),
|
|
1201
1214
|
save: Boolean(values.save),
|
|
1202
1215
|
discard: Boolean(values.discard),
|
|
1203
1216
|
say,
|
|
@@ -1216,14 +1229,19 @@ export async function run(argv, io = {}) {
|
|
|
1216
1229
|
return 0;
|
|
1217
1230
|
}
|
|
1218
1231
|
if (values.json) {
|
|
1219
|
-
const side = (s) =>
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1232
|
+
const side = (s) => {
|
|
1233
|
+
const f = s.tide.kind === "forecast" ? s.tide : null;
|
|
1234
|
+
return {
|
|
1235
|
+
endDate: s.project.endDate,
|
|
1236
|
+
open: s.open,
|
|
1237
|
+
likely: f?.likely ?? null,
|
|
1238
|
+
veryLikely: f?.veryLikely ?? null,
|
|
1239
|
+
chanceByEnd: f?.chanceByEnd ?? null,
|
|
1240
|
+
tasksPerWeek: f?.perWeek ?? null,
|
|
1241
|
+
// Why there are no dates, when there aren't: no pace yet, paused, done.
|
|
1242
|
+
noForecast: f ? null : s.tide.kind,
|
|
1243
|
+
};
|
|
1244
|
+
};
|
|
1227
1245
|
const c = result.comparison;
|
|
1228
1246
|
out(
|
|
1229
1247
|
jsonText({
|
|
@@ -1232,10 +1250,10 @@ export async function run(argv, io = {}) {
|
|
|
1232
1250
|
real: side(c.real),
|
|
1233
1251
|
dryDock: side(c.docked),
|
|
1234
1252
|
shiftDays: c.shift,
|
|
1235
|
-
|
|
1253
|
+
crewPacePercent: Math.round(c.pace * 100),
|
|
1236
1254
|
...(result.applied !== undefined ? { applied: result.applied } : {}),
|
|
1237
1255
|
...(result.saved ? { saved: true } : {}),
|
|
1238
|
-
notes,
|
|
1256
|
+
notes: [...c.notes, ...notes],
|
|
1239
1257
|
}),
|
|
1240
1258
|
);
|
|
1241
1259
|
return 0;
|
|
@@ -1790,7 +1808,9 @@ export async function run(argv, io = {}) {
|
|
|
1790
1808
|
);
|
|
1791
1809
|
}
|
|
1792
1810
|
if (values.confirm || values["over-limit"]) {
|
|
1793
|
-
throw new HarborError(
|
|
1811
|
+
throw new HarborError(
|
|
1812
|
+
"--confirm and --over-limit are for moving a task: harbor submit TASK.",
|
|
1813
|
+
);
|
|
1794
1814
|
}
|
|
1795
1815
|
if (values.mine && values.owner) throw new HarborError("Say --mine or --owner, not both.");
|
|
1796
1816
|
return call(
|