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.
Files changed (137) hide show
  1. package/README.md +118 -56
  2. package/bin/install.ts +48 -128
  3. package/package.json +11 -3
  4. package/skills/code-review/SKILL.md +137 -40
  5. package/skills/context-mode/SKILL.md +67 -56
  6. package/skills/creating-supi-agents/SKILL.md +204 -0
  7. package/skills/debugging/SKILL.md +86 -40
  8. package/skills/fix-pr/SKILL.md +96 -65
  9. package/skills/planning/SKILL.md +103 -46
  10. package/skills/qa-strategy/SKILL.md +68 -46
  11. package/skills/receiving-code-review/SKILL.md +60 -53
  12. package/skills/release/SKILL.md +111 -39
  13. package/skills/tdd/SKILL.md +118 -67
  14. package/skills/verification/SKILL.md +71 -37
  15. package/src/bootstrap.ts +27 -5
  16. package/src/commands/agents.ts +249 -0
  17. package/src/commands/ai-review.ts +1113 -0
  18. package/src/commands/config.ts +224 -95
  19. package/src/commands/doctor.ts +19 -13
  20. package/src/commands/fix-pr.ts +8 -11
  21. package/src/commands/generate.ts +200 -0
  22. package/src/commands/model-picker.ts +5 -15
  23. package/src/commands/model.ts +4 -5
  24. package/src/commands/optimize-context.ts +202 -0
  25. package/src/commands/plan.ts +148 -92
  26. package/src/commands/qa.ts +14 -23
  27. package/src/commands/release.ts +504 -275
  28. package/src/commands/review.ts +643 -86
  29. package/src/commands/status.ts +44 -17
  30. package/src/commands/supi.ts +69 -41
  31. package/src/commands/update.ts +57 -2
  32. package/src/config/defaults.ts +6 -39
  33. package/src/config/loader.ts +388 -40
  34. package/src/config/model-resolver.ts +26 -22
  35. package/src/config/schema.ts +113 -48
  36. package/src/context/analyzer.ts +61 -2
  37. package/src/context/optimizer.ts +199 -0
  38. package/src/context-mode/compressor.ts +14 -11
  39. package/src/context-mode/detector.ts +16 -54
  40. package/src/context-mode/event-extractor.ts +45 -16
  41. package/src/context-mode/event-store.ts +225 -16
  42. package/src/context-mode/hooks.ts +195 -22
  43. package/src/context-mode/knowledge/chunker.ts +235 -0
  44. package/src/context-mode/knowledge/store.ts +187 -0
  45. package/src/context-mode/routing.ts +12 -23
  46. package/src/context-mode/sandbox/executor.ts +183 -0
  47. package/src/context-mode/sandbox/runners.ts +40 -0
  48. package/src/context-mode/snapshot-builder.ts +243 -7
  49. package/src/context-mode/tools.ts +440 -0
  50. package/src/context-mode/web/fetcher.ts +117 -0
  51. package/src/context-mode/web/html-to-md.ts +293 -0
  52. package/src/debug/logger.ts +107 -0
  53. package/src/deps/registry.ts +0 -20
  54. package/src/docs/drift.ts +454 -0
  55. package/src/fix-pr/fetch-comments.ts +66 -0
  56. package/src/git/commit-msg.ts +2 -1
  57. package/src/git/commit.ts +123 -141
  58. package/src/git/conventions.ts +2 -2
  59. package/src/git/status.ts +4 -1
  60. package/src/lsp/bridge.ts +138 -12
  61. package/src/planning/approval-flow.ts +125 -19
  62. package/src/planning/plan-writer-prompt.ts +4 -11
  63. package/src/planning/planning-ask-tool.ts +81 -0
  64. package/src/planning/prompt-builder.ts +9 -169
  65. package/src/planning/system-prompt.ts +290 -0
  66. package/src/platform/omp.ts +50 -4
  67. package/src/platform/progress.ts +182 -0
  68. package/src/platform/test-utils.ts +4 -1
  69. package/src/platform/tui-colors.ts +30 -0
  70. package/src/platform/types.ts +1 -0
  71. package/src/qa/detect-app-type.ts +102 -0
  72. package/src/qa/discover-routes.ts +353 -0
  73. package/src/quality/ai-session.ts +96 -0
  74. package/src/quality/ai-setup.ts +86 -0
  75. package/src/quality/gates/ai-review.ts +129 -0
  76. package/src/quality/gates/build.ts +8 -0
  77. package/src/quality/gates/command.ts +150 -0
  78. package/src/quality/gates/format.ts +28 -0
  79. package/src/quality/gates/lint.ts +22 -0
  80. package/src/quality/gates/lsp-diagnostics.ts +84 -0
  81. package/src/quality/gates/test-suite.ts +8 -0
  82. package/src/quality/gates/typecheck.ts +22 -0
  83. package/src/quality/registry.ts +25 -0
  84. package/src/quality/review-gates.ts +33 -0
  85. package/src/quality/runner.ts +268 -0
  86. package/src/quality/schemas.ts +48 -0
  87. package/src/quality/setup.ts +227 -0
  88. package/src/release/changelog.ts +7 -3
  89. package/src/release/channels/custom.ts +43 -0
  90. package/src/release/channels/gitea.ts +35 -0
  91. package/src/release/channels/github.ts +35 -0
  92. package/src/release/channels/gitlab.ts +35 -0
  93. package/src/release/channels/registry.ts +52 -0
  94. package/src/release/channels/types.ts +27 -0
  95. package/src/release/detector.ts +10 -63
  96. package/src/release/executor.ts +61 -51
  97. package/src/release/prompt.ts +38 -38
  98. package/src/release/version.ts +129 -10
  99. package/src/review/agent-loader.ts +331 -0
  100. package/src/review/consolidator.ts +180 -0
  101. package/src/review/default-agents/correctness.md +72 -0
  102. package/src/review/default-agents/maintainability.md +64 -0
  103. package/src/review/default-agents/security.md +67 -0
  104. package/src/review/fixer.ts +219 -0
  105. package/src/review/multi-agent-runner.ts +135 -0
  106. package/src/review/output.ts +147 -0
  107. package/src/review/prompts/agent-review-wrapper.md +36 -0
  108. package/src/review/prompts/fix-findings.md +32 -0
  109. package/src/review/prompts/fix-output-schema.md +18 -0
  110. package/src/review/prompts/invalid-output-retry.md +22 -0
  111. package/src/review/prompts/output-instructions.md +14 -0
  112. package/src/review/prompts/review-output-schema.md +38 -0
  113. package/src/review/prompts/single-review.md +53 -0
  114. package/src/review/prompts/validation-review.md +30 -0
  115. package/src/review/runner.ts +128 -0
  116. package/src/review/scope.ts +353 -0
  117. package/src/review/template.ts +15 -0
  118. package/src/review/types.ts +296 -0
  119. package/src/review/validator.ts +160 -0
  120. package/src/storage/plans.ts +5 -3
  121. package/src/storage/reports.ts +50 -7
  122. package/src/storage/review-sessions.ts +117 -0
  123. package/src/text.ts +19 -0
  124. package/src/types.ts +336 -26
  125. package/src/utils/paths.ts +39 -0
  126. package/src/visual/companion.ts +5 -3
  127. package/src/visual/start-server.ts +101 -0
  128. package/src/visual/stop-server.ts +39 -0
  129. package/bin/ctx-mode-wrapper.mjs +0 -66
  130. package/src/config/profiles.ts +0 -64
  131. package/src/context-mode/installer.ts +0 -38
  132. package/src/quality/ai-review-gate.ts +0 -43
  133. package/src/quality/gate-runner.ts +0 -67
  134. package/src/quality/lsp-gate.ts +0 -24
  135. package/src/quality/test-gate.ts +0 -39
  136. package/src/visual/scripts/start-server.sh +0 -98
  137. 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) => `![${alt}](${src})`,
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) => `![${alt}](${src})`,
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(/&amp;/g, "&");
224
+ result = result.replace(/&lt;/g, "<");
225
+ result = result.replace(/&gt;/g, ">");
226
+ result = result.replace(/&quot;/g, '"');
227
+ result = result.replace(/&#39;|&apos;/g, "'");
228
+ result = result.replace(/&nbsp;/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
+ }
@@ -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",