Skip to content

feat(setup): self-sufficient ckb setup + Codex support - #247

Open
SimplyLiz wants to merge 11 commits into
developfrom
feat/setup-zero-config
Open

SimplyLiz wants to merge 11 commits into
developfrom
feat/setup-zero-config

Conversation

@SimplyLiz

Copy link
Copy Markdown
Owner

ckb setup now gets a repo ready without any manual steps, and Codex is a supported target.

Setup

  • Initializes the repo on its own if needed. That no longer changes the global default repo.
  • Writes the MCP config before indexing, so a failed indexer run doesn't leave the setup without a config.
  • Indexing no longer blocks by default: the generated config runs with --watch, and the watch loop builds the index when the agent starts the server. --index-now forces a foreground index. With --no-watch it still indexes in the foreground.

Codex

  • Project and global scope are supported, merged into config.toml robustly: inline comments and multiline arrays survive, and the file mode is kept on atomic rewrite.
  • Refuses to rewrite mcp_servers.ckb when it's written as an inline table or dotted keys, instead of breaking it.
  • On Windows the npx command is wrapped in cmd /c.

MCP watch loop

  • Builds a missing index and backs off on failures.
  • Runs the first check immediately rather than one poll interval later (up to 5 min).
  • Uses the same command builder as ckb index.

Rebased onto develop after #245. The only conflict was the tool count in CLAUDE.md (now 111 + Codex). go build, go vet, gofmt, sync-version --check and go test ./... pass.

🤖 Generated with Claude Code

SimplyLiz and others added 11 commits September 17, 2026 11:35
A developer shouldn't have to know 'ckb init' and 'ckb index' exist just to
get an MCP server running. 'ckb setup' now does the whole job for
project-scope tools: it inits .ckb/ if missing and builds the index if none
exists yet, using the same code paths as 'ckb init'/'ckb index' (not a
shell-out). Missing SCIP indexers, undetectable languages, or indexing
failures are reported as one-line notes and never fail setup — Git-based
features still work without SCIP. --no-index opts out.

That required index.go's runIndex to stop calling os.Exit directly: it's
refactored into performIndex(), which returns a structured indexResult
instead, so both 'ckb index' and 'ckb setup' can react to the same outcomes
(indexed / up-to-date / indexer missing / language undetected / ...)
without one call path killing the process out from under the other.

Generated MCP configs now include --watch by default (opt out with
--no-watch) so the server keeps the index fresh for the session, instead of
requiring a separate 'ckb mcp --watch' flag nobody would think to add.
Along the way, fixed writeOpenCodeConfig's npx branch, which rebuilt the
command array from scratch and silently dropped --preset/--watch for
OpenCode + npx setups.

Adds Codex CLI as a supported tool (global-only — Codex reads
~/.codex/config.toml, and there's no project-level config location
documented anywhere in this repo). Since config.toml is a file Codex itself
owns and may hand-edit, the merge is a surgical textual upsert of just the
[mcp_servers.ckb] table rather than a decode/re-encode round-trip through
a TOML library, which would reformat the whole file and drop comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex CLI supports trusted project-local .codex/config.toml in addition
to the user-global ~/.codex/config.toml, so marking it SupportsProject:
false was wrong — it forced --global and made `ckb setup --tool=codex`
skip the whole init/index auto-readiness path that every other
project-scope tool gets. Default (non --global) setup now writes
<repo>/.codex/config.toml; --global still writes the user-global file.
README/npm README updated (dropped the "no per-project config" claim)
and now document how `ckb mcp` resolves the repo from cwd when running
under the global config.

Also hardens the textual TOML upsert used to merge CKB's
[mcp_servers.ckb] table into Codex's config.toml:
- header matching is now regex/parser-based, tolerant of inline
  comments ([mcp_servers.ckb] # ...) and quoted keys
  ([mcp_servers."ckb"]) — previously an exact string match, so either
  form was treated as "table not found" and a second, TOML-illegal
  duplicate header got appended
- only the bare command/args keys are rewritten; any subtable of ckb
  (e.g. [mcp_servers.ckb.env], holding user env vars) is left
  completely untouched instead of being deleted by the old
  next-header-wins block boundary
- CRLF line endings are preserved if the existing file uses them
- the result is validated as TOML before writing; an unparsable
  existing file is left untouched with manual-edit instructions
  instead of being blindly edited
- writes are atomic (temp file + rename)

Confirms Codex review findings P0 (scope) and P1 (TOML header/comment
false-negative); see /tmp/codex-review/setup-zero-config.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
runWatchLoop silently skipped every tick when index metadata didn't
exist yet ("no metadata yet, skip"), so a project that had never been
indexed — including one where 'ckb setup' skipped indexing because it
was interrupted or the indexer wasn't installed at the time — would
never get indexed by --watch either, no matter how long the MCP server
ran. Language is now resolved via the saved project.json, or a fresh
auto-detect when that doesn't exist yet, so a genuinely first-time
index can be built from a watch tick.

Also fixes an unbounded retry: on a stale index with an indexer that's
missing or persistently failing, the old loop retried on every single
tick (every 10s by default) forever, spawning the same failing process
indefinitely and spamming the log. triggerReindex now returns a
sentinel (errIndexerUnavailable / errRepoTooLarge) for permanent
conditions — missing indexer binary, undetectable/ambiguous language,
or a repo over the SCIP auto-index threshold — which disables watch
mode for the rest of the process after logging once. Transient
failures (indexer crashes, etc.) get exponential backoff
(watchBackoff, capped at 10 min) and disable themselves after 5
consecutive failures rather than retrying forever; a restart of the
MCP server re-enables it.

watchBackoff is a pure function so the backoff policy itself is
unit-tested without needing a fake clock/ticker.

Confirms Codex review finding P2 (watch retry loop never gives up) and
the "does watch actually index when metadata is missing" question
raised for the setup-blocks-on-indexing fix; see
/tmp/codex-review/setup-zero-config.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ensureProjectReady() (init + index) ran before configureTool() wrote
the MCP config, so a slow indexer — or a repo just under the 50,000
file auto-SCIP threshold, or any indexer that simply takes a while —
blocked 'ckb setup' from ever reaching the config-write step. Ctrl-C
during that wait, or any indexing failure, meant no config at all,
contradicting the low-friction goal of this change: the agent should
be usable (Git-based features at minimum) the moment config is
written, independent of how indexing goes.

configureTool now runs first; ensureProjectReady (init + index) runs
after, with a note that it's safe to Ctrl-C at that point since the
config is already saved. That message is only accurate because the
prior commit (fix(mcp): watch loop builds missing index...) made
--watch actually build a missing/interrupted index on its own tick
instead of silently skipping when there's no metadata yet.

Confirms Codex review finding P1 (setup blocks on foreground indexing
before config is written); see
/tmp/codex-review/setup-zero-config.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… repo

ensureCkbInitialized called runInit(nil, nil), which read the
package-level initNoActivate flag var — always false unless the
'ckb init' CLI command itself set it via --no-activate. Since 'ckb
setup' never touches that flag, every auto-init it triggered called
registry.SetDefault(), silently making whatever project you ran
'ckb setup' in your new global default repo — clobbering the default
for every other session/terminal using CKB on a different project.

Refactored runInit into a thin cobra wrapper around runInitCore(opts
initOptions), so callers other than the CLI pass their intent
explicitly instead of relying on shared global flag state.
ensureCkbInitialized now calls runInitCore(initOptions{NoActivate:
true}) — the repo still gets registered (so it shows up in 'ckb repo
list' etc.), it just doesn't become active. 'ckb init' itself is
unaffected; its flags still flow into the same options struct.

Confirms Codex review finding P2 (setup changes global default repo);
see /tmp/codex-review/setup-zero-config.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex spawns [mcp_servers.*] commands directly (no shell), and npx on
Windows is npx.cmd, which only resolves through cmd.exe — this repo's
own Windows guidance already says as much for every other tool's
config, but the Codex writer always emitted command = "npx" verbatim,
which would silently fail to launch on Windows.

No other writer in this repo applies this wrapping automatically
either (grepped for it — none do); they all rely on the README's
manual "Windows" section. Rather than bolt automatic wrapping onto
runSetup's shared ckbCommand/ckbArgs (which the OpenCode writer
specifically re-derives its own npx array from when useNpx is set,
so mutating the shared command there would double-wrap), this is
scoped narrowly to the Codex writer via a small pure function,
codexWindowsWrap(goos, command, args), so it's unit-testable without
depending on the host OS and doesn't touch any other tool's output.

README/npm README gain a TOML-flavored example in the Windows section
(previously only JSON, which doesn't transfer directly to Codex's
config.toml) and a note that --npx handles this automatically.

Confirms Codex review's "Windows guidance says npx needs cmd /c...
while the Codex writer always emits command = npx" finding; see
/tmp/codex-review/setup-zero-config.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
triggerReindex (the ckb mcp --watch background loop) ran indexer.Command
verbatim, bypassing everything performIndex does to build a real command:
C++'s --compdb-path flag, Ruby's bundle-aware prefix, PHP's prerequisite
check, and a custom scip.indexPath's --output flag. That made setup+watch
silently fail to produce a usable SCIP index for C++ repos and for any repo
with a non-default scip.indexPath, while still reporting success.

Extract buildIndexPlan (plus resolveIndexPath/resolveIndexLanguage) as the
single place that resolves language, manifest, and the exact indexer
command/dir/output-path. performIndex and triggerReindex both call it now,
so the two paths can't drift again. triggerReindex also verifies the
resolved index file actually exists after the indexer exits 0, before
writing any metadata — previously a silent no-op indexer (or one whose
--output wasn't honored) was recorded as a successful reindex.

Tests cover a fake indexer for: a custom scip.indexPath output (command
gets --output, file lands at the configured path) and a missing output
file (indexer exits 0, triggerReindex still reports failure and writes
no metadata).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
writeCodexConfig always wrote through writeFileAtomic with a hardcoded
0644, so re-running 'ckb setup --tool=codex' (global or project) against a
config.toml the user had locked down to 0600 silently widened it back to
world-readable — a real risk since [mcp_servers.*.env] subtables can hold
secrets other tools stash alongside CKB's entry.

writeCodexConfig now takes the scope (global bool, already known at the
configureTool call site) and picks the file mode as: existing file's own
mode if one exists (never widened or narrowed), else 0600 for a freshly
created global ~/.codex/config.toml, else 0644 for a freshly created
project .codex/config.toml (matching every other project config CKB
writes).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rs.ckb

Valid TOML like:

    [mcp_servers]
    ckb = { command = "old", args = ["mcp"] }

or the fully dotted equivalent (mcp_servers.ckb.command = ...) has no
[mcp_servers.ckb] header line. upsertTOMLTable only recognizes that header
shape, so it didn't find an existing ckb table and appended one — but TOML
forbids extending an inline table with a later header, so the generated
file failed writeCodexConfig's own validate-before-write check and setup
aborted with a raw TOML parse error instead of a clear explanation.

writeCodexConfig now decodes the existing file and checks two independent
signals: does mcp_servers.ckb resolve to a value at all (BurntSushi folds
headers/inline-tables/dotted-keys into the same map shape), and does a
[mcp_servers.ckb]-shaped header line actually exist in the text. When ckb
exists per the parsed structure but no such header line does, the entry
must be inline-table or dotted-key form — refuse to touch the file and
return an error with the exact [mcp_servers.ckb] snippet to paste by hand,
so setup exits non-zero for Codex specifically with an actionable message
instead of a cryptic parse failure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…odex TOML

upsertTOMLTable replaced command/args by filtering the existing bare-key
block line-by-line on key name. Two cases broke:

- A trailing inline comment on the replaced line (command = "npx" # pinned
  for CI) was dropped along with the whole line — the surgical-edit
  contract was "preserve everything except command/args," but the comment
  riding on that same line got thrown away too.
- A multiline args array (args = [\n  "mcp",\n  "--watch",\n]) only had its
  opening line recognized as "the args key" — the continuation lines don't
  look like "key = value", so they fell through as ordinary "kept" lines,
  left stranded next to the newly-inserted single-line args and corrupting
  the table.

Added tomlValueSpanEnd (bracket-depth line scan, ignoring quoted strings
and comments, that only continues past the key's own line if it left an
unclosed "[") to find the whole span a key's value occupies, and
tomlTrailingComment to extract a comment from the span's last line and
reattach it to the replacement. Both command and args now go through the
same span-aware removal instead of a single-line key-name filter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
'ckb setup' ran a full foreground SCIP index for every project-scope setup
(any repo under the 50k-file threshold), even though the generated MCP
config already runs with --watch by default. That meant every setup paid
the indexer's full cost (seconds to tens of minutes) before returning,
despite the watch loop being fully capable of building the same index on
its own once the AI tool actually starts the server.

ensureProjectReady now skips the foreground index when watch mode is
enabled (the default), printing a one-line note that indexing happens in
the background when the agent starts, plus how to force it
('ckb index'). --no-watch has no watch loop to fall back on, so it still
indexes in the foreground — otherwise nothing would ever build it. A new
--index-now flag forces the foreground index regardless of --watch, for
anyone who wants it ready before setup exits.

Complementary fix: runWatchLoop only checked staleness on ticker.C, so the
first check waited a full poll interval (up to 5 minutes) after the MCP
server started — a real regression for a project that now skips indexing
at setup time. Extracted the per-tick logic into watchTick and call it once
immediately before entering the ticker loop, so a fresh project gets
indexed the moment the agent starts the server, not minutes later.

Updated the setup command's help text and the README/npm README setup
walkthroughs to describe the non-blocking default and --index-now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🟢 Change Impact Analysis

Metric Value
Risk Level LOW 🟢
Files Changed 11
Symbols Changed 7
Directly Affected 0
Transitively Affected 0

Blast Radius: 0 modules, 0 files, 0 unique callers

📝 Changed Symbols (7)
Symbol File Type Confidence
CLAUDE.md CLAUDE.md modified 30%
README.md README.md modified 30%
cmd/ckb/index.go cmd/ckb/index.go modified 30%
cmd/ckb/init.go cmd/ckb/init.go modified 30%
cmd/ckb/mcp.go cmd/ckb/mcp.go modified 30%
cmd/ckb/setup.go cmd/ckb/setup.go modified 30%
npm/README.md npm/README.md modified 30%

Recommendations

  • ℹ️ coverage: 7 symbols have low mapping confidence. Index may be stale.
    • Action: Run 'ckb index' to refresh the SCIP index

Generated by CKB

@github-actions

Copy link
Copy Markdown

CKB Analysis

Risk Files +3126 -239 Modules

🎯 7 changed → 0 affected · 🔥 10 hotspots · 📊 4 complex · 💣 4 blast · 📚 203 stale

Risk factors: Medium-sized PR with 11 files • High churn: 3365 lines changed • Touches 10 hotspot(s)

👥 Suggested: @lisa.welsch1985@gmail.com (91%), @talantyyr@gmail.com (45%), @lisa@tastehub.io (27%)

Metric Value
Impact Analysis 7 symbols → 0 affected 🟢
Doc Coverage 6.896551724137931% ⚠️
Complexity 4 violations ⚠️
Coupling 0 gaps
Blast Radius 0 modules, 0 files
Index indexed (0s) 🆕
🎯 Change Impact Analysis · 🟢 LOW · 7 changed → 0 affected
Metric Value
Symbols Changed 7
Directly Affected 0
Transitively Affected 0
Modules in Blast Radius 0
Files in Blast Radius 0

Symbols changed in this PR:

Recommendations:

  • ℹ️ 7 symbols have low mapping confidence. Index may be stale.
    • Action: Run 'ckb index' to refresh the SCIP index
💣 Blast radius · 0 symbols · 4 tests · 0 consumers

Tests that may break:

  • cmd/ckb/index_test.go
  • cmd/ckb/init_test.go
  • cmd/ckb/mcp_test.go
  • cmd/ckb/setup_codex_test.go
🔥 Hotspots · 10 volatile files
File Churn Score
CLAUDE.md 5.67
README.md 7.37
cmd/ckb/index.go 8.29
cmd/ckb/init.go 3.57
cmd/ckb/init_test.go 2.66
cmd/ckb/mcp.go 8.57
cmd/ckb/mcp_test.go 4.23
cmd/ckb/setup.go 14.40
📦 Modules · 1 at risk
Module Files
🟡 cmd/ckb 8
📊 Complexity · 4 violations
File Cyclomatic Cognitive
cmd/ckb/index.go ⚠️ 33 ⚠️ 57
cmd/ckb/init.go ⚠️ 18 ⚠️ 34
cmd/ckb/mcp.go ⚠️ 28 ⚠️ 65
cmd/ckb/setup.go ⚠️ 34 ⚠️ 62
💡 Quick wins · 10 suggestions
📚 Stale docs · 203 broken references

Generated by CKB · Run details

@github-actions

Copy link
Copy Markdown

🔐 Security Audit Results

Security gate FAILED - 4 HIGH severity gosec finding(s)

Category Findings
🔑 Secrets ✅ 0
🛡️ SAST ⚠️ 28
📦 Dependencies ⚠️ 21
📜 Licenses ⚠️ 142 non-permissive

🛡️ SAST Analysis

Found 28 issue(s) across 1 scanner(s)

Details

Gosec (28 findings)

  • /home/runner/work/ckb/ckb/internal/lip/subscribe.go:207 - G115: integer overflow conversion int -> uint32...
  • /home/runner/work/ckb/ckb/internal/lip/client.go:1030 - G115: integer overflow conversion int -> uint32...
  • /home/runner/work/ckb/ckb/internal/impact/enricher.go:43 - G101: Potential hardcoded credentials...
  • /home/runner/work/ckb/ckb/internal/query/review_coupling.go:43 - G702: Command injection via taint analysis...
  • /home/runner/work/ckb/ckb/internal/tier/runner.go:45 - G204: Subprocess launched with variable...
  • ... and 23 more

📦 Dependency Vulnerabilities

Found 21 vulnerability(ies) across 2 scanner(s)

Details

Trivy (14 findings)

  • CVE-2026-15157 (MEDIUM): undici - undici: undici: HTTP header injection via unvalida...
  • CVE-2026-16728 (MEDIUM): undici - undici: undici: Response desynchronization via ret...
  • CVE-2026-16729 (MEDIUM): undici - undici: Undici: Cookie attribute injection allows ...
  • CVE-2026-33997 (MEDIUM): github.com/docker/docker - moby: docker: github.com/moby/moby: Moby: Privileg...
  • CVE-2026-41568 (MEDIUM): github.com/docker/docker - github.com/docker/docker: github.com/moby/moby: Mo...
  • ... and 9 more

OSV-Scanner (7 findings)

  • github.com/docker/docker: 11 vulnerabilities
  • github.com/go-chi/chi/v5: 3 vulnerabilities
  • github.com/klauspost/compress: 1 vulnerabilities
  • github.com/rs/cors: 2 vulnerabilities
  • go.opentelemetry.io/otel: 1 vulnerabilities
  • ... and 2 more

📜 License Issues

Found 142 non-permissive license(s)

Details
  • github.com/BurntSushi/toml: MIT (notice)
  • github.com/google/uuid: BSD-3-Clause (notice)
  • github.com/klauspost/compress: Apache-2.0 (notice)
  • github.com/klauspost/compress: BSD-3-Clause (notice)
  • github.com/klauspost/compress: MIT (notice)
  • github.com/pelletier/go-toml/v2: MIT (notice)
  • github.com/smacker/go-tree-sitter: MIT (notice)
  • github.com/sourcegraph/go-diff: MIT (notice)
  • github.com/sourcegraph/scip: Apache-2.0 (notice)
  • github.com/spf13/cobra: Apache-2.0 (notice)
  • ... and 132 more

Generated by CKB Security Audit | View Details | Security Tab

@github-actions

Copy link
Copy Markdown

CKB Review: 🟡 WARN — 61/100

11 files (+3365 changes) · 4 modules · go

Changes 11 files across 4 modules (go). Risk score: 0.75 (high); 14 bug pattern(s) detected.

Check Status Detail
risk 🟡 WARN Risk score: 0.75 (high)
bug-patterns 🟡 WARN 14 bug pattern(s) detected
blast-radius ℹ️ INFO No symbols with callers in changes
hotspots ℹ️ INFO 10 hotspot file(s) touched
test-gaps ℹ️ INFO 25 untested function(s) in changed files (showing top 10)
comment-drift ✅ PASS No comment/code drift detected
breaking ✅ PASS No breaking API changes
secrets ✅ PASS No secrets detected
format-consistency ✅ PASS No format consistency issues
unwired ✅ PASS All exported symbols are reachable from entrypoints
tests ✅ PASS 4 test(s) cover the changes
complexity ✅ PASS +184 cyclomatic complexity across 4 file(s)
coupling ✅ PASS No missing co-change files
health ✅ PASS 1 file(s) degraded, 0 improved (avg -0.3)
dead-code ✅ PASS No dead code in changed files
layers ⚪ SKIP Cartographer not compiled in this build
arch-health ⚪ SKIP Cartographer not compiled in this build

Top Risks

  • Risk score: 0.75 (high)
  • 14 bug pattern(s) detected
Findings (10 actionable, 23 informational)
Severity File Finding
ℹ️ cmd/ckb/index.go Complexity 177→193 (+16 cyclomatic) in performIndex()
ℹ️ cmd/ckb/mcp.go Complexity 59→82 (+23 cyclomatic) in runMCP()
ℹ️ cmd/ckb/mcp.go:504 'err' shadowed — redeclared with := at depth 1 (outer declaration at line 468)
ℹ️ cmd/ckb/setup.go Complexity 206→350 (+144 cyclomatic) in runSetup()
ℹ️ cmd/ckb/setup.go:369 'err' shadowed — redeclared with := at depth 1 (outer declaration at line 358)
ℹ️ cmd/ckb/setup.go:1330 'err' shadowed — redeclared with := at depth 1 (outer declaration at line 1321)
ℹ️ cmd/ckb/setup.go:1334 'err' shadowed — redeclared with := at depth 1 (outer declaration at line 1321)
ℹ️ cmd/ckb/setup.go:1338 'err' shadowed — redeclared with := at depth 1 (outer declaration at line 1321)
ℹ️ cmd/ckb/setup.go:1341 'err' shadowed — redeclared with := at depth 1 (outer declaration at line 1321)
ℹ️ cmd/ckb/setup.go:1344 'err' shadowed — redeclared with := at depth 1 (outer declaration at line 1321)
Code Health — 1 degraded

Degraded:

File Before After Delta Grade Confidence
cmd/ckb/mcp.go 58 56 -2 C→C 100%

^1 File could not be parsed by tree-sitter

New files: 4 (avg health: 82)

1 degraded · 0 improved · avg -0.3

Estimated review: not feasible as a single PR (11 files, 3365 lines)

Reviewers: lisa.welsch1985 (91%) · talantyyr (45%) · lisa (27%)

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