Skip to content

feat(cue): enable cross-directory pipelines for subdirectory agents#845

Merged
reachrazamair merged 2 commits intoRunMaestro:rcfrom
chr1syy:feat/cue-cross-directory-pipelines
Apr 16, 2026
Merged

feat(cue): enable cross-directory pipelines for subdirectory agents#845
reachrazamair merged 2 commits intoRunMaestro:rcfrom
chr1syy:feat/cue-cross-directory-pipelines

Conversation

@chr1syy
Copy link
Copy Markdown
Contributor

@chr1syy chr1syy commented Apr 16, 2026

Summary

  • Allow Cue pipelines to span agents in subdirectories of a common project root (e.g. project/ + project/Digest)
  • Add ancestor config discovery so sub-agents inherit the parent's cue.yaml when they don't have their own
  • Relax pipeline editor save validation to detect common-ancestor roots instead of rejecting all multi-root pipelines

Details

Problem: When a user has a main agent at chr1syy/ and a sub-agent at chr1syy/Digest, they cannot create a Cue pipeline spanning both agents. The pipeline editor rejects the save with "agents span multiple project roots." Additionally, sub-agents never discover the parent's cue.yaml.

Root cause: Two independent constraints blocked this:

  1. UI save validation required all agent nodes to share the exact same projectRoot
  2. Per-session config loading only checked the session's own directory for cue.yaml

Solution:

  • src/shared/cue-path-utils.ts — New computeCommonAncestorPath() and isDescendantOrEqual() utilities
  • src/main/cue/cue-yaml-loader.ts — New findAncestorCueConfigRoot() walks up to 5 parent directories
  • src/main/cue/cue-session-runtime-service.ts — Ancestor fallback in initSession(): loads only explicitly targeted subscriptions (via agent_id or fan_out) to prevent duplicate triggers
  • src/main/cue/cue-session-state.ts — Added configRoot to track ancestor-loaded configs
  • src/renderer/hooks/cue/usePipelinePersistence.ts — Relaxed multi-root validation, updated post-save refresh for descendant sessions

Key design decisions:

  • Unowned subscriptions (no agent_id) are NOT inherited by child sessions — prevents duplicate trigger sources
  • Each agent still executes in its own CWD — the executor uses the target session's projectRoot, not the config root
  • The "one cue.yaml per pipeline" invariant is preserved (collapses to common ancestor root)

Test plan

  • 15 new tests for computeCommonAncestorPath and isDescendantOrEqual
  • 6 new tests for findAncestorCueConfigRoot (parent found, grandparent, depth limit, legacy path)
  • 9 existing test mock files updated with findAncestorCueConfigRoot: () => null
  • All 855 scoped tests pass (840 cue + 15 path utils)
  • TypeScript type check clean (no new errors)
  • Manual: create pipeline with parent + subdirectory agents, verify save and execution

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Projects can inherit Cue configs from ancestor directories; sessions track the actual config root and watch the correct config file.
    • Pipelines may span multiple subdirectories if they share a common ancestor; cross-root validation and session refresh behavior improved.
    • Ancestor config adoption filters subscriptions to avoid duplicating unowned/shared triggers.
  • Tests

    • Added and expanded tests for ancestor config discovery and shared path utilities.

Allow Cue pipelines to span agents whose working directories are
subdirectories of a common project root (e.g. project/ + project/Digest).
Previously the pipeline editor rejected any save where agents had different
projectRoot values, even parent/child relationships.

Changes:
- Add shared path utilities (computeCommonAncestorPath, isDescendantOrEqual)
- Add findAncestorCueConfigRoot to walk parent dirs for cue.yaml discovery
- Add ancestor config fallback in session init (only targeted subscriptions)
- Relax pipeline save validation to allow common-ancestor roots
- Refresh descendant sessions after pipeline save

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9413c468-b0c1-4441-9267-397c7d3a9f11

📥 Commits

Reviewing files that changed from the base of the PR and between adaa6fc and d164a0a.

