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
package/clients/interviewer.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Two modes:
|
|
6
|
-
* 1. Option selection: question + options with impact metrics
|
|
7
|
-
* 2. Confirmation: plan + diff with confirm/cancel/redo
|
|
8
|
-
*/
|
|
9
|
-
import * as path from "node:path";
|
|
10
|
-
import { Type } from "@sinclair/typebox";
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import * as http from "node:http";
|
|
3
|
+
import * as net from "node:net";
|
|
11
4
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
5
|
+
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { CONFIRMATION_HTML, INTERVIEW_HTML } from "./interviewer-templates.js";
|
|
12
7
|
|
|
13
8
|
export type InterviewOption = {
|
|
14
9
|
value: string;
|
|
@@ -24,147 +19,156 @@ export type InterviewOption = {
|
|
|
24
19
|
|
|
25
20
|
export function buildInterviewer(
|
|
26
21
|
pi: ExtensionAPI,
|
|
27
|
-
|
|
28
|
-
): (
|
|
29
|
-
|
|
22
|
+
_dbg: (msg: string) => void,
|
|
23
|
+
): (
|
|
24
|
+
question: string,
|
|
25
|
+
options: InterviewOption[],
|
|
26
|
+
timeoutSeconds: number,
|
|
27
|
+
plan?: string,
|
|
28
|
+
diff?: string,
|
|
29
|
+
confirmationMode?: boolean,
|
|
30
|
+
) => Promise<string | null> {
|
|
31
|
+
let interviewHandler:
|
|
32
|
+
| ((
|
|
33
|
+
question: string,
|
|
34
|
+
options: InterviewOption[],
|
|
35
|
+
timeoutSeconds: number,
|
|
36
|
+
plan?: string,
|
|
37
|
+
diff?: string,
|
|
38
|
+
confirmationMode?: boolean,
|
|
39
|
+
) => Promise<string | null>)
|
|
40
|
+
| null = null;
|
|
41
|
+
|
|
42
|
+
const esc = (s: string) =>
|
|
43
|
+
s
|
|
44
|
+
.replace(/&/g, "&")
|
|
45
|
+
.replace(/</g, "<")
|
|
46
|
+
.replace(/>/g, ">")
|
|
47
|
+
.replace(/"/g, """);
|
|
48
|
+
|
|
49
|
+
const mdToHtml = (md: string) =>
|
|
50
|
+
md
|
|
51
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
52
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
53
|
+
.replace(/^### (.+)/gm, "<h4>$1</h4>")
|
|
54
|
+
.replace(/^## (.+)/gm, "<h3>$1</h3>")
|
|
55
|
+
.replace(/^# (.+)/gm, "<h2>$1</h2>")
|
|
56
|
+
.replace(/^- (.+)/gm, "<li>$1</li>")
|
|
57
|
+
.replace(/\n\n/g, "</p><p>");
|
|
30
58
|
|
|
31
|
-
const confirmationHTML = (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.replace(/^# (.+)/gm, "<h2>$1</h2>").replace(/^- (.+)/gm, "<li>$1</li>")
|
|
37
|
-
.replace(/\n\n/g, "</p><p>");
|
|
59
|
+
const confirmationHTML = (
|
|
60
|
+
question: string,
|
|
61
|
+
plan: string,
|
|
62
|
+
diff: string,
|
|
63
|
+
): string => {
|
|
38
64
|
const diffLines = diff.split("\n");
|
|
39
|
-
const diffHtml = diffLines
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
65
|
+
const diffHtml = diffLines
|
|
66
|
+
.map((line) => {
|
|
67
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
68
|
+
return `<span class="df">${esc(line)}</span>`;
|
|
69
|
+
if (line.startsWith("@@"))
|
|
70
|
+
return `<span class="dh">${esc(line)}</span>`;
|
|
71
|
+
if (line.startsWith("+")) return `<span class="da">${esc(line)}</span>`;
|
|
72
|
+
if (line.startsWith("-")) return `<span class="dd">${esc(line)}</span>`;
|
|
73
|
+
return `<span class="dc">${esc(line)}</span>`;
|
|
74
|
+
})
|
|
75
|
+
.join("\n");
|
|
46
76
|
const addCount = (diff.match(/^\+/gm) || []).length;
|
|
47
|
-
const delCount =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.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}
|
|
61
|
-
.actions{display:flex;gap:10px;flex-wrap:wrap}
|
|
62
|
-
.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}
|
|
63
|
-
.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}
|
|
64
|
-
.chat-area{display:none;margin-top:12px}
|
|
65
|
-
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}
|
|
66
|
-
textarea:focus{border-color:#58a6ff}
|
|
67
|
-
.hint{color:#6e7681;font-size:12px;margin-top:10px}
|
|
68
|
-
</style></head><body>
|
|
69
|
-
<h2>${esc(question)}</h2>
|
|
70
|
-
<div class="plan"><p>${mdToHtml(plan)}</p></div>
|
|
71
|
-
${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>` : ""}
|
|
72
|
-
<form method="POST" id="f">
|
|
73
|
-
<input type="hidden" name="choice" id="c" value="Looks good">
|
|
74
|
-
<div class="actions">
|
|
75
|
-
<button class="btn-c" type="submit">✅ Looks good — move to next offender</button>
|
|
76
|
-
<button class="btn-chat" type="button" onclick="toggleChat()">💬 Request changes</button>
|
|
77
|
-
</div>
|
|
78
|
-
<div class="chat-area" id="ca"><textarea name="freeText" placeholder="Describe what you'd like changed..."></textarea>
|
|
79
|
-
<div style="margin-top:8px"><button class="btn-c" type="submit" onclick="document.getElementById('c').value='Redo'">Submit</button></div></div>
|
|
80
|
-
</form>
|
|
81
|
-
<p class="hint">Tab closes after submit · Ctrl+Enter to confirm</p>
|
|
82
|
-
<script>
|
|
83
|
-
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();}
|
|
84
|
-
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){document.getElementById('f').submit();}});
|
|
85
|
-
</script>
|
|
86
|
-
</body></html>`;
|
|
77
|
+
const delCount =
|
|
78
|
+
(diff.match(/^-/gm) || []).length - (diff.match(/^---/gm) || []).length;
|
|
79
|
+
|
|
80
|
+
return CONFIRMATION_HTML(
|
|
81
|
+
question,
|
|
82
|
+
plan,
|
|
83
|
+
diff,
|
|
84
|
+
esc,
|
|
85
|
+
mdToHtml,
|
|
86
|
+
diffHtml,
|
|
87
|
+
addCount,
|
|
88
|
+
delCount,
|
|
89
|
+
);
|
|
87
90
|
};
|
|
88
91
|
|
|
89
|
-
const interviewHTML = (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
92
|
+
const interviewHTML = (
|
|
93
|
+
question: string,
|
|
94
|
+
options: InterviewOption[],
|
|
95
|
+
_timeoutSeconds: number,
|
|
96
|
+
_plan?: string,
|
|
97
|
+
_diff?: string,
|
|
98
|
+
_confirmationMode?: boolean,
|
|
99
|
+
): string => {
|
|
100
|
+
if (_confirmationMode && _plan && _diff)
|
|
101
|
+
return confirmationHTML(question, _plan, _diff);
|
|
102
|
+
|
|
103
|
+
const optionsHtml = options
|
|
104
|
+
.map((opt, idx) => {
|
|
105
|
+
const impactBadge = (val: number, label: string, good: boolean) =>
|
|
106
|
+
`<span class="ib ${good ? "up" : "dn"}">${val > 0 ? "+" : ""}${val} ${label}</span>`;
|
|
107
|
+
let impactHtml = "";
|
|
108
|
+
if (opt.impact) {
|
|
109
|
+
const parts: string[] = [];
|
|
110
|
+
if (opt.impact.linesReduced !== undefined)
|
|
111
|
+
parts.push(impactBadge(opt.impact.linesReduced, "lines", true));
|
|
112
|
+
if (opt.impact.miProjection)
|
|
113
|
+
parts.push(
|
|
114
|
+
`<span class="ib proj">MI ${opt.impact.miProjection}</span>`,
|
|
115
|
+
);
|
|
116
|
+
if (opt.impact.cognitiveProjection)
|
|
117
|
+
parts.push(
|
|
118
|
+
`<span class="ib proj">Cognitive ${opt.impact.cognitiveProjection}</span>`,
|
|
119
|
+
);
|
|
120
|
+
if (parts.length)
|
|
121
|
+
impactHtml = `<div class="impact">${parts.join("")}</div>`;
|
|
122
|
+
}
|
|
123
|
+
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>`;
|
|
124
|
+
})
|
|
125
|
+
.join("\n");
|
|
104
126
|
const hasFreeText = options.some((o) => o.value === "__free__");
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
<style>
|
|
108
|
-
*{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}
|
|
109
|
-
.question{font-size:15px;font-weight:600;color:#f0f6fc;margin-bottom:12px}
|
|
110
|
-
.opts{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}
|
|
111
|
-
.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}
|
|
112
|
-
.card:hover,.card.selected{border-color:#58a6ff;background:#0d1f30}.card.rec{border-color:#1f6feb}
|
|
113
|
-
.card input{margin-top:3px;accent-color:#58a6ff;flex-shrink:0}.card-body{flex:1}
|
|
114
|
-
.card-top{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
115
|
-
.num{color:#6e7681;font-size:13px;min-width:18px}.lbl{font-size:13.5px;font-weight:500}
|
|
116
|
-
.badge-rec{background:#1f4e2e;color:#3fb950;font-size:10px;padding:1px 7px;border-radius:10px;margin-left:4px;font-weight:600}
|
|
117
|
-
.ctx{color:#8b949e;font-size:12px;margin-top:3px;padding-left:22px}
|
|
118
|
-
.impact{display:flex;gap:6px;margin-top:5px;padding-left:22px;flex-wrap:wrap}
|
|
119
|
-
.ib{font-size:11px;padding:2px 8px;border-radius:10px;font-family:monospace;font-weight:600}
|
|
120
|
-
.ib.up{background:#1a3a2a;color:#3fb950;border:1px solid #238636}
|
|
121
|
-
.ib.dn{background:#3a1a1a;color:#ff7b72;border:1px solid #f85149}
|
|
122
|
-
.ib.proj{background:#1a2a3a;color:#79c0ff;border:1px solid #1f6feb}
|
|
123
|
-
.free-area{display:none;margin-top:10px;padding-left:22px}
|
|
124
|
-
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}
|
|
125
|
-
textarea:focus{border-color:#58a6ff}
|
|
126
|
-
.submit-row{display:flex;align-items:center;gap:12px;margin-top:4px}
|
|
127
|
-
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}
|
|
128
|
-
button:hover{background:#2ea043}.hint{color:#6e7681;font-size:12px}
|
|
129
|
-
</style></head><body>
|
|
130
|
-
<div class="question">${esc(question)}</div>
|
|
131
|
-
<form method="POST" id="f">
|
|
132
|
-
<div class="opts">${optionsHtml}</div>
|
|
133
|
-
${hasFreeText ? '<div class="free-area" id="fa"><textarea name="freeText" placeholder="Describe your preferred approach..."></textarea></div>' : ""}
|
|
134
|
-
<div class="submit-row"><button type="submit">Submit</button><span class="hint">Ctrl+Enter</span></div>
|
|
135
|
-
</form>
|
|
136
|
-
<script>
|
|
137
|
-
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';}
|
|
138
|
-
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]);
|
|
139
|
-
document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter')document.getElementById('f').submit();});
|
|
140
|
-
</script></body></html>`;
|
|
127
|
+
|
|
128
|
+
return INTERVIEW_HTML(question, optionsHtml, hasFreeText, esc);
|
|
141
129
|
};
|
|
142
130
|
|
|
143
|
-
const openBrowserInterview = (
|
|
144
|
-
|
|
145
|
-
|
|
131
|
+
const openBrowserInterview = (
|
|
132
|
+
html: string,
|
|
133
|
+
timeoutSeconds: number,
|
|
134
|
+
): Promise<string | null> => {
|
|
146
135
|
return new Promise((resolve) => {
|
|
147
136
|
const getPort = (cb: (port: number) => void) => {
|
|
148
137
|
const s = net.createServer();
|
|
149
|
-
s.listen(0, () => {
|
|
138
|
+
s.listen(0, () => {
|
|
139
|
+
const p = (s.address() as net.AddressInfo).port;
|
|
140
|
+
s.close(() => cb(p));
|
|
141
|
+
});
|
|
150
142
|
s.on("error", () => cb(-1));
|
|
151
143
|
};
|
|
152
144
|
getPort((port) => {
|
|
153
|
-
if (port < 0) {
|
|
145
|
+
if (port < 0) {
|
|
146
|
+
resolve(null);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
154
149
|
const server = http.createServer((req, res) => {
|
|
155
150
|
if (req.method === "GET") {
|
|
156
151
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
157
152
|
res.end(html);
|
|
158
153
|
} else if (req.method === "POST") {
|
|
159
154
|
let body = "";
|
|
160
|
-
req.on("data", (c: Buffer) => {
|
|
155
|
+
req.on("data", (c: Buffer) => {
|
|
156
|
+
body += c.toString();
|
|
157
|
+
});
|
|
161
158
|
req.on("end", () => {
|
|
162
159
|
const p = new URLSearchParams(body);
|
|
163
160
|
const choice = p.get("choice") ?? "";
|
|
164
161
|
const freeText = p.get("freeText") ?? "";
|
|
165
|
-
const final =
|
|
166
|
-
|
|
167
|
-
|
|
162
|
+
const final =
|
|
163
|
+
choice === "__free__" || choice === "Redo"
|
|
164
|
+
? freeText.trim()
|
|
165
|
+
: choice;
|
|
166
|
+
res.writeHead(200, {
|
|
167
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
168
|
+
});
|
|
169
|
+
res.end(
|
|
170
|
+
`<!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>`,
|
|
171
|
+
);
|
|
168
172
|
clearTimeout(timer);
|
|
169
173
|
server.close();
|
|
170
174
|
resolve(final || null);
|
|
@@ -172,25 +176,48 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
172
176
|
}
|
|
173
177
|
});
|
|
174
178
|
server.listen(port);
|
|
175
|
-
const { spawnSync } = require("node:child_process");
|
|
176
179
|
const url = `http://localhost:${port}`;
|
|
177
|
-
if (process.platform === "win32")
|
|
180
|
+
if (process.platform === "win32")
|
|
181
|
+
spawnSync("cmd", ["/c", "start", "", url], { shell: false });
|
|
178
182
|
else if (process.platform === "darwin") spawnSync("open", [url]);
|
|
179
183
|
else spawnSync("xdg-open", [url]);
|
|
180
|
-
const timer = setTimeout(() => {
|
|
184
|
+
const timer = setTimeout(() => {
|
|
185
|
+
server.close();
|
|
186
|
+
resolve(null);
|
|
187
|
+
}, timeoutSeconds * 1000);
|
|
181
188
|
});
|
|
182
189
|
});
|
|
183
190
|
};
|
|
184
191
|
|
|
185
|
-
interviewHandler = (
|
|
186
|
-
|
|
192
|
+
interviewHandler = (
|
|
193
|
+
question,
|
|
194
|
+
options,
|
|
195
|
+
timeoutSeconds,
|
|
196
|
+
plan,
|
|
197
|
+
diff,
|
|
198
|
+
confirmationMode,
|
|
199
|
+
) =>
|
|
200
|
+
openBrowserInterview(
|
|
201
|
+
interviewHTML(
|
|
202
|
+
question,
|
|
203
|
+
options,
|
|
204
|
+
timeoutSeconds,
|
|
205
|
+
plan,
|
|
206
|
+
diff,
|
|
207
|
+
confirmationMode,
|
|
208
|
+
),
|
|
209
|
+
timeoutSeconds,
|
|
210
|
+
);
|
|
187
211
|
|
|
188
212
|
pi.registerTool({
|
|
189
213
|
name: "interviewer",
|
|
190
214
|
label: "Interview",
|
|
191
|
-
description:
|
|
215
|
+
description:
|
|
216
|
+
"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.",
|
|
192
217
|
parameters: Type.Object({
|
|
193
|
-
question: Type.String({
|
|
218
|
+
question: Type.String({
|
|
219
|
+
description: "The question to present to the user",
|
|
220
|
+
}),
|
|
194
221
|
options: Type.Optional(
|
|
195
222
|
Type.Array(
|
|
196
223
|
Type.Object({
|
|
@@ -208,14 +235,34 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
208
235
|
}),
|
|
209
236
|
),
|
|
210
237
|
),
|
|
211
|
-
plan: Type.Optional(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
238
|
+
plan: Type.Optional(
|
|
239
|
+
Type.String({
|
|
240
|
+
description:
|
|
241
|
+
"Refactoring plan (markdown) — shows in confirmation mode",
|
|
242
|
+
}),
|
|
243
|
+
),
|
|
244
|
+
diff: Type.Optional(
|
|
245
|
+
Type.String({
|
|
246
|
+
description: "Unified diff text — shows in confirmation mode",
|
|
247
|
+
}),
|
|
248
|
+
),
|
|
249
|
+
confirmationMode: Type.Optional(
|
|
250
|
+
Type.Boolean({ description: "Show plan+diff confirmation screen" }),
|
|
251
|
+
),
|
|
252
|
+
timeoutSeconds: Type.Optional(
|
|
253
|
+
Type.Number({
|
|
254
|
+
description: "Auto-close after this many seconds (default 600)",
|
|
255
|
+
}),
|
|
256
|
+
),
|
|
215
257
|
}),
|
|
216
258
|
async execute(_toolCallId, input, _signal, _onUpdate, _ctx) {
|
|
217
259
|
if (!interviewHandler)
|
|
218
|
-
return {
|
|
260
|
+
return {
|
|
261
|
+
content: [
|
|
262
|
+
{ type: "text" as const, text: "Interview tool not initialized" },
|
|
263
|
+
],
|
|
264
|
+
details: null,
|
|
265
|
+
};
|
|
219
266
|
const result = await interviewHandler(
|
|
220
267
|
input.question,
|
|
221
268
|
input.options ?? [],
|
|
@@ -224,7 +271,15 @@ document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Ente
|
|
|
224
271
|
input.diff,
|
|
225
272
|
input.confirmationMode,
|
|
226
273
|
);
|
|
227
|
-
return {
|
|
274
|
+
return {
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text" as const,
|
|
278
|
+
text: result ?? "No response (timed out or dismissed)",
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
details: result ?? null,
|
|
282
|
+
};
|
|
228
283
|
},
|
|
229
284
|
});
|
|
230
285
|
|
package/clients/knip-client.js
CHANGED
|
@@ -38,7 +38,7 @@ export class KnipClient {
|
|
|
38
38
|
/**
|
|
39
39
|
* Run knip analysis on the project
|
|
40
40
|
*/
|
|
41
|
-
analyze(cwd) {
|
|
41
|
+
analyze(cwd, ignore) {
|
|
42
42
|
if (!this.isAvailable()) {
|
|
43
43
|
return {
|
|
44
44
|
success: false,
|
|
@@ -52,12 +52,16 @@ export class KnipClient {
|
|
|
52
52
|
}
|
|
53
53
|
const targetDir = cwd || process.cwd();
|
|
54
54
|
try {
|
|
55
|
-
const
|
|
55
|
+
const args = [
|
|
56
56
|
"knip",
|
|
57
57
|
"--reporter=json",
|
|
58
58
|
"--include",
|
|
59
|
-
"exports,types,dependencies,unlisted",
|
|
60
|
-
]
|
|
59
|
+
"files,exports,types,dependencies,unlisted",
|
|
60
|
+
];
|
|
61
|
+
if (ignore && ignore.length > 0) {
|
|
62
|
+
args.push("--ignore", ignore.join(","));
|
|
63
|
+
}
|
|
64
|
+
const result = spawnSync("npx", args, {
|
|
61
65
|
encoding: "utf-8",
|
|
62
66
|
timeout: 30000,
|
|
63
67
|
cwd: targetDir,
|
|
@@ -65,6 +69,10 @@ export class KnipClient {
|
|
|
65
69
|
});
|
|
66
70
|
// Knip exits 0 on success (even with issues), 1 on errors
|
|
67
71
|
const output = result.stdout || "";
|
|
72
|
+
this.log(`Knip output length: ${output.length}`);
|
|
73
|
+
if (output.length < 500) {
|
|
74
|
+
this.log(`Knip output sample: ${output}`);
|
|
75
|
+
}
|
|
68
76
|
if (!output.trim()) {
|
|
69
77
|
return {
|
|
70
78
|
success: true,
|
package/clients/knip-client.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class KnipClient {
|
|
|
67
67
|
/**
|
|
68
68
|
* Run knip analysis on the project
|
|
69
69
|
*/
|
|
70
|
-
analyze(cwd?: string): KnipResult {
|
|
70
|
+
analyze(cwd?: string, ignore?: string[]): KnipResult {
|
|
71
71
|
if (!this.isAvailable()) {
|
|
72
72
|
return {
|
|
73
73
|
success: false,
|
|
@@ -83,24 +83,29 @@ export class KnipClient {
|
|
|
83
83
|
const targetDir = cwd || process.cwd();
|
|
84
84
|
|
|
85
85
|
try {
|
|
86
|
-
const
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
const args = [
|
|
87
|
+
"knip",
|
|
88
|
+
"--reporter=json",
|
|
89
|
+
"--include",
|
|
90
|
+
"files,exports,types,dependencies,unlisted",
|
|
91
|
+
];
|
|
92
|
+
if (ignore && ignore.length > 0) {
|
|
93
|
+
args.push("--ignore", ignore.join(","));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const result = spawnSync("npx", args, {
|
|
97
|
+
encoding: "utf-8",
|
|
98
|
+
timeout: 30000,
|
|
99
|
+
cwd: targetDir,
|
|
100
|
+
shell: true,
|
|
101
|
+
});
|
|
101
102
|
|
|
102
103
|
// Knip exits 0 on success (even with issues), 1 on errors
|
|
103
104
|
const output = result.stdout || "";
|
|
105
|
+
this.log(`Knip output length: ${output.length}`);
|
|
106
|
+
if (output.length < 500) {
|
|
107
|
+
this.log(`Knip output sample: ${output}`);
|
|
108
|
+
}
|
|
104
109
|
if (!output.trim()) {
|
|
105
110
|
return {
|
|
106
111
|
success: true,
|