Skip to content

chore: release - merge dev into main#1146

Merged
zbigniewsobiecki merged 58 commits intomainfrom
dev
Apr 18, 2026
Merged

chore: release - merge dev into main#1146
zbigniewsobiecki merged 58 commits intomainfrom
dev

Conversation

@zbigniewsobiecki
Copy link
Copy Markdown
Member

Automated release PR created by the release workflow.

Commits (58):

f145008e Merge pull request #1145 from mongrel-intelligence/feature/pm-status-changed-onCreate-onMove-params
0e66346b feat(triggers): onCreate/onMove params for pm:status-changed
297a7e0a Merge pull request #1141 from mongrel-intelligence/feature/pm-triggers-created-status
754c6b5d Merge pull request #1143 from mongrel-intelligence/spec-009-pm-hardening
1b5b2cf6 chore(spec-009): all plans complete, spec done
09bf5c03 chore(009/5): cleanup done, spec 009 complete
794cdca7 chore(009/5): lock plan 5 with narrowed scope
5017f0cc chore(009/4): linear migrated, plan done
d6a2edbf chore(009/4): lock plan 4 as .wip
8bcbbfd6 chore(009/3): jira migrated, plan done
4dc5ad6c chore(009/3): lock plan 3 as .wip
9896ed7c chore(009/2): trello migrated, plan done
34a434f2 feat(009/2): trello configSchema discovery branded ids
13bc507a chore(009/2): lock plan 2 as .wip
e6f2695c chore(009/1): mark plan 1 as .done
b5b72810 feat(009/1): generic pm.discover endpoint + wizard generator + docs
00dae2a5 feat(009/1): auth-header provenance test + lefthook wiring
623e812b feat(009/1): fake PM provider + expanded behavioral conformance harness
3bcd6c84 feat(009/1): single integrations entrypoint + usage guard
f752651f feat(009/1): branded ID types + PMProvider/manifest extensions
3a27fdb6 chore(009): lock plan 1 as .wip
32987ac9 docs(009): add spec + plans for PM integration hardening
37540c5e fix(config): add projectId to LinearConfigSchema so Zod stops stripping it (#1142)
2c2215d4 fix(tests): clear JIRA env vars in credential-scoping beforeEach
d1a3ac16 feat(triggers): support PM triggers for work items created directly in a triggering status
c4561c37 feat(008): inline markdown checklists for Linear and JIRA (#1140)
a4b9a254 fix(linear): pass stateId when creating checklist sub-issues (#1139)
93fddbf9 fix(linear): preserve projectId through config mapper (#1138)
4109c230 fix(linear): create issues directly in backlog state via stateId (#1137)
1225ea8f feat(007): robust review dispatch — per-type lock + post-completion hook (#1136)
cfad9b81 fix(cli): synthesize Linear PM scope so cascade-tools pm works for Linear projects (#1134)
db6a5bba fix(pm): unify listWorkItems contract so backlog-manager sees Linear cards (#1133)
23dd4587 fix(snapshots): actually rmi evicted images + reconcile orphans on startup (#1132)
cfbeedf0 fix(cli,backlog-mgr): register PM providers in CLI; load Linear pipeline lists (#1131)
906f8fc9 fix(prWorkItems): preserve workItemId across racing pipeline writers (#1130)
2c602442 Merge pull request #1120 from mongrel-intelligence/dependabot/npm_and_yarn/hono-4.12.14
ce101a3b docs(002): mark spec as .done — all plans already landed
491d9656 Merge pull request #1129 from mongrel-intelligence/feat/006-cleanup-legacy
6eccda5d docs(006): spec complete — all plans done
bec8d24c feat(006/5): delete legacy bootstrap; pmRegistry becomes a delegate
e2808f1f chore(006): lock plan 006/5 as .wip
aae4d45a Merge pull request #1128 from mongrel-intelligence/feat/006-migrate-linear
5697b1fe feat(006/4): linear migrated onto PM provider manifest
863ffb8a chore(006): lock plan 006/4 as .wip
0ed056fd Merge pull request #1127 from mongrel-intelligence/feat/006-migrate-jira
7a3fd90f feat(006/3): jira migrated onto PM provider manifest
cced9ace chore(006): lock plan 006/3 as .wip
ece9143a Merge pull request #1126 from mongrel-intelligence/feat/006-migrate-trello
4c12556f fix(006/2): inline trello config build — no buildTrelloIntegrationConfig export
46c5b353 feat(006/2): trello migrated onto PM provider manifest
394f0dba feat(006/2): trello manifest wiring (task 1)
9eadb31b chore(006): lock plan 006/2 as .wip
563ddba9 docs(006/2): fix plan drift — Trello HMAC-SHA1 + useProviderHooks extension
e7cc117e Merge pull request #1125 from mongrel-intelligence/feat/006-infrastructure
7c3161ef feat(integrations): plan 006/1 — PM provider manifest + conformance harness (dormant)
5f0144e9 chore(006): lock plan 006/1 as .wip
679c6da4 docs(006): add spec + plans + coverage map for PM integration plug-and-play refactor
5f9ef074 chore(deps): bump hono from 4.12.12 to 4.12.14

dependabot Bot and others added 30 commits April 16, 2026 02:46
Bumps [hono](https://github.com/honojs/hono) from 4.12.12 to 4.12.14.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](honojs/hono@v4.12.12...v4.12.14)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.14
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
…d-play refactor

Spec 006 captures the WHAT and WHY: the Linear rollout this session
shipped four separate silent bugs (status-mapping UUID, worker
credentials, Bearer-prefix auth, label parity) each traceable to the
same structural cause — adding a PM provider requires edits in ~10
cross-cutting locations with no single place enforcing the contract.

Five plans decompose the work:

- 006/1 — manifest contract + pmProviderRegistry + conformance harness
  + shared helpers (auth-headers, webhook-verifier, label-id-resolver,
  project-id-extractor) + generic wizard renderer. Dormant — TestProvider
  fixture proves the harness works. All three existing providers stay
  on legacy path.

- 006/2 — migrate Trello onto the manifest (chosen first: smallest
  surface, lowest risk).

- 006/3 — migrate JIRA.

- 006/4 — migrate Linear. Also consolidates the platform-client auth
  header and label-id resolver that diverged in this session.

- 006/5 — delete legacy registration infrastructure, remove transitional
  README note.

Each migration is independently revertable; per spec AC #6 there's no
"half-migrated provider" state at any merged commit.

OSS decisions: skip RJSF / JSON Forms (overkill for 5-provider domain),
use existing react-hook-form + zod + a lightweight manifest-driven
renderer. Reference the Backstage extension-point model architecturally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…arness (dormant)

Landed the dormant manifest infrastructure that spec 006 is built on:

- PMProviderManifest interface + sub-types (src/integrations/pm/manifest.ts)
  One declarative contract per provider: id, label, category, credential
  roles, webhook route + verifier, payload parser, router adapter,
  extractProjectIdFromJob hook, PMIntegration, trigger handlers, platform
  client factory, optional isSelfAuthoredHook + createLabel.

- pmProviderRegistry (src/integrations/pm/registry.ts)
  Process-singleton registry; registerPMProvider enforces unique ids so
  forgotten renames surface as runtime errors at startup.

- Shared helpers in src/integrations/pm/_shared/:
  - auth-headers.ts — linearAuthHeader (bare key), githubAuthHeader
    (Bearer + Accept + api-version), jiraAuthHeader (Basic). Guards
    against the Bearer-prefix regression fixed in PR #1119.
  - webhook-verifier.ts — makeHmacSha256Verifier factory; header-prefix
    tolerant, opt-out semantics when secret is null.
  - label-id-resolver.ts — UUID-validating label resolver, encapsulates
    the check currently duplicated in src/pm/linear/adapter.ts.
  - project-id-extractor.ts — extractProjectIdFromJobViaRegistry iterates
    the manifest registry; wired into src/router/worker-env.ts as a
    first-check fallback so the legacy per-provider branches still fire
    for Trello/JIRA/Linear until plans 006/2-006/4 migrate them.

- Conformance harness (tests/unit/integrations/pm-conformance.test.ts)
  Iterates listPMProviders() and asserts 11 contract invariants per
  manifest. Exercised against TestProvider fixture (tests/helpers/
  testPMProvider.ts) in this PR; real providers join in plans 006/2-4.

- pm.discovery tRPC router (src/api/routers/pm-discovery.ts)
  Registry-driven listProviders + providerCredentialRoles endpoints.
  Lives alongside the legacy integrationsDiscovery router during the
  migration window. Mounted at pm.discovery.* in appRouter.

- Frontend provider-wizard registry + generic step renderer
  (web/src/components/projects/pm-providers/)
  Parallel frontend registry keyed by the same id as the backend
  manifest. renderManifestStep helper wired into pm-wizard.tsx ahead of
  the legacy per-provider branches; falls back when no wizard is
  registered. In this PR, zero providers are registered, so the wizard
  behavior is byte-for-byte identical to main.

- src/integrations/README.md — full rewrite as the manifest-first
  author's guide with a transitional note that Trello/JIRA/Linear are
  migrating in plans 006/2-4. Legacy section kept at the bottom.

- CLAUDE.md — integration abstraction pointer updated to reflect the
  hybrid state (manifest for PM, legacy IntegrationModule for SCM +
  alerting).

Tests: 42 new (7687 pre-existing + 42 = 7729 total, all green).
Build, lint, typecheck all pass.

No operator-visible changes. Trello/JIRA/Linear continue to register
through bootstrap.ts and builtins.ts; the new registry is consulted
first and returns empty, so legacy paths handle every real request.

Plan: docs/plans/006-pm-integration-plug-and-play/1-infrastructure.md.done

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…cture

feat(006/1): PM provider manifest + conformance harness (dormant infra)
…ension

Plan 006/2 as written assumed Trello could use makeHmacSha256Verifier
from 006/1's shared helpers. Reality: Trello signs HMAC-SHA1(body +
callbackUrl) — different algorithm AND different signed payload. Plan
updated to wire the existing verifyTrelloSignature helper from
src/webhook/signatureVerification.ts into the manifest's
verifyWebhookSignature instead.

Plan 006/2 also needed a way for provider wizards to declare React
hooks (useTrelloDiscovery, useTrelloLabelCreation, etc.). The generic
renderer in 006/1 couldn't call those conditionally (React
rules-of-hooks). Added an optional useProviderHooks field to
ProviderWizardDefinition and a new ManifestProviderWizardSection child
component that hosts the unconditional hook calls. Plans 006/3 and
006/4 will use the same contract for JIRA and Linear.

Both are additive changes to the 006/1 contract — no rework of landed
code is required beyond adding the optional field to types.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds src/integrations/pm/trello/ with:
- manifest.ts: PMProviderManifest wiring TrelloIntegration, TrelloRouterAdapter,
  all 7 Trello trigger handlers, and TrelloPlatformClient. verifyWebhookSignature
  wraps the existing verifyTrelloSignature (HMAC-SHA1 of body+callbackUrl) with
  Host/X-Forwarded-Proto header reconstruction — no shared factory because
  Trello's signing scheme is unique among providers.
- index.ts: side-effect module that calls registerPMProvider(trelloManifest).

src/integrations/pm/index.ts: new barrel importing ./trello/index.js for
the side effect. Plans 006/3 and 006/4 append jira + linear.

Contract adjustments surfaced during TDD:
- Dropped parseWebhookPayload field from PMProviderManifest (redundant with
  routerAdapter.parseWebhook; had wrong return type in 006/1). Each caller
  uses the appropriate one: router uses routerAdapter, PM-domain code uses
  pmIntegration.parseWebhookPayload.
- Relaxed conformance harness's platform-client assertion from 3 methods to
  2 (postComment + deleteComment). updateComment/postReaction are provider
  extensions, not contract.
- registerTestProvider is now additive (no longer resets the registry), so
  the conformance harness iterates TestProvider AND every real provider
  side-by-side — validating AC #2 of the spec.

Tests: 15 new Trello manifest tests + conformance now 22 (11 per provider x 2).

Trello's legacy registrations (bootstrap.ts, builtins.ts, worker-env extractor
branch, pm-wizard.tsx branch) still fire — removed in task 3 of this plan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Completes plan 006/2. Trello is the first real provider on the manifest
pattern landed in 006/1.

Backend:
- src/integrations/pm/trello/manifest.ts — wires TrelloIntegration,
  TrelloRouterAdapter, all 7 Trello trigger handlers, TrelloPlatformClient.
  verifyWebhookSignature wraps the existing verifyTrelloSignature helper
  (HMAC-SHA1 of body+callbackUrl) with header-based URL reconstruction.
- src/integrations/pm/trello/index.ts + src/integrations/pm/index.ts —
  side-effect registration barrel imported by router and worker entries.
- src/triggers/builtins.ts — iterates listPMProviders() to register
  Trello triggers via the manifest; removed registerTrelloTriggers call.
- src/router/worker-env.ts — Trello branch of extractProjectIdFromJob
  deleted; registry path handles it via the manifest's extractor hook.

Frontend:
- web/src/components/projects/pm-providers/types.ts — extended
  ProviderWizardDefinition with optional useProviderHooks field. Context
  (state, dispatch, projectId, advanceToStep) flows into the provider
  hook composer.
- web/src/components/projects/pm-providers/manifest-section.tsx — new
  ManifestProviderWizardSection shell component. Only mounted when a
  manifest is registered, so the unconditional useProviderHooks call
  inside satisfies React's rules-of-hooks.
- web/src/components/projects/pm-providers/trello/wizard.ts —
  trelloProviderWizard composes useTrelloDiscovery +
  useTrelloLabelCreation + useTrelloCustomFieldCreation inside
  useProviderHooks. Three step adapters in adapters.tsx destructure
  providerHooks into the existing TrelloCredentialsStep /
  TrelloBoardStep / TrelloFieldMappingStep prop shape — step
  implementations stay unchanged.
- web/src/components/projects/pm-wizard.tsx — removed Trello-specific
  hook instantiations and three per-step `provider === 'trello'`
  branches. The top of the component looks up manifestDef = getProviderWizard(state.provider);
  when truthy it renders <ManifestProviderWizardSection .../>, else falls
  through to the legacy JIRA/Linear branches (they migrate in 006/3/4).

Contract adjustments surfaced during TDD:
- Dropped the redundant parseWebhookPayload field from PMProviderManifest
  (had wrong return type in 006/1; duplicated routerAdapter.parseWebhook).
- Relaxed conformance harness's platform-client assertion to the actual
  PlatformCommentClient contract (postComment + deleteComment only;
  updateComment / postReaction are provider extensions).
- registerTestProvider is additive (no longer resets), so the conformance
  harness iterates TestProvider AND every real provider side-by-side.
- Six existing test files gain a side-effect import of
  src/integrations/pm/trello/index.js so the Trello manifest is
  registered before they exercise Trello-dependent code.

Deferred to plan 006/5 (all documented in the .done plan):
- Removing Trello's registration from src/integrations/bootstrap.ts —
  nine-plus call sites of pmRegistry.get('trello') still rely on the
  legacy registration.
- Consolidating createTrelloLabel/createTrelloLabels tRPC endpoints into
  pm.discovery.createLabel — additive cleanup, not behavior-changing.

Tests: 7755/7755 pass. 15 new Trello manifest tests; conformance harness
now runs 22 assertions (11 × TestProvider + Trello).

Docs: src/integrations/README.md's migration status note updated.
CHANGELOG entry added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fig export

CI's build:web step failed because web/tsconfig's stricter resolution
caught an import of a function that doesn't exist. The legacy path at
`useSaveMutation` builds Trello's integration config inline (unlike the
already-extracted `buildLinearIntegrationConfig`). Mirroring that inline
shape inside `trelloProviderWizard.buildIntegrationConfig` keeps plan
006/2 compatible with the existing save path; plan 006/5 will consolidate
`saveMutation` onto `def.buildIntegrationConfig` and extract one shared
builder.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rello

feat(006/2): trello migrated onto PM provider manifest
Mirror of 006/2 for JIRA.

Backend:
- src/integrations/pm/jira/manifest.ts — wires JiraIntegration,
  JiraRouterAdapter, all 3 JIRA trigger handlers, JiraPlatformClient.
  verifyWebhookSignature uses the shared makeHmacSha256Verifier factory
  from 006/1 — JIRA is the first consumer since Trello's scheme is
  bespoke. Header: 'x-hub-signature' with 'sha256=' prefix, hex.
- src/integrations/pm/jira/index.ts — registers via side effect.
- src/integrations/pm/index.ts — appends the jira barrel import.
- src/triggers/builtins.ts — registerJiraTriggers removed; manifest
  iteration handles it.
- src/router/worker-env.ts — jira branch of extractProjectIdFromJob
  removed (registry handles it via manifest.extractProjectIdFromJob).

Frontend:
- web/src/components/projects/pm-providers/jira/wizard.ts —
  jiraProviderWizard composes useJiraDiscovery +
  useJiraCustomFieldCreation inside useProviderHooks. Three step adapters
  in adapters.tsx destructure providerHooks into the existing JIRA step
  component prop shape — implementations unchanged.
- pm-wizard.tsx — removed useJiraDiscovery +
  useJiraCustomFieldCreation + handleCreateJiraCostField +
  creatingJiraCostField state. The three per-step render branches
  collapse: with both Trello and JIRA on manifest, the non-manifest
  fallback is now Linear-only.

Credential roles for JIRA: email + api_token (required) +
webhook_secret (optional). Plan had a drift (claimed base_url was a
credential role) — corrected: base_url is an integration-config field,
not a credential.

Tests: 7782/7782 pass. 16 new JIRA manifest tests. Conformance harness
now runs 33 assertions (11 × TestProvider + Trello + JIRA).

Tests that exercise JIRA-typed jobs (worker-env, container-manager) and
the builtins-triggers mock all picked up with JIRA manifest side-effect
imports.

Same deferrals as 006/2 (documented in .done plan):
- bootstrap.ts JIRA block stays until plan 006/5 migrates pmRegistry.get('jira') callers.
- createJiraCustomField tRPC endpoint kept (additive consolidation).

Docs: README migration status note updated. CHANGELOG entry added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat(006/3): jira migrated onto PM provider manifest
Completes the third and final PM-provider migration of spec 006. All
three providers (Trello, JIRA, Linear) are now on the manifest pattern.

Backend:
- src/integrations/pm/linear/manifest.ts — wires LinearIntegration,
  LinearRouterAdapter, 3 Linear trigger handlers, LinearPlatformClient.
  verifyWebhookSignature uses the shared makeHmacSha256Verifier factory
  with header 'linear-signature' (no prefix).
- src/integrations/pm/linear/index.ts — side-effect registration.
- src/integrations/pm/index.ts — appends the linear barrel import.
- src/triggers/builtins.ts — registerLinearTriggers call gone; every
  PM provider now contributes triggers via the manifest registry. SCM +
  alerting stay on legacy per spec scope.
- src/router/worker-env.ts — Linear branch of extractProjectIdFromJob
  removed; the function now has zero PM-specific branches.

Shared-helper adoption (collapses divergent copies that caused the
session's production incidents):
- src/router/platformClients/linear.ts — switched to linearAuthHeader
  from _shared/auth-headers. In-file Authorization construction
  deleted.
- src/router/bot-identity-resolvers.ts — same adoption.
- src/pm/linear/adapter.ts::resolveLabelId — delegates to
  _shared/label-id-resolver. Private helper + UUID_PATTERN constant
  deleted. Single source of truth for the UUID-validation rule.

Frontend:
- web/src/components/projects/pm-providers/linear/wizard.ts —
  linearProviderWizard composes useLinearDiscovery +
  useLinearLabelCreation inside useProviderHooks, plus the creatingSlot
  state + onCreateLabel / onCreateAllMissingLabels handlers using
  LINEAR_LABEL_DEFAULTS.
- web/src/components/projects/pm-wizard.tsx — with all 3 providers on
  the manifest, the non-manifest fallback path is deleted entirely.
  Every PM provider renders via ManifestProviderWizardSection. The
  parent wizard no longer owns provider-specific hooks, state, or
  handlers.

Tests: 7808/7808 pass. 15 new Linear manifest tests. Conformance
harness runs 44 assertions (11 × TestProvider + Trello + JIRA + Linear).
Build (backend + web), typecheck, lint all clean.

Same deferrals as 006/2 and 006/3 (documented in .done plan):
- bootstrap.ts Linear block stays until plan 006/5 migrates the
  ~dozen pmRegistry.get(...) callers.
- createLinearLabel / createLinearLabels tRPC endpoints kept (additive
  consolidation only, not behavior-changing).

Docs: README migration status updated. CHANGELOG entry added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…inear

feat(006/4): linear migrated — all PM providers now on manifest
Closes plan 006/5 — the final cleanup plan of spec 006.

Backend:
- src/integrations/bootstrap.ts — DELETED. PM registrations flow through
  src/integrations/pm/index.ts (which also mirrors manifests into
  integrationRegistry). SCM (GitHub) and alerting (Sentry) self-register
  via new side-effect modules.
- src/github/register.ts + src/sentry/register.ts — new minimal
  side-effect modules that replace the respective branches of the
  deleted bootstrap. SCM + alerting stay on the legacy IntegrationModule
  pattern (out of spec 006 scope).
- src/pm/registry.ts — converted to a read-only adapter over
  pmProviderRegistry. get/getOrNull/all/createProvider/
  resolveLifecycleConfig all delegate to the manifest registry.
  register() is a deprecation warn. The ~9 unmigrated call sites
  (webhook handlers, manual runner, credential scope, lifecycle, GitHub
  adapter) keep working unchanged — they now transparently read from
  the manifest registry, so there is no divergent registration.
- src/router/index.ts + src/worker-entry.ts — updated to import the
  three new side-effect modules instead of the deleted bootstrap.
- src/integrations/registry.ts — header comment updated to reflect
  the new population topology.

Tests:
- tests/unit/integrations/bootstrap.test.ts — rewritten to cover the
  new side-effect-module wiring (the file name stays for audit clarity;
  its content asserts the same end-state invariants).
- 8 other test files that imported bootstrap.js for side effect are
  migrated to import the three new modules (pm barrel + github/register
  + sentry/register).

Docs:
- src/integrations/README.md — rewritten. Transitional note + "Legacy
  path" section removed. The README is now the single canonical
  author's guide for adding a new PM provider.
- CLAUDE.md — integration-abstraction pointer updated to final state.
- CHANGELOG.md — entry per plan.

Plan-divergence:
- AC #2 (delete pm/registry.ts) — became: convert to a read-only
  delegate. Deleting it would require migrating 9 call sites that are
  out of spec scope. The delegate preserves the end state (single
  source of truth = pmProviderRegistry) without downstream churn.
- AC #5 (consolidate createXxxLabel tRPC endpoints) — deferred to a
  follow-up PR. Purely additive cleanup; not required for any spec AC.

Both divergences documented in the .done plan.

Tests: 7809/7809 pass. Build + lint + typecheck clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 5 plans of spec 006 (pm-integration-plug-and-play) have landed:
- 006/1 infrastructure — manifest contract + registry + conformance
  harness + shared helpers + generic wizard renderer
- 006/2 migrate-trello
- 006/3 migrate-jira
- 006/4 migrate-linear — completes all 3 PM providers + shared-helper
  adoption (canonical auth headers + label resolver; divergent copies
  deleted)
- 006/5 cleanup-legacy — bootstrap.ts deleted; pmRegistry becomes a
  delegate; README rewritten as canonical author's guide

Outcome-level ACs of the spec all satisfied:
- A new PM provider is added by creating one backend folder + one
  frontend folder. No edits to shared registries, router routes,
  wizard routing, trigger dispatch, or job extractors.
- Conformance harness iterates every registered manifest (44 tests
  across TestProvider + Trello + JIRA + Linear) and fails CI on any
  missing contract surface.
- Trello, JIRA, Linear continue to work identically — operators see
  no change.
- Divergent cross-cutting copies (Bearer-prefix auth, UUID-vs-name
  label resolver, forgotten job-id extractor branch) are physically
  impossible now — shared helpers are the sole call sites.
- Wizard adapts entirely from the manifest registry.
- Each migration shipped independently and is independently revertable.
- src/integrations/README.md rewritten as the single canonical guide.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…egacy

feat(006/5): cleanup legacy — spec 006 complete
Spec 002's two plans landed on dev as 1-save-path-fix.md.done and
2-wizard-webhooks-step.md.done but the spec file itself was never
renamed. Closing the loop opportunistically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_yarn/hono-4.12.14

chore(deps): bump hono from 4.12.12 to 4.12.14
…1130)

When the implementation pipeline (PM-triggered) opens a PR, the GitHub
pr-opened webhook fires immediately and queues a review pipeline. The
review pipeline captures workItemId synchronously from a DB lookup that
returns null (the implementation hasn't yet linked the PR), then later
writes that stale null back to pr_work_items, clobbering the correct
work_item_id the implementation wrote in between.

Effect: lookupWorkItemForPR returns null for the PR, the
pr-ready-to-merge trigger bails with "No work item linked to PR", and
PRs flagged with `auto` on Linear never auto-merge. Affected every
recent llmist PR (15 in a row); Trello/JIRA-backed projects appear
unaffected so far but share the racing pipeline code path.

Three intersecting fixes, all narrow:

- linkPRToWorkItem Step 2 ON CONFLICT now uses
  COALESCE(EXCLUDED.work_item_id, pr_work_items.work_item_id) so
  work_item_id is one-way set — a later writer with null can't erase a
  known link.
- linkPRToWorkItem Step 1 deletes any racing orphan
  (projectId, NULL workItemId, prNumber) row before promoting the
  work-item-only row, preventing the partial-unique-index violation
  the implementation otherwise hits silently.
- runAgentExecutionPipeline re-resolves workItemId via the existing
  resolveWorkItemId helper at run time and patches agentInput so the
  corrected value flows into runAgent (and into agent_runs.work_item_id),
  not just into the post-execution link.

Tests: +2 integration (preservation, orphan cleanup), +5 unit
(re-resolution paths + delete/no-delete coverage). All 7814 unit and
524 integration tests pass.

Drive-by cleanups so lint+typecheck are clean: drop unused
`renderManifestStep` import in pm-wizard.tsx, rename a pm-conformance
describe string that triggered noTemplateCurlyInString.

Out of scope: backfilling the 15 broken historical llmist rows
(separate manual SQL), no PR-body-parsing fallback in
resolveWorkItemId, no rewrite of how PROpenedTrigger captures
workItemId.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ine lists (#1131)

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>
…artup (#1132)

The router's [SnapshotCleanup] loop has been a sham since it shipped:
evictSnapshots() removes entries from an in-memory Map and that's it. No
docker rmi anywhere in the codebase (verified — whole-source grep
returns zero matches). The Map dies on every router restart, so every
snapshot image for a work item that never re-runs is orphaned forever.

Symptom that surfaced: dev disk filled to 100% (50MB free of 436GB),
with 13 cascade-snapshot-llmist-* images on disk totaling ~40GB. Six
of those dated to March 25 — three weeks past the 24-hour TTL the
"cleanup" loop had been pretending to enforce.

Also: snapshotMaxSizeBytes (10GB) eviction has never fired because no
caller passed imageSizeBytes to registerSnapshot — container-manager:91
called it with three args, leaving size at 0 in the Map so the size
phase always thought the registry was empty.

Three narrow fixes that together make the configured TTL/max-count/
max-size limits actually mean something on disk:

1. evictSnapshots now returns the array of evicted SnapshotMetadata
   instead of a count. snapshot-cleanup.runSnapshotCleanup() iterates
   that list and calls docker.getImage(name).remove({ force: false }).
   `force: false` so an in-use image survives (Docker returns 409 — we
   swallow it). 404 means already gone — also swallowed. Anything else
   logs warn + sentry capture.

2. New snapshot-startup-sync module: on router boot, list every
   cascade-snapshot-* image via docker.listImages, register each as a
   "discovered" entry in the in-memory Map (synthetic project key
   `__discovered__` to avoid colliding with real registrations), then
   immediately runSnapshotCleanup so TTL/max-count/max-size apply right
   away. Wired from worker-manager.startWorkerProcessor() as a
   best-effort post-startup step. This is what would have caught the
   six March images after the post-Linear-migration router restart.

3. commitContainerToSnapshot now inspects the freshly committed image
   and passes Size to registerSnapshot, so the max-size eviction phase
   actually has data to work with. Inspect failures don't block the
   registration — they just leave imageSizeBytes undefined, which falls
   back to TTL/max-count enforcement.

Tests: extended snapshot-manager.test.ts (return-type change + new
registerDiscoveredSnapshot dedup tests), rewrote snapshot-cleanup.test.ts
to mock dockerode and assert image.remove + 409/404 swallow + sentry on
unexpected error, added snapshot-startup-sync.test.ts (NEW), updated
snapshot-integration.test.ts to expect the size 4th arg on
registerSnapshot. All 7830 unit + 524 integration tests pass.

Out of scope: backfilling the 40GB on disk (cleaned manually with
docker prune; the next router restart will keep on-disk state under
SNAPSHOT_MAX_COUNT/SNAPSHOT_MAX_SIZE_BYTES going forward). The 119GB
build cache hoarding is a separate operational concern (BuildKit GC
config, not a Cascade-level bug).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…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>
…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>
…ook (#1136)

* docs(007): spec + plans for robust review dispatch

Spec 007 addresses the silent review drop observed on MNG-122/PR-572:
the work-item lock's total-concurrency cap (MAX_WORK_ITEM_CONCURRENCY=2)
blocked review dispatch while other agents were enqueued for the same
work item. Three intersecting fixes specified: per-agent-type locking,
lock release timing, and a post-completion review hook.

Two plans:
- 007/1 (lock-infra): remove MAX_WORK_ITEM_CONCURRENCY total cap,
  keep per-type MAX_SAME_TYPE_PER_WORK_ITEM=1, enrich lock-skip log.
- 007/2 (post-completion-review): deterministic review dispatch from
  the implementation pipeline after success + green CI.

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

* chore(007): lock plan 007/1 as .wip

* feat(007/1): per-agent-type work-item lock — remove false cross-type serialization

Removes the MAX_WORK_ITEM_CONCURRENCY total cap from isWorkItemLocked.
The total cap falsely serialized unrelated agent types: the review for
MNG-122/PR-572 was silently dropped because 2 agents (implementation +
backlog-manager) were already enqueued for the same work item, hitting
the total limit of 2.

Now only MAX_SAME_TYPE_PER_WORK_ITEM = 1 is enforced. Different agent
types can run concurrently on the same work item (e.g. review starts
while implementation's container is still cleaning up). Same-type
duplicate prevention is preserved.

Changes:
- src/router/work-item-lock.ts — deleted MAX_WORK_ITEM_CONCURRENCY
  constant and the total-count checks (in-memory + DB). Simplified
  getInMemoryCounts → getInMemorySameTypeCount (no longer iterates all
  keys). Removed the dbTotal query (saves one DB round-trip per lock
  check). Deleted the unused keyPrefix helper.
- src/router/webhook-processor.ts — enriched the lock-skip log with
  source (adapter type) and renamed agentType → blockedAgentType for
  clarity.
- CLAUDE.md — added per-agent-type lock semantics note under "Agent
  triggers".

Tests: updated 6 existing tests + added 2 new cross-type concurrency
tests. All 7852 unit tests pass. Lint + typecheck clean.

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

* chore(007): lock plan 007/2 as .wip

* feat(007/2): post-completion review dispatch — deterministic review after implementation

When an implementation agent succeeds with a PR, the execution pipeline
now checks CI status and fires the review agent before the container
exits. This guarantees review dispatch within seconds of implementation
completion, regardless of GitHub webhook timing.

Uses the same recursive `runAgentExecutionPipeline` pattern as the
splitting → backlog-manager chain. The review runs in the same container,
same credential scope. Uses `claimReviewDispatch` with the same dedup key
format as the `check-suite-success` trigger, so the two paths cannot
double-enqueue.

The hook is best-effort: GitHub API failures, Redis errors, or any
exception is caught, logged as warn, and does NOT break the
implementation pipeline.

New function `tryDispatchPostCompletionReview` in agent-execution.ts:
1. Extracts prNumber from agentResult.prUrl
2. Fetches PR details (headSha, headRef) from GitHub
3. Checks CI status via getCheckSuiteStatus — if not allPassing, returns
   (check-suite-success webhook will handle it when CI finishes)
4. Claims the dedup key via claimReviewDispatch — if already claimed,
   returns (review was already dispatched by the webhook path)
5. Builds a review TriggerResult and calls runAgentExecutionPipeline
   recursively (same pattern as splitting → backlog-manager)

Tests: +7 new tests covering: fires review on success + green CI, skips
for non-implementation, skips on failure, skips when no prUrl, skips when
CI not green, skips when already dispatched, swallows errors gracefully.
All 7859 unit tests pass. Lint + typecheck clean.

CLAUDE.md updated with post-completion review dispatch documentation.

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

* docs(007): spec complete — all plans done

* chore(deps): update protobufjs to fix critical CVE

npm audit flagged protobufjs <7.5.5 for arbitrary code execution
(GHSA-xq3m-2v4x-88gg). Updated via `npm update protobufjs` — lockfile
only, no package.json change.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(007): spec + plans for robust review dispatch

Spec 007 addresses the silent review drop observed on MNG-122/PR-572:
the work-item lock's total-concurrency cap (MAX_WORK_ITEM_CONCURRENCY=2)
blocked review dispatch while other agents were enqueued for the same
work item. Three intersecting fixes specified: per-agent-type locking,
lock release timing, and a post-completion review hook.

Two plans:
- 007/1 (lock-infra): remove MAX_WORK_ITEM_CONCURRENCY total cap,
  keep per-type MAX_SAME_TYPE_PER_WORK_ITEM=1, enrich lock-skip log.
- 007/2 (post-completion-review): deterministic review dispatch from
  the implementation pipeline after success + green CI.

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

* chore(007): lock plan 007/1 as .wip

* feat(007/1): per-agent-type work-item lock — remove false cross-type serialization

Removes the MAX_WORK_ITEM_CONCURRENCY total cap from isWorkItemLocked.
The total cap falsely serialized unrelated agent types: the review for
MNG-122/PR-572 was silently dropped because 2 agents (implementation +
backlog-manager) were already enqueued for the same work item, hitting
the total limit of 2.

Now only MAX_SAME_TYPE_PER_WORK_ITEM = 1 is enforced. Different agent
types can run concurrently on the same work item (e.g. review starts
while implementation's container is still cleaning up). Same-type
duplicate prevention is preserved.

Changes:
- src/router/work-item-lock.ts — deleted MAX_WORK_ITEM_CONCURRENCY
  constant and the total-count checks (in-memory + DB). Simplified
  getInMemoryCounts → getInMemorySameTypeCount (no longer iterates all
  keys). Removed the dbTotal query (saves one DB round-trip per lock
  check). Deleted the unused keyPrefix helper.
- src/router/webhook-processor.ts — enriched the lock-skip log with
  source (adapter type) and renamed agentType → blockedAgentType for
  clarity.
- CLAUDE.md — added per-agent-type lock semantics note under "Agent
  triggers".

Tests: updated 6 existing tests + added 2 new cross-type concurrency
tests. All 7852 unit tests pass. Lint + typecheck clean.

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

* chore(007): lock plan 007/2 as .wip

* feat(007/2): post-completion review dispatch — deterministic review after implementation

When an implementation agent succeeds with a PR, the execution pipeline
now checks CI status and fires the review agent before the container
exits. This guarantees review dispatch within seconds of implementation
completion, regardless of GitHub webhook timing.

Uses the same recursive `runAgentExecutionPipeline` pattern as the
splitting → backlog-manager chain. The review runs in the same container,
same credential scope. Uses `claimReviewDispatch` with the same dedup key
format as the `check-suite-success` trigger, so the two paths cannot
double-enqueue.

The hook is best-effort: GitHub API failures, Redis errors, or any
exception is caught, logged as warn, and does NOT break the
implementation pipeline.

New function `tryDispatchPostCompletionReview` in agent-execution.ts:
1. Extracts prNumber from agentResult.prUrl
2. Fetches PR details (headSha, headRef) from GitHub
3. Checks CI status via getCheckSuiteStatus — if not allPassing, returns
   (check-suite-success webhook will handle it when CI finishes)
4. Claims the dedup key via claimReviewDispatch — if already claimed,
   returns (review was already dispatched by the webhook path)
5. Builds a review TriggerResult and calls runAgentExecutionPipeline
   recursively (same pattern as splitting → backlog-manager)

Tests: +7 new tests covering: fires review on success + green CI, skips
for non-implementation, skips on failure, skips when no prUrl, skips when
CI not green, skips when already dispatched, swallows errors gracefully.
All 7859 unit tests pass. Lint + typecheck clean.

CLAUDE.md updated with post-completion review dispatch documentation.

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

* docs(007): spec complete — all plans done

* fix(linear): create issues directly in backlog state via stateId

Linear's createIssue API supports stateId, but the adapter was creating
issues in the team's default state ("Ideas") then attempting a separate
moveWorkItem transition — which silently failed. Additionally, the
splitting agent's prompt context provided the backlog status UUID as
containerId, causing "Entity not found: Team" errors and wasting ~15
LLM iterations per run.

- Pass stateId to linearClient.createIssue() for atomic backlog placement
- Remove fragile post-creation moveWorkItem transition (13 LOC of
  error-swallowing code)
- Fix promptContext to provide teamId (not status UUID) as backlogListId
  for Linear, matching what CreateWorkItem expects as containerId

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
zbigniewsobiecki and others added 28 commits April 17, 2026 20:03
The configMapper.ts dropped projectId when mapping Linear integration
config from the DB to ProjectConfig. The JSON in project_integrations
had projectId, but buildLinearConfig() only copied teamId, statuses,
labels, and customFields — never projectId.

This broke every downstream consumer: worker env injection
(CASCADE_LINEAR_PROJECT_ID never set), createWorkItem/listWorkItems/
addChecklistItem (issues created without project scope), and the
router's webhook scope filter (never applied).

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
addChecklistItem() created sub-issues via linearClient.createIssue()
without stateId, so they landed in the team's default state ("Ideas")
instead of "Backlog". Same bug as createWorkItem() (fixed in #1137),
different code path.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(008): lock plan 008/1 as .wip

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

* feat(008/1): shared inline markdown checklist engine

Pure string transformer for reading, writing, and mutating inline
markdown checklists within issue descriptions. Supports parsing,
appending, adding items, toggling checked state, removing items,
and stable content-hash IDs.

Dormant — no adapter wiring yet (plan 2).

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

* chore(008): lock plan 008/2 as .wip

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

* feat(008/2): inline markdown checklists for Linear and JIRA

Replaces sub-issue/subtask checklist implementation with inline
markdown checkboxes appended to the parent issue's description.
Linear writes plain markdown; JIRA round-trips through ADF.

Both adapters use the shared engine from plan 1. PMProvider interface
unchanged. Read-modify-write with one retry on conflict.

Trello unchanged (uses native checklists).

Closes spec 008.

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

* docs(008): spec complete — all plans done

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

* chore(008): cleanup — gitignore lock file and refactor parser

Untrack .claude/scheduled_tasks.lock (committed by accident); add to
gitignore. Refactor parseInlineChecklists to extract state-handling
helper, removing the cognitive complexity warning.

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n a triggering status

- Extend LinearStatusChangedTrigger.matches() to also match action: 'create' events
- Extend JiraStatusChangedTrigger to match and handle jira:issue_created events
- Update Linear and JIRA unit tests to cover creation scenarios

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JIRA_EMAIL/JIRA_API_TOKEN/JIRA_BASE_URL were set in the CI/dev
environment causing resolvePmType() to return 'jira' instead of
'trello' for tests that don't set CASCADE_PM_TYPE, leading to
createPMProvider failing with "JIRA integration requires projectKey".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ng it (#1142)

PR #1138 added projectId to the configMapper, but Zod's
LinearConfigSchema didn't declare projectId — and z.object() defaults
to "strip" mode, silently removing unknown keys during .parse(). So
projectId survived the mapper but got dropped by validateConfig(),
never reaching augmentProjectSecrets() or the LinearPMProvider.

Result: new Linear issues created by splitting / planning agents had
no project assignment despite the configMapper fix.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spec 009 turns the PMProviderManifest contract from wiring-only to
behavioral. Decomposes into 5 plans: infra + 3 provider migrations +
cleanup, mirroring spec 006's proven shape.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 1: Branded StateId, LabelId, ContainerId + parsers + InvalidIdError.
Task 2: Extend PMProvider with optional discover? method + associated
        DiscoveryCapability / DiscoveryArgs / DiscoveryResult machinery.
Task 3: Extend PMProviderManifest with optional configSchema,
        discoveryCapabilities, wizardSpec, and lifecycle fields +
        validateManifestAgainstSchema helper.

Plan divergence note: plan 1's claim that switching the PMProvider method
signatures from string to branded types was non-breaking is incorrect —
branded types are NOT assignable from plain strings. Resolved by keeping
PMProvider method signatures as string at the interface level and
deferring per-adapter narrowing to plans 2/3/4, which matches both plan 1's
"dormant" intent and plans 2/3/4's per-adapter adoption ACs.

All new fields are optional → existing manifests compile unchanged. 44
existing pm-conformance tests still pass.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
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>
Task 6: tests/helpers/fakePMProvider.ts ships an in-memory FakePMProvider,
its matching PMProviderManifest (opts into configSchema, every
discoveryCapability, wizardSpec, lifecycle.enabled), and a shared
runLifecycleScenario runner. All seven fake-lifecycle tests pass.

Task 7: tests/unit/integrations/pm-conformance.test.ts extended with 5
new behavioral assertion groups:
  - config round-trip identity (guarded by manifest.configSchema)
  - discovery shape (guarded by manifest.discoveryCapabilities)
  - full lifecycle scenario (guarded by manifest.lifecycle.enabled)
  - trigger self-hook filter (guarded by manifest.isSelfAuthoredHook)
  - webhook verify accept/reject (runs against the fake's HMAC-SHA256)

Legacy providers skip the new groups until they opt in during plans
2/3/4. Harness now runs 80 tests (59 pass, 21 skip — the skips are
legacy providers pending migration, as designed).

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 8: tests/unit/integrations/auth-header-provenance.test.ts greps the
src tree for `Bearer ${...}` / bare string-concat auth-header patterns
outside src/integrations/pm/_shared/auth-headers.ts. Post-#1119, the PM
provider code is clean; 4 non-PM files (Sentry, GitHub SCM, OpenRouter
LLM) are explicitly accept-listed with reasons — all out of spec 009
scope.

Task 9: Biome can't natively express the required string-pattern rule,
so lefthook.yml runs the provenance test at pre-commit (~250ms) —
failures surface at commit time, not just test time. Equivalent to a
custom Biome rule; matches plan 009/1 task 9's fallback guidance.

Also tightened the expanded conformance harness + fake-lifecycle test
to eliminate Biome complexity/non-null-assertion warnings.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Task 10: src/api/routers/pm-discovery.ts now exposes a generic
`discover({ providerId, capability, args, credentials? })` mutation that
dispatches through the registry. Manifest must declare both
`discoveryCapabilities` and `createDiscoveryProvider`. Returns NOT_FOUND
for unknown providers, NOT_IMPLEMENTED for undeclared capabilities or
missing factories. 7 tests pass.

Task 11: web/src/components/projects/pm-providers/generator.tsx ships
`renderStandardStep(step, ctx)` as dormant scaffolding — returns a
typed placeholder for every StandardStepKind + custom steps. Plans 2/3/4
swap each placeholder for the shared component. Unknown kinds log a
warn-once and render a placeholder instead of crashing.

Task 12: tests/README.md and src/integrations/README.md document the
new fake provider fixture, the lifecycle scenario runner, the behavioral
contract fields on PMProviderManifest, the auth-header provenance test,
and the single-entrypoint invariant.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Ships branded ID types, PMProviderManifest behavioral contract fields,
single registration entrypoint, FakePMProvider fixture + lifecycle runner,
5 new behavioral conformance groups, auth-header provenance enforcement,
generic pm.discover tRPC endpoint, and wizard step generator scaffolding.

All primitives dormant — no provider migrated yet. Plans 2/3/4 flip each
real provider on; plan 5 cleans up legacy surfaces.

415 test files / 7963 tests pass / 21 intentional skips. Lint, typecheck,
and build all green.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Trello opts into configSchema, discoveryCapabilities, wizardSpec,
lifecycle, createDiscoveryProvider, and branded-ID narrowing on
adapter methods. Inline trello schema in src/config/schema.ts marked
@deprecated; plan 5 deletes it.

420 test files, 7988 pass, 19 skip. Lint + typecheck + build green.
Plan divergence notes in the .done file document 5 resolved issues.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
spec 009: PM integration hardening — make the next provider boring
…s-created-status

feat(triggers): support PM triggers for work items created directly in a triggering status
Make the create-path firing (added by PR #1141) independently configurable
per agent via the existing declarative YAML/CLI/dashboard pipeline.

Two boolean params on pm:status-changed:
- onMove   (default true)  — fire when an item is moved into the status
- onCreate (default false) — fire when an item is created in the status

Linear and JIRA: preserve pre-#1141 behavior by default; users opt in
explicitly. Trello: data migration backfills onCreate=true for existing
projects so fire-on-create keeps working without silent regression.

Also tightens create-path matches() to require status presence, restores
fromStatus in JIRA update-path log, de-dups the JIRA test helper's
ternary, and extracts resolveAgentType/shouldFireOnEvent helpers to keep
handler complexity in check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…changed-onCreate-onMove-params

feat(triggers): onCreate/onMove params for pm:status-changed
@zbigniewsobiecki zbigniewsobiecki merged commit ba3992a into main Apr 18, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant