@jkwd/inbase 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,12 +11,15 @@ import { editorFileUri, openInEditor } from './scripts/open-editor.mjs'
11
11
  import {
12
12
  answerBlueprint,
13
13
  continueDiff,
14
+ discardInactiveDiffSessions,
14
15
  inspectTargetFile,
15
16
  invokeStep,
17
+ listSessionIntents,
16
18
  readActiveSession,
17
19
  requestReplan,
18
20
  sendBlueprint,
19
21
  sessionIntent,
22
+ setStepByStep,
20
23
  stopSession,
21
24
  updateBlueprint,
22
25
  } from './scripts/session-store.mjs'
@@ -45,6 +48,17 @@ function readBody(req: IncomingMessage) {
45
48
  })
46
49
  }
47
50
 
51
+ function rescanTarget(when: string) {
52
+ const scan = spawnSync(process.execPath, [scanScript], {
53
+ cwd: here,
54
+ encoding: 'utf8',
55
+ env: scanEnv,
56
+ })
57
+ if (scan.status !== 0) {
58
+ console.error(scan.stderr || scan.stdout || `scan failed ${when}`)
59
+ }
60
+ }
61
+
48
62
  function knownFileIds() {
49
63
  try {
50
64
  const graph = JSON.parse(fs.readFileSync(codebaseFile, 'utf8')) as {
@@ -68,6 +82,8 @@ function jsonFilePlugin(): Plugin {
68
82
  return {
69
83
  name: 'visual-coder-json-files',
70
84
  configureServer(server) {
85
+ discardInactiveDiffSessions(dataDir, targetRoot)
86
+ rescanTarget('after discarding inactive sessions')
71
87
  server.middlewares.use('/api/user-context', (req, res, next) => {
72
88
  if (req.method === 'GET') {
73
89
  sendJson(res, 200, readUserContext())
@@ -91,12 +107,22 @@ function jsonFilePlugin(): Plugin {
91
107
  server.middlewares.use('/api/agent-intent', (req, res, next) => {
92
108
  if (req.method === 'GET') {
93
109
  const url = new URL(req.url ?? '/', 'http://visual-coder.local')
94
- const sessionId = url.searchParams.get('sessionId') ?? readActiveSession(dataDir)
110
+ const sessionId = url.searchParams.get('sessionId')
95
111
  const diffId = url.searchParams.get('diffId') ?? undefined
96
- const intent = sessionId
97
- ? sessionIntent(dataDir, sessionId, knownFileIds(), diffId)
98
- : null
99
- sendJson(res, 200, intent ?? { ...emptyIntent })
112
+ if (sessionId) {
113
+ const intent = sessionIntent(
114
+ dataDir,
115
+ sessionId,
116
+ knownFileIds(),
117
+ diffId,
118
+ )
119
+ sendJson(res, 200, intent ?? { ...emptyIntent })
120
+ return
121
+ }
122
+ sendJson(res, 200, {
123
+ focusedSessionId: readActiveSession(dataDir),
124
+ intents: listSessionIntents(dataDir, knownFileIds()),
125
+ })
100
126
  return
101
127
  }
102
128
 
@@ -155,6 +181,7 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
155
181
  diffId?: string
156
182
  instruction?: string
157
183
  step?: number
184
+ stepByStep?: boolean
158
185
  userCreatedBlocks?: unknown[]
159
186
  userCreatedIslands?: unknown[]
160
187
  addedFunctions?: unknown[]
@@ -170,7 +197,8 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
170
197
  action !== 'blueprint_yes' &&
171
198
  action !== 'blueprint_no' &&
172
199
  action !== 'blueprint_send' &&
173
- action !== 'blueprint_update'
200
+ action !== 'blueprint_update' &&
201
+ action !== 'set_step_by_step'
174
202
  ) {
175
203
  sendJson(res, 400, { error: 'invalid workflow action' })
176
204
  return
@@ -193,28 +221,14 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
193
221
  return
194
222
  }
195
223
  invokeStep(dataDir, body.sessionId, body.step as number, targetRoot)
196
- const scan = spawnSync(process.execPath, [scanScript], {
197
- cwd: here,
198
- encoding: 'utf8',
199
- env: scanEnv,
200
- })
201
- if (scan.status !== 0) {
202
- console.error(scan.stderr || scan.stdout || 'scan failed after invoking step')
203
- }
224
+ rescanTarget('after invoking step')
204
225
  } else if (action === 'continue') {
205
226
  if (!body.diffId) {
206
227
  sendJson(res, 400, { error: 'diffId is required for continue' })
207
228
  return
208
229
  }
209
230
  continueDiff(dataDir, targetRoot, body.sessionId, body.diffId)
210
- const scan = spawnSync(process.execPath, [scanScript], {
211
- cwd: here,
212
- encoding: 'utf8',
213
- env: scanEnv,
214
- })
215
- if (scan.status !== 0) {
216
- console.error(scan.stderr || scan.stdout || 'scan failed after applying patch')
217
- }
231
+ rescanTarget('after applying patch')
218
232
  } else if (action === 'instruct') {
219
233
  if (!body.diffId) {
220
234
  sendJson(res, 400, { error: 'diffId is required for instruct' })
@@ -246,8 +260,16 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
246
260
  addedVariables: body.addedVariables,
247
261
  addedImports: body.addedImports,
248
262
  })
263
+ } else if (action === 'set_step_by_step') {
264
+ if (typeof body.stepByStep !== 'boolean') {
265
+ sendJson(res, 400, { error: 'stepByStep is required' })
266
+ return
267
+ }
268
+ setStepByStep(dataDir, body.sessionId, body.stepByStep, targetRoot)
269
+ rescanTarget('after changing step-by-step mode')
249
270
  } else {
250
271
  stopSession(dataDir, body.sessionId, targetRoot)
272
+ rescanTarget('after stopping session')
251
273
  }
252
274
  const next = sessionIntent(dataDir, body.sessionId, knownFileIds())
253
275
  sendJson(res, 200, next ?? { ...emptyIntent })
package/bin/session.mjs CHANGED
@@ -52,6 +52,12 @@ export async function waitForBlueprint(args) {
52
52
 
53
53
  const started = Date.now()
54
54
  const initial = store.readManifest(config.dataDir, sessionId)
55
+ if (store.isWorkflowStopped(config.dataDir, sessionId)) {
56
+ console.error(
57
+ 'VISUAL_CODER_STOPPED The workflow was stopped. Do not modify project files.',
58
+ )
59
+ process.exit(2)
60
+ }
55
61
  if (!initial) {
56
62
  console.error(`No workflow session found for ${sessionId}`)
57
63
  process.exit(1)
@@ -68,6 +74,7 @@ export async function waitForBlueprint(args) {
68
74
  }
69
75
 
70
76
  while (Date.now() - started < timeoutMs) {
77
+ store.touchSessionConnection(config.dataDir, sessionId)
71
78
  const manifest = store.readManifest(config.dataDir, sessionId)
72
79
  if (!manifest || manifest.phase === 'stopped') {
73
80
  console.error(
@@ -81,7 +88,7 @@ export async function waitForBlueprint(args) {
81
88
  const islands = blueprint.userCreatedIslands ?? []
82
89
  console.log(
83
90
  blueprint.enabled
84
- ? `VISUAL_CODER_BLUEPRINT_READY The user sent ${blocks.length} file(s) and ${islands.length} island(s) for this chat. Create those paths in the plan even if they are not on disk.`
91
+ ? `VISUAL_CODER_BLUEPRINT_READY The user sent ${blocks.length} file(s) and ${islands.length} island(s) for this chat. 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.`
85
92
  : 'VISUAL_CODER_BLUEPRINT_READY The user skipped the blueprint. Continue without user-placed files or islands.',
86
93
  )
87
94
  console.log('VISUAL_CODER_BLUEPRINT_START')
@@ -118,7 +125,9 @@ export async function reportPlan(args) {
118
125
  stepTitles: stepsParsed.values,
119
126
  })
120
127
  console.log(
121
- `VISUAL_CODER_PLAN_READY Reported ${manifest.steps.length} plan step(s) for session ${sessionId}. Wait for the user to invoke step ${manifest.currentStep}.`,
128
+ manifest.phase === 'working'
129
+ ? `VISUAL_CODER_PLAN_READY Reported ${manifest.steps.length} plan step(s) for session ${sessionId}. Step by step is off, so step ${manifest.currentStep} is already invoked. Implement it, then wait again.`
130
+ : `VISUAL_CODER_PLAN_READY Reported ${manifest.steps.length} plan step(s) for session ${sessionId}. Wait for the user to invoke step ${manifest.currentStep}.`,
122
131
  )
123
132
  }
124
133
 
@@ -131,19 +140,31 @@ export async function waitForApproval(args) {
131
140
  const started = Date.now()
132
141
  const initial = store.readManifest(config.dataDir, sessionId)
133
142
  const initialDiff = initial?.diffs?.at(-1)
143
+ if (store.isWorkflowStopped(config.dataDir, sessionId)) {
144
+ console.error(
145
+ 'VISUAL_CODER_STOPPED The workflow was stopped. Do not modify project files.',
146
+ )
147
+ process.exit(2)
148
+ }
134
149
  if (!initial) {
135
150
  console.error(`No workflow session found for ${sessionId}`)
136
151
  process.exit(1)
137
152
  }
153
+ store.autoAdvance(config.dataDir, sessionId, config.targetRoot)
154
+ const afterAdvance = store.readManifest(config.dataDir, sessionId) ?? initial
138
155
  console.log(
139
- initial.phase === 'plan_ready'
140
- ? `Waiting for the user to invoke step ${initial.currentStep}...`
141
- : initial.phase === 'review' && initialDiff
142
- ? `Waiting for the user to run the next step after ${initialDiff.id}...`
156
+ afterAdvance.phase === 'plan_ready'
157
+ ? `Waiting for the user to invoke step ${afterAdvance.currentStep}...`
158
+ : afterAdvance.phase === 'review' && afterAdvance.diffs?.at(-1)
159
+ ? afterAdvance.stepByStep === false
160
+ ? `Waiting for the finish step after ${afterAdvance.diffs.at(-1).id}...`
161
+ : `Waiting for the user to run the next step after ${afterAdvance.diffs.at(-1).id}...`
143
162
  : `Waiting for the visual workflow in session ${sessionId}...`,
144
163
  )
145
164
 
146
165
  while (Date.now() - started < timeoutMs) {
166
+ store.touchSessionConnection(config.dataDir, sessionId)
167
+ store.autoAdvance(config.dataDir, sessionId, config.targetRoot)
147
168
  const manifest = store.readManifest(config.dataDir, sessionId)
148
169
  if (!manifest) {
149
170
  console.error(
@@ -173,7 +194,7 @@ export async function waitForApproval(args) {
173
194
  ? `\nVISUAL_CODER_INSTRUCTION_START\n${manifest.pendingInstruction}\nVISUAL_CODER_INSTRUCTION_END`
174
195
  : ''
175
196
  console.log(
176
- `VISUAL_CODER_REPLAN Keep accepted steps before step ${manifest.currentStep}. Replace the plan from step ${manifest.currentStep} onward using the instruction below. Report the revised tail with inbase report-plan, then wait for invocation.${instruction}`,
197
+ `VISUAL_CODER_REPLAN Keep accepted steps before step ${manifest.currentStep}. Replace the plan from step ${manifest.currentStep} onward using the instruction below. 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}`,
177
198
  )
178
199
  process.exit(4)
179
200
  }
@@ -235,7 +256,12 @@ export async function proposePatch(args) {
235
256
  patchText,
236
257
  })
237
258
 
259
+ const last = entry.step >= manifest.steps.length
238
260
  console.log(
239
- `VISUAL_CODER_STEP_READY Published diff ${entry.id} for session ${sessionId}, step ${entry.step}/${manifest.steps.length}: ${parsed.files.length} changed, ${parsed.creates.length} added. Wait for Continue or an alternative instruction.`,
261
+ manifest.phase === 'working'
262
+ ? `VISUAL_CODER_STEP_READY Published diff ${entry.id} for session ${sessionId}, step ${entry.step}/${manifest.steps.length}: ${parsed.files.length} changed, ${parsed.creates.length} added. Step by step is off, so the next step is already invoked. Wait again.`
263
+ : last
264
+ ? `VISUAL_CODER_STEP_READY Published diff ${entry.id} for session ${sessionId}, step ${entry.step}/${manifest.steps.length}: ${parsed.files.length} changed, ${parsed.creates.length} added. Walk the diffs, then Complete to finish.`
265
+ : `VISUAL_CODER_STEP_READY Published diff ${entry.id} for session ${sessionId}, step ${entry.step}/${manifest.steps.length}: ${parsed.files.length} changed, ${parsed.creates.length} added. Wait for Continue or an alternative instruction.`,
240
266
  )
241
267
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jkwd/inbase",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A first-person 3D map of a codebase, with a visual coding workflow for Cursor.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,10 +14,13 @@ Skip it for git, lockfiles, `.inbase`, `.cursor`, or questions with no code
14
14
  changes.
15
15
 
16
16
  The LLM uses a plan-first loop. It reports the complete plan before writing a
17
- patch, waits for the user to invoke the highlighted step, publishes only that
18
- step's diff, then waits for **Run step** on the following step or an alternative
19
- instruction. Always work via patch files. Do not Write, StrReplace, or Delete
20
- project files.
17
+ patch. When **Step by step** is on, it waits for the user to invoke the
18
+ highlighted step, publishes only that step's diff, then waits for **Run step**
19
+ on the following step or an alternative instruction. When **Step by step** is
20
+ off, `inbase wait-for-approval` returns `VISUAL_CODER_EXECUTE` for every
21
+ remaining step without the user clicking Run step; after the last patch, wait
22
+ for the user to **Complete**. They can still walk Previous/Next over the diffs.
23
+ Always work via patch files. Do not Write, StrReplace, or Delete project files.
21
24
 
22
25
  Every Cursor chat has an explicit session ID. Pass that same ID to every
23
26
  command. The visualizer stores immutable diffs under `.inbase/diff-sessions/<session-id>/diffs/`.
@@ -48,12 +51,17 @@ npx inbase wait-for-blueprint --session "<current-cursor-chat-id>"
48
51
  3. Read the handshake output between `VISUAL_CODER_BLUEPRINT_START` and
49
52
  `VISUAL_CODER_BLUEPRINT_END`, or read
50
53
  `.inbase/diff-sessions/<session-id>/blueprint.json`.
51
- If `enabled` is true, always create `userCreatedBlocks` and
52
- `userCreatedIslands` at the given path/folder. Those files and folders do
53
- not exist on disk yet. They belong to this chat only.
54
- Also honor `addedFunctions`, `addedVariables`, and `addedImports`: add those
55
- symbols and imports to the named files. They are blueprint intentions, not
56
- files that already exist on disk.
54
+ If `enabled` is true, **the blueprint is leading**. Treat
55
+ `userCreatedBlocks`, `userCreatedIslands`, `addedFunctions`,
56
+ `addedVariables`, and `addedImports` as the source of truth for this chat.
57
+ Create those paths and add those symbols even if they are not on disk.
58
+ They belong to this chat only.
59
+ Do not omit, rename, relocate, or replace a blueprint file, island, symbol,
60
+ or import. Extra edits to existing files are allowed when needed to finish
61
+ the feature. Extra new files that are not in the blueprint are a deviation.
62
+ If the user request, viewpoint, a later instruction, or your own plan would
63
+ differ from the blueprint, **stop and ask the user in chat** before
64
+ reporting the plan. Do not silently deviate.
57
65
  4. Read `.inbase/user-context.json` for viewpoint only.
58
66
  5. Use the user's viewpoint only when `followLook` is true:
59
67
  - `island` is where they are standing
@@ -62,8 +70,8 @@ npx inbase wait-for-blueprint --session "<current-cursor-chat-id>"
62
70
  - `filesOnIsland` is the rest of that folder
63
71
  Prefer those files while `followLook` is true, unless the request clearly
64
72
  needs something else. If `followLook` is false or missing, ignore viewpoint
65
- and choose files from the request itself. Still include this session's
66
- blueprint files and islands when `enabled` is true.
73
+ and choose files from the request itself. Viewpoint never overrides the
74
+ blueprint: still follow this session's blueprint when `enabled` is true.
67
75
  6. List **all** steps needed to finish the feature. Keep steps small enough that
68
76
  one patch is one landscape change (usually one new file, or a few related
69
77
  edits).
@@ -83,7 +91,8 @@ npx inbase report-plan \
83
91
  npx inbase wait-for-approval --session "<current-cursor-chat-id>"
84
92
  ```
85
93
 
86
- Do not write a patch until this prints `VISUAL_CODER_EXECUTE`.
94
+ Do not write a patch until this prints `VISUAL_CODER_EXECUTE`. If Step by
95
+ step is off, this returns immediately for each remaining step.
87
96
  9. Implement only the invoked step as a unified diff. Paths are relative to the
88
97
  project root (same ids as `codebase.json`):
89
98
 
@@ -116,8 +125,9 @@ npx inbase propose-patch \
116
125
  stored in the session folder.
117
126
 
118
127
  10. **Stop.** Do not apply the patch and do not edit project files directly.
119
- 11. Wait until the user clicks **Run step** on the next step, sends an alternative instruction,
120
- or stops the workflow:
128
+ 11. Wait until the user clicks **Run step** on the next step (or, when Step by
129
+ step is off, until the next step is auto-invoked), sends an alternative
130
+ instruction, clicks **Complete** on the last step, or stops the workflow:
121
131
 
122
132
  ```bash
123
133
  npx inbase wait-for-approval --session "<current-cursor-chat-id>"
@@ -136,10 +146,12 @@ npx inbase wait-for-approval --session "<current-cursor-chat-id>"
136
146
  an earlier diff. Follow the text between
137
147
  `VISUAL_CODER_INSTRUCTION_START` and `VISUAL_CODER_INSTRUCTION_END`, read
138
148
  this session's `blueprint.json` when it is enabled (files, islands,
139
- `addedFunctions`, `addedVariables`, `addedImports`), read
140
- `user-context.json` (follow the viewpoint only if `followLook` is true),
141
- replace the plan from the current step onward using `inbase report-plan`,
142
- then wait for the user to invoke the first revised step.
149
+ `addedFunctions`, `addedVariables`, `addedImports`). The blueprint stays
150
+ leading. If the new instruction would differ from it, ask the user before
151
+ replacing the plan. Read `user-context.json` (follow the viewpoint only if
152
+ `followLook` is true), replace the plan from the current step onward using
153
+ `inbase report-plan`, then wait for the user to invoke the first revised
154
+ step.
143
155
  - Exit `2` (`VISUAL_CODER_STOPPED`) or `3` (timeout): make no further
144
156
  project changes.
145
157
 
@@ -154,14 +166,15 @@ npx inbase propose-patch --session "<current-cursor-chat-id>" --clear
154
166
 
155
167
  - Skip `inbase start-session` once this skill applies
156
168
  - Skip `inbase wait-for-blueprint` or report a plan before `VISUAL_CODER_BLUEPRINT_READY`
157
- - Skip this session's `blueprint.json` `userCreatedBlocks` or `userCreatedIslands` when `enabled` is true
158
- - Skip `addedFunctions`, `addedVariables`, or `addedImports` from that blueprint when `enabled` is true
169
+ - Treat the chat request, viewpoint, or your own plan as overriding an enabled blueprint
170
+ - Skip, rename, relocate, or replace this session's `blueprint.json` files, islands, functions, variables, or imports when `enabled` is true
171
+ - Silently differ from the blueprint; ask the user first
159
172
  - Read global `user-context.json` for placed files; those live on the session blueprint
160
173
  - Follow the user's look when `followLook` is false
161
174
  - Write, edit, create, or delete project files directly
162
175
  - Announce file lists instead of a patch
163
176
  - Write a patch before its plan step is invoked
164
- - Propose the next step before the user clicks **Run step**
177
+ - Propose the next step before `inbase wait-for-approval` returns `VISUAL_CODER_EXECUTE`
165
178
  - Propose another patch after `VISUAL_CODER_FINISHED`
166
179
  - Reuse, overwrite, or expand an existing session diff
167
180
  - Use this flow for git, lockfiles, or other non-source work