docs: add Adding Engines guide and update backends README#1030
Conversation
…rray
GitHub fires check_suite webhooks with pull_requests: [] when CI runs on
the refs/pull/{N}/head virtual ref rather than the named feature branch.
The CheckSuiteFailureTrigger's matches() guard silently rejected these,
so respond-to-ci never fired even when CI failed on implementer PRs.
Root cause confirmed via webhook log f0f88951 for PR #1030: conclusion
was "failure", head_branch was "refs/pull/1030/head", pull_requests was [].
Changes:
- Extract parsePrNumberFromRef() to utils.ts (removes duplication between
the two trigger files, adds JSDoc explaining the merge-ref exclusion)
- Restrict the regex to refs/pull/{N}/head only — the /merge variant uses
a synthetic merge-commit SHA that is not part of the PR branch history
- Update matches() in both CheckSuiteFailureTrigger and CheckSuiteSuccessTrigger
to accept events where pull_requests is empty but head_branch parses as a PR ref
- Simplify prBranch resolution to always use prDetails.headRef (already
fetched) instead of a conditional that picked the same value either way
- Fix head_branch type to string | null (GitHub sends null, not undefined,
when the check suite is not associated with a named branch)
- Add respond-to-ci trigger example to docs/getting-started.md
Tests:
- New unit tests for the empty pull_requests + refs/pull/{N}/head path in
both trigger test files (matches and handle coverage)
- New describe('parsePrNumberFromRef') block in github-utils.test.ts
covering null/undefined/empty, plain branches, /merge rejection,
valid /head refs, and partial-match rejection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rray (#1031) GitHub fires check_suite webhooks with pull_requests: [] when CI runs on the refs/pull/{N}/head virtual ref rather than the named feature branch. The CheckSuiteFailureTrigger's matches() guard silently rejected these, so respond-to-ci never fired even when CI failed on implementer PRs. Root cause confirmed via webhook log f0f88951 for PR #1030: conclusion was "failure", head_branch was "refs/pull/1030/head", pull_requests was []. Changes: - Extract parsePrNumberFromRef() to utils.ts (removes duplication between the two trigger files, adds JSDoc explaining the merge-ref exclusion) - Restrict the regex to refs/pull/{N}/head only — the /merge variant uses a synthetic merge-commit SHA that is not part of the PR branch history - Update matches() in both CheckSuiteFailureTrigger and CheckSuiteSuccessTrigger to accept events where pull_requests is empty but head_branch parses as a PR ref - Simplify prBranch resolution to always use prDetails.headRef (already fetched) instead of a conditional that picked the same value either way - Fix head_branch type to string | null (GitHub sends null, not undefined, when the check suite is not associated with a named branch) - Add respond-to-ci trigger example to docs/getting-started.md Tests: - New unit tests for the empty pull_requests + refs/pull/{N}/head path in both trigger test files (matches and handle coverage) - New describe('parsePrNumberFromRef') block in github-utils.test.ts covering null/undefined/empty, plain branches, /merge rejection, valid /head refs, and partial-match rejection Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
Documentation-only PR that adds a comprehensive engine authoring guide and updates the backends README. The guide is well-structured, accurately reflects the codebase's current architecture, and the core CI checks (lint, test, typecheck, Docker build, integration) all pass. The two failing checks (Analyze (javascript-typescript) and Analyze (actions)) are CodeQL scans unrelated to markdown/documentation changes.
I verified the guide's code templates, file paths, type names, function signatures, and architectural descriptions against the actual codebase. Everything checks out with one minor inconsistency worth noting.
Should Fix
-
docs/adding-engines.md (Sections 4 vs 7) — The
env.tstemplate in Section 4 exportsALLOWED_ENV_EXACT, and the test template in Section 10 imports fromenv.ts— but the engine class template in Section 7 inlines the same set directly ingetAllowedEnvExact()rather than importing fromenv.ts. This creates a duplication hazard: a developer following the guide would define the same set in two places (env.tsand the class), which could drift. The Section 7 template should import from./env.jsinstead:import { ALLOWED_ENV_EXACT } from './env.js'; getAllowedEnvExact(): Set<string> { return ALLOWED_ENV_EXACT; }
This matches the pattern used by Claude Code's engine class (which imports
ALLOWED_ENV_EXACTfrom./env.jsand returns it directly). The Codex and OpenCode engines do inline the set, but they also have legacybuildEnv()functions in theirenv.tsthat predate theNativeToolEnginerefactor — new engines shouldn't follow the legacy pattern.
Notes
- The
NativeToolEnginebase class has aresolveSettings(input, schema)helper (line 121 ofNativeToolEngine.ts) that the guide's template doesn't mention. All existing engines use standaloneresolveXxxSettings()functions instead, so the template is consistent with current practice — but a brief mention of this base-class alternative could be useful for implementors who prefer it. - All file paths, export names, function signatures, and type references in the guide are accurate against the current codebase.
- The relative link in
src/backends/README.md(../../docs/adding-engines.md) resolves correctly.
🕵️ claude-code · claude-opus-4-6 · run details
…s template Import ALLOWED_ENV_EXACT from ./env.js and return it directly in getAllowedEnvExact(), eliminating the duplication hazard of defining the same set in both env.ts and the engine class. This matches the pattern used by the Claude Code engine class. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
nhopeatall
left a comment
There was a problem hiding this comment.
Summary
LGTM — Well-structured, comprehensive documentation that accurately reflects the codebase. The guide covers all necessary steps for adding a new engine, and the code examples are consistent with actual patterns in the repository.
Verification
I verified the guide's claims against the actual source files:
NativeToolEngineabstract methods —getAllowedEnvExact(),getExtraEnvVars(),resolveEngineModel(),execute()all match the abstract class contract insrc/backends/shared/NativeToolEngine.tsbuildSystemPrompt/buildTaskPromptsignatures — matchsrc/backends/shared/nativeToolPrompts.tsbuildEngineResult/extractAndBuildPrEvidence— matchsrc/backends/shared/engineResult.tsregisterEngineWithSettingspattern — matchessrc/backends/bootstrap.ts(checksgetSettingsSchemapresence, registers both engine and schema)AgentEngineDefinitiontype shape — capabilities array,modelSelectionunion type,settingsfield,archetypefield all matchsrc/backends/types.tsSHARED_ALLOWED_ENV_EXACT/SHARED_BLOCKED_ENV_EXACT— matchsrc/backends/shared/envFilter.ts- Engine directory layouts — verified
claude-code/,codex/,opencode/all follow the pattern described - Test directory — confirmed test files exist at
tests/unit/backends/as referenced - Architecture quick-reference — file tree matches actual
src/backends/shared/contents (core files present) - Real-world examples table — Claude Code is correctly noted as "SDK-based (not subprocess)", Codex correctly noted as "subprocess + JSONL"
- Relative link
../../docs/adding-engines.mdfromsrc/backends/README.mdresolves correctly to project root
All CI checks pass. The backends README update is a clean improvement — replacing the terse 3-line "how to add an engine" section with a proper archetype explanation and a link to the full guide.
🕵️ claude-code · claude-opus-4-6 · run details
Summary
docs/adding-engines.md— comprehensive step-by-step guide for adding a new agent engine to CASCADEsrc/backends/README.mdto reference the new guide and reflect theNativeToolEnginebase class and archetype systemWhat's in the guide
The guide covers every step needed to add a new subprocess-based engine (e.g. Gemini CLI, Kilo Code):
native-tool(subprocess CLI) vssdk(in-process like LLMist), with clear decision criteriasrc/backends/<engine-name>/AgentEngineDefinitionincatalog.tswitharchetypefieldSHARED_ALLOWED_ENV_EXACT, blocking server-side secretsmodels.tspattern, including free-text option for open-ended modelsNativeToolEnginesubclass with all abstract methods, lifecycle hooks, and key helpers annotatedregisterEngineWithSettings()inbootstrap.tsIncludes a reference table of all four existing engines (Claude Code, Codex, OpenCode, LLMist) with their archetypes and notable implementation patterns.
Test plan
npm run typecheck)npm run lint) — no issues in markdown or TS filesnpm test)Trello card
https://trello.com/c/EggwMeUU/542-as-a-developer-i-want-an-adding-engines-guide-so-that-adding-a-new-harness-takes-hours-instead-of-days
🤖 Generated with Claude Code
🕵️ claude-code · claude-sonnet-4-6 · run details