sid-harness 0.3.0__py3-none-any.whl

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 (45) hide show
  1. sid_harness/__init__.py +13 -0
  2. sid_harness/__main__.py +4 -0
  3. sid_harness/_native.py +1639 -0
  4. sid_harness/brave_proof.py +246 -0
  5. sid_harness/bridge.py +851 -0
  6. sid_harness/cli.py +937 -0
  7. sid_harness/client.py +161 -0
  8. sid_harness/companion.py +151 -0
  9. sid_harness/config.py +68 -0
  10. sid_harness/context/__init__.py +40 -0
  11. sid_harness/context/index.py +267 -0
  12. sid_harness/context/retrieval.py +74 -0
  13. sid_harness/editor.py +231 -0
  14. sid_harness/gateway.py +106 -0
  15. sid_harness/git_write.py +598 -0
  16. sid_harness/local_workspace.py +421 -0
  17. sid_harness/loop.py +394 -0
  18. sid_harness/lsp.py +598 -0
  19. sid_harness/policy/__init__.py +44 -0
  20. sid_harness/policy/repo_policy.py +626 -0
  21. sid_harness/receipt.py +142 -0
  22. sid_harness/runner.py +468 -0
  23. sid_harness/selftest.py +149 -0
  24. sid_harness/session/__init__.py +33 -0
  25. sid_harness/session/bounds.py +140 -0
  26. sid_harness/session/diff_gate.py +93 -0
  27. sid_harness/session/session.py +427 -0
  28. sid_harness/session/store.py +88 -0
  29. sid_harness/tools/__init__.py +52 -0
  30. sid_harness/tools/mcp_client.py +285 -0
  31. sid_harness/tools/plan.py +138 -0
  32. sid_harness/tools/registry.py +159 -0
  33. sid_harness/tools/skills.py +185 -0
  34. sid_harness/tools/subagents.py +112 -0
  35. sid_harness/tui.py +96 -0
  36. sid_harness/workspace.py +107 -0
  37. sid_harness-0.3.0.data/data/share/sid-harness/editors/README.md +46 -0
  38. sid_harness-0.3.0.data/data/share/sid-harness/editors/vscode/README.md +32 -0
  39. sid_harness-0.3.0.data/data/share/sid-harness/editors/vscode/extension.js +395 -0
  40. sid_harness-0.3.0.data/data/share/sid-harness/editors/vscode/package.json +120 -0
  41. sid_harness-0.3.0.dist-info/METADATA +229 -0
  42. sid_harness-0.3.0.dist-info/RECORD +45 -0
  43. sid_harness-0.3.0.dist-info/WHEEL +5 -0
  44. sid_harness-0.3.0.dist-info/entry_points.txt +2 -0
  45. sid_harness-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,395 @@
1
+ 'use strict';
2
+
3
+ const vscode = require('vscode');
4
+ const cp = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ let channel;
9
+ let lspProcess = null;
10
+ let lspBuffer = Buffer.alloc(0);
11
+ let currentSession = null;
12
+ let attachedSelection = '';
13
+
14
+ function configuration() {
15
+ return vscode.workspace.getConfiguration('sid');
16
+ }
17
+
18
+ function sidCommand() {
19
+ return configuration().get('command') || 'sid';
20
+ }
21
+
22
+ function workspaceRoot() {
23
+ const folders = vscode.workspace.workspaceFolders;
24
+ if (!folders || folders.length === 0) {
25
+ throw new Error('open a folder before starting a SID session');
26
+ }
27
+ return folders[0].uri.fsPath;
28
+ }
29
+
30
+ function spawnSid(args, cwd) {
31
+ return new Promise((resolve, reject) => {
32
+ const child = cp.spawn(sidCommand(), args, {
33
+ cwd,
34
+ env: process.env,
35
+ windowsHide: true,
36
+ });
37
+ let stdout = '';
38
+ let stderr = '';
39
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
40
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
41
+ child.on('error', reject);
42
+ child.on('close', (code) => resolve({ code, stdout, stderr }));
43
+ });
44
+ }
45
+
46
+ function frame(message) {
47
+ const body = Buffer.from(JSON.stringify(message), 'utf8');
48
+ return Buffer.concat([
49
+ Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, 'ascii'),
50
+ body,
51
+ ]);
52
+ }
53
+
54
+ function sendLsp(message) {
55
+ if (!lspProcess || !lspProcess.stdin || !lspProcess.stdin.writable) {
56
+ return;
57
+ }
58
+ lspProcess.stdin.write(frame(message));
59
+ }
60
+
61
+ function handleLspData(chunk) {
62
+ lspBuffer = Buffer.concat([lspBuffer, chunk]);
63
+ for (;;) {
64
+ const headerEnd = lspBuffer.indexOf('\r\n\r\n');
65
+ if (headerEnd < 0) {
66
+ return;
67
+ }
68
+ const header = lspBuffer.slice(0, headerEnd).toString('ascii');
69
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
70
+ if (!match) {
71
+ lspBuffer = lspBuffer.slice(headerEnd + 4);
72
+ continue;
73
+ }
74
+ const length = Number(match[1]);
75
+ const start = headerEnd + 4;
76
+ if (lspBuffer.length < start + length) {
77
+ return;
78
+ }
79
+ const body = lspBuffer.slice(start, start + length).toString('utf8');
80
+ lspBuffer = lspBuffer.slice(start + length);
81
+ try {
82
+ const message = JSON.parse(body);
83
+ if (message.method === 'textDocument/publishDiagnostics') {
84
+ for (const item of (message.params && message.params.diagnostics) || []) {
85
+ channel.appendLine(`[${item.source || 'sid-lsp'}] ${item.message}`);
86
+ }
87
+ }
88
+ } catch (error) {
89
+ channel.appendLine(`sid lsp: invalid message (${error.message})`);
90
+ }
91
+ }
92
+ }
93
+
94
+ function startLsp(root) {
95
+ if (lspProcess || !configuration().get('autoStartLsp', true)) {
96
+ return;
97
+ }
98
+ lspProcess = cp.spawn(sidCommand(), ['lsp', '--dir', root], {
99
+ cwd: root,
100
+ env: process.env,
101
+ windowsHide: true,
102
+ });
103
+ lspProcess.stdout.on('data', handleLspData);
104
+ lspProcess.stderr.on('data', (data) => channel.append(data.toString()));
105
+ lspProcess.on('error', (error) => {
106
+ channel.appendLine(`sid lsp failed: ${error.message}`);
107
+ lspProcess = null;
108
+ });
109
+ lspProcess.on('exit', () => { lspProcess = null; });
110
+ const rootUri = vscode.Uri.file(root).toString();
111
+ sendLsp({
112
+ jsonrpc: '2.0',
113
+ id: 1,
114
+ method: 'initialize',
115
+ params: {
116
+ processId: process.pid,
117
+ rootUri,
118
+ capabilities: {},
119
+ workspaceFolders: [{ uri: rootUri, name: path.basename(root) }],
120
+ },
121
+ });
122
+ sendLsp({ jsonrpc: '2.0', method: 'initialized', params: {} });
123
+ }
124
+
125
+ function stopLsp() {
126
+ if (!lspProcess) {
127
+ return;
128
+ }
129
+ const child = lspProcess;
130
+ sendLsp({ jsonrpc: '2.0', id: 2, method: 'shutdown' });
131
+ setTimeout(() => {
132
+ try { child.kill(); } catch (error) { channel.appendLine(`sid lsp: ${error.message}`); }
133
+ }, 250);
134
+ lspProcess = null;
135
+ }
136
+
137
+ function syncDocument(document) {
138
+ sendLsp({
139
+ jsonrpc: '2.0',
140
+ method: 'textDocument/didOpen',
141
+ params: {
142
+ textDocument: {
143
+ uri: document.uri.toString(),
144
+ languageId: document.languageId,
145
+ version: document.version,
146
+ text: document.getText(),
147
+ },
148
+ },
149
+ });
150
+ }
151
+
152
+ function changeDocument(event) {
153
+ sendLsp({
154
+ jsonrpc: '2.0',
155
+ method: 'textDocument/didChange',
156
+ params: {
157
+ textDocument: {
158
+ uri: event.document.uri.toString(),
159
+ version: event.document.version,
160
+ },
161
+ contentChanges: [{ text: event.document.getText() }],
162
+ },
163
+ });
164
+ }
165
+
166
+ function saveDocument(document) {
167
+ sendLsp({
168
+ jsonrpc: '2.0',
169
+ method: 'textDocument/didSave',
170
+ params: {
171
+ textDocument: { uri: document.uri.toString() },
172
+ text: document.getText(),
173
+ },
174
+ });
175
+ forwardDiagnostics();
176
+ }
177
+
178
+ function forwardDiagnostics() {
179
+ const diagnostics = [];
180
+ for (const [uri, entries] of vscode.languages.getDiagnostics()) {
181
+ for (const entry of entries) {
182
+ diagnostics.push({
183
+ uri: uri.toString(),
184
+ line: entry.range.start.line,
185
+ character: entry.range.start.character,
186
+ end_line: entry.range.end.line,
187
+ end_character: entry.range.end.character,
188
+ severity: entry.severity,
189
+ message: entry.message,
190
+ source: entry.source || 'vscode',
191
+ });
192
+ }
193
+ }
194
+ sendLsp({ jsonrpc: '2.0', method: 'sid/diagnostics', params: { diagnostics } });
195
+ }
196
+
197
+ function selectionContext() {
198
+ const editor = vscode.window.activeTextEditor;
199
+ if (!editor) {
200
+ return '';
201
+ }
202
+ const selection = editor.selection;
203
+ if (selection.isEmpty) {
204
+ return '';
205
+ }
206
+ const text = editor.document.getText(selection);
207
+ if (!text.trim()) {
208
+ return '';
209
+ }
210
+ const rel = vscode.workspace.asRelativePath(editor.document.uri, false);
211
+ return `\n\n[Editor selection: ${rel} lines ${selection.start.line + 1}-${selection.end.line + 1}]\n${text}\n[/Editor selection]`;
212
+ }
213
+
214
+ function attachSelection() {
215
+ const block = selectionContext();
216
+ if (!block) {
217
+ vscode.window.showInformationMessage('SID: select text in the editor first.');
218
+ return;
219
+ }
220
+ attachedSelection = block;
221
+ vscode.window.showInformationMessage(`SID: selection attached (${block.length} char(s)); the next session includes it.`);
222
+ }
223
+
224
+ function pushDiagnostics() {
225
+ forwardDiagnostics();
226
+ vscode.window.showInformationMessage('SID: editor diagnostics pushed to the session feed.');
227
+ }
228
+
229
+ async function openSession() {
230
+ let root;
231
+ try {
232
+ root = workspaceRoot();
233
+ } catch (error) {
234
+ vscode.window.showErrorMessage(`SID: ${error.message}`);
235
+ return;
236
+ }
237
+ const task = await vscode.window.showInputBox({
238
+ prompt: 'SID task',
239
+ placeHolder: 'Describe the change; the current selection is attached automatically.',
240
+ ignoreFocusOut: true,
241
+ });
242
+ if (!task) {
243
+ return;
244
+ }
245
+ const context = attachedSelection || selectionContext();
246
+ attachedSelection = '';
247
+ const args = ['session', 'start', '--task', task + context, '--dir', root, '--json', '--quiet'];
248
+ const baseUrl = configuration().get('baseUrl');
249
+ if (baseUrl) {
250
+ args.push('--base-url', baseUrl);
251
+ }
252
+ const model = configuration().get('model');
253
+ if (model) {
254
+ args.push('--model', model);
255
+ }
256
+ const testCommand = configuration().get('testCommand');
257
+ if (testCommand) {
258
+ args.push('--test-cmd', testCommand);
259
+ }
260
+ if (!configuration().get('includeDiagnostics', true)) {
261
+ args.push('--no-diagnostics');
262
+ }
263
+ channel.show(true);
264
+ channel.appendLine(`sid session start in ${root}`);
265
+ let result;
266
+ try {
267
+ result = await spawnSid(args, root);
268
+ } catch (error) {
269
+ vscode.window.showErrorMessage(`SID: could not start ${sidCommand()}: ${error.message}`);
270
+ return;
271
+ }
272
+ if (result.stderr) {
273
+ channel.append(result.stderr);
274
+ }
275
+ if (result.code !== 0) {
276
+ vscode.window.showErrorMessage(`SID session failed (exit ${result.code}): ${result.stderr.trim() || 'no stderr'}`);
277
+ return;
278
+ }
279
+ try {
280
+ currentSession = JSON.parse(result.stdout);
281
+ } catch (error) {
282
+ vscode.window.showErrorMessage(`SID: session output was not a receipt (${error.message}).`);
283
+ return;
284
+ }
285
+ const changed = currentSession.changed || [];
286
+ channel.appendLine(`sid session: ${changed.length} file(s) changed`);
287
+ const choice = await vscode.window.showInformationMessage(
288
+ `SID session complete: ${changed.length} file(s) changed.`,
289
+ 'Show diff',
290
+ 'Approve and apply'
291
+ );
292
+ if (choice === 'Show diff') {
293
+ await showDiff();
294
+ } else if (choice === 'Approve and apply') {
295
+ await applyApproved();
296
+ }
297
+ }
298
+
299
+ async function showDiff() {
300
+ if (!currentSession) {
301
+ vscode.window.showInformationMessage('SID: run a session first.');
302
+ return;
303
+ }
304
+ const content = currentSession.diff || 'No changes were proposed.';
305
+ const document = await vscode.workspace.openTextDocument({ content, language: 'diff' });
306
+ await vscode.window.showTextDocument(document, { preview: true });
307
+ }
308
+
309
+ async function applyApproved() {
310
+ if (!currentSession) {
311
+ vscode.window.showInformationMessage('SID: run a session first.');
312
+ return;
313
+ }
314
+ const changed = currentSession.changed || [];
315
+ if (changed.length === 0) {
316
+ vscode.window.showInformationMessage('SID: this run proposed no changes.');
317
+ return;
318
+ }
319
+ let root;
320
+ try {
321
+ root = workspaceRoot();
322
+ } catch (error) {
323
+ vscode.window.showErrorMessage(`SID: ${error.message}`);
324
+ return;
325
+ }
326
+ const receiptPath = path.join(root, '.sid', 'runs', `${currentSession.run_id}.json`);
327
+ if (!fs.existsSync(receiptPath)) {
328
+ vscode.window.showErrorMessage(`SID: receipt not found at ${receiptPath}`);
329
+ return;
330
+ }
331
+ const choice = await vscode.window.showWarningMessage(
332
+ `Apply SID changes to ${changed.length} file(s)?`,
333
+ { modal: true, detail: changed.join('\n') },
334
+ 'Apply'
335
+ );
336
+ if (choice !== 'Apply') {
337
+ return;
338
+ }
339
+ let result;
340
+ try {
341
+ result = await spawnSid(
342
+ ['apply', '--dir', root, '--receipt', receiptPath, '--approve', '--json'],
343
+ root
344
+ );
345
+ } catch (error) {
346
+ vscode.window.showErrorMessage(`SID: apply failed: ${error.message}`);
347
+ return;
348
+ }
349
+ if (result.code !== 0) {
350
+ vscode.window.showErrorMessage(`SID apply refused: ${result.stderr.trim() || result.stdout.trim()}`);
351
+ return;
352
+ }
353
+ let summary;
354
+ try {
355
+ summary = JSON.parse(result.stdout);
356
+ } catch (error) {
357
+ summary = { applied: changed, status: 'applied' };
358
+ }
359
+ const applied = summary.applied || [];
360
+ vscode.window.showInformationMessage(`SID: applied ${applied.length} file(s).`);
361
+ }
362
+
363
+ function activate(context) {
364
+ channel = vscode.window.createOutputChannel('SID');
365
+ context.subscriptions.push(channel);
366
+ context.subscriptions.push(
367
+ vscode.commands.registerCommand('sid.openSession', openSession),
368
+ vscode.commands.registerCommand('sid.showDiff', showDiff),
369
+ vscode.commands.registerCommand('sid.applyApproved', applyApproved),
370
+ vscode.commands.registerCommand('sid.attachSelection', attachSelection),
371
+ vscode.commands.registerCommand('sid.sendDiagnostics', pushDiagnostics),
372
+ vscode.workspace.onDidOpenTextDocument(syncDocument),
373
+ vscode.workspace.onDidChangeTextDocument(changeDocument),
374
+ vscode.workspace.onDidSaveTextDocument(saveDocument),
375
+ vscode.languages.onDidChangeDiagnostics(forwardDiagnostics),
376
+ vscode.workspace.onDidChangeWorkspaceFolders(() => {
377
+ stopLsp();
378
+ try { startLsp(workspaceRoot()); } catch (error) { channel.appendLine(`sid lsp: ${error.message}`); }
379
+ }),
380
+ { dispose: stopLsp }
381
+ );
382
+ let root = null;
383
+ try { root = workspaceRoot(); } catch (error) { root = null; }
384
+ if (root) {
385
+ startLsp(root);
386
+ vscode.workspace.textDocuments.forEach(syncDocument);
387
+ forwardDiagnostics();
388
+ }
389
+ }
390
+
391
+ function deactivate() {
392
+ stopLsp();
393
+ }
394
+
395
+ module.exports = { activate, deactivate };
@@ -0,0 +1,120 @@
1
+ {
2
+ "name": "sid-harness",
3
+ "displayName": "SID Harness",
4
+ "description": "Drive SID's first-party coding loop from VS Code: sessions, diffs, approve/apply, selection context, and compiler diagnostics.",
5
+ "publisher": "sid",
6
+ "version": "0.1.0",
7
+ "license": "UNLICENSED",
8
+ "engines": {
9
+ "vscode": "^1.85.0"
10
+ },
11
+ "categories": [
12
+ "Programming Languages",
13
+ "Other"
14
+ ],
15
+ "keywords": [
16
+ "sid",
17
+ "coding-agent",
18
+ "lsp",
19
+ "diagnostics"
20
+ ],
21
+ "activationEvents": [
22
+ "onStartupFinished",
23
+ "onCommand:sid.openSession",
24
+ "onCommand:sid.showDiff",
25
+ "onCommand:sid.applyApproved",
26
+ "onCommand:sid.attachSelection",
27
+ "onCommand:sid.sendDiagnostics"
28
+ ],
29
+ "main": "./extension.js",
30
+ "contributes": {
31
+ "commands": [
32
+ {
33
+ "command": "sid.openSession",
34
+ "title": "SID: Open Coding Session",
35
+ "category": "SID"
36
+ },
37
+ {
38
+ "command": "sid.showDiff",
39
+ "title": "SID: Show Proposed Diff",
40
+ "category": "SID"
41
+ },
42
+ {
43
+ "command": "sid.applyApproved",
44
+ "title": "SID: Approve and Apply",
45
+ "category": "SID"
46
+ },
47
+ {
48
+ "command": "sid.attachSelection",
49
+ "title": "SID: Attach Selection as Context",
50
+ "category": "SID"
51
+ },
52
+ {
53
+ "command": "sid.sendDiagnostics",
54
+ "title": "SID: Push Diagnostics to Session",
55
+ "category": "SID"
56
+ }
57
+ ],
58
+ "menus": {
59
+ "editor/context": [
60
+ {
61
+ "command": "sid.attachSelection",
62
+ "when": "editorHasSelection",
63
+ "group": "navigation@9"
64
+ }
65
+ ],
66
+ "commandPalette": [
67
+ {
68
+ "command": "sid.openSession"
69
+ },
70
+ {
71
+ "command": "sid.showDiff"
72
+ },
73
+ {
74
+ "command": "sid.applyApproved"
75
+ },
76
+ {
77
+ "command": "sid.attachSelection"
78
+ },
79
+ {
80
+ "command": "sid.sendDiagnostics"
81
+ }
82
+ ]
83
+ },
84
+ "configuration": {
85
+ "title": "SID",
86
+ "properties": {
87
+ "sid.command": {
88
+ "type": "string",
89
+ "default": "sid",
90
+ "description": "The SID harness executable (env inherited, so SID_API_KEY applies)."
91
+ },
92
+ "sid.baseUrl": {
93
+ "type": "string",
94
+ "default": "",
95
+ "description": "Optional SID /v1 base URL; leave empty to use the harness default."
96
+ },
97
+ "sid.model": {
98
+ "type": "string",
99
+ "default": "auto",
100
+ "description": "Model id or 'auto' (SID routes it)."
101
+ },
102
+ "sid.testCommand": {
103
+ "type": "string",
104
+ "default": "",
105
+ "description": "Optional approved test command (executed as an argv list by the harness)."
106
+ },
107
+ "sid.autoStartLsp": {
108
+ "type": "boolean",
109
+ "default": true,
110
+ "description": "Start 'sid lsp' for this workspace and feed diagnostics into the loop."
111
+ },
112
+ "sid.includeDiagnostics": {
113
+ "type": "boolean",
114
+ "default": true,
115
+ "description": "Include the editor/LSP diagnostics feed in each SID session."
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }