Skip to content

ci: enforce Conventional Commits with a commitlint gate - #660

Closed
EivMeyer wants to merge 276 commits into
mainfrom
eivind/ci-commitlint-gate
Closed

ci: enforce Conventional Commits with a commitlint gate#660
EivMeyer wants to merge 276 commits into
mainfrom
eivind/ci-commitlint-gate

Conversation

@EivMeyer

Copy link
Copy Markdown
Collaborator

What

Adds a commitlint gate so the Conventional Commit messages that semantic-release relies on are actually enforced.

  • .commitlintrc.json — extends @commitlint/config-conventional; long body/footer lines allowed (squash-merge bodies have Markdown tables and links). Mirrors the config in the-open-engine/agents, per "same approach everywhere."
  • .husky/commit-msg — local validation hook (none existed; only pre-commit/pre-push were present).
  • CI commitlint job — validates every PR commit's message via npx, running before the expensive check job so bad messages fail fast. Dependency-light (no npm ci needed for the gate).

Why

semantic-release derives the version bump from commit-message type (fix: → patch, feat: → minor, etc.). Nothing validated those messages, so a non-conventional subject silently produces no bump or the wrong one — a contributor to the version drift tracked in #640.

Validation

Ran the exact CI config against real messages:

FAIL  | updated stuff              -> subject may not be empty [subject-empty]
FAIL  | foo: bar                   -> type must be one of [...] [type-enum]
FAIL  | Feat: add thing            -> type must be lower-case [type-case]
PASS  | fix: correct version drift
PASS  | feat(release): enforce conventional commits

(Note: a doubled prefix like feat: feat(x): ... passes — commitlint reads feat as the type and the rest as the subject. This gate targets missing/unknown/wrong-case types, which is what breaks version computation.)

Scope boundary

This PR is deliberately just the gate. The trunk cutover items from #640 ride with the dev/main reconciliation, not here:

  • removing the enforce-main-pr-source CI job (which currently mandates the dev → main promotion that causes the divergence)
  • swapping the release job's GITHUB_TOKEN for a repo-scoped release GitHub App
  • the main-didn't-move guard on release.yml

Follow-ups surfaced while implementing (for the reconciliation, not this PR)

  1. Dual semantic-release config. main has .releaserc.json (with @semantic-release/git + changelog, which commit the version back), but dev's package.json has an inline release block without the git plugin — so under dev's config no version-bump commit is written. Likely a direct contributor to branch ≠ npm drift. Consolidate to one config.
  2. Squash-merge nuance. If PRs squash-merge, the message that reaches the trunk is the PR title, not these per-commit messages. If that's the flow, we should also lint the PR title. Left out to keep this focused.

Base is dev per the repo's current enforce-main-pr-source policy.

tomdps and others added 30 commits January 26, 2026 16:54
## Summary
- avoid treating "Task not found"/"Process terminated" substrings inside
JSON logs as fatal
- only treat standalone fatal lines as no-output
- add output-extraction unit tests for fatal-string handling

## Testing
- npm test

Fixes #165
## Summary

- Replace `src/tui/` blessed implementation with minimal Ink TSX app
- Add `tsconfig.tui.json` for TUI-specific TypeScript compilation
- Add `build:tui` script to package.json, run in prepublishOnly
- Repoint `zeroshot watch` to `lib/tui` compiled entrypoint
- Remove blessed dashboard tests (tui-layout.test.js)
- Update AGENTS.md with Ink TUI entrypoint documentation

## Changes

The new TUI renders "Hello Ink TUI" and exits on keypress (or auto-exits
after delay). This is the foundation for the TUI v2 implementation per
PRD.

**Files added:**
- `src/tui/index.tsx` - Minimal Ink entrypoint with `start(options)`
export
- `tsconfig.tui.json` - TypeScript config targeting `lib/tui/`

**Files modified:**
- `package.json` - Added ink, react deps and build:tui script
- `cli/index.js` - `zeroshot watch` now uses `lib/tui`
- `AGENTS.md` - Updated docs
- `.gitignore` - Added `lib/tui/`

**Files removed:**
- All old blessed TUI files under `src/tui/`
- `tests/tui-layout.test.js`

## Validation

Both validators approved:
- ✅ `npm run build:tui` produces `lib/tui/index.js`
- ✅ Ink entrypoint runs and exits cleanly
- ✅ `zeroshot watch` points to compiled Ink entrypoint
- ✅ Package tarball includes `lib/tui/`
- ✅ No blessed code remains in `src/tui/`

Closes #172
Fixes #176.

- relax git-pusher evidence gating to allow CANNOT_VALIDATE/empty
outputs
- add regression test for trigger evaluation
## Summary
- Fix maxRetries to use settings.maxRetries ?? 3 (was ?? 1 - NO
RETRIES!)
- Add rate-limit-aware backoff (30s base for 429s vs 2s for others)
- Add Gemini "No capacity available" to retryable patterns

## Problem
Both `falling-totem-77` and `mystic-vertex-66` failed because
`agent-lifecycle.js:738` had `maxRetries ?? 1` which meant **no actual
retries**.

## Test plan
- [x] Unit tests for rate-limit-backoff module
- [x] Manual verification: `node -e` script confirmed detection works
- [ ] Resume failed clusters after merge

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Fixes eslint errors from generated TUI output by disabling rules for
lib/tui in eslint config.
Closes #183

---------

Co-authored-by: Eivind Meyer <eiv.meyer@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Eivind Meyer <eivind.meyer@ksat.no>
Co-authored-by: Michael Eichelbeck <141341133+mkceichelbeck@users.noreply.github.com>
Co-authored-by: Michael Eichelbeck <michael.eichelbeck.ext@wtsde.onmicrosoft.de>
## Summary
- add typed command registry + dispatch wiring for TUI slash commands
- add CLI compatibility helpers for list/status output
- add tests for registry and dispatcher updates

## Testing
- npm test (via pre-commit)
- npm run lint (warnings only)

Fixes #185
## Summary
- add reusable start-cluster helper for TUI (text/issue/file)
- add TUI service wrapper for starting clusters
- add unit tests for helper

## Testing
- npm test (via pre-commit)
- npm run lint (warnings only)

Fixes #187
## Summary
- wire Launcher submit to start cluster from text
- treat numeric input as plain text
- optimistic cluster id + navigate to Cluster view
- tests for launcher behavior

## Testing
- npm test (via pre-commit)
- npm run lint (warnings only)

Fixes #189
## Summary
- implement /issue command to launch an issue cluster and move to
Cluster view
- add issue launch wiring + deps in dispatcher/context
- add cluster launch helper and tests

## Testing
- not run (eslint reports pre-existing issues in repo)
## Summary
- exclude generated lib/tui output from linting

## Testing
- npm run lint (warnings only)
- tsc --noEmit (pre-push)
## Summary
- add a cluster log streaming helper that polls the ledger and emits
normalized log lines
- render live logs with timestamps and agent attribution in Cluster view
(bounded to last N lines)
- add unit coverage for db path resolution and stream readiness
- stabilize /status detection under parallel tests by honoring env home
paths and adding a task/cluster fallback

## Testing
- `npm test`

Closes #194.
## Summary
- add a cluster registry helper to read orchestrator cluster summaries
- render a selectable Monitor view list with refresh + keyboard
navigation
- wire Monitor view into app/router and open selected cluster
- add unit coverage for cluster sorting and cwd resolution

## Testing
- `npx mocha tests/unit/tui-cluster-registry.test.js`

Closes #196.
## Summary
- add cluster metrics aggregation using pidusage
- render CPU/memory columns in Monitor view
- extend cluster registry tests for metrics handling

## Testing
- npx mocha tests/unit/tui-cluster-registry.test.js

Closes #198
## Summary
- start TUI in Monitor view when using `zeroshot watch`
- keep default Launcher view for `zeroshot tui`

## Testing
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/mocha
tests/cli-rendering.test.js

Closes #201
## Summary
- add cluster topology model from config (agents + topic wiring)
- render topology section in Cluster view (agents + adjacency list)
- add unit coverage for topology model parsing

## Testing
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/tsc -p
tsconfig.tui.json
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/mocha
tests/unit/tui-cluster-topology.test.js

Closes #203
## Summary
- add ledger-backed workflow timeline stream for key topics
- render timeline section in Cluster view
- add unit coverage for timeline service

## Testing
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/tsc -p
tsconfig.tui.json
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/mocha
tests/unit/tui-cluster-timeline.test.js

Closes #205
## Summary
- add agent selection in Cluster view with Enter to open Agent view
- tail logs for a specific agent using filtered cluster log stream
- add agent log filtering coverage

## Testing
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/tsc -p
tsconfig.tui.json
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/mocha
tests/unit/tui-cluster-logs.test.js

Closes #207
## Summary
- add agent message queue helper utilities
- render message input + queued list in Agent view
- queue pending messages in app state for Agent view

## Testing
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/tsc -p
tsconfig.tui.json
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/mocha
tests/unit/tui-agent-messages.test.js

Closes #209
## Summary
- add guidance topics module and ledger mailbox query helper
- map target_agent_id to receiver on ledger writes
- expose guidance mailbox via message bus and add tests

## Testing
- NODE_PATH=/Users/tom/code/covibes/external/zeroshot/node_modules
/Users/tom/code/covibes/external/zeroshot/node_modules/.bin/mocha
tests/unit/guidance-mailbox.test.js

Closes #211
## Summary

When Claude CLI returns an error result without actual agent output, the
extraction pipeline was falling through to `extractDirectJson` which
blindly accepted any JSON object - including raw CLI metadata like:
```json
{"type":"result","subtype":"error","duration_ms":1234,"session_id":"abc",...}
```

This caused schema validation to run against the wrong data structure,
generating confusing warnings:
```
Agent worker output failed JSON schema validation: #/required must have required property 'summary'; 
#/required must have required property 'completionStatus'
```

## Fix

Add `isCliMetadata()` check to reject objects that have:
- `type:result` (CLI wrapper format)
- 2+ CLI-specific metadata fields (duration_ms, session_id,
total_cost_usd, etc.)

This ensures `parseResultOutput` returns `null` for error cases,
triggering proper error handling rather than schema validation against
wrong data.

## Test plan

- [x] Added unit tests for CLI metadata rejection
- [x] Added regression test for error result scenario
- [x] All 70 output extraction tests pass

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary

- Add `extractCliError()` for Claude, Codex, Gemini, Opencode error
formats
- Check CLI errors FIRST before JSON extraction in `parseResultOutput()`
- Surface real error messages instead of useless schema validation
errors

## Before/After

**Before:**
```
Agent worker output failed JSON schema validation: must have required property 'summary'
```

**After:**
```
CLI error (claude): Permission denied for tool Write
```

## Provider Error Formats

| Provider | Error Event Format |
|----------|-------------------|
| Claude | `{type:"result", is_error:true, errors:[...]}` |
| Codex | `{type:"turn.failed", error:{message:...}}` |
| Gemini | `{type:"result", success:false, error:...}` |
| Opencode | `{type:"session.error", error:{...}}` |

## Test Coverage

14 tests covering all providers, error variants, and edge cases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
## Summary
- add live guidance injection via attach stdin
- persist guidance delivery metadata on guidance messages
- add tests for attach stdin + guidance delivery

## Testing
- npm test -- tests/unit/attach-stdin.test.js
tests/unit/guidance-delivery.test.js
- npm run lint
- npm run typecheck
mkceichelbeck and others added 25 commits July 9, 2026 13:25
## Summary
Adds **GitHub Copilot** as a registry-backed provider engine
(`copilot`), modeled on the Pi provider: adapter + JSONL parser +
registry/type/contract wiring.

The parser is grounded in the **real** `copilot --output-format json`
schema (verified against CLI v1.0.69): dot-namespaced events with
payload under `data`, `phase:"commentary"` → thinking / `final_answer` →
text, and success derived from the terminal `exitCode`. Fail-open on
unknown event types.

Also makes two capabilities genuinely functional (verified, not
assumed):
- **thinkingMode** — commentary narration is surfaced as thinking, the
final answer as text.
- **dockerIsolation** — `--docker` now auto-activates the *running*
provider's credential preset (mount + env passthrough) in
`isolation-manager.js`, so `COPILOT_GITHUB_TOKEN` reaches the container
with no `dockerMounts` change (also auto-mounts codex/gemini/opencode/pi
creds; claude stays special-cased). A keychain-token provider with no
token warns loudly instead of failing silently.

## Verification
- `npm run check:agent-cli-provider` 100/100; full unit suite green;
adapter + docker preset/warning unit tests.
- Real-schema parser verified by replaying captured copilot output.
- A real `zeroshot run --provider copilot` cluster ran end-to-end
(parsed streamed JSONL, wrote the expected file, fired completion).
- Offline `fake-copilot` e2e (`tests/e2e/copilot-provider.test.js`) as
the committed regression.

## Follow-up (not blocking)
- No live `--docker` container round-trip was run (no image built
locally; keychain token extraction is interactive). Docker is verified
at the unit + direct-token-auth + mechanism level — a single live
container smoke test would fully close it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- add an explicit opt-in live provider smoke runner for real installed
CLIs/gateway endpoints
- document how to run Pi, Copilot, and gateway live smoke checks
- keep normal CI deterministic/offline while making paid/authenticated
provider checks available before release

## Verification
- node scripts/live-provider-smoke.js exits with usage when
ZEROSHOT_LIVE_PROVIDERS is missing
- PATH=<fake-copilot> ZEROSHOT_LIVE_PROVIDERS=copilot npm run
test:providers:live
- npx eslint scripts/live-provider-smoke.js
- npm run check:agent-cli-provider:ci
- npm run test:e2e -- --grep 'copilot provider'

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- trust noninteractive Gemini workspaces by setting
`GEMINI_CLI_TRUST_WORKSPACE=true`
- classify Gemini unsupported-client / `IneligibleTierError` failures as
permanent instead of retryable
- include provider error classification in live smoke summaries

## Live smoke evidence
- `claude` live smoke passed
- `codex` live smoke passed
- `opencode` live smoke passed
- `gemini` live smoke now fails as permanent `IneligibleTierError` due
external account/client eligibility, not workspace trust or a retryable
Zeroshot error
- `pi`, `copilot`, and `kiro` were attempted but the CLIs are not
installed on this machine
- `gateway` was attempted but gateway env vars are not configured here

