ur-agent 1.36.0 → 1.37.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 +26 -15
- package/bin/ur.js +13 -3
- package/dist/cli.js +94 -73
- package/docs/IDE.md +183 -37
- package/documentation/index.html +2 -2
- package/extensions/vscode-ur-inline-diffs/README.md +72 -10
- package/extensions/vscode-ur-inline-diffs/out/extension.js +2333 -0
- package/extensions/vscode-ur-inline-diffs/package.json +174 -7
- package/package.json +2 -2
- package/extensions/vscode-ur-inline-diffs/extension.js +0 -383
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "ur-inline-diffs",
|
|
3
3
|
"displayName": "UR Inline Diffs",
|
|
4
4
|
"description": "Review, apply, and reject UR inline diff bundles from .ur/ide/diffs inside VS Code.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.37.0",
|
|
6
6
|
"publisher": "ur-agent",
|
|
7
7
|
"engines": {
|
|
8
8
|
"vscode": "^1.92.0"
|
|
@@ -12,14 +12,46 @@
|
|
|
12
12
|
],
|
|
13
13
|
"activationEvents": [
|
|
14
14
|
"onView:urInlineDiffs",
|
|
15
|
+
"onView:urActions",
|
|
15
16
|
"onCommand:urInlineDiffs.refresh",
|
|
16
17
|
"onCommand:urInlineDiffs.open",
|
|
17
18
|
"onCommand:urInlineDiffs.comment",
|
|
18
19
|
"onCommand:urInlineDiffs.apply",
|
|
19
20
|
"onCommand:urInlineDiffs.reject",
|
|
20
|
-
"onCommand:urInlineDiffs.status"
|
|
21
|
+
"onCommand:urInlineDiffs.status",
|
|
22
|
+
"onCommand:urInlineDiffs.chat.new",
|
|
23
|
+
"onCommand:urInlineDiffs.chat.open",
|
|
24
|
+
"onCommand:urInlineDiffs.chat.cancel",
|
|
25
|
+
"onCommand:urInlineDiffs.chat.addFile",
|
|
26
|
+
"onCommand:urInlineDiffs.chat.addSelection",
|
|
27
|
+
"onCommand:urInlineDiffs.chat.explainSelection",
|
|
28
|
+
"onCommand:urInlineDiffs.chat.fixSelection",
|
|
29
|
+
"onCommand:urInlineDiffs.chat.generateTests",
|
|
30
|
+
"onCommand:urInlineDiffs.agentStatus",
|
|
31
|
+
"onCommand:urInlineDiffs.agentOptions",
|
|
32
|
+
"onCommand:urInlineDiffs.reviewCurrentDiff",
|
|
33
|
+
"onCommand:urInlineDiffs.runVerifier",
|
|
34
|
+
"onCommand:urInlineDiffs.searchActions",
|
|
35
|
+
"onCommand:urInlineDiffs.openSettings",
|
|
36
|
+
"onCommand:urInlineDiffs.openDocs",
|
|
37
|
+
"onCommand:urInlineDiffs.openArtifacts",
|
|
38
|
+
"onCommand:urInlineDiffs.runSpec",
|
|
39
|
+
"onCommand:urInlineDiffs.runWorkflow",
|
|
40
|
+
"onCommand:urActions.refresh",
|
|
41
|
+
"onCommand:urActions.openBackgroundLog"
|
|
21
42
|
],
|
|
22
|
-
"main": "./extension.js",
|
|
43
|
+
"main": "./out/extension.js",
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "node esbuild.js",
|
|
46
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"@types/vscode": "^1.92.0",
|
|
51
|
+
"bun-types": "^1.2.0",
|
|
52
|
+
"esbuild": "^0.28.1",
|
|
53
|
+
"typescript": "^5.7.0"
|
|
54
|
+
},
|
|
23
55
|
"contributes": {
|
|
24
56
|
"viewsContainers": {
|
|
25
57
|
"activitybar": [
|
|
@@ -35,6 +67,10 @@
|
|
|
35
67
|
{
|
|
36
68
|
"id": "urInlineDiffs",
|
|
37
69
|
"name": "Inline Diffs"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "urActions",
|
|
73
|
+
"name": "Actions"
|
|
38
74
|
}
|
|
39
75
|
]
|
|
40
76
|
},
|
|
@@ -68,6 +104,95 @@
|
|
|
68
104
|
"command": "urInlineDiffs.status",
|
|
69
105
|
"title": "UR: Show Status (provider, model, plugins)",
|
|
70
106
|
"icon": "$(pulse)"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"command": "urInlineDiffs.chat.new",
|
|
110
|
+
"title": "UR: New Chat",
|
|
111
|
+
"icon": "$(comment-add)"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"command": "urInlineDiffs.chat.open",
|
|
115
|
+
"title": "UR: Open Chat",
|
|
116
|
+
"icon": "$(comment)"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"command": "urInlineDiffs.chat.cancel",
|
|
120
|
+
"title": "UR: Cancel Current Chat Request",
|
|
121
|
+
"icon": "$(stop-circle)"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"command": "urInlineDiffs.chat.addFile",
|
|
125
|
+
"title": "UR: Add Current File to Chat"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"command": "urInlineDiffs.chat.addSelection",
|
|
129
|
+
"title": "UR: Add Selection to Chat"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"command": "urInlineDiffs.chat.explainSelection",
|
|
133
|
+
"title": "UR: Explain Selection"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"command": "urInlineDiffs.chat.fixSelection",
|
|
137
|
+
"title": "UR: Fix Selection"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"command": "urInlineDiffs.chat.generateTests",
|
|
141
|
+
"title": "UR: Generate Tests for Selection"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"command": "urInlineDiffs.agentStatus",
|
|
145
|
+
"title": "UR: Agent Status",
|
|
146
|
+
"icon": "$(info)"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"command": "urInlineDiffs.agentOptions",
|
|
150
|
+
"title": "UR: Agent Options",
|
|
151
|
+
"icon": "$(settings-gear)"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"command": "urInlineDiffs.reviewCurrentDiff",
|
|
155
|
+
"title": "UR: Review Current Diff",
|
|
156
|
+
"icon": "$(git-compare)"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"command": "urInlineDiffs.runVerifier",
|
|
160
|
+
"title": "UR: Run Verifier",
|
|
161
|
+
"icon": "$(shield)"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"command": "urInlineDiffs.searchActions",
|
|
165
|
+
"title": "UR: Search Actions",
|
|
166
|
+
"icon": "$(search)"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"command": "urInlineDiffs.openSettings",
|
|
170
|
+
"title": "UR: Open Settings"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"command": "urInlineDiffs.openDocs",
|
|
174
|
+
"title": "UR: Open Docs"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"command": "urInlineDiffs.openArtifacts",
|
|
178
|
+
"title": "UR: Open Artifacts"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"command": "urInlineDiffs.runSpec",
|
|
182
|
+
"title": "UR: Run Spec"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"command": "urInlineDiffs.runWorkflow",
|
|
186
|
+
"title": "UR: Run Workflow"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"command": "urActions.refresh",
|
|
190
|
+
"title": "UR: Refresh IDE Actions",
|
|
191
|
+
"icon": "$(refresh)"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"command": "urActions.openBackgroundLog",
|
|
195
|
+
"title": "UR: Open Background Task Log"
|
|
71
196
|
}
|
|
72
197
|
],
|
|
73
198
|
"viewsWelcome": [
|
|
@@ -78,6 +203,16 @@
|
|
|
78
203
|
],
|
|
79
204
|
"menus": {
|
|
80
205
|
"view/title": [
|
|
206
|
+
{
|
|
207
|
+
"command": "urInlineDiffs.chat.new",
|
|
208
|
+
"when": "view == urInlineDiffs",
|
|
209
|
+
"group": "navigation"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"command": "urInlineDiffs.chat.open",
|
|
213
|
+
"when": "view == urInlineDiffs",
|
|
214
|
+
"group": "navigation"
|
|
215
|
+
},
|
|
81
216
|
{
|
|
82
217
|
"command": "urInlineDiffs.refresh",
|
|
83
218
|
"when": "view == urInlineDiffs",
|
|
@@ -87,29 +222,61 @@
|
|
|
87
222
|
"command": "urInlineDiffs.status",
|
|
88
223
|
"when": "view == urInlineDiffs",
|
|
89
224
|
"group": "navigation"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"command": "urActions.refresh",
|
|
228
|
+
"when": "view == urActions",
|
|
229
|
+
"group": "navigation"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"command": "urInlineDiffs.agentStatus",
|
|
233
|
+
"when": "view == urActions",
|
|
234
|
+
"group": "navigation"
|
|
90
235
|
}
|
|
91
236
|
],
|
|
92
237
|
"view/item/context": [
|
|
93
238
|
{
|
|
94
239
|
"command": "urInlineDiffs.open",
|
|
95
|
-
"when": "view == urInlineDiffs && viewItem == diff",
|
|
240
|
+
"when": "(view == urInlineDiffs || view == urActions) && viewItem == diff",
|
|
96
241
|
"group": "inline"
|
|
97
242
|
},
|
|
98
243
|
{
|
|
99
244
|
"command": "urInlineDiffs.apply",
|
|
100
|
-
"when": "view == urInlineDiffs && viewItem == diff",
|
|
245
|
+
"when": "(view == urInlineDiffs || view == urActions) && viewItem == diff",
|
|
101
246
|
"group": "1_actions"
|
|
102
247
|
},
|
|
103
248
|
{
|
|
104
249
|
"command": "urInlineDiffs.reject",
|
|
105
|
-
"when": "view == urInlineDiffs && viewItem == diff",
|
|
250
|
+
"when": "(view == urInlineDiffs || view == urActions) && viewItem == diff",
|
|
106
251
|
"group": "1_actions"
|
|
107
252
|
},
|
|
108
253
|
{
|
|
109
254
|
"command": "urInlineDiffs.comment",
|
|
110
|
-
"when": "view == urInlineDiffs && viewItem == diff",
|
|
255
|
+
"when": "(view == urInlineDiffs || view == urActions) && viewItem == diff",
|
|
111
256
|
"group": "2_review"
|
|
112
257
|
}
|
|
258
|
+
],
|
|
259
|
+
"editor/context": [
|
|
260
|
+
{
|
|
261
|
+
"command": "urInlineDiffs.chat.explainSelection",
|
|
262
|
+
"when": "editorHasSelection",
|
|
263
|
+
"group": "ur@1"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"command": "urInlineDiffs.chat.fixSelection",
|
|
267
|
+
"when": "editorHasSelection",
|
|
268
|
+
"group": "ur@2"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"command": "urInlineDiffs.chat.generateTests",
|
|
272
|
+
"when": "editorHasSelection",
|
|
273
|
+
"group": "ur@3"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"command": "urInlineDiffs.chat.addSelection",
|
|
277
|
+
"when": "editorHasSelection",
|
|
278
|
+
"group": "ur@4"
|
|
279
|
+
}
|
|
113
280
|
]
|
|
114
281
|
}
|
|
115
282
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ur-agent",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "UR-AGENT
|
|
3
|
+
"version": "1.37.0",
|
|
4
|
+
"description": "UR-AGENT \u2014 autonomous engineering workflow engine (plan, execute, test, verify, document, benchmark, reproduce)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.14",
|
|
7
7
|
"engines": {
|
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
const fs = require('node:fs')
|
|
2
|
-
const path = require('node:path')
|
|
3
|
-
const { execFile } = require('node:child_process')
|
|
4
|
-
const { promisify } = require('node:util')
|
|
5
|
-
const vscode = require('vscode')
|
|
6
|
-
|
|
7
|
-
const execFileAsync = promisify(execFile)
|
|
8
|
-
|
|
9
|
-
function workspaceRoot() {
|
|
10
|
-
return vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function diffsRoot(root) {
|
|
14
|
-
return path.join(root, '.ur', 'ide', 'diffs')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function manifestPath(root) {
|
|
18
|
-
return path.join(diffsRoot(root), 'manifest.json')
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function readJson(file, fallback) {
|
|
22
|
-
try {
|
|
23
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
24
|
-
} catch {
|
|
25
|
-
return fallback
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function writeJson(file, value) {
|
|
30
|
-
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
31
|
-
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function loadManifest(root) {
|
|
35
|
-
const manifest = readJson(manifestPath(root), { version: 1, diffs: [] })
|
|
36
|
-
return Array.isArray(manifest.diffs) ? manifest : { version: 1, diffs: [] }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function patchPath(root, bundle) {
|
|
40
|
-
return path.join(diffsRoot(root), bundle.patchFile)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function metadataPath(root, bundle) {
|
|
44
|
-
return path.join(diffsRoot(root), bundle.metadataFile)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function escapeHtml(text) {
|
|
48
|
-
return String(text)
|
|
49
|
-
.replace(/&/g, '&')
|
|
50
|
-
.replace(/</g, '<')
|
|
51
|
-
.replace(/>/g, '>')
|
|
52
|
-
.replace(/"/g, '"')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function formatCount(count, singular, plural = `${singular}s`) {
|
|
56
|
-
return `${count} ${count === 1 ? singular : plural}`
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function formatRelativeTime(value) {
|
|
60
|
-
if (!value) return 'unknown time'
|
|
61
|
-
const date = new Date(value)
|
|
62
|
-
if (Number.isNaN(date.getTime())) return String(value)
|
|
63
|
-
const deltaMs = Date.now() - date.getTime()
|
|
64
|
-
const minute = 60 * 1000
|
|
65
|
-
const hour = 60 * minute
|
|
66
|
-
const day = 24 * hour
|
|
67
|
-
if (deltaMs < minute) return 'just now'
|
|
68
|
-
if (deltaMs < hour) return `${Math.max(1, Math.floor(deltaMs / minute))}m ago`
|
|
69
|
-
if (deltaMs < day) return `${Math.floor(deltaMs / hour)}h ago`
|
|
70
|
-
if (deltaMs < 7 * day) return `${Math.floor(deltaMs / day)}d ago`
|
|
71
|
-
return date.toLocaleDateString()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function statusIcon(status) {
|
|
75
|
-
switch (status) {
|
|
76
|
-
case 'applied':
|
|
77
|
-
return new vscode.ThemeIcon('check', new vscode.ThemeColor('testing.iconPassed'))
|
|
78
|
-
case 'rejected':
|
|
79
|
-
return new vscode.ThemeIcon('circle-slash', new vscode.ThemeColor('testing.iconFailed'))
|
|
80
|
-
case 'commented':
|
|
81
|
-
return new vscode.ThemeIcon('comment-discussion', new vscode.ThemeColor('charts.yellow'))
|
|
82
|
-
default:
|
|
83
|
-
return new vscode.ThemeIcon('diff', new vscode.ThemeColor('charts.blue'))
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
class DiffItem extends vscode.TreeItem {
|
|
88
|
-
constructor(bundle) {
|
|
89
|
-
const title = bundle.title || bundle.id
|
|
90
|
-
super(title, vscode.TreeItemCollapsibleState.None)
|
|
91
|
-
this.bundle = bundle
|
|
92
|
-
this.contextValue = 'diff'
|
|
93
|
-
const fileCount = bundle.files?.length ?? 0
|
|
94
|
-
const changedAt = bundle.updatedAt ?? bundle.createdAt
|
|
95
|
-
this.description = `${bundle.status ?? 'captured'} · ${formatCount(fileCount, 'file')} · ${formatRelativeTime(changedAt)}`
|
|
96
|
-
this.iconPath = statusIcon(bundle.status)
|
|
97
|
-
this.tooltip = new vscode.MarkdownString(
|
|
98
|
-
[
|
|
99
|
-
`**${escapeHtml(title)}**`,
|
|
100
|
-
'',
|
|
101
|
-
`- ID: \`${escapeHtml(bundle.id)}\``,
|
|
102
|
-
`- Status: ${escapeHtml(bundle.status ?? 'captured')}`,
|
|
103
|
-
`- Files: ${fileCount}`,
|
|
104
|
-
`- Patch: \`${escapeHtml(bundle.patchFile)}\``,
|
|
105
|
-
].join('\n'),
|
|
106
|
-
)
|
|
107
|
-
this.command = {
|
|
108
|
-
command: 'urInlineDiffs.open',
|
|
109
|
-
title: 'Open Inline Diff',
|
|
110
|
-
arguments: [this],
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
class ActionItem extends vscode.TreeItem {
|
|
116
|
-
constructor(label, description, icon, command, tooltip) {
|
|
117
|
-
super(label, vscode.TreeItemCollapsibleState.None)
|
|
118
|
-
this.contextValue = 'urAction'
|
|
119
|
-
this.description = description
|
|
120
|
-
this.iconPath = new vscode.ThemeIcon(icon)
|
|
121
|
-
this.tooltip = tooltip ?? `${label}${description ? ` — ${description}` : ''}`
|
|
122
|
-
this.command = command
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
class InfoItem extends vscode.TreeItem {
|
|
127
|
-
constructor(label, description, icon = 'info') {
|
|
128
|
-
super(label, vscode.TreeItemCollapsibleState.None)
|
|
129
|
-
this.contextValue = 'urInfo'
|
|
130
|
-
this.description = description
|
|
131
|
-
this.iconPath = new vscode.ThemeIcon(icon)
|
|
132
|
-
this.tooltip = `${label}${description ? ` — ${description}` : ''}`
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
class DiffProvider {
|
|
137
|
-
constructor() {
|
|
138
|
-
this._onDidChangeTreeData = new vscode.EventEmitter()
|
|
139
|
-
this.onDidChangeTreeData = this._onDidChangeTreeData.event
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
refresh() {
|
|
143
|
-
this._onDidChangeTreeData.fire()
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
getTreeItem(item) {
|
|
147
|
-
return item
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
getChildren() {
|
|
151
|
-
const root = workspaceRoot()
|
|
152
|
-
if (!root) {
|
|
153
|
-
return [
|
|
154
|
-
new InfoItem('Open a workspace folder', 'UR inline diffs are scoped to the active project', 'folder-opened'),
|
|
155
|
-
]
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const manifest = loadManifest(root)
|
|
159
|
-
const diffs = manifest
|
|
160
|
-
.diffs
|
|
161
|
-
.slice()
|
|
162
|
-
.sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)))
|
|
163
|
-
|
|
164
|
-
if (diffs.length === 0) {
|
|
165
|
-
return [
|
|
166
|
-
new InfoItem(
|
|
167
|
-
'Ready for inline review',
|
|
168
|
-
fs.existsSync(manifestPath(root)) ? 'No pending diff bundles' : 'No diff bundles captured yet',
|
|
169
|
-
'pass',
|
|
170
|
-
),
|
|
171
|
-
new ActionItem(
|
|
172
|
-
'Show UR status',
|
|
173
|
-
'Provider, model, plugins',
|
|
174
|
-
'pulse',
|
|
175
|
-
{ command: 'urInlineDiffs.status', title: 'Show UR Status' },
|
|
176
|
-
),
|
|
177
|
-
new ActionItem(
|
|
178
|
-
'Refresh',
|
|
179
|
-
path.relative(root, manifestPath(root)),
|
|
180
|
-
'refresh',
|
|
181
|
-
{ command: 'urInlineDiffs.refresh', title: 'Refresh Inline Diffs' },
|
|
182
|
-
),
|
|
183
|
-
]
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return diffs.map(bundle => new DiffItem(bundle))
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function renderDiffHtml(root, bundle) {
|
|
191
|
-
const patch = fs.existsSync(patchPath(root, bundle))
|
|
192
|
-
? fs.readFileSync(patchPath(root, bundle), 'utf8')
|
|
193
|
-
: ''
|
|
194
|
-
const comments = bundle.comments ?? []
|
|
195
|
-
return `<!doctype html>
|
|
196
|
-
<html lang="en">
|
|
197
|
-
<head>
|
|
198
|
-
<meta charset="utf-8">
|
|
199
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
200
|
-
<style>
|
|
201
|
-
:root { color-scheme: light dark; }
|
|
202
|
-
body { font: 13px/1.5 var(--vscode-font-family); color: var(--vscode-foreground); padding: 20px; margin: 0; }
|
|
203
|
-
header { border-bottom: 1px solid var(--vscode-panel-border); padding-bottom: 14px; margin-bottom: 16px; }
|
|
204
|
-
h1 { font-size: 20px; font-weight: 600; margin: 0 0 6px; }
|
|
205
|
-
h2 { font-size: 14px; margin: 20px 0 10px; }
|
|
206
|
-
.meta, .where { color: var(--vscode-descriptionForeground); }
|
|
207
|
-
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
208
|
-
.chip { border: 1px solid var(--vscode-panel-border); border-radius: 4px; padding: 3px 8px; background: var(--vscode-sideBar-background); }
|
|
209
|
-
pre { background: var(--vscode-editor-background); border: 1px solid var(--vscode-panel-border); border-radius: 6px; padding: 14px; overflow: auto; font-family: var(--vscode-editor-font-family); }
|
|
210
|
-
.comments { margin-top: 18px; }
|
|
211
|
-
.comment { border: 1px solid var(--vscode-panel-border); border-radius: 6px; padding: 10px 12px; margin-bottom: 10px; background: var(--vscode-sideBar-background); }
|
|
212
|
-
</style>
|
|
213
|
-
</head>
|
|
214
|
-
<body>
|
|
215
|
-
<header>
|
|
216
|
-
<h1>${escapeHtml(bundle.title)}</h1>
|
|
217
|
-
<div class="meta">${escapeHtml(bundle.id)} · ${escapeHtml(formatRelativeTime(bundle.updatedAt ?? bundle.createdAt))}</div>
|
|
218
|
-
<div class="chips">
|
|
219
|
-
<span class="chip">${escapeHtml(bundle.status ?? 'captured')}</span>
|
|
220
|
-
<span class="chip">${escapeHtml(formatCount(bundle.files?.length ?? 0, 'file'))}</span>
|
|
221
|
-
<span class="chip">${escapeHtml(formatCount(comments.length, 'comment'))}</span>
|
|
222
|
-
</div>
|
|
223
|
-
</header>
|
|
224
|
-
<pre>${escapeHtml(patch)}</pre>
|
|
225
|
-
<section class="comments">
|
|
226
|
-
<h2>Comments</h2>
|
|
227
|
-
${comments.length === 0 ? '<p class="meta">No comments yet.</p>' : comments.map(comment => {
|
|
228
|
-
const where = comment.file ? `${comment.file}${comment.line ? `:${comment.line}` : ''}` : 'General'
|
|
229
|
-
return `<div class="comment"><div class="where">${escapeHtml(where)} · ${escapeHtml(comment.at ?? '')}</div><div>${escapeHtml(comment.text)}</div></div>`
|
|
230
|
-
}).join('')}
|
|
231
|
-
</section>
|
|
232
|
-
</body>
|
|
233
|
-
</html>`
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async function openDiff(item) {
|
|
237
|
-
const root = workspaceRoot()
|
|
238
|
-
const bundle = item?.bundle
|
|
239
|
-
if (!root || !bundle) {
|
|
240
|
-
vscode.window.showWarningMessage('No UR inline diff selected.')
|
|
241
|
-
return
|
|
242
|
-
}
|
|
243
|
-
const panel = vscode.window.createWebviewPanel(
|
|
244
|
-
'urInlineDiff',
|
|
245
|
-
`UR ${bundle.id}`,
|
|
246
|
-
vscode.ViewColumn.Active,
|
|
247
|
-
{ enableScripts: false },
|
|
248
|
-
)
|
|
249
|
-
const latest = readJson(metadataPath(root, bundle), bundle)
|
|
250
|
-
panel.webview.html = renderDiffHtml(root, latest)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async function commentDiff(item, provider) {
|
|
254
|
-
const root = workspaceRoot()
|
|
255
|
-
const bundle = item?.bundle
|
|
256
|
-
if (!root || !bundle) {
|
|
257
|
-
vscode.window.showWarningMessage('No UR inline diff selected.')
|
|
258
|
-
return
|
|
259
|
-
}
|
|
260
|
-
const text = await vscode.window.showInputBox({
|
|
261
|
-
title: `Comment on ${bundle.id}`,
|
|
262
|
-
prompt: 'Comment text',
|
|
263
|
-
ignoreFocusOut: true,
|
|
264
|
-
})
|
|
265
|
-
if (!text?.trim()) return
|
|
266
|
-
|
|
267
|
-
const manifest = loadManifest(root)
|
|
268
|
-
const manifestBundle = manifest.diffs.find(diff => diff.id === bundle.id)
|
|
269
|
-
if (!manifestBundle) {
|
|
270
|
-
vscode.window.showErrorMessage(`UR inline diff not found: ${bundle.id}`)
|
|
271
|
-
return
|
|
272
|
-
}
|
|
273
|
-
const at = new Date().toISOString()
|
|
274
|
-
const comment = { at, text: text.trim() }
|
|
275
|
-
manifestBundle.status = 'commented'
|
|
276
|
-
manifestBundle.updatedAt = at
|
|
277
|
-
manifestBundle.comments = [...(manifestBundle.comments ?? []), comment]
|
|
278
|
-
writeJson(manifestPath(root), manifest)
|
|
279
|
-
writeJson(metadataPath(root, manifestBundle), manifestBundle)
|
|
280
|
-
provider.refresh()
|
|
281
|
-
vscode.window.showInformationMessage(`Added UR comment to ${bundle.id}.`)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function setBundleStatus(root, bundleId, status) {
|
|
285
|
-
const manifest = loadManifest(root)
|
|
286
|
-
const bundle = manifest.diffs.find(diff => diff.id === bundleId)
|
|
287
|
-
if (!bundle) return null
|
|
288
|
-
bundle.status = status
|
|
289
|
-
bundle.updatedAt = new Date().toISOString()
|
|
290
|
-
writeJson(manifestPath(root), manifest)
|
|
291
|
-
writeJson(metadataPath(root, bundle), bundle)
|
|
292
|
-
return bundle
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// Apply the captured patch to the working tree. Never silent: the user is asked
|
|
296
|
-
// to confirm, and the result is reported. Uses `git apply` from the repo root.
|
|
297
|
-
async function applyDiff(item, provider) {
|
|
298
|
-
const root = workspaceRoot()
|
|
299
|
-
const bundle = item?.bundle
|
|
300
|
-
if (!root || !bundle) {
|
|
301
|
-
vscode.window.showWarningMessage('No UR inline diff selected.')
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
const patch = patchPath(root, bundle)
|
|
305
|
-
if (!fs.existsSync(patch)) {
|
|
306
|
-
vscode.window.showErrorMessage(`UR patch file missing for ${bundle.id}.`)
|
|
307
|
-
return
|
|
308
|
-
}
|
|
309
|
-
const choice = await vscode.window.showWarningMessage(
|
|
310
|
-
`Apply UR patch ${bundle.id} to your working tree? This modifies ${bundle.files?.length ?? 0} file(s).`,
|
|
311
|
-
{ modal: true },
|
|
312
|
-
'Apply',
|
|
313
|
-
)
|
|
314
|
-
if (choice !== 'Apply') return
|
|
315
|
-
try {
|
|
316
|
-
await execFileAsync('git', ['apply', '--whitespace=nowarn', patch], { cwd: root })
|
|
317
|
-
setBundleStatus(root, bundle.id, 'applied')
|
|
318
|
-
provider.refresh()
|
|
319
|
-
vscode.window.showInformationMessage(`Applied UR patch ${bundle.id}.`)
|
|
320
|
-
} catch (error) {
|
|
321
|
-
const message = error?.stderr || error?.message || String(error)
|
|
322
|
-
vscode.window.showErrorMessage(`Failed to apply UR patch ${bundle.id}: ${message}`)
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async function rejectDiff(item, provider) {
|
|
327
|
-
const root = workspaceRoot()
|
|
328
|
-
const bundle = item?.bundle
|
|
329
|
-
if (!root || !bundle) {
|
|
330
|
-
vscode.window.showWarningMessage('No UR inline diff selected.')
|
|
331
|
-
return
|
|
332
|
-
}
|
|
333
|
-
const updated = setBundleStatus(root, bundle.id, 'rejected')
|
|
334
|
-
if (!updated) {
|
|
335
|
-
vscode.window.showErrorMessage(`UR inline diff not found: ${bundle.id}`)
|
|
336
|
-
return
|
|
337
|
-
}
|
|
338
|
-
provider.refresh()
|
|
339
|
-
vscode.window.showInformationMessage(`Rejected UR patch ${bundle.id} (no files changed).`)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
async function showStatus(channel) {
|
|
343
|
-
const root = workspaceRoot()
|
|
344
|
-
if (!root) {
|
|
345
|
-
vscode.window.showWarningMessage('Open a workspace folder to query UR status.')
|
|
346
|
-
return
|
|
347
|
-
}
|
|
348
|
-
channel.clear()
|
|
349
|
-
channel.show(true)
|
|
350
|
-
channel.appendLine('Running: ur ide status')
|
|
351
|
-
try {
|
|
352
|
-
const { stdout } = await execFileAsync('ur', ['ide', 'status'], { cwd: root })
|
|
353
|
-
channel.appendLine(stdout.trim())
|
|
354
|
-
} catch (error) {
|
|
355
|
-
channel.appendLine(
|
|
356
|
-
`Could not run \`ur ide status\`: ${error?.message || String(error)}\n` +
|
|
357
|
-
'Ensure the UR CLI is installed and on PATH.',
|
|
358
|
-
)
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function activate(context) {
|
|
363
|
-
const provider = new DiffProvider()
|
|
364
|
-
const channel = vscode.window.createOutputChannel('UR')
|
|
365
|
-
const tree = vscode.window.createTreeView('urInlineDiffs', {
|
|
366
|
-
treeDataProvider: provider,
|
|
367
|
-
showCollapseAll: false,
|
|
368
|
-
})
|
|
369
|
-
context.subscriptions.push(
|
|
370
|
-
channel,
|
|
371
|
-
tree,
|
|
372
|
-
vscode.commands.registerCommand('urInlineDiffs.refresh', () => provider.refresh()),
|
|
373
|
-
vscode.commands.registerCommand('urInlineDiffs.open', item => openDiff(item)),
|
|
374
|
-
vscode.commands.registerCommand('urInlineDiffs.comment', item => commentDiff(item, provider)),
|
|
375
|
-
vscode.commands.registerCommand('urInlineDiffs.apply', item => applyDiff(item, provider)),
|
|
376
|
-
vscode.commands.registerCommand('urInlineDiffs.reject', item => rejectDiff(item, provider)),
|
|
377
|
-
vscode.commands.registerCommand('urInlineDiffs.status', () => showStatus(channel)),
|
|
378
|
-
)
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function deactivate() {}
|
|
382
|
-
|
|
383
|
-
module.exports = { activate, deactivate }
|