@diffohq/diffo 0.8.0 → 0.9.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 +8 -6
- package/dist/cli.mjs +462 -4
- package/dist/client/assets/{abnfDiagram-O67JEVCF-BUIFF3Lv.js → abnfDiagram-O67JEVCF-Dh4is2Nj.js} +1 -1
- package/dist/client/assets/{arc-x6Ulf5MI.js → arc-CMN0Gb1g.js} +1 -1
- package/dist/client/assets/architecture-7GRP2DOG-DaIFNiR2.js +1 -0
- package/dist/client/assets/{architectureDiagram-NJMV4G6O-D25O1A2B.js → architectureDiagram-NJMV4G6O-DxVGDSE1.js} +1 -1
- package/dist/client/assets/{blockDiagram-BEXU5L5S-Dy7P3IZw.js → blockDiagram-BEXU5L5S-T1F9hSwZ.js} +1 -1
- package/dist/client/assets/{c4Diagram-YGBWAQC7-BnE5HTw4.js → c4Diagram-YGBWAQC7-DC6zwYuX.js} +1 -1
- package/dist/client/assets/channel-Ci5ZF5xD.js +1 -0
- package/dist/client/assets/{chunk-3FUC2YCW-6C72_i5n.js → chunk-3FUC2YCW-D0PAoCRl.js} +1 -1
- package/dist/client/assets/{chunk-5DYCD2WN-B0IgEc5I.js → chunk-5DYCD2WN-NqHzOtt0.js} +1 -1
- package/dist/client/assets/{chunk-742MDFTN-BNLZzbxp.js → chunk-742MDFTN-B9Y5HHB7.js} +1 -1
- package/dist/client/assets/{chunk-7INBJB4K-K1DkKka3.js → chunk-7INBJB4K-CBOWlj91.js} +1 -1
- package/dist/client/assets/{chunk-7M6MHVWA-B-KIWC1T.js → chunk-7M6MHVWA-D2dZ_rQI.js} +1 -1
- package/dist/client/assets/{chunk-7PRAP22T-DR6CzDYx.js → chunk-7PRAP22T-7dilu8x-.js} +1 -1
- package/dist/client/assets/{chunk-GTNCS2PH-BWkvnYRd.js → chunk-GTNCS2PH-at0ENjhB.js} +1 -1
- package/dist/client/assets/{chunk-GWA4HPMP-l8NLPoZc.js → chunk-GWA4HPMP-C06n_37U.js} +1 -1
- package/dist/client/assets/{chunk-MBY4JIJT-k1mwME9y.js → chunk-MBY4JIJT-DuowBunG.js} +1 -1
- package/dist/client/assets/{chunk-NETBCI7D-DV1F8c_R.js → chunk-NETBCI7D-ClNA7QFH.js} +1 -1
- package/dist/client/assets/{chunk-O7XYJQB3-Bb8gIxaB.js → chunk-O7XYJQB3-BjcjInBL.js} +1 -1
- package/dist/client/assets/{chunk-UA2S7LBM-BL7m6fd_.js → chunk-UA2S7LBM-B-PAn1hd.js} +1 -1
- package/dist/client/assets/{chunk-WEXAMYUT-CAnQv1qY.js → chunk-WEXAMYUT-Br84uK4B.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-DooI1miH.js → chunk-XXDRQBXY-BbBZykgX.js} +1 -1
- package/dist/client/assets/{chunk-Z7XXMR3K-DPVaCcdM.js → chunk-Z7XXMR3K-CQ-grwfe.js} +1 -1
- package/dist/client/assets/{chunk-ZIGJFQKS-Bz4IWpFy.js → chunk-ZIGJFQKS-CLwfE-hs.js} +1 -1
- package/dist/client/assets/{classDiagram-v2-NBCMYWYE-Dso7ogb5.js → classDiagram-v2-NBCMYWYE-DD-bcOHO.js} +1 -1
- package/dist/client/assets/{core-Dy-hJrbg.js → core-BADyutHL.js} +1 -1
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Baj8S6bg.js → cose-bilkent-JH36ORCC-fuMg3J_l.js} +1 -1
- package/dist/client/assets/{cynefin-OW5HDTMX-BkA1ccYX.js → cynefin-OW5HDTMX-DwFXJ_x8.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-VND7K2PF-nyK9OZGN.js → cynefinDiagram-VND7K2PF-cGL_k8gj.js} +1 -1
- package/dist/client/assets/{dagre-6A5THRUB-BP_dyQLS.js → dagre-6A5THRUB-BetgCBn9.js} +1 -1
- package/dist/client/assets/{diagram-22UHCM2B-BIt_aIJx.js → diagram-22UHCM2B-C7qT2Ix2.js} +1 -1
- package/dist/client/assets/{diagram-3UASUU5V-UX2eUwyi.js → diagram-3UASUU5V-DGUO9DWV.js} +1 -1
- package/dist/client/assets/{diagram-ATOU4E4O-CEYK5oZO.js → diagram-ATOU4E4O-f520gjx5.js} +1 -1
- package/dist/client/assets/{diagram-CDSNMT55-4uIYrmGp.js → diagram-CDSNMT55-Dhrm4GzB.js} +1 -1
- package/dist/client/assets/{diagram-MLGK6HIB-5BGrc92k.js → diagram-MLGK6HIB-Di0aRiuU.js} +1 -1
- package/dist/client/assets/{diagram-MPIPVDR6-0BG375Nf.js → diagram-MPIPVDR6-BHhVZUxI.js} +1 -1
- package/dist/client/assets/{dist-hfzhbdYS.js → dist-CbdYocyN.js} +1 -1
- package/dist/client/assets/{dist-uIEEXDIm.js → dist-Cezvykkn.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-ZINNZB2B-CnAveRvy.js → ebnfDiagram-ZINNZB2B-CQA0eib_.js} +1 -1
- package/dist/client/assets/{elk-276RUBZZ-CejJ0q5w.js → elk-276RUBZZ-CZKKVSu0.js} +1 -1
- package/dist/client/assets/{engine-oniguruma-BpOI5GVJ.js → engine-oniguruma-B68c_YAs.js} +1 -1
- package/dist/client/assets/{erDiagram-OPXOYQCR-Lg3_iVkX.js → erDiagram-OPXOYQCR-DcNnfq_S.js} +1 -1
- package/dist/client/assets/eventmodeling-NTZA5JFV-C61pKQjq.js +1 -0
- package/dist/client/assets/flowDiagram-KWPJA3E3-BolWtw3C.js +1 -0
- package/dist/client/assets/{ganttDiagram-FUAMR5RP-D5nJpDg8.js → ganttDiagram-FUAMR5RP-Dteh33ci.js} +1 -1
- package/dist/client/assets/{gitGraph-4MIJSDKK-BLNIFBUu.js → gitGraph-4MIJSDKK-DMTE1jTV.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-X574FWY7-YHwgA3VE.js → gitGraphDiagram-X574FWY7-EV_0IPok.js} +1 -1
- package/dist/client/assets/index-B__poZ2b.js +93 -0
- package/dist/client/assets/{index-CE0LasmM.css → index-CL4K6Y07.css} +1 -1
- package/dist/client/assets/{info-A6RAGUB7-Bfa8ErwC.js → info-A6RAGUB7-WkHzxEDS.js} +1 -1
- package/dist/client/assets/{infoDiagram-VRGFBTTK-Dzp_xQpV.js → infoDiagram-VRGFBTTK-BcH5U5G2.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-OU5B5YK6-Hun-xe-t.js → ishikawaDiagram-OU5B5YK6-Z8vZnj1l.js} +1 -1
- package/dist/client/assets/{journeyDiagram-ZHPQQLJL-CT5fXqj_.js → journeyDiagram-ZHPQQLJL-Cn-m-UYg.js} +1 -1
- package/dist/client/assets/{kanban-definition-PNTS6WVX-kFYcpsMx.js → kanban-definition-PNTS6WVX-DLOrUOML.js} +1 -1
- package/dist/client/assets/{line-Cda_OIfO.js → line-DhKwT3E3.js} +1 -1
- package/dist/client/assets/{linear-CDQGk4q-.js → linear-DvwuMCFu.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-CCoRJPAh.js → mermaid-parser.core-CwcOI9nG.js} +3 -3
- package/dist/client/assets/{mermaid.core-SnjyC0ZV.js → mermaid.core-BabKb2iQ.js} +4 -4
- package/dist/client/assets/{mindmap-definition-NLK3R4M7-C3oLUr5b.js → mindmap-definition-NLK3R4M7-D7wpNtz6.js} +1 -1
- package/dist/client/assets/{packet-AYTQ26CC-BT6BgV6a.js → packet-AYTQ26CC-BJablqte.js} +1 -1
- package/dist/client/assets/{pegDiagram-GJSIUBJH-OT5BsfLg.js → pegDiagram-GJSIUBJH-EAlXAkUz.js} +1 -1
- package/dist/client/assets/{pie-WAS4IAKB-Bou89QXR.js → pie-WAS4IAKB-BAAbtIiO.js} +1 -1
- package/dist/client/assets/{pieDiagram-5QR66LMP-AzEtJfJu.js → pieDiagram-5QR66LMP-b1pYP4ES.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-O4NWA36T-BUKCkyd7.js → quadrantDiagram-O4NWA36T-B_LJNNdE.js} +1 -1
- package/dist/client/assets/{radar-RG4KPBEZ-D77V4y6h.js → radar-RG4KPBEZ-BKldNRRC.js} +1 -1
- package/dist/client/assets/{railroad-74A4TZTK-CIYb8bnG.js → railroad-74A4TZTK-LvULpHpQ.js} +1 -1
- package/dist/client/assets/railroad-abnf-HS5TGJTU-CORGw4Le.js +1 -0
- package/dist/client/assets/railroad-ebnf-LZEXJU2U-BXCX4hkl.js +1 -0
- package/dist/client/assets/railroad-peg-WCYAUIDC-C6BQUpjn.js +1 -0
- package/dist/client/assets/{railroadDiagram-XR7U4H2S-DhUQyIW9.js → railroadDiagram-XR7U4H2S-kp6LeaeM.js} +1 -1
- package/dist/client/assets/{requirementDiagram-PLB6GJNP-5HRstoAa.js → requirementDiagram-PLB6GJNP-Br-OgP6b.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-IPEJSGJF-sNbgZbEH.js → sankeyDiagram-IPEJSGJF-DSIbAxcK.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-PO4LG4MO-B_vIrQ3k.js → sequenceDiagram-PO4LG4MO-uVv8u8VI.js} +1 -1
- package/dist/client/assets/{src-BWz11yEV.js → src-Bmfs-TFn.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-GCMORJYK-BnRt6hQF.js → stateDiagram-v2-GCMORJYK-Crql1z4d.js} +1 -1
- package/dist/client/assets/{swimlanes-2SLR337P-CLlJdLDU.js → swimlanes-2SLR337P-CnpU7AM-.js} +1 -1
- package/dist/client/assets/swimlanesDiagram-TC7HE7FX-Crca2YKi.js +8 -0
- package/dist/client/assets/{timeline-definition-EJHVYXUP-sXwAwUmH.js → timeline-definition-EJHVYXUP-CqzGi-Sd.js} +1 -1
- package/dist/client/assets/{treeView-Q6P3EWNA-BmhLEMPr.js → treeView-Q6P3EWNA-N_EzEUvg.js} +1 -1
- package/dist/client/assets/{treemap-WGGIJYW6-D_PGkPFQ.js → treemap-WGGIJYW6-BYeVgP-v.js} +1 -1
- package/dist/client/assets/{usecaseDiagram-POWQR4AR-GARihMJS.js → usecaseDiagram-POWQR4AR-Dyeq2Zcy.js} +1 -1
- package/dist/client/assets/{vennDiagram-UO4OBE2U-DXSYoTtG.js → vennDiagram-UO4OBE2U-CuCjJaOo.js} +1 -1
- package/dist/client/assets/{wardley-WFR3VGLG-Caebv6bl.js → wardley-WFR3VGLG-AV0-dO_Z.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-VNRHLVJA-BnFa4EKC.js → wardleyDiagram-VNRHLVJA-f0dVm2LA.js} +1 -1
- package/dist/client/assets/{xychartDiagram-PMCCYNJV-XS2BbOyB.js → xychartDiagram-PMCCYNJV-BNjSW7P1.js} +1 -1
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/dist/client/assets/architecture-7GRP2DOG-Cx2ezWq8.js +0 -1
- package/dist/client/assets/channel-IK-3bv-L.js +0 -1
- package/dist/client/assets/eventmodeling-NTZA5JFV-Dh34QIVY.js +0 -1
- package/dist/client/assets/flowDiagram-KWPJA3E3-Bcos9jHd.js +0 -1
- package/dist/client/assets/index-DYt1d3uP.js +0 -93
- package/dist/client/assets/railroad-abnf-HS5TGJTU-OVikttJY.js +0 -1
- package/dist/client/assets/railroad-ebnf-LZEXJU2U-DPAI1fGw.js +0 -1
- package/dist/client/assets/railroad-peg-WCYAUIDC-CcsZpvwt.js +0 -1
- package/dist/client/assets/swimlanesDiagram-TC7HE7FX-D6iEiuTl.js +0 -8
package/README.md
CHANGED
|
@@ -60,8 +60,9 @@ That's the whole install. From then on, one command in any session:
|
|
|
60
60
|
|
|
61
61
|
Every time, the agent opens the review and hands you the URL. It all runs on your
|
|
62
62
|
machine, and nothing needs to be committed or pushed first. A pull request needs the
|
|
63
|
-
[GitHub CLI](https://cli.github.com) signed in
|
|
64
|
-
|
|
63
|
+
[GitHub CLI](https://cli.github.com) signed in. Otherwise the only thing that leaves your
|
|
64
|
+
machine is two small anonymous usage events per review, [listed in full](https://diffohq.github.io/diffo/telemetry)
|
|
65
|
+
and off with `diffo telemetry off`.
|
|
65
66
|
|
|
66
67
|
New here? [**Your first review, end to end**](https://diffohq.github.io/diffo/tutorial) takes about five minutes.
|
|
67
68
|
|
|
@@ -135,10 +136,11 @@ read, and the agent is there to answer, explain, and fix.
|
|
|
135
136
|
<img alt="Diffo's architecture: your agent writes the code and opens the review; a local Diffo server watches the changeset and serves it to your browser; your comments and Finish review return to the agent through diffo poll, and its answers and fixes land back in the review live." src="assets/how-it-works-light.svg" width="100%">
|
|
136
137
|
</picture>
|
|
137
138
|
|
|
138
|
-
One process on your machine, bound to loopback: no account, no cloud, no
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
One process on your machine, bound to loopback: no account, no cloud, and no model API.
|
|
140
|
+
The one thing it reports is [anonymous usage data](https://diffohq.github.io/diffo/telemetry),
|
|
141
|
+
two events per review, never code or paths, off with one command. Diffo spawns no agents
|
|
142
|
+
of its own; the one you're already talking to stays attached through the `diffo` CLI, so
|
|
143
|
+
your comments land in the session that holds the context. The viewer itself is a real diff viewer, unified and split, keyboard-first, with
|
|
142
144
|
GitHub's conventions. The CLI, the flags, and everything under the hood are in the docs.
|
|
143
145
|
|
|
144
146
|
## Docs
|
package/dist/cli.mjs
CHANGED
|
@@ -17,9 +17,24 @@ import { stream, streamSSE } from "hono/streaming";
|
|
|
17
17
|
* token only: under Claude Code the transient `zsh -c` carries
|
|
18
18
|
* `~/.claude/shell-snapshots/…` in its arguments, which would match HARNESS. */
|
|
19
19
|
const SHELL = /^-?(?:\/[^ ]*\/)?(?:sh|bash|zsh|fish|dash|ksh|csh|tcsh)(?:\s|$)/;
|
|
20
|
+
/** The coding agents whose processes the ancestor walk recognises. Also the
|
|
21
|
+
* vocabulary of the `agent` field in usage data, so a name never travels unless
|
|
22
|
+
* it is on this list. */
|
|
23
|
+
const HARNESS_NAMES = [
|
|
24
|
+
"claude",
|
|
25
|
+
"cursor",
|
|
26
|
+
"codex",
|
|
27
|
+
"copilot",
|
|
28
|
+
"windsurf",
|
|
29
|
+
"zed",
|
|
30
|
+
"aider",
|
|
31
|
+
"goose",
|
|
32
|
+
"cline",
|
|
33
|
+
"amp"
|
|
34
|
+
];
|
|
20
35
|
/** A harness name at the start of a path segment or token, never inside a word:
|
|
21
36
|
* `/Users/sam/example/` must not read as `amp`. */
|
|
22
|
-
const HARNESS =
|
|
37
|
+
const HARNESS = new RegExp(`(?:^|[/\\s@_.-])(${HARNESS_NAMES.join("|")})`, "i");
|
|
23
38
|
function ownInvocation() {
|
|
24
39
|
return {
|
|
25
40
|
script: process.argv[1] ?? "",
|
|
@@ -34,15 +49,26 @@ function isOwnLauncher(command, own) {
|
|
|
34
49
|
const tail = own.args.length > 0 ? `${base} ${own.args.join(" ")}` : own.script;
|
|
35
50
|
return command === tail || command.endsWith(` ${tail}`) || command.endsWith(`/${tail}`);
|
|
36
51
|
}
|
|
37
|
-
function
|
|
52
|
+
function pickSession(ancestors, own) {
|
|
38
53
|
for (const row of ancestors) {
|
|
39
54
|
const command = row.command.trim();
|
|
40
55
|
if (SHELL.test(command)) continue;
|
|
41
56
|
if (isOwnLauncher(command, own)) continue;
|
|
42
|
-
|
|
57
|
+
const match = HARNESS.exec(command);
|
|
58
|
+
if (match) return {
|
|
59
|
+
pid: row.pid,
|
|
60
|
+
harness: match[1].toLowerCase()
|
|
61
|
+
};
|
|
43
62
|
}
|
|
44
63
|
return null;
|
|
45
64
|
}
|
|
65
|
+
function pickSessionAncestor(ancestors, own = ownInvocation()) {
|
|
66
|
+
return pickSession(ancestors, own)?.pid ?? null;
|
|
67
|
+
}
|
|
68
|
+
/** Which agent the session is, by the same walk that finds it. */
|
|
69
|
+
function pickSessionHarness(ancestors, own = ownInvocation()) {
|
|
70
|
+
return pickSession(ancestors, own)?.harness ?? null;
|
|
71
|
+
}
|
|
46
72
|
function readRow(pid) {
|
|
47
73
|
try {
|
|
48
74
|
const ppid = Number.parseInt(execFileSync("ps", [
|
|
@@ -87,6 +113,16 @@ function detectSessionPid() {
|
|
|
87
113
|
return null;
|
|
88
114
|
}
|
|
89
115
|
}
|
|
116
|
+
/** The agent this CLI runs under, or null from a plain terminal (and on Windows,
|
|
117
|
+
* where the walk is not available). Only ever a name from HARNESS_NAMES. */
|
|
118
|
+
function detectHarness() {
|
|
119
|
+
if (process.platform === "win32") return null;
|
|
120
|
+
try {
|
|
121
|
+
return pickSessionHarness(readAncestors(process.ppid));
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
90
126
|
//#endregion
|
|
91
127
|
//#region src/shared/review.ts
|
|
92
128
|
const THREAD_INTENTS = ["fix", "question"];
|
|
@@ -1019,6 +1055,8 @@ For the reviewer:
|
|
|
1019
1055
|
setup Register diffo with the coding agents on this machine
|
|
1020
1056
|
(Claude Code, Cursor, VS Code, Copilot, and the shared
|
|
1021
1057
|
~/.agents skills dir read by Codex, Gemini, Amp, Goose, …)
|
|
1058
|
+
telemetry Show whether anonymous usage data is on, or turn it
|
|
1059
|
+
off / on (telemetry off · telemetry on)
|
|
1022
1060
|
|
|
1023
1061
|
For the agent (the AI that wrote the change):
|
|
1024
1062
|
poll Wait for the reviewer's feedback (blocking long-poll;
|
|
@@ -1358,7 +1396,33 @@ are left as they are, and opening a review keeps installed skills fresh
|
|
|
1358
1396
|
automatically after upgrades.
|
|
1359
1397
|
|
|
1360
1398
|
Example:
|
|
1361
|
-
diffo setup
|
|
1399
|
+
diffo setup`,
|
|
1400
|
+
telemetry: `diffo telemetry: anonymous usage data — see it, turn it off, turn it on
|
|
1401
|
+
|
|
1402
|
+
Usage: diffo telemetry [status|on|off]
|
|
1403
|
+
|
|
1404
|
+
Diffo reports two small anonymous events per review, "opened" and "finished":
|
|
1405
|
+
its version, your OS and Node major, the kind of review (working tree, branch,
|
|
1406
|
+
or pull request), which agent opened it, counts (threads, comments, whether
|
|
1407
|
+
layers were used, a coarse duration), and whether it runs from a source
|
|
1408
|
+
checkout (dev: true, so the maintainers' own use is filtered out). Never code,
|
|
1409
|
+
paths, repository or branch names, comment text, or anything about the pull
|
|
1410
|
+
request. One random id per machine tells returning machines from new ones;
|
|
1411
|
+
turning reporting off deletes it. Nothing is sent until the review page has
|
|
1412
|
+
shown you the notice once, and nothing from the review that showed it.
|
|
1413
|
+
|
|
1414
|
+
status what is on, why it is off, and where the data goes (the default)
|
|
1415
|
+
off stop reporting on this machine, for good (a machine that has
|
|
1416
|
+
reported sends one last event saying so, then nothing)
|
|
1417
|
+
on start again (a fresh id, not the old history)
|
|
1418
|
+
|
|
1419
|
+
DIFFO_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1 turns it off for one process or
|
|
1420
|
+
for a fleet; CI does the same. DIFFO_TELEMETRY_DEBUG=1 writes every payload to
|
|
1421
|
+
the server log before it is sent. The full list, and where it goes:
|
|
1422
|
+
https://diffohq.github.io/diffo/telemetry
|
|
1423
|
+
|
|
1424
|
+
Example:
|
|
1425
|
+
diffo telemetry off`
|
|
1362
1426
|
};
|
|
1363
1427
|
function helpFor(topic) {
|
|
1364
1428
|
return topic && VERB_HELP[topic] || HELP_TEXT;
|
|
@@ -1370,6 +1434,7 @@ const VERBS = /* @__PURE__ */ new Set([
|
|
|
1370
1434
|
"layers",
|
|
1371
1435
|
"end",
|
|
1372
1436
|
"setup",
|
|
1437
|
+
"telemetry",
|
|
1373
1438
|
"status",
|
|
1374
1439
|
"stop",
|
|
1375
1440
|
"clean",
|
|
@@ -1653,9 +1718,38 @@ function parseCleanVerb(rest) {
|
|
|
1653
1718
|
all: parsed.value.values.all === true
|
|
1654
1719
|
};
|
|
1655
1720
|
}
|
|
1721
|
+
function parseTelemetryVerb(rest) {
|
|
1722
|
+
const parsed = tryParse(() => parseArgs({
|
|
1723
|
+
args: rest,
|
|
1724
|
+
allowPositionals: true,
|
|
1725
|
+
options: { help: {
|
|
1726
|
+
type: "boolean",
|
|
1727
|
+
short: "h"
|
|
1728
|
+
} }
|
|
1729
|
+
}));
|
|
1730
|
+
if (!parsed.ok) return {
|
|
1731
|
+
kind: "error",
|
|
1732
|
+
message: parsed.message
|
|
1733
|
+
};
|
|
1734
|
+
const { values, positionals } = parsed.value;
|
|
1735
|
+
if (values.help) return {
|
|
1736
|
+
kind: "help",
|
|
1737
|
+
topic: "telemetry"
|
|
1738
|
+
};
|
|
1739
|
+
const action = positionals[0] ?? "status";
|
|
1740
|
+
if (positionals.length > 1 || action !== "status" && action !== "on" && action !== "off") return {
|
|
1741
|
+
kind: "error",
|
|
1742
|
+
message: "telemetry takes one of: status, on, off"
|
|
1743
|
+
};
|
|
1744
|
+
return {
|
|
1745
|
+
kind: "telemetry",
|
|
1746
|
+
action
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1656
1749
|
function parseVerb(verb, rest) {
|
|
1657
1750
|
if (verb === "layers") return parseLayersVerb(rest);
|
|
1658
1751
|
if (verb === "clean") return parseCleanVerb(rest);
|
|
1752
|
+
if (verb === "telemetry") return parseTelemetryVerb(rest);
|
|
1659
1753
|
const parsed = tryParse(() => parseArgs({
|
|
1660
1754
|
args: rest,
|
|
1661
1755
|
allowPositionals: true,
|
|
@@ -5993,6 +6087,286 @@ var ChangesetStore = class {
|
|
|
5993
6087
|
}
|
|
5994
6088
|
};
|
|
5995
6089
|
//#endregion
|
|
6090
|
+
//#region src/server/telemetry.ts
|
|
6091
|
+
/**
|
|
6092
|
+
* Anonymous usage data, Homebrew-style: on by default, announced in the review
|
|
6093
|
+
* page before the first event leaves, one command or variable to turn off. Two
|
|
6094
|
+
* events per review, `review_opened` and `review_finished`, and one last
|
|
6095
|
+
* `telemetry_disabled` when a machine that has reported turns it off; `send`
|
|
6096
|
+
* below is the authoritative list of what they carry, and the docs page links here. Never
|
|
6097
|
+
* paths, repo or branch names, code, comment text, or anything IP-derived. The
|
|
6098
|
+
* one identifier is a random UUID per machine, in a file of its own
|
|
6099
|
+
* (`~/.diffo/telemetry-id`) rather than the database: `DIFFO_DB` moves the
|
|
6100
|
+
* database, and one machine must count as one machine wherever it keeps state.
|
|
6101
|
+
*/
|
|
6102
|
+
const TELEMETRY_DOCS_URL = "https://diffohq.github.io/diffo/telemetry";
|
|
6103
|
+
/** PostHog Cloud, EU region. */
|
|
6104
|
+
const TELEMETRY_ENDPOINT = "https://eu.i.posthog.com/capture/";
|
|
6105
|
+
/** Public and write-only by design: adds events, reads nothing. */
|
|
6106
|
+
const TELEMETRY_KEY = "phc_nZqoJQbyccraPZwxsHaaAUPjf5z8k2QqdnWYedyhQzCx";
|
|
6107
|
+
/** A blocked host must never be felt. */
|
|
6108
|
+
const SEND_TIMEOUT_MS = 2e3;
|
|
6109
|
+
function defaultMachineIdPath() {
|
|
6110
|
+
return join(homedir(), ".diffo", "telemetry-id");
|
|
6111
|
+
}
|
|
6112
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6113
|
+
/** One UUID and a newline, owner-readable like the database next to it. */
|
|
6114
|
+
function fileMachineIdStore(path = defaultMachineIdPath()) {
|
|
6115
|
+
const read = () => {
|
|
6116
|
+
try {
|
|
6117
|
+
const id = readFileSync(path, "utf8").trim();
|
|
6118
|
+
return UUID.test(id) ? id : null;
|
|
6119
|
+
} catch {
|
|
6120
|
+
return null;
|
|
6121
|
+
}
|
|
6122
|
+
};
|
|
6123
|
+
const write = (id, flag) => {
|
|
6124
|
+
try {
|
|
6125
|
+
mkdirSync(dirname(path), {
|
|
6126
|
+
recursive: true,
|
|
6127
|
+
mode: 448
|
|
6128
|
+
});
|
|
6129
|
+
writeFileSync(path, `${id}\n`, {
|
|
6130
|
+
flag,
|
|
6131
|
+
mode: 384
|
|
6132
|
+
});
|
|
6133
|
+
return id;
|
|
6134
|
+
} catch (err) {
|
|
6135
|
+
if (flag === "wx" && err.code === "EEXIST") return read() ?? write(id, "w");
|
|
6136
|
+
return null;
|
|
6137
|
+
}
|
|
6138
|
+
};
|
|
6139
|
+
return {
|
|
6140
|
+
read,
|
|
6141
|
+
claim: (id) => write(id, "wx"),
|
|
6142
|
+
clear() {
|
|
6143
|
+
try {
|
|
6144
|
+
rmSync(path, { force: true });
|
|
6145
|
+
} catch {}
|
|
6146
|
+
}
|
|
6147
|
+
};
|
|
6148
|
+
}
|
|
6149
|
+
const KEY = {
|
|
6150
|
+
enabled: "telemetry.enabled",
|
|
6151
|
+
notice: "telemetry.notice",
|
|
6152
|
+
noticeAt: "telemetry.notice_at"
|
|
6153
|
+
};
|
|
6154
|
+
const DEBUG_VAR = "DIFFO_TELEMETRY_DEBUG";
|
|
6155
|
+
/** `0`, `false` and the empty string mean unset, as most tools read them. */
|
|
6156
|
+
function isSet(value) {
|
|
6157
|
+
if (value === void 0) return false;
|
|
6158
|
+
const v = value.trim().toLowerCase();
|
|
6159
|
+
return v !== "" && v !== "0" && v !== "false";
|
|
6160
|
+
}
|
|
6161
|
+
/** The variable that turns usage data off, in precedence order, or null. */
|
|
6162
|
+
function envOptOut(env) {
|
|
6163
|
+
if (isSet(env.DO_NOT_TRACK)) return "DO_NOT_TRACK";
|
|
6164
|
+
if (isSet(env.DIFFO_TELEMETRY_DISABLED)) return "DIFFO_TELEMETRY_DISABLED";
|
|
6165
|
+
if (isSet(env.CI)) return "CI";
|
|
6166
|
+
return null;
|
|
6167
|
+
}
|
|
6168
|
+
function reviewKindOf(spec, pr) {
|
|
6169
|
+
if (pr) return "pr";
|
|
6170
|
+
return spec.kind === "branch" ? "branch" : "tree";
|
|
6171
|
+
}
|
|
6172
|
+
const AGENT_NAMES = /* @__PURE__ */ new Set([...HARNESS_NAMES, "unknown"]);
|
|
6173
|
+
/** Whatever the CLI reported, narrowed to a name this build knows. */
|
|
6174
|
+
function agentNameOf(raw) {
|
|
6175
|
+
return typeof raw === "string" && AGENT_NAMES.has(raw) ? raw : "other";
|
|
6176
|
+
}
|
|
6177
|
+
/** Coarse on purpose: low cardinality, and nobody's afternoon timed. */
|
|
6178
|
+
function durationBucket(ms) {
|
|
6179
|
+
const min = ms / 6e4;
|
|
6180
|
+
if (min < 5) return "<5m";
|
|
6181
|
+
if (min < 15) return "5-15m";
|
|
6182
|
+
if (min < 30) return "15-30m";
|
|
6183
|
+
if (min < 60) return "30-60m";
|
|
6184
|
+
return "1h+";
|
|
6185
|
+
}
|
|
6186
|
+
var Telemetry = class {
|
|
6187
|
+
store;
|
|
6188
|
+
ids;
|
|
6189
|
+
env;
|
|
6190
|
+
fetchImpl;
|
|
6191
|
+
now;
|
|
6192
|
+
log;
|
|
6193
|
+
dev;
|
|
6194
|
+
base;
|
|
6195
|
+
/** Decided at open and kept for the finish, so the review that first shows the
|
|
6196
|
+
* notice reports nothing at all. Undefined until the first open. */
|
|
6197
|
+
armedReview;
|
|
6198
|
+
openedAt;
|
|
6199
|
+
inFlight = [];
|
|
6200
|
+
/** The id this process settled on when the disk refused to keep one. */
|
|
6201
|
+
fallbackId;
|
|
6202
|
+
constructor(deps) {
|
|
6203
|
+
this.store = deps.store;
|
|
6204
|
+
this.ids = deps.ids;
|
|
6205
|
+
this.env = deps.env ?? process.env;
|
|
6206
|
+
this.fetchImpl = deps.fetch ?? globalThis.fetch;
|
|
6207
|
+
this.now = deps.now ?? Date.now;
|
|
6208
|
+
this.log = deps.log ?? (() => {});
|
|
6209
|
+
this.dev = deps.dev ?? false;
|
|
6210
|
+
this.base = {
|
|
6211
|
+
version: deps.version ?? VERSION,
|
|
6212
|
+
platform: deps.platform ?? process.platform,
|
|
6213
|
+
arch: deps.arch ?? process.arch,
|
|
6214
|
+
node: deps.nodeMajor ?? Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10),
|
|
6215
|
+
dev: this.dev
|
|
6216
|
+
};
|
|
6217
|
+
}
|
|
6218
|
+
status() {
|
|
6219
|
+
const shared = {
|
|
6220
|
+
notice: this.notice(),
|
|
6221
|
+
machineId: this.readMachineId(),
|
|
6222
|
+
dev: this.dev,
|
|
6223
|
+
docs: TELEMETRY_DOCS_URL
|
|
6224
|
+
};
|
|
6225
|
+
const variable = envOptOut(this.env);
|
|
6226
|
+
if (variable !== null) return {
|
|
6227
|
+
enabled: false,
|
|
6228
|
+
source: "env",
|
|
6229
|
+
variable,
|
|
6230
|
+
...shared
|
|
6231
|
+
};
|
|
6232
|
+
if (this.store.get(KEY.enabled) === "false") return {
|
|
6233
|
+
enabled: false,
|
|
6234
|
+
source: "setting",
|
|
6235
|
+
...shared
|
|
6236
|
+
};
|
|
6237
|
+
return {
|
|
6238
|
+
enabled: true,
|
|
6239
|
+
source: "default",
|
|
6240
|
+
...shared
|
|
6241
|
+
};
|
|
6242
|
+
}
|
|
6243
|
+
/**
|
|
6244
|
+
* Either way the machine id goes: back on means a new history, not the old one.
|
|
6245
|
+
* Turning off a machine that has reported sends one last `telemetry_disabled`,
|
|
6246
|
+
* so opt-outs can be counted; a machine that never reported, or one an
|
|
6247
|
+
* environment variable already silenced, sends nothing. Returns whether it did.
|
|
6248
|
+
*/
|
|
6249
|
+
setEnabled(on, source) {
|
|
6250
|
+
const farewell = !on && this.status().enabled && this.readMachineId() !== null;
|
|
6251
|
+
if (farewell) this.send("telemetry_disabled", { source });
|
|
6252
|
+
this.store.set(KEY.enabled, on ? "true" : "false");
|
|
6253
|
+
this.ids.clear();
|
|
6254
|
+
this.fallbackId = void 0;
|
|
6255
|
+
if (on) this.store.set(KEY.notice, "acknowledged");
|
|
6256
|
+
return farewell;
|
|
6257
|
+
}
|
|
6258
|
+
/** The review page rendered the notice; nothing is sent before this. */
|
|
6259
|
+
noticeShown() {
|
|
6260
|
+
if (this.notice() !== "pending") return;
|
|
6261
|
+
this.store.set(KEY.notice, "shown");
|
|
6262
|
+
this.store.set(KEY.noticeAt, new Date(this.now()).toISOString());
|
|
6263
|
+
}
|
|
6264
|
+
/** The reviewer dismissed the notice; it stays gone. */
|
|
6265
|
+
acknowledge() {
|
|
6266
|
+
if (this.notice() === "pending") this.noticeShown();
|
|
6267
|
+
this.store.set(KEY.notice, "acknowledged");
|
|
6268
|
+
}
|
|
6269
|
+
/** A `diffo` open landed. Decides whether this review reports, and says so. */
|
|
6270
|
+
reviewOpened(props) {
|
|
6271
|
+
const armed = this.armed();
|
|
6272
|
+
this.armedReview = armed;
|
|
6273
|
+
this.openedAt = this.now();
|
|
6274
|
+
if (armed) this.send("review_opened", {
|
|
6275
|
+
kind: props.kind,
|
|
6276
|
+
agent: props.agent
|
|
6277
|
+
});
|
|
6278
|
+
return armed;
|
|
6279
|
+
}
|
|
6280
|
+
/** Only for a review that was opened reporting, and is still on. */
|
|
6281
|
+
reviewFinished(props) {
|
|
6282
|
+
if (this.armedReview !== true || !this.armed()) return;
|
|
6283
|
+
this.send("review_finished", {
|
|
6284
|
+
kind: props.kind,
|
|
6285
|
+
threads: props.threads,
|
|
6286
|
+
comments: props.comments,
|
|
6287
|
+
layers: props.layers,
|
|
6288
|
+
...props.event ? { event: props.event } : {},
|
|
6289
|
+
...this.openedAt !== void 0 ? { duration: durationBucket(this.now() - this.openedAt) } : {}
|
|
6290
|
+
});
|
|
6291
|
+
}
|
|
6292
|
+
/** Every send so far, settled. Tests, and an orderly shutdown. */
|
|
6293
|
+
async flush() {
|
|
6294
|
+
await Promise.all(this.inFlight.splice(0));
|
|
6295
|
+
}
|
|
6296
|
+
notice() {
|
|
6297
|
+
const raw = this.store.get(KEY.notice);
|
|
6298
|
+
return raw === "shown" || raw === "acknowledged" ? raw : "pending";
|
|
6299
|
+
}
|
|
6300
|
+
/** On, and the notice has been on a screen at least once. */
|
|
6301
|
+
armed() {
|
|
6302
|
+
return this.status().enabled && this.notice() !== "pending";
|
|
6303
|
+
}
|
|
6304
|
+
readMachineId() {
|
|
6305
|
+
return this.ids.read() ?? this.fallbackId ?? null;
|
|
6306
|
+
}
|
|
6307
|
+
/** The id on disk, minted on the first send; a disk that refuses it gets one
|
|
6308
|
+
* id per process rather than one per event. */
|
|
6309
|
+
machineId() {
|
|
6310
|
+
const existing = this.readMachineId();
|
|
6311
|
+
if (existing) return existing;
|
|
6312
|
+
const id = randomUUID();
|
|
6313
|
+
const claimed = this.ids.claim(id);
|
|
6314
|
+
if (claimed === null) this.fallbackId = id;
|
|
6315
|
+
return claimed ?? id;
|
|
6316
|
+
}
|
|
6317
|
+
send(event, properties) {
|
|
6318
|
+
const payload = {
|
|
6319
|
+
api_key: TELEMETRY_KEY,
|
|
6320
|
+
event,
|
|
6321
|
+
distinct_id: this.machineId(),
|
|
6322
|
+
timestamp: new Date(this.now()).toISOString(),
|
|
6323
|
+
properties: {
|
|
6324
|
+
$lib: "diffo",
|
|
6325
|
+
$lib_version: this.base.version,
|
|
6326
|
+
$process_person_profile: false,
|
|
6327
|
+
$geoip_disable: true,
|
|
6328
|
+
...this.base,
|
|
6329
|
+
...properties
|
|
6330
|
+
}
|
|
6331
|
+
};
|
|
6332
|
+
if (isSet(this.env[DEBUG_VAR])) this.log(`telemetry: ${JSON.stringify(payload)}`);
|
|
6333
|
+
let request;
|
|
6334
|
+
try {
|
|
6335
|
+
request = this.fetchImpl(TELEMETRY_ENDPOINT, {
|
|
6336
|
+
method: "POST",
|
|
6337
|
+
headers: { "Content-Type": "application/json" },
|
|
6338
|
+
body: JSON.stringify(payload),
|
|
6339
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS)
|
|
6340
|
+
});
|
|
6341
|
+
} catch {
|
|
6342
|
+
request = Promise.resolve();
|
|
6343
|
+
}
|
|
6344
|
+
const debug = isSet(this.env[DEBUG_VAR]);
|
|
6345
|
+
this.inFlight.push(request.then((res) => {
|
|
6346
|
+
if (debug) this.log(`telemetry: ${event} → ${res?.status ?? "sent"}`);
|
|
6347
|
+
}, (err) => {
|
|
6348
|
+
if (debug) this.log(`telemetry: ${event} failed: ${err?.message ?? String(err)}`);
|
|
6349
|
+
}));
|
|
6350
|
+
}
|
|
6351
|
+
};
|
|
6352
|
+
/** `diffo telemetry [status|on|off]`, for a human's terminal. */
|
|
6353
|
+
function describeTelemetry(status, asked, farewell = false) {
|
|
6354
|
+
const lines = [];
|
|
6355
|
+
if (status.enabled) {
|
|
6356
|
+
lines.push(asked === "on" ? "usage data: on (turned on)" : "usage data: on", " what: two anonymous events per review (opened, finished) — version, OS,", " kind of review, agent, counts. Never code, paths, or comment text", " off: diffo telemetry off, or DIFFO_TELEMETRY_DISABLED=1 (DO_NOT_TRACK=1 counts too)");
|
|
6357
|
+
if (status.notice === "pending") lines.push(" note: nothing has been sent yet; the review page shows a notice first");
|
|
6358
|
+
if (status.dev) lines.push(" dev: running from a source checkout; every event carries dev: true");
|
|
6359
|
+
} else {
|
|
6360
|
+
const why = status.source === "env" ? `${status.variable} is set` : asked === "off" ? "turned off" : "turned off with `diffo telemetry off`";
|
|
6361
|
+
lines.push(`usage data: off (${why})`);
|
|
6362
|
+
if (asked === "off" && farewell) lines.push(" sent: one last event saying this machine turned it off; nothing follows");
|
|
6363
|
+
if (asked === "on" && status.source === "env") lines.push(` note: it stays off while ${status.variable} is set`);
|
|
6364
|
+
if (status.source === "setting") lines.push(" on: diffo telemetry on");
|
|
6365
|
+
}
|
|
6366
|
+
lines.push(` more: ${status.docs}`);
|
|
6367
|
+
return lines.join("\n");
|
|
6368
|
+
}
|
|
6369
|
+
//#endregion
|
|
5996
6370
|
//#region src/server/watcher.ts
|
|
5997
6371
|
const DEBOUNCE_MS = 300;
|
|
5998
6372
|
const MAX_COOLDOWN_MS = 5e3;
|
|
@@ -6185,6 +6559,31 @@ function createApp(ctx, store, review, queue) {
|
|
|
6185
6559
|
ctx.uiSettings.set("theme", theme);
|
|
6186
6560
|
return c.json({ ok: true });
|
|
6187
6561
|
});
|
|
6562
|
+
const reviewKind = () => reviewKindOf(ctx.spec, ctx.pr !== void 0);
|
|
6563
|
+
app.get("/api/telemetry", (c) => {
|
|
6564
|
+
if (!ctx.telemetry) return c.json({ error: "telemetry unavailable" }, 503);
|
|
6565
|
+
return c.json(ctx.telemetry.status());
|
|
6566
|
+
});
|
|
6567
|
+
app.put("/api/telemetry", async (c) => {
|
|
6568
|
+
if (!ctx.telemetry) return c.json({ error: "telemetry unavailable" }, 503);
|
|
6569
|
+
const body = await c.req.json().catch(() => null);
|
|
6570
|
+
if (typeof body?.enabled === "boolean") ctx.telemetry.setEnabled(body.enabled, "ui");
|
|
6571
|
+
else if (body?.notice === "shown") ctx.telemetry.noticeShown();
|
|
6572
|
+
else if (body?.notice === "acknowledged") ctx.telemetry.acknowledge();
|
|
6573
|
+
else return c.json({ error: "need {enabled: boolean} or {notice: \"shown\" | \"acknowledged\"}" }, 400);
|
|
6574
|
+
return c.json(ctx.telemetry.status());
|
|
6575
|
+
});
|
|
6576
|
+
app.post("/api/telemetry/opened", async (c) => {
|
|
6577
|
+
const body = await c.req.json().catch(() => null);
|
|
6578
|
+
const armed = ctx.telemetry?.reviewOpened({
|
|
6579
|
+
kind: reviewKind(),
|
|
6580
|
+
agent: agentNameOf(body?.agent)
|
|
6581
|
+
}) ?? false;
|
|
6582
|
+
return c.json({
|
|
6583
|
+
ok: true,
|
|
6584
|
+
armed
|
|
6585
|
+
});
|
|
6586
|
+
});
|
|
6188
6587
|
const repoInfo = () => store?.get().repo ?? {
|
|
6189
6588
|
path: resolve(ctx.root),
|
|
6190
6589
|
name: "",
|
|
@@ -6543,6 +6942,13 @@ function createApp(ctx, store, review, queue) {
|
|
|
6543
6942
|
queue.enqueueFinish(coverage);
|
|
6544
6943
|
}
|
|
6545
6944
|
review.clearWithheld(batch.map((t) => t.id));
|
|
6945
|
+
ctx.telemetry?.reviewFinished({
|
|
6946
|
+
kind: reviewKind(),
|
|
6947
|
+
threads: batch.length,
|
|
6948
|
+
comments: batch.reduce((n, t) => n + t.messages.filter((m) => m.author === "reviewer").length, 0),
|
|
6949
|
+
layers: (review.get().layers?.items.length ?? 0) > 0,
|
|
6950
|
+
...event !== null ? { event } : {}
|
|
6951
|
+
});
|
|
6546
6952
|
return c.json({
|
|
6547
6953
|
threads: review.get().threads,
|
|
6548
6954
|
prompt,
|
|
@@ -6870,6 +7276,15 @@ function startServer(options) {
|
|
|
6870
7276
|
queue.rescope(store.get().repo.branch);
|
|
6871
7277
|
rehydrateQueue(review, queue);
|
|
6872
7278
|
const log = (message) => console.log(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString("en-GB", { hour12: false })}] ${message}`);
|
|
7279
|
+
const telemetry = options.telemetry ?? new Telemetry({
|
|
7280
|
+
store: {
|
|
7281
|
+
get: (key) => db.getUiSetting(key),
|
|
7282
|
+
set: (key, value) => db.setUiSetting(key, value)
|
|
7283
|
+
},
|
|
7284
|
+
ids: fileMachineIdStore(),
|
|
7285
|
+
log,
|
|
7286
|
+
dev: SRC_STAMP !== null || IS_DEV
|
|
7287
|
+
});
|
|
6873
7288
|
queue.subscribe((presence) => log(`agent ${presence}`));
|
|
6874
7289
|
const unsubscribeBatch = queue.onBatchClosed((closed) => {
|
|
6875
7290
|
const took = Math.round((closed.closedAt - closed.deliveredAt) / 1e3);
|
|
@@ -6931,6 +7346,7 @@ function startServer(options) {
|
|
|
6931
7346
|
...options,
|
|
6932
7347
|
...prContext ? { pr: prContext } : {},
|
|
6933
7348
|
idle,
|
|
7349
|
+
telemetry,
|
|
6934
7350
|
onShutdownRequest: options.onShutdownRequest ?? (() => process.exit(0)),
|
|
6935
7351
|
uiSettings: {
|
|
6936
7352
|
get: (key) => db.getUiSetting(key),
|
|
@@ -6963,6 +7379,7 @@ function startServer(options) {
|
|
|
6963
7379
|
store,
|
|
6964
7380
|
review,
|
|
6965
7381
|
queue,
|
|
7382
|
+
telemetry,
|
|
6966
7383
|
stopWatching: async () => {
|
|
6967
7384
|
idle?.stop();
|
|
6968
7385
|
puller?.stop();
|
|
@@ -7686,6 +8103,21 @@ function printBaseHint(spec, files) {
|
|
|
7686
8103
|
if (!hint) return;
|
|
7687
8104
|
console.log(`this branch has ${hint.commits} ${hint.commits === 1 ? "commit" : "commits"} since ${hint.base}. Review them with \`diffo --base ${hint.base}\``);
|
|
7688
8105
|
}
|
|
8106
|
+
/**
|
|
8107
|
+
* Tells the server a review was opened, and by which agent — the server decides
|
|
8108
|
+
* whether that is reported (see server/telemetry.ts). Best effort: an open never
|
|
8109
|
+
* waits on, or fails over, usage data.
|
|
8110
|
+
*/
|
|
8111
|
+
async function announceOpen(port) {
|
|
8112
|
+
try {
|
|
8113
|
+
await fetch(apiUrl(port, "/api/telemetry/opened"), {
|
|
8114
|
+
method: "POST",
|
|
8115
|
+
headers: { "Content-Type": "application/json" },
|
|
8116
|
+
body: JSON.stringify({ agent: detectHarness() ?? "unknown" }),
|
|
8117
|
+
signal: AbortSignal.timeout(2e3)
|
|
8118
|
+
});
|
|
8119
|
+
} catch {}
|
|
8120
|
+
}
|
|
7689
8121
|
async function fetchChangesetInfo(port) {
|
|
7690
8122
|
try {
|
|
7691
8123
|
return await (await fetch(apiUrl(port, "/api/changeset"), { signal: AbortSignal.timeout(2e3) })).json();
|
|
@@ -7829,6 +8261,26 @@ if (command.kind === "stop") {
|
|
|
7829
8261
|
}
|
|
7830
8262
|
process.exit(0);
|
|
7831
8263
|
}
|
|
8264
|
+
if (command.kind === "telemetry") {
|
|
8265
|
+
const db = new DiffoDb();
|
|
8266
|
+
try {
|
|
8267
|
+
const telemetry = new Telemetry({
|
|
8268
|
+
store: {
|
|
8269
|
+
get: (key) => db.getUiSetting(key),
|
|
8270
|
+
set: (key, value) => db.setUiSetting(key, value)
|
|
8271
|
+
},
|
|
8272
|
+
ids: fileMachineIdStore(),
|
|
8273
|
+
log: (line) => process.stderr.write(`diffo: ${line}\n`),
|
|
8274
|
+
dev: SRC_STAMP !== null || process.env.ENV === "development"
|
|
8275
|
+
});
|
|
8276
|
+
const farewell = command.action === "status" ? false : telemetry.setEnabled(command.action === "on", "cli");
|
|
8277
|
+
console.log(describeTelemetry(telemetry.status(), command.action, farewell));
|
|
8278
|
+
await telemetry.flush();
|
|
8279
|
+
} finally {
|
|
8280
|
+
db.close();
|
|
8281
|
+
}
|
|
8282
|
+
process.exit(0);
|
|
8283
|
+
}
|
|
7832
8284
|
if (command.kind === "poll") {
|
|
7833
8285
|
const port = await requireServer();
|
|
7834
8286
|
process.stderr.write("diffo: waiting for the reviewer. Keep this process attended: a tracked\nbackground task or the foreground, never detached. If it dies, just\nre-run it; feedback is held in the review and survives.\n");
|
|
@@ -8047,6 +8499,7 @@ if (existing !== null && takeOverPort === null) {
|
|
|
8047
8499
|
const url = reviewUrl(existing.port);
|
|
8048
8500
|
console.log(prOpen ? "diffo is already watching this pull request" : "diffo is already watching this repo");
|
|
8049
8501
|
await warnSpecMismatch(existing.port, spec);
|
|
8502
|
+
await announceOpen(existing.port);
|
|
8050
8503
|
console.log(`→ ${url}`);
|
|
8051
8504
|
await printAgentNextStep(existing.port, prOpen?.pr);
|
|
8052
8505
|
process.exit(0);
|
|
@@ -8066,6 +8519,7 @@ if (existing !== null && takeOverPort !== null) {
|
|
|
8066
8519
|
}
|
|
8067
8520
|
if (!command.foreground) {
|
|
8068
8521
|
const daemonPort = await requireServer(command.port, spec.kind === "branch" ? spec.base : void 0, SRC_STAMP, prOpen ? command.target : void 0);
|
|
8522
|
+
await announceOpen(daemonPort);
|
|
8069
8523
|
const url = reviewUrl(daemonPort);
|
|
8070
8524
|
await printChangesetSummary(daemonPort);
|
|
8071
8525
|
console.log(`→ ${url}`);
|
|
@@ -8107,6 +8561,10 @@ try {
|
|
|
8107
8561
|
}
|
|
8108
8562
|
throw err;
|
|
8109
8563
|
}
|
|
8564
|
+
started.telemetry.reviewOpened({
|
|
8565
|
+
kind: reviewKindOf(spec, prOpen !== null),
|
|
8566
|
+
agent: detectHarness() ?? "unknown"
|
|
8567
|
+
});
|
|
8110
8568
|
const { stats, repo } = started.store.get();
|
|
8111
8569
|
const url = reviewUrl(port);
|
|
8112
8570
|
const label = prOpen ? `pull request #${prOpen.pr.number} "${prOpen.pr.title}"` : specLabel(spec);
|
package/dist/client/assets/{abnfDiagram-O67JEVCF-BUIFF3Lv.js → abnfDiagram-O67JEVCF-Dh4is2Nj.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-CwcOI9nG.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-Bmfs-TFn.js";import"./chunk-O7XYJQB3-BjcjInBL.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-at0ENjhB.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-BabKb2iQ.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-
|
|
1
|
+
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-Cezvykkn.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-FOHPRMQF-DzxwRta7.js";import{M as e}from"./mermaid-parser.core-CwcOI9nG.js";export{e as createArchitectureServices};
|