@jpkme/diff-review 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/diff-review.mjs +72 -53
- package/lib/acp-policy.mjs +99 -12
- package/lib/acp-policy.test.mjs +56 -3
- package/lib/acp-shell.mjs +38 -8
- package/lib/acp-shell.test.mjs +32 -1
- package/lib/agent-login.mjs +105 -105
- package/lib/check-report.mjs +1 -0
- package/lib/check-report.test.mjs +1 -0
- package/lib/extract-cache.test.mjs +162 -162
- package/lib/model-label.mjs +263 -263
- package/lib/model-label.test.mjs +77 -77
- package/lib/models-cache.mjs +75 -75
- package/lib/models-cache.test.mjs +50 -50
- package/lib/prompt.mjs +8 -6
- package/lib/review-prep.mjs +63 -63
- package/package.json +1 -1
- package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css → 0.BKRG1f36.css} +1 -1
- package/web/build/client/_app/immutable/assets/0.BKRG1f36.css.br +0 -0
- package/web/build/client/_app/immutable/assets/{0.CLQiq3Ed.css.gz → 0.BKRG1f36.css.gz} +0 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css +1 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.C4whE4xz.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css +1 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.BT54aVKb.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.C8XGESwn.css +1 -0
- package/web/build/client/_app/immutable/assets/storage.C8XGESwn.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.C8XGESwn.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/C0hrdiUt.js +2 -0
- package/web/build/client/_app/immutable/chunks/C0hrdiUt.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/C0hrdiUt.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/CpeIO6nz.js +210 -0
- package/web/build/client/_app/immutable/chunks/CpeIO6nz.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/CpeIO6nz.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/{CPyWH418.js → FOc6rgIW.js} +1 -1
- package/web/build/client/_app/immutable/chunks/FOc6rgIW.js.br +2 -0
- package/web/build/client/_app/immutable/chunks/FOc6rgIW.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/{app.BiBohpyO.js → app.DcAtSJ3n.js} +2 -2
- package/web/build/client/_app/immutable/entry/app.DcAtSJ3n.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.DcAtSJ3n.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.CO5l0Zbx.js +1 -0
- package/web/build/client/_app/immutable/entry/start.CO5l0Zbx.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.CO5l0Zbx.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/{1.BzPXaJJj.js → 1.E-Hs7ura.js} +1 -1
- package/web/build/client/_app/immutable/nodes/1.E-Hs7ura.js.br +4 -0
- package/web/build/client/_app/immutable/nodes/1.E-Hs7ura.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.s1_irV1Q.js +10 -0
- package/web/build/client/_app/immutable/nodes/2.s1_irV1Q.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.s1_irV1Q.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.pgwe-_5b.js +171 -0
- package/web/build/client/_app/immutable/nodes/3.pgwe-_5b.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.pgwe-_5b.js.gz +0 -0
- package/web/build/client/_app/version.json +1 -1
- package/web/build/client/_app/version.json.br +0 -0
- package/web/build/client/_app/version.json.gz +0 -0
- package/web/build/handler.js +3 -3
- package/web/build/index.js +3 -3
- package/web/build/server/chunks/{0-BYok4D0n.js → 0-DUStDOsr.js} +3 -3
- package/web/build/server/chunks/{0-BYok4D0n.js.map → 0-DUStDOsr.js.map} +1 -1
- package/web/build/server/chunks/1-BFhhV5JB.js +7 -0
- package/web/build/server/chunks/{1-C7zcB0q0.js.map → 1-BFhhV5JB.js.map} +1 -1
- package/web/build/server/chunks/2-BZ_MVqj8.js +7 -0
- package/web/build/server/chunks/2-BZ_MVqj8.js.map +1 -0
- package/web/build/server/chunks/3-BV-TIUp1.js +7 -0
- package/web/build/server/chunks/{3-DAAmG6VX.js.map → 3-BV-TIUp1.js.map} +1 -1
- package/web/build/server/chunks/{_server.ts-qYVrMYPB.js → _server.ts-CdFoXw36.js} +4 -4
- package/web/build/server/chunks/{_server.ts-qYVrMYPB.js.map → _server.ts-CdFoXw36.js.map} +1 -1
- package/web/build/server/chunks/{agent-sessions-DSqGavSn.js → agent-sessions-D0WwtWgR.js} +223 -51
- package/web/build/server/chunks/agent-sessions-D0WwtWgR.js.map +1 -0
- package/web/build/server/chunks/{git-review-CpO_G8z8.js → git-review-CgcBIbvM.js} +7 -7
- package/web/build/server/chunks/{git-review-CpO_G8z8.js.map → git-review-CgcBIbvM.js.map} +1 -1
- package/web/build/server/chunks/{handler-Di1wJQuU.js → handler-DbxjtK9M.js} +3 -3
- package/web/build/server/chunks/{handler-Di1wJQuU.js.map → handler-DbxjtK9M.js.map} +1 -1
- package/web/build/server/chunks/{hooks.server-BLOHobEK.js → hooks.server-Dg6gYUqs.js} +3 -3
- package/web/build/server/chunks/{hooks.server-BLOHobEK.js.map → hooks.server-Dg6gYUqs.js.map} +1 -1
- package/web/build/server/chunks/{index.js-yLYmrAWy.js → index.js-NNne-njN.js} +876 -879
- package/web/build/server/chunks/index.js-NNne-njN.js.map +1 -0
- package/web/build/server/chunks/{manifest.js-CXj8IIo3.js → manifest.js-V8ViI5bl.js} +9 -9
- package/web/build/server/chunks/{manifest.js-CXj8IIo3.js.map → manifest.js-V8ViI5bl.js.map} +1 -1
- package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js → remote-1g4ayag-CCsG2QXw.js} +4 -4
- package/web/build/server/chunks/{remote-1g4ayag-BiJeU0Pk.js.map → remote-1g4ayag-CCsG2QXw.js.map} +1 -1
- package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js → remote-wh6t6j-BCvmX37z.js} +5 -5
- package/web/build/server/chunks/{remote-wh6t6j-CHanXurm.js.map → remote-wh6t6j-BCvmX37z.js.map} +1 -1
- package/web/build/server/chunks/{validate-BsTKyhsq.js → validate-CAQ31C7k.js} +2 -2
- package/web/build/server/chunks/{validate-BsTKyhsq.js.map → validate-CAQ31C7k.js.map} +1 -1
- package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css +0 -1
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CKE12GwZ.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css +0 -1
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.Dx3c_e7d.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css +0 -1
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.br +0 -0
- package/web/build/client/_app/immutable/assets/storage.gDV-BhtT.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js +0 -2
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/B5ByWcPE.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/CPyWH418.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/CPyWH418.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js +0 -210
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/NwkMsx0h.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.BiBohpyO.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js +0 -1
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.br +0 -0
- package/web/build/client/_app/immutable/entry/start.CjWbsiSO.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.br +0 -2
- package/web/build/client/_app/immutable/nodes/1.BzPXaJJj.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js +0 -10
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.Cp6Ttd_9.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js +0 -171
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.DLiDQm__.js.gz +0 -0
- package/web/build/server/chunks/1-C7zcB0q0.js +0 -7
- package/web/build/server/chunks/2-v6ejI_0-.js +0 -7
- package/web/build/server/chunks/2-v6ejI_0-.js.map +0 -1
- package/web/build/server/chunks/3-DAAmG6VX.js +0 -7
- package/web/build/server/chunks/agent-sessions-DSqGavSn.js.map +0 -1
- package/web/build/server/chunks/index.js-yLYmrAWy.js.map +0 -1
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js → 0.Dj97JW46.js} +0 -0
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.br → 0.Dj97JW46.js.br} +0 -0
- /package/web/build/client/_app/immutable/nodes/{0.Cak4ehiE.js.gz → 0.Dj97JW46.js.gz} +0 -0
package/lib/agent-login.mjs
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Login de agentes ACP: mensajes claros + abrir una terminal interactiva cuando se pueda.
|
|
3
|
-
*/
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {{ bin: string, args: string[], display: string }} LoginCommand
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Comando de login interactivo por agente (null = no hay CLI de login, p. ej. Cursor app).
|
|
12
|
-
* @param {string} agentId
|
|
13
|
-
* @returns {LoginCommand | null}
|
|
14
|
-
*/
|
|
15
|
-
export function loginCommand(agentId) {
|
|
16
|
-
switch (agentId) {
|
|
17
|
-
case "claude":
|
|
18
|
-
return { bin: "claude", args: ["/login"], display: "claude /login" };
|
|
19
|
-
case "copilot":
|
|
20
|
-
return { bin: "gh", args: ["auth", "login"], display: "gh auth login" };
|
|
21
|
-
case "codex":
|
|
22
|
-
return { bin: "codex", args: ["login"], display: "codex login" };
|
|
23
|
-
case "gemini":
|
|
24
|
-
return { bin: "gemini", args: [], display: "gemini" }; // el CLI guía el auth al arrancar
|
|
25
|
-
default:
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** @param {string} text */
|
|
31
|
-
export function looksLikeAuthFailure(text) {
|
|
32
|
-
const s = String(text || "").toLowerCase();
|
|
33
|
-
return /(not\s+)?auth|login|unauthor|not logged|sign[\s-]?in|credentials|token.*(invalid|expired|missing)|please\s+run/.test(
|
|
34
|
-
s
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Mensaje en español si el agente murió / falló por falta de login.
|
|
40
|
-
* @param {string} agentId
|
|
41
|
-
* @param {string} agentLabel
|
|
42
|
-
* @param {string} detail
|
|
43
|
-
* @returns {string | null}
|
|
44
|
-
*/
|
|
45
|
-
export function authFailureMessage(agentId, agentLabel, detail) {
|
|
46
|
-
if (!looksLikeAuthFailure(detail)) return null;
|
|
47
|
-
const cmd = loginCommand(agentId);
|
|
48
|
-
if (agentId === "cursor") {
|
|
49
|
-
return `${agentLabel} no está autenticado. Iniciá sesión en la app de Cursor y reintentá.`;
|
|
50
|
-
}
|
|
51
|
-
if (cmd) {
|
|
52
|
-
return `${agentLabel} no está autenticado. Corré \`${cmd.display}\` en una terminal y reintentá.`;
|
|
53
|
-
}
|
|
54
|
-
return `${agentLabel} no está autenticado. Revisá el login del agente y reintentá.`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Abre una terminal nueva con el login del agente (Windows / macOS / Linux).
|
|
59
|
-
* @param {string} agentId
|
|
60
|
-
* @returns {{ display: string }}
|
|
61
|
-
*/
|
|
62
|
-
export function openLoginTerminal(agentId) {
|
|
63
|
-
const cmd = loginCommand(agentId);
|
|
64
|
-
if (!cmd) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
agentId === "cursor"
|
|
67
|
-
? "Cursor se autentica desde la app de escritorio, no desde una terminal."
|
|
68
|
-
: "Este agente no tiene un comando de login conocido."
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const platform = process.platform;
|
|
73
|
-
if (platform === "win32") {
|
|
74
|
-
// `start "title" cmd /k …` — el primer quoted es el título de la ventana.
|
|
75
|
-
spawn("cmd.exe", ["/c", "start", `${cmd.bin} login`, "cmd.exe", "/k", cmd.bin, ...cmd.args], {
|
|
76
|
-
detached: true,
|
|
77
|
-
stdio: "ignore",
|
|
78
|
-
windowsHide: false,
|
|
79
|
-
}).unref();
|
|
80
|
-
} else if (platform === "darwin") {
|
|
81
|
-
const script = `tell application "Terminal" to do script ${JSON.stringify(`${cmd.bin} ${cmd.args.join(" ")}`.trim())}`;
|
|
82
|
-
spawn("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
|
|
83
|
-
} else {
|
|
84
|
-
const line = `${cmd.bin} ${cmd.args.join(" ")}`.trim();
|
|
85
|
-
const tried = [
|
|
86
|
-
["x-terminal-emulator", ["-e", "bash", "-lc", `${line}; exec bash`]],
|
|
87
|
-
["gnome-terminal", ["--", "bash", "-lc", `${line}; exec bash`]],
|
|
88
|
-
["konsole", ["-e", "bash", "-lc", `${line}; exec bash`]],
|
|
89
|
-
["xterm", ["-e", "bash", "-lc", `${line}; exec bash`]],
|
|
90
|
-
];
|
|
91
|
-
let ok = false;
|
|
92
|
-
for (const [bin, args] of tried) {
|
|
93
|
-
try {
|
|
94
|
-
spawn(bin, args, { detached: true, stdio: "ignore" }).unref();
|
|
95
|
-
ok = true;
|
|
96
|
-
break;
|
|
97
|
-
} catch {
|
|
98
|
-
/* siguiente */
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (!ok) throw new Error(`No pude abrir una terminal. Corré a mano: ${cmd.display}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return { display: cmd.display };
|
|
105
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Login de agentes ACP: mensajes claros + abrir una terminal interactiva cuando se pueda.
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {{ bin: string, args: string[], display: string }} LoginCommand
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Comando de login interactivo por agente (null = no hay CLI de login, p. ej. Cursor app).
|
|
12
|
+
* @param {string} agentId
|
|
13
|
+
* @returns {LoginCommand | null}
|
|
14
|
+
*/
|
|
15
|
+
export function loginCommand(agentId) {
|
|
16
|
+
switch (agentId) {
|
|
17
|
+
case "claude":
|
|
18
|
+
return { bin: "claude", args: ["/login"], display: "claude /login" };
|
|
19
|
+
case "copilot":
|
|
20
|
+
return { bin: "gh", args: ["auth", "login"], display: "gh auth login" };
|
|
21
|
+
case "codex":
|
|
22
|
+
return { bin: "codex", args: ["login"], display: "codex login" };
|
|
23
|
+
case "gemini":
|
|
24
|
+
return { bin: "gemini", args: [], display: "gemini" }; // el CLI guía el auth al arrancar
|
|
25
|
+
default:
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** @param {string} text */
|
|
31
|
+
export function looksLikeAuthFailure(text) {
|
|
32
|
+
const s = String(text || "").toLowerCase();
|
|
33
|
+
return /(not\s+)?auth|login|unauthor|not logged|sign[\s-]?in|credentials|token.*(invalid|expired|missing)|please\s+run/.test(
|
|
34
|
+
s
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Mensaje en español si el agente murió / falló por falta de login.
|
|
40
|
+
* @param {string} agentId
|
|
41
|
+
* @param {string} agentLabel
|
|
42
|
+
* @param {string} detail
|
|
43
|
+
* @returns {string | null}
|
|
44
|
+
*/
|
|
45
|
+
export function authFailureMessage(agentId, agentLabel, detail) {
|
|
46
|
+
if (!looksLikeAuthFailure(detail)) return null;
|
|
47
|
+
const cmd = loginCommand(agentId);
|
|
48
|
+
if (agentId === "cursor") {
|
|
49
|
+
return `${agentLabel} no está autenticado. Iniciá sesión en la app de Cursor y reintentá.`;
|
|
50
|
+
}
|
|
51
|
+
if (cmd) {
|
|
52
|
+
return `${agentLabel} no está autenticado. Corré \`${cmd.display}\` en una terminal y reintentá.`;
|
|
53
|
+
}
|
|
54
|
+
return `${agentLabel} no está autenticado. Revisá el login del agente y reintentá.`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Abre una terminal nueva con el login del agente (Windows / macOS / Linux).
|
|
59
|
+
* @param {string} agentId
|
|
60
|
+
* @returns {{ display: string }}
|
|
61
|
+
*/
|
|
62
|
+
export function openLoginTerminal(agentId) {
|
|
63
|
+
const cmd = loginCommand(agentId);
|
|
64
|
+
if (!cmd) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
agentId === "cursor"
|
|
67
|
+
? "Cursor se autentica desde la app de escritorio, no desde una terminal."
|
|
68
|
+
: "Este agente no tiene un comando de login conocido."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const platform = process.platform;
|
|
73
|
+
if (platform === "win32") {
|
|
74
|
+
// `start "title" cmd /k …` — el primer quoted es el título de la ventana.
|
|
75
|
+
spawn("cmd.exe", ["/c", "start", `${cmd.bin} login`, "cmd.exe", "/k", cmd.bin, ...cmd.args], {
|
|
76
|
+
detached: true,
|
|
77
|
+
stdio: "ignore",
|
|
78
|
+
windowsHide: false,
|
|
79
|
+
}).unref();
|
|
80
|
+
} else if (platform === "darwin") {
|
|
81
|
+
const script = `tell application "Terminal" to do script ${JSON.stringify(`${cmd.bin} ${cmd.args.join(" ")}`.trim())}`;
|
|
82
|
+
spawn("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
|
|
83
|
+
} else {
|
|
84
|
+
const line = `${cmd.bin} ${cmd.args.join(" ")}`.trim();
|
|
85
|
+
const tried = [
|
|
86
|
+
["x-terminal-emulator", ["-e", "bash", "-lc", `${line}; exec bash`]],
|
|
87
|
+
["gnome-terminal", ["--", "bash", "-lc", `${line}; exec bash`]],
|
|
88
|
+
["konsole", ["-e", "bash", "-lc", `${line}; exec bash`]],
|
|
89
|
+
["xterm", ["-e", "bash", "-lc", `${line}; exec bash`]],
|
|
90
|
+
];
|
|
91
|
+
let ok = false;
|
|
92
|
+
for (const [bin, args] of tried) {
|
|
93
|
+
try {
|
|
94
|
+
spawn(bin, args, { detached: true, stdio: "ignore" }).unref();
|
|
95
|
+
ok = true;
|
|
96
|
+
break;
|
|
97
|
+
} catch {
|
|
98
|
+
/* siguiente */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!ok) throw new Error(`No pude abrir una terminal. Corré a mano: ${cmd.display}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { display: cmd.display };
|
|
105
|
+
}
|
package/lib/check-report.mjs
CHANGED
|
@@ -177,6 +177,7 @@ export function formatCheckReport(file, command, result) {
|
|
|
177
177
|
"How to continue:",
|
|
178
178
|
"- Fix every problem above in place. Keep existing meta/intent/groups/blocks/findings; only change what is wrong or missing.",
|
|
179
179
|
'- Every diff path must appear in "blocks" or "skipped" (a skipped glob counts).',
|
|
180
|
+
"- If a problem lists missing paths: ADD a block or a skipped entry for each exact path. Do not only bump meta.generatedAt, notes, or claim the file is already complete.",
|
|
180
181
|
"- meta.agent and meta.model are required (product name and the exact model id you are running).",
|
|
181
182
|
"- intent ≥40 chars; enums exactly as in the schema.",
|
|
182
183
|
"- Human-readable strings in Spanish.",
|
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { afterEach, test } from "node:test";
|
|
6
|
-
import { extractBranchDiff, parseGitDiffFiles } from "./extract.mjs";
|
|
7
|
-
import { OUTPUT_DIR } from "./output-name.mjs";
|
|
8
|
-
import { companionPaths, ensureReviewDir, writeDiffText } from "./review-dir.mjs";
|
|
9
|
-
import { checkReportFile } from "./check-report.mjs";
|
|
10
|
-
|
|
11
|
-
const sample = [
|
|
12
|
-
"diff --git a/src/a.ts b/src/a.ts",
|
|
13
|
-
"index 111..222 100644",
|
|
14
|
-
"--- a/src/a.ts",
|
|
15
|
-
"+++ b/src/a.ts",
|
|
16
|
-
"@@ -1,2 +1,3 @@",
|
|
17
|
-
" keep",
|
|
18
|
-
"-old",
|
|
19
|
-
"+new",
|
|
20
|
-
"+extra",
|
|
21
|
-
"diff --git a/package-lock.json b/package-lock.json",
|
|
22
|
-
"index 1..2 100644",
|
|
23
|
-
"--- a/package-lock.json",
|
|
24
|
-
"+++ b/package-lock.json",
|
|
25
|
-
"@@ -1 +1 @@",
|
|
26
|
-
"-{}",
|
|
27
|
-
"+{ }",
|
|
28
|
-
"diff --git a/old.ts b/new.ts",
|
|
29
|
-
"similarity index 90%",
|
|
30
|
-
"rename from old.ts",
|
|
31
|
-
"rename to new.ts",
|
|
32
|
-
"index 1..2 100644",
|
|
33
|
-
"--- a/old.ts",
|
|
34
|
-
"+++ b/new.ts",
|
|
35
|
-
"@@ -1 +1 @@",
|
|
36
|
-
"-x",
|
|
37
|
-
"+y",
|
|
38
|
-
].join("\n");
|
|
39
|
-
|
|
40
|
-
const dirs = [];
|
|
41
|
-
afterEach(() => {
|
|
42
|
-
for (const d of dirs.splice(0)) {
|
|
43
|
-
try {
|
|
44
|
-
rmSync(d, { recursive: true, force: true });
|
|
45
|
-
} catch {
|
|
46
|
-
/* ignore */
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("parseGitDiffFiles splits paths and change types", () => {
|
|
52
|
-
const files = parseGitDiffFiles(sample);
|
|
53
|
-
assert.equal(files.length, 3);
|
|
54
|
-
assert.equal(files[0].path, "src/a.ts");
|
|
55
|
-
assert.equal(files[0].changeType, "modified");
|
|
56
|
-
assert.match(files[0].hunkDiff, /^@@/);
|
|
57
|
-
assert.equal(files[1].path, "package-lock.json");
|
|
58
|
-
assert.equal(files[2].changeType, "renamed");
|
|
59
|
-
assert.equal(files[2].path, "new.ts");
|
|
60
|
-
assert.equal(files[2].oldPath, "old.ts");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("extractBranchDiff with cachedDiff applies DEFAULT_IGNORE", () => {
|
|
64
|
-
const repo = mkdtempSync(join(tmpdir(), "diff-review-cache-"));
|
|
65
|
-
dirs.push(repo);
|
|
66
|
-
mkdirSync(join(repo, "src"), { recursive: true });
|
|
67
|
-
// repo path only used for config/guidelines; no git needed when cachedDiff is set
|
|
68
|
-
const skeleton = extractBranchDiff({
|
|
69
|
-
repo,
|
|
70
|
-
branch: "feature",
|
|
71
|
-
base: "develop",
|
|
72
|
-
cachedDiff: sample,
|
|
73
|
-
});
|
|
74
|
-
assert.deepEqual(
|
|
75
|
-
skeleton.files.map((f) => f.path).sort(),
|
|
76
|
-
["new.ts", "src/a.ts"]
|
|
77
|
-
);
|
|
78
|
-
assert.ok(skeleton.context.ignored.includes("package-lock.json"));
|
|
79
|
-
assert.match(skeleton.files.find((f) => f.path === "src/a.ts").diff, /^@@/);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("checkReportFile reuses companion .diff paths without inventing coverage", () => {
|
|
83
|
-
const repo = mkdtempSync(join(tmpdir(), "diff-review-check-"));
|
|
84
|
-
dirs.push(repo);
|
|
85
|
-
ensureReviewDir(repo);
|
|
86
|
-
const filename = "diff-rev_aaaaaaaaaa_bbbbbbbbbb.json";
|
|
87
|
-
writeDiffText(repo, filename, sample);
|
|
88
|
-
const payload = {
|
|
89
|
-
meta: {
|
|
90
|
-
agent: "Cursor",
|
|
91
|
-
model: "composer",
|
|
92
|
-
repo,
|
|
93
|
-
branch: "feature",
|
|
94
|
-
base: "develop",
|
|
95
|
-
},
|
|
96
|
-
intent: "Cambia src/a.ts y renombra old.ts para dejar el módulo listo en la rama.",
|
|
97
|
-
groups: [{ id: "g1", kind: "feat", title: "Ajuste", intent: "base" }],
|
|
98
|
-
blocks: [
|
|
99
|
-
{
|
|
100
|
-
id: "b1",
|
|
101
|
-
group: "g1",
|
|
102
|
-
file: "src/a.ts",
|
|
103
|
-
lines: "L1-3",
|
|
104
|
-
op: "mod",
|
|
105
|
-
what: "cambia la lógica",
|
|
106
|
-
why: "hace falta",
|
|
107
|
-
source: "code",
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
id: "b2",
|
|
111
|
-
group: "g1",
|
|
112
|
-
file: "new.ts",
|
|
113
|
-
lines: "L1",
|
|
114
|
-
op: "rename",
|
|
115
|
-
what: "renombra el módulo",
|
|
116
|
-
why: "nombre más claro",
|
|
117
|
-
source: "code",
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
findings: [],
|
|
121
|
-
skipped: [{ file: "package-lock.json", reason: "lockfile" }],
|
|
122
|
-
};
|
|
123
|
-
const jsonPath = companionPaths(repo, filename).json;
|
|
124
|
-
writeFileSync(jsonPath, JSON.stringify(payload));
|
|
125
|
-
// Sin git: loadBranchDiff lee el .diff y aplica ignore (lockfile fuera de paths).
|
|
126
|
-
const result = checkReportFile(jsonPath, { repo, branch: "feature", base: "develop" });
|
|
127
|
-
assert.equal(result.ok, true, result.errors.join("; "));
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("buildPrompt with precomputed context skips merge-base rediscovery", async () => {
|
|
131
|
-
const { buildPrompt } = await import("./prompt.mjs");
|
|
132
|
-
const text = buildPrompt(
|
|
133
|
-
{ source: "local", repo: "/tmp/r", branch: "feat", base: "develop" },
|
|
134
|
-
{
|
|
135
|
-
outputFilename: "diff-rev_aaaaaaaaaa_bbbbbbbbbb.json",
|
|
136
|
-
diffRelPath: ".diff-review/diff-rev_aaaaaaaaaa_bbbbbbbbbb.diff",
|
|
137
|
-
mergeBase: "abc123",
|
|
138
|
-
branchSha: "def456",
|
|
139
|
-
baseSha: "789abc",
|
|
140
|
-
files: [{ path: "src/a.ts", changeType: "modified" }],
|
|
141
|
-
ignored: ["package-lock.json"],
|
|
142
|
-
ignoreGlobs: ["**/package-lock.json"],
|
|
143
|
-
}
|
|
144
|
-
);
|
|
145
|
-
assert.match(text, /Precomputed git context/);
|
|
146
|
-
assert.match(text, /diff-rev_aaaaaaaaaa_bbbbbbbbbb\.diff/);
|
|
147
|
-
assert.match(text, /Merge-base SHA: `abc123`/);
|
|
148
|
-
assert.match(text, /modified: src\/a\.ts/);
|
|
149
|
-
assert.doesNotMatch(text, /Resolve the merge-base/);
|
|
150
|
-
assert.doesNotMatch(text, /Run exactly: `git diff/);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("buildPrompt acp omits validate CLI", async () => {
|
|
154
|
-
const { buildPrompt, validateCliCommand } = await import("./prompt.mjs");
|
|
155
|
-
const filename = "diff-rev_aaaaaaaaaa_bbbbbbbbbb.json";
|
|
156
|
-
const text = buildPrompt(
|
|
157
|
-
{ source: "local", repo: "/tmp/r", branch: "feat", base: "develop" },
|
|
158
|
-
{ outputFilename: filename, acp: true, diffRelPath: `.${OUTPUT_DIR}/${filename.replace(/\.json$/, ".diff")}` }
|
|
159
|
-
);
|
|
160
|
-
assert.doesNotMatch(text, new RegExp(validateCliCommand(filename).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
161
|
-
assert.match(text, /host validates/i);
|
|
162
|
-
});
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { afterEach, test } from "node:test";
|
|
6
|
+
import { extractBranchDiff, parseGitDiffFiles } from "./extract.mjs";
|
|
7
|
+
import { OUTPUT_DIR } from "./output-name.mjs";
|
|
8
|
+
import { companionPaths, ensureReviewDir, writeDiffText } from "./review-dir.mjs";
|
|
9
|
+
import { checkReportFile } from "./check-report.mjs";
|
|
10
|
+
|
|
11
|
+
const sample = [
|
|
12
|
+
"diff --git a/src/a.ts b/src/a.ts",
|
|
13
|
+
"index 111..222 100644",
|
|
14
|
+
"--- a/src/a.ts",
|
|
15
|
+
"+++ b/src/a.ts",
|
|
16
|
+
"@@ -1,2 +1,3 @@",
|
|
17
|
+
" keep",
|
|
18
|
+
"-old",
|
|
19
|
+
"+new",
|
|
20
|
+
"+extra",
|
|
21
|
+
"diff --git a/package-lock.json b/package-lock.json",
|
|
22
|
+
"index 1..2 100644",
|
|
23
|
+
"--- a/package-lock.json",
|
|
24
|
+
"+++ b/package-lock.json",
|
|
25
|
+
"@@ -1 +1 @@",
|
|
26
|
+
"-{}",
|
|
27
|
+
"+{ }",
|
|
28
|
+
"diff --git a/old.ts b/new.ts",
|
|
29
|
+
"similarity index 90%",
|
|
30
|
+
"rename from old.ts",
|
|
31
|
+
"rename to new.ts",
|
|
32
|
+
"index 1..2 100644",
|
|
33
|
+
"--- a/old.ts",
|
|
34
|
+
"+++ b/new.ts",
|
|
35
|
+
"@@ -1 +1 @@",
|
|
36
|
+
"-x",
|
|
37
|
+
"+y",
|
|
38
|
+
].join("\n");
|
|
39
|
+
|
|
40
|
+
const dirs = [];
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
for (const d of dirs.splice(0)) {
|
|
43
|
+
try {
|
|
44
|
+
rmSync(d, { recursive: true, force: true });
|
|
45
|
+
} catch {
|
|
46
|
+
/* ignore */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("parseGitDiffFiles splits paths and change types", () => {
|
|
52
|
+
const files = parseGitDiffFiles(sample);
|
|
53
|
+
assert.equal(files.length, 3);
|
|
54
|
+
assert.equal(files[0].path, "src/a.ts");
|
|
55
|
+
assert.equal(files[0].changeType, "modified");
|
|
56
|
+
assert.match(files[0].hunkDiff, /^@@/);
|
|
57
|
+
assert.equal(files[1].path, "package-lock.json");
|
|
58
|
+
assert.equal(files[2].changeType, "renamed");
|
|
59
|
+
assert.equal(files[2].path, "new.ts");
|
|
60
|
+
assert.equal(files[2].oldPath, "old.ts");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("extractBranchDiff with cachedDiff applies DEFAULT_IGNORE", () => {
|
|
64
|
+
const repo = mkdtempSync(join(tmpdir(), "diff-review-cache-"));
|
|
65
|
+
dirs.push(repo);
|
|
66
|
+
mkdirSync(join(repo, "src"), { recursive: true });
|
|
67
|
+
// repo path only used for config/guidelines; no git needed when cachedDiff is set
|
|
68
|
+
const skeleton = extractBranchDiff({
|
|
69
|
+
repo,
|
|
70
|
+
branch: "feature",
|
|
71
|
+
base: "develop",
|
|
72
|
+
cachedDiff: sample,
|
|
73
|
+
});
|
|
74
|
+
assert.deepEqual(
|
|
75
|
+
skeleton.files.map((f) => f.path).sort(),
|
|
76
|
+
["new.ts", "src/a.ts"]
|
|
77
|
+
);
|
|
78
|
+
assert.ok(skeleton.context.ignored.includes("package-lock.json"));
|
|
79
|
+
assert.match(skeleton.files.find((f) => f.path === "src/a.ts").diff, /^@@/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("checkReportFile reuses companion .diff paths without inventing coverage", () => {
|
|
83
|
+
const repo = mkdtempSync(join(tmpdir(), "diff-review-check-"));
|
|
84
|
+
dirs.push(repo);
|
|
85
|
+
ensureReviewDir(repo);
|
|
86
|
+
const filename = "diff-rev_aaaaaaaaaa_bbbbbbbbbb.json";
|
|
87
|
+
writeDiffText(repo, filename, sample);
|
|
88
|
+
const payload = {
|
|
89
|
+
meta: {
|
|
90
|
+
agent: "Cursor",
|
|
91
|
+
model: "composer",
|
|
92
|
+
repo,
|
|
93
|
+
branch: "feature",
|
|
94
|
+
base: "develop",
|
|
95
|
+
},
|
|
96
|
+
intent: "Cambia src/a.ts y renombra old.ts para dejar el módulo listo en la rama.",
|
|
97
|
+
groups: [{ id: "g1", kind: "feat", title: "Ajuste", intent: "base" }],
|
|
98
|
+
blocks: [
|
|
99
|
+
{
|
|
100
|
+
id: "b1",
|
|
101
|
+
group: "g1",
|
|
102
|
+
file: "src/a.ts",
|
|
103
|
+
lines: "L1-3",
|
|
104
|
+
op: "mod",
|
|
105
|
+
what: "cambia la lógica",
|
|
106
|
+
why: "hace falta",
|
|
107
|
+
source: "code",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "b2",
|
|
111
|
+
group: "g1",
|
|
112
|
+
file: "new.ts",
|
|
113
|
+
lines: "L1",
|
|
114
|
+
op: "rename",
|
|
115
|
+
what: "renombra el módulo",
|
|
116
|
+
why: "nombre más claro",
|
|
117
|
+
source: "code",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
findings: [],
|
|
121
|
+
skipped: [{ file: "package-lock.json", reason: "lockfile" }],
|
|
122
|
+
};
|
|
123
|
+
const jsonPath = companionPaths(repo, filename).json;
|
|
124
|
+
writeFileSync(jsonPath, JSON.stringify(payload));
|
|
125
|
+
// Sin git: loadBranchDiff lee el .diff y aplica ignore (lockfile fuera de paths).
|
|
126
|
+
const result = checkReportFile(jsonPath, { repo, branch: "feature", base: "develop" });
|
|
127
|
+
assert.equal(result.ok, true, result.errors.join("; "));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("buildPrompt with precomputed context skips merge-base rediscovery", async () => {
|
|
131
|
+
const { buildPrompt } = await import("./prompt.mjs");
|
|
132
|
+
const text = buildPrompt(
|
|
133
|
+
{ source: "local", repo: "/tmp/r", branch: "feat", base: "develop" },
|
|
134
|
+
{
|
|
135
|
+
outputFilename: "diff-rev_aaaaaaaaaa_bbbbbbbbbb.json",
|
|
136
|
+
diffRelPath: ".diff-review/diff-rev_aaaaaaaaaa_bbbbbbbbbb.diff",
|
|
137
|
+
mergeBase: "abc123",
|
|
138
|
+
branchSha: "def456",
|
|
139
|
+
baseSha: "789abc",
|
|
140
|
+
files: [{ path: "src/a.ts", changeType: "modified" }],
|
|
141
|
+
ignored: ["package-lock.json"],
|
|
142
|
+
ignoreGlobs: ["**/package-lock.json"],
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
assert.match(text, /Precomputed git context/);
|
|
146
|
+
assert.match(text, /diff-rev_aaaaaaaaaa_bbbbbbbbbb\.diff/);
|
|
147
|
+
assert.match(text, /Merge-base SHA: `abc123`/);
|
|
148
|
+
assert.match(text, /modified: src\/a\.ts/);
|
|
149
|
+
assert.doesNotMatch(text, /Resolve the merge-base/);
|
|
150
|
+
assert.doesNotMatch(text, /Run exactly: `git diff/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("buildPrompt acp omits validate CLI", async () => {
|
|
154
|
+
const { buildPrompt, validateCliCommand } = await import("./prompt.mjs");
|
|
155
|
+
const filename = "diff-rev_aaaaaaaaaa_bbbbbbbbbb.json";
|
|
156
|
+
const text = buildPrompt(
|
|
157
|
+
{ source: "local", repo: "/tmp/r", branch: "feat", base: "develop" },
|
|
158
|
+
{ outputFilename: filename, acp: true, diffRelPath: `.${OUTPUT_DIR}/${filename.replace(/\.json$/, ".diff")}` }
|
|
159
|
+
);
|
|
160
|
+
assert.doesNotMatch(text, new RegExp(validateCliCommand(filename).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
161
|
+
assert.match(text, /host validates/i);
|
|
162
|
+
});
|