@harborppm/cli 0.51.0 → 0.53.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 +5 -0
- package/dist/app.mjs +871 -59
- package/lib/cli.mjs +44 -2
- package/lib/format.mjs +29 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
soundingText,
|
|
38
38
|
soundingsText,
|
|
39
39
|
voyageText,
|
|
40
|
+
whyText,
|
|
40
41
|
handoverText,
|
|
41
42
|
fogText,
|
|
42
43
|
agentWorkText,
|
|
@@ -89,6 +90,8 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
89
90
|
dry dock: the forecast with plan
|
|
90
91
|
changes tried on a copy; nothing
|
|
91
92
|
changes without --apply
|
|
93
|
+
harbor why PROJECT|TASK|MILESTONE what's holding it up: the line of
|
|
94
|
+
task waits that sets its date
|
|
92
95
|
harbor hill [--program P] where each project is on its hill
|
|
93
96
|
harbor hill PROJECT --at 0-100|top|done move it: up to 50 is figuring it
|
|
94
97
|
out, past 50 is getting it done
|
|
@@ -158,6 +161,10 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
158
161
|
the review: programs worst first,
|
|
159
162
|
each with its next move (reads
|
|
160
163
|
only; harbor submit moves a task)
|
|
164
|
+
harbor bridge [--harbor H] run the review as a meeting: one
|
|
165
|
+
project a screen, what changed
|
|
166
|
+
since the last one, decisions,
|
|
167
|
+
actions and risks logged live
|
|
161
168
|
harbor next-move PROGRAM "TEXT" write a program's next move
|
|
162
169
|
harbor health --program P|--project P|--portfolio P STATE [--note WHY]
|
|
163
170
|
set its health by hand, or auto
|
|
@@ -721,6 +728,20 @@ const DETAIL = {
|
|
|
721
728
|
" never marks its own work Done; a person does, on the website or in the",
|
|
722
729
|
" terminal app.",
|
|
723
730
|
],
|
|
731
|
+
why: [
|
|
732
|
+
" harbor why PROJECT the line of task waits that sets a",
|
|
733
|
+
" project's date, against its end",
|
|
734
|
+
" harbor why TASK the waits behind one task (a key",
|
|
735
|
+
" like RR-42), against its due date",
|
|
736
|
+
" harbor why MILESTONE the project a milestone waits on",
|
|
737
|
+
" that finishes last, and its line",
|
|
738
|
+
"",
|
|
739
|
+
" Tasks have due dates, not lengths, so a task can finish on its due date",
|
|
740
|
+
" at the soonest, never before what it waits on, and never in the past.",
|
|
741
|
+
" Each link's slack is the days it can slip before it moves the next one",
|
|
742
|
+
" (or the end); a negative one already does. PROJECT is a name, a key",
|
|
743
|
+
" like RR, or an id.",
|
|
744
|
+
],
|
|
724
745
|
voyage: [
|
|
725
746
|
" harbor voyage PROJECT reads a Done project's voyage log",
|
|
726
747
|
' harbor voyage PROJECT "YOUR LINE" writes: adds or changes your line in it',
|
|
@@ -750,6 +771,20 @@ const DETAIL = {
|
|
|
750
771
|
" enough of it to pick one out; harbor risks lists them. Changes several",
|
|
751
772
|
" at once, as one change.",
|
|
752
773
|
],
|
|
774
|
+
bridge: [
|
|
775
|
+
" harbor bridge opens the full-screen app on the",
|
|
776
|
+
" review meeting (b on the Programs",
|
|
777
|
+
" screen, or : Run the review meeting)",
|
|
778
|
+
"",
|
|
779
|
+
" One open project a screen, with only what changed since its program was",
|
|
780
|
+
" last reviewed (a week back the first time): health moves, work finished,",
|
|
781
|
+
" newly late or added, risks raised, decisions made. ←→ move between",
|
|
782
|
+
" projects; d logs a decision, a an action (who takes it, due in a week),",
|
|
783
|
+
" r a risk, m the program's next move; e ends it. The summary lists what",
|
|
784
|
+
" was logged; p posts it to each program's chat channel where one is set",
|
|
785
|
+
" (Ship to shore, set on the website), y copies it. The next meeting",
|
|
786
|
+
" counts from when this one ended.",
|
|
787
|
+
],
|
|
753
788
|
review: [
|
|
754
789
|
" harbor review reads the review: every program,",
|
|
755
790
|
" worst health first, then soonest end,",
|
|
@@ -973,7 +1008,7 @@ export async function run(argv, io = {}) {
|
|
|
973
1008
|
}
|
|
974
1009
|
|
|
975
1010
|
// On its own in a terminal: the full-screen app. Piped or scripted: the help.
|
|
976
|
-
if (!command || command === "app") {
|
|
1011
|
+
if (!command || command === "app" || command === "bridge") {
|
|
977
1012
|
const terminal = io.terminal ?? Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
978
1013
|
if (!command && !terminal) {
|
|
979
1014
|
out(HELP);
|
|
@@ -982,7 +1017,7 @@ export async function run(argv, io = {}) {
|
|
|
982
1017
|
if (!terminal) throw new HarborError("The full-screen app needs a terminal.");
|
|
983
1018
|
const { values } = parseArgs(rest, { flags: [], options: ["harbor", "server"] });
|
|
984
1019
|
// In a folder with a local plan (harbor init --local), the app opens on it.
|
|
985
|
-
if (!values.harbor && !values.server) {
|
|
1020
|
+
if (!values.harbor && !values.server && command !== "bridge") {
|
|
986
1021
|
const bundled = await loadApp();
|
|
987
1022
|
const dir = await bundled.localPlanAt(io.cwd ?? process.cwd());
|
|
988
1023
|
if (dir) {
|
|
@@ -1039,6 +1074,7 @@ export async function run(argv, io = {}) {
|
|
|
1039
1074
|
},
|
|
1040
1075
|
theme: (await readSettings(env)).theme,
|
|
1041
1076
|
browse: io.browse ?? openBrowser,
|
|
1077
|
+
...(command === "bridge" ? { startIn: "bridge" } : {}),
|
|
1042
1078
|
});
|
|
1043
1079
|
return 0;
|
|
1044
1080
|
}
|
|
@@ -2173,6 +2209,12 @@ export async function run(argv, io = {}) {
|
|
|
2173
2209
|
soundingsText(r),
|
|
2174
2210
|
);
|
|
2175
2211
|
}
|
|
2212
|
+
case "why": {
|
|
2213
|
+
const { positionals, harbor } = parse(spec());
|
|
2214
|
+
const of = positionals.join(" ").trim();
|
|
2215
|
+
if (!of) throw new HarborError("Say what: a project, a task key like RR-42, or a milestone.");
|
|
2216
|
+
return call("long_pole", { harbor, of }, whyText);
|
|
2217
|
+
}
|
|
2176
2218
|
case "voyage": {
|
|
2177
2219
|
const { positionals, harbor } = parse(spec());
|
|
2178
2220
|
const [project, ...words] = positionals;
|
package/lib/format.mjs
CHANGED
|
@@ -487,6 +487,35 @@ export function voyageText(r) {
|
|
|
487
487
|
);
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
+
/** What's holding something up, as long_pole answers it: the headline, then the line of waits, first blocker at the top. */
|
|
491
|
+
export function whyText(r) {
|
|
492
|
+
const what = r.of.kind === "task" ? r.of.name : `${r.of.name}${r.of.kind === "milestone" ? " (milestone)" : ""}`;
|
|
493
|
+
if (r.none) return `What's holding up ${what}: ${r.none.replace(/^\w/, (c) => c.toLowerCase())}`;
|
|
494
|
+
const head = [`What's holding up ${what}`];
|
|
495
|
+
if (r.headline) head.push(r.headline);
|
|
496
|
+
const chain = r.chain ?? [];
|
|
497
|
+
const width = Math.min(12, Math.max(0, ...chain.map((l) => (l.key ?? "").length)));
|
|
498
|
+
const lines = chain.flatMap((l, i) => {
|
|
499
|
+
const due = l.due ? (l.late ? `was due ${l.due}` : `due ${l.due}`) : "no due date";
|
|
500
|
+
const finish = l.canFinish !== l.due ? ` · can finish ${l.canFinish}` : "";
|
|
501
|
+
const row = [
|
|
502
|
+
` ${(l.key ?? "").padEnd(width)}${width ? " " : ""}${l.title}`,
|
|
503
|
+
` ${" ".repeat(width)}${width ? " " : ""}${l.assignee ?? "nobody yet"} · ${due}${finish} · ${l.slackText}`,
|
|
504
|
+
];
|
|
505
|
+
return i < chain.length - 1 ? [...row, ` ${" ".repeat(width ? Math.floor(width / 2) : 0)}│`] : row;
|
|
506
|
+
});
|
|
507
|
+
const via = r.via ? [`It waits longest on ${r.via.project}; inside it:`] : [];
|
|
508
|
+
const others = (r.others ?? []).length
|
|
509
|
+
? [
|
|
510
|
+
"",
|
|
511
|
+
`Also waits on ${r.others
|
|
512
|
+
.map((o) => `${o.project} (finishes ${o.finish}, ${o.slack >= 0 ? `${o.slack} day${o.slack === 1 ? "" : "s"} spare` : `${-o.slack} day${o.slack === -1 ? "" : "s"} over`})`)
|
|
513
|
+
.join(", ")}.`,
|
|
514
|
+
]
|
|
515
|
+
: [];
|
|
516
|
+
return [...head, "", ...via, ...lines, ...others].join("\n");
|
|
517
|
+
}
|
|
518
|
+
|
|
490
519
|
/** A position on a 21-wide track, the top of the hill marked in the middle. */
|
|
491
520
|
function hillTrack(at) {
|
|
492
521
|
const cells = Array.from({ length: 21 }, (_, i) => (i === 10 ? "┼" : "─"));
|