pi-lens 2.0.37 โ 2.0.39
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/clients/architect-client.js +50 -20
- package/clients/architect-client.ts +61 -22
- package/clients/ast-grep-client.js +32 -127
- package/clients/ast-grep-client.test.js +2 -14
- package/clients/ast-grep-client.test.ts +2 -16
- package/clients/ast-grep-client.ts +34 -150
- package/clients/auto-loop.js +117 -0
- package/clients/auto-loop.ts +171 -0
- package/clients/biome-client.js +25 -22
- package/clients/biome-client.ts +28 -25
- package/clients/complexity-client.js +111 -74
- package/clients/complexity-client.ts +149 -105
- package/clients/dependency-checker.js +16 -30
- package/clients/dependency-checker.ts +15 -35
- package/clients/fix-scanners.js +195 -0
- package/clients/fix-scanners.ts +297 -0
- package/clients/interviewer-templates.js +75 -0
- package/clients/interviewer-templates.ts +90 -0
- package/clients/interviewer.js +73 -101
- package/clients/interviewer.ts +195 -140
- package/clients/knip-client.js +12 -4
- package/clients/knip-client.ts +21 -16
- package/clients/metrics-history.js +215 -0
- package/clients/metrics-history.ts +300 -0
- package/clients/scan-architectural-debt.js +62 -72
- package/clients/scan-architectural-debt.ts +79 -64
- package/clients/scan-utils.js +98 -0
- package/clients/scan-utils.ts +112 -0
- package/clients/sg-runner.js +138 -0
- package/clients/sg-runner.ts +168 -0
- package/clients/subprocess-client.js +0 -37
- package/clients/subprocess-client.ts +0 -60
- package/clients/ts-service.ts +1 -7
- package/clients/type-safety-client.js +3 -8
- package/clients/type-safety-client.ts +10 -14
- package/clients/typescript-client.js +55 -56
- package/clients/typescript-client.ts +94 -52
- package/default-architect.yaml +87 -0
- package/index.ts +143 -1165
- package/package.json +2 -1
- package/rules/ast-grep-rules/rules/large-class.yml +6 -2
- package/rules/ast-grep-rules/rules/long-method.yml +1 -1
- package/rules/ast-grep-rules/rules/no-single-char-var.yml +2 -2
- package/rules/ast-grep-rules/rules/switch-without-default.yml +0 -12
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export const CONFIRMATION_HTML = (question, plan, diff, esc, mdToHtml, diffHtml, addCount, delCount) => `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
2
|
+
<title>๐๏ธ Changes Applied</title>
|
|
3
|
+
<style>
|
|
4
|
+
*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#e6edf3;padding:28px 32px;max-width:960px;margin:0 auto;line-height:1.5}
|
|
5
|
+
h2{font-size:16px;color:#58a6ff;margin-bottom:14px}
|
|
6
|
+
.plan{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:14px 18px;margin-bottom:18px;font-size:13px;line-height:1.6}
|
|
7
|
+
.plan h3{color:#f0f6fc;font-size:14px;margin:10px 0 4px}.plan h4{color:#c9d1d9;font-size:13px;margin:8px 0 3px}
|
|
8
|
+
.plan li{margin:2px 0 2px 16px;list-style:disc}.plan code{background:#21262d;padding:1px 5px;border-radius:3px;font-size:12px}
|
|
9
|
+
.diff-wrap{background:#161b22;border:1px solid #30363d;border-radius:8px;margin-bottom:18px;overflow:hidden}
|
|
10
|
+
.diff-hdr{padding:7px 14px;font-size:11px;color:#8b949e;border-bottom:1px solid #30363d;font-family:monospace;display:flex;justify-content:space-between}
|
|
11
|
+
.diff-stats{display:flex;gap:10px}.stat-add{color:#3fb950}.stat-del{color:#ff7b72}
|
|
12
|
+
.diff-pre{padding:12px;font-family:'Fira Code',Consolas,monospace;font-size:12px;line-height:1.55;overflow-x:auto;white-space:pre;margin:0}
|
|
13
|
+
.da{color:#3fb950;display:block}.dd{color:#ff7b72;display:block}.dh{color:#79c0ff;display:block}.df{color:#8b949e;display:block}.dc{color:#e6edf3;display:block}
|
|
14
|
+
.actions{display:flex;gap:10px;flex-wrap:wrap}
|
|
15
|
+
.btn-c{background:#238636;color:#fff;border:1px solid #2ea043;padding:10px 24px;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer}.btn-c:hover{background:#2ea043}
|
|
16
|
+
.btn-chat{background:#1a2332;color:#79c0ff;border:1px solid #1f6feb;padding:10px 24px;border-radius:6px;font-size:14px;cursor:pointer}.btn-chat:hover{background:#1f3050}
|
|
17
|
+
.chat-area{display:none;margin-top:12px}
|
|
18
|
+
textarea{width:100%;background:#161b22;border:1px solid #30363d;color:#e6edf3;padding:9px;border-radius:6px;font-family:inherit;font-size:13px;resize:vertical;min-height:72px;outline:none}
|
|
19
|
+
textarea:focus{border-color:#58a6ff}
|
|
20
|
+
.hint{color:#6e7681;font-size:12px;margin-top:10px}
|
|
21
|
+
</style></head><body>
|
|
22
|
+
<h2>${esc(question)}</h2>
|
|
23
|
+
<div class="plan"><p>${mdToHtml(plan)}</p></div>
|
|
24
|
+
${diff ? `<div class="diff-wrap"><div class="diff-hdr"><span>Changes</span><div class="diff-stats"><span class="stat-add">+${addCount}</span><span class="stat-del">โ${delCount}</span></div></div><pre class="diff-pre">${diffHtml}</pre></div>` : ""}
|
|
25
|
+
<form method="POST" id="f">
|
|
26
|
+
<input type="hidden" name="choice" id="c" value="Looks good">
|
|
27
|
+
<div class="actions">
|
|
28
|
+
<button class="btn-c" type="submit">โ
Looks good โ move to next offender</button>
|
|
29
|
+
<button class="btn-chat" type="button" onclick="toggleChat()">๐ฌ Request changes</button>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="chat-area" id="ca"><textarea name="freeText" placeholder="Describe what you'd like changed..."></textarea>
|
|
32
|
+
<div style="margin-top:8px"><button class="btn-c" type="submit" onclick="document.getElementById('c').value='Redo'">Submit</button></div></div>
|
|
33
|
+
</form>
|
|
34
|
+
<p class="hint">Tab closes after submit ยท Ctrl+Enter to confirm</p>
|
|
35
|
+
<script>
|
|
36
|
+
function toggleChat(){const r=document.getElementById('ca');r.style.display=r.style.display==='none'?'block':'none';if(r.style.display==='block')r.querySelector('textarea').focus();}
|
|
37
|
+
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){document.getElementById('f').submit();}});
|
|
38
|
+
</script>
|
|
39
|
+
</body></html>`;
|
|
40
|
+
export const INTERVIEW_HTML = (question, optionsHtml, hasFreeText, esc) => `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
41
|
+
<title>๐๏ธ Decision</title>
|
|
42
|
+
<style>
|
|
43
|
+
*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#e6edf3;padding:28px 32px;max-width:880px;margin:0 auto;line-height:1.5}
|
|
44
|
+
.question{font-size:15px;font-weight:600;color:#f0f6fc;margin-bottom:12px}
|
|
45
|
+
.opts{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
|
|
46
|
+
.card{border:1px solid #30363d;border-radius:8px;padding:11px 14px;cursor:pointer;transition:border-color .12s,background .12s;display:flex;align-items:flex-start;gap:10px}
|
|
47
|
+
.card:hover,.card.selected{border-color:#58a6ff;background:#0d1f30}.card.rec{border-color:#1f6feb}
|
|
48
|
+
.card input{margin-top:3px;accent-color:#58a6ff;flex-shrink:0}.card-body{flex:1}
|
|
49
|
+
.card-top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
50
|
+
.num{color:#6e7681;font-size:13px;min-width:18px}.lbl{font-size:13.5px;font-weight:500}
|
|
51
|
+
.badge-rec{background:#1f4e2e;color:#3fb950;font-size:10px;padding:1px 7px;border-radius:10px;margin-left:4px;font-weight:600}
|
|
52
|
+
.ctx{color:#8b949e;font-size:12px;margin-top:3px;padding-left:22px}
|
|
53
|
+
.impact{display:flex;gap:6px;margin-top:5px;padding-left:22px;flex-wrap:wrap}
|
|
54
|
+
.ib{font-size:11px;padding:2px 8px;border-radius:10px;font-family:monospace;font-weight:600}
|
|
55
|
+
.ib.up{background:#1a3a2a;color:#3fb950;border:1px solid #238636}
|
|
56
|
+
.ib.dn{background:#3a1a1a;color:#ff7b72;border:1px solid #f85149}
|
|
57
|
+
.ib.proj{background:#1a2a3a;color:#79c0ff;border:1px solid #1f6feb}
|
|
58
|
+
.free-area{display:none;margin-top:10px;padding-left:22px}
|
|
59
|
+
textarea{width:100%;background:#161b22;border:1px solid #30363d;color:#e6edf3;padding:9px;border-radius:6px;font-family:inherit;font-size:13px;resize:vertical;min-height:72px;outline:none}
|
|
60
|
+
textarea:focus{border-color:#58a6ff}
|
|
61
|
+
.submit-row{display:flex;align-items:center;gap:12px;margin-top:4px}
|
|
62
|
+
button{background:#238636;color:#fff;border:1px solid #2ea043;padding:9px 22px;border-radius:6px;font-size:13.5px;font-weight:600;cursor:pointer;transition:background .12s}
|
|
63
|
+
button:hover{background:#2ea043}.hint{color:#6e7681;font-size:12px}
|
|
64
|
+
</style></head><body>
|
|
65
|
+
<div class="question">${esc(question)}</div>
|
|
66
|
+
<form method="POST" id="f">
|
|
67
|
+
<div class="opts">${optionsHtml}</div>
|
|
68
|
+
${hasFreeText ? '<div class="free-area" id="fa"><textarea name="freeText" placeholder="Describe your preferred approach..."></textarea></div>' : ""}
|
|
69
|
+
<div class="submit-row"><button type="submit">Submit</button><span class="hint">Ctrl+Enter</span></div>
|
|
70
|
+
</form>
|
|
71
|
+
<script>
|
|
72
|
+
const cards=document.querySelectorAll('.card');function sel(c){cards.forEach(x=>{x.classList.remove('selected');x.querySelector('input').checked=false});c.classList.add('selected');c.querySelector('input').checked=true;const fa=document.getElementById('fa');if(fa)fa.style.display=c.querySelector('input').value==='__free__'?'block':'none';}
|
|
73
|
+
cards.forEach(c=>c.addEventListener('click',()=>sel(c)));const rec=document.querySelector('.card.rec');if(rec)sel(rec);else if(cards.length)sel(cards[0]);
|
|
74
|
+
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter')document.getElementById('f').submit();});
|
|
75
|
+
</script></body></html>`;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export const CONFIRMATION_HTML = (
|
|
2
|
+
question: string,
|
|
3
|
+
plan: string,
|
|
4
|
+
diff: string,
|
|
5
|
+
esc: (s: string) => string,
|
|
6
|
+
mdToHtml: (md: string) => string,
|
|
7
|
+
diffHtml: string,
|
|
8
|
+
addCount: number,
|
|
9
|
+
delCount: number,
|
|
10
|
+
): string => `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
11
|
+
<title>๐๏ธ Changes Applied</title>
|
|
12
|
+
<style>
|
|
13
|
+
*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#e6edf3;padding:28px 32px;max-width:960px;margin:0 auto;line-height:1.5}
|
|
14
|
+
h2{font-size:16px;color:#58a6ff;margin-bottom:14px}
|
|
15
|
+
.plan{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:14px 18px;margin-bottom:18px;font-size:13px;line-height:1.6}
|
|
16
|
+
.plan h3{color:#f0f6fc;font-size:14px;margin:10px 0 4px}.plan h4{color:#c9d1d9;font-size:13px;margin:8px 0 3px}
|
|
17
|
+
.plan li{margin:2px 0 2px 16px;list-style:disc}.plan code{background:#21262d;padding:1px 5px;border-radius:3px;font-size:12px}
|
|
18
|
+
.diff-wrap{background:#161b22;border:1px solid #30363d;border-radius:8px;margin-bottom:18px;overflow:hidden}
|
|
19
|
+
.diff-hdr{padding:7px 14px;font-size:11px;color:#8b949e;border-bottom:1px solid #30363d;font-family:monospace;display:flex;justify-content:space-between}
|
|
20
|
+
.diff-stats{display:flex;gap:10px}.stat-add{color:#3fb950}.stat-del{color:#ff7b72}
|
|
21
|
+
.diff-pre{padding:12px;font-family:'Fira Code',Consolas,monospace;font-size:12px;line-height:1.55;overflow-x:auto;white-space:pre;margin:0}
|
|
22
|
+
.da{color:#3fb950;display:block}.dd{color:#ff7b72;display:block}.dh{color:#79c0ff;display:block}.df{color:#8b949e;display:block}.dc{color:#e6edf3;display:block}
|
|
23
|
+
.actions{display:flex;gap:10px;flex-wrap:wrap}
|
|
24
|
+
.btn-c{background:#238636;color:#fff;border:1px solid #2ea043;padding:10px 24px;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer}.btn-c:hover{background:#2ea043}
|
|
25
|
+
.btn-chat{background:#1a2332;color:#79c0ff;border:1px solid #1f6feb;padding:10px 24px;border-radius:6px;font-size:14px;cursor:pointer}.btn-chat:hover{background:#1f3050}
|
|
26
|
+
.chat-area{display:none;margin-top:12px}
|
|
27
|
+
textarea{width:100%;background:#161b22;border:1px solid #30363d;color:#e6edf3;padding:9px;border-radius:6px;font-family:inherit;font-size:13px;resize:vertical;min-height:72px;outline:none}
|
|
28
|
+
textarea:focus{border-color:#58a6ff}
|
|
29
|
+
.hint{color:#6e7681;font-size:12px;margin-top:10px}
|
|
30
|
+
</style></head><body>
|
|
31
|
+
<h2>${esc(question)}</h2>
|
|
32
|
+
<div class="plan"><p>${mdToHtml(plan)}</p></div>
|
|
33
|
+
${diff ? `<div class="diff-wrap"><div class="diff-hdr"><span>Changes</span><div class="diff-stats"><span class="stat-add">+${addCount}</span><span class="stat-del">โ${delCount}</span></div></div><pre class="diff-pre">${diffHtml}</pre></div>` : ""}
|
|
34
|
+
<form method="POST" id="f">
|
|
35
|
+
<input type="hidden" name="choice" id="c" value="Looks good">
|
|
36
|
+
<div class="actions">
|
|
37
|
+
<button class="btn-c" type="submit">โ
Looks good โ move to next offender</button>
|
|
38
|
+
<button class="btn-chat" type="button" onclick="toggleChat()">๐ฌ Request changes</button>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="chat-area" id="ca"><textarea name="freeText" placeholder="Describe what you'd like changed..."></textarea>
|
|
41
|
+
<div style="margin-top:8px"><button class="btn-c" type="submit" onclick="document.getElementById('c').value='Redo'">Submit</button></div></div>
|
|
42
|
+
</form>
|
|
43
|
+
<p class="hint">Tab closes after submit ยท Ctrl+Enter to confirm</p>
|
|
44
|
+
<script>
|
|
45
|
+
function toggleChat(){const r=document.getElementById('ca');r.style.display=r.style.display==='none'?'block':'none';if(r.style.display==='block')r.querySelector('textarea').focus();}
|
|
46
|
+
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){document.getElementById('f').submit();}});
|
|
47
|
+
</script>
|
|
48
|
+
</body></html>`;
|
|
49
|
+
|
|
50
|
+
export const INTERVIEW_HTML = (
|
|
51
|
+
question: string,
|
|
52
|
+
optionsHtml: string,
|
|
53
|
+
hasFreeText: boolean,
|
|
54
|
+
esc: (s: string) => string,
|
|
55
|
+
): string => `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
56
|
+
<title>๐๏ธ Decision</title>
|
|
57
|
+
<style>
|
|
58
|
+
*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#e6edf3;padding:28px 32px;max-width:880px;margin:0 auto;line-height:1.5}
|
|
59
|
+
.question{font-size:15px;font-weight:600;color:#f0f6fc;margin-bottom:12px}
|
|
60
|
+
.opts{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
|
|
61
|
+
.card{border:1px solid #30363d;border-radius:8px;padding:11px 14px;cursor:pointer;transition:border-color .12s,background .12s;display:flex;align-items:flex-start;gap:10px}
|
|
62
|
+
.card:hover,.card.selected{border-color:#58a6ff;background:#0d1f30}.card.rec{border-color:#1f6feb}
|
|
63
|
+
.card input{margin-top:3px;accent-color:#58a6ff;flex-shrink:0}.card-body{flex:1}
|
|
64
|
+
.card-top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
65
|
+
.num{color:#6e7681;font-size:13px;min-width:18px}.lbl{font-size:13.5px;font-weight:500}
|
|
66
|
+
.badge-rec{background:#1f4e2e;color:#3fb950;font-size:10px;padding:1px 7px;border-radius:10px;margin-left:4px;font-weight:600}
|
|
67
|
+
.ctx{color:#8b949e;font-size:12px;margin-top:3px;padding-left:22px}
|
|
68
|
+
.impact{display:flex;gap:6px;margin-top:5px;padding-left:22px;flex-wrap:wrap}
|
|
69
|
+
.ib{font-size:11px;padding:2px 8px;border-radius:10px;font-family:monospace;font-weight:600}
|
|
70
|
+
.ib.up{background:#1a3a2a;color:#3fb950;border:1px solid #238636}
|
|
71
|
+
.ib.dn{background:#3a1a1a;color:#ff7b72;border:1px solid #f85149}
|
|
72
|
+
.ib.proj{background:#1a2a3a;color:#79c0ff;border:1px solid #1f6feb}
|
|
73
|
+
.free-area{display:none;margin-top:10px;padding-left:22px}
|
|
74
|
+
textarea{width:100%;background:#161b22;border:1px solid #30363d;color:#e6edf3;padding:9px;border-radius:6px;font-family:inherit;font-size:13px;resize:vertical;min-height:72px;outline:none}
|
|
75
|
+
textarea:focus{border-color:#58a6ff}
|
|
76
|
+
.submit-row{display:flex;align-items:center;gap:12px;margin-top:4px}
|
|
77
|
+
button{background:#238636;color:#fff;border:1px solid #2ea043;padding:9px 22px;border-radius:6px;font-size:13.5px;font-weight:600;cursor:pointer;transition:background .12s}
|
|
78
|
+
button:hover{background:#2ea043}.hint{color:#6e7681;font-size:12px}
|
|
79
|
+
</style></head><body>
|
|
80
|
+
<div class="question">${esc(question)}</div>
|
|
81
|
+
<form method="POST" id="f">
|
|
82
|
+
<div class="opts">${optionsHtml}</div>
|
|
83
|
+
${hasFreeText ? '<div class="free-area" id="fa"><textarea name="freeText" placeholder="Describe your preferred approach..."></textarea></div>' : ""}
|
|
84
|
+
<div class="submit-row"><button type="submit">Submit</button><span class="hint">Ctrl+Enter</span></div>
|
|
85
|
+
</form>
|
|
86
|
+
<script>
|
|
87
|
+
const cards=document.querySelectorAll('.card');function sel(c){cards.forEach(x=>{x.classList.remove('selected');x.querySelector('input').checked=false});c.classList.add('selected');c.querySelector('input').checked=true;const fa=document.getElementById('fa');if(fa)fa.style.display=c.querySelector('input').value==='__free__'?'block':'none';}
|
|
88
|
+
cards.forEach(c=>c.addEventListener('click',()=>sel(c)));const rec=document.querySelector('.card.rec');if(rec)sel(rec);else if(cards.length)sel(cards[0]);
|
|
89
|
+
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter')document.getElementById('f').submit();});
|
|
90
|
+
</script></body></html>`;
|
package/clients/interviewer.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import * as http from "node:http";
|
|
3
|
+
import * as net from "node:net";
|
|
1
4
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
|
|
5
|
+
import { CONFIRMATION_HTML, INTERVIEW_HTML } from "./interviewer-templates.js";
|
|
6
|
+
export function buildInterviewer(pi, _dbg) {
|
|
3
7
|
let interviewHandler = null;
|
|
8
|
+
const esc = (s) => s
|
|
9
|
+
.replace(/&/g, "&")
|
|
10
|
+
.replace(/</g, "<")
|
|
11
|
+
.replace(/>/g, ">")
|
|
12
|
+
.replace(/"/g, """);
|
|
13
|
+
const mdToHtml = (md) => md
|
|
14
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
15
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
16
|
+
.replace(/^### (.+)/gm, "<h4>$1</h4>")
|
|
17
|
+
.replace(/^## (.+)/gm, "<h3>$1</h3>")
|
|
18
|
+
.replace(/^# (.+)/gm, "<h2>$1</h2>")
|
|
19
|
+
.replace(/^- (.+)/gm, "<li>$1</li>")
|
|
20
|
+
.replace(/\n\n/g, "</p><p>");
|
|
4
21
|
const confirmationHTML = (question, plan, diff) => {
|
|
5
|
-
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6
|
-
const mdToHtml = (md) => md.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
7
|
-
.replace(/^### (.+)/gm, "<h4>$1</h4>").replace(/^## (.+)/gm, "<h3>$1</h3>")
|
|
8
|
-
.replace(/^# (.+)/gm, "<h2>$1</h2>").replace(/^- (.+)/gm, "<li>$1</li>")
|
|
9
|
-
.replace(/\n\n/g, "</p><p>");
|
|
10
22
|
const diffLines = diff.split("\n");
|
|
11
|
-
const diffHtml = diffLines
|
|
23
|
+
const diffHtml = diffLines
|
|
24
|
+
.map((line) => {
|
|
12
25
|
if (line.startsWith("+++") || line.startsWith("---"))
|
|
13
26
|
return `<span class="df">${esc(line)}</span>`;
|
|
14
27
|
if (line.startsWith("@@"))
|
|
@@ -18,54 +31,17 @@ export function buildInterviewer(pi, dbg) {
|
|
|
18
31
|
if (line.startsWith("-"))
|
|
19
32
|
return `<span class="dd">${esc(line)}</span>`;
|
|
20
33
|
return `<span class="dc">${esc(line)}</span>`;
|
|
21
|
-
})
|
|
34
|
+
})
|
|
35
|
+
.join("\n");
|
|
22
36
|
const addCount = (diff.match(/^\+/gm) || []).length;
|
|
23
37
|
const delCount = (diff.match(/^-/gm) || []).length - (diff.match(/^---/gm) || []).length;
|
|
24
|
-
return
|
|
25
|
-
<title>๐๏ธ Changes Applied</title>
|
|
26
|
-
<style>
|
|
27
|
-
*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#e6edf3;padding:28px 32px;max-width:960px;margin:0 auto;line-height:1.5}
|
|
28
|
-
h2{font-size:16px;color:#58a6ff;margin-bottom:14px}
|
|
29
|
-
.plan{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:14px 18px;margin-bottom:18px;font-size:13px;line-height:1.6}
|
|
30
|
-
.plan h3{color:#f0f6fc;font-size:14px;margin:10px 0 4px}.plan h4{color:#c9d1d9;font-size:13px;margin:8px 0 3px}
|
|
31
|
-
.plan li{margin:2px 0 2px 16px;list-style:disc}.plan code{background:#21262d;padding:1px 5px;border-radius:3px;font-size:12px}
|
|
32
|
-
.diff-wrap{background:#161b22;border:1px solid #30363d;border-radius:8px;margin-bottom:18px;overflow:hidden}
|
|
33
|
-
.diff-hdr{padding:7px 14px;font-size:11px;color:#8b949e;border-bottom:1px solid #30363d;font-family:monospace;display:flex;justify-content:space-between}
|
|
34
|
-
.diff-stats{display:flex;gap:10px}.stat-add{color:#3fb950}.stat-del{color:#ff7b72}
|
|
35
|
-
.diff-pre{padding:12px;font-family:'Fira Code',Consolas,monospace;font-size:12px;line-height:1.55;overflow-x:auto;white-space:pre;margin:0}
|
|
36
|
-
.da{color:#3fb950;display:block}.dd{color:#ff7b72;display:block}.dh{color:#79c0ff;display:block}.df{color:#8b949e;display:block}.dc{color:#e6edf3;display:block}
|
|
37
|
-
.actions{display:flex;gap:10px;flex-wrap:wrap}
|
|
38
|
-
.btn-c{background:#238636;color:#fff;border:1px solid #2ea043;padding:10px 24px;border-radius:6px;font-size:14px;font-weight:600;cursor:pointer}.btn-c:hover{background:#2ea043}
|
|
39
|
-
.btn-chat{background:#1a2332;color:#79c0ff;border:1px solid #1f6feb;padding:10px 24px;border-radius:6px;font-size:14px;cursor:pointer}.btn-chat:hover{background:#1f3050}
|
|
40
|
-
.chat-area{display:none;margin-top:12px}
|
|
41
|
-
textarea{width:100%;background:#161b22;border:1px solid #30363d;color:#e6edf3;padding:9px;border-radius:6px;font-family:inherit;font-size:13px;resize:vertical;min-height:72px;outline:none}
|
|
42
|
-
textarea:focus{border-color:#58a6ff}
|
|
43
|
-
.hint{color:#6e7681;font-size:12px;margin-top:10px}
|
|
44
|
-
</style></head><body>
|
|
45
|
-
<h2>${esc(question)}</h2>
|
|
46
|
-
<div class="plan"><p>${mdToHtml(plan)}</p></div>
|
|
47
|
-
${diff ? `<div class="diff-wrap"><div class="diff-hdr"><span>Changes</span><div class="diff-stats"><span class="stat-add">+${addCount}</span><span class="stat-del">โ${delCount}</span></div></div><pre class="diff-pre">${diffHtml}</pre></div>` : ""}
|
|
48
|
-
<form method="POST" id="f">
|
|
49
|
-
<input type="hidden" name="choice" id="c" value="Looks good">
|
|
50
|
-
<div class="actions">
|
|
51
|
-
<button class="btn-c" type="submit">โ
Looks good โ move to next offender</button>
|
|
52
|
-
<button class="btn-chat" type="button" onclick="toggleChat()">๐ฌ Request changes</button>
|
|
53
|
-
</div>
|
|
54
|
-
<div class="chat-area" id="ca"><textarea name="freeText" placeholder="Describe what you'd like changed..."></textarea>
|
|
55
|
-
<div style="margin-top:8px"><button class="btn-c" type="submit" onclick="document.getElementById('c').value='Redo'">Submit</button></div></div>
|
|
56
|
-
</form>
|
|
57
|
-
<p class="hint">Tab closes after submit ยท Ctrl+Enter to confirm</p>
|
|
58
|
-
<script>
|
|
59
|
-
function toggleChat(){const r=document.getElementById('ca');r.style.display=r.style.display==='none'?'block':'none';if(r.style.display==='block')r.querySelector('textarea').focus();}
|
|
60
|
-
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){document.getElementById('f').submit();}});
|
|
61
|
-
</script>
|
|
62
|
-
</body></html>`;
|
|
38
|
+
return CONFIRMATION_HTML(question, plan, diff, esc, mdToHtml, diffHtml, addCount, delCount);
|
|
63
39
|
};
|
|
64
40
|
const interviewHTML = (question, options, _timeoutSeconds, _plan, _diff, _confirmationMode) => {
|
|
65
41
|
if (_confirmationMode && _plan && _diff)
|
|
66
42
|
return confirmationHTML(question, _plan, _diff);
|
|
67
|
-
const
|
|
68
|
-
|
|
43
|
+
const optionsHtml = options
|
|
44
|
+
.map((opt, idx) => {
|
|
69
45
|
const impactBadge = (val, label, good) => `<span class="ib ${good ? "up" : "dn"}">${val > 0 ? "+" : ""}${val} ${label}</span>`;
|
|
70
46
|
let impactHtml = "";
|
|
71
47
|
if (opt.impact) {
|
|
@@ -80,52 +56,19 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
80
56
|
impactHtml = `<div class="impact">${parts.join("")}</div>`;
|
|
81
57
|
}
|
|
82
58
|
return `<label class="card${opt.recommended ? " rec" : ""}"><input type="radio" name="choice" value="${esc(opt.value)}"${opt.recommended ? " checked" : ""}><div class="card-body"><div class="card-top"><span class="num">${idx + 1}.</span><span class="lbl">${esc(opt.label)}</span>${opt.recommended ? '<span class="badge-rec">Recommended</span>' : ""}</div>${impactHtml}${opt.context ? `<div class="ctx">${esc(opt.context)}</div>` : ""}</div></label>`;
|
|
83
|
-
})
|
|
59
|
+
})
|
|
60
|
+
.join("\n");
|
|
84
61
|
const hasFreeText = options.some((o) => o.value === "__free__");
|
|
85
|
-
return
|
|
86
|
-
<title>๐๏ธ Decision</title>
|
|
87
|
-
<style>
|
|
88
|
-
*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#e6edf3;padding:28px 32px;max-width:880px;margin:0 auto;line-height:1.5}
|
|
89
|
-
.question{font-size:15px;font-weight:600;color:#f0f6fc;margin-bottom:12px}
|
|
90
|
-
.opts{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
|
|
91
|
-
.card{border:1px solid #30363d;border-radius:8px;padding:11px 14px;cursor:pointer;transition:border-color .12s,background .12s;display:flex;align-items:flex-start;gap:10px}
|
|
92
|
-
.card:hover,.card.selected{border-color:#58a6ff;background:#0d1f30}.card.rec{border-color:#1f6feb}
|
|
93
|
-
.card input{margin-top:3px;accent-color:#58a6ff;flex-shrink:0}.card-body{flex:1}
|
|
94
|
-
.card-top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
95
|
-
.num{color:#6e7681;font-size:13px;min-width:18px}.lbl{font-size:13.5px;font-weight:500}
|
|
96
|
-
.badge-rec{background:#1f4e2e;color:#3fb950;font-size:10px;padding:1px 7px;border-radius:10px;margin-left:4px;font-weight:600}
|
|
97
|
-
.ctx{color:#8b949e;font-size:12px;margin-top:3px;padding-left:22px}
|
|
98
|
-
.impact{display:flex;gap:6px;margin-top:5px;padding-left:22px;flex-wrap:wrap}
|
|
99
|
-
.ib{font-size:11px;padding:2px 8px;border-radius:10px;font-family:monospace;font-weight:600}
|
|
100
|
-
.ib.up{background:#1a3a2a;color:#3fb950;border:1px solid #238636}
|
|
101
|
-
.ib.dn{background:#3a1a1a;color:#ff7b72;border:1px solid #f85149}
|
|
102
|
-
.ib.proj{background:#1a2a3a;color:#79c0ff;border:1px solid #1f6feb}
|
|
103
|
-
.free-area{display:none;margin-top:10px;padding-left:22px}
|
|
104
|
-
textarea{width:100%;background:#161b22;border:1px solid #30363d;color:#e6edf3;padding:9px;border-radius:6px;font-family:inherit;font-size:13px;resize:vertical;min-height:72px;outline:none}
|
|
105
|
-
textarea:focus{border-color:#58a6ff}
|
|
106
|
-
.submit-row{display:flex;align-items:center;gap:12px;margin-top:4px}
|
|
107
|
-
button{background:#238636;color:#fff;border:1px solid #2ea043;padding:9px 22px;border-radius:6px;font-size:13.5px;font-weight:600;cursor:pointer;transition:background .12s}
|
|
108
|
-
button:hover{background:#2ea043}.hint{color:#6e7681;font-size:12px}
|
|
109
|
-
</style></head><body>
|
|
110
|
-
<div class="question">${esc(question)}</div>
|
|
111
|
-
<form method="POST" id="f">
|
|
112
|
-
<div class="opts">${optionsHtml}</div>
|
|
113
|
-
${hasFreeText ? '<div class="free-area" id="fa"><textarea name="freeText" placeholder="Describe your preferred approach..."></textarea></div>' : ""}
|
|
114
|
-
<div class="submit-row"><button type="submit">Submit</button><span class="hint">Ctrl+Enter</span></div>
|
|
115
|
-
</form>
|
|
116
|
-
<script>
|
|
117
|
-
const cards=document.querySelectorAll('.card');function sel(c){cards.forEach(x=>{x.classList.remove('selected');x.querySelector('input').checked=false});c.classList.add('selected');c.querySelector('input').checked=true;const fa=document.getElementById('fa');if(fa)fa.style.display=c.querySelector('input').value==='__free__'?'block':'none';}
|
|
118
|
-
cards.forEach(c=>c.addEventListener('click',()=>sel(c)));const rec=document.querySelector('.card.rec');if(rec)sel(rec);else if(cards.length)sel(cards[0]);
|
|
119
|
-
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter')document.getElementById('f').submit();});
|
|
120
|
-
</script></body></html>`;
|
|
62
|
+
return INTERVIEW_HTML(question, optionsHtml, hasFreeText, esc);
|
|
121
63
|
};
|
|
122
64
|
const openBrowserInterview = (html, timeoutSeconds) => {
|
|
123
|
-
const http = require("node:http");
|
|
124
|
-
const net = require("node:net");
|
|
125
65
|
return new Promise((resolve) => {
|
|
126
66
|
const getPort = (cb) => {
|
|
127
67
|
const s = net.createServer();
|
|
128
|
-
s.listen(0, () => {
|
|
68
|
+
s.listen(0, () => {
|
|
69
|
+
const p = s.address().port;
|
|
70
|
+
s.close(() => cb(p));
|
|
71
|
+
});
|
|
129
72
|
s.on("error", () => cb(-1));
|
|
130
73
|
};
|
|
131
74
|
getPort((port) => {
|
|
@@ -140,14 +83,20 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
140
83
|
}
|
|
141
84
|
else if (req.method === "POST") {
|
|
142
85
|
let body = "";
|
|
143
|
-
req.on("data", (c) => {
|
|
86
|
+
req.on("data", (c) => {
|
|
87
|
+
body += c.toString();
|
|
88
|
+
});
|
|
144
89
|
req.on("end", () => {
|
|
145
90
|
const p = new URLSearchParams(body);
|
|
146
91
|
const choice = p.get("choice") ?? "";
|
|
147
92
|
const freeText = p.get("freeText") ?? "";
|
|
148
|
-
const final = choice === "__free__" || choice === "Redo"
|
|
149
|
-
|
|
150
|
-
|
|
93
|
+
const final = choice === "__free__" || choice === "Redo"
|
|
94
|
+
? freeText.trim()
|
|
95
|
+
: choice;
|
|
96
|
+
res.writeHead(200, {
|
|
97
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
98
|
+
});
|
|
99
|
+
res.end(`<!DOCTYPE html><html><head><meta charset='UTF-8'><style>body{font-family:system-ui;background:#0d1117;color:#e6edf3;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;text-align:center}.fade{transition:opacity 0.5s}</style></head><body><div class="fade"><h2>โ
Response received</h2><p style='color:#8b949e;margin-top:8px'>Closing tab...</p><p id="count" style='color:#58a6ff;margin-top:4px'></p></div><script>let s=3;const el=document.getElementById('count');const tick=()=>{el.textContent=s+'s';if(s<=0){window.close();}else{s--;setTimeout(tick,1000);}};tick();</script></body></html>`);
|
|
151
100
|
clearTimeout(timer);
|
|
152
101
|
server.close();
|
|
153
102
|
resolve(final || null);
|
|
@@ -155,7 +104,6 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
155
104
|
}
|
|
156
105
|
});
|
|
157
106
|
server.listen(port);
|
|
158
|
-
const { spawnSync } = require("node:child_process");
|
|
159
107
|
const url = `http://localhost:${port}`;
|
|
160
108
|
if (process.platform === "win32")
|
|
161
109
|
spawnSync("cmd", ["/c", "start", "", url], { shell: false });
|
|
@@ -163,7 +111,10 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
163
111
|
spawnSync("open", [url]);
|
|
164
112
|
else
|
|
165
113
|
spawnSync("xdg-open", [url]);
|
|
166
|
-
const timer = setTimeout(() => {
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
server.close();
|
|
116
|
+
resolve(null);
|
|
117
|
+
}, timeoutSeconds * 1000);
|
|
167
118
|
});
|
|
168
119
|
});
|
|
169
120
|
};
|
|
@@ -173,7 +124,9 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
173
124
|
label: "Interview",
|
|
174
125
|
description: "Present a multiple-choice interview to the user via browser form. Use this when you need the user to make a decision with options. Returns their choice or null on timeout. Supports confirmation mode with plan+diff display.",
|
|
175
126
|
parameters: Type.Object({
|
|
176
|
-
question: Type.String({
|
|
127
|
+
question: Type.String({
|
|
128
|
+
description: "The question to present to the user",
|
|
129
|
+
}),
|
|
177
130
|
options: Type.Optional(Type.Array(Type.Object({
|
|
178
131
|
value: Type.String(),
|
|
179
132
|
label: Type.String(),
|
|
@@ -185,16 +138,35 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
185
138
|
cognitiveProjection: Type.Optional(Type.String()),
|
|
186
139
|
})),
|
|
187
140
|
}))),
|
|
188
|
-
plan: Type.Optional(Type.String({
|
|
189
|
-
|
|
141
|
+
plan: Type.Optional(Type.String({
|
|
142
|
+
description: "Refactoring plan (markdown) โ shows in confirmation mode",
|
|
143
|
+
})),
|
|
144
|
+
diff: Type.Optional(Type.String({
|
|
145
|
+
description: "Unified diff text โ shows in confirmation mode",
|
|
146
|
+
})),
|
|
190
147
|
confirmationMode: Type.Optional(Type.Boolean({ description: "Show plan+diff confirmation screen" })),
|
|
191
|
-
timeoutSeconds: Type.Optional(Type.Number({
|
|
148
|
+
timeoutSeconds: Type.Optional(Type.Number({
|
|
149
|
+
description: "Auto-close after this many seconds (default 600)",
|
|
150
|
+
})),
|
|
192
151
|
}),
|
|
193
152
|
async execute(_toolCallId, input, _signal, _onUpdate, _ctx) {
|
|
194
153
|
if (!interviewHandler)
|
|
195
|
-
return {
|
|
154
|
+
return {
|
|
155
|
+
content: [
|
|
156
|
+
{ type: "text", text: "Interview tool not initialized" },
|
|
157
|
+
],
|
|
158
|
+
details: null,
|
|
159
|
+
};
|
|
196
160
|
const result = await interviewHandler(input.question, input.options ?? [], input.timeoutSeconds ?? 600, input.plan, input.diff, input.confirmationMode);
|
|
197
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: "text",
|
|
165
|
+
text: result ?? "No response (timed out or dismissed)",
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
details: result ?? null,
|
|
169
|
+
};
|
|
198
170
|
},
|
|
199
171
|
});
|
|
200
172
|
return interviewHandler;
|