@jkwd/inbase 0.1.21 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +13 -7
  2. package/apps/explorer/package.json +1 -0
  3. package/apps/explorer/scripts/explain-store.d.ts +135 -0
  4. package/apps/explorer/scripts/explain-store.mjs +666 -0
  5. package/apps/explorer/scripts/patch-lib.mjs +4 -0
  6. package/apps/explorer/scripts/scan-target.mjs +22 -5
  7. package/apps/explorer/scripts/session-store.d.ts +86 -11
  8. package/apps/explorer/scripts/session-store.mjs +371 -58
  9. package/apps/explorer/scripts/target-config.d.ts +38 -3
  10. package/apps/explorer/scripts/target-config.mjs +147 -3
  11. package/apps/explorer/src/App.tsx +1073 -158
  12. package/apps/explorer/src/agentIntent.ts +61 -7
  13. package/apps/explorer/src/codebase.ts +1 -1
  14. package/apps/explorer/src/devTargets.ts +66 -0
  15. package/apps/explorer/src/explain.ts +312 -0
  16. package/apps/explorer/src/index.css +874 -222
  17. package/apps/explorer/src/layout.ts +55 -0
  18. package/apps/explorer/src/scene/DistantFileBlocks.tsx +4 -2
  19. package/apps/explorer/src/scene/FileBlock.tsx +95 -72
  20. package/apps/explorer/src/scene/FolderArea.tsx +55 -32
  21. package/apps/explorer/src/scene/MapView.tsx +506 -33
  22. package/apps/explorer/src/scene/RelationLines.tsx +7 -0
  23. package/apps/explorer/src/scene/World.tsx +146 -32
  24. package/apps/explorer/src/speech.ts +228 -0
  25. package/apps/explorer/src/theme.ts +29 -0
  26. package/apps/explorer/src/types.ts +98 -8
  27. package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +1 -1
  28. package/apps/explorer/src/ui/ExplainAskCard.tsx +142 -0
  29. package/apps/explorer/src/ui/ExplainHud.tsx +524 -0
  30. package/apps/explorer/src/ui/ExplainInfoPanel.tsx +135 -0
  31. package/apps/explorer/src/ui/ExplainPointer.tsx +73 -0
  32. package/apps/explorer/src/ui/EyeIcon.tsx +38 -1
  33. package/apps/explorer/src/ui/HUD.tsx +1067 -795
  34. package/apps/explorer/src/ui/NameInput.tsx +114 -5
  35. package/apps/explorer/src/userContext.ts +0 -11
  36. package/apps/explorer/src/userCreated.ts +54 -1
  37. package/apps/explorer/vite.config.ts +173 -26
  38. package/bin/inbase.mjs +11 -2
  39. package/bin/project.mjs +1 -1
  40. package/bin/session.mjs +287 -38
  41. package/package.json +4 -1
  42. package/skill/commands/amber.md +23 -0
  43. package/skill/commands/blue.md +13 -0
  44. package/skill/commands/coral.md +23 -0
  45. package/skill/commands/explain.md +77 -0
  46. package/skill/commands/green.md +23 -0
  47. package/skill/commands/inbase.md +7 -5
  48. package/skill/commands/lime.md +23 -0
  49. package/skill/commands/orange.md +23 -0
  50. package/skill/commands/purple.md +23 -0
  51. package/skill/commands/red.md +23 -0
  52. package/skill/commands/skipinbase.md +1 -1
  53. package/skill/commands/violet.md +23 -0
  54. package/skill/commands/yellow.md +23 -0
  55. package/skill/inbase/SKILL.md +124 -76
  56. package/apps/explorer/src/scene/BlockPlacer.tsx +0 -78
  57. package/apps/explorer/src/scene/IslandPlacer.tsx +0 -31
  58. package/apps/explorer/src/scene/SelectionThumbnail.tsx +0 -1069
package/bin/session.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { pathToFileURL } from 'node:url'
4
- import { explorerRoot, takeFlagValue, takeFlagValues } from './project.mjs'
4
+ import { explorerRoot, instanceFile, readInstanceFile, takeFlagValue, takeFlagValues } from './project.mjs'
5
5
 
6
6
  async function loadExplorer() {
7
7
  const storePath = pathToFileURL(
@@ -104,7 +104,19 @@ function emitStopped(store, dataDir, sessionId) {
104
104
  process.exit(2)
105
105
  }
106
106
 
107
- function emitApprovalHandshake(store, dataDir, sessionId, manifest, initialDiff) {
107
+ function emitTargetExplain(store, explain, dataDir, sessionId) {
108
+ const consumed = explain.consumeExplainStart(dataDir)
109
+ if (!consumed) return false
110
+ const label = explain.explainTargetLabel(consumed)
111
+ signalAck(store, dataDir, sessionId, 'explain', label)
112
+ const quoted = JSON.stringify(consumed.question)
113
+ console.log(
114
+ `VISUAL_CODER_EXPLAIN The user clicked Explain on the ${label}. Do not edit project files. Do not accept or invoke the next step. Do not start the map walk overlay. The visualizer shows a single-explanation card. Run: npx inbase explain start --question ${quoted} Then inspect that ${consumed.kind} and where it fits in the codebase, and report one explanation with npx inbase explain report --question ${quoted} --step "..." --body "...". Use a single --step. After reporting, run npx inbase explain wait. If explain wait returns VISUAL_CODER_EXPLAIN for another file or folder, replace the explanation with one new step. When explain wait returns stopped or timeout, run wait-for-approval again.`,
115
+ )
116
+ process.exit(7)
117
+ }
118
+
119
+ function emitApprovalHandshake(store, explain, dataDir, sessionId, manifest, initialDiff, pendingStart) {
108
120
  const current = initialDiff
109
121
  ? manifest?.diffs.find((entry) => entry.id === initialDiff.id)
110
122
  : null
@@ -119,6 +131,36 @@ function emitApprovalHandshake(store, dataDir, sessionId, manifest, initialDiff)
119
131
  )
120
132
  process.exit(5)
121
133
  }
134
+ if (pendingStart) {
135
+ emitTargetExplain(store, explain, dataDir, sessionId)
136
+ }
137
+ if (manifest.pendingExplain) {
138
+ const consumed = store.consumeExplainRequest(dataDir, sessionId) ?? manifest
139
+ const reviewing = consumed.phase === 'review'
140
+ const active = reviewing ? consumed.diffs?.at(-1) : null
141
+ const step = active?.step ?? consumed.currentStep
142
+ const title =
143
+ consumed.steps?.find((item) => item.index === step)?.title ||
144
+ active?.title ||
145
+ `step ${step}`
146
+ signalAck(
147
+ store,
148
+ dataDir,
149
+ sessionId,
150
+ 'explain',
151
+ `the proposal for ${title}`,
152
+ )
153
+ const target = reviewing
154
+ ? 'inspect the live files this proposal changed'
155
+ : 'inspect the live files and folders this plan step will use'
156
+ const waiting = reviewing
157
+ ? 'The proposal is still waiting for Accept proposal.'
158
+ : 'The plan is still waiting for Create proposal.'
159
+ console.log(
160
+ `VISUAL_CODER_EXPLAIN The user clicked Explain proposal for step ${step}: ${title}. Do not edit project files. Do not accept or invoke the next step. Start explain mode and walk this proposal on the map. Run: npx inbase explain start --question "Explain the current proposal: ${title}" Then ${target} and report steps with npx inbase explain report --question "Explain the current proposal: ${title}" --step "..." --body "..." --files path [--folders path] [--select path] [--zoom path] [--relations from:to] [--info] [--highlight function:name] [--point function:name]. After reporting, run npx inbase explain wait. If the user asks about a step, report sub-steps with --parent and wait again. When explain wait returns stopped or timeout, run wait-for-approval again. ${waiting}`,
161
+ )
162
+ process.exit(7)
163
+ }
122
164
  if (manifest.phase === 'working') {
123
165
  const next = manifest.steps.find((step) => step.index === manifest.currentStep)
124
166
  const title = next?.title
@@ -150,7 +192,7 @@ function emitApprovalHandshake(store, dataDir, sessionId, manifest, initialDiff)
150
192
  console.log(
151
193
  continuing
152
194
  ? `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.`
153
- : `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.`,
195
+ : `VISUAL_CODER_EXECUTE Step ${manifest.currentStep} is invoked${title ? `: ${title}` : ''}. Re-read the global blueprint.json and this session's local blueprint 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.`,
154
196
  )
155
197
  }
156
198
  process.exit(0)
@@ -190,26 +232,84 @@ export async function startSession(args) {
190
232
 
191
233
  export async function attachSession(args) {
192
234
  const { store, config } = await loadExplorer()
235
+ if (!readInstanceFile(instanceFile(config.dataDir))) {
236
+ console.error(store.NOT_RUNNING_MESSAGE)
237
+ process.exit(1)
238
+ }
193
239
  const sessionId = takeFlagValue(args, '--session')
194
- const manifest = store.attachSession(config.dataDir, sessionId)
240
+ const colorQuery = takeFlagValue(args, '--color')
241
+ const manifest = store.attachSession(config.dataDir, sessionId, { color: colorQuery })
242
+ const color = store.resolveSessionColor(manifest.color)
243
+ const colorName = color?.name || null
195
244
  console.log(`VISUAL_CODER_SESSION ${manifest.sessionId}`)
196
- printAck('attached', manifest.name || manifest.sessionId)
245
+ if (colorName) console.log(`VISUAL_CODER_COLOR ${colorName}`)
246
+ printAck('attached', colorName || manifest.name || manifest.sessionId)
197
247
  console.log(
198
- `VISUAL_CODER_ATTACHED Attached to the next waiting visualizer session ${manifest.name || manifest.sessionId} (${manifest.phase}). Use --session ${manifest.sessionId} for every later command. Run inbase wait-for-blueprint --session ${manifest.sessionId} to read the optional blueprint, instruction, and attached files; it does not wait.`,
248
+ colorName
249
+ ? `VISUAL_CODER_ATTACHED Attached to the ${colorName} session (${manifest.phase}). Tell the user you connected to the ${colorName} chat. Use --session ${manifest.sessionId} for every later command. Run inbase wait-for-blueprint --session ${manifest.sessionId} to read the optional blueprint, instruction, and attached files; it does not wait. Then run wait-for-approval so map Explain clicks are heard.`
250
+ : `VISUAL_CODER_ATTACHED Attached to the next waiting visualizer session ${manifest.name || manifest.sessionId} (${manifest.phase}). Use --session ${manifest.sessionId} for every later command. Run inbase wait-for-blueprint --session ${manifest.sessionId} to read the optional blueprint, instruction, and attached files; it does not wait. Then run wait-for-approval so map Explain clicks are heard.`,
199
251
  )
200
252
  }
201
253
 
202
- function printBlueprintDump(blueprint) {
254
+ function printBlueprintDump(blueprint, options = {}) {
203
255
  const blocks = blueprint.userCreatedBlocks ?? []
204
256
  const islands = blueprint.userCreatedIslands ?? []
205
- console.log(
206
- blueprint.enabled
207
- ? `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.`
208
- : '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.',
209
- )
210
- console.log('VISUAL_CODER_BLUEPRINT_START')
257
+ const local = options.local === true
258
+ const colorName = options.colorName || 'session'
259
+ const readyTag = local
260
+ ? 'VISUAL_CODER_LOCAL_BLUEPRINT_READY'
261
+ : 'VISUAL_CODER_BLUEPRINT_READY'
262
+ const startTag = local
263
+ ? 'VISUAL_CODER_LOCAL_BLUEPRINT_START'
264
+ : 'VISUAL_CODER_BLUEPRINT_START'
265
+ const endTag = local
266
+ ? 'VISUAL_CODER_LOCAL_BLUEPRINT_END'
267
+ : 'VISUAL_CODER_BLUEPRINT_END'
268
+ if (local) {
269
+ console.log(
270
+ blueprint.enabled
271
+ ? `${readyTag} The ${colorName} session blueprint has ${blocks.length} file(s) and ${islands.length} island(s). This local blueprint is only for this ${colorName} chat. It is leading together with the global blueprint: create those paths and honor addedFunctions, addedVariables, addedImports, and notes even if they are not on disk. Do not omit, rename, relocate, or replace them. Extra new files not in either blueprint are a deviation. If you would differ from this local blueprint, ask the user first.`
272
+ : `${readyTag} The ${colorName} session blueprint is empty. Only this ${colorName} chat can see a local blueprint if the user places one later.`,
273
+ )
274
+ } else {
275
+ console.log(
276
+ blueprint.enabled
277
+ ? `${readyTag} The global blueprint has ${blocks.length} file(s) and ${islands.length} island(s). The global blueprint is shared with every session and 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 that are not in the global or this session's local 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 global blueprint.json when it is printed again.`
278
+ : `${readyTag} The global blueprint is empty. The user can still place files and islands on the global or this session's color; re-read the global blueprint.json when it is printed again. Continue without user-placed files until that file has content.`,
279
+ )
280
+ }
281
+ console.log(startTag)
211
282
  console.log(JSON.stringify(blueprint, null, 2))
212
- console.log('VISUAL_CODER_BLUEPRINT_END')
283
+ console.log(endTag)
284
+ }
285
+
286
+ function printSessionBlueprints(store, dataDir, sessionId) {
287
+ const global = store.readBlueprint(dataDir)
288
+ const local = store.readLocalBlueprint(dataDir, sessionId)
289
+ const colorName =
290
+ store.resolveSessionColor(store.readManifest(dataDir, sessionId)?.color)?.name ||
291
+ 'session'
292
+ const blocks = (global.userCreatedBlocks ?? []).length
293
+ const islands = (global.userCreatedIslands ?? []).length
294
+ const localBlocks = (local.userCreatedBlocks ?? []).length
295
+ const localIslands = (local.userCreatedIslands ?? []).length
296
+ const detail = [
297
+ global.enabled ? `global ${blocks} file(s), ${islands} island(s)` : null,
298
+ local.enabled
299
+ ? `${colorName} ${localBlocks} file(s), ${localIslands} island(s)`
300
+ : null,
301
+ ]
302
+ .filter(Boolean)
303
+ .join('; ')
304
+ printBlueprintDump(global)
305
+ printBlueprintDump(local, { local: true, colorName })
306
+ store.markBlueprintSeen(dataDir, sessionId, global.revision, local.revision)
307
+ return {
308
+ global,
309
+ local,
310
+ colorName,
311
+ detail: detail || 'none',
312
+ }
213
313
  }
214
314
 
215
315
  export async function waitForBlueprint(args) {
@@ -233,20 +333,14 @@ export async function waitForBlueprint(args) {
233
333
  emitStopped(store, config.dataDir, sessionId)
234
334
  }
235
335
 
236
- const blueprint = store.readBlueprint(config.dataDir)
237
- const blocks = blueprint.userCreatedBlocks ?? []
238
- const islands = blueprint.userCreatedIslands ?? []
336
+ const dumped = printSessionBlueprints(store, config.dataDir, sessionId)
239
337
  signalAck(
240
338
  store,
241
339
  config.dataDir,
242
340
  sessionId,
243
341
  'blueprint',
244
- blueprint.enabled
245
- ? `${blocks.length} file(s), ${islands.length} island(s)`
246
- : 'none',
342
+ dumped.detail,
247
343
  )
248
- printBlueprintDump(blueprint)
249
- store.markBlueprintSeen(config.dataDir, sessionId, blueprint.revision)
250
344
  const instruction =
251
345
  typeof manifest.initialInstruction === 'string'
252
346
  ? manifest.initialInstruction.trim()
@@ -273,6 +367,10 @@ export async function waitForBlueprint(args) {
273
367
  console.log('VISUAL_CODER_CONTEXT_FILE_END')
274
368
  }
275
369
  }
370
+ const explain = await loadExplainStore()
371
+ if (explain.readExplain(config.dataDir).pendingStart) {
372
+ emitTargetExplain(store, explain, config.dataDir, sessionId)
373
+ }
276
374
  process.exit(0)
277
375
  }
278
376
 
@@ -323,35 +421,57 @@ export async function waitForApproval(args) {
323
421
  }
324
422
  store.autoAdvance(config.dataDir, sessionId, config.targetRoot)
325
423
 
424
+ const explain = await loadExplainStore()
326
425
  const { manifest: manifestPath } = store.sessionPaths(config.dataDir, sessionId)
426
+ const explainFile = path.join(config.dataDir, explain.EXPLAIN_FILE)
327
427
  const gate = createManifestGate(manifestPath)
428
+ const explainGate = createManifestGate(explainFile)
328
429
  let lastWaiting = null
329
430
  try {
330
431
  while (Date.now() - started < timeoutMs) {
331
432
  store.touchSessionConnection(config.dataDir, sessionId)
332
433
  store.autoAdvance(config.dataDir, sessionId, config.targetRoot)
333
434
  const manifest = store.readManifest(config.dataDir, sessionId)
334
- emitApprovalHandshake(store, config.dataDir, sessionId, manifest, initialDiff)
435
+ const pendingStart = explain.readExplain(config.dataDir).pendingStart
436
+ emitApprovalHandshake(
437
+ store,
438
+ explain,
439
+ config.dataDir,
440
+ sessionId,
441
+ manifest,
442
+ initialDiff,
443
+ pendingStart,
444
+ )
335
445
  if (!manifest) continue
336
- const blueprint = store.readBlueprint(config.dataDir)
337
- const seen = manifest.blueprintRevision ?? 0
338
- if (blueprint.revision > seen) {
339
- const blocks = blueprint.userCreatedBlocks ?? []
340
- const islands = blueprint.userCreatedIslands ?? []
446
+ const global = store.readBlueprint(config.dataDir)
447
+ const local = store.readLocalBlueprint(config.dataDir, sessionId)
448
+ const seenGlobal = manifest.blueprintRevision ?? 0
449
+ const seenLocal = manifest.localBlueprintRevision ?? 0
450
+ const globalChanged = global.revision > seenGlobal
451
+ const localChanged = local.revision > seenLocal
452
+ if (globalChanged || localChanged) {
453
+ const dumped = printSessionBlueprints(store, config.dataDir, sessionId)
454
+ const colorName = dumped.colorName
341
455
  signalAck(
342
456
  store,
343
457
  config.dataDir,
344
458
  sessionId,
345
459
  'blueprint',
346
- blueprint.enabled
347
- ? `${blocks.length} file(s), ${islands.length} island(s)`
348
- : 'none',
349
- )
350
- console.log(
351
- '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.',
460
+ dumped.detail,
352
461
  )
353
- printBlueprintDump(blueprint)
354
- store.markBlueprintSeen(config.dataDir, sessionId, blueprint.revision)
462
+ if (globalChanged && localChanged) {
463
+ console.log(
464
+ `VISUAL_CODER_BLUEPRINT The global blueprint and the ${colorName} session blueprint changed. Follow the latest files, islands, functions, variables, imports, and notes from both. Do not omit, rename, relocate, or replace them. The ${colorName} blueprint is only for this chat. If this would differ from the current plan, ask the user before replacing the plan. Then run wait-for-approval again.`,
465
+ )
466
+ } else if (localChanged) {
467
+ console.log(
468
+ `VISUAL_CODER_BLUEPRINT The ${colorName} session blueprint changed. This local blueprint is only for this chat. Follow its latest files, islands, functions, variables, imports, and notes together with the global blueprint. 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.`,
469
+ )
470
+ } else {
471
+ console.log(
472
+ 'VISUAL_CODER_BLUEPRINT The global 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.',
473
+ )
474
+ }
355
475
  process.exit(6)
356
476
  }
357
477
  const waiting = waitingMessage(sessionId, manifest)
@@ -359,13 +479,15 @@ export async function waitForApproval(args) {
359
479
  console.log(waiting)
360
480
  lastWaiting = waiting
361
481
  }
362
- await gate.wait(50)
482
+ await Promise.race([gate.wait(50), explainGate.wait(50)])
363
483
  }
364
484
  } finally {
365
485
  gate.close()
486
+ explainGate.close()
366
487
  }
367
488
 
368
489
  signalAck(store, config.dataDir, sessionId, 'timeout', 'no visualizer signal')
490
+ store.stopSession(config.dataDir, sessionId, config.targetRoot)
369
491
  console.error('Timed out waiting for visualizer review. Do not modify files.')
370
492
  process.exit(3)
371
493
  }
@@ -383,7 +505,7 @@ export async function proposePatch(args) {
383
505
  if (!sessionId) usage('propose-patch', '--session <cursor-chat-id> --clear')
384
506
  store.stopSession(config.dataDir, sessionId, config.targetRoot)
385
507
  console.log(
386
- `Cleared session ${sessionId}; stored diffs were removed. The shared blueprint remains.`,
508
+ `Cleared session ${sessionId}; stored diffs were removed. The global blueprint remains.`,
387
509
  )
388
510
  process.exit(0)
389
511
  }
@@ -429,3 +551,130 @@ export async function proposePatch(args) {
429
551
  : `VISUAL_CODER_STEP_READY Recorded live edits as patch ${entry.id} for session ${sessionId}, step ${entry.step}/${manifest.steps.length}: ${parsed.files.length} changed, ${parsed.creates.length} added. Immediately run wait-for-approval. Do not explore or plan. Accept proposal invokes the next step; when EXECUTE returns, implement that step at once.`,
430
552
  )
431
553
  }
554
+
555
+ async function loadExplainStore() {
556
+ const explainPath = pathToFileURL(
557
+ path.join(explorerRoot, 'scripts/explain-store.mjs'),
558
+ ).href
559
+ return import(explainPath)
560
+ }
561
+
562
+ export async function runExplain(args) {
563
+ const { store, config } = await loadExplorer()
564
+ if (!readInstanceFile(instanceFile(config.dataDir))) {
565
+ console.error(store.NOT_RUNNING_MESSAGE)
566
+ process.exit(1)
567
+ }
568
+ const explain = await loadExplainStore()
569
+ const parsed = explain.parseExplainCli(args)
570
+ if (parsed.action === 'stop') {
571
+ explain.stopExplain(config.dataDir)
572
+ console.log('VISUAL_CODER_EXPLAIN_STOPPED Explain mode is off.')
573
+ return
574
+ }
575
+ if (parsed.action === 'wait') {
576
+ await waitForExplain(explain, config.dataDir, args)
577
+ return
578
+ }
579
+ if (parsed.action === 'start') {
580
+ if (!parsed.question) {
581
+ usage('explain start', '--question "How does this work?"')
582
+ }
583
+ explain.startExplain(config.dataDir, parsed.question)
584
+ console.log(`VISUAL_CODER_EXPLAIN_STARTED ${parsed.question}`)
585
+ console.log(
586
+ 'The map is in explain mode. Explore the codebase, then run inbase explain report with --step / --body / --files / --folders / --select / --zoom / --relations / --info / --highlight / --point.',
587
+ )
588
+ return
589
+ }
590
+ if (!parsed.steps.length) {
591
+ usage(
592
+ 'explain report',
593
+ '--question "How does this work?" --step "Title" --body "..." --files path [--folders path] [--select path] [--zoom path] [--relations from:to] [--info] [--highlight function:name] [--point function:name] [--parent 7]',
594
+ )
595
+ }
596
+ const next = explain.reportExplain(config.dataDir, {
597
+ question: parsed.question,
598
+ parent: parsed.parent,
599
+ steps: parsed.steps,
600
+ })
601
+ if (parsed.parent) {
602
+ const added = next.steps.filter((step) =>
603
+ explain.isExplainDescendant(step.index, parsed.parent),
604
+ ).length
605
+ console.log(
606
+ `VISUAL_CODER_EXPLAIN_READY Reported ${added} follow-up step(s) under ${parsed.parent} for "${parsed.question || next.question}". Walk ${parsed.parent}.1 … then continue at the next parent step.`,
607
+ )
608
+ } else {
609
+ console.log(
610
+ `VISUAL_CODER_EXPLAIN_READY Reported ${next.steps.length} explanation step(s) for "${next.question}". The user can walk them on the map.`,
611
+ )
612
+ }
613
+ console.log(
614
+ 'Run inbase explain wait for a question about the current step, or until the user exits.',
615
+ )
616
+ }
617
+
618
+ async function waitForExplain(explain, dataDir, args) {
619
+ const timeoutMs = Number(takeFlagValue(args, '--timeout') ?? 600000)
620
+ const started = Date.now()
621
+ const explainFile = path.join(dataDir, explain.EXPLAIN_FILE)
622
+ const gate = createManifestGate(explainFile)
623
+ let lastWaiting = null
624
+ try {
625
+ while (Date.now() - started < timeoutMs) {
626
+ const current = explain.readExplain(dataDir)
627
+ if (!current.active) {
628
+ printAck('stopped', 'explain mode was closed')
629
+ console.log(
630
+ 'VISUAL_CODER_EXPLAIN_STOPPED The user exited explain mode. Do not report more steps.',
631
+ )
632
+ process.exit(2)
633
+ }
634
+ if (current.pendingStart) {
635
+ const request = explain.consumeExplainStart(dataDir)
636
+ if (!request) continue
637
+ const quoted = JSON.stringify(request.question)
638
+ printAck('explain', explain.explainTargetLabel(request))
639
+ console.log(
640
+ `VISUAL_CODER_EXPLAIN The user clicked Explain on the ${explain.explainTargetLabel(request)}. Replace the current explanation. Do not edit project files. Do not report sub-steps. Do not start the map walk overlay. The visualizer shows a single-explanation card. Run: npx inbase explain start --question ${quoted} Then inspect that ${request.kind} and where it fits in the codebase, and report one explanation with npx inbase explain report --question ${quoted} --step "..." --body "...". Use a single --step. Then run npx inbase explain wait again.`,
641
+ )
642
+ process.exit(0)
643
+ }
644
+ if (current.pendingQuestion && !current.answering) {
645
+ const asked = explain.consumeExplainQuestion(dataDir)
646
+ if (!asked) continue
647
+ const insertParent = asked.parent
648
+ const aboutId = asked.from && asked.from !== asked.parent ? asked.from : asked.parent
649
+ const about =
650
+ current.steps.find((step) => step.index === aboutId) ??
651
+ current.steps.find((step) => step.index === insertParent)
652
+ const title = asked.fromTitle || about?.title || `step ${aboutId}`
653
+ printAck('question', `step ${aboutId}`)
654
+ console.log(
655
+ `VISUAL_CODER_EXPLAIN_QUESTION The user asked about step ${aboutId}: ${title}. Do not replace the whole explanation. Report one-level sub-steps under ${insertParent} with --parent "${insertParent}". This replaces any current sub-steps. Do not nest further (no ${insertParent}.1.1).`,
656
+ )
657
+ console.log(`VISUAL_CODER_PARENT ${insertParent}`)
658
+ console.log(`VISUAL_CODER_INSTRUCTION_START\n${asked.question}\nVISUAL_CODER_INSTRUCTION_END`)
659
+ console.log(
660
+ `Run: npx inbase explain report --parent "${insertParent}" --question ${JSON.stringify(asked.question)} --step "..." --body "..." --files path [--folders path] [--select path] [--zoom path] [--relations from:to] [--info] [--highlight function:name] [--point function:name]. Repeat --step for ${insertParent}.1, ${insertParent}.2, … Then run npx inbase explain wait again.`,
661
+ )
662
+ process.exit(0)
663
+ }
664
+ const waiting = 'Waiting for a question on an explanation step...'
665
+ if (waiting !== lastWaiting) {
666
+ console.log(waiting)
667
+ lastWaiting = waiting
668
+ }
669
+ await gate.wait(50)
670
+ }
671
+ } finally {
672
+ gate.close()
673
+ }
674
+
675
+ printAck('timeout', 'no explain question')
676
+ console.error(
677
+ 'Timed out waiting for an explain question. Do not report more steps.',
678
+ )
679
+ process.exit(3)
680
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jkwd/inbase",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
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",
@@ -45,6 +45,8 @@
45
45
  "apps/explorer/src/ui",
46
46
  "apps/explorer/scripts/branch-changes.d.ts",
47
47
  "apps/explorer/scripts/branch-changes.mjs",
48
+ "apps/explorer/scripts/explain-store.d.ts",
49
+ "apps/explorer/scripts/explain-store.mjs",
48
50
  "apps/explorer/scripts/js-source.mjs",
49
51
  "apps/explorer/scripts/open-editor.d.ts",
50
52
  "apps/explorer/scripts/open-editor.mjs",
@@ -76,6 +78,7 @@
76
78
  "@react-three/drei": "^9.117.3",
77
79
  "@react-three/fiber": "^8.17.10",
78
80
  "@vitejs/plugin-react": "^4.3.4",
81
+ "kokoro-js": "^1.2.1",
79
82
  "react": "^18.3.1",
80
83
  "react-dom": "^18.3.1",
81
84
  "three": "^0.170.0",
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Attach this chat to the Amber Inbase session
3
+ ---
4
+
5
+ The user invoked `/amber`. Attach this chat to the **Amber** Inbase session, not the next empty slot.
6
+
7
+ Do **not** ask for a session id. Do **not** run `inbase start-session`.
8
+
9
+ 1. Attach to the Amber session:
10
+
11
+ ```bash
12
+ npx inbase attach --color amber
13
+ ```
14
+
15
+ If attach fails with `VISUAL_CODER_NOT_RUNNING`, `VISUAL_CODER_CHAT_LIMIT`, `VISUAL_CODER_COLOR_BUSY`, or `VISUAL_CODER_COLOR_UNKNOWN`, reply with that message and stop.
16
+
17
+ 2. Read `VISUAL_CODER_SESSION` from the output. That id is the session to use for every later `inbase` command.
18
+
19
+ 3. Continue the Inbase visual edits skill from `wait-for-blueprint` onward with that `--session` id. Attach already started the session. `wait-for-blueprint` only reads the optional blueprint, instruction, and attached files; it does not wait.
20
+
21
+ The user's request is:
22
+
23
+ $ARGUMENTS
@@ -0,0 +1,13 @@
1
+ ---
2
+ description: Blue is the global blueprint, not an Inbase chat
3
+ ---
4
+
5
+ The user invoked `/blue`. Blue is the **global blueprint**, not a chat slot. There is no Blue LLM session.
6
+
7
+ Reply with exactly this, then stop. Do not run `inbase attach`.
8
+
9
+ Blue is the global blueprint, not a chat. Connect with `/coral`, `/amber`, `/lime`, `/orange`, or `/violet` (or `/red`, `/yellow`, `/green`, `/purple`).
10
+
11
+ The user's request was:
12
+
13
+ $ARGUMENTS
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Attach this chat to the Coral Inbase session
3
+ ---
4
+
5
+ The user invoked `/coral`. Attach this chat to the **Coral** Inbase session, not the next empty slot.
6
+
7
+ Do **not** ask for a session id. Do **not** run `inbase start-session`.
8
+
9
+ 1. Attach to the Coral session:
10
+
11
+ ```bash
12
+ npx inbase attach --color coral
13
+ ```
14
+
15
+ If attach fails with `VISUAL_CODER_NOT_RUNNING`, `VISUAL_CODER_CHAT_LIMIT`, `VISUAL_CODER_COLOR_BUSY`, or `VISUAL_CODER_COLOR_UNKNOWN`, reply with that message and stop.
16
+
17
+ 2. Read `VISUAL_CODER_SESSION` from the output. That id is the session to use for every later `inbase` command.
18
+
19
+ 3. Continue the Inbase visual edits skill from `wait-for-blueprint` onward with that `--session` id. Attach already started the session. `wait-for-blueprint` only reads the optional blueprint, instruction, and attached files; it does not wait.
20
+
21
+ The user's request is:
22
+
23
+ $ARGUMENTS
@@ -0,0 +1,77 @@
1
+ ---
2
+ description: Explain a codebase question on the Inbase map
3
+ ---
4
+
5
+ The user invoked `/explain`. Do **not** attach an Inbase coding session. Do **not** edit project files. Do **not** run `inbase start-session`.
6
+
7
+ Explain mode only runs on the map. Start it so the visualizer switches to a map-only overlay, then walk the question step by step on that map.
8
+
9
+ The user's question is:
10
+
11
+ $ARGUMENTS
12
+
13
+ 1. Start explain mode (requires `npx inbase run`):
14
+
15
+ ```bash
16
+ npx inbase explain start --question "$ARGUMENTS"
17
+ ```
18
+
19
+ If that fails with `VISUAL_CODER_NOT_RUNNING`, reply with that message and stop.
20
+
21
+ 2. Reply in this chat first with one short sentence that you opened explain mode on the map.
22
+
23
+ 3. Read the codebase for this question. Use the map's files and folders as the source of truth: file ids are repo-relative paths.
24
+
25
+ 4. Report the explanation as ordered steps. Each step can highlight files and folders, select a block to show import relations, and zoom the map:
26
+
27
+ ```bash
28
+ npx inbase explain report \
29
+ --question "$ARGUMENTS" \
30
+ --step "Short title" \
31
+ --body "What this step is showing." \
32
+ --files path/to/file.ts \
33
+ --folders path/to/folder \
34
+ --select path/to/file.ts \
35
+ --zoom path/to/folder \
36
+ --relations path/to/file.ts:path/to/other.ts \
37
+ --info \
38
+ --highlight function:currentExplainStep \
39
+ --point function:currentExplainStep
40
+ ```
41
+
42
+ Repeat `--step` for every step. Flags after a `--step` apply to that step. Use `--imported-by` when the step should show who imports the selected file. `--files` and `--folders` stay at full opacity; everything else on the map goes to 0.5. `--select` clicks that block so its relations draw. `--zoom` frames that folder or file.
43
+
44
+ `--info` opens the file info panel (functions, vars, classes). Pass `--info path/to/file.ts` to choose the file; otherwise it uses `--select` or the first `--files` path. `--highlight function:name` (or `variable:`, `class:`, or a bare name) lights those symbols in the panel. `--point function:name` draws an arrow from this step to that row. `--point file` points at the file title. `--highlight` / `--point` imply `--info`.
45
+
46
+ Keep steps small. Prefer real paths from the repo.
47
+
48
+ 5. Wait for a follow-up on a step, or until the user exits:
49
+
50
+ ```bash
51
+ npx inbase explain wait
52
+ ```
53
+
54
+ 6. Read the wait output. Reply in this chat with the `VISUAL_CODER_ACK` line first, then:
55
+
56
+ - Exit `0` (`VISUAL_CODER_ACK explain` / `VISUAL_CODER_EXPLAIN`): the user clicked `?` on a file, folder, or symbol. Replace the current explanation. Do not use `--parent`. Do not walk the map. Read the path from the ACK line, then start and report **one** explanation for the modal:
57
+
58
+ ```bash
59
+ npx inbase explain start --question "Explain the function of the file path/to/file.ts and where it fits in the codebase."
60
+ ```
61
+
62
+ Use the printed `--question` text. Then inspect that file or folder and report a single `--step` / `--body` with `npx inbase explain report`. Then run `npx inbase explain wait` again.
63
+
64
+ - Exit `0` (`VISUAL_CODER_ACK question` / `VISUAL_CODER_EXPLAIN_QUESTION`): the user asked about the printed parent step. Do not replace the whole explanation. Read the question between `VISUAL_CODER_INSTRUCTION_START` and `VISUAL_CODER_INSTRUCTION_END`. Report closer sub-steps with `--parent` (they become `7.1`, `7.2`, … and Next walks those until the original next step):
65
+
66
+ ```bash
67
+ npx inbase explain report \
68
+ --parent "7" \
69
+ --question "the user's follow-up" \
70
+ --step "Closer look" \
71
+ --body "What this sub-step is showing." \
72
+ --files path/to/file.ts
73
+ ```
74
+
75
+ Then run `npx inbase explain wait` again. Asking another question replaces the current sub-steps. Sub-steps stay one level (`7.1`, `7.2`) — never `7.1.1`. If they asked from `7.1`, still report with `--parent "7"`.
76
+
77
+ - Exit `2` (`VISUAL_CODER_ACK stopped` / `VISUAL_CODER_EXPLAIN_STOPPED`) or `3` (`VISUAL_CODER_ACK timeout`): stop. Do not attach a coding session.
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Attach this chat to the Lime (green) Inbase session
3
+ ---
4
+
5
+ The user invoked `/green`. Attach this chat to the **Lime** Inbase session, not the next empty slot.
6
+
7
+ Do **not** ask for a session id. Do **not** run `inbase start-session`.
8
+
9
+ 1. Attach to the Lime session:
10
+
11
+ ```bash
12
+ npx inbase attach --color green
13
+ ```
14
+
15
+ If attach fails with `VISUAL_CODER_NOT_RUNNING`, `VISUAL_CODER_CHAT_LIMIT`, `VISUAL_CODER_COLOR_BUSY`, or `VISUAL_CODER_COLOR_UNKNOWN`, reply with that message and stop.
16
+
17
+ 2. Read `VISUAL_CODER_SESSION` from the output. That id is the session to use for every later `inbase` command.
18
+
19
+ 3. Continue the Inbase visual edits skill from `wait-for-blueprint` onward with that `--session` id. Attach already started the session. `wait-for-blueprint` only reads the optional blueprint, instruction, and attached files; it does not wait.
20
+
21
+ The user's request is:
22
+
23
+ $ARGUMENTS
@@ -1,17 +1,19 @@
1
1
  ---
2
- description: Attach this chat to the next waiting Inbase visualizer session
2
+ description: Attach this chat to the next empty Inbase session
3
3
  ---
4
4
 
5
- The user invoked `/inbase`. This is how a chat joins the visualizer session that **Setup LLM session** already created.
5
+ The user invoked `/inbase`. Attach this chat to the next unconnected Inbase session. You do not need this command — a regular Cursor chat does the same thing.
6
6
 
7
- Do **not** ask for a session id. Do **not** run `inbase start-session`. Do **not** print the "direct chat interaction not allowed" message.
7
+ Do **not** ask for a session id. Do **not** run `inbase start-session`.
8
8
 
9
- 1. Attach to the next waiting visualizer session (oldest first; skip sessions that already have an LLM):
9
+ 1. Attach to the next unconnected visualizer session (oldest first; skip sessions that already have an LLM):
10
10
 
11
11
  ```bash
12
12
  npx inbase attach
13
13
  ```
14
14
 
15
+ If attach fails with `VISUAL_CODER_NOT_RUNNING`, `VISUAL_CODER_CHAT_LIMIT`, `VISUAL_CODER_COLOR_BUSY`, or `VISUAL_CODER_COLOR_UNKNOWN`, reply with that message and stop.
16
+
15
17
  2. Read `VISUAL_CODER_SESSION` from the output. That id is the session to use for every later `inbase` command.
16
18
 
17
- 3. Continue the Inbase visual edits skill from `wait-for-blueprint` onward with that `--session` id. `/inbase` already started the session. `wait-for-blueprint` only reads the optional blueprint, instruction, and attached files; it does not wait.
19
+ 3. Continue the Inbase visual edits skill from `wait-for-blueprint` onward with that `--session` id. Attach already started the session. `wait-for-blueprint` only reads the optional blueprint, instruction, and attached files; it does not wait.