📒 Files selected for processing (2)
  • src/__tests__/shared/cue-path-utils.test.ts
  • src/renderer/hooks/cue/usePipelinePersistence.ts
✅ Files skipped from review due to trivial changes (1)
  • src/tests/shared/cue-path-utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/hooks/cue/usePipelinePersistence.ts

📝 Walkthrough

Walkthrough

Adds ancestor Cue config discovery and adoption when a session's local config is missing, introduces shared path utilities for cross-directory pipelines, updates session state and runtime to track/use ancestor config roots (with subscription filtering), adjusts YAML watch root, and expands pipeline persistence validation to allow common-ancestor pipelines.

Changes

Cohort / File(s) Summary
Test Mock Updates
src/__tests__/main/cue/cue-completion-chains.test.ts, src/__tests__/main/cue/cue-concurrency.test.ts, src/__tests__/main/cue/cue-engine.test.ts, src/__tests__/main/cue/cue-ipc-handlers.test.ts, src/__tests__/main/cue/cue-multi-hop-chains.test.ts, src/__tests__/main/cue/cue-session-lifecycle.test.ts, src/__tests__/main/cue/cue-sleep-prevention.test.ts, src/__tests__/main/cue/cue-sleep-wake.test.ts, src/__tests__/main/cue/cue-startup.test.ts
Added findAncestorCueConfigRoot: () => null to the cue-yaml-loader Vitest mocks so tests match the loader's new exported API.
Path Utility Implementation
src/shared/cue-path-utils.ts, src/__tests__/shared/cue-path-utils.test.ts
New utilities computeCommonAncestorPath and isDescendantOrEqual with tests for common-prefix computation, descendant detection, edge cases, and path-normalization.
Ancestor Discovery Function
src/main/cue/cue-yaml-loader.ts, src/__tests__/main/cue/cue-yaml-loader.test.ts
Added findAncestorCueConfigRoot(projectRoot) (walks up to 5 ancestor levels, uses resolveCueConfigPath) and tests covering discovery, depth limit, legacy filename support, and root handling.
Session State Tracking
src/main/cue/cue-session-state.ts
Added optional configRoot?: string to SessionState to record when a config is loaded from an ancestor directory.
Session Runtime Integration
src/main/cue/cue-session-runtime-service.ts
When detailed load fails with missing, now queries findAncestorCueConfigRoot; if found loads ancestor config, sets configRoot, filters ancestor subscriptions to ones targeting the current session, and switches YAML watch root to the actual config location.
Cross-Directory Pipeline Support
src/renderer/hooks/cue/usePipelinePersistence.ts
Validates cross-root pipelines via computed commonRoot: allows pipeline when all roots are descendants of that ancestor (collapses roots to commonRoot), otherwise errors; also refreshes sessions whose projectRoot is a descendant of any touched root.

Sequence Diagram(s)

sequenceDiagram
    participant SessionRuntime as SessionRuntimeService
    participant Loader as CueYamlLoader
    participant Repo as CueConfigRepository
    participant FS as FileSystem/Watcher

    SessionRuntime->>Loader: loadCueConfigDetailed(session.projectRoot)
    alt result === missing
        SessionRuntime->>Loader: findAncestorCueConfigRoot(session.projectRoot)
        Loader-->>SessionRuntime: ancestorRoot | null
        alt ancestorRoot found
            SessionRuntime->>Loader: loadCueConfigDetailed(ancestorRoot)
            Loader->>Repo: resolveCueConfigPath(ancestorRoot)
            Repo-->>Loader: configPath
            Loader-->>SessionRuntime: ancestorResult
            SessionRuntime->>SessionRuntime: filter ancestorResult.config.subscriptions for session
            SessionRuntime-->>FS: watchCueYaml(ancestorRoot, ...)
        else no ancestor
            SessionRuntime-->>FS: watchCueYaml(session.projectRoot, ...)
        end
    else config loaded
        SessionRuntime-->>FS: watchCueYaml(session.projectRoot, ...)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

