prreviewbuddy 0.27.2 → 0.30.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/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as removeWorktree, B as workspaceUrl, Bt as resolveTarget, C as recordTelemetryUploadConsent, Dt as saveWorkspace, Et as reviewedRepositories, G as DEFAULT_WORKSPACE_PORT, H as BUILD_VERSION, Ht as git, I as ensureServer, J as parseWorkspacePort, Jt as CONFIG_PATH, K as MAX_WORKSPACE_PORT, Kt as readAgentPreference, M as relativeTime, N as readIndexToken, O as MANAGED_ROOT, Ot as summarise, P as bootstrapUrl, Q as forgeResolver, Qt as agentById, R as reviewsUrl, S as record, U as PACKAGE_NAME, Ut as resolveAgent, Vt as displayRef, Wt as resolveModel, X as statedWorkspacePort, Xt as AGENT_IDS, Yt as STORE_ROOT, Z as writePortPreference, Zt as DEFAULT_AGENT_ID, _t as lineageKeyFor, a as wasBlocked, at as isTerminal, b as startJob, bt as matchingWorkspaceIds, c as updateReview, ct as PHASES, dt as purposeOf, en as detectAgents, f as UpdateAlreadyRunningError, ft as clearClaim, g as recordKissRun, ht as groupByLineage, i as liveJobsFor, j as modelHelpLines, k as readMarker, m as runningJobs, mt as followedRefName, n as deleteReview$1, nt as allJobs, ot as loadJob, p as liveUpdateFor, pt as isClaimed, q as MIN_WORKSPACE_PORT, qt as writeAgentPreference, tt as checkFreshness, ut as progressSteps, v as reanalyseReview, w as telemetryUploadConsent, wt as recentWorkspaces, xt as positionInLineage, y as runJob, yt as loadWorkspace, z as stopServer } from "./delete_review-B3xciJ03.js";
2
+ import { $t as writeAgentPreference, A as removeWorktree, B as workspaceUrl, C as recordTelemetryUploadConsent, Dt as saveWorkspace, Et as reviewedRepositories, G as DEFAULT_WORKSPACE_PORT, H as BUILD_VERSION, I as ensureServer, J as parseWorkspacePort, Jt as git, K as MAX_WORKSPACE_PORT, Kt as resolveTarget, M as relativeTime, N as readIndexToken, O as MANAGED_ROOT, Ot as summarise, P as bootstrapUrl, Q as forgeResolver, Qt as readAgentPreference, R as reviewsUrl, S as record$1, St as positionInLineage, U as PACKAGE_NAME, X as statedWorkspacePort, Xt as resolveModel, Yt as resolveAgent, Z as writePortPreference, a as wasBlocked, at as isTerminal, b as startJob, bt as loadWorkspace, c as updateReview, ct as PHASES, dt as purposeOf, en as CONFIG_PATH, f as UpdateAlreadyRunningError, ft as clearClaim, g as recordKissRun, ht as groupByLineage, i as liveJobsFor, in as agentById, j as modelHelpLines, k as readMarker, m as runningJobs, mt as followedRefName, n as deleteReview$1, nn as AGENT_IDS, nt as allJobs, on as detectAgents, ot as loadJob, p as liveUpdateFor, pt as isClaimed, q as MIN_WORKSPACE_PORT, qt as displayRef, rn as DEFAULT_AGENT_ID, tn as STORE_ROOT, tt as checkFreshness, ut as progressSteps, v as reanalyseReview, vt as lineageKeyFor, w as telemetryUploadConsent, wt as recentWorkspaces, xt as matchingWorkspaceIds, y as runJob, z as stopServer } from "./delete_review-B_2J2Wn9.js";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
4
  import { spawn } from "node:child_process";
5
5
  import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -987,9 +987,9 @@ your own checkout is never read or written while it works.
987
987
  prreviewbuddy config get agent the coding agent used when you do not name one
988
988
  prreviewbuddy config set agent <agent> remember ${AGENT_NAMES}
989
989
  prreviewbuddy config get telemetry-upload
990
- whether anonymous usage counts are sent
990
+ whether usage telemetry is sent. On by default
991
991
  prreviewbuddy config set telemetry-upload on|off
992
- send them, or stop
992
+ send it, or stop
993
993
  prreviewbuddy config get port the port the workspace runs on
994
994
  prreviewbuddy config set port <port> move it, from the next start onwards
995
995
  prreviewbuddy config path where these settings are kept, to edit by hand
@@ -1119,48 +1119,46 @@ function unknownSetting(key, known) {
1119
1119
  return `There is no setting called \`${key}\`. This can read and set: ${known.join(", ")}.`;
1120
1120
  }
1121
1121
  /**
1122
- * The first-run question about transmission, in three lines.
1122
+ * The one-time disclosure, in four lines.
1123
1123
  *
1124
- * Three because a first run is not the place for a privacy policy, and the policy is one line
1125
- * away. What it names is what a person would otherwise have to assume: the list of things that are
1126
- * never sent is longer than the list of things that are, and printing the longer one is the honest
1127
- * way round.
1124
+ * Four because a first run is not the place for a privacy policy, and the policy is one line away.
1125
+ * It names both lists, and the one that is never sent is the longer of the two: printing the longer
1126
+ * one is the honest way round when the thing being disclosed is on without anybody asking.
1127
+ *
1128
+ * "Non-identifying" rather than "anonymous", which this copy said until 0.30.0. The payload carries
1129
+ * a random installation UUID that persists, and a stable identifier makes data pseudonymous however
1130
+ * little else is attached to it. "Anonymous" is a word with a technical meaning strong enough to be
1131
+ * challenged on, and the claim below does not need it to be true.
1128
1132
  */
