@harborppm/cli 0.42.0 → 0.43.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 +27 -21
- package/lib/cli.mjs +5 -4
- package/lib/format.mjs +1 -1
- package/package.json +1 -1
package/dist/app.mjs
CHANGED
|
@@ -3009,10 +3009,18 @@ function tideForecast(project, tasks, now) {
|
|
|
3009
3009
|
remaining
|
|
3010
3010
|
};
|
|
3011
3011
|
const today = todayISO(now);
|
|
3012
|
-
const runs = simulate(remaining, weekly, hash(`${project.id}|${remaining}|${today}`));
|
|
3013
3012
|
const base = noon(today);
|
|
3013
|
+
const paced = simulate(remaining, weekly, hash(`${project.id}|${remaining}|${today}`));
|
|
3014
|
+
const at = (share, list = runs) => list[Math.min(list.length - 1, Math.ceil(share * list.length) - 1)];
|
|
3015
|
+
let lastDue = null;
|
|
3016
|
+
for (const t of tasks) {
|
|
3017
|
+
if (t.status === "done" || !t.dueDate) continue;
|
|
3018
|
+
if (!lastDue || t.dueDate > lastDue.dueDate) lastDue = t;
|
|
3019
|
+
}
|
|
3020
|
+
const floor = lastDue ? differenceInCalendarDays(noon(lastDue.dueDate), base) : 0;
|
|
3021
|
+
const runs = floor > 0 ? paced.map((d) => Math.max(d, floor)) : paced;
|
|
3022
|
+
const heldBy = lastDue && floor > at(.5, paced) ? lastDue : null;
|
|
3014
3023
|
const dayOf = (days) => Number.isFinite(days) ? todayISO(addDays(base, days)) : null;
|
|
3015
|
-
const at = (share) => runs[Math.min(runs.length - 1, Math.ceil(share * runs.length) - 1)];
|
|
3016
3024
|
const toEnd = differenceInCalendarDays(noon(project.endDate), base);
|
|
3017
3025
|
const byEnd = runs.filter((d) => d <= toEnd).length / runs.length;
|
|
3018
3026
|
const last = Math.max(Number.isFinite(at(.99)) ? at(.99) : 728, toEnd + 14, 7);
|
|
@@ -3037,7 +3045,8 @@ function tideForecast(project, tasks, now) {
|
|
|
3037
3045
|
almostCertain: dayOf(at(.95)),
|
|
3038
3046
|
chanceByEnd: Math.round(byEnd * 100) / 100,
|
|
3039
3047
|
curve,
|
|
3040
|
-
rough: finished < ROUGH_BELOW
|
|
3048
|
+
rough: finished < ROUGH_BELOW,
|
|
3049
|
+
heldBy
|
|
3041
3050
|
};
|
|
3042
3051
|
}
|
|
3043
3052
|
/** "15%", with "under 5%" and "over 95%" at the ends, where a thousand runs can't be that sure. */
|
|
@@ -3047,18 +3056,11 @@ function chanceText(chance) {
|
|
|
3047
3056
|
return `${Math.round(chance * 20) * 5}%`;
|
|
3048
3057
|
}
|
|
3049
3058
|
/**
|
|
3050
|
-
* The open task due
|
|
3051
|
-
*
|
|
3052
|
-
* longer than the pace says.
|
|
3059
|
+
* The open task whose due date sets the finish, when the crew's pace alone
|
|
3060
|
+
* would be done sooner; null otherwise.
|
|
3053
3061
|
*/
|
|
3054
|
-
function dueAfterFinish(tide
|
|
3055
|
-
|
|
3056
|
-
let last = null;
|
|
3057
|
-
for (const t of tasks) {
|
|
3058
|
-
if (t.status === "done" || !t.dueDate) continue;
|
|
3059
|
-
if (!last || t.dueDate > last.dueDate) last = t;
|
|
3060
|
-
}
|
|
3061
|
-
return last && last.dueDate.slice(0, 10) > tide.likely ? last : null;
|
|
3062
|
+
function dueAfterFinish(tide) {
|
|
3063
|
+
return tide.kind === "forecast" ? tide.heldBy : null;
|
|
3062
3064
|
}
|
|
3063
3065
|
|
|
3064
3066
|
//#endregion
|
|
@@ -3970,8 +3972,8 @@ function filterTasks(tasks, f) {
|
|
|
3970
3972
|
}
|
|
3971
3973
|
/**
|
|
3972
3974
|
* When the project will likely finish at its crew's recent pace, in one
|
|
3973
|
-
* line under its name, whether that misses the end date, and
|
|
3974
|
-
* due
|
|
3975
|
+
* line under its name, whether that misses the end date, and the open task
|
|
3976
|
+
* whose due date holds it later than the pace would. Empty when
|
|
3975
3977
|
* there's no forecast (all done, paused, or nothing finished lately).
|
|
3976
3978
|
*/
|
|
3977
3979
|
function tideLine(data, project, today) {
|
|
@@ -3988,8 +3990,8 @@ function tideLine(data, project, today) {
|
|
|
3988
3990
|
};
|
|
3989
3991
|
const very = tide.veryLikely ? `, very likely by ${shortDay(tide.veryLikely, today)}` : "";
|
|
3990
3992
|
const end = project.endDate >= today ? ` · ${chanceText(tide.chanceByEnd)} by its end, ${shortDay(project.endDate, today)}` : "";
|
|
3991
|
-
const later = dueAfterFinish(tide
|
|
3992
|
-
const due = later ? ` · ${taskKey(later, project) ?? `“${later.title}”`} is due
|
|
3993
|
+
const later = dueAfterFinish(tide);
|
|
3994
|
+
const due = later ? ` · ${taskKey(later, project) ?? `“${later.title}”`} is due ${shortDay(later.dueDate.slice(0, 10), today)}` : "";
|
|
3993
3995
|
return {
|
|
3994
3996
|
text: `Probably done ${shortDay(tide.likely, today)}${very}${end}${due}${tide.rough ? " · rough" : ""}`,
|
|
3995
3997
|
late: tide.chanceByEnd < .5
|
|
@@ -15657,9 +15659,12 @@ async function initLinked(opts) {
|
|
|
15657
15659
|
consumed: /* @__PURE__ */ new Set(),
|
|
15658
15660
|
skipped: /* @__PURE__ */ new Set()
|
|
15659
15661
|
}, report, true);
|
|
15662
|
+
const mine = data.tasks.filter((t) => t.projectId === project.id);
|
|
15663
|
+
const open = mine.filter((t) => t.status !== "done").length;
|
|
15660
15664
|
return {
|
|
15661
15665
|
config,
|
|
15662
|
-
tasks:
|
|
15666
|
+
tasks: mine.length,
|
|
15667
|
+
open,
|
|
15663
15668
|
moved,
|
|
15664
15669
|
report
|
|
15665
15670
|
};
|
|
@@ -15956,12 +15961,13 @@ async function initHere(opts) {
|
|
|
15956
15961
|
dryRun: opts.dryRun
|
|
15957
15962
|
});
|
|
15958
15963
|
const { project, harbor } = made.config;
|
|
15964
|
+
const counted = made.open === made.tasks ? plural(made.tasks, "task") : `${made.open} open and ${made.tasks - made.open} done ${made.tasks === 1 ? "task" : "tasks"}`;
|
|
15959
15965
|
if (opts.dryRun) {
|
|
15960
|
-
opts.say(made.moved ? `Would move this folder's ${plural(made.moved, "task")} into ${project.name} (${project.key}) in ${harbor.name}.` : `Would make ${shownDir(opts.cwd, dir)} for ${project.name} (${project.key}) in ${harbor.name}, with ${
|
|
15966
|
+
opts.say(made.moved ? `Would move this folder's ${plural(made.moved, "task")} into ${project.name} (${project.key}) in ${harbor.name}.` : `Would make ${shownDir(opts.cwd, dir)} for ${project.name} (${project.key}) in ${harbor.name}, with a file for each of its ${counted}.`);
|
|
15961
15967
|
opts.say("Dry run: nothing was written or sent. Run it again without --dry-run to do it.");
|
|
15962
15968
|
return made.config;
|
|
15963
15969
|
}
|
|
15964
|
-
opts.say(made.moved ? `Moved this folder's ${plural(made.moved, "task")} into ${project.name} in ${harbor.name}.` : `Made ${shownDir(opts.cwd, dir)} for ${project.name} (${project.key}) in ${harbor.name}, with ${
|
|
15970
|
+
opts.say(made.moved ? `Moved this folder's ${plural(made.moved, "task")} into ${project.name} in ${harbor.name}.` : `Made ${shownDir(opts.cwd, dir)} for ${project.name} (${project.key}) in ${harbor.name}, with a file for each of its ${counted}. Commit it like any code.`);
|
|
15965
15971
|
opts.say("Edit a file, or add one, then run harbor sync. Changes on the website come back the same way.");
|
|
15966
15972
|
if (opts.hook) opts.say(await addHook(home));
|
|
15967
15973
|
return made.config;
|
package/lib/cli.mjs
CHANGED
|
@@ -506,12 +506,13 @@ const DETAIL = {
|
|
|
506
506
|
" harbor forecast PROJECT reads one project's forecast and what",
|
|
507
507
|
" it rests on",
|
|
508
508
|
"",
|
|
509
|
-
" Worked out from the crew's pace
|
|
510
|
-
"
|
|
509
|
+
" Worked out from the crew's pace: how many tasks they finished each week",
|
|
510
|
+
" over the last six (or the project's life, if shorter), replayed a",
|
|
511
511
|
" thousand times until its open tasks run out. Probably is half of those",
|
|
512
512
|
" runs, very likely 85%, almost certainly 95%. No estimates or points.",
|
|
513
|
-
"
|
|
514
|
-
"
|
|
513
|
+
" No run finishes before the last open task is due, so a task due later",
|
|
514
|
+
" than the pace would finish sets the dates, and the forecast names it. It",
|
|
515
|
+
" warns when under two weeks of pace is all there is.",
|
|
515
516
|
],
|
|
516
517
|
report: [
|
|
517
518
|
" harbor report PROGRAM reads the program's status report",
|
package/lib/format.mjs
CHANGED
|
@@ -346,7 +346,7 @@ export function forecastOne(p) {
|
|
|
346
346
|
if (f.lastDue) {
|
|
347
347
|
const which = f.lastDue.key ? `${f.lastDue.key} “${f.lastDue.title}”` : `“${f.lastDue.title}”`;
|
|
348
348
|
lines.push(
|
|
349
|
-
`${which} is due ${f.lastDue.due},
|
|
349
|
+
`${which} is due ${f.lastDue.due}, later than the crew's pace alone would finish, so the dates wait for it.`,
|
|
350
350
|
);
|
|
351
351
|
}
|
|
352
352
|
return lines.join("\n");
|