ready to merge

Poem

🐰 Hoppity-hop up folders I go,

Seeking configs where ancestors grow,
Five levels checked with a careful sweep,
Filters applied so subscriptions keep,
Pipelines unite where common roots show. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and specifically summarizes the main change: enabling cross-directory pipelines for subdirectory agents. It is concise, clear, and directly reflects the core functionality added throughout the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

This PR enables Cue pipelines to span agents in subdirectories of a common project root by adding ancestor config discovery, a new common-ancestor path utility, and relaxed pipeline-editor save validation. The main process and renderer changes are largely well-constructed, but there is one P1 correctness issue.

  • P1 — handleDiscard corrupts lastWrittenRootsRef for cross-directory pipelines: After a successful save the ref correctly holds the collapsed common-ancestor root (e.g. /project). handleDiscard re-derives roots by iterating individual agent projectRoot values, so it re-expands to {'/project', '/project/Digest'}. On the next save the "clear stale roots" loop calls writeCueConfigFile for /project/Digest, which unconditionally creates .maestro/ and writes the empty YAML. The sub-agent then finds its own cue.yaml on the next init and the ancestor-config fallback is never triggered, silently breaking the cross-directory pipeline.

Confidence Score: 4/5

Not safe to merge until the handleDiscard root-derivation bug is fixed; it will silently create stray cue.yaml files in subdirectories on the save-discard-save workflow.

One P1 logic bug in usePipelinePersistence.handleDiscard that concretely corrupts lastWrittenRootsRef and produces stray files on disk, breaking the key new feature in a common user workflow. All other changes (path utilities, ancestor loader, runtime service, session state) are well-implemented with good test coverage.

src/renderer/hooks/cue/usePipelinePersistence.ts — specifically the handleDiscard root-derivation logic around lines 353–366.

Important Files Changed

Filename Overview
src/shared/cue-path-utils.ts New utility file with computeCommonAncestorPath and isDescendantOrEqual; logic is correct with proper path.sep boundary checks preventing false-positive matches (e.g. /a/bar vs /a/b).
src/main/cue/cue-yaml-loader.ts Added findAncestorCueConfigRoot that correctly walks up to 5 parent directories; depth limit and filesystem-root stop guard look sound.
src/main/cue/cue-session-runtime-service.ts Ancestor-config fallback in initSession is well-structured: correctly filters to explicitly targeted subscriptions (agent_id or fan_out), watches the ancestor root for hot-reload, and is idempotent.
src/main/cue/cue-session-state.ts Added optional configRoot field to SessionState with clear documentation; non-breaking additive change.
src/renderer/hooks/cue/usePipelinePersistence.ts P1 bug: handleDiscard re-derives lastWrittenRootsRef from individual agent project roots instead of the collapsed common-ancestor root, causing spurious empty YAML writes to subdirectory paths on the next save which can create stray .maestro/cue.yaml files and silently break ancestor config inheritance.
src/tests/shared/cue-path-utils.test.ts 15 new tests for path utilities covering all edge cases including the /a/bar vs /a/b boundary check and trailing-separator normalization; comprehensive.
src/tests/main/cue/cue-yaml-loader.test.ts 6 new tests for findAncestorCueConfigRoot covering parent, grandparent, depth-limit enforcement, and legacy path; thorough.

Sequence Diagram

