selfware 0.6.7

Your personal AI workshop — software you own, software that lasts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
{
  "component": "analysis",
  "tier": "full",
  "loop_stage": "perceive",
  "summary": "The analysis component is the engine's retrieval and search substrate for the perceive stage. It runs three tiers over code: a BM25Index for lexical keyword search, a VectorStore of embedded CodeChunks searched through an HNSW VectorIndex for semantic recall, and a CodeGraph / WorkspaceGraph of GraphNodes and GraphEdges for structural traversal. An ErrorAnalyzer classifies RawErrors into AnalyzedErrors with FixSuggestions, and the tech_debt module scores and prioritizes DebtItems. On the loop it is how the agent finds the right code: chunk, index, search, filter, rank, then feed grounded results into reasoning.",
  "loop_objects": ["CodeChunk", "ChunkMetadata", "ChunkType", "BM25Index", "BM25Result", "VectorStore", "VectorIndex", "SearchFilter", "EmbeddingBackend", "GraphNode", "GraphEdge", "NodeType", "EdgeType", "CodeGraph", "WorkspaceGraphOptions", "WorkspaceGraphSummary", "RawError", "AnalyzedError", "ErrorCategory", "FixSuggestion", "ErrorAnalyzer", "DebtItem"],
  "context_basis": "Recommendations formed with src/analysis/ read in the context of the full engine under a ~600k budget framing; hybrid lexical/semantic/structural retrieval assumes a large codebase whose relevant slices must be surfaced without loading everything.",
  "examples": [
    {
      "id": "analysis-01",
      "title": "Chunk source files before indexing anything",
      "loop_stage": "perceive",
      "pattern": "chunk-first",
      "intent": "Break code into retrievable units with metadata so every index has something coherent to hold.",
      "how_it_shapes_the_loop": "chunk_rust splits a file into symbol-aware CodeChunks (falling back to chunk_fixed_size), each stamped with ChunkMetadata, giving the perceive stage the atomic units all three search tiers index.",
      "loop_objects_touched": ["CodeChunk", "ChunkMetadata", "ChunkType"],
      "wiring": {
        "inputs_from": ["source files", "vector_store::chunk_rust"],
        "outputs_to": ["BM25Index", "VectorStore::add_chunk", "CodeGraph"]
      },
      "touch_interaction": {
        "gesture": "drag",
        "canvas_action": "Dragging a file node onto the chunker splits it into a fan of labeled chunk tiles by symbol.",
        "visual": "The file bursts into chunk tiles, each color-tagged by ChunkType (Function blue, Struct green, Test amber)."
      },
      "mini_scenario": "The user drags analyzer.rs onto the chunker and chunk_rust fans it into function and impl chunks, each stamped with its line range.",
      "pitfall": "Chunk boundaries must preserve line ranges accurately; a chunk whose metadata lines drift makes every later search result cite the wrong location."
    },
    {
      "id": "analysis-02",
      "title": "Build a BM25Index for lexical keyword search",
      "loop_stage": "foundation",
      "pattern": "lexical-index-build",
      "intent": "Enable fast exact-term retrieval over symbol names and documented words.",
      "how_it_shapes_the_loop": "BM25Index::add_batch ingests chunks (with_params tunes k1/b), tokenizing camelCase and snake_case into term frequencies and IDF, arming the perceive stage with O(fast) keyword lookup.",
      "loop_objects_touched": ["BM25Index", "CodeChunk"],
      "wiring": {
        "inputs_from": ["CodeChunks"],
        "outputs_to": ["BM25Index::search", "hybrid ranking"]
      },
      "touch_interaction": {
        "gesture": "long-press",
        "canvas_action": "Long-pressing the lexical node triggers add_batch over the chunk pool; term buckets fill and the IDF table settles.",
        "visual": "A word-cloud of terms condenses into an index grid; the k1/b tuning knobs from with_params sit on the node's rim."
      },
      "mini_scenario": "The user long-presses to index new chunks, and 'getUserName' tokenizes to get/user/name so BM25Index::terms can retrieve it.",
      "pitfall": "The IDF and avgdl must be recomputed when documents change; searching a stale index scores terms against outdated frequencies."
    },
    {
      "id": "analysis-03",
      "title": "Query the BM25Index for exact-term hits",
      "loop_stage": "perceive",
      "pattern": "lexical-lookup",
      "intent": "Retrieve chunks that contain the query's exact terms, ranked by BM25 score.",
      "how_it_shapes_the_loop": "BM25Index::search returns BM25Results (document id plus score) that the perceive stage folds into context; contains and terms let the loop probe index coverage before searching.",
      "loop_objects_touched": ["BM25Index", "BM25Result"],
      "wiring": {
        "inputs_from": ["user query", "BM25Index"],
        "outputs_to": ["hybrid ranking", "LlmContext"]
      },
      "touch_interaction": {
        "gesture": "tap",
        "canvas_action": "Tapping the query chip fires it at the lexical index; matching chunk tiles light up in score order.",
        "visual": "Hits glow brighter with their BM25 score; a small badge shows the result count from the search."
      },
      "mini_scenario": "A query for 'find_cycles' hits the exact symbol via BM25Index::search even when the semantic index ranks unrelated graph code higher.",
      "pitfall": "BM25 only matches terms it has seen; a query word absent from the index vocabulary returns nothing regardless of relevance."
    },
    {
      "id": "analysis-04",
      "title": "Rebuild the lexical index after files change",
      "loop_stage": "control",
      "pattern": "stale-index-refresh",
      "intent": "Keep keyword search honest as the codebase mutates under the loop.",
      "how_it_shapes_the_loop": "BM25Index::remove drops a stale document and rebuild recomputes the whole statistics table, letting the control layer schedule re-indexing between loop turns instead of mid-search.",
      "loop_objects_touched": ["BM25Index", "CodeChunk"],
      "wiring": {
        "inputs_from": ["file-change events", "BM25Index::remove"],
        "outputs_to": ["fresh BM25Index::search results"]
      },
      "touch_interaction": {
        "gesture": "flick",
        "canvas_action": "Flicking a stale chunk tile off the index removes its document; a rebuild badge appears when drift accumulates.",
        "visual": "The tile greys out and slides off the grid; the index node shows a pulsing 'rebuild' badge until statistics settle."
      },
      "mini_scenario": "After the agent rewrites config.rs, the control step removes its old chunks and rebuild runs before the next perceive turn.",
      "pitfall": "Removing documents without rebuilding leaves IDF stale; interleaving remove and search across loop turns yields inconsistent scores."
    },
    {
      "id": "analysis-05",
      "title": "Embed chunks into the VectorStore with a backend",
      "loop_stage": "foundation",
      "pattern": "semantic-embedding",
      "intent": "Turn code chunks into vectors so meaning-based recall becomes possible.",
      "how_it_shapes_the_loop": "VectorStore::add_chunk embeds each CodeChunk through the configured EmbeddingBackend (with_api_key arms the Http backend) and inserts the normalized vector into the HNSW VectorIndex.",
      "loop_objects_touched": ["VectorStore", "EmbeddingBackend", "CodeChunk", "VectorIndex"],
      "wiring": {
        "inputs_from": ["CodeChunks", "EmbeddingBackend"],
        "outputs_to": ["VectorIndex (HNSW)", "VectorStore::search"]
      },
      "touch_interaction": {
        "gesture": "tap",
        "canvas_action": "Tapping a chunk sends it through add_chunk; a vector trail flows into the HNSW node.",
        "visual": "The chunk emits a shimmering vector arrow that threads into the index sphere, which pulses as it inserts."
      },
      "mini_scenario": "With the Http backend configured via with_api_key, the user taps a batch of chunks and their embeddings stream into the HNSW index.",
      "pitfall": "Embedding dimension must match the index; feeding vectors from a different backend dimension into an existing VectorIndex corrupts search."
    },
    {
      "id": "analysis-06",
      "title": "Search the VectorIndex for the k nearest chunks",
      "loop_stage": "perceive",
      "pattern": "semantic-knn",
      "intent": "Retrieve the chunks whose meaning is closest to the query, not just its words.",
      "how_it_shapes_the_loop": "VectorStore::search embeds the query and runs HNSW k-NN ranked by cosine_similarity, returning scored chunks the perceive stage folds into context.",
      "loop_objects_touched": ["VectorStore", "VectorIndex", "CodeChunk"],
      "wiring": {
        "inputs_from": ["query", "VectorIndex"],
        "outputs_to": ["hybrid ranking", "LlmContext"]
      },
      "touch_interaction": {
        "gesture": "spread",
        "canvas_action": "Spreading from the query node radiates a similarity field; the nearest chunk tiles pull toward it.",
        "visual": "Chunks arrange by cosine_similarity in concentric rings; the closest glow bright, distant ones fade."
      },
      "mini_scenario": "A query about retry logic returns the three nearest chunks by cosine similarity even though none contain the word 'retry'.",
      "pitfall": "Score derives from distance, not raw distance; treating HNSW distance as relevance inverts the ranking and surfaces the least similar chunks."
    },
    {
      "id": "analysis-07",
      "title": "Apply a SearchFilter to scope results",
      "loop_stage": "perceive",
      "pattern": "scoped-retrieval",
      "intent": "Restrict search to relevant files, chunk types, languages, or a min score.",
      "how_it_shapes_the_loop": "A SearchFilter built with with_file_pattern, with_chunk_type, with_language and with_min_score narrows candidates via matches before ranking, so the perceive stage only ingests chunks that pass the scope.",
      "loop_objects_touched": ["SearchFilter", "ChunkType", "CodeChunk"],
      "wiring": {
        "inputs_from": ["raw search results"],
        "outputs_to": ["ranked results", "LlmContext"]
      },
      "touch_interaction": {
        "gesture": "long-press",
        "canvas_action": "Long-pressing the results panel opens filter chips; toggling one dims out chunks that fail matches.",
        "visual": "Filter chips (Function, *.rs, score>0.5) light when active; failing chunks grey out and drop from the stack."
      },
      "mini_scenario": "The user filters to Function chunks in src/ with min_score above 0.6, cutting a noisy result set to five precise hits.",
      "pitfall": "A min_score set too high can empty the result set; over-filtering starves reasoning of context it needed to answer."
    },
    {
      "id": "analysis-08",
      "title": "Weight chunks by type and tags before final rank",
      "loop_stage": "reason",
      "pattern": "type-weighted-ranking",
      "intent": "Prefer higher-signal chunk kinds when two results score similarly.",
      "how_it_shapes_the_loop": "ChunkType::weight (Function outranks Import) multiplies raw similarity, and metadata set via with_symbol/with_tag sharpens matchability, so the reason stage ranks a matching function above a matching import at equal cosine.",
      "loop_objects_touched": ["ChunkType", "ChunkMetadata", "CodeChunk"],
      "wiring": {
        "inputs_from": ["semantic search results"],
        "outputs_to": ["final ranked list"]
      },
      "touch_interaction": {
        "gesture": "two-finger-rotate",
        "canvas_action": "Rotating the ranking dial re-weights by ChunkType; function tiles climb the list while imports sink.",
        "visual": "Tiles reshuffle as their weight badges apply; the list reorders with a smooth vertical slide."
      },
      "mini_scenario": "Two chunks tie on similarity; the type weight lifts the Function above the Comment so it ranks first in context.",
      "pitfall": "Type weight should nudge, not dominate; over-weighting can bury a highly relevant comment that actually answers the query."
    },
    {
      "id": "analysis-09",
      "title": "Fuse BM25 and vector scores into a hybrid rank",
      "loop_stage": "reason",
      "pattern": "hybrid-fusion",
      "intent": "Get the precision of keyword match plus the recall of semantic similarity.",
      "how_it_shapes_the_loop": "The reason stage merges BM25Results from BM25Index::search with scored chunks from VectorStore::search, so a chunk strong on either exact terms or meaning surfaces, widening what the loop can find.",
      "loop_objects_touched": ["BM25Result", "BM25Index", "VectorStore"],
      "wiring": {
        "inputs_from": ["BM25Index::search results", "VectorStore::search results"],
        "outputs_to": ["fused ranked list", "LlmContext"]
      },
      "touch_interaction": {
        "gesture": "pinch",
        "canvas_action": "Pinching the lexical and semantic result columns together merges them into one fused ranking.",
        "visual": "Two colored streams (blue lexical, green semantic) braid into a single ranked column with blended tile borders."
      },
      "mini_scenario": "A query for 'BM25Index' hits exactly via lexical and its callers via semantic; fusion returns both the definition and its usages.",
      "pitfall": "Normalize the two score scales before fusing; adding raw BM25 to cosine lets one metric silently swamp the other."
    },
    {
      "id": "analysis-10",
      "title": "Soft-delete a chunk and compact when the index degrades",
      "loop_stage": "control",
      "pattern": "soft-delete",
      "intent": "Remove stale chunks from results without an expensive index rebuild, then sweep when health drops.",
      "how_it_shapes_the_loop": "VectorStore::remove_chunk and remove_file mark entries deleted and rebuild_id_index keeps lookups coherent; the control layer calls compact only when check_health or verify_index_integrity reports the index has degraded.",
      "loop_objects_touched": ["VectorStore", "VectorIndex", "CodeChunk"],
      "wiring": {
        "inputs_from": ["stale chunk ids", "check_health report"],
        "outputs_to": ["filtered search results", "compacted VectorIndex"]
      },
      "touch_interaction": {
        "gesture": "flick",
        "canvas_action": "Flicking a chunk tile off the canvas marks it deleted; a health gauge on the index node ticks toward the compaction line.",
        "visual": "The tile fades to a ghost outline; the index node's health ring shifts green to amber as deletions accumulate."
      },
      "mini_scenario": "A refactored file's chunks are removed via remove_file; they vanish from results while compact waits for check_health to signal real degradation.",
      "pitfall": "Compact when integrity checks say so, not on a whim; compacting after every delete stalls the loop on rebuilds it did not need."
    },
    {
      "id": "analysis-11",
      "title": "Triage compiler output into prioritized AnalyzedErrors",
      "loop_stage": "perceive",
      "pattern": "error-classification",
      "intent": "Turn raw compiler output into categorized, ordered errors the loop can act on.",
      "how_it_shapes_the_loop": "ErrorAnalyzer::analyze maps each RawError to an AnalyzedError with an ErrorCategory and priority; analyze_batch, group_by_category and first_to_fix give the perceive stage an ordered queue instead of a wall of text.",
      "loop_objects_touched": ["ErrorAnalyzer", "RawError", "AnalyzedError", "ErrorCategory"],
      "wiring": {
        "inputs_from": ["compiler stderr (RawError)"],
        "outputs_to": ["first_to_fix queue", "error recovery"]
      },
      "touch_interaction": {
        "gesture": "tap",
        "canvas_action": "Tapping a raw error line classifies it into a category chip and slots it into the priority lane.",
        "visual": "The error line snaps a category badge (BorrowError red, StyleWarning grey) and drops into the lane first_to_fix would pop."
      },
      "mini_scenario": "A cargo build fails; analyze tags an E0502 as a borrow error at high priority so first_to_fix surfaces it before a style warning.",
      "pitfall": "Category drives priority; misclassifying a blocking type error as a style warning lets the loop skip past a fatal error."
    },
    {
      "id": "analysis-12",
      "title": "Emit a FixSuggestion the loop can gate on",
      "loop_stage": "reason",
      "pattern": "fix-proposal",
      "intent": "Propose a concrete fix and mark whether it is safe to apply automatically.",
      "how_it_shapes_the_loop": "Each AnalyzedError carries a FixSuggestion whose confidence and auto-fix flag let the reason stage decide between applying a safe fix directly and routing a risky one to review before the act stage.",
      "loop_objects_touched": ["FixSuggestion", "AnalyzedError"],
      "wiring": {
        "inputs_from": ["AnalyzedError"],
        "outputs_to": ["act stage (auto-fix or review)"]
      },
      "touch_interaction": {
        "gesture": "long-press",
        "canvas_action": "Long-pressing an error reveals its fix card with a confidence meter and an auto-fix toggle.",
        "visual": "The fix card unfolds; a green auto-fix badge shows when the suggestion is auto-fixable, the confidence bar filling proportionally."
      },
      "mini_scenario": "An unused import gets a high-confidence auto-fixable suggestion applied silently, while a lifetime fix routes to operator review.",
      "pitfall": "Only auto-apply high-confidence suggestions; auto-applying a low-confidence structural fix can compile away real intent."
    },
    {
      "id": "analysis-13",
      "title": "Build the CodeGraph of nodes and typed edges",
      "loop_stage": "foundation",
      "pattern": "structural-graph-build",
      "intent": "Represent the codebase as a traversable structure so structural queries become possible.",
      "how_it_shapes_the_loop": "add_node and add_edge (or connect) assemble GraphNodes via add_file/add_module/add_function/add_struct/add_trait, linked by typed GraphEdges, giving the foundation layer a structure beside the text indices.",
      "loop_objects_touched": ["CodeGraph", "GraphNode", "GraphEdge", "NodeType", "EdgeType"],
      "wiring": {
        "inputs_from": ["parsed symbols"],
        "outputs_to": ["find_path", "find_cycles", "build_workspace_graph"]
      },
      "touch_interaction": {
        "gesture": "spread",
        "canvas_action": "Spreading over the codebase node expands it into a live node-and-edge graph laid out by dependency.",
        "visual": "Nodes settle into a force-directed layout; call edges thin blue, import edges dashed, implement edges bold arrows."
      },
      "mini_scenario": "The user spreads the graph and sees the call structure of the vector_store module render as connected function nodes.",
      "pitfall": "Edge types must reflect real relations; a fabricated call edge corrupts every path query built on the graph."
    },
    {
      "id": "analysis-14",
      "title": "Walk dependencies and dependents from one node",
      "loop_stage": "perceive",
      "pattern": "neighborhood-traversal",
      "intent": "See what a symbol uses and what uses it before touching it.",
      "how_it_shapes_the_loop": "From a node found by get_node_by_id, outgoing_edges/incoming_edges and the dependencies/dependents helpers give the perceive stage the immediate structural neighborhood around a change target.",
      "loop_objects_touched": ["CodeGraph", "GraphNode", "GraphEdge"],
      "wiring": {
        "inputs_from": ["focus symbol", "CodeGraph"],
        "outputs_to": ["impact preview", "reason stage"]
      },
      "touch_interaction": {
        "gesture": "double-tap",
        "canvas_action": "Double-tapping a node fans out its dependencies on one side and its dependents on the other.",
        "visual": "Outbound edges slide left in blue, inbound edges right in amber; the focus node holds center with a bright ring."
      },
      "mini_scenario": "The user double-taps VectorStore::search and sees its callees on the left and every module that calls it on the right.",
      "pitfall": "Dependents change as the graph rebuilds; caching a dependents list across an edit turn acts on stale impact data."
    },
    {
      "id": "analysis-15",
      "title": "Find a path between two GraphNodes",
      "loop_stage": "reason",
      "pattern": "path-finding",
      "intent": "Discover how one code entity reaches another through the dependency structure.",
      "how_it_shapes_the_loop": "CodeGraph::find_path runs BFS over GraphEdges between two GraphNodes, giving the reason stage the concrete chain linking a caller to a callee.",
      "loop_objects_touched": ["CodeGraph", "GraphNode", "GraphEdge"],
      "wiring": {
        "inputs_from": ["source and target GraphNode"],
        "outputs_to": ["reason stage (impact analysis)"]
      },
      "touch_interaction": {
        "gesture": "draw-connection",
        "canvas_action": "Drawing from one node to another traces the shortest edge path; the route highlights hop by hop.",
        "visual": "A glowing path lights the intermediate nodes and edges; off-path nodes dim to focus the route."
      },
      "mini_scenario": "The user draws from a public API node to a private helper and find_path highlights the three-hop call chain between them.",
      "pitfall": "BFS finds a shortest path, not the only one; presenting it as the sole route hides other dependency chains that also matter."
    },
    {
      "id": "analysis-16",
      "title": "Detect cycles in the CodeGraph",
      "loop_stage": "verify",
      "pattern": "cycle-detection",
      "intent": "Flag circular dependencies that signal fragile architecture.",
      "how_it_shapes_the_loop": "CodeGraph::find_cycles surfaces circular GraphEdge chains, letting the verify stage reject or warn on structures that would tangle the dependency order.",
      "loop_objects_touched": ["CodeGraph", "GraphEdge"],
      "wiring": {
        "inputs_from": ["CodeGraph edges"],
        "outputs_to": ["verify gate", "DebtItem flagging"]
      },
      "touch_interaction": {
        "gesture": "long-press",
        "canvas_action": "Long-pressing the graph runs a cycle sweep; any loop of edges lights red and its nodes pulse together.",
        "visual": "A detected cycle rings its nodes in red and animates the loop direction with a rotating dashed edge."
      },
      "mini_scenario": "The verify stage runs find_cycles before a merge and flags a new A-to-B-to-A import loop for the human to break.",
      "pitfall": "A cycle in test-only edges may be acceptable; treating every cycle as fatal blocks harmless mutual references."
    },
    {
      "id": "analysis-17",
      "title": "Rank hub nodes by graph metrics",
      "loop_stage": "reason",
      "pattern": "hub-identification",
      "intent": "Find the most-connected entities that a change would ripple through.",
      "how_it_shapes_the_loop": "CodeGraph::node_metrics computes per-node degree and find_hubs ranks the top GraphNodes, so the reason stage knows which entities are high-impact before proposing a change near them; subgraph can isolate a hub's region.",
      "loop_objects_touched": ["CodeGraph", "GraphNode"],
      "wiring": {
        "inputs_from": ["node_metrics"],
        "outputs_to": ["impact assessment", "reason stage"]
      },
      "touch_interaction": {
        "gesture": "pinch",
        "canvas_action": "Pinching on the graph rescales each node by its degree; hubs swell large while leaves shrink to dots.",
        "visual": "Node sizes rescale to degree; the biggest hubs glow warm and cast the most edges."
      },
      "mini_scenario": "The user pinches and sees the VectorStore node swell as the largest hub, warning that touching it ripples widely.",
      "pitfall": "Degree measures coupling, not importance; a high-degree utility may be safe to change while a low-degree core type is not."
    },
    {
      "id": "analysis-18",
      "title": "Summarize a WorkspaceGraph around a focus symbol",
      "loop_stage": "perceive",
      "pattern": "focus-neighborhood",
      "intent": "Retrieve a bounded structural neighborhood around one symbol of interest.",
      "how_it_shapes_the_loop": "build_workspace_graph assembles the workspace-wide graph and summarize_graph applies WorkspaceGraphOptions (focus, depth, node cap) to produce a WorkspaceGraphSummary, giving the perceive stage a size-capped subgraph.",
      "loop_objects_touched": ["WorkspaceGraphOptions", "WorkspaceGraphSummary", "GraphNode", "GraphEdge"],
      "wiring": {
        "inputs_from": ["focus symbol", "WorkspaceGraphOptions"],
        "outputs_to": ["LlmContext", "reason stage"]
      },
      "touch_interaction": {
        "gesture": "pinch",
        "canvas_action": "Pinching around a focus node crops the graph to its neighborhood at the chosen depth.",
        "visual": "The graph fades to grey except the focus and its depth-N neighbors, which stay sharp within a lens circle."
      },
      "mini_scenario": "The user pinches around VectorStore at depth 2 and summarize_graph returns its callers, callees, and their types capped at the node limit.",
      "pitfall": "Respect the node cap in WorkspaceGraphOptions; an unbounded neighborhood around a hub expands to the whole workspace and blows the budget."
    },
    {
      "id": "analysis-19",
      "title": "Prioritize tech debt into a roadmap",
      "loop_stage": "reason",
      "pattern": "debt-prioritization",
      "intent": "Rank technical debt so the loop fixes the highest-value items first.",
      "how_it_shapes_the_loop": "The tech_debt module scores each DebtItem — total_cost compounds fix_cost with monthly_interest, severity_weight scales impact, and hotspot_score folds in churn_rate — then prioritize and generate_roadmap order the work for the reason stage.",
      "loop_objects_touched": ["DebtItem"],
      "wiring": {
        "inputs_from": ["detected DebtItems", "hotspot_score metrics"],
        "outputs_to": ["improvement roadmap", "reason stage"]
      },
      "touch_interaction": {
        "gesture": "flick",
        "canvas_action": "Flicking the debt list re-sorts it by priority_score; the costliest items rise to the top.",
        "visual": "Debt cards re-order by score; high-interest items carry a rising cost curve, security items a red shield accent."
      },
      "mini_scenario": "The user flicks the debt list and a high-churn file with mounting monthly_interest jumps to the top of the generated roadmap.",
      "pitfall": "The strategy changes the order, not the debt; sorting by effort alone can bury a critical security item that severity_weight would surface."
    },
    {
      "id": "analysis-20",
      "title": "Render the graph to a shareable diagram",
      "loop_stage": "act",
      "pattern": "graph-rendering",
      "intent": "Export the structural graph as a diagram a human can read and reason over.",
      "how_it_shapes_the_loop": "CodeGraph::render and render_to emit DOT, Mermaid, ASCII, or PlantUML (cluster and with_direction shape layout), and render_workspace_graph does the same at workspace scale, letting the act stage produce a human-facing artifact from the structure the loop reasons on.",
      "loop_objects_touched": ["CodeGraph", "GraphNode", "GraphEdge", "WorkspaceGraphSummary"],
      "wiring": {
        "inputs_from": ["CodeGraph", "WorkspaceGraphSummary"],
        "outputs_to": ["diagram artifact", "human review"]
      },
      "touch_interaction": {
        "gesture": "double-tap",
        "canvas_action": "Double-tapping the graph exports it via render_to in the selected format; a rendered diagram card slides out.",
        "visual": "A format chip (Mermaid/DOT) highlights and a clean rendered diagram peels off the live graph as a shareable card."
      },
      "mini_scenario": "The user double-taps to export the module graph as Mermaid for a design doc, matching the graph the agent searched.",
      "pitfall": "Rendering must reflect the current graph; exporting a cached diagram after the graph re-scanned ships a picture that lies about the code."
    }
  ]
}