@jkwd/inbase 0.1.0

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 (44) hide show
  1. package/README.md +76 -0
  2. package/apps/explorer/index.html +12 -0
  3. package/apps/explorer/package.json +28 -0
  4. package/apps/explorer/scripts/js-source.mjs +188 -0
  5. package/apps/explorer/scripts/patch-lib.d.ts +115 -0
  6. package/apps/explorer/scripts/patch-lib.mjs +472 -0
  7. package/apps/explorer/scripts/scan-target.mjs +188 -0
  8. package/apps/explorer/scripts/session-store.d.ts +156 -0
  9. package/apps/explorer/scripts/session-store.mjs +809 -0
  10. package/apps/explorer/scripts/target-config.d.ts +8 -0
  11. package/apps/explorer/scripts/target-config.mjs +42 -0
  12. package/apps/explorer/src/App.tsx +941 -0
  13. package/apps/explorer/src/agentIntent.ts +182 -0
  14. package/apps/explorer/src/codebase.ts +15 -0
  15. package/apps/explorer/src/index.css +632 -0
  16. package/apps/explorer/src/layout.ts +508 -0
  17. package/apps/explorer/src/main.tsx +16 -0
  18. package/apps/explorer/src/scene/BlockPlacer.tsx +87 -0
  19. package/apps/explorer/src/scene/Bridge.tsx +290 -0
  20. package/apps/explorer/src/scene/FileBlock.tsx +256 -0
  21. package/apps/explorer/src/scene/FolderArea.tsx +96 -0
  22. package/apps/explorer/src/scene/IslandPlacer.tsx +40 -0
  23. package/apps/explorer/src/scene/MapSelectBorder.tsx +57 -0
  24. package/apps/explorer/src/scene/MapView.tsx +247 -0
  25. package/apps/explorer/src/scene/Player.tsx +245 -0
  26. package/apps/explorer/src/scene/RelationLines.tsx +223 -0
  27. package/apps/explorer/src/scene/SelectionController.tsx +89 -0
  28. package/apps/explorer/src/scene/UserContextTracker.tsx +152 -0
  29. package/apps/explorer/src/scene/World.tsx +323 -0
  30. package/apps/explorer/src/theme.ts +111 -0
  31. package/apps/explorer/src/types.ts +245 -0
  32. package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +38 -0
  33. package/apps/explorer/src/ui/HUD.tsx +1090 -0
  34. package/apps/explorer/src/ui/NameInput.tsx +45 -0
  35. package/apps/explorer/src/userContext.ts +73 -0
  36. package/apps/explorer/src/userCreated.ts +354 -0
  37. package/apps/explorer/src/vite-env.d.ts +1 -0
  38. package/apps/explorer/tsconfig.json +21 -0
  39. package/apps/explorer/vite.config.ts +295 -0
  40. package/bin/inbase.mjs +170 -0
  41. package/bin/project.mjs +94 -0
  42. package/bin/session.mjs +241 -0
  43. package/package.json +63 -0
  44. package/skill/inbase/SKILL.md +167 -0
@@ -0,0 +1,241 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+ import { explorerRoot, takeFlagValue, takeFlagValues } from './project.mjs'
5
+
6
+ async function loadExplorer() {
7
+ const storePath = pathToFileURL(
8
+ path.join(explorerRoot, 'scripts/session-store.mjs'),
9
+ ).href
10
+ const patchPath = pathToFileURL(
11
+ path.join(explorerRoot, 'scripts/patch-lib.mjs'),
12
+ ).href
13
+ const configPath = pathToFileURL(
14
+ path.join(explorerRoot, 'scripts/target-config.mjs'),
15
+ ).href
16
+ const [store, patchLib, config] = await Promise.all([
17
+ import(storePath),
18
+ import(patchPath),
19
+ import(configPath),
20
+ ])
21
+ return { store, patchLib, config }
22
+ }
23
+
24
+ function usage(name, example) {
25
+ console.error(`Usage: inbase ${name} ${example}`)
26
+ process.exit(1)
27
+ }
28
+
29
+ export async function startSession(args) {
30
+ const { store, config } = await loadExplorer()
31
+ const sessionId = takeFlagValue(args, '--session')
32
+ const feature = takeFlagValue(args, '--feature')
33
+ if (!sessionId) usage('start-session', '--session <cursor-chat-id> [--feature "name"]')
34
+
35
+ const manifest = store.startSession(config.dataDir, { sessionId, feature })
36
+ if (manifest.phase === 'blueprint_ask' || manifest.phase === 'blueprint') {
37
+ console.log(
38
+ `VISUAL_CODER_BLUEPRINT_WAIT Session ${sessionId} is visible in the visualizer (${manifest.phase}). Wait with inbase wait-for-blueprint before drafting the plan.`,
39
+ )
40
+ } else {
41
+ console.log(
42
+ `VISUAL_CODER_PREPARING Session ${sessionId} is visible in the visualizer (${manifest.phase}). Draft the plan next with inbase report-plan.`,
43
+ )
44
+ }
45
+ }
46
+
47
+ export async function waitForBlueprint(args) {
48
+ const { store, config } = await loadExplorer()
49
+ const sessionId = takeFlagValue(args, '--session')
50
+ const timeoutMs = Number(takeFlagValue(args, '--timeout') ?? 600000)
51
+ if (!sessionId) usage('wait-for-blueprint', '--session <cursor-chat-id> [--timeout ms]')
52
+
53
+ const started = Date.now()
54
+ const initial = store.readManifest(config.dataDir, sessionId)
55
+ if (!initial) {
56
+ console.error(`No workflow session found for ${sessionId}`)
57
+ process.exit(1)
58
+ }
59
+
60
+ if (initial.phase === 'blueprint_ask' || initial.phase === 'blueprint') {
61
+ console.log(
62
+ initial.phase === 'blueprint_ask'
63
+ ? 'Waiting for the user to choose Setup blueprint: Yes or No...'
64
+ : 'Waiting for the user to send the blueprint...',
65
+ )
66
+ } else {
67
+ console.log(`Blueprint handshake already finished for session ${sessionId}.`)
68
+ }
69
+
70
+ while (Date.now() - started < timeoutMs) {
71
+ const manifest = store.readManifest(config.dataDir, sessionId)
72
+ if (!manifest || manifest.phase === 'stopped') {
73
+ console.error(
74
+ 'VISUAL_CODER_STOPPED The workflow was stopped. Do not modify project files.',
75
+ )
76
+ process.exit(2)
77
+ }
78
+ if (manifest.phase !== 'blueprint_ask' && manifest.phase !== 'blueprint') {
79
+ const blueprint = store.readBlueprint(config.dataDir, sessionId)
80
+ const blocks = blueprint.userCreatedBlocks ?? []
81
+ const islands = blueprint.userCreatedIslands ?? []
82
+ console.log(
83
+ 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.`
85
+ : 'VISUAL_CODER_BLUEPRINT_READY The user skipped the blueprint. Continue without user-placed files or islands.',
86
+ )
87
+ console.log('VISUAL_CODER_BLUEPRINT_START')
88
+ console.log(JSON.stringify(blueprint, null, 2))
89
+ console.log('VISUAL_CODER_BLUEPRINT_END')
90
+ process.exit(0)
91
+ }
92
+ await new Promise((resolve) => setTimeout(resolve, 500))
93
+ }
94
+
95
+ console.error('Timed out waiting for the blueprint handshake. Do not modify files.')
96
+ process.exit(3)
97
+ }
98
+
99
+ export async function reportPlan(args) {
100
+ const { store, config } = await loadExplorer()
101
+ const sessionParsed = takeFlagValues(args, '--session')
102
+ const featureParsed = takeFlagValues(sessionParsed.rest, '--feature')
103
+ const stepsParsed = takeFlagValues(featureParsed.rest, '--steps')
104
+ const sessionId = sessionParsed.values[0]
105
+ const existing = sessionId ? store.readManifest(config.dataDir, sessionId) : null
106
+ const feature = featureParsed.values[0] ?? existing?.feature
107
+
108
+ if (!sessionId || !feature || stepsParsed.values.length === 0) {
109
+ usage(
110
+ 'report-plan',
111
+ '--session <cursor-chat-id> --feature "name" --steps "one" [--steps "two"]',
112
+ )
113
+ }
114
+
115
+ const manifest = store.reportPlan(config.dataDir, {
116
+ sessionId,
117
+ feature,
118
+ stepTitles: stepsParsed.values,
119
+ })
120
+ 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}.`,
122
+ )
123
+ }
124
+
125
+ export async function waitForApproval(args) {
126
+ const { store, config } = await loadExplorer()
127
+ const sessionId = takeFlagValue(args, '--session')
128
+ const timeoutMs = Number(takeFlagValue(args, '--timeout') ?? 600000)
129
+ if (!sessionId) usage('wait-for-approval', '--session <cursor-chat-id> [--timeout ms]')
130
+
131
+ const started = Date.now()
132
+ const initial = store.readManifest(config.dataDir, sessionId)
133
+ const initialDiff = initial?.diffs?.at(-1)
134
+ if (!initial) {
135
+ console.error(`No workflow session found for ${sessionId}`)
136
+ process.exit(1)
137
+ }
138
+ 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}...`
143
+ : `Waiting for the visual workflow in session ${sessionId}...`,
144
+ )
145
+
146
+ while (Date.now() - started < timeoutMs) {
147
+ const manifest = store.readManifest(config.dataDir, sessionId)
148
+ if (!manifest) {
149
+ console.error(
150
+ 'VISUAL_CODER_STOPPED The workflow was stopped. Do not modify project files.',
151
+ )
152
+ process.exit(2)
153
+ }
154
+ const current = initialDiff
155
+ ? manifest.diffs.find((entry) => entry.id === initialDiff.id)
156
+ : null
157
+
158
+ if (manifest.phase === 'finished') {
159
+ console.log(
160
+ `VISUAL_CODER_FINISHED The final step was applied. Feature is done. Run inbase propose-patch --session ${sessionId} --clear, then tell the user it is finished.`,
161
+ )
162
+ process.exit(5)
163
+ }
164
+ if (manifest.phase === 'working') {
165
+ const next = manifest.steps.find((step) => step.index === manifest.currentStep)
166
+ console.log(
167
+ `VISUAL_CODER_EXECUTE Step ${manifest.currentStep} is invoked${next ? `: ${next.title}` : ''}. Implement only this step, publish its incremental diff with inbase propose-patch --session ${sessionId}, then wait again.`,
168
+ )
169
+ process.exit(0)
170
+ }
171
+ if (manifest.phase === 'replanning') {
172
+ const instruction = manifest.pendingInstruction
173
+ ? `\nVISUAL_CODER_INSTRUCTION_START\n${manifest.pendingInstruction}\nVISUAL_CODER_INSTRUCTION_END`
174
+ : ''
175
+ 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}`,
177
+ )
178
+ process.exit(4)
179
+ }
180
+ if (manifest.phase === 'stopped' || current?.status === 'rejected') {
181
+ console.error(
182
+ 'VISUAL_CODER_STOPPED The workflow was stopped. Do not modify project files.',
183
+ )
184
+ process.exit(2)
185
+ }
186
+ await new Promise((resolve) => setTimeout(resolve, 500))
187
+ }
188
+
189
+ console.error('Timed out waiting for visualizer review. Do not modify files.')
190
+ process.exit(3)
191
+ }
192
+
193
+ export async function proposePatch(args) {
194
+ const { store, patchLib, config } = await loadExplorer()
195
+ const clear = args.includes('--clear')
196
+ const withoutClear = args.filter((arg) => arg !== '--clear')
197
+ const sessionParsed = takeFlagValues(withoutClear, '--session')
198
+ const patchFile = sessionParsed.rest[0]
199
+ const sessionId = sessionParsed.values[0]
200
+ const cwd = process.cwd()
201
+
202
+ if (clear) {
203
+ if (!sessionId) usage('propose-patch', '--session <cursor-chat-id> --clear')
204
+ store.stopSession(config.dataDir, sessionId)
205
+ console.log(
206
+ `Cleared session ${sessionId}; stored diffs and blueprint drafts were removed.`,
207
+ )
208
+ process.exit(0)
209
+ }
210
+
211
+ if (!sessionId || !patchFile) {
212
+ usage('propose-patch', '--session <cursor-chat-id> <file.patch|->')
213
+ }
214
+
215
+ let patchText = ''
216
+ if (patchFile && patchFile !== '-') {
217
+ patchText = fs.readFileSync(path.resolve(cwd, patchFile), 'utf8')
218
+ } else if (patchFile === '-') {
219
+ patchText = fs.readFileSync(0, 'utf8')
220
+ }
221
+
222
+ if (!patchText.trim()) {
223
+ console.error('No unified diff provided. Pass a .patch file or use stdin.')
224
+ process.exit(1)
225
+ }
226
+
227
+ const parsed = patchLib.parseUnifiedPatch(patchText)
228
+ if (parsed.entries.length === 0) {
229
+ console.error('Patch did not contain any file changes.')
230
+ process.exit(1)
231
+ }
232
+
233
+ const { entry, manifest } = store.appendDiff(config.dataDir, config.targetRoot, {
234
+ sessionId,
235
+ patchText,
236
+ })
237
+
238
+ 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.`,
240
+ )
241
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@jkwd/inbase",
3
+ "version": "0.1.0",
4
+ "description": "A first-person 3D map of a codebase, with a visual coding workflow for Cursor.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/jk-wd/inbase"
8
+ },
9
+ "homepage": "https://github.com/jk-wd/inbase",
10
+ "bugs": {
11
+ "url": "https://github.com/jk-wd/inbase/issues"
12
+ },
13
+ "type": "module",
14
+ "bin": {
15
+ "inbase": "bin/inbase.mjs"
16
+ },
17
+ "files": [
18
+ "bin/inbase.mjs",
19
+ "bin/project.mjs",
20
+ "bin/session.mjs",
21
+ "skill",
22
+ "apps/explorer/index.html",
23
+ "apps/explorer/package.json",
24
+ "apps/explorer/tsconfig.json",
25
+ "apps/explorer/vite.config.ts",
26
+ "apps/explorer/src/*.ts",
27
+ "apps/explorer/src/*.tsx",
28
+ "apps/explorer/src/*.css",
29
+ "apps/explorer/src/scene",
30
+ "apps/explorer/src/ui",
31
+ "apps/explorer/scripts/js-source.mjs",
32
+ "apps/explorer/scripts/patch-lib.d.ts",
33
+ "apps/explorer/scripts/patch-lib.mjs",
34
+ "apps/explorer/scripts/scan-target.mjs",
35
+ "apps/explorer/scripts/session-store.d.ts",
36
+ "apps/explorer/scripts/session-store.mjs",
37
+ "apps/explorer/scripts/target-config.d.ts",
38
+ "apps/explorer/scripts/target-config.mjs"
39
+ ],
40
+ "workspaces": [
41
+ "apps/*"
42
+ ],
43
+ "scripts": {
44
+ "dev": "npm run dev -w @visual-coder/explorer",
45
+ "dev:target": "npm run dev -w @visual-coder/example-target",
46
+ "build": "npm run build -w @visual-coder/explorer",
47
+ "scan": "npm run scan -w @visual-coder/explorer",
48
+ "inbase": "node bin/inbase.mjs",
49
+ "test": "npm test -w @visual-coder/explorer && node --test bin/*.test.mjs"
50
+ },
51
+ "engines": {
52
+ "node": ">=20"
53
+ },
54
+ "dependencies": {
55
+ "@react-three/drei": "^9.117.3",
56
+ "@react-three/fiber": "^8.17.10",
57
+ "@vitejs/plugin-react": "^4.3.4",
58
+ "react": "^18.3.1",
59
+ "react-dom": "^18.3.1",
60
+ "three": "^0.170.0",
61
+ "vite": "^6.0.3"
62
+ }
63
+ }
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: inbase
3
+ description: >-
4
+ Grounds source-file changes in the Inbase visual map. Use when creating,
5
+ editing, or deleting application source files in this repository. Lists every
6
+ feature step, then works only via patch files. Do not use for git, docs-only,
7
+ lockfiles, or questions.
8
+ ---
9
+
10
+ # Inbase visual edits
11
+
12
+ Apply this skill **whenever the work is file changes in this repository**.
13
+ Skip it for git, lockfiles, `.inbase`, `.cursor`, or questions with no code
14
+ changes.
15
+
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.
21
+
22
+ Every Cursor chat has an explicit session ID. Pass that same ID to every
23
+ command. The visualizer stores immutable diffs under `.inbase/diff-sessions/<session-id>/diffs/`.
24
+
25
+ Inbase must already be running (`inbase run` or `npx inbase run`). Prefer
26
+ `npx inbase` so the local package is used.
27
+
28
+ ## Required sequence
29
+
30
+ 1. As soon as this skill applies, start the visual session so the explorer can
31
+ offer a blueprint handshake. Do this before reading context or listing steps:
32
+
33
+ ```bash
34
+ npx inbase start-session --session "<current-cursor-chat-id>"
35
+ ```
36
+
37
+ 2. **Stop and wait for the blueprint handshake**. Do not report a plan and do
38
+ not write a patch until this prints `VISUAL_CODER_BLUEPRINT_READY`:
39
+
40
+ ```bash
41
+ npx inbase wait-for-blueprint --session "<current-cursor-chat-id>"
42
+ ```
43
+
44
+ The explorer asks **Setup blueprint: Yes vs No**.
45
+ - **No**: skip placement; continue without user-placed files or islands.
46
+ - **Yes**: the user places files (`Space`) and islands (`B`), then clicks
47
+ **Send blueprint**.
48
+ 3. Read the handshake output between `VISUAL_CODER_BLUEPRINT_START` and
49
+ `VISUAL_CODER_BLUEPRINT_END`, or read
50
+ `.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.
57
+ 4. Read `.inbase/user-context.json` for viewpoint only.
58
+ 5. Use the user's viewpoint only when `followLook` is true:
59
+ - `island` is where they are standing
60
+ - `lookingAt` / `lookingAtFiles` are the blocks they are looking at
61
+ - `selected` is the block they clicked
62
+ - `filesOnIsland` is the rest of that folder
63
+ Prefer those files while `followLook` is true, unless the request clearly
64
+ 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.
67
+ 6. List **all** steps needed to finish the feature. Keep steps small enough that
68
+ one patch is one landscape change (usually one new file, or a few related
69
+ edits).
70
+ 7. Report the plan before making a diff:
71
+
72
+ ```bash
73
+ npx inbase report-plan \
74
+ --session "<current-cursor-chat-id>" \
75
+ --feature "short feature name" \
76
+ --steps "Add Clock component" \
77
+ --steps "Show Clock on Home"
78
+ ```
79
+
80
+ 8. **Stop and wait for invocation**:
81
+
82
+ ```bash
83
+ npx inbase wait-for-approval --session "<current-cursor-chat-id>"
84
+ ```
85
+
86
+ Do not write a patch until this prints `VISUAL_CODER_EXECUTE`.
87
+ 9. Implement only the invoked step as a unified diff. Paths are relative to the
88
+ project root (same ids as `codebase.json`):
89
+
90
+ ```diff
91
+ --- /dev/null
92
+ +++ b/src/components/Clock.tsx
93
+ @@ -0,0 +1,5 @@
94
+ +export function Clock() {
95
+ + return <time>00:00</time>
96
+ +}
97
+ ```
98
+
99
+ ```diff
100
+ --- a/src/pages/Home.tsx
101
+ +++ b/src/pages/Home.tsx
102
+ @@ -1,3 +1,4 @@
103
+ +import { Clock } from '../components/Clock'
104
+ import { Counter } from '../components/Counter'
105
+ ```
106
+
107
+ Write that diff to a new `.patch` file, then publish it:
108
+
109
+ ```bash
110
+ npx inbase propose-patch \
111
+ --session "<current-cursor-chat-id>" \
112
+ /tmp/step.patch
113
+ ```
114
+
115
+ The patch path or stdin is required. Never write or replace a patch already
116
+ stored in the session folder.
117
+
118
+ 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:
121
+
122
+ ```bash
123
+ npx inbase wait-for-approval --session "<current-cursor-chat-id>"
124
+ ```
125
+
126
+ 12. Read the wait command output:
127
+
128
+ - Exit `0` (`VISUAL_CODER_EXECUTE`): the highlighted step was invoked. Build
129
+ only that step, publish its incremental diff with `inbase propose-patch`,
130
+ then wait again.
131
+ - Exit `5` (`VISUAL_CODER_FINISHED`): that was the last step. The visualizer
132
+ already applied the final patch and removed stored session diffs and
133
+ blueprint drafts. Optionally run `--clear` if anything remains, tell the
134
+ user the feature is done, and **stop**. Do not propose another patch.
135
+ - Exit `4` (`VISUAL_CODER_REPLAN`): do **not** apply files and do not rewrite
136
+ an earlier diff. Follow the text between
137
+ `VISUAL_CODER_INSTRUCTION_START` and `VISUAL_CODER_INSTRUCTION_END`, read
138
+ 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.
143
+ - Exit `2` (`VISUAL_CODER_STOPPED`) or `3` (timeout): make no further
144
+ project changes.
145
+
146
+ 13. After a finished handshake, the explorer already removed stored session
147
+ diffs and blueprint drafts. Optionally run:
148
+
149
+ ```bash
150
+ npx inbase propose-patch --session "<current-cursor-chat-id>" --clear
151
+ ```
152
+
153
+ ## Do not
154
+
155
+ - Skip `inbase start-session` once this skill applies
156
+ - 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
159
+ - Read global `user-context.json` for placed files; those live on the session blueprint
160
+ - Follow the user's look when `followLook` is false
161
+ - Write, edit, create, or delete project files directly
162
+ - Announce file lists instead of a patch
163
+ - Write a patch before its plan step is invoked
164
+ - Propose the next step before the user clicks **Run step**
165
+ - Propose another patch after `VISUAL_CODER_FINISHED`
166
+ - Reuse, overwrite, or expand an existing session diff
167
+ - Use this flow for git, lockfiles, or other non-source work