Skip to content

[Spec 7] feat: GitLab + Gitea installer + Codev auto-integration (v0.4.0)#9

Merged
waleedkadous merged 16 commits into
mainfrom
builder/aspir-7-spec-7-feat-gitlab-gitea-insta
Apr 11, 2026
Merged

[Spec 7] feat: GitLab + Gitea installer + Codev auto-integration (v0.4.0)#9
waleedkadous merged 16 commits into
mainfrom
builder/aspir-7-spec-7-feat-gitlab-gitea-insta

Conversation

@waleedkadous

Copy link
Copy Markdown
Contributor

Summary

Extends ci-channel setup (v0.3.1, GitHub-only) to support three forges via --forge github|gitlab|gitea and to auto-wire Codev projects by appending the channel loader flag to .codev/config.json's shell.architect.

Closes #7. Ships as v0.4.0.

Changes

lib/setup.ts (194 → 294 lines, under 300-line spec cap)

  • New GitLab branch: glab api subprocess, URL-encoded path_with_namespace, PUT (not PATCH) for hook updates, canonical payload with pipeline_events: true and every other event flag explicitly false
  • New Gitea branch: global fetch() with 10s AbortController timeout, Authorization: token ${GITEA_TOKEN} header, Content-Type: application/json on POST/PATCH only, POST body includes type: 'gitea' but PATCH body excludes it (Gitea rejects type on update)
  • Gitea token check runs at step 3 of the common flow — before state provisioning — so a missing token fails fast without burning a smee channel. Only exception to state-first ordering.
  • New readEnvToken helper reads GITEA_TOKEN from <project-root>/.claude/channels/ci/.env (handles export prefix, quoted values, comments). process.env.GITEA_TOKEN takes precedence; empty strings are treated as missing.
  • New codevIntegrate helper: if .codev/config.json exists, appends --dangerously-load-development-channels server:ci to shell.architect (idempotent substring check). Wrapped in a local try/catch that warns and continues on failure — the webhook is already live by this point.
  • New classifyForgeError(bin, err, repo) helper shared between GitHub and GitLab branches. Replaces the old GhError class. Error messages match the spec verbatim.
  • cliApi helper (renamed from ghApi) parameterized by binary name.
  • New --gitea-url validation: must start with http:// or https:// (caught by Codex in impl review, fixed mid-phase).

tests/setup.test.ts (198 → 399 lines, 8 → 18 tests, under 400/20 caps)

  • Scenario 8 modified: --forge gitlab--nonsense (the former is now valid input)
  • 10 new scenarios: 3 GitLab (happy path, idempotent PUT, subgroup encoding) + 4 Gitea (happy path, idempotent PATCH without type, missing token short-circuit, 401 state-first) + 3 Codev (append flag, already-present skip, no-op when absent)
  • mkFakeGhmkFakeCli(bin, name, responses) generalization — same pattern for gh and glab
  • New withGiteaServer(handler, fn) helper: local http.createServer on an ephemeral port with request logging
  • inProject extended to save/restore process.env.GITEA_TOKEN so tests don't leak state into each other

Docs

  • 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 users
  • CLAUDE.md + AGENTS.md: installation section lists all three forges; critical-patterns section updated with GitLab PUT, Gitea type field 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.0
  • package-lock.json: both version fields → 0.4.0

Testing

  • Build: npm run build passes
  • Tests: npm test passes 191/191 (baseline 181 + 10 new setup scenarios). No flaky tests.
  • Platform: Scenarios 1-6, 9-18 skip on win32 (POSIX shell script in mkFakeCli); Scenarios 7-8 run on all platforms
  • Constraint verification:
    • wc -l lib/setup.ts = 294 ✓ (≤300)
    • wc -l tests/setup.test.ts = 399 ✓ (≤400)
    • 18 tests in setup.test.ts ✓ (≤20)
    • git diff server.ts returns empty ✓
    • No new dependencies ✓
    • No lib/setup/ directory ✓
    • GitLab uses PUT ✓ (line 229)
    • Gitea PATCH body excludes 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.

  • 3-way consultation at Specify: Gemini (COMMENT), Codex (REQUEST_CHANGES 5 findings), Claude (COMMENT 6 findings) — all addressed
  • 3-way consultation at Plan: Gemini (APPROVE), Codex (REQUEST_CHANGES 5 findings), Claude (COMMENT 5 findings) — all addressed
  • 3-way consultation at Impl phase 1 (impl): Gemini (APPROVE), Codex (REQUEST_CHANGES — gitea-url scheme), Claude (APPROVE) — fix committed in 9f9a971
  • 3-way consultation at Impl phase 2 (tests): Gemini (APPROVE), Codex (APPROVE), Claude (APPROVE) — no changes needed

Spec / Plan / Review

…-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.
@waleedkadous waleedkadous merged commit c3f91c6 into main Apr 11, 2026
2 checks passed
@waleedkadous waleedkadous deleted the builder/aspir-7-spec-7-feat-gitlab-gitea-insta branch April 11, 2026 14:23
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.

[Spec 7] feat: GitLab + Gitea installer support and Codev auto-integration

1 participant