@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.
- package/dist/console/assets/{index-FtTaDku8.js → index-BZ6HkxGf.js} +1 -1
- package/dist/console/index.html +1 -1
- package/dist/manifest.json +3 -3
- package/docs/README.md +57 -0
- package/docs/adrs/001-hybrid-storage-backend.md +38 -0
- package/docs/adrs/002-four-layer-context-classification.md +38 -0
- package/docs/adrs/003-checkpoint-trigger-strategy.md +35 -0
- package/docs/adrs/004-opt-in-encryption-strategy.md +36 -0
- package/docs/adrs/005-agent-first-workflow-execution-tokens.md +105 -0
- package/docs/adrs/006-append-only-session-run-event-log.md +76 -0
- package/docs/adrs/007-resume-and-checkpoint-only-sessions.md +51 -0
- package/docs/adrs/008-blocked-nodes-architectural-upgrade.md +178 -0
- package/docs/adrs/009-bridge-mode-single-instance-mcp.md +195 -0
- package/docs/adrs/010-release-pipeline.md +89 -0
- package/docs/architecture/README.md +7 -0
- package/docs/architecture/refactor-audit.md +364 -0
- package/docs/authoring-v2.md +527 -0
- package/docs/authoring.md +873 -0
- package/docs/changelog-recent.md +201 -0
- package/docs/configuration.md +505 -0
- package/docs/ctc-mcp-proposal.md +518 -0
- package/docs/design/README.md +22 -0
- package/docs/design/agent-cascade-protocol.md +96 -0
- package/docs/design/autonomous-console-design-candidates.md +253 -0
- package/docs/design/autonomous-console-design-review.md +111 -0
- package/docs/design/autonomous-platform-mvp-discovery.md +525 -0
- package/docs/design/claude-code-source-deep-dive.md +713 -0
- package/docs/design/console-cyberpunk-ui-discovery.md +504 -0
- package/docs/design/console-execution-trace-candidates-final.md +160 -0
- package/docs/design/console-execution-trace-candidates.md +211 -0
- package/docs/design/console-execution-trace-design-candidates-v2.md +113 -0
- package/docs/design/console-execution-trace-design-review.md +74 -0
- package/docs/design/console-execution-trace-discovery.md +394 -0
- package/docs/design/console-execution-trace-final-review.md +77 -0
- package/docs/design/console-execution-trace-review.md +92 -0
- package/docs/design/console-performance-discovery.md +415 -0
- package/docs/design/console-ui-backlog.md +280 -0
- package/docs/design/daemon-architecture-discovery.md +853 -0
- package/docs/design/daemon-design-candidates.md +318 -0
- package/docs/design/daemon-design-review-findings.md +119 -0
- package/docs/design/daemon-engine-design-candidates.md +210 -0
- package/docs/design/daemon-engine-design-review.md +131 -0
- package/docs/design/daemon-execution-engine-discovery.md +280 -0
- package/docs/design/daemon-gap-analysis.md +554 -0
- package/docs/design/daemon-owns-console-plan.md +168 -0
- package/docs/design/daemon-owns-console-review.md +91 -0
- package/docs/design/daemon-owns-console.md +195 -0
- package/docs/design/data-model-erd.md +11 -0
- package/docs/design/design-candidates-consolidate-dev-staleness.md +98 -0
- package/docs/design/design-candidates-walk-cache-depth-limit.md +80 -0
- package/docs/design/design-review-consolidate-dev-staleness.md +54 -0
- package/docs/design/design-review-walk-cache-depth-limit.md +48 -0
- package/docs/design/implementation-plan-consolidate-dev-staleness.md +142 -0
- package/docs/design/implementation-plan-walk-cache-depth-limit.md +141 -0
- package/docs/design/layer3b-ghost-nodes-design-candidates.md +229 -0
- package/docs/design/layer3b-ghost-nodes-design-review.md +93 -0
- package/docs/design/layer3b-ghost-nodes-implementation-plan.md +219 -0
- package/docs/design/list-workflows-latency-fix-plan.md +128 -0
- package/docs/design/list-workflows-latency-fix-review.md +55 -0
- package/docs/design/list-workflows-latency-fix.md +109 -0
- package/docs/design/native-context-management-api.md +11 -0
- package/docs/design/performance-sweep-2026-04.md +96 -0
- package/docs/design/routines-guide.md +219 -0
- package/docs/design/sequence-diagrams.md +11 -0
- package/docs/design/subagent-design-principles.md +220 -0
- package/docs/design/temporal-patterns-design-candidates.md +312 -0
- package/docs/design/temporal-patterns-design-review-findings.md +163 -0
- package/docs/design/test-isolation-from-config-file.md +335 -0
- package/docs/design/v2-core-design-locks.md +2746 -0
- package/docs/design/v2-lock-registry.json +734 -0
- package/docs/design/workflow-authoring-v2.md +1044 -0
- package/docs/design/workflow-docs-spec.md +218 -0
- package/docs/design/workflow-extension-points.md +687 -0
- package/docs/design/workrail-auto-trigger-system.md +359 -0
- package/docs/design/workrail-config-file-discovery.md +513 -0
- package/docs/docker.md +110 -0
- package/docs/generated/v2-lock-closure-plan.md +26 -0
- package/docs/generated/v2-lock-coverage.json +797 -0
- package/docs/generated/v2-lock-coverage.md +177 -0
- package/docs/ideas/backlog.md +3927 -0
- package/docs/ideas/design-candidates-mcp-resilience.md +208 -0
- package/docs/ideas/design-review-findings-mcp-resilience.md +119 -0
- package/docs/ideas/implementation_plan.md +249 -0
- package/docs/ideas/third-party-workflow-setup-design-thinking.md +1948 -0
- package/docs/implementation/02-architecture.md +316 -0
- package/docs/implementation/04-testing-strategy.md +124 -0
- package/docs/implementation/09-simple-workflow-guide.md +835 -0
- package/docs/implementation/13-advanced-validation-guide.md +874 -0
- package/docs/implementation/README.md +21 -0
- package/docs/integrations/claude-code.md +300 -0
- package/docs/integrations/firebender.md +315 -0
- package/docs/migration/v0.1.0.md +147 -0
- package/docs/naming-conventions.md +45 -0
- package/docs/planning/README.md +104 -0
- package/docs/planning/github-ticketing-playbook.md +195 -0
- package/docs/plans/README.md +24 -0
- package/docs/plans/agent-managed-ticketing-design.md +605 -0
- package/docs/plans/agentic-orchestration-roadmap.md +112 -0
- package/docs/plans/assessment-gates-engine-handoff.md +536 -0
- package/docs/plans/content-coherence-and-references.md +151 -0
- package/docs/plans/library-extraction-plan.md +340 -0
- package/docs/plans/mr-review-workflow-redesign.md +1451 -0
- package/docs/plans/native-context-management-epic.md +11 -0
- package/docs/plans/perf-fixes-design-candidates.md +225 -0
- package/docs/plans/perf-fixes-design-review-findings.md +61 -0
- package/docs/plans/perf-fixes-new-issues-candidates.md +264 -0
- package/docs/plans/perf-fixes-new-issues-review.md +110 -0
- package/docs/plans/prompt-fragments.md +53 -0
- package/docs/plans/ui-ux-workflow-design-candidates.md +120 -0
- package/docs/plans/ui-ux-workflow-discovery.md +100 -0
- package/docs/plans/ui-ux-workflow-review.md +48 -0
- package/docs/plans/v2-followup-enhancements.md +587 -0
- package/docs/plans/workflow-categories-candidates.md +105 -0
- package/docs/plans/workflow-categories-discovery.md +110 -0
- package/docs/plans/workflow-categories-review.md +51 -0
- package/docs/plans/workflow-discovery-model-candidates.md +94 -0
- package/docs/plans/workflow-discovery-model-discovery.md +74 -0
- package/docs/plans/workflow-discovery-model-review.md +48 -0
- package/docs/plans/workflow-source-setup-phase-1.md +245 -0
- package/docs/plans/workflow-source-setup-phase-2.md +361 -0
- package/docs/plans/workflow-staleness-detection-candidates.md +104 -0
- package/docs/plans/workflow-staleness-detection-review.md +58 -0
- package/docs/plans/workflow-staleness-detection.md +80 -0
- package/docs/plans/workflow-v2-design.md +69 -0
- package/docs/plans/workflow-v2-roadmap.md +74 -0
- package/docs/plans/workflow-validation-design.md +98 -0
- package/docs/plans/workflow-validation-roadmap.md +108 -0
- package/docs/plans/workrail-platform-vision.md +420 -0
- package/docs/reference/agent-context-cleaner-snippet.md +94 -0
- package/docs/reference/agent-context-guidance.md +140 -0
- package/docs/reference/context-optimization.md +284 -0
- package/docs/reference/example-workflow-repository-template/.github/workflows/validate.yml +125 -0
- package/docs/reference/example-workflow-repository-template/README.md +268 -0
- package/docs/reference/example-workflow-repository-template/workflows/example-workflow.json +80 -0
- package/docs/reference/external-workflow-repositories.md +916 -0
- package/docs/reference/feature-flags-architecture.md +472 -0
- package/docs/reference/feature-flags.md +349 -0
- package/docs/reference/god-tier-workflow-validation.md +272 -0
- package/docs/reference/loop-optimization.md +209 -0
- package/docs/reference/loop-validation.md +176 -0
- package/docs/reference/loops.md +465 -0
- package/docs/reference/mcp-platform-constraints.md +59 -0
- package/docs/reference/recovery.md +88 -0
- package/docs/reference/releases.md +177 -0
- package/docs/reference/troubleshooting.md +105 -0
- package/docs/reference/workflow-execution-contract.md +998 -0
- package/docs/roadmap/README.md +22 -0
- package/docs/roadmap/legacy-planning-status.md +103 -0
- package/docs/roadmap/now-next-later.md +70 -0
- package/docs/roadmap/open-work-inventory.md +389 -0
- package/docs/tickets/README.md +39 -0
- package/docs/tickets/next-up.md +76 -0
- package/docs/workflow-management.md +317 -0
- package/docs/workflow-templates.md +423 -0
- package/docs/workflow-validation.md +184 -0
- package/docs/workflows.md +254 -0
- package/package.json +3 -1
- package/spec/authoring-spec.json +61 -16
- package/workflows/workflow-for-workflows.json +252 -93
- 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`
|