## Verification
- `npm run build:agent-cli-provider && npx mocha
tests/provider-cli-builder.test.js --grep 'Gemini provider helper
builder' --timeout 120000`\n- `npm run build:agent-cli-provider && node
--test tests/agent-cli-provider/executable-contract-output.test.js`\n-
`npm run check:agent-cli-provider:ci`\n- `ZEROSHOT_LIVE_PROVIDERS=claude
npm run test:providers:live`\n- `ZEROSHOT_LIVE_PROVIDERS=codex npm run
test:providers:live`\n- `ZEROSHOT_LIVE_PROVIDERS=opencode npm run
test:providers:live`\n- `ZEROSHOT_LIVE_PROVIDERS=gemini npm run
test:providers:live` expected external failure: permanent
`IneligibleTierError`\n

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- add a manual/opt-in scheduled `Live Provider Smoke` workflow for real
provider CLI/gateway checks
- let `ZEROSHOT_LIVE_PROVIDERS=all` expand from the provider registry
- document release-gate provider smoke usage, required secrets, and
self-hosted-runner requirements

## Why
The normal CI suite proves the provider contract with fixtures and fake
providers. It does not prove real installed CLIs or gateway credentials
work. This workflow creates an explicit live gate: selected providers
fail if their CLI or credential is missing.

## Verification
- `npx prettier --check .github/workflows/live-provider-smoke.yml
docs/providers.md scripts/live-provider-smoke.js`
- registry matrix parse check for `all` -> all provider ids
- `npm run check:agent-cli-provider:ci`
- `ruby -e 'require "yaml";
YAML.load_file(".github/workflows/live-provider-smoke.yml"); puts "yaml
ok"'`\n\n## Notes\nThis does not claim every provider has now passed
live smoke. It adds the release gate needed to run those tests with real
secrets/runners. Earlier local live smoke passed for Claude, Codex, and
Opencode; Gemini reached a real permanent account/client eligibility
failure; Pi, Copilot, Kiro, and Gateway still require installed CLIs
and/or configured credentials/endpoints.\n

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
…ntract schema (1/5) (#612)

Closes #605

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
…undo conflict rules (2/5) (#613)

Closes #606

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- align source package metadata with the published 6.4.0 package
- add semantic-release changelog/git prepare steps so future release
tags include bumped package metadata

## Verification
- node -p "require('./package.json').version + ' ' +
require('./package-lock.json').version + ' ' +
require('./package-lock.json').packages[''].version"
- npx mocha tests/package-smoke.test.js --timeout 30000
- npm run typecheck
- npx semantic-release --dry-run (loads new plugins; stops locally on
missing NPM_TOKEN/GITHUB_TOKEN, expected outside CI)

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
…ts them (#616)

Fixes #614.

## Problem

The injected validator quality-gate schema declared
`completedAt`/`timestamp` as union `type: ['string', 'number']` arrays.
Strict-mode AJV consumers reject union `type` arrays:

```
strict mode: use allowUnionTypes to allow union type keyword
  at "#/properties/qualityGates/items/properties/completedAt" (strictTypes)
```

That threw during validator output validation before the validator's
output could be checked, marking the task failed and burning validator
retries until ship runs failed. Observed live killing an opcore ship run
in validation.

## Fix

Replace the union `type` arrays with `anyOf`, which strict mode accepts
with no `allowUnionTypes` flag and keeps the string|number constraint.
Instance-agnostic: works regardless of which AJV consumer compiles the
schema.

```js
completedAt: { anyOf: [{ type: 'string' }, { type: 'number' }], ... }
timestamp:   { anyOf: [{ type: 'string' }, { type: 'number' }], ... }
```

## Why tests didn't catch it

The prior tests only asserted whether the schema was *injected*, never
that it *compiles* under a strict AJV. And AJV's default `new Ajv()`
only **logs** `strictTypes` (it does not throw), so a naive compile test
would pass on the buggy schema too — that is the exact gap that let this
ship.

New `tests/quality-gate-schema.test.js` compiles under `{ strict: true
}` (which throws on the union shape) and includes a guard test proving
that config actually discriminates the bug, plus string/numeric
acceptance and a wrong-type rejection. Verified: the real schema
reverted to unions throws the exact `strictTypes` error; the `anyOf`
version passes all 5.

## Test

- `node tests/run-tests.js tests/quality-gate-schema.test.js` → 5
passing
- `node tests/run-tests.js tests/required-quality-gates-context.test.js`
→ 3 passing (no regression)
- `tsc --noEmit` clean (pre-push)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Restores `main` ancestry into `dev` so the `dev → main` release (#617)
can merge cleanly. `main` had one divergent commit — `ac354df` (#591,
deterministic e2e + run-plan hardening) — that overlapped the same
run-plan refactor developed on `dev` (#588/#590).

## Conflicts resolved (2 files)

- `lib/start-cluster.js` — kept dev's `settings.defaultDelivery` pr/ship
resolution in `resolveEffectiveRunPlan` (feature from #606/#580; main's
older squash lacked it).
- `tests/unit/start-cluster-config.test.js` — kept dev's
`defaultDelivery` describe block and preserved main's net-new
`buildStartOptions() isolation` block.

Everything else auto-merged.

## Verification

- `node tests/run-tests.js tests/unit/start-cluster-config.test.js` → 17
passing (both branches' test blocks present and green)
- `git diff --check` clean, no residual conflict markers
- `tsc --noEmit` clean

Merge this, then #617 (dev → main) merges clean and semantic-release
publishes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Eivind Meyer <eivind.meyer@ksat.no>
Co-authored-by: Michael Eichelbeck <141341133+mkceichelbeck@users.noreply.github.com>
Co-authored-by: tomdps <tom.dupuis24@gmail.com>
Co-authored-by: tomdps <60640908+tomdps@users.noreply.github.com>
Co-authored-by: Michael Eichelbeck <michael.eichelbeck.ext@wtsde.onmicrosoft.de>
Co-authored-by: Ubuntu <ubuntu@ip-172-31-38-53.eu-north-1.compute.internal>
Co-authored-by: Eivind <eivind@covibes.ai>
Co-authored-by: CI Test <ci-test@covibes.ai>
Co-authored-by: Codex <codex@example.com>
Co-authored-by: Atharv Singh <132380045+atharvwasthere@users.noreply.github.com>
Co-authored-by: Sense_wang <167664334+haosenwang1018@users.noreply.github.com>
Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com>
Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
Co-authored-by: Zeroshot Agent <agent@covibes.ai>
## Summary
- records current main as a true parent of dev so the dev -> main
release PR can merge cleanly
- no tree changes versus current dev; this preserves the conflict
resolutions already present on dev from #618

## Verification
- git merge-tree --write-tree origin/main HEAD -> clean locally after
the merge commit
- node tests/run-tests.js tests/unit/start-cluster-config.test.js
- npm run typecheck

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Eivind Meyer <eivind.meyer@ksat.no>
Co-authored-by: Michael Eichelbeck <141341133+mkceichelbeck@users.noreply.github.com>
Co-authored-by: tomdps <tom.dupuis24@gmail.com>
Co-authored-by: tomdps <60640908+tomdps@users.noreply.github.com>
Co-authored-by: Michael Eichelbeck <michael.eichelbeck.ext@wtsde.onmicrosoft.de>
Co-authored-by: Ubuntu <ubuntu@ip-172-31-38-53.eu-north-1.compute.internal>
Co-authored-by: Eivind <eivind@covibes.ai>
Co-authored-by: CI Test <ci-test@covibes.ai>
Co-authored-by: Codex <codex@example.com>
Co-authored-by: Atharv Singh <132380045+atharvwasthere@users.noreply.github.com>
Co-authored-by: Sense_wang <167664334+haosenwang1018@users.noreply.github.com>
Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com>
Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
Co-authored-by: Zeroshot Agent <agent@covibes.ai>
…se-image build fixes) (#622)

## Summary

Four related changes for the Copilot provider and Docker isolation mode:

1. **Copilot MCP support** — the copilot adapter now emits
`--additional-mcp-config`, so MCP servers configured for a run reach
Copilot.
2. **Cached per-provider Docker install** — the running provider's CLI
is installed into a per-provider image variant as a Docker-cached layer,
so `--docker --provider <p>` actually has the provider CLI available.
3. **ca-certificates regression guard** — a unit test that keeps the
cluster image buildable for Copilot's auth.
4. **Two pre-existing build-blocking fixes** — the base image could not
be built at all before this; both are fixed here so Docker mode works
end-to-end.

## Details

### 1. Copilot MCP (`--additional-mcp-config`)
- New validated `mcpConfig` field on `BuildProviderCommandOptions`
(array of JSON strings / `@file`), normalized in `contract-options`.
- `copilot.ts` emits one `--additional-mcp-config` per entry, gated on
feature detection (`supportsMcpConfig` from `--additional-mcp-config` in
help), with a warning when unsupported.
- Threaded end-to-end via **two** paths: (a) CLI `--mcp-config` → runner
→ adapter; (b) orchestrator agent spawn reads the repo
`.claude/.mcp.json` (the same source Claude uses), **inlines** it (so
the value survives host→container translation), and forwards it.
Registry flips copilot `mcpServers: true`.
- Copilot consumes MCP only from its own config locations, not the
worktree overlay, so the CLI-flag approach is the correct fit (unlike
Claude's `.mcp.json` convention).

### 2. Cached per-provider Docker install
- Docker mode previously baked **only** the Claude CLI into
`zeroshot-cluster-base`; copilot/codex/gemini were never installed, so
`--docker --provider <p>` failed with "command not found".
- Now: a per-provider image variant `zeroshot-cluster-base-<provider>` =
base + a single `RUN <install>` layer, driven by a new **registry**
field `docker.install` (no hardcoded provider names in logic). Claude
stays baked (variant = base); installer-less providers fall back to the
base image.
- `IsolationManager.imageForProvider` / `providerBuildArgs` resolve the
image + `--build-arg`; threaded through
`orchestrator._initializeIsolation` and validator isolation, and
**persisted for resume** (the resolved variant, not the base, is stored
in `cluster.isolation.image`).
- The `ARG PROVIDER_INSTALL` sits after all heavy layers, so the base
layers stay shared/Docker-cached across variants — building a second
provider variant reuses the whole base and only runs its install layer
(~70s vs a full build).

### 3. ca-certificates guard
- Copilot's Rust HTTP client uses the **system** CA store (Node bundles
its own, so `fetch` works but copilot doesn't). On the `node:20-slim`
base, a missing `ca-certificates` makes auth fail with a cryptic
`network fetch failed: builder error`. A unit test keeps the Dockerfile
line from being silently dropped.

### 4. Pre-existing build-blocking fixes
- `RUN npm install -g npm@latest` now pulls npm 12, which requires node
≥22 and fails `EBADENGINE` on `node:20-slim` → pinned to `npm@11`.
- `buildImage` inherited `runSync`'s 30s default timeout, killing every
heavy image build with `ETIMEDOUT` → `timeout: 0` for the build
subprocess.

## Testing
- `check:agent-cli-provider:ci` 127/127 · new unit tests (image
selection + ca-certs guard) 13/13 · `test:e2e` 7/7 (incl. asserting the
exact inlined `--additional-mcp-config` argv) · `copilot-mcp-docker`
integration 1/1 · full unit suite 1490 passing (4 failing are
pre-existing node-pty/better-sqlite3 environment issues).
- Real image builds verified: `zeroshot-cluster-base` (claude),
`-copilot` (copilot 1.0.70), `-codex` (codex 0.144.1); base-layer
caching confirmed.
- Live: copilot in the real image authenticates via
`COPILOT_GITHUB_TOKEN` and completes the MCP handshake through
`--additional-mcp-config` (the model turn itself was gated by an
exhausted Copilot quota, not code).
- Regression: claude still uses the unchanged base image; codex docker
was previously broken (not installed) and is now fixed; `claude -p` /
`codex exec` work non-docker.

## Reviewer notes (minor, non-blocking follow-ups)
- Validators use `DEFAULT_VALIDATOR_IMAGE`, so a custom `--docker-image`
would diverge between main container and validators (pre-existing —
validators already ignored `--docker-image`).
- `buildImage` always builds from the repo Dockerfile regardless of tag
(pre-existing).
- `timeout: 0` removes the build ceiling entirely (deliberate, to avoid
re-introducing the killed-slow-build bug).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- moves defaultDelivery application out of the release-conflicting
buildStartOptions hunk and into the production start path
- moves defaultDelivery regression coverage into a separate test file
- keeps behavior: defaultDelivery=ship still produces worktree +
autoMerge on start

## Verification
- node tests/run-tests.js tests/unit/start-cluster-config.test.js
tests/unit/start-cluster-default-delivery.test.js
tests/unit/setup-apply.test.js
- npm run typecheck
- git merge-tree --write-tree origin/main HEAD -> merge_tree_status=0

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- teach semantic-release that repository promotion commits using the
`release:` type should publish a minor version
- preserves the existing semantic-release default handling for `feat`,
`fix`, and breaking changes

## Verification
- `node -e
"JSON.parse(require('fs').readFileSync('.releaserc.json','utf8'));
console.log('releaserc json ok')"`
- direct commit-analyzer probe: `release: promote dev to main` =>
`minor`
- commit hook: `tsc --noEmit`
- push hook: `npm run lint` (warnings only), `tsc --noEmit`

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- remove semantic-release changelog/git prepare plugins that write
release artifacts back to `main`
- keep commit analysis, release notes, npm publishing, and GitHub
release publishing

## Why
The #625 release run computed `6.5.0`, then failed because
`@semantic-release/git` ran `git push --tags ... HEAD:main`; branch
protection rejected the generated commit because `main` requires PR +
merge queue.

## Verification
- `node -e
"JSON.parse(require('fs').readFileSync('.releaserc.json','utf8'));
console.log('releaserc json ok')"`

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- merge current `main` back into `dev` so release promotion ancestry is
explicit
- restore the no-branch-write semantic-release config after that merge

## Why
The prior `dev -> main` promotions merged through the queue but left
`main` with `@semantic-release/changelog` and `@semantic-release/git`,
so Release kept trying to push generated commits directly to protected
`main`.

This PR makes `dev` contain current `main` history plus the corrected
release config. The next `dev -> main` promotion should then have an
unambiguous tree delta.

## Verification
- `node -e
"JSON.parse(require('fs').readFileSync('.releaserc.json','utf8'));
console.log('releaserc json ok')"`
- release config diff removes only changelog/git branch-write plugins

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Eivind Meyer <eivind.meyer@ksat.no>
Co-authored-by: Michael Eichelbeck <141341133+mkceichelbeck@users.noreply.github.com>
Co-authored-by: tomdps <tom.dupuis24@gmail.com>
Co-authored-by: tomdps <60640908+tomdps@users.noreply.github.com>
Co-authored-by: Michael Eichelbeck <michael.eichelbeck.ext@wtsde.onmicrosoft.de>
Co-authored-by: Ubuntu <ubuntu@ip-172-31-38-53.eu-north-1.compute.internal>
Co-authored-by: Eivind <eivind@covibes.ai>
Co-authored-by: CI Test <ci-test@covibes.ai>
Co-authored-by: Codex <codex@example.com>
Co-authored-by: Atharv Singh <132380045+atharvwasthere@users.noreply.github.com>
Co-authored-by: Sense_wang <167664334+haosenwang1018@users.noreply.github.com>
Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com>
Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
Co-authored-by: Zeroshot Agent <agent@covibes.ai>
## Summary
- add semantic-release config under `package.json#release`
- package config is discovered before `.releaserc.json`, so release
publishes with npm/GitHub only even if the divergent main/dev merge
keeps resolving `.releaserc.json` to the stale branch-writing version

## Why
The merge queue repeatedly leaves `main:.releaserc.json` with
`@semantic-release/changelog` and `@semantic-release/git`;
semantic-release then computes `6.5.0` but fails when
`@semantic-release/git` tries to push generated commits to protected
`main`.

This moves the effective release contract to the first config source
semantic-release reads.

## Verification
- package release plugin list: `commit-analyzer,
release-notes-generator, npm, github`
- simulated `git merge-tree origin/main HEAD` preserves
`package.json#release` with no changelog/git plugins even though
`.releaserc.json` resolves stale

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- add a release preflight that validates the effective semantic-release
config and release-worthy promotion signal
- run the preflight on main promotion PRs/merge queue and in the release
workflow
- assert after release that npm latest and the git tag both point at the
released HEAD

## Verification
- node -c scripts/release-preflight.js && node -c
scripts/assert-release-published.js
- node tests/run-tests.js tests/release-preflight.test.js
- npx eslint scripts/release-preflight.js
scripts/assert-release-published.js tests/release-preflight.test.js
- npx prettier --check .github/workflows/ci.yml
.github/workflows/release.yml package.json scripts/release-preflight.js
scripts/assert-release-published.js tests/release-preflight.test.js
- npm run release:preflight

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- merge current main back into dev to repair the divergent squash
ancestry
- preserve the protected-branch-safe release config; do not reintroduce
@semantic-release/git or changelog into the effective release path
- keep the new release preflight job from #631

## Verification
- node -c scripts/release-preflight.js && node -c
scripts/assert-release-published.js
- node tests/run-tests.js tests/release-preflight.test.js
- npx prettier --check .github/workflows/ci.yml .releaserc.json
package.json scripts/release-preflight.js
scripts/assert-release-published.js tests/release-preflight.test.js
- commit hook: typecheck passed
- push hook: lint warnings only, typecheck passed

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Eivind Meyer <eivind.meyer@ksat.no>
Co-authored-by: Michael Eichelbeck <141341133+mkceichelbeck@users.noreply.github.com>
Co-authored-by: tomdps <tom.dupuis24@gmail.com>
Co-authored-by: tomdps <60640908+tomdps@users.noreply.github.com>
Co-authored-by: Michael Eichelbeck <michael.eichelbeck.ext@wtsde.onmicrosoft.de>
Co-authored-by: Ubuntu <ubuntu@ip-172-31-38-53.eu-north-1.compute.internal>
Co-authored-by: Eivind <eivind@covibes.ai>
Co-authored-by: CI Test <ci-test@covibes.ai>
Co-authored-by: Codex <codex@example.com>
Co-authored-by: Atharv Singh <132380045+atharvwasthere@users.noreply.github.com>
Co-authored-by: Sense_wang <167664334+haosenwang1018@users.noreply.github.com>
Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com>
Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
Co-authored-by: Zeroshot Agent <agent@covibes.ai>
## Summary
- move the release-preflight CI job to the end of the workflow to avoid
the old dev/main merge-base conflict hunk
- preserve the same release preflight behavior

## Verification
- npx prettier --check .github/workflows/ci.yml
- commit hook: typecheck passed
- local merge test: origin/main merges codex/relocate-release-preflight
without conflicts
- push hook: lint warnings only, typecheck passed

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## Summary
- make post-release verification use the git tag on HEAD as the expected
version
- poll npm latest until the registry dist-tag catches up instead of
failing on propagation lag
- add focused unit coverage for release-tag selection

## Verification
- node -c scripts/assert-release-published.js
- node tests/run-tests.js tests/assert-release-published.test.js
- npx eslint scripts/assert-release-published.js
tests/assert-release-published.test.js
- npx prettier --check scripts/assert-release-published.js
tests/assert-release-published.test.js
- commit hook: typecheck passed
- push hook: lint warnings only, typecheck passed

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
…#639)

Closes #637

Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
## What

Reconciles the diverged `main` and `dev` histories by merging
`origin/main` into `dev`. Heals the divergence root cause behind #640
and unblocks releasing the `resume --detach` zombie fix (#639, #637).

## Why

`main` and `dev` last shared history on 2026-07-09 and could not
fast-forward:
- `main` carried 6 release/promotion commits (incl. `#591`, `#617`,
`#632`) plus the `@semantic-release/changelog` + `@semantic-release/git`
plugins in `.releaserc.json` that never flowed back to `dev`.
- `dev` carried 59 commits including the `resume --detach` fix (#639).

A plain merge hit 4 conflicts. This PR resolves them **without losing
anything from either side**.

## Conflict resolution (both sides preserved)

| File | Resolution | Rationale |
|---|---|---|
| `src/orchestrator.js`, `lib/detached-startup.js` | dev | dev's shared
`process-liveness` predicate supersedes main's inline pre-fix
duplicates. Taking main would **revert #639**. |
| `scripts/assert-release-published.js` | dev | dev's `#635`
npm-propagation-wait version supersedes main's older `#632` snapshot. |
| `tests/e2e/helpers/e2e-harness.js` | dev | dev's `buildEnv` export. |
| `.releaserc.json` | main (auto-merge) | main's semantic-release
plugins are genuinely main-unique; kept. |

**Net delta vs `dev` = 8 lines in `.releaserc.json`, zero deletions vs
`dev`** (fix fully intact). All of main's real feature content
(`run-plan.js`, `setup-plan.js`, `release-preflight.js`, etc.) was
already present on `dev` via prior promotions.

## Verification

- `npm install` + build clean.
- 34 unit tests green: fix suites (`process-liveness` 3,
`detached-startup` 27) **and** main's release tooling
(`release-preflight`, `assert-release-published`).
- 2 real-daemon e2e cases green: zombie recovery without manual `stop`,
and single-daemon win on concurrent `resume --detach`.

## Merge instructions

**Merge as a merge commit, NOT squash.** A squash flattens the two
parents and leaves `main` un-ancestored, so the branches would
immediately re-diverge. The merge commit is what actually reconciles
them.

Follow-up: adopt the trunk + Changesets pipeline in #640 so this drift
cannot recur.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Eivind Meyer <eivind.meyer@ksat.no>
Co-authored-by: Michael Eichelbeck <141341133+mkceichelbeck@users.noreply.github.com>
Co-authored-by: tomdps <tom.dupuis24@gmail.com>
Co-authored-by: tomdps <60640908+tomdps@users.noreply.github.com>
Co-authored-by: Michael Eichelbeck <michael.eichelbeck.ext@wtsde.onmicrosoft.de>
Co-authored-by: Ubuntu <ubuntu@ip-172-31-38-53.eu-north-1.compute.internal>
Co-authored-by: Eivind <eivind@covibes.ai>
Co-authored-by: CI Test <ci-test@covibes.ai>
Co-authored-by: Codex <codex@example.com>
Co-authored-by: Atharv Singh <132380045+atharvwasthere@users.noreply.github.com>
Co-authored-by: Sense_wang <167664334+haosenwang1018@users.noreply.github.com>
Co-authored-by: haosenwang1018 <haosenwang1018@users.noreply.github.com>
Co-authored-by: Eivind <eivindcovibes.ai@Eivinds-MacBook-Pro.local>
Co-authored-by: Zeroshot Agent <agent@covibes.ai>
semantic-release derives the release version from commit-message types,
but nothing validated those messages, so a non-conventional subject
(missing, unknown, or wrong-case type) can silently break version bumps.

- add .commitlintrc.json (extends config-conventional; long body/footer
  lines allowed for squash-merge bodies)
- add a Husky commit-msg hook for local validation
- add a fast, dependency-light commitlint CI job that validates PR commit
  messages via npx, failing before the expensive check job

Part of #640. The trunk cutover items (removing enforce-main-pr-source,
the GitHub App release token) ride with the dev/main reconciliation, not
this PR.
@tomdps

tomdps commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Superseded by the completed main-trunk release controls in #809 and the cutover in #810. The repository now uses the protected main trunk, semantic-release, immutable release tags, and the protected release environment.

@tomdps tomdps closed this Jul 27, 2026
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.

5 participants