fixo-cli 1.0.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/LICENSE +201 -0
- package/README.md +530 -0
- package/dist/agent/agent-client.d.ts +108 -0
- package/dist/agent/agent-client.d.ts.map +1 -0
- package/dist/agent/agent-client.js +1247 -0
- package/dist/agent/agent-client.js.map +1 -0
- package/dist/agent/agent-pool.d.ts +20 -0
- package/dist/agent/agent-pool.d.ts.map +1 -0
- package/dist/agent/agent-pool.js +217 -0
- package/dist/agent/agent-pool.js.map +1 -0
- package/dist/agent/background-awareness.d.ts +55 -0
- package/dist/agent/background-awareness.d.ts.map +1 -0
- package/dist/agent/background-awareness.js +104 -0
- package/dist/agent/background-awareness.js.map +1 -0
- package/dist/agent/command-parser.d.ts +33 -0
- package/dist/agent/command-parser.d.ts.map +1 -0
- package/dist/agent/command-parser.js +120 -0
- package/dist/agent/command-parser.js.map +1 -0
- package/dist/agent/context-budget.d.ts +91 -0
- package/dist/agent/context-budget.d.ts.map +1 -0
- package/dist/agent/context-budget.js +219 -0
- package/dist/agent/context-budget.js.map +1 -0
- package/dist/agent/conversation.d.ts +190 -0
- package/dist/agent/conversation.d.ts.map +1 -0
- package/dist/agent/conversation.js +547 -0
- package/dist/agent/conversation.js.map +1 -0
- package/dist/agent/hooks.d.ts +72 -0
- package/dist/agent/hooks.d.ts.map +1 -0
- package/dist/agent/hooks.js +214 -0
- package/dist/agent/hooks.js.map +1 -0
- package/dist/agent/mcp-bridge.d.ts +13 -0
- package/dist/agent/mcp-bridge.d.ts.map +1 -0
- package/dist/agent/mcp-bridge.js +86 -0
- package/dist/agent/mcp-bridge.js.map +1 -0
- package/dist/agent/mcp-client.d.ts +24 -0
- package/dist/agent/mcp-client.d.ts.map +1 -0
- package/dist/agent/mcp-client.js +146 -0
- package/dist/agent/mcp-client.js.map +1 -0
- package/dist/agent/mcp-manager.d.ts +13 -0
- package/dist/agent/mcp-manager.d.ts.map +1 -0
- package/dist/agent/mcp-manager.js +84 -0
- package/dist/agent/mcp-manager.js.map +1 -0
- package/dist/agent/mcp-registry.d.ts +45 -0
- package/dist/agent/mcp-registry.d.ts.map +1 -0
- package/dist/agent/mcp-registry.js +98 -0
- package/dist/agent/mcp-registry.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +14 -0
- package/dist/agent/orchestrator.d.ts.map +1 -0
- package/dist/agent/orchestrator.js +118 -0
- package/dist/agent/orchestrator.js.map +1 -0
- package/dist/agent/parser-adapter.d.ts +120 -0
- package/dist/agent/parser-adapter.d.ts.map +1 -0
- package/dist/agent/parser-adapter.js +265 -0
- package/dist/agent/parser-adapter.js.map +1 -0
- package/dist/agent/parsers/imports.d.ts +11 -0
- package/dist/agent/parsers/imports.d.ts.map +1 -0
- package/dist/agent/parsers/imports.js +94 -0
- package/dist/agent/parsers/imports.js.map +1 -0
- package/dist/agent/parsers/shell.d.ts +23 -0
- package/dist/agent/parsers/shell.d.ts.map +1 -0
- package/dist/agent/parsers/shell.js +200 -0
- package/dist/agent/parsers/shell.js.map +1 -0
- package/dist/agent/parsers/symbols.d.ts +17 -0
- package/dist/agent/parsers/symbols.d.ts.map +1 -0
- package/dist/agent/parsers/symbols.js +103 -0
- package/dist/agent/parsers/symbols.js.map +1 -0
- package/dist/agent/permissions.d.ts +65 -0
- package/dist/agent/permissions.d.ts.map +1 -0
- package/dist/agent/permissions.js +219 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/agent/predictive-gate.d.ts +69 -0
- package/dist/agent/predictive-gate.d.ts.map +1 -0
- package/dist/agent/predictive-gate.js +128 -0
- package/dist/agent/predictive-gate.js.map +1 -0
- package/dist/agent/provider-cooldown.d.ts +144 -0
- package/dist/agent/provider-cooldown.d.ts.map +1 -0
- package/dist/agent/provider-cooldown.js +300 -0
- package/dist/agent/provider-cooldown.js.map +1 -0
- package/dist/agent/providers-manager.d.ts +109 -0
- package/dist/agent/providers-manager.d.ts.map +1 -0
- package/dist/agent/providers-manager.js +464 -0
- package/dist/agent/providers-manager.js.map +1 -0
- package/dist/agent/repo-map.d.ts +6 -0
- package/dist/agent/repo-map.d.ts.map +1 -0
- package/dist/agent/repo-map.js +221 -0
- package/dist/agent/repo-map.js.map +1 -0
- package/dist/agent/retry.d.ts +103 -0
- package/dist/agent/retry.d.ts.map +1 -0
- package/dist/agent/retry.js +276 -0
- package/dist/agent/retry.js.map +1 -0
- package/dist/agent/search/index.d.ts +61 -0
- package/dist/agent/search/index.d.ts.map +1 -0
- package/dist/agent/search/index.js +314 -0
- package/dist/agent/search/index.js.map +1 -0
- package/dist/agent/single-agent.d.ts +76 -0
- package/dist/agent/single-agent.d.ts.map +1 -0
- package/dist/agent/single-agent.js +697 -0
- package/dist/agent/single-agent.js.map +1 -0
- package/dist/agent/skills.d.ts +22 -0
- package/dist/agent/skills.d.ts.map +1 -0
- package/dist/agent/skills.js +139 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/stream-glue.d.ts +85 -0
- package/dist/agent/stream-glue.d.ts.map +1 -0
- package/dist/agent/stream-glue.js +120 -0
- package/dist/agent/stream-glue.js.map +1 -0
- package/dist/agent/subagent.d.ts +72 -0
- package/dist/agent/subagent.d.ts.map +1 -0
- package/dist/agent/subagent.js +193 -0
- package/dist/agent/subagent.js.map +1 -0
- package/dist/agent/telemetry.d.ts +192 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +400 -0
- package/dist/agent/telemetry.js.map +1 -0
- package/dist/agent/tokenizer.d.ts +42 -0
- package/dist/agent/tokenizer.d.ts.map +1 -0
- package/dist/agent/tokenizer.js +107 -0
- package/dist/agent/tokenizer.js.map +1 -0
- package/dist/agent/tool-executor.d.ts +289 -0
- package/dist/agent/tool-executor.d.ts.map +1 -0
- package/dist/agent/tool-executor.js +2519 -0
- package/dist/agent/tool-executor.js.map +1 -0
- package/dist/agent/web-impl.d.ts +2 -0
- package/dist/agent/web-impl.d.ts.map +1 -0
- package/dist/agent/web-impl.js +34 -0
- package/dist/agent/web-impl.js.map +1 -0
- package/dist/agent/web.d.ts +8 -0
- package/dist/agent/web.d.ts.map +1 -0
- package/dist/agent/web.js +8 -0
- package/dist/agent/web.js.map +1 -0
- package/dist/agent/worker-agent.d.ts +27 -0
- package/dist/agent/worker-agent.d.ts.map +1 -0
- package/dist/agent/worker-agent.js +503 -0
- package/dist/agent/worker-agent.js.map +1 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +138 -0
- package/dist/config.js.map +1 -0
- package/dist/context/fixo-md-watcher.d.ts +42 -0
- package/dist/context/fixo-md-watcher.d.ts.map +1 -0
- package/dist/context/fixo-md-watcher.js +126 -0
- package/dist/context/fixo-md-watcher.js.map +1 -0
- package/dist/context/fixo-md.d.ts +50 -0
- package/dist/context/fixo-md.d.ts.map +1 -0
- package/dist/context/fixo-md.js +118 -0
- package/dist/context/fixo-md.js.map +1 -0
- package/dist/context/todo.d.ts +65 -0
- package/dist/context/todo.d.ts.map +1 -0
- package/dist/context/todo.js +194 -0
- package/dist/context/todo.js.map +1 -0
- package/dist/git/git-manager.d.ts +33 -0
- package/dist/git/git-manager.d.ts.map +1 -0
- package/dist/git/git-manager.js +293 -0
- package/dist/git/git-manager.js.map +1 -0
- package/dist/git/git-ops.d.ts +10 -0
- package/dist/git/git-ops.d.ts.map +1 -0
- package/dist/git/git-ops.js +131 -0
- package/dist/git/git-ops.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +352 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer.d.ts +30 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +273 -0
- package/dist/indexer.js.map +1 -0
- package/dist/lsp/lsp-client.d.ts +24 -0
- package/dist/lsp/lsp-client.d.ts.map +1 -0
- package/dist/lsp/lsp-client.js +205 -0
- package/dist/lsp/lsp-client.js.map +1 -0
- package/dist/lsp/lsp-manager.d.ts +17 -0
- package/dist/lsp/lsp-manager.d.ts.map +1 -0
- package/dist/lsp/lsp-manager.js +154 -0
- package/dist/lsp/lsp-manager.js.map +1 -0
- package/dist/lsp/lsp-pre-save.d.ts +137 -0
- package/dist/lsp/lsp-pre-save.d.ts.map +1 -0
- package/dist/lsp/lsp-pre-save.js +245 -0
- package/dist/lsp/lsp-pre-save.js.map +1 -0
- package/dist/lsp/syntax-fallback.d.ts +83 -0
- package/dist/lsp/syntax-fallback.d.ts.map +1 -0
- package/dist/lsp/syntax-fallback.js +275 -0
- package/dist/lsp/syntax-fallback.js.map +1 -0
- package/dist/model-outcomes.d.ts +12 -0
- package/dist/model-outcomes.d.ts.map +1 -0
- package/dist/model-outcomes.js +46 -0
- package/dist/model-outcomes.js.map +1 -0
- package/dist/planner.d.ts +32 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +163 -0
- package/dist/planner.js.map +1 -0
- package/dist/project-memory.d.ts +29 -0
- package/dist/project-memory.d.ts.map +1 -0
- package/dist/project-memory.js +349 -0
- package/dist/project-memory.js.map +1 -0
- package/dist/review.d.ts +2 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +61 -0
- package/dist/review.js.map +1 -0
- package/dist/runtime/background-jobs.d.ts +97 -0
- package/dist/runtime/background-jobs.d.ts.map +1 -0
- package/dist/runtime/background-jobs.js +331 -0
- package/dist/runtime/background-jobs.js.map +1 -0
- package/dist/runtime/credential-vault.d.ts +124 -0
- package/dist/runtime/credential-vault.d.ts.map +1 -0
- package/dist/runtime/credential-vault.js +184 -0
- package/dist/runtime/credential-vault.js.map +1 -0
- package/dist/runtime/loop-trap.d.ts +197 -0
- package/dist/runtime/loop-trap.d.ts.map +1 -0
- package/dist/runtime/loop-trap.js +420 -0
- package/dist/runtime/loop-trap.js.map +1 -0
- package/dist/runtime/policy.d.ts +15 -0
- package/dist/runtime/policy.d.ts.map +1 -0
- package/dist/runtime/policy.js +60 -0
- package/dist/runtime/policy.js.map +1 -0
- package/dist/runtime/redaction.d.ts +66 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +155 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/session-snapshots.d.ts +76 -0
- package/dist/runtime/session-snapshots.d.ts.map +1 -0
- package/dist/runtime/session-snapshots.js +166 -0
- package/dist/runtime/session-snapshots.js.map +1 -0
- package/dist/runtime/staging.d.ts +205 -0
- package/dist/runtime/staging.d.ts.map +1 -0
- package/dist/runtime/staging.js +526 -0
- package/dist/runtime/staging.js.map +1 -0
- package/dist/runtime/task-session.d.ts +95 -0
- package/dist/runtime/task-session.d.ts.map +1 -0
- package/dist/runtime/task-session.js +263 -0
- package/dist/runtime/task-session.js.map +1 -0
- package/dist/runtime/worktree.d.ts +55 -0
- package/dist/runtime/worktree.d.ts.map +1 -0
- package/dist/runtime/worktree.js +175 -0
- package/dist/runtime/worktree.js.map +1 -0
- package/dist/setup-wizard.d.ts +8 -0
- package/dist/setup-wizard.d.ts.map +1 -0
- package/dist/setup-wizard.js +73 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/shared/content.d.ts +43 -0
- package/dist/shared/content.d.ts.map +1 -0
- package/dist/shared/content.js +61 -0
- package/dist/shared/content.js.map +1 -0
- package/dist/shared/types.d.ts +217 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +3 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/test-runner.d.ts +5 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +42 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/ascii.d.ts +23 -0
- package/dist/ui/ascii.d.ts.map +1 -0
- package/dist/ui/ascii.js +45 -0
- package/dist/ui/ascii.js.map +1 -0
- package/dist/ui/colors.d.ts +111 -0
- package/dist/ui/colors.d.ts.map +1 -0
- package/dist/ui/colors.js +166 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/image-attach.d.ts +27 -0
- package/dist/ui/image-attach.d.ts.map +1 -0
- package/dist/ui/image-attach.js +100 -0
- package/dist/ui/image-attach.js.map +1 -0
- package/dist/ui/index.d.ts +18 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +18 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/markdown-stream.d.ts +91 -0
- package/dist/ui/markdown-stream.d.ts.map +1 -0
- package/dist/ui/markdown-stream.js +524 -0
- package/dist/ui/markdown-stream.js.map +1 -0
- package/dist/ui/plan-renderer.d.ts +36 -0
- package/dist/ui/plan-renderer.d.ts.map +1 -0
- package/dist/ui/plan-renderer.js +79 -0
- package/dist/ui/plan-renderer.js.map +1 -0
- package/dist/ui/prompt.d.ts +11 -0
- package/dist/ui/prompt.d.ts.map +1 -0
- package/dist/ui/prompt.js +1960 -0
- package/dist/ui/prompt.js.map +1 -0
- package/dist/ui/render-primitives.d.ts +117 -0
- package/dist/ui/render-primitives.d.ts.map +1 -0
- package/dist/ui/render-primitives.js +322 -0
- package/dist/ui/render-primitives.js.map +1 -0
- package/dist/ui/render.d.ts +133 -0
- package/dist/ui/render.d.ts.map +1 -0
- package/dist/ui/render.js +547 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/session-header.d.ts +30 -0
- package/dist/ui/session-header.d.ts.map +1 -0
- package/dist/ui/session-header.js +74 -0
- package/dist/ui/session-header.js.map +1 -0
- package/dist/workspace-guard.d.ts +68 -0
- package/dist/workspace-guard.d.ts.map +1 -0
- package/dist/workspace-guard.js +168 -0
- package/dist/workspace-guard.js.map +1 -0
- package/dist/workspace-lock.d.ts +27 -0
- package/dist/workspace-lock.d.ts.map +1 -0
- package/dist/workspace-lock.js +95 -0
- package/dist/workspace-lock.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { LspClient, getLanguageId } from './lsp-client.js';
|
|
4
|
+
function findBinaryInPath(binaryName) {
|
|
5
|
+
const pathEnv = process.env.PATH || '';
|
|
6
|
+
const delimiter = process.platform === 'win32' ? ';' : ':';
|
|
7
|
+
const dirs = pathEnv.split(delimiter);
|
|
8
|
+
for (const dir of dirs) {
|
|
9
|
+
const baseNames = [binaryName];
|
|
10
|
+
if (process.platform === 'win32') {
|
|
11
|
+
baseNames.push(`${binaryName}.cmd`, `${binaryName}.bat`, `${binaryName}.exe`);
|
|
12
|
+
}
|
|
13
|
+
for (const name of baseNames) {
|
|
14
|
+
const fullPath = path.join(dir, name);
|
|
15
|
+
try {
|
|
16
|
+
if (fs.existsSync(fullPath)) {
|
|
17
|
+
const stat = fs.statSync(fullPath);
|
|
18
|
+
if (process.platform === 'win32' || (stat.mode & 0o111) !== 0) {
|
|
19
|
+
return fullPath;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Ignore files we cannot access
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export class LspManager {
|
|
31
|
+
workspaceRoot;
|
|
32
|
+
clients = new Map();
|
|
33
|
+
failedSearches = new Set();
|
|
34
|
+
constructor(workspaceRoot) {
|
|
35
|
+
this.workspaceRoot = workspaceRoot;
|
|
36
|
+
process.on('exit', () => this.killAllSync());
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
this.killAllSync();
|
|
39
|
+
process.exit(130);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
killAllSync() {
|
|
43
|
+
for (const client of this.clients.values()) {
|
|
44
|
+
client.killSync();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async getOrStartClient(filePath) {
|
|
48
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
49
|
+
let binaryName = '';
|
|
50
|
+
let args = [];
|
|
51
|
+
const languageId = getLanguageId(filePath);
|
|
52
|
+
if (!languageId)
|
|
53
|
+
return null;
|
|
54
|
+
if (languageId === 'typescript' || languageId === 'javascript') {
|
|
55
|
+
binaryName = 'typescript-language-server';
|
|
56
|
+
args = ['--stdio'];
|
|
57
|
+
}
|
|
58
|
+
else if (languageId === 'python') {
|
|
59
|
+
binaryName = 'pyright-langserver';
|
|
60
|
+
args = ['--stdio'];
|
|
61
|
+
}
|
|
62
|
+
else if (languageId === 'go') {
|
|
63
|
+
binaryName = 'gopls';
|
|
64
|
+
args = [];
|
|
65
|
+
}
|
|
66
|
+
else if (languageId === 'rust') {
|
|
67
|
+
binaryName = 'rust-analyzer';
|
|
68
|
+
args = [];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (this.clients.has(languageId)) {
|
|
74
|
+
return this.clients.get(languageId);
|
|
75
|
+
}
|
|
76
|
+
if (this.failedSearches.has(binaryName)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
let binaryPath = findBinaryInPath(binaryName);
|
|
80
|
+
// If JS/TS and not found, we no longer forcefully globally install.
|
|
81
|
+
// Let it fail gracefully and inform the user.
|
|
82
|
+
if (!binaryPath && (binaryName === 'typescript-language-server')) {
|
|
83
|
+
console.warn(`\n[LSP Manager] JS/TS language server not found on PATH. Run 'npm install -g typescript-language-server typescript' to enable LSP features.`);
|
|
84
|
+
}
|
|
85
|
+
if (!binaryPath) {
|
|
86
|
+
this.failedSearches.add(binaryName);
|
|
87
|
+
console.warn(`\n[LSP Manager] Warning: '${binaryName}' not found on PATH. LSP features for '${ext}' files are disabled.`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const client = new LspClient(binaryPath, args, this.workspaceRoot);
|
|
91
|
+
try {
|
|
92
|
+
await client.start();
|
|
93
|
+
this.clients.set(languageId, client);
|
|
94
|
+
return client;
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
console.error(`[LSP Manager] Failed to start LSP client for ${binaryName}:`, err);
|
|
98
|
+
this.failedSearches.add(binaryName);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
syncFileFromDisk(filePath, client) {
|
|
103
|
+
try {
|
|
104
|
+
if (fs.existsSync(filePath)) {
|
|
105
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
106
|
+
client.syncFile(filePath, content);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
console.error(`[LSP Manager] Failed to read and sync file: ${filePath}`, err.message);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async getClientAndSync(filePath) {
|
|
114
|
+
const client = await this.getOrStartClient(filePath);
|
|
115
|
+
if (client) {
|
|
116
|
+
this.syncFileFromDisk(filePath, client);
|
|
117
|
+
}
|
|
118
|
+
return client;
|
|
119
|
+
}
|
|
120
|
+
async gotoDefinition(filePath, line, character) {
|
|
121
|
+
const client = await this.getClientAndSync(filePath);
|
|
122
|
+
if (!client)
|
|
123
|
+
return null;
|
|
124
|
+
return client.gotoDefinition(filePath, line, character);
|
|
125
|
+
}
|
|
126
|
+
async findReferences(filePath, line, character) {
|
|
127
|
+
const client = await this.getClientAndSync(filePath);
|
|
128
|
+
if (!client)
|
|
129
|
+
return null;
|
|
130
|
+
return client.findReferences(filePath, line, character);
|
|
131
|
+
}
|
|
132
|
+
async hover(filePath, line, character) {
|
|
133
|
+
const client = await this.getClientAndSync(filePath);
|
|
134
|
+
if (!client)
|
|
135
|
+
return null;
|
|
136
|
+
return client.hover(filePath, line, character);
|
|
137
|
+
}
|
|
138
|
+
async getDiagnostics(filePath) {
|
|
139
|
+
const client = await this.getClientAndSync(filePath);
|
|
140
|
+
if (!client)
|
|
141
|
+
return [];
|
|
142
|
+
// Give LSP a short tick (100ms) to publish diagnostics if this was the first open/sync
|
|
143
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
144
|
+
return client.getDiagnostics(filePath);
|
|
145
|
+
}
|
|
146
|
+
async stopAll() {
|
|
147
|
+
for (const client of this.clients.values()) {
|
|
148
|
+
await client.stop();
|
|
149
|
+
}
|
|
150
|
+
this.clients.clear();
|
|
151
|
+
this.failedSearches.clear();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=lsp-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lsp-manager.js","sourceRoot":"","sources":["../../src/lsp/lsp-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Pre-Save LSP Compilation Check — Pillar 3 of the Phase 2
|
|
3
|
+
* safety refactor. The problem: an LLM can produce syntactically
|
|
4
|
+
* valid TypeScript that is semantically broken (undefined
|
|
5
|
+
* references, wrong import paths, type errors). A workspace
|
|
6
|
+
* corrupted by a "looks-fine" write is worse than a workspace
|
|
7
|
+
* that crashes loudly.
|
|
8
|
+
*
|
|
9
|
+
* The fix: after the new content is staged, query the local
|
|
10
|
+
* language server for diagnostics. If the user-configured mode
|
|
11
|
+
* is `block` and any errors are present, refuse the commit and
|
|
12
|
+
* roll the staging back so the user keeps the original file. If
|
|
13
|
+
* the mode is `warn`, log the diagnostics but commit anyway. If
|
|
14
|
+
* the mode is `off`, the gate is a no-op.
|
|
15
|
+
*
|
|
16
|
+
* The gate is constructed lazily on first use and cached in a
|
|
17
|
+
* module-level singleton. The diagnostics call is bounded by a
|
|
18
|
+
* 500ms hard timeout so the staging path never blocks the agent
|
|
19
|
+
* loop indefinitely waiting for a slow or hung language server.
|
|
20
|
+
*
|
|
21
|
+
* The gate is fully decoupled from `LspManager` via a
|
|
22
|
+
* `LspDiagnosticsProvider` function — production code supplies
|
|
23
|
+
* one that wraps `LspManager.getDiagnostics`; tests supply a
|
|
24
|
+
* mock that returns canned data.
|
|
25
|
+
*/
|
|
26
|
+
import type { StagedWrite } from '../runtime/staging.js';
|
|
27
|
+
/** Pre-save gate severity. Mirrors `LspPreSaveMode` in `config.ts`. */
|
|
28
|
+
export type LspPreSaveMode = 'off' | 'warn' | 'block' | 'sandbox-mock';
|
|
29
|
+
/** Severity, normalised from the LSP `DiagnosticSeverity` int. */
|
|
30
|
+
export type LspDiagnosticSeverity = 'error' | 'warning' | 'info' | 'hint';
|
|
31
|
+
/** A single diagnostic, normalised. */
|
|
32
|
+
export interface LspDiagnostic {
|
|
33
|
+
readonly severity: LspDiagnosticSeverity;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
readonly line: number;
|
|
36
|
+
readonly column: number;
|
|
37
|
+
readonly source: string;
|
|
38
|
+
readonly code: string | number | null;
|
|
39
|
+
}
|
|
40
|
+
/** Result of a single pre-save check. */
|
|
41
|
+
export type LspPreSaveResult = {
|
|
42
|
+
readonly state: 'ok';
|
|
43
|
+
} | {
|
|
44
|
+
readonly state: 'no-language-server';
|
|
45
|
+
} | {
|
|
46
|
+
readonly state: 'diagnostics';
|
|
47
|
+
readonly diagnostics: ReadonlyArray<LspDiagnostic>;
|
|
48
|
+
/** Convenience: the count of `error`-severity items. */
|
|
49
|
+
readonly errorCount: number;
|
|
50
|
+
};
|
|
51
|
+
/** Function that returns diagnostics for a file (or empty). */
|
|
52
|
+
export type LspDiagnosticsProvider = (filePath: string) => Promise<LspDiagnostic[]>;
|
|
53
|
+
/** Options for {@link LspPreSaveGate}. */
|
|
54
|
+
export interface LspPreSaveGateOptions {
|
|
55
|
+
/** The mode. Defaults to `'off'`. */
|
|
56
|
+
mode?: LspPreSaveMode;
|
|
57
|
+
/** The diagnostics provider (defaults to a noop). */
|
|
58
|
+
provider?: LspDiagnosticsProvider;
|
|
59
|
+
/**
|
|
60
|
+
* Predicate that reports whether a real language server is
|
|
61
|
+
* installed for the given file path. Used by `sandbox-mock`
|
|
62
|
+
* mode to distinguish "LSP returned 0 diagnostics" (real pass)
|
|
63
|
+
* from "no LSP installed, unvalidated write" (must be
|
|
64
|
+
* blocked in sandbox-mock mode). Defaults to `true` — i.e.
|
|
65
|
+
* the gate assumes an LSP is available unless the caller
|
|
66
|
+
* overrides this.
|
|
67
|
+
*/
|
|
68
|
+
hasLanguageServer?: (filePath: string) => boolean;
|
|
69
|
+
/** Maximum time (ms) to wait for diagnostics. Defaults to 500. */
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Optional callback invoked with the gate's decision before the
|
|
73
|
+
* caller (the staging manager) acts on it. Useful for telemetry
|
|
74
|
+
* and for warning-mode logging. Errors thrown here are caught
|
|
75
|
+
* and swallowed — they never block the write.
|
|
76
|
+
*/
|
|
77
|
+
onResult?: (result: LspPreSaveResult, entry: StagedWrite) => void;
|
|
78
|
+
}
|
|
79
|
+
/** Map the LSP `DiagnosticSeverity` int to our normalised severity. */
|
|
80
|
+
export declare function normaliseSeverity(int: unknown): LspDiagnosticSeverity;
|
|
81
|
+
/** Normalise a raw LSP `Diagnostic` payload into our typed shape. */
|
|
82
|
+
export declare function normaliseDiagnostic(raw: unknown): LspDiagnostic;
|
|
83
|
+
export declare class LspPreSaveGate {
|
|
84
|
+
readonly mode: LspPreSaveMode;
|
|
85
|
+
readonly provider: LspDiagnosticsProvider;
|
|
86
|
+
readonly timeoutMs: number;
|
|
87
|
+
readonly onResult: ((r: LspPreSaveResult, e: StagedWrite) => void) | undefined;
|
|
88
|
+
readonly hasLanguageServer: (filePath: string) => boolean;
|
|
89
|
+
constructor(options?: LspPreSaveGateOptions);
|
|
90
|
+
/**
|
|
91
|
+
* Run the gate against a staged write. Returns one of:
|
|
92
|
+
*
|
|
93
|
+
* - `ok` — the file passes the gate.
|
|
94
|
+
* - `no-language-server`— the provider returned no diagnostics and
|
|
95
|
+
* we have no LSP installed; treat as a
|
|
96
|
+
* pass-through so users without a language
|
|
97
|
+
* server aren't blocked.
|
|
98
|
+
* - `diagnostics` — there are diagnostics; the caller
|
|
99
|
+
* decides what to do based on the gate's
|
|
100
|
+
* mode and on the `errorCount`.
|
|
101
|
+
*/
|
|
102
|
+
check(entry: StagedWrite): Promise<LspPreSaveResult>;
|
|
103
|
+
/**
|
|
104
|
+
* Throw an {@link LspPreSaveBlockedError} if the result should
|
|
105
|
+
* be a hard block. The caller (the staging pipeline) uses this
|
|
106
|
+
* as the pre-commit hook. In `warn` mode this always returns
|
|
107
|
+
* without throwing. In `block` mode it throws when there is at
|
|
108
|
+
* least one `error`-severity diagnostic. In `sandbox-mock` mode
|
|
109
|
+
* (Pillar 5 / Protection 3) it throws when the result was
|
|
110
|
+
* `no-language-server` — the gate refuses to allow unvalidated
|
|
111
|
+
* writes when no LSP is available, forcing the operator to
|
|
112
|
+
* install a real language server or downgrade to `off`.
|
|
113
|
+
*/
|
|
114
|
+
enforce(result: LspPreSaveResult, entry: StagedWrite): void;
|
|
115
|
+
private notify;
|
|
116
|
+
private withTimeout;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Thrown by {@link LspPreSaveGate.enforce} when the gate's mode is
|
|
120
|
+
* `block` and the file has at least one `error`-severity
|
|
121
|
+
* diagnostic. Caught by {@link AtomicStagingManager.commit} and
|
|
122
|
+
* surfaced as a {@link PreCommitHookRejectedError}.
|
|
123
|
+
*/
|
|
124
|
+
export declare class LspPreSaveBlockedError extends Error {
|
|
125
|
+
readonly targetPath: string;
|
|
126
|
+
readonly diagnostics: ReadonlyArray<LspDiagnostic>;
|
|
127
|
+
constructor(targetPath: string, diagnostics: ReadonlyArray<LspDiagnostic>);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Build a {@link LspDiagnosticsProvider} from an
|
|
131
|
+
* {@link LspManager}-shaped object. Decouples the gate from the
|
|
132
|
+
* concrete `LspManager` class so tests can inject a mock.
|
|
133
|
+
*/
|
|
134
|
+
export declare function makeLspProvider(lspManager: {
|
|
135
|
+
getClientAndSync(filePath: string): Promise<unknown>;
|
|
136
|
+
}): LspDiagnosticsProvider;
|
|
137
|
+
//# sourceMappingURL=lsp-pre-save.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lsp-pre-save.d.ts","sourceRoot":"","sources":["../../src/lsp/lsp-pre-save.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Pre-Save LSP Compilation Check — Pillar 3 of the Phase 2
|
|
3
|
+
* safety refactor. The problem: an LLM can produce syntactically
|
|
4
|
+
* valid TypeScript that is semantically broken (undefined
|
|
5
|
+
* references, wrong import paths, type errors). A workspace
|
|
6
|
+
* corrupted by a "looks-fine" write is worse than a workspace
|
|
7
|
+
* that crashes loudly.
|
|
8
|
+
*
|
|
9
|
+
* The fix: after the new content is staged, query the local
|
|
10
|
+
* language server for diagnostics. If the user-configured mode
|
|
11
|
+
* is `block` and any errors are present, refuse the commit and
|
|
12
|
+
* roll the staging back so the user keeps the original file. If
|
|
13
|
+
* the mode is `warn`, log the diagnostics but commit anyway. If
|
|
14
|
+
* the mode is `off`, the gate is a no-op.
|
|
15
|
+
*
|
|
16
|
+
* The gate is constructed lazily on first use and cached in a
|
|
17
|
+
* module-level singleton. The diagnostics call is bounded by a
|
|
18
|
+
* 500ms hard timeout so the staging path never blocks the agent
|
|
19
|
+
* loop indefinitely waiting for a slow or hung language server.
|
|
20
|
+
*
|
|
21
|
+
* The gate is fully decoupled from `LspManager` via a
|
|
22
|
+
* `LspDiagnosticsProvider` function — production code supplies
|
|
23
|
+
* one that wraps `LspManager.getDiagnostics`; tests supply a
|
|
24
|
+
* mock that returns canned data.
|
|
25
|
+
*/
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Helpers
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/** Map the LSP `DiagnosticSeverity` int to our normalised severity. */
|
|
30
|
+
export function normaliseSeverity(int) {
|
|
31
|
+
switch (int) {
|
|
32
|
+
case 1:
|
|
33
|
+
return 'error';
|
|
34
|
+
case 2:
|
|
35
|
+
return 'warning';
|
|
36
|
+
case 3:
|
|
37
|
+
return 'info';
|
|
38
|
+
case 4:
|
|
39
|
+
return 'hint';
|
|
40
|
+
default:
|
|
41
|
+
return 'info';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Normalise a raw LSP `Diagnostic` payload into our typed shape. */
|
|
45
|
+
export function normaliseDiagnostic(raw) {
|
|
46
|
+
if (!raw || typeof raw !== 'object') {
|
|
47
|
+
return {
|
|
48
|
+
severity: 'info',
|
|
49
|
+
message: String(raw),
|
|
50
|
+
line: 0,
|
|
51
|
+
column: 0,
|
|
52
|
+
source: '',
|
|
53
|
+
code: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const r = raw;
|
|
57
|
+
const start = r.range?.start;
|
|
58
|
+
return {
|
|
59
|
+
severity: normaliseSeverity(r.severity),
|
|
60
|
+
message: typeof r.message === 'string' ? r.message : String(r.message ?? ''),
|
|
61
|
+
line: typeof start?.line === 'number' ? start.line : 0,
|
|
62
|
+
column: typeof start?.character === 'number' ? start.character : 0,
|
|
63
|
+
source: typeof r.source === 'string' ? r.source : '',
|
|
64
|
+
code: typeof r.code === 'string' || typeof r.code === 'number' ? r.code : null,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const noopProvider = async () => [];
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// LspPreSaveGate
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
export class LspPreSaveGate {
|
|
72
|
+
mode;
|
|
73
|
+
provider;
|
|
74
|
+
timeoutMs;
|
|
75
|
+
onResult;
|
|
76
|
+
hasLanguageServer;
|
|
77
|
+
constructor(options = {}) {
|
|
78
|
+
this.mode = options.mode ?? 'off';
|
|
79
|
+
this.provider = options.provider ?? noopProvider;
|
|
80
|
+
this.timeoutMs = options.timeoutMs ?? 500;
|
|
81
|
+
this.onResult = options.onResult;
|
|
82
|
+
this.hasLanguageServer = options.hasLanguageServer ?? (() => true);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Run the gate against a staged write. Returns one of:
|
|
86
|
+
*
|
|
87
|
+
* - `ok` — the file passes the gate.
|
|
88
|
+
* - `no-language-server`— the provider returned no diagnostics and
|
|
89
|
+
* we have no LSP installed; treat as a
|
|
90
|
+
* pass-through so users without a language
|
|
91
|
+
* server aren't blocked.
|
|
92
|
+
* - `diagnostics` — there are diagnostics; the caller
|
|
93
|
+
* decides what to do based on the gate's
|
|
94
|
+
* mode and on the `errorCount`.
|
|
95
|
+
*/
|
|
96
|
+
async check(entry) {
|
|
97
|
+
if (this.mode === 'off') {
|
|
98
|
+
return { state: 'ok' };
|
|
99
|
+
}
|
|
100
|
+
let raw;
|
|
101
|
+
try {
|
|
102
|
+
raw = await this.withTimeout(this.provider(entry.targetPath), this.timeoutMs);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Provider threw or timed out — treat as a pass-through so a
|
|
106
|
+
// hung language server does not block the agent loop.
|
|
107
|
+
return { state: 'no-language-server' };
|
|
108
|
+
}
|
|
109
|
+
if (raw.length === 0) {
|
|
110
|
+
// Provider returned zero diagnostics. Distinguish
|
|
111
|
+
// between "LSP installed, file is clean" (→ `ok`) and
|
|
112
|
+
// "no LSP installed, write is unvalidated" (→
|
|
113
|
+
// `no-language-server`). The latter is what the
|
|
114
|
+
// sandbox-mock mode rejects.
|
|
115
|
+
if (!this.hasLanguageServer(entry.targetPath)) {
|
|
116
|
+
const result = { state: 'no-language-server' };
|
|
117
|
+
this.notify(result, entry);
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
const result = { state: 'ok' };
|
|
121
|
+
this.notify(result, entry);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
const errorCount = raw.filter((d) => d.severity === 'error').length;
|
|
125
|
+
const result = {
|
|
126
|
+
state: 'diagnostics',
|
|
127
|
+
diagnostics: raw,
|
|
128
|
+
errorCount,
|
|
129
|
+
};
|
|
130
|
+
this.notify(result, entry);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Throw an {@link LspPreSaveBlockedError} if the result should
|
|
135
|
+
* be a hard block. The caller (the staging pipeline) uses this
|
|
136
|
+
* as the pre-commit hook. In `warn` mode this always returns
|
|
137
|
+
* without throwing. In `block` mode it throws when there is at
|
|
138
|
+
* least one `error`-severity diagnostic. In `sandbox-mock` mode
|
|
139
|
+
* (Pillar 5 / Protection 3) it throws when the result was
|
|
140
|
+
* `no-language-server` — the gate refuses to allow unvalidated
|
|
141
|
+
* writes when no LSP is available, forcing the operator to
|
|
142
|
+
* install a real language server or downgrade to `off`.
|
|
143
|
+
*/
|
|
144
|
+
enforce(result, entry) {
|
|
145
|
+
if (this.mode === 'off')
|
|
146
|
+
return;
|
|
147
|
+
if (this.mode === 'warn')
|
|
148
|
+
return;
|
|
149
|
+
if (this.mode === 'sandbox-mock') {
|
|
150
|
+
// Pillar 5 / Protection 3 — refuse to commit a write
|
|
151
|
+
// when the gate has no language server backing it. The
|
|
152
|
+
// operator must explicitly opt out by setting the mode
|
|
153
|
+
// to `off` (or by installing a real LSP).
|
|
154
|
+
if (result.state === 'no-language-server') {
|
|
155
|
+
throw new LspPreSaveBlockedError(entry.targetPath, [
|
|
156
|
+
{
|
|
157
|
+
severity: 'error',
|
|
158
|
+
message: 'sandbox-mock: no language server is available to validate this write. ' +
|
|
159
|
+
'Install typescript-language-server / pyright etc., or set ' +
|
|
160
|
+
'preferences.safety.lspPreSave to "off" to disable pre-save validation.',
|
|
161
|
+
line: 0,
|
|
162
|
+
column: 0,
|
|
163
|
+
source: 'fixo-safety',
|
|
164
|
+
code: 'SANDBOX_MOCK_NO_LSP',
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// `block` mode — fail on any error-severity diagnostic.
|
|
171
|
+
if (result.state !== 'diagnostics')
|
|
172
|
+
return;
|
|
173
|
+
if (result.errorCount === 0)
|
|
174
|
+
return;
|
|
175
|
+
throw new LspPreSaveBlockedError(entry.targetPath, result.diagnostics);
|
|
176
|
+
}
|
|
177
|
+
notify(result, entry) {
|
|
178
|
+
if (!this.onResult)
|
|
179
|
+
return;
|
|
180
|
+
try {
|
|
181
|
+
this.onResult(result, entry);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Swallow observer errors — they are best-effort.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
withTimeout(p, ms) {
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
const timer = setTimeout(() => reject(new Error('LSP gate timeout')), ms);
|
|
190
|
+
p.then((v) => {
|
|
191
|
+
clearTimeout(timer);
|
|
192
|
+
resolve(v);
|
|
193
|
+
}, (err) => {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
reject(err);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Errors
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
/**
|
|
204
|
+
* Thrown by {@link LspPreSaveGate.enforce} when the gate's mode is
|
|
205
|
+
* `block` and the file has at least one `error`-severity
|
|
206
|
+
* diagnostic. Caught by {@link AtomicStagingManager.commit} and
|
|
207
|
+
* surfaced as a {@link PreCommitHookRejectedError}.
|
|
208
|
+
*/
|
|
209
|
+
export class LspPreSaveBlockedError extends Error {
|
|
210
|
+
targetPath;
|
|
211
|
+
diagnostics;
|
|
212
|
+
constructor(targetPath, diagnostics) {
|
|
213
|
+
const summary = diagnostics
|
|
214
|
+
.filter((d) => d.severity === 'error')
|
|
215
|
+
.slice(0, 3)
|
|
216
|
+
.map((d) => `${d.line + 1}:${d.column + 1} ${d.message}`)
|
|
217
|
+
.join('; ');
|
|
218
|
+
super(`LSP pre-save blocked: ${diagnostics.filter((d) => d.severity === 'error').length} ` +
|
|
219
|
+
`error(s) in ${targetPath}${summary ? ` — ${summary}` : ''}`);
|
|
220
|
+
this.name = 'LspPreSaveBlockedError';
|
|
221
|
+
this.targetPath = targetPath;
|
|
222
|
+
this.diagnostics = diagnostics;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Adapter — wire LspManager into the gate
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
/**
|
|
229
|
+
* Build a {@link LspDiagnosticsProvider} from an
|
|
230
|
+
* {@link LspManager}-shaped object. Decouples the gate from the
|
|
231
|
+
* concrete `LspManager` class so tests can inject a mock.
|
|
232
|
+
*/
|
|
233
|
+
export function makeLspProvider(lspManager) {
|
|
234
|
+
return async (filePath) => {
|
|
235
|
+
const client = (await lspManager.getClientAndSync(filePath));
|
|
236
|
+
if (!client)
|
|
237
|
+
return [];
|
|
238
|
+
// Give the LSP a short tick to publish (matches LspManager's
|
|
239
|
+
// existing behaviour for the very first open).
|
|
240
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
241
|
+
const raw = client.getDiagnostics(filePath);
|
|
242
|
+
return Array.isArray(raw) ? raw.map((d) => normaliseDiagnostic(d)) : [];
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=lsp-pre-save.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lsp-pre-save.js","sourceRoot":"","sources":["../../src/lsp/lsp-pre-save.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* syntax-fallback.ts — Pure-JS brace/paren/bracket balance check
|
|
3
|
+
* used as a fallback when no real language server is on the PATH.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists
|
|
6
|
+
* ---------------
|
|
7
|
+
* The LspPreSaveGate calls into `LspManager` to ask the local
|
|
8
|
+
* language server for diagnostics. On a freshly-installed system
|
|
9
|
+
* there is no `tsserver` / `gopls` / `rust-analyzer` on the PATH —
|
|
10
|
+
* the user has a working FixO CLI but no editor infrastructure.
|
|
11
|
+
* In that case the pre-save gate would silently fall through and
|
|
12
|
+
* commit syntactically broken edits.
|
|
13
|
+
*
|
|
14
|
+
* This module provides a *very* cheap structural sanity check
|
|
15
|
+
* (brace/paren/bracket/quote balance) that runs in microseconds
|
|
16
|
+
* and catches the most common form of "LLM forgot a closing
|
|
17
|
+
* brace" corruption. It is intentionally not a real parser — it
|
|
18
|
+
* is a smoke detector, not a smoke alarm. If the syntax check
|
|
19
|
+
* reports `ok`, the real LSP may still surface semantic errors;
|
|
20
|
+
* if it reports `unbalanced`, the file is almost certainly
|
|
21
|
+
* broken and we should refuse the write.
|
|
22
|
+
*
|
|
23
|
+
* The check is env-gated behind `FIXO_LSP_FALLBACK=syntax-only`
|
|
24
|
+
* so a developer who *does* have a language server is never
|
|
25
|
+
* bothered by it.
|
|
26
|
+
*/
|
|
27
|
+
export type SyntaxHealthVerdict = {
|
|
28
|
+
readonly state: 'ok';
|
|
29
|
+
} | {
|
|
30
|
+
readonly state: 'unbalanced';
|
|
31
|
+
/** The first unclosed delimiter, in document order. */
|
|
32
|
+
readonly opener: '{' | '(' | '[';
|
|
33
|
+
/** 1-based line where the imbalance was detected. */
|
|
34
|
+
readonly line: number;
|
|
35
|
+
} | {
|
|
36
|
+
readonly state: 'unterminated-string';
|
|
37
|
+
/** 1-based line where the runaway string starts. */
|
|
38
|
+
readonly line: number;
|
|
39
|
+
} | {
|
|
40
|
+
readonly state: 'unterminated-comment';
|
|
41
|
+
/** 1-based line where the block comment starts. */
|
|
42
|
+
readonly line: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Run the structural sanity check on a source string. Pure,
|
|
46
|
+
* sync, and allocation-light. The output is stable so tests can
|
|
47
|
+
* pin the verdict exactly.
|
|
48
|
+
*/
|
|
49
|
+
export declare function syntaxHealthCheck(source: string): SyntaxHealthVerdict;
|
|
50
|
+
/**
|
|
51
|
+
* Format a {@link SyntaxHealthVerdict} as a single-line summary
|
|
52
|
+
* suitable for the LLM's `tool_result`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function formatSyntaxVerdict(verdict: SyntaxHealthVerdict): string;
|
|
55
|
+
export interface LspSanityResult {
|
|
56
|
+
/** True when at least one common language server is on the PATH,
|
|
57
|
+
* or when the syntax-fallback mode is explicitly enabled. */
|
|
58
|
+
ok: boolean;
|
|
59
|
+
/** Human-readable reason. Empty when `ok`. */
|
|
60
|
+
reason: string;
|
|
61
|
+
/** The language servers we looked for. */
|
|
62
|
+
checked: string[];
|
|
63
|
+
/** The ones we actually found on the PATH. */
|
|
64
|
+
found: string[];
|
|
65
|
+
/** Whether `FIXO_LSP_FALLBACK=syntax-only` is set. */
|
|
66
|
+
syntaxOnly: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Synchronous PATH check for common language servers. Returns a
|
|
70
|
+
* {@link LspSanityResult} the boot code can use to decide whether
|
|
71
|
+
* to warn the user. Pure — no side effects, no I/O beyond
|
|
72
|
+
* `which`-style PATH scanning.
|
|
73
|
+
*
|
|
74
|
+
* The check is intentionally permissive: a missing language server
|
|
75
|
+
* is a `reason` to warn, not a hard failure. The user can always
|
|
76
|
+
* install one later. The hard fail mode is when neither a
|
|
77
|
+
* language server nor `FIXO_LSP_FALLBACK=syntax-only` is present
|
|
78
|
+
* AND the user has configured `lspPreSave: 'block'` — in that
|
|
79
|
+
* case the pre-save gate would block all writes, which is a much
|
|
80
|
+
* worse experience than a boot-time warning.
|
|
81
|
+
*/
|
|
82
|
+
export declare function checkLspSanity(env?: NodeJS.ProcessEnv): LspSanityResult;
|
|
83
|
+
//# sourceMappingURL=syntax-fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syntax-fallback.d.ts","sourceRoot":"","sources":["../../src/lsp/syntax-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GACxB;IACE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACjC,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IACvC,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEN;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,mBAAmB,CA8IrE;AAcD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAWxE;AAMD,MAAM,WAAW,eAAe;IAC9B;kEAC8D;IAC9D,EAAE,EAAE,OAAO,CAAC;IACZ,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,sDAAsD;IACtD,UAAU,EAAE,OAAO,CAAC;CACrB;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,eAAe,CA4CpF"}
|