@jkwd/inbase 0.1.4 → 0.1.6

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 CHANGED
@@ -1,4 +1,9 @@
1
- # Inbase
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/inbase-logo-white.png" />
4
+ <img src="docs/inbase-logo.png" alt="InBase — Dive into your codebase" width="520" />
5
+ </picture>
6
+ </p>
2
7
 
3
8
  A first-person 3D map of a JavaScript or TypeScript codebase. Files become blocks, folders become walkable areas, and imports become lines in the air.
4
9
 
@@ -46,14 +46,23 @@ export function extractPatchAdditions(
46
46
  addedFunctions: PatchSymbolAddition[]
47
47
  addedVariables: PatchSymbolAddition[]
48
48
  addedImports: PatchImportAddition[]
49
+ changedFunctions: PatchSymbolAddition[]
50
+ changedVariables: PatchSymbolAddition[]
49
51
  }
50
52
 
51
53
  export function accumulatePatchAdditions(patches?: string[]): {
52
54
  addedFunctions: PatchSymbolAddition[]
53
55
  addedVariables: PatchSymbolAddition[]
54
56
  addedImports: PatchImportAddition[]
57
+ changedFunctions: PatchSymbolAddition[]
58
+ changedVariables: PatchSymbolAddition[]
55
59
  }
56
60
 
