@jpkme/diff-review 0.1.12 → 0.1.14
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 +1 -1
- package/bin/diff-review.mjs +72 -53
- package/lib/acp-policy.mjs +99 -12
- package/lib/acp-policy.test.mjs +56 -3
- package/lib/acp-shell.mjs +38 -8
- package/lib/acp-shell.test.mjs +32 -1
- package/lib/agent-login.mjs +105 -105
- package/lib/check-report.mjs +1 -0
- package/lib/check-report.test.mjs +1 -0
- package/lib/extract-cache.test.mjs +162 -162
- package/lib/model-label.mjs +263 -263
- package/lib/model-label.test.mjs +77 -77
- package/lib/models-cache.mjs +75 -75
- package/lib/models-cache.test.mjs +50 -50
- package/lib/prompt.mjs +8 -6
- package/lib/review-prep.mjs +63 -63
- package/package.json +1 -1
- package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css → 0.BKRG1f36.css} +1 -1
- package/web/build/client/_app/immutable/assets/0.BKRG1f36.css.br +0 -0
- package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css.gz → 0.BKRG1f36.css.gz} +0 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css +1 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css +1 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.C8XGESwn.css +1 -0
- package/web/build/client/_app/immutable/assets/storage.C8XGESwn.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.C8XGESwn.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/C0hrdiUt.js +2 -0
- package/web/build/client/_app/immutable/chunks/C0hrdiUt.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/C0hrdiUt.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/CpeIO6nz.js +210 -0
- package/web/build/client/_app/immutable/chunks/CpeIO6nz.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/CpeIO6nz.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{CPyWH418.js → FOc6rgIW.js} +1 -1
- package/web/build/client/_app/immutable/chunks/FOc6rgIW.js.br +2 -0
- package/web/build/client/_app/immutable/chunks/FOc6rgIW.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/{app.BiBohpyO.js → app.DcAtSJ3n.js} +2 -2
- package/web/build/client/_app/immutable/entry/app.DcAtSJ3n.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.DcAtSJ3n.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.CO5l0Zbx.js +1 -0
- package/web/build/client/_app/immutable/entry/start.CO5l0Zbx.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.CO5l0Zbx.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/{1.BzPXaJJj.js → 1.E-Hs7ura.js} +1 -1
- package/web/build/client/_app/immutable/nodes/1.E-Hs7ura.js.br +4 -0
- package/web/build/client/_app/immutable/nodes/1.E-Hs7ura.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.s1_irV1Q.js +10 -0
- package/web/build/client/_app/immutable/nodes/2.s1_irV1Q.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.s1_irV1Q.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.pgwe-_5b.js +171 -0
- package/web/build/client/_app/immutable/nodes/3.pgwe-_5b.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.pgwe-_5b.js.gz +0 -0
- package/web/build/client/_app/version.json +1 -1
- package/web/build/client/_app/version.json.br +0 -0
- package/web/build/client/_app/version.json.gz +0 -0
- package/web/build/handler.js +3 -3
- package/web/build/index.js +3 -3
- package/web/build/server/chunks/{0-BYok4D0n.js → 0-DUStDOsr.js} +3 -3
- package/web/build/server/chunks/{0-BYok4D0n.js.map → 0-DUStDOsr.js.map} +1 -1
- package/web/build/server/chunks/1-BFhhV5JB.js +7 -0
- package/web/build/server/chunks/{1-C7zcB0q0.js.map → 1-BFhhV5JB.js.map} +1 -1
- package/web/build/server/chunks/2-BZ_MVqj8.js +7 -0
- package/web/build/server/chunks/2-BZ_MVqj8.js.map +1 -0
- package/web/build/server/chunks/3-BV-TIUp1.js +7 -0
- package/web/build/server/chunks/{3-DAAmG6VX.js.map → 3-BV-TIUp1.js.map} +1 -1
- package/web/build/server/chunks/{_server.ts-qYVrMYPB.js → _server.ts-CdFoXw36.js} +4 -4
- package/web/build/server/chunks/{_server.ts-qYVrMYPB.js.map → _server.ts-CdFoXw36.js.map} +1 -1
- package/web/build/server/chunks/{agent-sessions-DSqGavSn.js → agent-sessions-D0WwtWgR.js} +223 -51
- package/web/build/server/chunks/agent-sessions-D0WwtWgR.js.map +1 -0
- package/web/build/server/chunks/{git-review-CpO_G8z8.js → git-review-CgcBIbvM.js} +7 -7
- package/web/build/server/chunks/{git-review-CpO_G8z8.js.map → git-review-CgcBIbvM.js.map} +1 -1
- package/web/build/server/chunks/{handler-Di1wJQuU.js → handler-DbxjtK9M.js} +3 -3
- package/web/build/server/chunks/{handler-Di1wJQuU.js.map → handler-DbxjtK9M.js.map} +1 -1
- package/web/build/server/chunks/{hooks.server-BLOHobEK.js → hooks.server-Dg6gYUqs.js} +3 -3
- package/web/build/server/chunks/{hooks.server-BLOHobEK.js.map → hooks.server-Dg6gYUqs.js.map} +1 -1
- package/web/build/server/chunks/{index.js-yLYmrAWy.js → index.js-NNne-njN.js} +876 -879
- package/web/build/server/chunks/index.js-NNne-njN.js.map +1 -0
- package/web/build/server/chunks/{manifest.js-CXj8IIo3.js → manifest.js-V8ViI5bl.js} +9 -9
- package/web/build/server/chunks/{manifest.js-CXj8IIo3.js.map → manifest.js-V8ViI5bl.js.map} +1 -1
- package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js → remote-1g4ayag-CCsG2QXw.js} +4 -4
- package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js.map → remote-1g4ayag-CCsG2QXw.js.map} +1 -1
- package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js → remote-wh6t6j-BCvmX37z.js} +5 -5
- package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js.map → remote-wh6t6j-BCvmX37z.js.map} +1 -1
- package/web/build/server/chunks/{validate-BsTKyhsq.js → validate-CAQ31C7k.js} +2 -2
- package/web/build/server/chunks/{validate-BsTKyhsq.js.map → validate-CAQ31C7k.js.map} +1 -1
- package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css +0 -1
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css +0 -1
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css +0 -1
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js +0 -2
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/CPyWH418.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/CPyWH418.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js +0 -210
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js +0 -1
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.br +0 -2
- package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js +0 -10
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js +0 -171
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.gz +0 -0
- package/web/build/server/chunks/1-C7zcB0q0.js +0 -7
- package/web/build/server/chunks/2-v6ejI_0-.js +0 -7
- package/web/build/server/chunks/2-v6ejI_0-.js.map +0 -1
- package/web/build/server/chunks/3-DAAmG6VX.js +0 -7
- package/web/build/server/chunks/agent-sessions-DSqGavSn.js.map +0 -1
- package/web/build/server/chunks/index.js-yLYmrAWy.js.map +0 -1
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js → 0.Dj97JW46.js} +0 -0
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.br → 0.Dj97JW46.js.br} +0 -0
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.gz → 0.Dj97JW46.js.gz} +0 -0
package/lib/model-label.test.mjs
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { test } from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
buildAcpModelSelectOptions,
|
|
5
|
-
compactModelMeta,
|
|
6
|
-
formatModelOption,
|
|
7
|
-
humanizeModelId,
|
|
8
|
-
modelOptionLabel,
|
|
9
|
-
} from "./model-label.mjs";
|
|
10
|
-
|
|
11
|
-
test("humanizeModelId", () => {
|
|
12
|
-
assert.equal(humanizeModelId("claude-opus-5-5"), "Claude Opus 5.5");
|
|
13
|
-
assert.equal(humanizeModelId("claude-sonnet-5.5"), "Claude Sonnet 5.5");
|
|
14
|
-
assert.equal(humanizeModelId("gemini-3.8-flash"), "Gemini 3.8 Flash");
|
|
15
|
-
assert.equal(humanizeModelId("gpt-5.6-sol"), "GPT 5.6 Sol");
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
test("compactModelMeta", () => {
|
|
19
|
-
assert.deepEqual(compactModelMeta("context=300k,effort=medium,fast=false"), ["300k", "medium"]);
|
|
20
|
-
assert.deepEqual(compactModelMeta("thinking=true,context=300k,effort=high,fast=false"), [
|
|
21
|
-
"thinking",
|
|
22
|
-
"300k",
|
|
23
|
-
"high",
|
|
24
|
-
]);
|
|
25
|
-
assert.deepEqual(compactModelMeta("1m"), ["1m"]);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("formatModelOption compacto y amigable", () => {
|
|
29
|
-
const a = formatModelOption("claude-opus-5-5", "claude-opus-5-5[context=300k,effort=medium,fast=false]");
|
|
30
|
-
assert.equal(a.title, "Claude Opus 5.5");
|
|
31
|
-
assert.deepEqual(a.badges, ["300k", "medium"]);
|
|
32
|
-
assert.equal(a.label, "Claude Opus 5.5 · [ 300k, medium ]");
|
|
33
|
-
assert.equal(a.hint, "[ 300k, medium ]");
|
|
34
|
-
|
|
35
|
-
const b = formatModelOption("Claude Sonnet 5.5", "claude-sonnet-5.5");
|
|
36
|
-
assert.equal(b.title, "Claude Sonnet 5.5");
|
|
37
|
-
assert.deepEqual(b.badges, []);
|
|
38
|
-
assert.equal(b.label, "Claude Sonnet 5.5");
|
|
39
|
-
|
|
40
|
-
const c = formatModelOption("Sonnet (1M context)", "sonnet[1m]");
|
|
41
|
-
assert.equal(c.title, "Sonnet (1M context)");
|
|
42
|
-
assert.deepEqual(c.badges, []);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("buildAcpModelSelectOptions fusiona aliases y formatea ACP", () => {
|
|
46
|
-
const { options } = buildAcpModelSelectOptions(
|
|
47
|
-
{
|
|
48
|
-
currentModel: "default",
|
|
49
|
-
models: [
|
|
50
|
-
{ value: "default", name: "Default (recommended)" },
|
|
51
|
-
{
|
|
52
|
-
value: "claude-opus-5-5[context=300k,effort=medium,fast=false]",
|
|
53
|
-
name: "claude-opus-5-5",
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
options: [
|
|
59
|
-
{ value: "", label: "Por defecto (opus)" },
|
|
60
|
-
{ value: "opus", label: "opus", hint: "el más capaz" },
|
|
61
|
-
],
|
|
62
|
-
}
|
|
63
|
-
);
|
|
64
|
-
assert.equal(options[0].label, "Por defecto (opus)");
|
|
65
|
-
assert.ok(options.some((o) => o.value === "opus"));
|
|
66
|
-
const opusAcp = options.find((o) => o.value.startsWith("claude-opus-5-5"));
|
|
67
|
-
assert.equal(opusAcp?.title, "Claude Opus 5.5");
|
|
68
|
-
assert.deepEqual(opusAcp?.badges, ["300k", "medium"]);
|
|
69
|
-
assert.equal(opusAcp?.hint, "[ 300k, medium ]");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("modelOptionLabel compat", () => {
|
|
73
|
-
assert.equal(
|
|
74
|
-
modelOptionLabel("grok-4.7", "grok-4.7[context=256k,fast=true]"),
|
|
75
|
-
"Grok 4.7 · [ 256k, fast ]"
|
|
76
|
-
);
|
|
77
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
buildAcpModelSelectOptions,
|
|
5
|
+
compactModelMeta,
|
|
6
|
+
formatModelOption,
|
|
7
|
+
humanizeModelId,
|
|
8
|
+
modelOptionLabel,
|
|
9
|
+
} from "./model-label.mjs";
|
|
10
|
+
|
|
11
|
+
test("humanizeModelId", () => {
|
|
12
|
+
assert.equal(humanizeModelId("claude-opus-5-5"), "Claude Opus 5.5");
|
|
13
|
+
assert.equal(humanizeModelId("claude-sonnet-5.5"), "Claude Sonnet 5.5");
|
|
14
|
+
assert.equal(humanizeModelId("gemini-3.8-flash"), "Gemini 3.8 Flash");
|
|
15
|
+
assert.equal(humanizeModelId("gpt-5.6-sol"), "GPT 5.6 Sol");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("compactModelMeta", () => {
|
|
19
|
+
assert.deepEqual(compactModelMeta("context=300k,effort=medium,fast=false"), ["300k", "medium"]);
|
|
20
|
+
assert.deepEqual(compactModelMeta("thinking=true,context=300k,effort=high,fast=false"), [
|
|
21
|
+
"thinking",
|
|
22
|
+
"300k",
|
|
23
|
+
"high",
|
|
24
|
+
]);
|
|
25
|
+
assert.deepEqual(compactModelMeta("1m"), ["1m"]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("formatModelOption compacto y amigable", () => {
|
|
29
|
+
const a = formatModelOption("claude-opus-5-5", "claude-opus-5-5[context=300k,effort=medium,fast=false]");
|
|
30
|
+
assert.equal(a.title, "Claude Opus 5.5");
|
|
31
|
+
assert.deepEqual(a.badges, ["300k", "medium"]);
|
|
32
|
+
assert.equal(a.label, "Claude Opus 5.5 · [ 300k, medium ]");
|
|
33
|
+
assert.equal(a.hint, "[ 300k, medium ]");
|
|
34
|
+
|
|
35
|
+
const b = formatModelOption("Claude Sonnet 5.5", "claude-sonnet-5.5");
|
|
36
|
+
assert.equal(b.title, "Claude Sonnet 5.5");
|
|
37
|
+
assert.deepEqual(b.badges, []);
|
|
38
|
+
assert.equal(b.label, "Claude Sonnet 5.5");
|
|
39
|
+
|
|
40
|
+
const c = formatModelOption("Sonnet (1M context)", "sonnet[1m]");
|
|
41
|
+
assert.equal(c.title, "Sonnet (1M context)");
|
|
42
|
+
assert.deepEqual(c.badges, []);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("buildAcpModelSelectOptions fusiona aliases y formatea ACP", () => {
|
|
46
|
+
const { options } = buildAcpModelSelectOptions(
|
|
47
|
+
{
|
|
48
|
+
currentModel: "default",
|
|
49
|
+
models: [
|
|
50
|
+
{ value: "default", name: "Default (recommended)" },
|
|
51
|
+
{
|
|
52
|
+
value: "claude-opus-5-5[context=300k,effort=medium,fast=false]",
|
|
53
|
+
name: "claude-opus-5-5",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
options: [
|
|
59
|
+
{ value: "", label: "Por defecto (opus)" },
|
|
60
|
+
{ value: "opus", label: "opus", hint: "el más capaz" },
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
assert.equal(options[0].label, "Por defecto (opus)");
|
|
65
|
+
assert.ok(options.some((o) => o.value === "opus"));
|
|
66
|
+
const opusAcp = options.find((o) => o.value.startsWith("claude-opus-5-5"));
|
|
67
|
+
assert.equal(opusAcp?.title, "Claude Opus 5.5");
|
|
68
|
+
assert.deepEqual(opusAcp?.badges, ["300k", "medium"]);
|
|
69
|
+
assert.equal(opusAcp?.hint, "[ 300k, medium ]");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("modelOptionLabel compat", () => {
|
|
73
|
+
assert.equal(
|
|
74
|
+
modelOptionLabel("grok-4.7", "grok-4.7[context=256k,fast=true]"),
|
|
75
|
+
"Grok 4.7 · [ 256k, fast ]"
|
|
76
|
+
);
|
|
77
|
+
});
|
package/lib/models-cache.mjs
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Caché en disco de las listas de modelos ACP por agente.
|
|
3
|
-
* TTL 24h: los catálogos cambian de a poco (semanas); un día evita spawnear el CLI en cada review
|
|
4
|
-
* y sigue fresco si aparece un modelo nuevo.
|
|
5
|
-
*/
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
9
|
-
|
|
10
|
-
/** 24 horas. */
|
|
11
|
-
export const MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
|
-
|
|
13
|
-
/** @returns {string} */
|
|
14
|
-
export function modelsCachePath(home = homedir()) {
|
|
15
|
-
return join(home, ".diff-review", "models-cache.json");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {string} agentId
|
|
20
|
-
* @param {{ home?: string, now?: number, ttlMs?: number }} [opts]
|
|
21
|
-
* @returns {{ models: { value: string, name: string }[], currentModel: string, fetchedAt: string } | null}
|
|
22
|
-
*/
|
|
23
|
-
export function readModelsCache(agentId, opts = {}) {
|
|
24
|
-
const id = String(agentId || "").trim();
|
|
25
|
-
if (!id) return null;
|
|
26
|
-
const path = modelsCachePath(opts.home);
|
|
27
|
-
let root;
|
|
28
|
-
try {
|
|
29
|
-
root = JSON.parse(readFileSync(path, "utf8"));
|
|
30
|
-
} catch {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
const entry = root?.agents?.[id];
|
|
34
|
-
if (!entry || !Array.isArray(entry.models)) return null;
|
|
35
|
-
const fetchedAt = Date.parse(String(entry.fetchedAt || ""));
|
|
36
|
-
if (!Number.isFinite(fetchedAt)) return null;
|
|
37
|
-
const ttl = opts.ttlMs ?? MODELS_CACHE_TTL_MS;
|
|
38
|
-
const now = opts.now ?? Date.now();
|
|
39
|
-
if (now - fetchedAt > ttl) return null;
|
|
40
|
-
return {
|
|
41
|
-
models: entry.models
|
|
42
|
-
.filter((m) => m && typeof m.value === "string")
|
|
43
|
-
.map((m) => ({ value: m.value, name: String(m.name ?? m.value) })),
|
|
44
|
-
currentModel: String(entry.currentModel ?? ""),
|
|
45
|
-
fetchedAt: new Date(fetchedAt).toISOString(),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @param {string} agentId
|
|
51
|
-
* @param {{ models: { value: string, name: string }[], currentModel?: string }} data
|
|
52
|
-
* @param {{ home?: string, now?: number }} [opts]
|
|
53
|
-
*/
|
|
54
|
-
export function writeModelsCache(agentId, data, opts = {}) {
|
|
55
|
-
const id = String(agentId || "").trim();
|
|
56
|
-
if (!id || !Array.isArray(data?.models) || !data.models.length) return;
|
|
57
|
-
const path = modelsCachePath(opts.home);
|
|
58
|
-
let root = { version: 1, agents: {} };
|
|
59
|
-
try {
|
|
60
|
-
if (existsSync(path)) root = { ...root, ...JSON.parse(readFileSync(path, "utf8")) };
|
|
61
|
-
} catch {
|
|
62
|
-
/* archivo nuevo */
|
|
63
|
-
}
|
|
64
|
-
if (!root.agents || typeof root.agents !== "object") root.agents = {};
|
|
65
|
-
root.agents[id] = {
|
|
66
|
-
fetchedAt: new Date(opts.now ?? Date.now()).toISOString(),
|
|
67
|
-
currentModel: String(data.currentModel ?? ""),
|
|
68
|
-
models: data.models.map((m) => ({
|
|
69
|
-
value: String(m.value),
|
|
70
|
-
name: String(m.name ?? m.value),
|
|
71
|
-
})),
|
|
72
|
-
};
|
|
73
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
74
|
-
writeFileSync(path, `${JSON.stringify(root, null, 2)}\n`);
|
|
75
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Caché en disco de las listas de modelos ACP por agente.
|
|
3
|
+
* TTL 24h: los catálogos cambian de a poco (semanas); un día evita spawnear el CLI en cada review
|
|
4
|
+
* y sigue fresco si aparece un modelo nuevo.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
/** 24 horas. */
|
|
11
|
+
export const MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
/** @returns {string} */
|
|
14
|
+
export function modelsCachePath(home = homedir()) {
|
|
15
|
+
return join(home, ".diff-review", "models-cache.json");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} agentId
|
|
20
|
+
* @param {{ home?: string, now?: number, ttlMs?: number }} [opts]
|
|
21
|
+
* @returns {{ models: { value: string, name: string }[], currentModel: string, fetchedAt: string } | null}
|
|
22
|
+
*/
|
|
23
|
+
export function readModelsCache(agentId, opts = {}) {
|
|
24
|
+
const id = String(agentId || "").trim();
|
|
25
|
+
if (!id) return null;
|
|
26
|
+
const path = modelsCachePath(opts.home);
|
|
27
|
+
let root;
|
|
28
|
+
try {
|
|
29
|
+
root = JSON.parse(readFileSync(path, "utf8"));
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const entry = root?.agents?.[id];
|
|
34
|
+
if (!entry || !Array.isArray(entry.models)) return null;
|
|
35
|
+
const fetchedAt = Date.parse(String(entry.fetchedAt || ""));
|
|
36
|
+
if (!Number.isFinite(fetchedAt)) return null;
|
|
37
|
+
const ttl = opts.ttlMs ?? MODELS_CACHE_TTL_MS;
|
|
38
|
+
const now = opts.now ?? Date.now();
|
|
39
|
+
if (now - fetchedAt > ttl) return null;
|
|
40
|
+
return {
|
|
41
|
+
models: entry.models
|
|
42
|
+
.filter((m) => m && typeof m.value === "string")
|
|
43
|
+
.map((m) => ({ value: m.value, name: String(m.name ?? m.value) })),
|
|
44
|
+
currentModel: String(entry.currentModel ?? ""),
|
|
45
|
+
fetchedAt: new Date(fetchedAt).toISOString(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} agentId
|
|
51
|
+
* @param {{ models: { value: string, name: string }[], currentModel?: string }} data
|
|
52
|
+
* @param {{ home?: string, now?: number }} [opts]
|
|
53
|
+
*/
|
|
54
|
+
export function writeModelsCache(agentId, data, opts = {}) {
|
|
55
|
+
const id = String(agentId || "").trim();
|
|
56
|
+
if (!id || !Array.isArray(data?.models) || !data.models.length) return;
|
|
57
|
+
const path = modelsCachePath(opts.home);
|
|
58
|
+
let root = { version: 1, agents: {} };
|
|
59
|
+
try {
|
|
60
|
+
if (existsSync(path)) root = { ...root, ...JSON.parse(readFileSync(path, "utf8")) };
|
|
61
|
+
} catch {
|
|
62
|
+
/* archivo nuevo */
|
|
63
|
+
}
|
|
64
|
+
if (!root.agents || typeof root.agents !== "object") root.agents = {};
|
|
65
|
+
root.agents[id] = {
|
|
66
|
+
fetchedAt: new Date(opts.now ?? Date.now()).toISOString(),
|
|
67
|
+
currentModel: String(data.currentModel ?? ""),
|
|
68
|
+
models: data.models.map((m) => ({
|
|
69
|
+
value: String(m.value),
|
|
70
|
+
name: String(m.name ?? m.value),
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
74
|
+
writeFileSync(path, `${JSON.stringify(root, null, 2)}\n`);
|
|
75
|
+
}
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, readFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { test } from "node:test";
|
|
6
|
-
import {
|
|
7
|
-
MODELS_CACHE_TTL_MS,
|
|
8
|
-
modelsCachePath,
|
|
9
|
-
readModelsCache,
|
|
10
|
-
writeModelsCache,
|
|
11
|
-
} from "./models-cache.mjs";
|
|
12
|
-
import { sortModelSelectOptions } from "./model-label.mjs";
|
|
13
|
-
|
|
14
|
-
test("sortModelSelectOptions: default y auto primero, resto lexico", () => {
|
|
15
|
-
const sorted = sortModelSelectOptions([
|
|
16
|
-
{ value: "zulu", label: "Zulu" },
|
|
17
|
-
{ value: "auto", label: "Auto" },
|
|
18
|
-
{ value: "alpha", label: "Alpha" },
|
|
19
|
-
{ value: "", label: "Por defecto" },
|
|
20
|
-
{ value: "bravo", label: "Bravo" },
|
|
21
|
-
]);
|
|
22
|
-
assert.deepEqual(
|
|
23
|
-
sorted.map((o) => o.value),
|
|
24
|
-
["", "auto", "alpha", "bravo", "zulu"]
|
|
25
|
-
);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("models cache roundtrip + TTL", () => {
|
|
29
|
-
const home = mkdtempSync(join(tmpdir(), "diff-review-mc-"));
|
|
30
|
-
const now = Date.parse("2026-01-01T12:00:00.000Z");
|
|
31
|
-
writeModelsCache(
|
|
32
|
-
"claude",
|
|
33
|
-
{
|
|
34
|
-
models: [{ value: "opus", name: "Opus" }],
|
|
35
|
-
currentModel: "default",
|
|
36
|
-
},
|
|
37
|
-
{ home, now }
|
|
38
|
-
);
|
|
39
|
-
assert.ok(readFileSync(modelsCachePath(home), "utf8").includes("opus"));
|
|
40
|
-
|
|
41
|
-
const hit = readModelsCache("claude", { home, now: now + 1000 });
|
|
42
|
-
assert.equal(hit?.currentModel, "default");
|
|
43
|
-
assert.equal(hit?.models[0].value, "opus");
|
|
44
|
-
|
|
45
|
-
const expired = readModelsCache("claude", {
|
|
46
|
-
home,
|
|
47
|
-
now: now + MODELS_CACHE_TTL_MS + 1,
|
|
48
|
-
});
|
|
49
|
-
assert.equal(expired, null);
|
|
50
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, readFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import {
|
|
7
|
+
MODELS_CACHE_TTL_MS,
|
|
8
|
+
modelsCachePath,
|
|
9
|
+
readModelsCache,
|
|
10
|
+
writeModelsCache,
|
|
11
|
+
} from "./models-cache.mjs";
|
|
12
|
+
import { sortModelSelectOptions } from "./model-label.mjs";
|
|
13
|
+
|
|
14
|
+
test("sortModelSelectOptions: default y auto primero, resto lexico", () => {
|
|
15
|
+
const sorted = sortModelSelectOptions([
|
|
16
|
+
{ value: "zulu", label: "Zulu" },
|
|
17
|
+
{ value: "auto", label: "Auto" },
|
|
18
|
+
{ value: "alpha", label: "Alpha" },
|
|
19
|
+
{ value: "", label: "Por defecto" },
|
|
20
|
+
{ value: "bravo", label: "Bravo" },
|
|
21
|
+
]);
|
|
22
|
+
assert.deepEqual(
|
|
23
|
+
sorted.map((o) => o.value),
|
|
24
|
+
["", "auto", "alpha", "bravo", "zulu"]
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("models cache roundtrip + TTL", () => {
|
|
29
|
+
const home = mkdtempSync(join(tmpdir(), "diff-review-mc-"));
|
|
30
|
+
const now = Date.parse("2026-01-01T12:00:00.000Z");
|
|
31
|
+
writeModelsCache(
|
|
32
|
+
"claude",
|
|
33
|
+
{
|
|
34
|
+
models: [{ value: "opus", name: "Opus" }],
|
|
35
|
+
currentModel: "default",
|
|
36
|
+
},
|
|
37
|
+
{ home, now }
|
|
38
|
+
);
|
|
39
|
+
assert.ok(readFileSync(modelsCachePath(home), "utf8").includes("opus"));
|
|
40
|
+
|
|
41
|
+
const hit = readModelsCache("claude", { home, now: now + 1000 });
|
|
42
|
+
assert.equal(hit?.currentModel, "default");
|
|
43
|
+
assert.equal(hit?.models[0].value, "opus");
|
|
44
|
+
|
|
45
|
+
const expired = readModelsCache("claude", {
|
|
46
|
+
home,
|
|
47
|
+
now: now + MODELS_CACHE_TTL_MS + 1,
|
|
48
|
+
});
|
|
49
|
+
assert.equal(expired, null);
|
|
50
|
+
});
|
package/lib/prompt.mjs
CHANGED
|
@@ -91,7 +91,7 @@ const RULES = `- Ids: themes use \`t1\`, \`t2\`, …; findings use \`f1\`, \`f2\
|
|
|
91
91
|
function workflowLocal(base, branch, ctx = {}) {
|
|
92
92
|
if (ctx.hasDiff) {
|
|
93
93
|
return `Workflow:
|
|
94
|
-
1.
|
|
94
|
+
1. The host already computed the three-dot diff and wrote it to disk (see "Precomputed git context"). Read that file with your file tools. Do not run \`git diff\`, \`git merge-base\`, or rebuild the patch yourself unless that file is missing.
|
|
95
95
|
2. Read enough surrounding code (types, callers, tests) to judge behavior, not only the hunk lines.
|
|
96
96
|
3. Emit the JSON file. Do not modify the repo.
|
|
97
97
|
4. Validate it with the command in the output section. If it prints INVALID, follow "How to continue" and re-run until it prints OK.`;
|
|
@@ -115,7 +115,7 @@ function workflowUrl(base, branch, ctx = {}) {
|
|
|
115
115
|
if (ctx.hasDiff) {
|
|
116
116
|
return `Workflow:
|
|
117
117
|
1. If the repo is not local, clone it (or use whatever access you have). Do not modify it.
|
|
118
|
-
2.
|
|
118
|
+
2. The host already computed the three-dot diff and wrote it to disk (see "Precomputed git context"). Read that file with your file tools. Do not run \`git diff\`, \`git merge-base\`, or rebuild the patch yourself unless that file is missing.
|
|
119
119
|
3. Read enough surrounding code (types, callers, tests) to judge behavior, not only the hunk lines.
|
|
120
120
|
4. Emit the JSON file.
|
|
121
121
|
5. Validate it with the command in the output section. If it prints INVALID, follow "How to continue" and re-run until it prints OK.`;
|
|
@@ -133,7 +133,7 @@ function workflowUrl(base, branch, ctx = {}) {
|
|
|
133
133
|
function workflowLocalAcp(ctx = {}) {
|
|
134
134
|
if (ctx.hasDiff) {
|
|
135
135
|
return `Workflow:
|
|
136
|
-
1.
|
|
136
|
+
1. The host already computed the three-dot diff and wrote it to disk (see "Precomputed git context"). Read that file with your file tools. Do not run \`git diff\`, \`git merge-base\`, or rebuild the patch yourself unless that file is missing.
|
|
137
137
|
2. Read enough surrounding code (types, callers, tests) to judge behavior, not only the hunk lines.
|
|
138
138
|
3. Write the JSON with your file Write/Edit tool (never shell redirection). Do not modify other repo files. The host validates when your turn ends — do not run the validate CLI.`;
|
|
139
139
|
}
|
|
@@ -147,7 +147,7 @@ function workflowUrlAcp(ctx = {}) {
|
|
|
147
147
|
if (ctx.hasDiff) {
|
|
148
148
|
return `Workflow:
|
|
149
149
|
1. If the repo is not local, clone it (or use whatever access you have). Do not modify it.
|
|
150
|
-
2.
|
|
150
|
+
2. The host already computed the three-dot diff and wrote it to disk (see "Precomputed git context"). Read that file with your file tools. Do not run \`git diff\`, \`git merge-base\`, or rebuild the patch yourself unless that file is missing.
|
|
151
151
|
3. Read enough surrounding code (types, callers, tests) to judge behavior, not only the hunk lines.
|
|
152
152
|
4. Write the JSON with your file Write/Edit tool (never shell redirection). The host validates when your turn ends — do not run the validate CLI.`;
|
|
153
153
|
}
|
|
@@ -197,13 +197,15 @@ export function outputSchemaBlock(meta) {
|
|
|
197
197
|
* }} ctx
|
|
198
198
|
*/
|
|
199
199
|
function formatPrecomputedContext(ctx) {
|
|
200
|
-
const lines = [
|
|
200
|
+
const lines = [
|
|
201
|
+
"Precomputed git context (provided by the host — do not rediscover or recompute):",
|
|
202
|
+
];
|
|
201
203
|
if (ctx.mergeBase) lines.push(`- Merge-base SHA: \`${ctx.mergeBase}\``);
|
|
202
204
|
if (ctx.branchSha) lines.push(`- Branch tip SHA: \`${ctx.branchSha}\``);
|
|
203
205
|
if (ctx.baseSha) lines.push(`- Base tip SHA: \`${ctx.baseSha}\``);
|
|
204
206
|
if (ctx.diffRelPath) {
|
|
205
207
|
lines.push(
|
|
206
|
-
`- Diff file (relative to repo root): \`${ctx.diffRelPath}\` —
|
|
208
|
+
`- Diff file (relative to repo root): \`${ctx.diffRelPath}\` — Read this file for the full patch. Do not run \`git diff\` / \`git merge-base\` to rebuild it unless the file is missing.`
|
|
207
209
|
);
|
|
208
210
|
}
|
|
209
211
|
const files = Array.isArray(ctx.files) ? ctx.files : [];
|
package/lib/review-prep.mjs
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Precomputa contexto git para el prompt de review (diff cache + lista de archivos).
|
|
3
|
-
*/
|
|
4
|
-
import { loadReviewConfig } from "./config.mjs";
|
|
5
|
-
import { extractBranchDiff } from "./extract.mjs";
|
|
6
|
-
import { outputRelPath } from "./output-name.mjs";
|
|
7
|
-
import { ensureReviewDir, readDiffCache, writeDiffCache } from "./review-dir.mjs";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Cachea el three-dot diff y arma las opciones de `buildPrompt` (tips, files, ignores).
|
|
11
|
-
* @param {{
|
|
12
|
-
* repo: string,
|
|
13
|
-
* branch: string,
|
|
14
|
-
* base?: string,
|
|
15
|
-
* outputFilename: string,
|
|
16
|
-
* acp?: boolean,
|
|
17
|
-
* }} input
|
|
18
|
-
*/
|
|
19
|
-
export function prepareReviewPromptOpts(input) {
|
|
20
|
-
const repo = String(input.repo || "").trim();
|
|
21
|
-
const branch = String(input.branch || "").trim();
|
|
22
|
-
const base = String(input.base || "develop").trim() || "develop";
|
|
23
|
-
const outputFilename = String(input.outputFilename || "").trim();
|
|
24
|
-
if (!repo || !branch || !outputFilename) {
|
|
25
|
-
throw new Error("prepareReviewPromptOpts requiere repo, branch y outputFilename.");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
ensureReviewDir(repo);
|
|
29
|
-
writeDiffCache(repo, outputFilename, { branch, base });
|
|
30
|
-
const cached = readDiffCache(repo, outputFilename);
|
|
31
|
-
|
|
32
|
-
let extracted;
|
|
33
|
-
try {
|
|
34
|
-
extracted = extractBranchDiff({
|
|
35
|
-
repo,
|
|
36
|
-
branch,
|
|
37
|
-
base,
|
|
38
|
-
...(cached ? { cachedDiff: cached } : {}),
|
|
39
|
-
});
|
|
40
|
-
} catch {
|
|
41
|
-
extracted = extractBranchDiff({ repo, branch, base });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const config = loadReviewConfig(repo);
|
|
45
|
-
const diffRelPath = cached
|
|
46
|
-
? outputRelPath(outputFilename).replace(/\.json$/i, ".diff")
|
|
47
|
-
: null;
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
outputFilename,
|
|
51
|
-
acp: Boolean(input.acp),
|
|
52
|
-
diffRelPath,
|
|
53
|
-
mergeBase: extracted.mergeBase || undefined,
|
|
54
|
-
branchSha: extracted.branchSha || undefined,
|
|
55
|
-
baseSha: extracted.baseSha || undefined,
|
|
56
|
-
files: (extracted.files || []).map((f) => ({
|
|
57
|
-
path: f.path,
|
|
58
|
-
changeType: f.changeType,
|
|
59
|
-
})),
|
|
60
|
-
ignored: extracted.context?.ignored || [],
|
|
61
|
-
ignoreGlobs: config.ignore,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Precomputa contexto git para el prompt de review (diff cache + lista de archivos).
|
|
3
|
+
*/
|
|
4
|
+
import { loadReviewConfig } from "./config.mjs";
|
|
5
|
+
import { extractBranchDiff } from "./extract.mjs";
|
|
6
|
+
import { outputRelPath } from "./output-name.mjs";
|
|
7
|
+
import { ensureReviewDir, readDiffCache, writeDiffCache } from "./review-dir.mjs";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cachea el three-dot diff y arma las opciones de `buildPrompt` (tips, files, ignores).
|
|
11
|
+
* @param {{
|
|
12
|
+
* repo: string,
|
|
13
|
+
* branch: string,
|
|
14
|
+
* base?: string,
|
|
15
|
+
* outputFilename: string,
|
|
16
|
+
* acp?: boolean,
|
|
17
|
+
* }} input
|
|
18
|
+
*/
|
|
19
|
+
export function prepareReviewPromptOpts(input) {
|
|
20
|
+
const repo = String(input.repo || "").trim();
|
|
21
|
+
const branch = String(input.branch || "").trim();
|
|
22
|
+
const base = String(input.base || "develop").trim() || "develop";
|
|
23
|
+
const outputFilename = String(input.outputFilename || "").trim();
|
|
24
|
+
if (!repo || !branch || !outputFilename) {
|
|
25
|
+
throw new Error("prepareReviewPromptOpts requiere repo, branch y outputFilename.");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ensureReviewDir(repo);
|
|
29
|
+
writeDiffCache(repo, outputFilename, { branch, base });
|
|
30
|
+
const cached = readDiffCache(repo, outputFilename);
|
|
31
|
+
|
|
32
|
+
let extracted;
|
|
33
|
+
try {
|
|
34
|
+
extracted = extractBranchDiff({
|
|
35
|
+
repo,
|
|
36
|
+
branch,
|
|
37
|
+
base,
|
|
38
|
+
...(cached ? { cachedDiff: cached } : {}),
|
|
39
|
+
});
|
|
40
|
+
} catch {
|
|
41
|
+
extracted = extractBranchDiff({ repo, branch, base });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = loadReviewConfig(repo);
|
|
45
|
+
const diffRelPath = cached
|
|
46
|
+
? outputRelPath(outputFilename).replace(/\.json$/i, ".diff")
|
|
47
|
+
: null;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
outputFilename,
|
|
51
|
+
acp: Boolean(input.acp),
|
|
52
|
+
diffRelPath,
|
|
53
|
+
mergeBase: extracted.mergeBase || undefined,
|
|
54
|
+
branchSha: extracted.branchSha || undefined,
|
|
55
|
+
baseSha: extracted.baseSha || undefined,
|
|
56
|
+
files: (extracted.files || []).map((f) => ({
|
|
57
|
+
path: f.path,
|
|
58
|
+
changeType: f.changeType,
|
|
59
|
+
})),
|
|
60
|
+
ignored: extracted.context?.ignored || [],
|
|
61
|
+
ignoreGlobs: config.ignore,
|
|
62
|
+
};
|
|
63
|
+
}
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--bg:#0b0e14;--bg-elev:#11151d;--bg-card:#151a24;--bg-card-hover:#1a2130;--border:#242b3a;--border-soft:#1c2230;--text:#e6e9ef;--text-dim:#9aa4b8;--text-faint:#5f6b82;--accent:#6ea8fe;--accent-soft:#1c2b45;--ok:#7ee2a8;--ok-bg:#0d2a1c;--warn:#e2c67e;--danger:#ff9a9a;--danger-bg:#2a1414;--danger-border:#4a1e1e;--mono:ui-monospace, "JetBrains Mono", "Fira Code", Consolas, "SFMono-Regular", Menlo, monospace;--sans:-apple-system, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;--btn-primary-bg:#1c2b45;--btn-primary-border:#3a5a8f;--btn-primary-text:#cfe0ff;--kind-feat:#7ee2a8;--kind-fix:#ff9a9a;--kind-refactor:#c9a5ff;--kind-perf:#e2c67e;--kind-test:#6ea8fe;--kind-chore:#9aa4b8;--kind-docs:#7fd6e0;--kind-infra:#f0a868;--sev-high:#ff9a9a;--sev-med:#f08a5a;--sev-low:#c4b86a;--sev-nit:#7f8aa3;--diff-add-bg:#0f2419;--diff-add-fg:#7ee2a8;--diff-del-bg:#2a1418;--diff-del-fg:#ff9a9a;--diff-hunk-bg:#141924;--diff-gutter:#46516a;--diff-mark:#2b3f63;--diff-mark-edge:#6ea8fe;--diff-add-strong:#1f6b3c;--diff-del-strong:#6d2530;--diff-blank-bg:repeating-linear-gradient(135deg, #11151d 0 6px, #151a24 6px 12px);--fix-bg:#0e1a13;--fix-border:#2f6b46;--fix-text:#7ee2a8;--publish-bg:#0e1622;--publish-border:#3a5a8f;--publish-text:#7db8ff;--scrollbar-thumb:#2a3348;--scrollbar-thumb-hover:#3a4660;--selection-bg:#33507a;--header-bg:#0b0e14eb}:root[data-theme=light]{--bg:#fff;--bg-elev:#f6f7f9;--bg-card:#eef0f3;--bg-card-hover:#e4e7ec;--border:#d7dbe2;--border-soft:#e4e7ec;--text:#16181d;--text-dim:#4b5563;--text-faint:#6b7280;--accent:#2f6fe0;--accent-soft:#dce8ff;--ok:#1f9d55;--ok-bg:#e3f8ea;--warn:#a5720a;--danger:#c92a2a;--danger-bg:#fbe4e4;--danger-border:#f3b4b4;--btn-primary-bg:#2f6fe0;--btn-primary-border:#2358b8;--btn-primary-text:#fff;--kind-feat:#1f9d55;--kind-fix:#c92a2a;--kind-refactor:#7c4dd6;--kind-perf:#a5720a;--kind-test:#2f6fe0;--kind-chore:#5b6472;--kind-docs:#0f8fa3;--kind-infra:#c1690f;--sev-high:#c92a2a;--sev-med:#c44d1a;--sev-low:#8b7d2e;--sev-nit:#6b7280;--diff-add-bg:#e6f7ec;--diff-add-fg:#1f9d55;--diff-del-bg:#fbe9e9;--diff-del-fg:#c92a2a;--diff-hunk-bg:#eef1f5;--diff-gutter:#9aa3b2;--diff-mark:#cfe0ff;--diff-mark-edge:#2f6fe0;--diff-add-strong:#a7ebbd;--diff-del-strong:#f9b8b8;--diff-blank-bg:repeating-linear-gradient(135deg, #f6f7f9 0 6px, #eef0f3 6px 12px);--fix-bg:#e6f7ec;--fix-border:#1f9d55;--fix-text:#157a41;--publish-bg:#e8f0fe;--publish-border:#2f6fe0;--publish-text:#1d4fa8;--scrollbar-thumb:#c7cbd4;--scrollbar-thumb-hover:#aab0bb;--selection-bg:#cfe0ff;--header-bg:#ffffffeb}.code .chg{border-radius:2px}.code.del .chg{background:var(--diff-del-strong)}.code.add .chg{background:var(--diff-add-strong)}.hljs-comment,.hljs-quote{color:#6b7689;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-doctag,.hljs-formula{color:#c9a5ff}.hljs-string,.hljs-regexp,.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string{color:#a5d6a7}.hljs-number,.hljs-variable,.hljs-template-variable,.hljs-type,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo{color:#f0a868}.hljs-title,.hljs-section,.hljs-title.class_,.hljs-title.function_{color:#7fd6e0}.hljs-attr,.hljs-property,.hljs-symbol,.hljs-bullet,.hljs-link{color:#6ea8fe}.hljs-built_in,.hljs-class,.hljs-params{color:#e6e9ef}.hljs-meta,.hljs-tag{color:#9aa4b8}:root[data-theme=light] .hljs-comment,:root[data-theme=light] .hljs-quote{color:#6b7280}:root[data-theme=light] .hljs-keyword,:root[data-theme=light] .hljs-selector-tag,:root[data-theme=light] .hljs-literal,:root[data-theme=light] .hljs-doctag,:root[data-theme=light] .hljs-formula{color:#7c4dd6}:root[data-theme=light] .hljs-string,:root[data-theme=light] .hljs-regexp,:root[data-theme=light] .hljs-addition,:root[data-theme=light] .hljs-attribute,:root[data-theme=light] .hljs-meta .hljs-string{color:#1f9d55}:root[data-theme=light] .hljs-number,:root[data-theme=light] .hljs-variable,:root[data-theme=light] .hljs-template-variable,:root[data-theme=light] .hljs-type,:root[data-theme=light] .hljs-selector-class,:root[data-theme=light] .hljs-selector-attr,:root[data-theme=light] .hljs-selector-pseudo{color:#b5650f}:root[data-theme=light] .hljs-title,:root[data-theme=light] .hljs-section,:root[data-theme=light] .hljs-title.class_,:root[data-theme=light] .hljs-title.function_{color:#0f8fa3}:root[data-theme=light] .hljs-attr,:root[data-theme=light] .hljs-property,:root[data-theme=light] .hljs-symbol,:root[data-theme=light] .hljs-bullet,:root[data-theme=light] .hljs-link{color:#2f6fe0}:root[data-theme=light] .hljs-built_in,:root[data-theme=light] .hljs-class,:root[data-theme=light] .hljs-params{color:#16181d}:root[data-theme=light] .hljs-meta,:root[data-theme=light] .hljs-tag{color:#5b6472}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.anchor-flash{animation:1.4s ease-out anchor-flash}@keyframes anchor-flash{0%{box-shadow:0 0 0 2px var(--accent)}to{box-shadow:0 0 0 2px #0000}}*{box-sizing:border-box}html,body{background:var(--bg);color:var(--text);font-family:var(--sans);min-height:100%;margin:0;padding:0}body{-webkit-font-smoothing:antialiased}button,input,select{font:inherit;color:inherit}button{cursor:pointer}::selection{background:var(--selection-bg)}html,body,*{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);background-clip:padding-box;border:2px solid #0000;border-radius:999px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover);background-clip:padding-box;border:2px solid #0000}::-webkit-scrollbar-corner{background:0 0}.boot.svelte-12qhfyh{color:var(--text-dim);font-family:var(--mono);padding:48px 28px;font-size:13px}.boot.svelte-12qhfyh button:where(.svelte-12qhfyh){background:var(--btn-primary-bg);border:1px solid var(--btn-primary-border);color:var(--btn-primary-text);border-radius:
|
|
1
|
+
:root{--bg:#0b0e14;--bg-elev:#11151d;--bg-card:#151a24;--bg-card-hover:#1a2130;--border:#242b3a;--border-soft:#1c2230;--text:#e6e9ef;--text-dim:#9aa4b8;--text-faint:#5f6b82;--accent:#6ea8fe;--accent-soft:#1c2b45;--ok:#7ee2a8;--ok-bg:#0d2a1c;--warn:#e2c67e;--danger:#ff9a9a;--danger-bg:#2a1414;--danger-border:#4a1e1e;--mono:ui-monospace, "JetBrains Mono", "Fira Code", Consolas, "SFMono-Regular", Menlo, monospace;--sans:-apple-system, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;--btn-primary-bg:#1c2b45;--btn-primary-border:#3a5a8f;--btn-primary-text:#cfe0ff;--kind-feat:#7ee2a8;--kind-fix:#ff9a9a;--kind-refactor:#c9a5ff;--kind-perf:#e2c67e;--kind-test:#6ea8fe;--kind-chore:#9aa4b8;--kind-docs:#7fd6e0;--kind-infra:#f0a868;--sev-high:#ff9a9a;--sev-med:#f08a5a;--sev-low:#c4b86a;--sev-nit:#7f8aa3;--diff-add-bg:#0f2419;--diff-add-fg:#7ee2a8;--diff-del-bg:#2a1418;--diff-del-fg:#ff9a9a;--diff-hunk-bg:#141924;--diff-gutter:#46516a;--diff-mark:#2b3f63;--diff-mark-edge:#6ea8fe;--diff-add-strong:#1f6b3c;--diff-del-strong:#6d2530;--diff-blank-bg:repeating-linear-gradient(135deg, #11151d 0 6px, #151a24 6px 12px);--fix-bg:#0e1a13;--fix-border:#2f6b46;--fix-text:#7ee2a8;--publish-bg:#0e1622;--publish-border:#3a5a8f;--publish-text:#7db8ff;--scrollbar-thumb:#2a3348;--scrollbar-thumb-hover:#3a4660;--selection-bg:#33507a;--header-bg:#0b0e14eb}:root[data-theme=light]{--bg:#fff;--bg-elev:#f6f7f9;--bg-card:#eef0f3;--bg-card-hover:#e4e7ec;--border:#d7dbe2;--border-soft:#e4e7ec;--text:#16181d;--text-dim:#4b5563;--text-faint:#6b7280;--accent:#2f6fe0;--accent-soft:#dce8ff;--ok:#1f9d55;--ok-bg:#e3f8ea;--warn:#a5720a;--danger:#c92a2a;--danger-bg:#fbe4e4;--danger-border:#f3b4b4;--btn-primary-bg:#2f6fe0;--btn-primary-border:#2358b8;--btn-primary-text:#fff;--kind-feat:#1f9d55;--kind-fix:#c92a2a;--kind-refactor:#7c4dd6;--kind-perf:#a5720a;--kind-test:#2f6fe0;--kind-chore:#5b6472;--kind-docs:#0f8fa3;--kind-infra:#c1690f;--sev-high:#c92a2a;--sev-med:#c44d1a;--sev-low:#8b7d2e;--sev-nit:#6b7280;--diff-add-bg:#e6f7ec;--diff-add-fg:#1f9d55;--diff-del-bg:#fbe9e9;--diff-del-fg:#c92a2a;--diff-hunk-bg:#eef1f5;--diff-gutter:#9aa3b2;--diff-mark:#cfe0ff;--diff-mark-edge:#2f6fe0;--diff-add-strong:#a7ebbd;--diff-del-strong:#f9b8b8;--diff-blank-bg:repeating-linear-gradient(135deg, #f6f7f9 0 6px, #eef0f3 6px 12px);--fix-bg:#e6f7ec;--fix-border:#1f9d55;--fix-text:#157a41;--publish-bg:#e8f0fe;--publish-border:#2f6fe0;--publish-text:#1d4fa8;--scrollbar-thumb:#c7cbd4;--scrollbar-thumb-hover:#aab0bb;--selection-bg:#cfe0ff;--header-bg:#ffffffeb}.code .chg{border-radius:2px}.code.del .chg{background:var(--diff-del-strong)}.code.add .chg{background:var(--diff-add-strong)}.hljs-comment,.hljs-quote{color:#6b7689;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-literal,.hljs-doctag,.hljs-formula{color:#c9a5ff}.hljs-string,.hljs-regexp,.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string{color:#a5d6a7}.hljs-number,.hljs-variable,.hljs-template-variable,.hljs-type,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo{color:#f0a868}.hljs-title,.hljs-section,.hljs-title.class_,.hljs-title.function_{color:#7fd6e0}.hljs-attr,.hljs-property,.hljs-symbol,.hljs-bullet,.hljs-link{color:#6ea8fe}.hljs-built_in,.hljs-class,.hljs-params{color:#e6e9ef}.hljs-meta,.hljs-tag{color:#9aa4b8}:root[data-theme=light] .hljs-comment,:root[data-theme=light] .hljs-quote{color:#6b7280}:root[data-theme=light] .hljs-keyword,:root[data-theme=light] .hljs-selector-tag,:root[data-theme=light] .hljs-literal,:root[data-theme=light] .hljs-doctag,:root[data-theme=light] .hljs-formula{color:#7c4dd6}:root[data-theme=light] .hljs-string,:root[data-theme=light] .hljs-regexp,:root[data-theme=light] .hljs-addition,:root[data-theme=light] .hljs-attribute,:root[data-theme=light] .hljs-meta .hljs-string{color:#1f9d55}:root[data-theme=light] .hljs-number,:root[data-theme=light] .hljs-variable,:root[data-theme=light] .hljs-template-variable,:root[data-theme=light] .hljs-type,:root[data-theme=light] .hljs-selector-class,:root[data-theme=light] .hljs-selector-attr,:root[data-theme=light] .hljs-selector-pseudo{color:#b5650f}:root[data-theme=light] .hljs-title,:root[data-theme=light] .hljs-section,:root[data-theme=light] .hljs-title.class_,:root[data-theme=light] .hljs-title.function_{color:#0f8fa3}:root[data-theme=light] .hljs-attr,:root[data-theme=light] .hljs-property,:root[data-theme=light] .hljs-symbol,:root[data-theme=light] .hljs-bullet,:root[data-theme=light] .hljs-link{color:#2f6fe0}:root[data-theme=light] .hljs-built_in,:root[data-theme=light] .hljs-class,:root[data-theme=light] .hljs-params{color:#16181d}:root[data-theme=light] .hljs-meta,:root[data-theme=light] .hljs-tag{color:#5b6472}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.anchor-flash{animation:1.4s ease-out anchor-flash}@keyframes anchor-flash{0%{box-shadow:0 0 0 2px var(--accent)}to{box-shadow:0 0 0 2px #0000}}*{box-sizing:border-box}html,body{background:var(--bg);color:var(--text);font-family:var(--sans);min-height:100%;margin:0;padding:0}body{-webkit-font-smoothing:antialiased}button,input,select{font:inherit;color:inherit}button{cursor:pointer}::selection{background:var(--selection-bg)}html,body,*{scrollbar-width:thin;scrollbar-color:var(--scrollbar-thumb) transparent}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);background-clip:padding-box;border:2px solid #0000;border-radius:999px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover);background-clip:padding-box;border:2px solid #0000}::-webkit-scrollbar-corner{background:0 0}.boot.svelte-12qhfyh{color:var(--text-dim);font-family:var(--mono);padding:48px 28px;font-size:13px}.boot.svelte-12qhfyh button:where(.svelte-12qhfyh){background:var(--btn-primary-bg);border:1px solid var(--btn-primary-border);color:var(--btn-primary-text);border-radius:0;margin-top:12px;padding:8px 14px}
|
|
Binary file
|