@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.
@@ -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 (action !== 'setup_session' && !body.sessionId) {
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 = sessionIntent(dataDir, manifest.sessionId, knownFileIds())
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 = sessionIntent(dataDir, body.sessionId, knownFileIds())
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 this session's blueprint.json before implementing; the user can place files and islands on any step. 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.`,
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 session 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}`,
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, sessionId)
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
- console.log(
220
- blueprint.enabled
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 and blueprint drafts were removed.`,
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.13",
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",
@@ -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 (newest first; skip sessions that already have an LLM):
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
@@ -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 (newest first).
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 choice is already on the session. They can keep
98
- placing on later steps. This chat's blueprint is stored only for this
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
- They belong to this chat only.
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 choose files from the request itself. Viewpoint never overrides the
128
- blueprint: still follow this session's blueprint when `enabled` is true.
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 this session's `blueprint.json`; the
154
- user can place files and islands on any step.
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 this
184
- session's `blueprint.json` only if you need placed files. Edit live files
185
- for that step only, record with `inbase propose-patch` (no patch file),
186
- then wait again.
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 and blueprint drafts. Optionally run `--clear` if
190
- anything remains, tell the user the feature is done, and **stop**. Do not
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 this session's `blueprint.json` when
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 and blueprint drafts. Optionally run:
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 this session's `blueprint.json` files, islands, functions, variables, or imports when `enabled` is true
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 session blueprint
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`