fix(cli,backlog-mgr): register PM providers in CLI; load Linear pipeline lists#1131
Merged
zbigniewsobiecki merged 1 commit intodevfrom Apr 16, 2026
Merged
Conversation
…ine lists
After spec 006/5 ("delete legacy bootstrap; pmRegistry becomes a delegate")
landed, two paths that the worker container relies on broke for Linear:
1. `cascade-tools` CLI ran with an empty PM registry. Spec 006/5 removed
the only path that previously self-bootstrapped on registry access.
Router (src/router/index.ts:8) and worker (src/worker-entry.ts:19) were
updated to import the integrations barrel explicitly, but the CLI's
lazy oclif command loader was missed. Result: every `cascade-tools pm
<cmd>` call from inside an agent run threw `Unknown PM integration
type: 'linear'. Registered:` (empty), failing every backlog-manager
chain that tries to enumerate work items.
2. `buildPipelineLists` in src/agents/definitions/contextSteps.ts only
read Trello (`lists`) and JIRA (`statuses`) configs. Linear projects
exposed `statuses` in their config (identical shape to JIRA's), but
the function never called `getLinearConfig`. Result: every Linear
project's `fetchPipelineSnapshotStep` logged `No pipeline lists
configured, skipping` and returned `[]`, so the backlog-manager
prompt never received the pipeline snapshot it expected.
Combined effect: after PR #1130 landed (auto-merge link preservation), the
chained backlog-manager fired correctly on llmist (Linear-backed) but
bailed without picking up a next card.
Fix 1 — `src/cli/bootstrap.ts` (NEW): three side-effect imports that
register every provider manifest. Loaded from `bin/cascade-tools.js` (the
CLI entry script) before `Config.load`, mirroring the router/worker
bootstrap pattern.
Routing through `bin/cascade-tools.js` rather than `src/cli/base.ts`
intentionally — `cli/base.ts` is transitively imported by gadgets that
some integration tests pull in, and prepending the bootstrap there made
`tests/integration/trigger-registry.test.ts` fail with a circular-import
symptom (`new ReadyToProcessLabelTrigger()` from `trello/manifest.ts`
threw "is not a constructor"). Loading from the binary keeps the
side-effect off any test path.
Fix 2 — extend `buildPipelineLists` to also call `getLinearConfig` and
chain `?? linearConfig?.statuses?.X` onto each `addList` call. JIRA and
Linear share the `Record<string, string>` `statuses` shape, so the
existing nullish-coalescing pattern extends naturally. No behavior
change for Trello/JIRA.
Tests: tests/unit/cli/bootstrap.test.ts (NEW) asserts
`listPMProviders()` returns `['linear', 'jira', 'trello']` after
importing the bootstrap module. Extended pipelineSnapshot.test.ts with a
Linear fixture asserting all 6 list IDs are passed to
`provider.listWorkItems`. All 7816 unit + 524 integration tests pass.
Out of scope: re-running the failed MNG-94 backlog-manager (it reported
success — just found nothing actionable). Worker container image will
pick up the fix on the next deploy via dist/cli/bootstrap.js (verified
present after `npm run build`).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
zbigniewsobiecki
added a commit
that referenced
this pull request
Apr 16, 2026
…cards
Backlog-manager for Linear-backed projects (e.g. llmist) saw BACKLOG as
empty even when items existed (MNG-97 confirmed in backlog). Tracing
the chained backlog-manager run for MNG-96/PR-571 confirmed:
```
## Pipeline Check
**Active pipeline count:** 0 (TODO: 0, IN_PROGRESS: 0, IN_REVIEW: 0)
The pipeline has capacity (0 < 1), but the BACKLOG is empty
```
Root cause: `PMProvider.listWorkItems(containerId, filter?)` had
incompatible per-provider semantics for `containerId`:
- Trello: containerId = list ID directly → works (lists ARE statuses)
- JIRA: containerId = project key, status filter via `filter.status` →
passing a status name as containerId returned 0 from JQL
- Linear: containerId = team ID, status filter via `filter.status` →
passing a state UUID as containerId queried `listIssues({ teamId:
<state-uuid> })` → returned 0
The snapshot loader (`contextSteps.fetchPipelineLists`) called
`provider.listWorkItems(list.id)` where `list.id` was a status
identifier — silently returning [] for both JIRA and Linear since the
day PR #1131 made `buildPipelineLists` populate Linear statuses.
This commit unifies the abstraction:
- `PMProvider.listWorkItems(containerId: string | undefined, filter?)`
— containerId becomes optional. Each provider self-resolves the
natural scope from its own config when omitted: Trello uses
`config.lists[filter.status]`, JIRA defaults to `config.projectKey`,
Linear defaults to `config.teamId`. The `filter.status` is the
CASCADE-canonical key (`'backlog'`, `'todo'`, ...), mapped to the
provider's native identifier internally.
- `contextSteps.fetchPipelineLists` calls
`provider.listWorkItems(undefined, { status: list.statusKey })` —
one code path for all 3 providers.
- `PipelineList` shape: `{ name, statusKey }` (dropped the dead `id`
field). Snapshot output now shows `## BACKLOG (status: backlog)`
instead of `(list ID: <UUID>)` — the agent uses CASCADE keys with
`move-work-item`, not the underlying provider IDs.
- `backlog-check.ts:isPipelineAtCapacity` collapsed Trello/JIRA
dispatch into one unified path (~50 lines deleted). Now also works
for Linear without code change. The per-provider knowledge survives
only in `isProviderMisconfigured`, which preserves the pre-existing
conservative behaviour: when a project's config is incomplete,
return `'misconfigured'` (caller runs the agent anyway) rather than
silently treating it as `'backlog-empty'` (which would skip the
agent run).
`isProviderMisconfigured` uses an exhaustive `switch` over
`PMType` with `assertNeverPMType(provider.type)` in the default
branch — TypeScript fails the build when a 4th `PMType` member is
added without updating the switch.
- `agent-execution.ts:propagateAutoLabelAfterSplitting` similarly
collapsed (~25 lines deleted).
- `TrelloPMProvider` constructor now takes `TrelloConfig` (was
optional before this change made it useful). The CLI's
`CredentialScopedCommand` synthesizes a minimal Trello shell with
no `trello` field for gadget-scope purposes; `TrelloIntegration.
createProvider` falls back to an empty config in that case so the
adapter still constructs cleanly (the CLI's gadget callers always
pass containerId explicitly, so self-resolution is never exercised
on this path).
Tests added/updated:
- `tests/unit/pm/{trello,jira,linear}-adapter.test.ts` — 10 new tests
for self-resolution from each provider's config + backwards-compat
fallback for explicit containerId.
- `tests/unit/agents/definitions/pipelineSnapshot.test.ts` — updated
to assert unified call shape; updated `list ID:` → `status:` in
output assertions.
- `tests/unit/triggers/shared/backlog-check.test.ts` — dropped the
`STATUS_KEY_BY_FIXTURE` translation shim, rewrote 27 fixture keys
to CASCADE form (so test fixtures correctly say what they ARE),
added 5 Linear-specific tests, replaced unsupported-provider test
with `.rejects.toThrow(/Unhandled PMType/)` exhaustiveness check.
- `tests/helpers/factories.ts` — new `createMockLinearProject` next
to the existing Trello/JIRA factories.
- `tests/unit/triggers/agent-execution.test.ts` and `tests/unit/
triggers/shared/agent-execution.test.ts` — updated 5 assertions to
use the unified call shape; replaced `mockResolvedValue` with
per-status `mockImplementation` so capacity checks see empty
in-flight statuses correctly.
7845 unit + 524 integration tests pass. lint+typecheck+build clean.
Out of scope: the CLI gadget (`cascade-tools pm list-work-items
--containerId X`) keeps its existing explicit-containerId form;
backfilling MNG-97 manually (next chained backlog-manager will see
it after this lands).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 tasks
zbigniewsobiecki
added a commit
that referenced
this pull request
Apr 16, 2026
…cards (#1133) Backlog-manager for Linear-backed projects (e.g. llmist) saw BACKLOG as empty even when items existed (MNG-97 confirmed in backlog). Tracing the chained backlog-manager run for MNG-96/PR-571 confirmed: ``` ## Pipeline Check **Active pipeline count:** 0 (TODO: 0, IN_PROGRESS: 0, IN_REVIEW: 0) The pipeline has capacity (0 < 1), but the BACKLOG is empty ``` Root cause: `PMProvider.listWorkItems(containerId, filter?)` had incompatible per-provider semantics for `containerId`: - Trello: containerId = list ID directly → works (lists ARE statuses) - JIRA: containerId = project key, status filter via `filter.status` → passing a status name as containerId returned 0 from JQL - Linear: containerId = team ID, status filter via `filter.status` → passing a state UUID as containerId queried `listIssues({ teamId: <state-uuid> })` → returned 0 The snapshot loader (`contextSteps.fetchPipelineLists`) called `provider.listWorkItems(list.id)` where `list.id` was a status identifier — silently returning [] for both JIRA and Linear since the day PR #1131 made `buildPipelineLists` populate Linear statuses. This commit unifies the abstraction: - `PMProvider.listWorkItems(containerId: string | undefined, filter?)` — containerId becomes optional. Each provider self-resolves the natural scope from its own config when omitted: Trello uses `config.lists[filter.status]`, JIRA defaults to `config.projectKey`, Linear defaults to `config.teamId`. The `filter.status` is the CASCADE-canonical key (`'backlog'`, `'todo'`, ...), mapped to the provider's native identifier internally. - `contextSteps.fetchPipelineLists` calls `provider.listWorkItems(undefined, { status: list.statusKey })` — one code path for all 3 providers. - `PipelineList` shape: `{ name, statusKey }` (dropped the dead `id` field). Snapshot output now shows `## BACKLOG (status: backlog)` instead of `(list ID: <UUID>)` — the agent uses CASCADE keys with `move-work-item`, not the underlying provider IDs. - `backlog-check.ts:isPipelineAtCapacity` collapsed Trello/JIRA dispatch into one unified path (~50 lines deleted). Now also works for Linear without code change. The per-provider knowledge survives only in `isProviderMisconfigured`, which preserves the pre-existing conservative behaviour: when a project's config is incomplete, return `'misconfigured'` (caller runs the agent anyway) rather than silently treating it as `'backlog-empty'` (which would skip the agent run). `isProviderMisconfigured` uses an exhaustive `switch` over `PMType` with `assertNeverPMType(provider.type)` in the default branch — TypeScript fails the build when a 4th `PMType` member is added without updating the switch. - `agent-execution.ts:propagateAutoLabelAfterSplitting` similarly collapsed (~25 lines deleted). - `TrelloPMProvider` constructor now takes `TrelloConfig` (was optional before this change made it useful). The CLI's `CredentialScopedCommand` synthesizes a minimal Trello shell with no `trello` field for gadget-scope purposes; `TrelloIntegration. createProvider` falls back to an empty config in that case so the adapter still constructs cleanly (the CLI's gadget callers always pass containerId explicitly, so self-resolution is never exercised on this path). Tests added/updated: - `tests/unit/pm/{trello,jira,linear}-adapter.test.ts` — 10 new tests for self-resolution from each provider's config + backwards-compat fallback for explicit containerId. - `tests/unit/agents/definitions/pipelineSnapshot.test.ts` — updated to assert unified call shape; updated `list ID:` → `status:` in output assertions. - `tests/unit/triggers/shared/backlog-check.test.ts` — dropped the `STATUS_KEY_BY_FIXTURE` translation shim, rewrote 27 fixture keys to CASCADE form (so test fixtures correctly say what they ARE), added 5 Linear-specific tests, replaced unsupported-provider test with `.rejects.toThrow(/Unhandled PMType/)` exhaustiveness check. - `tests/helpers/factories.ts` — new `createMockLinearProject` next to the existing Trello/JIRA factories. - `tests/unit/triggers/agent-execution.test.ts` and `tests/unit/ triggers/shared/agent-execution.test.ts` — updated 5 assertions to use the unified call shape; replaced `mockResolvedValue` with per-status `mockImplementation` so capacity checks see empty in-flight statuses correctly. 7845 unit + 524 integration tests pass. lint+typecheck+build clean. Out of scope: the CLI gadget (`cascade-tools pm list-work-items --containerId X`) keeps its existing explicit-containerId form; backfilling MNG-97 manually (next chained backlog-manager will see it after this lands). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
zbigniewsobiecki
added a commit
that referenced
this pull request
Apr 16, 2026
…near projects
The splitting agent for MNG-98 on llmist (Linear-backed) ran today. Every
`cascade-tools pm <cmd>` call from inside the worker container threw:
```
Error: Linear integration requires teamId in config
at LinearIntegration.createProvider (file:///app/dist/pm/linear/integration.js:65)
```
The agent fell back to direct Linear API calls and got the work done, but
the CLI gadget path was broken for every Linear-backed project. Three
combined causes:
1. `src/cli/base.ts` cast `process.env.CASCADE_PM_TYPE` as `'trello' |
'jira' | undefined` — but `secretBuilder.ts` injects the actual
`project.pm.type`, so `'linear'` was arriving unboxed at runtime.
2. The `pmProject` synthesis only had a JIRA conditional spread; for
`pmType === 'linear'` it produced `{ pm: { type: 'linear' } }` with no
`linear` field. `LinearIntegration.createProvider` then read
`getLinearConfig(project)` → undefined → threw.
3. Even after the synthesis was fixed, no `withLinearCredentials` wrap
meant gadgets calling Linear API would fail with "No Linear credentials
in scope". Lines 44-58 wrapped GitHub/Trello/JIRA but not Linear.
And on the worker-spawn side, `secretBuilder.ts:46-53` injected
`CASCADE_JIRA_*` env vars from `getJiraConfig(project)` but had no Linear
equivalent — so even if the CLI tried to read them, they wouldn't be
there.
This is the third generation of the same architectural omission: the CLI
was Trello/JIRA-aware long before Linear existed (spec 006). PR #1131
unblocked the registry (cascade-tools commands actually load), PR #1133
tightened provider validation — both made the latent bug louder. Now
LinearIntegration throws cleanly when it has no teamId, and that throw
surfaces here.
Fix mirrors the JIRA pattern end-to-end:
- `src/backends/secretBuilder.ts` — when `getLinearConfig(project)` is
truthy, inject `CASCADE_LINEAR_TEAM_ID`, optional
`CASCADE_LINEAR_PROJECT_ID`, and `CASCADE_LINEAR_STATUSES` into the
worker's env. Mirrors the JIRA injection block.
- `src/cli/base.ts`:
- Replace the `'trello' | 'jira' | undefined` type lie with `PMType |
undefined` (canonical type already exists in `src/pm/types.ts:6`),
so future provider additions can't reintroduce this footgun.
- Add a `withLinearCredentials` wrap when `LINEAR_API_KEY` is set,
mirroring the GitHub/Trello/JIRA wrap pattern.
- Add a Linear-config synthesis branch, reading
`CASCADE_LINEAR_TEAM_ID`/`PROJECT_ID`/`STATUSES`.
- Add `LINEAR_API_KEY`-based pmType inference as a fallback when
`CASCADE_PM_TYPE` isn't set (mirrors the JIRA-baseUrl inference).
Worker-spawned use is unaffected because `secretBuilder` always sets
`CASCADE_PM_TYPE` explicitly; this just helps human-invoked CLI use.
- Refactor `run()` into three small helpers
(`wrapWithCredentialScopes`, `resolvePmType`,
`synthesizeProjectFromEnv`) to keep cognitive complexity inside
biome's threshold.
Tests: +3 secretBuilder Linear-injection tests, +3 credential-scoping
Linear tests (withLinearCredentials wrap, populated linear synthesis,
LINEAR_API_KEY-based inference). 7851 unit + 524 integration tests pass.
Lint + typecheck + build clean.
Out of scope:
- Refactoring CLI to load full project config from DB instead of
synthesising from env vars. Bigger change; env-var pattern works for
JIRA and is the established convention.
- `--linear-team-id` flag override. Worker-injected env vars suffice.
- Backfilling MNG-98 splitting outputs (agent created them via direct
Linear API; they're correct).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 tasks
zbigniewsobiecki
added a commit
that referenced
this pull request
Apr 16, 2026
…near projects (#1134) The splitting agent for MNG-98 on llmist (Linear-backed) ran today. Every `cascade-tools pm <cmd>` call from inside the worker container threw: ``` Error: Linear integration requires teamId in config at LinearIntegration.createProvider (file:///app/dist/pm/linear/integration.js:65) ``` The agent fell back to direct Linear API calls and got the work done, but the CLI gadget path was broken for every Linear-backed project. Three combined causes: 1. `src/cli/base.ts` cast `process.env.CASCADE_PM_TYPE` as `'trello' | 'jira' | undefined` — but `secretBuilder.ts` injects the actual `project.pm.type`, so `'linear'` was arriving unboxed at runtime. 2. The `pmProject` synthesis only had a JIRA conditional spread; for `pmType === 'linear'` it produced `{ pm: { type: 'linear' } }` with no `linear` field. `LinearIntegration.createProvider` then read `getLinearConfig(project)` → undefined → threw. 3. Even after the synthesis was fixed, no `withLinearCredentials` wrap meant gadgets calling Linear API would fail with "No Linear credentials in scope". Lines 44-58 wrapped GitHub/Trello/JIRA but not Linear. And on the worker-spawn side, `secretBuilder.ts:46-53` injected `CASCADE_JIRA_*` env vars from `getJiraConfig(project)` but had no Linear equivalent — so even if the CLI tried to read them, they wouldn't be there. This is the third generation of the same architectural omission: the CLI was Trello/JIRA-aware long before Linear existed (spec 006). PR #1131 unblocked the registry (cascade-tools commands actually load), PR #1133 tightened provider validation — both made the latent bug louder. Now LinearIntegration throws cleanly when it has no teamId, and that throw surfaces here. Fix mirrors the JIRA pattern end-to-end: - `src/backends/secretBuilder.ts` — when `getLinearConfig(project)` is truthy, inject `CASCADE_LINEAR_TEAM_ID`, optional `CASCADE_LINEAR_PROJECT_ID`, and `CASCADE_LINEAR_STATUSES` into the worker's env. Mirrors the JIRA injection block. - `src/cli/base.ts`: - Replace the `'trello' | 'jira' | undefined` type lie with `PMType | undefined` (canonical type already exists in `src/pm/types.ts:6`), so future provider additions can't reintroduce this footgun. - Add a `withLinearCredentials` wrap when `LINEAR_API_KEY` is set, mirroring the GitHub/Trello/JIRA wrap pattern. - Add a Linear-config synthesis branch, reading `CASCADE_LINEAR_TEAM_ID`/`PROJECT_ID`/`STATUSES`. - Add `LINEAR_API_KEY`-based pmType inference as a fallback when `CASCADE_PM_TYPE` isn't set (mirrors the JIRA-baseUrl inference). Worker-spawned use is unaffected because `secretBuilder` always sets `CASCADE_PM_TYPE` explicitly; this just helps human-invoked CLI use. - Refactor `run()` into three small helpers (`wrapWithCredentialScopes`, `resolvePmType`, `synthesizeProjectFromEnv`) to keep cognitive complexity inside biome's threshold. Tests: +3 secretBuilder Linear-injection tests, +3 credential-scoping Linear tests (withLinearCredentials wrap, populated linear synthesis, LINEAR_API_KEY-based inference). 7851 unit + 524 integration tests pass. Lint + typecheck + build clean. Out of scope: - Refactoring CLI to load full project config from DB instead of synthesising from env vars. Bigger change; env-var pattern works for JIRA and is the established convention. - `--linear-team-id` flag override. Worker-injected env vars suffice. - Backfilling MNG-98 splitting outputs (agent created them via direct Linear API; they're correct). Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
zbigniewsobiecki
added a commit
that referenced
this pull request
Apr 18, 2026
Task 4: src/integrations/entrypoint.ts centralises every PM/SCM/alerting registration barrel. Router, worker, CLI bootstrap, and dashboard all side-effect-import this single file — no per-surface list of imports that can drift when a new provider is added. Previously dashboard.ts didn't register any PM providers at all, which is also fixed here. Task 5: tests/unit/integrations/entrypoint-usage.test.ts grep-asserts the invariant that every process entry file imports src/integrations/entrypoint.js. Missing this import was the root cause of #1097, #1118, #1131, #1134 — the test references those bug numbers in its failure message. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two regressions from spec 006/5 ("delete legacy bootstrap; pmRegistry becomes a delegate") that surfaced as backlog-manager bailing without picking up a next card on Linear-backed projects after PR #1130's auto-merge fix landed:
cascade-toolsCLI ran with an empty PM registry. Router (src/router/index.ts:8) and worker (src/worker-entry.ts:19) were updated to import the integrations barrel explicitly when the legacy self-bootstrap was removed, but the CLI's lazy oclif command loader was missed. Result: everycascade-tools pm <cmd>call from inside an agent run threwUnknown PM integration type: 'linear'. Registered:(empty list), failing every backlog-manager attempt to enumerate work items.buildPipelineListsignored Linear configs.src/agents/definitions/contextSteps.ts:376-393only calledgetTrelloConfigandgetJiraConfig, nevergetLinearConfig. Result: every Linear project'sfetchPipelineSnapshotSteploggedNo pipeline lists configured, skippingand returned[].Combined: backlog-manager fired on llmist after MNG-94 / PR #569 merged, found no pipeline snapshot in context, tried to fetch via CLI, hit the registry error, and gracefully exited without promoting any next card.
Fix
src/cli/bootstrap.ts(NEW) — three side-effect imports (integrations/pm/index,github/register,sentry/register) that mirror router/worker bootstrap.bin/cascade-tools.js—await import('../dist/cli/bootstrap.js')beforeConfig.load. Done at the entry script, notcli/base.ts, because the latter is transitively imported by gadgets that some integration tests pull in — prepending the bootstrap there madetests/integration/trigger-registry.test.tsfail with a circular-import symptom (new ReadyToProcessLabelTrigger()fromtrello/manifest.tsthrew "is not a constructor"). Loading from the binary keeps the side-effect off any test path.src/agents/definitions/contextSteps.ts—buildPipelineListsnow also callsgetLinearConfigand chains?? linearConfig?.statuses?.Xonto eachaddListcall. JIRA and Linear share theRecord<string, string>shape, so the existing pattern extends naturally. No behavior change for Trello/JIRA.Tests
tests/unit/cli/bootstrap.test.ts(NEW): assertslistPMProviders().map(p => p.id)containslinear,jira,trelloafter side-effect import.tests/unit/agents/definitions/pipelineSnapshot.test.ts: extended with a Linear fixture asserting all 6 list IDs (st-backlog,st-todo, …) are passed toprovider.listWorkItems.Out of scope
buildPipelineListsto be data-driven overgetXxxConfigaccessors (tempting but a separate cleanup; do it only when a 4th provider lands).Test plan
npm test(7816/7816)npm run test:integration(524/524, ~5.5 min)npm run typecheck,npm run lintcleannpm run buildproducesdist/cli/bootstrap.jsNo pipeline lists configuredwarn andUnknown PM integration typeerror are both gone, and the chained backlog-manager either picks a next card or logs "backlog empty / capacity full" instead of bailing on errors.🤖 Generated with Claude Code