1129
- function telemetryUploadQuestion() {
1133
+ function telemetryNotice() {
1130
1134
  return [
1131
1135
  "",
1132
- "Help improve PR Review Buddy by sending anonymous usage counts?",
1133
- "Never your code, diffs, file paths, repository names, prompts or review content.",
1134
- "https://prreviewbuddy.com/privacy lists every field, exactly.",
1136
+ "Non-identifying usage telemetry is on by default: review start and finish, version,",
1137
+ "platform, agent, success and duration. Never your code, diffs, repository names or",
1138
+ "review content.",
1139
+ "Turn it off: `prreviewbuddy config set telemetry-upload off`",
1140
+ "Details: https://prreviewbuddy.com/privacy",
1135
1141
  ""
1136
1142
  ].join("\n");
1137
1143
  }
1138
- function telemetryUploadUnclear() {
1139
- return "Please answer y or n.\n";
1140
- }
1141
- function telemetryUploadGranted() {
1142
- return "Thank you. Turn it off whenever you like with `prreviewbuddy config set telemetry-upload off`.";
1143
- }
1144
- function telemetryUploadDeclined() {
1145
- return "Nothing will be sent. Turn it on with `prreviewbuddy config set telemetry-upload on`.";
1146
- }
1147
1144
  /**
1148
- * What `config get telemetry-upload` says, including for the machine that has not been asked.
1145
+ * What `config get telemetry-upload` says, including for the machine that never chose.
1149
1146
  *
1150
- * Three answers rather than two, because "off" and "never asked" are different facts and only one
1151
- * of them is a decision somebody made.
1147
+ * Three answers rather than two, and the third survives the move to a default because it still
1148
+ * names a different fact: "on because that is the default" and "on because you turned it on" are
1149
+ * the same behaviour arrived at two ways, and only one of them is a decision somebody made. The
1150
+ * same distinction `noAgentPreference` keeps next door.
1152
1151
  */
1153
1152
  function telemetryUploadIs(consent) {
1154
1153
  if (consent === null) return [
1155
- "Anonymous usage counts are not being sent, and nobody has been asked yet.",
1154
+ "Non-identifying usage telemetry is on by default, and you have not changed it.",
1156
1155
  "",
1157
- "The question is asked the first time you run PR Review Buddy in a terminal.",
1158
- "Answer it now with `prreviewbuddy config set telemetry-upload on` or `off`."
1156
+ "Turn it off with `prreviewbuddy config set telemetry-upload off`."
1159
1157
  ].join("\n");
1160
- return consent ? "Anonymous usage counts are being sent. Turn them off with `prreviewbuddy config set telemetry-upload off`." : "Anonymous usage counts are not being sent.";
1158
+ return consent ? "Non-identifying usage telemetry is being sent, because you turned it on. Turn it off with `prreviewbuddy config set telemetry-upload off`." : "Usage telemetry is not being sent, because you turned it off.";
1161
1159
  }
1162
1160
  function telemetryUploadSet(on) {
1163
- return on ? "Anonymous usage counts will be sent. Never your code, diffs, file paths, repository names, prompts or review content." : "Nothing will be sent. The usage log on this machine is unchanged; `PRB_TELEMETRY=off` turns that off too.";
1161
+ return on ? "Non-identifying usage telemetry will be sent. Never your code, diffs, file paths, repository names, prompts or review content." : "Nothing will be sent. The usage log on this machine is unchanged; `PRB_TELEMETRY=off` turns that off too.";
1164
1162
  }
1165
1163
  /**
1166
1164
  * The workspace port as it stands, and how it is changed.
@@ -2384,7 +2382,7 @@ async function operate(command, workspace) {
2384
2382
  }
2385
2383
  workspace.lastUpdate = outcome;
2386
2384
  saveWorkspace(workspace);
2387
- record({
2385
+ record$1({
2388
2386
  event: "review_updated",
2389
2387
  reviewId: workspace.id,
2390
2388
  ok: outcome.ok,
@@ -3214,7 +3212,11 @@ async function uninstall(command, deps = REAL) {
3214
3212
  //#endregion
3215
3213
  //#region src/first_run.ts
3216
3214
  /**
3217
- * The two lines somebody needs the first time they run this, and never again.
3215
+ * The things somebody is told once on a machine, and never again.
3216
+ *
3217
+ * Two of them now: the two lines of hello, and the disclosure that usage telemetry is on. They are
3218
+ * the same kind of fact and are kept the same way, so they share the marker helper at the bottom
3219
+ * rather than each growing their own copy of it.
3218
3220
  *
3219
3221
  * This began as a `postinstall` script, which is the obvious place for it and the wrong one: npm 11
3220
3222
  * does not run install scripts unless they are allow-listed, so the banner would have reached
@@ -3231,84 +3233,78 @@ async function uninstall(command, deps = REAL) {
3231
3233
  * as the telemetry notice next to it. A home directory that cannot be written to costs somebody a
3232
3234
  * repeated banner and never a review, which is why nothing here throws.
3233
3235
  */
3234
- var MARKER = join(STORE_ROOT, "onboarding-shown");
3236
+ var GREETING = "onboarding-shown";
3237
+ var TELEMETRY_NOTICE = "telemetry-notice-shown";
3238
+ /** Whether this machine has been greeted. */
3239
+ function greeted() {
3240
+ return shown(GREETING);
3241
+ }
3242
+ function recordGreeting() {
3243
+ record(GREETING);
3244
+ }
3235
3245
  /**
3236
- * Whether this machine has been greeted.
3246
+ * Whether this machine has been told that usage telemetry is on.
3237
3247
  *
3238
- * Unreadable reads as greeted. Getting this wrong in one direction repeats a banner, and in the
3239
- * other it prints one over somebody's output every single time, so the failure goes quiet.
3248
+ * A second marker rather than a reuse of the greeting: every machine installed before 0.30.0 was
3249
+ * greeted long ago and has still never been told about the default, so reading one from the other
3250
+ * would silence the disclosure for exactly the population it exists for.
3240
3251
  */
3241
- function greeted() {
3252
+ function telemetryNoticed() {
3253
+ return shown(TELEMETRY_NOTICE);
3254
+ }
3255
+ function recordTelemetryNotice() {
3256
+ record(TELEMETRY_NOTICE);
3257
+ }
3258
+ /**
3259
+ * Unreadable reads as already shown.
3260
+ *
3261
+ * Failing closed on repetition: the cost of this being wrong is a disclosure or a hello that never
3262
+ * arrives on a machine whose home cannot be read, and the cost of the other direction is one of
3263
+ * them printed over somebody's output every single time they run a command.
3264
+ */
3265
+ function shown(name) {
3242
3266
  try {
3243
- return existsSync(MARKER);
3267
+ return existsSync(join(STORE_ROOT, name));
3244
3268
  } catch {
3245
3269
  return true;
3246
3270
  }
3247
3271
  }
3248
- function recordGreeting() {
3272
+ function record(name) {
3249
3273
  try {
3250
3274
  mkdirSync(STORE_ROOT, { recursive: true });
3251
- writeFileSync(MARKER, `${(/* @__PURE__ */ new Date()).toISOString()}\n`, { mode: 384 });
3275
+ writeFileSync(join(STORE_ROOT, name), `${(/* @__PURE__ */ new Date()).toISOString()}\n`, { mode: 384 });
3252
3276
  } catch {}
3253
3277
  }
3254
3278
  //#endregion
3255
- //#region src/telemetry_consent.ts
3256
- /**
3257
- * The one question this product asks about itself, asked once per machine.
3258
- *
3259
- * `packages/review-harness/src/telemetry/` has recorded usage events to a local file since it
3260
- * existed, disclosed and on by default. Sending a subset of them to a server we run is a different
3261
- * act, and `apps/website/src/pages/Privacy.tsx` promised in as many words that if it ever happened
3262
- * it would be opt in and off by default. This is how that promise is kept.
3263
- *
3264
- * Hand-rolled on `node:readline` beside `select.ts` and `confirm.ts`, for the reason they both
3265
- * give: this binary has no runtime dependencies and one question is not worth acquiring one. The io
3266
- * is injected for the same reason theirs is, so what the answer means is testable without a shell.
3267
- *
3268
- * **A bare Return re-asks rather than declining.** `confirm.ts` next door defaults to no on Return,
3269
- * which is right there because every one of its callers is about to delete something. Here it would
3270
- * be wrong in a subtler way: "no default" was the decision, and a capitalised `N` in the brackets
3271
- * turns it into opt-out-by-keystroke, which is a different thing wearing the same words. Somebody
3272
- * who has not read the question has not answered it.
3273
- *
3274
- * **No terminal means no question and no answer.** A durable job, a CI run and a piped invocation
3275
- * must not block on a question nobody can see, and must not be recorded as having declined one:
3276
- * the setting stays unset, nothing is sent, and the same machine is asked properly the first time
3277
- * a person runs this interactively.
3278
- */
3279
- async function askTelemetryUpload(options) {
3280
- const stored = telemetryUploadConsent();
3281
- if (stored !== null) return { kind: stored ? "granted" : "declined" };
3282
- if (!options.isTTY) return { kind: "unanswered" };
3283
- const reader = createInterface({ input: options.input });
3284
- const lines = reader[Symbol.asyncIterator]();
3285
- try {
3286
- options.write(`${telemetryUploadQuestion()}\n`);
3287
- for (let attempt = 0; attempt < 3; attempt += 1) {
3288
- options.write("[y/n] ");
3289
- const next = await lines.next();
3290
- if (next.done) return { kind: "unanswered" };
3291
- const answer = next.value.trim().toLowerCase();
3292
- if (answer === "y" || answer === "yes") return settle(options, true);
3293
- if (answer === "n" || answer === "no") return settle(options, false);
3294
- options.write(telemetryUploadUnclear());
3295
- }
3296
- return { kind: "unanswered" };
3297
- } finally {
3298
- reader.close();
3299
- }
3300
- }
3279
+ //#region src/telemetry_notice.ts
3301
3280
  /**
3302
- * Store the answer, and say what was stored.
3281
+ * What this product says about itself, said once per machine.
3282
+ *
3283
+ * Until 0.30.0 this file's predecessor asked a question: nothing was transmitted until somebody
3284
+ * typed y, and `apps/website/src/pages/Privacy.tsx` promised in as many words that it would work
3285
+ * that way. The sample that produced was not a sample of users, it was a sample of people who
3286
+ * answer questions, and one or two installations reporting against hundreds of weekly downloads
3287
+ * answers none of the four questions the telemetry exists for. So the default changed, the promise
3288
+ * was rewritten rather than quietly edited, and the question became this.
3289
+ *
3290
+ * **A disclosure, not a question wearing different words.** No prompt, no keystroke, no default in
3291
+ * brackets. Asking anything at all, however phrased, goes back to measuring who answers.
3292
+ *
3293
+ * **Shown only where the new default is doing the work.** A machine that answered yes on an earlier
3294
+ * version learns nothing from being told; a machine that answered no would be told something false.
3295
+ * Both are left alone, which is why the claim everywhere else is "every installation whose
3296
+ * behaviour changes is told once" rather than "every machine is told".
3303
3297
  *
3304
- * Said out loud in both directions, because a setting written to a file the person did not open is
3305
- * a change they need to know how to undo, and because "yes" and "nothing happened" look identical
3306
- * from a terminal otherwise.
3298
+ * **No terminal check.** The old question could not be asked without one. A disclosure has nobody
3299
+ * to wait for, and a headless install that transmits is exactly the install that must not be the
3300
+ * one nobody ever told. It costs a CI log three lines on a persisted home, once.
3307
3301
  */
3308
- function settle(options, answer) {
3309
- recordTelemetryUploadConsent(answer);
3310
- options.write(`${answer ? telemetryUploadGranted() : telemetryUploadDeclined()}\n`);
3311
- return { kind: answer ? "granted" : "declined" };
3302
+ function noticeTelemetry(write) {
3303
+ if (process.env.PRB_TELEMETRY === "off") return;
3304
+ if (telemetryUploadConsent() !== null) return;
3305
+ if (telemetryNoticed()) return;
3306
+ write(telemetryNotice());
3307
+ recordTelemetryNotice();
3312
3308
  }
3313
3309
  //#endregion
3314
3310
  //#region src/main.ts
@@ -3344,32 +3340,30 @@ function greet(command) {
3344
3340
  recordGreeting();
3345
3341
  }
3346
3342
  /**
3347
- * The one question, asked after the hello and before the work.
3343
+ * The one-time telemetry disclosure, printed after the hello and before the work.
3348
3344
  *
3349
- * The greeting's commands minus `config`, which is where the answer is changed: somebody typing
3350
- * `config set telemetry-upload off` is answering the question, and prompting them for it first
3351
- * would be this binary interrupting the answer in order to ask.
3345
+ * The greeting's commands minus `config`, which is where the setting is changed: somebody typing
3346
+ * `config set telemetry-upload off` should be able to turn it off without being told first that it
3347
+ * is on. That was true when this was a question and it is still true now it is a notice.
3352
3348
  *
3353
- * On stderr with its input from stdin, like every other question here, so a piped stdout still
3354
- * carries only the result and a piped stdin is a run with nobody to ask.
3349
+ * On stderr, like every other line here that is not a result, so `prreviewbuddy review | pbcopy`
3350
+ * still copies a URL. Unlike the question it replaced it takes no input and checks for no terminal:
3351
+ * there is nobody to wait for, and a headless install that transmits is the one that most needs
3352
+ * telling.
3355
3353
  */
3356
- var ASKED = [
3354
+ var NOTIFIED = [
3357
3355
  "review",
3358
3356
  "open",
3359
3357
  "agents"
3360
3358
  ];
3361
- async function ask(command) {
3362
- if (!ASKED.includes(command.name)) return;
3363
- await askTelemetryUpload({
3364
- isTTY: Boolean(process.stdin.isTTY && process.stderr.isTTY),
3365
- input: process.stdin,
3366
- write: (chunk) => void process.stderr.write(chunk)
3367
- });
3359
+ function notify(command) {
3360
+ if (!NOTIFIED.includes(command.name)) return;
3361
+ noticeTelemetry((chunk) => void process.stderr.write(chunk));
3368
3362
  }
3369
3363
  async function main(argv) {
3370
3364
  const command = parse(argv);
3371
3365
  greet(command);
3372
- await ask(command);
3366
+ notify(command);
3373
3367
  switch (command.name) {
3374
3368
  case "help":
3375
3369
  out(HELP);