@jpkme/diff-review 0.1.12 → 0.1.13

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.
Files changed (120) hide show
  1. package/README.md +1 -1
  2. package/lib/acp-policy.mjs +99 -12
  3. package/lib/acp-policy.test.mjs +56 -3
  4. package/lib/acp-shell.mjs +38 -8
  5. package/lib/acp-shell.test.mjs +32 -1
  6. package/lib/agent-login.mjs +105 -105
  7. package/lib/check-report.mjs +1 -0
  8. package/lib/check-report.test.mjs +1 -0
  9. package/lib/extract-cache.test.mjs +162 -162
  10. package/lib/model-label.mjs +263 -263
  11. package/lib/model-label.test.mjs +77 -77
  12. package/lib/models-cache.mjs +75 -75
  13. package/lib/models-cache.test.mjs +50 -50
  14. package/lib/review-prep.mjs +63 -63
  15. package/package.json +1 -1
  16. package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css → 0.BKRG1f36.css} +1 -1
  17. package/web/build/client/_app/immutable/assets/0.BKRG1f36.css.br +0 -0
  18. package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css.gz → 0.BKRG1f36.css.gz} +0 -0
  19. package/web/build/client/_app/immutable/assets/2.tqrMRTM0.css +1 -0
  20. package/web/build/client/_app/immutable/assets/2.tqrMRTM0.css.br +0 -0
  21. package/web/build/client/_app/immutable/assets/2.tqrMRTM0.css.gz +0 -0
  22. package/web/build/client/_app/immutable/assets/3.BT54aVKb.css +1 -0
  23. package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.br +0 -0
  24. package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.gz +0 -0
  25. package/web/build/client/_app/immutable/assets/storage.DqYH0w6R.css +1 -0
  26. package/web/build/client/_app/immutable/assets/storage.DqYH0w6R.css.br +0 -0
  27. package/web/build/client/_app/immutable/assets/storage.DqYH0w6R.css.gz +0 -0
  28. package/web/build/client/_app/immutable/chunks/BXUWMim4.js +213 -0
  29. package/web/build/client/_app/immutable/chunks/BXUWMim4.js.br +0 -0
  30. package/web/build/client/_app/immutable/chunks/BXUWMim4.js.gz +0 -0
  31. package/web/build/client/_app/immutable/chunks/{CPyWH418.js → Bu1mm6Oz.js} +1 -1
  32. package/web/build/client/_app/immutable/chunks/Bu1mm6Oz.js.br +0 -0
  33. package/web/build/client/_app/immutable/chunks/Bu1mm6Oz.js.gz +0 -0
  34. package/web/build/client/_app/immutable/chunks/{B5ByWcPE.js → DPfXzQnm.js} +2 -2
  35. package/web/build/client/_app/immutable/chunks/DPfXzQnm.js.br +0 -0
  36. package/web/build/client/_app/immutable/chunks/DPfXzQnm.js.gz +0 -0
  37. package/web/build/client/_app/immutable/entry/{app.BiBohpyO.js → app.CTgiuiU1.js} +2 -2
  38. package/web/build/client/_app/immutable/entry/app.CTgiuiU1.js.br +0 -0
  39. package/web/build/client/_app/immutable/entry/app.CTgiuiU1.js.gz +0 -0
  40. package/web/build/client/_app/immutable/entry/start.CFSl0kht.js +1 -0
  41. package/web/build/client/_app/immutable/entry/start.CFSl0kht.js.br +0 -0
  42. package/web/build/client/_app/immutable/entry/start.CFSl0kht.js.gz +0 -0
  43. package/web/build/client/_app/immutable/nodes/{1.BzPXaJJj.js → 1.0PQZpSbJ.js} +1 -1
  44. package/web/build/client/_app/immutable/nodes/1.0PQZpSbJ.js.br +2 -0
  45. package/web/build/client/_app/immutable/nodes/1.0PQZpSbJ.js.gz +0 -0
  46. package/web/build/client/_app/immutable/nodes/2.DQaoQoy4.js +10 -0
  47. package/web/build/client/_app/immutable/nodes/2.DQaoQoy4.js.br +0 -0
  48. package/web/build/client/_app/immutable/nodes/2.DQaoQoy4.js.gz +0 -0
  49. package/web/build/client/_app/immutable/nodes/3.AciugOtL.js +171 -0
  50. package/web/build/client/_app/immutable/nodes/3.AciugOtL.js.br +0 -0
  51. package/web/build/client/_app/immutable/nodes/3.AciugOtL.js.gz +0 -0
  52. package/web/build/client/_app/version.json +1 -1
  53. package/web/build/client/_app/version.json.br +0 -0
  54. package/web/build/client/_app/version.json.gz +0 -0
  55. package/web/build/handler.js +3 -3
  56. package/web/build/index.js +3 -3
  57. package/web/build/server/chunks/{0-BYok4D0n.js → 0-DUStDOsr.js} +3 -3
  58. package/web/build/server/chunks/{0-BYok4D0n.js.map → 0-DUStDOsr.js.map} +1 -1
  59. package/web/build/server/chunks/1-C2qlInYk.js +7 -0
  60. package/web/build/server/chunks/{1-C7zcB0q0.js.map → 1-C2qlInYk.js.map} +1 -1
  61. package/web/build/server/chunks/2-DyE177MK.js +7 -0
  62. package/web/build/server/chunks/2-DyE177MK.js.map +1 -0
  63. package/web/build/server/chunks/3-SO3atuWO.js +7 -0
  64. package/web/build/server/chunks/{3-DAAmG6VX.js.map → 3-SO3atuWO.js.map} +1 -1
  65. package/web/build/server/chunks/{_server.ts-qYVrMYPB.js → _server.ts-CRNhzL2a.js} +3 -3
  66. package/web/build/server/chunks/{_server.ts-qYVrMYPB.js.map → _server.ts-CRNhzL2a.js.map} +1 -1
  67. package/web/build/server/chunks/{agent-sessions-DSqGavSn.js → agent-sessions-kcUaLhK5.js} +171 -40
  68. package/web/build/server/chunks/agent-sessions-kcUaLhK5.js.map +1 -0
  69. package/web/build/server/chunks/{git-review-CpO_G8z8.js → git-review-CXKcjYgZ.js} +2 -2
  70. package/web/build/server/chunks/{git-review-CpO_G8z8.js.map → git-review-CXKcjYgZ.js.map} +1 -1
  71. package/web/build/server/chunks/{handler-Di1wJQuU.js → handler-_E0NucKN.js} +3 -3
  72. package/web/build/server/chunks/{handler-Di1wJQuU.js.map → handler-_E0NucKN.js.map} +1 -1
  73. package/web/build/server/chunks/{hooks.server-BLOHobEK.js → hooks.server-D7yPODoc.js} +2 -2
  74. package/web/build/server/chunks/{hooks.server-BLOHobEK.js.map → hooks.server-D7yPODoc.js.map} +1 -1
  75. package/web/build/server/chunks/{index.js-yLYmrAWy.js → index.js-lMtDM8ZQ.js} +3 -3
  76. package/web/build/server/chunks/{index.js-yLYmrAWy.js.map → index.js-lMtDM8ZQ.js.map} +1 -1
  77. package/web/build/server/chunks/{manifest.js-CXj8IIo3.js → manifest.js-BIm6jot5.js} +9 -9
  78. package/web/build/server/chunks/{manifest.js-CXj8IIo3.js.map → manifest.js-BIm6jot5.js.map} +1 -1
  79. package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js → remote-1g4ayag-DmqxwUIz.js} +3 -3
  80. package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js.map → remote-1g4ayag-DmqxwUIz.js.map} +1 -1
  81. package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js → remote-wh6t6j-DZk0W9Pp.js} +4 -4
  82. package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js.map → remote-wh6t6j-DZk0W9Pp.js.map} +1 -1
  83. package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.br +0 -0
  84. package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css +0 -1
  85. package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.br +0 -0
  86. package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.gz +0 -0
  87. package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css +0 -1
  88. package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.br +0 -0
  89. package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.gz +0 -0
  90. package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css +0 -1
  91. package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.br +0 -0
  92. package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.gz +0 -0
  93. package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.br +0 -0
  94. package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.gz +0 -0
  95. package/web/build/client/_app/immutable/chunks/CPyWH418.js.br +0 -0
  96. package/web/build/client/_app/immutable/chunks/CPyWH418.js.gz +0 -0
  97. package/web/build/client/_app/immutable/chunks/NwkMsx0h.js +0 -210
  98. package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.br +0 -0
  99. package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.gz +0 -0
  100. package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.br +0 -0
  101. package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.gz +0 -0
  102. package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js +0 -1
  103. package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.br +0 -0
  104. package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.gz +0 -0
  105. package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.br +0 -2
  106. package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.gz +0 -0
  107. package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js +0 -10
  108. package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.br +0 -0
  109. package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.gz +0 -0
  110. package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js +0 -171
  111. package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.br +0 -0
  112. package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.gz +0 -0
  113. package/web/build/server/chunks/1-C7zcB0q0.js +0 -7
  114. package/web/build/server/chunks/2-v6ejI_0-.js +0 -7
  115. package/web/build/server/chunks/2-v6ejI_0-.js.map +0 -1
  116. package/web/build/server/chunks/3-DAAmG6VX.js +0 -7
  117. package/web/build/server/chunks/agent-sessions-DSqGavSn.js.map +0 -1
  118. /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js → 0.Dj97JW46.js} +0 -0
  119. /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.br → 0.Dj97JW46.js.br} +0 -0
  120. /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.gz → 0.Dj97JW46.js.gz} +0 -0
@@ -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
+ });
@@ -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
+ });
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@jpkme/diff-review",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Local UI to prompt a coding agent for a branch review and browse the result.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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:8px;margin-top:12px;padding:8px 14px}
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}
@@ -0,0 +1 @@
1
+ .live.svelte-18urbyr{background:radial-gradient(900px 420px at 8% -8%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 70%), var(--bg);flex-direction:column;flex:1;min-height:0;display:flex}.bar.svelte-18urbyr{border-bottom:1px solid var(--border);background:color-mix(in srgb, var(--bg-elev) 90%, transparent);justify-content:space-between;align-items:center;gap:12px;padding:12px 20px;display:flex}.brand.svelte-18urbyr{align-items:center;gap:10px;min-width:0;font-size:14px;display:flex}.dot.svelte-18urbyr{background:var(--accent);width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);border-radius:50%;flex:none}.pill.svelte-18urbyr{letter-spacing:.02em;border:1px solid var(--border);color:var(--text-dim);background:var(--bg-card);text-transform:lowercase;border-radius:0;padding:3px 9px;font-size:11px;font-weight:500}.pill.busy.svelte-18urbyr{border-color:color-mix(in srgb, var(--accent) 45%, var(--border));color:var(--accent);background:var(--accent-soft)}.pill.error.svelte-18urbyr{border-color:var(--danger-border,var(--danger));color:var(--danger);background:var(--danger-bg,transparent)}.pill.gone.svelte-18urbyr{color:var(--text-faint)}.actions.svelte-18urbyr{flex:none;align-items:center;gap:8px;display:flex}.ghost.svelte-18urbyr{border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;background:0 0;padding:6px 11px;font-size:13px}.ghost.svelte-18urbyr:hover{border-color:var(--accent)}.body.svelte-18urbyr{flex:1;grid-template-columns:minmax(280px,320px) 1fr;gap:20px;min-height:0;padding:20px 24px 24px;display:grid}.card.svelte-18urbyr{border:1px solid var(--border);background:var(--bg-elev);border-radius:0;align-self:start;padding:16px 16px 14px}.fields.svelte-18urbyr{flex-direction:column;gap:14px;margin:0;display:flex}.fields.svelte-18urbyr dt:where(.svelte-18urbyr){text-transform:uppercase;letter-spacing:.04em;color:var(--text-faint);margin:0 0 3px;font-size:11px}.fields.svelte-18urbyr dd:where(.svelte-18urbyr){font-size:13px;font-family:var(--mono);text-overflow:ellipsis;white-space:nowrap;color:var(--text);margin:0;overflow:hidden}.branch.svelte-18urbyr{color:var(--accent);font-weight:600}.arrow.svelte-18urbyr{color:var(--text-faint);margin:0 2px}.card-note.svelte-18urbyr{border-top:1px solid var(--border-soft,var(--border));color:var(--text-faint);margin:16px 0 0;padding-top:12px;font-size:12.5px;line-height:1.4}.stage.svelte-18urbyr{border:1px solid var(--border);background:var(--bg-elev);border-radius:0;flex-direction:column;min-width:0;min-height:0;display:flex;overflow:hidden}.center.svelte-18urbyr{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:40px 24px;display:flex}.center.fail.svelte-18urbyr .title:where(.svelte-18urbyr){color:var(--danger)}.auth-actions.svelte-18urbyr{flex-wrap:wrap;justify-content:center;gap:8px;margin-top:12px;display:flex}.primary.svelte-18urbyr{border:1px solid color-mix(in srgb, var(--accent) 55%, var(--border));background:var(--accent-soft);color:var(--accent);font:inherit;cursor:pointer;border-radius:0;padding:7px 14px;font-size:13px;font-weight:600}.primary.svelte-18urbyr:hover:not(:disabled){border-color:var(--accent)}.primary.svelte-18urbyr:disabled{opacity:.6;cursor:default}.auth-note.svelte-18urbyr{max-width:28rem;color:var(--text-dim);margin:10px 0 0;font-size:13px}.auth-err.svelte-18urbyr{max-width:28rem;color:var(--danger);margin:8px 0 0;font-size:13px}.spinner.svelte-18urbyr{border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;width:34px;height:34px;margin-bottom:6px;animation:.8s linear infinite svelte-18urbyr-spin}@keyframes svelte-18urbyr-spin{to{transform:rotate(360deg)}}.title.svelte-18urbyr{margin:0;font-size:17px;font-weight:600}.detail.svelte-18urbyr{color:var(--text-dim);max-width:28rem;margin:0;font-size:14px}.hint.svelte-18urbyr{max-width:32rem;color:var(--text-faint);margin:6px 0 0;font-size:13px}.stage-header.svelte-18urbyr{border-bottom:1px solid var(--border);border-radius:0;flex:none;align-items:center;gap:8px;min-width:0;padding:10px 14px;display:flex}.stage-header.svelte-18urbyr strong:where(.svelte-18urbyr){font-size:13.5px;font-weight:650}.stage-header.svelte-18urbyr small:where(.svelte-18urbyr){font-family:var(--mono);color:var(--text-faint);text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:11.5px;overflow:hidden}.log-wrap.svelte-18urbyr{border-radius:0;flex-direction:column;flex:1;gap:10px;min-height:0;padding:12px 14px;display:flex}.log-wrap.svelte-18urbyr .log{flex:1;min-height:0}.stage-footer.svelte-18urbyr{border-top:1px solid var(--border);border-radius:0;flex:none;align-items:center;gap:10px;margin-top:auto;padding-top:10px;display:flex}.footer-spacer.svelte-18urbyr{flex:1}.launch-banner.svelte-18urbyr{border:1px solid var(--border);background:color-mix(in srgb, var(--accent-soft) 70%, var(--bg-card));border-radius:0;align-items:flex-start;gap:12px;padding:10px 12px;display:flex}.launch-banner.svelte-18urbyr .title:where(.svelte-18urbyr){font-size:14px}.launch-banner.svelte-18urbyr .detail:where(.svelte-18urbyr){margin-top:2px;font-size:12.5px}.spinner.sm.svelte-18urbyr{flex:none;width:18px;height:18px;margin:2px 0 0}@media (width<=800px){.body.svelte-18urbyr{grid-template-rows:auto 1fr;grid-template-columns:1fr;gap:14px;padding:16px}.card.svelte-18urbyr{align-self:stretch}.stage.svelte-18urbyr{min-height:280px}}@media (width<=480px){.bar.svelte-18urbyr{flex-wrap:wrap;padding:10px 14px}.pill.svelte-18urbyr{font-size:10px}}.page.svelte-1uha8ag{flex-direction:column;min-height:100vh;display:flex}.page.live.svelte-1uha8ag{height:100vh;min-height:100vh;overflow:hidden}.diff-spinner.svelte-1uha8ag{border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;width:28px;height:28px;margin:0 auto;animation:.7s linear infinite svelte-1uha8ag-diff-spin}.cli-wait.svelte-1uha8ag{text-align:center;background:var(--bg);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:12px;min-height:0;padding:32px 24px;display:flex}.cli-wait-title.svelte-1uha8ag{color:var(--text);margin:0;font-size:15px}.cli-wait-detail.svelte-1uha8ag{color:var(--text-faint);margin:0;font-size:13px}.cli-wait.svelte-1uha8ag .ghost:where(.svelte-1uha8ag){border:1px solid var(--border);color:var(--text-dim);cursor:pointer;background:0 0;margin-top:8px;padding:6px 12px;font-size:13px}@keyframes svelte-1uha8ag-diff-spin{to{transform:rotate(360deg)}}.toast.svelte-1uha8ag{z-index:50;border:1px solid var(--border);background:var(--bg-card);color:var(--text);padding:10px 16px;font-size:13px;position:fixed;top:16px;left:50%;transform:translate(-50%);box-shadow:0 8px 24px #00000029}.topbar.svelte-1uha8ag{border-bottom:1px solid var(--border);background:var(--header-bg);z-index:5;justify-content:space-between;align-items:center;gap:12px;padding:16px 28px;display:flex;position:sticky;top:0}.brand.svelte-1uha8ag{color:var(--text);align-items:center;gap:10px;text-decoration:none;display:flex}.brand-dot.svelte-1uha8ag{background:linear-gradient(135deg,#6ea8fe,#c9a5ff);border-radius:50%;flex-shrink:0;width:9px;height:9px;box-shadow:0 0 12px #6ea8fe88}.title-block.svelte-1uha8ag{text-align:left;flex-direction:column;line-height:1.25;display:flex}.title-block.svelte-1uha8ag span:where(.svelte-1uha8ag):first-child{font-size:15px;font-weight:700}.title-block.svelte-1uha8ag small:where(.svelte-1uha8ag){color:var(--text-faint);font-size:11.5px;font-weight:400}.workspace.svelte-1uha8ag{flex:1;grid-template-columns:minmax(0,1fr) minmax(300px,380px);align-items:start;gap:28px;width:100%;max-width:1280px;margin:0 auto;padding:22px 28px 40px;display:grid;overflow:auto}.setup.svelte-1uha8ag{min-width:0}h2.svelte-1uha8ag{letter-spacing:.06em;text-transform:uppercase;color:var(--text-faint);margin:0 0 4px;font-size:13px;font-weight:600}.result-head.svelte-1uha8ag{margin-top:24px}.section-head.svelte-1uha8ag{justify-content:space-between;align-items:baseline;margin-top:22px;display:flex}.hint-text.svelte-1uha8ag{color:var(--text-dim);margin:0 0 12px;font-size:12.5px;line-height:1.5}.hint-text.svelte-1uha8ag code:where(.svelte-1uha8ag){font-family:var(--mono);color:var(--text);font-size:12px}.detect-banner.svelte-1uha8ag,.detect-wait.svelte-1uha8ag{color:var(--text-dim);margin:0 0 10px;font-size:13px}button.file-link.svelte-1uha8ag{color:var(--accent);font:inherit;cursor:pointer;background:0 0;border:0;padding:0;text-decoration:underline}.error-line.svelte-1uha8ag{color:var(--danger);margin:0 0 10px;font-size:12px}.ok-line.svelte-1uha8ag{color:var(--ok);margin:0 0 10px;font-size:12px}.row.svelte-1uha8ag{flex-wrap:wrap;gap:8px;display:flex}.repo-setup.svelte-1uha8ag{flex-direction:column;gap:10px;display:flex}.field.svelte-1uha8ag{flex-direction:column;flex:1;gap:4px;min-width:180px;display:flex}.repo-field.svelte-1uha8ag{flex:none;width:100%;min-width:0}.branches-row.svelte-1uha8ag .field:where(.svelte-1uha8ag){flex:1;min-width:160px}.field.svelte-1uha8ag span:where(.svelte-1uha8ag){color:var(--text-faint);font-size:11px}.repo-row.svelte-1uha8ag{gap:6px;min-width:0;display:flex}.repo-row.svelte-1uha8ag input[type=text]:where(.svelte-1uha8ag){flex:1;min-width:0}.repo-row.svelte-1uha8ag button:where(.svelte-1uha8ag){flex-shrink:0}.source-toggle.svelte-1uha8ag{margin-bottom:10px}input[type=text].svelte-1uha8ag,select.svelte-1uha8ag{background:var(--bg-card);border:1px solid var(--border);flex:1;min-width:0;padding:9px 12px}button.svelte-1uha8ag{background:var(--bg-card);border:1px solid var(--border);color:var(--text);padding:9px 12px;font-size:12.5px}button.svelte-1uha8ag:hover:not(:disabled){border-color:var(--accent)}button.svelte-1uha8ag:disabled{opacity:.5;cursor:not-allowed}.mode-toggle.svelte-1uha8ag{border:1px solid var(--border);width:fit-content;margin-bottom:10px;display:flex;overflow:hidden}.mode-toggle.svelte-1uha8ag button:where(.svelte-1uha8ag){background:0 0;border:0;padding:6px 12px;font-size:12px}.mode-toggle.svelte-1uha8ag button:where(.svelte-1uha8ag)+button:where(.svelte-1uha8ag){border-left:1px solid var(--border)}.mode-toggle.svelte-1uha8ag button.on:where(.svelte-1uha8ag){background:var(--accent-soft)}textarea.svelte-1uha8ag{resize:vertical;background:var(--bg);border:1px solid var(--border);width:100%;height:220px;font-family:var(--mono);color:var(--text);padding:12px;font-size:12px;line-height:1.5}.loading-models.svelte-1uha8ag{color:var(--text-faint);font-weight:400}.models-error.svelte-1uha8ag{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}button.linkish.svelte-1uha8ag{color:var(--accent,#3b82f6);cursor:pointer;font:inherit;background:0 0;border:none;padding:0;text-decoration:underline}.actions.svelte-1uha8ag{gap:8px;margin-top:10px;display:flex}button.primary.svelte-1uha8ag{background:var(--btn-primary-bg);border-color:var(--btn-primary-border);color:var(--btn-primary-text);font-weight:600}.coverage-box.svelte-1uha8ag{border:1px solid var(--border);border-left:3px solid var(--sev-med);background:var(--bg-card);flex-direction:column;gap:10px;margin-top:12px;padding:12px 14px;display:flex}.coverage-head.svelte-1uha8ag{flex-wrap:wrap;align-items:baseline;gap:8px;display:flex}.coverage-head.svelte-1uha8ag strong:where(.svelte-1uha8ag){color:var(--sev-med);font-size:13px}.coverage-head.svelte-1uha8ag span:where(.svelte-1uha8ag){color:var(--text-faint);font-size:11.5px}.coverage-box.svelte-1uha8ag summary:where(.svelte-1uha8ag){color:var(--text-dim);cursor:pointer;font-size:12px}.coverage-list.svelte-1uha8ag{max-height:180px;font-family:var(--mono);color:var(--text-faint);margin:8px 0 0;padding-left:18px;font-size:11px;overflow:auto}.coverage-box.svelte-1uha8ag textarea:where(.svelte-1uha8ag){resize:vertical;background:var(--bg);border:1px solid var(--border);width:100%;font-family:var(--mono);color:var(--text);padding:10px;font-size:11.5px;line-height:1.5}.coverage-actions.svelte-1uha8ag{flex-wrap:wrap;gap:8px;display:flex}.dropzone.svelte-1uha8ag{border:1.5px dashed var(--border);text-align:center;background:var(--bg-elev);padding:26px;transition:background .15s,border-color .15s}.dropzone.drag.svelte-1uha8ag{border-color:var(--accent);background:var(--accent-soft)}.dropzone.busy.svelte-1uha8ag{opacity:.7}.dropzone.svelte-1uha8ag p:where(.svelte-1uha8ag){color:var(--text-dim);margin:0 0 6px;font-size:14px}.visually-hidden.svelte-1uha8ag{clip:rect(0 0 0 0);width:1px;height:1px;position:absolute;overflow:hidden}.drop-errors.svelte-1uha8ag{background:var(--danger-bg);color:var(--danger);margin-top:10px;padding:10px 12px;font-size:12.5px}.drop-errors.svelte-1uha8ag strong:where(.svelte-1uha8ag){margin-bottom:4px;display:block}.drop-errors.svelte-1uha8ag ul:where(.svelte-1uha8ag){margin:0;padding-left:18px;line-height:1.6}.reports-col.svelte-1uha8ag{min-width:0;max-height:calc(100vh - 110px);position:sticky;top:88px;overflow:auto}.report-list.svelte-1uha8ag{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.report-item.svelte-1uha8ag{border:1px solid var(--border);background:var(--bg-card);align-items:stretch;display:flex;position:relative;overflow:visible}.report-item.svelte-1uha8ag:hover{background:var(--bg-card-hover);border-color:#33507a}.report-card.svelte-1uha8ag{min-width:0;color:var(--text);flex:1;padding:12px 8px 12px 14px;text-decoration:none;display:block}.report-actions.svelte-1uha8ag{flex-shrink:0;padding:8px 8px 0 0;position:relative}.kebab.svelte-1uha8ag{width:26px;height:26px;color:var(--text-faint);cursor:pointer;background:0 0;border:1px solid #0000;justify-content:center;align-items:center;padding:0;display:inline-flex}.kebab.svelte-1uha8ag:hover{border-color:var(--border);color:var(--text);background:var(--bg)}.report-menu.svelte-1uha8ag{z-index:40;border:1px solid var(--border);background:var(--bg-card);width:148px;padding:4px;position:fixed;box-shadow:0 8px 24px #0000002e}.report-menu.svelte-1uha8ag .menu-item:where(.svelte-1uha8ag){width:100%;color:var(--text);text-align:left;cursor:pointer;background:0 0;border:0;padding:8px 10px;font-size:12.5px;display:block}.report-menu.svelte-1uha8ag .menu-item:where(.svelte-1uha8ag):hover{background:var(--accent-soft)}.report-menu.svelte-1uha8ag .menu-item.danger:where(.svelte-1uha8ag){color:var(--danger)}.report-top.svelte-1uha8ag{justify-content:space-between;align-items:baseline;gap:12px;display:flex}.report-top.svelte-1uha8ag strong:where(.svelte-1uha8ag){font-size:14px}.report-prov.svelte-1uha8ag{min-width:0;color:var(--text-dim);align-items:center;gap:6px;margin-top:4px;font-size:12px;display:flex}.report-prov.svelte-1uha8ag .prov-agent:where(.svelte-1uha8ag){text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;overflow:hidden}.report-prov.svelte-1uha8ag .prov-sep:where(.svelte-1uha8ag){color:var(--text-faint);flex-shrink:0}.report-prov.svelte-1uha8ag .prov-model:where(.svelte-1uha8ag){min-width:0;font-family:var(--mono);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:11.5px;overflow:hidden}.when.svelte-1uha8ag{color:var(--text-dim);font-size:12px}.report-meta.svelte-1uha8ag{color:var(--text-dim);justify-content:space-between;align-items:baseline;gap:12px;margin-top:6px;font-size:12px;display:flex}.report-base.svelte-1uha8ag{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.base-arrow.svelte-1uha8ag{color:var(--text-faint)}.report-counts.svelte-1uha8ag{flex-shrink:0;align-items:baseline;gap:8px;display:flex}.pill.svelte-1uha8ag{font-family:var(--mono);font-size:11px}.pill.bad.svelte-1uha8ag{color:var(--danger)}.report-intent.svelte-1uha8ag{color:var(--text-dim);-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;margin:8px 0 0;font-size:12.5px;line-height:1.45;display:-webkit-box;overflow:hidden}@media (width<=960px){.workspace.svelte-1uha8ag{grid-template-columns:1fr}.reports-col.svelte-1uha8ag{max-height:none;position:static}}