@jkwd/inbase 0.1.13 → 0.1.16
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 +2 -2
- package/apps/explorer/scripts/branch-changes.mjs +2 -19
- package/apps/explorer/scripts/patch-lib.d.ts +3 -0
- package/apps/explorer/scripts/patch-lib.mjs +3 -0
- package/apps/explorer/scripts/scan-ignore.mjs +156 -0
- package/apps/explorer/scripts/scan-target.mjs +67 -29
- package/apps/explorer/scripts/session-store.d.ts +24 -3
- package/apps/explorer/scripts/session-store.mjs +180 -74
- package/apps/explorer/src/App.tsx +258 -147
- package/apps/explorer/src/agentIntent.ts +87 -5
- package/apps/explorer/src/index.css +106 -5
- package/apps/explorer/src/keyboard.ts +26 -0
- package/apps/explorer/src/scene/BlockPlacer.tsx +2 -11
- package/apps/explorer/src/scene/FileBlock.tsx +12 -4
- package/apps/explorer/src/scene/FolderArea.tsx +20 -0
- package/apps/explorer/src/scene/IslandPlacer.tsx +2 -11
- package/apps/explorer/src/scene/MapView.tsx +74 -37
- package/apps/explorer/src/scene/Player.tsx +9 -9
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +2 -0
- package/apps/explorer/src/scene/World.tsx +46 -1
- package/apps/explorer/src/types.ts +28 -0
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +4 -0
- package/apps/explorer/src/ui/HUD.tsx +435 -99
- package/apps/explorer/src/ui/MapContextMenu.tsx +2 -0
- package/apps/explorer/src/userCreated.ts +98 -0
- package/apps/explorer/vite.config.ts +71 -5
- package/bin/session.mjs +40 -12
- package/package.json +2 -1
- package/skill/commands/inbase.md +1 -1
- package/skill/inbase/SKILL.md +24 -22
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
|
+
import { shouldIgnoreShortcut } from '../keyboard'
|
|
3
4
|
|
|
4
5
|
export type MapContextMenuState = {
|
|
5
6
|
x: number
|
|
@@ -24,6 +25,7 @@ export function MapContextMenu({
|
|
|
24
25
|
if (!menu) return
|
|
25
26
|
const onKey = (event: KeyboardEvent) => {
|
|
26
27
|
if (event.code !== 'Escape') return
|
|
28
|
+
if (shouldIgnoreShortcut(event)) return
|
|
27
29
|
event.preventDefault()
|
|
28
30
|
onClose()
|
|
29
31
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CONFIG, fileHeight } from './theme'
|
|
2
2
|
import { folderOfFile, folderParent } from './layout'
|
|
3
3
|
import type {
|
|
4
|
+
BlueprintNote,
|
|
5
|
+
BlueprintNoteKind,
|
|
4
6
|
CodebaseGraph,
|
|
5
7
|
FileNode,
|
|
6
8
|
PatchImportAddition,
|
|
@@ -339,6 +341,102 @@ export function isBlueprintSymbolName(value: string) {
|
|
|
339
341
|
return /^[A-Za-z_$][\w$]*$/.test(value.trim())
|
|
340
342
|
}
|
|
341
343
|
|
|
344
|
+
export function blueprintNoteKey(
|
|
345
|
+
note: Pick<BlueprintNote, 'file' | 'kind' | 'name'>,
|
|
346
|
+
) {
|
|
347
|
+
return note.kind === 'file'
|
|
348
|
+
? `file:${note.file}`
|
|
349
|
+
: `${note.kind}:${note.file}:${note.name ?? ''}`
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function parseBlueprintNote(value: unknown): BlueprintNote | null {
|
|
353
|
+
if (!value || typeof value !== 'object') return null
|
|
354
|
+
const item = value as Partial<BlueprintNote>
|
|
355
|
+
const file = typeof item.file === 'string' ? item.file.trim() : ''
|
|
356
|
+
const note = typeof item.note === 'string' ? item.note : ''
|
|
357
|
+
if (!file || !note.trim()) return null
|
|
358
|
+
if (item.kind === 'file') return { file, kind: 'file', note }
|
|
359
|
+
if (item.kind !== 'function' && item.kind !== 'variable') return null
|
|
360
|
+
const name = typeof item.name === 'string' ? item.name.trim() : ''
|
|
361
|
+
if (!name) return null
|
|
362
|
+
return { file, kind: item.kind, name, note }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function parseBlueprintNotes(value: unknown): BlueprintNote[] {
|
|
366
|
+
if (!Array.isArray(value)) return []
|
|
367
|
+
const seen = new Set<string>()
|
|
368
|
+
const notes: BlueprintNote[] = []
|
|
369
|
+
for (const item of value) {
|
|
370
|
+
const parsed = parseBlueprintNote(item)
|
|
371
|
+
if (!parsed) continue
|
|
372
|
+
const key = blueprintNoteKey(parsed)
|
|
373
|
+
if (seen.has(key)) continue
|
|
374
|
+
seen.add(key)
|
|
375
|
+
notes.push(parsed)
|
|
376
|
+
}
|
|
377
|
+
return notes
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function findBlueprintNote(
|
|
381
|
+
notes: BlueprintNote[],
|
|
382
|
+
file: string,
|
|
383
|
+
kind: BlueprintNoteKind,
|
|
384
|
+
name?: string,
|
|
385
|
+
) {
|
|
386
|
+
return (
|
|
387
|
+
notes.find((item) =>
|
|
388
|
+
kind === 'file'
|
|
389
|
+
? item.kind === 'file' && item.file === file
|
|
390
|
+
: item.kind === kind && item.file === file && item.name === name,
|
|
391
|
+
)?.note ?? ''
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function setBlueprintNote(
|
|
396
|
+
notes: BlueprintNote[],
|
|
397
|
+
next: {
|
|
398
|
+
file: string
|
|
399
|
+
kind: BlueprintNoteKind
|
|
400
|
+
name?: string
|
|
401
|
+
note: string
|
|
402
|
+
},
|
|
403
|
+
): BlueprintNote[] {
|
|
404
|
+
const key = blueprintNoteKey(next)
|
|
405
|
+
const without = notes.filter((item) => blueprintNoteKey(item) !== key)
|
|
406
|
+
if (next.note === '') return without
|
|
407
|
+
const stored: BlueprintNote =
|
|
408
|
+
next.kind === 'file'
|
|
409
|
+
? { file: next.file, kind: 'file', note: next.note }
|
|
410
|
+
: {
|
|
411
|
+
file: next.file,
|
|
412
|
+
kind: next.kind,
|
|
413
|
+
name: (next.name ?? '').trim(),
|
|
414
|
+
note: next.note,
|
|
415
|
+
}
|
|
416
|
+
if (stored.kind !== 'file' && !stored.name) return without
|
|
417
|
+
return [...without, stored]
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function dropBlueprintFileNotes(
|
|
421
|
+
notes: BlueprintNote[],
|
|
422
|
+
fileIds: Iterable<string>,
|
|
423
|
+
) {
|
|
424
|
+
const removed = new Set(fileIds)
|
|
425
|
+
return notes.filter((item) => !removed.has(item.file))
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function dropBlueprintSymbolNote(
|
|
429
|
+
notes: BlueprintNote[],
|
|
430
|
+
file: string,
|
|
431
|
+
kind: 'function' | 'variable',
|
|
432
|
+
name: string,
|
|
433
|
+
) {
|
|
434
|
+
return notes.filter(
|
|
435
|
+
(item) =>
|
|
436
|
+
!(item.file === file && item.kind === kind && item.name === name),
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
|
|
342
440
|
export function parseBlueprintImport(
|
|
343
441
|
raw: string,
|
|
344
442
|
file: string,
|
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
focusSession,
|
|
29
29
|
stopSession,
|
|
30
30
|
updateBlueprint,
|
|
31
|
+
readBlueprint,
|
|
32
|
+
setBlueprintHidden,
|
|
33
|
+
clearBlueprint,
|
|
34
|
+
cleanupBlueprint,
|
|
31
35
|
} from './scripts/session-store.mjs'
|
|
32
36
|
|
|
33
37
|
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
@@ -80,6 +84,44 @@ function knownFileIds() {
|
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
function knownFolderPaths() {
|
|
88
|
+
try {
|
|
89
|
+
const graph = JSON.parse(fs.readFileSync(codebaseFile, 'utf8')) as {
|
|
90
|
+
folders?: Array<{ path?: string }>
|
|
91
|
+
}
|
|
92
|
+
return Array.isArray(graph.folders)
|
|
93
|
+
? graph.folders
|
|
94
|
+
.map((folder) => folder.path)
|
|
95
|
+
.filter((path): path is string => Boolean(path))
|
|
96
|
+
: []
|
|
97
|
+
} catch {
|
|
98
|
+
return []
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function blueprintIntentFields() {
|
|
103
|
+
const blueprint = readBlueprint(dataDir)
|
|
104
|
+
return {
|
|
105
|
+
creationMode: true,
|
|
106
|
+
blueprintHidden: Boolean(blueprint.hidden),
|
|
107
|
+
blueprintRevision: blueprint.revision,
|
|
108
|
+
userCreatedBlocks: blueprint.userCreatedBlocks,
|
|
109
|
+
userCreatedIslands: blueprint.userCreatedIslands,
|
|
110
|
+
blueprintFunctions: blueprint.addedFunctions,
|
|
111
|
+
blueprintVariables: blueprint.addedVariables,
|
|
112
|
+
blueprintImports: blueprint.addedImports,
|
|
113
|
+
blueprintNotes: blueprint.notes,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function intentResponse(sessionId?: string) {
|
|
118
|
+
const base =
|
|
119
|
+
sessionId !== undefined && sessionId !== ''
|
|
120
|
+
? (sessionIntent(dataDir, sessionId, knownFileIds()) ?? { ...emptyIntent })
|
|
121
|
+
: { ...emptyIntent }
|
|
122
|
+
return { ...base, ...blueprintIntentFields() }
|
|
123
|
+
}
|
|
124
|
+
|
|
83
125
|
function sendJson(res: ServerResponse, status: number, body: unknown) {
|
|
84
126
|
res.statusCode = status
|
|
85
127
|
res.setHeader('Content-Type', 'application/json')
|
|
@@ -145,6 +187,7 @@ function jsonFilePlugin(): Plugin {
|
|
|
145
187
|
focusedSessionId: readActiveSession(dataDir),
|
|
146
188
|
nextAttachSessionId: nextAttachSessionId(dataDir),
|
|
147
189
|
intents: listSessionIntents(dataDir, knownFileIds()),
|
|
190
|
+
blueprint: readBlueprint(dataDir),
|
|
148
191
|
})
|
|
149
192
|
return
|
|
150
193
|
}
|
|
@@ -214,13 +257,21 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
214
257
|
name?: string
|
|
215
258
|
step?: number
|
|
216
259
|
stepByStep?: boolean
|
|
260
|
+
hidden?: boolean
|
|
217
261
|
userCreatedBlocks?: unknown[]
|
|
218
262
|
userCreatedIslands?: unknown[]
|
|
219
263
|
addedFunctions?: unknown[]
|
|
220
264
|
addedVariables?: unknown[]
|
|
221
265
|
addedImports?: unknown[]
|
|
266
|
+
notes?: unknown[]
|
|
222
267
|
}
|
|
223
268
|
const action = body.action
|
|
269
|
+
const blueprintActions = new Set([
|
|
270
|
+
'blueprint_update',
|
|
271
|
+
'blueprint_clear',
|
|
272
|
+
'blueprint_cleanup',
|
|
273
|
+
'blueprint_set_hidden',
|
|
274
|
+
])
|
|
224
275
|
if (
|
|
225
276
|
action !== 'invoke' &&
|
|
226
277
|
action !== 'continue' &&
|
|
@@ -230,6 +281,9 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
230
281
|
action !== 'blueprint_no' &&
|
|
231
282
|
action !== 'blueprint_send' &&
|
|
232
283
|
action !== 'blueprint_update' &&
|
|
284
|
+
action !== 'blueprint_clear' &&
|
|
285
|
+
action !== 'blueprint_cleanup' &&
|
|
286
|
+
action !== 'blueprint_set_hidden' &&
|
|
233
287
|
action !== 'focus' &&
|
|
234
288
|
action !== 'set_step_by_step' &&
|
|
235
289
|
action !== 'set_initial_instruction' &&
|
|
@@ -238,7 +292,11 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
238
292
|
sendJson(res, 400, { error: 'invalid workflow action' })
|
|
239
293
|
return
|
|
240
294
|
}
|
|
241
|
-
if (
|
|
295
|
+
if (
|
|
296
|
+
action !== 'setup_session' &&
|
|
297
|
+
!blueprintActions.has(action ?? '') &&
|
|
298
|
+
!body.sessionId
|
|
299
|
+
) {
|
|
242
300
|
sendJson(res, 400, { error: 'sessionId is required' })
|
|
243
301
|
return
|
|
244
302
|
}
|
|
@@ -295,7 +353,14 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
295
353
|
addedFunctions: body.addedFunctions,
|
|
296
354
|
addedVariables: body.addedVariables,
|
|
297
355
|
addedImports: body.addedImports,
|
|
356
|
+
notes: body.notes,
|
|
298
357
|
})
|
|
358
|
+
} else if (action === 'blueprint_clear') {
|
|
359
|
+
clearBlueprint(dataDir)
|
|
360
|
+
} else if (action === 'blueprint_cleanup') {
|
|
361
|
+
cleanupBlueprint(dataDir, knownFileIds(), knownFolderPaths())
|
|
362
|
+
} else if (action === 'blueprint_set_hidden') {
|
|
363
|
+
setBlueprintHidden(dataDir, Boolean(body.hidden))
|
|
299
364
|
} else if (action === 'blueprint_send') {
|
|
300
365
|
sendBlueprint(dataDir, body.sessionId, {
|
|
301
366
|
userCreatedBlocks: body.userCreatedBlocks,
|
|
@@ -303,14 +368,15 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
303
368
|
addedFunctions: body.addedFunctions,
|
|
304
369
|
addedVariables: body.addedVariables,
|
|
305
370
|
addedImports: body.addedImports,
|
|
371
|
+
notes: body.notes,
|
|
306
372
|
})
|
|
307
373
|
} else if (action === 'setup_session') {
|
|
308
374
|
const manifest = setupSession(dataDir, {
|
|
309
375
|
sessionId: body.sessionId,
|
|
310
376
|
name: body.name,
|
|
311
377
|
})
|
|
312
|
-
const next =
|
|
313
|
-
sendJson(res, 200, next ?? { ...emptyIntent })
|
|
378
|
+
const next = intentResponse(manifest.sessionId)
|
|
379
|
+
sendJson(res, 200, next ?? { ...emptyIntent, ...blueprintIntentFields() })
|
|
314
380
|
return
|
|
315
381
|
} else if (action === 'set_initial_instruction') {
|
|
316
382
|
setInitialInstruction(dataDir, body.sessionId, body.instruction ?? '')
|
|
@@ -327,8 +393,8 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
327
393
|
stopSession(dataDir, body.sessionId, targetRoot)
|
|
328
394
|
rescanTarget('after stopping session')
|
|
329
395
|
}
|
|
330
|
-
const next =
|
|
331
|
-
sendJson(res, 200, next ?? { ...emptyIntent })
|
|
396
|
+
const next = intentResponse(body.sessionId)
|
|
397
|
+
sendJson(res, 200, next ?? { ...emptyIntent, ...blueprintIntentFields() })
|
|
332
398
|
} catch (error) {
|
|
333
399
|
const message = error instanceof Error ? error.message : 'invalid request'
|
|
334
400
|
sendJson(res, 400, { error: message })
|
package/bin/session.mjs
CHANGED
|
@@ -135,7 +135,7 @@ function emitApprovalHandshake(store, dataDir, sessionId, manifest, initialDiff)
|
|
|
135
135
|
console.log(
|
|
136
136
|
continuing
|
|
137
137
|
? `VISUAL_CODER_EXECUTE Step ${manifest.currentStep} is invoked${title ? `: ${title}` : ''}. Continue immediately: edit live files for this step only, then inbase propose-patch --session ${sessionId} with no patch file. Do not explore, re-plan, or run wait-for-blueprint.`
|
|
138
|
-
: `VISUAL_CODER_EXECUTE Step ${manifest.currentStep} is invoked${title ? `: ${title}` : ''}. Re-read
|
|
138
|
+
: `VISUAL_CODER_EXECUTE Step ${manifest.currentStep} is invoked${title ? `: ${title}` : ''}. Re-read the shared blueprint.json before implementing; the user can place files and islands at any time. Edit the live project files for this step only (Write, StrReplace, Delete). Then record the step with inbase propose-patch --session ${sessionId} — no patch file. Inbase diffs those edits against the invoke snapshot and stores the patch. Do not write a unified diff yourself.`,
|
|
139
139
|
)
|
|
140
140
|
process.exit(0)
|
|
141
141
|
}
|
|
@@ -151,7 +151,7 @@ function emitApprovalHandshake(store, dataDir, sessionId, manifest, initialDiff)
|
|
|
151
151
|
? `\nVISUAL_CODER_INSTRUCTION_START\n${manifest.pendingInstruction}\nVISUAL_CODER_INSTRUCTION_END`
|
|
152
152
|
: ''
|
|
153
153
|
console.log(
|
|
154
|
-
`VISUAL_CODER_REPLAN Keep accepted patch files before step ${manifest.currentStep}. Disk is baseline + accepted patches. Do not edit project files until the next EXECUTE. The
|
|
154
|
+
`VISUAL_CODER_REPLAN Keep accepted patch files before step ${manifest.currentStep}. Disk is baseline + accepted patches. Do not edit project files until the next EXECUTE. The shared blueprint remains leading; if this instruction would differ from it, ask the user before replacing the plan. Report the revised tail with inbase report-plan, then wait for invocation.${instruction}`,
|
|
155
155
|
)
|
|
156
156
|
process.exit(4)
|
|
157
157
|
}
|
|
@@ -183,6 +183,19 @@ export async function attachSession(args) {
|
|
|
183
183
|
)
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
function printBlueprintDump(blueprint) {
|
|
187
|
+
const blocks = blueprint.userCreatedBlocks ?? []
|
|
188
|
+
const islands = blueprint.userCreatedIslands ?? []
|
|
189
|
+
console.log(
|
|
190
|
+
blueprint.enabled
|
|
191
|
+
? `VISUAL_CODER_BLUEPRINT_READY The shared blueprint has ${blocks.length} file(s) and ${islands.length} island(s). The blueprint is leading: create those paths and honor addedFunctions, addedVariables, addedImports, and notes even if they are not on disk. Notes are extra instructions or pseudo code for a file, function, or variable — follow them when implementing those items. Do not omit, rename, relocate, or replace them. Extra new files not in the blueprint are a deviation. If you would differ from the blueprint, ask the user first; do not silently deviate. The user can keep placing files and islands; re-read the shared blueprint.json when it is printed again.`
|
|
192
|
+
: 'VISUAL_CODER_BLUEPRINT_READY The shared blueprint is empty. The user can still place files and islands; re-read the shared blueprint.json when it is printed again. Continue without user-placed files until that file has content.',
|
|
193
|
+
)
|
|
194
|
+
console.log('VISUAL_CODER_BLUEPRINT_START')
|
|
195
|
+
console.log(JSON.stringify(blueprint, null, 2))
|
|
196
|
+
console.log('VISUAL_CODER_BLUEPRINT_END')
|
|
197
|
+
}
|
|
198
|
+
|
|
186
199
|
export async function waitForBlueprint(args) {
|
|
187
200
|
const { store, config } = await loadExplorer()
|
|
188
201
|
const sessionId = takeFlagValue(args, '--session')
|
|
@@ -204,7 +217,7 @@ export async function waitForBlueprint(args) {
|
|
|
204
217
|
emitStopped(store, config.dataDir, sessionId)
|
|
205
218
|
}
|
|
206
219
|
|
|
207
|
-
const blueprint = store.readBlueprint(config.dataDir
|
|
220
|
+
const blueprint = store.readBlueprint(config.dataDir)
|
|
208
221
|
const blocks = blueprint.userCreatedBlocks ?? []
|
|
209
222
|
const islands = blueprint.userCreatedIslands ?? []
|
|
210
223
|
signalAck(
|
|
@@ -216,14 +229,8 @@ export async function waitForBlueprint(args) {
|
|
|
216
229
|
? `${blocks.length} file(s), ${islands.length} island(s)`
|
|
217
230
|
: 'none',
|
|
218
231
|
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
? `VISUAL_CODER_BLUEPRINT_READY The session started with ${blocks.length} file(s) and ${islands.length} island(s). The blueprint is leading: create those paths and honor addedFunctions, addedVariables, and addedImports even if they are not on disk. Do not omit, rename, relocate, or replace them. Extra new files not in the blueprint are a deviation. If you would differ from the blueprint, ask the user first; do not silently deviate. The user can still place files and islands on later steps; re-read this session's blueprint.json before each step.`
|
|
222
|
-
: 'VISUAL_CODER_BLUEPRINT_READY The session started without a blueprint. They can still place files and islands on later steps; re-read this session\'s blueprint.json before each step. Continue without user-placed files until that file is enabled.',
|
|
223
|
-
)
|
|
224
|
-
console.log('VISUAL_CODER_BLUEPRINT_START')
|
|
225
|
-
console.log(JSON.stringify(blueprint, null, 2))
|
|
226
|
-
console.log('VISUAL_CODER_BLUEPRINT_END')
|
|
232
|
+
printBlueprintDump(blueprint)
|
|
233
|
+
store.markBlueprintSeen(config.dataDir, sessionId, blueprint.revision)
|
|
227
234
|
const instruction =
|
|
228
235
|
typeof manifest.initialInstruction === 'string'
|
|
229
236
|
? manifest.initialInstruction.trim()
|
|
@@ -296,6 +303,27 @@ export async function waitForApproval(args) {
|
|
|
296
303
|
const manifest = store.readManifest(config.dataDir, sessionId)
|
|
297
304
|
emitApprovalHandshake(store, config.dataDir, sessionId, manifest, initialDiff)
|
|
298
305
|
if (!manifest) continue
|
|
306
|
+
const blueprint = store.readBlueprint(config.dataDir)
|
|
307
|
+
const seen = manifest.blueprintRevision ?? 0
|
|
308
|
+
if (blueprint.revision > seen) {
|
|
309
|
+
const blocks = blueprint.userCreatedBlocks ?? []
|
|
310
|
+
const islands = blueprint.userCreatedIslands ?? []
|
|
311
|
+
signalAck(
|
|
312
|
+
store,
|
|
313
|
+
config.dataDir,
|
|
314
|
+
sessionId,
|
|
315
|
+
'blueprint',
|
|
316
|
+
blueprint.enabled
|
|
317
|
+
? `${blocks.length} file(s), ${islands.length} island(s)`
|
|
318
|
+
: 'none',
|
|
319
|
+
)
|
|
320
|
+
console.log(
|
|
321
|
+
'VISUAL_CODER_BLUEPRINT The shared blueprint changed. Follow the latest files, islands, functions, variables, imports, and notes. Do not omit, rename, relocate, or replace them. If this would differ from the current plan, ask the user before replacing the plan. Then run wait-for-approval again.',
|
|
322
|
+
)
|
|
323
|
+
printBlueprintDump(blueprint)
|
|
324
|
+
store.markBlueprintSeen(config.dataDir, sessionId, blueprint.revision)
|
|
325
|
+
process.exit(6)
|
|
326
|
+
}
|
|
299
327
|
const waiting = waitingMessage(sessionId, manifest)
|
|
300
328
|
if (waiting !== lastWaiting) {
|
|
301
329
|
console.log(waiting)
|
|
@@ -325,7 +353,7 @@ export async function proposePatch(args) {
|
|
|
325
353
|
if (!sessionId) usage('propose-patch', '--session <cursor-chat-id> --clear')
|
|
326
354
|
store.stopSession(config.dataDir, sessionId, config.targetRoot)
|
|
327
355
|
console.log(
|
|
328
|
-
`Cleared session ${sessionId}; stored diffs
|
|
356
|
+
`Cleared session ${sessionId}; stored diffs were removed. The shared blueprint remains.`,
|
|
329
357
|
)
|
|
330
358
|
process.exit(0)
|
|
331
359
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jkwd/inbase",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "A first-person 3D map of a codebase, with a visual coding workflow for Cursor.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Joris Kuijper",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"apps/explorer/scripts/open-editor.mjs",
|
|
51
51
|
"apps/explorer/scripts/patch-lib.d.ts",
|
|
52
52
|
"apps/explorer/scripts/patch-lib.mjs",
|
|
53
|
+
"apps/explorer/scripts/scan-ignore.mjs",
|
|
53
54
|
"apps/explorer/scripts/scan-target.mjs",
|
|
54
55
|
"apps/explorer/scripts/session-store.d.ts",
|
|
55
56
|
"apps/explorer/scripts/session-store.mjs",
|
package/skill/commands/inbase.md
CHANGED
|
@@ -6,7 +6,7 @@ The user invoked `/inbase`. This is how a chat joins the visualizer session that
|
|
|
6
6
|
|
|
7
7
|
Do **not** ask for a session id. Do **not** run `inbase start-session`. Do **not** print the "direct chat interaction not allowed" message.
|
|
8
8
|
|
|
9
|
-
1. Attach to the next waiting visualizer session (
|
|
9
|
+
1. Attach to the next waiting visualizer session (oldest first; skip sessions that already have an LLM):
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx inbase attach
|
package/skill/inbase/SKILL.md
CHANGED
|
@@ -57,7 +57,7 @@ If this chat is not yet attached, run:
|
|
|
57
57
|
npx inbase attach
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
That attaches this chat to the next waiting visualizer session (
|
|
60
|
+
That attaches this chat to the next waiting visualizer session (oldest first).
|
|
61
61
|
Already-attached sessions are skipped. Window focus does not matter. No id is
|
|
62
62
|
passed in; read `VISUAL_CODER_SESSION` from the output and use that
|
|
63
63
|
`--session` value for every later command. Then continue from
|
|
@@ -94,22 +94,20 @@ npx inbase wait-for-blueprint --session "<session-id>"
|
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
The user may have placed files (`Space`) and islands (`B`), or left the
|
|
97
|
-
blueprint empty. That
|
|
98
|
-
placing
|
|
99
|
-
session.
|
|
97
|
+
shared blueprint empty. That blueprint is shared across sessions. They can
|
|
98
|
+
keep placing at any time.
|
|
100
99
|
If `wait-for-blueprint` prints `VISUAL_CODER_INSTRUCTION_START` /
|
|
101
100
|
`VISUAL_CODER_INSTRUCTION_END`, that text is the user's request for this
|
|
102
101
|
session. Plan from that instruction and the blueprint together. The
|
|
103
102
|
instruction does not override an enabled blueprint; if they conflict,
|
|
104
103
|
ask the user.
|
|
105
104
|
3. Read the handshake output between `VISUAL_CODER_BLUEPRINT_START` and
|
|
106
|
-
`VISUAL_CODER_BLUEPRINT_END`, or read
|
|
107
|
-
`.inbase/diff-sessions/<session-id>/blueprint.json`.
|
|
105
|
+
`VISUAL_CODER_BLUEPRINT_END`, or read `.inbase/blueprint.json`.
|
|
108
106
|
If `enabled` is true, **the blueprint is leading**. Treat
|
|
109
107
|
`userCreatedBlocks`, `userCreatedIslands`, `addedFunctions`,
|
|
110
108
|
`addedVariables`, and `addedImports` as the source of truth for this chat.
|
|
111
109
|
Create those paths and add those symbols even if they are not on disk.
|
|
112
|
-
|
|
110
|
+
The same blueprint is shared with every session.
|
|
113
111
|
Do not omit, rename, relocate, or replace a blueprint file, island, symbol,
|
|
114
112
|
or import. Extra edits to existing files are allowed when needed to finish
|
|
115
113
|
the feature. Extra new files that are not in the blueprint are a deviation.
|
|
@@ -124,8 +122,8 @@ npx inbase wait-for-blueprint --session "<session-id>"
|
|
|
124
122
|
- `filesOnIsland` is the rest of that folder
|
|
125
123
|
Prefer those files while `followLook` is true, unless the request clearly
|
|
126
124
|
needs something else. If `followLook` is false or missing, ignore viewpoint
|
|
127
|
-
and
|
|
128
|
-
blueprint: still follow
|
|
125
|
+
and choose files from the request itself. Viewpoint never overrides the
|
|
126
|
+
blueprint: still follow the shared blueprint when `enabled` is true.
|
|
129
127
|
6. List **all** steps needed to finish the feature. Keep steps small enough that
|
|
130
128
|
one recorded step is one landscape change (usually one new file, or a few
|
|
131
129
|
related edits).
|
|
@@ -150,8 +148,8 @@ npx inbase wait-for-approval --session "<session-id>"
|
|
|
150
148
|
returns, they already did. Do not edit project files until this prints
|
|
151
149
|
`VISUAL_CODER_ACK execute` / `VISUAL_CODER_EXECUTE`. If Step by step is off,
|
|
152
150
|
this returns immediately for each remaining step. First reply in chat
|
|
153
|
-
acknowledging the ack, then re-read
|
|
154
|
-
user can place files and islands
|
|
151
|
+
acknowledging the ack, then re-read the shared `blueprint.json`; the
|
|
152
|
+
user can place files and islands at any time.
|
|
155
153
|
9. Implement only the invoked step by editing the live project files (Write,
|
|
156
154
|
StrReplace, Delete). Paths are the same ids as `codebase.json`. Then record
|
|
157
155
|
the step — Inbase diffs the working tree against the snapshot taken at
|
|
@@ -180,20 +178,24 @@ npx inbase wait-for-approval --session "<session-id>"
|
|
|
180
178
|
|
|
181
179
|
- Exit `0` (`VISUAL_CODER_ACK execute` / `VISUAL_CODER_EXECUTE`): the
|
|
182
180
|
highlighted step was invoked. If this is a later step, implement it now —
|
|
183
|
-
do not explore, re-plan, or run `wait-for-blueprint`. Re-read
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
do not explore, re-plan, or run `wait-for-blueprint`. Re-read the shared
|
|
182
|
+
`blueprint.json` if you need placed files. Edit live files for that step
|
|
183
|
+
only, record with `inbase propose-patch` (no patch file), then wait again.
|
|
184
|
+
- Exit `6` (`VISUAL_CODER_ACK blueprint` / `VISUAL_CODER_BLUEPRINT`): the
|
|
185
|
+
shared blueprint changed. Follow the latest files, islands, functions,
|
|
186
|
+
variables, and imports. Do not omit, rename, relocate, or replace them.
|
|
187
|
+
If this would differ from the current plan, ask the user before replacing
|
|
188
|
+
the plan. Then run `wait-for-approval` again.
|
|
187
189
|
- Exit `5` (`VISUAL_CODER_ACK finished` / `VISUAL_CODER_FINISHED`): that was
|
|
188
190
|
the last step. The visualizer already applied the final patch and removed
|
|
189
|
-
stored session diffs
|
|
190
|
-
anything remains, tell the user the feature is done, and
|
|
191
|
-
propose another patch.
|
|
191
|
+
stored session diffs. The shared blueprint remains. Optionally run
|
|
192
|
+
`--clear` if anything remains, tell the user the feature is done, and
|
|
193
|
+
**stop**. Do not propose another patch.
|
|
192
194
|
- Exit `4` (`VISUAL_CODER_ACK replan` / `VISUAL_CODER_REPLAN`): do **not**
|
|
193
195
|
edit project files and do not rewrite an earlier accepted patch. The
|
|
194
196
|
withdrawn proposal is no longer live; disk is baseline + accepted patch
|
|
195
197
|
files. Follow the text between `VISUAL_CODER_INSTRUCTION_START` and
|
|
196
|
-
`VISUAL_CODER_INSTRUCTION_END`, read
|
|
198
|
+
`VISUAL_CODER_INSTRUCTION_END`, read the shared `blueprint.json` when
|
|
197
199
|
it is enabled (files, islands, `addedFunctions`, `addedVariables`,
|
|
198
200
|
`addedImports`). The blueprint stays leading. If the new instruction would
|
|
199
201
|
differ from it, ask the user before replacing the plan. Read
|
|
@@ -206,7 +208,7 @@ npx inbase wait-for-approval --session "<session-id>"
|
|
|
206
208
|
(`VISUAL_CODER_ACK timeout`): make no further project changes.
|
|
207
209
|
|
|
208
210
|
13. After a finished handshake, the explorer already removed stored session
|
|
209
|
-
diffs
|
|
211
|
+
diffs. The shared blueprint remains. Optionally run:
|
|
210
212
|
|
|
211
213
|
```bash
|
|
212
214
|
npx inbase propose-patch --session "<session-id>" --clear
|
|
@@ -220,9 +222,9 @@ npx inbase propose-patch --session "<session-id>" --clear
|
|
|
220
222
|
- Invent a session id for `/inbase`; run `npx inbase attach` with no `--session`
|
|
221
223
|
- Skip `inbase wait-for-blueprint`; it returns immediately and provides the optional blueprint and instruction
|
|
222
224
|
- Treat the chat request, viewpoint, or your own plan as overriding an enabled blueprint
|
|
223
|
-
- Skip, rename, relocate, or replace
|
|
225
|
+
- Skip, rename, relocate, or replace the shared `blueprint.json` files, islands, functions, variables, or imports when `enabled` is true
|
|
224
226
|
- Silently differ from the blueprint; ask the user first
|
|
225
|
-
- Read global `user-context.json` for placed files; those live on the
|
|
227
|
+
- Read global `user-context.json` for placed files; those live on the shared blueprint
|
|
226
228
|
- Follow the user's look when `followLook` is false
|
|
227
229
|
- Edit project files before `VISUAL_CODER_EXECUTE`
|
|
228
230
|
- Keep editing after `inbase propose-patch` until the next `VISUAL_CODER_EXECUTE`
|