Skip to content

Commit 6fdb41c

Browse files
committed
feat: improve boostrap speed on empty project
1 parent 2fdc3e5 commit 6fdb41c

4 files changed

Lines changed: 106 additions & 8 deletions

File tree

.archcore/.sync-state.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,6 +3216,21 @@
32163216
"source": "plugin/codex-path-resolution.adr.md",
32173217
"target": "plugin/codex-host-support.prd.md",
32183218
"type": "related"
3219+
},
3220+
{
3221+
"source": "plugin/codex-mcp-cwd-rebase-to-user-project.idea.md",
3222+
"target": "plugin/codex-path-resolution.adr.md",
3223+
"type": "extends"
3224+
},
3225+
{
3226+
"source": "plugin/codex-mcp-cwd-rebase-to-user-project.idea.md",
3227+
"target": "plugin/codex-local-plugin-testing.guide.md",
3228+
"type": "related"
3229+
},
3230+
{
3231+
"source": "plugin/codex-mcp-cwd-rebase-to-user-project.idea.md",
3232+
"target": "plugin/bundled-cli-launcher.adr.md",
3233+
"type": "related"
32193234
}
32203235
]
32213236
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: "Codex MCP — User-Project CWD Without Shell-Resync"
3+
status: draft
4+
tags:
5+
- "codex"
6+
- "multi-host"
7+
- "plugin"
8+
---
9+
10+
## Idea
11+
12+
Restore the archcore MCP server's working directory to the user's project root when invoked from Codex, **without** depending on the inherited `$PWD` env var (which POSIX shells overwrite at startup).
13+
14+
Codex spawns plugin MCP servers from the plugin install dir (via `.codex.mcp.json` `cwd: "."` rebase). The launcher itself is a `#!/bin/sh` script, so any `$PWD` value Codex inherits from the user's shell is **erased on launcher startup** — POSIX shells (sh, dash, bash --posix) resync `$PWD` to match `getcwd()` if the two diverge. That kills the obvious "follow `$PWD`" workaround.
15+
16+
Two viable paths that survive sh's PWD-resync:
17+
18+
1. **An explicit env var the shell does NOT resync** — e.g., `ARCHCORE_CWD` (or `CODEX_PROJECT_DIR`, if Codex ever exposes one). The launcher reads it before exec'ing the real CLI:
19+
20+
```sh
21+
if [ -n "${ARCHCORE_CWD:-}" ] && [ -d "$ARCHCORE_CWD" ]; then
22+
cd "$ARCHCORE_CWD" 2>/dev/null || true
23+
fi
24+
```
25+
26+
Sh's PWD-resync only touches `PWD` and `OLDPWD`. A custom name passes through untouched. The cost: someone (Codex, or the user) has to set it. Until Codex exposes a canonical name, the user opts in per-session.
27+
28+
2. **archcore CLI argument** — `archcore --project-root=PATH mcp`. Lets Codex (or a future plugin manifest version) pass the path explicitly. Doesn't require a custom env var, but does require Codex to thread an argument through MCP `args`. As of Codex 0.130.0 there is no env substitution in `args`, so this only helps if upstream adds it (openai/codex#19582).
29+
30+
## Value
31+
32+
Today, calling any `mcp__archcore__*` tool from Codex creates `.archcore/` documents in `~/.codex/plugins/cache/<marketplace>/archcore/<version>/.archcore/` instead of the user's project. Observed during a Codex `/archcore:bootstrap` session against `test_project/`: two seed docs landed in the plugin cache before the agent gave up and started a manual MCP rooted at the project. ~9m41s of confused wallclock.
33+
34+
Closing this gap turns Codex into a first-class Archcore host. Without the fix, the bundled-launcher value-prop of "no global install needed" silently degrades for Codex users — every MCP write pollutes the cache.
35+
36+
## Possible Implementation
37+
38+
1. **Add `ARCHCORE_CWD` support in `bin/archcore`** — guarded `cd "$ARCHCORE_CWD"` near the top of the launcher (after `SCRIPT_DIR=...` resolution), with `[ -d ]` and `cd ... || true` safety. Unit-tested via the existing Bats pattern (env var → fake `ARCHCORE_BIN` stub → assert stub's `pwd` output).
39+
2. **Diagnostic mode for env discovery**`archcore mcp --debug-env` that streams a single JSON-RPC notification listing all `getenv` keys the MCP server sees at startup. Run it once from a real Codex session to identify which env vars Codex actually injects (PLUGIN_ROOT, CODEX_HOME, CODEX_CWD, ...). If a Codex-provided user-project var exists, switch the launcher to read it first; fall back to `ARCHCORE_CWD`.
40+
3. **Document the contract** in `codex-local-plugin-testing.guide.md` step 8 — add an assertion that an MCP `create_document` call from `mktemp -d` creates files under that tmpdir, **not** under `~/.codex/plugins/cache/...`. Pin the contract with a Bats E2E test if feasible.
41+
4. **Upstream issue** — file or watch openai/codex#19582 for `${PLUGIN_ROOT}` MCP substitution. Once shipped, drop `cwd: "."`, use `${PLUGIN_ROOT}/bin/archcore` directly, and Codex inherits the user's CWD by default — both legs become unnecessary.
42+
5. **One-time cleanup utility** — `archcore mcp cleanup-cache` that removes stray `.archcore/` directories that may have landed under `~/.codex/plugins/cache/<marketplace>/archcore/<version>/` before this fix shipped. Opt-in.
43+
44+
## Risks and Constraints
45+
46+
- **POSIX `$PWD`-resync is non-negotiable.** Verified across `/bin/sh`, `dash`, and `bash --posix` on macOS: each rewrites `$PWD` to match `getcwd()` on every shell startup when they disagree. Any fix that depends on the kernel-inherited `$PWD` is moot in a shell-script launcher.
47+
- **`ARCHCORE_CWD` is opt-in.** Users who don't set it stay in the broken state. Mitigation: surface a clear "your MCP CWD looks like a plugin cache; set `ARCHCORE_CWD` to your project" warning when archcore CLI detects `getcwd()` matches a plugin-cache pattern (e.g., contains `/plugins/cache/` or `/plugin-install/`). Don't refuse to operate — too disruptive — but make the next step obvious.
48+
- **Codex env vars may not exist for MCP spawns.** The existing `codex-path-resolution.adr` confirms `${PLUGIN_ROOT}` is injected for hooks but says nothing about MCP. Step 2 (diagnostic mode) is the way to find out. Do this before committing to `ARCHCORE_CWD` as the canonical name.
49+
- **Cross-host concern.** Claude Code uses `${CLAUDE_PLUGIN_ROOT}/bin/archcore` with no chdir, so `$PWD == $(pwd)` and the issue doesn't surface. Cursor and other hosts may behave like Codex or like Claude — verify each before extending.
50+
- **Naming.** If we name our env var `ARCHCORE_CWD`, that mirrors `ARCHCORE_BIN` already used by the launcher. Alternative `ARCHCORE_PROJECT_ROOT` is more explicit. Stick with `ARCHCORE_CWD` for brevity unless a survey of other plugins' conventions argues otherwise.

skills/bootstrap/SKILL.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,17 @@ First-time onboarding. Detects repo scale (small / medium / large) and seeds sca
2929

3030
## Routing table
3131

32-
**Mode routing** — Step 0.5 classifier, evaluated top-to-bottom, first match wins. Precise conditions in `lib/detect-scale.md`.
32+
**Mode routing** — Step 0.5 classifier, evaluated top-to-bottom, first match wins. The **empty** route is decided earlier in Step 0(b) and short-circuits the classifier entirely. Precise conditions for the rest in `lib/detect-scale.md`.
3333

3434
| Signal | Route | Seeded artifacts |
3535
|---|---|---|
36+
| No manifest AND no top-level source (Step 0b) |**empty** | none — acknowledge-only, no placeholder docs |
3637
| `--mode=X` flag | → forced `X` (detected mode still reported) | per row below |
3738
| `domain_count ≤ 1` AND `module_count ≤ 15` |**small** | stack rule, run guide |
3839
| `domain_count ≤ 2` AND `module_count ≤ 40` |**medium** | small + entry-point inventory |
3940
| `domain_count ≥ 3` OR `module_count > 40` |**large** | medium + top-level map + domain dialog |
4041

41-
Each mode additionally runs hotspot capture-candidate proposal (Step 6) and optional agent-file import (Step 8). Medium additionally runs cross-cutting rule candidate (Step 7).
42+
Each non-empty mode additionally runs hotspot capture-candidate proposal (Step 6) and optional agent-file import (Step 8). Medium additionally runs cross-cutting rule candidate (Step 7). The empty route exits after Step 0.
4243

4344
**Follow-up routing** — closing-message hand-offs. Bootstrap surfaces these as todos; MUST NOT auto-invoke.
4445

@@ -54,15 +55,28 @@ Each mode additionally runs hotspot capture-candidate proposal (Step 6) and opti
5455

5556
## Execution
5657

57-
### Step -1: Ensure initialization
58+
### Pre-flight: lazy reading
5859

59-
Call `mcp__archcore__init_project()` before any other MCP operation. It is idempotent — safe on an already-initialized project (returns existing settings). It creates `.archcore/` and the settings file if they do not exist.
60+
Bootstrap MUST give the user fast feedback. The detection catalogs under `skills/bootstrap/lib/` are heavy (≥ 350 lines for scale alone) and they are read **lazily**: do NOT open any `lib/*.md` file until you reach the step that explicitly tells you to read it. Step 0 finishes before any `lib/` file is opened.
61+
62+
### Step -1: Initialize and acknowledge (fast)
63+
64+
Call `mcp__archcore__init_project()` exactly once. It is idempotent — safe on an already-initialized project (returns existing settings). It creates `.archcore/` and `settings.json` if missing.
65+
66+
Immediately after the call, give the user a one-line confirmation so they see something tangible without waiting for any detection:
67+
68+
- If the response includes `initialized: true` (created now) — print: *"Archcore initialized at `.archcore/`."*
69+
- If `already_initialized: true` — print nothing here; the existing knowledge base will speak for itself in Step 0(a).
6070

6171
Do NOT ask the user to run `archcore init` in the terminal — `mcp__archcore__init_project` is the correct path in a plugin session.
6272

63-
### Step 0: Check state
73+
### Step 0: Check state and source signal
6474

65-
Call `mcp__archcore__list_documents()`. Derive:
75+
Two cheap probes, in order. Each can short-circuit the whole skill. Neither reads anything under `lib/`.
76+
77+
#### Step 0(a) — Existing documents
78+
79+
Call `mcp__archcore__list_documents()` once. Derive:
6680

6781
- `has_stack_rule` — any `rule` whose title contains "stack" in `conventions/`.
6882
- `has_run_guide` — any `guide` whose title contains "run" or "running" in `onboarding/`.
@@ -76,7 +90,22 @@ If `has_stack_rule` AND `has_run_guide` are both true, reply:
7690
7791
Then stop. Per-step idempotency checks (below) handle mode-specific artifacts when the user asks for a selective refresh.
7892

79-
Otherwise proceed to Step 0.5.
93+
#### Step 0(b) — Source-signal gate (empty-repo early exit)
94+
95+
Single filesystem probe — one shell call, no catalog reads. Detect whether the repository has any executable shape yet:
96+
97+
- **`has_manifest`** — at least one of these exists at the project root (depth ≤ 2 for monorepo workspaces): `package.json`, `pyproject.toml`, `Pipfile`, `requirements.txt`, `Cargo.toml`, `go.mod`, `Gemfile`, `composer.json`, `*.csproj`, `*.fsproj`, `*.vbproj`, `pom.xml`, `build.gradle`, `build.gradle.kts`, `mix.exs`, `Package.swift`.
98+
- **`has_top_level_source`** — at least one file with a recognizable source extension exists anywhere under the project root, capped at depth 3, excluding `.archcore/`, `.git/`, `node_modules/`, `vendor/`, `dist/`, `build/`, `out/`, `target/`, `coverage/`, `.venv/`, `__pycache__/`, `.next/`, `.turbo/`. Extensions: `.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`, `.py`, `.rs`, `.go`, `.rb`, `.php`, `.java`, `.kt`, `.kts`, `.swift`, `.cs`, `.fs`, `.ex`, `.exs`, `.scala`, `.clj`, `.cljs`.
99+
100+
If BOTH are false, take the **empty** route. Reply with exactly:
101+
102+
> Archcore is ready at `.archcore/`. No source code detected yet — nothing to bootstrap.
103+
>
104+
> Re-run `/archcore:bootstrap` after the first manifest or source file lands. The SessionStart empty-state nudge will keep pointing here until then.
105+
106+
Then stop. **Do NOT** create placeholder documents (no "no stack selected yet" rule, no "no run command yet" guide). They have no practical value, they cost MCP roundtrips and tokens, and they suppress the SessionStart empty-state nudge — which is the user's primary breadcrumb back to bootstrap once code actually exists.
107+
108+
Otherwise (`has_manifest` OR `has_top_level_source`), proceed to Step 0.5.
80109

81110
### Step 0.5: Detect scale
82111

@@ -299,8 +328,9 @@ Always end with:
299328
300329
Mode-appropriate `.archcore/` seed:
301330
331+
- **Empty**: 0 seeded — `.archcore/` and `settings.json` only. Fast acknowledge + early exit. No catalog files read.
302332
- **Small**: 2 seeded (`rule`, `guide`) + 3 hotspot proposals.
303333
- **Medium**: 3 seeded (`rule`, `guide`, entry-point `doc`) + 5 hotspot proposals + ≤ 1 cross-cutting rule candidate.
304334
- **Large**: 4 seeded (`rule`, `guide`, top-level-map `doc`, entry-point `doc`) + domain selection + 3-per-domain hotspot proposals.
305335
306-
All seeds idempotent. Agent-file import is opt-in and previewed.
336+
All seeds idempotent. Agent-file import is opt-in and previewed. The empty route never creates placeholder documents — it keeps `.archcore/` functionally empty so the SessionStart nudge continues pointing the user back here.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"sync": "none"
3+
}

0 commit comments

Comments
 (0)