supipowers 1.2.6 → 1.5.0
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 +118 -56
- package/bin/install.ts +48 -128
- package/package.json +11 -3
- package/skills/code-review/SKILL.md +137 -40
- package/skills/context-mode/SKILL.md +67 -56
- package/skills/creating-supi-agents/SKILL.md +204 -0
- package/skills/debugging/SKILL.md +86 -40
- package/skills/fix-pr/SKILL.md +96 -65
- package/skills/planning/SKILL.md +103 -46
- package/skills/qa-strategy/SKILL.md +68 -46
- package/skills/receiving-code-review/SKILL.md +60 -53
- package/skills/release/SKILL.md +111 -39
- package/skills/tdd/SKILL.md +118 -67
- package/skills/verification/SKILL.md +71 -37
- package/src/bootstrap.ts +27 -5
- package/src/commands/agents.ts +249 -0
- package/src/commands/ai-review.ts +1113 -0
- package/src/commands/config.ts +224 -95
- package/src/commands/doctor.ts +19 -13
- package/src/commands/fix-pr.ts +8 -11
- package/src/commands/generate.ts +200 -0
- package/src/commands/model-picker.ts +5 -15
- package/src/commands/model.ts +4 -5
- package/src/commands/optimize-context.ts +202 -0
- package/src/commands/plan.ts +148 -92
- package/src/commands/qa.ts +14 -23
- package/src/commands/release.ts +504 -275
- package/src/commands/review.ts +643 -86
- package/src/commands/status.ts +44 -17
- package/src/commands/supi.ts +69 -41
- package/src/commands/update.ts +57 -2
- package/src/config/defaults.ts +6 -39
- package/src/config/loader.ts +388 -40
- package/src/config/model-resolver.ts +26 -22
- package/src/config/schema.ts +113 -48
- package/src/context/analyzer.ts +61 -2
- package/src/context/optimizer.ts +199 -0
- package/src/context-mode/compressor.ts +14 -11
- package/src/context-mode/detector.ts +16 -54
- package/src/context-mode/event-extractor.ts +45 -16
- package/src/context-mode/event-store.ts +225 -16
- package/src/context-mode/hooks.ts +195 -22
- package/src/context-mode/knowledge/chunker.ts +235 -0
- package/src/context-mode/knowledge/store.ts +187 -0
- package/src/context-mode/routing.ts +12 -23
- package/src/context-mode/sandbox/executor.ts +183 -0
- package/src/context-mode/sandbox/runners.ts +40 -0
- package/src/context-mode/snapshot-builder.ts +243 -7
- package/src/context-mode/tools.ts +440 -0
- package/src/context-mode/web/fetcher.ts +117 -0
- package/src/context-mode/web/html-to-md.ts +293 -0
- package/src/debug/logger.ts +107 -0
- package/src/deps/registry.ts +0 -20
- package/src/docs/drift.ts +454 -0
- package/src/fix-pr/fetch-comments.ts +66 -0
- package/src/git/commit-msg.ts +2 -1
- package/src/git/commit.ts +123 -141
- package/src/git/conventions.ts +2 -2
- package/src/git/status.ts +4 -1
- package/src/lsp/bridge.ts +138 -12
- package/src/planning/approval-flow.ts +125 -19
- package/src/planning/plan-writer-prompt.ts +4 -11
- package/src/planning/planning-ask-tool.ts +81 -0
- package/src/planning/prompt-builder.ts +9 -169
- package/src/planning/system-prompt.ts +290 -0
- package/src/platform/omp.ts +50 -4
- package/src/platform/progress.ts +182 -0
- package/src/platform/test-utils.ts +4 -1
- package/src/platform/tui-colors.ts +30 -0
- package/src/platform/types.ts +1 -0
- package/src/qa/detect-app-type.ts +102 -0
- package/src/qa/discover-routes.ts +353 -0
- package/src/quality/ai-session.ts +96 -0
- package/src/quality/ai-setup.ts +86 -0
- package/src/quality/gates/ai-review.ts +129 -0
- package/src/quality/gates/build.ts +8 -0
- package/src/quality/gates/command.ts +150 -0
- package/src/quality/gates/format.ts +28 -0
- package/src/quality/gates/lint.ts +22 -0
- package/src/quality/gates/lsp-diagnostics.ts +84 -0
- package/src/quality/gates/test-suite.ts +8 -0
- package/src/quality/gates/typecheck.ts +22 -0
- package/src/quality/registry.ts +25 -0
- package/src/quality/review-gates.ts +33 -0
- package/src/quality/runner.ts +268 -0
- package/src/quality/schemas.ts +48 -0
- package/src/quality/setup.ts +227 -0
- package/src/release/changelog.ts +7 -3
- package/src/release/channels/custom.ts +43 -0
- package/src/release/channels/gitea.ts +35 -0
- package/src/release/channels/github.ts +35 -0
- package/src/release/channels/gitlab.ts +35 -0
- package/src/release/channels/registry.ts +52 -0
- package/src/release/channels/types.ts +27 -0
- package/src/release/detector.ts +10 -63
- package/src/release/executor.ts +61 -51
- package/src/release/prompt.ts +38 -38
- package/src/release/version.ts +129 -10
- package/src/review/agent-loader.ts +331 -0
- package/src/review/consolidator.ts +180 -0
- package/src/review/default-agents/correctness.md +72 -0
- package/src/review/default-agents/maintainability.md +64 -0
- package/src/review/default-agents/security.md +67 -0
- package/src/review/fixer.ts +219 -0
- package/src/review/multi-agent-runner.ts +135 -0
- package/src/review/output.ts +147 -0
- package/src/review/prompts/agent-review-wrapper.md +36 -0
- package/src/review/prompts/fix-findings.md +32 -0
- package/src/review/prompts/fix-output-schema.md +18 -0
- package/src/review/prompts/invalid-output-retry.md +22 -0
- package/src/review/prompts/output-instructions.md +14 -0
- package/src/review/prompts/review-output-schema.md +38 -0
- package/src/review/prompts/single-review.md +53 -0
- package/src/review/prompts/validation-review.md +30 -0
- package/src/review/runner.ts +128 -0
- package/src/review/scope.ts +353 -0
- package/src/review/template.ts +15 -0
- package/src/review/types.ts +296 -0
- package/src/review/validator.ts +160 -0
- package/src/storage/plans.ts +5 -3
- package/src/storage/reports.ts +50 -7
- package/src/storage/review-sessions.ts +117 -0
- package/src/text.ts +19 -0
- package/src/types.ts +336 -26
- package/src/utils/paths.ts +39 -0
- package/src/visual/companion.ts +5 -3
- package/src/visual/start-server.ts +101 -0
- package/src/visual/stop-server.ts +39 -0
- package/bin/ctx-mode-wrapper.mjs +0 -66
- package/src/config/profiles.ts +0 -64
- package/src/context-mode/installer.ts +0 -38
- package/src/quality/ai-review-gate.ts +0 -43
- package/src/quality/gate-runner.ts +0 -67
- package/src/quality/lsp-gate.ts +0 -24
- package/src/quality/test-gate.ts +0 -39
- package/src/visual/scripts/start-server.sh +0 -98
- package/src/visual/scripts/stop-server.sh +0 -21
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regex-based HTML-to-Markdown converter.
|
|
3
|
+
* No external dependencies — operates on raw HTML strings.
|
|
4
|
+
*
|
|
5
|
+
* Three phases:
|
|
6
|
+
* 1. Strip unwanted elements (script, style, nav, footer, header, aside, hidden, comments)
|
|
7
|
+
* 2. Convert remaining HTML to Markdown (pre/code protected via placeholders)
|
|
8
|
+
* 3. Normalize (entities, whitespace, trim)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ── Phase 1: Stripping ──────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const STRIP_TAGS = ["script", "style", "nav", "footer", "header", "aside"];
|
|
14
|
+
const STRIP_PATTERNS: RegExp[] = [
|
|
15
|
+
// Unwanted block elements
|
|
16
|
+
...STRIP_TAGS.map(
|
|
17
|
+
(tag) => new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, "gi"),
|
|
18
|
+
),
|
|
19
|
+
// Elements with display:none
|
|
20
|
+
/<[^>]+style\s*=\s*"[^"]*display\s*:\s*none[^"]*"[^>]*>[\s\S]*?<\/[^>]+>/gi,
|
|
21
|
+
// Elements with aria-hidden="true"
|
|
22
|
+
/<[^>]+aria-hidden\s*=\s*"true"[^>]*>[\s\S]*?<\/[^>]+>/gi,
|
|
23
|
+
// HTML comments
|
|
24
|
+
/<!--[\s\S]*?-->/g,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function stripUnwanted(html: string): string {
|
|
28
|
+
let result = html;
|
|
29
|
+
for (const pattern of STRIP_PATTERNS) {
|
|
30
|
+
result = result.replace(pattern, "");
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Phase 2: Conversion ─────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
type Placeholder = { key: string; content: string };
|
|
38
|
+
|
|
39
|
+
let placeholderCounter = 0;
|
|
40
|
+
|
|
41
|
+
function protectPreBlocks(html: string): { html: string; placeholders: Placeholder[] } {
|
|
42
|
+
const placeholders: Placeholder[] = [];
|
|
43
|
+
|
|
44
|
+
// Match <pre><code class="language-X">...</code></pre>
|
|
45
|
+
// and <pre><code>...</code></pre>
|
|
46
|
+
// and <pre>...</pre> (no nested code)
|
|
47
|
+
const result = html.replace(
|
|
48
|
+
/<pre[^>]*>\s*(?:<code(?:\s+class\s*=\s*"([^"]*)")?[^>]*>([\s\S]*?)<\/code>|([\s\S]*?))\s*<\/pre>/gi,
|
|
49
|
+
(_match, codeClass: string | undefined, codeContent: string | undefined, preContent: string | undefined) => {
|
|
50
|
+
const raw = codeContent ?? preContent ?? "";
|
|
51
|
+
// Decode entities inside code blocks so content is verbatim
|
|
52
|
+
const text = decodeEntities(raw).replace(/^\n|\n$/g, "");
|
|
53
|
+
|
|
54
|
+
let lang = "";
|
|
55
|
+
if (codeClass) {
|
|
56
|
+
const langMatch = codeClass.match(/language-(\S+)/);
|
|
57
|
+
if (langMatch) lang = langMatch[1];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const md = lang ? `\`\`\`${lang}\n${text}\n\`\`\`` : `\`\`\`\n${text}\n\`\`\``;
|
|
61
|
+
const key = `__PRE_PLACEHOLDER_${placeholderCounter++}__`;
|
|
62
|
+
placeholders.push({ key, content: md });
|
|
63
|
+
return key;
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return { html: result, placeholders };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function restorePlaceholders(md: string, placeholders: Placeholder[]): string {
|
|
71
|
+
let result = md;
|
|
72
|
+
for (const { key, content } of placeholders) {
|
|
73
|
+
result = result.replace(key, content);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function convertInlineCode(html: string): string {
|
|
79
|
+
return html.replace(/<code[^>]*>([\s\S]*?)<\/code>/gi, (_m, content: string) => {
|
|
80
|
+
return `\`${content.trim()}\``;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function convertHeadings(html: string): string {
|
|
85
|
+
return html.replace(
|
|
86
|
+
/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi,
|
|
87
|
+
(_m, level: string, content: string) => {
|
|
88
|
+
const hashes = "#".repeat(Number(level));
|
|
89
|
+
return `\n\n${hashes} ${content.trim()}\n\n`;
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function convertParagraphs(html: string): string {
|
|
95
|
+
return html.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_m, content: string) => {
|
|
96
|
+
return `\n\n${content.trim()}\n\n`;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function convertLinks(html: string): string {
|
|
101
|
+
return html.replace(
|
|
102
|
+
/<a\s+[^>]*href\s*=\s*"([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi,
|
|
103
|
+
(_m, href: string, text: string) => `[${text.trim()}](${href})`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function convertImages(html: string): string {
|
|
108
|
+
return html.replace(
|
|
109
|
+
/<img\s+[^>]*src\s*=\s*"([^"]*)"[^>]*alt\s*=\s*"([^"]*)"[^>]*\/?>/gi,
|
|
110
|
+
(_m, src: string, alt: string) => ``,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function convertImagesAltFirst(html: string): string {
|
|
115
|
+
// alt before src ordering
|
|
116
|
+
return html.replace(
|
|
117
|
+
/<img\s+[^>]*alt\s*=\s*"([^"]*)"[^>]*src\s*=\s*"([^"]*)"[^>]*\/?>/gi,
|
|
118
|
+
(_m, alt: string, src: string) => ``,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function convertBoldItalic(html: string): string {
|
|
123
|
+
let result = html;
|
|
124
|
+
result = result.replace(/<(?:strong|b)[^>]*>([\s\S]*?)<\/(?:strong|b)>/gi, "**$1**");
|
|
125
|
+
result = result.replace(/<(?:em|i)[^>]*>([\s\S]*?)<\/(?:em|i)>/gi, "*$1*");
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function convertLists(html: string): string {
|
|
130
|
+
let result = html;
|
|
131
|
+
|
|
132
|
+
// Unordered lists
|
|
133
|
+
result = result.replace(/<ul[^>]*>([\s\S]*?)<\/ul>/gi, (_m, inner: string) => {
|
|
134
|
+
const items: string[] = [];
|
|
135
|
+
inner.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m2: string, content: string) => {
|
|
136
|
+
items.push(`- ${content.trim()}`);
|
|
137
|
+
return "";
|
|
138
|
+
});
|
|
139
|
+
return `\n${items.join("\n")}\n`;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Ordered lists
|
|
143
|
+
result = result.replace(/<ol[^>]*>([\s\S]*?)<\/ol>/gi, (_m, inner: string) => {
|
|
144
|
+
const items: string[] = [];
|
|
145
|
+
let idx = 1;
|
|
146
|
+
inner.replace(/<li[^>]*>([\s\S]*?)<\/li>/gi, (_m2: string, content: string) => {
|
|
147
|
+
items.push(`${idx++}. ${content.trim()}`);
|
|
148
|
+
return "";
|
|
149
|
+
});
|
|
150
|
+
return `\n${items.join("\n")}\n`;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function convertTables(html: string): string {
|
|
157
|
+
return html.replace(/<table[^>]*>([\s\S]*?)<\/table>/gi, (_m, tableInner: string) => {
|
|
158
|
+
const rows: string[][] = [];
|
|
159
|
+
|
|
160
|
+
// Extract rows from thead and tbody, or directly
|
|
161
|
+
const allRows = tableInner.match(/<tr[^>]*>[\s\S]*?<\/tr>/gi) ?? [];
|
|
162
|
+
for (const row of allRows) {
|
|
163
|
+
const cells: string[] = [];
|
|
164
|
+
const cellPattern = /<(?:th|td)[^>]*>([\s\S]*?)<\/(?:th|td)>/gi;
|
|
165
|
+
let cellMatch: RegExpExecArray | null;
|
|
166
|
+
while ((cellMatch = cellPattern.exec(row)) !== null) {
|
|
167
|
+
cells.push(cellMatch[1].trim());
|
|
168
|
+
}
|
|
169
|
+
if (cells.length > 0) rows.push(cells);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (rows.length === 0) return "";
|
|
173
|
+
|
|
174
|
+
const colCount = Math.max(...rows.map((r) => r.length));
|
|
175
|
+
const lines: string[] = [];
|
|
176
|
+
|
|
177
|
+
for (let i = 0; i < rows.length; i++) {
|
|
178
|
+
const cells = rows[i];
|
|
179
|
+
// Pad to colCount
|
|
180
|
+
while (cells.length < colCount) cells.push("");
|
|
181
|
+
lines.push(`| ${cells.join(" | ")} |`);
|
|
182
|
+
|
|
183
|
+
// After the first row (header), insert separator
|
|
184
|
+
if (i === 0) {
|
|
185
|
+
lines.push(`| ${cells.map(() => "---").join(" | ")} |`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return `\n${lines.join("\n")}\n`;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function convertBlockquotes(html: string): string {
|
|
194
|
+
return html.replace(/<blockquote[^>]*>([\s\S]*?)<\/blockquote>/gi, (_m, content: string) => {
|
|
195
|
+
const lines = content.trim().split("\n");
|
|
196
|
+
return `\n${lines.map((l) => `> ${l.trim()}`).join("\n")}\n`;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function convertBrHr(html: string): string {
|
|
201
|
+
let result = html;
|
|
202
|
+
result = result.replace(/<br\s*\/?>/gi, "\n");
|
|
203
|
+
result = result.replace(/<hr\s*\/?>/gi, "\n---\n");
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function unwrapTags(html: string): string {
|
|
208
|
+
// Remove wrapper-only tags, keeping inner content
|
|
209
|
+
return html.replace(
|
|
210
|
+
/<\/?(div|section|article|main|span)[^>]*>/gi,
|
|
211
|
+
"",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function stripRemainingTags(html: string): string {
|
|
216
|
+
return html.replace(/<\/?[^>]+(>|$)/g, "");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── Phase 3: Normalize ──────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
function decodeEntities(html: string): string {
|
|
222
|
+
let result = html;
|
|
223
|
+
result = result.replace(/&/g, "&");
|
|
224
|
+
result = result.replace(/</g, "<");
|
|
225
|
+
result = result.replace(/>/g, ">");
|
|
226
|
+
result = result.replace(/"/g, '"');
|
|
227
|
+
result = result.replace(/'|'/g, "'");
|
|
228
|
+
result = result.replace(/ /g, " ");
|
|
229
|
+
// Numeric entities &#NNN;
|
|
230
|
+
result = result.replace(/&#(\d+);/g, (_m, code: string) =>
|
|
231
|
+
String.fromCharCode(Number(code)),
|
|
232
|
+
);
|
|
233
|
+
// Hex entities &#xHHH;
|
|
234
|
+
result = result.replace(/&#x([0-9a-fA-F]+);/g, (_m, hex: string) =>
|
|
235
|
+
String.fromCharCode(parseInt(hex, 16)),
|
|
236
|
+
);
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function normalizeWhitespace(md: string): string {
|
|
241
|
+
// Collapse 3+ consecutive newlines to 2
|
|
242
|
+
return md.replace(/\n{3,}/g, "\n\n");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── Main ─────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
export function htmlToMarkdown(html: string): string {
|
|
248
|
+
if (!html || !html.trim()) return "";
|
|
249
|
+
|
|
250
|
+
// Phase 1: Strip unwanted elements
|
|
251
|
+
let result = stripUnwanted(html);
|
|
252
|
+
|
|
253
|
+
// Phase 2: Convert
|
|
254
|
+
|
|
255
|
+
// Protect <pre>/<code> blocks first
|
|
256
|
+
const { html: withPlaceholders, placeholders } = protectPreBlocks(result);
|
|
257
|
+
result = withPlaceholders;
|
|
258
|
+
|
|
259
|
+
// Inline code (not inside pre — those are already placeholders)
|
|
260
|
+
result = convertInlineCode(result);
|
|
261
|
+
|
|
262
|
+
// Block-level conversions
|
|
263
|
+
result = convertHeadings(result);
|
|
264
|
+
result = convertBlockquotes(result);
|
|
265
|
+
result = convertTables(result);
|
|
266
|
+
result = convertLists(result);
|
|
267
|
+
result = convertParagraphs(result);
|
|
268
|
+
|
|
269
|
+
// Inline conversions
|
|
270
|
+
result = convertLinks(result);
|
|
271
|
+
result = convertImages(result);
|
|
272
|
+
result = convertImagesAltFirst(result);
|
|
273
|
+
result = convertBoldItalic(result);
|
|
274
|
+
|
|
275
|
+
// Line breaks and rules
|
|
276
|
+
result = convertBrHr(result);
|
|
277
|
+
|
|
278
|
+
// Unwrap structural tags
|
|
279
|
+
result = unwrapTags(result);
|
|
280
|
+
|
|
281
|
+
// Strip any remaining HTML tags
|
|
282
|
+
result = stripRemainingTags(result);
|
|
283
|
+
|
|
284
|
+
// Restore protected code blocks
|
|
285
|
+
result = restorePlaceholders(result, placeholders);
|
|
286
|
+
|
|
287
|
+
// Phase 3: Normalize
|
|
288
|
+
result = decodeEntities(result);
|
|
289
|
+
result = normalizeWhitespace(result);
|
|
290
|
+
result = result.trim();
|
|
291
|
+
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { PlatformPaths } from "../platform/types.js";
|
|
4
|
+
|
|
5
|
+
export type DebugSessionContext = {
|
|
6
|
+
cwd?: string;
|
|
7
|
+
sessionManager?: { getSessionFile?: () => string } | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface DebugLogger {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
tool: string;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
filePath: string | null;
|
|
15
|
+
log(event: string, data?: Record<string, unknown>): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TRUTHY_DEBUG_VALUES = new Set(["1", "true", "yes", "on"]);
|
|
19
|
+
|
|
20
|
+
export function isDebugEnabled(): boolean {
|
|
21
|
+
const value = process.env.SUPI_DEBUG?.trim().toLowerCase();
|
|
22
|
+
return value != null && TRUTHY_DEBUG_VALUES.has(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sanitizeSegment(value: string): string {
|
|
26
|
+
return value
|
|
27
|
+
.trim()
|
|
28
|
+
.replace(/\.[^./\\]+$/, "")
|
|
29
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
30
|
+
.replace(/-+/g, "-")
|
|
31
|
+
.replace(/^[-.]+|[-.]+$/g, "")
|
|
32
|
+
.toLowerCase();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveSessionMetadata(ctx?: DebugSessionContext): {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
sessionFile: string | null;
|
|
38
|
+
} {
|
|
39
|
+
try {
|
|
40
|
+
const sessionFile = ctx?.sessionManager?.getSessionFile?.();
|
|
41
|
+
if (typeof sessionFile === "string" && sessionFile.length > 0) {
|
|
42
|
+
const baseName = path.basename(sessionFile);
|
|
43
|
+
const sessionId = sanitizeSegment(baseName) || "unknown-session";
|
|
44
|
+
return { sessionId, sessionFile };
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Debug logging must never break the main workflow.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { sessionId: `process-${process.pid}`, sessionFile: null };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function appendJsonLine(filePath: string, entry: Record<string, unknown>): void {
|
|
54
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
55
|
+
fs.appendFileSync(filePath, JSON.stringify(entry) + "\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createDebugLogger(
|
|
59
|
+
paths: PlatformPaths,
|
|
60
|
+
ctx: DebugSessionContext | undefined,
|
|
61
|
+
tool: string,
|
|
62
|
+
): DebugLogger {
|
|
63
|
+
const cwd = ctx?.cwd ?? process.cwd();
|
|
64
|
+
const { sessionId, sessionFile } = resolveSessionMetadata(ctx);
|
|
65
|
+
const sanitizedTool = sanitizeSegment(tool) || "unknown-tool";
|
|
66
|
+
|
|
67
|
+
if (!isDebugEnabled()) {
|
|
68
|
+
return {
|
|
69
|
+
enabled: false,
|
|
70
|
+
tool: sanitizedTool,
|
|
71
|
+
sessionId,
|
|
72
|
+
filePath: null,
|
|
73
|
+
log() {},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const filePath = paths.project(cwd, "debug", `tool-${sanitizedTool}__session-${sessionId}.jsonl`);
|
|
78
|
+
|
|
79
|
+
const logger: DebugLogger = {
|
|
80
|
+
enabled: true,
|
|
81
|
+
tool: sanitizedTool,
|
|
82
|
+
sessionId,
|
|
83
|
+
filePath,
|
|
84
|
+
log(event, data = {}) {
|
|
85
|
+
try {
|
|
86
|
+
appendJsonLine(filePath, {
|
|
87
|
+
ts: new Date().toISOString(),
|
|
88
|
+
tool: sanitizedTool,
|
|
89
|
+
sessionId,
|
|
90
|
+
event,
|
|
91
|
+
data,
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
// Debug logging must never block the primary code path.
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
logger.log("debug_logger_initialized", {
|
|
100
|
+
cwd,
|
|
101
|
+
filePath,
|
|
102
|
+
sessionFile,
|
|
103
|
+
env: process.env.SUPI_DEBUG ?? null,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return logger;
|
|
107
|
+
}
|
package/src/deps/registry.ts
CHANGED
|
@@ -93,26 +93,6 @@ export const DEPENDENCIES: Dependency[] = [
|
|
|
93
93
|
installCmd: "npm install -g @apify/mcpc",
|
|
94
94
|
url: "https://github.com/apify/mcpc",
|
|
95
95
|
},
|
|
96
|
-
{
|
|
97
|
-
name: "context-mode",
|
|
98
|
-
binary: "context-mode",
|
|
99
|
-
required: false,
|
|
100
|
-
category: "mcp",
|
|
101
|
-
description: "Context-mode MCP server for context window protection",
|
|
102
|
-
checkFn: async (_exec) => {
|
|
103
|
-
// context-mode is installed as a platform extension (git clone), not globally.
|
|
104
|
-
// start.mjs lives at the repo root after cloning.
|
|
105
|
-
const { existsSync } = await import("node:fs");
|
|
106
|
-
const { join } = await import("node:path");
|
|
107
|
-
const { homedir } = await import("node:os");
|
|
108
|
-
const home = homedir();
|
|
109
|
-
const startMjs = join(home, ".omp", "extensions", "context-mode", "start.mjs");
|
|
110
|
-
if (existsSync(startMjs)) return { installed: true, version: "extension" };
|
|
111
|
-
return { installed: false };
|
|
112
|
-
},
|
|
113
|
-
installCmd: null, // Handled by installer (git clone + npm install + npm run build)
|
|
114
|
-
url: "https://github.com/mksglu/context-mode",
|
|
115
|
-
},
|
|
116
96
|
{
|
|
117
97
|
name: "TypeScript LSP",
|
|
118
98
|
binary: "typescript-language-server",
|