@exaudeus/workrail 3.27.0 → 3.29.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 (160) hide show
  1. package/dist/console/assets/{index-FtTaDku8.js → index-BZ6HkxGf.js} +1 -1
  2. package/dist/console/index.html +1 -1
  3. package/dist/manifest.json +3 -3
  4. package/docs/README.md +57 -0
  5. package/docs/adrs/001-hybrid-storage-backend.md +38 -0
  6. package/docs/adrs/002-four-layer-context-classification.md +38 -0
  7. package/docs/adrs/003-checkpoint-trigger-strategy.md +35 -0
  8. package/docs/adrs/004-opt-in-encryption-strategy.md +36 -0
  9. package/docs/adrs/005-agent-first-workflow-execution-tokens.md +105 -0
  10. package/docs/adrs/006-append-only-session-run-event-log.md +76 -0
  11. package/docs/adrs/007-resume-and-checkpoint-only-sessions.md +51 -0
  12. package/docs/adrs/008-blocked-nodes-architectural-upgrade.md +178 -0
  13. package/docs/adrs/009-bridge-mode-single-instance-mcp.md +195 -0
  14. package/docs/adrs/010-release-pipeline.md +89 -0
  15. package/docs/architecture/README.md +7 -0
  16. package/docs/architecture/refactor-audit.md +364 -0
  17. package/docs/authoring-v2.md +527 -0
  18. package/docs/authoring.md +873 -0
  19. package/docs/changelog-recent.md +201 -0
  20. package/docs/configuration.md +505 -0
  21. package/docs/ctc-mcp-proposal.md +518 -0
  22. package/docs/design/README.md +22 -0
  23. package/docs/design/agent-cascade-protocol.md +96 -0
  24. package/docs/design/autonomous-console-design-candidates.md +253 -0
  25. package/docs/design/autonomous-console-design-review.md +111 -0
  26. package/docs/design/autonomous-platform-mvp-discovery.md +525 -0
  27. package/docs/design/claude-code-source-deep-dive.md +713 -0
  28. package/docs/design/console-cyberpunk-ui-discovery.md +504 -0
  29. package/docs/design/console-execution-trace-candidates-final.md +160 -0
  30. package/docs/design/console-execution-trace-candidates.md +211 -0
  31. package/docs/design/console-execution-trace-design-candidates-v2.md +113 -0
  32. package/docs/design/console-execution-trace-design-review.md +74 -0
  33. package/docs/design/console-execution-trace-discovery.md +394 -0
  34. package/docs/design/console-execution-trace-final-review.md +77 -0
  35. package/docs/design/console-execution-trace-review.md +92 -0
  36. package/docs/design/console-performance-discovery.md +415 -0
  37. package/docs/design/console-ui-backlog.md +280 -0
  38. package/docs/design/daemon-architecture-discovery.md +853 -0
  39. package/docs/design/daemon-design-candidates.md +318 -0
  40. package/docs/design/daemon-design-review-findings.md +119 -0
  41. package/docs/design/daemon-engine-design-candidates.md +210 -0
  42. package/docs/design/daemon-engine-design-review.md +131 -0
  43. package/docs/design/daemon-execution-engine-discovery.md +280 -0
  44. package/docs/design/daemon-gap-analysis.md +554 -0
  45. package/docs/design/daemon-owns-console-plan.md +168 -0
  46. package/docs/design/daemon-owns-console-review.md +91 -0
  47. package/docs/design/daemon-owns-console.md +195 -0
  48. package/docs/design/data-model-erd.md +11 -0
  49. package/docs/design/design-candidates-consolidate-dev-staleness.md +98 -0
  50. package/docs/design/design-candidates-walk-cache-depth-limit.md +80 -0
  51. package/docs/design/design-review-consolidate-dev-staleness.md +54 -0
  52. package/docs/design/design-review-walk-cache-depth-limit.md +48 -0
  53. package/docs/design/implementation-plan-consolidate-dev-staleness.md +142 -0
  54. package/docs/design/implementation-plan-walk-cache-depth-limit.md +141 -0
  55. package/docs/design/layer3b-ghost-nodes-design-candidates.md +229 -0
  56. package/docs/design/layer3b-ghost-nodes-design-review.md +93 -0
  57. package/docs/design/layer3b-ghost-nodes-implementation-plan.md +219 -0
  58. package/docs/design/list-workflows-latency-fix-plan.md +128 -0
  59. package/docs/design/list-workflows-latency-fix-review.md +55 -0
  60. package/docs/design/list-workflows-latency-fix.md +109 -0
  61. package/docs/design/native-context-management-api.md +11 -0
  62. package/docs/design/performance-sweep-2026-04.md +96 -0
  63. package/docs/design/routines-guide.md +219 -0
  64. package/docs/design/sequence-diagrams.md +11 -0
  65. package/docs/design/subagent-design-principles.md +220 -0
  66. package/docs/design/temporal-patterns-design-candidates.md +312 -0
  67. package/docs/design/temporal-patterns-design-review-findings.md +163 -0
  68. package/docs/design/test-isolation-from-config-file.md +335 -0
  69. package/docs/design/v2-core-design-locks.md +2746 -0
  70. package/docs/design/v2-lock-registry.json +734 -0
  71. package/docs/design/workflow-authoring-v2.md +1044 -0
  72. package/docs/design/workflow-docs-spec.md +218 -0
  73. package/docs/design/workflow-extension-points.md +687 -0
  74. package/docs/design/workrail-auto-trigger-system.md +359 -0
  75. package/docs/design/workrail-config-file-discovery.md +513 -0
  76. package/docs/docker.md +110 -0
  77. package/docs/generated/v2-lock-closure-plan.md +26 -0
  78. package/docs/generated/v2-lock-coverage.json +797 -0
  79. package/docs/generated/v2-lock-coverage.md +177 -0
  80. package/docs/ideas/backlog.md +3927 -0
  81. package/docs/ideas/design-candidates-mcp-resilience.md +208 -0
  82. package/docs/ideas/design-review-findings-mcp-resilience.md +119 -0
  83. package/docs/ideas/implementation_plan.md +249 -0
  84. package/docs/ideas/third-party-workflow-setup-design-thinking.md +1948 -0
  85. package/docs/implementation/02-architecture.md +316 -0
  86. package/docs/implementation/04-testing-strategy.md +124 -0
  87. package/docs/implementation/09-simple-workflow-guide.md +835 -0
  88. package/docs/implementation/13-advanced-validation-guide.md +874 -0
  89. package/docs/implementation/README.md +21 -0
  90. package/docs/integrations/claude-code.md +300 -0
  91. package/docs/integrations/firebender.md +315 -0
  92. package/docs/migration/v0.1.0.md +147 -0
  93. package/docs/naming-conventions.md +45 -0
  94. package/docs/planning/README.md +104 -0
  95. package/docs/planning/github-ticketing-playbook.md +195 -0
  96. package/docs/plans/README.md +24 -0
  97. package/docs/plans/agent-managed-ticketing-design.md +605 -0
  98. package/docs/plans/agentic-orchestration-roadmap.md +112 -0
  99. package/docs/plans/assessment-gates-engine-handoff.md +536 -0
  100. package/docs/plans/content-coherence-and-references.md +151 -0
  101. package/docs/plans/library-extraction-plan.md +340 -0
  102. package/docs/plans/mr-review-workflow-redesign.md +1451 -0
  103. package/docs/plans/native-context-management-epic.md +11 -0
  104. package/docs/plans/perf-fixes-design-candidates.md +225 -0
  105. package/docs/plans/perf-fixes-design-review-findings.md +61 -0
  106. package/docs/plans/perf-fixes-new-issues-candidates.md +264 -0
  107. package/docs/plans/perf-fixes-new-issues-review.md +110 -0
  108. package/docs/plans/prompt-fragments.md +53 -0
  109. package/docs/plans/ui-ux-workflow-design-candidates.md +120 -0
  110. package/docs/plans/ui-ux-workflow-discovery.md +100 -0
  111. package/docs/plans/ui-ux-workflow-review.md +48 -0
  112. package/docs/plans/v2-followup-enhancements.md +587 -0
  113. package/docs/plans/workflow-categories-candidates.md +105 -0
  114. package/docs/plans/workflow-categories-discovery.md +110 -0
  115. package/docs/plans/workflow-categories-review.md +51 -0
  116. package/docs/plans/workflow-discovery-model-candidates.md +94 -0
  117. package/docs/plans/workflow-discovery-model-discovery.md +74 -0
  118. package/docs/plans/workflow-discovery-model-review.md +48 -0
  119. package/docs/plans/workflow-source-setup-phase-1.md +245 -0
  120. package/docs/plans/workflow-source-setup-phase-2.md +361 -0
  121. package/docs/plans/workflow-staleness-detection-candidates.md +104 -0
  122. package/docs/plans/workflow-staleness-detection-review.md +58 -0
  123. package/docs/plans/workflow-staleness-detection.md +80 -0
  124. package/docs/plans/workflow-v2-design.md +69 -0
  125. package/docs/plans/workflow-v2-roadmap.md +74 -0
  126. package/docs/plans/workflow-validation-design.md +98 -0
  127. package/docs/plans/workflow-validation-roadmap.md +108 -0
  128. package/docs/plans/workrail-platform-vision.md +420 -0
  129. package/docs/reference/agent-context-cleaner-snippet.md +94 -0
  130. package/docs/reference/agent-context-guidance.md +140 -0
  131. package/docs/reference/context-optimization.md +284 -0
  132. package/docs/reference/example-workflow-repository-template/.github/workflows/validate.yml +125 -0
  133. package/docs/reference/example-workflow-repository-template/README.md +268 -0
  134. package/docs/reference/example-workflow-repository-template/workflows/example-workflow.json +80 -0
  135. package/docs/reference/external-workflow-repositories.md +916 -0
  136. package/docs/reference/feature-flags-architecture.md +472 -0
  137. package/docs/reference/feature-flags.md +349 -0
  138. package/docs/reference/god-tier-workflow-validation.md +272 -0
  139. package/docs/reference/loop-optimization.md +209 -0
  140. package/docs/reference/loop-validation.md +176 -0
  141. package/docs/reference/loops.md +465 -0
  142. package/docs/reference/mcp-platform-constraints.md +59 -0
  143. package/docs/reference/recovery.md +88 -0
  144. package/docs/reference/releases.md +177 -0
  145. package/docs/reference/troubleshooting.md +105 -0
  146. package/docs/reference/workflow-execution-contract.md +998 -0
  147. package/docs/roadmap/README.md +22 -0
  148. package/docs/roadmap/legacy-planning-status.md +103 -0
  149. package/docs/roadmap/now-next-later.md +70 -0
  150. package/docs/roadmap/open-work-inventory.md +389 -0
  151. package/docs/tickets/README.md +39 -0
  152. package/docs/tickets/next-up.md +76 -0
  153. package/docs/workflow-management.md +317 -0
  154. package/docs/workflow-templates.md +423 -0
  155. package/docs/workflow-validation.md +184 -0
  156. package/docs/workflows.md +254 -0
  157. package/package.json +3 -1
  158. package/spec/authoring-spec.json +61 -16
  159. package/workflows/workflow-for-workflows.json +252 -93
  160. package/workflows/workflow-for-workflows.v2.json +188 -77