sequenceDiagram
    participant UI as Pipeline Editor
    participant PH as usePipelinePersistence
    participant CueService as cueService (IPC)
    participant RT as cue-session-runtime-service
    participant YL as cue-yaml-loader

    Note over UI,YL: Save — cross-directory pipeline
    UI->>PH: handleSave()
    PH->>PH: computeCommonAncestorPath([/project, /project/Digest]) → /project
    PH->>CueService: writeYaml(/project, yaml)
    PH->>CueService: refreshSession(subAgent, /project/Digest)
    Note over PH: lastWrittenRootsRef = {/project} ✓

    Note over UI,YL: Sub-agent session init (ancestor fallback)
    RT->>YL: loadCueConfigDetailed(/project/Digest) → missing
    RT->>YL: findAncestorCueConfigRoot(/project/Digest) → /project
    RT->>YL: loadCueConfigDetailed(/project) → ok
    RT->>RT: filter targeted subscriptions only
    RT->>RT: watchCueYaml(/project, refresh subAgent)

    Note over UI,YL: Discard then Save again (BUG)
    UI->>PH: handleDiscard()
    PH->>PH: re-derive roots from agent projectRoots
    Note over PH: lastWrittenRootsRef = {/project, /project/Digest} ✗
    UI->>PH: handleSave()
    PH->>PH: currentRoots = {/project}
    PH->>CueService: writeYaml(/project/Digest, emptyYaml) ← creates stray file
    Note over RT: Next init: /project/Digest has own cue.yaml → no ancestor fallback
Loading

Comments Outside Diff (1)

  1. src/renderer/hooks/cue/usePipelinePersistence.ts, line 353-366 (link)

    P1 handleDiscard doesn't apply common-ancestor collapse to lastWrittenRootsRef

    For a cross-directory pipeline (e.g. agents at /project and /project/Digest) the YAML is written to the common ancestor /project, so after a successful save lastWrittenRootsRef = {'/project'}. But handleDiscard re-derives roots from each agent's individual projectRoot, setting lastWrittenRootsRef = {'/project', '/project/Digest'}. On the very next save:

    • previousRoots = {'/project', '/project/Digest'}
    • currentRoots = {'/project'} (collapsed)
    • The "clear stale roots" loop writes an empty YAML to /project/Digest

    writeCueConfigFile unconditionally creates .maestro/ and writes the file, so this materialises a new /project/Digest/.maestro/cue.yaml. On the next session init, loadCueConfigDetailed('/project/Digest') finds that file and returns ok: true — the ancestor-config fallback is never reached and the sub-agent silently drops out of cross-directory pipelines.

    The fix is to apply the same ancestor-collapse logic in handleDiscard when building restoredRoots:

    // Per pipeline, collapse agent roots to a common ancestor (same as handleSave).
    const restoredRoots = new Set<string>();
    for (const pipeline of restoredPipelines) {
        const agentRoots = new Set<string>();
        for (const node of pipeline.nodes) {
            if (node.type !== 'agent') continue;
            const agentData = node.data as AgentNodeData;
            const root =
                sessionsById.get(agentData.sessionId)?.projectRoot ??
                sessionsByName.get(agentData.sessionName)?.projectRoot;
            if (root) agentRoots.add(root);
        }
        if (agentRoots.size === 0) continue;
        if (agentRoots.size === 1) {
            restoredRoots.add([...agentRoots][0]);
        } else {
            const commonRoot = computeCommonAncestorPath([...agentRoots]);
            const allDescendants =
                commonRoot !== null && [...agentRoots].every((r) => isDescendantOrEqual(r, commonRoot));
            restoredRoots.add(allDescendants && commonRoot !== null ? commonRoot : [...agentRoots][0]);
        }
    }
    lastWrittenRootsRef.current = restoredRoots;

Reviews (1): Last reviewed commit: "feat(cue): enable cross-directory pipeli..." | Re-trigger Greptile

Comment thread src/renderer/hooks/cue/usePipelinePersistence.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/__tests__/shared/cue-path-utils.test.ts (1)

2-2: Unused import.

The path module is imported but not used in any test assertions.

🧹 Remove unused import
 import { describe, it, expect } from 'vitest';
-import * as path from 'path';
 import { computeCommonAncestorPath, isDescendantOrEqual } from '../../shared/cue-path-utils';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/shared/cue-path-utils.test.ts` at line 2, The test file imports
the Node 'path' module but never uses it; in
src/__tests__/shared/cue-path-utils.test.ts remove the unused import statement
"import * as path from 'path';" (or replace it with a used helper if intended)
so there are no unused imports flagged — look for the top-level import line
referencing path and delete it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/__tests__/shared/cue-path-utils.test.ts`:
- Line 2: The test file imports the Node 'path' module but never uses it; in
src/__tests__/shared/cue-path-utils.test.ts remove the unused import statement
"import * as path from 'path';" (or replace it with a used helper if intended)
so there are no unused imports flagged — look for the top-level import line
referencing path and delete it.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a690099c-c7c5-4307-956e-d4363eede9ba

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe4baf and adaa6fc.

📒 Files selected for processing (16)
  • src/__tests__/main/cue/cue-completion-chains.test.ts
  • src/__tests__/main/cue/cue-concurrency.test.ts
  • src/__tests__/main/cue/cue-engine.test.ts
  • src/__tests__/main/cue/cue-ipc-handlers.test.ts
  • src/__tests__/main/cue/cue-multi-hop-chains.test.ts
  • src/__tests__/main/cue/cue-session-lifecycle.test.ts
  • src/__tests__/main/cue/cue-sleep-prevention.test.ts
  • src/__tests__/main/cue/cue-sleep-wake.test.ts
  • src/__tests__/main/cue/cue-startup.test.ts
  • src/__tests__/main/cue/cue-yaml-loader.test.ts
  • src/__tests__/shared/cue-path-utils.test.ts
  • src/main/cue/cue-session-runtime-service.ts
  • src/main/cue/cue-session-state.ts
  • src/main/cue/cue-yaml-loader.ts
  • src/renderer/hooks/cue/usePipelinePersistence.ts
  • src/shared/cue-path-utils.ts

- Remove redundant `|| commonRoot === null` guard in usePipelinePersistence
  (allDescendants is already false when commonRoot is null)
- Remove unused `path` import in cue-path-utils.test.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chr1syy chr1syy added the ready to merge This PR is ready to merge label Apr 16, 2026
@reachrazamair reachrazamair merged commit f886ea3 into RunMaestro:rc Apr 16, 2026
3 checks passed
jSydorowicz21 added a commit to jSydorowicz21/Maestro that referenced this pull request Apr 17, 2026
…Maestro#847)

Fixes RunMaestro#847: a Cue Scenario deleted from the dropdown and saved reappears
the next time the user opens the Cue view.

Root cause:

PR RunMaestro#845 added cross-directory pipeline support. When a pipeline's agents
span subdirectories (e.g. /project/a and /project/Digest), handleSave
resolves their common ancestor (/project) and writes ONE YAML at
/project/.maestro/cue.yaml. But the load path in usePipelineLayout was
seeding lastWrittenRootsRef with EACH agent's individual projectRoot —
so the common ancestor was missing from the set.

That set becomes `previousRoots` in handleSave's clear loop
(usePipelinePersistence:254). On delete+save:
- currentRoots = {} (the deleted pipeline had no survivors at its root)
- previousRoots = {/project/a, /project/Digest} — missing /project
- The loop only writes empty YAML at the two subdirectory paths; the
  real YAML at /project is never touched. On reopen, getGraphData()
  still reports the pipeline from /project/.maestro/cue.yaml, so the
  "deleted" pipeline reappears.

The same mis-seeding had a secondary effect: on every delete+save, the
loop would also write empty cue.yaml files at the subdirectory roots
that were never write targets, creating stray config files.

Fix:

Extract the write-root resolution into a shared pure helper,
`resolvePipelineWriteRoot(pipeline, sessionsById, sessionsByName)`,
mirroring handleSave's happy-path partitioning:

- Single shared root across all agents → that root.
- Multiple roots sharing a common ancestor where every agent root is
  a descendant → the common ancestor.
- Otherwise (no agents, any unresolvable, unrelated trees) → null.

Use `resolvePipelinesWriteRoots` in usePipelineLayout's load seeding in
place of the per-agent-root loop. The persisted `savedLayout.writtenRoots`
union is preserved as the authoritative source when agents have since
been renamed or deleted.

Scope notes:

- handleSave's inline partitioning is left unchanged — it carries
  error-reporting branches the helper intentionally does not replicate.
  A doc comment on the helper calls out the invariant.
- No change to on-disk YAML format, layout storage schema, or the
  ipc surface.

Tests:

13 new cases in `pipelineRoots.test.ts` covering single-root,
cross-directory (the RunMaestro#847 regression), unrelated-tree rejection,
unresolvable pipelines (empty, unknown sessionId, partial missingRoot,
undefined projectRoot), and the set-level behavior of
`resolvePipelinesWriteRoots` (union, dedup, skipping unresolvable).

Verified:

- tsc clean, prettier clean, eslint clean
- 13 new + 334 existing Cue pipeline editor tests pass (347 total)
- 209 cue hook tests pass
jSydorowicz21 added a commit to jSydorowicz21/Maestro that referenced this pull request Apr 17, 2026
…Maestro#847)

Fixes RunMaestro#847: a Cue Scenario deleted from the dropdown and saved reappears
the next time the user opens the Cue view.

Root cause:

PR RunMaestro#845 added cross-directory pipeline support. When a pipeline's agents
span subdirectories (e.g. /project/a and /project/Digest), handleSave
resolves their common ancestor (/project) and writes ONE YAML at
/project/.maestro/cue.yaml. But the load path in usePipelineLayout was
seeding lastWrittenRootsRef with EACH agent's individual projectRoot -
so the common ancestor was missing from the set.

That set becomes `previousRoots` in handleSave's clear loop
(usePipelinePersistence.ts:254). On delete+save:
- currentRoots = {} (the deleted pipeline had no survivors at its root)
- previousRoots = {/project/a, /project/Digest} - missing /project
- The loop only writes empty YAML at the two subdirectory paths; the
  real YAML at /project is never touched. On reopen, getGraphData()
  still reports the pipeline from /project/.maestro/cue.yaml, so the
  "deleted" pipeline reappears.

The same mis-seeding had a secondary effect: on every delete+save, the
loop would also write empty cue.yaml files at the subdirectory roots
that were never write targets, creating stray config files.

Fix:

Extract the write-root resolution into a shared pure helper,
`resolvePipelineWriteRoot(pipeline, sessionsById, sessionsByName)`,
mirroring handleSave's happy-path partitioning:

- Single shared root across all agents -> that root.
- Multiple roots sharing a common ancestor where every agent root is
  a descendant -> the common ancestor.
- Otherwise (no agents, any unresolvable, unrelated trees) -> null.

Use `resolvePipelinesWriteRoots` in usePipelineLayout's load seeding in
place of the per-agent-root loop. The persisted `savedLayout.writtenRoots`
union is preserved as the authoritative source when agents have since
been renamed or deleted.

CodeRabbit review follow-up: aligned sessionsByName construction with
handleSave's first-wins rule (handleSave: `if (!has(name)) set(...)`;
this file previously used last-wins via `new Map(sessions.map(...))`).
Without this change, a pipeline that falls back to sessionName lookup
could resolve to a different projectRoot than handleSave actually writes
to - defeating the parity invariant the helper exists to guarantee.

Scope notes:

- handleSave's inline partitioning is left unchanged - it carries
  error-reporting branches the helper intentionally does not replicate.
  A doc comment on the helper calls out the invariant.
- No change to on-disk YAML format, layout storage schema, or the
  ipc surface.

Tests:

13 new cases in `pipelineRoots.test.ts` covering single-root,
cross-directory (the RunMaestro#847 regression), unrelated-tree rejection,
unresolvable pipelines (empty, unknown sessionId, partial missingRoot,
undefined projectRoot), and the set-level behavior of
`resolvePipelinesWriteRoots` (union, dedup, skipping unresolvable).

Verified:

- tsc clean, prettier clean, eslint clean
- 13 new + 334 existing Cue pipeline editor tests pass (347 total)
- 209 cue hook tests pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready to merge This PR is ready to merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants