diffsplain 0.5.0 → 0.6.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 +17 -12
- package/dist/assets/{angular-html-CoQle5-T.js → angular-html-vBvPnd6a.js} +1 -1
- package/dist/assets/{angular-ts-BB_A1Lpy.js → angular-ts-CDzF1Z6a.js} +1 -1
- package/dist/assets/{apl-B7_4_FNq.js → apl-Bm9bxvP7.js} +1 -1
- package/dist/assets/{astro-CXVo4VE4.js → astro-RNrNWCYO.js} +1 -1
- package/dist/assets/{blade-BVX41dq7.js → blade-On-NBYUv.js} +1 -1
- package/dist/assets/{c-Dhyi8Nd2.js → c-hRmhFgD0.js} +1 -1
- package/dist/assets/{cobol-CU5oLeZj.js → cobol-CIqc3k2t.js} +1 -1
- package/dist/assets/{coffee-D_FU6gC_.js → coffee-Cd5EyvvS.js} +1 -1
- package/dist/assets/{cpp-CxoYLSi3.js → cpp-zXFAAp_S.js} +1 -1
- package/dist/assets/{crystal-BdgqBN3z.js → crystal-DRYY4cSw.js} +1 -1
- package/dist/assets/{css-wLk1vVwJ.js → css-C4op_MsD.js} +1 -1
- package/dist/assets/{edge-DMylWoGl.js → edge-d3rJkErf.js} +1 -1
- package/dist/assets/{elixir-Djmgto_Z.js → elixir-DPqlJhYM.js} +1 -1
- package/dist/assets/{elm-D7B-eBLA.js → elm-DgU37I8a.js} +1 -1
- package/dist/assets/{erb-g1sso1GW.js → erb-CZVB_ntC.js} +1 -1
- package/dist/assets/{git-rebase-DEAhxutZ.js → git-rebase-hUkltHZG.js} +1 -1
- package/dist/assets/{glimmer-js-CHvGFGMp.js → glimmer-js-5XOT9GQ8.js} +1 -1
- package/dist/assets/{glimmer-ts-DvhL705p.js → glimmer-ts-DT7D0Kfq.js} +1 -1
- package/dist/assets/{glsl-C8mS_t8I.js → glsl-Br4_Eh5O.js} +1 -1
- package/dist/assets/{graphql-DW2pnvqi.js → graphql--BmZ_AYP.js} +1 -1
- package/dist/assets/{hack-DYc1d3he.js → hack-CMdqGDoA.js} +1 -1
- package/dist/assets/{haml-_5OmzH31.js → haml-DLEOHGmg.js} +1 -1
- package/dist/assets/{handlebars-rN-CaXUf.js → handlebars-BfUftoV0.js} +1 -1
- package/dist/assets/{html-derivative-BovOubry.js → html-derivative-D5cZE6C4.js} +1 -1
- package/dist/assets/{html-DQnaJUjl.js → html-uOgx2iYC.js} +1 -1
- package/dist/assets/{http-BKTgwOYc.js → http-cW9c66J_.js} +1 -1
- package/dist/assets/{hurl-ChS3_IgD.js → hurl-BztURO2n.js} +1 -1
- package/dist/assets/{index-BLtRBc2n.css → index-C2p_1iAk.css} +1 -1
- package/dist/assets/{index-CEglkSs-.js → index-DZFM4-3L.js} +11 -11
- package/dist/assets/{java-Dgnyk--z.js → java-Cy4mnKaj.js} +1 -1
- package/dist/assets/{javascript-BfnrwkRN.js → javascript-_Z02t_9Q.js} +1 -1
- package/dist/assets/{jinja-CD2AIvOi.js → jinja-BppBm5gt.js} +1 -1
- package/dist/assets/{jison-DmKNw3Pl.js → jison-Dzns436M.js} +1 -1
- package/dist/assets/{json-CKB5a8aj.js → json-BYkfLOry.js} +1 -1
- package/dist/assets/{jsx-jJXrDRBM.js → jsx-DtAm8NUb.js} +1 -1
- package/dist/assets/{julia-G8vVBW-t.js → julia-CLIMRuh0.js} +1 -1
- package/dist/assets/{just-DMXLrLZL.js → just-DMg47an6.js} +1 -1
- package/dist/assets/{latex-DS3xuxld.js → latex-D05ztCOx.js} +1 -1
- package/dist/assets/{liquid-COQnD_C4.js → liquid-DpImQR4t.js} +1 -1
- package/dist/assets/{lua-BwMKwosd.js → lua--ZHUYQUU.js} +1 -1
- package/dist/assets/{marko-cQCK_WIk.js → marko-Dh3Xi08i.js} +1 -1
- package/dist/assets/{mdc-DQUjY13N.js → mdc-Br6Pt78u.js} +1 -1
- package/dist/assets/{nginx-C4Kv3DJi.js → nginx-CwSU6U6d.js} +1 -1
- package/dist/assets/{nim-ChzLzuYD.js → nim-CBbTwyeq.js} +1 -1
- package/dist/assets/{perl-z5xUiWAY.js → perl-D3jQu3LN.js} +1 -1
- package/dist/assets/{php-BHqm-Xeh.js → php-BMeXBzlU.js} +1 -1
- package/dist/assets/{pug-C9C_Llft.js → pug-BGdY1g5E.js} +1 -1
- package/dist/assets/{qml-dtpuwXJZ.js → qml-DsT5biza.js} +1 -1
- package/dist/assets/{r-BicQMs_c.js → r-CbS9PfN5.js} +1 -1
- package/dist/assets/{razor-D5h00YTZ.js → razor-Bwm8roHi.js} +1 -1
- package/dist/assets/{regexp-CTa8S86v.js → regexp-Bc_9DyAu.js} +1 -1
- package/dist/assets/{rst-Bg-b-7HK.js → rst-Cj1Aii91.js} +1 -1
- package/dist/assets/{ruby-BiWYm056.js → ruby-BPMtFZnc.js} +1 -1
- package/dist/assets/{sas-bJTdfgUz.js → sas-CoOau1t2.js} +1 -1
- package/dist/assets/{scss-0qkNyHr1.js → scss-CvfaI808.js} +1 -1
- package/dist/assets/{shellscript-3sDkDE9z.js → shellscript-B0C-TdLE.js} +1 -1
- package/dist/assets/{shellsession-D4EBhEmz.js → shellsession-UhmLiqcA.js} +1 -1
- package/dist/assets/{soy-DG9hUwLL.js → soy-D419u0RL.js} +1 -1
- package/dist/assets/{sql-B0UoKHkx.js → sql-Do9k0rRn.js} +1 -1
- package/dist/assets/{stata-CsQbuXQp.js → stata-x47J6BC4.js} +1 -1
- package/dist/assets/{surrealql-D63VLxos.js → surrealql-BERQ6pHu.js} +1 -1
- package/dist/assets/{svelte-BApdnAma.js → svelte-dRWkyqKW.js} +1 -1
- package/dist/assets/{templ-B64pN6Lz.js → templ-CTq-KoWN.js} +1 -1
- package/dist/assets/{tex-DR2UVIPF.js → tex-CdRGjyYI.js} +1 -1
- package/dist/assets/{ts-tags-IwhomvQv.js → ts-tags-CgaMqyVb.js} +1 -1
- package/dist/assets/{tsx-DROdxqzS.js → tsx-cpBQKcPG.js} +1 -1
- package/dist/assets/{twig-BtbUqpR4.js → twig-vbs_PM4-.js} +1 -1
- package/dist/assets/{typescript-CHpSjYZL.js → typescript-Cc--nwIo.js} +1 -1
- package/dist/assets/{vue-DCvEu_ug.js → vue-CgHwdBmB.js} +1 -1
- package/dist/assets/{vue-html-BgL4Rf-L.js → vue-html-BHfoMw47.js} +1 -1
- package/dist/assets/{vue-vine-CRNdhPTn.js → vue-vine-_lVRp_NK.js} +1 -1
- package/dist/assets/{xml-CkfPxZuz.js → xml-G4NVmTde.js} +1 -1
- package/dist/assets/{xsl-CteaoFSV.js → xsl-s0so2M1i.js} +1 -1
- package/dist/assets/{yaml-CUr3TDRN.js → yaml-CpsmW05t.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +49 -23
- package/scripts/access-token.mjs +21 -0
- package/scripts/build-diff-data.mjs +260 -39
- package/scripts/cache.mjs +301 -0
- package/scripts/cli-args.mjs +206 -57
- package/scripts/coding-agents.mjs +279 -162
- package/scripts/dev.mjs +218 -0
- package/scripts/doctor.mjs +265 -46
- package/scripts/generate-summaries.mjs +760 -260
- package/scripts/mock-agent.mjs +165 -0
- package/scripts/present.mjs +394 -85
- package/scripts/presenter-runtime.mjs +148 -0
- package/scripts/serve-built.mjs +343 -71
- package/scripts/summary-path.mjs +24 -7
- package/scripts/support-record.mjs +242 -0
package/scripts/summary-path.mjs
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
|
|
5
|
+
export function defaultCacheRoot({
|
|
6
|
+
env = process.env,
|
|
7
|
+
platform = process.platform,
|
|
8
|
+
homeDirectory = homedir(),
|
|
9
|
+
} = {}) {
|
|
10
|
+
if (env.XDG_CACHE_HOME) {
|
|
11
|
+
return resolve(env.XDG_CACHE_HOME, 'diffsplain');
|
|
12
|
+
}
|
|
13
|
+
const platformRoot =
|
|
14
|
+
{
|
|
15
|
+
darwin: resolve(homeDirectory, 'Library/Caches'),
|
|
16
|
+
win32: resolve(
|
|
17
|
+
env.LOCALAPPDATA || resolve(homeDirectory, 'AppData/Local'),
|
|
18
|
+
),
|
|
19
|
+
}[platform] || resolve(homeDirectory, '.cache');
|
|
20
|
+
return resolve(platformRoot, 'diffsplain');
|
|
21
|
+
}
|
|
22
|
+
|
|
4
23
|
export function summaryPath({
|
|
5
|
-
projectRoot,
|
|
6
24
|
callerDirectory,
|
|
7
25
|
repo,
|
|
26
|
+
cacheRoot = defaultCacheRoot(),
|
|
8
27
|
explicit,
|
|
9
28
|
pr,
|
|
10
29
|
branch,
|
|
@@ -14,10 +33,6 @@ export function summaryPath({
|
|
|
14
33
|
remote = 'origin',
|
|
15
34
|
}) {
|
|
16
35
|
if (explicit) return resolve(callerDirectory, explicit);
|
|
17
|
-
if (!pr && !branch && !checkout && !(base && head)) {
|
|
18
|
-
return resolve(repo, '.diffsplain/summaries.json');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
36
|
const pullRequest =
|
|
22
37
|
pr?.match(/\/pull\/(\d+)(?:\/|$)/)?.[1] || pr || undefined;
|
|
23
38
|
const target = pr
|
|
@@ -26,10 +41,12 @@ export function summaryPath({
|
|
|
26
41
|
? { kind: 'branch', branch, base: base || 'default', remote }
|
|
27
42
|
: checkout
|
|
28
43
|
? { kind: 'checkout', base: base || 'default', remote }
|
|
29
|
-
:
|
|
44
|
+
: base && head
|
|
45
|
+
? { kind: 'range', base, head }
|
|
46
|
+
: { kind: 'worktree' };
|
|
30
47
|
const key = createHash('sha256')
|
|
31
48
|
.update(JSON.stringify({ repo, target }))
|
|
32
49
|
.digest('hex')
|
|
33
50
|
.slice(0, 24);
|
|
34
|
-
return resolve(
|
|
51
|
+
return resolve(cacheRoot, 'summaries', `${target.kind}-${key}.json`);
|
|
35
52
|
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { performance } from 'node:perf_hooks';
|
|
5
|
+
import { writePrivateFile } from './cache.mjs';
|
|
6
|
+
|
|
7
|
+
const packageJson = JSON.parse(
|
|
8
|
+
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
|
|
9
|
+
);
|
|
10
|
+
const stageNames = ['cache', 'snapshot', 'agent', 'publish', 'serve'];
|
|
11
|
+
const byteNames = ['snapshot', 'agentInput', 'agentOutput'];
|
|
12
|
+
const providers = new Set([
|
|
13
|
+
'codex',
|
|
14
|
+
'claude',
|
|
15
|
+
'copilot',
|
|
16
|
+
'cursor',
|
|
17
|
+
'opencode',
|
|
18
|
+
]);
|
|
19
|
+
const versionPattern =
|
|
20
|
+
/^\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?$/;
|
|
21
|
+
const providerLabels = {
|
|
22
|
+
codex: /\bcodex(?:-cli)?\b/i,
|
|
23
|
+
claude: /\bclaude\b/i,
|
|
24
|
+
copilot: /\b(?:github\s+)?copilot\b/i,
|
|
25
|
+
cursor: /\bcursor(?:-agent)?\b/i,
|
|
26
|
+
opencode: /\bopencode\b/i,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function boundedNumber(value) {
|
|
30
|
+
if (!Number.isFinite(value) || value < 0) return 0;
|
|
31
|
+
return Math.min(Math.round(value), Number.MAX_SAFE_INTEGER);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function safeVersion(value) {
|
|
35
|
+
const version = String(value || '').replace(/^v/, '');
|
|
36
|
+
if (version.length > 64) return null;
|
|
37
|
+
return versionPattern.test(version) ? version : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function versionEnvironment(env = process.env) {
|
|
41
|
+
const names = [
|
|
42
|
+
'COMSPEC',
|
|
43
|
+
'LANG',
|
|
44
|
+
'LC_ALL',
|
|
45
|
+
'PATH',
|
|
46
|
+
'PATHEXT',
|
|
47
|
+
'SystemRoot',
|
|
48
|
+
'SYSTEMROOT',
|
|
49
|
+
];
|
|
50
|
+
return Object.fromEntries(
|
|
51
|
+
names
|
|
52
|
+
.filter((name) => typeof env[name] === 'string')
|
|
53
|
+
.map((name) => [name, env[name]]),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function usableVersionResult(result) {
|
|
58
|
+
return result.status === 0 && !result.error;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function versionOutput(command, run, env) {
|
|
62
|
+
try {
|
|
63
|
+
const result = run(command, ['--version'], {
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
env: versionEnvironment(env),
|
|
66
|
+
maxBuffer: 64 * 1024,
|
|
67
|
+
timeout: 2_000,
|
|
68
|
+
});
|
|
69
|
+
if (!usableVersionResult(result)) return '';
|
|
70
|
+
return String(result.stdout || '');
|
|
71
|
+
} catch {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function versionFromOutput(provider, output) {
|
|
77
|
+
const line = output
|
|
78
|
+
.split(/\r?\n/)
|
|
79
|
+
.find((value) => providerLabels[provider]?.test(value));
|
|
80
|
+
return line?.match(
|
|
81
|
+
/(?:^|\s)v?(\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?)(?:\s|$)/,
|
|
82
|
+
)?.[1];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function safeCommandVersion(
|
|
86
|
+
provider,
|
|
87
|
+
command,
|
|
88
|
+
{
|
|
89
|
+
run = spawnSync,
|
|
90
|
+
env = process.env,
|
|
91
|
+
} = {},
|
|
92
|
+
) {
|
|
93
|
+
return safeVersion(
|
|
94
|
+
versionFromOutput(provider, versionOutput(command, run, env)),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function cleanProvider(provider) {
|
|
99
|
+
return providers.has(provider) ? provider : 'unknown';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function clippedIntervals(intervals, startedTick, endedTick) {
|
|
103
|
+
return intervals
|
|
104
|
+
.map(([start, end]) => [
|
|
105
|
+
Math.max(startedTick, Math.min(endedTick, start)),
|
|
106
|
+
Math.max(startedTick, Math.min(endedTick, end)),
|
|
107
|
+
])
|
|
108
|
+
.filter(([start, end]) => end >= start)
|
|
109
|
+
.sort(([left], [right]) => left - right);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function mergeIntervals(intervals) {
|
|
113
|
+
return intervals.reduce((merged, interval) => {
|
|
114
|
+
const previous = merged.at(-1);
|
|
115
|
+
if (previous && interval[0] <= previous[1]) {
|
|
116
|
+
previous[1] = Math.max(previous[1], interval[1]);
|
|
117
|
+
} else {
|
|
118
|
+
merged.push([...interval]);
|
|
119
|
+
}
|
|
120
|
+
return merged;
|
|
121
|
+
}, []);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function unionDuration(intervals, startedTick, endedTick) {
|
|
125
|
+
const merged = mergeIntervals(
|
|
126
|
+
clippedIntervals(intervals, startedTick, endedTick),
|
|
127
|
+
);
|
|
128
|
+
return boundedNumber(
|
|
129
|
+
merged.reduce((total, [start, end]) => total + end - start, 0),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function stageRecord(stages, startedTick, endedTick) {
|
|
134
|
+
return Object.fromEntries(
|
|
135
|
+
stageNames
|
|
136
|
+
.filter((name) => stages[name].calls > 0)
|
|
137
|
+
.map((name) => [
|
|
138
|
+
name,
|
|
139
|
+
{
|
|
140
|
+
durationMs: unionDuration(
|
|
141
|
+
stages[name].intervals,
|
|
142
|
+
startedTick,
|
|
143
|
+
endedTick,
|
|
144
|
+
),
|
|
145
|
+
calls: stages[name].calls,
|
|
146
|
+
state: stages[name].state,
|
|
147
|
+
},
|
|
148
|
+
]),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function createSupportRecorder({
|
|
153
|
+
provider,
|
|
154
|
+
providerVersion,
|
|
155
|
+
now = () => new Date(),
|
|
156
|
+
clock = () => performance.now(),
|
|
157
|
+
runId = randomUUID(),
|
|
158
|
+
} = {}) {
|
|
159
|
+
const startedAt = now().toISOString();
|
|
160
|
+
const startedTick = clock();
|
|
161
|
+
const stages = Object.fromEntries(
|
|
162
|
+
stageNames.map((name) => [
|
|
163
|
+
name,
|
|
164
|
+
{ intervals: [], calls: 0, state: 'ok' },
|
|
165
|
+
]),
|
|
166
|
+
);
|
|
167
|
+
const bytes = Object.fromEntries(byteNames.map((name) => [name, 0]));
|
|
168
|
+
let failedStage;
|
|
169
|
+
let selectedProvider = provider;
|
|
170
|
+
let selectedProviderVersion = providerVersion;
|
|
171
|
+
|
|
172
|
+
function addStageInterval(name, start, end, state) {
|
|
173
|
+
if (!stageNames.includes(name)) return;
|
|
174
|
+
stages[name].intervals.push([start, end]);
|
|
175
|
+
stages[name].calls = boundedNumber(stages[name].calls + 1);
|
|
176
|
+
if (state === 'failed') {
|
|
177
|
+
stages[name].state = 'failed';
|
|
178
|
+
failedStage ||= name;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
setProvider(name, version) {
|
|
184
|
+
selectedProvider = name;
|
|
185
|
+
selectedProviderVersion = version;
|
|
186
|
+
},
|
|
187
|
+
startStage(name) {
|
|
188
|
+
const stageStarted = clock();
|
|
189
|
+
let finished = false;
|
|
190
|
+
return (state = 'ok') => {
|
|
191
|
+
if (finished) return;
|
|
192
|
+
finished = true;
|
|
193
|
+
addStageInterval(name, stageStarted, clock(), state);
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
addBytes(name, value) {
|
|
197
|
+
if (!byteNames.includes(name)) return;
|
|
198
|
+
bytes[name] = boundedNumber(bytes[name] + boundedNumber(value));
|
|
199
|
+
},
|
|
200
|
+
addStage(name, durationMs, state = 'ok') {
|
|
201
|
+
const stageEnded = clock();
|
|
202
|
+
addStageInterval(
|
|
203
|
+
name,
|
|
204
|
+
stageEnded - boundedNumber(durationMs),
|
|
205
|
+
stageEnded,
|
|
206
|
+
state,
|
|
207
|
+
);
|
|
208
|
+
},
|
|
209
|
+
failure(code = 1) {
|
|
210
|
+
const endedTick = Math.max(startedTick, clock());
|
|
211
|
+
return {
|
|
212
|
+
schemaVersion: 1,
|
|
213
|
+
runId: /^[0-9a-f-]{36}$/i.test(runId) ? runId : randomUUID(),
|
|
214
|
+
startedAt,
|
|
215
|
+
durationMs: boundedNumber(endedTick - startedTick),
|
|
216
|
+
provider: {
|
|
217
|
+
name: cleanProvider(selectedProvider),
|
|
218
|
+
version: safeVersion(selectedProviderVersion),
|
|
219
|
+
},
|
|
220
|
+
tools: {
|
|
221
|
+
diffsplain: safeVersion(packageJson.version),
|
|
222
|
+
node: safeVersion(process.versions.node),
|
|
223
|
+
},
|
|
224
|
+
stages: stageRecord(stages, startedTick, endedTick),
|
|
225
|
+
bytes: { ...bytes },
|
|
226
|
+
exit: {
|
|
227
|
+
state: 'failed',
|
|
228
|
+
code: boundedNumber(code),
|
|
229
|
+
stage: failedStage || 'unknown',
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function formatSupportRecord(record) {
|
|
237
|
+
return `${JSON.stringify(record, null, 2)}\n`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function writeSupportRecord(path, record) {
|
|
241
|
+
writePrivateFile(path, formatSupportRecord(record));
|
|
242
|
+
}
|