@jpkme/diff-review 0.1.7 → 0.1.10
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/bin/diff-review.mjs +7 -2
- package/lib/agents.mjs +15 -0
- package/lib/check-report.mjs +2 -1
- package/lib/check-report.test.mjs +1 -0
- package/lib/prompt.mjs +14 -13
- package/lib/review-meta.mjs +33 -0
- package/lib/review-meta.test.mjs +30 -0
- package/lib/review.test.mjs +1 -0
- package/lib/validate.mjs +28 -11
- package/lib/validate.test.mjs +25 -0
- package/lib/wait-output.test.mjs +1 -0
- package/package.json +1 -1
- package/schema.json +9 -10
- package/web/build/client/_app/immutable/assets/{2.COsJT9ly.css → 2.CNFDEnjx.css} +1 -1
- package/web/build/client/_app/immutable/assets/2.CNFDEnjx.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CNFDEnjx.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/{3.qhPDz1xQ.css → 3.PHTPLlZ7.css} +1 -1
- package/web/build/client/_app/immutable/assets/3.PHTPLlZ7.css.br +0 -0
- package/web/build/client/_app/immutable/assets/{3.qhPDz1xQ.css.gz → 3.PHTPLlZ7.css.gz} +0 -0
- package/web/build/client/_app/immutable/chunks/BBWAo_yW.js +2 -0
- package/web/build/client/_app/immutable/chunks/BBWAo_yW.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/BBWAo_yW.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{D8sAxZVo.js → QdZF3nkf.js} +18 -16
- package/web/build/client/_app/immutable/chunks/QdZF3nkf.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/QdZF3nkf.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/{app.Dl9OTE-n.js → app.Dce6uJU3.js} +2 -2
- package/web/build/client/_app/immutable/entry/app.Dce6uJU3.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.Dce6uJU3.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.DnBSC38l.js +1 -0
- package/web/build/client/_app/immutable/entry/start.DnBSC38l.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.DnBSC38l.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/{1.B1c4AH1e.js → 1.5Iv4ssDT.js} +1 -1
- package/web/build/client/_app/immutable/nodes/1.5Iv4ssDT.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/1.5Iv4ssDT.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.BomzYVBF.js +66 -0
- package/web/build/client/_app/immutable/nodes/2.BomzYVBF.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.BomzYVBF.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/{3.C-USexyX.js → 3.CeUs4lBX.js} +2 -2
- package/web/build/client/_app/immutable/nodes/3.CeUs4lBX.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.CeUs4lBX.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/1-2OO2bb7H.js +7 -0
- package/web/build/server/chunks/{1-0X6Jp9nH.js.map → 1-2OO2bb7H.js.map} +1 -1
- package/web/build/server/chunks/2-BJvuqKN6.js +7 -0
- package/web/build/server/chunks/{2-HlcVALg6.js.map → 2-BJvuqKN6.js.map} +1 -1
- package/web/build/server/chunks/3-ZIl1rHef.js +7 -0
- package/web/build/server/chunks/{3-BN1eS7L2.js.map → 3-ZIl1rHef.js.map} +1 -1
- package/web/build/server/chunks/{handler-cq3QoEzW.js → handler-B5P65tLk.js} +3 -3
- package/web/build/server/chunks/{handler-cq3QoEzW.js.map → handler-B5P65tLk.js.map} +1 -1
- package/web/build/server/chunks/{index.js-D4qkHe3r.js → index.js-CL95e0Gl.js} +2 -2
- package/web/build/server/chunks/index.js-CL95e0Gl.js.map +1 -0
- package/web/build/server/chunks/{manifest.js-DQHMKyJh.js → manifest.js-uD_a0079.js} +6 -6
- package/web/build/server/chunks/{manifest.js-DQHMKyJh.js.map → manifest.js-uD_a0079.js.map} +1 -1
- package/web/build/server/chunks/{remote-1g4ayag-B8KZPTkn.js → remote-1g4ayag-B67QMR_2.js} +2 -2
- package/web/build/server/chunks/{remote-1g4ayag-B8KZPTkn.js.map → remote-1g4ayag-B67QMR_2.js.map} +1 -1
- package/web/build/client/_app/immutable/assets/2.COsJT9ly.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.COsJT9ly.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.qhPDz1xQ.css.br +0 -0
- package/web/build/client/_app/immutable/chunks/C4--ziCr.js +0 -2
- package/web/build/client/_app/immutable/chunks/C4--ziCr.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/C4--ziCr.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/D8sAxZVo.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/D8sAxZVo.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.Dl9OTE-n.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.Dl9OTE-n.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.CDz8Qs8Q.js +0 -1
- package/web/build/client/_app/immutable/entry/start.CDz8Qs8Q.js.br +0 -1
- package/web/build/client/_app/immutable/entry/start.CDz8Qs8Q.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.B1c4AH1e.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/1.B1c4AH1e.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.Dx-oOpEf.js +0 -66
- package/web/build/client/_app/immutable/nodes/2.Dx-oOpEf.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.Dx-oOpEf.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.C-USexyX.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.C-USexyX.js.gz +0 -0
- package/web/build/server/chunks/1-0X6Jp9nH.js +0 -7
- package/web/build/server/chunks/2-HlcVALg6.js +0 -7
- package/web/build/server/chunks/3-BN1eS7L2.js +0 -7
- package/web/build/server/chunks/index.js-D4qkHe3r.js.map +0 -1
package/bin/diff-review.mjs
CHANGED
|
@@ -16,13 +16,14 @@ import { createServer } from 'node:net';
|
|
|
16
16
|
import { dirname, join } from 'node:path';
|
|
17
17
|
import { fileURLToPath } from 'node:url';
|
|
18
18
|
import * as p from '@clack/prompts';
|
|
19
|
-
import { AGENTS, detectAgents, modelChoices, OTHER_MODEL, runAgent } from '../lib/agents.mjs';
|
|
19
|
+
import { AGENTS, detectAgents, modelChoices, OTHER_MODEL, resolvedRunModel, runAgent } from '../lib/agents.mjs';
|
|
20
20
|
import { autocompleteEs } from '../lib/autocomplete-es.mjs';
|
|
21
21
|
import { copyTextToClipboard } from '../lib/clipboard.mjs';
|
|
22
22
|
import { branchTips, getRemoteUrl, listBranches, tryResolveGitRoot } from '../lib/git.mjs';
|
|
23
23
|
import { makeOutputFilename } from '../lib/output-name.mjs';
|
|
24
24
|
import { buildPrompt } from '../lib/prompt.mjs';
|
|
25
25
|
import { existingReviewOutput, waitForReviewOutput } from '../lib/wait-output.mjs';
|
|
26
|
+
import { stampReviewProvenance } from '../lib/review-meta.mjs';
|
|
26
27
|
import { runValidateCli } from '../lib/check-report.mjs';
|
|
27
28
|
|
|
28
29
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -439,6 +440,10 @@ async function runAgentAndWait(agent, { repo, prompt, model, outputFilename, use
|
|
|
439
440
|
process.exit(1);
|
|
440
441
|
}
|
|
441
442
|
if (code !== 0) console.warn(`${agent.def.label} terminó con código ${code}, pero el JSON es válido.`);
|
|
443
|
+
stampReviewProvenance(result.path, {
|
|
444
|
+
agent: agent.def.label,
|
|
445
|
+
model: resolvedRunModel(agent.def, model)
|
|
446
|
+
});
|
|
442
447
|
return /** @type {{ path: string }} */ (result);
|
|
443
448
|
}
|
|
444
449
|
|
|
@@ -623,7 +628,7 @@ async function main() {
|
|
|
623
628
|
console.log(`Repo: ${launch.repo}`);
|
|
624
629
|
console.log(`Branch: ${branch} (base: ${base})`);
|
|
625
630
|
} else {
|
|
626
|
-
p.note(`${branch}
|
|
631
|
+
p.note(`${branch} → ${base}\n${outputFilename}`, 'Review');
|
|
627
632
|
}
|
|
628
633
|
|
|
629
634
|
const prompt = buildPrompt(
|
package/lib/agents.mjs
CHANGED
|
@@ -275,6 +275,21 @@ export function modelChoices(def, ctx) {
|
|
|
275
275
|
};
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Id de modelo que realmente se usó: el elegido, o el configurado del agente si no se pasó `--model`.
|
|
280
|
+
* @param {AgentDef} def
|
|
281
|
+
* @param {string} [model]
|
|
282
|
+
*/
|
|
283
|
+
export function resolvedRunModel(def, model) {
|
|
284
|
+
const chosen = String(model || "").trim();
|
|
285
|
+
if (chosen) return chosen;
|
|
286
|
+
try {
|
|
287
|
+
return def.configuredModel?.(context())?.model || "";
|
|
288
|
+
} catch {
|
|
289
|
+
return "";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
278
293
|
/** Comillas para cmd.exe (solo hace falta con los shims .cmd/.bat de npm). */
|
|
279
294
|
function quoteForCmd(arg) {
|
|
280
295
|
return /^[\w./:@=-]+$/.test(arg) ? arg : `"${arg.replace(/"/g, '""')}"`;
|
package/lib/check-report.mjs
CHANGED
|
@@ -159,7 +159,8 @@ export function formatCheckReport(file, command, result) {
|
|
|
159
159
|
"How to continue:",
|
|
160
160
|
"- Fix every problem above in place. Keep existing meta/intent/groups/blocks/findings; only change what is wrong or missing.",
|
|
161
161
|
'- Every diff path must appear in "blocks" or "skipped" (a skipped glob counts).',
|
|
162
|
-
"-
|
|
162
|
+
"- meta.agent and meta.model are required (product name and the exact model id you are running).",
|
|
163
|
+
"- intent ≥40 chars; enums exactly as in the schema.",
|
|
163
164
|
"- Human-readable strings in Spanish.",
|
|
164
165
|
`- Re-run exactly: \`${command}\``,
|
|
165
166
|
"- Stop only when that command prints OK."
|
|
@@ -3,6 +3,7 @@ import { test } from "node:test";
|
|
|
3
3
|
import { checkPayload, formatCheckReport } from "./check-report.mjs";
|
|
4
4
|
|
|
5
5
|
const valid = () => ({
|
|
6
|
+
meta: { agent: "Cursor", model: "composer" },
|
|
6
7
|
intent: "Agrega el ABM de depósitos locales end-to-end para dejar de administrarlos por SQL.",
|
|
7
8
|
groups: [{ id: "g1", kind: "feat", title: "CRUD de depósitos", intent: "base de todo" }],
|
|
8
9
|
blocks: [
|
package/lib/prompt.mjs
CHANGED
|
@@ -21,16 +21,16 @@ export const OUTPUT_SCHEMA_BLOCK = `{
|
|
|
21
21
|
"branch": "<branch under review>",
|
|
22
22
|
"base": "develop",
|
|
23
23
|
"remoteUrl": "<origin URL; optional when source=url>",
|
|
24
|
-
"generatedAt": "<ISO-8601 datetime; optional
|
|
25
|
-
"agent": "<
|
|
26
|
-
"model": "<model id
|
|
24
|
+
"generatedAt": "<ISO-8601 datetime; optional>",
|
|
25
|
+
"agent": "<required: product name you are, e.g. Claude Code>",
|
|
26
|
+
"model": "<required: exact model id you are running>"
|
|
27
27
|
},
|
|
28
28
|
"intent": "string",
|
|
29
29
|
"groups": [
|
|
30
30
|
{
|
|
31
31
|
"id": "g1",
|
|
32
32
|
"kind": "feat|fix|refactor|perf|test|chore|docs|infra",
|
|
33
|
-
"title": "string ≤72 chars, imperative, no kind prefix",
|
|
33
|
+
"title": "string, aim for ≤72 chars, imperative, no kind prefix",
|
|
34
34
|
"intent": "string"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
@@ -44,8 +44,8 @@ export const OUTPUT_SCHEMA_BLOCK = `{
|
|
|
44
44
|
"start": 40,
|
|
45
45
|
"end": 58,
|
|
46
46
|
"op": "add|mod|del|rename",
|
|
47
|
-
"what": "string",
|
|
48
|
-
"why": "string",
|
|
47
|
+
"what": "string, aim for ≤120 chars",
|
|
48
|
+
"why": "string, aim for ≤120 chars",
|
|
49
49
|
"source": "code|commit|pr|issue|inferred"
|
|
50
50
|
}
|
|
51
51
|
],
|
|
@@ -59,8 +59,8 @@ export const OUTPUT_SCHEMA_BLOCK = `{
|
|
|
59
59
|
"file": "relative/path.ext",
|
|
60
60
|
"line": 87,
|
|
61
61
|
"block": "b1",
|
|
62
|
-
"what": "string",
|
|
63
|
-
"fix": "string"
|
|
62
|
+
"what": "string, aim for ≤120 chars",
|
|
63
|
+
"fix": "string, aim for ≤120 chars"
|
|
64
64
|
}
|
|
65
65
|
],
|
|
66
66
|
"skipped": [
|
|
@@ -76,7 +76,8 @@ const RULES = `- Coverage (hard): every path in the diff MUST appear in "blocks"
|
|
|
76
76
|
- Quality findings must name a concrete cost, not a vague feeling.
|
|
77
77
|
- Signal over volume: prefer fewer high-signal findings. Order findings by severity (high, then nit). Use "nit" sparingly.
|
|
78
78
|
- "blocking": true ONLY if the branch should not merge as-is. Use only with class "risk".
|
|
79
|
-
- Write human-readable strings (intent, title, what, why, fix, notes) in Spanish. Keep enums/ids/paths/JSON keys exactly as in the schema
|
|
79
|
+
- Write human-readable strings (intent, title, what, why, fix, notes) in Spanish. Keep enums/ids/paths/JSON keys exactly as in the schema.
|
|
80
|
+
- Length is a target, not a check: aim for title ≤72 characters and what/why/fix ≤120 characters. Longer text is still valid. Do not rewrite the file only to shorten it.`;
|
|
80
81
|
|
|
81
82
|
function workflowLocal(base, branch) {
|
|
82
83
|
return `Workflow:
|
|
@@ -122,8 +123,8 @@ export function outputSchemaBlock(meta) {
|
|
|
122
123
|
if (remoteUrl) metaLines.push(` "remoteUrl": ${JSON.stringify(remoteUrl)},`);
|
|
123
124
|
metaLines.push(
|
|
124
125
|
` "generatedAt": "<ISO-8601 datetime>",`,
|
|
125
|
-
` "agent": "<
|
|
126
|
-
` "model": "<model id>"`
|
|
126
|
+
` "agent": "<required: product name you are, e.g. Claude Code>",`,
|
|
127
|
+
` "model": "<required: exact model id you are running>"`
|
|
127
128
|
);
|
|
128
129
|
const metaBlock = `"meta": {\n${metaLines.join("\n")}\n }`;
|
|
129
130
|
return OUTPUT_SCHEMA_BLOCK.replace(/"meta": \{[\s\S]*?\n \}/, metaBlock);
|
|
@@ -156,11 +157,11 @@ ${RULES}
|
|
|
156
157
|
Write the result to \`${outputFilename}\` at the repo root.
|
|
157
158
|
- One JSON object only: first character \`{\`, last character \`}\`.
|
|
158
159
|
- No prose before/after, no markdown fences.
|
|
159
|
-
- Shape (copy "meta" source/repo/branch/base/remoteUrl as given;
|
|
160
|
+
- Shape (copy "meta" source/repo/branch/base/remoteUrl as given; "agent" and "model" are required):
|
|
160
161
|
|
|
161
162
|
${outputSchemaBlock(meta)}
|
|
162
163
|
|
|
163
|
-
Include "meta" with the source/repo/branch/base above (and origin "remoteUrl" when local).
|
|
164
|
+
Include "meta" with the source/repo/branch/base above (and origin "remoteUrl" when local). "agent" and "model" are required: set "agent" to the product you are (Claude Code, Cursor, Codex, Copilot, Gemini, …) and "model" to the exact model id you are running. Do not leave placeholders or omit them; validation fails without both. "generatedAt" is optional (ISO-8601 now).
|
|
164
165
|
|
|
165
166
|
Do NOT include diffs or a "files" array. The tool computes them with git when opening the report. Do not invent hunks or paste patches into the JSON.
|
|
166
167
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estampa en el JSON quién generó la review. La CLI lo sabe mejor que el modelo.
|
|
3
|
+
*/
|
|
4
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { decodePayload } from "./json-payload.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} filePath
|
|
9
|
+
* @param {{ agent?: string, model?: string }} provenance
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export function stampReviewProvenance(filePath, { agent, model } = {}) {
|
|
13
|
+
const agentName = String(agent || "").trim();
|
|
14
|
+
const modelId = String(model || "").trim();
|
|
15
|
+
if (!agentName && !modelId) return false;
|
|
16
|
+
|
|
17
|
+
let payload;
|
|
18
|
+
try {
|
|
19
|
+
payload = decodePayload(readFileSync(filePath, "utf8"));
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
|
|
24
|
+
|
|
25
|
+
const current = payload.meta;
|
|
26
|
+
const meta =
|
|
27
|
+
current && typeof current === "object" && !Array.isArray(current) ? { ...current } : {};
|
|
28
|
+
if (agentName) meta.agent = agentName;
|
|
29
|
+
if (modelId) meta.model = modelId;
|
|
30
|
+
payload.meta = meta;
|
|
31
|
+
writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { stampReviewProvenance } from "./review-meta.mjs";
|
|
7
|
+
|
|
8
|
+
test("stampReviewProvenance escribe agente y modelo sin perder el resto", () => {
|
|
9
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-meta-"));
|
|
10
|
+
const file = join(dir, "diff-rev_a_b.json");
|
|
11
|
+
writeFileSync(file, JSON.stringify({ intent: "algo concreto", meta: { branch: "feat" } }));
|
|
12
|
+
|
|
13
|
+
assert.equal(stampReviewProvenance(file, { agent: "Claude Code", model: "opus" }), true);
|
|
14
|
+
|
|
15
|
+
const saved = JSON.parse(readFileSync(file, "utf8"));
|
|
16
|
+
assert.equal(saved.intent, "algo concreto");
|
|
17
|
+
assert.deepEqual(saved.meta, { branch: "feat", agent: "Claude Code", model: "opus" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("stampReviewProvenance no pisa el modelo si no hay uno", () => {
|
|
21
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-meta-"));
|
|
22
|
+
const file = join(dir, "diff-rev_a_b.json");
|
|
23
|
+
writeFileSync(file, JSON.stringify({ meta: { model: "sonnet" }, intent: "x" }));
|
|
24
|
+
|
|
25
|
+
stampReviewProvenance(file, { agent: "Codex" });
|
|
26
|
+
|
|
27
|
+
const saved = JSON.parse(readFileSync(file, "utf8"));
|
|
28
|
+
assert.equal(saved.meta.agent, "Codex");
|
|
29
|
+
assert.equal(saved.meta.model, "sonnet");
|
|
30
|
+
});
|
package/lib/review.test.mjs
CHANGED
|
@@ -132,6 +132,7 @@ test("buildReview deduplica hallazgos repetidos y limpia referencias colgadas",
|
|
|
132
132
|
|
|
133
133
|
test("una respuesta JSON del modelo atraviesa decode, validación y normalización", () => {
|
|
134
134
|
const answer = JSON.stringify({
|
|
135
|
+
meta: { agent: "Claude Code", model: "claude-opus" },
|
|
135
136
|
intent: "Hace que las búsquedas con varias palabras matcheen los tokens en cualquier orden.",
|
|
136
137
|
groups: [
|
|
137
138
|
{ id: "g1", kind: "feat", title: "feat(search): matchear tokens", intent: "el cambio" },
|
package/lib/validate.mjs
CHANGED
|
@@ -13,9 +13,18 @@ export const SKIP_REASONS = ["generated", "lockfile", "format", "vendored", "bin
|
|
|
13
13
|
export const REVIEW_SCHEMA = {
|
|
14
14
|
$id: "diff-review",
|
|
15
15
|
type: "object",
|
|
16
|
-
required: ["intent", "groups", "blocks"],
|
|
16
|
+
required: ["meta", "intent", "groups", "blocks"],
|
|
17
17
|
additionalProperties: true,
|
|
18
18
|
properties: {
|
|
19
|
+
meta: {
|
|
20
|
+
type: "object",
|
|
21
|
+
required: ["agent", "model"],
|
|
22
|
+
additionalProperties: true,
|
|
23
|
+
properties: {
|
|
24
|
+
agent: { type: "string", minLength: 1 },
|
|
25
|
+
model: { type: "string", minLength: 1 },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
19
28
|
intent: { type: "string", minLength: 40 },
|
|
20
29
|
groups: {
|
|
21
30
|
type: "array",
|
|
@@ -45,8 +54,8 @@ export const REVIEW_SCHEMA = {
|
|
|
45
54
|
file: { type: "string", minLength: 1 },
|
|
46
55
|
lines: { type: "string", minLength: 1 },
|
|
47
56
|
op: { type: "string", enum: BLOCK_OPS },
|
|
48
|
-
what: { type: "string", minLength: 1
|
|
49
|
-
why: { type: "string"
|
|
57
|
+
what: { type: "string", minLength: 1 },
|
|
58
|
+
why: { type: "string" },
|
|
50
59
|
source: { type: "string", enum: WHY_SOURCES },
|
|
51
60
|
},
|
|
52
61
|
},
|
|
@@ -66,8 +75,8 @@ export const REVIEW_SCHEMA = {
|
|
|
66
75
|
file: { type: "string", minLength: 1 },
|
|
67
76
|
line: { type: ["integer", "null"] },
|
|
68
77
|
block: { type: "string" },
|
|
69
|
-
what: { type: "string", minLength: 1
|
|
70
|
-
fix: { type: "string"
|
|
78
|
+
what: { type: "string", minLength: 1 },
|
|
79
|
+
fix: { type: "string" },
|
|
71
80
|
},
|
|
72
81
|
},
|
|
73
82
|
},
|
|
@@ -112,9 +121,6 @@ export function validateAgainst(schema, data, path = "$") {
|
|
|
112
121
|
if (actual === "string" && schema.minLength && data.length < schema.minLength) {
|
|
113
122
|
errors.push(`${path}: texto demasiado corto (mínimo ${schema.minLength})`);
|
|
114
123
|
}
|
|
115
|
-
if (actual === "string" && schema.maxLength && data.length > schema.maxLength) {
|
|
116
|
-
errors.push(`${path}: texto demasiado largo (máximo ${schema.maxLength})`);
|
|
117
|
-
}
|
|
118
124
|
if (schema.enum && (actual === "string" || actual === "number") && !schema.enum.includes(data)) {
|
|
119
125
|
errors.push(`${path}: valor no permitido (${data}); usá uno de: ${schema.enum.join(", ")}`);
|
|
120
126
|
}
|
|
@@ -145,6 +151,16 @@ export function validateAgainst(schema, data, path = "$") {
|
|
|
145
151
|
*/
|
|
146
152
|
export function reviewIssues(payload, knownPaths = []) {
|
|
147
153
|
const errors = [];
|
|
154
|
+
const meta = payload?.meta && typeof payload.meta === "object" ? payload.meta : {};
|
|
155
|
+
const agentName = typeof meta.agent === "string" ? meta.agent.trim() : "";
|
|
156
|
+
const modelId = typeof meta.model === "string" ? meta.model.trim() : "";
|
|
157
|
+
if (!agentName || agentName.startsWith("<")) {
|
|
158
|
+
errors.push("meta.agent es obligatorio: nombre del producto (Claude Code, Cursor, Codex, …), sin placeholder");
|
|
159
|
+
}
|
|
160
|
+
if (!modelId || modelId.startsWith("<")) {
|
|
161
|
+
errors.push("meta.model es obligatorio: id exacto del modelo, sin placeholder");
|
|
162
|
+
}
|
|
163
|
+
|
|
148
164
|
const groups = Array.isArray(payload?.groups) ? payload.groups : [];
|
|
149
165
|
const blocks = Array.isArray(payload?.blocks) ? payload.blocks : [];
|
|
150
166
|
const findings = Array.isArray(payload?.findings) ? payload.findings : [];
|
|
@@ -202,9 +218,10 @@ TEXT LANGUAGE: Spanish (rioplatense, technical) for intent, groups.intent, block
|
|
|
202
218
|
|
|
203
219
|
{
|
|
204
220
|
"intent": "2-4 sentences on what the branch solves",
|
|
205
|
-
"groups": [{ "id": "g1", "kind": "feat", "title": "scope: summary
|
|
206
|
-
"
|
|
207
|
-
"
|
|
221
|
+
"groups": [{ "id": "g1", "kind": "feat", "title": "scope: summary (do not repeat kind)", "intent": "why this theme exists" }],
|
|
222
|
+
"meta": { "agent": "<required: product name, e.g. Claude Code>", "model": "<required: exact model id>" },
|
|
223
|
+
"blocks": [{ "id": "b1", "group": "g1", "file": "exact/path.ts", "lines": "L40-58", "op": "mod", "what": "what changed", "why": "why", "source": "code" }],
|
|
224
|
+
"findings": [{ "id": "f1", "class": "risk", "severity": "high", "blocking": true, "kind": "bug", "file": "path.ts", "line": 42, "block": "b1", "what": "what is wrong", "fix": "what to do" }],
|
|
208
225
|
"skipped": [{ "file": "bun.lock", "reason": "lockfile" }]
|
|
209
226
|
}
|
|
210
227
|
|
package/lib/validate.test.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import { REVIEW_SCHEMA, reviewIssues, jsonContract, validateAgainst } from "./validate.mjs";
|
|
4
4
|
|
|
5
5
|
const valid = () => ({
|
|
6
|
+
meta: { agent: "Claude Code", model: "claude-opus" },
|
|
6
7
|
intent: "Agrega el ABM de depósitos locales end-to-end para dejar de administrarlos por SQL.",
|
|
7
8
|
groups: [{ id: "g1", kind: "feat", title: "feat(product): CRUD de depósitos", intent: "base de todo" }],
|
|
8
9
|
blocks: [
|
|
@@ -16,6 +17,30 @@ test("validateAgainst acepta una review bien formada", () => {
|
|
|
16
17
|
assert.deepEqual(validateAgainst(REVIEW_SCHEMA, valid()), []);
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
test("validateAgainst exige meta.agent y meta.model", () => {
|
|
21
|
+
const missing = valid();
|
|
22
|
+
delete missing.meta;
|
|
23
|
+
assert.ok(validateAgainst(REVIEW_SCHEMA, missing).some((e) => e.includes("meta")));
|
|
24
|
+
assert.ok(reviewIssues(missing).some((e) => e.includes("meta.agent")));
|
|
25
|
+
assert.ok(reviewIssues(missing).some((e) => e.includes("meta.model")));
|
|
26
|
+
|
|
27
|
+
const blank = valid();
|
|
28
|
+
blank.meta = { agent: " ", model: "<required: exact model id>" };
|
|
29
|
+
assert.ok(reviewIssues(blank).some((e) => e.includes("meta.agent")));
|
|
30
|
+
assert.ok(reviewIssues(blank).some((e) => e.includes("meta.model")));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("validateAgainst no limita el largo de what, why ni fix", () => {
|
|
34
|
+
const doc = valid();
|
|
35
|
+
const long = "x".repeat(500);
|
|
36
|
+
doc.blocks[0].what = long;
|
|
37
|
+
doc.blocks[0].why = long;
|
|
38
|
+
doc.findings = [
|
|
39
|
+
{ id: "f1", class: "risk", severity: "high", blocking: true, kind: "bug", file: "src/a.ts", line: 4, what: long, fix: long },
|
|
40
|
+
];
|
|
41
|
+
assert.deepEqual(validateAgainst(REVIEW_SCHEMA, doc), []);
|
|
42
|
+
});
|
|
43
|
+
|
|
19
44
|
test("validateAgainst exige una intención de verdad", () => {
|
|
20
45
|
const errors = validateAgainst(REVIEW_SCHEMA, { ...valid(), intent: "corto" });
|
|
21
46
|
assert.ok(errors.some((e) => e.includes("intent")));
|
package/lib/wait-output.test.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { describe, it } from "node:test";
|
|
|
6
6
|
import { existingReviewOutput, waitForReviewOutput } from "./wait-output.mjs";
|
|
7
7
|
|
|
8
8
|
const valid = () => ({
|
|
9
|
+
meta: { agent: "Codex", model: "gpt-5" },
|
|
9
10
|
intent: "Agrega el ABM de depósitos locales end-to-end para dejar de administrarlos por SQL.",
|
|
10
11
|
groups: [{ id: "g1", kind: "feat", title: "feat(product): CRUD de depósitos", intent: "base de todo" }],
|
|
11
12
|
blocks: [
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
"title": "Diff Review Document",
|
|
5
5
|
"description": "JSON que escribe el agente (meta/intent/groups/blocks/findings/skipped). La UI completa files con git al abrir el reporte; no hace falta que el modelo incluya diffs.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": ["intent", "groups", "blocks", "findings"],
|
|
7
|
+
"required": ["meta", "intent", "groups", "blocks", "findings"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"meta": {
|
|
10
10
|
"type": "object",
|
|
11
11
|
"description": "Contexto del repo para reabrir el reporte sin rellenar el formulario. Copiá source/repo/branch/base del prompt.",
|
|
12
|
-
"required": ["source", "repo", "branch", "base"],
|
|
12
|
+
"required": ["source", "repo", "branch", "base", "agent", "model"],
|
|
13
13
|
"properties": {
|
|
14
14
|
"source": { "type": "string", "enum": ["local", "url"] },
|
|
15
15
|
"repo": {
|
|
@@ -28,11 +28,13 @@
|
|
|
28
28
|
},
|
|
29
29
|
"agent": {
|
|
30
30
|
"type": "string",
|
|
31
|
-
"
|
|
31
|
+
"minLength": 1,
|
|
32
|
+
"description": "Nombre del producto que escribió la review, ej. Claude Code o Cursor. Obligatorio."
|
|
32
33
|
},
|
|
33
34
|
"model": {
|
|
34
35
|
"type": "string",
|
|
35
|
-
"
|
|
36
|
+
"minLength": 1,
|
|
37
|
+
"description": "Id exacto del modelo que escribió la review. Obligatorio."
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
},
|
|
@@ -57,10 +59,9 @@
|
|
|
57
59
|
},
|
|
58
60
|
"title": {
|
|
59
61
|
"type": "string",
|
|
60
|
-
"maxLength": 72,
|
|
61
62
|
"description": "Resumen del tema en una línea sin repetir kind (el chip ya lo muestra), ej. 'warehouses: CRUD de depósitos locales' o 'product: alta de depósitos'."
|
|
62
63
|
},
|
|
63
|
-
"intent": { "type": "string", "
|
|
64
|
+
"intent": { "type": "string", "description": "Por qué existe este tema." }
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
},
|
|
@@ -86,12 +87,10 @@
|
|
|
86
87
|
"op": { "type": "string", "enum": ["add", "mod", "del", "rename"] },
|
|
87
88
|
"what": {
|
|
88
89
|
"type": "string",
|
|
89
|
-
"maxLength": 200,
|
|
90
90
|
"description": "Qué cambia este bloque."
|
|
91
91
|
},
|
|
92
92
|
"why": {
|
|
93
93
|
"type": "string",
|
|
94
|
-
"maxLength": 200,
|
|
95
94
|
"description": "Por qué. Vacío solo si no es inferible."
|
|
96
95
|
},
|
|
97
96
|
"source": {
|
|
@@ -123,8 +122,8 @@
|
|
|
123
122
|
"file": { "type": "string" },
|
|
124
123
|
"line": { "type": ["integer", "null"], "description": "Línea de la post-imagen (columna derecha / @@ +N)." },
|
|
125
124
|
"block": { "type": ["string", "null"], "description": "Id del bloque, o vacío/null si es transversal." },
|
|
126
|
-
"what": { "type": "string"
|
|
127
|
-
"fix": { "type": "string", "
|
|
125
|
+
"what": { "type": "string" },
|
|
126
|
+
"fix": { "type": "string", "description": "Acción concreta." }
|
|
128
127
|
}
|
|
129
128
|
}
|
|
130
129
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.page.svelte-1uha8ag{flex-direction:column;min-height:100vh;display:flex}.diff-backdrop.svelte-1uha8ag{z-index:80;background:color-mix(in srgb, var(--bg) 88%, transparent);-webkit-backdrop-filter:blur(28px);place-items:center;padding:24px;display:grid;position:fixed;inset:0}.diff-modal.svelte-1uha8ag{background:var(--bg-card);border:1px solid var(--border);text-align:center;width:min(380px,100%);padding:28px 24px;box-shadow:0 16px 48px #00000047}.diff-modal.svelte-1uha8ag p:where(.svelte-1uha8ag){color:var(--text);margin:14px 0 0;font-size:15px}.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}@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}.command-box.svelte-1uha8ag{background:var(--bg);border:1px solid var(--border);padding:10px 12px;overflow:auto}.command-box.svelte-1uha8ag code:where(.svelte-1uha8ag){font-family:var(--mono);color:var(--text);white-space:nowrap;font-size:12.5px}.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}button.ghost.svelte-1uha8ag{color:var(--text-dim);background:0 0}.skill-block.svelte-1uha8ag{border-top:1px solid var(--border);margin-top:16px;padding-top:14px}.skill-block.svelte-1uha8ag h3:where(.svelte-1uha8ag){letter-spacing:.06em;text-transform:uppercase;color:var(--text-faint);margin:0 0 4px;font-size:11.5px;font-weight:650}.skill-buttons.svelte-1uha8ag{flex-wrap:wrap;gap:8px;display:flex}.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:30;border:1px solid var(--border);background:var(--bg-card);min-width:140px;padding:4px;position:absolute;top:auto;bottom:calc(100% - 2px);right:8px;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}.when.svelte-1uha8ag{color:var(--text-dim);font-size:12px}.report-meta.svelte-1uha8ag{color:var(--text-dim);flex-wrap:wrap;align-items:center;gap:8px;margin-top:6px;font-size:12px;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}}
|
|
1
|
+
.page.svelte-1uha8ag{flex-direction:column;min-height:100vh;display:flex}.diff-backdrop.svelte-1uha8ag{z-index:80;background:color-mix(in srgb, var(--bg) 88%, transparent);-webkit-backdrop-filter:blur(28px);place-items:center;padding:24px;display:grid;position:fixed;inset:0}.diff-modal.svelte-1uha8ag{background:var(--bg-card);border:1px solid var(--border);text-align:center;width:min(380px,100%);padding:28px 24px;box-shadow:0 16px 48px #00000047}.diff-modal.svelte-1uha8ag p:where(.svelte-1uha8ag){color:var(--text);margin:14px 0 0;font-size:15px}.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}@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}.command-box.svelte-1uha8ag{background:var(--bg);border:1px solid var(--border);padding:10px 12px;overflow:auto}.command-box.svelte-1uha8ag code:where(.svelte-1uha8ag){font-family:var(--mono);color:var(--text);white-space:nowrap;font-size:12.5px}.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}button.ghost.svelte-1uha8ag{color:var(--text-dim);background:0 0}.skill-block.svelte-1uha8ag{border-top:1px solid var(--border);margin-top:16px;padding-top:14px}.skill-block.svelte-1uha8ag h3:where(.svelte-1uha8ag){letter-spacing:.06em;text-transform:uppercase;color:var(--text-faint);margin:0 0 4px;font-size:11.5px;font-weight:650}.skill-buttons.svelte-1uha8ag{flex-wrap:wrap;gap:8px;display:flex}.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}}
|
|
Binary file
|
|
Binary file
|