61
+ export function applyUnifiedPatchToContents(
62
+ files: Map<string, string>,
63
+ patch: string,
64
+ ): ReturnType<typeof parseUnifiedPatch>
65
+
57
66
  export function applyUnifiedPatch(
58
67
  patch: string,
59
68
  targetRoot: string,
@@ -74,6 +83,7 @@ export const emptyIntent: {
74
83
  feature: null
75
84
  steps: unknown[]
76
85
  step: null
86
+ stepByStep: boolean
77
87
  files: string[]
78
88
  creates: string[]
79
89
  deletes: string[]
@@ -83,6 +93,8 @@ export const emptyIntent: {
83
93
  addedFunctions: PatchSymbolAddition[]
84
94
  addedVariables: PatchSymbolAddition[]
85
95
  addedImports: PatchImportAddition[]
96
+ changedFunctions: PatchSymbolAddition[]
97
+ changedVariables: PatchSymbolAddition[]
86
98
  reason: null
87
99
  sessionId: null
88
100
  diffId: null
@@ -93,6 +105,7 @@ export const emptyIntent: {
93
105
  preview: boolean
94
106
  phase: null
95
107
  working: boolean
108
+ stalledWait: boolean
96
109
  creationMode: boolean
97
110
  canEnterBlueprint: boolean
98
111
  blueprintSessionId: string | null
@@ -39,7 +39,7 @@ function parseHunks(section) {
39
39
  let current = null
40
40
 
41
41
  for (const line of lines) {
42
- const header = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
42
+ const header = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/)
43
43
  if (header) {
44
44
  if (current) hunks.push(current)
45
45
  current = {
@@ -47,6 +47,7 @@ function parseHunks(section) {
47
47
  oldCount: Number(header[2] ?? '1'),
48
48
  newStart: Number(header[3]),
49
49
  newCount: Number(header[4] ?? '1'),
50
+ headerContext: (header[5] ?? '').trim(),
50
51
  lines: [],
51
52
  }
52
53
  continue
@@ -182,30 +183,107 @@ function emptyAdditions() {
182
183
  addedFunctions: [],
183
184
  addedVariables: [],
184
185
  addedImports: [],
186
+ changedFunctions: [],
187
+ changedVariables: [],
185
188
  }
186
189
  }
187
190
 
191
+ function isImportLine(line) {
192
+ return /^\s*import\b/.test(line)
193
+ }
194
+
195
+ function isTopLevelSymbolLine(line) {
196
+ return extractSymbols(line).length > 0 && !/^\s/.test(line)
197
+ }
198
+
199
+ function enclosingChangedSymbols(entry) {
200
+ if (entry.kind === 'add') return []
201
+ const found = []
202
+ const seen = new Set()
203
+
204
+ for (const hunk of entry.hunks) {
205
+ const contextLines = []
206
+ let addedTopLevel = false
207
+ let hasBodyEdit = false
208
+ for (const line of hunk.lines) {
209
+ const tag = line[0]
210
+ const body = line.slice(1)
211
+ if (tag === ' ') contextLines.push(body)
212
+ if (tag !== '+' && tag !== '-') continue
213
+ if (tag === '+' && isTopLevelSymbolLine(body)) addedTopLevel = true
214
+ if (
215
+ !addedTopLevel &&
216
+ extractSymbols(body).length === 0 &&
217
+ !isImportLine(body) &&
218
+ body.trim()
219
+ ) {
220
+ hasBodyEdit = true
221
+ }
222
+ }
223
+ if (!hasBodyEdit) continue
224
+ for (const symbol of [
225
+ ...extractSymbols(hunk.headerContext ?? ''),
226
+ ...extractSymbols(contextLines.join('\n')),
227
+ ]) {
228
+ const key = `${symbol.kind}:${symbol.name}`
229
+ if (seen.has(key)) continue
230
+ seen.add(key)
231
+ found.push(symbol)
232
+ }
233
+ }
234
+
235
+ return found
236
+ }
237
+
188
238
  function applyEntriesToAdditions(current, entries) {
189
239
  const functions = new Map(
190
- current.addedFunctions.map((item) => [additionKey(item.file, item.name), item]),
240
+ (current.addedFunctions ?? []).map((item) => [additionKey(item.file, item.name), item]),
191
241
  )
192
242
  const variables = new Map(
193
- current.addedVariables.map((item) => [additionKey(item.file, item.name), item]),
243
+ (current.addedVariables ?? []).map((item) => [additionKey(item.file, item.name), item]),
244
+ )
245
+ const changedFunctions = new Map(
246
+ (current.changedFunctions ?? []).map((item) => [
247
+ additionKey(item.file, item.name),
248
+ item,
249
+ ]),
250
+ )
251
+ const changedVariables = new Map(
252
+ (current.changedVariables ?? []).map((item) => [
253
+ additionKey(item.file, item.name),
254
+ item,
255
+ ]),
194
256
  )
195
257
  const imports = new Map(
196
- current.addedImports.map((item) => [additionKey(item.file, item.name, item.from), item]),
258
+ (current.addedImports ?? []).map((item) => [
259
+ additionKey(item.file, item.name, item.from),
260
+ item,
261
+ ]),
197
262
  )
198
263
 
199
- const dropFile = (fileId) => {
200
- for (const key of [...functions.keys()]) {
201
- if (functions.get(key).file === fileId) functions.delete(key)
264
+ const dropFrom = (bucket, fileId) => {
265
+ for (const key of [...bucket.keys()]) {
266
+ if (bucket.get(key).file === fileId) bucket.delete(key)
202
267
  }
203
- for (const key of [...variables.keys()]) {
204
- if (variables.get(key).file === fileId) variables.delete(key)
205
- }
206
- for (const key of [...imports.keys()]) {
207
- if (imports.get(key).file === fileId) imports.delete(key)
268
+ }
269
+
270
+ const dropFile = (fileId) => {
271
+ dropFrom(functions, fileId)
272
+ dropFrom(variables, fileId)
273
+ dropFrom(changedFunctions, fileId)
274
+ dropFrom(changedVariables, fileId)
275
+ dropFrom(imports, fileId)
276
+ }
277
+
278
+ const markChanged = (kind, file, name) => {
279
+ const key = additionKey(file, name)
280
+ if (kind === 'function') {
281
+ if (functions.has(key)) return
282
+ changedFunctions.set(key, { name, file })
283
+ return
208
284
  }
285
+ if (variables.has(key)) return
286
+ changedVariables.set(key, { name, file })
209
287
  }
210
288
 
211
289
  for (const entry of entries) {
@@ -224,15 +302,36 @@ function applyEntriesToAdditions(current, entries) {
224
302
  )
225
303
 
226
304
  for (const symbol of removedSymbols) {
227
- if (keptSymbolKeys.has(`${symbol.kind}:${symbol.name}`)) continue
228
- if (symbol.kind === 'function') functions.delete(additionKey(entry.id, symbol.name))
229
- else variables.delete(additionKey(entry.id, symbol.name))
305
+ const key = additionKey(entry.id, symbol.name)
306
+ if (keptSymbolKeys.has(`${symbol.kind}:${symbol.name}`)) {
307
+ markChanged(symbol.kind, entry.id, symbol.name)
308
+ continue
309
+ }
310
+ if (symbol.kind === 'function') {
311
+ functions.delete(key)
312
+ changedFunctions.delete(key)
313
+ } else {
314
+ variables.delete(key)
315
+ changedVariables.delete(key)
316
+ }
230
317
  }
231
318
  for (const symbol of addedSymbols) {
232
- if (previousSymbolKeys.has(`${symbol.kind}:${symbol.name}`)) continue
319
+ if (previousSymbolKeys.has(`${symbol.kind}:${symbol.name}`)) {
320
+ markChanged(symbol.kind, entry.id, symbol.name)
321
+ continue
322
+ }
233
323
  const item = { name: symbol.name, file: entry.id }
234
- if (symbol.kind === 'function') functions.set(additionKey(entry.id, symbol.name), item)
235
- else variables.set(additionKey(entry.id, symbol.name), item)
324
+ const key = additionKey(entry.id, symbol.name)
325
+ if (symbol.kind === 'function') {
326
+ changedFunctions.delete(key)
327
+ functions.set(key, item)
328
+ } else {
329
+ changedVariables.delete(key)
330
+ variables.set(key, item)
331
+ }
332
+ }
333
+ for (const symbol of enclosingChangedSymbols(entry)) {
334
+ markChanged(symbol.kind, entry.id, symbol.name)
236
335
  }
237
336
 
238
337
  const removedBindings = extractImportBindings(removedSource(entry))
@@ -262,6 +361,8 @@ function applyEntriesToAdditions(current, entries) {
262
361
  addedFunctions: [...functions.values()],
263
362
  addedVariables: [...variables.values()],
264
363
  addedImports: [...imports.values()],
364
+ changedFunctions: [...changedFunctions.values()],
365
+ changedVariables: [...changedVariables.values()],
265
366
  }
266
367
  }
267
368
 
@@ -345,30 +446,58 @@ function applyHunks(original, hunks) {
345
446
  return `${lines.join('\n')}\n`
346
447
  }
347
448
 
348
- export function applyUnifiedPatch(patch, targetRoot) {
449
+ export function applyUnifiedPatchToContents(files, patch) {
349
450
  const parsed = parseUnifiedPatch(patch)
350
451
  if (parsed.entries.length === 0) {
351
452
  throw new Error('Patch did not contain any file changes')
352
453
  }
353
454
 
354
455
  for (const file of parsed.entries) {
355
- const absolute = path.join(targetRoot, file.id)
356
456
  if (file.kind === 'delete') {
357
- if (fs.existsSync(absolute)) fs.rmSync(absolute)
457
+ files.delete(file.id)
358
458
  continue
359
459
  }
360
460
 
361
461
  const original =
362
- file.kind === 'add' || !fs.existsSync(absolute)
363
- ? ''
364
- : fs.readFileSync(absolute, 'utf8')
462
+ file.kind === 'add' || !files.has(file.id) ? '' : files.get(file.id)
365
463
 
366
464
  if (file.kind === 'modify' && original === '') {
367
465
  throw new Error(`Cannot modify missing file ${file.id}`)
368
466
  }
369
467
 
468
+ files.set(file.id, applyHunks(original, file.hunks))
469
+ }
470
+
471
+ return parsed
472
+ }
473
+
474
+ export function applyUnifiedPatch(patch, targetRoot) {
475
+ const parsed = parseUnifiedPatch(patch)
476
+ if (parsed.entries.length === 0) {
477
+ throw new Error('Patch did not contain any file changes')
478
+ }
479
+
480
+ const files = new Map()
481
+ for (const file of parsed.entries) {
482
+ const absolute = path.join(targetRoot, file.id)
483
+ if (
484
+ file.kind !== 'add' &&
485
+ fs.existsSync(absolute) &&
486
+ fs.statSync(absolute).isFile()
487
+ ) {
488
+ files.set(file.id, fs.readFileSync(absolute, 'utf8'))
489
+ }
490
+ }
491
+ applyUnifiedPatchToContents(files, patch)
492
+
493
+ for (const file of parsed.entries) {
494
+ const absolute = path.join(targetRoot, file.id)
495
+ if (file.kind === 'delete') {
496
+ if (fs.existsSync(absolute)) fs.rmSync(absolute)
497
+ continue
498
+ }
370
499
  fs.mkdirSync(path.dirname(absolute), { recursive: true })
371
- fs.writeFileSync(absolute, applyHunks(original, file.hunks))
500
+ fs.writeFileSync(absolute, files.get(file.id))
372
501
  }
373
502
 
374
503
  return parsed
@@ -381,6 +510,7 @@ export const emptyIntent = {
381
510
  feature: null,
382
511
  steps: [],
383
512
  step: null,
513
+ stepByStep: true,
384
514
  files: [],
385
515
  creates: [],
386
516
  deletes: [],
@@ -390,6 +520,8 @@ export const emptyIntent = {
390
520
  addedFunctions: [],
391
521
  addedVariables: [],
392
522
  addedImports: [],
523
+ changedFunctions: [],
524
+ changedVariables: [],
393
525
  reason: null,
394
526
  sessionId: null,
395
527
  diffId: null,
@@ -400,6 +532,7 @@ export const emptyIntent = {
400
532
  preview: false,
401
533
  phase: null,
402
534
  working: false,
535
+ stalledWait: false,
403
536
  creationMode: false,
404
537
  canEnterBlueprint: false,
405
538
  blueprintSessionId: null,
@@ -435,6 +568,8 @@ export function overlayPatch(intent, patchText, knownFileIds = []) {
435
568
  addedFunctions: [],
436
569
  addedVariables: [],
437
570
  addedImports: [],
571
+ changedFunctions: [],
572
+ changedVariables: [],
438
573
  }
439
574
  }
440
575
 
@@ -451,6 +586,8 @@ export function overlayPatch(intent, patchText, knownFileIds = []) {
451
586
  addedFunctions: base.addedFunctions ?? [],
452
587
  addedVariables: base.addedVariables ?? [],
453
588
  addedImports: base.addedImports ?? [],
589
+ changedFunctions: base.changedFunctions ?? [],
590
+ changedVariables: base.changedVariables ?? [],
454
591
  }
455
592
  }
456
593
 
@@ -37,6 +37,7 @@ export type DiffManifest = {
37
37
  activeDiffId: string | null
38
38
  pendingInstruction: string | null
39
39
  workStartedAt: string | null
40
+ stepByStep: boolean
40
41
  createdAt: string
41
42
  updatedAt: string
42
43
  diffs: DiffEntry[]
@@ -45,10 +46,33 @@ export type DiffManifest = {
45
46
  export function assertSessionId(value: unknown): string
46
47
  export function readActiveSession(dataDir: string): string | null
47
48
  export function writeActiveSession(dataDir: string, sessionId: string | null): void
49
+ export function touchSessionConnection(dataDir: string, sessionId: string): void
50
+ export function isSessionConnected(
51
+ dataDir: string,
52
+ sessionId: string,
53
+ waiterIds?: Set<string>,
54
+ ): boolean
55
+ export function listStoredSessionIds(dataDir: string): string[]
56
+ export function listOpenSessionIds(
57
+ dataDir: string,
58
+ waiterIds?: Set<string>,
59
+ ): string[]
60
+ export function discardInactiveDiffSessions(
61
+ dataDir: string,
62
+ targetRoot?: string | null,
63
+ waiterIds?: Iterable<string>,
64
+ ): string[]
65
+ export function listSessionIntents(
66
+ dataDir: string,
67
+ knownFileIds?: string[],
68
+ ): Array<Record<string, unknown>>
48
69
  export function readBlueprintSession(dataDir: string): string | null
49
70
  export function writeBlueprintSession(dataDir: string, sessionId: string | null): void
50
71
  export function readManifest(dataDir: string, sessionId: string): DiffManifest | null
51
72
  export function writeManifest(dataDir: string, manifest: DiffManifest): void
73
+ export function isSessionStopped(dataDir: string, sessionId: string): boolean
74
+ export function isWorkflowStopped(dataDir: string, sessionId: string): boolean
75
+ export function sessionStoppedError(sessionId: string): Error
52
76
  export function startSession(
53
77
  dataDir: string,
54
78
  input: {
@@ -107,6 +131,18 @@ export function reportPlan(
107
131
  stepTitles: string[]
108
132
  },
109
133
  ): DiffManifest
134
+ export function isStepByStep(manifest: DiffManifest | null | undefined): boolean
135
+ export function autoAdvance(
136
+ dataDir: string,
137
+ sessionId: string,
138
+ targetRoot?: string | null,
139
+ ): DiffManifest | null
140
+ export function setStepByStep(
141
+ dataDir: string,
142
+ sessionId: string,
143
+ enabled: boolean,
144
+ targetRoot?: string | null,
145
+ ): DiffManifest
110
146
  export function invokeStep(
111
147
  dataDir: string,
112
148
  sessionId: string,
@@ -118,6 +154,7 @@ export function sessionIntent(
118
154
  sessionId: string,
119
155
  knownFileIds?: string[],
120
156
  selectedDiffId?: string,
157
+ waiterIds?: Set<string>,
121
158
  ): Record<string, unknown> | null
122
159
  export function resolveTargetFile(
123
160
  targetRoot: string,