[Spec 7] feat: GitLab + Gitea installer + Codev auto-integration (v0.4.0)#9
Merged
waleedkadous merged 16 commits intoApr 11, 2026
Merged
Conversation
…-integration Extends lib/setup.ts (v0.3.1 GitHub-only, 194 lines) to support --forge gitlab and --forge gitea plus Codev auto-wiring. Final file is 293 lines, under the 300-line spec cap. GitLab: glab api subprocess, URL-encoded path_with_namespace, PUT for updates (not PATCH), canonical payload with pipeline_events=true and all other event flags explicitly false. Error classification for 404/403/401/ ENOENT via shared classifyForgeError helper. Gitea: global fetch() with AbortController timeout, token check BEFORE state provisioning (fail-fast before smee/state is touched), GITEA_TOKEN read from process.env first then <project>/.claude/channels/ci/.env with export-prefix handling and empty-string-as-missing. Update payload excludes 'type' field (create-only in Gitea API). Codev: if <project>/.codev/config.json exists, appends --dangerously-load-development-channels server:ci to shell.architect (idempotent substring check). Wrapped in local try/catch that warns and continues on failure — the webhook is already live by this point. Also: - Replaces GhError class with classifyForgeError helper shared by gh+glab - Renames ghApi -> cliApi (parameterized by binary name) - Scenario 8 in tests/setup.test.ts rewritten to use --nonsense instead of --forge gitlab (which is now valid input) - Updates README/INSTALL/CLAUDE/AGENTS/arch.md to document multi-forge installer and Codev auto-integration - Bumps package.json + package-lock.json from 0.3.1 to 0.4.0 Baseline: 181 tests passing. Phase 1 adds no new tests; Scenario 8 edit keeps the count unchanged. New scenarios land in Phase 2.
Codex spec-review found that --gitea-url was accepted without checking
for an http:// or https:// prefix, which the spec explicitly requires
('it's a string that starts with http:// or https://'). Adds a regex
check in parseArgs after the forge-presence validation.
Error message: --gitea-url must start with http:// or https:// (got '...')
Extends tests/setup.test.ts from 198 lines (8 tests) to 399 lines
(18 tests) — under the 400-line / 20-test spec caps.
New helpers:
- mkFakeCli(bin, name, responses) — renamed from mkFakeGh, parameterized
by binary name; used for both 'gh' and 'glab' fake scripts
- cliArgs / cliStdin / cliCount — parameterized by binary name
- writeEnv / seedCodev / CI_DIR — helpers for seeding .env and
.codev/config.json
- withGiteaServer(handler, fn) — local http.createServer on an ephemeral
port; captures all requests into a reqs[] array tests can assert on
- inProject extended to save/restore process.env.GITEA_TOKEN so tests
don't leak state into each other (protects local dev env where
GITEA_TOKEN may already be exported)
New scenarios (10 total):
GitLab (3):
- 9. GitLab happy path → POST with canonical payload; URL-encoded path
- 10. GitLab idempotent re-run → PUT (not PATCH) called exactly once
- 11. GitLab subgroup path encoding → group/subgroup/project round-trips
Gitea (4):
- 12. Gitea happy path → POST with type field + Authorization header;
GET does not carry Content-Type
- 13. Gitea idempotent → PATCH body omits 'type' field (create-only)
- 14. Gitea missing token → exit 1 BEFORE state.json is written AND
BEFORE any HTTP request (proves step-3 ordering); also covers
the empty-string case
- 15. Gitea 401 from server → state-first ordering preserved
(state.json has fresh secret before API call)
Codev (3):
- 16. .codev/config.json exists with no flag → installer appends the
channel loader flag to shell.architect; other fields untouched
- 17. .codev/config.json already has the flag → file byte-equal,
'already loads ci channel' logged
- 18. No .codev/ directory → silent skip, setup succeeds, no Codev
log lines in stderr
Final test suite: 191 tests (baseline 181 + 10 new). All passing.
Codex PR review caught that the installer registered the same default
.mcp.json entry ({ command: 'npx', args: ['-y', 'ci-channel'] }) for
every forge, so a GitLab or Gitea install would create the webhook on
the correct forge but the runtime MCP server would start with the
default forge='github' and reject/misparse the incoming events.
The fix builds a forge-specific args array when writing .mcp.json:
- github: no extra args (Spec 5 behavior preserved)
- gitlab: appends '--forge', 'gitlab'
- gitea: appends '--forge', 'gitea', '--gitea-url', <normalized base>
Tests 9 (GitLab happy path) and 12 (Gitea happy path) now assert the
forge-specific entry via JSON.parse + deepEqual instead of the generic
MCP_CI_ONLY byte-comparison. Tests 1-8, 10-11, 13-18 are unaffected
(they're GitHub path or they seed .mcp.json before setup runs so the
key-presence rule preserves their seeded content).
Removed CI_MCP_ENTRY constant; inlined the { command, args } literal at
the sole call site with the forge-specific args appended based on the
live 'forge' and 'giteaUrl' values from parseArgs.
lib/setup.ts: 294 → 300 lines (at the cap).
tests/setup.test.ts: 399 lines (unchanged).
All 191 tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Extends
ci-channel setup(v0.3.1, GitHub-only) to support three forges via--forge github|gitlab|giteaand to auto-wire Codev projects by appending the channel loader flag to.codev/config.json'sshell.architect.Closes #7. Ships as v0.4.0.
Changes
lib/setup.ts(194 → 294 lines, under 300-line spec cap)glab apisubprocess, URL-encodedpath_with_namespace, PUT (not PATCH) for hook updates, canonical payload withpipeline_events: trueand every other event flag explicitlyfalsefetch()with 10sAbortControllertimeout,Authorization: token ${GITEA_TOKEN}header,Content-Type: application/jsonon POST/PATCH only, POST body includestype: 'gitea'but PATCH body excludes it (Gitea rejectstypeon update)readEnvTokenhelper readsGITEA_TOKENfrom<project-root>/.claude/channels/ci/.env(handlesexportprefix, quoted values, comments).process.env.GITEA_TOKENtakes precedence; empty strings are treated as missing.codevIntegratehelper: if.codev/config.jsonexists, appends--dangerously-load-development-channels server:citoshell.architect(idempotent substring check). Wrapped in a localtry/catchthat warns and continues on failure — the webhook is already live by this point.classifyForgeError(bin, err, repo)helper shared between GitHub and GitLab branches. Replaces the oldGhErrorclass. Error messages match the spec verbatim.cliApihelper (renamed fromghApi) parameterized by binary name.--gitea-urlvalidation: must start withhttp://orhttps://(caught by Codex in impl review, fixed mid-phase).tests/setup.test.ts(198 → 399 lines, 8 → 18 tests, under 400/20 caps)--forge gitlab→--nonsense(the former is now valid input)type, missing token short-circuit, 401 state-first) + 3 Codev (append flag, already-present skip, no-op when absent)mkFakeGh→mkFakeCli(bin, name, responses)generalization — same pattern forghandglabwithGiteaServer(handler, fn)helper: localhttp.createServeron an ephemeral port with request logginginProjectextended to save/restoreprocess.env.GITEA_TOKENso tests don't leak state into each otherDocs
README.md: quick-start section now shows all three forges; per-forge guides reference the one-command installer; Codev section simplified to "installer auto-wires"INSTALL.md: "Quick Path" section covers all three forges; manual path still documented for advanced usersCLAUDE.md+AGENTS.md: installation section lists all three forges; critical-patterns section updated with GitLab PUT, Giteatypefield rules, and Gitea token-before-state exception. Both files kept in sync.codev/resources/arch.md: installer section rewritten to document multi-forge support, per-forge API differences, Codev auto-integration, and the shared error classification helper.Version
package.json: 0.3.1 → 0.4.0package-lock.json: bothversionfields → 0.4.0Testing
npm run buildpassesnpm testpasses 191/191 (baseline 181 + 10 new setup scenarios). No flaky tests.win32(POSIX shell script inmkFakeCli); Scenarios 7-8 run on all platformswc -l lib/setup.ts= 294 ✓ (≤300)wc -l tests/setup.test.ts= 399 ✓ (≤400)setup.test.ts✓ (≤20)git diff server.tsreturns empty ✓lib/setup/directory ✓type✓ (line 257)Protocol
This project ran through the ASPIR protocol (autonomous Specify → Plan → Implement → Review with 3-way consultations and no human gates on spec/plan). Each phase completed in one iteration after addressing all reviewer concerns in-place.
impl): Gemini (APPROVE), Codex (REQUEST_CHANGES — gitea-url scheme), Claude (APPROVE) — fix committed in 9f9a971tests): Gemini (APPROVE), Codex (APPROVE), Claude (APPROVE) — no changes neededSpec / Plan / Review