@@ -0,0 +1,54 @@
1
+ # Design Review: Consolidate WORKRAIL_DEV_STALENESS into WORKRAIL_DEV
2
+
3
+ ## Tradeoff Review
4
+
5
+ | Tradeoff | Status | Notes |
6
+ |---|---|---|
7
+ | `WORKRAIL_DEV_STALENESS` env var stops working | Acceptable | Never publicly documented; not present in any checked-in configs |
8
+ | Per-call DI resolution instead of module-level const | Acceptable | `isDevMode()` is a map lookup; `dev-mode.ts` has fallback for uninitialized DI |
9
+ | 5 documentation locations to update | Manageable | All identified; none in shipped user-facing docs requires backward-compat consideration |
10
+
11
+ ## Failure Mode Review
12
+
13
+ | Failure mode | Handled | Notes |
14
+ |---|---|---|
15
+ | DI container uninitialized at call time | Yes | `dev-mode.ts` try/catch fallback to `process.env['WORKRAIL_DEV']` |
16
+ | Test regression from default param change | Yes | All tests pass `devMode` explicitly; default is not exercised by tests |
17
+ | Missing a doc update | Low risk | 5 locations identified; missing one would be cosmetic only |
18
+
19
+ ## Runner-Up / Simpler Alternative Review
20
+
21
+ - **Runner-up (explicit call sites):** No strengths worth borrowing. Strictly more code for the same behavior.
22
+ - **Simpler variant:** None exists. The selected approach is already the minimum change.
23
+
24
+ ## Philosophy Alignment
25
+
26
+ All relevant principles satisfied:
27
+ - DI for boundaries: `isDevMode()` via `IFeatureFlagProvider`
28
+ - Determinism: flag is stable during a request
29
+ - YAGNI: no deprecated alias kept
30
+ - Document "why": JSDoc updated
31
+ - Immutability: `shouldShowStaleness` signature unchanged
32
+
33
+ No philosophy tensions.
34
+
35
+ ## Findings
36
+
37
+ No red or orange findings. One yellow:
38
+
39
+ **Yellow:** The design doc `docs/design/workrail-config-file-discovery.md` contains 3 references to `WORKRAIL_DEV_STALENESS` (lines 75, 147, 436). This is a historical design document, not a runtime or user-facing doc. Updating it is optional but recommended for consistency.
40
+
41
+ ## Recommended Revisions
42
+
43
+ None required. The implementation plan is:
44
+ 1. `src/mcp/handlers/v2-workflow.ts`: Remove `DEV_STALENESS` const; change default param; add `isDevMode` import; update JSDoc on `shouldShowStaleness`.
45
+ 2. `src/config/feature-flags.ts` line 109: Update description to include staleness.
46
+ 3. `src/config/config-file.ts` lines 10 and 32: Remove `WORKRAIL_DEV_STALENESS` from comments.
47
+ 4. `AGENTS.md` line 141: Change to `WORKRAIL_DEV=1`.
48
+ 5. `docs/authoring-v2.md` line 495: Change to `WORKRAIL_DEV=1`.
49
+ 6. `docs/configuration.md` line 44: Remove `WORKRAIL_DEV_STALENESS`.
50
+ 7. (Optional) `docs/design/workrail-config-file-discovery.md`: Update 3 references.
51
+
52
+ ## Residual Concerns
53
+
54
+ None. This is a low-risk, well-scoped change.
@@ -0,0 +1,48 @@
1
+ # Design Review: Walk Cache, Depth Limit, Skip Dirs, Graceful Degradation
2
+
3
+ ## Tradeoff Review
4
+
5
+ | Tradeoff | Acceptable? | Conditions for Failure |
6
+ |---|---|---|
7
+ | Non-cancelling timeout | Yes | Would fail if depth limit + skip dirs didn't bound the walk; they do |
8
+ | Module-level mutable Map cache | Yes | Would fail if Node.js were multi-threaded; it is not |
9
+ | 30s staleness window | Yes | Would fail if users frequently create/delete .workrail dirs within 30s; unlikely |
10
+ | listRememberedRoots returning [] | Yes | Acceptable because callers cannot recover from throws anyway |
11
+
12
+ ## Failure Mode Review
13
+
14
+ | Failure Mode | Mitigation | Risk Level |
15
+ |---|---|---|
16
+ | Depth guard at wrong position | Test #8 (depth boundary) catches it | Low (test-covered) |
17
+ | Cache key collision | `path.resolve` + sort + `\0` separator prevents it | Low |
18
+ | Timeout error propagating out | try/catch inside `createWorkflowReaderForRequest` | Low |
19
+ | withTimeout import path in request-workflow-reader.ts | Import as `./with-timeout.js` (same shared/ dir) | Low |
20
+ | Existing tests poisoning cache | Unique mkdtempSync paths = different cache keys; afterEach clears | Low |
21
+
22
+ ## Runner-Up / Simpler Alternative Review
23
+
24
+ No real runner-up. The spec fully prescribes the implementation. The selected design is already the simplest approach that satisfies all acceptance criteria.
25
+
26
+ ## Philosophy Alignment
27
+
28
+ **Satisfied:** Errors are data, immutability (Set), document-why, validate-at-boundaries, compose with small pure functions, YAGNI.
29
+
30
+ **Tensions:**
31
+ - Module-level mutable Map: acceptable -- mutation is confined and `clearWalkCacheForTesting()` makes the boundary explicit
32
+ - Time-dependent TTL behavior: acceptable -- documented and short
33
+
34
+ ## Findings
35
+
36
+ **Yellow (advisory):**
37
+ 1. The depth guard MUST be placed INSIDE the loop, AFTER the `.workrail` entry check, BEFORE the recursive call. Placing it at the top of the function would miss `.workrail` entries at exactly depth 5. This is the highest-risk implementation detail.
38
+ 2. The timeout try/catch must wrap ONLY the `discoverRootedWorkflowDirectories` call, not the entire `createWorkflowReaderForRequest` function body.
39
+
40
+ **No Red or Orange findings.** The design is straightforward and well-bounded.
41
+
42
+ ## Recommended Revisions
43
+
44
+ None. Proceed with implementation as planned.
45
+
46
+ ## Residual Concerns
47
+
48
+ - The background walk after timeout could theoretically consume file handles on very large repos with many remembered roots. The depth limit and skip dirs list mitigate this significantly. Not a concern in practice for the expected workload.
@@ -0,0 +1,142 @@
1
+ # Implementation Plan: Consolidate WORKRAIL_DEV_STALENESS into WORKRAIL_DEV
2
+
3
+ ## Problem Statement
4
+
5
+ Two separate dev flags exist:
6
+ - `WORKRAIL_DEV=1` -- controls perf timing and the perf console endpoint via DI `isDevMode()`
7
+ - `WORKRAIL_DEV_STALENESS=1` -- controls staleness visibility for all workflow categories via a direct `process.env` read in `src/mcp/handlers/v2-workflow.ts`
8
+
9
+ The module-level `DEV_STALENESS` const bypasses the DI-injected feature flag system, making it inconsistent with other dev features and preventing it from being set via `~/.workrail/config.json`.
10
+
11
+ ## Acceptance Criteria
12
+
13
+ 1. `WORKRAIL_DEV=1` enables staleness visibility for all workflow categories (including built-in and legacy_project).
14
+ 2. `WORKRAIL_DEV_STALENESS` env var is no longer supported (silently ignored if set).
15
+ 3. `npx vitest run` passes with no new failures.
16
+ 4. `npm run build` compiles cleanly.
17
+ 5. All documentation references to `WORKRAIL_DEV_STALENESS` are updated to `WORKRAIL_DEV`.
18
+
19
+ ## Non-Goals
20
+
21
+ - Backward compatibility for `WORKRAIL_DEV_STALENESS` (never publicly documented).
22
+ - Updating worktree copies of affected files (separate branches).
23
+ - Any change to the `shouldShowStaleness()` public API or its explicit test coverage.
24
+
25
+ ## Philosophy-Driven Constraints
26
+
27
+ - **DI for boundaries** -- no raw `process.env` reads in handler code for feature flags.
28
+ - **YAGNI** -- no deprecated alias for `WORKRAIL_DEV_STALENESS`.
29
+ - **Immutability** -- `shouldShowStaleness()` signature unchanged (optional `devMode` param stays).
30
+ - **Document "why"** -- JSDoc updated to explain consolidation.
31
+
32
+ ## Invariants
33
+
34
+ - `shouldShowStaleness()` remains exported and its signature is unchanged.
35
+ - Tests that pass `devMode` explicitly continue to work unchanged.
36
+ - Call sites that pass no second arg now get `isDevMode()` as the effective default.
37
+ - `isDevMode()` already handles uninitialized DI via fallback to `process.env['WORKRAIL_DEV']`.
38
+
39
+ ## Selected Approach
40
+
41
+ Change `shouldShowStaleness`'s default parameter from `= DEV_STALENESS` to `= isDevMode()`. Remove the `DEV_STALENESS` const and its `process.env` read. Add `isDevMode` import.
42
+
43
+ **Runner-up:** Pass `isDevMode()` explicitly at each of the 2 call sites. Lost because it's more verbose with no benefit.
44
+
45
+ ## Vertical Slices
46
+
47
+ ### Slice 1: Code change in `src/mcp/handlers/v2-workflow.ts`
48
+
49
+ Files:
50
+ - `src/mcp/handlers/v2-workflow.ts`
51
+
52
+ Changes:
53
+ 1. Remove lines 46-49 (the `DEV_STALENESS` const and its JSDoc comment).
54
+ 2. Add `import { isDevMode } from '../dev-mode.js'` near the top (with other imports from `../`).
55
+ 3. Change `shouldShowStaleness` default parameter from `= DEV_STALENESS` to `= isDevMode()`.
56
+ 4. Update the JSDoc comment on `shouldShowStaleness` to remove the reference to `DEV_STALENESS` and explain that it now delegates to `isDevMode()`.
57
+
58
+ ### Slice 2: Update `devMode` flag description in `src/config/feature-flags.ts`
59
+
60
+ Files:
61
+ - `src/config/feature-flags.ts`
62
+
63
+ Changes:
64
+ 1. Line 109: Replace the description to include staleness visibility:
65
+ ```
66
+ 'Enable development features: staleness visibility for all workflow categories, structured tool-call timing on stderr, and /api/v2/perf/tool-calls endpoint'
67
+ ```
68
+
69
+ ### Slice 3: Update comments in `src/config/config-file.ts`
70
+
71
+ Files:
72
+ - `src/config/config-file.ts`
73
+
74
+ Changes:
75
+ 1. Line 10 (module JSDoc): Remove `WORKRAIL_DEV_STALENESS` from the exclusion list.
76
+ 2. Lines 32-33 (ALLOWED_CONFIG_FILE_KEYS comment): Remove the bullet about `WORKRAIL_DEV_STALENESS`.
77
+
78
+ ### Slice 4: Update `AGENTS.md`
79
+
80
+ Files:
81
+ - `AGENTS.md`
82
+
83
+ Changes:
84
+ 1. Line 141: Change `WORKRAIL_DEV_STALENESS=1` to `WORKRAIL_DEV=1` and update the description.
85
+
86
+ ### Slice 5: Update `docs/authoring-v2.md`
87
+
88
+ Files:
89
+ - `docs/authoring-v2.md`
90
+
91
+ Changes:
92
+ 1. Line 495: Change `Set WORKRAIL_DEV_STALENESS=1` to `Set WORKRAIL_DEV=1`.
93
+
94
+ ### Slice 6: Update `docs/configuration.md`
95
+
96
+ Files:
97
+ - `docs/configuration.md`
98
+
99
+ Changes:
100
+ 1. Line 44: Remove `WORKRAIL_DEV_STALENESS` from the excluded keys list.
101
+
102
+ ### Slice 7 (Optional): Update `docs/design/workrail-config-file-discovery.md`
103
+
104
+ Files:
105
+ - `docs/design/workrail-config-file-discovery.md`
106
+
107
+ Changes:
108
+ 1. Lines 75, 147, 436: Update references from `WORKRAIL_DEV_STALENESS` to `WORKRAIL_DEV` or remove the separate entry.
109
+
110
+ ## Test Design
111
+
112
+ - Run `npx vitest run` -- all existing tests should pass. No new tests needed; the logic in `shouldShowStaleness` is unchanged and the existing unit tests in `tests/unit/mcp/workflow-staleness.test.ts` cover it with explicit `devMode` parameters.
113
+ - Run `npm run build` -- confirm TypeScript compiles cleanly after the import and default parameter changes.
114
+
115
+ ## Risk Register
116
+
117
+ | Risk | Likelihood | Severity | Mitigation |
118
+ |---|---|---|---|
119
+ | DI container uninitialized when `isDevMode()` called | Very low | Low | `dev-mode.ts` fallback to `process.env['WORKRAIL_DEV']` |
120
+ | Missing a doc location | Low | Cosmetic | All 6+1 locations identified and listed |
121
+ | TypeScript import error | Low | Caught at build | `npm run build` verification |
122
+
123
+ ## PR Packaging Strategy
124
+
125
+ Single PR: `feat(mcp): consolidate WORKRAIL_DEV_STALENESS into WORKRAIL_DEV`
126
+
127
+ ## Philosophy Alignment
128
+
129
+ | Principle | Status | Why |
130
+ |---|---|---|
131
+ | DI for boundaries | Satisfied | `isDevMode()` uses DI-injected `IFeatureFlagProvider` |
132
+ | Determinism | Satisfied | Flag is stable during a request |
133
+ | YAGNI | Satisfied | No deprecated alias kept |
134
+ | Document "why" | Satisfied | JSDoc and docs updated |
135
+ | Immutability by default | Satisfied | `shouldShowStaleness` signature unchanged |
136
+
137
+ ## Plan Confidence
138
+
139
+ - `unresolvedUnknownCount`: 0
140
+ - `planConfidenceBand`: High
141
+ - `estimatedPRCount`: 1
142
+ - `followUpTickets`: None
@@ -0,0 +1,141 @@
1
+ # Implementation Plan: Walk Cache, Depth Limit, Skip Dirs, Graceful Degradation
2
+
3
+ ## Problem Statement
4
+
5
+ The `walkForRootedWorkflowDirectories` function has no depth limit and only skips `.git` and `node_modules`, making it vulnerable to very deep directory trees and slow on large repos. There is no caching, so every call to `createWorkflowReaderForRequest` does a fresh walk. `listRememberedRoots` throws on store failure, preventing callers from recovering. Both v2-workflow.ts handlers let `createWorkflowReaderForRequest` throws propagate uncaught, crashing handler responses.
6
+
7
+ ## Acceptance Criteria
8
+
9
+ 1. `shouldSkipDirectory` uses a `const SKIP_DIRS = new Set([...])` with 20 entries
10
+ 2. `walkForRootedWorkflowDirectories` takes a `depth` param (default 0), stops recursing at depth >= 5, logs to console.debug if `WORKRAIL_DEV=1`, and still discovers `.workrail` entries at exactly depth 5
11
+ 3. `discoverRootedWorkflowDirectories` has a module-level Map cache keyed on sorted resolved roots joined by `\0`, TTL 30s, exports `clearWalkCacheForTesting()`
12
+ 4. `createWorkflowReaderForRequest` wraps the discovery call in a 10s timeout; timeout errors are caught inside the function and return the graceful fallback with `managedStoreError` set
13
+ 5. `listRememberedRoots` returns `[]` on store failure (logs error, does not throw)
14
+ 6. `handleV2ListWorkflows` wraps `createWorkflowReaderForRequest` in try/catch returning `errNotRetryable('INTERNAL_ERROR', ...)`
15
+ 7. `handleV2InspectWorkflow` same fix
16
+ 8. Test: depth-5 found, depth-6 not found
17
+ 9. Test: cache hit (strict reference equality on second call)
18
+ 10. Test: skip list (build/ subdirectory not discovered)
19
+ 11. `npx vitest run` passes
20
+
21
+ ## Non-Goals
22
+
23
+ - Cancelling the background walk after timeout
24
+ - Making TTL configurable
25
+ - Adding cache to `discoverWorkflowDirectoriesUnderRoot`
26
+ - Changing the v2 execution engine or session handling
27
+
28
+ ## Philosophy-Driven Constraints
29
+
30
+ - `SKIP_DIRS` must be a `const Set` (immutability by default)
31
+ - `listRememberedRoots` must return `[]` on error (errors are data)
32
+ - Comments must explain WHY: cache TTL tradeoff, non-cancelling timeout, why `[]` not throw
33
+ - Try/catch in v2-workflow.ts must return structured `errNotRetryable` (errors are data)
34
+ - No new abstractions beyond what is specified (YAGNI)
35
+
36
+ ## Invariants
37
+
38
+ - A `.workrail` entry at depth 5 MUST be discoverable
39
+ - Cache key uses `path.resolve` before building, then `sort().join('\0')`
40
+ - The timeout error must NOT propagate out of `createWorkflowReaderForRequest`
41
+ - `clearWalkCacheForTesting()` must be exported from `request-workflow-reader.ts`
42
+
43
+ ## Selected Approach
44
+
45
+ Implement all 10 changes as specified. No runner-up. See design-candidates doc.
46
+
47
+ **Critical implementation detail:** The depth guard must be placed INSIDE the loop in `walkForRootedWorkflowDirectories`, AFTER the `.workrail` check, BEFORE the recursive call:
48
+ ```typescript
49
+ if (entry.name === '.workrail') { ... continue; }
50
+ if (depth >= MAX_WALK_DEPTH) {
51
+ if (process.env.WORKRAIL_DEV === '1') console.debug('[workrail] walk depth limit reached at:', currentDirectory);
52
+ continue;
53
+ }
54
+ await walkForRootedWorkflowDirectories(entryPath, discoveredPaths, depth + 1);
55
+ ```
56
+
57
+ ## Vertical Slices
58
+
59
+ ### Slice 1: `request-workflow-reader.ts` structural changes
60
+ - Replace `shouldSkipDirectory` with `SKIP_DIRS` Set
61
+ - Add `MAX_WALK_DEPTH = 5` constant
62
+ - Add `depth` param to `walkForRootedWorkflowDirectories`
63
+ - Fix depth guard placement
64
+
65
+ ### Slice 2: `request-workflow-reader.ts` cache
66
+ - Add module-level `walkCache: Map<string, { result: WorkflowRootDiscoveryResult; expiresAt: number }>`
67
+ - Add cache key derivation and TTL check in `discoverRootedWorkflowDirectories`
68
+ - Export `clearWalkCacheForTesting()`
69
+ - Add cache comment
70
+
71
+ ### Slice 3: `request-workflow-reader.ts` timeout + listRememberedRoots fix
72
+ - Import `withTimeout` from `./with-timeout.js`
73
+ - Add `DISCOVERY_TIMEOUT_MS = 10_000`
74
+ - Wrap `discoverRootedWorkflowDirectories` call in try/catch with timeout inside `createWorkflowReaderForRequest`
75
+ - Fix `listRememberedRoots` to return `[]` on error
76
+
77
+ ### Slice 4: `v2-workflow.ts` handler fixes
78
+ - Wrap `createWorkflowReaderForRequest` ternary in try/catch in `handleV2ListWorkflows`
79
+ - Same for `handleV2InspectWorkflow`
80
+
81
+ ### Slice 5: Tests
82
+ - Add depth boundary test (#8)
83
+ - Add cache hit test (#9)
84
+ - Add skip list test (#10)
85
+ - Add `afterEach(() => clearWalkCacheForTesting())` to test suite
86
+
87
+ ## Test Design
88
+
89
+ **Test 8 (depth boundary):**
90
+ - Create temp dir with `.workrail/workflows` at depth 5 (5 levels deep from root)
91
+ - Create temp dir with `.workrail/workflows` at depth 6 (6 levels deep from root)
92
+ - Call `discoverRootedWorkflowDirectories([root])`
93
+ - Assert depth-5 path is in `discovered`
94
+ - Assert depth-6 path is NOT in `discovered`
95
+ - Must call `clearWalkCacheForTesting()` in afterEach
96
+
97
+ **Test 9 (cache hit):**
98
+ - Create a unique temp dir with a `.workrail/workflows` entry
99
+ - Call `discoverRootedWorkflowDirectories([root])` twice
100
+ - Assert `result1 === result2` (strict reference equality)
101
+
102
+ **Test 10 (skip list):**
103
+ - Create temp dir with `.workrail/workflows` at root level (should be found)
104
+ - Create `build/.workrail/workflows` inside the same root (should NOT be found)
105
+ - Call `discoverRootedWorkflowDirectories([root])`
106
+ - Assert root-level path is in `discovered`
107
+ - Assert build/ path is NOT in `discovered`
108
+
109
+ ## Risk Register
110
+
111
+ | Risk | Likelihood | Impact | Mitigation |
112
+ |---|---|---|---|
113
+ | Depth guard placed at wrong position | Low | Medium | Test #8 catches it |
114
+ | Cache TTL check using wrong comparison | Low | Low | Test #9 catches stale results |
115
+ | withTimeout import path wrong | Low | Low | TypeScript compile error will surface it |
116
+ | try/catch wrapping too much of createWorkflowReaderForRequest | Low | Medium | Code review + test |
117
+
118
+ ## PR Packaging Strategy
119
+
120
+ Single PR. All changes are in two source files and one test file. The changes are tightly coupled (cache + timeout must both be present; handler fixes are independent but small).
121
+
122
+ ## Philosophy Alignment Per Slice
123
+
124
+ | Slice | Principle | Status |
125
+ |---|---|---|
126
+ | Slice 1: SKIP_DIRS Set | Immutability by default | Satisfied -- const Set |
127
+ | Slice 1: depth limit | Validate at boundaries | Satisfied -- guard inside walk |
128
+ | Slice 2: module-level Map cache | Immutability by default | Tension -- mutation confined, clearWalkCacheForTesting() export makes it explicit |
129
+ | Slice 2: cache comment | Document why not what | Satisfied |
130
+ | Slice 3: listRememberedRoots fix | Errors are data | Satisfied -- returns [] not throws |
131
+ | Slice 3: timeout | Validate at boundaries | Satisfied -- at createWorkflowReaderForRequest boundary |
132
+ | Slice 4: handler fixes | Errors are data | Satisfied -- errNotRetryable not throw |
133
+ | Slice 5: tests | Prefer fakes over mocks | Satisfied -- real temp dirs |
134
+
135
+ ## Unresolved Unknowns
136
+
137
+ `unresolvedUnknownCount`: 0
138
+
139
+ ## Plan Confidence Band
140
+
141
+ High
@@ -0,0 +1,229 @@
1
+ # Layer 3b Ghost Nodes -- Design Candidates
2
+
3
+ *Investigative material. Not a final decision.*
4
+
5
+ ---
6
+
7
+ ## Problem Understanding
8
+
9
+ ### What Layer 3b Means
10
+
11
+ The console's session detail view shows a DAG of workflow execution nodes via `RunLineageDag` (xyflow). Currently only nodes that were actually created appear in the DAG. Layer 3b adds "ghost nodes" for steps that were skipped due to `runCondition` evaluating to false at the top level:
12
+
13
+ - Skipped nodes rendered at 0.25 opacity with a `[ SKIPPED ]` badge
14
+ - Makes the DAG show the full workflow shape, not just the executed path
15
+ - Helps users understand why a sparse DAG looks sparse (e.g. a small-task fast path jumped from phase 0 to phase 6)
16
+
17
+ ### Core Tensions
18
+
19
+ 1. **Labels vs simplicity**: Step labels (human-readable titles like "Phase 0: Triage and classify") are what make ghost nodes useful. But resolving them requires the compiled workflow, which is a backend I/O operation. Skipping labels is simpler but produces raw step IDs (`routine-context-gathering-depth`) that users can't parse.
20
+
21
+ 2. **Type safety vs ease**: Adding `isGhost: boolean` to `ConsoleDagNode` is one line but violates "make illegal states unrepresentable" -- ghost steps have no `hasRecap`, `hasFailedValidations`, `isTip`, `parentNodeId`, `createdAtEventIndex`, etc. A separate `ConsoleGhostStep` interface is correct but requires touching both mirrored type files.
22
+
23
+ 3. **Positioning accuracy vs scope**: Exact column positioning requires knowing the workflow's step index order, which the frontend doesn't have without an extra API call. Approximate positioning (sort by trace event index, which matches `nextTopLevel`'s evaluation order for top-level steps) is sufficient for MVP.
24
+
25
+ 4. **ReactFlow nodes vs overlays**: ReactFlow nodes participate in the layout graph (edges could connect to them). Absolute-positioned divs (like loop brackets) are simpler and sufficient since ghost nodes have no edges.
26
+
27
+ ### Existing Patterns
28
+
29
+ Layer 3a (edge cause diamonds, loop brackets, CAUSE footer) set the pattern for this layer:
30
+ - New data added as a field on `ConsoleDagRun` (`executionTraceSummary`)
31
+ - Frontend extracts overlay positions in separate `useMemo` blocks in `RunLineageDag`
32
+ - Overlay components rendered as absolute-positioned divs, NOT as ReactFlow nodes
33
+ - Pure logic functions live in `session-detail-use-cases.ts`
34
+
35
+ Relevant existing code:
36
+ - `console/src/components/RunLineageDag.tsx` -- sub-features A/B/C as separate `useMemo` blocks
37
+ - `console/src/views/session-detail-use-cases.ts` -- `groupTraceEntries`, `findEdgeCauseItem`, `getNodeRoutingItems`
38
+ - `src/v2/usecases/console-service.ts` -- `projectSessionDetail`, `resolveStepLabels`, `extractStepTitlesFromCompiled`
39
+ - `src/v2/durable-core/domain/decision-trace-builder.ts` -- `traceStepRunConditionSkipped` emits `evaluated_condition` + `step_id` ref + `SKIP:` summary
40
+
41
+ ### Where Skipped Steps Live
42
+
43
+ `traceStepRunConditionSkipped()` is called in `workflow-interpreter.ts` `nextTopLevel()` for each top-level step whose `runCondition` returned false. This emits:
44
+ ```
45
+ { kind: 'evaluated_condition', summary: 'SKIP: taskComplexity (equals)', refs: [{kind: 'step_id', value: 'phase-2-deep-exploration'}] }
46
+ ```
47
+
48
+ These appear in `run.executionTraceSummary.items`. The step IDs are recoverable from the frontend without backend changes. Labels are not.
49
+
50
+ ### What Makes This Hard
51
+
52
+ - Ghost node labels require resolving a compiled workflow by hash -- the backend already does this for real node labels but via snapshot refs. Ghost steps need direct lookup by stepId from the compiled workflow.
53
+ - Two mirrored type files (`console-types.ts` + `console/src/api/types.ts`) must be kept in sync manually.
54
+ - Ghost nodes must be positioned within the ReactFlow canvas coordinate space -- they are siblings of real nodes in the same scrollable div.
55
+ - The `evaluateCondition` trace items also appear for loop conditions (loop body entries) -- those must NOT become ghost nodes. Filter: only top-level step skips have `step_id` refs (not `loop_id` refs).
56
+
57
+ ---
58
+
59
+ ## Philosophy Constraints
60
+
61
+ From `AGENTS.md` and `console/CLAUDE.md`:
62
+
63
+ - **Make illegal states unrepresentable**: Ghost steps have fundamentally different shape from real nodes. Separate type required.
64
+ - **Immutability by default**: All new interfaces must use `readonly` everywhere.
65
+ - **Pure functions at use-case layer**: Ghost step extraction and positioning are pure functions in `session-detail-use-cases.ts`.
66
+ - **Validate at boundaries**: Backend is the boundary for label resolution (I/O).
67
+ - **YAGNI**: Don't add step-order-exact positioning if approximate is sufficient.
68
+ - **Compose with small pure functions**: One function per concern -- extraction, positioning, rendering are separate.
69
+
70
+ No philosophy conflicts found between stated rules and repo patterns.
71
+
72
+ ---
73
+
74
+ ## Impact Surface
75
+
76
+ Adding `skippedSteps: readonly ConsoleGhostStep[]` to `ConsoleDagRun` is additive. Consumers:
77
+ - `RunLineageDag.tsx` -- reads `run.skippedSteps` (new `useMemo` block)
78
+ - `session-detail-use-cases.ts` -- positioning pure function
79
+ - Backend `console-service.ts` -- populates the field
80
+ - `console-types.ts` + `console/src/api/types.ts` -- both must be updated (manual mirror)
81
+ - Existing test assertions on `ConsoleDagRun` shape -- `skippedSteps` is additive; old tests still pass
82
+
83
+ The `buildLineageDagModel` signature does NOT need to change -- ghost positioning is a separate pure function consuming the layout model output.
84
+
85
+ ---
86
+
87
+ ## Candidates
88
+
89
+ ### Candidate A: Frontend-only ghost nodes (no labels)
90
+
91
+ **Summary**: Extract skipped step IDs from `evaluated_condition` SKIP trace items on the frontend; render ghost nodes without step labels (show raw step ID).
92
+
93
+ **Tensions resolved**: Zero backend changes. No mirrored type file sync needed.
94
+ **Tensions accepted**: No step labels. Ghost nodes show raw IDs like `routine-context-gathering-depth`.
95
+
96
+ **Boundary**: `session-detail-use-cases.ts` -- new pure function `getSkippedStepsFromTrace(items: readonly ConsoleExecutionTraceItem[]): readonly string[]` returning step IDs. `RunLineageDag.tsx` -- new sub-feature D `useMemo` computes ghost positions from active lineage model, renders absolute-positioned `GhostNodeOverlay` components.
97
+
98
+ **Why this boundary**: Consistent with Layer 3a pattern. Pure function in use-case layer, rendering in view.
99
+
100
+ **Failure mode**: Raw step IDs are unreadable to users. Feature ships but provides poor UX. Silent failure -- no error, just confusing UI.
101
+
102
+ **Repo pattern**: Directly adapts Layer 3a overlay pattern. No new fields.
103
+
104
+ **Gain**: Zero backend risk, minimal diff, fast to ship.
105
+ **Give up**: Step labels (the primary user-facing value of the feature).
106
+
107
+ **Scope**: Too narrow -- labels are 80% of the value.
108
+
109
+ **Philosophy fit**: Honors YAGNI, small pure functions. Conflicts with "prefer explicit domain types" if ghost step is just `string`.
110
+
111
+ ---
112
+
113
+ ### Candidate B: Backend-emitted skippedSteps with labels (recommended)
114
+
115
+ **Summary**: Add `skippedSteps: readonly ConsoleGhostStep[]` to `ConsoleDagRun`; backend populates it by scanning `executionTraceSummary.items` for SKIP entries and resolving titles from the already-loaded compiled workflow.
116
+
117
+ **Tensions resolved**: Full step labels. Named `ConsoleGhostStep` interface. Follows exact Layer 3a precedent (`executionTraceSummary` was added the same way). Approximate positioning is sufficient.
118
+ **Tensions accepted**: Two mirrored type files must sync. Backend `projectSessionDetail` grows slightly.
119
+
120
+ **New types**:
121
+ ```typescript
122
+ // In both console-types.ts and console/src/api/types.ts
123
+ export interface ConsoleGhostStep {
124
+ readonly stepId: string;
125
+ readonly stepLabel: string | null;
126
+ }
127
+ // ConsoleDagRun gains:
128
+ readonly skippedSteps: readonly ConsoleGhostStep[];
129
+ ```
130
+
131
+ **Backend helper** in `console-service.ts`:
132
+ ```typescript
133
+ function resolveSkippedSteps(
134
+ executionTrace: ConsoleExecutionTraceSummary | null,
135
+ workflowHash: string | null,
136
+ titlesByHash: Map<string, ReadonlyMap<string, string>>,
137
+ ): readonly ConsoleGhostStep[]
138
+ ```
139
+ Filters `items` for `evaluated_condition` with `step_id` ref and `SKIP:` summary prefix; resolves labels from `titlesByHash` (already loaded by `resolveStepLabels`).
140
+
141
+ **Frontend pure function** in `session-detail-use-cases.ts`:
142
+ ```typescript
143
+ export function positionGhostNodes(
144
+ skippedSteps: readonly ConsoleGhostStep[],
145
+ model: LineageDagModel,
146
+ ): readonly PositionedGhostNode[]
147
+ ```
148
+ Places ghost nodes at `depth = maxActiveDepth + 1`, evenly spaced in a dedicated ghost lane below the active lineage. `PositionedGhostNode` has `{ stepId, stepLabel, x, y }`.
149
+
150
+ **Rendering in `RunLineageDag.tsx`**: New sub-feature D `useMemo` + `GhostNodeOverlay` absolute-positioned component. Not ReactFlow nodes. Not clickable (`pointerEvents: 'none'` on the node body, but tooltip on hover to show full step label).
151
+
152
+ **Why this boundary**: `ConsoleGhostStep` as backend DTO matches how `executionTraceSummary` was added. Label resolution is I/O -- belongs at the backend boundary. Pure positioning function belongs in use-case layer.
153
+
154
+ **Failure mode**: If `workflowHash` is null (no-workflow run), labels fall back to null, and ghost nodes show step ID. Same graceful degradation as real node labels.
155
+
156
+ **Repo pattern**: Directly follows how Layer 3a added `executionTraceSummary` to `ConsoleDagRun`. Same file sequence, same pattern.
157
+
158
+ **Gain**: Full step labels. Named type. Clean architecture. Graceful null fallback.
159
+ **Give up**: Two type files must sync. Moderate backend diff.
160
+
161
+ **Scope**: Best-fit. Satisfies acceptance criteria without extra API endpoints.
162
+
163
+ **Philosophy fit**: Honors all principles. Explicit domain type, immutability, pure functions, validate at boundaries.
164
+
165
+ ---
166
+
167
+ ### Candidate C: ReactFlow custom node type with exact positioning
168
+
169
+ **Summary**: Add ghost steps as `Node<GhostNodeData>[]` in the ReactFlow graph with `nodeType: 'ghost'`, positioned at the exact workflow-step-order column using the step's index in the compiled workflow fetched from a new frontend API call.
170
+
171
+ **Tensions resolved**: Exact column positioning matching workflow step order. Ghost nodes as real ReactFlow nodes (future edge support). Hover/click behavior handled by ReactFlow.
172
+ **Tensions accepted**: Extra API call creates loading race. New API endpoint needed. Custom `nodeTypes` map requires memoization in `RunLineageDag`.
173
+
174
+ **Boundary**: New `useWorkflowStepsRepository` hook fetching `/api/v2/workflows/:hash/steps` (or reuse existing catalog endpoint). Session detail ViewModel joins session data + workflow steps. `buildLineageDagModel` extended to accept ghost steps with exact column indices.
175
+
176
+ **Why this boundary**: Solves exact positioning by having the frontend know step order. But this is a new data dependency not currently in the session detail view's data model.
177
+
178
+ **Failure mode**: If workflow steps API call fails, ghost nodes disappear entirely (non-graceful). Loading state race: ghost nodes flicker in when the secondary call resolves. `nodeTypes` must be defined outside render or in `useMemo` (easy to get wrong, causes ReactFlow remount warnings).
179
+
180
+ **Repo pattern**: Departs significantly. No existing custom node types. No extra API calls in session detail view. Over-engineered for the goal.
181
+
182
+ **Gain**: Exact positioning. Future edge support.
183
+ **Give up**: Extra API call. Loading complexity. New endpoint needed. Significant scope expansion.
184
+
185
+ **Scope**: Too broad. Not justified by acceptance criteria.
186
+
187
+ **Philosophy fit**: Conflicts with YAGNI. Honors explicit types and exhaustiveness.
188
+
189
+ ---
190
+
191
+ ## Comparison and Recommendation
192
+
193
+ | Criterion | A | B | C |
194
+ |---|---|---|---|
195
+ | Step labels | No | Yes | Yes |
196
+ | Backend changes | None | Small additive | New endpoint |
197
+ | Type safety | Weak | Strong | Strong |
198
+ | Positioning | Approximate | Approximate | Exact |
199
+ | Failure mode | Silent bad UX | Graceful null | Hard failure + flicker |
200
+ | Repo pattern fit | Direct | Direct | Departs |
201
+ | YAGNI | Over-honors | Balanced | Violates |
202
+
203
+ **Recommendation: Candidate B.**
204
+
205
+ Labels are not optional -- raw step IDs are meaningless to users. Candidate A saves 30 minutes and ships a feature that doesn't work. Candidate C solves an exact-positioning problem that the acceptance criteria don't require and introduces loading complexity.
206
+
207
+ Candidate B follows the exact path Layer 3a established (`executionTraceSummary` was added as a backend-assembled field on `ConsoleDagRun`). The backend helper is small and reuses `extractStepTitlesFromCompiled` which already exists.
208
+
209
+ ---
210
+
211
+ ## Self-Critique
212
+
213
+ **Strongest argument against B**: `projectSessionDetail` is already complex. Every new helper adds cognitive overhead and another I/O fan-out point. A determined advocate for A would argue: "Ship step IDs now, add labels in a follow-up when we have a codegen solution for the mirrored types."
214
+
215
+ **Why A still loses**: The roadmap already says "requires backend to emit skipped step IDs" -- that's an acknowledgment that backend work is expected. The label resolution is 10 lines of code (reuses existing infra). The UX gap is not cosmetic.
216
+
217
+ **What would justify C**: A product requirement saying ghost nodes must appear at their workflow-order column (not just after the active lineage). Evidence required: user feedback that approximate positioning is confusing. Not present.
218
+
219
+ **Invalidating assumption**: If `traceStepRunConditionSkipped` is NOT actually called in the v2 engine (only in the v1 interpreter), then the trace items won't exist and ghost nodes won't appear for v2 sessions. `workflow-interpreter.ts` is the v1 interpreter. Need to verify whether the v2 engine emits equivalent traces. If not, this entire feature is a no-op for v2 sessions and the real work is adding trace emission to the v2 engine first.
220
+
221
+ ---
222
+
223
+ ## Open Questions for the Main Agent
224
+
225
+ 1. **V2 engine trace emission**: Does the v2 durable engine (`src/v2/`) emit `evaluated_condition` trace entries for top-level step `runCondition` evaluations? Or is this only in the v1 interpreter (`workflow-interpreter.ts`)? If v2 doesn't emit these, ghost nodes won't appear for any v2 sessions and the backend/frontend work is pointless without fixing the v2 engine first.
226
+
227
+ 2. **Ghost lane layout**: Should ghost nodes appear in a single horizontal band below the active lineage (all at the same Y, different X)? Or in a vertical column to the right of the active lineage (all at the same X, different Y)? The current layout is horizontal (depth = X axis, lane = Y axis) -- vertical stacking (same depth, different lanes) may be less intuitive.
228
+
229
+ 3. **Deduplication**: If a step is evaluated multiple times across multiple `continue_workflow` calls (e.g., re-entry in a session with multiple runs), the same `step_id` could appear in multiple SKIP trace items. Should ghost nodes be deduplicated by `stepId`? Answer: yes, show each skipped step only once.