@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,93 @@
1
+ # Layer 3b Ghost Nodes -- Design Review Findings
2
+
3
+ *Design review of the Candidate B approach: backend-emitted `skippedSteps` with label resolution.*
4
+
5
+ ---
6
+
7
+ ## Tradeoff Review
8
+
9
+ ### Approximate positioning (trace event index order)
10
+ Safe. `nextTopLevel` iterates `compiled.steps` in array order, so trace event index IS workflow definition order. Hidden assumption (compiler preserves step order) is guaranteed by the workflow compiler. Tradeoff accepted.
11
+
12
+ ### Ghost nodes not clickable
13
+ Acceptable. The `[ TRACE ]` tab in session detail already shows all `evaluated_condition` SKIP items. Ghost nodes can surface the SKIP reason via a hover tooltip (using `hoveredLabel` state already in `RunLineageDag`). No dedicated panel needed.
14
+
15
+ ### Two manually-mirrored type files
16
+ Acceptable with mitigation. Both files updated in the same PR. Frontend adds `run.skippedSteps ?? []` defensive fallback at consumption site in `RunLineageDag`.
17
+
18
+ ### Labels fall back to null when no workflow hash
19
+ Safe. The null fallback is unreachable in practice (no-workflow sessions cannot have `runCondition`). Fallback degrades gracefully to showing raw stepId.
20
+
21
+ ---
22
+
23
+ ## Failure Mode Review
24
+
25
+ ### FM1: Compiled workflow not pinned -> null labels
26
+ **Coverage**: Adequate. Graceful degradation to stepId display. Low risk.
27
+
28
+ ### FM2: Frontend receives skippedSteps as undefined
29
+ **Coverage**: Needs explicit mitigation. Add `run.skippedSteps ?? []` at the `useMemo` consumption site in `RunLineageDag.tsx`. Medium risk in rollback scenarios.
30
+
31
+ ### FM3: Duplicate SKIP entries for same stepId
32
+ **Coverage**: Needs explicit implementation. Backend `resolveSkippedSteps` must deduplicate by stepId using a `Set<string>`. Without this, a step appears as multiple ghost nodes, which is confusing. Medium risk.
33
+
34
+ ### FM4: Ghost nodes clipping at canvas right edge
35
+ **Coverage**: Needs explicit implementation. `graphWidth` in `buildLineageDagModel` (or the ghost positioning function) must ensure the canvas is wide enough to include the ghost column (`depth = maxActiveDepth + 1`). Medium risk -- visually obvious if missed.
36
+
37
+ ### FM5: Ghost nodes in OverviewRail
38
+ **Coverage**: Handled by construction. Ghost nodes never enter `model.nodes`. No action needed.
39
+
40
+ ---
41
+
42
+ ## Runner-Up / Simpler Alternative Review
43
+
44
+ Candidate A (frontend-only, no labels) loses on UX value -- raw step IDs are not readable to users. No elements worth borrowing.
45
+
46
+ No simpler variant exists that preserves label quality without backend involvement. The named `ConsoleGhostStep` interface is the correct type-safe representation; `nodeKind: 'ghost'` on `ConsoleDagNode` would pollute all existing node-handling code.
47
+
48
+ ---
49
+
50
+ ## Philosophy Alignment
51
+
52
+ All core principles satisfied:
53
+ - Make illegal states unrepresentable: ghost steps cannot masquerade as `ConsoleDagNode`
54
+ - Immutability: all `readonly`
55
+ - Validate at boundaries: label resolution at backend
56
+ - Small pure functions: three single-responsibility functions
57
+ - YAGNI: no exact positioning, no click handling, no rail integration
58
+ - Errors are data: `stepLabel: null` not thrown
59
+
60
+ One acceptable tension: exhaustiveness enforcement is not needed for `ConsoleGhostStep` (not a discriminated union participant).
61
+
62
+ ---
63
+
64
+ ## Findings
65
+
66
+ ### Yellow: FM2 -- Type mismatch fallback missing
67
+ The design does not explicitly specify `run.skippedSteps ?? []` at the consumption site. If `skippedSteps` arrives as `undefined` (old backend, rollback), ghost nodes silently disappear. Not a crash, but invisible feature loss.
68
+ **Severity**: Yellow (not a crash, graceful but invisible).
69
+
70
+ ### Yellow: FM3 -- Deduplication not in spec
71
+ Backend `resolveSkippedSteps` spec does not explicitly require dedup by stepId. A session with multiple condition evaluations of the same step would produce duplicate ghost nodes.
72
+ **Severity**: Yellow (confusing UX, not broken).
73
+
74
+ ### Yellow: FM4 -- graphWidth extension not specified
75
+ The `graphWidth` formula in `buildLineageDagModel` does not account for the ghost column. Ghost nodes at `depth = maxActiveDepth + 1` would clip at the right edge of the canvas.
76
+ **Severity**: Yellow (visually broken but easy to fix once noticed).
77
+
78
+ ---
79
+
80
+ ## Recommended Revisions
81
+
82
+ 1. **Add `run.skippedSteps ?? []` fallback** in `RunLineageDag.tsx` at the `useMemo` that reads skipped steps.
83
+ 2. **Specify dedup requirement** in `resolveSkippedSteps`: collect stepIds into a `Set<string>` and skip already-seen stepIds.
84
+ 3. **Extend graphWidth** in the ghost positioning function or in `buildLineageDagModel` to ensure the ghost column is within canvas bounds: `ghostDepth = maxActiveDepth + 1; canvasWidth = max(current, LINEAGE_SCROLL_OVERHANG * 2 + LINEAGE_PADDING * 2 + ghostDepth * LINEAGE_COLUMN_WIDTH + ACTIVE_NODE_WIDTH)`.
85
+ 4. **Add hover tooltip** for ghost nodes using the existing `hoveredLabel`/`tooltipPos` state in `RunLineageDag` (shows the step label or SKIP reason on hover). Low-effort, improves usability.
86
+
87
+ ---
88
+
89
+ ## Residual Concerns
90
+
91
+ None that would block implementation. All three yellow findings are implementation-level details (not architectural) and are addressed by the recommended revisions above.
92
+
93
+ The design is sound for implementation.
@@ -0,0 +1,219 @@
1
+ # Layer 3b Ghost Nodes -- Implementation Plan
2
+
3
+ *Execution-ready plan. Design is locked. Do not re-open design questions during implementation.*
4
+
5
+ ---
6
+
7
+ ## 1. Problem Statement
8
+
9
+ The console's session detail DAG only shows nodes that were actually executed. When a workflow has `runCondition` on top-level steps, the DAG can look sparse -- jumping from phase 0 to phase 6 with no explanation. Users cannot tell whether the missing steps were skipped intentionally or represent a bug.
10
+
11
+ Layer 3b adds "ghost nodes" for skipped steps: rendered at 0.25 opacity with a `[ SKIPPED ]` badge, positioned after the active lineage. This makes the full workflow shape visible and explains sparse DAGs.
12
+
13
+ ---
14
+
15
+ ## 2. Acceptance Criteria
16
+
17
+ - [ ] When a session has `executionTraceSummary` with `evaluated_condition` items whose summaries start with `SKIP:` and have `step_id` refs, the DAG renders ghost nodes for those step IDs
18
+ - [ ] Ghost nodes are rendered at 0.25 opacity
19
+ - [ ] Ghost nodes show a `[ SKIPPED ]` MonoLabel badge
20
+ - [ ] Ghost nodes show the human-readable step label (from compiled workflow) or fall back to the raw stepId
21
+ - [ ] Ghost nodes are NOT clickable (no node detail panel opens)
22
+ - [ ] Ghost nodes appear ONLY when `run.executionTraceSummary !== null`
23
+ - [ ] Ghost nodes do NOT appear in the OverviewRail
24
+ - [ ] Duplicate skipped step IDs (same step evaluated multiple times) produce a single ghost node
25
+ - [ ] Ghost nodes are positioned within canvas bounds (no visual clipping)
26
+ - [ ] Ghost nodes show a hover tooltip with the full step label or SKIP summary
27
+ - [ ] All existing tests pass: `npx vitest run` from repo root AND `cd console && npx vitest run`
28
+ - [ ] New pure-function tests cover: SKIP item extraction, deduplication, non-SKIP items excluded
29
+
30
+ ---
31
+
32
+ ## 3. Non-Goals
33
+
34
+ - Ghost nodes for loop body steps skipped inside loops (only top-level `runCondition` skips)
35
+ - Ghost nodes clickable / showing node detail panel
36
+ - Ghost nodes in the OverviewRail
37
+ - Exact workflow-step-order column positioning (approximate ordering by trace event index is sufficient)
38
+ - Any new domain events or changes to the session event schema
39
+ - Ghost nodes when no `executionTraceSummary` is present (legacy sessions)
40
+
41
+ ---
42
+
43
+ ## 4. Philosophy-Driven Constraints
44
+
45
+ - `ConsoleGhostStep` must be a named interface with all-`readonly` fields -- not an inline type
46
+ - Ghost steps must never appear in `run.nodes` -- they are a separate array `run.skippedSteps`
47
+ - Ghost step extraction is a pure function in `session-detail-use-cases.ts` (no logic in view)
48
+ - Ghost node rendering is a separate `useMemo` block in `RunLineageDag.tsx` (sub-feature D), not mixed with `flowNodes`/`flowEdges`
49
+ - Backend label resolution reuses `extractStepTitlesFromCompiled` -- no new I/O paths
50
+ - `run.skippedSteps ?? []` defensive fallback at frontend consumption site
51
+
52
+ ---
53
+
54
+ ## 5. Invariants
55
+
56
+ - `ConsoleGhostStep` has shape: `{ readonly stepId: string; readonly stepLabel: string | null }`
57
+ - `ConsoleDagRun.skippedSteps` is always an array (never undefined) -- initialized to `[]` by backend
58
+ - Ghost steps are deduplicated by `stepId` (same step only appears once)
59
+ - Ghost node `x` position: `LINEAGE_SCROLL_OVERHANG + LINEAGE_PADDING + ghostDepth * LINEAGE_COLUMN_WIDTH` where `ghostDepth = maxActiveLineageDepth + 1`
60
+ - `graphWidth` must accommodate the ghost column: `max(current graphWidth, LINEAGE_SCROLL_OVERHANG * 2 + LINEAGE_PADDING * 2 + ghostDepth * LINEAGE_COLUMN_WIDTH + ACTIVE_NODE_WIDTH)`
61
+ - Ghost nodes are never ReactFlow `Node` objects in the `flowNodes` array -- they are absolute-positioned overlays
62
+ - Ghost nodes only rendered when `run.executionTraceSummary !== null`
63
+
64
+ ---
65
+
66
+ ## 6. Selected Approach
67
+
68
+ **Candidate B: Backend-emitted `skippedSteps` with label resolution**
69
+
70
+ Backend assembles `skippedSteps: readonly ConsoleGhostStep[]` by scanning `executionTraceSummary.items` for `evaluated_condition` items with `SKIP:` summaries and `step_id` refs, then resolves step labels from the already-loaded compiled workflow via `extractStepTitlesFromCompiled`. Frontend renders as absolute-positioned overlays (sub-feature D), following the Layer 3a pattern for edge diamonds and loop brackets.
71
+
72
+ **Runner-up**: Candidate A (frontend-only, no labels) -- rejected because raw step IDs are unreadable to users.
73
+
74
+ **Rationale**: Labels are the primary user-facing value. Backend label resolution reuses existing infrastructure. The Layer 3a pattern (separate `useMemo` + absolute overlay) is well-established.
75
+
76
+ ---
77
+
78
+ ## 7. Vertical Slices
79
+
80
+ ### Slice 1: Backend types and DTO assembly
81
+ **Scope**: Add `ConsoleGhostStep` interface and `skippedSteps` field to both mirrored type files. Implement `resolveSkippedSteps` helper in `console-service.ts`. Wire it into `projectSessionDetail`.
82
+
83
+ **Files**:
84
+ - `src/v2/usecases/console-types.ts` -- add `ConsoleGhostStep`, add `skippedSteps` to `ConsoleDagRun`
85
+ - `console/src/api/types.ts` -- mirror the same changes
86
+ - `src/v2/usecases/console-service.ts` -- add `resolveSkippedSteps` helper, call it in `projectSessionDetail`
87
+
88
+ **Done when**: Backend assembles `skippedSteps` correctly; existing tests still pass.
89
+
90
+ **Verification**: `npx vitest run` passes. Manual inspection: a session with skipped steps shows populated `skippedSteps` array in the API response.
91
+
92
+ ---
93
+
94
+ ### Slice 2: Frontend use-case helper and tests
95
+ **Scope**: Add `getSkippedStepsFromTrace` pure function to `session-detail-use-cases.ts`. Add unit tests.
96
+
97
+ **Files**:
98
+ - `console/src/views/session-detail-use-cases.ts` -- add `getSkippedStepsFromTrace`
99
+ - `tests/unit/console-session-detail-use-cases.test.ts` -- add tests
100
+
101
+ **Done when**: Pure function correctly extracts and deduplicates skipped step IDs from trace items; tests pass.
102
+
103
+ **Verification**: `npx vitest run` passes. Tests cover: SKIP items extracted, non-SKIP items excluded, dedup by stepId, items with no step_id ref excluded, empty input returns empty array.
104
+
105
+ ---
106
+
107
+ ### Slice 3: Ghost node positioning
108
+ **Scope**: Add `positionGhostNodes` pure function (takes `skippedSteps` + `LineageDagModel`, returns `readonly PositionedGhostNode[]`). Define `PositionedGhostNode` interface. Add `graphWidth` extension logic.
109
+
110
+ **Files**:
111
+ - `console/src/lib/lineage-dag-layout.ts` -- add `PositionedGhostNode` interface and `positionGhostNodes` function; or add as a new dedicated file `console/src/lib/ghost-node-layout.ts`
112
+ - `tests/unit/console-lineage-dag-layout.test.ts` (new) -- test positioning logic
113
+
114
+ **Done when**: `positionGhostNodes` places ghost nodes at correct coordinates; canvas width accommodates the ghost column.
115
+
116
+ **Verification**: Unit tests cover: no skipped steps returns empty array; N skipped steps produces N positioned nodes at `ghostDepth = maxActiveDepth + 1`; Y positions are spaced correctly; `requiredWidth` accounts for ghost column.
117
+
118
+ ---
119
+
120
+ ### Slice 4: Ghost node rendering
121
+ **Scope**: Add sub-feature D `useMemo` in `RunLineageDag.tsx`. Add `GhostNodeOverlay` component. Wire hover tooltip.
122
+
123
+ **Files**:
124
+ - `console/src/components/RunLineageDag.tsx` -- sub-feature D useMemo, GhostNodeOverlay component, `?? []` fallback
125
+
126
+ **Done when**: Ghost nodes render at 0.25 opacity with `[ SKIPPED ]` badge and step label; hover tooltip works; ghost nodes are not clickable; ghost nodes only appear when `executionTraceSummary !== null`.
127
+
128
+ **Verification**: Visual inspection in browser. No existing tests broken. `cd console && npx vitest run` passes.
129
+
130
+ ---
131
+
132
+ ## 8. Test Design
133
+
134
+ ### New unit tests in `tests/unit/console-session-detail-use-cases.test.ts`
135
+
136
+ ```
137
+ getSkippedStepsFromTrace:
138
+ - returns [] for empty items
139
+ - extracts stepId from evaluated_condition with SKIP: summary and step_id ref
140
+ - excludes evaluated_condition without SKIP: prefix (loop conditions, PASS conditions)
141
+ - excludes evaluated_condition with no step_id ref
142
+ - deduplicates by stepId (same step evaluated twice -> one entry)
143
+ - preserves order by recordedAtEventIndex
144
+ - excludes items of other kinds (selected_next_step, entered_loop, etc.)
145
+ ```
146
+
147
+ ### New unit tests in `tests/unit/console-lineage-dag-layout.test.ts`
148
+
149
+ ```
150
+ positionGhostNodes:
151
+ - returns [] for empty skippedSteps
152
+ - returns [] when model has no active lineage nodes
153
+ - places ghost nodes at depth = maxActiveLineageDepth + 1
154
+ - stacks multiple ghost nodes in separate Y lanes
155
+ - requiredWidth >= x + ACTIVE_NODE_WIDTH for rightmost ghost node
156
+ ```
157
+
158
+ ### No new integration tests needed (ghost nodes are purely visual / read-only)
159
+
160
+ ---
161
+
162
+ ## 9. Risk Register
163
+
164
+ | Risk | Likelihood | Impact | Mitigation |
165
+ |------|-----------|--------|------------|
166
+ | Type file sync mismatch | Low | Medium | Update both files in same commit; `?? []` fallback at frontend |
167
+ | Ghost nodes clip at right edge | Medium | Medium | FM4: extend graphWidth; unit test covers this |
168
+ | Duplicate ghost nodes | Medium | Low | FM3: dedup in backend helper; unit test covers this |
169
+ | `WorkflowInterpreter` not emitting SKIP traces for all workflow types | Low | High | Verified: `outcome-success.ts` uses `WorkflowInterpreter.next()` -> `traceStepRunConditionSkipped` |
170
+
171
+ ---
172
+
173
+ ## 10. PR Packaging
174
+
175
+ Single PR: `feature/etienneb/execution-trace-layer3b`
176
+
177
+ All 4 slices in one PR. They are tightly coupled (backend type -> use-case helper -> positioning -> rendering). Splitting would create intermediate states where the backend field exists but the frontend doesn't render it, which is confusing.
178
+
179
+ Commit sequence (each squashed into the PR final commit):
180
+ 1. Backend types + DTO assembly (Slice 1)
181
+ 2. Use-case helper + tests (Slice 2)
182
+ 3. Ghost positioning + tests (Slice 3)
183
+ 4. Ghost rendering (Slice 4)
184
+
185
+ Final PR commit message: `feat(console): add ghost nodes for skipped steps in execution trace DAG`
186
+
187
+ ---
188
+
189
+ ## 11. Philosophy Alignment Per Slice
190
+
191
+ ### Slice 1 (Backend types + DTO)
192
+ - Immutability by default -> satisfied: all fields `readonly`
193
+ - Make illegal states unrepresentable -> satisfied: `ConsoleGhostStep` separate from `ConsoleDagNode`
194
+ - Validate at boundaries -> satisfied: label resolution at backend I/O boundary
195
+ - Errors are data -> satisfied: `stepLabel: null` not thrown
196
+
197
+ ### Slice 2 (Use-case helper)
198
+ - Compose with small pure functions -> satisfied: single-responsibility `getSkippedStepsFromTrace`
199
+ - Exhaustiveness everywhere -> N/A: no discriminated union switch needed
200
+ - Prefer fakes over mocks -> satisfied: pure function, no mocks needed
201
+
202
+ ### Slice 3 (Positioning)
203
+ - Determinism over cleverness -> satisfied: same inputs always produce same layout
204
+ - Compose with small pure functions -> satisfied: `positionGhostNodes` is standalone
205
+
206
+ ### Slice 4 (Rendering)
207
+ - YAGNI with discipline -> satisfied: no click handling, no rail integration
208
+ - Functional/declarative over imperative -> satisfied: useMemo pattern, no mutation
209
+
210
+ ---
211
+
212
+ ## Metadata
213
+
214
+ - `implementationPlan`: complete
215
+ - `slices`: 4
216
+ - `estimatedPRCount`: 1
217
+ - `unresolvedUnknownCount`: 0
218
+ - `planConfidenceBand`: High
219
+ - `followUpTickets`: none identified
@@ -0,0 +1,128 @@
1
+ # list_workflows Latency Fix -- Implementation Plan
2
+
3
+ ## Problem Statement
4
+
5
+ `list_workflows` latency exceeds 20 seconds (measured at 36.9s for zillow-android-2). Root cause: `walkForRootedWorkflowDirectories` in `src/mcp/handlers/shared/request-workflow-reader.ts` runs a fresh, unbounded recursive DFS on every call across all accumulated remembered roots. Four compounding factors: no walk cache, insufficient skip list, indefinitely accumulated roots, and the 30s timeout wrapping the wrong operation (AFTER the walk).
6
+
7
+ ## Acceptance Criteria
8
+
9
+ 1. `shouldSkipDirectory` skips: `build`, `dist`, `out`, `target`, `.gradle`, `.gradle-cache`, `.cache`, `DerivedData`, `Pods`, `vendor`, `__pycache__`, `.venv`, `venv`, `.next`, `.nuxt`, `.turbo`, `.parcel-cache`, `coverage`, `.nyc_output`
10
+ 2. `walkForRootedWorkflowDirectories` stops recursing at depth 5
11
+ 3. `discoverRootedWorkflowDirectories` caches the result with a 30s TTL, keyed on sorted root paths
12
+ 4. All existing tests in `tests/unit/mcp/request-workflow-reader.test.ts` continue to pass
13
+ 5. `npx vitest run` passes with no failures
14
+
15
+ ## Non-Goals
16
+
17
+ - No changes to remembered-roots eviction policy
18
+ - No persistent disk cache
19
+ - No changes to `v2-workflow.ts` or the 30s loadAllWorkflows timeout
20
+ - No parallelization of the walk
21
+ - No explicit cache invalidation from write paths (TTL-only)
22
+
23
+ ## Philosophy-Driven Constraints
24
+
25
+ - Cache value must be `readonly` (immutability by default)
26
+ - Cache miss must fall through to fresh walk -- no thrown exceptions (errors are data)
27
+ - Cache key must be deterministic: sorted root paths joined with null byte (determinism over cleverness)
28
+ - Comments must explain TTL rationale and depth limit reasoning (document why, not what)
29
+
30
+ ## Invariants
31
+
32
+ - Stale path semantics: a root that does not exist (ENOENT) is reported as stale, not thrown
33
+ - Non-ENOENT errors from the root directory are re-thrown
34
+ - Subdirectory ENOENT mid-walk is silently swallowed (the root is not stale)
35
+ - Discovery order is deterministic (directory entries sorted lexicographically)
36
+ - All three invariants are covered by existing tests and must remain green
37
+
38
+ ## Selected Approach
39
+
40
+ **Candidate C**: Expand skip list + add depth limit (5) + module-level 30s TTL cache in `discoverRootedWorkflowDirectories`.
41
+
42
+ Runner-up: Candidate B (skip list + depth, no cache) -- loses because repeated calls within a session still re-walk.
43
+
44
+ Rationale: All three compounding factors are addressed. The staleness window is explicitly specified in the acceptance criteria and is self-healing.
45
+
46
+ ## Vertical Slices
47
+
48
+ ### Slice 1: Expand shouldSkipDirectory
49
+ - File: `src/mcp/handlers/shared/request-workflow-reader.ts`
50
+ - Change: add 18 entries to the `shouldSkipDirectory` predicate
51
+ - Risk: none -- pure additive change to a pure function
52
+ - Test: no new tests needed; existing tests exercise `shouldSkipDirectory` indirectly
53
+
54
+ ### Slice 2: Add depth limit to walkForRootedWorkflowDirectories
55
+ - File: `src/mcp/handlers/shared/request-workflow-reader.ts`
56
+ - Change: add `depth: number` parameter (default 0), stop at `depth >= 5`
57
+ - Risk: low -- theoretical miss for `.workrail` nested deeper than 5 levels
58
+ - Test: add one test: walk with `.workrail` at depth exactly 5 is found; at depth 6 is not found (validates the boundary)
59
+
60
+ ### Slice 3: Add module-level TTL cache to discoverRootedWorkflowDirectories
61
+ - File: `src/mcp/handlers/shared/request-workflow-reader.ts`
62
+ - Change: module-level `Map<string, {readonly result: WorkflowRootDiscoveryResult, readonly expiresAt: number}>`, cache key = `[...roots].sort().join('\0')`, TTL = 30_000ms
63
+ - Risk: low -- mutable module state; acceptable given Node.js single-threaded execution
64
+ - Test: add one test: calling `discoverRootedWorkflowDirectories` twice with the same roots returns the same object reference (proves cache hit, not re-walk)
65
+
66
+ ## Test Design
67
+
68
+ ### Existing tests (must stay green)
69
+ All 9 tests in `tests/unit/mcp/request-workflow-reader.test.ts` -- cover deterministic ordering, stale paths, ENOENT handling, mid-walk disappearance. No changes needed to these tests.
70
+
71
+ ### New tests to add (in the same file)
72
+
73
+ **Slice 2 test**: depth limit boundary
74
+ ```
75
+ it('discovers .workrail at depth 5 but not depth 6', async () => {
76
+ // create dir structure: root/a/b/c/d/.workrail/workflows (depth 5 -- found)
77
+ // root/a/b/c/d/e/.workrail/workflows (depth 6 -- not found)
78
+ // assert discovered contains depth-5 path, not depth-6 path
79
+ })
80
+ ```
81
+
82
+ **Slice 3 test**: cache hit returns same result
83
+ ```
84
+ it('returns cached result on second call with same roots within TTL', async () => {
85
+ // call discoverRootedWorkflowDirectories([root]) twice
86
+ // assert result1 === result2 (same object reference -- proves cache hit)
87
+ })
88
+ ```
89
+
90
+ ## Risk Register
91
+
92
+ | Risk | Likelihood | Impact | Mitigation |
93
+ |---|---|---|---|
94
+ | .workrail at depth > 5 missed | Very low | Medium | Convention places .workrail at project root; document in comment |
95
+ | 30s staleness confuses users | Low | Low | Self-healing; acceptable per acceptance criteria |
96
+ | Cache state leaks between tests | Very low | Low | Unique temp dir paths per test = unique cache keys |
97
+
98
+ ## PR Packaging Strategy
99
+
100
+ Single PR. All three changes are in one file, are tightly related, and address a single root cause. Splitting would not add clarity.
101
+
102
+ Commit message: `perf(mcp): bound walk depth, expand skip list, cache discovery results`
103
+
104
+ ## Philosophy Alignment per Slice
105
+
106
+ ### Slice 1 (skip list)
107
+ - Immutability by default -> satisfied (pure function, no state)
108
+ - Architectural fixes over patches -> satisfied (changes the structural constraint)
109
+ - YAGNI with discipline -> satisfied (known real dirs, no speculation)
110
+
111
+ ### Slice 2 (depth limit)
112
+ - Determinism over cleverness -> satisfied (fixed depth, predictable behavior)
113
+ - Compose with small pure functions -> satisfied (depth flows through recursion cleanly)
114
+ - Immutability by default -> satisfied (no new state)
115
+
116
+ ### Slice 3 (cache)
117
+ - Immutability by default -> tension (module-level Map is mutable) -- acceptable, confined
118
+ - Dependency injection for boundaries -> tension (Date.now() not injected) -- acceptable, unique per-test keys prevent leakage
119
+ - Determinism over cleverness -> satisfied (sorted key = stable behavior)
120
+ - YAGNI with discipline -> satisfied (TTL-only, no persistent cache)
121
+
122
+ ## Summary
123
+
124
+ - `implementationPlan`: Candidate C, all changes in `request-workflow-reader.ts`
125
+ - `slices`: 3 (skip list, depth limit, TTL cache)
126
+ - `estimatedPRCount`: 1
127
+ - `unresolvedUnknownCount`: 0
128
+ - `planConfidenceBand`: High
@@ -0,0 +1,55 @@
1
+ # list_workflows Latency Fix -- Design Review Findings
2
+
3
+ ## Tradeoff Review
4
+
5
+ | Tradeoff | Safe? | Condition for failure | Hidden assumption |
6
+ |---|---|---|---|
7
+ | Module-level mutable Map | Yes | Would fail with concurrent writes -- not possible in single-threaded Node.js | Module loaded once per process (true for Node.js ESM/CJS) |
8
+ | 30s staleness window | Yes | Explicitly specified in acceptance criteria; self-healing | Roots list change within TTL causes cache miss automatically (new key = cache miss) |
9
+ | Depth limit of 5 | Yes | `.workrail` nested > 5 levels deep -- no real-world evidence | `.workrail` is always near the top of a project tree by convention |
10
+
11
+ ## Failure Mode Review
12
+
13
+ | Failure mode | Handled? | Missing mitigation | Danger |
14
+ |---|---|---|---|
15
+ | `.workrail` at depth > 5 | No (silently missed) | Optional: log when depth limit hit | Low -- no real-world evidence of this pattern |
16
+ | 30s staleness for new `.workrail` dir | Yes (self-heals) | Optional: expose `invalidateWalkCache()` | Low -- edge case scenario |
17
+ | Skip list misses large dir | Depth limit backstop | None needed | Low -- two independent mitigations |
18
+ | Cache key collision | Not possible | None needed | None |
19
+
20
+ ## Runner-Up / Simpler Alternative Review
21
+
22
+ - **Runner-up (B: no cache)**: the only difference is absence of cache -- a weakness, not a strength. Nothing to borrow.
23
+ - **Simpler (skip list + cache, no depth)**: saves 4 lines but removes depth safety net. Not worth it.
24
+ - **Hybrid**: no uncomfortable tradeoff to resolve. Candidate C stands.
25
+
26
+ ## Philosophy Alignment
27
+
28
+ | Principle | Status |
29
+ |---|---|
30
+ | Determinism over cleverness | Satisfied -- sorted cache key, stable behavior |
31
+ | Compose with small pure functions | Satisfied -- each function stays focused |
32
+ | YAGNI with discipline | Satisfied -- TTL-only, no persistent cache |
33
+ | Architectural fixes over patches | Satisfied -- structural constraints changed |
34
+ | Immutability by default | Tension -- module-level Map is mutable; acceptable, confined behind pure interface |
35
+ | Dependency injection for boundaries | Tension -- `Date.now()` not injected; acceptable, unique per-test keys prevent leakage |
36
+
37
+ ## Findings
38
+
39
+ ### Yellow: Mutable module-level cache
40
+ The module-level `Map` is the only mutable state in the file. Acceptable given Node.js single-threaded execution and confinement behind the public functional interface. Not a blocking concern.
41
+
42
+ ### Yellow: Injected clock not used
43
+ `Date.now()` is called directly in the cache check. Tests work correctly without fake clocks because each test uses unique temp dir paths. If future tests need to verify TTL expiry behavior, they would need to restructure the test rather than inject a clock. Document this as a known limitation in the code comment.
44
+
45
+ ## Recommended Revisions
46
+
47
+ None required. The design satisfies all acceptance criteria without revision.
48
+
49
+ Optional improvements (low priority):
50
+ - Add a debug log when depth limit is reached (helps diagnose missed `.workrail` dirs in exotic repos)
51
+ - Export `clearWalkCache()` for testing TTL expiry behavior (not needed for current test suite)
52
+
53
+ ## Residual Concerns
54
+
55
+ None blocking. The 30s staleness window is the most user-visible issue but is explicitly specified in the acceptance criteria and self-heals.
@@ -0,0 +1,109 @@
1
+ # list_workflows Latency Fix -- Design Candidates
2
+
3
+ ## Problem Understanding
4
+
5
+ ### Core tensions
6
+ 1. **Correctness vs speed**: A deeper skip list and depth limit could miss legitimately nested `.workrail` directories. Chosen conservatively -- standard monorepo conventions don't nest `.workrail` deeper than 5 levels.
7
+ 2. **Simplicity vs invalidation accuracy**: A TTL cache may serve stale results if a user creates a new `.workrail` dir within the 30s window. Explicit invalidation would require threading a cache-invalidation signal through unrelated write paths.
8
+ 3. **Module-level mutable state vs dependency injection**: The cache as a module-level Map is pragmatic. Injecting a clock for testability would be over-engineered for a 30s TTL.
9
+
10
+ ### Likely seam
11
+ All three fixes are confined to `src/mcp/handlers/shared/request-workflow-reader.ts`. No API surface changes. No callers need to change.
12
+
13
+ ### What makes this hard
14
+ Nothing technically hard. The risk is under-fixing (skip list only) or over-engineering (persistent cross-process cache).
15
+
16
+ ---
17
+
18
+ ## Philosophy Constraints
19
+
20
+ Source: `/Users/etienneb/git/personal/workrail/AGENTS.md`
21
+
22
+ - **Immutability by default** -- cache value should be `readonly`; the Map is mutable but confined
23
+ - **Errors are data** -- cache miss falls through to fresh walk, no exceptions
24
+ - **Determinism over cleverness** -- sort roots for stable cache key
25
+ - **YAGNI with discipline** -- TTL only, no persistent cache
26
+
27
+ No conflicts between stated philosophy and existing repo patterns.
28
+
29
+ ---
30
+
31
+ ## Impact Surface
32
+
33
+ - `discoverRootedWorkflowDirectories` is called once per `createWorkflowReaderForRequest` invocation
34
+ - The cache is internal to the function -- callers observe no API change
35
+ - Tests in `tests/unit/mcp/request-workflow-reader.test.ts` use fresh temp dirs per test, so cache keys never collide between test cases (TTL won't cause cross-test leakage)
36
+
37
+ ---
38
+
39
+ ## Candidates
40
+
41
+ ### Candidate A: Skip list expansion only
42
+
43
+ Expand `shouldSkipDirectory` to skip: `build`, `dist`, `out`, `target`, `.gradle`, `.gradle-cache`, `.cache`, `DerivedData`, `Pods`, `vendor`, `__pycache__`, `.venv`, `venv`, `.next`, `.nuxt`, `.turbo`, `.parcel-cache`, `coverage`, `.nyc_output`.
44
+
45
+ - **Tensions resolved**: width of walk (88% of Android monorepo eliminated)
46
+ - **Tensions accepted**: repeated calls still re-walk; no depth bound
47
+ - **Boundary**: `shouldSkipDirectory` pure predicate
48
+ - **Failure mode**: large tree with unusual directory names not in the list
49
+ - **Repo pattern**: follows exactly -- extends an existing two-entry check
50
+ - **Gains**: zero added complexity, zero new state
51
+ - **Losses**: no protection against deep trees or repeated calls
52
+ - **Scope**: too narrow for the stated acceptance criteria
53
+ - **Philosophy fit**: perfect -- pure function, no mutable state
54
+
55
+ ### Candidate B: Skip list + depth limit
56
+
57
+ All of A, plus `depth: number` parameter (default 0) passed through `walkForRootedWorkflowDirectories`, stopping recursion at `depth >= 5`.
58
+
59
+ - **Tensions resolved**: wide trees and deep trees both bounded
60
+ - **Tensions accepted**: repeated calls still re-walk
61
+ - **Boundary**: `walkForRootedWorkflowDirectories` internal function; depth flows through the recursion
62
+ - **Failure mode**: `.workrail` at depth > 5 (e.g., `root/a/b/c/d/e/.workrail`) -- no real-world evidence of this
63
+ - **Repo pattern**: adapts -- passing context through recursion already done with `discoveredPaths[]`
64
+ - **Gains**: worst-case walk is bounded even for exotic directory structures
65
+ - **Losses**: minor theoretical miss for very deep repos
66
+ - **Scope**: best-fit if cache is not required
67
+ - **Philosophy fit**: honors determinism, small pure functions
68
+
69
+ ### Candidate C: Skip list + depth limit + module-level TTL cache (full fix)
70
+
71
+ All of B, plus a `Map<string, {readonly result: WorkflowRootDiscoveryResult, readonly expiresAt: number}>` at module level in `discoverRootedWorkflowDirectories`. Cache key = root paths sorted and joined with `\0`. TTL = 30 seconds (matches diagnosis acceptance criteria).
72
+
73
+ - **Tensions resolved**: all three compounding factors; repeated calls within a session are near-zero cost
74
+ - **Tensions accepted**: up to 30s staleness if a new `.workrail` dir is created while cache is warm
75
+ - **Boundary**: `discoverRootedWorkflowDirectories` -- the public API for the discovery step; cache is entirely internal
76
+ - **Failure mode**: new `.workrail` directory not visible until TTL expires
77
+ - **Repo pattern**: departs -- no precedent for in-memory caching in this module, but pattern is standard
78
+ - **Gains**: eliminates 30s wall-clock penalty for repeated calls in the same process lifetime
79
+ - **Losses**: module-level mutable state; slight staleness window
80
+ - **Scope**: best-fit -- all changes in one file, no API changes, matches stated acceptance criteria
81
+ - **Philosophy fit**: slight tension with immutability (module Map is mutable) -- mitigated by confining it behind a pure functional interface
82
+
83
+ ---
84
+
85
+ ## Comparison and Recommendation
86
+
87
+ **Recommendation: Candidate C**
88
+
89
+ The diagnosis identified four compounding factors and three required fixes. Candidate C addresses all of them. The skip list alone eliminates the bulk of the Android walk but leaves repeated-call overhead and offers no protection against other large monorepos. The depth limit adds a safety net. The cache converts a 30s wall-clock penalty into a sub-millisecond repeat for the common case.
90
+
91
+ The 30s staleness window is the most manageable failure mode: it self-heals, requires no user action, and matches the stated acceptance criteria from the prior investigation.
92
+
93
+ ---
94
+
95
+ ## Self-Critique
96
+
97
+ **Strongest counter-argument**: The cache introduces the only real mutable state in the module. If a test runner reuses the module between test cases, cache state could leak. Mitigation: cache keys are based on the actual root paths, which are unique temp dirs per test -- no leakage in practice.
98
+
99
+ **Narrower option (B)**: Would lose the cache benefit for repeated calls within a session. Even with the skip list, repeated walks without cache cost real latency on large monorepos.
100
+
101
+ **Broader option (persistent disk cache)**: Not justified. The 30s in-memory TTL is sufficient; persistent cache adds I/O and invalidation complexity with no material gain.
102
+
103
+ **Pivot condition**: If `.workrail` conventions change to allow deeper nesting, increase the depth limit. If the 30s staleness window causes user-reported issues, add explicit cache invalidation triggered from the remembered-roots write path.
104
+
105
+ ---
106
+
107
+ ## Open Questions
108
+
109
+ None requiring human decision. The diagnosis and acceptance criteria are fully specified.
@@ -0,0 +1,11 @@
1
+ # Native Context Management: API Design
2
+
3
+ > **Not pursuing**
4
+ >
5
+ > WorkRail is not planning to implement native context management.
6
+ >
7
+ > This file is kept only as a stable tombstone so old links do not break.
8
+ >
9
+ > See:
10
+ > - `docs/roadmap/legacy-planning-status.md`
11
+ > - `docs/plans/native-context-management-epic.md`